diff --git a/.gitignore b/.gitignore index 15f20011..3ff2bd65 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,6 @@ /pkg/ /spec/reports/ /tmp/ +/vendor/bundle/ Gemfile.lock /.rspec_status diff --git a/README.md b/README.md index 3c81da2e..818d9f97 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ A toolkit for working with Custom Shopify Apps built on Rails. +*Developed and maintained by [Nebulab](https://nebulab.com/).* + ### Assumptions - You are using Rails 7.0 or later diff --git a/vendor/bundle/ruby/3.2.0/bin/erb b/vendor/bundle/ruby/3.2.0/bin/erb new file mode 100755 index 00000000..6c0716b7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/bin/erb @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.2 +# +# This file was generated by RubyGems. +# +# The application 'erb' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +Gem.use_gemdeps + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('erb', 'erb', version) +else +gem "erb", version +load Gem.bin_path("erb", "erb", version) +end diff --git a/vendor/bundle/ruby/3.2.0/bin/htmldiff b/vendor/bundle/ruby/3.2.0/bin/htmldiff new file mode 100755 index 00000000..58aadf2e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/bin/htmldiff @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.2 +# +# This file was generated by RubyGems. +# +# The application 'diff-lcs' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +Gem.use_gemdeps + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('diff-lcs', 'htmldiff', version) +else +gem "diff-lcs", version +load Gem.bin_path("diff-lcs", "htmldiff", version) +end diff --git a/vendor/bundle/ruby/3.2.0/bin/httparty b/vendor/bundle/ruby/3.2.0/bin/httparty new file mode 100755 index 00000000..eae5dd89 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/bin/httparty @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.2 +# +# This file was generated by RubyGems. +# +# The application 'httparty' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +Gem.use_gemdeps + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('httparty', 'httparty', version) +else +gem "httparty", version +load Gem.bin_path("httparty", "httparty", version) +end diff --git a/vendor/bundle/ruby/3.2.0/bin/irb b/vendor/bundle/ruby/3.2.0/bin/irb new file mode 100755 index 00000000..472c392c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/bin/irb @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.2 +# +# This file was generated by RubyGems. +# +# The application 'irb' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +Gem.use_gemdeps + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('irb', 'irb', version) +else +gem "irb", version +load Gem.bin_path("irb", "irb", version) +end diff --git a/vendor/bundle/ruby/3.2.0/bin/ldiff b/vendor/bundle/ruby/3.2.0/bin/ldiff new file mode 100755 index 00000000..7a548292 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/bin/ldiff @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.2 +# +# This file was generated by RubyGems. +# +# The application 'diff-lcs' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +Gem.use_gemdeps + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('diff-lcs', 'ldiff', version) +else +gem "diff-lcs", version +load Gem.bin_path("diff-lcs", "ldiff", version) +end diff --git a/vendor/bundle/ruby/3.2.0/bin/nokogiri b/vendor/bundle/ruby/3.2.0/bin/nokogiri new file mode 100755 index 00000000..28de80d7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/bin/nokogiri @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.2 +# +# This file was generated by RubyGems. +# +# The application 'nokogiri' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +Gem.use_gemdeps + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('nokogiri', 'nokogiri', version) +else +gem "nokogiri", version +load Gem.bin_path("nokogiri", "nokogiri", version) +end diff --git a/vendor/bundle/ruby/3.2.0/bin/racc b/vendor/bundle/ruby/3.2.0/bin/racc new file mode 100755 index 00000000..9d74320b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/bin/racc @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.2 +# +# This file was generated by RubyGems. +# +# The application 'racc' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +Gem.use_gemdeps + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('racc', 'racc', version) +else +gem "racc", version +load Gem.bin_path("racc", "racc", version) +end diff --git a/vendor/bundle/ruby/3.2.0/bin/rackup b/vendor/bundle/ruby/3.2.0/bin/rackup new file mode 100755 index 00000000..387be6e9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/bin/rackup @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.2 +# +# This file was generated by RubyGems. +# +# The application 'rackup' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +Gem.use_gemdeps + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('rackup', 'rackup', version) +else +gem "rackup", version +load Gem.bin_path("rackup", "rackup", version) +end diff --git a/vendor/bundle/ruby/3.2.0/bin/rails b/vendor/bundle/ruby/3.2.0/bin/rails new file mode 100755 index 00000000..68bef7cd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/bin/rails @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.2 +# +# This file was generated by RubyGems. +# +# The application 'railties' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +Gem.use_gemdeps + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('railties', 'rails', version) +else +gem "railties", version +load Gem.bin_path("railties", "rails", version) +end diff --git a/vendor/bundle/ruby/3.2.0/bin/rake b/vendor/bundle/ruby/3.2.0/bin/rake new file mode 100755 index 00000000..6b572b3e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/bin/rake @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.2 +# +# This file was generated by RubyGems. +# +# The application 'rake' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +Gem.use_gemdeps + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('rake', 'rake', version) +else +gem "rake", version +load Gem.bin_path("rake", "rake", version) +end diff --git a/vendor/bundle/ruby/3.2.0/bin/rdoc b/vendor/bundle/ruby/3.2.0/bin/rdoc new file mode 100755 index 00000000..ea3f3861 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/bin/rdoc @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.2 +# +# This file was generated by RubyGems. +# +# The application 'rdoc' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +Gem.use_gemdeps + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('rdoc', 'rdoc', version) +else +gem "rdoc", version +load Gem.bin_path("rdoc", "rdoc", version) +end diff --git a/vendor/bundle/ruby/3.2.0/bin/ri b/vendor/bundle/ruby/3.2.0/bin/ri new file mode 100755 index 00000000..8ccc297b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/bin/ri @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.2 +# +# This file was generated by RubyGems. +# +# The application 'rdoc' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +Gem.use_gemdeps + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('rdoc', 'ri', version) +else +gem "rdoc", version +load Gem.bin_path("rdoc", "ri", version) +end diff --git a/vendor/bundle/ruby/3.2.0/bin/rspec b/vendor/bundle/ruby/3.2.0/bin/rspec new file mode 100755 index 00000000..b8bf169e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/bin/rspec @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.2 +# +# This file was generated by RubyGems. +# +# The application 'rspec-core' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +Gem.use_gemdeps + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('rspec-core', 'rspec', version) +else +gem "rspec-core", version +load Gem.bin_path("rspec-core", "rspec", version) +end diff --git a/vendor/bundle/ruby/3.2.0/bin/shopify-toolkit b/vendor/bundle/ruby/3.2.0/bin/shopify-toolkit new file mode 100755 index 00000000..ed03e3a8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/bin/shopify-toolkit @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.2 +# +# This file was generated by RubyGems. +# +# The application 'shopify_toolkit' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +Gem.use_gemdeps + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('shopify_toolkit', 'shopify-toolkit', version) +else +gem "shopify_toolkit", version +load Gem.bin_path("shopify_toolkit", "shopify-toolkit", version) +end diff --git a/vendor/bundle/ruby/3.2.0/bin/thor b/vendor/bundle/ruby/3.2.0/bin/thor new file mode 100755 index 00000000..89d64005 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/bin/thor @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby3.2 +# +# This file was generated by RubyGems. +# +# The application 'thor' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +Gem.use_gemdeps + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('thor', 'thor', version) +else +gem "thor", version +load Gem.bin_path("thor", "thor", version) +end diff --git a/vendor/bundle/ruby/3.2.0/cache/actionpack-8.0.2.gem b/vendor/bundle/ruby/3.2.0/cache/actionpack-8.0.2.gem new file mode 100644 index 00000000..f681836e Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/actionpack-8.0.2.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/actionview-8.0.2.gem b/vendor/bundle/ruby/3.2.0/cache/actionview-8.0.2.gem new file mode 100644 index 00000000..50855e77 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/actionview-8.0.2.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/activemodel-8.0.2.gem b/vendor/bundle/ruby/3.2.0/cache/activemodel-8.0.2.gem new file mode 100644 index 00000000..c1c11140 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/activemodel-8.0.2.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/activerecord-8.0.2.gem b/vendor/bundle/ruby/3.2.0/cache/activerecord-8.0.2.gem new file mode 100644 index 00000000..60a7ec1c Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/activerecord-8.0.2.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/activesupport-8.0.2.gem b/vendor/bundle/ruby/3.2.0/cache/activesupport-8.0.2.gem new file mode 100644 index 00000000..24b49354 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/activesupport-8.0.2.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/base64-0.3.0.gem b/vendor/bundle/ruby/3.2.0/cache/base64-0.3.0.gem new file mode 100644 index 00000000..12f53f14 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/base64-0.3.0.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/benchmark-0.4.1.gem b/vendor/bundle/ruby/3.2.0/cache/benchmark-0.4.1.gem new file mode 100644 index 00000000..90cd2725 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/benchmark-0.4.1.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/bigdecimal-3.2.2.gem b/vendor/bundle/ruby/3.2.0/cache/bigdecimal-3.2.2.gem new file mode 100644 index 00000000..ed8d2e43 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/bigdecimal-3.2.2.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/builder-3.3.0.gem b/vendor/bundle/ruby/3.2.0/cache/builder-3.3.0.gem new file mode 100644 index 00000000..f0412140 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/builder-3.3.0.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/concurrent-ruby-1.3.5.gem b/vendor/bundle/ruby/3.2.0/cache/concurrent-ruby-1.3.5.gem new file mode 100644 index 00000000..1cd9f527 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/concurrent-ruby-1.3.5.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/connection_pool-2.5.3.gem b/vendor/bundle/ruby/3.2.0/cache/connection_pool-2.5.3.gem new file mode 100644 index 00000000..23c398fc Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/connection_pool-2.5.3.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/crass-1.0.6.gem b/vendor/bundle/ruby/3.2.0/cache/crass-1.0.6.gem new file mode 100644 index 00000000..7128f385 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/crass-1.0.6.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/csv-3.3.5.gem b/vendor/bundle/ruby/3.2.0/cache/csv-3.3.5.gem new file mode 100644 index 00000000..3a1c844c Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/csv-3.3.5.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/date-3.4.1.gem b/vendor/bundle/ruby/3.2.0/cache/date-3.4.1.gem new file mode 100644 index 00000000..fe7bd0ad Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/date-3.4.1.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/diff-lcs-1.6.2.gem b/vendor/bundle/ruby/3.2.0/cache/diff-lcs-1.6.2.gem new file mode 100644 index 00000000..21c4c77c Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/diff-lcs-1.6.2.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/drb-2.2.3.gem b/vendor/bundle/ruby/3.2.0/cache/drb-2.2.3.gem new file mode 100644 index 00000000..0c78b283 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/drb-2.2.3.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/erb-5.0.1.gem b/vendor/bundle/ruby/3.2.0/cache/erb-5.0.1.gem new file mode 100644 index 00000000..d0902424 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/erb-5.0.1.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/erubi-1.13.1.gem b/vendor/bundle/ruby/3.2.0/cache/erubi-1.13.1.gem new file mode 100644 index 00000000..2b1dd030 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/erubi-1.13.1.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/hash_diff-1.1.1.gem b/vendor/bundle/ruby/3.2.0/cache/hash_diff-1.1.1.gem new file mode 100644 index 00000000..0c64f5f4 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/hash_diff-1.1.1.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/httparty-0.23.1.gem b/vendor/bundle/ruby/3.2.0/cache/httparty-0.23.1.gem new file mode 100644 index 00000000..b02c3296 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/httparty-0.23.1.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/i18n-1.14.7.gem b/vendor/bundle/ruby/3.2.0/cache/i18n-1.14.7.gem new file mode 100644 index 00000000..9307337f Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/i18n-1.14.7.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/io-console-0.8.0.gem b/vendor/bundle/ruby/3.2.0/cache/io-console-0.8.0.gem new file mode 100644 index 00000000..7a39c003 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/io-console-0.8.0.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/irb-1.15.2.gem b/vendor/bundle/ruby/3.2.0/cache/irb-1.15.2.gem new file mode 100644 index 00000000..1d053448 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/irb-1.15.2.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/jwt-3.1.2.gem b/vendor/bundle/ruby/3.2.0/cache/jwt-3.1.2.gem new file mode 100644 index 00000000..b38ea83d Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/jwt-3.1.2.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/logger-1.7.0.gem b/vendor/bundle/ruby/3.2.0/cache/logger-1.7.0.gem new file mode 100644 index 00000000..061f1ccc Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/logger-1.7.0.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/loofah-2.24.1.gem b/vendor/bundle/ruby/3.2.0/cache/loofah-2.24.1.gem new file mode 100644 index 00000000..a86dc7ac Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/loofah-2.24.1.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/mini_mime-1.1.5.gem b/vendor/bundle/ruby/3.2.0/cache/mini_mime-1.1.5.gem new file mode 100644 index 00000000..b16e88f5 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/mini_mime-1.1.5.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/minitest-5.25.5.gem b/vendor/bundle/ruby/3.2.0/cache/minitest-5.25.5.gem new file mode 100644 index 00000000..2ffec491 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/minitest-5.25.5.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/multi_xml-0.7.2.gem b/vendor/bundle/ruby/3.2.0/cache/multi_xml-0.7.2.gem new file mode 100644 index 00000000..a231a253 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/multi_xml-0.7.2.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/nokogiri-1.18.8-x86_64-linux-gnu.gem b/vendor/bundle/ruby/3.2.0/cache/nokogiri-1.18.8-x86_64-linux-gnu.gem new file mode 100644 index 00000000..73db7f3c Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/nokogiri-1.18.8-x86_64-linux-gnu.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/oj-3.16.11.gem b/vendor/bundle/ruby/3.2.0/cache/oj-3.16.11.gem new file mode 100644 index 00000000..a7939217 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/oj-3.16.11.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/openssl-3.3.0.gem b/vendor/bundle/ruby/3.2.0/cache/openssl-3.3.0.gem new file mode 100644 index 00000000..f3447ad2 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/openssl-3.3.0.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/ostruct-0.6.2.gem b/vendor/bundle/ruby/3.2.0/cache/ostruct-0.6.2.gem new file mode 100644 index 00000000..896eaedc Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/ostruct-0.6.2.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/pp-0.6.2.gem b/vendor/bundle/ruby/3.2.0/cache/pp-0.6.2.gem new file mode 100644 index 00000000..25704968 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/pp-0.6.2.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/prettyprint-0.2.0.gem b/vendor/bundle/ruby/3.2.0/cache/prettyprint-0.2.0.gem new file mode 100644 index 00000000..0944aaba Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/prettyprint-0.2.0.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/psych-5.2.6.gem b/vendor/bundle/ruby/3.2.0/cache/psych-5.2.6.gem new file mode 100644 index 00000000..becbf807 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/psych-5.2.6.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/racc-1.8.1.gem b/vendor/bundle/ruby/3.2.0/cache/racc-1.8.1.gem new file mode 100644 index 00000000..ad9e6bbd Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/racc-1.8.1.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/rack-3.1.16.gem b/vendor/bundle/ruby/3.2.0/cache/rack-3.1.16.gem new file mode 100644 index 00000000..0a48c300 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/rack-3.1.16.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/rack-session-2.1.1.gem b/vendor/bundle/ruby/3.2.0/cache/rack-session-2.1.1.gem new file mode 100644 index 00000000..f0372322 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/rack-session-2.1.1.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/rack-test-2.2.0.gem b/vendor/bundle/ruby/3.2.0/cache/rack-test-2.2.0.gem new file mode 100644 index 00000000..b0b9c9d8 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/rack-test-2.2.0.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/rackup-2.2.1.gem b/vendor/bundle/ruby/3.2.0/cache/rackup-2.2.1.gem new file mode 100644 index 00000000..286eb159 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/rackup-2.2.1.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/rails-dom-testing-2.3.0.gem b/vendor/bundle/ruby/3.2.0/cache/rails-dom-testing-2.3.0.gem new file mode 100644 index 00000000..b5f4b25a Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/rails-dom-testing-2.3.0.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/rails-html-sanitizer-1.6.2.gem b/vendor/bundle/ruby/3.2.0/cache/rails-html-sanitizer-1.6.2.gem new file mode 100644 index 00000000..4e9da15e Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/rails-html-sanitizer-1.6.2.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/railties-8.0.2.gem b/vendor/bundle/ruby/3.2.0/cache/railties-8.0.2.gem new file mode 100644 index 00000000..02b5b976 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/railties-8.0.2.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/rake-13.3.0.gem b/vendor/bundle/ruby/3.2.0/cache/rake-13.3.0.gem new file mode 100644 index 00000000..fe11cec2 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/rake-13.3.0.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/rdoc-6.14.1.gem b/vendor/bundle/ruby/3.2.0/cache/rdoc-6.14.1.gem new file mode 100644 index 00000000..5810bfb3 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/rdoc-6.14.1.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/reline-0.6.1.gem b/vendor/bundle/ruby/3.2.0/cache/reline-0.6.1.gem new file mode 100644 index 00000000..98ae6be5 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/reline-0.6.1.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/rspec-3.13.1.gem b/vendor/bundle/ruby/3.2.0/cache/rspec-3.13.1.gem new file mode 100644 index 00000000..74792105 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/rspec-3.13.1.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/rspec-core-3.13.5.gem b/vendor/bundle/ruby/3.2.0/cache/rspec-core-3.13.5.gem new file mode 100644 index 00000000..64d5cb8f Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/rspec-core-3.13.5.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/rspec-expectations-3.13.5.gem b/vendor/bundle/ruby/3.2.0/cache/rspec-expectations-3.13.5.gem new file mode 100644 index 00000000..51409fdd Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/rspec-expectations-3.13.5.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/rspec-mocks-3.13.5.gem b/vendor/bundle/ruby/3.2.0/cache/rspec-mocks-3.13.5.gem new file mode 100644 index 00000000..05da2b39 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/rspec-mocks-3.13.5.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/rspec-support-3.13.4.gem b/vendor/bundle/ruby/3.2.0/cache/rspec-support-3.13.4.gem new file mode 100644 index 00000000..0d49eada Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/rspec-support-3.13.4.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/securerandom-0.4.1.gem b/vendor/bundle/ruby/3.2.0/cache/securerandom-0.4.1.gem new file mode 100644 index 00000000..05072cab Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/securerandom-0.4.1.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/shopify_api-14.10.0.gem b/vendor/bundle/ruby/3.2.0/cache/shopify_api-14.10.0.gem new file mode 100644 index 00000000..24139271 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/shopify_api-14.10.0.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/sorbet-runtime-0.5.12219.gem b/vendor/bundle/ruby/3.2.0/cache/sorbet-runtime-0.5.12219.gem new file mode 100644 index 00000000..41b0c67e Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/sorbet-runtime-0.5.12219.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/stringio-3.1.7.gem b/vendor/bundle/ruby/3.2.0/cache/stringio-3.1.7.gem new file mode 100644 index 00000000..bca0b39f Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/stringio-3.1.7.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/thor-1.3.2.gem b/vendor/bundle/ruby/3.2.0/cache/thor-1.3.2.gem new file mode 100644 index 00000000..aa6cf803 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/thor-1.3.2.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/timeout-0.4.3.gem b/vendor/bundle/ruby/3.2.0/cache/timeout-0.4.3.gem new file mode 100644 index 00000000..8aecf8c6 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/timeout-0.4.3.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/tzinfo-2.0.6.gem b/vendor/bundle/ruby/3.2.0/cache/tzinfo-2.0.6.gem new file mode 100644 index 00000000..2c16da8a Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/tzinfo-2.0.6.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/uri-1.0.3.gem b/vendor/bundle/ruby/3.2.0/cache/uri-1.0.3.gem new file mode 100644 index 00000000..afd77ba9 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/uri-1.0.3.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/useragent-0.16.11.gem b/vendor/bundle/ruby/3.2.0/cache/useragent-0.16.11.gem new file mode 100644 index 00000000..75ba7558 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/useragent-0.16.11.gem differ diff --git a/vendor/bundle/ruby/3.2.0/cache/zeitwerk-2.7.3.gem b/vendor/bundle/ruby/3.2.0/cache/zeitwerk-2.7.3.gem new file mode 100644 index 00000000..31cb70ca Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/cache/zeitwerk-2.7.3.gem differ diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/bigdecimal-3.2.2/bigdecimal.so b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/bigdecimal-3.2.2/bigdecimal.so new file mode 100755 index 00000000..ef74012e Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/bigdecimal-3.2.2/bigdecimal.so differ diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/bigdecimal-3.2.2/gem.build_complete b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/bigdecimal-3.2.2/gem.build_complete new file mode 100644 index 00000000..e69de29b diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/bigdecimal-3.2.2/gem_make.out b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/bigdecimal-3.2.2/gem_make.out new file mode 100644 index 00000000..ad3198cb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/bigdecimal-3.2.2/gem_make.out @@ -0,0 +1,44 @@ +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal +/usr/bin/ruby3.2 -I/usr/lib/ruby/vendor_ruby extconf.rb +checking for __builtin_clz()... yes +checking for __builtin_clzl()... yes +checking for __builtin_clzll()... yes +checking for float.h... yes +checking for math.h... yes +checking for stdbool.h... yes +checking for stdlib.h... yes +checking for x86intrin.h... yes +checking for _lzcnt_u32() in x86intrin.h... no +checking for _lzcnt_u64() in x86intrin.h... no +checking for intrin.h... no +checking for labs() in stdlib.h... yes +checking for llabs() in stdlib.h... yes +checking for finite() in math.h... yes +checking for isfinite() in math.h... no +checking for ruby/atomic.h... yes +checking for ruby/internal/has/builtin.h... yes +checking for ruby/internal/static_assert.h... yes +checking for rb_rational_num() in ruby.h... yes +checking for rb_rational_den() in ruby.h... yes +checking for rb_complex_real() in ruby.h... yes +checking for rb_complex_imag() in ruby.h... yes +checking for rb_opts_exception_p() in ruby.h... yes +checking for rb_category_warn() in ruby.h... yes +checking for RB_WARN_CATEGORY_DEPRECATED in ruby.h... yes +creating Makefile + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-a7whb9 sitelibdir\=./.gem.20250703-3393-a7whb9 clean + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-a7whb9 sitelibdir\=./.gem.20250703-3393-a7whb9 +compiling bigdecimal.c +compiling missing.c +linking shared-object bigdecimal.so + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-a7whb9 sitelibdir\=./.gem.20250703-3393-a7whb9 install +/usr/bin/install -c -m 0755 bigdecimal.so ./.gem.20250703-3393-a7whb9 + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-a7whb9 sitelibdir\=./.gem.20250703-3393-a7whb9 clean diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/bigdecimal-3.2.2/mkmf.log b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/bigdecimal-3.2.2/mkmf.log new file mode 100644 index 00000000..d1e8f6eb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/bigdecimal-3.2.2/mkmf.log @@ -0,0 +1,605 @@ +have_builtin_func: checking for __builtin_clz()... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return !!argv[argc]; +6: } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int foo; +4: int main() { __builtin_clz(0); return 0; } +/* end */ + +-------------------- + +have_builtin_func: checking for __builtin_clzl()... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int foo; +4: int main() { __builtin_clzl(0); return 0; } +/* end */ + +-------------------- + +have_builtin_func: checking for __builtin_clzll()... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int foo; +4: int main() { __builtin_clzll(0); return 0; } +/* end */ + +-------------------- + +have_header: checking for float.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_header: checking for math.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_header: checking for stdbool.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_header: checking for stdlib.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_header: checking for x86intrin.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_func: checking for _lzcnt_u32() in x86intrin.h... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +/usr/bin/ld: /tmp/cc5w0ZzA.o: in function `t': +/home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/conftest.c:16:(.text+0xb): undefined reference to `_lzcnt_u32' +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))_lzcnt_u32; return !p; } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +conftest.c:16:13: error: conflicting types for ‘_lzcnt_u32’; have ‘void()’ + 16 | extern void _lzcnt_u32(); + | ^~~~~~~~~~ +In file included from /usr/lib/gcc/x86_64-linux-gnu/13/include/x86gprintrin.h:61, + from /usr/lib/gcc/x86_64-linux-gnu/13/include/x86intrin.h:27, + from conftest.c:3: +/usr/lib/gcc/x86_64-linux-gnu/13/include/lzcntintrin.h:51:1: note: previous definition of ‘_lzcnt_u32’ with type ‘unsigned int(unsigned int)’ + 51 | _lzcnt_u32 (unsigned int __X) + | ^~~~~~~~~~ +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: extern void _lzcnt_u32(); +17: int t(void) { _lzcnt_u32(); return 0; } +/* end */ + +-------------------- + +have_func: checking for _lzcnt_u64() in x86intrin.h... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +/usr/bin/ld: /tmp/ccrzYicN.o: in function `t': +/home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/conftest.c:16:(.text+0xb): undefined reference to `_lzcnt_u64' +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))_lzcnt_u64; return !p; } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +conftest.c:16:13: error: conflicting types for ‘_lzcnt_u64’; have ‘void()’ + 16 | extern void _lzcnt_u64(); + | ^~~~~~~~~~ +In file included from /usr/lib/gcc/x86_64-linux-gnu/13/include/x86gprintrin.h:61, + from /usr/lib/gcc/x86_64-linux-gnu/13/include/x86intrin.h:27, + from conftest.c:3: +/usr/lib/gcc/x86_64-linux-gnu/13/include/lzcntintrin.h:64:1: note: previous definition of ‘_lzcnt_u64’ with type ‘long long unsigned int(long long unsigned int)’ + 64 | _lzcnt_u64 (unsigned long long __X) + | ^~~~~~~~~~ +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: extern void _lzcnt_u64(); +17: int t(void) { _lzcnt_u64(); return 0; } +/* end */ + +-------------------- + +have_header: checking for intrin.h... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +conftest.c:3:10: fatal error: intrin.h: No such file or directory + 3 | #include + | ^~~~~~~~~~ +compilation terminated. +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_func: checking for labs() in stdlib.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))labs; return !p; } +/* end */ + +-------------------- + +have_func: checking for llabs() in stdlib.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))llabs; return !p; } +/* end */ + +-------------------- + +have_func: checking for finite() in math.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))finite; return !p; } +/* end */ + +-------------------- + +have_func: checking for isfinite() in math.h... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:16:57: error: ‘isfinite’ undeclared (first use in this function); did you mean ‘finite’? + 16 | int t(void) { void ((*volatile p)()); p = (void ((*)()))isfinite; return !p; } + | ^~~~~~~~ + | finite +conftest.c:16:57: note: each undeclared identifier is reported only once for each function it appears in +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))isfinite; return !p; } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +/usr/bin/ld: /tmp/ccIhwHKY.o: in function `t': +/home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/conftest.c:17:(.text+0xb): undefined reference to `__builtin_isfinite' +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: extern void isfinite(); +17: int t(void) { isfinite(); return 0; } +/* end */ + +-------------------- + +have_header: checking for ruby/atomic.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_header: checking for ruby/internal/has/builtin.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_header: checking for ruby/internal/static_assert.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_func: checking for rb_rational_num() in ruby.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_rational_num; return !p; } +/* end */ + +-------------------- + +have_func: checking for rb_rational_den() in ruby.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_rational_den; return !p; } +/* end */ + +-------------------- + +have_func: checking for rb_complex_real() in ruby.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_complex_real; return !p; } +/* end */ + +-------------------- + +have_func: checking for rb_complex_imag() in ruby.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_complex_imag; return !p; } +/* end */ + +-------------------- + +have_func: checking for rb_opts_exception_p() in ruby.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:16:57: error: ‘rb_opts_exception_p’ undeclared (first use in this function); did you mean ‘rb_make_exception’? + 16 | int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_opts_exception_p; return !p; } + | ^~~~~~~~~~~~~~~~~~~ + | rb_make_exception +conftest.c:16:57: note: each undeclared identifier is reported only once for each function it appears in +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_opts_exception_p; return !p; } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: extern void rb_opts_exception_p(); +17: int t(void) { rb_opts_exception_p(); return 0; } +/* end */ + +-------------------- + +have_func: checking for rb_category_warn() in ruby.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_category_warn; return !p; } +/* end */ + +-------------------- + +have_const: checking for RB_WARN_CATEGORY_DEPRECATED in ruby.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +4: +5: /*top*/ +6: typedef int conftest_type; +7: conftest_type conftestval = (int)RB_WARN_CATEGORY_DEPRECATED; +/* end */ + +-------------------- + diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/date-3.4.1/date_core.so b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/date-3.4.1/date_core.so new file mode 100755 index 00000000..ee653573 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/date-3.4.1/date_core.so differ diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/date-3.4.1/gem.build_complete b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/date-3.4.1/gem.build_complete new file mode 100644 index 00000000..e69de29b diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/date-3.4.1/gem_make.out b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/date-3.4.1/gem_make.out new file mode 100644 index 00000000..d6e8ca71 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/date-3.4.1/gem_make.out @@ -0,0 +1,24 @@ +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date +/usr/bin/ruby3.2 -I/usr/lib/ruby/vendor_ruby extconf.rb +checking for rb_category_warn()... yes +checking for timezone in time.h with -Werror... yes +checking for altzone in time.h with -Werror... no +creating Makefile + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-9cjxa1 sitelibdir\=./.gem.20250703-3393-9cjxa1 clean + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-9cjxa1 sitelibdir\=./.gem.20250703-3393-9cjxa1 +compiling date_core.c +compiling date_parse.c +compiling date_strftime.c +compiling date_strptime.c +linking shared-object date_core.so + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-9cjxa1 sitelibdir\=./.gem.20250703-3393-9cjxa1 install +/usr/bin/install -c -m 0755 date_core.so ./.gem.20250703-3393-9cjxa1 + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-9cjxa1 sitelibdir\=./.gem.20250703-3393-9cjxa1 clean diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/date-3.4.1/mkmf.log b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/date-3.4.1/mkmf.log new file mode 100644 index 00000000..6d3f4c7b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/date-3.4.1/mkmf.log @@ -0,0 +1,89 @@ +have_func: checking for rb_category_warn()... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return !!argv[argc]; +6: } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_category_warn; return !p; } +/* end */ + +-------------------- + +have_var: checking for timezone in time.h with -Werror... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -Werror -c conftest.c" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: int t(void) { const volatile void *volatile p; p = &(&timezone)[0]; return !p; } +/* end */ + +-------------------- + +have_var: checking for altzone in time.h with -Werror... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -Werror -c conftest.c" +conftest.c: In function ‘t’: +conftest.c:16:55: error: ‘altzone’ undeclared (first use in this function) + 16 | int t(void) { const volatile void *volatile p; p = &(&altzone)[0]; return !p; } + | ^~~~~~~ +conftest.c:16:55: note: each undeclared identifier is reported only once for each function it appears in +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: int t(void) { const volatile void *volatile p; p = &(&altzone)[0]; return !p; } +/* end */ + +-------------------- + diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/erb-5.0.1/erb/escape.so b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/erb-5.0.1/erb/escape.so new file mode 100755 index 00000000..9edd3d66 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/erb-5.0.1/erb/escape.so differ diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/erb-5.0.1/gem.build_complete b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/erb-5.0.1/gem.build_complete new file mode 100644 index 00000000..e69de29b diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/erb-5.0.1/gem_make.out b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/erb-5.0.1/gem_make.out new file mode 100644 index 00000000..d8ae332b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/erb-5.0.1/gem_make.out @@ -0,0 +1,18 @@ +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/ext/erb/escape +/usr/bin/ruby3.2 -I/usr/lib/ruby/vendor_ruby extconf.rb +creating Makefile + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/ext/erb/escape +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-w7duco sitelibdir\=./.gem.20250703-3393-w7duco clean + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/ext/erb/escape +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-w7duco sitelibdir\=./.gem.20250703-3393-w7duco +compiling escape.c +linking shared-object erb/escape.so + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/ext/erb/escape +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-w7duco sitelibdir\=./.gem.20250703-3393-w7duco install +/usr/bin/install -c -m 0755 escape.so ./.gem.20250703-3393-w7duco/erb + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/ext/erb/escape +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-w7duco sitelibdir\=./.gem.20250703-3393-w7duco clean diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/io-console-0.8.0/gem.build_complete b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/io-console-0.8.0/gem.build_complete new file mode 100644 index 00000000..e69de29b diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/io-console-0.8.0/gem_make.out b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/io-console-0.8.0/gem_make.out new file mode 100644 index 00000000..6902a7f7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/io-console-0.8.0/gem_make.out @@ -0,0 +1,31 @@ +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/ext/io/console +/usr/bin/ruby3.2 -I/usr/lib/ruby/vendor_ruby extconf.rb +checking for rb_syserr_fail_str(0, Qnil)... yes +checking for rb_interned_str_cstr()... yes +checking for rb_io_path()... no +checking for rb_io_descriptor()... yes +checking for rb_io_get_write_io()... yes +checking for rb_io_closed_p()... no +checking for rb_io_open_descriptor()... no +checking for rb_ractor_local_storage_value_newkey()... yes +checking for termios.h... yes +checking for cfmakeraw() in termios.h... yes +checking for sys/ioctl.h... yes +checking for HAVE_RUBY_FIBER_SCHEDULER_H... yes +checking for ttyname_r()... yes +creating Makefile + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/ext/io/console +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-wf1ygq sitelibdir\=./.gem.20250703-3393-wf1ygq clean + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/ext/io/console +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-wf1ygq sitelibdir\=./.gem.20250703-3393-wf1ygq +compiling console.c +linking shared-object io/console.so + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/ext/io/console +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-wf1ygq sitelibdir\=./.gem.20250703-3393-wf1ygq install +/usr/bin/install -c -m 0755 console.so ./.gem.20250703-3393-wf1ygq/io + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/ext/io/console +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-wf1ygq sitelibdir\=./.gem.20250703-3393-wf1ygq clean diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/io-console-0.8.0/io/console.so b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/io-console-0.8.0/io/console.so new file mode 100755 index 00000000..4003c126 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/io-console-0.8.0/io/console.so differ diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/io-console-0.8.0/mkmf.log b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/io-console-0.8.0/mkmf.log new file mode 100644 index 00000000..22f94296 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/io-console-0.8.0/mkmf.log @@ -0,0 +1,463 @@ +have_func: checking for rb_syserr_fail_str(0, Qnil)... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return !!argv[argc]; +6: } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: +15: int t(void) { rb_syserr_fail_str(0, Qnil); return 0; } +/* end */ + +-------------------- + +have_func: checking for rb_interned_str_cstr()... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_interned_str_cstr; return !p; } +/* end */ + +-------------------- + +have_func: checking for rb_io_path()... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘rb_io_path’ undeclared (first use in this function); did you mean ‘rb_io_puts’? + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_io_path; return !p; } + | ^~~~~~~~~~ + | rb_io_puts +conftest.c:14:57: note: each undeclared identifier is reported only once for each function it appears in +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_io_path; return !p; } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +/usr/bin/ld: /tmp/ccCCb0tP.o: in function `t': +/home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/ext/io/console/conftest.c:15:(.text+0xb): undefined reference to `rb_io_path' +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: extern void rb_io_path(); +15: int t(void) { rb_io_path(); return 0; } +/* end */ + +-------------------- + +have_func: checking for rb_io_descriptor()... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘rb_io_descriptor’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_io_descriptor; return !p; } + | ^~~~~~~~~~~~~~~~ +conftest.c:14:57: note: each undeclared identifier is reported only once for each function it appears in +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_io_descriptor; return !p; } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: extern void rb_io_descriptor(); +15: int t(void) { rb_io_descriptor(); return 0; } +/* end */ + +-------------------- + +have_func: checking for rb_io_get_write_io()... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘rb_io_get_write_io’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_io_get_write_io; return !p; } + | ^~~~~~~~~~~~~~~~~~ +conftest.c:14:57: note: each undeclared identifier is reported only once for each function it appears in +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_io_get_write_io; return !p; } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: extern void rb_io_get_write_io(); +15: int t(void) { rb_io_get_write_io(); return 0; } +/* end */ + +-------------------- + +have_func: checking for rb_io_closed_p()... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘rb_io_closed_p’ undeclared (first use in this function); did you mean ‘rb_io_close’? + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_io_closed_p; return !p; } + | ^~~~~~~~~~~~~~ + | rb_io_close +conftest.c:14:57: note: each undeclared identifier is reported only once for each function it appears in +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_io_closed_p; return !p; } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +/usr/bin/ld: /tmp/ccjaw8Ds.o: in function `t': +/home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/ext/io/console/conftest.c:15:(.text+0xb): undefined reference to `rb_io_closed_p' +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: extern void rb_io_closed_p(); +15: int t(void) { rb_io_closed_p(); return 0; } +/* end */ + +-------------------- + +have_func: checking for rb_io_open_descriptor()... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘rb_io_open_descriptor’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_io_open_descriptor; return !p; } + | ^~~~~~~~~~~~~~~~~~~~~ +conftest.c:14:57: note: each undeclared identifier is reported only once for each function it appears in +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_io_open_descriptor; return !p; } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +/usr/bin/ld: /tmp/cc21fpQq.o: in function `t': +/home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/ext/io/console/conftest.c:15:(.text+0xb): undefined reference to `rb_io_open_descriptor' +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: extern void rb_io_open_descriptor(); +15: int t(void) { rb_io_open_descriptor(); return 0; } +/* end */ + +-------------------- + +have_func: checking for rb_ractor_local_storage_value_newkey()... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘rb_ractor_local_storage_value_newkey’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_ractor_local_storage_value_newkey; return !p; } + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +conftest.c:14:57: note: each undeclared identifier is reported only once for each function it appears in +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_ractor_local_storage_value_newkey; return !p; } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: extern void rb_ractor_local_storage_value_newkey(); +15: int t(void) { rb_ractor_local_storage_value_newkey(); return 0; } +/* end */ + +-------------------- + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +conftest.c:5:3: error: #error + 5 | # error + | ^~~~~ +conftest.c:6:1: error: expected identifier or ‘(’ before ‘|’ token + 6 | |:/ === _WIN32 undefined === /:| + | ^ +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: /*top*/ +4: #ifndef _WIN32 +5: # error +6: |:/ === _WIN32 undefined === /:| +7: #endif +/* end */ + +have_header: checking for termios.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_func: checking for cfmakeraw() in termios.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))cfmakeraw; return !p; } +/* end */ + +-------------------- + +have_header: checking for sys/ioctl.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_macro: checking for HAVE_RUBY_FIBER_SCHEDULER_H... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: /*top*/ +4: #ifndef HAVE_RUBY_FIBER_SCHEDULER_H +5: # error +6: |:/ === HAVE_RUBY_FIBER_SCHEDULER_H undefined === /:| +7: #endif +/* end */ + +-------------------- + +have_func: checking for ttyname_r()... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))ttyname_r; return !p; } +/* end */ + +-------------------- + diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/oj-3.16.11/gem.build_complete b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/oj-3.16.11/gem.build_complete new file mode 100644 index 00000000..e69de29b diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/oj-3.16.11/gem_make.out b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/oj-3.16.11/gem_make.out new file mode 100644 index 00000000..cf39b2fb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/oj-3.16.11/gem_make.out @@ -0,0 +1,64 @@ +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/oj-3.16.11/ext/oj +/usr/bin/ruby3.2 -I/usr/lib/ruby/vendor_ruby extconf.rb +>>>>> Creating Makefile for ruby version 3.2.3 on x86_64-linux-gnu <<<<< +checking for rb_gc_mark_movable()... yes +checking for stpcpy()... yes +checking for pthread_mutex_init()... yes +checking for getrlimit() in sys/resource.h... yes +checking for rb_enc_interned_str()... yes +checking for rb_ext_ractor_safe() in ruby.h... yes +creating Makefile + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/oj-3.16.11/ext/oj +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-wd8hyc sitelibdir\=./.gem.20250703-3393-wd8hyc clean + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/oj-3.16.11/ext/oj +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-wd8hyc sitelibdir\=./.gem.20250703-3393-wd8hyc +compiling cache.c +compiling cache8.c +compiling circarray.c +compiling code.c +compiling compat.c +compiling custom.c +compiling debug.c +compiling dump.c +compiling dump_compat.c +compiling dump_leaf.c +compiling dump_object.c +compiling dump_strict.c +compiling encoder.c +compiling err.c +compiling fast.c +compiling intern.c +compiling mem.c +compiling mimic_json.c +compiling object.c +compiling odd.c +compiling oj.c +compiling parse.c +compiling parser.c +compiling rails.c +compiling reader.c +compiling resolve.c +compiling rxclass.c +compiling saj.c +compiling saj2.c +compiling scp.c +compiling sparse.c +compiling stream_writer.c +compiling strict.c +compiling string_writer.c +compiling trace.c +compiling usual.c +compiling util.c +compiling val_stack.c +compiling validate.c +compiling wab.c +linking shared-object oj/oj.so + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/oj-3.16.11/ext/oj +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-wd8hyc sitelibdir\=./.gem.20250703-3393-wd8hyc install +/usr/bin/install -c -m 0755 oj.so ./.gem.20250703-3393-wd8hyc/oj + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/oj-3.16.11/ext/oj +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-wd8hyc sitelibdir\=./.gem.20250703-3393-wd8hyc clean diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/oj-3.16.11/oj/oj.so b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/oj-3.16.11/oj/oj.so new file mode 100755 index 00000000..1085ea22 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/oj-3.16.11/oj/oj.so differ diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/openssl-3.3.0/gem.build_complete b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/openssl-3.3.0/gem.build_complete new file mode 100644 index 00000000..e69de29b diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/openssl-3.3.0/gem_make.out b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/openssl-3.3.0/gem_make.out new file mode 100644 index 00000000..2b4475a5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/openssl-3.3.0/gem_make.out @@ -0,0 +1,124 @@ +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/openssl-3.3.0/ext/openssl +/usr/bin/ruby3.2 -I/usr/lib/ruby/vendor_ruby extconf.rb +checking for rb_io_descriptor()... yes +checking for rb_io_maybe_wait(0, Qnil, Qnil, Qnil) in ruby/io.h... yes +checking for rb_io_timeout() in ruby/io.h... yes +checking for t_open() in -lnsl... no +checking for socket() in -lsocket... no +checking for openssl/ssl.h... yes +checking for LIBRESSL_VERSION_NUMBER in openssl/opensslv.h... no +checking for OpenSSL version >= 1.0.2... yes +checking for RAND_egd() in openssl/rand.h... no +checking for ENGINE_load_dynamic() in openssl/engine.h... yes +checking for ENGINE_load_4758cca() in openssl/engine.h... no +checking for ENGINE_load_aep() in openssl/engine.h... no +checking for ENGINE_load_atalla() in openssl/engine.h... no +checking for ENGINE_load_chil() in openssl/engine.h... no +checking for ENGINE_load_cswift() in openssl/engine.h... no +checking for ENGINE_load_nuron() in openssl/engine.h... no +checking for ENGINE_load_sureware() in openssl/engine.h... no +checking for ENGINE_load_ubsec() in openssl/engine.h... no +checking for ENGINE_load_padlock() in openssl/engine.h... no +checking for ENGINE_load_capi() in openssl/engine.h... no +checking for ENGINE_load_gmp() in openssl/engine.h... no +checking for ENGINE_load_gost() in openssl/engine.h... no +checking for ENGINE_load_cryptodev() in openssl/engine.h... yes +checking for i2d_re_X509_tbs(NULL, NULL) in openssl/x509.h... yes +checking for SSL.ctx in openssl/ssl.h... no +checking for EVP_MD_CTX_new() in openssl/evp.h... yes +checking for EVP_MD_CTX_free(NULL) in openssl/evp.h... yes +checking for EVP_MD_CTX_pkey_ctx(NULL) in openssl/evp.h... yes +checking for X509_STORE_get_ex_data(NULL, 0) in openssl/x509.h... yes +checking for X509_STORE_set_ex_data(NULL, 0, NULL) in openssl/x509.h... yes +checking for X509_STORE_get_ex_new_index(0, NULL, NULL, NULL, NULL) in openssl/x509.h... yes +checking for X509_CRL_get0_signature(NULL, NULL, NULL) in openssl/x509.h... yes +checking for X509_REQ_get0_signature(NULL, NULL, NULL) in openssl/x509.h... yes +checking for X509_REVOKED_get0_serialNumber(NULL) in openssl/x509.h... yes +checking for X509_REVOKED_get0_revocationDate(NULL) in openssl/x509.h... yes +checking for X509_get0_tbs_sigalg(NULL) in openssl/x509.h... yes +checking for X509_STORE_CTX_get0_untrusted(NULL) in openssl/x509.h... yes +checking for X509_STORE_CTX_get0_cert(NULL) in openssl/x509.h... yes +checking for X509_STORE_CTX_get0_chain(NULL) in openssl/x509.h... yes +checking for OCSP_SINGLERESP_get0_id(NULL) in openssl/ocsp.h... yes +checking for SSL_CTX_get_ciphers(NULL) in openssl/ssl.h... yes +checking for X509_up_ref(NULL) in openssl/x509.h... yes +checking for X509_CRL_up_ref(NULL) in openssl/x509.h... yes +checking for X509_STORE_up_ref(NULL) in openssl/x509.h... yes +checking for SSL_SESSION_up_ref(NULL) in openssl/ssl.h... yes +checking for EVP_PKEY_up_ref(NULL) in openssl/evp.h... yes +checking for SSL_CTX_set_min_proto_version(NULL, 0) in openssl/ssl.h... yes +checking for SSL_CTX_get_security_level(NULL) in openssl/ssl.h... yes +checking for X509_get0_notBefore(NULL) in openssl/x509.h... yes +checking for SSL_SESSION_get_protocol_version(NULL) in openssl/ssl.h... yes +checking for TS_STATUS_INFO_get0_status(NULL) in openssl/ts.h... yes +checking for TS_STATUS_INFO_get0_text(NULL) in openssl/ts.h... yes +checking for TS_STATUS_INFO_get0_failure_info(NULL) in openssl/ts.h... yes +checking for TS_VERIFY_CTS_set_certs(NULL, NULL) in openssl/ts.h... yes +checking for TS_VERIFY_CTX_set_store(NULL, NULL) in openssl/ts.h... yes +checking for TS_VERIFY_CTX_add_flags(NULL, 0) in openssl/ts.h... yes +checking for TS_RESP_CTX_set_time_cb(NULL, NULL, NULL) in openssl/ts.h... yes +checking for EVP_PBE_scrypt("", 0, (unsigned char *)"", 0, 0, 0, 0, 0, NULL, 0) in openssl/evp.h... yes +checking for SSL_CTX_set_post_handshake_auth(NULL, 0) in openssl/ssl.h... yes +checking for X509_STORE_get0_param(NULL) in openssl/x509.h... yes +checking for EVP_PKEY_check(NULL) in openssl/evp.h... yes +checking for EVP_PKEY_new_raw_private_key(0, NULL, (unsigned char *)"", 0) in openssl/evp.h... yes +checking for SSL_CTX_set_ciphersuites(NULL, "") in openssl/ssl.h... yes +checking for SSL_set0_tmp_dh_pkey(NULL, NULL) in openssl/ssl.h... yes +checking for ERR_get_error_all(NULL, NULL, NULL, NULL, NULL) in openssl/err.h... yes +checking for TS_VERIFY_CTX_set_certs(NULL, NULL) in openssl/ts.h... yes +checking for SSL_CTX_load_verify_file(NULL, "") in openssl/ssl.h... yes +checking for BN_check_prime(NULL, NULL, NULL) in openssl/bn.h... yes +checking for EVP_MD_CTX_get0_md(NULL) in openssl/evp.h... yes +checking for EVP_MD_CTX_get_pkey_ctx(NULL) in openssl/evp.h... yes +checking for EVP_PKEY_eq(NULL, NULL) in openssl/evp.h... yes +checking for EVP_PKEY_dup(NULL) in openssl/evp.h... yes +creating extconf.h +creating Makefile + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/openssl-3.3.0/ext/openssl +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-cmco0k sitelibdir\=./.gem.20250703-3393-cmco0k clean + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/openssl-3.3.0/ext/openssl +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-cmco0k sitelibdir\=./.gem.20250703-3393-cmco0k +compiling openssl_missing.c +compiling ossl.c +compiling ossl_asn1.c +compiling ossl_bio.c +compiling ossl_bn.c +compiling ossl_cipher.c +compiling ossl_config.c +compiling ossl_digest.c +compiling ossl_engine.c +compiling ossl_hmac.c +compiling ossl_kdf.c +compiling ossl_ns_spki.c +compiling ossl_ocsp.c +compiling ossl_pkcs12.c +compiling ossl_pkcs7.c +compiling ossl_pkey.c +compiling ossl_pkey_dh.c +compiling ossl_pkey_dsa.c +compiling ossl_pkey_ec.c +compiling ossl_pkey_rsa.c +compiling ossl_provider.c +compiling ossl_rand.c +compiling ossl_ssl.c +compiling ossl_ssl_session.c +compiling ossl_ts.c +compiling ossl_x509.c +compiling ossl_x509attr.c +compiling ossl_x509cert.c +compiling ossl_x509crl.c +compiling ossl_x509ext.c +compiling ossl_x509name.c +compiling ossl_x509req.c +compiling ossl_x509revoked.c +compiling ossl_x509store.c +linking shared-object openssl.so + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/openssl-3.3.0/ext/openssl +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-cmco0k sitelibdir\=./.gem.20250703-3393-cmco0k install +/usr/bin/install -c -m 0755 openssl.so ./.gem.20250703-3393-cmco0k + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/openssl-3.3.0/ext/openssl +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-cmco0k sitelibdir\=./.gem.20250703-3393-cmco0k clean diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/openssl-3.3.0/mkmf.log b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/openssl-3.3.0/mkmf.log new file mode 100644 index 00000000..21dc7bf4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/openssl-3.3.0/mkmf.log @@ -0,0 +1,2117 @@ +=== OpenSSL for Ruby configurator === +have_func: checking for rb_io_descriptor()... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return !!argv[argc]; +6: } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘rb_io_descriptor’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_io_descriptor; return !p; } + | ^~~~~~~~~~~~~~~~ +conftest.c:14:57: note: each undeclared identifier is reported only once for each function it appears in +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_io_descriptor; return !p; } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: extern void rb_io_descriptor(); +15: int t(void) { rb_io_descriptor(); return 0; } +/* end */ + +-------------------- + +have_func: checking for rb_io_maybe_wait(0, Qnil, Qnil, Qnil) in ruby/io.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { rb_io_maybe_wait(0, Qnil, Qnil, Qnil); return 0; } +/* end */ + +-------------------- + +have_func: checking for rb_io_timeout() in ruby/io.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_io_timeout; return !p; } +/* end */ + +-------------------- + +=== Checking for system dependent stuff... === +have_library: checking for t_open() in -lnsl... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lnsl -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘t_open’ undeclared (first use in this function); did you mean ‘popen’? + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))t_open; return !p; } + | ^~~~~~ + | popen +conftest.c:14:57: note: each undeclared identifier is reported only once for each function it appears in +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))t_open; return !p; } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lnsl -lm -lpthread -lc" +/usr/bin/ld: cannot find -lnsl: No such file or directory +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: extern void t_open(); +15: int t(void) { t_open(); return 0; } +/* end */ + +-------------------- + +have_library: checking for socket() in -lsocket... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lsocket -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘socket’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))socket; return !p; } + | ^~~~~~ +conftest.c:14:57: note: each undeclared identifier is reported only once for each function it appears in +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))socket; return !p; } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lsocket -lm -lpthread -lc" +/usr/bin/ld: cannot find -lsocket: No such file or directory +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: extern void socket(); +15: int t(void) { socket(); return 0; } +/* end */ + +-------------------- + +=== Checking for required stuff... === +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu x86_64-linux-gnu-pkg-config --exists openssl +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu x86_64-linux-gnu-pkg-config --libs openssl | +=> "-lssl -lcrypto \n" +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return !!argv[argc]; +6: } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu x86_64-linux-gnu-pkg-config --cflags-only-I openssl | +=> "\n" +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu x86_64-linux-gnu-pkg-config --cflags-only-other openssl | +=> "\n" +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu x86_64-linux-gnu-pkg-config --libs-only-l openssl | +=> "-lssl -lcrypto \n" +package configuration for openssl +incflags: +cflags: +ldflags: +libs: -lssl -lcrypto + +have_header: checking for openssl/ssl.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_macro: checking for LIBRESSL_VERSION_NUMBER in openssl/opensslv.h... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +conftest.c:6:3: error: #error + 6 | # error + | ^~~~~ +conftest.c:7:1: error: expected identifier or ‘(’ before ‘|’ token + 7 | |:/ === LIBRESSL_VERSION_NUMBER undefined === /:| + | ^ +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +4: /*top*/ +5: #ifndef LIBRESSL_VERSION_NUMBER +6: # error +7: |:/ === LIBRESSL_VERSION_NUMBER undefined === /:| +8: #endif +/* end */ + +-------------------- + +checking for OpenSSL version >= 1.0.2... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +4: +5: /*top*/ +6: int conftest_const[(OPENSSL_VERSION_NUMBER >= 0x10002000L) ? 1 : -1]; +/* end */ + +-------------------- + +=== Checking for OpenSSL features... === +have_func: checking for RAND_egd() in openssl/rand.h... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:17:15: warning: implicit declaration of function ‘RAND_egd’; did you mean ‘RAND_add’? [-Wimplicit-function-declaration] + 17 | int t(void) { RAND_egd(); return 0; } + | ^~~~~~~~ + | RAND_add +/usr/bin/ld: /tmp/ccaeKrAz.o: in function `t': +/home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/openssl-3.3.0/ext/openssl/conftest.c:17:(.text+0xb): undefined reference to `RAND_egd' +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { RAND_egd(); return 0; } +/* end */ + +-------------------- + +have_func: checking for ENGINE_load_dynamic() in openssl/engine.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { ENGINE_load_dynamic(); return 0; } +/* end */ + +-------------------- + +have_func: checking for ENGINE_load_4758cca() in openssl/engine.h... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:17:15: warning: implicit declaration of function ‘ENGINE_load_4758cca’ [-Wimplicit-function-declaration] + 17 | int t(void) { ENGINE_load_4758cca(); return 0; } + | ^~~~~~~~~~~~~~~~~~~ +/usr/bin/ld: /tmp/cc9MjGjd.o: in function `t': +/home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/openssl-3.3.0/ext/openssl/conftest.c:17:(.text+0xb): undefined reference to `ENGINE_load_4758cca' +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { ENGINE_load_4758cca(); return 0; } +/* end */ + +-------------------- + +have_func: checking for ENGINE_load_aep() in openssl/engine.h... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:17:15: warning: implicit declaration of function ‘ENGINE_load_aep’; did you mean ‘ENGINE_load_rdrand’? [-Wimplicit-function-declaration] + 17 | int t(void) { ENGINE_load_aep(); return 0; } + | ^~~~~~~~~~~~~~~ + | ENGINE_load_rdrand +/usr/bin/ld: /tmp/ccVDPP5q.o: in function `t': +/home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/openssl-3.3.0/ext/openssl/conftest.c:17:(.text+0xb): undefined reference to `ENGINE_load_aep' +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { ENGINE_load_aep(); return 0; } +/* end */ + +-------------------- + +have_func: checking for ENGINE_load_atalla() in openssl/engine.h... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:17:15: warning: implicit declaration of function ‘ENGINE_load_atalla’; did you mean ‘ENGINE_load_dynamic’? [-Wimplicit-function-declaration] + 17 | int t(void) { ENGINE_load_atalla(); return 0; } + | ^~~~~~~~~~~~~~~~~~ + | ENGINE_load_dynamic +/usr/bin/ld: /tmp/ccuSnXd7.o: in function `t': +/home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/openssl-3.3.0/ext/openssl/conftest.c:17:(.text+0xb): undefined reference to `ENGINE_load_atalla' +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { ENGINE_load_atalla(); return 0; } +/* end */ + +-------------------- + +have_func: checking for ENGINE_load_chil() in openssl/engine.h... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:17:15: warning: implicit declaration of function ‘ENGINE_load_chil’; did you mean ‘ENGINE_load_dynamic’? [-Wimplicit-function-declaration] + 17 | int t(void) { ENGINE_load_chil(); return 0; } + | ^~~~~~~~~~~~~~~~ + | ENGINE_load_dynamic +/usr/bin/ld: /tmp/ccOl3Thb.o: in function `t': +/home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/openssl-3.3.0/ext/openssl/conftest.c:17:(.text+0xb): undefined reference to `ENGINE_load_chil' +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { ENGINE_load_chil(); return 0; } +/* end */ + +-------------------- + +have_func: checking for ENGINE_load_cswift() in openssl/engine.h... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:17:15: warning: implicit declaration of function ‘ENGINE_load_cswift’; did you mean ‘ENGINE_load_rdrand’? [-Wimplicit-function-declaration] + 17 | int t(void) { ENGINE_load_cswift(); return 0; } + | ^~~~~~~~~~~~~~~~~~ + | ENGINE_load_rdrand +/usr/bin/ld: /tmp/ccGlwJLW.o: in function `t': +/home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/openssl-3.3.0/ext/openssl/conftest.c:17:(.text+0xb): undefined reference to `ENGINE_load_cswift' +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { ENGINE_load_cswift(); return 0; } +/* end */ + +-------------------- + +have_func: checking for ENGINE_load_nuron() in openssl/engine.h... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:17:15: warning: implicit declaration of function ‘ENGINE_load_nuron’; did you mean ‘ENGINE_load_rdrand’? [-Wimplicit-function-declaration] + 17 | int t(void) { ENGINE_load_nuron(); return 0; } + | ^~~~~~~~~~~~~~~~~ + | ENGINE_load_rdrand +/usr/bin/ld: /tmp/ccYvE4TG.o: in function `t': +/home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/openssl-3.3.0/ext/openssl/conftest.c:17:(.text+0xb): undefined reference to `ENGINE_load_nuron' +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { ENGINE_load_nuron(); return 0; } +/* end */ + +-------------------- + +have_func: checking for ENGINE_load_sureware() in openssl/engine.h... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:17:15: warning: implicit declaration of function ‘ENGINE_load_sureware’; did you mean ‘ENGINE_load_rdrand’? [-Wimplicit-function-declaration] + 17 | int t(void) { ENGINE_load_sureware(); return 0; } + | ^~~~~~~~~~~~~~~~~~~~ + | ENGINE_load_rdrand +/usr/bin/ld: /tmp/ccNde9Z4.o: in function `t': +/home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/openssl-3.3.0/ext/openssl/conftest.c:17:(.text+0xb): undefined reference to `ENGINE_load_sureware' +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { ENGINE_load_sureware(); return 0; } +/* end */ + +-------------------- + +have_func: checking for ENGINE_load_ubsec() in openssl/engine.h... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:17:15: warning: implicit declaration of function ‘ENGINE_load_ubsec’; did you mean ‘ENGINE_load_dynamic’? [-Wimplicit-function-declaration] + 17 | int t(void) { ENGINE_load_ubsec(); return 0; } + | ^~~~~~~~~~~~~~~~~ + | ENGINE_load_dynamic +/usr/bin/ld: /tmp/ccLoRtwC.o: in function `t': +/home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/openssl-3.3.0/ext/openssl/conftest.c:17:(.text+0xb): undefined reference to `ENGINE_load_ubsec' +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { ENGINE_load_ubsec(); return 0; } +/* end */ + +-------------------- + +have_func: checking for ENGINE_load_padlock() in openssl/engine.h... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:17:15: warning: implicit declaration of function ‘ENGINE_load_padlock’; did you mean ‘ENGINE_load_public_key’? [-Wimplicit-function-declaration] + 17 | int t(void) { ENGINE_load_padlock(); return 0; } + | ^~~~~~~~~~~~~~~~~~~ + | ENGINE_load_public_key +/usr/bin/ld: /tmp/ccB18ARv.o: in function `t': +/home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/openssl-3.3.0/ext/openssl/conftest.c:17:(.text+0xb): undefined reference to `ENGINE_load_padlock' +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { ENGINE_load_padlock(); return 0; } +/* end */ + +-------------------- + +have_func: checking for ENGINE_load_capi() in openssl/engine.h... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:17:15: warning: implicit declaration of function ‘ENGINE_load_capi’; did you mean ‘ENGINE_load_dynamic’? [-Wimplicit-function-declaration] + 17 | int t(void) { ENGINE_load_capi(); return 0; } + | ^~~~~~~~~~~~~~~~ + | ENGINE_load_dynamic +/usr/bin/ld: /tmp/ccSib9Qi.o: in function `t': +/home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/openssl-3.3.0/ext/openssl/conftest.c:17:(.text+0xb): undefined reference to `ENGINE_load_capi' +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { ENGINE_load_capi(); return 0; } +/* end */ + +-------------------- + +have_func: checking for ENGINE_load_gmp() in openssl/engine.h... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:17:15: warning: implicit declaration of function ‘ENGINE_load_gmp’ [-Wimplicit-function-declaration] + 17 | int t(void) { ENGINE_load_gmp(); return 0; } + | ^~~~~~~~~~~~~~~ +/usr/bin/ld: /tmp/ccjZ4hir.o: in function `t': +/home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/openssl-3.3.0/ext/openssl/conftest.c:17:(.text+0xb): undefined reference to `ENGINE_load_gmp' +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { ENGINE_load_gmp(); return 0; } +/* end */ + +-------------------- + +have_func: checking for ENGINE_load_gost() in openssl/engine.h... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:17:15: warning: implicit declaration of function ‘ENGINE_load_gost’ [-Wimplicit-function-declaration] + 17 | int t(void) { ENGINE_load_gost(); return 0; } + | ^~~~~~~~~~~~~~~~ +/usr/bin/ld: /tmp/ccQar49Z.o: in function `t': +/home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/openssl-3.3.0/ext/openssl/conftest.c:17:(.text+0xb): undefined reference to `ENGINE_load_gost' +collect2: error: ld returned 1 exit status +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { ENGINE_load_gost(); return 0; } +/* end */ + +-------------------- + +have_func: checking for ENGINE_load_cryptodev() in openssl/engine.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { ENGINE_load_cryptodev(); return 0; } +/* end */ + +-------------------- + +have_func: checking for i2d_re_X509_tbs(NULL, NULL) in openssl/x509.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { i2d_re_X509_tbs(NULL, NULL); return 0; } +/* end */ + +-------------------- + +have_struct_member: checking for SSL.ctx in openssl/ssl.h... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +conftest.c:6:27: error: invalid use of incomplete typedef ‘SSL’ {aka ‘struct ssl_st’} + 6 | int s = (char *)&((SSL*)0)->ctx - (char *)0; + | ^~ +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: int s = (char *)&((SSL*)0)->ctx - (char *)0; + 7: int main(int argc, char **argv) + 8: { + 9: return !!argv[argc]; +10: } +/* end */ + +-------------------- + +have_func: checking for EVP_MD_CTX_new() in openssl/evp.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { EVP_MD_CTX_new(); return 0; } +/* end */ + +-------------------- + +have_func: checking for EVP_MD_CTX_free(NULL) in openssl/evp.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { EVP_MD_CTX_free(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for EVP_MD_CTX_pkey_ctx(NULL) in openssl/evp.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { EVP_MD_CTX_pkey_ctx(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for X509_STORE_get_ex_data(NULL, 0) in openssl/x509.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { X509_STORE_get_ex_data(NULL, 0); return 0; } +/* end */ + +-------------------- + +have_func: checking for X509_STORE_set_ex_data(NULL, 0, NULL) in openssl/x509.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { X509_STORE_set_ex_data(NULL, 0, NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for X509_STORE_get_ex_new_index(0, NULL, NULL, NULL, NULL) in openssl/x509.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { X509_STORE_get_ex_new_index(0, NULL, NULL, NULL, NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for X509_CRL_get0_signature(NULL, NULL, NULL) in openssl/x509.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { X509_CRL_get0_signature(NULL, NULL, NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for X509_REQ_get0_signature(NULL, NULL, NULL) in openssl/x509.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { X509_REQ_get0_signature(NULL, NULL, NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for X509_REVOKED_get0_serialNumber(NULL) in openssl/x509.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { X509_REVOKED_get0_serialNumber(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for X509_REVOKED_get0_revocationDate(NULL) in openssl/x509.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { X509_REVOKED_get0_revocationDate(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for X509_get0_tbs_sigalg(NULL) in openssl/x509.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { X509_get0_tbs_sigalg(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for X509_STORE_CTX_get0_untrusted(NULL) in openssl/x509.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { X509_STORE_CTX_get0_untrusted(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for X509_STORE_CTX_get0_cert(NULL) in openssl/x509.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { X509_STORE_CTX_get0_cert(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for X509_STORE_CTX_get0_chain(NULL) in openssl/x509.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { X509_STORE_CTX_get0_chain(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for OCSP_SINGLERESP_get0_id(NULL) in openssl/ocsp.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { OCSP_SINGLERESP_get0_id(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for SSL_CTX_get_ciphers(NULL) in openssl/ssl.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { SSL_CTX_get_ciphers(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for X509_up_ref(NULL) in openssl/x509.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { X509_up_ref(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for X509_CRL_up_ref(NULL) in openssl/x509.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { X509_CRL_up_ref(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for X509_STORE_up_ref(NULL) in openssl/x509.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { X509_STORE_up_ref(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for SSL_SESSION_up_ref(NULL) in openssl/ssl.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { SSL_SESSION_up_ref(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for EVP_PKEY_up_ref(NULL) in openssl/evp.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { EVP_PKEY_up_ref(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for SSL_CTX_set_min_proto_version(NULL, 0) in openssl/ssl.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { SSL_CTX_set_min_proto_version(NULL, 0); return 0; } +/* end */ + +-------------------- + +have_func: checking for SSL_CTX_get_security_level(NULL) in openssl/ssl.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { SSL_CTX_get_security_level(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for X509_get0_notBefore(NULL) in openssl/x509.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { X509_get0_notBefore(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for SSL_SESSION_get_protocol_version(NULL) in openssl/ssl.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { SSL_SESSION_get_protocol_version(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for TS_STATUS_INFO_get0_status(NULL) in openssl/ts.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { TS_STATUS_INFO_get0_status(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for TS_STATUS_INFO_get0_text(NULL) in openssl/ts.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { TS_STATUS_INFO_get0_text(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for TS_STATUS_INFO_get0_failure_info(NULL) in openssl/ts.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { TS_STATUS_INFO_get0_failure_info(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for TS_VERIFY_CTS_set_certs(NULL, NULL) in openssl/ts.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { TS_VERIFY_CTS_set_certs(NULL, NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for TS_VERIFY_CTX_set_store(NULL, NULL) in openssl/ts.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { TS_VERIFY_CTX_set_store(NULL, NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for TS_VERIFY_CTX_add_flags(NULL, 0) in openssl/ts.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { TS_VERIFY_CTX_add_flags(NULL, 0); return 0; } +/* end */ + +-------------------- + +have_func: checking for TS_RESP_CTX_set_time_cb(NULL, NULL, NULL) in openssl/ts.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { TS_RESP_CTX_set_time_cb(NULL, NULL, NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for EVP_PBE_scrypt("", 0, (unsigned char *)"", 0, 0, 0, 0, 0, NULL, 0) in openssl/evp.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { char s1[1024], s2[1024]; EVP_PBE_scrypt(s1, 0, (unsigned char *)s2, 0, 0, 0, 0, 0, NULL, 0); return 0; } +/* end */ + +-------------------- + +have_func: checking for SSL_CTX_set_post_handshake_auth(NULL, 0) in openssl/ssl.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { SSL_CTX_set_post_handshake_auth(NULL, 0); return 0; } +/* end */ + +-------------------- + +have_func: checking for X509_STORE_get0_param(NULL) in openssl/x509.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { X509_STORE_get0_param(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for EVP_PKEY_check(NULL) in openssl/evp.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { EVP_PKEY_check(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for EVP_PKEY_new_raw_private_key(0, NULL, (unsigned char *)"", 0) in openssl/evp.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { char s1[1024]; EVP_PKEY_new_raw_private_key(0, NULL, (unsigned char *)s1, 0); return 0; } +/* end */ + +-------------------- + +have_func: checking for SSL_CTX_set_ciphersuites(NULL, "") in openssl/ssl.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { char s1[1024]; SSL_CTX_set_ciphersuites(NULL, s1); return 0; } +/* end */ + +-------------------- + +have_func: checking for SSL_set0_tmp_dh_pkey(NULL, NULL) in openssl/ssl.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { SSL_set0_tmp_dh_pkey(NULL, NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for ERR_get_error_all(NULL, NULL, NULL, NULL, NULL) in openssl/err.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { ERR_get_error_all(NULL, NULL, NULL, NULL, NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for TS_VERIFY_CTX_set_certs(NULL, NULL) in openssl/ts.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { TS_VERIFY_CTX_set_certs(NULL, NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for SSL_CTX_load_verify_file(NULL, "") in openssl/ssl.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { char s1[1024]; SSL_CTX_load_verify_file(NULL, s1); return 0; } +/* end */ + +-------------------- + +have_func: checking for BN_check_prime(NULL, NULL, NULL) in openssl/bn.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { BN_check_prime(NULL, NULL, NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for EVP_MD_CTX_get0_md(NULL) in openssl/evp.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { EVP_MD_CTX_get0_md(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for EVP_MD_CTX_get_pkey_ctx(NULL) in openssl/evp.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { EVP_MD_CTX_get_pkey_ctx(NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for EVP_PKEY_eq(NULL, NULL) in openssl/evp.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { EVP_PKEY_eq(NULL, NULL); return 0; } +/* end */ + +-------------------- + +have_func: checking for EVP_PKEY_dup(NULL) in openssl/evp.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lssl -lcrypto -lruby-3.2 -lssl -lcrypto -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return !!argv[argc]; +15: } +16: +17: int t(void) { EVP_PKEY_dup(NULL); return 0; } +/* end */ + +-------------------- + +=== Checking done. === +extconf.h is: +/* begin */ + 1: #ifndef EXTCONF_H + 2: #define EXTCONF_H + 3: #define OPENSSL_SUPPRESS_DEPRECATED 1 + 4: #define HAVE_RB_IO_DESCRIPTOR 1 + 5: #define HAVE_RB_IO_MAYBE_WAIT 1 + 6: #define HAVE_RB_IO_TIMEOUT 1 + 7: #define HAVE_OPENSSL_SSL_H 1 + 8: #define HAVE_ENGINE_LOAD_DYNAMIC 1 + 9: #define HAVE_ENGINE_LOAD_CRYPTODEV 1 +10: #define HAVE_I2D_RE_X509_TBS 1 +11: #define HAVE_OPAQUE_OPENSSL 1 +12: #define HAVE_EVP_MD_CTX_NEW 1 +13: #define HAVE_EVP_MD_CTX_FREE 1 +14: #define HAVE_EVP_MD_CTX_PKEY_CTX 1 +15: #define HAVE_X509_STORE_GET_EX_DATA 1 +16: #define HAVE_X509_STORE_SET_EX_DATA 1 +17: #define HAVE_X509_STORE_GET_EX_NEW_INDEX 1 +18: #define HAVE_X509_CRL_GET0_SIGNATURE 1 +19: #define HAVE_X509_REQ_GET0_SIGNATURE 1 +20: #define HAVE_X509_REVOKED_GET0_SERIALNUMBER 1 +21: #define HAVE_X509_REVOKED_GET0_REVOCATIONDATE 1 +22: #define HAVE_X509_GET0_TBS_SIGALG 1 +23: #define HAVE_X509_STORE_CTX_GET0_UNTRUSTED 1 +24: #define HAVE_X509_STORE_CTX_GET0_CERT 1 +25: #define HAVE_X509_STORE_CTX_GET0_CHAIN 1 +26: #define HAVE_OCSP_SINGLERESP_GET0_ID 1 +27: #define HAVE_SSL_CTX_GET_CIPHERS 1 +28: #define HAVE_X509_UP_REF 1 +29: #define HAVE_X509_CRL_UP_REF 1 +30: #define HAVE_X509_STORE_UP_REF 1 +31: #define HAVE_SSL_SESSION_UP_REF 1 +32: #define HAVE_EVP_PKEY_UP_REF 1 +33: #define HAVE_SSL_CTX_SET_MIN_PROTO_VERSION 1 +34: #define HAVE_SSL_CTX_GET_SECURITY_LEVEL 1 +35: #define HAVE_X509_GET0_NOTBEFORE 1 +36: #define HAVE_SSL_SESSION_GET_PROTOCOL_VERSION 1 +37: #define HAVE_TS_STATUS_INFO_GET0_STATUS 1 +38: #define HAVE_TS_STATUS_INFO_GET0_TEXT 1 +39: #define HAVE_TS_STATUS_INFO_GET0_FAILURE_INFO 1 +40: #define HAVE_TS_VERIFY_CTS_SET_CERTS 1 +41: #define HAVE_TS_VERIFY_CTX_SET_STORE 1 +42: #define HAVE_TS_VERIFY_CTX_ADD_FLAGS 1 +43: #define HAVE_TS_RESP_CTX_SET_TIME_CB 1 +44: #define HAVE_EVP_PBE_SCRYPT 1 +45: #define HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH 1 +46: #define HAVE_X509_STORE_GET0_PARAM 1 +47: #define HAVE_EVP_PKEY_CHECK 1 +48: #define HAVE_EVP_PKEY_NEW_RAW_PRIVATE_KEY 1 +49: #define HAVE_SSL_CTX_SET_CIPHERSUITES 1 +50: #define HAVE_SSL_SET0_TMP_DH_PKEY 1 +51: #define HAVE_ERR_GET_ERROR_ALL 1 +52: #define HAVE_TS_VERIFY_CTX_SET_CERTS 1 +53: #define HAVE_SSL_CTX_LOAD_VERIFY_FILE 1 +54: #define HAVE_BN_CHECK_PRIME 1 +55: #define HAVE_EVP_MD_CTX_GET0_MD 1 +56: #define HAVE_EVP_MD_CTX_GET_PKEY_CTX 1 +57: #define HAVE_EVP_PKEY_EQ 1 +58: #define HAVE_EVP_PKEY_DUP 1 +59: #endif +/* end */ + +Done. diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/openssl-3.3.0/openssl.so b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/openssl-3.3.0/openssl.so new file mode 100755 index 00000000..71ffad8f Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/openssl-3.3.0/openssl.so differ diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/psych-5.2.6/gem.build_complete b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/psych-5.2.6/gem.build_complete new file mode 100644 index 00000000..e69de29b diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/psych-5.2.6/gem_make.out b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/psych-5.2.6/gem_make.out new file mode 100644 index 00000000..678a68e5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/psych-5.2.6/gem_make.out @@ -0,0 +1,30 @@ +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/psych-5.2.6/ext/psych +/usr/bin/ruby3.2 -I/usr/lib/ruby/vendor_ruby extconf.rb +checking for yaml.h... yes +checking for yaml_get_version() in -lyaml... yes +creating Makefile + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/psych-5.2.6/ext/psych +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-uejz5i sitelibdir\=./.gem.20250703-3393-uejz5i clean +cd libyaml && make clean +/bin/sh: 1: cd: can't cd to libyaml +make: [Makefile:283: clean-so] Error 2 (ignored) + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/psych-5.2.6/ext/psych +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-uejz5i sitelibdir\=./.gem.20250703-3393-uejz5i +compiling psych.c +compiling psych_emitter.c +compiling psych_parser.c +compiling psych_to_ruby.c +compiling psych_yaml_tree.c +linking shared-object psych.so + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/psych-5.2.6/ext/psych +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-uejz5i sitelibdir\=./.gem.20250703-3393-uejz5i install +/usr/bin/install -c -m 0755 psych.so ./.gem.20250703-3393-uejz5i + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/psych-5.2.6/ext/psych +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-uejz5i sitelibdir\=./.gem.20250703-3393-uejz5i clean +cd libyaml && make clean +/bin/sh: 1: cd: can't cd to libyaml +make: [Makefile:283: clean-so] Error 2 (ignored) diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/psych-5.2.6/mkmf.log b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/psych-5.2.6/mkmf.log new file mode 100644 index 00000000..c2bf310b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/psych-5.2.6/mkmf.log @@ -0,0 +1,97 @@ +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu x86_64-linux-gnu-pkg-config --exists yaml-0.1 +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu x86_64-linux-gnu-pkg-config --libs yaml-0.1 | +=> "-lyaml \n" +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return !!argv[argc]; +6: } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lyaml -lm -lpthread -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return !!argv[argc]; +6: } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu x86_64-linux-gnu-pkg-config --cflags-only-I yaml-0.1 | +=> "\n" +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu x86_64-linux-gnu-pkg-config --cflags-only-other yaml-0.1 | +=> "\n" +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu x86_64-linux-gnu-pkg-config --libs-only-l yaml-0.1 | +=> "-lyaml \n" +package configuration for yaml-0.1 +incflags: +cflags: +ldflags: +libs: -lyaml + +find_header: checking for yaml.h... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +find_library: checking for yaml_get_version() in -lyaml... -------------------- yes + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lyaml -lruby-3.2 -lyaml -lyaml -lm -lpthread -lc" +conftest.c: In function ‘t’: +conftest.c:14:57: error: ‘yaml_get_version’ undeclared (first use in this function) + 14 | int t(void) { void ((*volatile p)()); p = (void ((*)()))yaml_get_version; return !p; } + | ^~~~~~~~~~~~~~~~ +conftest.c:14:57: note: each undeclared identifier is reported only once for each function it appears in +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))yaml_get_version; return !p; } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lyaml -lruby-3.2 -lyaml -lyaml -lm -lpthread -lc" +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return !!argv[argc]; +13: } +14: extern void yaml_get_version(); +15: int t(void) { yaml_get_version(); return 0; } +/* end */ + +-------------------- + diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/psych-5.2.6/psych.so b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/psych-5.2.6/psych.so new file mode 100755 index 00000000..a138ba31 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/psych-5.2.6/psych.so differ diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/racc-1.8.1/gem.build_complete b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/racc-1.8.1/gem.build_complete new file mode 100644 index 00000000..e69de29b diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/racc-1.8.1/gem_make.out b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/racc-1.8.1/gem_make.out new file mode 100644 index 00000000..139e75e0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/racc-1.8.1/gem_make.out @@ -0,0 +1,18 @@ +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/racc-1.8.1/ext/racc/cparse +/usr/bin/ruby3.2 -I/usr/lib/ruby/vendor_ruby extconf.rb +creating Makefile + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/racc-1.8.1/ext/racc/cparse +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-j6obti sitelibdir\=./.gem.20250703-3393-j6obti clean + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/racc-1.8.1/ext/racc/cparse +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-j6obti sitelibdir\=./.gem.20250703-3393-j6obti +compiling cparse.c +linking shared-object racc/cparse.so + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/racc-1.8.1/ext/racc/cparse +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-j6obti sitelibdir\=./.gem.20250703-3393-j6obti install +/usr/bin/install -c -m 0755 cparse.so ./.gem.20250703-3393-j6obti/racc + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/racc-1.8.1/ext/racc/cparse +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-j6obti sitelibdir\=./.gem.20250703-3393-j6obti clean diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/racc-1.8.1/racc/cparse.so b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/racc-1.8.1/racc/cparse.so new file mode 100755 index 00000000..69899126 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/racc-1.8.1/racc/cparse.so differ diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/stringio-3.1.7/gem.build_complete b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/stringio-3.1.7/gem.build_complete new file mode 100644 index 00000000..e69de29b diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/stringio-3.1.7/gem_make.out b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/stringio-3.1.7/gem_make.out new file mode 100644 index 00000000..f5d49fcf --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/stringio-3.1.7/gem_make.out @@ -0,0 +1,19 @@ +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/stringio-3.1.7/ext/stringio +/usr/bin/ruby3.2 -I/usr/lib/ruby/vendor_ruby extconf.rb +checking for rb_io_mode_t in ruby/io.h... no +creating Makefile + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/stringio-3.1.7/ext/stringio +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-dwac15 sitelibdir\=./.gem.20250703-3393-dwac15 clean + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/stringio-3.1.7/ext/stringio +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-dwac15 sitelibdir\=./.gem.20250703-3393-dwac15 +compiling stringio.c +linking shared-object stringio.so + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/stringio-3.1.7/ext/stringio +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-dwac15 sitelibdir\=./.gem.20250703-3393-dwac15 install +/usr/bin/install -c -m 0755 stringio.so ./.gem.20250703-3393-dwac15 + +current directory: /home/runner/work/shopify_toolkit/shopify_toolkit/vendor/bundle/ruby/3.2.0/gems/stringio-3.1.7/ext/stringio +make DESTDIR\= sitearchdir\=./.gem.20250703-3393-dwac15 sitelibdir\=./.gem.20250703-3393-dwac15 clean diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/stringio-3.1.7/mkmf.log b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/stringio-3.1.7/mkmf.log new file mode 100644 index 00000000..f3b57029 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/stringio-3.1.7/mkmf.log @@ -0,0 +1,30 @@ +have_type: checking for rb_io_mode_t in ruby/io.h... -------------------- no + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC conftest.c -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -lruby-3.2 -lm -lpthread -lc" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return !!argv[argc]; +6: } +/* end */ + +LD_LIBRARY_PATH=.:/usr/lib/x86_64-linux-gnu "x86_64-linux-gnu-gcc -I/usr/include/x86_64-linux-gnu/ruby-3.2.0 -I/usr/include/ruby-3.2.0/ruby/backward -I/usr/include/ruby-3.2.0 -I. -Wdate-time -D_FORTIFY_SOURCE=3 -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC -c conftest.c" +conftest.c:6:9: error: unknown type name ‘rb_io_mode_t’ + 6 | typedef rb_io_mode_t conftest_type; + | ^~~~~~~~~~~~ +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +4: +5: /*top*/ +6: typedef rb_io_mode_t conftest_type; +7: int conftestval[sizeof(conftest_type)?1:-1]; +/* end */ + +-------------------- + diff --git a/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/stringio-3.1.7/stringio.so b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/stringio-3.1.7/stringio.so new file mode 100755 index 00000000..7ed3b91b Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux-gnu/3.2.0/stringio-3.1.7/stringio.so differ diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/CHANGELOG.md b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/CHANGELOG.md new file mode 100644 index 00000000..93540ba5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/CHANGELOG.md @@ -0,0 +1,245 @@ +## Rails 8.0.2 (March 12, 2025) ## + +* No changes. + + +## Rails 8.0.2 (March 12, 2025) ## + +* Improve `with_routing` test helper to not rebuild the middleware stack. + + Otherwise some middleware configuration could be lost. + + *Édouard Chin* + +* Add resource name to the `ArgumentError` that's raised when invalid `:only` or `:except` options are given to `#resource` or `#resources` + + This makes it easier to locate the source of the problem, especially for routes drawn by gems. + + Before: + ``` + :only and :except must include only [:index, :create, :new, :show, :update, :destroy, :edit], but also included [:foo, :bar] + ``` + + After: + ``` + Route `resources :products` - :only and :except must include only [:index, :create, :new, :show, :update, :destroy, :edit], but also included [:foo, :bar] + ``` + + *Jeremy Green* + +* Fix `url_for` to handle `:path_params` gracefully when it's not a `Hash`. + + Prevents various security scanners from causing exceptions. + + *Martin Emde* + +* Fix `ActionDispatch::Executor` to unwrap exceptions like other error reporting middlewares. + + *Jean Boussier* + + +## Rails 8.0.1 (December 13, 2024) ## + +* Add `ActionDispatch::Request::Session#store` method to conform Rack spec. + + *Yaroslav* + + +## Rails 8.0.0.1 (December 10, 2024) ## + +* Add validation to content security policies to disallow spaces and semicolons. + Developers should use multiple arguments, and different directive methods instead. + + [CVE-2024-54133] + + *Gannon McGibbon* + + +## Rails 8.0.0 (November 07, 2024) ## + +* No changes. + + +## Rails 8.0.0.rc2 (October 30, 2024) ## + +* Fix routes with `::` in the path. + + *Rafael Mendonça França* + +* Maintain Rack 2 parameter parsing behaviour. + + *Matthew Draper* + + +## Rails 8.0.0.rc1 (October 19, 2024) ## + +* Remove `Rails.application.config.action_controller.allow_deprecated_parameters_hash_equality`. + + *Rafael Mendonça França* + +* Improve `ActionController::TestCase` to expose a binary encoded `request.body`. + + The rack spec clearly states: + + > The input stream is an IO-like object which contains the raw HTTP POST data. + > When applicable, its external encoding must be “ASCII-8BIT” and it must be opened in binary mode. + + Until now its encoding was generally UTF-8, which doesn't accurately reflect production + behavior. + + *Jean Boussier* + +* Update `ActionController::AllowBrowser` to support passing method names to `:block` + + ```ruby + class ApplicationController < ActionController::Base + allow_browser versions: :modern, block: :handle_outdated_browser + + private + def handle_outdated_browser + render file: Rails.root.join("public/custom-error.html"), status: :not_acceptable + end + end + ``` + + *Sean Doyle* + +* Raise an `ArgumentError` when invalid `:only` or `:except` options are passed into `#resource` and `#resources`. + + *Joshua Young* + +## Rails 8.0.0.beta1 (September 26, 2024) ## + +* Fix non-GET requests not updating cookies in `ActionController::TestCase`. + + *Jon Moss*, *Hartley McGuire* + +* Update `ActionController::Live` to use a thread-pool to reuse threads across requests. + + *Adam Renberg Tamm* + +* Introduce safer, more explicit params handling method with `params#expect` such that + `params.expect(table: [ :attr ])` replaces `params.require(:table).permit(:attr)` + + Ensures params are filtered with consideration for the expected + types of values, improving handling of params and avoiding ignorable + errors caused by params tampering. + + ```ruby + # If the url is altered to ?person=hacked + # Before + params.require(:person).permit(:name, :age, pets: [:name]) + # raises NoMethodError, causing a 500 and potential error reporting + + # After + params.expect(person: [ :name, :age, pets: [[:name]] ]) + # raises ActionController::ParameterMissing, correctly returning a 400 error + ``` + + You may also notice the new double array `[[:name]]`. In order to + declare when a param is expected to be an array of parameter hashes, + this new double array syntax is used to explicitly declare an array. + `expect` requires you to declare expected arrays in this way, and will + ignore arrays that are passed when, for example, `pet: [:name]` is used. + + In order to preserve compatibility, `permit` does not adopt the new + double array syntax and is therefore more permissive about unexpected + types. Using `expect` everywhere is recommended. + + We suggest replacing `params.require(:person).permit(:name, :age)` + with the direct replacement `params.expect(person: [:name, :age])` + to prevent external users from manipulating params to trigger 500 + errors. A 400 error will be returned instead, using public/400.html + + Usage of `params.require(:id)` should likewise be replaced with + `params.expect(:id)` which is designed to ensure that `params[:id]` + is a scalar and not an array or hash, also requiring the param. + + ```ruby + # Before + User.find(params.require(:id)) # allows an array, altering behavior + + # After + User.find(params.expect(:id)) # expect only returns non-blank permitted scalars (excludes Hash, Array, nil, "", etc) + ``` + + *Martin Emde* + +* System Testing: Disable Chrome's search engine choice by default in system tests. + + *glaszig* + +* Fix `Request#raw_post` raising `NoMethodError` when `rack.input` is `nil`. + + *Hartley McGuire* + +* Remove `racc` dependency by manually writing `ActionDispatch::Journey::Scanner`. + + *Gannon McGibbon* + +* Speed up `ActionDispatch::Routing::Mapper::Scope#[]` by merging frame hashes. + + *Gannon McGibbon* + +* Allow bots to ignore `allow_browser`. + + *Matthew Nguyen* + +* Deprecate drawing routes with multiple paths to make routing faster. + You may use `with_options` or a loop to make drawing multiple paths easier. + + ```ruby + # Before + get "/users", "/other_path", to: "users#index" + + # After + get "/users", to: "users#index" + get "/other_path", to: "users#index" + ``` + + *Gannon McGibbon* + +* Make `http_cache_forever` use `immutable: true` + + *Nate Matykiewicz* + +* Add `config.action_dispatch.strict_freshness`. + + When set to `true`, the `ETag` header takes precedence over the `Last-Modified` header when both are present, + as specified by RFC 7232, Section 6. + + Defaults to `false` to maintain compatibility with previous versions of Rails, but is enabled as part of + Rails 8.0 defaults. + + *heka1024* + +* Support `immutable` directive in Cache-Control + + ```ruby + expires_in 1.minute, public: true, immutable: true + # Cache-Control: public, max-age=60, immutable + ``` + + *heka1024* + +* Add `:wasm_unsafe_eval` mapping for `content_security_policy` + + ```ruby + # Before + policy.script_src "'wasm-unsafe-eval'" + + # After + policy.script_src :wasm_unsafe_eval + ``` + + *Joe Haig* + +* Add `display_capture` and `keyboard_map` in `permissions_policy` + + *Cyril Blaecke* + +* Add `connect` route helper. + + *Samuel Williams* + +Please check [7-2-stable](https://github.com/rails/rails/blob/7-2-stable/actionpack/CHANGELOG.md) for previous changes. diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/MIT-LICENSE b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/MIT-LICENSE new file mode 100644 index 00000000..7be9ac63 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/MIT-LICENSE @@ -0,0 +1,21 @@ +Copyright (c) David Heinemeier Hansson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/README.rdoc b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/README.rdoc new file mode 100644 index 00000000..028f5910 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/README.rdoc @@ -0,0 +1,57 @@ += Action Pack -- From request to response + +Action Pack is a framework for handling and responding to web requests. It +provides mechanisms for *routing* (mapping request URLs to actions), defining +*controllers* that implement actions, and generating responses. In short, Action Pack +provides the controller layer in the MVC paradigm. + +It consists of several modules: + +* Action Dispatch, which parses information about the web request, handles + routing as defined by the user, and does advanced processing related to HTTP + such as MIME-type negotiation, decoding parameters in POST, PATCH, or PUT bodies, + handling HTTP caching logic, cookies and sessions. + +* Action Controller, which provides a base controller class that can be + subclassed to implement filters and actions to handle requests. The result + of an action is typically content generated from views. + +With the Ruby on \Rails framework, users only directly interface with the +Action Controller module. Necessary Action Dispatch functionality is activated +by default and Action View rendering is implicitly triggered by Action +Controller. However, these modules are designed to function on their own and +can be used outside of \Rails. + +You can read more about Action Pack in the {Action Controller Overview}[https://guides.rubyonrails.org/action_controller_overview.html] guide. + +== Download and installation + +The latest version of Action Pack can be installed with RubyGems: + + $ gem install actionpack + +Source code can be downloaded as part of the \Rails project on GitHub: + +* https://github.com/rails/rails/tree/main/actionpack + + +== License + +Action Pack is released under the MIT license: + +* https://opensource.org/licenses/MIT + + +== Support + +API documentation is at: + +* https://api.rubyonrails.org + +Bug reports for the Ruby on \Rails project can be filed here: + +* https://github.com/rails/rails/issues + +Feature requests should be discussed on the rails-core mailing list here: + +* https://discuss.rubyonrails.org/c/rubyonrails-core diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller.rb new file mode 100644 index 00000000..6b300404 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "action_pack" +require "active_support" +require "active_support/rails" +require "active_support/i18n" +require "abstract_controller/deprecator" + +module AbstractController + extend ActiveSupport::Autoload + + autoload :ActionNotFound, "abstract_controller/base" + autoload :Base + autoload :Caching + autoload :Callbacks + autoload :Collector + autoload :DoubleRenderError, "abstract_controller/rendering" + autoload :Helpers + autoload :Logger + autoload :Rendering + autoload :Translation + autoload :AssetPaths + autoload :UrlFor + + def self.eager_load! + super + AbstractController::Caching.eager_load! + AbstractController::Base.descendants.each do |controller| + unless controller.abstract? + controller.eager_load! + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/asset_paths.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/asset_paths.rb new file mode 100644 index 00000000..bbfdea52 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/asset_paths.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# :markup: markdown + +module AbstractController + module AssetPaths # :nodoc: + extend ActiveSupport::Concern + + included do + config_accessor :asset_host, :assets_dir, :javascripts_dir, + :stylesheets_dir, :default_asset_host_protocol, :relative_url_root + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/base.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/base.rb new file mode 100644 index 00000000..8b1a0c99 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/base.rb @@ -0,0 +1,299 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "abstract_controller/error" +require "active_support/configurable" +require "active_support/descendants_tracker" +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/module/attr_internal" + +module AbstractController + # Raised when a non-existing controller action is triggered. + class ActionNotFound < StandardError + attr_reader :controller, :action # :nodoc: + + def initialize(message = nil, controller = nil, action = nil) # :nodoc: + @controller = controller + @action = action + super(message) + end + + if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker) + include DidYouMean::Correctable # :nodoc: + + def corrections # :nodoc: + @corrections ||= DidYouMean::SpellChecker.new(dictionary: controller.class.action_methods).correct(action) + end + end + end + + # # Abstract Controller Base + # + # AbstractController::Base is a low-level API. Nobody should be using it + # directly, and subclasses (like ActionController::Base) are expected to provide + # their own `render` method, since rendering means different things depending on + # the context. + class Base + ## + # Returns the body of the HTTP response sent by the controller. + attr_internal :response_body + + ## + # Returns the name of the action this controller is processing. + attr_internal :action_name + + ## + # Returns the formats that can be processed by the controller. + attr_internal :formats + + include ActiveSupport::Configurable + extend ActiveSupport::DescendantsTracker + + class << self + attr_reader :abstract + alias_method :abstract?, :abstract + + # Define a controller as abstract. See internal_methods for more details. + def abstract! + @abstract = true + end + + def inherited(klass) # :nodoc: + # Define the abstract ivar on subclasses so that we don't get uninitialized ivar + # warnings + unless klass.instance_variable_defined?(:@abstract) + klass.instance_variable_set(:@abstract, false) + end + super + end + + # A list of all internal methods for a controller. This finds the first abstract + # superclass of a controller, and gets a list of all public instance methods on + # that abstract class. Public instance methods of a controller would normally be + # considered action methods, so methods declared on abstract classes are being + # removed. (ActionController::Metal and ActionController::Base are defined as + # abstract) + def internal_methods + controller = self + methods = [] + + until controller.abstract? + methods += controller.public_instance_methods(false) + controller = controller.superclass + end + + controller.public_instance_methods(true) - methods + end + + # A list of method names that should be considered actions. This includes all + # public instance methods on a controller, less any internal methods (see + # internal_methods), adding back in any methods that are internal, but still + # exist on the class itself. + # + # #### Returns + # * `Set` - A set of all methods that should be considered actions. + # + def action_methods + @action_methods ||= begin + # All public instance methods of this class, including ancestors except for + # public instance methods of Base and its ancestors. + methods = public_instance_methods(true) - internal_methods + # Be sure to include shadowed public instance methods of this class. + methods.concat(public_instance_methods(false)) + methods.map!(&:to_s) + methods.to_set + end + end + + # action_methods are cached and there is sometimes a need to refresh them. + # ::clear_action_methods! allows you to do that, so next time you run + # action_methods, they will be recalculated. + def clear_action_methods! + @action_methods = nil + end + + # Returns the full controller name, underscored, without the ending Controller. + # + # class MyApp::MyPostsController < AbstractController::Base + # + # end + # + # MyApp::MyPostsController.controller_path # => "my_app/my_posts" + # + # #### Returns + # * `String` + # + def controller_path + @controller_path ||= name.delete_suffix("Controller").underscore unless anonymous? + end + + # Refresh the cached action_methods when a new action_method is added. + def method_added(name) + super + clear_action_methods! + end + + def eager_load! # :nodoc: + action_methods + nil + end + end + + abstract! + + # Calls the action going through the entire Action Dispatch stack. + # + # The actual method that is called is determined by calling #method_for_action. + # If no method can handle the action, then an AbstractController::ActionNotFound + # error is raised. + # + # #### Returns + # * `self` + # + def process(action, ...) + @_action_name = action.to_s + + unless action_name = _find_action_name(@_action_name) + raise ActionNotFound.new("The action '#{action}' could not be found for #{self.class.name}", self, action) + end + + @_response_body = nil + + process_action(action_name, ...) + end + + # Delegates to the class's ::controller_path. + def controller_path + self.class.controller_path + end + + # Delegates to the class's ::action_methods. + def action_methods + self.class.action_methods + end + + # Returns true if a method for the action is available and can be dispatched, + # false otherwise. + # + # Notice that `action_methods.include?("foo")` may return false and + # `available_action?("foo")` returns true because this method considers actions + # that are also available through other means, for example, implicit render + # ones. + # + # #### Parameters + # * `action_name` - The name of an action to be tested + # + def available_action?(action_name) + _find_action_name(action_name) + end + + # Tests if a response body is set. Used to determine if the `process_action` + # callback needs to be terminated in AbstractController::Callbacks. + def performed? + response_body + end + + # Returns true if the given controller is capable of rendering a path. A + # subclass of `AbstractController::Base` may return false. An Email controller + # for example does not support paths, only full URLs. + def self.supports_path? + true + end + + def inspect # :nodoc: + "#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>" + end + + private + # Returns true if the name can be considered an action because it has a method + # defined in the controller. + # + # #### Parameters + # * `name` - The name of an action to be tested + # + def action_method?(name) + self.class.action_methods.include?(name) + end + + # Call the action. Override this in a subclass to modify the behavior around + # processing an action. This, and not #process, is the intended way to override + # action dispatching. + # + # Notice that the first argument is the method to be dispatched which is **not** + # necessarily the same as the action name. + def process_action(...) + send_action(...) + end + + # Actually call the method associated with the action. Override this method if + # you wish to change how action methods are called, not to add additional + # behavior around it. For example, you would override #send_action if you want + # to inject arguments into the method. + alias send_action send + + # If the action name was not found, but a method called "action_missing" was + # found, #method_for_action will return "_handle_action_missing". This method + # calls #action_missing with the current action name. + def _handle_action_missing(*args) + action_missing(@_action_name, *args) + end + + # Takes an action name and returns the name of the method that will handle the + # action. + # + # It checks if the action name is valid and returns false otherwise. + # + # See method_for_action for more information. + # + # #### Parameters + # * `action_name` - An action name to find a method name for + # + # + # #### Returns + # * `string` - The name of the method that handles the action + # * false - No valid method name could be found. + # + # Raise `AbstractController::ActionNotFound`. + def _find_action_name(action_name) + _valid_action_name?(action_name) && method_for_action(action_name) + end + + # Takes an action name and returns the name of the method that will handle the + # action. In normal cases, this method returns the same name as it receives. By + # default, if #method_for_action receives a name that is not an action, it will + # look for an #action_missing method and return "_handle_action_missing" if one + # is found. + # + # Subclasses may override this method to add additional conditions that should + # be considered an action. For instance, an HTTP controller with a template + # matching the action name is considered to exist. + # + # If you override this method to handle additional cases, you may also provide a + # method (like `_handle_method_missing`) to handle the case. + # + # If none of these conditions are true, and `method_for_action` returns `nil`, + # an `AbstractController::ActionNotFound` exception will be raised. + # + # #### Parameters + # * `action_name` - An action name to find a method name for + # + # + # #### Returns + # * `string` - The name of the method that handles the action + # * `nil` - No method name could be found. + # + def method_for_action(action_name) + if action_method?(action_name) + action_name + elsif respond_to?(:action_missing, true) + "_handle_action_missing" + end + end + + # Checks if the action name is valid and returns false otherwise. + def _valid_action_name?(action_name) + !action_name.to_s.include? File::SEPARATOR + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/caching.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/caching.rb new file mode 100644 index 00000000..3e5fc6ac --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/caching.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +# :markup: markdown + +module AbstractController + module Caching + extend ActiveSupport::Concern + extend ActiveSupport::Autoload + + eager_autoload do + autoload :Fragments + end + + module ConfigMethods + def cache_store + config.cache_store + end + + def cache_store=(store) + config.cache_store = ActiveSupport::Cache.lookup_store(*store) + end + + private + def cache_configured? + perform_caching && cache_store + end + end + + include ConfigMethods + include AbstractController::Caching::Fragments + + included do + extend ConfigMethods + + config_accessor :default_static_extension + self.default_static_extension ||= ".html" + + config_accessor :perform_caching + self.perform_caching = true if perform_caching.nil? + + config_accessor :enable_fragment_cache_logging + self.enable_fragment_cache_logging = false + + class_attribute :_view_cache_dependencies, default: [] + helper_method :view_cache_dependencies if respond_to?(:helper_method) + end + + module ClassMethods + def view_cache_dependency(&dependency) + self._view_cache_dependencies += [dependency] + end + end + + def view_cache_dependencies + self.class._view_cache_dependencies.filter_map { |dep| instance_exec(&dep) } + end + + private + # Convenience accessor. + def cache(key, options = {}, &block) # :doc: + if cache_configured? + cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block) + else + yield + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/caching/fragments.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/caching/fragments.rb new file mode 100644 index 00000000..8998e204 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/caching/fragments.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +# :markup: markdown + +module AbstractController + module Caching + # # Abstract Controller Caching Fragments + # + # Fragment caching is used for caching various blocks within views without + # caching the entire action as a whole. This is useful when certain elements of + # an action change frequently or depend on complicated state while other parts + # rarely change or can be shared amongst multiple parties. The caching is done + # using the `cache` helper available in the Action View. See + # ActionView::Helpers::CacheHelper for more information. + # + # While it's strongly recommended that you use key-based cache expiration (see + # links in CacheHelper for more information), it is also possible to manually + # expire caches. For example: + # + # expire_fragment('name_of_cache') + module Fragments + extend ActiveSupport::Concern + + included do + if respond_to?(:class_attribute) + class_attribute :fragment_cache_keys + else + mattr_writer :fragment_cache_keys + end + + self.fragment_cache_keys = [] + + if respond_to?(:helper_method) + helper_method :combined_fragment_cache_key + end + end + + module ClassMethods + # Allows you to specify controller-wide key prefixes for cache fragments. Pass + # either a constant `value`, or a block which computes a value each time a cache + # key is generated. + # + # For example, you may want to prefix all fragment cache keys with a global + # version identifier, so you can easily invalidate all caches. + # + # class ApplicationController + # fragment_cache_key "v1" + # end + # + # When it's time to invalidate all fragments, simply change the string constant. + # Or, progressively roll out the cache invalidation using a computed value: + # + # class ApplicationController + # fragment_cache_key do + # @account.id.odd? ? "v1" : "v2" + # end + # end + def fragment_cache_key(value = nil, &key) + self.fragment_cache_keys += [key || -> { value }] + end + end + + # Given a key (as described in `expire_fragment`), returns a key array suitable + # for use in reading, writing, or expiring a cached fragment. All keys begin + # with `:views`, followed by `ENV["RAILS_CACHE_ID"]` or + # `ENV["RAILS_APP_VERSION"]` if set, followed by any controller-wide key prefix + # values, ending with the specified `key` value. + def combined_fragment_cache_key(key) + head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) } + tail = key.is_a?(Hash) ? url_for(key).split("://").last : key + + cache_key = [:views, ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"], head, tail] + cache_key.flatten!(1) + cache_key.compact! + cache_key + end + + # Writes `content` to the location signified by `key` (see `expire_fragment` for + # acceptable formats). + def write_fragment(key, content, options = nil) + return content unless cache_configured? + + key = combined_fragment_cache_key(key) + instrument_fragment_cache :write_fragment, key do + content = content.to_str + cache_store.write(key, content, options) + end + content + end + + # Reads a cached fragment from the location signified by `key` (see + # `expire_fragment` for acceptable formats). + def read_fragment(key, options = nil) + return unless cache_configured? + + key = combined_fragment_cache_key(key) + instrument_fragment_cache :read_fragment, key do + result = cache_store.read(key, options) + result.respond_to?(:html_safe) ? result.html_safe : result + end + end + + # Check if a cached fragment from the location signified by `key` exists (see + # `expire_fragment` for acceptable formats). + def fragment_exist?(key, options = nil) + return unless cache_configured? + key = combined_fragment_cache_key(key) + + instrument_fragment_cache :exist_fragment?, key do + cache_store.exist?(key, options) + end + end + + # Removes fragments from the cache. + # + # `key` can take one of three forms: + # + # * String - This would normally take the form of a path, like + # `pages/45/notes`. + # * Hash - Treated as an implicit call to `url_for`, like `{ controller: + # 'pages', action: 'notes', id: 45}` + # * Regexp - Will remove any fragment that matches, so `%r{pages/\d*/notes}` + # might remove all notes. Make sure you don't use anchors in the regex (`^` + # or `$`) because the actual filename matched looks like + # `./cache/filename/path.cache`. Note: Regexp expiration is only supported + # on caches that can iterate over all keys (unlike memcached). + # + # + # `options` is passed through to the cache store's `delete` method (or + # `delete_matched`, for Regexp keys). + def expire_fragment(key, options = nil) + return unless cache_configured? + key = combined_fragment_cache_key(key) unless key.is_a?(Regexp) + + instrument_fragment_cache :expire_fragment, key do + if key.is_a?(Regexp) + cache_store.delete_matched(key, options) + else + cache_store.delete(key, options) + end + end + end + + def instrument_fragment_cache(name, key, &block) # :nodoc: + ActiveSupport::Notifications.instrument("#{name}.#{instrument_name}", instrument_payload(key), &block) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/callbacks.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/callbacks.rb new file mode 100644 index 00000000..cba63b2e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/callbacks.rb @@ -0,0 +1,265 @@ +# frozen_string_literal: true + +# :markup: markdown + +module AbstractController + # # Abstract Controller Callbacks + # + # Abstract Controller provides hooks during the life cycle of a controller + # action. Callbacks allow you to trigger logic during this cycle. Available + # callbacks are: + # + # * `after_action` + # * `append_after_action` + # * `append_around_action` + # * `append_before_action` + # * `around_action` + # * `before_action` + # * `prepend_after_action` + # * `prepend_around_action` + # * `prepend_before_action` + # * `skip_after_action` + # * `skip_around_action` + # * `skip_before_action` + module Callbacks + extend ActiveSupport::Concern + + # Uses ActiveSupport::Callbacks as the base functionality. For more details on + # the whole callback system, read the documentation for + # ActiveSupport::Callbacks. + include ActiveSupport::Callbacks + + included do + define_callbacks :process_action, + terminator: ->(controller, result_lambda) { result_lambda.call; controller.performed? }, + skip_after_callbacks_if_terminated: true + mattr_accessor :raise_on_missing_callback_actions, default: false + end + + class ActionFilter # :nodoc: + def initialize(filters, conditional_key, actions) + @filters = filters.to_a + @conditional_key = conditional_key + @actions = Array(actions).map(&:to_s).to_set + end + + def match?(controller) + if controller.raise_on_missing_callback_actions + missing_action = @actions.find { |action| !controller.available_action?(action) } + if missing_action + filter_names = @filters.length == 1 ? @filters.first.inspect : @filters.inspect + + message = <<~MSG + The #{missing_action} action could not be found for the #{filter_names} + callback on #{controller.class.name}, but it is listed in the controller's + #{@conditional_key.inspect} option. + + Raising for missing callback actions is a new default in Rails 7.1, if you'd + like to turn this off you can delete the option from the environment configurations + or set `config.action_controller.raise_on_missing_callback_actions` to `false`. + MSG + + raise ActionNotFound.new(message, controller, missing_action) + end + end + + @actions.include?(controller.action_name) + end + + alias after match? + alias before match? + alias around match? + end + + module ClassMethods + # If `:only` or `:except` are used, convert the options into the `:if` and + # `:unless` options of ActiveSupport::Callbacks. + # + # The basic idea is that `:only => :index` gets converted to `:if => proc {|c| + # c.action_name == "index" }`. + # + # Note that `:only` has priority over `:if` in case they are used together. + # + # only: :index, if: -> { true } # the :if option will be ignored. + # + # Note that `:if` has priority over `:except` in case they are used together. + # + # except: :index, if: -> { true } # the :except option will be ignored. + # + # #### Options + # * `only` - The callback should be run only for this action. + # * `except` - The callback should be run for all actions except this action. + # + def _normalize_callback_options(options) + _normalize_callback_option(options, :only, :if) + _normalize_callback_option(options, :except, :unless) + end + + def _normalize_callback_option(options, from, to) # :nodoc: + if from_value = options.delete(from) + filters = options[:filters] + from_value = ActionFilter.new(filters, from, from_value) + options[to] = Array(options[to]).unshift(from_value) + end + end + + # Take callback names and an optional callback proc, normalize them, then call + # the block with each callback. This allows us to abstract the normalization + # across several methods that use it. + # + # #### Parameters + # * `callbacks` - An array of callbacks, with an optional options hash as the + # last parameter. + # * `block` - A proc that should be added to the callbacks. + # + # + # #### Block Parameters + # * `name` - The callback to be added. + # * `options` - A hash of options to be used when adding the callback. + # + def _insert_callbacks(callbacks, block = nil) + options = callbacks.extract_options! + callbacks.push(block) if block + options[:filters] = callbacks + _normalize_callback_options(options) + options.delete(:filters) + callbacks.each do |callback| + yield callback, options + end + end + + ## + # :method: before_action + # + # :call-seq: before_action(names, block) + # + # Append a callback before actions. See _insert_callbacks for parameter details. + # + # If the callback renders or redirects, the action will not run. If there are + # additional callbacks scheduled to run after that callback, they are also + # cancelled. + + ## + # :method: prepend_before_action + # + # :call-seq: prepend_before_action(names, block) + # + # Prepend a callback before actions. See _insert_callbacks for parameter + # details. + # + # If the callback renders or redirects, the action will not run. If there are + # additional callbacks scheduled to run after that callback, they are also + # cancelled. + + ## + # :method: skip_before_action + # + # :call-seq: skip_before_action(names) + # + # Skip a callback before actions. See _insert_callbacks for parameter details. + + ## + # :method: append_before_action + # + # :call-seq: append_before_action(names, block) + # + # Append a callback before actions. See _insert_callbacks for parameter details. + # + # If the callback renders or redirects, the action will not run. If there are + # additional callbacks scheduled to run after that callback, they are also + # cancelled. + + ## + # :method: after_action + # + # :call-seq: after_action(names, block) + # + # Append a callback after actions. See _insert_callbacks for parameter details. + + ## + # :method: prepend_after_action + # + # :call-seq: prepend_after_action(names, block) + # + # Prepend a callback after actions. See _insert_callbacks for parameter details. + + ## + # :method: skip_after_action + # + # :call-seq: skip_after_action(names) + # + # Skip a callback after actions. See _insert_callbacks for parameter details. + + ## + # :method: append_after_action + # + # :call-seq: append_after_action(names, block) + # + # Append a callback after actions. See _insert_callbacks for parameter details. + + ## + # :method: around_action + # + # :call-seq: around_action(names, block) + # + # Append a callback around actions. See _insert_callbacks for parameter details. + + ## + # :method: prepend_around_action + # + # :call-seq: prepend_around_action(names, block) + # + # Prepend a callback around actions. See _insert_callbacks for parameter + # details. + + ## + # :method: skip_around_action + # + # :call-seq: skip_around_action(names) + # + # Skip a callback around actions. See _insert_callbacks for parameter details. + + ## + # :method: append_around_action + # + # :call-seq: append_around_action(names, block) + # + # Append a callback around actions. See _insert_callbacks for parameter details. + # set up before_action, prepend_before_action, skip_before_action, etc. for each + # of before, after, and around. + [:before, :after, :around].each do |callback| + define_method "#{callback}_action" do |*names, &blk| + _insert_callbacks(names, blk) do |name, options| + set_callback(:process_action, callback, name, options) + end + end + + define_method "prepend_#{callback}_action" do |*names, &blk| + _insert_callbacks(names, blk) do |name, options| + set_callback(:process_action, callback, name, options.merge(prepend: true)) + end + end + + # Skip a before, after or around callback. See _insert_callbacks for details on + # the allowed parameters. + define_method "skip_#{callback}_action" do |*names| + _insert_callbacks(names) do |name, options| + skip_callback(:process_action, callback, name, options) + end + end + + # *_action is the same as append_*_action + alias_method :"append_#{callback}_action", :"#{callback}_action" + end + end + + private + # Override `AbstractController::Base#process_action` to run the `process_action` + # callbacks around the normal behavior. + def process_action(...) + run_callbacks(:process_action) do + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/collector.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/collector.rb new file mode 100644 index 00000000..dee0b497 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/collector.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "action_dispatch/http/mime_type" + +module AbstractController + module Collector + def self.generate_method_for_mime(mime) + sym = mime.is_a?(Symbol) ? mime : mime.to_sym + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{sym}(...) + custom(Mime[:#{sym}], ...) + end + RUBY + end + + Mime::SET.each do |mime| + generate_method_for_mime(mime) + end + + Mime::Type.register_callback do |mime| + generate_method_for_mime(mime) unless instance_methods.include?(mime.to_sym) + end + + private + def method_missing(symbol, ...) + unless mime_constant = Mime[symbol] + raise NoMethodError, "To respond to a custom format, register it as a MIME type first: " \ + "https://guides.rubyonrails.org/action_controller_overview.html#restful-downloads. " \ + "If you meant to respond to a variant like :tablet or :phone, not a custom format, " \ + "be sure to nest your variant response within a format response: " \ + "format.html { |html| html.tablet { ... } }" + end + + if Mime::SET.include?(mime_constant) + AbstractController::Collector.generate_method_for_mime(mime_constant) + public_send(symbol, ...) + else + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/deprecator.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/deprecator.rb new file mode 100644 index 00000000..989500fa --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/deprecator.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# :markup: markdown + +module AbstractController + def self.deprecator # :nodoc: + @deprecator ||= ActiveSupport::Deprecation.new + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/error.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/error.rb new file mode 100644 index 00000000..812241bf --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/error.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# :markup: markdown + +module AbstractController + class Error < StandardError # :nodoc: + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/helpers.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/helpers.rb new file mode 100644 index 00000000..8be59d05 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/helpers.rb @@ -0,0 +1,245 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/dependencies" +require "active_support/core_ext/name_error" + +module AbstractController + module Helpers + extend ActiveSupport::Concern + + included do + class_attribute :_helper_methods, default: Array.new + + # This is here so that it is always higher in the inheritance chain than the + # definition in lib/action_view/rendering.rb + redefine_singleton_method(:_helpers) do + if @_helpers ||= nil + @_helpers + else + superclass._helpers + end + end + + self._helpers = define_helpers_module(self) + end + + def _helpers + self.class._helpers + end + + module Resolution # :nodoc: + def modules_for_helpers(modules_or_helper_prefixes) + modules_or_helper_prefixes.flatten.map! do |module_or_helper_prefix| + case module_or_helper_prefix + when Module + module_or_helper_prefix + when String, Symbol + helper_prefix = module_or_helper_prefix.to_s + helper_prefix = helper_prefix.camelize unless helper_prefix.start_with?(/[A-Z]/) + "#{helper_prefix}Helper".constantize + else + raise ArgumentError, "helper must be a String, Symbol, or Module" + end + end + end + + def all_helpers_from_path(path) + helpers = Array(path).flat_map do |_path| + names = Dir["#{_path}/**/*_helper.rb"].map { |file| file[_path.to_s.size + 1..-"_helper.rb".size - 1] } + names.sort! + end + helpers.uniq! + helpers + end + + def helper_modules_from_paths(paths) + modules_for_helpers(all_helpers_from_path(paths)) + end + end + + extend Resolution + + module ClassMethods + # When a class is inherited, wrap its helper module in a new module. This + # ensures that the parent class's module can be changed independently of the + # child class's. + def inherited(klass) + # Inherited from parent by default + klass._helpers = nil + + klass.class_eval { default_helper_module! } unless klass.anonymous? + super + end + + attr_writer :_helpers + + include Resolution + + ## + # :method: modules_for_helpers + # :call-seq: modules_for_helpers(modules_or_helper_prefixes) + # + # Given an array of values like the ones accepted by `helper`, this method + # returns an array with the corresponding modules, in the same order. + # + # ActionController::Base.modules_for_helpers(["application", "chart", "rubygems"]) + # # => [ApplicationHelper, ChartHelper, RubygemsHelper] + # + #-- + # Implemented by Resolution#modules_for_helpers. + + # :method: # all_helpers_from_path + # :call-seq: all_helpers_from_path(path) + # + # Returns a list of helper names in a given path. + # + # ActionController::Base.all_helpers_from_path 'app/helpers' + # # => ["application", "chart", "rubygems"] + # + #-- + # Implemented by Resolution#all_helpers_from_path. + + # Declare a controller method as a helper. For example, the following + # makes the `current_user` and `logged_in?` controller methods available + # to the view: + # + # class ApplicationController < ActionController::Base + # helper_method :current_user, :logged_in? + # + # private + # def current_user + # @current_user ||= User.find_by(id: session[:user]) + # end + # + # def logged_in? + # current_user != nil + # end + # end + # + # In a view: + # + # <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%> + # + # #### Parameters + # * `method[, method]` - A name or names of a method on the controller to be + # made available on the view. + def helper_method(*methods) + methods.flatten! + self._helper_methods += methods + + location = caller_locations(1, 1).first + file, line = location.path, location.lineno + + methods.each do |method| + # def current_user(...) + # controller.send(:'current_user', ...) + # end + _helpers_for_modification.class_eval <<~ruby_eval.lines.map(&:strip).join(";"), file, line + def #{method}(...) + controller.send(:'#{method}', ...) + end + ruby_eval + end + end + + # Includes the given modules in the template class. + # + # Modules can be specified in different ways. All of the following calls include + # `FooHelper`: + # + # # Module, recommended. + # helper FooHelper + # + # # String/symbol without the "helper" suffix, camel or snake case. + # helper "Foo" + # helper :Foo + # helper "foo" + # helper :foo + # + # The last two assume that `"foo".camelize` returns "Foo". + # + # When strings or symbols are passed, the method finds the actual module object + # using String#constantize. Therefore, if the module has not been yet loaded, it + # has to be autoloadable, which is normally the case. + # + # Namespaces are supported. The following calls include `Foo::BarHelper`: + # + # # Module, recommended. + # helper Foo::BarHelper + # + # # String/symbol without the "helper" suffix, camel or snake case. + # helper "Foo::Bar" + # helper :"Foo::Bar" + # helper "foo/bar" + # helper :"foo/bar" + # + # The last two assume that `"foo/bar".camelize` returns "Foo::Bar". + # + # The method accepts a block too. If present, the block is evaluated in the + # context of the controller helper module. This simple call makes the `wadus` + # method available in templates of the enclosing controller: + # + # helper do + # def wadus + # "wadus" + # end + # end + # + # Furthermore, all the above styles can be mixed together: + # + # helper FooHelper, "woo", "bar/baz" do + # def wadus + # "wadus" + # end + # end + # + def helper(*args, &block) + modules_for_helpers(args).each do |mod| + next if _helpers.include?(mod) + _helpers_for_modification.include(mod) + end + + _helpers_for_modification.module_eval(&block) if block_given? + end + + # Clears up all existing helpers in this class, only keeping the helper with the + # same name as this class. + def clear_helpers + inherited_helper_methods = _helper_methods + self._helpers = Module.new + self._helper_methods = Array.new + + inherited_helper_methods.each { |meth| helper_method meth } + default_helper_module! unless anonymous? + end + + def _helpers_for_modification + unless @_helpers + self._helpers = define_helpers_module(self, superclass._helpers) + end + _helpers + end + + private + def define_helpers_module(klass, helpers = nil) + # In some tests inherited is called explicitly. In that case, just return the + # module from the first time it was defined + return klass.const_get(:HelperMethods) if klass.const_defined?(:HelperMethods, false) + + mod = Module.new + klass.const_set(:HelperMethods, mod) + mod.include(helpers) if helpers + mod + end + + def default_helper_module! + helper_prefix = name.delete_suffix("Controller") + helper(helper_prefix) + rescue NameError => e + raise unless e.missing_name?("#{helper_prefix}Helper") + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/logger.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/logger.rb new file mode 100644 index 00000000..ba3e06ff --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/logger.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/benchmarkable" + +module AbstractController + module Logger # :nodoc: + extend ActiveSupport::Concern + + included do + config_accessor :logger + include ActiveSupport::Benchmarkable + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/railties/routes_helpers.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/railties/routes_helpers.rb new file mode 100644 index 00000000..3e909512 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/railties/routes_helpers.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/core_ext/module/introspection" + +module AbstractController + module Railties + module RoutesHelpers + def self.with(routes, include_path_helpers = true) + Module.new do + define_method(:inherited) do |klass| + super(klass) + + if namespace = klass.module_parents.detect { |m| m.respond_to?(:railtie_routes_url_helpers) } + klass.include(namespace.railtie_routes_url_helpers(include_path_helpers)) + else + klass.include(routes.url_helpers(include_path_helpers)) + end + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/rendering.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/rendering.rb new file mode 100644 index 00000000..346bb4e2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/rendering.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "abstract_controller/error" +require "action_view" +require "action_view/view_paths" + +module AbstractController + class DoubleRenderError < Error + DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...); return\"." + + def initialize(message = nil) + super(message || DEFAULT_MESSAGE) + end + end + + module Rendering + extend ActiveSupport::Concern + include ActionView::ViewPaths + + # Normalizes arguments and options, and then delegates to render_to_body and + # sticks the result in `self.response_body`. + # + # Supported options depend on the underlying `render_to_body` implementation. + def render(*args, &block) + options = _normalize_render(*args, &block) + rendered_body = render_to_body(options) + if options[:html] + _set_html_content_type + else + _set_rendered_content_type rendered_format + end + _set_vary_header + self.response_body = rendered_body + end + + # Similar to #render, but only returns the rendered template as a string, + # instead of setting `self.response_body`. + # + # If a component extends the semantics of `response_body` (as ActionController + # extends it to be anything that responds to the method each), this method needs + # to be overridden in order to still return a string. + def render_to_string(*args, &block) + options = _normalize_render(*args, &block) + render_to_body(options) + end + + # Performs the actual template rendering. + def render_to_body(options = {}) + end + + # Returns `Content-Type` of rendered content. + def rendered_format + Mime[:text] + end + + DEFAULT_PROTECTED_INSTANCE_VARIABLES = %i(@_action_name @_response_body @_formats @_prefixes) + + # This method should return a hash with assigns. You can overwrite this + # configuration per controller. + def view_assigns + variables = instance_variables - _protected_ivars + + variables.each_with_object({}) do |name, hash| + hash[name.slice(1, name.length)] = instance_variable_get(name) + end + end + + private + # Normalize args by converting `render "foo"` to `render action: "foo"` and + # `render "foo/bar"` to `render file: "foo/bar"`. + def _normalize_args(action = nil, options = {}) # :doc: + if action.respond_to?(:permitted?) + if action.permitted? + action + else + raise ArgumentError, "render parameters are not permitted" + end + elsif action.is_a?(Hash) + action + else + options + end + end + + # Normalize options. + def _normalize_options(options) # :doc: + options + end + + # Process extra options. + def _process_options(options) # :doc: + options + end + + # Process the rendered format. + def _process_format(format) # :nodoc: + end + + def _process_variant(options) + end + + def _set_html_content_type # :nodoc: + end + + def _set_vary_header # :nodoc: + end + + def _set_rendered_content_type(format) # :nodoc: + end + + # Normalize args and options. + def _normalize_render(*args, &block) # :nodoc: + options = _normalize_args(*args, &block) + _process_variant(options) + _normalize_options(options) + options + end + + def _protected_ivars + DEFAULT_PROTECTED_INSTANCE_VARIABLES + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/translation.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/translation.rb new file mode 100644 index 00000000..21219047 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/translation.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/html_safe_translation" + +module AbstractController + module Translation + # Delegates to `I18n.translate`. + # + # When the given key starts with a period, it will be scoped by the current + # controller and action. So if you call `translate(".foo")` from + # `PeopleController#index`, it will convert the call to + # `I18n.translate("people.index.foo")`. This makes it less repetitive to + # translate many keys within the same controller / action and gives you a simple + # framework for scoping them consistently. + def translate(key, **options) + if key&.start_with?(".") + path = controller_path.tr("/", ".") + defaults = [:"#{path}#{key}"] + defaults << options[:default] if options[:default] + options[:default] = defaults.flatten + key = "#{path}.#{action_name}#{key}" + end + + if options[:default] && ActiveSupport::HtmlSafeTranslation.html_safe_translation_key?(key) + options[:default] = Array(options[:default]).map do |value| + value.is_a?(String) ? ERB::Util.html_escape(value) : value + end + end + + ActiveSupport::HtmlSafeTranslation.translate(key, **options) + end + alias :t :translate + + # Delegates to `I18n.localize`. + def localize(object, **options) + I18n.localize(object, **options) + end + alias :l :localize + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/url_for.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/url_for.rb new file mode 100644 index 00000000..c2d2da70 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/abstract_controller/url_for.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# :markup: markdown + +module AbstractController + # # URL For + # + # Includes `url_for` into the host class (e.g. an abstract controller or + # mailer). The class has to provide a `RouteSet` by implementing the `_routes` + # methods. Otherwise, an exception will be raised. + # + # Note that this module is completely decoupled from HTTP - the only requirement + # is a valid `_routes` implementation. + module UrlFor + extend ActiveSupport::Concern + include ActionDispatch::Routing::UrlFor + + def _routes + raise "In order to use #url_for, you must include routing helpers explicitly. " \ + "For instance, `include Rails.application.routes.url_helpers`." + end + + module ClassMethods + def _routes + nil + end + + def action_methods + @action_methods ||= if _routes + super - _routes.named_routes.helper_names + else + super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller.rb new file mode 100644 index 00000000..847199aa --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "abstract_controller" +require "action_dispatch" +require "action_controller/deprecator" +require "action_controller/metal/strong_parameters" +require "action_controller/metal/exceptions" + +# # Action Controller +# +# Action Controller is a module of Action Pack. +# +# Action Controller provides a base controller class that can be subclassed to +# implement filters and actions to handle requests. The result of an action is +# typically content generated from views. +module ActionController + extend ActiveSupport::Autoload + + autoload :API + autoload :Base + autoload :Metal + autoload :Renderer + autoload :FormBuilder + + eager_autoload do + autoload :Caching + end + + autoload_under "metal" do + autoload :AllowBrowser + autoload :ConditionalGet + autoload :ContentSecurityPolicy + autoload :Cookies + autoload :DataStreaming + autoload :DefaultHeaders + autoload :EtagWithTemplateDigest + autoload :EtagWithFlash + autoload :PermissionsPolicy + autoload :Flash + autoload :Head + autoload :Helpers + autoload :HttpAuthentication + autoload :BasicImplicitRender + autoload :ImplicitRender + autoload :Instrumentation + autoload :Live + autoload :Logging + autoload :MimeResponds + autoload :ParamsWrapper + autoload :RateLimiting + autoload :Redirecting + autoload :Renderers + autoload :Rendering + autoload :RequestForgeryProtection + autoload :Rescue + autoload :Streaming + autoload :StrongParameters + autoload :ParameterEncoding + autoload :Testing + autoload :UrlFor + end + + autoload_under "api" do + autoload :ApiRendering + end + + autoload_at "action_controller/test_case" do + autoload :TestCase + autoload :TestRequest + autoload :TemplateAssertions + end +end + +# Common Active Support usage in Action Controller +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/module/attr_internal" +require "active_support/core_ext/name_error" +require "active_support/inflector" diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/api.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/api.rb new file mode 100644 index 00000000..b5243046 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/api.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "action_view" +require "action_controller" +require "action_controller/log_subscriber" + +module ActionController + # # Action Controller API + # + # API Controller is a lightweight version of ActionController::Base, created for + # applications that don't require all functionalities that a complete Rails + # controller provides, allowing you to create controllers with just the features + # that you need for API only applications. + # + # An API Controller is different from a normal controller in the sense that by + # default it doesn't include a number of features that are usually required by + # browser access only: layouts and templates rendering, flash, assets, and so + # on. This makes the entire controller stack thinner, suitable for API + # applications. It doesn't mean you won't have such features if you need them: + # they're all available for you to include in your application, they're just not + # part of the default API controller stack. + # + # Normally, `ApplicationController` is the only controller that inherits from + # `ActionController::API`. All other controllers in turn inherit from + # `ApplicationController`. + # + # A sample controller could look like this: + # + # class PostsController < ApplicationController + # def index + # posts = Post.all + # render json: posts + # end + # end + # + # Request, response, and parameters objects all work the exact same way as + # ActionController::Base. + # + # ## Renders + # + # The default API Controller stack includes all renderers, which means you can + # use `render :json` and siblings freely in your controllers. Keep in mind that + # templates are not going to be rendered, so you need to ensure your controller + # is calling either `render` or `redirect_to` in all actions, otherwise it will + # return `204 No Content`. + # + # def show + # post = Post.find(params[:id]) + # render json: post + # end + # + # ## Redirects + # + # Redirects are used to move from one action to another. You can use the + # `redirect_to` method in your controllers in the same way as in + # ActionController::Base. For example: + # + # def create + # redirect_to root_url and return if not_authorized? + # # do stuff here + # end + # + # ## Adding New Behavior + # + # In some scenarios you may want to add back some functionality provided by + # ActionController::Base that is not present by default in + # `ActionController::API`, for instance `MimeResponds`. This module gives you + # the `respond_to` method. Adding it is quite simple, you just need to include + # the module in a specific controller or in `ApplicationController` in case you + # want it available in your entire application: + # + # class ApplicationController < ActionController::API + # include ActionController::MimeResponds + # end + # + # class PostsController < ApplicationController + # def index + # posts = Post.all + # + # respond_to do |format| + # format.json { render json: posts } + # format.xml { render xml: posts } + # end + # end + # end + # + # Make sure to check the modules included in ActionController::Base if you want + # to use any other functionality that is not provided by `ActionController::API` + # out of the box. + class API < Metal + abstract! + + # Shortcut helper that returns all the ActionController::API modules except the + # ones passed as arguments: + # + # class MyAPIBaseController < ActionController::Metal + # ActionController::API.without_modules(:UrlFor).each do |left| + # include left + # end + # end + # + # This gives better control over what you want to exclude and makes it easier to + # create an API controller class, instead of listing the modules required + # manually. + def self.without_modules(*modules) + modules = modules.map do |m| + m.is_a?(Symbol) ? ActionController.const_get(m) : m + end + + MODULES - modules + end + + MODULES = [ + AbstractController::Rendering, + + UrlFor, + Redirecting, + ApiRendering, + Renderers::All, + ConditionalGet, + BasicImplicitRender, + StrongParameters, + RateLimiting, + Caching, + + DataStreaming, + DefaultHeaders, + Logging, + + # Before callbacks should also be executed as early as possible, so also include + # them at the bottom. + AbstractController::Callbacks, + + # Append rescue at the bottom to wrap as much as possible. + Rescue, + + # Add instrumentations hooks at the bottom, to ensure they instrument all the + # methods properly. + Instrumentation, + + # Params wrapper should come before instrumentation so they are properly showed + # in logs + ParamsWrapper + ] + + MODULES.each do |mod| + include mod + end + + ActiveSupport.run_load_hooks(:action_controller_api, self) + ActiveSupport.run_load_hooks(:action_controller, self) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/api/api_rendering.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/api/api_rendering.rb new file mode 100644 index 00000000..dcb27315 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/api/api_rendering.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + module ApiRendering + extend ActiveSupport::Concern + + included do + include Rendering + end + + def render_to_body(options = {}) + _process_options(options) + super + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/base.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/base.rb new file mode 100644 index 00000000..3e14980d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/base.rb @@ -0,0 +1,332 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "action_view" +require "action_controller/log_subscriber" +require "action_controller/metal/params_wrapper" + +module ActionController + # # Action Controller Base + # + # Action Controllers are the core of a web request in Rails. They are made up of + # one or more actions that are executed on request and then either it renders a + # template or redirects to another action. An action is defined as a public + # method on the controller, which will automatically be made accessible to the + # web-server through Rails Routes. + # + # By default, only the ApplicationController in a Rails application inherits + # from `ActionController::Base`. All other controllers inherit from + # ApplicationController. This gives you one class to configure things such as + # request forgery protection and filtering of sensitive request parameters. + # + # A sample controller could look like this: + # + # class PostsController < ApplicationController + # def index + # @posts = Post.all + # end + # + # def create + # @post = Post.create params[:post] + # redirect_to posts_path + # end + # end + # + # Actions, by default, render a template in the `app/views` directory + # corresponding to the name of the controller and action after executing code in + # the action. For example, the `index` action of the PostsController would + # render the template `app/views/posts/index.html.erb` by default after + # populating the `@posts` instance variable. + # + # Unlike index, the create action will not render a template. After performing + # its main purpose (creating a new post), it initiates a redirect instead. This + # redirect works by returning an external `302 Moved` HTTP response that takes + # the user to the index action. + # + # These two methods represent the two basic action archetypes used in Action + # Controllers: Get-and-show and do-and-redirect. Most actions are variations on + # these themes. + # + # ## Requests + # + # For every request, the router determines the value of the `controller` and + # `action` keys. These determine which controller and action are called. The + # remaining request parameters, the session (if one is available), and the full + # request with all the HTTP headers are made available to the action through + # accessor methods. Then the action is performed. + # + # The full request object is available via the request accessor and is primarily + # used to query for HTTP headers: + # + # def server_ip + # location = request.env["REMOTE_ADDR"] + # render plain: "This server hosted at #{location}" + # end + # + # ## Parameters + # + # All request parameters, whether they come from a query string in the URL or + # form data submitted through a POST request are available through the `params` + # method which returns a hash. For example, an action that was performed through + # `/posts?category=All&limit=5` will include `{ "category" => "All", "limit" => + # "5" }` in `params`. + # + # It's also possible to construct multi-dimensional parameter hashes by + # specifying keys using brackets, such as: + # + # + # + # + # A request coming from a form holding these inputs will include `{ "post" => { + # "name" => "david", "address" => "hyacintvej" } }`. If the address input had + # been named `post[address][street]`, the `params` would have included `{ "post" + # => { "address" => { "street" => "hyacintvej" } } }`. There's no limit to the + # depth of the nesting. + # + # ## Sessions + # + # Sessions allow you to store objects in between requests. This is useful for + # objects that are not yet ready to be persisted, such as a Signup object + # constructed in a multi-paged process, or objects that don't change much and + # are needed all the time, such as a User object for a system that requires + # login. The session should not be used, however, as a cache for objects where + # it's likely they could be changed unknowingly. It's usually too much work to + # keep it all synchronized -- something databases already excel at. + # + # You can place objects in the session by using the `session` method, which + # accesses a hash: + # + # session[:person] = Person.authenticate(user_name, password) + # + # You can retrieve it again through the same hash: + # + # "Hello #{session[:person]}" + # + # For removing objects from the session, you can either assign a single key to + # `nil`: + # + # # removes :person from session + # session[:person] = nil + # + # or you can remove the entire session with `reset_session`. + # + # By default, sessions are stored in an encrypted browser cookie (see + # ActionDispatch::Session::CookieStore). Thus the user will not be able to read + # or edit the session data. However, the user can keep a copy of the cookie even + # after it has expired, so you should avoid storing sensitive information in + # cookie-based sessions. + # + # ## Responses + # + # Each action results in a response, which holds the headers and document to be + # sent to the user's browser. The actual response object is generated + # automatically through the use of renders and redirects and requires no user + # intervention. + # + # ## Renders + # + # Action Controller sends content to the user by using one of five rendering + # methods. The most versatile and common is the rendering of a template. + # Included in the Action Pack is the Action View, which enables rendering of ERB + # templates. It's automatically configured. The controller passes objects to the + # view by assigning instance variables: + # + # def show + # @post = Post.find(params[:id]) + # end + # + # Which are then automatically available to the view: + # + # Title: <%= @post.title %> + # + # You don't have to rely on the automated rendering. For example, actions that + # could result in the rendering of different templates will use the manual + # rendering methods: + # + # def search + # @results = Search.find(params[:query]) + # case @results.count + # when 0 then render action: "no_results" + # when 1 then render action: "show" + # when 2..10 then render action: "show_many" + # end + # end + # + # Read more about writing ERB and Builder templates in ActionView::Base. + # + # ## Redirects + # + # Redirects are used to move from one action to another. For example, after a + # `create` action, which stores a blog entry to the database, we might like to + # show the user the new entry. Because we're following good DRY principles + # (Don't Repeat Yourself), we're going to reuse (and redirect to) a `show` + # action that we'll assume has already been created. The code might look like + # this: + # + # def create + # @entry = Entry.new(params[:entry]) + # if @entry.save + # # The entry was saved correctly, redirect to show + # redirect_to action: 'show', id: @entry.id + # else + # # things didn't go so well, do something else + # end + # end + # + # In this case, after saving our new entry to the database, the user is + # redirected to the `show` method, which is then executed. Note that this is an + # external HTTP-level redirection which will cause the browser to make a second + # request (a GET to the show action), and not some internal re-routing which + # calls both "create" and then "show" within one request. + # + # Learn more about `redirect_to` and what options you have in + # ActionController::Redirecting. + # + # ## Calling multiple redirects or renders + # + # An action may perform only a single render or a single redirect. Attempting to + # do either again will result in a DoubleRenderError: + # + # def do_something + # redirect_to action: "elsewhere" + # render action: "overthere" # raises DoubleRenderError + # end + # + # If you need to redirect on the condition of something, then be sure to add + # "return" to halt execution. + # + # def do_something + # if monkeys.nil? + # redirect_to(action: "elsewhere") + # return + # end + # render action: "overthere" # won't be called if monkeys is nil + # end + # + class Base < Metal + abstract! + + # Shortcut helper that returns all the modules included in + # ActionController::Base except the ones passed as arguments: + # + # class MyBaseController < ActionController::Metal + # ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left| + # include left + # end + # end + # + # This gives better control over what you want to exclude and makes it easier to + # create a bare controller class, instead of listing the modules required + # manually. + def self.without_modules(*modules) + modules = modules.map do |m| + m.is_a?(Symbol) ? ActionController.const_get(m) : m + end + + MODULES - modules + end + + MODULES = [ + AbstractController::Rendering, + AbstractController::Translation, + AbstractController::AssetPaths, + Helpers, + UrlFor, + Redirecting, + ActionView::Layouts, + Rendering, + Renderers::All, + ConditionalGet, + EtagWithTemplateDigest, + EtagWithFlash, + Caching, + MimeResponds, + ImplicitRender, + StrongParameters, + ParameterEncoding, + Cookies, + Flash, + FormBuilder, + RequestForgeryProtection, + ContentSecurityPolicy, + PermissionsPolicy, + RateLimiting, + AllowBrowser, + Streaming, + DataStreaming, + HttpAuthentication::Basic::ControllerMethods, + HttpAuthentication::Digest::ControllerMethods, + HttpAuthentication::Token::ControllerMethods, + DefaultHeaders, + Logging, + AbstractController::Callbacks, + Rescue, + Instrumentation, + ParamsWrapper + ] + + # Note: Documenting these severely degrades the performance of rdoc + # :stopdoc: + include AbstractController::Rendering + include AbstractController::Translation + include AbstractController::AssetPaths + include Helpers + include UrlFor + include Redirecting + include ActionView::Layouts + include Rendering + include Renderers::All + include ConditionalGet + include EtagWithTemplateDigest + include EtagWithFlash + include Caching + include MimeResponds + include ImplicitRender + include StrongParameters + include ParameterEncoding + include Cookies + include Flash + include FormBuilder + include RequestForgeryProtection + include ContentSecurityPolicy + include PermissionsPolicy + include RateLimiting + include AllowBrowser + include Streaming + include DataStreaming + include HttpAuthentication::Basic::ControllerMethods + include HttpAuthentication::Digest::ControllerMethods + include HttpAuthentication::Token::ControllerMethods + include DefaultHeaders + include Logging + # Before callbacks should also be executed as early as possible, so also include + # them at the bottom. + include AbstractController::Callbacks + # Append rescue at the bottom to wrap as much as possible. + include Rescue + # Add instrumentations hooks at the bottom, to ensure they instrument all the + # methods properly. + include Instrumentation + # Params wrapper should come before instrumentation so they are properly showed + # in logs + include ParamsWrapper + # :startdoc: + setup_renderer! + + # Define some internal variables that should not be propagated to the view. + PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + %i( + @_params @_response @_request @_config @_url_options @_action_has_layout @_view_context_class + @_view_renderer @_lookup_context @_routes @_view_runtime @_db_runtime @_helper_proxy + @_marked_for_same_origin_verification @_rendered_format + ) + + def _protected_ivars + PROTECTED_IVARS + end + private :_protected_ivars + + ActiveSupport.run_load_hooks(:action_controller_base, self) + ActiveSupport.run_load_hooks(:action_controller, self) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/caching.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/caching.rb new file mode 100644 index 00000000..ece325a2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/caching.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + # # Action Controller Caching + # + # Caching is a cheap way of speeding up slow applications by keeping the result + # of calculations, renderings, and database calls around for subsequent + # requests. + # + # You can read more about each approach by clicking the modules below. + # + # Note: To turn off all caching provided by Action Controller, set + # config.action_controller.perform_caching = false + # + # ## Caching stores + # + # All the caching stores from ActiveSupport::Cache are available to be used as + # backends for Action Controller caching. + # + # Configuration examples (FileStore is the default): + # + # config.action_controller.cache_store = :memory_store + # config.action_controller.cache_store = :file_store, '/path/to/cache/directory' + # config.action_controller.cache_store = :mem_cache_store, 'localhost' + # config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new('localhost:11211') + # config.action_controller.cache_store = MyOwnStore.new('parameter') + module Caching + extend ActiveSupport::Concern + + included do + include AbstractController::Caching + end + + private + def instrument_payload(key) + { + controller: controller_name, + action: action_name, + key: key + } + end + + def instrument_name + "action_controller" + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/deprecator.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/deprecator.rb new file mode 100644 index 00000000..3c0ea2a2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/deprecator.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + def self.deprecator # :nodoc: + AbstractController.deprecator + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/form_builder.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/form_builder.rb new file mode 100644 index 00000000..df846132 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/form_builder.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + # # Action Controller Form Builder + # + # Override the default form builder for all views rendered by this controller + # and any of its descendants. Accepts a subclass of + # ActionView::Helpers::FormBuilder. + # + # For example, given a form builder: + # + # class AdminFormBuilder < ActionView::Helpers::FormBuilder + # def special_field(name) + # end + # end + # + # The controller specifies a form builder as its default: + # + # class AdminAreaController < ApplicationController + # default_form_builder AdminFormBuilder + # end + # + # Then in the view any form using `form_with` or `form_for` will be an + # instance of the specified form builder: + # + # <%= form_with(model: @instance) do |builder| %> + # <%= builder.special_field(:name) %> + # <% end %> + module FormBuilder + extend ActiveSupport::Concern + + included do + class_attribute :_default_form_builder, instance_accessor: false + end + + module ClassMethods + # Set the form builder to be used as the default for all forms in the views + # rendered by this controller and its subclasses. + # + # #### Parameters + # * `builder` - Default form builder, an instance of + # ActionView::Helpers::FormBuilder + def default_form_builder(builder) + self._default_form_builder = builder + end + end + + # Default form builder for the controller + def default_form_builder + self.class._default_form_builder + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/log_subscriber.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/log_subscriber.rb new file mode 100644 index 00000000..02f8493c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/log_subscriber.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + class LogSubscriber < ActiveSupport::LogSubscriber + INTERNAL_PARAMS = %w(controller action format _method only_path) + + def start_processing(event) + return unless logger.info? + + payload = event.payload + params = {} + payload[:params].each_pair do |k, v| + params[k] = v unless INTERNAL_PARAMS.include?(k) + end + format = payload[:format] + format = format.to_s.upcase if format.is_a?(Symbol) + format = "*/*" if format.nil? + + info "Processing by #{payload[:controller]}##{payload[:action]} as #{format}" + info " Parameters: #{params.inspect}" unless params.empty? + end + subscribe_log_level :start_processing, :info + + def process_action(event) + info do + payload = event.payload + additions = ActionController::Base.log_process_action(payload) + status = payload[:status] + + if status.nil? && (exception_class_name = payload[:exception]&.first) + status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name) + end + + additions << "GC: #{event.gc_time.round(1)}ms" + + message = +"Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms" \ + " (#{additions.join(" | ")})" + message << "\n\n" if defined?(Rails.env) && Rails.env.development? + + message + end + end + subscribe_log_level :process_action, :info + + def halted_callback(event) + info { "Filter chain halted as #{event.payload[:filter].inspect} rendered or redirected" } + end + subscribe_log_level :halted_callback, :info + + def send_file(event) + info { "Sent file #{event.payload[:path]} (#{event.duration.round(1)}ms)" } + end + subscribe_log_level :send_file, :info + + def redirect_to(event) + info { "Redirected to #{event.payload[:location]}" } + end + subscribe_log_level :redirect_to, :info + + def send_data(event) + info { "Sent data #{event.payload[:filename]} (#{event.duration.round(1)}ms)" } + end + subscribe_log_level :send_data, :info + + def unpermitted_parameters(event) + debug do + unpermitted_keys = event.payload[:keys] + display_unpermitted_keys = unpermitted_keys.map { |e| ":#{e}" }.join(", ") + context = event.payload[:context].map { |k, v| "#{k}: #{v}" }.join(", ") + color("Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{display_unpermitted_keys}. Context: { #{context} }", RED) + end + end + subscribe_log_level :unpermitted_parameters, :debug + + %w(write_fragment read_fragment exist_fragment? expire_fragment).each do |method| + class_eval <<-METHOD, __FILE__, __LINE__ + 1 + # frozen_string_literal: true + def #{method}(event) + return unless ActionController::Base.enable_fragment_cache_logging + key = ActiveSupport::Cache.expand_cache_key(event.payload[:key] || event.payload[:path]) + human_name = #{method.to_s.humanize.inspect} + info("\#{human_name} \#{key} (\#{event.duration.round(1)}ms)") + end + subscribe_log_level :#{method}, :info + METHOD + end + + def logger + ActionController::Base.logger + end + end +end + +ActionController::LogSubscriber.attach_to :action_controller diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal.rb new file mode 100644 index 00000000..4c58735f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal.rb @@ -0,0 +1,339 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/core_ext/array/extract_options" +require "action_dispatch/middleware/stack" + +module ActionController + # # Action Controller MiddlewareStack + # + # Extend ActionDispatch middleware stack to make it aware of options allowing + # the following syntax in controllers: + # + # class PostsController < ApplicationController + # use AuthenticationMiddleware, except: [:index, :show] + # end + # + class MiddlewareStack < ActionDispatch::MiddlewareStack # :nodoc: + class Middleware < ActionDispatch::MiddlewareStack::Middleware # :nodoc: + def initialize(klass, args, actions, strategy, block) + @actions = actions + @strategy = strategy + super(klass, args, block) + end + + def valid?(action) + @strategy.call @actions, action + end + end + + def build(action, app = nil, &block) + action = action.to_s + + middlewares.reverse.inject(app || block) do |a, middleware| + middleware.valid?(action) ? middleware.build(a) : a + end + end + + private + INCLUDE = ->(list, action) { list.include? action } + EXCLUDE = ->(list, action) { !list.include? action } + NULL = ->(list, action) { true } + + def build_middleware(klass, args, block) + options = args.extract_options! + only = Array(options.delete(:only)).map(&:to_s) + except = Array(options.delete(:except)).map(&:to_s) + args << options unless options.empty? + + strategy = NULL + list = nil + + if only.any? + strategy = INCLUDE + list = only + elsif except.any? + strategy = EXCLUDE + list = except + end + + Middleware.new(klass, args, list, strategy, block) + end + end + + # # Action Controller Metal + # + # `ActionController::Metal` is the simplest possible controller, providing a + # valid Rack interface without the additional niceties provided by + # ActionController::Base. + # + # A sample metal controller might look like this: + # + # class HelloController < ActionController::Metal + # def index + # self.response_body = "Hello World!" + # end + # end + # + # And then to route requests to your metal controller, you would add something + # like this to `config/routes.rb`: + # + # get 'hello', to: HelloController.action(:index) + # + # The ::action method returns a valid Rack application for the Rails router to + # dispatch to. + # + # ## Rendering Helpers + # + # By default, `ActionController::Metal` provides no utilities for rendering + # views, partials, or other responses aside from some low-level setters such + # as #response_body=, #content_type=, and #status=. To add the render helpers + # you're used to having in a normal controller, you can do the following: + # + # class HelloController < ActionController::Metal + # include AbstractController::Rendering + # include ActionView::Layouts + # append_view_path "#{Rails.root}/app/views" + # + # def index + # render "hello/index" + # end + # end + # + # ## Redirection Helpers + # + # To add redirection helpers to your metal controller, do the following: + # + # class HelloController < ActionController::Metal + # include ActionController::Redirecting + # include Rails.application.routes.url_helpers + # + # def index + # redirect_to root_url + # end + # end + # + # ## Other Helpers + # + # You can refer to the modules included in ActionController::Base to see other + # features you can bring into your metal controller. + class Metal < AbstractController::Base + abstract! + + # Returns the last part of the controller's name, underscored, without the + # ending `Controller`. For instance, `PostsController` returns `posts`. + # Namespaces are left out, so `Admin::PostsController` returns `posts` as well. + # + # #### Returns + # * `string` + def self.controller_name + @controller_name ||= (name.demodulize.delete_suffix("Controller").underscore unless anonymous?) + end + + def self.make_response!(request) + ActionDispatch::Response.new.tap do |res| + res.request = request + end + end + + def self.action_encoding_template(action) # :nodoc: + false + end + + class << self + private + def inherited(subclass) + super + subclass.middleware_stack = middleware_stack.dup + subclass.class_eval do + @controller_name = nil + end + end + end + + # Delegates to the class's ::controller_name. + def controller_name + self.class.controller_name + end + + ## + # :attr_reader: request + # + # The ActionDispatch::Request instance for the current request. + attr_internal :request + + ## + # :attr_reader: response + # + # The ActionDispatch::Response instance for the current response. + attr_internal_reader :response + + ## + # The ActionDispatch::Request::Session instance for the current request. + # See further details in the + # [Active Controller Session guide](https://guides.rubyonrails.org/action_controller_overview.html#session). + delegate :session, to: "@_request" + + ## + # Delegates to ActionDispatch::Response#headers. + delegate :headers, to: "@_response" + + ## + # Delegates to ActionDispatch::Response#status= + delegate :status=, to: "@_response" + + ## + # Delegates to ActionDispatch::Response#location= + delegate :location=, to: "@_response" + + ## + # Delegates to ActionDispatch::Response#content_type= + delegate :content_type=, to: "@_response" + + ## + # Delegates to ActionDispatch::Response#status + delegate :status, to: "@_response" + + ## + # Delegates to ActionDispatch::Response#location + delegate :location, to: "@_response" + + ## + # Delegates to ActionDispatch::Response#content_type + delegate :content_type, to: "@_response" + + ## + # Delegates to ActionDispatch::Response#media_type + delegate :media_type, to: "@_response" + + def initialize + @_request = nil + @_response = nil + @_response_body = nil + @_routes = nil + @_params = nil + super + end + + def params + @_params ||= request.parameters + end + + def params=(val) + @_params = val + end + + alias :response_code :status # :nodoc: + + # Basic `url_for` that can be overridden for more robust functionality. + def url_for(string) + string + end + + def response_body=(body) + if body + body = [body] if body.is_a?(String) + response.body = body + super + else + response.reset_body! + end + end + + # Tests if render or redirect has already happened. + def performed? + response_body || response.committed? + end + + def dispatch(name, request, response) # :nodoc: + set_request!(request) + set_response!(response) + process(name) + request.commit_flash + to_a + end + + def set_response!(response) # :nodoc: + if @_response + _, _, body = @_response + body.close if body.respond_to?(:close) + end + + @_response = response + end + + # Assign the response and mark it as committed. No further processing will + # occur. + def response=(response) + set_response!(response) + + # Force `performed?` to return true: + @_response_body = true + end + + def set_request!(request) # :nodoc: + @_request = request + @_request.controller_instance = self + end + + def to_a # :nodoc: + response.to_a + end + + def reset_session + @_request.reset_session + end + + class_attribute :middleware_stack, default: ActionController::MiddlewareStack.new + + class << self + # Pushes the given Rack middleware and its arguments to the bottom of the + # middleware stack. + def use(...) + middleware_stack.use(...) + end + end + + # The middleware stack used by this controller. + # + # By default uses a variation of ActionDispatch::MiddlewareStack which allows + # for the following syntax: + # + # class PostsController < ApplicationController + # use AuthenticationMiddleware, except: [:index, :show] + # end + # + # Read more about [Rails middleware stack] + # (https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack) + # in the guides. + def self.middleware + middleware_stack + end + + # Returns a Rack endpoint for the given action name. + def self.action(name) + app = lambda { |env| + req = ActionDispatch::Request.new(env) + res = make_response! req + new.dispatch(name, req, res) + } + + if middleware_stack.any? + middleware_stack.build(name, app) + else + app + end + end + + # Direct dispatch to the controller. Instantiates the controller, then executes + # the action named `name`. + def self.dispatch(name, req, res) + if middleware_stack.any? + middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env + else + new.dispatch(name, req, res) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/allow_browser.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/allow_browser.rb new file mode 100644 index 00000000..33afed24 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/allow_browser.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController # :nodoc: + module AllowBrowser + extend ActiveSupport::Concern + + module ClassMethods + # Specify the browser versions that will be allowed to access all actions (or + # some, as limited by `only:` or `except:`). Only browsers matched in the hash + # or named set passed to `versions:` will be blocked if they're below the + # versions specified. This means that all other browsers, as well as agents that + # aren't reporting a user-agent header, will be allowed access. + # + # A browser that's blocked will by default be served the file in + # public/406-unsupported-browser.html with a HTTP status code of "406 Not + # Acceptable". + # + # In addition to specifically named browser versions, you can also pass + # `:modern` as the set to restrict support to browsers natively supporting webp + # images, web push, badges, import maps, CSS nesting, and CSS :has. This + # includes Safari 17.2+, Chrome 120+, Firefox 121+, Opera 106+. + # + # You can use https://caniuse.com to check for browser versions supporting the + # features you use. + # + # You can use `ActiveSupport::Notifications` to subscribe to events of browsers + # being blocked using the `browser_block.action_controller` event name. + # + # Examples: + # + # class ApplicationController < ActionController::Base + # # Allow only browsers natively supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has + # allow_browser versions: :modern + # end + # + # class ApplicationController < ActionController::Base + # # Allow only browsers natively supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has + # allow_browser versions: :modern, block: :handle_outdated_browser + # + # private + # def handle_outdated_browser + # render file: Rails.root.join("public/custom-error.html"), status: :not_acceptable + # end + # end + # + # class ApplicationController < ActionController::Base + # # All versions of Chrome and Opera will be allowed, but no versions of "internet explorer" (ie). Safari needs to be 16.4+ and Firefox 121+. + # allow_browser versions: { safari: 16.4, firefox: 121, ie: false } + # end + # + # class MessagesController < ApplicationController + # # In addition to the browsers blocked by ApplicationController, also block Opera below 104 and Chrome below 119 for the show action. + # allow_browser versions: { opera: 104, chrome: 119 }, only: :show + # end + def allow_browser(versions:, block: -> { render file: Rails.root.join("public/406-unsupported-browser.html"), layout: false, status: :not_acceptable }, **options) + before_action -> { allow_browser(versions: versions, block: block) }, **options + end + end + + private + def allow_browser(versions:, block:) + require "useragent" + + if BrowserBlocker.new(request, versions: versions).blocked? + ActiveSupport::Notifications.instrument("browser_block.action_controller", request: request, versions: versions) do + block.is_a?(Symbol) ? send(block) : instance_exec(&block) + end + end + end + + class BrowserBlocker # :nodoc: + SETS = { + modern: { safari: 17.2, chrome: 120, firefox: 121, opera: 106, ie: false } + } + + attr_reader :request, :versions + + def initialize(request, versions:) + @request, @versions = request, versions + end + + def blocked? + user_agent_version_reported? && unsupported_browser? + end + + private + def parsed_user_agent + @parsed_user_agent ||= UserAgent.parse(request.user_agent) + end + + def user_agent_version_reported? + request.user_agent.present? && parsed_user_agent.version.to_s.present? + end + + def unsupported_browser? + version_guarded_browser? && version_below_minimum_required? && !bot? + end + + def version_guarded_browser? + minimum_browser_version_for_browser != nil + end + + def bot? + parsed_user_agent.bot? + end + + def version_below_minimum_required? + if minimum_browser_version_for_browser + parsed_user_agent.version < UserAgent::Version.new(minimum_browser_version_for_browser.to_s) + else + true + end + end + + def minimum_browser_version_for_browser + expanded_versions[normalized_browser_name] + end + + def expanded_versions + @expanded_versions ||= (SETS[versions] || versions).with_indifferent_access + end + + def normalized_browser_name + case name = parsed_user_agent.browser.downcase + when "internet explorer" then "ie" + else name + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/basic_implicit_render.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/basic_implicit_render.rb new file mode 100644 index 00000000..08230e93 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/basic_implicit_render.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + module BasicImplicitRender # :nodoc: + def send_action(method, *args) + ret = super + default_render unless performed? + ret + end + + def default_render + head :no_content + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/conditional_get.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/conditional_get.rb new file mode 100644 index 00000000..890eea84 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/conditional_get.rb @@ -0,0 +1,340 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/core_ext/object/try" +require "active_support/core_ext/integer/time" + +module ActionController + module ConditionalGet + extend ActiveSupport::Concern + + include Head + + included do + class_attribute :etaggers, default: [] + end + + module ClassMethods + # Allows you to consider additional controller-wide information when generating + # an ETag. For example, if you serve pages tailored depending on who's logged in + # at the moment, you may want to add the current user id to be part of the ETag + # to prevent unauthorized displaying of cached pages. + # + # class InvoicesController < ApplicationController + # etag { current_user&.id } + # + # def show + # # Etag will differ even for the same invoice when it's viewed by a different current_user + # @invoice = Invoice.find(params[:id]) + # fresh_when etag: @invoice + # end + # end + def etag(&etagger) + self.etaggers += [etagger] + end + end + + # Sets the `etag`, `last_modified`, or both on the response, and renders a `304 + # Not Modified` response if the request is already fresh. + # + # #### Options + # + # `:etag` + # : Sets a "weak" ETag validator on the response. See the `:weak_etag` option. + # + # `:weak_etag` + # : Sets a "weak" ETag validator on the response. Requests that specify an + # `If-None-Match` header may receive a `304 Not Modified` response if the + # ETag matches exactly. + # + # : A weak ETag indicates semantic equivalence, not byte-for-byte equality, so + # they're good for caching HTML pages in browser caches. They can't be used + # for responses that must be byte-identical, like serving `Range` requests + # within a PDF file. + # + # `:strong_etag` + # : Sets a "strong" ETag validator on the response. Requests that specify an + # `If-None-Match` header may receive a `304 Not Modified` response if the + # ETag matches exactly. + # + # : A strong ETag implies exact equality -- the response must match byte for + # byte. This is necessary for serving `Range` requests within a large video + # or PDF file, for example, or for compatibility with some CDNs that don't + # support weak ETags. + # + # `:last_modified` + # : Sets a "weak" last-update validator on the response. Subsequent requests + # that specify an `If-Modified-Since` header may receive a `304 Not + # Modified` response if `last_modified` <= `If-Modified-Since`. + # + # `:public` + # : By default the `Cache-Control` header is private. Set this option to + # `true` if you want your application to be cacheable by other devices, such + # as proxy caches. + # + # `:cache_control` + # : When given, will overwrite an existing `Cache-Control` header. For a list + # of `Cache-Control` directives, see the [article on + # MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control). + # + # `:template` + # : By default, the template digest for the current controller/action is + # included in ETags. If the action renders a different template, you can + # include its digest instead. If the action doesn't render a template at + # all, you can pass `template: false` to skip any attempt to check for a + # template digest. + # + # + # #### Examples + # + # def show + # @article = Article.find(params[:id]) + # fresh_when(etag: @article, last_modified: @article.updated_at, public: true) + # end + # + # This will send a `304 Not Modified` response if the request specifies a + # matching ETag and `If-Modified-Since` header. Otherwise, it will render the + # `show` template. + # + # You can also just pass a record: + # + # def show + # @article = Article.find(params[:id]) + # fresh_when(@article) + # end + # + # `etag` will be set to the record, and `last_modified` will be set to the + # record's `updated_at`. + # + # You can also pass an object that responds to `maximum`, such as a collection + # of records: + # + # def index + # @articles = Article.all + # fresh_when(@articles) + # end + # + # In this case, `etag` will be set to the collection, and `last_modified` will + # be set to `maximum(:updated_at)` (the timestamp of the most recently updated + # record). + # + # When passing a record or a collection, you can still specify other options, + # such as `:public` and `:cache_control`: + # + # def show + # @article = Article.find(params[:id]) + # fresh_when(@article, public: true, cache_control: { no_cache: true }) + # end + # + # The above will set `Cache-Control: public, no-cache` in the response. + # + # When rendering a different template than the controller/action's default + # template, you can indicate which digest to include in the ETag: + # + # before_action { fresh_when @article, template: "widgets/show" } + # + def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, cache_control: {}, template: nil) + response.cache_control.delete(:no_store) + weak_etag ||= etag || object unless strong_etag + last_modified ||= object.try(:updated_at) || object.try(:maximum, :updated_at) + + if strong_etag + response.strong_etag = combine_etags strong_etag, + last_modified: last_modified, public: public, template: template + elsif weak_etag || template + response.weak_etag = combine_etags weak_etag, + last_modified: last_modified, public: public, template: template + end + + response.last_modified = last_modified if last_modified + response.cache_control[:public] = true if public + response.cache_control.merge!(cache_control) + + head :not_modified if request.fresh?(response) + end + + # Sets the `etag` and/or `last_modified` on the response and checks them against + # the request. If the request doesn't match the provided options, it is + # considered stale, and the response should be rendered from scratch. Otherwise, + # it is fresh, and a `304 Not Modified` is sent. + # + # #### Options + # + # See #fresh_when for supported options. + # + # #### Examples + # + # def show + # @article = Article.find(params[:id]) + # + # if stale?(etag: @article, last_modified: @article.updated_at) + # @statistics = @article.really_expensive_call + # respond_to do |format| + # # all the supported formats + # end + # end + # end + # + # You can also just pass a record: + # + # def show + # @article = Article.find(params[:id]) + # + # if stale?(@article) + # @statistics = @article.really_expensive_call + # respond_to do |format| + # # all the supported formats + # end + # end + # end + # + # `etag` will be set to the record, and `last_modified` will be set to the + # record's `updated_at`. + # + # You can also pass an object that responds to `maximum`, such as a collection + # of records: + # + # def index + # @articles = Article.all + # + # if stale?(@articles) + # @statistics = @articles.really_expensive_call + # respond_to do |format| + # # all the supported formats + # end + # end + # end + # + # In this case, `etag` will be set to the collection, and `last_modified` will + # be set to `maximum(:updated_at)` (the timestamp of the most recently updated + # record). + # + # When passing a record or a collection, you can still specify other options, + # such as `:public` and `:cache_control`: + # + # def show + # @article = Article.find(params[:id]) + # + # if stale?(@article, public: true, cache_control: { no_cache: true }) + # @statistics = @articles.really_expensive_call + # respond_to do |format| + # # all the supported formats + # end + # end + # end + # + # The above will set `Cache-Control: public, no-cache` in the response. + # + # When rendering a different template than the controller/action's default + # template, you can indicate which digest to include in the ETag: + # + # def show + # super if stale?(@article, template: "widgets/show") + # end + # + def stale?(object = nil, **freshness_kwargs) + fresh_when(object, **freshness_kwargs) + !request.fresh?(response) + end + + # Sets the `Cache-Control` header, overwriting existing directives. This method + # will also ensure an HTTP `Date` header for client compatibility. + # + # Defaults to issuing the `private` directive, so that intermediate caches must + # not cache the response. + # + # #### Options + # + # `:public` + # : If true, replaces the default `private` directive with the `public` + # directive. + # + # `:must_revalidate` + # : If true, adds the `must-revalidate` directive. + # + # `:stale_while_revalidate` + # : Sets the value of the `stale-while-revalidate` directive. + # + # `:stale_if_error` + # : Sets the value of the `stale-if-error` directive. + # + # `:immutable` + # : If true, adds the `immutable` directive. + # + # + # Any additional key-value pairs are concatenated as directives. For a list of + # supported `Cache-Control` directives, see the [article on + # MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control). + # + # #### Examples + # + # expires_in 10.minutes + # # => Cache-Control: max-age=600, private + # + # expires_in 10.minutes, public: true + # # => Cache-Control: max-age=600, public + # + # expires_in 10.minutes, public: true, must_revalidate: true + # # => Cache-Control: max-age=600, public, must-revalidate + # + # expires_in 1.hour, stale_while_revalidate: 60.seconds + # # => Cache-Control: max-age=3600, private, stale-while-revalidate=60 + # + # expires_in 1.hour, stale_if_error: 5.minutes + # # => Cache-Control: max-age=3600, private, stale-if-error=300 + # + # expires_in 1.hour, public: true, "s-maxage": 3.hours, "no-transform": true + # # => Cache-Control: max-age=3600, public, s-maxage=10800, no-transform=true + # + def expires_in(seconds, options = {}) + response.cache_control.delete(:no_store) + response.cache_control.merge!( + max_age: seconds, + public: options.delete(:public), + must_revalidate: options.delete(:must_revalidate), + stale_while_revalidate: options.delete(:stale_while_revalidate), + stale_if_error: options.delete(:stale_if_error), + immutable: options.delete(:immutable), + ) + options.delete(:private) + + response.cache_control[:extras] = options.map { |k, v| "#{k}=#{v}" } + response.date = Time.now unless response.date? + end + + # Sets an HTTP 1.1 `Cache-Control` header of `no-cache`. This means the resource + # will be marked as stale, so clients must always revalidate. + # Intermediate/browser caches may still store the asset. + def expires_now + response.cache_control.replace(no_cache: true) + end + + # Cache or yield the block. The cache is supposed to never expire. + # + # You can use this method when you have an HTTP response that never changes, and + # the browser and proxies should cache it indefinitely. + # + # * `public`: By default, HTTP responses are private, cached only on the + # user's web browser. To allow proxies to cache the response, set `true` to + # indicate that they can serve the cached response to all users. + def http_cache_forever(public: false) + expires_in 100.years, public: public, immutable: true + + yield if stale?(etag: request.fullpath, + last_modified: Time.new(2011, 1, 1).utc, + public: public) + end + + # Sets an HTTP 1.1 `Cache-Control` header of `no-store`. This means the resource + # may not be stored in any cache. + def no_store + response.cache_control.replace(no_store: true) + end + + private + def combine_etags(validator, options) + [validator, *etaggers.map { |etagger| instance_exec(options, &etagger) }].compact + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/content_security_policy.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/content_security_policy.rb new file mode 100644 index 00000000..6af6706c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/content_security_policy.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController # :nodoc: + module ContentSecurityPolicy + extend ActiveSupport::Concern + + include AbstractController::Helpers + include AbstractController::Callbacks + + included do + helper_method :content_security_policy? + helper_method :content_security_policy_nonce + end + + module ClassMethods + # Overrides parts of the globally configured `Content-Security-Policy` header: + # + # class PostsController < ApplicationController + # content_security_policy do |policy| + # policy.base_uri "https://www.example.com" + # end + # end + # + # Options can be passed similar to `before_action`. For example, pass `only: + # :index` to override the header on the index action only: + # + # class PostsController < ApplicationController + # content_security_policy(only: :index) do |policy| + # policy.default_src :self, :https + # end + # end + # + # Pass `false` to remove the `Content-Security-Policy` header: + # + # class PostsController < ApplicationController + # content_security_policy false, only: :index + # end + def content_security_policy(enabled = true, **options, &block) + before_action(options) do + if block_given? + policy = current_content_security_policy + instance_exec(policy, &block) + request.content_security_policy = policy + end + + unless enabled + request.content_security_policy = nil + end + end + end + + # Overrides the globally configured `Content-Security-Policy-Report-Only` + # header: + # + # class PostsController < ApplicationController + # content_security_policy_report_only only: :index + # end + # + # Pass `false` to remove the `Content-Security-Policy-Report-Only` header: + # + # class PostsController < ApplicationController + # content_security_policy_report_only false, only: :index + # end + def content_security_policy_report_only(report_only = true, **options) + before_action(options) do + request.content_security_policy_report_only = report_only + end + end + end + + private + def content_security_policy? + request.content_security_policy + end + + def content_security_policy_nonce + request.content_security_policy_nonce + end + + def current_content_security_policy + request.content_security_policy&.clone || ActionDispatch::ContentSecurityPolicy.new + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/cookies.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/cookies.rb new file mode 100644 index 00000000..a738532c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/cookies.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController # :nodoc: + module Cookies + extend ActiveSupport::Concern + + included do + helper_method :cookies if defined?(helper_method) + end + + private + # The cookies for the current request. See ActionDispatch::Cookies for more + # information. + def cookies # :doc: + request.cookie_jar + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/data_streaming.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/data_streaming.rb new file mode 100644 index 00000000..6c665f64 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/data_streaming.rb @@ -0,0 +1,156 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "action_controller/metal/exceptions" +require "action_dispatch/http/content_disposition" + +module ActionController # :nodoc: + # # Action Controller Data Streaming + # + # Methods for sending arbitrary data and for streaming files to the browser, + # instead of rendering. + module DataStreaming + extend ActiveSupport::Concern + + include ActionController::Rendering + + DEFAULT_SEND_FILE_TYPE = "application/octet-stream" # :nodoc: + DEFAULT_SEND_FILE_DISPOSITION = "attachment" # :nodoc: + + private + # Sends the file. This uses a server-appropriate method (such as `X-Sendfile`) + # via the `Rack::Sendfile` middleware. The header to use is set via + # `config.action_dispatch.x_sendfile_header`. Your server can also configure + # this for you by setting the `X-Sendfile-Type` header. + # + # Be careful to sanitize the path parameter if it is coming from a web page. + # `send_file(params[:path])` allows a malicious user to download any file on + # your server. + # + # #### Options: + # + # * `:filename` - suggests a filename for the browser to use. Defaults to + # `File.basename(path)`. + # * `:type` - specifies an HTTP content type. You can specify either a string + # or a symbol for a registered type with `Mime::Type.register`, for example + # `:json`. If omitted, the type will be inferred from the file extension + # specified in `:filename`. If no content type is registered for the + # extension, the default type `application/octet-stream` will be used. + # * `:disposition` - specifies whether the file will be shown inline or + # downloaded. Valid values are `"inline"` and `"attachment"` (default). + # * `:status` - specifies the status code to send with the response. Defaults + # to 200. + # * `:url_based_filename` - set to `true` if you want the browser to guess the + # filename from the URL, which is necessary for i18n filenames on certain + # browsers (setting `:filename` overrides this option). + # + # + # The default `Content-Type` and `Content-Disposition` headers are set to + # download arbitrary binary files in as many browsers as possible. IE versions + # 4, 5, 5.5, and 6 are all known to have a variety of quirks (especially when + # downloading over SSL). + # + # Simple download: + # + # send_file '/path/to.zip' + # + # Show a JPEG in the browser: + # + # send_file '/path/to.jpeg', type: 'image/jpeg', disposition: 'inline' + # + # Show a 404 page in the browser: + # + # send_file '/path/to/404.html', type: 'text/html; charset=utf-8', disposition: 'inline', status: 404 + # + # You can use other `Content-*` HTTP headers to provide additional information + # to the client. See MDN for a [list of HTTP + # headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers). + # + # Also be aware that the document may be cached by proxies and browsers. The + # `Pragma` and `Cache-Control` headers declare how the file may be cached by + # intermediaries. They default to require clients to validate with the server + # before releasing cached responses. See https://www.mnot.net/cache_docs/ for an + # overview of web caching and [RFC + # 9111](https://www.rfc-editor.org/rfc/rfc9111.html#name-cache-control) for the + # `Cache-Control` header spec. + def send_file(path, options = {}) # :doc: + raise MissingFile, "Cannot read file #{path}" unless File.file?(path) && File.readable?(path) + + options[:filename] ||= File.basename(path) unless options[:url_based_filename] + send_file_headers! options + + self.status = options[:status] || 200 + self.content_type = options[:content_type] if options.key?(:content_type) + response.send_file path + end + + # Sends the given binary data to the browser. This method is similar to `render + # plain: data`, but also allows you to specify whether the browser should + # display the response as a file attachment (i.e. in a download dialog) or as + # inline data. You may also set the content type, the file name, and other + # things. + # + # #### Options: + # + # * `:filename` - suggests a filename for the browser to use. + # * `:type` - specifies an HTTP content type. Defaults to + # `application/octet-stream`. You can specify either a string or a symbol + # for a registered type with `Mime::Type.register`, for example `:json`. If + # omitted, type will be inferred from the file extension specified in + # `:filename`. If no content type is registered for the extension, the + # default type `application/octet-stream` will be used. + # * `:disposition` - specifies whether the file will be shown inline or + # downloaded. Valid values are `"inline"` and `"attachment"` (default). + # * `:status` - specifies the status code to send with the response. Defaults + # to 200. + # + # + # Generic data download: + # + # send_data buffer + # + # Download a dynamically-generated tarball: + # + # send_data generate_tgz('dir'), filename: 'dir.tgz' + # + # Display an image Active Record in the browser: + # + # send_data image.data, type: image.content_type, disposition: 'inline' + # + # See `send_file` for more information on HTTP `Content-*` headers and caching. + def send_data(data, options = {}) # :doc: + send_file_headers! options + render options.slice(:status, :content_type).merge(body: data) + end + + def send_file_headers!(options) + type_provided = options.has_key?(:type) + + content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE) + self.content_type = content_type + response.sending_file = true + + raise ArgumentError, ":type option required" if content_type.nil? + + if content_type.is_a?(Symbol) + extension = Mime[content_type] + raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension + self.content_type = extension + else + if !type_provided && options[:filename] + # If type wasn't provided, try guessing from file extension. + content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete(".")) || content_type + end + self.content_type = content_type + end + + disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION) + if disposition + headers["Content-Disposition"] = ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: options[:filename]) + end + + headers["Content-Transfer-Encoding"] = "binary" + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/default_headers.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/default_headers.rb new file mode 100644 index 00000000..fef8e789 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/default_headers.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + # # Action Controller Default Headers + # + # Allows configuring default headers that will be automatically merged into each + # response. + module DefaultHeaders + extend ActiveSupport::Concern + + module ClassMethods + def make_response!(request) + ActionDispatch::Response.create.tap do |res| + res.request = request + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/etag_with_flash.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/etag_with_flash.rb new file mode 100644 index 00000000..6caf8a43 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/etag_with_flash.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + # # Action Controller Etag With Flash + # + # When you're using the flash, it's generally used as a conditional on the view. + # This means the content of the view depends on the flash. Which in turn means + # that the ETag for a response should be computed with the content of the flash + # in mind. This does that by including the content of the flash as a component + # in the ETag that's generated for a response. + module EtagWithFlash + extend ActiveSupport::Concern + + include ActionController::ConditionalGet + + included do + etag { flash if request.respond_to?(:flash) && !flash.empty? } + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/etag_with_template_digest.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/etag_with_template_digest.rb new file mode 100644 index 00000000..20715b1f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/etag_with_template_digest.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + # # Action Controller Etag With Template Digest + # + # When our views change, they should bubble up into HTTP cache freshness and + # bust browser caches. So the template digest for the current action is + # automatically included in the ETag. + # + # Enabled by default for apps that use Action View. Disable by setting + # + # config.action_controller.etag_with_template_digest = false + # + # Override the template to digest by passing `:template` to `fresh_when` and + # `stale?` calls. For example: + # + # # We're going to render widgets/show, not posts/show + # fresh_when @post, template: 'widgets/show' + # + # # We're not going to render a template, so omit it from the ETag. + # fresh_when @post, template: false + # + module EtagWithTemplateDigest + extend ActiveSupport::Concern + + include ActionController::ConditionalGet + + included do + class_attribute :etag_with_template_digest, default: true + + etag do |options| + determine_template_etag(options) if etag_with_template_digest + end + end + + private + def determine_template_etag(options) + if template = pick_template_for_etag(options) + lookup_and_digest_template(template) + end + end + + # Pick the template digest to include in the ETag. If the `:template` option is + # present, use the named template. If `:template` is `nil` or absent, use the + # default controller/action template. If `:template` is false, omit the template + # digest from the ETag. + def pick_template_for_etag(options) + unless options[:template] == false + options[:template] || lookup_context.find_all(action_name, _prefixes).first&.virtual_path + end + end + + def lookup_and_digest_template(template) + ActionView::Digestor.digest name: template, format: nil, finder: lookup_context + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/exceptions.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/exceptions.rb new file mode 100644 index 00000000..35dd1a91 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/exceptions.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + class ActionControllerError < StandardError # :nodoc: + end + + class BadRequest < ActionControllerError # :nodoc: + def initialize(msg = nil) + super(msg) + set_backtrace $!.backtrace if $! + end + end + + class RenderError < ActionControllerError # :nodoc: + end + + class RoutingError < ActionControllerError # :nodoc: + attr_reader :failures + def initialize(message, failures = []) + super(message) + @failures = failures + end + end + + class UrlGenerationError < ActionControllerError # :nodoc: + attr_reader :routes, :route_name, :method_name + + def initialize(message, routes = nil, route_name = nil, method_name = nil) + @routes = routes + @route_name = route_name + @method_name = method_name + + super(message) + end + + if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker) + include DidYouMean::Correctable + + def corrections + @corrections ||= begin + maybe_these = routes&.named_routes&.helper_names&.grep(/#{route_name}/) || [] + maybe_these -= [method_name.to_s] # remove exact match + + DidYouMean::SpellChecker.new(dictionary: maybe_these).correct(route_name) + end + end + end + end + + class MethodNotAllowed < ActionControllerError # :nodoc: + def initialize(*allowed_methods) + super("Only #{allowed_methods.to_sentence} requests are allowed.") + end + end + + class NotImplemented < MethodNotAllowed # :nodoc: + end + + class MissingFile < ActionControllerError # :nodoc: + end + + class SessionOverflowError < ActionControllerError # :nodoc: + DEFAULT_MESSAGE = "Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data." + + def initialize(message = nil) + super(message || DEFAULT_MESSAGE) + end + end + + class UnknownHttpMethod < ActionControllerError # :nodoc: + end + + class UnknownFormat < ActionControllerError # :nodoc: + end + + # Raised when a nested respond_to is triggered and the content types of each are + # incompatible. For example: + # + # respond_to do |outer_type| + # outer_type.js do + # respond_to do |inner_type| + # inner_type.html { render body: "HTML" } + # end + # end + # end + class RespondToMismatchError < ActionControllerError + DEFAULT_MESSAGE = "respond_to was called multiple times and matched with conflicting formats in this action. Please note that you may only call respond_to and match on a single format per action." + + def initialize(message = nil) + super(message || DEFAULT_MESSAGE) + end + end + + class MissingExactTemplate < UnknownFormat # :nodoc: + attr_reader :controller, :action_name + + def initialize(message, controller, action_name) + @controller = controller + @action_name = action_name + + super(message) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/flash.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/flash.rb new file mode 100644 index 00000000..c6c052dc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/flash.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController # :nodoc: + module Flash + extend ActiveSupport::Concern + + included do + class_attribute :_flash_types, instance_accessor: false, default: [] + + delegate :flash, to: :request + add_flash_types(:alert, :notice) + end + + module ClassMethods + # Creates new flash types. You can pass as many types as you want to create + # flash types other than the default `alert` and `notice` in your controllers + # and views. For instance: + # + # # in application_controller.rb + # class ApplicationController < ActionController::Base + # add_flash_types :warning + # end + # + # # in your controller + # redirect_to user_path(@user), warning: "Incomplete profile" + # + # # in your view + # <%= warning %> + # + # This method will automatically define a new method for each of the given + # names, and it will be available in your views. + def add_flash_types(*types) + types.each do |type| + next if _flash_types.include?(type) + + define_method(type) do + request.flash[type] + end + helper_method(type) if respond_to?(:helper_method) + + self._flash_types += [type] + end + end + + def action_methods # :nodoc: + @action_methods ||= super - _flash_types.map(&:to_s).to_set + end + end + + private + def redirect_to(options = {}, response_options_and_flash = {}) # :doc: + self.class._flash_types.each do |flash_type| + if type = response_options_and_flash.delete(flash_type) + flash[flash_type] = type + end + end + + if other_flashes = response_options_and_flash.delete(:flash) + flash.update(other_flashes) + end + + super(options, response_options_and_flash) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/head.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/head.rb new file mode 100644 index 00000000..4509aa13 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/head.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + module Head + # Returns a response that has no content (merely headers). The options argument + # is interpreted to be a hash of header names and values. This allows you to + # easily return a response that consists only of significant headers: + # + # head :created, location: person_path(@person) + # + # head :created, location: @person + # + # It can also be used to return exceptional conditions: + # + # return head(:method_not_allowed) unless request.post? + # return head(:bad_request) unless valid_request? + # render + # + # See `Rack::Utils::SYMBOL_TO_STATUS_CODE` for a full list of valid `status` + # symbols. + def head(status, options = nil) + if status.is_a?(Hash) + raise ArgumentError, "#{status.inspect} is not a valid value for `status`." + end + + status ||= :ok + + if options + location = options.delete(:location) + content_type = options.delete(:content_type) + + options.each do |key, value| + headers[key.to_s.split(/[-_]/).each { |v| v[0] = v[0].upcase }.join("-")] = value.to_s + end + end + + self.status = status + self.location = url_for(location) if location + + if include_content?(response_code) + unless self.media_type + self.content_type = content_type || ((f = formats) && Mime[f.first]) || Mime[:html] + end + + response.charset = false + end + + self.response_body = "" + + true + end + + private + def include_content?(status) + case status + when 100..199 + false + when 204, 205, 304 + false + else + true + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/helpers.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/helpers.rb new file mode 100644 index 00000000..3b660843 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/helpers.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + # # Action Controller Helpers + # + # The Rails framework provides a large number of helpers for working with + # assets, dates, forms, numbers and model objects, to name a few. These helpers + # are available to all templates by default. + # + # In addition to using the standard template helpers provided, creating custom + # helpers to extract complicated logic or reusable functionality is strongly + # encouraged. By default, each controller will include all helpers. These + # helpers are only accessible on the controller through `#helpers` + # + # In previous versions of Rails the controller will include a helper which + # matches the name of the controller, e.g., `MyController` will automatically + # include `MyHelper`. You can revert to the old behavior with the following: + # + # # config/application.rb + # class Application < Rails::Application + # config.action_controller.include_all_helpers = false + # end + # + # Additional helpers can be specified using the `helper` class method in + # ActionController::Base or any controller which inherits from it. + # + # The `to_s` method from the Time class can be wrapped in a helper method to + # display a custom message if a Time object is blank: + # + # module FormattedTimeHelper + # def format_time(time, format=:long, blank_message=" ") + # time.blank? ? blank_message : time.to_fs(format) + # end + # end + # + # FormattedTimeHelper can now be included in a controller, using the `helper` + # class method: + # + # class EventsController < ActionController::Base + # helper FormattedTimeHelper + # def index + # @events = Event.all + # end + # end + # + # Then, in any view rendered by `EventsController`, the `format_time` method can + # be called: + # + # <% @events.each do |event| -%> + #

+ # <%= format_time(event.time, :short, "N/A") %> | <%= event.name %> + #

+ # <% end -%> + # + # Finally, assuming we have two event instances, one which has a time and one + # which does not, the output might look like this: + # + # 23 Aug 11:30 | Carolina Railhawks Soccer Match + # N/A | Carolina Railhawks Training Workshop + # + module Helpers + extend ActiveSupport::Concern + + class << self; attr_accessor :helpers_path; end + include AbstractController::Helpers + + included do + class_attribute :helpers_path, default: [] + class_attribute :include_all_helpers, default: true + end + + module ClassMethods + # Declares helper accessors for controller attributes. For example, the + # following adds new `name` and `name=` instance methods to a controller and + # makes them available to the view: + # attr_accessor :name + # helper_attr :name + # + # #### Parameters + # * `attrs` - Names of attributes to be converted into helpers. + # + def helper_attr(*attrs) + attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") } + end + + # Provides a proxy to access helper methods from outside the view. + # + # Note that the proxy is rendered under a different view context. This may cause + # incorrect behavior with capture methods. Consider using + # [helper](rdoc-ref:AbstractController::Helpers::ClassMethods#helper) instead + # when using `capture`. + def helpers + @helper_proxy ||= begin + proxy = ActionView::Base.empty + proxy.config = config.inheritable_copy + proxy.extend(_helpers) + end + end + + # Override modules_for_helpers to accept `:all` as argument, which loads all + # helpers in helpers_path. + # + # #### Parameters + # * `args` - A list of helpers + # + # + # #### Returns + # * `array` - A normalized list of modules for the list of helpers provided. + # + def modules_for_helpers(args) + args += all_application_helpers if args.delete(:all) + super(args) + end + + private + # Extract helper names from files in `app/helpers/***/**_helper.rb` + def all_application_helpers + all_helpers_from_path(helpers_path) + end + end + + # Provides a proxy to access helper methods from outside the view. + def helpers + @_helper_proxy ||= view_context + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/http_authentication.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/http_authentication.rb new file mode 100644 index 00000000..a651242f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/http_authentication.rb @@ -0,0 +1,562 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "base64" +require "active_support/security_utils" +require "active_support/core_ext/array/access" + +module ActionController + # HTTP Basic, Digest, and Token authentication. + module HttpAuthentication + # # HTTP Basic authentication + # + # ### Simple Basic example + # + # class PostsController < ApplicationController + # http_basic_authenticate_with name: "dhh", password: "secret", except: :index + # + # def index + # render plain: "Everyone can see me!" + # end + # + # def edit + # render plain: "I'm only accessible if you know the password" + # end + # end + # + # ### Advanced Basic example + # + # Here is a more advanced Basic example where only Atom feeds and the XML API + # are protected by HTTP authentication. The regular HTML interface is protected + # by a session approach: + # + # class ApplicationController < ActionController::Base + # before_action :set_account, :authenticate + # + # private + # def set_account + # @account = Account.find_by(url_name: request.subdomains.first) + # end + # + # def authenticate + # case request.format + # when Mime[:xml], Mime[:atom] + # if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) } + # @current_user = user + # else + # request_http_basic_authentication + # end + # else + # if session_authenticated? + # @current_user = @account.users.find(session[:authenticated][:user_id]) + # else + # redirect_to(login_url) and return false + # end + # end + # end + # end + # + # In your integration tests, you can do something like this: + # + # def test_access_granted_from_xml + # authorization = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password) + # + # get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization } + # + # assert_equal 200, status + # end + module Basic + extend self + + module ControllerMethods + extend ActiveSupport::Concern + + module ClassMethods + # Enables HTTP Basic authentication. + # + # See ActionController::HttpAuthentication::Basic for example usage. + def http_basic_authenticate_with(name:, password:, realm: nil, **options) + raise ArgumentError, "Expected name: to be a String, got #{name.class}" unless name.is_a?(String) + raise ArgumentError, "Expected password: to be a String, got #{password.class}" unless password.is_a?(String) + before_action(options) { http_basic_authenticate_or_request_with name: name, password: password, realm: realm } + end + end + + def http_basic_authenticate_or_request_with(name:, password:, realm: nil, message: nil) + authenticate_or_request_with_http_basic(realm, message) do |given_name, given_password| + # This comparison uses & so that it doesn't short circuit and uses + # `secure_compare` so that length information isn't leaked. + ActiveSupport::SecurityUtils.secure_compare(given_name.to_s, name) & + ActiveSupport::SecurityUtils.secure_compare(given_password.to_s, password) + end + end + + def authenticate_or_request_with_http_basic(realm = nil, message = nil, &login_procedure) + authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm || "Application", message) + end + + def authenticate_with_http_basic(&login_procedure) + HttpAuthentication::Basic.authenticate(request, &login_procedure) + end + + def request_http_basic_authentication(realm = "Application", message = nil) + HttpAuthentication::Basic.authentication_request(self, realm, message) + end + end + + def authenticate(request, &login_procedure) + if has_basic_credentials?(request) + login_procedure.call(*user_name_and_password(request)) + end + end + + def has_basic_credentials?(request) + request.authorization.present? && (auth_scheme(request).downcase == "basic") + end + + def user_name_and_password(request) + decode_credentials(request).split(":", 2) + end + + def decode_credentials(request) + ::Base64.decode64(auth_param(request) || "") + end + + def auth_scheme(request) + request.authorization.to_s.split(" ", 2).first + end + + def auth_param(request) + request.authorization.to_s.split(" ", 2).second + end + + def encode_credentials(user_name, password) + "Basic #{::Base64.strict_encode64("#{user_name}:#{password}")}" + end + + def authentication_request(controller, realm, message) + message ||= "HTTP Basic: Access denied.\n" + controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.tr('"', "")}") + controller.status = 401 + controller.response_body = message + end + end + + # # HTTP Digest authentication + # + # ### Simple Digest example + # + # require "openssl" + # class PostsController < ApplicationController + # REALM = "SuperSecret" + # USERS = {"dhh" => "secret", #plain text password + # "dap" => OpenSSL::Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password + # + # before_action :authenticate, except: [:index] + # + # def index + # render plain: "Everyone can see me!" + # end + # + # def edit + # render plain: "I'm only accessible if you know the password" + # end + # + # private + # def authenticate + # authenticate_or_request_with_http_digest(REALM) do |username| + # USERS[username] + # end + # end + # end + # + # ### Notes + # + # The `authenticate_or_request_with_http_digest` block must return the user's + # password or the ha1 digest hash so the framework can appropriately hash to + # check the user's credentials. Returning `nil` will cause authentication to + # fail. + # + # Storing the ha1 hash: MD5(username:realm:password), is better than storing a + # plain password. If the password file or database is compromised, the attacker + # would be able to use the ha1 hash to authenticate as the user at this `realm`, + # but would not have the user's password to try using at other sites. + # + # In rare instances, web servers or front proxies strip authorization headers + # before they reach your application. You can debug this situation by logging + # all environment variables, and check for HTTP_AUTHORIZATION, amongst others. + module Digest + extend self + + module ControllerMethods + # Authenticate using an HTTP Digest, or otherwise render an HTTP header + # requesting the client to send a Digest. + # + # See ActionController::HttpAuthentication::Digest for example usage. + def authenticate_or_request_with_http_digest(realm = "Application", message = nil, &password_procedure) + authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm, message) + end + + # Authenticate using an HTTP Digest. Returns true if authentication is + # successful, false otherwise. + def authenticate_with_http_digest(realm = "Application", &password_procedure) + HttpAuthentication::Digest.authenticate(request, realm, &password_procedure) + end + + # Render an HTTP header requesting the client to send a Digest for + # authentication. + def request_http_digest_authentication(realm = "Application", message = nil) + HttpAuthentication::Digest.authentication_request(self, realm, message) + end + end + + # Returns true on a valid response, false otherwise. + def authenticate(request, realm, &password_procedure) + request.authorization && validate_digest_response(request, realm, &password_procedure) + end + + # Returns false unless the request credentials response value matches the + # expected value. First try the password as a ha1 digest password. If this + # fails, then try it as a plain text password. + def validate_digest_response(request, realm, &password_procedure) + secret_key = secret_token(request) + credentials = decode_credentials_header(request) + valid_nonce = validate_nonce(secret_key, request, credentials[:nonce]) + + if valid_nonce && realm == credentials[:realm] && opaque(secret_key) == credentials[:opaque] + password = password_procedure.call(credentials[:username]) + return false unless password + + method = request.get_header("rack.methodoverride.original_method") || request.get_header("REQUEST_METHOD") + uri = credentials[:uri] + + [true, false].any? do |trailing_question_mark| + [true, false].any? do |password_is_ha1| + _uri = trailing_question_mark ? uri + "?" : uri + expected = expected_response(method, _uri, credentials, password, password_is_ha1) + expected == credentials[:response] + end + end + end + end + + # Returns the expected response for a request of `http_method` to `uri` with the + # decoded `credentials` and the expected `password` Optional parameter + # `password_is_ha1` is set to `true` by default, since best practice is to store + # ha1 digest instead of a plain-text password. + def expected_response(http_method, uri, credentials, password, password_is_ha1 = true) + ha1 = password_is_ha1 ? password : ha1(credentials, password) + ha2 = OpenSSL::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":")) + OpenSSL::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(":")) + end + + def ha1(credentials, password) + OpenSSL::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":")) + end + + def encode_credentials(http_method, credentials, password, password_is_ha1) + credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1) + "Digest " + credentials.sort_by { |x| x[0].to_s }.map { |v| "#{v[0]}='#{v[1]}'" }.join(", ") + end + + def decode_credentials_header(request) + decode_credentials(request.authorization) + end + + def decode_credentials(header) + ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, "").split(",").map do |pair| + key, value = pair.split("=", 2) + [key.strip, value.to_s.gsub(/^"|"$/, "").delete("'")] + end] + end + + def authentication_header(controller, realm) + secret_key = secret_token(controller.request) + nonce = self.nonce(secret_key) + opaque = opaque(secret_key) + controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}") + end + + def authentication_request(controller, realm, message = nil) + message ||= "HTTP Digest: Access denied.\n" + authentication_header(controller, realm) + controller.status = 401 + controller.response_body = message + end + + def secret_token(request) + key_generator = request.key_generator + http_auth_salt = request.http_auth_salt + key_generator.generate_key(http_auth_salt) + end + + # Uses an MD5 digest based on time to generate a value to be used only once. + # + # A server-specified data string which should be uniquely generated each time a + # 401 response is made. It is recommended that this string be base64 or + # hexadecimal data. Specifically, since the string is passed in the header lines + # as a quoted string, the double-quote character is not allowed. + # + # The contents of the nonce are implementation dependent. The quality of the + # implementation depends on a good choice. A nonce might, for example, be + # constructed as the base 64 encoding of + # + # time-stamp H(time-stamp ":" ETag ":" private-key) + # + # where time-stamp is a server-generated time or other non-repeating value, ETag + # is the value of the HTTP ETag header associated with the requested entity, and + # private-key is data known only to the server. With a nonce of this form a + # server would recalculate the hash portion after receiving the client + # authentication header and reject the request if it did not match the nonce + # from that header or if the time-stamp value is not recent enough. In this way + # the server can limit the time of the nonce's validity. The inclusion of the + # ETag prevents a replay request for an updated version of the resource. (Note: + # including the IP address of the client in the nonce would appear to offer the + # server the ability to limit the reuse of the nonce to the same client that + # originally got it. However, that would break proxy farms, where requests from + # a single user often go through different proxies in the farm. Also, IP address + # spoofing is not that hard.) + # + # An implementation might choose not to accept a previously used nonce or a + # previously used digest, in order to protect against a replay attack. Or, an + # implementation might choose to use one-time nonces or digests for POST, PUT, + # or PATCH requests, and a time-stamp for GET requests. For more details on the + # issues involved see Section 4 of this document. + # + # The nonce is opaque to the client. Composed of Time, and hash of Time with + # secret key from the Rails session secret generated upon creation of project. + # Ensures the time cannot be modified by client. + def nonce(secret_key, time = Time.now) + t = time.to_i + hashed = [t, secret_key] + digest = OpenSSL::Digest::MD5.hexdigest(hashed.join(":")) + ::Base64.strict_encode64("#{t}:#{digest}") + end + + # Might want a shorter timeout depending on whether the request is a PATCH, PUT, + # or POST, and if the client is a browser or web service. Can be much shorter if + # the Stale directive is implemented. This would allow a user to use new nonce + # without prompting the user again for their username and password. + def validate_nonce(secret_key, request, value, seconds_to_timeout = 5 * 60) + return false if value.nil? + t = ::Base64.decode64(value).split(":").first.to_i + nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout + end + + # Opaque based on digest of secret key + def opaque(secret_key) + OpenSSL::Digest::MD5.hexdigest(secret_key) + end + end + + # # HTTP Token authentication + # + # ### Simple Token example + # + # class PostsController < ApplicationController + # TOKEN = "secret" + # + # before_action :authenticate, except: [ :index ] + # + # def index + # render plain: "Everyone can see me!" + # end + # + # def edit + # render plain: "I'm only accessible if you know the password" + # end + # + # private + # def authenticate + # authenticate_or_request_with_http_token do |token, options| + # # Compare the tokens in a time-constant manner, to mitigate + # # timing attacks. + # ActiveSupport::SecurityUtils.secure_compare(token, TOKEN) + # end + # end + # end + # + # Here is a more advanced Token example where only Atom feeds and the XML API + # are protected by HTTP token authentication. The regular HTML interface is + # protected by a session approach: + # + # class ApplicationController < ActionController::Base + # before_action :set_account, :authenticate + # + # private + # def set_account + # @account = Account.find_by(url_name: request.subdomains.first) + # end + # + # def authenticate + # case request.format + # when Mime[:xml], Mime[:atom] + # if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) } + # @current_user = user + # else + # request_http_token_authentication + # end + # else + # if session_authenticated? + # @current_user = @account.users.find(session[:authenticated][:user_id]) + # else + # redirect_to(login_url) and return false + # end + # end + # end + # end + # + # In your integration tests, you can do something like this: + # + # def test_access_granted_from_xml + # authorization = ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token) + # + # get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization } + # + # assert_equal 200, status + # end + # + # On shared hosts, Apache sometimes doesn't pass authentication headers to FCGI + # instances. If your environment matches this description and you cannot + # authenticate, try this rule in your Apache setup: + # + # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L] + module Token + TOKEN_KEY = "token=" + TOKEN_REGEX = /^(Token|Bearer)\s+/ + AUTHN_PAIR_DELIMITERS = /(?:,|;|\t)/ + extend self + + module ControllerMethods + # Authenticate using an HTTP Bearer token, or otherwise render an HTTP header + # requesting the client to send a Bearer token. For the authentication to be + # considered successful, `login_procedure` must not return a false value. + # Typically, the authenticated user is returned. + # + # See ActionController::HttpAuthentication::Token for example usage. + def authenticate_or_request_with_http_token(realm = "Application", message = nil, &login_procedure) + authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm, message) + end + + # Authenticate using an HTTP Bearer token. Returns the return value of + # `login_procedure` if a token is found. Returns `nil` if no token is found. + # + # See ActionController::HttpAuthentication::Token for example usage. + def authenticate_with_http_token(&login_procedure) + Token.authenticate(self, &login_procedure) + end + + # Render an HTTP header requesting the client to send a Bearer token for + # authentication. + def request_http_token_authentication(realm = "Application", message = nil) + Token.authentication_request(self, realm, message) + end + end + + # If token Authorization header is present, call the login procedure with the + # present token and options. + # + # Returns the return value of `login_procedure` if a token is found. Returns + # `nil` if no token is found. + # + # #### Parameters + # + # * `controller` - ActionController::Base instance for the current request. + # * `login_procedure` - Proc to call if a token is present. The Proc should + # take two arguments: + # + # authenticate(controller) { |token, options| ... } + # + # + def authenticate(controller, &login_procedure) + token, options = token_and_options(controller.request) + unless token.blank? + login_procedure.call(token, options) + end + end + + # Parses the token and options out of the token Authorization header. The value + # for the Authorization header is expected to have the prefix `"Token"` or + # `"Bearer"`. If the header looks like this: + # + # Authorization: Token token="abc", nonce="def" + # + # Then the returned token is `"abc"`, and the options are `{nonce: "def"}`. + # + # Returns an `Array` of `[String, Hash]` if a token is present. Returns `nil` if + # no token is found. + # + # #### Parameters + # + # * `request` - ActionDispatch::Request instance with the current headers. + # + def token_and_options(request) + authorization_request = request.authorization.to_s + if authorization_request[TOKEN_REGEX] + params = token_params_from authorization_request + [params.shift[1], Hash[params].with_indifferent_access] + end + end + + def token_params_from(auth) + rewrite_param_values params_array_from raw_params auth + end + + # Takes `raw_params` and turns it into an array of parameters. + def params_array_from(raw_params) + raw_params.map { |param| param.split %r/=(.+)?/ } + end + + # This removes the `"` characters wrapping the value. + def rewrite_param_values(array_params) + array_params.each { |param| (param[1] || +"").gsub! %r/^"|"$/, "" } + end + + # This method takes an authorization body and splits up the key-value pairs by + # the standardized `:`, `;`, or `\t` delimiters defined in + # `AUTHN_PAIR_DELIMITERS`. + def raw_params(auth) + _raw_params = auth.sub(TOKEN_REGEX, "").split(AUTHN_PAIR_DELIMITERS).map(&:strip) + _raw_params.reject!(&:empty?) + + if !_raw_params.first&.start_with?(TOKEN_KEY) + _raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}" + end + + _raw_params + end + + # Encodes the given token and options into an Authorization header value. + # + # Returns String. + # + # #### Parameters + # + # * `token` - String token. + # * `options` - Optional Hash of the options. + # + def encode_credentials(token, options = {}) + values = ["#{TOKEN_KEY}#{token.to_s.inspect}"] + options.map do |key, value| + "#{key}=#{value.to_s.inspect}" + end + "Token #{values * ", "}" + end + + # Sets a WWW-Authenticate header to let the client know a token is desired. + # + # Returns nothing. + # + # #### Parameters + # + # * `controller` - ActionController::Base instance for the outgoing response. + # * `realm` - String realm to use in the header. + # + def authentication_request(controller, realm, message = nil) + message ||= "HTTP Token: Access denied.\n" + controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"', "")}") + controller.__send__ :render, plain: message, status: :unauthorized + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/implicit_render.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/implicit_render.rb new file mode 100644 index 00000000..fe2590e7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/implicit_render.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + # # Action Controller Implicit Render + # + # Handles implicit rendering for a controller action that does not explicitly + # respond with `render`, `respond_to`, `redirect`, or `head`. + # + # For API controllers, the implicit response is always `204 No Content`. + # + # For all other controllers, we use these heuristics to decide whether to render + # a template, raise an error for a missing template, or respond with `204 No + # Content`: + # + # First, if we DO find a template, it's rendered. Template lookup accounts for + # the action name, locales, format, variant, template handlers, and more (see + # `render` for details). + # + # Second, if we DON'T find a template but the controller action does have + # templates for other formats, variants, etc., then we trust that you meant to + # provide a template for this response, too, and we raise + # ActionController::UnknownFormat with an explanation. + # + # Third, if we DON'T find a template AND the request is a page load in a web + # browser (technically, a non-XHR GET request for an HTML response) where you + # reasonably expect to have rendered a template, then we raise + # ActionController::MissingExactTemplate with an explanation. + # + # Finally, if we DON'T find a template AND the request isn't a browser page + # load, then we implicitly respond with `204 No Content`. + module ImplicitRender + # :stopdoc: + include BasicImplicitRender + + def default_render + if template_exists?(action_name.to_s, _prefixes, variants: request.variant) + render + elsif any_templates?(action_name.to_s, _prefixes) + message = "#{self.class.name}\##{action_name} is missing a template " \ + "for this request format and variant.\n" \ + "\nrequest.formats: #{request.formats.map(&:to_s).inspect}" \ + "\nrequest.variant: #{request.variant.inspect}" + + raise ActionController::UnknownFormat, message + elsif interactive_browser_request? + message = "#{self.class.name}\##{action_name} is missing a template for request formats: #{request.formats.map(&:to_s).join(',')}" + raise ActionController::MissingExactTemplate.new(message, self.class, action_name) + else + logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger + super + end + end + + def method_for_action(action_name) + super || if template_exists?(action_name.to_s, _prefixes) + "default_render" + end + end + + private + def interactive_browser_request? + request.get? && request.format == Mime[:html] && !request.xhr? + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/instrumentation.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/instrumentation.rb new file mode 100644 index 00000000..8f7f6348 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/instrumentation.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "abstract_controller/logger" + +module ActionController + # # Action Controller Instrumentation + # + # Adds instrumentation to several ends in ActionController::Base. It also + # provides some hooks related with process_action. This allows an ORM like + # Active Record and/or DataMapper to plug in ActionController and show related + # information. + # + # Check ActiveRecord::Railties::ControllerRuntime for an example. + module Instrumentation + extend ActiveSupport::Concern + + include AbstractController::Logger + + attr_internal :view_runtime + + def initialize(...) # :nodoc: + super + self.view_runtime = nil + end + + def render(*) + render_output = nil + self.view_runtime = cleanup_view_runtime do + ActiveSupport::Benchmark.realtime(:float_millisecond) { render_output = super } + end + render_output + end + + def send_file(path, options = {}) + ActiveSupport::Notifications.instrument("send_file.action_controller", + options.merge(path: path)) do + super + end + end + + def send_data(data, options = {}) + ActiveSupport::Notifications.instrument("send_data.action_controller", options) do + super + end + end + + def redirect_to(*) + ActiveSupport::Notifications.instrument("redirect_to.action_controller", request: request) do |payload| + result = super + payload[:status] = response.status + payload[:location] = response.filtered_location + result + end + end + + private + def process_action(*) + ActiveSupport::ExecutionContext[:controller] = self + + raw_payload = { + controller: self.class.name, + action: action_name, + request: request, + params: request.filtered_parameters, + headers: request.headers, + format: request.format.ref, + method: request.request_method, + path: request.filtered_path + } + + ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload) + + ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload| + result = super + payload[:response] = response + payload[:status] = response.status + result + rescue => error + payload[:status] = ActionDispatch::ExceptionWrapper.status_code_for_exception(error.class.name) + raise + ensure + append_info_to_payload(payload) + end + end + + # A hook invoked every time a before callback is halted. + def halted_callback_hook(filter, _) + ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter) + end + + # A hook which allows you to clean up any time, wrongly taken into account in + # views, like database querying time. + # + # def cleanup_view_runtime + # super - time_taken_in_something_expensive + # end + def cleanup_view_runtime # :doc: + yield + end + + # Every time after an action is processed, this method is invoked with the + # payload, so you can add more information. + def append_info_to_payload(payload) # :doc: + payload[:view_runtime] = view_runtime + end + + module ClassMethods + # A hook which allows other frameworks to log what happened during controller + # process action. This method should return an array with the messages to be + # added. + def log_process_action(payload) # :nodoc: + messages, view_runtime = [], payload[:view_runtime] + messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime + messages + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/live.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/live.rb new file mode 100644 index 00000000..28b7d673 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/live.rb @@ -0,0 +1,406 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "action_dispatch/http/response" +require "delegate" +require "active_support/json" + +module ActionController + # # Action Controller Live + # + # Mix this module into your controller, and all actions in that controller will + # be able to stream data to the client as it's written. + # + # class MyController < ActionController::Base + # include ActionController::Live + # + # def stream + # response.headers['Content-Type'] = 'text/event-stream' + # 100.times { + # response.stream.write "hello world\n" + # sleep 1 + # } + # ensure + # response.stream.close + # end + # end + # + # There are a few caveats with this module. You **cannot** write headers after + # the response has been committed (Response#committed? will return truthy). + # Calling `write` or `close` on the response stream will cause the response + # object to be committed. Make sure all headers are set before calling write or + # close on your stream. + # + # You **must** call close on your stream when you're finished, otherwise the + # socket may be left open forever. + # + # The final caveat is that your actions are executed in a separate thread than + # the main thread. Make sure your actions are thread safe, and this shouldn't be + # a problem (don't share state across threads, etc). + # + # Note that Rails includes `Rack::ETag` by default, which will buffer your + # response. As a result, streaming responses may not work properly with Rack + # 2.2.x, and you may need to implement workarounds in your application. You can + # either set the `ETag` or `Last-Modified` response headers or remove + # `Rack::ETag` from the middleware stack to address this issue. + # + # Here's an example of how you can set the `Last-Modified` header if your Rack + # version is 2.2.x: + # + # def stream + # response.headers["Content-Type"] = "text/event-stream" + # response.headers["Last-Modified"] = Time.now.httpdate # Add this line if your Rack version is 2.2.x + # ... + # end + module Live + extend ActiveSupport::Concern + + module ClassMethods + def make_response!(request) + if (request.get_header("SERVER_PROTOCOL") || request.get_header("HTTP_VERSION")) == "HTTP/1.0" + super + else + Live::Response.new.tap do |res| + res.request = request + end + end + end + end + + # # Action Controller Live Server Sent Events + # + # This class provides the ability to write an SSE (Server Sent Event) to an IO + # stream. The class is initialized with a stream and can be used to either write + # a JSON string or an object which can be converted to JSON. + # + # Writing an object will convert it into standard SSE format with whatever + # options you have configured. You may choose to set the following options: + # + # `:event` + # : If specified, an event with this name will be dispatched on the browser. + # + # `:retry` + # : The reconnection time in milliseconds used when attempting to send the event. + # + # `:id` + # : If the connection dies while sending an SSE to the browser, then the + # server will receive a `Last-Event-ID` header with value equal to `id`. + # + # After setting an option in the constructor of the SSE object, all future SSEs + # sent across the stream will use those options unless overridden. + # + # Example Usage: + # + # class MyController < ActionController::Base + # include ActionController::Live + # + # def index + # response.headers['Content-Type'] = 'text/event-stream' + # sse = SSE.new(response.stream, retry: 300, event: "event-name") + # sse.write({ name: 'John'}) + # sse.write({ name: 'John'}, id: 10) + # sse.write({ name: 'John'}, id: 10, event: "other-event") + # sse.write({ name: 'John'}, id: 10, event: "other-event", retry: 500) + # ensure + # sse.close + # end + # end + # + # Note: SSEs are not currently supported by IE. However, they are supported by + # Chrome, Firefox, Opera, and Safari. + class SSE + PERMITTED_OPTIONS = %w( retry event id ) + + def initialize(stream, options = {}) + @stream = stream + @options = options + end + + def close + @stream.close + end + + def write(object, options = {}) + case object + when String + perform_write(object, options) + else + perform_write(ActiveSupport::JSON.encode(object), options) + end + end + + private + def perform_write(json, options) + current_options = @options.merge(options).stringify_keys + + PERMITTED_OPTIONS.each do |option_name| + if (option_value = current_options[option_name]) + @stream.write "#{option_name}: #{option_value}\n" + end + end + + message = json.gsub("\n", "\ndata: ") + @stream.write "data: #{message}\n\n" + end + end + + class ClientDisconnected < RuntimeError + end + + class Buffer < ActionDispatch::Response::Buffer # :nodoc: + include MonitorMixin + + class << self + attr_accessor :queue_size + end + @queue_size = 10 + + # Ignore that the client has disconnected. + # + # If this value is `true`, calling `write` after the client disconnects will + # result in the written content being silently discarded. If this value is + # `false` (the default), a ClientDisconnected exception will be raised. + attr_accessor :ignore_disconnect + + def initialize(response) + super(response, build_queue(self.class.queue_size)) + @error_callback = lambda { true } + @cv = new_cond + @aborted = false + @ignore_disconnect = false + end + + # ActionDispatch::Response delegates #to_ary to the internal + # ActionDispatch::Response::Buffer, defining #to_ary is an indicator that the + # response body can be buffered and/or cached by Rack middlewares, this is not + # the case for Live responses so we undefine it for this Buffer subclass. + undef_method :to_ary + + def write(string) + unless @response.committed? + @response.headers["Cache-Control"] ||= "no-cache" + @response.delete_header "Content-Length" + end + + super + + unless connected? + @buf.clear + + unless @ignore_disconnect + # Raise ClientDisconnected, which is a RuntimeError (not an IOError), because + # that's more appropriate for something beyond the developer's control. + raise ClientDisconnected, "client disconnected" + end + end + end + + # Same as `write` but automatically include a newline at the end of the string. + def writeln(string) + write string.end_with?("\n") ? string : "#{string}\n" + end + + # Write a 'close' event to the buffer; the producer/writing thread uses this to + # notify us that it's finished supplying content. + # + # See also #abort. + def close + synchronize do + super + @buf.push nil + @cv.broadcast + end + end + + # Inform the producer/writing thread that the client has disconnected; the + # reading thread is no longer interested in anything that's being written. + # + # See also #close. + def abort + synchronize do + @aborted = true + @buf.clear + end + end + + # Is the client still connected and waiting for content? + # + # The result of calling `write` when this is `false` is determined by + # `ignore_disconnect`. + def connected? + !@aborted + end + + def on_error(&block) + @error_callback = block + end + + def call_on_error + @error_callback.call + end + + private + def each_chunk(&block) + loop do + str = nil + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + str = @buf.pop + end + break unless str + yield str + end + end + + def build_queue(queue_size) + queue_size ? SizedQueue.new(queue_size) : Queue.new + end + end + + class Response < ActionDispatch::Response # :nodoc: all + private + def before_committed + super + jar = request.cookie_jar + # The response can be committed multiple times + jar.write self unless committed? + end + + def build_buffer(response, body) + buf = Live::Buffer.new response + body.each { |part| buf.write part } + buf + end + end + + def process(name) + t1 = Thread.current + locals = t1.keys.map { |key| [key, t1[key]] } + + error = nil + # This processes the action in a child thread. It lets us return the response + # code and headers back up the Rack stack, and still process the body in + # parallel with sending data to the client. + new_controller_thread { + ActiveSupport::Dependencies.interlock.running do + t2 = Thread.current + + # Since we're processing the view in a different thread, copy the thread locals + # from the main thread to the child thread. :'( + locals.each { |k, v| t2[k] = v } + ActiveSupport::IsolatedExecutionState.share_with(t1) + + begin + super(name) + rescue => e + if @_response.committed? + begin + @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html + @_response.stream.call_on_error + rescue => exception + log_error(exception) + ensure + log_error(e) + @_response.stream.close + end + else + error = e + end + ensure + ActiveSupport::IsolatedExecutionState.clear + clean_up_thread_locals(locals, t2) + + @_response.commit! + end + end + } + + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @_response.await_commit + end + + raise error if error + end + + def response_body=(body) + super + response.close if response + end + + # Sends a stream to the browser, which is helpful when you're generating exports + # or other running data where you don't want the entire file buffered in memory + # first. Similar to send_data, but where the data is generated live. + # + # #### Options: + # + # * `:filename` - suggests a filename for the browser to use. + # * `:type` - specifies an HTTP content type. You can specify either a string + # or a symbol for a registered type with `Mime::Type.register`, for example + # :json. If omitted, type will be inferred from the file extension specified + # in `:filename`. If no content type is registered for the extension, the + # default type 'application/octet-stream' will be used. + # * `:disposition` - specifies whether the file will be shown inline or + # downloaded. Valid values are 'inline' and 'attachment' (default). + # + # + # Example of generating a csv export: + # + # send_stream(filename: "subscribers.csv") do |stream| + # stream.write "email_address,updated_at\n" + # + # @subscribers.find_each do |subscriber| + # stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n" + # end + # end + def send_stream(filename:, disposition: "attachment", type: nil) + payload = { filename: filename, disposition: disposition, type: type } + ActiveSupport::Notifications.instrument("send_stream.action_controller", payload) do + response.headers["Content-Type"] = + (type.is_a?(Symbol) ? Mime[type].to_s : type) || + Mime::Type.lookup_by_extension(File.extname(filename).downcase.delete("."))&.to_s || + "application/octet-stream" + + response.headers["Content-Disposition"] = + ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: filename) + + yield response.stream + end + ensure + response.stream.close + end + + private + # Spawn a new thread to serve up the controller in. This is to get around the + # fact that Rack isn't based around IOs and we need to use a thread to stream + # data from the response bodies. Nobody should call this method except in Rails + # internals. Seriously! + def new_controller_thread # :nodoc: + ActionController::Live.live_thread_pool_executor.post do + t2 = Thread.current + t2.abort_on_exception = true + yield + end + end + + # Ensure we clean up any thread locals we copied so that the thread can reused. + def clean_up_thread_locals(locals, thread) # :nodoc: + locals.each { |k, _| thread[k] = nil } + end + + def self.live_thread_pool_executor + @live_thread_pool_executor ||= Concurrent::CachedThreadPool.new(name: "action_controller.live") + end + + def log_error(exception) + logger = ActionController::Base.logger + return unless logger + + logger.fatal do + message = +"\n#{exception.class} (#{exception.message}):\n" + message << exception.annotated_source_code.to_s if exception.respond_to?(:annotated_source_code) + message << " " << exception.backtrace.join("\n ") + "#{message}\n\n" + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/logging.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/logging.rb new file mode 100644 index 00000000..c00f1614 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/logging.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + module Logging + extend ActiveSupport::Concern + + module ClassMethods + # Set a different log level per request. + # + # # Use the debug log level if a particular cookie is set. + # class ApplicationController < ActionController::Base + # log_at :debug, if: -> { cookies[:debug] } + # end + # + def log_at(level, **options) + around_action ->(_, action) { logger.log_at(level, &action) }, **options + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/mime_responds.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/mime_responds.rb new file mode 100644 index 00000000..c03b1770 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/mime_responds.rb @@ -0,0 +1,337 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "abstract_controller/collector" + +module ActionController # :nodoc: + module MimeResponds + # Without web-service support, an action which collects the data for displaying + # a list of people might look something like this: + # + # def index + # @people = Person.all + # end + # + # That action implicitly responds to all formats, but formats can also be + # explicitly enumerated: + # + # def index + # @people = Person.all + # respond_to :html, :js + # end + # + # Here's the same action, with web-service support baked in: + # + # def index + # @people = Person.all + # + # respond_to do |format| + # format.html + # format.js + # format.xml { render xml: @people } + # end + # end + # + # What that says is, "if the client wants HTML or JS in response to this action, + # just respond as we would have before, but if the client wants XML, return them + # the list of people in XML format." (Rails determines the desired response + # format from the HTTP Accept header submitted by the client.) + # + # Supposing you have an action that adds a new person, optionally creating their + # company (by name) if it does not already exist, without web-services, it might + # look like this: + # + # def create + # @company = Company.find_or_create_by(name: params[:company][:name]) + # @person = @company.people.create(params[:person]) + # + # redirect_to(person_list_url) + # end + # + # Here's the same action, with web-service support baked in: + # + # def create + # company = params[:person].delete(:company) + # @company = Company.find_or_create_by(name: company[:name]) + # @person = @company.people.create(params[:person]) + # + # respond_to do |format| + # format.html { redirect_to(person_list_url) } + # format.js + # format.xml { render xml: @person.to_xml(include: @company) } + # end + # end + # + # If the client wants HTML, we just redirect them back to the person list. If + # they want JavaScript, then it is an Ajax request and we render the JavaScript + # template associated with this action. Lastly, if the client wants XML, we + # render the created person as XML, but with a twist: we also include the + # person's company in the rendered XML, so you get something like this: + # + # + # ... + # ... + # + # ... + # ... + # ... + # + # + # + # Note, however, the extra bit at the top of that action: + # + # company = params[:person].delete(:company) + # @company = Company.find_or_create_by(name: company[:name]) + # + # This is because the incoming XML document (if a web-service request is in + # process) can only contain a single root-node. So, we have to rearrange things + # so that the request looks like this (url-encoded): + # + # person[name]=...&person[company][name]=...&... + # + # And, like this (xml-encoded): + # + # + # ... + # + # ... + # + # + # + # In other words, we make the request so that it operates on a single entity's + # person. Then, in the action, we extract the company data from the request, + # find or create the company, and then create the new person with the remaining + # data. + # + # Note that you can define your own XML parameter parser which would allow you + # to describe multiple entities in a single request (i.e., by wrapping them all + # in a single root node), but if you just go with the flow and accept Rails' + # defaults, life will be much easier. + # + # If you need to use a MIME type which isn't supported by default, you can + # register your own handlers in `config/initializers/mime_types.rb` as follows. + # + # Mime::Type.register "image/jpeg", :jpg + # + # `respond_to` also allows you to specify a common block for different formats + # by using `any`: + # + # def index + # @people = Person.all + # + # respond_to do |format| + # format.html + # format.any(:xml, :json) { render request.format.to_sym => @people } + # end + # end + # + # In the example above, if the format is xml, it will render: + # + # render xml: @people + # + # Or if the format is json: + # + # render json: @people + # + # `any` can also be used with no arguments, in which case it will be used for + # any format requested by the user: + # + # respond_to do |format| + # format.html + # format.any { redirect_to support_path } + # end + # + # Formats can have different variants. + # + # The request variant is a specialization of the request format, like `:tablet`, + # `:phone`, or `:desktop`. + # + # We often want to render different html/json/xml templates for phones, tablets, + # and desktop browsers. Variants make it easy. + # + # You can set the variant in a `before_action`: + # + # request.variant = :tablet if /iPad/.match?(request.user_agent) + # + # Respond to variants in the action just like you respond to formats: + # + # respond_to do |format| + # format.html do |variant| + # variant.tablet # renders app/views/projects/show.html+tablet.erb + # variant.phone { extra_setup; render ... } + # variant.none { special_setup } # executed only if there is no variant set + # end + # end + # + # Provide separate templates for each format and variant: + # + # app/views/projects/show.html.erb + # app/views/projects/show.html+tablet.erb + # app/views/projects/show.html+phone.erb + # + # When you're not sharing any code within the format, you can simplify defining + # variants using the inline syntax: + # + # respond_to do |format| + # format.js { render "trash" } + # format.html.phone { redirect_to progress_path } + # format.html.none { render "trash" } + # end + # + # Variants also support common `any`/`all` block that formats have. + # + # It works for both inline: + # + # respond_to do |format| + # format.html.any { render html: "any" } + # format.html.phone { render html: "phone" } + # end + # + # and block syntax: + # + # respond_to do |format| + # format.html do |variant| + # variant.any(:tablet, :phablet){ render html: "any" } + # variant.phone { render html: "phone" } + # end + # end + # + # You can also set an array of variants: + # + # request.variant = [:tablet, :phone] + # + # This will work similarly to formats and MIME types negotiation. If there is no + # `:tablet` variant declared, the `:phone` variant will be used: + # + # respond_to do |format| + # format.html.none + # format.html.phone # this gets rendered + # end + def respond_to(*mimes) + raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given? + + collector = Collector.new(mimes, request.variant) + yield collector if block_given? + + if format = collector.negotiate_format(request) + if media_type && media_type != format + raise ActionController::RespondToMismatchError + end + _process_format(format) + _set_rendered_content_type(format) unless collector.any_response? + response = collector.response + response.call if response + else + raise ActionController::UnknownFormat + end + end + + # A container for responses available from the current controller for requests + # for different mime-types sent to a particular action. + # + # The public controller methods `respond_to` may be called with a block that is + # used to define responses to different mime-types, e.g. for `respond_to` : + # + # respond_to do |format| + # format.html + # format.xml { render xml: @people } + # end + # + # In this usage, the argument passed to the block (`format` above) is an + # instance of the ActionController::MimeResponds::Collector class. This object + # serves as a container in which available responses can be stored by calling + # any of the dynamically generated, mime-type-specific methods such as `html`, + # `xml` etc on the Collector. Each response is represented by a corresponding + # block if present. + # + # A subsequent call to #negotiate_format(request) will enable the Collector to + # determine which specific mime-type it should respond with for the current + # request, with this response then being accessible by calling #response. + class Collector + include AbstractController::Collector + attr_accessor :format + + def initialize(mimes, variant = nil) + @responses = {} + @variant = variant + + mimes.each { |mime| @responses[Mime[mime]] = nil } + end + + def any(*args, &block) + if args.any? + args.each { |type| send(type, &block) } + else + custom(Mime::ALL, &block) + end + end + alias :all :any + + def custom(mime_type, &block) + mime_type = Mime::Type.lookup(mime_type.to_s) unless mime_type.is_a?(Mime::Type) + @responses[mime_type] ||= if block_given? + block + else + VariantCollector.new(@variant) + end + end + + def any_response? + !@responses.fetch(format, false) && @responses[Mime::ALL] + end + + def response + response = @responses.fetch(format, @responses[Mime::ALL]) + if response.is_a?(VariantCollector) # `format.html.phone` - variant inline syntax + response.variant + elsif response.nil? || response.arity == 0 # `format.html` - just a format, call its block + response + else # `format.html{ |variant| variant.phone }` - variant block syntax + variant_collector = VariantCollector.new(@variant) + response.call(variant_collector) # call format block with variants collector + variant_collector.variant + end + end + + def negotiate_format(request) + @format = request.negotiate_mime(@responses.keys) + end + + class VariantCollector # :nodoc: + def initialize(variant = nil) + @variant = variant + @variants = {} + end + + def any(*args, &block) + if block_given? + if args.any? && args.none? { |a| a == @variant } + args.each { |v| @variants[v] = block } + else + @variants[:any] = block + end + end + end + alias :all :any + + def method_missing(name, *, &block) + @variants[name] = block if block_given? + end + + def variant + if @variant.empty? + @variants[:none] || @variants[:any] + else + @variants[variant_key] + end + end + + private + def variant_key + @variant.find { |variant| @variants.key?(variant) } || :any + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/parameter_encoding.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/parameter_encoding.rb new file mode 100644 index 00000000..cf570cd9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/parameter_encoding.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + # Specify binary encoding for parameters for a given action. + module ParameterEncoding + extend ActiveSupport::Concern + + module ClassMethods + def inherited(klass) # :nodoc: + super + klass.setup_param_encode + end + + def setup_param_encode # :nodoc: + @_parameter_encodings = Hash.new { |h, k| h[k] = {} } + end + + def action_encoding_template(action) # :nodoc: + if @_parameter_encodings.has_key?(action.to_s) + @_parameter_encodings[action.to_s] + end + end + + # Specify that a given action's parameters should all be encoded as ASCII-8BIT + # (it "skips" the encoding default of UTF-8). + # + # For example, a controller would use it like this: + # + # class RepositoryController < ActionController::Base + # skip_parameter_encoding :show + # + # def show + # @repo = Repository.find_by_filesystem_path params[:file_path] + # + # # `repo_name` is guaranteed to be UTF-8, but was ASCII-8BIT, so + # # tag it as such + # @repo_name = params[:repo_name].force_encoding 'UTF-8' + # end + # + # def index + # @repositories = Repository.all + # end + # end + # + # The show action in the above controller would have all parameter values + # encoded as ASCII-8BIT. This is useful in the case where an application must + # handle data but encoding of the data is unknown, like file system data. + def skip_parameter_encoding(action) + @_parameter_encodings[action.to_s] = Hash.new { Encoding::ASCII_8BIT } + end + + # Specify the encoding for a parameter on an action. If not specified the + # default is UTF-8. + # + # You can specify a binary (ASCII_8BIT) parameter with: + # + # class RepositoryController < ActionController::Base + # # This specifies that file_path is not UTF-8 and is instead ASCII_8BIT + # param_encoding :show, :file_path, Encoding::ASCII_8BIT + # + # def show + # @repo = Repository.find_by_filesystem_path params[:file_path] + # + # # params[:repo_name] remains UTF-8 encoded + # @repo_name = params[:repo_name] + # end + # + # def index + # @repositories = Repository.all + # end + # end + # + # The file_path parameter on the show action would be encoded as ASCII-8BIT, but + # all other arguments will remain UTF-8 encoded. This is useful in the case + # where an application must handle data but encoding of the data is unknown, + # like file system data. + def param_encoding(action, param, encoding) + @_parameter_encodings[action.to_s][param.to_s] = encoding + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/params_wrapper.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/params_wrapper.rb new file mode 100644 index 00000000..d91c624b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/params_wrapper.rb @@ -0,0 +1,312 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/hash/except" +require "active_support/core_ext/module/anonymous" +require "action_dispatch/http/mime_type" + +module ActionController + # # Action Controller Params Wrapper + # + # Wraps the parameters hash into a nested hash. This will allow clients to + # submit requests without having to specify any root elements. + # + # This functionality is enabled by default for JSON, and can be customized by + # setting the format array: + # + # class ApplicationController < ActionController::Base + # wrap_parameters format: [:json, :xml] + # end + # + # You could also turn it on per controller: + # + # class UsersController < ApplicationController + # wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form] + # end + # + # If you enable `ParamsWrapper` for `:json` format, instead of having to send + # JSON parameters like this: + # + # {"user": {"name": "Konata"}} + # + # You can send parameters like this: + # + # {"name": "Konata"} + # + # And it will be wrapped into a nested hash with the key name matching the + # controller's name. For example, if you're posting to `UsersController`, your + # new `params` hash will look like this: + # + # {"name" => "Konata", "user" => {"name" => "Konata"}} + # + # You can also specify the key in which the parameters should be wrapped to, and + # also the list of attributes it should wrap by using either `:include` or + # `:exclude` options like this: + # + # class UsersController < ApplicationController + # wrap_parameters :person, include: [:username, :password] + # end + # + # On Active Record models with no `:include` or `:exclude` option set, it will + # only wrap the parameters returned by the class method `attribute_names`. + # + # If you're going to pass the parameters to an `ActiveModel` object (such as + # `User.new(params[:user])`), you might consider passing the model class to the + # method instead. The `ParamsWrapper` will actually try to determine the list of + # attribute names from the model and only wrap those attributes: + # + # class UsersController < ApplicationController + # wrap_parameters Person + # end + # + # You still could pass `:include` and `:exclude` to set the list of attributes + # you want to wrap. + # + # By default, if you don't specify the key in which the parameters would be + # wrapped to, `ParamsWrapper` will actually try to determine if there's a model + # related to it or not. This controller, for example: + # + # class Admin::UsersController < ApplicationController + # end + # + # will try to check if `Admin::User` or `User` model exists, and use it to + # determine the wrapper key respectively. If both models don't exist, it will + # then fall back to use `user` as the key. + # + # To disable this functionality for a controller: + # + # class UsersController < ApplicationController + # wrap_parameters false + # end + module ParamsWrapper + extend ActiveSupport::Concern + + EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8) + + class Options < Struct.new(:name, :format, :include, :exclude, :klass, :model) # :nodoc: + def self.from_hash(hash) + name = hash[:name] + format = Array(hash[:format]) + include = hash[:include] && Array(hash[:include]).collect(&:to_s) + exclude = hash[:exclude] && Array(hash[:exclude]).collect(&:to_s) + new name, format, include, exclude, nil, nil + end + + def initialize(name, format, include, exclude, klass, model) # :nodoc: + super + @mutex = Mutex.new + @include_set = include + @name_set = name + end + + def model + super || self.model = _default_wrap_model + end + + def include + return super if @include_set + + m = model + @mutex.synchronize do + return super if @include_set + + @include_set = true + + unless super || exclude + if m.respond_to?(:attribute_names) && m.attribute_names.any? + self.include = m.attribute_names + + if m.respond_to?(:stored_attributes) && !m.stored_attributes.empty? + self.include += m.stored_attributes.values.flatten.map(&:to_s) + end + + if m.respond_to?(:attribute_aliases) && m.attribute_aliases.any? + self.include += m.attribute_aliases.keys + end + + if m.respond_to?(:nested_attributes_options) && m.nested_attributes_options.keys.any? + self.include += m.nested_attributes_options.keys.map do |key| + (+key.to_s).concat("_attributes") + end + end + + self.include + end + end + end + end + + def name + return super if @name_set + + m = model + @mutex.synchronize do + return super if @name_set + + @name_set = true + + unless super || klass.anonymous? + self.name = m ? m.to_s.demodulize.underscore : + klass.controller_name.singularize + end + end + end + + private + # Determine the wrapper model from the controller's name. By convention, this + # could be done by trying to find the defined model that has the same singular + # name as the controller. For example, `UsersController` will try to find if the + # `User` model exists. + # + # This method also does namespace lookup. Foo::Bar::UsersController will try to + # find Foo::Bar::User, Foo::User and finally User. + def _default_wrap_model + return nil if klass.anonymous? + model_name = klass.name.delete_suffix("Controller").classify + + begin + if model_klass = model_name.safe_constantize + model_klass + else + namespaces = model_name.split("::") + namespaces.delete_at(-2) + break if namespaces.last == model_name + model_name = namespaces.join("::") + end + end until model_klass + + model_klass + end + end + + included do + class_attribute :_wrapper_options, default: Options.from_hash(format: []) + end + + module ClassMethods + def _set_wrapper_options(options) + self._wrapper_options = Options.from_hash(options) + end + + # Sets the name of the wrapper key, or the model which `ParamsWrapper` would use + # to determine the attribute names from. + # + # #### Examples + # wrap_parameters format: :xml + # # enables the parameter wrapper for XML format + # + # wrap_parameters :person + # # wraps parameters into params[:person] hash + # + # wrap_parameters Person + # # wraps parameters by determining the wrapper key from Person class + # # (:person, in this case) and the list of attribute names + # + # wrap_parameters include: [:username, :title] + # # wraps only :username and :title attributes from parameters. + # + # wrap_parameters false + # # disables parameters wrapping for this controller altogether. + # + # #### Options + # * `:format` - The list of formats in which the parameters wrapper will be + # enabled. + # * `:include` - The list of attribute names which parameters wrapper will + # wrap into a nested hash. + # * `:exclude` - The list of attribute names which parameters wrapper will + # exclude from a nested hash. + # + def wrap_parameters(name_or_model_or_options, options = {}) + model = nil + + case name_or_model_or_options + when Hash + options = name_or_model_or_options + when false + options = options.merge(format: []) + when Symbol, String + options = options.merge(name: name_or_model_or_options) + else + model = name_or_model_or_options + end + + opts = Options.from_hash _wrapper_options.to_h.slice(:format).merge(options) + opts.model = model + opts.klass = self + + self._wrapper_options = opts + end + + # Sets the default wrapper key or model which will be used to determine wrapper + # key and attribute names. Called automatically when the module is inherited. + def inherited(klass) + if klass._wrapper_options.format.any? + params = klass._wrapper_options.dup + params.klass = klass + klass._wrapper_options = params + end + super + end + end + + private + # Performs parameters wrapping upon the request. Called automatically by the + # metal call stack. + def process_action(*) + _perform_parameter_wrapping if _wrapper_enabled? + super + end + + # Returns the wrapper key which will be used to store wrapped parameters. + def _wrapper_key + _wrapper_options.name + end + + # Returns the list of enabled formats. + def _wrapper_formats + _wrapper_options.format + end + + # Returns the list of parameters which will be selected for wrapped. + def _wrap_parameters(parameters) + { _wrapper_key => _extract_parameters(parameters) } + end + + def _extract_parameters(parameters) + if include_only = _wrapper_options.include + parameters.slice(*include_only) + elsif _wrapper_options.exclude + exclude = _wrapper_options.exclude + EXCLUDE_PARAMETERS + parameters.except(*exclude) + else + parameters.except(*EXCLUDE_PARAMETERS) + end + end + + # Checks if we should perform parameters wrapping. + def _wrapper_enabled? + return false unless request.has_content_type? + + ref = request.content_mime_type.ref + + _wrapper_formats.include?(ref) && _wrapper_key && !request.parameters.key?(_wrapper_key) + rescue ActionDispatch::Http::Parameters::ParseError + false + end + + def _perform_parameter_wrapping + wrapped_hash = _wrap_parameters request.request_parameters + wrapped_keys = request.request_parameters.keys + wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys) + + # This will make the wrapped hash accessible from controller and view. + request.parameters.merge! wrapped_hash + request.request_parameters.merge! wrapped_hash + + # This will display the wrapped hash in the log file. + request.filtered_parameters.merge! wrapped_filtered_hash + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/permissions_policy.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/permissions_policy.rb new file mode 100644 index 00000000..9e1e4a45 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/permissions_policy.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController # :nodoc: + module PermissionsPolicy + extend ActiveSupport::Concern + + module ClassMethods + # Overrides parts of the globally configured `Feature-Policy` header: + # + # class PagesController < ApplicationController + # permissions_policy do |policy| + # policy.geolocation "https://example.com" + # end + # end + # + # Options can be passed similar to `before_action`. For example, pass `only: + # :index` to override the header on the index action only: + # + # class PagesController < ApplicationController + # permissions_policy(only: :index) do |policy| + # policy.camera :self + # end + # end + # + def permissions_policy(**options, &block) + before_action(options) do + if block_given? + policy = request.permissions_policy.clone + instance_exec(policy, &block) + request.permissions_policy = policy + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/rate_limiting.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/rate_limiting.rb new file mode 100644 index 00000000..33940664 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/rate_limiting.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController # :nodoc: + module RateLimiting + extend ActiveSupport::Concern + + module ClassMethods + # Applies a rate limit to all actions or those specified by the normal + # `before_action` filters with `only:` and `except:`. + # + # The maximum number of requests allowed is specified `to:` and constrained to + # the window of time given by `within:`. + # + # Rate limits are by default unique to the ip address making the request, but + # you can provide your own identity function by passing a callable in the `by:` + # parameter. It's evaluated within the context of the controller processing the + # request. + # + # Requests that exceed the rate limit are refused with a `429 Too Many Requests` + # response. You can specialize this by passing a callable in the `with:` + # parameter. It's evaluated within the context of the controller processing the + # request. + # + # Rate limiting relies on a backing `ActiveSupport::Cache` store and defaults to + # `config.action_controller.cache_store`, which itself defaults to the global + # `config.cache_store`. If you don't want to store rate limits in the same + # datastore as your general caches, you can pass a custom store in the `store` + # parameter. + # + # If you want to use multiple rate limits per controller, you need to give each of + # them an explicit name via the `name:` option. + # + # Examples: + # + # class SessionsController < ApplicationController + # rate_limit to: 10, within: 3.minutes, only: :create + # end + # + # class SignupsController < ApplicationController + # rate_limit to: 1000, within: 10.seconds, + # by: -> { request.domain }, with: -> { redirect_to busy_controller_url, alert: "Too many signups on domain!" }, only: :new + # end + # + # class APIController < ApplicationController + # RATE_LIMIT_STORE = ActiveSupport::Cache::RedisCacheStore.new(url: ENV["REDIS_URL"]) + # rate_limit to: 10, within: 3.minutes, store: RATE_LIMIT_STORE + # end + # + # class SessionsController < ApplicationController + # rate_limit to: 3, within: 2.seconds, name: "short-term" + # rate_limit to: 10, within: 5.minutes, name: "long-term" + # end + def rate_limit(to:, within:, by: -> { request.remote_ip }, with: -> { head :too_many_requests }, store: cache_store, name: nil, **options) + before_action -> { rate_limiting(to: to, within: within, by: by, with: with, store: store, name: name) }, **options + end + end + + private + def rate_limiting(to:, within:, by:, with:, store:, name:) + cache_key = ["rate-limit", controller_path, name, instance_exec(&by)].compact.join(":") + count = store.increment(cache_key, 1, expires_in: within) + if count && count > to + ActiveSupport::Notifications.instrument("rate_limit.action_controller", request: request) do + instance_exec(&with) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/redirecting.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/redirecting.rb new file mode 100644 index 00000000..68241cdb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/redirecting.rb @@ -0,0 +1,252 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + module Redirecting + extend ActiveSupport::Concern + + include AbstractController::Logger + include ActionController::UrlFor + + class UnsafeRedirectError < StandardError; end + + ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/ + + included do + mattr_accessor :raise_on_open_redirects, default: false + end + + # Redirects the browser to the target specified in `options`. This parameter can + # be any one of: + # + # * `Hash` - The URL will be generated by calling url_for with the `options`. + # * `Record` - The URL will be generated by calling url_for with the + # `options`, which will reference a named URL for that record. + # * `String` starting with `protocol://` (like `http://`) or a protocol + # relative reference (like `//`) - Is passed straight through as the target + # for redirection. + # * `String` not containing a protocol - The current protocol and host is + # prepended to the string. + # * `Proc` - A block that will be executed in the controller's context. Should + # return any option accepted by `redirect_to`. + # + # + # ### Examples + # + # redirect_to action: "show", id: 5 + # redirect_to @post + # redirect_to "http://www.rubyonrails.org" + # redirect_to "/images/screenshot.jpg" + # redirect_to posts_url + # redirect_to proc { edit_post_url(@post) } + # + # The redirection happens as a `302 Found` header unless otherwise specified + # using the `:status` option: + # + # redirect_to post_url(@post), status: :found + # redirect_to action: 'atom', status: :moved_permanently + # redirect_to post_url(@post), status: 301 + # redirect_to action: 'atom', status: 302 + # + # The status code can either be a standard [HTTP Status + # code](https://www.iana.org/assignments/http-status-codes) as an integer, or a + # symbol representing the downcased, underscored and symbolized description. + # Note that the status code must be a 3xx HTTP code, or redirection will not + # occur. + # + # If you are using XHR requests other than GET or POST and redirecting after the + # request then some browsers will follow the redirect using the original request + # method. This may lead to undesirable behavior such as a double DELETE. To work + # around this you can return a `303 See Other` status code which will be + # followed using a GET request. + # + # redirect_to posts_url, status: :see_other + # redirect_to action: 'index', status: 303 + # + # It is also possible to assign a flash message as part of the redirection. + # There are two special accessors for the commonly used flash names `alert` and + # `notice` as well as a general purpose `flash` bucket. + # + # redirect_to post_url(@post), alert: "Watch it, mister!" + # redirect_to post_url(@post), status: :found, notice: "Pay attention to the road" + # redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id } + # redirect_to({ action: 'atom' }, alert: "Something serious happened") + # + # Statements after `redirect_to` in our controller get executed, so + # `redirect_to` doesn't stop the execution of the function. To terminate the + # execution of the function immediately after the `redirect_to`, use return. + # + # redirect_to post_url(@post) and return + # + # ### Open Redirect protection + # + # By default, Rails protects against redirecting to external hosts for your + # app's safety, so called open redirects. Note: this was a new default in Rails + # 7.0, after upgrading opt-in by uncommenting the line with + # `raise_on_open_redirects` in + # `config/initializers/new_framework_defaults_7_0.rb` + # + # Here #redirect_to automatically validates the potentially-unsafe URL: + # + # redirect_to params[:redirect_url] + # + # Raises UnsafeRedirectError in the case of an unsafe redirect. + # + # To allow any external redirects pass `allow_other_host: true`, though using a + # user-provided param in that case is unsafe. + # + # redirect_to "https://rubyonrails.org", allow_other_host: true + # + # See #url_from for more information on what an internal and safe URL is, or how + # to fall back to an alternate redirect URL in the unsafe case. + def redirect_to(options = {}, response_options = {}) + raise ActionControllerError.new("Cannot redirect to nil!") unless options + raise AbstractController::DoubleRenderError if response_body + + allow_other_host = response_options.delete(:allow_other_host) { _allow_other_host } + + proposed_status = _extract_redirect_to_status(options, response_options) + + redirect_to_location = _compute_redirect_to_location(request, options) + _ensure_url_is_http_header_safe(redirect_to_location) + + self.location = _enforce_open_redirect_protection(redirect_to_location, allow_other_host: allow_other_host) + self.response_body = "" + self.status = proposed_status + end + + # Soft deprecated alias for #redirect_back_or_to where the `fallback_location` + # location is supplied as a keyword argument instead of the first positional + # argument. + def redirect_back(fallback_location:, allow_other_host: _allow_other_host, **args) + redirect_back_or_to fallback_location, allow_other_host: allow_other_host, **args + end + + # Redirects the browser to the page that issued the request (the referrer) if + # possible, otherwise redirects to the provided default fallback location. + # + # The referrer information is pulled from the HTTP `Referer` (sic) header on the + # request. This is an optional header and its presence on the request is subject + # to browser security settings and user preferences. If the request is missing + # this header, the `fallback_location` will be used. + # + # redirect_back_or_to({ action: "show", id: 5 }) + # redirect_back_or_to @post + # redirect_back_or_to "http://www.rubyonrails.org" + # redirect_back_or_to "/images/screenshot.jpg" + # redirect_back_or_to posts_url + # redirect_back_or_to proc { edit_post_url(@post) } + # redirect_back_or_to '/', allow_other_host: false + # + # #### Options + # * `:allow_other_host` - Allow or disallow redirection to the host that is + # different to the current host, defaults to true. + # + # + # All other options that can be passed to #redirect_to are accepted as options, + # and the behavior is identical. + def redirect_back_or_to(fallback_location, allow_other_host: _allow_other_host, **options) + if request.referer && (allow_other_host || _url_host_allowed?(request.referer)) + redirect_to request.referer, allow_other_host: allow_other_host, **options + else + # The method level `allow_other_host` doesn't apply in the fallback case, omit + # and let the `redirect_to` handling take over. + redirect_to fallback_location, **options + end + end + + def _compute_redirect_to_location(request, options) # :nodoc: + case options + # The scheme name consist of a letter followed by any combination of letters, + # digits, and the plus ("+"), period ("."), or hyphen ("-") characters; and is + # terminated by a colon (":"). See + # https://tools.ietf.org/html/rfc3986#section-3.1 The protocol relative scheme + # starts with a double slash "//". + when /\A([a-z][a-z\d\-+.]*:|\/\/).*/i + options.to_str + when String + request.protocol + request.host_with_port + options + when Proc + _compute_redirect_to_location request, instance_eval(&options) + else + url_for(options) + end.delete("\0\r\n") + end + module_function :_compute_redirect_to_location + public :_compute_redirect_to_location + + # Verifies the passed `location` is an internal URL that's safe to redirect to + # and returns it, or nil if not. Useful to wrap a params provided redirect URL + # and fall back to an alternate URL to redirect to: + # + # redirect_to url_from(params[:redirect_url]) || root_url + # + # The `location` is considered internal, and safe, if it's on the same host as + # `request.host`: + # + # # If request.host is example.com: + # url_from("https://example.com/profile") # => "https://example.com/profile" + # url_from("http://example.com/profile") # => "http://example.com/profile" + # url_from("http://evil.com/profile") # => nil + # + # Subdomains are considered part of the host: + # + # # If request.host is on https://example.com or https://app.example.com, you'd get: + # url_from("https://dev.example.com/profile") # => nil + # + # NOTE: there's a similarity with + # [url_for](rdoc-ref:ActionDispatch::Routing::UrlFor#url_for), which generates + # an internal URL from various options from within the app, e.g. + # `url_for(@post)`. However, #url_from is meant to take an external parameter to + # verify as in `url_from(params[:redirect_url])`. + def url_from(location) + location = location.presence + location if location && _url_host_allowed?(location) + end + + private + def _allow_other_host + !raise_on_open_redirects + end + + def _extract_redirect_to_status(options, response_options) + if options.is_a?(Hash) && options.key?(:status) + Rack::Utils.status_code(options.delete(:status)) + elsif response_options.key?(:status) + Rack::Utils.status_code(response_options[:status]) + else + 302 + end + end + + def _enforce_open_redirect_protection(location, allow_other_host:) + if allow_other_host || _url_host_allowed?(location) + location + else + raise UnsafeRedirectError, "Unsafe redirect to #{location.truncate(100).inspect}, pass allow_other_host: true to redirect anyway." + end + end + + def _url_host_allowed?(url) + host = URI(url.to_s).host + + return true if host == request.host + return false unless host.nil? + return false unless url.to_s.start_with?("/") + !url.to_s.start_with?("//") + rescue ArgumentError, URI::Error + false + end + + def _ensure_url_is_http_header_safe(url) + # Attempt to comply with the set of valid token characters defined for an HTTP + # header value in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6 + if url.match?(ILLEGAL_HEADER_VALUE_REGEX) + msg = "The redirect URL #{url} contains one or more illegal HTTP header field character. " \ + "Set of legal characters defined in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6" + raise UnsafeRedirectError, msg + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/renderers.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/renderers.rb new file mode 100644 index 00000000..5bd7bca4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/renderers.rb @@ -0,0 +1,180 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + # See Renderers.add + def self.add_renderer(key, &block) + Renderers.add(key, &block) + end + + # See Renderers.remove + def self.remove_renderer(key) + Renderers.remove(key) + end + + # See `Responder#api_behavior` + class MissingRenderer < LoadError + def initialize(format) + super "No renderer defined for format: #{format}" + end + end + + module Renderers + extend ActiveSupport::Concern + + # A Set containing renderer names that correspond to available renderer procs. + # Default values are `:json`, `:js`, `:xml`. + RENDERERS = Set.new + + included do + class_attribute :_renderers, default: Set.new.freeze + end + + # Used in ActionController::Base and ActionController::API to include all + # renderers by default. + module All + extend ActiveSupport::Concern + include Renderers + + included do + self._renderers = RENDERERS + end + end + + # Adds a new renderer to call within controller actions. A renderer is invoked + # by passing its name as an option to AbstractController::Rendering#render. To + # create a renderer pass it a name and a block. The block takes two arguments, + # the first is the value paired with its key and the second is the remaining + # hash of options passed to `render`. + # + # Create a csv renderer: + # + # ActionController::Renderers.add :csv do |obj, options| + # filename = options[:filename] || 'data' + # str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s + # send_data str, type: Mime[:csv], + # disposition: "attachment; filename=#{filename}.csv" + # end + # + # Note that we used [Mime](:csv) for the csv mime type as it comes with Rails. + # For a custom renderer, you'll need to register a mime type with + # `Mime::Type.register`. + # + # To use the csv renderer in a controller action: + # + # def show + # @csvable = Csvable.find(params[:id]) + # respond_to do |format| + # format.html + # format.csv { render csv: @csvable, filename: @csvable.name } + # end + # end + def self.add(key, &block) + define_method(_render_with_renderer_method_name(key), &block) + RENDERERS << key.to_sym + end + + # This method is the opposite of add method. + # + # To remove a csv renderer: + # + # ActionController::Renderers.remove(:csv) + def self.remove(key) + RENDERERS.delete(key.to_sym) + method_name = _render_with_renderer_method_name(key) + remove_possible_method(method_name) + end + + def self._render_with_renderer_method_name(key) + "_render_with_renderer_#{key}" + end + + module ClassMethods + # Adds, by name, a renderer or renderers to the `_renderers` available to call + # within controller actions. + # + # It is useful when rendering from an ActionController::Metal controller or + # otherwise to add an available renderer proc to a specific controller. + # + # Both ActionController::Base and ActionController::API include + # ActionController::Renderers::All, making all renderers available in the + # controller. See Renderers::RENDERERS and Renderers.add. + # + # Since ActionController::Metal controllers cannot render, the controller must + # include AbstractController::Rendering, ActionController::Rendering, and + # ActionController::Renderers, and have at least one renderer. + # + # Rather than including ActionController::Renderers::All and including all + # renderers, you may specify which renderers to include by passing the renderer + # name or names to `use_renderers`. For example, a controller that includes only + # the `:json` renderer (`_render_with_renderer_json`) might look like: + # + # class MetalRenderingController < ActionController::Metal + # include AbstractController::Rendering + # include ActionController::Rendering + # include ActionController::Renderers + # + # use_renderers :json + # + # def show + # render json: record + # end + # end + # + # You must specify a `use_renderer`, else the `controller.renderer` and + # `controller._renderers` will be `nil`, and the action will fail. + def use_renderers(*args) + renderers = _renderers + args + self._renderers = renderers.freeze + end + alias use_renderer use_renderers + end + + # Called by `render` in AbstractController::Rendering which sets the return + # value as the `response_body`. + # + # If no renderer is found, `super` returns control to + # `ActionView::Rendering.render_to_body`, if present. + def render_to_body(options) + _render_to_body_with_renderer(options) || super + end + + def _render_to_body_with_renderer(options) + _renderers.each do |name| + if options.key?(name) + _process_options(options) + method_name = Renderers._render_with_renderer_method_name(name) + return send(method_name, options.delete(name), options) + end + end + nil + end + + add :json do |json, options| + json_options = options.except(:callback, :content_type, :status) + json = json.to_json(json_options) unless json.kind_of?(String) + + if options[:callback].present? + if media_type.nil? || media_type == Mime[:json] + self.content_type = Mime[:js] + end + + "/**/#{options[:callback]}(#{json})" + else + self.content_type = Mime[:json] if media_type.nil? + json + end + end + + add :js do |js, options| + self.content_type = Mime[:js] if media_type.nil? + js.respond_to?(:to_js) ? js.to_js(options) : js + end + + add :xml do |xml, options| + self.content_type = Mime[:xml] if media_type.nil? + xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/rendering.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/rendering.rb new file mode 100644 index 00000000..a411c7e1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/rendering.rb @@ -0,0 +1,260 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + module Rendering + extend ActiveSupport::Concern + + RENDER_FORMATS_IN_PRIORITY = [:body, :plain, :html] + + module ClassMethods + # Documentation at ActionController::Renderer#render + delegate :render, to: :renderer + + # Returns a renderer instance (inherited from ActionController::Renderer) for + # the controller. + attr_reader :renderer + + def setup_renderer! # :nodoc: + @renderer = Renderer.for(self) + end + + def inherited(klass) + klass.setup_renderer! + super + end + end + + # Renders a template and assigns the result to `self.response_body`. + # + # If no rendering mode option is specified, the template will be derived from + # the first argument. + # + # render "posts/show" + # # => renders app/views/posts/show.html.erb + # + # # In a PostsController action... + # render :show + # # => renders app/views/posts/show.html.erb + # + # If the first argument responds to `render_in`, the template will be rendered + # by calling `render_in` with the current view context. + # + # class Greeting + # def render_in(view_context) + # view_context.render html: "

Hello, World

" + # end + # + # def format + # :html + # end + # end + # + # render(Greeting.new) + # # => "

Hello, World

" + # + # render(renderable: Greeting.new) + # # => "

Hello, World

" + # + # #### Rendering Mode + # + # `:partial` + # : See ActionView::PartialRenderer for details. + # + # render partial: "posts/form", locals: { post: Post.new } + # # => renders app/views/posts/_form.html.erb + # + # `:file` + # : Renders the contents of a file. This option should **not** be used with + # unsanitized user input. + # + # render file: "/path/to/some/file" + # # => renders /path/to/some/file + # + # `:inline` + # : Renders an ERB template string. + # + # @name = "World" + # render inline: "

Hello, <%= @name %>!

" + # # => renders "

Hello, World!

" + # + # `:body` + # : Renders the provided text, and sets the content type as `text/plain`. + # + # render body: "Hello, World!" + # # => renders "Hello, World!" + # + # `:plain` + # : Renders the provided text, and sets the content type as `text/plain`. + # + # render plain: "Hello, World!" + # # => renders "Hello, World!" + # + # `:html` + # : Renders the provided HTML string, and sets the content type as + # `text/html`. If the string is not `html_safe?`, performs HTML escaping on + # the string before rendering. + # + # render html: "

Hello, World!

".html_safe + # # => renders "

Hello, World!

" + # + # render html: "

Hello, World!

" + # # => renders "<h1>Hello, World!</h1>" + # + # `:json` + # : Renders the provided object as JSON, and sets the content type as + # `application/json`. If the object is not a string, it will be converted to + # JSON by calling `to_json`. + # + # render json: { hello: "world" } + # # => renders "{\"hello\":\"world\"}" + # + # `:renderable` + # : Renders the provided object by calling `render_in` with the current view + # context. The response format is determined by calling `format` on the + # renderable if it responds to `format`, falling back to `text/html` by + # default. + # + # render renderable: Greeting.new + # # => renders "

Hello, World

" + # + # + # By default, when a rendering mode is specified, no layout template is + # rendered. + # + # #### Options + # + # `:assigns` + # : Hash of instance variable assignments for the template. + # + # render inline: "

Hello, <%= @name %>!

", assigns: { name: "World" } + # # => renders "

Hello, World!

" + # + # `:locals` + # : Hash of local variable assignments for the template. + # + # render inline: "

Hello, <%= name %>!

", locals: { name: "World" } + # # => renders "

Hello, World!

" + # + # `:layout` + # : The layout template to render. Can also be `false` or `true` to disable or + # (re)enable the default layout template. + # + # render "posts/show", layout: "holiday" + # # => renders app/views/posts/show.html.erb with the app/views/layouts/holiday.html.erb layout + # + # render "posts/show", layout: false + # # => renders app/views/posts/show.html.erb with no layout + # + # render inline: "

Hello, World!

", layout: true + # # => renders "

Hello, World!

" with the default layout + # + # `:status` + # : The HTTP status code to send with the response. Can be specified as a + # number or as the status name in Symbol form. Defaults to 200. + # + # render "posts/new", status: 422 + # # => renders app/views/posts/new.html.erb with HTTP status code 422 + # + # render "posts/new", status: :unprocessable_entity + # # => renders app/views/posts/new.html.erb with HTTP status code 422 + # + #-- + # Check for double render errors and set the content_type after rendering. + def render(*args) + raise ::AbstractController::DoubleRenderError if response_body + super + end + + # Similar to #render, but only returns the rendered template as a string, + # instead of setting `self.response_body`. + #-- + # Override render_to_string because body can now be set to a Rack body. + def render_to_string(*) + result = super + if result.respond_to?(:each) + string = +"" + result.each { |r| string << r } + string + else + result + end + end + + def render_to_body(options = {}) # :nodoc: + super || _render_in_priorities(options) || " " + end + + private + # Before processing, set the request formats in current controller formats. + def process_action(*) # :nodoc: + self.formats = request.formats.filter_map(&:ref) + super + end + + def _process_variant(options) + if defined?(request) && !request.nil? && request.variant.present? + options[:variant] = request.variant + end + end + + def _render_in_priorities(options) + RENDER_FORMATS_IN_PRIORITY.each do |format| + return options[format] if options.key?(format) + end + + nil + end + + def _set_html_content_type + self.content_type = Mime[:html].to_s + end + + def _set_rendered_content_type(format) + if format && !response.media_type + self.content_type = format.to_s + end + end + + def _set_vary_header + if response.headers["Vary"].blank? && request.should_apply_vary_header? + response.headers["Vary"] = "Accept" + end + end + + # Normalize both text and status options. + def _normalize_options(options) + _normalize_text(options) + + if options[:html] + options[:html] = ERB::Util.html_escape(options[:html]) + end + + if options[:status] + options[:status] = Rack::Utils.status_code(options[:status]) + end + + super + end + + def _normalize_text(options) + RENDER_FORMATS_IN_PRIORITY.each do |format| + if options.key?(format) && options[format].respond_to?(:to_text) + options[format] = options[format].to_text + end + end + end + + # Process controller specific options, as status, content-type and location. + def _process_options(options) + status, content_type, location = options.values_at(:status, :content_type, :location) + + self.status = status if status + self.content_type = content_type if content_type + headers["Location"] = url_for(location) if location + + super + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/request_forgery_protection.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/request_forgery_protection.rb new file mode 100644 index 00000000..93ae1d4a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/request_forgery_protection.rb @@ -0,0 +1,669 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "rack/session/abstract/id" +require "action_controller/metal/exceptions" +require "active_support/security_utils" + +module ActionController # :nodoc: + class InvalidAuthenticityToken < ActionControllerError # :nodoc: + end + + class InvalidCrossOriginRequest < ActionControllerError # :nodoc: + end + + # # Action Controller Request Forgery Protection + # + # Controller actions are protected from Cross-Site Request Forgery (CSRF) + # attacks by including a token in the rendered HTML for your application. This + # token is stored as a random string in the session, to which an attacker does + # not have access. When a request reaches your application, Rails verifies the + # received token with the token in the session. All requests are checked except + # GET requests as these should be idempotent. Keep in mind that all + # session-oriented requests are CSRF protected by default, including JavaScript + # and HTML requests. + # + # Since HTML and JavaScript requests are typically made from the browser, we + # need to ensure to verify request authenticity for the web browser. We can use + # session-oriented authentication for these types of requests, by using the + # `protect_from_forgery` method in our controllers. + # + # GET requests are not protected since they don't have side effects like writing + # to the database and don't leak sensitive information. JavaScript requests are + # an exception: a third-party site can use a + # + # The first two characters (`">`) are required in case the exception happens + # while rendering attributes for a given tag. You can check the real cause for + # the exception in your logger. + # + # ## Web server support + # + # Rack 3+ compatible servers all support streaming. + module Streaming + private + # Call render_body if we are streaming instead of usual `render`. + def _render_template(options) + if options.delete(:stream) + # It shouldn't be necessary to set this. + headers["cache-control"] ||= "no-cache" + + view_renderer.render_body(view_context, options) + else + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/strong_parameters.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/strong_parameters.rb new file mode 100644 index 00000000..e81f82a9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/strong_parameters.rb @@ -0,0 +1,1530 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/core_ext/hash/indifferent_access" +require "active_support/core_ext/array/wrap" +require "active_support/core_ext/string/filters" +require "active_support/core_ext/object/to_query" +require "active_support/deep_mergeable" +require "action_dispatch/http/upload" +require "rack/test" +require "stringio" +require "yaml" + +module ActionController + # Raised when a required parameter is missing. + # + # params = ActionController::Parameters.new(a: {}) + # params.fetch(:b) + # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: b + # params.require(:a) + # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: a + # params.expect(a: []) + # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: a + class ParameterMissing < KeyError + attr_reader :param, :keys # :nodoc: + + def initialize(param, keys = nil) # :nodoc: + @param = param + @keys = keys + super("param is missing or the value is empty or invalid: #{param}") + end + + if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker) + include DidYouMean::Correctable # :nodoc: + + def corrections # :nodoc: + @corrections ||= DidYouMean::SpellChecker.new(dictionary: keys).correct(param.to_s) + end + end + end + + # Raised from `expect!` when an expected parameter is missing or is of an + # incompatible type. + # + # params = ActionController::Parameters.new(a: {}) + # params.expect!(:a) + # # => ActionController::ExpectedParameterMissing: param is missing or the value is empty or invalid: a + class ExpectedParameterMissing < ParameterMissing + end + + # Raised when a supplied parameter is not expected and + # ActionController::Parameters.action_on_unpermitted_parameters is set to + # `:raise`. + # + # params = ActionController::Parameters.new(a: "123", b: "456") + # params.permit(:c) + # # => ActionController::UnpermittedParameters: found unpermitted parameters: :a, :b + class UnpermittedParameters < IndexError + attr_reader :params # :nodoc: + + def initialize(params) # :nodoc: + @params = params + super("found unpermitted parameter#{'s' if params.size > 1 }: #{params.map { |e| ":#{e}" }.join(", ")}") + end + end + + # Raised when a Parameters instance is not marked as permitted and an operation + # to transform it to hash is called. + # + # params = ActionController::Parameters.new(a: "123", b: "456") + # params.to_h + # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash + class UnfilteredParameters < ArgumentError + def initialize # :nodoc: + super("unable to convert unpermitted parameters to hash") + end + end + + # Raised when initializing Parameters with keys that aren't strings or symbols. + # + # ActionController::Parameters.new(123 => 456) + # # => ActionController::InvalidParameterKey: all keys must be Strings or Symbols, got: Integer + class InvalidParameterKey < ArgumentError + end + + # # Action Controller Parameters + # + # Allows you to choose which attributes should be permitted for mass updating + # and thus prevent accidentally exposing that which shouldn't be exposed. + # + # Provides methods for filtering and requiring params: + # + # * `expect` to safely permit and require parameters in one step. + # * `permit` to filter params for mass assignment. + # * `require` to require a parameter or raise an error. + # + # Examples: + # + # params = ActionController::Parameters.new({ + # person: { + # name: "Francesco", + # age: 22, + # role: "admin" + # } + # }) + # + # permitted = params.expect(person: [:name, :age]) + # permitted # => #"Francesco", "age"=>22} permitted: true> + # + # Person.first.update!(permitted) + # # => # + # + # Parameters provides two options that control the top-level behavior of new + # instances: + # + # * `permit_all_parameters` - If it's `true`, all the parameters will be + # permitted by default. The default is `false`. + # * `action_on_unpermitted_parameters` - Controls behavior when parameters + # that are not explicitly permitted are found. The default value is `:log` + # in test and development environments, `false` otherwise. The values can + # be: + # * `false` to take no action. + # * `:log` to emit an `ActiveSupport::Notifications.instrument` event on + # the `unpermitted_parameters.action_controller` topic and log at the + # DEBUG level. + # * `:raise` to raise an ActionController::UnpermittedParameters + # exception. + # + # Examples: + # + # params = ActionController::Parameters.new + # params.permitted? # => false + # + # ActionController::Parameters.permit_all_parameters = true + # + # params = ActionController::Parameters.new + # params.permitted? # => true + # + # params = ActionController::Parameters.new(a: "123", b: "456") + # params.permit(:c) + # # => # + # + # ActionController::Parameters.action_on_unpermitted_parameters = :raise + # + # params = ActionController::Parameters.new(a: "123", b: "456") + # params.permit(:c) + # # => ActionController::UnpermittedParameters: found unpermitted keys: a, b + # + # Please note that these options *are not thread-safe*. In a multi-threaded + # environment they should only be set once at boot-time and never mutated at + # runtime. + # + # You can fetch values of `ActionController::Parameters` using either `:key` or + # `"key"`. + # + # params = ActionController::Parameters.new(key: "value") + # params[:key] # => "value" + # params["key"] # => "value" + class Parameters + include ActiveSupport::DeepMergeable + + cattr_accessor :permit_all_parameters, instance_accessor: false, default: false + + cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false + + ## + # :method: deep_merge + # + # :call-seq: + # deep_merge(other_hash, &block) + # + # Returns a new `ActionController::Parameters` instance with `self` and + # `other_hash` merged recursively. + # + # Like with `Hash#merge` in the standard library, a block can be provided to + # merge values. + # + #-- + # Implemented by ActiveSupport::DeepMergeable#deep_merge. + + ## + # :method: deep_merge! + # + # :call-seq: + # deep_merge!(other_hash, &block) + # + # Same as `#deep_merge`, but modifies `self`. + # + #-- + # Implemented by ActiveSupport::DeepMergeable#deep_merge!. + + ## + # :method: as_json + # + # :call-seq: + # as_json(options=nil) + # + # Returns a hash that can be used as the JSON representation for the parameters. + + ## + # :method: each_key + # + # :call-seq: + # each_key(&block) + # + # Calls block once for each key in the parameters, passing the key. If no block + # is given, an enumerator is returned instead. + + ## + # :method: empty? + # + # :call-seq: + # empty?() + # + # Returns true if the parameters have no key/value pairs. + + ## + # :method: exclude? + # + # :call-seq: + # exclude?(key) + # + # Returns true if the given key is not present in the parameters. + + ## + # :method: include? + # + # :call-seq: + # include?(key) + # + # Returns true if the given key is present in the parameters. + + ## + # :method: keys + # + # :call-seq: + # keys() + # + # Returns a new array of the keys of the parameters. + + ## + # :method: to_s + # + # :call-seq: + # to_s() + # + # Returns the content of the parameters as a string. + + delegate :keys, :empty?, :exclude?, :include?, + :as_json, :to_s, :each_key, to: :@parameters + + alias_method :has_key?, :include? + alias_method :key?, :include? + alias_method :member?, :include? + + # By default, never raise an UnpermittedParameters exception if these params are + # present. The default includes both 'controller' and 'action' because they are + # added by Rails and should be of no concern. One way to change these is to + # specify `always_permitted_parameters` in your config. For instance: + # + # config.action_controller.always_permitted_parameters = %w( controller action format ) + cattr_accessor :always_permitted_parameters, default: %w( controller action ) + + class << self + def nested_attribute?(key, value) # :nodoc: + /\A-?\d+\z/.match?(key) && (value.is_a?(Hash) || value.is_a?(Parameters)) + end + end + + # Returns a new `ActionController::Parameters` instance. Also, sets the + # `permitted` attribute to the default value of + # `ActionController::Parameters.permit_all_parameters`. + # + # class Person < ActiveRecord::Base + # end + # + # params = ActionController::Parameters.new(name: "Francesco") + # params.permitted? # => false + # Person.new(params) # => ActiveModel::ForbiddenAttributesError + # + # ActionController::Parameters.permit_all_parameters = true + # + # params = ActionController::Parameters.new(name: "Francesco") + # params.permitted? # => true + # Person.new(params) # => # + def initialize(parameters = {}, logging_context = {}) + parameters.each_key do |key| + unless key.is_a?(String) || key.is_a?(Symbol) + raise InvalidParameterKey, "all keys must be Strings or Symbols, got: #{key.class}" + end + end + + @parameters = parameters.with_indifferent_access + @logging_context = logging_context + @permitted = self.class.permit_all_parameters + end + + # Returns true if another `Parameters` object contains the same content and + # permitted flag. + def ==(other) + if other.respond_to?(:permitted?) + permitted? == other.permitted? && parameters == other.parameters + else + super + end + end + + def eql?(other) + self.class == other.class && + permitted? == other.permitted? && + parameters.eql?(other.parameters) + end + + def hash + [self.class, @parameters, @permitted].hash + end + + # Returns a safe ActiveSupport::HashWithIndifferentAccess representation of the + # parameters with all unpermitted keys removed. + # + # params = ActionController::Parameters.new({ + # name: "Senjougahara Hitagi", + # oddity: "Heavy stone crab" + # }) + # params.to_h + # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash + # + # safe_params = params.permit(:name) + # safe_params.to_h # => {"name"=>"Senjougahara Hitagi"} + def to_h(&block) + if permitted? + convert_parameters_to_hashes(@parameters, :to_h, &block) + else + raise UnfilteredParameters + end + end + + # Returns a safe `Hash` representation of the parameters with all unpermitted + # keys removed. + # + # params = ActionController::Parameters.new({ + # name: "Senjougahara Hitagi", + # oddity: "Heavy stone crab" + # }) + # params.to_hash + # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash + # + # safe_params = params.permit(:name) + # safe_params.to_hash # => {"name"=>"Senjougahara Hitagi"} + def to_hash + to_h.to_hash + end + + # Returns a string representation of the receiver suitable for use as a URL + # query string: + # + # params = ActionController::Parameters.new({ + # name: "David", + # nationality: "Danish" + # }) + # params.to_query + # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash + # + # safe_params = params.permit(:name, :nationality) + # safe_params.to_query + # # => "name=David&nationality=Danish" + # + # An optional namespace can be passed to enclose key names: + # + # params = ActionController::Parameters.new({ + # name: "David", + # nationality: "Danish" + # }) + # safe_params = params.permit(:name, :nationality) + # safe_params.to_query("user") + # # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish" + # + # The string pairs `"key=value"` that conform the query string are sorted + # lexicographically in ascending order. + def to_query(*args) + to_h.to_query(*args) + end + alias_method :to_param, :to_query + + # Returns an unsafe, unfiltered ActiveSupport::HashWithIndifferentAccess + # representation of the parameters. + # + # params = ActionController::Parameters.new({ + # name: "Senjougahara Hitagi", + # oddity: "Heavy stone crab" + # }) + # params.to_unsafe_h + # # => {"name"=>"Senjougahara Hitagi", "oddity" => "Heavy stone crab"} + def to_unsafe_h + convert_parameters_to_hashes(@parameters, :to_unsafe_h) + end + alias_method :to_unsafe_hash, :to_unsafe_h + + # Convert all hashes in values into parameters, then yield each pair in the same + # way as `Hash#each_pair`. + def each_pair(&block) + return to_enum(__callee__) unless block_given? + @parameters.each_pair do |key, value| + yield [key, convert_hashes_to_parameters(key, value)] + end + + self + end + alias_method :each, :each_pair + + # Convert all hashes in values into parameters, then yield each value in the + # same way as `Hash#each_value`. + def each_value(&block) + return to_enum(:each_value) unless block_given? + @parameters.each_pair do |key, value| + yield convert_hashes_to_parameters(key, value) + end + + self + end + + # Returns a new array of the values of the parameters. + def values + to_enum(:each_value).to_a + end + + # Attribute that keeps track of converted arrays, if any, to avoid double + # looping in the common use case permit + mass-assignment. Defined in a method + # to instantiate it only if needed. + # + # Testing membership still loops, but it's going to be faster than our own loop + # that converts values. Also, we are not going to build a new array object per + # fetch. + def converted_arrays + @converted_arrays ||= Set.new + end + + # Returns `true` if the parameter is permitted, `false` otherwise. + # + # params = ActionController::Parameters.new + # params.permitted? # => false + # params.permit! + # params.permitted? # => true + def permitted? + @permitted + end + + # Sets the `permitted` attribute to `true`. This can be used to pass mass + # assignment. Returns `self`. + # + # class Person < ActiveRecord::Base + # end + # + # params = ActionController::Parameters.new(name: "Francesco") + # params.permitted? # => false + # Person.new(params) # => ActiveModel::ForbiddenAttributesError + # params.permit! + # params.permitted? # => true + # Person.new(params) # => # + def permit! + each_pair do |key, value| + Array.wrap(value).flatten.each do |v| + v.permit! if v.respond_to? :permit! + end + end + + @permitted = true + self + end + + # This method accepts both a single key and an array of keys. + # + # When passed a single key, if it exists and its associated value is either + # present or the singleton `false`, returns said value: + # + # ActionController::Parameters.new(person: { name: "Francesco" }).require(:person) + # # => #"Francesco"} permitted: false> + # + # Otherwise raises ActionController::ParameterMissing: + # + # ActionController::Parameters.new.require(:person) + # # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person + # + # ActionController::Parameters.new(person: nil).require(:person) + # # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person + # + # ActionController::Parameters.new(person: "\t").require(:person) + # # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person + # + # ActionController::Parameters.new(person: {}).require(:person) + # # ActionController::ParameterMissing: param is missing or the value is empty or invalid: person + # + # When given an array of keys, the method tries to require each one of them in + # order. If it succeeds, an array with the respective return values is returned: + # + # params = ActionController::Parameters.new(user: { ... }, profile: { ... }) + # user_params, profile_params = params.require([:user, :profile]) + # + # Otherwise, the method re-raises the first exception found: + # + # params = ActionController::Parameters.new(user: {}, profile: {}) + # user_params, profile_params = params.require([:user, :profile]) + # # ActionController::ParameterMissing: param is missing or the value is empty or invalid: user + # + # This method is not recommended for fetching terminal values because it does + # not permit the values. For example, this can cause problems: + # + # # CAREFUL + # params = ActionController::Parameters.new(person: { name: "Finn" }) + # name = params.require(:person).require(:name) # CAREFUL + # + # It is recommended to use `expect` instead: + # + # def person_params + # params.expect(person: :name).require(:name) + # end + # + def require(key) + return key.map { |k| require(k) } if key.is_a?(Array) + value = self[key] + if value.present? || value == false + value + else + raise ParameterMissing.new(key, @parameters.keys) + end + end + + alias :required :require + + # Returns a new `ActionController::Parameters` instance that includes only the + # given `filters` and sets the `permitted` attribute for the object to `true`. + # This is useful for limiting which attributes should be allowed for mass + # updating. + # + # params = ActionController::Parameters.new(name: "Francesco", age: 22, role: "admin") + # permitted = params.permit(:name, :age) + # permitted.permitted? # => true + # permitted.has_key?(:name) # => true + # permitted.has_key?(:age) # => true + # permitted.has_key?(:role) # => false + # + # Only permitted scalars pass the filter. For example, given + # + # params.permit(:name) + # + # `:name` passes if it is a key of `params` whose associated value is of type + # `String`, `Symbol`, `NilClass`, `Numeric`, `TrueClass`, `FalseClass`, `Date`, + # `Time`, `DateTime`, `StringIO`, `IO`, ActionDispatch::Http::UploadedFile or + # `Rack::Test::UploadedFile`. Otherwise, the key `:name` is filtered out. + # + # You may declare that the parameter should be an array of permitted scalars by + # mapping it to an empty array: + # + # params = ActionController::Parameters.new(tags: ["rails", "parameters"]) + # params.permit(tags: []) + # + # Sometimes it is not possible or convenient to declare the valid keys of a hash + # parameter or its internal structure. Just map to an empty hash: + # + # params.permit(preferences: {}) + # + # Be careful because this opens the door to arbitrary input. In this case, + # `permit` ensures values in the returned structure are permitted scalars and + # filters out anything else. + # + # You can also use `permit` on nested parameters: + # + # params = ActionController::Parameters.new({ + # person: { + # name: "Francesco", + # age: 22, + # pets: [{ + # name: "Purplish", + # category: "dogs" + # }] + # } + # }) + # + # permitted = params.permit(person: [ :name, { pets: :name } ]) + # permitted.permitted? # => true + # permitted[:person][:name] # => "Francesco" + # permitted[:person][:age] # => nil + # permitted[:person][:pets][0][:name] # => "Purplish" + # permitted[:person][:pets][0][:category] # => nil + # + # This has the added benefit of rejecting user-modified inputs that send a + # string when a hash is expected. + # + # When followed by `require`, you can both filter and require parameters + # following the typical pattern of a Rails form. The `expect` method was + # made specifically for this use case and is the recommended way to require + # and permit parameters. + # + # permitted = params.expect(person: [:name, :age]) + # + # When using `permit` and `require` separately, pay careful attention to the + # order of the method calls. + # + # params = ActionController::Parameters.new(person: { name: "Martin", age: 40, role: "admin" }) + # permitted = params.permit(person: [:name, :age]).require(:person) # correct + # + # When require is used first, it is possible for users of your application to + # trigger a NoMethodError when the user, for example, sends a string for :person. + # + # params = ActionController::Parameters.new(person: "tampered") + # permitted = params.require(:person).permit(:name, :age) # not recommended + # # => NoMethodError: undefined method `permit' for an instance of String + # + # Note that if you use `permit` in a key that points to a hash, it won't allow + # all the hash. You also need to specify which attributes inside the hash should + # be permitted. + # + # params = ActionController::Parameters.new({ + # person: { + # contact: { + # email: "none@test.com", + # phone: "555-1234" + # } + # } + # }) + # + # params.permit(person: :contact).require(:person) + # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: person + # + # params.permit(person: { contact: :phone }).require(:person) + # # => ##"555-1234"} permitted: true>} permitted: true> + # + # params.permit(person: { contact: [ :email, :phone ] }).require(:person) + # # => ##"none@test.com", "phone"=>"555-1234"} permitted: true>} permitted: true> + # + # If your parameters specify multiple parameters indexed by a number, you can + # permit each set of parameters under the numeric key to be the same using the + # same syntax as permitting a single item. + # + # params = ActionController::Parameters.new({ + # person: { + # '0': { + # email: "none@test.com", + # phone: "555-1234" + # }, + # '1': { + # email: "nothing@test.com", + # phone: "555-6789" + # }, + # } + # }) + # params.permit(person: [:email]).to_h + # # => {"person"=>{"0"=>{"email"=>"none@test.com"}, "1"=>{"email"=>"nothing@test.com"}}} + # + # If you want to specify what keys you want from each numeric key, you can + # instead specify each one individually + # + # params = ActionController::Parameters.new({ + # person: { + # '0': { + # email: "none@test.com", + # phone: "555-1234" + # }, + # '1': { + # email: "nothing@test.com", + # phone: "555-6789" + # }, + # } + # }) + # params.permit(person: { '0': [:email], '1': [:phone]}).to_h + # # => {"person"=>{"0"=>{"email"=>"none@test.com"}, "1"=>{"phone"=>"555-6789"}}} + def permit(*filters) + permit_filters(filters, on_unpermitted: self.class.action_on_unpermitted_parameters, explicit_arrays: false) + end + + # `expect` is the preferred way to require and permit parameters. + # It is safer than the previous recommendation to call `permit` and `require` + # in sequence, which could allow user triggered 500 errors. + # + # `expect` is more strict with types to avoid a number of potential pitfalls + # that may be encountered with the `.require.permit` pattern. + # + # For example: + # + # params = ActionController::Parameters.new(comment: { text: "hello" }) + # params.expect(comment: [:text]) + # # => # + # + # params = ActionController::Parameters.new(comment: [{ text: "hello" }, { text: "world" }]) + # params.expect(comment: [:text]) + # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: comment + # + # In order to permit an array of parameters, the array must be defined + # explicitly. Use double array brackets, an array inside an array, to + # declare that an array of parameters is expected. + # + # params = ActionController::Parameters.new(comments: [{ text: "hello" }, { text: "world" }]) + # params.expect(comments: [[:text]]) + # # => [# "hello" } permitted: true>, + # # # "world" } permitted: true>] + # + # params = ActionController::Parameters.new(comments: { text: "hello" }) + # params.expect(comments: [[:text]]) + # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: comments + # + # `expect` is intended to protect against array tampering. + # + # params = ActionController::Parameters.new(user: "hack") + # # The previous way of requiring and permitting parameters will error + # params.require(:user).permit(:name, pets: [:name]) # wrong + # # => NoMethodError: undefined method `permit' for an instance of String + # + # # similarly with nested parameters + # params = ActionController::Parameters.new(user: { name: "Martin", pets: { name: "hack" } }) + # user_params = params.require(:user).permit(:name, pets: [:name]) # wrong + # # user_params[:pets] is expected to be an array but is a hash + # + # `expect` solves this by being more strict with types. + # + # params = ActionController::Parameters.new(user: "hack") + # params.expect(user: [ :name, pets: [[:name]] ]) + # # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: user + # + # # with nested parameters + # params = ActionController::Parameters.new(user: { name: "Martin", pets: { name: "hack" } }) + # user_params = params.expect(user: [:name, pets: [[:name]] ]) + # user_params[:pets] # => nil + # + # As the examples show, `expect` requires the `:user` key, and any root keys + # similar to the `.require.permit` pattern. If multiple root keys are + # expected, they will all be required. + # + # params = ActionController::Parameters.new(name: "Martin", pies: [{ type: "dessert", flavor: "pumpkin"}]) + # name, pies = params.expect(:name, pies: [[:type, :flavor]]) + # name # => "Martin" + # pies # => [#"dessert", "flavor"=>"pumpkin"} permitted: true>] + # + # When called with a hash with multiple keys, `expect` will permit the + # parameters and require the keys in the order they are given in the hash, + # returning an array of the permitted parameters. + # + # params = ActionController::Parameters.new(subject: { name: "Martin" }, object: { pie: "pumpkin" }) + # subject, object = params.expect(subject: [:name], object: [:pie]) + # subject # => #"Martin"} permitted: true> + # object # => #"pumpkin"} permitted: true> + # + # Besides being more strict about array vs hash params, `expect` uses permit + # internally, so it will behave similarly. + # + # params = ActionController::Parameters.new({ + # person: { + # name: "Francesco", + # age: 22, + # pets: [{ + # name: "Purplish", + # category: "dogs" + # }] + # } + # }) + # + # permitted = params.expect(person: [ :name, { pets: [[:name]] } ]) + # permitted.permitted? # => true + # permitted[:name] # => "Francesco" + # permitted[:age] # => nil + # permitted[:pets][0][:name] # => "Purplish" + # permitted[:pets][0][:category] # => nil + # + # An array of permitted scalars may be expected with the following: + # + # params = ActionController::Parameters.new(tags: ["rails", "parameters"]) + # permitted = params.expect(tags: []) + # permitted # => ["rails", "parameters"] + # permitted.is_a?(Array) # => true + # permitted.size # => 2 + # + def expect(*filters) + params = permit_filters(filters) + keys = filters.flatten.flat_map { |f| f.is_a?(Hash) ? f.keys : f } + values = params.require(keys) + values.size == 1 ? values.first : values + end + + # Same as `expect`, but raises an `ActionController::ExpectedParameterMissing` + # instead of `ActionController::ParameterMissing`. Unlike `expect` which + # will render a 400 response, `expect!` will raise an exception that is + # not handled. This is intended for debugging invalid params for an + # internal API where incorrectly formatted params would indicate a bug + # in a client library that should be fixed. + # + def expect!(*filters) + expect(*filters) + rescue ParameterMissing => e + raise ExpectedParameterMissing.new(e.param, e.keys) + end + + # Returns a parameter for the given `key`. If not found, returns `nil`. + # + # params = ActionController::Parameters.new(person: { name: "Francesco" }) + # params[:person] # => #"Francesco"} permitted: false> + # params[:none] # => nil + def [](key) + convert_hashes_to_parameters(key, @parameters[key]) + end + + # Assigns a value to a given `key`. The given key may still get filtered out + # when #permit is called. + def []=(key, value) + @parameters[key] = value + end + + # Returns a parameter for the given `key`. If the `key` can't be found, there + # are several options: With no other arguments, it will raise an + # ActionController::ParameterMissing error; if a second argument is given, then + # that is returned (converted to an instance of `ActionController::Parameters` + # if possible); if a block is given, then that will be run and its result + # returned. + # + # params = ActionController::Parameters.new(person: { name: "Francesco" }) + # params.fetch(:person) # => #"Francesco"} permitted: false> + # params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty or invalid: none + # params.fetch(:none, {}) # => # + # params.fetch(:none, "Francesco") # => "Francesco" + # params.fetch(:none) { "Francesco" } # => "Francesco" + def fetch(key, *args) + convert_value_to_parameters( + @parameters.fetch(key) { + if block_given? + yield + else + args.fetch(0) { raise ActionController::ParameterMissing.new(key, @parameters.keys) } + end + } + ) + end + + # Extracts the nested parameter from the given `keys` by calling `dig` at each + # step. Returns `nil` if any intermediate step is `nil`. + # + # params = ActionController::Parameters.new(foo: { bar: { baz: 1 } }) + # params.dig(:foo, :bar, :baz) # => 1 + # params.dig(:foo, :zot, :xyz) # => nil + # + # params2 = ActionController::Parameters.new(foo: [10, 11, 12]) + # params2.dig(:foo, 1) # => 11 + def dig(*keys) + convert_hashes_to_parameters(keys.first, @parameters[keys.first]) + @parameters.dig(*keys) + end + + # Returns a new `ActionController::Parameters` instance that includes only the + # given `keys`. If the given `keys` don't exist, returns an empty hash. + # + # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) + # params.slice(:a, :b) # => #1, "b"=>2} permitted: false> + # params.slice(:d) # => # + def slice(*keys) + new_instance_with_inherited_permitted_status(@parameters.slice(*keys)) + end + + # Returns the current `ActionController::Parameters` instance which contains + # only the given `keys`. + def slice!(*keys) + @parameters.slice!(*keys) + self + end + + # Returns a new `ActionController::Parameters` instance that filters out the + # given `keys`. + # + # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) + # params.except(:a, :b) # => #3} permitted: false> + # params.except(:d) # => #1, "b"=>2, "c"=>3} permitted: false> + def except(*keys) + new_instance_with_inherited_permitted_status(@parameters.except(*keys)) + end + alias_method :without, :except + + # Removes and returns the key/value pairs matching the given keys. + # + # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) + # params.extract!(:a, :b) # => #1, "b"=>2} permitted: false> + # params # => #3} permitted: false> + def extract!(*keys) + new_instance_with_inherited_permitted_status(@parameters.extract!(*keys)) + end + + # Returns a new `ActionController::Parameters` instance with the results of + # running `block` once for every value. The keys are unchanged. + # + # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) + # params.transform_values { |x| x * 2 } + # # => #2, "b"=>4, "c"=>6} permitted: false> + def transform_values + return to_enum(:transform_values) unless block_given? + new_instance_with_inherited_permitted_status( + @parameters.transform_values { |v| yield convert_value_to_parameters(v) } + ) + end + + # Performs values transformation and returns the altered + # `ActionController::Parameters` instance. + def transform_values! + return to_enum(:transform_values!) unless block_given? + @parameters.transform_values! { |v| yield convert_value_to_parameters(v) } + self + end + + # Returns a new `ActionController::Parameters` instance with the results of + # running `block` once for every key. The values are unchanged. + def transform_keys(&block) + return to_enum(:transform_keys) unless block_given? + new_instance_with_inherited_permitted_status( + @parameters.transform_keys(&block) + ) + end + + # Performs keys transformation and returns the altered + # `ActionController::Parameters` instance. + def transform_keys!(&block) + return to_enum(:transform_keys!) unless block_given? + @parameters.transform_keys!(&block) + self + end + + # Returns a new `ActionController::Parameters` instance with the results of + # running `block` once for every key. This includes the keys from the root hash + # and from all nested hashes and arrays. The values are unchanged. + def deep_transform_keys(&block) + new_instance_with_inherited_permitted_status( + _deep_transform_keys_in_object(@parameters, &block).to_unsafe_h + ) + end + + # Returns the same `ActionController::Parameters` instance with changed keys. + # This includes the keys from the root hash and from all nested hashes and + # arrays. The values are unchanged. + def deep_transform_keys!(&block) + @parameters = _deep_transform_keys_in_object(@parameters, &block).to_unsafe_h + self + end + + # Deletes a key-value pair from `Parameters` and returns the value. If `key` is + # not found, returns `nil` (or, with optional code block, yields `key` and + # returns the result). This method is similar to #extract!, which returns the + # corresponding `ActionController::Parameters` object. + def delete(key, &block) + convert_value_to_parameters(@parameters.delete(key, &block)) + end + + # Returns a new `ActionController::Parameters` instance with only items that the + # block evaluates to true. + def select(&block) + new_instance_with_inherited_permitted_status(@parameters.select(&block)) + end + + # Equivalent to Hash#keep_if, but returns `nil` if no changes were made. + def select!(&block) + @parameters.select!(&block) + self + end + alias_method :keep_if, :select! + + # Returns a new `ActionController::Parameters` instance with items that the + # block evaluates to true removed. + def reject(&block) + new_instance_with_inherited_permitted_status(@parameters.reject(&block)) + end + + # Removes items that the block evaluates to true and returns self. + def reject!(&block) + @parameters.reject!(&block) + self + end + alias_method :delete_if, :reject! + + # Returns a new `ActionController::Parameters` instance with `nil` values + # removed. + def compact + new_instance_with_inherited_permitted_status(@parameters.compact) + end + + # Removes all `nil` values in place and returns `self`, or `nil` if no changes + # were made. + def compact! + self if @parameters.compact! + end + + # Returns a new `ActionController::Parameters` instance without the blank + # values. Uses Object#blank? for determining if a value is blank. + def compact_blank + reject { |_k, v| v.blank? } + end + + # Removes all blank values in place and returns self. Uses Object#blank? for + # determining if a value is blank. + def compact_blank! + reject! { |_k, v| v.blank? } + end + + # Returns true if the given value is present for some key in the parameters. + def has_value?(value) + each_value.include?(convert_value_to_parameters(value)) + end + + alias value? has_value? + + # Returns values that were assigned to the given `keys`. Note that all the + # `Hash` objects will be converted to `ActionController::Parameters`. + def values_at(*keys) + convert_value_to_parameters(@parameters.values_at(*keys)) + end + + # Returns a new `ActionController::Parameters` instance with all keys from + # `other_hash` merged into current hash. + def merge(other_hash) + new_instance_with_inherited_permitted_status( + @parameters.merge(other_hash.to_h) + ) + end + + ## + # :call-seq: merge!(other_hash) + # + # Returns the current `ActionController::Parameters` instance with `other_hash` + # merged into current hash. + def merge!(other_hash, &block) + @parameters.merge!(other_hash.to_h, &block) + self + end + + def deep_merge?(other_hash) # :nodoc: + other_hash.is_a?(ActiveSupport::DeepMergeable) + end + + # Returns a new `ActionController::Parameters` instance with all keys from + # current hash merged into `other_hash`. + def reverse_merge(other_hash) + new_instance_with_inherited_permitted_status( + other_hash.to_h.merge(@parameters) + ) + end + alias_method :with_defaults, :reverse_merge + + # Returns the current `ActionController::Parameters` instance with current hash + # merged into `other_hash`. + def reverse_merge!(other_hash) + @parameters.merge!(other_hash.to_h) { |key, left, right| left } + self + end + alias_method :with_defaults!, :reverse_merge! + + # This is required by ActiveModel attribute assignment, so that user can pass + # `Parameters` to a mass assignment methods in a model. It should not matter as + # we are using `HashWithIndifferentAccess` internally. + def stringify_keys # :nodoc: + dup + end + + def inspect + "#<#{self.class} #{@parameters} permitted: #{@permitted}>" + end + + def self.hook_into_yaml_loading # :nodoc: + # Wire up YAML format compatibility with Rails 4.2 and Psych 2.0.8 and 2.0.9+. + # Makes the YAML parser call `init_with` when it encounters the keys below + # instead of trying its own parsing routines. + YAML.load_tags["!ruby/hash-with-ivars:ActionController::Parameters"] = name + YAML.load_tags["!ruby/hash:ActionController::Parameters"] = name + end + hook_into_yaml_loading + + def init_with(coder) # :nodoc: + case coder.tag + when "!ruby/hash:ActionController::Parameters" + # YAML 2.0.8's format where hash instance variables weren't stored. + @parameters = coder.map.with_indifferent_access + @permitted = false + when "!ruby/hash-with-ivars:ActionController::Parameters" + # YAML 2.0.9's Hash subclass format where keys and values were stored under an + # elements hash and `permitted` within an ivars hash. + @parameters = coder.map["elements"].with_indifferent_access + @permitted = coder.map["ivars"][:@permitted] + when "!ruby/object:ActionController::Parameters" + # YAML's Object format. Only needed because of the format backwards + # compatibility above, otherwise equivalent to YAML's initialization. + @parameters, @permitted = coder.map["parameters"], coder.map["permitted"] + end + end + + def encode_with(coder) # :nodoc: + coder.map = { "parameters" => @parameters, "permitted" => @permitted } + end + + # Returns a duplicate `ActionController::Parameters` instance with the same + # permitted parameters. + def deep_dup + self.class.new(@parameters.deep_dup, @logging_context).tap do |duplicate| + duplicate.permitted = @permitted + end + end + + # Returns parameter value for the given `key` separated by `delimiter`. + # + # params = ActionController::Parameters.new(id: "1_123", tags: "ruby,rails") + # params.extract_value(:id) # => ["1", "123"] + # params.extract_value(:tags, delimiter: ",") # => ["ruby", "rails"] + # params.extract_value(:non_existent_key) # => nil + # + # Note that if the given `key`'s value contains blank elements, then the + # returned array will include empty strings. + # + # params = ActionController::Parameters.new(tags: "ruby,rails,,web") + # params.extract_value(:tags, delimiter: ",") # => ["ruby", "rails", "", "web"] + def extract_value(key, delimiter: "_") + @parameters[key]&.split(delimiter, -1) + end + + protected + attr_reader :parameters + + attr_writer :permitted + + def nested_attributes? + @parameters.any? { |k, v| Parameters.nested_attribute?(k, v) } + end + + def each_nested_attribute + hash = self.class.new + self.each { |k, v| hash[k] = yield v if Parameters.nested_attribute?(k, v) } + hash + end + + # Filters self and optionally checks for unpermitted keys + def permit_filters(filters, on_unpermitted: nil, explicit_arrays: true) + params = self.class.new + + filters.flatten.each do |filter| + case filter + when Symbol, String + # Declaration [:name, "age"] + permitted_scalar_filter(params, filter) + when Hash + # Declaration [{ person: ... }] + hash_filter(params, filter, on_unpermitted:, explicit_arrays:) + end + end + + unpermitted_parameters!(params, on_unpermitted:) + + params.permit! + end + + private + def new_instance_with_inherited_permitted_status(hash) + self.class.new(hash, @logging_context).tap do |new_instance| + new_instance.permitted = @permitted + end + end + + def convert_parameters_to_hashes(value, using, &block) + case value + when Array + value.map { |v| convert_parameters_to_hashes(v, using) } + when Hash + transformed = value.transform_values do |v| + convert_parameters_to_hashes(v, using) + end + (block_given? ? transformed.to_h(&block) : transformed).with_indifferent_access + when Parameters + value.send(using) + else + value + end + end + + def convert_hashes_to_parameters(key, value) + converted = convert_value_to_parameters(value) + @parameters[key] = converted unless converted.equal?(value) + converted + end + + def convert_value_to_parameters(value) + case value + when Array + return value if converted_arrays.member?(value) + converted = value.map { |_| convert_value_to_parameters(_) } + converted_arrays << converted.dup + converted + when Hash + self.class.new(value, @logging_context) + else + value + end + end + + def _deep_transform_keys_in_object(object, &block) + case object + when Hash + object.each_with_object(self.class.new) do |(key, value), result| + result[yield(key)] = _deep_transform_keys_in_object(value, &block) + end + when Parameters + if object.permitted? + object.to_h.deep_transform_keys(&block) + else + object.to_unsafe_h.deep_transform_keys(&block) + end + when Array + object.map { |e| _deep_transform_keys_in_object(e, &block) } + else + object + end + end + + def _deep_transform_keys_in_object!(object, &block) + case object + when Hash + object.keys.each do |key| + value = object.delete(key) + object[yield(key)] = _deep_transform_keys_in_object!(value, &block) + end + object + when Parameters + if object.permitted? + object.to_h.deep_transform_keys!(&block) + else + object.to_unsafe_h.deep_transform_keys!(&block) + end + when Array + object.map! { |e| _deep_transform_keys_in_object!(e, &block) } + else + object + end + end + + def specify_numeric_keys?(filter) + if filter.respond_to?(:keys) + filter.keys.any? { |key| /\A-?\d+\z/.match?(key) } + end + end + + # When an array is expected, you must specify an array explicitly + # using the following format: + # + # params.expect(comments: [[:flavor]]) + # + # Which will match only the following array formats: + # + # { pies: [{ flavor: "rhubarb" }, { flavor: "apple" }] } + # { pies: { "0" => { flavor: "key lime" }, "1" => { flavor: "mince" } } } + # + # When using `permit`, arrays are specified the same way as hashes: + # + # params.expect(pies: [:flavor]) + # + # In this case, `permit` would also allow matching with a hash (or vice versa): + # + # { pies: { flavor: "cherry" } } + # + def array_filter?(filter) + filter.is_a?(Array) && filter.size == 1 && filter.first.is_a?(Array) + end + + # Called when an explicit array filter is encountered. + def each_array_element(object, filter, &block) + case object + when Array + object.grep(Parameters).filter_map(&block) + when Parameters + if object.nested_attributes? && !specify_numeric_keys?(filter) + object.each_nested_attribute(&block) + end + end + end + + def unpermitted_parameters!(params, on_unpermitted: self.class.action_on_unpermitted_parameters) + return unless on_unpermitted + unpermitted_keys = unpermitted_keys(params) + if unpermitted_keys.any? + case on_unpermitted + when :log + name = "unpermitted_parameters.action_controller" + ActiveSupport::Notifications.instrument(name, keys: unpermitted_keys, context: @logging_context) + when :raise + raise ActionController::UnpermittedParameters.new(unpermitted_keys) + end + end + end + + def unpermitted_keys(params) + keys - params.keys - always_permitted_parameters + end + + # This is a list of permitted scalar types that includes the ones supported in + # XML and JSON requests. + # + # This list is in particular used to filter ordinary requests, String goes as + # first element to quickly short-circuit the common case. + # + # If you modify this collection please update the one in the #permit doc as + # well. + PERMITTED_SCALAR_TYPES = [ + String, + Symbol, + NilClass, + Numeric, + TrueClass, + FalseClass, + Date, + Time, + # DateTimes are Dates, we document the type but avoid the redundant check. + StringIO, + IO, + ActionDispatch::Http::UploadedFile, + Rack::Test::UploadedFile, + ] + + def permitted_scalar?(value) + PERMITTED_SCALAR_TYPES.any? { |type| value.is_a?(type) } + end + + # Adds existing keys to the params if their values are scalar. + # + # For example: + # + # puts self.keys #=> ["zipcode(90210i)"] + # params = {} + # + # permitted_scalar_filter(params, "zipcode") + # + # puts params.keys # => ["zipcode"] + def permitted_scalar_filter(params, permitted_key) + permitted_key = permitted_key.to_s + + if has_key?(permitted_key) && permitted_scalar?(self[permitted_key]) + params[permitted_key] = self[permitted_key] + end + + each_key do |key| + next unless key =~ /\(\d+[if]?\)\z/ + next unless $~.pre_match == permitted_key + + params[key] = self[key] if permitted_scalar?(self[key]) + end + end + + def non_scalar?(value) + value.is_a?(Array) || value.is_a?(Parameters) + end + + EMPTY_ARRAY = [] # :nodoc: + EMPTY_HASH = {} # :nodoc: + def hash_filter(params, filter, on_unpermitted: self.class.action_on_unpermitted_parameters, explicit_arrays: false) + filter = filter.with_indifferent_access + + # Slicing filters out non-declared keys. + slice(*filter.keys).each do |key, value| + next unless value + next unless has_key? key + result = permit_value(value, filter[key], on_unpermitted:, explicit_arrays:) + params[key] = result unless result.nil? + end + end + + def permit_value(value, filter, on_unpermitted:, explicit_arrays:) + if filter == EMPTY_ARRAY # Declaration { comment_ids: [] }. + permit_array_of_scalars(value) + elsif filter == EMPTY_HASH # Declaration { preferences: {} }. + permit_hash(value, filter, on_unpermitted:, explicit_arrays:) + elsif array_filter?(filter) # Declaration { comments: [[:text]] } + permit_array_of_hashes(value, filter.first, on_unpermitted:, explicit_arrays:) + elsif explicit_arrays # Declaration { user: { address: ... } } or { user: [:name, ...] } (only allows hash value) + permit_hash(value, filter, on_unpermitted:, explicit_arrays:) + elsif non_scalar?(value) # Declaration { user: { address: ... } } or { user: [:name, ...] } + permit_hash_or_array(value, filter, on_unpermitted:, explicit_arrays:) + end + end + + def permit_array_of_scalars(value) + value if value.is_a?(Array) && value.all? { |element| permitted_scalar?(element) } + end + + def permit_array_of_hashes(value, filter, on_unpermitted:, explicit_arrays:) + each_array_element(value, filter) do |element| + element.permit_filters(Array.wrap(filter), on_unpermitted:, explicit_arrays:) + end + end + + def permit_hash(value, filter, on_unpermitted:, explicit_arrays:) + return unless value.is_a?(Parameters) + + if filter == EMPTY_HASH + permit_any_in_parameters(value) + else + value.permit_filters(Array.wrap(filter), on_unpermitted:, explicit_arrays:) + end + end + + def permit_hash_or_array(value, filter, on_unpermitted:, explicit_arrays:) + permit_array_of_hashes(value, filter, on_unpermitted:, explicit_arrays:) || + permit_hash(value, filter, on_unpermitted:, explicit_arrays:) + end + + def permit_any_in_parameters(params) + self.class.new.tap do |sanitized| + params.each do |key, value| + case value + when ->(v) { permitted_scalar?(v) } + sanitized[key] = value + when Array + sanitized[key] = permit_any_in_array(value) + when Parameters + sanitized[key] = permit_any_in_parameters(value) + else + # Filter this one out. + end + end + end + end + + def permit_any_in_array(array) + [].tap do |sanitized| + array.each do |element| + case element + when ->(e) { permitted_scalar?(e) } + sanitized << element + when Array + sanitized << permit_any_in_array(element) + when Parameters + sanitized << permit_any_in_parameters(element) + else + # Filter this one out. + end + end + end + end + + def initialize_copy(source) + super + @parameters = @parameters.dup + end + end + + # # Strong Parameters + # + # It provides an interface for protecting attributes from end-user assignment. + # This makes Action Controller parameters forbidden to be used in Active Model + # mass assignment until they have been explicitly enumerated. + # + # In addition, parameters can be marked as required and flow through a + # predefined raise/rescue flow to end up as a `400 Bad Request` with no effort. + # + # class PeopleController < ActionController::Base + # # Using "Person.create(params[:person])" would raise an + # # ActiveModel::ForbiddenAttributesError exception because it'd + # # be using mass assignment without an explicit permit step. + # # This is the recommended form: + # def create + # Person.create(person_params) + # end + # + # # This will pass with flying colors as long as there's a person key in the + # # parameters, otherwise it'll raise an ActionController::ParameterMissing + # # exception, which will get caught by ActionController::Base and turned + # # into a 400 Bad Request reply. + # def update + # redirect_to current_account.people.find(params[:id]).tap { |person| + # person.update!(person_params) + # } + # end + # + # private + # # Using a private method to encapsulate the permissible parameters is + # # a good pattern since you'll be able to reuse the same permit + # # list between create and update. Also, you can specialize this method + # # with per-user checking of permissible attributes. + # def person_params + # params.expect(person: [:name, :age]) + # end + # end + # + # In order to use `accepts_nested_attributes_for` with Strong Parameters, you + # will need to specify which nested attributes should be permitted. You might + # want to allow `:id` and `:_destroy`, see ActiveRecord::NestedAttributes for + # more information. + # + # class Person + # has_many :pets + # accepts_nested_attributes_for :pets + # end + # + # class PeopleController < ActionController::Base + # def create + # Person.create(person_params) + # end + # + # ... + # + # private + # + # def person_params + # # It's mandatory to specify the nested attributes that should be permitted. + # # If you use `permit` with just the key that points to the nested attributes hash, + # # it will return an empty hash. + # params.expect(person: [ :name, :age, pets_attributes: [ :id, :name, :category ] ]) + # end + # end + # + # See ActionController::Parameters.expect, + # See ActionController::Parameters.require, and + # ActionController::Parameters.permit for more information. + module StrongParameters + # Returns a new ActionController::Parameters object that has been instantiated + # with the `request.parameters`. + def params + @_params ||= begin + context = { + controller: self.class.name, + action: action_name, + request: request, + params: request.filtered_parameters + } + Parameters.new(request.parameters, context) + end + end + + # Assigns the given `value` to the `params` hash. If `value` is a Hash, this + # will create an ActionController::Parameters object that has been instantiated + # with the given `value` hash. + def params=(value) + @_params = value.is_a?(Hash) ? Parameters.new(value) : value + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/testing.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/testing.rb new file mode 100644 index 00000000..125299f7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/testing.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + module Testing + # Behavior specific to functional tests + module Functional # :nodoc: + def clear_instance_variables_between_requests + if defined?(@_ivars) + new_ivars = instance_variables - @_ivars + new_ivars.each { |ivar| remove_instance_variable(ivar) } + end + + @_ivars = instance_variables + end + + def recycle! + @_url_options = nil + self.formats = nil + self.params = nil + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/url_for.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/url_for.rb new file mode 100644 index 00000000..c240399b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/metal/url_for.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + # # Action Controller UrlFor + # + # Includes `url_for` into the host class. The class has to provide a `RouteSet` + # by implementing the `_routes` method. Otherwise, an exception will be raised. + # + # In addition to AbstractController::UrlFor, this module accesses the HTTP layer + # to define URL options like the `host`. In order to do so, this module requires + # the host class to implement `env` which needs to be Rack-compatible, and + # `request` which returns an ActionDispatch::Request instance. + # + # class RootUrl + # include ActionController::UrlFor + # include Rails.application.routes.url_helpers + # + # delegate :env, :request, to: :controller + # + # def initialize(controller) + # @controller = controller + # @url = root_path # named route from the application. + # end + # end + module UrlFor + extend ActiveSupport::Concern + + include AbstractController::UrlFor + + def initialize(...) + super + @_url_options = nil + end + + def url_options + @_url_options ||= { + host: request.host, + port: request.optional_port, + protocol: request.protocol, + _recall: request.path_parameters + }.merge!(super).freeze + + if (same_origin = _routes.equal?(request.routes)) || + (script_name = request.engine_script_name(_routes)) || + (original_script_name = request.original_script_name) + + options = @_url_options.dup + if original_script_name + options[:original_script_name] = original_script_name + else + if same_origin + options[:script_name] = request.script_name.empty? ? "" : request.script_name.dup + else + options[:script_name] = script_name + end + end + options.freeze + else + @_url_options + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/railtie.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/railtie.rb new file mode 100644 index 00000000..ee05b16e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/railtie.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "rails" +require "action_controller" +require "action_dispatch/railtie" +require "abstract_controller/railties/routes_helpers" +require "action_controller/railties/helpers" +require "action_view/railtie" + +module ActionController + class Railtie < Rails::Railtie # :nodoc: + config.action_controller = ActiveSupport::OrderedOptions.new + config.action_controller.raise_on_open_redirects = false + config.action_controller.log_query_tags_around_actions = true + config.action_controller.wrap_parameters_by_default = false + + config.eager_load_namespaces << AbstractController + config.eager_load_namespaces << ActionController + + initializer "action_controller.deprecator", before: :load_environment_config do |app| + app.deprecators[:action_controller] = ActionController.deprecator + end + + initializer "action_controller.assets_config", group: :all do |app| + app.config.action_controller.assets_dir ||= app.config.paths["public"].first + end + + initializer "action_controller.set_helpers_path" do |app| + ActionController::Helpers.helpers_path = app.helpers_paths + end + + initializer "action_controller.parameters_config" do |app| + options = app.config.action_controller + + ActiveSupport.on_load(:action_controller, run_once: true) do + ActionController::Parameters.permit_all_parameters = options.permit_all_parameters || false + if app.config.action_controller[:always_permitted_parameters] + ActionController::Parameters.always_permitted_parameters = + app.config.action_controller.always_permitted_parameters + end + + action_on_unpermitted_parameters = options.action_on_unpermitted_parameters + + if action_on_unpermitted_parameters.nil? + action_on_unpermitted_parameters = Rails.env.local? ? :log : false + end + + ActionController::Parameters.action_on_unpermitted_parameters = action_on_unpermitted_parameters + end + end + + initializer "action_controller.set_configs" do |app| + paths = app.config.paths + options = app.config.action_controller + + options.logger ||= Rails.logger + options.cache_store ||= Rails.cache + + options.javascripts_dir ||= paths["public/javascripts"].first + options.stylesheets_dir ||= paths["public/stylesheets"].first + + # Ensure readers methods get compiled. + options.asset_host ||= app.config.asset_host + options.relative_url_root ||= app.config.relative_url_root + + ActiveSupport.on_load(:action_controller) do + include app.routes.mounted_helpers + extend ::AbstractController::Railties::RoutesHelpers.with(app.routes) + extend ::ActionController::Railties::Helpers + + wrap_parameters format: [:json] if options.wrap_parameters_by_default && respond_to?(:wrap_parameters) + + # Configs used in other initializers + filtered_options = options.except( + :default_protect_from_forgery, + :log_query_tags_around_actions, + :permit_all_parameters, + :action_on_unpermitted_parameters, + :always_permitted_parameters, + :wrap_parameters_by_default, + ) + + filtered_options.each do |k, v| + k = "#{k}=" + if respond_to?(k) + send(k, v) + elsif !Base.respond_to?(k) + raise "Invalid option key: #{k}" + end + end + end + end + + initializer "action_controller.compile_config_methods" do + ActiveSupport.on_load(:action_controller) do + config.compile_methods! if config.respond_to?(:compile_methods!) + end + end + + initializer "action_controller.request_forgery_protection" do |app| + ActiveSupport.on_load(:action_controller_base) do + if app.config.action_controller.default_protect_from_forgery + protect_from_forgery with: :exception + end + end + end + + initializer "action_controller.query_log_tags" do |app| + query_logs_tags_enabled = app.config.respond_to?(:active_record) && + app.config.active_record.query_log_tags_enabled && + app.config.action_controller.log_query_tags_around_actions + + if query_logs_tags_enabled + app.config.active_record.query_log_tags |= [:controller] unless app.config.active_record.query_log_tags.include?(:namespaced_controller) + app.config.active_record.query_log_tags |= [:action] + + ActiveSupport.on_load(:active_record) do + ActiveRecord::QueryLogs.taggings = ActiveRecord::QueryLogs.taggings.merge( + controller: ->(context) { context[:controller]&.controller_name }, + action: ->(context) { context[:controller]&.action_name }, + namespaced_controller: ->(context) { + if context[:controller] + controller_class = context[:controller].class + # based on ActionController::Metal#controller_name, but does not demodulize + unless controller_class.anonymous? + controller_class.name.delete_suffix("Controller").underscore + end + end + } + ) + end + end + end + + initializer "action_controller.test_case" do |app| + ActiveSupport.on_load(:action_controller_test_case) do + ActionController::TestCase.executor_around_each_request = app.config.active_support.executor_around_test_case + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/railties/helpers.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/railties/helpers.rb new file mode 100644 index 00000000..2ae7c877 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/railties/helpers.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + module Railties + module Helpers + def inherited(klass) + super + return unless klass.respond_to?(:helpers_path=) + + if namespace = klass.module_parents.detect { |m| m.respond_to?(:railtie_helpers_paths) } + paths = namespace.railtie_helpers_paths + else + paths = ActionController::Helpers.helpers_path + end + + klass.helpers_path = paths + + if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers + klass.helper :all + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/renderer.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/renderer.rb new file mode 100644 index 00000000..068d023b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/renderer.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + # # Action Controller Renderer + # + # ActionController::Renderer allows you to render arbitrary templates without + # being inside a controller action. + # + # You can get a renderer instance by calling `renderer` on a controller class: + # + # ApplicationController.renderer + # PostsController.renderer + # + # and render a template by calling the #render method: + # + # ApplicationController.renderer.render template: "posts/show", assigns: { post: Post.first } + # PostsController.renderer.render :show, assigns: { post: Post.first } + # + # As a shortcut, you can also call `render` directly on the controller class + # itself: + # + # ApplicationController.render template: "posts/show", assigns: { post: Post.first } + # PostsController.render :show, assigns: { post: Post.first } + # + class Renderer + attr_reader :controller + + DEFAULTS = { + method: "get", + input: "" + }.freeze + + def self.normalize_env(env) # :nodoc: + new_env = {} + + env.each_pair do |key, value| + case key + when :https + value = value ? "on" : "off" + when :method + value = -value.upcase + end + + key = RACK_KEY_TRANSLATION[key] || key.to_s + + new_env[key] = value + end + + if new_env["HTTP_HOST"] + new_env["HTTPS"] ||= "off" + new_env["SCRIPT_NAME"] ||= "" + end + + if new_env["HTTPS"] + new_env["rack.url_scheme"] = new_env["HTTPS"] == "on" ? "https" : "http" + end + + new_env + end + + # Creates a new renderer using the given controller class. See ::new. + def self.for(controller, env = nil, defaults = DEFAULTS) + new(controller, env, defaults) + end + + # Creates a new renderer using the same controller, but with a new Rack env. + # + # ApplicationController.renderer.new(method: "post") + # + def new(env = nil) + self.class.new controller, env, @defaults + end + + # Creates a new renderer using the same controller, but with the given defaults + # merged on top of the previous defaults. + def with_defaults(defaults) + self.class.new controller, @env, @defaults.merge(defaults) + end + + # Initializes a new Renderer. + # + # #### Parameters + # + # * `controller` - The controller class to instantiate for rendering. + # * `env` - The Rack env to use for mocking a request when rendering. Entries + # can be typical Rack env keys and values, or they can be any of the + # following, which will be converted appropriately: + # * `:http_host` - The HTTP host for the incoming request. Converts to + # Rack's `HTTP_HOST`. + # * `:https` - Boolean indicating whether the incoming request uses HTTPS. + # Converts to Rack's `HTTPS`. + # * `:method` - The HTTP method for the incoming request, + # case-insensitive. Converts to Rack's `REQUEST_METHOD`. + # * `:script_name` - The portion of the incoming request's URL path that + # corresponds to the application. Converts to Rack's `SCRIPT_NAME`. + # * `:input` - The input stream. Converts to Rack's `rack.input`. + # + # * `defaults` - Default values for the Rack env. Entries are specified in the + # same format as `env`. `env` will be merged on top of these values. + # `defaults` will be retained when calling #new on a renderer instance. + # + # + # If no `http_host` is specified, the env HTTP host will be derived from the + # routes' `default_url_options`. In this case, the `https` boolean and the + # `script_name` will also be derived from `default_url_options` if they were not + # specified. Additionally, the `https` boolean will fall back to + # `Rails.application.config.force_ssl` if `default_url_options` does not specify + # a `protocol`. + def initialize(controller, env, defaults) + @controller = controller + @defaults = defaults + if env.blank? && @defaults == DEFAULTS + @env = DEFAULT_ENV + else + @env = normalize_env(@defaults) + @env.merge!(normalize_env(env)) unless env.blank? + end + end + + def defaults + @defaults = @defaults.dup if @defaults.frozen? + @defaults + end + + # Renders a template to a string, just like + # ActionController::Rendering#render_to_string. + def render(*args) + request = ActionDispatch::Request.new(env_for_request) + request.routes = controller._routes + + instance = controller.new + instance.set_request! request + instance.set_response! controller.make_response!(request) + instance.render_to_string(*args) + end + alias_method :render_to_string, :render # :nodoc: + + private + RACK_KEY_TRANSLATION = { + http_host: "HTTP_HOST", + https: "HTTPS", + method: "REQUEST_METHOD", + script_name: "SCRIPT_NAME", + input: "rack.input" + } + + DEFAULT_ENV = normalize_env(DEFAULTS).freeze # :nodoc: + + delegate :normalize_env, to: :class + + def env_for_request + if @env.key?("HTTP_HOST") || controller._routes.nil? + @env.dup + else + controller._routes.default_env.merge(@env) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/template_assertions.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/template_assertions.rb new file mode 100644 index 00000000..c3bf41fa --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/template_assertions.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionController + module TemplateAssertions # :nodoc: + def assert_template(options = {}, message = nil) + raise NoMethodError, + 'assert_template has been extracted to a gem. To continue using it, + add `gem "rails-controller-testing"` to your Gemfile.' + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/test_case.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/test_case.rb new file mode 100644 index 00000000..d3a3bf21 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_controller/test_case.rb @@ -0,0 +1,698 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "rack/session/abstract/id" +require "active_support/core_ext/hash/conversions" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/module/redefine_method" +require "active_support/core_ext/hash/keys" +require "active_support/testing/constant_lookup" +require "action_controller/template_assertions" +require "rails-dom-testing" + +module ActionController + class Metal + include Testing::Functional + end + + module Live + # Disable controller / rendering threads in tests. User tests can access the + # database on the main thread, so they could open a txn, then the controller + # thread will open a new connection and try to access data that's only visible + # to the main thread's txn. This is the problem in #23483. + alias_method :original_new_controller_thread, :new_controller_thread + + silence_redefinition_of_method :new_controller_thread + def new_controller_thread # :nodoc: + yield + end + + # Because of the above, we need to prevent the clearing of thread locals, since + # no new thread is actually spawned in the test environment. + alias_method :original_clean_up_thread_locals, :clean_up_thread_locals + + silence_redefinition_of_method :clean_up_thread_locals + def clean_up_thread_locals(*args) # :nodoc: + end + + # Avoid a deadlock from the queue filling up + Buffer.queue_size = nil + end + + # ActionController::TestCase will be deprecated and moved to a gem in the + # future. Please use ActionDispatch::IntegrationTest going forward. + class TestRequest < ActionDispatch::TestRequest # :nodoc: + DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup + DEFAULT_ENV.delete "PATH_INFO" + + def self.new_session + TestSession.new + end + + attr_reader :controller_class + + # Create a new test request with default `env` values. + def self.create(controller_class) + env = {} + env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application + env["rack.request.cookie_hash"] = {}.with_indifferent_access + new(default_env.merge(env), new_session, controller_class) + end + + def self.default_env + DEFAULT_ENV + end + private_class_method :default_env + + def initialize(env, session, controller_class) + super(env) + + self.session = session + self.session_options = TestSession::DEFAULT_OPTIONS.dup + @controller_class = controller_class + @custom_param_parsers = { + xml: lambda { |raw_post| Hash.from_xml(raw_post)["hash"] } + } + end + + def query_string=(string) + set_header Rack::QUERY_STRING, string + end + + def content_type=(type) + set_header "CONTENT_TYPE", type + end + + def assign_parameters(routes, controller_path, action, parameters, generated_path, query_string_keys) + non_path_parameters = {} + path_parameters = {} + + parameters.each do |key, value| + if query_string_keys.include?(key) + non_path_parameters[key] = value + else + if value.is_a?(Array) + value = value.map(&:to_param) + else + value = value.to_param + end + + path_parameters[key.to_sym] = value + end + end + + if get? + if query_string.blank? + self.query_string = non_path_parameters.to_query + end + else + if ENCODER.should_multipart?(non_path_parameters) + self.content_type = ENCODER.content_type + data = ENCODER.build_multipart non_path_parameters + else + fetch_header("CONTENT_TYPE") do |k| + set_header k, "application/x-www-form-urlencoded" + end + + case content_mime_type&.to_sym + when nil + raise "Unknown Content-Type: #{content_type}" + when :json + data = ActiveSupport::JSON.encode(non_path_parameters) + when :xml + data = non_path_parameters.to_xml + when :url_encoded_form + data = non_path_parameters.to_query + else + @custom_param_parsers[content_mime_type.symbol] = ->(_) { non_path_parameters } + data = non_path_parameters.to_query + end + end + + data_stream = StringIO.new(data.b) + set_header "CONTENT_LENGTH", data_stream.length.to_s + set_header "rack.input", data_stream + end + + fetch_header("PATH_INFO") do |k| + set_header k, generated_path + end + fetch_header("ORIGINAL_FULLPATH") do |k| + set_header k, fullpath + end + path_parameters[:controller] = controller_path + path_parameters[:action] = action + + self.path_parameters = path_parameters + end + + ENCODER = Class.new do + include Rack::Test::Utils + + def should_multipart?(params) + # FIXME: lifted from Rack-Test. We should push this separation upstream. + multipart = false + query = lambda { |value| + case value + when Array + value.each(&query) + when Hash + value.values.each(&query) + when Rack::Test::UploadedFile + multipart = true + end + } + params.values.each(&query) + multipart + end + + public :build_multipart + + def content_type + "multipart/form-data; boundary=#{Rack::Test::MULTIPART_BOUNDARY}" + end + end.new + + private + def params_parsers + super.merge @custom_param_parsers + end + end + + class LiveTestResponse < Live::Response + # Was the response successful? + alias_method :success?, :successful? + + # Was the URL not found? + alias_method :missing?, :not_found? + + # Was there a server-side error? + alias_method :error?, :server_error? + end + + # Methods #destroy and #load! are overridden to avoid calling methods on the + # @store object, which does not exist for the TestSession class. + class TestSession < Rack::Session::Abstract::PersistedSecure::SecureSessionHash # :nodoc: + DEFAULT_OPTIONS = Rack::Session::Abstract::Persisted::DEFAULT_OPTIONS + + def initialize(session = {}, id = Rack::Session::SessionId.new(SecureRandom.hex(16))) + super(nil, nil) + @id = id + @data = stringify_keys(session) + @loaded = true + @initially_empty = @data.empty? + end + + def exists? + true + end + + def keys + @data.keys + end + + def values + @data.values + end + + def destroy + clear + end + + def dig(*keys) + keys = keys.map.with_index { |key, i| i.zero? ? key.to_s : key } + @data.dig(*keys) + end + + def fetch(key, *args, &block) + @data.fetch(key.to_s, *args, &block) + end + + def enabled? + true + end + + def id_was + @id + end + + private + def load! + @id + end + end + + # # Action Controller Test Case + # + # Superclass for ActionController functional tests. Functional tests allow you + # to test a single controller action per test method. + # + # ## Use integration style controller tests over functional style controller tests. + # + # Rails discourages the use of functional tests in favor of integration tests + # (use ActionDispatch::IntegrationTest). + # + # New Rails applications no longer generate functional style controller tests + # and they should only be used for backward compatibility. Integration style + # controller tests perform actual requests, whereas functional style controller + # tests merely simulate a request. Besides, integration tests are as fast as + # functional tests and provide lot of helpers such as `as`, `parsed_body` for + # effective testing of controller actions including even API endpoints. + # + # ## Basic example + # + # Functional tests are written as follows: + # 1. First, one uses the `get`, `post`, `patch`, `put`, `delete`, or `head` + # method to simulate an HTTP request. + # 2. Then, one asserts whether the current state is as expected. "State" can be + # anything: the controller's HTTP response, the database contents, etc. + # + # + # For example: + # + # class BooksControllerTest < ActionController::TestCase + # def test_create + # # Simulate a POST response with the given HTTP parameters. + # post(:create, params: { book: { title: "Love Hina" }}) + # + # # Asserts that the controller tried to redirect us to + # # the created book's URI. + # assert_response :found + # + # # Asserts that the controller really put the book in the database. + # assert_not_nil Book.find_by(title: "Love Hina") + # end + # end + # + # You can also send a real document in the simulated HTTP request. + # + # def test_create + # json = {book: { title: "Love Hina" }}.to_json + # post :create, body: json + # end + # + # ## Special instance variables + # + # ActionController::TestCase will also automatically provide the following + # instance variables for use in the tests: + # + # @controller + # : The controller instance that will be tested. + # + # @request + # : An ActionController::TestRequest, representing the current HTTP request. + # You can modify this object before sending the HTTP request. For example, + # you might want to set some session properties before sending a GET + # request. + # + # @response + # : An ActionDispatch::TestResponse object, representing the response of the + # last HTTP response. In the above example, `@response` becomes valid after + # calling `post`. If the various assert methods are not sufficient, then you + # may use this object to inspect the HTTP response in detail. + # + # + # ## Controller is automatically inferred + # + # ActionController::TestCase will automatically infer the controller under test + # from the test class name. If the controller cannot be inferred from the test + # class name, you can explicitly set it with `tests`. + # + # class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase + # tests WidgetController + # end + # + # ## Testing controller internals + # + # In addition to these specific assertions, you also have easy access to various + # collections that the regular test/unit assertions can be used against. These + # collections are: + # + # * session: Objects being saved in the session. + # * flash: The flash objects currently in the session. + # * cookies: Cookies being sent to the user on this request. + # + # + # These collections can be used just like any other hash: + # + # assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave" + # assert flash.empty? # makes sure that there's nothing in the flash + # + # On top of the collections, you have the complete URL that a given action + # redirected to available in `redirect_to_url`. + # + # For redirects within the same controller, you can even call follow_redirect + # and the redirect will be followed, triggering another action call which can + # then be asserted against. + # + # ## Manipulating session and cookie variables + # + # Sometimes you need to set up the session and cookie variables for a test. To + # do this just assign a value to the session or cookie collection: + # + # session[:key] = "value" + # cookies[:key] = "value" + # + # To clear the cookies for a test just clear the cookie collection: + # + # cookies.clear + # + # ## Testing named routes + # + # If you're using named routes, they can be easily tested using the original + # named routes' methods straight in the test case. + # + # assert_redirected_to page_url(title: 'foo') + class TestCase < ActiveSupport::TestCase + singleton_class.attr_accessor :executor_around_each_request + + module Behavior + extend ActiveSupport::Concern + include ActionDispatch::TestProcess + include ActiveSupport::Testing::ConstantLookup + include Rails::Dom::Testing::Assertions + + attr_reader :response, :request + + module ClassMethods + # Sets the controller class name. Useful if the name can't be inferred from test + # class. Normalizes `controller_class` before using. + # + # tests WidgetController + # tests :widget + # tests 'widget' + def tests(controller_class) + case controller_class + when String, Symbol + self.controller_class = "#{controller_class.to_s.camelize}Controller".constantize + when Class + self.controller_class = controller_class + else + raise ArgumentError, "controller class must be a String, Symbol, or Class" + end + end + + def controller_class=(new_class) + self._controller_class = new_class + end + + def controller_class + if current_controller_class = _controller_class + current_controller_class + else + self.controller_class = determine_default_controller_class(name) + end + end + + def determine_default_controller_class(name) + determine_constant_from_test_name(name) do |constant| + Class === constant && constant < ActionController::Metal + end + end + end + + # Simulate a GET request with the given parameters. + # + # * `action`: The controller action to call. + # * `params`: The hash with HTTP parameters that you want to pass. This may be + # `nil`. + # * `body`: The request body with a string that is appropriately encoded + # (`application/x-www-form-urlencoded` or `multipart/form-data`). + # * `session`: A hash of parameters to store in the session. This may be + # `nil`. + # * `flash`: A hash of parameters to store in the flash. This may be `nil`. + # + # + # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with `post`, + # `patch`, `put`, `delete`, and `head`. Example sending parameters, session, and + # setting a flash message: + # + # get :show, + # params: { id: 7 }, + # session: { user_id: 1 }, + # flash: { notice: 'This is flash message' } + # + # Note that the request method is not verified. The different methods are + # available to make the tests more expressive. + def get(action, **args) + process(action, method: "GET", **args) + end + + # Simulate a POST request with the given parameters and set/volley the response. + # See `get` for more details. + def post(action, **args) + process(action, method: "POST", **args) + end + + # Simulate a PATCH request with the given parameters and set/volley the + # response. See `get` for more details. + def patch(action, **args) + process(action, method: "PATCH", **args) + end + + # Simulate a PUT request with the given parameters and set/volley the response. + # See `get` for more details. + def put(action, **args) + process(action, method: "PUT", **args) + end + + # Simulate a DELETE request with the given parameters and set/volley the + # response. See `get` for more details. + def delete(action, **args) + process(action, method: "DELETE", **args) + end + + # Simulate a HEAD request with the given parameters and set/volley the response. + # See `get` for more details. + def head(action, **args) + process(action, method: "HEAD", **args) + end + + # Simulate an HTTP request to `action` by specifying request method, parameters + # and set/volley the response. + # + # * `action`: The controller action to call. + # * `method`: Request method used to send the HTTP request. Possible values + # are `GET`, `POST`, `PATCH`, `PUT`, `DELETE`, `HEAD`. Defaults to `GET`. + # Can be a symbol. + # * `params`: The hash with HTTP parameters that you want to pass. This may be + # `nil`. + # * `body`: The request body with a string that is appropriately encoded + # (`application/x-www-form-urlencoded` or `multipart/form-data`). + # * `session`: A hash of parameters to store in the session. This may be + # `nil`. + # * `flash`: A hash of parameters to store in the flash. This may be `nil`. + # * `format`: Request format. Defaults to `nil`. Can be string or symbol. + # * `as`: Content type. Defaults to `nil`. Must be a symbol that corresponds + # to a mime type. + # + # + # Example calling `create` action and sending two params: + # + # process :create, + # method: 'POST', + # params: { + # user: { name: 'Gaurish Sharma', email: 'user@example.com' } + # }, + # session: { user_id: 1 }, + # flash: { notice: 'This is flash message' } + # + # To simulate `GET`, `POST`, `PATCH`, `PUT`, `DELETE`, and `HEAD` requests + # prefer using #get, #post, #patch, #put, #delete and #head methods respectively + # which will make tests more expressive. + # + # It's not recommended to make more than one request in the same test. Instance + # variables that are set in one request will not persist to the next request, + # but it's not guaranteed that all Rails internal state will be reset. Prefer + # ActionDispatch::IntegrationTest for making multiple requests in the same test. + # + # Note that the request method is not verified. + def process(action, method: "GET", params: nil, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil) + check_required_ivars + @controller.clear_instance_variables_between_requests + + action = +action.to_s + http_method = method.to_s.upcase + + @html_document = nil + + cookies.update(@request.cookies) + cookies.update_cookies_from_jar + @request.set_header "HTTP_COOKIE", cookies.to_header + @request.delete_header "action_dispatch.cookies" + + @request = TestRequest.new scrub_env!(@request.env), @request.session, @controller.class + @response = build_response @response_klass + @response.request = @request + @controller.recycle! + + if body + @request.set_header "RAW_POST_DATA", body + end + + @request.set_header "REQUEST_METHOD", http_method + + if as + @request.content_type = Mime[as].to_s + format ||= as + end + + parameters = (params || {}).symbolize_keys + + if format + parameters[:format] = format + end + + setup_request(controller_class_name, action, parameters, session, flash, xhr) + process_controller_response(action, cookies, xhr) + end + + def controller_class_name + @controller.class.anonymous? ? "anonymous" : @controller.class.controller_path + end + + def generated_path(generated_extras) + generated_extras[0] + end + + def query_parameter_names(generated_extras) + generated_extras[1] + [:controller, :action] + end + + def setup_controller_request_and_response + @controller = nil unless defined? @controller + + @response_klass = ActionDispatch::TestResponse + + if klass = self.class.controller_class + if klass < ActionController::Live + @response_klass = LiveTestResponse + end + unless @controller + begin + @controller = klass.new + rescue + warn "could not construct controller #{klass}" if $VERBOSE + end + end + end + + @request = TestRequest.create(@controller.class) + @response = build_response @response_klass + @response.request = @request + + if @controller + @controller.request = @request + @controller.params = {} + end + end + + def build_response(klass) + klass.create + end + + included do + include ActionController::TemplateAssertions + include ActionDispatch::Assertions + class_attribute :_controller_class + setup :setup_controller_request_and_response + ActiveSupport.run_load_hooks(:action_controller_test_case, self) + end + + private + def setup_request(controller_class_name, action, parameters, session, flash, xhr) + generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action)) + generated_path = generated_path(generated_extras) + query_string_keys = query_parameter_names(generated_extras) + + @request.assign_parameters(@routes, controller_class_name, action, parameters, generated_path, query_string_keys) + + @request.session.update(session) if session + @request.flash.update(flash || {}) + + if xhr + @request.set_header "HTTP_X_REQUESTED_WITH", "XMLHttpRequest" + @request.fetch_header("HTTP_ACCEPT") do |k| + @request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ") + end + end + + @request.fetch_header("SCRIPT_NAME") do |k| + @request.set_header k, @controller.config.relative_url_root + end + end + + def wrap_execution(&block) + if ActionController::TestCase.executor_around_each_request && defined?(Rails.application) && Rails.application + Rails.application.executor.wrap(&block) + else + yield + end + end + + def process_controller_response(action, cookies, xhr) + begin + @controller.recycle! + + wrap_execution { @controller.dispatch(action, @request, @response) } + ensure + @request = @controller.request + @response = @controller.response + + if @request.have_cookie_jar? + unless @request.cookie_jar.committed? + @request.cookie_jar.write(@response) + cookies.update(@request.cookie_jar.instance_variable_get(:@cookies)) + cookies.update(@response.cookies) + end + end + @response.prepare! + + if flash_value = @request.flash.to_session_value + @request.session["flash"] = flash_value + else + @request.session.delete("flash") + end + + if xhr + @request.delete_header "HTTP_X_REQUESTED_WITH" + @request.delete_header "HTTP_ACCEPT" + end + @request.query_string = "" + + @response.sent! + end + + @response + end + + def scrub_env!(env) + env.delete_if do |k, _| + k.start_with?("rack.request", "action_dispatch.request", "action_dispatch.rescue") + end + env["rack.input"] = StringIO.new + env.delete "CONTENT_LENGTH" + env.delete "RAW_POST_DATA" + env + end + + def document_root_element + html_document.root + end + + def check_required_ivars + # Check for required instance variables so we can give an understandable error + # message. + [:@routes, :@controller, :@request, :@response].each do |iv_name| + if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil? + raise "#{iv_name} is nil: make sure you set it in your test's setup method." + end + end + end + end + + include Behavior + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch.rb new file mode 100644 index 00000000..24107c90 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch.rb @@ -0,0 +1,153 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) David Heinemeier Hansson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +#++ + +# :markup: markdown + +require "active_support" +require "active_support/rails" +require "active_support/core_ext/module/attribute_accessors" + +require "action_pack" +require "rack" +require "action_dispatch/deprecator" + +module Rack # :nodoc: + autoload :Test, "rack/test" +end + +# # Action Dispatch +# +# Action Dispatch is a module of Action Pack. +# +# Action Dispatch parses information about the web request, handles routing as +# defined by the user, and does advanced processing related to HTTP such as +# MIME-type negotiation, decoding parameters in POST, PATCH, or PUT bodies, +# handling HTTP caching logic, cookies and sessions. +module ActionDispatch + extend ActiveSupport::Autoload + + class MissingController < NameError + end + + eager_autoload do + autoload_under "http" do + autoload :ContentSecurityPolicy + autoload :InvalidParameterError, "action_dispatch/http/param_error" + autoload :ParamBuilder + autoload :ParamError + autoload :ParameterTypeError, "action_dispatch/http/param_error" + autoload :ParamsTooDeepError, "action_dispatch/http/param_error" + autoload :PermissionsPolicy + autoload :QueryParser + autoload :Request + autoload :Response + end + end + + autoload_under "middleware" do + autoload :AssumeSSL + autoload :HostAuthorization + autoload :RequestId + autoload :Callbacks + autoload :Cookies + autoload :ActionableExceptions + autoload :DebugExceptions + autoload :DebugLocks + autoload :DebugView + autoload :ExceptionWrapper + autoload :Executor + autoload :Flash + autoload :PublicExceptions + autoload :Reloader + autoload :RemoteIp + autoload :ServerTiming + autoload :ShowExceptions + autoload :SSL + autoload :Static + end + + autoload :Constants + autoload :Journey + autoload :MiddlewareStack, "action_dispatch/middleware/stack" + autoload :Routing + + module Http + extend ActiveSupport::Autoload + + autoload :Cache + autoload :Headers + autoload :MimeNegotiation + autoload :Parameters + autoload :UploadedFile, "action_dispatch/http/upload" + autoload :URL + end + + module Session + autoload :AbstractStore, "action_dispatch/middleware/session/abstract_store" + autoload :AbstractSecureStore, "action_dispatch/middleware/session/abstract_store" + autoload :CookieStore, "action_dispatch/middleware/session/cookie_store" + autoload :MemCacheStore, "action_dispatch/middleware/session/mem_cache_store" + autoload :CacheStore, "action_dispatch/middleware/session/cache_store" + + def self.resolve_store(session_store) # :nodoc: + self.const_get(session_store.to_s.camelize) + rescue NameError + raise <<~ERROR + Unable to resolve session store #{session_store.inspect}. + + #{session_store.inspect} resolves to ActionDispatch::Session::#{session_store.to_s.camelize}, + but that class is undefined. + + Is #{session_store.inspect} spelled correctly, and are any necessary gems installed? + ERROR + end + end + + mattr_accessor :test_app + + autoload_under "testing" do + autoload :Assertions + autoload :Integration + autoload :IntegrationTest, "action_dispatch/testing/integration" + autoload :TestProcess + autoload :TestRequest + autoload :TestResponse + autoload :AssertionResponse + end + + autoload :SystemTestCase, "action_dispatch/system_test_case" + + def eager_load! + super + Routing.eager_load! + end +end + +autoload :Mime, "action_dispatch/http/mime_type" + +ActiveSupport.on_load(:action_view) do + ActionView::Base.default_formats ||= Mime::SET.symbols + ActionView::Template.mime_types_implementation = Mime + ActionView::LookupContext::DetailsKey.clear +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/constants.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/constants.rb new file mode 100644 index 00000000..c1b53150 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/constants.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "rack/version" + +module ActionDispatch + module Constants + # Response Header keys for Rack 2.x and 3.x + if Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3") + VARY = "Vary" + CONTENT_ENCODING = "Content-Encoding" + CONTENT_SECURITY_POLICY = "Content-Security-Policy" + CONTENT_SECURITY_POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only" + LOCATION = "Location" + FEATURE_POLICY = "Feature-Policy" + X_REQUEST_ID = "X-Request-Id" + X_CASCADE = "X-Cascade" + SERVER_TIMING = "Server-Timing" + STRICT_TRANSPORT_SECURITY = "Strict-Transport-Security" + else + VARY = "vary" + CONTENT_ENCODING = "content-encoding" + CONTENT_SECURITY_POLICY = "content-security-policy" + CONTENT_SECURITY_POLICY_REPORT_ONLY = "content-security-policy-report-only" + LOCATION = "location" + FEATURE_POLICY = "feature-policy" + X_REQUEST_ID = "x-request-id" + X_CASCADE = "x-cascade" + SERVER_TIMING = "server-timing" + STRICT_TRANSPORT_SECURITY = "strict-transport-security" + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/deprecator.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/deprecator.rb new file mode 100644 index 00000000..453ad294 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/deprecator.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + def self.deprecator # :nodoc: + @deprecator ||= ActiveSupport::Deprecation.new + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/cache.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/cache.rb new file mode 100644 index 00000000..8f9bfc85 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/cache.rb @@ -0,0 +1,249 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + module Http + module Cache + module Request + HTTP_IF_MODIFIED_SINCE = "HTTP_IF_MODIFIED_SINCE" + HTTP_IF_NONE_MATCH = "HTTP_IF_NONE_MATCH" + + mattr_accessor :strict_freshness, default: false + + def if_modified_since + if since = get_header(HTTP_IF_MODIFIED_SINCE) + Time.rfc2822(since) rescue nil + end + end + + def if_none_match + get_header HTTP_IF_NONE_MATCH + end + + def if_none_match_etags + if_none_match ? if_none_match.split(",").each(&:strip!) : [] + end + + def not_modified?(modified_at) + if_modified_since && modified_at && if_modified_since >= modified_at + end + + def etag_matches?(etag) + if etag + validators = if_none_match_etags + validators.include?(etag) || validators.include?("*") + end + end + + # Check response freshness (`Last-Modified` and `ETag`) against request + # `If-Modified-Since` and `If-None-Match` conditions. + # If both headers are supplied, based on configuration, either `ETag` is preferred over `Last-Modified` + # or both are considered equally. You can adjust the preference with + # `config.action_dispatch.strict_freshness`. + # Reference: http://tools.ietf.org/html/rfc7232#section-6 + def fresh?(response) + if Request.strict_freshness + if if_none_match + etag_matches?(response.etag) + elsif if_modified_since + not_modified?(response.last_modified) + else + false + end + else + last_modified = if_modified_since + etag = if_none_match + + return false unless last_modified || etag + + success = true + success &&= not_modified?(response.last_modified) if last_modified + success &&= etag_matches?(response.etag) if etag + success + end + end + end + + module Response + attr_reader :cache_control + + def last_modified + if last = get_header(LAST_MODIFIED) + Time.httpdate(last) + end + end + + def last_modified? + has_header? LAST_MODIFIED + end + + def last_modified=(utc_time) + set_header LAST_MODIFIED, utc_time.httpdate + end + + def date + if date_header = get_header(DATE) + Time.httpdate(date_header) + end + end + + def date? + has_header? DATE + end + + def date=(utc_time) + set_header DATE, utc_time.httpdate + end + + # This method sets a weak ETag validator on the response so browsers and proxies + # may cache the response, keyed on the ETag. On subsequent requests, the + # `If-None-Match` header is set to the cached ETag. If it matches the current + # ETag, we can return a `304 Not Modified` response with no body, letting the + # browser or proxy know that their cache is current. Big savings in request time + # and network bandwidth. + # + # Weak ETags are considered to be semantically equivalent but not byte-for-byte + # identical. This is perfect for browser caching of HTML pages where we don't + # care about exact equality, just what the user is viewing. + # + # Strong ETags are considered byte-for-byte identical. They allow a browser or + # proxy cache to support `Range` requests, useful for paging through a PDF file + # or scrubbing through a video. Some CDNs only support strong ETags and will + # ignore weak ETags entirely. + # + # Weak ETags are what we almost always need, so they're the default. Check out + # #strong_etag= to provide a strong ETag validator. + def etag=(weak_validators) + self.weak_etag = weak_validators + end + + def weak_etag=(weak_validators) + set_header "ETag", generate_weak_etag(weak_validators) + end + + def strong_etag=(strong_validators) + set_header "ETag", generate_strong_etag(strong_validators) + end + + def etag?; etag; end + + # True if an ETag is set, and it's a weak validator (preceded with `W/`). + def weak_etag? + etag? && etag.start_with?('W/"') + end + + # True if an ETag is set, and it isn't a weak validator (not preceded with + # `W/`). + def strong_etag? + etag? && !weak_etag? + end + + private + DATE = "Date" + LAST_MODIFIED = "Last-Modified" + SPECIAL_KEYS = Set.new(%w[extras no-store no-cache max-age public private must-revalidate]) + + def generate_weak_etag(validators) + "W/#{generate_strong_etag(validators)}" + end + + def generate_strong_etag(validators) + %("#{ActiveSupport::Digest.hexdigest(ActiveSupport::Cache.expand_cache_key(validators))}") + end + + def cache_control_segments + if cache_control = _cache_control + cache_control.delete(" ").split(",") + end + end + + def cache_control_headers + cache_control = {} + + cache_control_segments&.each do |segment| + directive, argument = segment.split("=", 2) + + if SPECIAL_KEYS.include? directive + directive.tr!("-", "_") + cache_control[directive.to_sym] = argument || true + else + cache_control[:extras] ||= [] + cache_control[:extras] << segment + end + end + + cache_control + end + + def prepare_cache_control! + @cache_control = cache_control_headers + end + + DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate" + NO_STORE = "no-store" + NO_CACHE = "no-cache" + PUBLIC = "public" + PRIVATE = "private" + MUST_REVALIDATE = "must-revalidate" + IMMUTABLE = "immutable" + + def handle_conditional_get! + # Normally default cache control setting is handled by ETag middleware. But, if + # an etag is already set, the middleware defaults to `no-cache` unless a default + # `Cache-Control` value is previously set. So, set a default one here. + if (etag? || last_modified?) && !self._cache_control + self._cache_control = DEFAULT_CACHE_CONTROL + end + end + + def merge_and_normalize_cache_control!(cache_control) + control = cache_control_headers + + return if control.empty? && cache_control.empty? # Let middleware handle default behavior + + if cache_control.any? + # Any caching directive coming from a controller overrides no-cache/no-store in + # the default Cache-Control header. + control.delete(:no_cache) + control.delete(:no_store) + + if extras = control.delete(:extras) + cache_control[:extras] ||= [] + cache_control[:extras] += extras + cache_control[:extras].uniq! + end + + control.merge! cache_control + end + + options = [] + + if control[:no_store] + options << PRIVATE if control[:private] + options << NO_STORE + elsif control[:no_cache] + options << PUBLIC if control[:public] + options << NO_CACHE + options.concat(control[:extras]) if control[:extras] + else + extras = control[:extras] + max_age = control[:max_age] + stale_while_revalidate = control[:stale_while_revalidate] + stale_if_error = control[:stale_if_error] + + options << "max-age=#{max_age.to_i}" if max_age + options << (control[:public] ? PUBLIC : PRIVATE) + options << MUST_REVALIDATE if control[:must_revalidate] + options << "stale-while-revalidate=#{stale_while_revalidate.to_i}" if stale_while_revalidate + options << "stale-if-error=#{stale_if_error.to_i}" if stale_if_error + options << IMMUTABLE if control[:immutable] + options.concat(extras) if extras + end + + self._cache_control = options.join(", ") + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/content_disposition.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/content_disposition.rb new file mode 100644 index 00000000..da5ce42b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/content_disposition.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + module Http + class ContentDisposition # :nodoc: + def self.format(disposition:, filename:) + new(disposition: disposition, filename: filename).to_s + end + + attr_reader :disposition, :filename + + def initialize(disposition:, filename:) + @disposition = disposition + @filename = filename + end + + TRADITIONAL_ESCAPED_CHAR = /[^ A-Za-z0-9!\#$+.^_`|~-]/ + + def ascii_filename + 'filename="' + percent_escape(I18n.transliterate(filename), TRADITIONAL_ESCAPED_CHAR) + '"' + end + + RFC_5987_ESCAPED_CHAR = /[^A-Za-z0-9!\#$&+.^_`|~-]/ + + def utf8_filename + "filename*=UTF-8''" + percent_escape(filename, RFC_5987_ESCAPED_CHAR) + end + + def to_s + if filename + "#{disposition}; #{ascii_filename}; #{utf8_filename}" + else + "#{disposition}" + end + end + + private + def percent_escape(string, pattern) + string.gsub(pattern) do |char| + char.bytes.map { |byte| "%%%02X" % byte }.join + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/content_security_policy.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/content_security_policy.rb new file mode 100644 index 00000000..9194765a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/content_security_policy.rb @@ -0,0 +1,378 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/core_ext/object/deep_dup" +require "active_support/core_ext/array/wrap" + +module ActionDispatch # :nodoc: + # # Action Dispatch Content Security Policy + # + # Configures the HTTP [Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy) + # response header to help protect against XSS and + # injection attacks. + # + # Example global policy: + # + # Rails.application.config.content_security_policy do |policy| + # policy.default_src :self, :https + # policy.font_src :self, :https, :data + # policy.img_src :self, :https, :data + # policy.object_src :none + # policy.script_src :self, :https + # policy.style_src :self, :https + # + # # Specify URI for violation reports + # policy.report_uri "/csp-violation-report-endpoint" + # end + class ContentSecurityPolicy + class InvalidDirectiveError < StandardError + end + + class Middleware + def initialize(app) + @app = app + end + + def call(env) + status, headers, _ = response = @app.call(env) + + # Returning CSP headers with a 304 Not Modified is harmful, since nonces in the + # new CSP headers might not match nonces in the cached HTML. + return response if status == 304 + + return response if policy_present?(headers) + + request = ActionDispatch::Request.new env + + if policy = request.content_security_policy + nonce = request.content_security_policy_nonce + nonce_directives = request.content_security_policy_nonce_directives + context = request.controller_instance || request + headers[header_name(request)] = policy.build(context, nonce, nonce_directives) + end + + response + end + + private + def header_name(request) + if request.content_security_policy_report_only + ActionDispatch::Constants::CONTENT_SECURITY_POLICY_REPORT_ONLY + else + ActionDispatch::Constants::CONTENT_SECURITY_POLICY + end + end + + def policy_present?(headers) + headers[ActionDispatch::Constants::CONTENT_SECURITY_POLICY] || + headers[ActionDispatch::Constants::CONTENT_SECURITY_POLICY_REPORT_ONLY] + end + end + + module Request + POLICY = "action_dispatch.content_security_policy" + POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only" + NONCE_GENERATOR = "action_dispatch.content_security_policy_nonce_generator" + NONCE = "action_dispatch.content_security_policy_nonce" + NONCE_DIRECTIVES = "action_dispatch.content_security_policy_nonce_directives" + + def content_security_policy + get_header(POLICY) + end + + def content_security_policy=(policy) + set_header(POLICY, policy) + end + + def content_security_policy_report_only + get_header(POLICY_REPORT_ONLY) + end + + def content_security_policy_report_only=(value) + set_header(POLICY_REPORT_ONLY, value) + end + + def content_security_policy_nonce_generator + get_header(NONCE_GENERATOR) + end + + def content_security_policy_nonce_generator=(generator) + set_header(NONCE_GENERATOR, generator) + end + + def content_security_policy_nonce_directives + get_header(NONCE_DIRECTIVES) + end + + def content_security_policy_nonce_directives=(generator) + set_header(NONCE_DIRECTIVES, generator) + end + + def content_security_policy_nonce + if content_security_policy_nonce_generator + if nonce = get_header(NONCE) + nonce + else + set_header(NONCE, generate_content_security_policy_nonce) + end + end + end + + private + def generate_content_security_policy_nonce + content_security_policy_nonce_generator.call(self) + end + end + + MAPPINGS = { + self: "'self'", + unsafe_eval: "'unsafe-eval'", + wasm_unsafe_eval: "'wasm-unsafe-eval'", + unsafe_hashes: "'unsafe-hashes'", + unsafe_inline: "'unsafe-inline'", + none: "'none'", + http: "http:", + https: "https:", + data: "data:", + mediastream: "mediastream:", + allow_duplicates: "'allow-duplicates'", + blob: "blob:", + filesystem: "filesystem:", + report_sample: "'report-sample'", + script: "'script'", + strict_dynamic: "'strict-dynamic'", + ws: "ws:", + wss: "wss:" + }.freeze + + DIRECTIVES = { + base_uri: "base-uri", + child_src: "child-src", + connect_src: "connect-src", + default_src: "default-src", + font_src: "font-src", + form_action: "form-action", + frame_ancestors: "frame-ancestors", + frame_src: "frame-src", + img_src: "img-src", + manifest_src: "manifest-src", + media_src: "media-src", + object_src: "object-src", + prefetch_src: "prefetch-src", + require_trusted_types_for: "require-trusted-types-for", + script_src: "script-src", + script_src_attr: "script-src-attr", + script_src_elem: "script-src-elem", + style_src: "style-src", + style_src_attr: "style-src-attr", + style_src_elem: "style-src-elem", + trusted_types: "trusted-types", + worker_src: "worker-src" + }.freeze + + DEFAULT_NONCE_DIRECTIVES = %w[script-src style-src].freeze + + private_constant :MAPPINGS, :DIRECTIVES, :DEFAULT_NONCE_DIRECTIVES + + attr_reader :directives + + def initialize + @directives = {} + yield self if block_given? + end + + def initialize_copy(other) + @directives = other.directives.deep_dup + end + + DIRECTIVES.each do |name, directive| + define_method(name) do |*sources| + if sources.first + @directives[directive] = apply_mappings(sources) + else + @directives.delete(directive) + end + end + end + + # Specify whether to prevent the user agent from loading any assets over HTTP + # when the page uses HTTPS: + # + # policy.block_all_mixed_content + # + # Pass `false` to allow it again: + # + # policy.block_all_mixed_content false + # + def block_all_mixed_content(enabled = true) + if enabled + @directives["block-all-mixed-content"] = true + else + @directives.delete("block-all-mixed-content") + end + end + + # Restricts the set of plugins that can be embedded: + # + # policy.plugin_types "application/x-shockwave-flash" + # + # Leave empty to allow all plugins: + # + # policy.plugin_types + # + def plugin_types(*types) + if types.first + @directives["plugin-types"] = types + else + @directives.delete("plugin-types") + end + end + + # Enable the [report-uri](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri) + # directive. Violation reports will be sent to the + # specified URI: + # + # policy.report_uri "/csp-violation-report-endpoint" + # + def report_uri(uri) + @directives["report-uri"] = [uri] + end + + # Specify asset types for which [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) is required: + # + # policy.require_sri_for :script, :style + # + # Leave empty to not require Subresource Integrity: + # + # policy.require_sri_for + # + def require_sri_for(*types) + if types.first + @directives["require-sri-for"] = types + else + @directives.delete("require-sri-for") + end + end + + # Specify whether a [sandbox](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox) + # should be enabled for the requested resource: + # + # policy.sandbox + # + # Values can be passed as arguments: + # + # policy.sandbox "allow-scripts", "allow-modals" + # + # Pass `false` to disable the sandbox: + # + # policy.sandbox false + # + def sandbox(*values) + if values.empty? + @directives["sandbox"] = true + elsif values.first + @directives["sandbox"] = values + else + @directives.delete("sandbox") + end + end + + # Specify whether user agents should treat any assets over HTTP as HTTPS: + # + # policy.upgrade_insecure_requests + # + # Pass `false` to disable it: + # + # policy.upgrade_insecure_requests false + # + def upgrade_insecure_requests(enabled = true) + if enabled + @directives["upgrade-insecure-requests"] = true + else + @directives.delete("upgrade-insecure-requests") + end + end + + def build(context = nil, nonce = nil, nonce_directives = nil) + nonce_directives = DEFAULT_NONCE_DIRECTIVES if nonce_directives.nil? + build_directives(context, nonce, nonce_directives).compact.join("; ") + end + + private + def apply_mappings(sources) + sources.map do |source| + case source + when Symbol + apply_mapping(source) + when String, Proc + source + else + raise ArgumentError, "Invalid content security policy source: #{source.inspect}" + end + end + end + + def apply_mapping(source) + MAPPINGS.fetch(source) do + raise ArgumentError, "Unknown content security policy source mapping: #{source.inspect}" + end + end + + def build_directives(context, nonce, nonce_directives) + @directives.map do |directive, sources| + if sources.is_a?(Array) + if nonce && nonce_directive?(directive, nonce_directives) + "#{directive} #{build_directive(directive, sources, context).join(' ')} 'nonce-#{nonce}'" + else + "#{directive} #{build_directive(directive, sources, context).join(' ')}" + end + elsif sources + directive + else + nil + end + end + end + + def validate(directive, sources) + sources.flatten.each do |source| + if source.include?(";") || source != source.gsub(/[[:space:]]/, "") + raise InvalidDirectiveError, <<~MSG.squish + Invalid Content Security Policy #{directive}: "#{source}". + Directive values must not contain whitespace or semicolons. + Please use multiple arguments or other directive methods instead. + MSG + end + end + end + + def build_directive(directive, sources, context) + resolved_sources = sources.map { |source| resolve_source(source, context) } + + validate(directive, resolved_sources) + end + + def resolve_source(source, context) + case source + when String + source + when Symbol + source.to_s + when Proc + if context.nil? + raise RuntimeError, "Missing context for the dynamic content security policy source: #{source.inspect}" + else + resolved = context.instance_exec(&source) + apply_mappings(Array.wrap(resolved)) + end + else + raise RuntimeError, "Unexpected content security policy source: #{source.inspect}" + end + end + + def nonce_directive?(directive, nonce_directives) + nonce_directives.include?(directive) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/filter_parameters.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/filter_parameters.rb new file mode 100644 index 00000000..5cd409db --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/filter_parameters.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/parameter_filter" + +module ActionDispatch + module Http + # # Action Dispatch HTTP Filter Parameters + # + # Allows you to specify sensitive query string and POST parameters to filter + # from the request log. + # + # # Replaces values with "[FILTERED]" for keys that match /foo|bar/i. + # env["action_dispatch.parameter_filter"] = [:foo, "bar"] + # + # For more information about filter behavior, see + # ActiveSupport::ParameterFilter. + module FilterParameters + ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc: + NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new # :nodoc: + NULL_ENV_FILTER = ActiveSupport::ParameterFilter.new ENV_MATCH # :nodoc: + + def initialize + super + @filtered_parameters = nil + @filtered_env = nil + @filtered_path = nil + @parameter_filter = nil + end + + # Returns a hash of parameters with all sensitive data replaced. + def filtered_parameters + @filtered_parameters ||= parameter_filter.filter(parameters) + rescue ActionDispatch::Http::Parameters::ParseError + @filtered_parameters = {} + end + + # Returns a hash of request.env with all sensitive data replaced. + def filtered_env + @filtered_env ||= env_filter.filter(@env) + end + + # Reconstructs a path with all sensitive GET parameters replaced. + def filtered_path + @filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}" + end + + # Returns the `ActiveSupport::ParameterFilter` object used to filter in this + # request. + def parameter_filter + @parameter_filter ||= if has_header?("action_dispatch.parameter_filter") + parameter_filter_for get_header("action_dispatch.parameter_filter") + else + NULL_PARAM_FILTER + end + end + + private + def env_filter # :doc: + user_key = fetch_header("action_dispatch.parameter_filter") { + return NULL_ENV_FILTER + } + parameter_filter_for(Array(user_key) + ENV_MATCH) + end + + def parameter_filter_for(filters) # :doc: + ActiveSupport::ParameterFilter.new(filters) + end + + def filtered_query_string # :doc: + parts = query_string.split(/([&;])/) + filtered_parts = parts.map do |part| + if part.include?("=") + key, value = part.split("=", 2) + parameter_filter.filter(key => value).first.join("=") + else + part + end + end + filtered_parts.join("") + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/filter_redirect.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/filter_redirect.rb new file mode 100644 index 00000000..ed9e81f7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/filter_redirect.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + module Http + module FilterRedirect + FILTERED = "[FILTERED]" # :nodoc: + + def filtered_location # :nodoc: + if location_filter_match? + FILTERED + else + parameter_filtered_location + end + end + + private + def location_filters + if request + request.get_header("action_dispatch.redirect_filter") || [] + else + [] + end + end + + def location_filter_match? + location_filters.any? do |filter| + if String === filter + location.include?(filter) + elsif Regexp === filter + location.match?(filter) + end + end + end + + def parameter_filtered_location + uri = URI.parse(location) + unless uri.query.nil? || uri.query.empty? + parts = uri.query.split(/([&;])/) + filtered_parts = parts.map do |part| + if part.include?("=") + key, value = part.split("=", 2) + request.parameter_filter.filter(key => value).first.join("=") + else + part + end + end + uri.query = filtered_parts.join("") + end + uri.to_s + rescue URI::Error + FILTERED + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/headers.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/headers.rb new file mode 100644 index 00000000..d703acf3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/headers.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + module Http + # # Action Dispatch HTTP Headers + # + # Provides access to the request's HTTP headers from the environment. + # + # env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" } + # headers = ActionDispatch::Http::Headers.from_hash(env) + # headers["Content-Type"] # => "text/plain" + # headers["User-Agent"] # => "curl/7.43.0" + # + # Also note that when headers are mapped to CGI-like variables by the Rack + # server, both dashes and underscores are converted to underscores. This + # ambiguity cannot be resolved at this stage anymore. Both underscores and + # dashes have to be interpreted as if they were originally sent as dashes. + # + # # GET / HTTP/1.1 + # # ... + # # User-Agent: curl/7.43.0 + # # X_Custom_Header: token + # + # headers["X_Custom_Header"] # => nil + # headers["X-Custom-Header"] # => "token" + class Headers + CGI_VARIABLES = Set.new(%W[ + AUTH_TYPE + CONTENT_LENGTH + CONTENT_TYPE + GATEWAY_INTERFACE + HTTPS + PATH_INFO + PATH_TRANSLATED + QUERY_STRING + REMOTE_ADDR + REMOTE_HOST + REMOTE_IDENT + REMOTE_USER + REQUEST_METHOD + SCRIPT_NAME + SERVER_NAME + SERVER_PORT + SERVER_PROTOCOL + SERVER_SOFTWARE + ]).freeze + + HTTP_HEADER = /\A[A-Za-z0-9-]+\z/ + + include Enumerable + + def self.from_hash(hash) + new ActionDispatch::Request.new hash + end + + def initialize(request) # :nodoc: + @req = request + end + + # Returns the value for the given key mapped to @env. + def [](key) + @req.get_header env_name(key) + end + + # Sets the given value for the key mapped to @env. + def []=(key, value) + @req.set_header env_name(key), value + end + + # Add a value to a multivalued header like `Vary` or `Accept-Encoding`. + def add(key, value) + @req.add_header env_name(key), value + end + + def key?(key) + @req.has_header? env_name(key) + end + alias :include? :key? + + DEFAULT = Object.new # :nodoc: + + # Returns the value for the given key mapped to @env. + # + # If the key is not found and an optional code block is not provided, raises a + # `KeyError` exception. + # + # If the code block is provided, then it will be run and its result returned. + def fetch(key, default = DEFAULT) + @req.fetch_header(env_name(key)) do + return default unless default == DEFAULT + return yield if block_given? + raise KeyError, key + end + end + + def each(&block) + @req.each_header(&block) + end + + # Returns a new Http::Headers instance containing the contents of + # `headers_or_env` and the original instance. + def merge(headers_or_env) + headers = @req.dup.headers + headers.merge!(headers_or_env) + headers + end + + # Adds the contents of `headers_or_env` to original instance entries; duplicate + # keys are overwritten with the values from `headers_or_env`. + def merge!(headers_or_env) + headers_or_env.each do |key, value| + @req.set_header env_name(key), value + end + end + + def env; @req.env.dup; end + + private + # Converts an HTTP header name to an environment variable name if it is not + # contained within the headers hash. + def env_name(key) + key = key.to_s + if HTTP_HEADER.match?(key) + key = key.upcase + key.tr!("-", "_") + key.prepend("HTTP_") unless CGI_VARIABLES.include?(key) + end + key + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/mime_negotiation.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/mime_negotiation.rb new file mode 100644 index 00000000..ce44efb9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/mime_negotiation.rb @@ -0,0 +1,187 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/core_ext/module/attribute_accessors" + +module ActionDispatch + module Http + module MimeNegotiation + extend ActiveSupport::Concern + + class InvalidType < ::Mime::Type::InvalidMimeType; end + + RESCUABLE_MIME_FORMAT_ERRORS = [ + ActionController::BadRequest, + ActionDispatch::Http::Parameters::ParseError, + ] + + included do + mattr_accessor :ignore_accept_header, default: false + end + + # The MIME type of the HTTP request, such as [Mime](:xml). + def content_mime_type + fetch_header("action_dispatch.request.content_type") do |k| + v = if get_header("CONTENT_TYPE") =~ /^([^,;]*)/ + Mime::Type.lookup($1.strip.downcase) + else + nil + end + set_header k, v + rescue ::Mime::Type::InvalidMimeType => e + raise InvalidType, e.message + end + end + + def has_content_type? # :nodoc: + get_header "CONTENT_TYPE" + end + + # Returns the accepted MIME type for the request. + def accepts + fetch_header("action_dispatch.request.accepts") do |k| + header = get_header("HTTP_ACCEPT").to_s.strip + + v = if header.empty? + [content_mime_type] + else + Mime::Type.parse(header) + end + set_header k, v + rescue ::Mime::Type::InvalidMimeType => e + raise InvalidType, e.message + end + end + + # Returns the MIME type for the format used in the request. + # + # GET /posts/5.xml | request.format => Mime[:xml] + # GET /posts/5.xhtml | request.format => Mime[:html] + # GET /posts/5 | request.format => Mime[:html] or Mime[:js], or request.accepts.first + # + def format(_view_path = nil) + formats.first || Mime::NullType.instance + end + + def formats + fetch_header("action_dispatch.request.formats") do |k| + v = if params_readable? + Array(Mime[parameters[:format]]) + elsif use_accept_header && valid_accept_header + accepts.dup + elsif extension_format = format_from_path_extension + [extension_format] + elsif xhr? + [Mime[:js]] + else + [Mime[:html]] + end + + v.select! do |format| + format.symbol || format.ref == "*/*" + end + + set_header k, v + end + end + + # Sets the variant for template. + def variant=(variant) + variant = Array(variant) + + if variant.all?(Symbol) + @variant = ActiveSupport::ArrayInquirer.new(variant) + else + raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols." + end + end + + def variant + @variant ||= ActiveSupport::ArrayInquirer.new + end + + # Sets the format by string extension, which can be used to force custom formats + # that are not controlled by the extension. + # + # class ApplicationController < ActionController::Base + # before_action :adjust_format_for_iphone + # + # private + # def adjust_format_for_iphone + # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/] + # end + # end + def format=(extension) + parameters[:format] = extension.to_s + set_header "action_dispatch.request.formats", [Mime::Type.lookup_by_extension(parameters[:format])] + end + + # Sets the formats by string extensions. This differs from #format= by allowing + # you to set multiple, ordered formats, which is useful when you want to have a + # fallback. + # + # In this example, the `:iphone` format will be used if it's available, + # otherwise it'll fall back to the `:html` format. + # + # class ApplicationController < ActionController::Base + # before_action :adjust_format_for_iphone_with_html_fallback + # + # private + # def adjust_format_for_iphone_with_html_fallback + # request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/] + # end + # end + def formats=(extensions) + parameters[:format] = extensions.first.to_s + set_header "action_dispatch.request.formats", extensions.collect { |extension| + Mime::Type.lookup_by_extension(extension) + } + end + + # Returns the first MIME type that matches the provided array of MIME types. + def negotiate_mime(order) + formats.each do |priority| + if priority == Mime::ALL + return order.first + elsif order.include?(priority) + return priority + end + end + + order.include?(Mime::ALL) ? format : nil + end + + def should_apply_vary_header? + !params_readable? && use_accept_header && valid_accept_header + end + + private + # We use normal content negotiation unless you include **/** in your list, in + # which case we assume you're a browser and send HTML. + BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/ + + def params_readable? + parameters[:format] + rescue *RESCUABLE_MIME_FORMAT_ERRORS + false + end + + def valid_accept_header + (xhr? && (accept.present? || content_mime_type)) || + (accept.present? && !accept.match?(BROWSER_LIKE_ACCEPTS)) + end + + def use_accept_header + !self.class.ignore_accept_header + end + + def format_from_path_extension + path = get_header("action_dispatch.original_path") || get_header("PATH_INFO") + if match = path && path.match(/\.(\w+)\z/) + Mime[match.captures.first] + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/mime_type.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/mime_type.rb new file mode 100644 index 00000000..e91d2b13 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/mime_type.rb @@ -0,0 +1,389 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "singleton" + +module Mime + class Mimes + attr_reader :symbols + + include Enumerable + + def initialize + @mimes = [] + @symbols = [] + @symbols_set = Set.new + end + + def each(&block) + @mimes.each(&block) + end + + def <<(type) + @mimes << type + sym_type = type.to_sym + @symbols << sym_type + @symbols_set << sym_type + end + + def delete_if + @mimes.delete_if do |x| + if yield x + sym_type = x.to_sym + @symbols.delete(sym_type) + @symbols_set.delete(sym_type) + true + end + end + end + + def valid_symbols?(symbols) # :nodoc + symbols.all? { |s| @symbols_set.include?(s) } + end + end + + SET = Mimes.new + EXTENSION_LOOKUP = {} + LOOKUP = {} + + class << self + def [](type) + return type if type.is_a?(Type) + Type.lookup_by_extension(type) + end + + def symbols + SET.symbols + end + + def valid_symbols?(symbols) # :nodoc: + SET.valid_symbols?(symbols) + end + + def fetch(type, &block) + return type if type.is_a?(Type) + EXTENSION_LOOKUP.fetch(type.to_s, &block) + end + end + + # Encapsulates the notion of a MIME type. Can be used at render time, for + # example, with: + # + # class PostsController < ActionController::Base + # def show + # @post = Post.find(params[:id]) + # + # respond_to do |format| + # format.html + # format.ics { render body: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") } + # format.xml { render xml: @post } + # end + # end + # end + class Type + attr_reader :symbol + + @register_callbacks = [] + + # A simple helper class used in parsing the accept header. + class AcceptItem # :nodoc: + attr_accessor :index, :name, :q + alias :to_s :name + + def initialize(index, name, q = nil) + @index = index + @name = name + q ||= 0.0 if @name == "*/*" # Default wildcard match to end of list. + @q = ((q || 1.0).to_f * 100).to_i + end + + def <=>(item) + result = item.q <=> @q + result = @index <=> item.index if result == 0 + result + end + end + + class AcceptList # :nodoc: + def self.sort!(list) + list.sort! + + text_xml_idx = find_item_by_name list, "text/xml" + app_xml_idx = find_item_by_name list, Mime[:xml].to_s + + # Take care of the broken text/xml entry by renaming or deleting it. + if text_xml_idx && app_xml_idx + app_xml = list[app_xml_idx] + text_xml = list[text_xml_idx] + + app_xml.q = [text_xml.q, app_xml.q].max # Set the q value to the max of the two. + if app_xml_idx > text_xml_idx # Make sure app_xml is ahead of text_xml in the list. + list[app_xml_idx], list[text_xml_idx] = text_xml, app_xml + app_xml_idx, text_xml_idx = text_xml_idx, app_xml_idx + end + list.delete_at(text_xml_idx) # Delete text_xml from the list. + elsif text_xml_idx + list[text_xml_idx].name = Mime[:xml].to_s + end + + # Look for more specific XML-based types and sort them ahead of app/xml. + if app_xml_idx + app_xml = list[app_xml_idx] + idx = app_xml_idx + + while idx < list.length + type = list[idx] + break if type.q < app_xml.q + + if type.name.end_with? "+xml" + list[app_xml_idx], list[idx] = list[idx], app_xml + app_xml_idx = idx + end + idx += 1 + end + end + + list.map! { |i| Mime::Type.lookup(i.name) }.uniq! + list + end + + def self.find_item_by_name(array, name) + array.index { |item| item.name == name } + end + end + + class << self + TRAILING_STAR_REGEXP = /^(text|application)\/\*/ + # all media-type parameters need to be before the q-parameter + # https://www.rfc-editor.org/rfc/rfc7231#section-5.3.2 + PARAMETER_SEPARATOR_REGEXP = /;\s*q="?/ + ACCEPT_HEADER_REGEXP = /[^,\s"](?:[^,"]|"[^"]*")*/ + + def register_callback(&block) + @register_callbacks << block + end + + def lookup(string) + return LOOKUP[string] if LOOKUP.key?(string) + + # fallback to the media-type without parameters if it was not found + string = string.split(";", 2)[0]&.rstrip + LOOKUP[string] || Type.new(string) + end + + def lookup_by_extension(extension) + EXTENSION_LOOKUP[extension.to_s] + end + + # Registers an alias that's not used on MIME type lookup, but can be referenced + # directly. Especially useful for rendering different HTML versions depending on + # the user agent, like an iPhone. + def register_alias(string, symbol, extension_synonyms = []) + register(string, symbol, [], extension_synonyms, true) + end + + def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false) + new_mime = Type.new(string, symbol, mime_type_synonyms) + + SET << new_mime + + ([string] + mime_type_synonyms).each { |str| LOOKUP[str] = new_mime } unless skip_lookup + ([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = new_mime } + + @register_callbacks.each do |callback| + callback.call(new_mime) + end + new_mime + end + + def parse(accept_header) + if !accept_header.include?(",") + if (index = accept_header.index(PARAMETER_SEPARATOR_REGEXP)) + accept_header = accept_header[0, index].strip + end + return [] if accept_header.blank? + parse_trailing_star(accept_header) || Array(Mime::Type.lookup(accept_header)) + else + list, index = [], 0 + accept_header.scan(ACCEPT_HEADER_REGEXP).each do |header| + params, q = header.split(PARAMETER_SEPARATOR_REGEXP) + + next unless params + params.strip! + next if params.empty? + + params = parse_trailing_star(params) || [params] + + params.each do |m| + list << AcceptItem.new(index, m.to_s, q) + index += 1 + end + end + AcceptList.sort! list + end + end + + def parse_trailing_star(accept_header) + parse_data_with_trailing_star($1) if accept_header =~ TRAILING_STAR_REGEXP + end + + # For an input of `'text'`, returns `[Mime[:json], Mime[:xml], Mime[:ics], + # Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]]`. + # + # For an input of `'application'`, returns `[Mime[:html], Mime[:js], Mime[:xml], + # Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]]`. + def parse_data_with_trailing_star(type) + Mime::SET.select { |m| m.match?(type) } + end + + # This method is opposite of register method. + # + # To unregister a MIME type: + # + # Mime::Type.unregister(:mobile) + def unregister(symbol) + symbol = symbol.downcase + if mime = Mime[symbol] + SET.delete_if { |v| v.eql?(mime) } + LOOKUP.delete_if { |_, v| v.eql?(mime) } + EXTENSION_LOOKUP.delete_if { |_, v| v.eql?(mime) } + end + end + end + + attr_reader :hash + + MIME_NAME = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}" + MIME_PARAMETER_VALUE = "(?:#{MIME_NAME}|\"[^\"\r\\\\]*\")" + MIME_PARAMETER = "\s*;\s*#{MIME_NAME}(?:=#{MIME_PARAMETER_VALUE})?" + MIME_REGEXP = /\A(?:\*\/\*|#{MIME_NAME}\/(?:\*|#{MIME_NAME})(?>#{MIME_PARAMETER})*\s*)\z/ + + class InvalidMimeType < StandardError; end + + def initialize(string, symbol = nil, synonyms = []) + unless MIME_REGEXP.match?(string) + raise InvalidMimeType, "#{string.inspect} is not a valid MIME type" + end + @symbol, @synonyms = symbol, synonyms + @string = string + @hash = [@string, @synonyms, @symbol].hash + end + + def to_s + @string + end + + def to_str + to_s + end + + def to_sym + @symbol + end + + def ref + symbol || to_s + end + + def ===(list) + if list.is_a?(Array) + (@synonyms + [ self ]).any? { |synonym| list.include?(synonym) } + else + super + end + end + + def ==(mime_type) + return false unless mime_type + (@synonyms + [ self ]).any? do |synonym| + synonym.to_s == mime_type.to_s || synonym.to_sym == mime_type.to_sym + end + end + + def eql?(other) + super || (self.class == other.class && + @string == other.string && + @synonyms == other.synonyms && + @symbol == other.symbol) + end + + def =~(mime_type) + return false unless mime_type + regexp = Regexp.new(Regexp.quote(mime_type.to_s)) + @synonyms.any? { |synonym| synonym.to_s =~ regexp } || @string =~ regexp + end + + def match?(mime_type) + return false unless mime_type + regexp = Regexp.new(Regexp.quote(mime_type.to_s)) + @synonyms.any? { |synonym| synonym.to_s.match?(regexp) } || @string.match?(regexp) + end + + def html? + (symbol == :html) || @string.include?("html") + end + + def all?; false; end + + protected + attr_reader :string, :synonyms + + private + def to_ary; end + def to_a; end + + def method_missing(method, ...) + if method.end_with?("?") + method[0..-2].downcase.to_sym == to_sym + else + super + end + end + + def respond_to_missing?(method, include_private = false) + method.end_with?("?") || super + end + end + + class AllType < Type + include Singleton + + def initialize + super "*/*", nil + end + + def all?; true; end + def html?; true; end + end + + # ALL isn't a real MIME type, so we don't register it for lookup with the other + # concrete types. It's a wildcard match that we use for `respond_to` negotiation + # internals. + ALL = AllType.instance + + class NullType + include Singleton + + def nil? + true + end + + def to_s + "" + end + + def ref; end + + private + def respond_to_missing?(method, _) + method.end_with?("?") + end + + def method_missing(method, ...) + false if method.end_with?("?") + end + end +end + +require "action_dispatch/http/mime_types" diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/mime_types.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/mime_types.rb new file mode 100644 index 00000000..426154fe --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/mime_types.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +# Build list of Mime types for HTTP responses +# https://www.iana.org/assignments/media-types/ + +# :markup: markdown + +Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml ) +Mime::Type.register "text/plain", :text, [], %w(txt) +Mime::Type.register "text/javascript", :js, %w( application/javascript application/x-javascript ) +Mime::Type.register "text/css", :css +Mime::Type.register "text/calendar", :ics +Mime::Type.register "text/csv", :csv +Mime::Type.register "text/vcard", :vcf +Mime::Type.register "text/vtt", :vtt, %w(vtt) + +Mime::Type.register "image/png", :png, [], %w(png) +Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg) +Mime::Type.register "image/gif", :gif, [], %w(gif) +Mime::Type.register "image/bmp", :bmp, [], %w(bmp) +Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff) +Mime::Type.register "image/svg+xml", :svg +Mime::Type.register "image/webp", :webp, [], %w(webp) + +Mime::Type.register "video/mpeg", :mpeg, [], %w(mpg mpeg mpe) + +Mime::Type.register "audio/mpeg", :mp3, [], %w(mp1 mp2 mp3) +Mime::Type.register "audio/ogg", :ogg, [], %w(oga ogg spx opus) +Mime::Type.register "audio/aac", :m4a, %w( audio/mp4 ), %w(m4a mpg4 aac) + +Mime::Type.register "video/webm", :webm, [], %w(webm) +Mime::Type.register "video/mp4", :mp4, [], %w(mp4 m4v) + +Mime::Type.register "font/otf", :otf, [], %w(otf) +Mime::Type.register "font/ttf", :ttf, [], %w(ttf) +Mime::Type.register "font/woff", :woff, [], %w(woff) +Mime::Type.register "font/woff2", :woff2, [], %w(woff2) + +Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml ) +Mime::Type.register "application/rss+xml", :rss +Mime::Type.register "application/atom+xml", :atom +Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml ), %w(yml yaml) + +Mime::Type.register "multipart/form-data", :multipart_form +Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form + +# https://www.ietf.org/rfc/rfc4627.txt +# http://www.json.org/JSONRequest.html +# https://www.ietf.org/rfc/rfc7807.txt +Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest application/problem+json ) + +Mime::Type.register "application/pdf", :pdf, [], %w(pdf) +Mime::Type.register "application/zip", :zip, [], %w(zip) +Mime::Type.register "application/gzip", :gzip, %w(application/x-gzip), %w(gz) diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/param_builder.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/param_builder.rb new file mode 100644 index 00000000..cf6cd6f9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/param_builder.rb @@ -0,0 +1,186 @@ +# frozen_string_literal: true + +module ActionDispatch + class ParamBuilder + # -- + # This implementation is based on Rack::QueryParser, + # Copyright (C) 2007-2021 Leah Neukirchen + + def self.make_default(param_depth_limit) + new param_depth_limit + end + + attr_reader :param_depth_limit + + def initialize(param_depth_limit) + @param_depth_limit = param_depth_limit + end + + cattr_accessor :ignore_leading_brackets + + LEADING_BRACKETS_COMPAT = defined?(::Rack::RELEASE) && ::Rack::RELEASE.to_s.start_with?("2.") + + cattr_accessor :default + self.default = make_default(100) + + class << self + delegate :from_query_string, :from_pairs, :from_hash, to: :default + end + + def from_query_string(qs, separator: nil, encoding_template: nil) + from_pairs QueryParser.each_pair(qs, separator), encoding_template: encoding_template + end + + def from_pairs(pairs, encoding_template: nil) + params = make_params + + pairs.each do |k, v| + if Hash === v + v = ActionDispatch::Http::UploadedFile.new(v) + end + + store_nested_param(params, k, v, 0, encoding_template) + end + + params + rescue ArgumentError => e + raise InvalidParameterError, e.message, e.backtrace + end + + def from_hash(hash, encoding_template: nil) + # Force encodings from encoding template + hash = Request::Utils::CustomParamEncoder.encode_for_template(hash, encoding_template) + + # Assert valid encoding + Request::Utils.check_param_encoding(hash) + + # Convert hashes to HWIA (or UploadedFile), and deep-munge nils + # out of arrays + hash = Request::Utils.normalize_encode_params(hash) + + hash + end + + private + def store_nested_param(params, name, v, depth, encoding_template = nil) + raise ParamsTooDeepError if depth >= param_depth_limit + + if !name + # nil name, treat same as empty string (required by tests) + k = after = "" + elsif depth == 0 + if ignore_leading_brackets || (ignore_leading_brackets.nil? && LEADING_BRACKETS_COMPAT) + # Rack 2 compatible behavior, ignore leading brackets + if name =~ /\A[\[\]]*([^\[\]]+)\]*/ + k = $1 + after = $' || "" + + if !ignore_leading_brackets && (k != $& || !after.empty? && !after.start_with?("[")) + ActionDispatch.deprecator.warn("Skipping over leading brackets in parameter name #{name.inspect} is deprecated and will parse differently in Rails 8.1 or Rack 3.0.") + end + else + k = name + after = "" + end + else + # Start of parsing, don't treat [] or [ at start of string specially + if start = name.index("[", 1) + # Start of parameter nesting, use part before brackets as key + k = name[0, start] + after = name[start, name.length] + else + # Plain parameter with no nesting + k = name + after = "" + end + end + elsif name.start_with?("[]") + # Array nesting + k = "[]" + after = name[2, name.length] + elsif name.start_with?("[") && (start = name.index("]", 1)) + # Hash nesting, use the part inside brackets as the key + k = name[1, start - 1] + after = name[start + 1, name.length] + else + # Probably malformed input, nested but not starting with [ + # treat full name as key for backwards compatibility. + k = name + after = "" + end + + return if k.empty? + + if depth == 0 && String === v + # We have to wait until we've found the top part of the name, + # because that's what the encoding template is configured with + if encoding_template && (designated_encoding = encoding_template[k]) && !v.frozen? + v.force_encoding(designated_encoding) + end + + # ... and we can't validate the encoding until after we've + # applied any template override + unless v.valid_encoding? + raise InvalidParameterError, "Invalid encoding for parameter: #{v.scrub}" + end + end + + if after == "" + if k == "[]" && depth != 0 + return (v || !ActionDispatch::Request::Utils.perform_deep_munge) ? [v] : [] + else + params[k] = v + end + elsif after == "[" + params[name] = v + elsif after == "[]" + params[k] ||= [] + raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array) + params[k] << v if v || !ActionDispatch::Request::Utils.perform_deep_munge + elsif after.start_with?("[]") + # Recognize x[][y] (hash inside array) parameters + unless after[2] == "[" && after.end_with?("]") && (child_key = after[3, after.length - 4]) && !child_key.empty? && !child_key.index("[") && !child_key.index("]") + # Handle other nested array parameters + child_key = after[2, after.length] + end + params[k] ||= [] + raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array) + if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key) + store_nested_param(params[k].last, child_key, v, depth + 1) + else + params[k] << store_nested_param(make_params, child_key, v, depth + 1) + end + else + params[k] ||= make_params + raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k]) + params[k] = store_nested_param(params[k], after, v, depth + 1) + end + + params + end + + def make_params + ActiveSupport::HashWithIndifferentAccess.new + end + + def new_depth_limit(param_depth_limit) + self.class.new @params_class, param_depth_limit + end + + def params_hash_type?(obj) + Hash === obj + end + + def params_hash_has_key?(hash, key) + return false if key.include?("[]") + + key.split(/[\[\]]+/).inject(hash) do |h, part| + next h if part == "" + return false unless params_hash_type?(h) && h.key?(part) + h[part] + end + + true + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/param_error.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/param_error.rb new file mode 100644 index 00000000..4bf8a1af --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/param_error.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module ActionDispatch + class ParamError < ActionDispatch::Http::Parameters::ParseError + def initialize(message = nil) + super + end + + def self.===(other) + super || ( + defined?(Rack::Utils::ParameterTypeError) && Rack::Utils::ParameterTypeError === other || + defined?(Rack::Utils::InvalidParameterError) && Rack::Utils::InvalidParameterError === other || + defined?(Rack::QueryParser::ParamsTooDeepError) && Rack::QueryParser::ParamsTooDeepError === other + ) + end + end + + class ParameterTypeError < ParamError + end + + class InvalidParameterError < ParamError + end + + class ParamsTooDeepError < ParamError + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/parameters.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/parameters.rb new file mode 100644 index 00000000..1c702569 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/parameters.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + module Http + module Parameters + extend ActiveSupport::Concern + + PARAMETERS_KEY = "action_dispatch.request.path_parameters" + + DEFAULT_PARSERS = { + Mime[:json].symbol => -> (raw_post) { + data = ActiveSupport::JSON.decode(raw_post) + data.is_a?(Hash) ? data : { _json: data } + } + } + + # Raised when raw data from the request cannot be parsed by the parser defined + # for request's content MIME type. + class ParseError < StandardError + def initialize(message = $!.message) + super(message) + end + end + + included do + class << self + # Returns the parameter parsers. + attr_reader :parameter_parsers + end + + self.parameter_parsers = DEFAULT_PARSERS + end + + module ClassMethods + # Configure the parameter parser for a given MIME type. + # + # It accepts a hash where the key is the symbol of the MIME type and the value + # is a proc. + # + # original_parsers = ActionDispatch::Request.parameter_parsers + # xml_parser = -> (raw_post) { Hash.from_xml(raw_post) || {} } + # new_parsers = original_parsers.merge(xml: xml_parser) + # ActionDispatch::Request.parameter_parsers = new_parsers + def parameter_parsers=(parsers) + @parameter_parsers = parsers.transform_keys { |key| key.respond_to?(:symbol) ? key.symbol : key } + end + end + + # Returns both GET and POST parameters in a single hash. + def parameters + params = get_header("action_dispatch.request.parameters") + return params if params + + params = begin + request_parameters.merge(query_parameters) + rescue EOFError + query_parameters.dup + end + params.merge!(path_parameters) + set_header("action_dispatch.request.parameters", params) + params + end + alias :params :parameters + + def path_parameters=(parameters) # :nodoc: + delete_header("action_dispatch.request.parameters") + + parameters = Request::Utils.set_binary_encoding(self, parameters, parameters[:controller], parameters[:action]) + # If any of the path parameters has an invalid encoding then raise since it's + # likely to trigger errors further on. + Request::Utils.check_param_encoding(parameters) + + set_header PARAMETERS_KEY, parameters + rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e + raise ActionController::BadRequest.new("Invalid path parameters: #{e.message}") + end + + # Returns a hash with the parameters used to form the path of the request. + # Returned hash keys are symbols: + # + # { action: "my_action", controller: "my_controller" } + def path_parameters + get_header(PARAMETERS_KEY) || set_header(PARAMETERS_KEY, {}) + end + + private + def parse_formatted_parameters(parsers) + return yield if content_length.zero? || content_mime_type.nil? + + strategy = parsers.fetch(content_mime_type.symbol) { return yield } + + begin + strategy.call(raw_post) + rescue # JSON or Ruby code block errors. + log_parse_error_once + raise ParseError, "Error occurred while parsing request parameters" + end + end + + def log_parse_error_once + @parse_error_logged ||= begin + parse_logger = logger || ActiveSupport::Logger.new($stderr) + parse_logger.debug <<~MSG.chomp + Error occurred while parsing request parameters. + Contents: + + #{raw_post} + MSG + end + end + + def params_parsers + ActionDispatch::Request.parameter_parsers + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/permissions_policy.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/permissions_policy.rb new file mode 100644 index 00000000..682bec18 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/permissions_policy.rb @@ -0,0 +1,189 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/core_ext/object/deep_dup" + +module ActionDispatch # :nodoc: + # # Action Dispatch PermissionsPolicy + # + # Configures the HTTP + # [Feature-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy) + # response header to specify which browser features the current + # document and its iframes can use. + # + # Example global policy: + # + # Rails.application.config.permissions_policy do |policy| + # policy.camera :none + # policy.gyroscope :none + # policy.microphone :none + # policy.usb :none + # policy.fullscreen :self + # policy.payment :self, "https://secure.example.com" + # end + # + # The Feature-Policy header has been renamed to Permissions-Policy. The + # Permissions-Policy requires a different implementation and isn't yet supported + # by all browsers. To avoid having to rename this middleware in the future we + # use the new name for the middleware but keep the old header name and + # implementation for now. + class PermissionsPolicy + class Middleware + def initialize(app) + @app = app + end + + def call(env) + _, headers, _ = response = @app.call(env) + + return response if policy_present?(headers) + + request = ActionDispatch::Request.new(env) + + if policy = request.permissions_policy + headers[ActionDispatch::Constants::FEATURE_POLICY] = policy.build(request.controller_instance) + end + + if policy_empty?(policy) + headers.delete(ActionDispatch::Constants::FEATURE_POLICY) + end + + response + end + + private + def policy_present?(headers) + headers[ActionDispatch::Constants::FEATURE_POLICY] + end + + def policy_empty?(policy) + policy&.directives&.empty? + end + end + + module Request + POLICY = "action_dispatch.permissions_policy" + + def permissions_policy + get_header(POLICY) + end + + def permissions_policy=(policy) + set_header(POLICY, policy) + end + end + + MAPPINGS = { + self: "'self'", + none: "'none'", + }.freeze + + # List of available permissions can be found at + # https://github.com/w3c/webappsec-permissions-policy/blob/main/features.md#policy-controlled-features + DIRECTIVES = { + accelerometer: "accelerometer", + ambient_light_sensor: "ambient-light-sensor", + autoplay: "autoplay", + camera: "camera", + display_capture: "display-capture", + encrypted_media: "encrypted-media", + fullscreen: "fullscreen", + geolocation: "geolocation", + gyroscope: "gyroscope", + hid: "hid", + idle_detection: "idle-detection", + keyboard_map: "keyboard-map", + magnetometer: "magnetometer", + microphone: "microphone", + midi: "midi", + payment: "payment", + picture_in_picture: "picture-in-picture", + screen_wake_lock: "screen-wake-lock", + serial: "serial", + sync_xhr: "sync-xhr", + usb: "usb", + web_share: "web-share", + }.freeze + + private_constant :MAPPINGS, :DIRECTIVES + + attr_reader :directives + + def initialize + @directives = {} + yield self if block_given? + end + + def initialize_copy(other) + @directives = other.directives.deep_dup + end + + DIRECTIVES.each do |name, directive| + define_method(name) do |*sources| + if sources.first + @directives[directive] = apply_mappings(sources) + else + @directives.delete(directive) + end + end + end + + def build(context = nil) + build_directives(context).compact.join("; ") + end + + private + def apply_mappings(sources) + sources.map do |source| + case source + when Symbol + apply_mapping(source) + when String, Proc + source + else + raise ArgumentError, "Invalid HTTP permissions policy source: #{source.inspect}" + end + end + end + + def apply_mapping(source) + MAPPINGS.fetch(source) do + raise ArgumentError, "Unknown HTTP permissions policy source mapping: #{source.inspect}" + end + end + + def build_directives(context) + @directives.map do |directive, sources| + if sources.is_a?(Array) + "#{directive} #{build_directive(sources, context).join(' ')}" + elsif sources + directive + else + nil + end + end + end + + def build_directive(sources, context) + sources.map { |source| resolve_source(source, context) } + end + + def resolve_source(source, context) + case source + when String + source + when Symbol + source.to_s + when Proc + if context.nil? + raise RuntimeError, "Missing context for the dynamic permissions policy source: #{source.inspect}" + else + context.instance_exec(&source) + end + else + raise RuntimeError, "Unexpected permissions policy source: #{source.inspect}" + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/query_parser.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/query_parser.rb new file mode 100644 index 00000000..55488b61 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/query_parser.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require "uri" +require "rack" + +module ActionDispatch + class QueryParser + DEFAULT_SEP = /& */n + COMPAT_SEP = /[&;] */n + COMMON_SEP = { ";" => /; */n, ";," => /[;,] */n, "&" => /& */n, "&;" => /[&;] */n } + + cattr_accessor :strict_query_string_separator + + SEMICOLON_COMPAT = defined?(::Rack::QueryParser::DEFAULT_SEP) && ::Rack::QueryParser::DEFAULT_SEP.to_s.include?(";") + + #-- + # Note this departs from WHATWG's specified parsing algorithm by + # giving a nil value for keys that do not use '='. Callers that need + # the standard's interpretation can use `v.to_s`. + def self.each_pair(s, separator = nil) + return enum_for(:each_pair, s, separator) unless block_given? + + s ||= "" + + splitter = + if separator + COMMON_SEP[separator] || /[#{separator}] */n + elsif strict_query_string_separator + DEFAULT_SEP + elsif SEMICOLON_COMPAT && s.include?(";") + if strict_query_string_separator.nil? + ActionDispatch.deprecator.warn("Using semicolon as a query string separator is deprecated and will not be supported in Rails 8.1 or Rack 3.0. Use `&` instead.") + end + COMPAT_SEP + else + DEFAULT_SEP + end + + s.split(splitter).each do |part| + next if part.empty? + + k, v = part.split("=", 2) + + k = URI.decode_www_form_component(k) + v &&= URI.decode_www_form_component(v) + + yield k, v + end + + nil + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/rack_cache.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/rack_cache.rb new file mode 100644 index 00000000..1429e74b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/rack_cache.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +# :enddoc: + +# :markup: markdown + +require "rack/cache" +require "rack/cache/context" +require "active_support/cache" + +module ActionDispatch + class RailsMetaStore < Rack::Cache::MetaStore + def self.resolve(uri) + new + end + + def initialize(store = Rails.cache) + @store = store + end + + def read(key) + if data = @store.read(key) + Marshal.load(data) + else + [] + end + end + + def write(key, value) + @store.write(key, Marshal.dump(value)) + end + + ::Rack::Cache::MetaStore::RAILS = self + end + + class RailsEntityStore < Rack::Cache::EntityStore + def self.resolve(uri) + new + end + + def initialize(store = Rails.cache) + @store = store + end + + def exist?(key) + @store.exist?(key) + end + + def open(key) + @store.read(key) + end + + def read(key) + body = open(key) + body.join if body + end + + def write(body) + buf = [] + key, size = slurp(body) { |part| buf << part } + @store.write(key, buf) + [key, size] + end + + ::Rack::Cache::EntityStore::RAILS = self + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/request.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/request.rb new file mode 100644 index 00000000..a863baa1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/request.rb @@ -0,0 +1,541 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "stringio" + +require "active_support/inflector" +require "action_dispatch/http/headers" +require "action_controller/metal/exceptions" +require "rack/request" +require "action_dispatch/http/cache" +require "action_dispatch/http/mime_negotiation" +require "action_dispatch/http/parameters" +require "action_dispatch/http/filter_parameters" +require "action_dispatch/http/upload" +require "action_dispatch/http/url" +require "active_support/core_ext/array/conversions" + +module ActionDispatch + class Request + include Rack::Request::Helpers + include ActionDispatch::Http::Cache::Request + include ActionDispatch::Http::MimeNegotiation + include ActionDispatch::Http::Parameters + include ActionDispatch::Http::FilterParameters + include ActionDispatch::Http::URL + include ActionDispatch::ContentSecurityPolicy::Request + include ActionDispatch::PermissionsPolicy::Request + include Rack::Request::Env + + autoload :Session, "action_dispatch/request/session" + autoload :Utils, "action_dispatch/request/utils" + + LOCALHOST = Regexp.union [/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/] + + ENV_METHODS = %w[ AUTH_TYPE GATEWAY_INTERFACE + PATH_TRANSLATED REMOTE_HOST + REMOTE_IDENT REMOTE_USER REMOTE_ADDR + SERVER_NAME SERVER_PROTOCOL + ORIGINAL_SCRIPT_NAME + + HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING + HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM + HTTP_NEGOTIATE HTTP_PRAGMA HTTP_CLIENT_IP + HTTP_X_FORWARDED_FOR HTTP_ORIGIN HTTP_VERSION + HTTP_X_CSRF_TOKEN HTTP_X_REQUEST_ID HTTP_X_FORWARDED_HOST + ].freeze + + ENV_METHODS.each do |env| + class_eval <<-METHOD, __FILE__, __LINE__ + 1 + # frozen_string_literal: true + def #{env.delete_prefix("HTTP_").downcase} # def accept_charset + get_header "#{env}" # get_header "HTTP_ACCEPT_CHARSET" + end # end + METHOD + end + + TRANSFER_ENCODING = "HTTP_TRANSFER_ENCODING" # :nodoc: + + def self.empty + new({}) + end + + def initialize(env) + super + + @rack_request = Rack::Request.new(env) + + @method = nil + @request_method = nil + @remote_ip = nil + @original_fullpath = nil + @fullpath = nil + @ip = nil + end + + attr_reader :rack_request + + def commit_cookie_jar! # :nodoc: + end + + PASS_NOT_FOUND = Class.new { # :nodoc: + def self.action(_); self; end + def self.call(_); [404, { Constants::X_CASCADE => "pass" }, []]; end + def self.action_encoding_template(action); false; end + } + + def controller_class + params = path_parameters + params[:action] ||= "index" + controller_class_for(params[:controller]) + end + + def controller_class_for(name) + if name + controller_param = name.underscore + const_name = controller_param.camelize << "Controller" + begin + const_name.constantize + rescue NameError => error + if error.missing_name == const_name || const_name.start_with?("#{error.missing_name}::") + raise MissingController.new(error.message, error.name) + else + raise + end + end + else + PASS_NOT_FOUND + end + end + + # Returns true if the request has a header matching the given key parameter. + # + # request.key? :ip_spoofing_check # => true + def key?(key) + has_header? key + end + + # HTTP methods from [RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1](https://www.ietf.org/rfc/rfc2616.txt) + RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT) + # HTTP methods from [RFC 2518: HTTP Extensions for Distributed Authoring -- WEBDAV](https://www.ietf.org/rfc/rfc2518.txt) + RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK) + # HTTP methods from [RFC 3253: Versioning Extensions to WebDAV](https://www.ietf.org/rfc/rfc3253.txt) + RFC3253 = %w(VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL MKACTIVITY) + # HTTP methods from [RFC 3648: WebDAV Ordered Collections Protocol](https://www.ietf.org/rfc/rfc3648.txt) + RFC3648 = %w(ORDERPATCH) + # HTTP methods from [RFC 3744: WebDAV Access Control Protocol](https://www.ietf.org/rfc/rfc3744.txt) + RFC3744 = %w(ACL) + # HTTP methods from [RFC 5323: WebDAV SEARCH](https://www.ietf.org/rfc/rfc5323.txt) + RFC5323 = %w(SEARCH) + # HTTP methods from [RFC 4791: Calendaring Extensions to WebDAV](https://www.ietf.org/rfc/rfc4791.txt) + RFC4791 = %w(MKCALENDAR) + # HTTP methods from [RFC 5789: PATCH Method for HTTP](https://www.ietf.org/rfc/rfc5789.txt) + RFC5789 = %w(PATCH) + + HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789 + + HTTP_METHOD_LOOKUP = {} + + # Populate the HTTP method lookup cache. + HTTP_METHODS.each { |method| + HTTP_METHOD_LOOKUP[method] = method.downcase.underscore.to_sym + } + + alias raw_request_method request_method # :nodoc: + + # Returns the HTTP method that the application should see. In the case where the + # method was overridden by a middleware (for instance, if a HEAD request was + # converted to a GET, or if a _method parameter was used to determine the method + # the application should use), this method returns the overridden value, not the + # original. + def request_method + @request_method ||= check_method(super) + end + + # Returns the URI pattern of the matched route for the request, using the same + # format as `bin/rails routes`: + # + # request.route_uri_pattern # => "/:controller(/:action(/:id))(.:format)" + def route_uri_pattern + get_header("action_dispatch.route_uri_pattern") + end + + def route_uri_pattern=(pattern) # :nodoc: + set_header("action_dispatch.route_uri_pattern", pattern) + end + + def routes # :nodoc: + get_header("action_dispatch.routes") + end + + def routes=(routes) # :nodoc: + set_header("action_dispatch.routes", routes) + end + + def engine_script_name(_routes) # :nodoc: + get_header(_routes.env_key) + end + + def engine_script_name=(name) # :nodoc: + set_header(routes.env_key, name.dup) + end + + def request_method=(request_method) # :nodoc: + if check_method(request_method) + @request_method = set_header("REQUEST_METHOD", request_method) + end + end + + def controller_instance # :nodoc: + get_header("action_controller.instance") + end + + def controller_instance=(controller) # :nodoc: + set_header("action_controller.instance", controller) + end + + def http_auth_salt + get_header "action_dispatch.http_auth_salt" + end + + # Returns a symbol form of the #request_method. + def request_method_symbol + HTTP_METHOD_LOOKUP[request_method] + end + + # Returns the original value of the environment's REQUEST_METHOD, even if it was + # overridden by middleware. See #request_method for more information. + # + # For debugging purposes, when called with arguments this method will fall back + # to Object#method + def method(*args) + if args.empty? + @method ||= check_method( + get_header("rack.methodoverride.original_method") || + get_header("REQUEST_METHOD") + ) + else + super + end + end + ruby2_keywords(:method) + + # Returns a symbol form of the #method. + def method_symbol + HTTP_METHOD_LOOKUP[method] + end + + # Provides access to the request's HTTP headers, for example: + # + # request.headers["Content-Type"] # => "text/plain" + def headers + @headers ||= Http::Headers.new(self) + end + + # Early Hints is an HTTP/2 status code that indicates hints to help a client + # start making preparations for processing the final response. + # + # If the env contains `rack.early_hints` then the server accepts HTTP2 push for + # link headers. + # + # The `send_early_hints` method accepts a hash of links as follows: + # + # send_early_hints("link" => "; rel=preload; as=style,; rel=preload") + # + # If you are using {javascript_include_tag}[rdoc-ref:ActionView::Helpers::AssetTagHelper#javascript_include_tag] + # or {stylesheet_link_tag}[rdoc-ref:ActionView::Helpers::AssetTagHelper#stylesheet_link_tag] + # the Early Hints headers are included by default if supported. + def send_early_hints(links) + env["rack.early_hints"]&.call(links) + end + + # Returns a `String` with the last requested path including their params. + # + # # get '/foo' + # request.original_fullpath # => '/foo' + # + # # get '/foo?bar' + # request.original_fullpath # => '/foo?bar' + def original_fullpath + @original_fullpath ||= (get_header("ORIGINAL_FULLPATH") || fullpath) + end + + # Returns the `String` full path including params of the last URL requested. + # + # # get "/articles" + # request.fullpath # => "/articles" + # + # # get "/articles?page=2" + # request.fullpath # => "/articles?page=2" + def fullpath + @fullpath ||= super + end + + # Returns the original request URL as a `String`. + # + # # get "/articles?page=2" + # request.original_url # => "http://www.example.com/articles?page=2" + def original_url + base_url + original_fullpath + end + + # The `String` MIME type of the request. + # + # # get "/articles" + # request.media_type # => "application/x-www-form-urlencoded" + def media_type + content_mime_type&.to_s + end + + # Returns the content length of the request as an integer. + def content_length + return raw_post.bytesize if has_header?(TRANSFER_ENCODING) + super.to_i + end + + # Returns true if the `X-Requested-With` header contains "XMLHttpRequest" + # (case-insensitive), which may need to be manually added depending on the + # choice of JavaScript libraries and frameworks. + def xml_http_request? + /XMLHttpRequest/i.match?(get_header("HTTP_X_REQUESTED_WITH")) + end + alias :xhr? :xml_http_request? + + # Returns the IP address of client as a `String`. + def ip + @ip ||= super + end + + # Returns the IP address of client as a `String`, usually set by the RemoteIp + # middleware. + def remote_ip + @remote_ip ||= (get_header("action_dispatch.remote_ip") || ip).to_s + end + + def remote_ip=(remote_ip) + @remote_ip = nil + set_header "action_dispatch.remote_ip", remote_ip + end + + ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id" # :nodoc: + + # Returns the unique request id, which is based on either the `X-Request-Id` + # header that can be generated by a firewall, load balancer, or web server, or + # by the RequestId middleware (which sets the `action_dispatch.request_id` + # environment variable). + # + # This unique ID is useful for tracing a request from end-to-end as part of + # logging or debugging. This relies on the Rack variable set by the + # ActionDispatch::RequestId middleware. + def request_id + get_header ACTION_DISPATCH_REQUEST_ID + end + + def request_id=(id) # :nodoc: + set_header ACTION_DISPATCH_REQUEST_ID, id + end + + alias_method :uuid, :request_id + + # Returns the lowercase name of the HTTP server software. + def server_software + (get_header("SERVER_SOFTWARE") && /^([a-zA-Z]+)/ =~ get_header("SERVER_SOFTWARE")) ? $1.downcase : nil + end + + # Read the request body. This is useful for web services that need to work with + # raw requests directly. + def raw_post + unless has_header? "RAW_POST_DATA" + set_header("RAW_POST_DATA", read_body_stream) + end + get_header "RAW_POST_DATA" + end + + # The request body is an IO input stream. If the RAW_POST_DATA environment + # variable is already set, wrap it in a StringIO. + def body + if raw_post = get_header("RAW_POST_DATA") + raw_post = (+raw_post).force_encoding(Encoding::BINARY) + StringIO.new(raw_post) + else + body_stream + end + end + + # Determine whether the request body contains form-data by checking the request + # `Content-Type` for one of the media-types: `application/x-www-form-urlencoded` + # or `multipart/form-data`. The list of form-data media types can be modified + # through the `FORM_DATA_MEDIA_TYPES` array. + # + # A request body is not assumed to contain form-data when no `Content-Type` + # header is provided and the request_method is POST. + def form_data? + FORM_DATA_MEDIA_TYPES.include?(media_type) + end + + def body_stream # :nodoc: + get_header("rack.input") + end + + def reset_session + session.destroy + reset_csrf_token + end + + def session=(session) # :nodoc: + Session.set self, session + end + + def session_options=(options) + Session::Options.set self, options + end + + # Override Rack's GET method to support indifferent access. + def GET + fetch_header("action_dispatch.request.query_parameters") do |k| + encoding_template = Request::Utils::CustomParamEncoder.action_encoding_template(self, path_parameters[:controller], path_parameters[:action]) + rack_query_params = ActionDispatch::ParamBuilder.from_query_string(rack_request.query_string, encoding_template: encoding_template) + + set_header k, rack_query_params + end + rescue ActionDispatch::ParamError => e + raise ActionController::BadRequest.new("Invalid query parameters: #{e.message}") + end + alias :query_parameters :GET + + # Override Rack's POST method to support indifferent access. + def POST + fetch_header("action_dispatch.request.request_parameters") do + encoding_template = Request::Utils::CustomParamEncoder.action_encoding_template(self, path_parameters[:controller], path_parameters[:action]) + + param_list = nil + pr = parse_formatted_parameters(params_parsers) do + if param_list = request_parameters_list + ActionDispatch::ParamBuilder.from_pairs(param_list, encoding_template: encoding_template) + else + # We're not using a version of Rack that provides raw form + # pairs; we must use its hash (and thus post-process it below). + fallback_request_parameters + end + end + + # If the request body was parsed by a custom parser like JSON + # (and thus the above block was not run), we need to + # post-process the result hash. + if param_list.nil? + pr = ActionDispatch::ParamBuilder.from_hash(pr, encoding_template: encoding_template) + end + + self.request_parameters = pr + end + rescue ActionDispatch::ParamError, EOFError => e + raise ActionController::BadRequest.new("Invalid request parameters: #{e.message}") + end + alias :request_parameters :POST + + def request_parameters_list + # We don't use Rack's parse result, but we must call it so Rack + # can populate the rack.request.* keys we need. + rack_post = rack_request.POST + + if form_pairs = get_header("rack.request.form_pairs") + # Multipart + form_pairs + elsif form_vars = get_header("rack.request.form_vars") + # URL-encoded + ActionDispatch::QueryParser.each_pair(form_vars) + elsif rack_post && !rack_post.empty? + # It was multipart, but Rack did not preserve a pair list + # (probably too old). Flat parameter list is not available. + nil + else + # No request body, or not a format Rack knows + [] + end + end + + # Returns the authorization header regardless of whether it was specified + # directly or through one of the proxy alternatives. + def authorization + get_header("HTTP_AUTHORIZATION") || + get_header("X-HTTP_AUTHORIZATION") || + get_header("X_HTTP_AUTHORIZATION") || + get_header("REDIRECT_X_HTTP_AUTHORIZATION") + end + + # True if the request came from localhost, 127.0.0.1, or ::1. + def local? + LOCALHOST.match?(remote_addr) && LOCALHOST.match?(remote_ip) + end + + def request_parameters=(params) + raise if params.nil? + set_header("action_dispatch.request.request_parameters", params) + end + + def logger + get_header("action_dispatch.logger") + end + + def commit_flash + end + + def inspect # :nodoc: + "#<#{self.class.name} #{method} #{original_url.dump} for #{remote_ip}>" + end + + def reset_csrf_token + controller_instance.reset_csrf_token(self) if controller_instance.respond_to?(:reset_csrf_token) + end + + def commit_csrf_token + controller_instance.commit_csrf_token(self) if controller_instance.respond_to?(:commit_csrf_token) + end + + private + def check_method(name) + if name + HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(locale: false)}") + end + + name + end + + def default_session + Session.disabled(self) + end + + def read_body_stream + if body_stream + reset_stream(body_stream) do + if has_header?(TRANSFER_ENCODING) + body_stream.read # Read body stream until EOF if "Transfer-Encoding" is present + else + body_stream.read(content_length) + end + end + end + end + + def reset_stream(body_stream) + if body_stream.respond_to?(:rewind) + body_stream.rewind + + content = yield + + body_stream.rewind + + content + else + yield + end + end + + def fallback_request_parameters + rack_request.POST + end + end +end + +ActiveSupport.run_load_hooks :action_dispatch_request, ActionDispatch::Request diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/response.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/response.rb new file mode 100644 index 00000000..77486647 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/response.rb @@ -0,0 +1,556 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/core_ext/module/attribute_accessors" +require "action_dispatch/http/filter_redirect" +require "action_dispatch/http/cache" +require "monitor" + +module ActionDispatch # :nodoc: + # # Action Dispatch Response + # + # Represents an HTTP response generated by a controller action. Use it to + # retrieve the current state of the response, or customize the response. It can + # either represent a real HTTP response (i.e. one that is meant to be sent back + # to the web browser) or a TestResponse (i.e. one that is generated from + # integration tests). + # + # The Response object for the current request is exposed on controllers as + # ActionController::Metal#response. ActionController::Metal also provides a few + # additional methods that delegate to attributes of the Response such as + # ActionController::Metal#headers. + # + # Integration tests will likely also want to inspect responses in more detail. + # Methods such as Integration::RequestHelpers#get and + # Integration::RequestHelpers#post return instances of TestResponse (which + # inherits from Response) for this purpose. + # + # For example, the following demo integration test prints the body of the + # controller response to the console: + # + # class DemoControllerTest < ActionDispatch::IntegrationTest + # def test_print_root_path_to_console + # get('/') + # puts response.body + # end + # end + class Response + begin + # For `Rack::Headers` (Rack 3+): + require "rack/headers" + Headers = ::Rack::Headers + rescue LoadError + # For `Rack::Utils::HeaderHash`: + require "rack/utils" + Headers = ::Rack::Utils::HeaderHash + end + + # To be deprecated: + Header = Headers + + # The request that the response is responding to. + attr_accessor :request + + # The HTTP status code. + attr_reader :status + + # The headers for the response. + # + # header["Content-Type"] # => "text/plain" + # header["Content-Type"] = "application/json" + # header["Content-Type"] # => "application/json" + # + # Also aliased as `headers`. + # + # headers["Content-Type"] # => "text/plain" + # headers["Content-Type"] = "application/json" + # headers["Content-Type"] # => "application/json" + # + # Also aliased as `header` for compatibility. + attr_reader :headers + + alias_method :header, :headers + + delegate :[], :[]=, to: :@headers + + def each(&block) + sending! + x = @stream.each(&block) + sent! + x + end + + CONTENT_TYPE = "Content-Type" + SET_COOKIE = "Set-Cookie" + NO_CONTENT_CODES = [100, 101, 102, 103, 204, 205, 304] + + cattr_accessor :default_charset, default: "utf-8" + cattr_accessor :default_headers + + include Rack::Response::Helpers + # Aliasing these off because AD::Http::Cache::Response defines them. + alias :_cache_control :cache_control + alias :_cache_control= :cache_control= + + include ActionDispatch::Http::FilterRedirect + include ActionDispatch::Http::Cache::Response + include MonitorMixin + + class Buffer # :nodoc: + def initialize(response, buf) + @response = response + @buf = buf + @closed = false + @str_body = nil + end + + def to_ary + @buf.respond_to?(:to_ary) ? + @buf.to_ary : + @buf.each + end + + def body + @str_body ||= begin + buf = +"" + each { |chunk| buf << chunk } + buf + end + end + + def write(string) + raise IOError, "closed stream" if closed? + + @str_body = nil + @response.commit! + @buf.push string + end + alias_method :<<, :write + + def each(&block) + if @str_body + return enum_for(:each) unless block_given? + + yield @str_body + else + each_chunk(&block) + end + end + + def abort + end + + def close + @response.commit! + @closed = true + end + + def closed? + @closed + end + + private + def each_chunk(&block) + @buf.each(&block) + end + end + + def self.create(status = 200, headers = {}, body = [], default_headers: self.default_headers) + headers = merge_default_headers(headers, default_headers) + new status, headers, body + end + + def self.merge_default_headers(original, default) + default.respond_to?(:merge) ? default.merge(original) : original + end + + # The underlying body, as a streamable object. + attr_reader :stream + + def initialize(status = 200, headers = nil, body = []) + super() + + @headers = Headers.new + + headers&.each do |key, value| + @headers[key] = value + end + + self.body, self.status = body, status + + @cv = new_cond + @committed = false + @sending = false + @sent = false + + prepare_cache_control! + + yield self if block_given? + end + + def has_header?(key); @headers.key? key; end + def get_header(key); @headers[key]; end + def set_header(key, v); @headers[key] = v; end + def delete_header(key); @headers.delete key; end + + def await_commit + synchronize do + @cv.wait_until { @committed } + end + end + + def await_sent + synchronize { @cv.wait_until { @sent } } + end + + def commit! + synchronize do + before_committed + @committed = true + @cv.broadcast + end + end + + def sending! + synchronize do + before_sending + @sending = true + @cv.broadcast + end + end + + def sent! + synchronize do + @sent = true + @cv.broadcast + end + end + + def sending?; synchronize { @sending }; end + def committed?; synchronize { @committed }; end + def sent?; synchronize { @sent }; end + + ## + # :method: location + # + # Location of the response. + + ## + # :method: location= + # + # :call-seq: location=(location) + # + # Sets the location of the response + + # Sets the HTTP status code. + def status=(status) + @status = Rack::Utils.status_code(status) + end + + # Sets the HTTP response's content MIME type. For example, in the controller you + # could write this: + # + # response.content_type = "text/plain" + # + # If a character set has been defined for this response (see #charset=) then the + # character set information will also be included in the content type + # information. + def content_type=(content_type) + return unless content_type + new_header_info = parse_content_type(content_type.to_s) + prev_header_info = parsed_content_type_header + charset = new_header_info.charset || prev_header_info.charset + charset ||= self.class.default_charset unless prev_header_info.mime_type + set_content_type new_header_info.mime_type, charset + end + + # Content type of response. + def content_type + super.presence + end + + # Media type of response. + def media_type + parsed_content_type_header.mime_type + end + + def sending_file=(v) + if true == v + self.charset = false + end + end + + # Sets the HTTP character set. In case of `nil` parameter it sets the charset to + # `default_charset`. + # + # response.charset = 'utf-16' # => 'utf-16' + # response.charset = nil # => 'utf-8' + def charset=(charset) + content_type = parsed_content_type_header.mime_type + if false == charset + set_content_type content_type, nil + else + set_content_type content_type, charset || self.class.default_charset + end + end + + # The charset of the response. HTML wants to know the encoding of the content + # you're giving them, so we need to send that along. + def charset + header_info = parsed_content_type_header + header_info.charset || self.class.default_charset + end + + # The response code of the request. + def response_code + @status + end + + # Returns a string to ensure compatibility with `Net::HTTPResponse`. + def code + @status.to_s + end + + # Returns the corresponding message for the current HTTP status code: + # + # response.status = 200 + # response.message # => "OK" + # + # response.status = 404 + # response.message # => "Not Found" + # + def message + Rack::Utils::HTTP_STATUS_CODES[@status] + end + alias_method :status_message, :message + + # Returns the content of the response as a string. This contains the contents of + # any calls to `render`. + def body + @stream.body + end + + def write(string) + @stream.write string + end + + # Allows you to manually set or override the response body. + def body=(body) + if body.respond_to?(:to_path) + @stream = body + else + synchronize do + @stream = build_buffer self, munge_body_object(body) + end + end + end + + # Avoid having to pass an open file handle as the response body. Rack::Sendfile + # will usually intercept the response and uses the path directly, so there is no + # reason to open the file. + class FileBody # :nodoc: + attr_reader :to_path + + def initialize(path) + @to_path = path + end + + def body + File.binread(to_path) + end + + # Stream the file's contents if Rack::Sendfile isn't present. + def each + File.open(to_path, "rb") do |file| + while chunk = file.read(16384) + yield chunk + end + end + end + end + + # Send the file stored at `path` as the response body. + def send_file(path) + commit! + @stream = FileBody.new(path) + end + + def reset_body! + @stream = build_buffer(self, []) + end + + def body_parts + parts = [] + @stream.each { |x| parts << x } + parts + end + + # The location header we'll be responding with. + alias_method :redirect_url, :location + + def close + stream.close if stream.respond_to?(:close) + end + + def abort + if stream.respond_to?(:abort) + stream.abort + elsif stream.respond_to?(:close) + # `stream.close` should really be reserved for a close from the other direction, + # but we must fall back to it for compatibility. + stream.close + end + end + + # Turns the Response into a Rack-compatible array of the status, headers, and + # body. Allows explicit splatting: + # + # status, headers, body = *response + def to_a + commit! + rack_response @status, @headers.to_hash + end + alias prepare! to_a + + # Returns the response cookies, converted to a Hash of (name => value) pairs + # + # assert_equal 'AuthorOfNewPage', r.cookies['author'] + def cookies + cookies = {} + if header = get_header(SET_COOKIE) + header = header.split("\n") if header.respond_to?(:to_str) + header.each do |cookie| + if pair = cookie.split(";").first + key, value = pair.split("=").map { |v| Rack::Utils.unescape(v) } + cookies[key] = value + end + end + end + cookies + end + + private + ContentTypeHeader = Struct.new :mime_type, :charset + NullContentTypeHeader = ContentTypeHeader.new nil, nil + + CONTENT_TYPE_PARSER = / + \A + (?[^;\s]+\s*(?:;\s*(?:(?!charset)[^;\s])+)*)? + (?:;\s*charset=(?"?)(?[^;\s]+)\k)? + /x # :nodoc: + + def parse_content_type(content_type) + if content_type && match = CONTENT_TYPE_PARSER.match(content_type) + ContentTypeHeader.new(match[:mime_type], match[:charset]) + else + NullContentTypeHeader + end + end + + # Small internal convenience method to get the parsed version of the current + # content type header. + def parsed_content_type_header + parse_content_type(get_header(CONTENT_TYPE)) + end + + def set_content_type(content_type, charset) + type = content_type || "" + type = "#{type}; charset=#{charset.to_s.downcase}" if charset + set_header CONTENT_TYPE, type + end + + def before_committed + return if committed? + assign_default_content_type_and_charset! + merge_and_normalize_cache_control!(@cache_control) + handle_conditional_get! + handle_no_content! + end + + def before_sending + # Normally we've already committed by now, but it's possible (e.g., if the + # controller action tries to read back its own response) to get here before + # that. In that case, we must force an "early" commit: we're about to freeze the + # headers, so this is our last chance. + commit! unless committed? + + @request.commit_cookie_jar! unless committed? + end + + def build_buffer(response, body) + Buffer.new response, body + end + + def munge_body_object(body) + body.respond_to?(:each) ? body : [body] + end + + def assign_default_content_type_and_charset! + return if media_type + + ct = parsed_content_type_header + set_content_type(ct.mime_type || Mime[:html].to_s, + ct.charset || self.class.default_charset) + end + + class RackBody + def initialize(response) + @response = response + end + + def close + # Rack "close" maps to Response#abort, and **not** Response#close (which is used + # when the controller's finished writing) + @response.abort + end + + def body + @response.body + end + + BODY_METHODS = { to_ary: true, each: true, call: true, to_path: true } + + def respond_to?(method, include_private = false) + if BODY_METHODS.key?(method) + @response.stream.respond_to?(method) + else + super + end + end + + def to_ary + @response.stream.to_ary + end + + def each(*args, &block) + @response.each(*args, &block) + end + + def call(*arguments, &block) + @response.stream.call(*arguments, &block) + end + + def to_path + @response.stream.to_path + end + end + + def handle_no_content! + if NO_CONTENT_CODES.include?(@status) + @headers.delete CONTENT_TYPE + @headers.delete "Content-Length" + end + end + + def rack_response(status, headers) + if NO_CONTENT_CODES.include?(status) + [status, headers, []] + else + [status, headers, RackBody.new(self)] + end + end + end + + ActiveSupport.run_load_hooks(:action_dispatch_response, Response) +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/upload.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/upload.rb new file mode 100644 index 00000000..9b14b54a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/upload.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + module Http + # # Action Dispatch HTTP UploadedFile + # + # Models uploaded files. + # + # The actual file is accessible via the `tempfile` accessor, though some of its + # interface is available directly for convenience. + # + # Uploaded files are temporary files whose lifespan is one request. When the + # object is finalized Ruby unlinks the file, so there is no need to clean them + # with a separate maintenance task. + class UploadedFile + # The basename of the file in the client. + attr_accessor :original_filename + + # A string with the MIME type of the file. + attr_accessor :content_type + + # A `Tempfile` object with the actual uploaded file. Note that some of its + # interface is available directly. + attr_accessor :tempfile + + # A string with the headers of the multipart request. + attr_accessor :headers + + def initialize(hash) # :nodoc: + @tempfile = hash[:tempfile] + raise(ArgumentError, ":tempfile is required") unless @tempfile + + @content_type = hash[:type] + + if hash[:filename] + @original_filename = hash[:filename].dup + + begin + @original_filename.encode!(Encoding::UTF_8) + rescue EncodingError + @original_filename.force_encoding(Encoding::UTF_8) + end + else + @original_filename = nil + end + + if hash[:head] + @headers = hash[:head].dup + + begin + @headers.encode!(Encoding::UTF_8) + rescue EncodingError + @headers.force_encoding(Encoding::UTF_8) + end + else + @headers = nil + end + end + + # Shortcut for `tempfile.read`. + def read(length = nil, buffer = nil) + @tempfile.read(length, buffer) + end + + # Shortcut for `tempfile.open`. + def open + @tempfile.open + end + + # Shortcut for `tempfile.close`. + def close(unlink_now = false) + @tempfile.close(unlink_now) + end + + # Shortcut for `tempfile.path`. + def path + @tempfile.path + end + + # Shortcut for `tempfile.to_path`. + def to_path + @tempfile.to_path + end + + # Shortcut for `tempfile.rewind`. + def rewind + @tempfile.rewind + end + + # Shortcut for `tempfile.size`. + def size + @tempfile.size + end + + # Shortcut for `tempfile.eof?`. + def eof? + @tempfile.eof? + end + + def to_io + @tempfile.to_io + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/url.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/url.rb new file mode 100644 index 00000000..669c34f9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/http/url.rb @@ -0,0 +1,344 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/core_ext/module/attribute_accessors" + +module ActionDispatch + module Http + module URL + IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ + HOST_REGEXP = /(^[^:]+:\/\/)?(\[[^\]]+\]|[^:]+)(?::(\d+$))?/ + PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/ + + mattr_accessor :secure_protocol, default: false + mattr_accessor :tld_length, default: 1 + + class << self + # Returns the domain part of a host given the domain level. + # + # # Top-level domain example + # extract_domain('www.example.com', 1) # => "example.com" + # # Second-level domain example + # extract_domain('dev.www.example.co.uk', 2) # => "example.co.uk" + def extract_domain(host, tld_length) + extract_domain_from(host, tld_length) if named_host?(host) + end + + # Returns the subdomains of a host as an Array given the domain level. + # + # # Top-level domain example + # extract_subdomains('www.example.com', 1) # => ["www"] + # # Second-level domain example + # extract_subdomains('dev.www.example.co.uk', 2) # => ["dev", "www"] + def extract_subdomains(host, tld_length) + if named_host?(host) + extract_subdomains_from(host, tld_length) + else + [] + end + end + + # Returns the subdomains of a host as a String given the domain level. + # + # # Top-level domain example + # extract_subdomain('www.example.com', 1) # => "www" + # # Second-level domain example + # extract_subdomain('dev.www.example.co.uk', 2) # => "dev.www" + def extract_subdomain(host, tld_length) + extract_subdomains(host, tld_length).join(".") + end + + def url_for(options) + if options[:only_path] + path_for options + else + full_url_for options + end + end + + def full_url_for(options) + host = options[:host] + protocol = options[:protocol] + port = options[:port] + + unless host + raise ArgumentError, "Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true" + end + + build_host_url(host, port, protocol, options, path_for(options)) + end + + def path_for(options) + path = options[:script_name].to_s.chomp("/") + path << options[:path] if options.key?(:path) + + path = "/" if options[:trailing_slash] && path.blank? + + add_params(path, options[:params]) if options.key?(:params) + add_anchor(path, options[:anchor]) if options.key?(:anchor) + + path + end + + private + def add_params(path, params) + params = { params: params } unless params.is_a?(Hash) + params.reject! { |_, v| v.to_param.nil? } + query = params.to_query + path << "?#{query}" unless query.empty? + end + + def add_anchor(path, anchor) + if anchor + path << "##{Journey::Router::Utils.escape_fragment(anchor.to_param)}" + end + end + + def extract_domain_from(host, tld_length) + host.split(".").last(1 + tld_length).join(".") + end + + def extract_subdomains_from(host, tld_length) + parts = host.split(".") + parts[0..-(tld_length + 2)] + end + + def build_host_url(host, port, protocol, options, path) + if match = host.match(HOST_REGEXP) + protocol ||= match[1] unless protocol == false + host = match[2] + port = match[3] unless options.key? :port + end + + protocol = normalize_protocol protocol + host = normalize_host(host, options) + + result = protocol.dup + + if options[:user] && options[:password] + result << "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@" + end + + result << host + normalize_port(port, protocol) { |normalized_port| + result << ":#{normalized_port}" + } + + result.concat path + end + + def named_host?(host) + !IP_HOST_REGEXP.match?(host) + end + + def normalize_protocol(protocol) + case protocol + when nil + secure_protocol ? "https://" : "http://" + when false, "//" + "//" + when PROTOCOL_REGEXP + "#{$1}://" + else + raise ArgumentError, "Invalid :protocol option: #{protocol.inspect}" + end + end + + def normalize_host(_host, options) + return _host unless named_host?(_host) + + tld_length = options[:tld_length] || @@tld_length + subdomain = options.fetch :subdomain, true + domain = options[:domain] + + host = +"" + if subdomain == true + return _host if domain.nil? + + host << extract_subdomains_from(_host, tld_length).join(".") + elsif subdomain + host << subdomain.to_param + end + host << "." unless host.empty? + host << (domain || extract_domain_from(_host, tld_length)) + host + end + + def normalize_port(port, protocol) + return unless port + + case protocol + when "//" then yield port + when "https://" + yield port unless port.to_i == 443 + else + yield port unless port.to_i == 80 + end + end + end + + def initialize + super + @protocol = nil + @port = nil + end + + # Returns the complete URL used for this request. + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' + # req.url # => "http://example.com" + def url + protocol + host_with_port + fullpath + end + + # Returns 'https://' if this is an SSL request and 'http://' otherwise. + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' + # req.protocol # => "http://" + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on' + # req.protocol # => "https://" + def protocol + @protocol ||= ssl? ? "https://" : "http://" + end + + # Returns the host and port for this request, such as "example.com:8080". + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' + # req.raw_host_with_port # => "example.com" + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' + # req.raw_host_with_port # => "example.com:80" + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.raw_host_with_port # => "example.com:8080" + def raw_host_with_port + if forwarded = x_forwarded_host.presence + forwarded.split(/,\s?/).last + else + get_header("HTTP_HOST") || "#{server_name}:#{get_header('SERVER_PORT')}" + end + end + + # Returns the host for this request, such as "example.com". + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.host # => "example.com" + def host + raw_host_with_port.sub(/:\d+$/, "") + end + + # Returns a host:port string for this request, such as "example.com" or + # "example.com:8080". Port is only included if it is not a default port (80 or + # 443) + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' + # req.host_with_port # => "example.com" + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' + # req.host_with_port # => "example.com" + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.host_with_port # => "example.com:8080" + def host_with_port + "#{host}#{port_string}" + end + + # Returns the port number of this request as an integer. + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' + # req.port # => 80 + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.port # => 8080 + def port + @port ||= if raw_host_with_port =~ /:(\d+)$/ + $1.to_i + else + standard_port + end + end + + # Returns the standard port number for this request's protocol. + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.standard_port # => 80 + def standard_port + if "https://" == protocol + 443 + else + 80 + end + end + + # Returns whether this request is using the standard port + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' + # req.standard_port? # => true + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.standard_port? # => false + def standard_port? + port == standard_port + end + + # Returns a number port suffix like 8080 if the port number of this request is + # not the default HTTP port 80 or HTTPS port 443. + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' + # req.optional_port # => nil + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.optional_port # => 8080 + def optional_port + standard_port? ? nil : port + end + + # Returns a string port suffix, including colon, like ":8080" if the port number + # of this request is not the default HTTP port 80 or HTTPS port 443. + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' + # req.port_string # => "" + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.port_string # => ":8080" + def port_string + standard_port? ? "" : ":#{port}" + end + + # Returns the requested port, such as 8080, based on SERVER_PORT + # + # req = ActionDispatch::Request.new 'SERVER_PORT' => '80' + # req.server_port # => 80 + # + # req = ActionDispatch::Request.new 'SERVER_PORT' => '8080' + # req.server_port # => 8080 + def server_port + get_header("SERVER_PORT").to_i + end + + # Returns the domain part of a host, such as "rubyonrails.org" in + # "www.rubyonrails.org". You can specify a different `tld_length`, such as 2 to + # catch rubyonrails.co.uk in "www.rubyonrails.co.uk". + def domain(tld_length = @@tld_length) + ActionDispatch::Http::URL.extract_domain(host, tld_length) + end + + # Returns all the subdomains as an array, so `["dev", "www"]` would be returned + # for "dev.www.rubyonrails.org". You can specify a different `tld_length`, such + # as 2 to catch `["www"]` instead of `["www", "rubyonrails"]` in + # "www.rubyonrails.co.uk". + def subdomains(tld_length = @@tld_length) + ActionDispatch::Http::URL.extract_subdomains(host, tld_length) + end + + # Returns all the subdomains as a string, so `"dev.www"` would be returned for + # "dev.www.rubyonrails.org". You can specify a different `tld_length`, such as 2 + # to catch `"www"` instead of `"www.rubyonrails"` in "www.rubyonrails.co.uk". + def subdomain(tld_length = @@tld_length) + ActionDispatch::Http::URL.extract_subdomain(host, tld_length) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey.rb new file mode 100644 index 00000000..336469f8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "action_dispatch/journey/router" +require "action_dispatch/journey/gtg/builder" +require "action_dispatch/journey/gtg/simulator" diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/formatter.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/formatter.rb new file mode 100644 index 00000000..8343616f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/formatter.rb @@ -0,0 +1,231 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "action_controller/metal/exceptions" + +module ActionDispatch + # :stopdoc: + module Journey + # The Formatter class is used for formatting URLs. For example, parameters + # passed to `url_for` in Rails will eventually call Formatter#generate. + class Formatter + attr_reader :routes + + def initialize(routes) + @routes = routes + @cache = nil + end + + class RouteWithParams + attr_reader :params + + def initialize(route, parameterized_parts, params) + @route = route + @parameterized_parts = parameterized_parts + @params = params + end + + def path(_) + @route.format(@parameterized_parts) + end + end + + class MissingRoute + attr_reader :routes, :name, :constraints, :missing_keys, :unmatched_keys + + def initialize(constraints, missing_keys, unmatched_keys, routes, name) + @constraints = constraints + @missing_keys = missing_keys + @unmatched_keys = unmatched_keys + @routes = routes + @name = name + end + + def path(method_name) + raise ActionController::UrlGenerationError.new(message, routes, name, method_name) + end + + def params + path("unknown") + end + + def message + message = +"No route matches #{Hash[constraints.sort_by { |k, v| k.to_s }].inspect}" + message << ", missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty? + message << ", possible unmatched constraints: #{unmatched_keys.sort.inspect}" if unmatched_keys && !unmatched_keys.empty? + message + end + end + + def generate(name, options, path_parameters) + original_options = options.dup + path_params = options.delete(:path_params) + if path_params.is_a?(Hash) + options = path_params.merge(options) + else + path_params = nil + options = options.dup + end + constraints = path_parameters.merge(options) + missing_keys = nil + + match_route(name, constraints) do |route| + parameterized_parts = extract_parameterized_parts(route, options, path_parameters) + + # Skip this route unless a name has been provided or it is a standard Rails + # route since we can't determine whether an options hash passed to url_for + # matches a Rack application or a redirect. + next unless name || route.dispatcher? + + missing_keys = missing_keys(route, parameterized_parts) + next if missing_keys && !missing_keys.empty? + params = options.delete_if do |key, _| + # top-level params' normal behavior of generating query_params should be + # preserved even if the same key is also a bind_param + parameterized_parts.key?(key) || route.defaults.key?(key) || + (path_params&.key?(key) && !original_options.key?(key)) + end + + defaults = route.defaults + required_parts = route.required_parts + + route.parts.reverse_each do |key| + break if defaults[key].nil? && parameterized_parts[key].present? + next if parameterized_parts[key].to_s != defaults[key].to_s + break if required_parts.include?(key) + + parameterized_parts.delete(key) + end + + return RouteWithParams.new(route, parameterized_parts, params) + end + + unmatched_keys = (missing_keys || []) & constraints.keys + missing_keys = (missing_keys || []) - unmatched_keys + + MissingRoute.new(constraints, missing_keys, unmatched_keys, routes, name) + end + + def clear + @cache = nil + end + + def eager_load! + cache + nil + end + + private + def extract_parameterized_parts(route, options, recall) + parameterized_parts = recall.merge(options) + + keys_to_keep = route.parts.reverse_each.drop_while { |part| + !(options.key?(part) || route.scope_options.key?(part)) || (options[part].nil? && recall[part].nil?) + } | route.required_parts + + parameterized_parts.delete_if do |bad_key, _| + !keys_to_keep.include?(bad_key) + end + + parameterized_parts.each do |k, v| + if k == :controller + parameterized_parts[k] = v + else + parameterized_parts[k] = v.to_param + end + end + + parameterized_parts.compact! + parameterized_parts + end + + def named_routes + routes.named_routes + end + + def match_route(name, options) + if named_routes.key?(name) + yield named_routes[name] + else + routes = non_recursive(cache, options) + + supplied_keys = options.each_with_object({}) do |(k, v), h| + h[k.to_s] = true if v + end + + hash = routes.group_by { |_, r| r.score(supplied_keys) } + + hash.keys.sort.reverse_each do |score| + break if score < 0 + + hash[score].sort_by { |i, _| i }.each do |_, route| + yield route + end + end + end + end + + def non_recursive(cache, options) + routes = [] + queue = [cache] + + while queue.any? + c = queue.shift + routes.concat(c[:___routes]) if c.key?(:___routes) + + options.each do |pair| + queue << c[pair] if c.key?(pair) + end + end + + routes + end + + # Returns an array populated with missing keys if any are present. + def missing_keys(route, parts) + missing_keys = nil + tests = route.path.requirements_for_missing_keys_check + route.required_parts.each { |key| + case tests[key] + when nil + unless parts[key] + missing_keys ||= [] + missing_keys << key + end + else + unless tests[key].match?(parts[key]) + missing_keys ||= [] + missing_keys << key + end + end + } + missing_keys + end + + def possibles(cache, options, depth = 0) + cache.fetch(:___routes) { [] } + options.find_all { |pair| + cache.key?(pair) + }.flat_map { |pair| + possibles(cache[pair], options, depth + 1) + } + end + + def build_cache + root = { ___routes: [] } + routes.routes.each_with_index do |route, i| + leaf = route.required_defaults.inject(root) do |h, tuple| + h[tuple] ||= {} + end + (leaf[:___routes] ||= []) << [i, route] + end + root + end + + def cache + @cache ||= build_cache + end + end + end + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/gtg/builder.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/gtg/builder.rb new file mode 100644 index 00000000..dbcefa71 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/gtg/builder.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "action_dispatch/journey/gtg/transition_table" + +module ActionDispatch + module Journey # :nodoc: + module GTG # :nodoc: + class Builder # :nodoc: + DUMMY_END_NODE = Nodes::Dummy.new + + attr_reader :root, :ast, :endpoints + + def initialize(root) + @root = root + @ast = Nodes::Cat.new root, DUMMY_END_NODE + @followpos = build_followpos + end + + def transition_table + dtrans = TransitionTable.new + marked = {}.compare_by_identity + state_id = Hash.new { |h, k| h[k] = h.length }.compare_by_identity + dstates = [firstpos(root)] + + until dstates.empty? + s = dstates.shift + next if marked[s] + marked[s] = true # mark s + + s.group_by { |state| symbol(state) }.each do |sym, ps| + u = ps.flat_map { |l| @followpos[l] }.uniq + next if u.empty? + + from = state_id[s] + + if u.all? { |pos| pos == DUMMY_END_NODE } + to = state_id[Object.new] + dtrans[from, to] = sym + dtrans.add_accepting(to) + + ps.each { |state| dtrans.add_memo(to, state.memo) } + else + to = state_id[u] + dtrans[from, to] = sym + + if u.include?(DUMMY_END_NODE) + ps.each do |state| + if @followpos[state].include?(DUMMY_END_NODE) + dtrans.add_memo(to, state.memo) + end + end + + dtrans.add_accepting(to) + end + end + + dstates << u + end + end + + dtrans + end + + def nullable?(node) + case node + when Nodes::Group + true + when Nodes::Star + # the default star regex is /(.+)/ which is NOT nullable but since different + # constraints can be provided we must actually check if this is the case or not. + node.regexp.match?("") + when Nodes::Or + node.children.any? { |c| nullable?(c) } + when Nodes::Cat + nullable?(node.left) && nullable?(node.right) + when Nodes::Terminal + !node.left + when Nodes::Unary + nullable?(node.left) + else + raise ArgumentError, "unknown nullable: %s" % node.class.name + end + end + + def firstpos(node) + case node + when Nodes::Star + firstpos(node.left) + when Nodes::Cat + if nullable?(node.left) + firstpos(node.left) | firstpos(node.right) + else + firstpos(node.left) + end + when Nodes::Or + node.children.flat_map { |c| firstpos(c) }.tap(&:uniq!) + when Nodes::Unary + firstpos(node.left) + when Nodes::Terminal + nullable?(node) ? [] : [node] + else + raise ArgumentError, "unknown firstpos: %s" % node.class.name + end + end + + def lastpos(node) + case node + when Nodes::Star + lastpos(node.left) + when Nodes::Or + node.children.flat_map { |c| lastpos(c) }.tap(&:uniq!) + when Nodes::Cat + if nullable?(node.right) + lastpos(node.left) | lastpos(node.right) + else + lastpos(node.right) + end + when Nodes::Terminal + nullable?(node) ? [] : [node] + when Nodes::Unary + lastpos(node.left) + else + raise ArgumentError, "unknown lastpos: %s" % node.class.name + end + end + + private + def build_followpos + table = Hash.new { |h, k| h[k] = [] }.compare_by_identity + @ast.each do |n| + case n + when Nodes::Cat + lastpos(n.left).each do |i| + table[i] += firstpos(n.right) + end + end + end + table + end + + def symbol(edge) + edge.symbol? ? edge.regexp : edge.left + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/gtg/simulator.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/gtg/simulator.rb new file mode 100644 index 00000000..0738ac52 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/gtg/simulator.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "strscan" + +module ActionDispatch + module Journey # :nodoc: + module GTG # :nodoc: + class MatchData # :nodoc: + attr_reader :memos + + def initialize(memos) + @memos = memos + end + end + + class Simulator # :nodoc: + INITIAL_STATE = [ [0, nil] ].freeze + + attr_reader :tt + + def initialize(transition_table) + @tt = transition_table + end + + def memos(string) + input = StringScanner.new(string) + state = INITIAL_STATE + start_index = 0 + + while sym = input.scan(%r([/.?]|[^/.?]+)) + end_index = start_index + sym.length + + state = tt.move(state, string, start_index, end_index) + + start_index = end_index + end + + acceptance_states = state.each_with_object([]) do |s_d, memos| + s, idx = s_d + memos.concat(tt.memo(s)) if idx.nil? && tt.accepting?(s) + end + + acceptance_states.empty? ? yield : acceptance_states + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/gtg/transition_table.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/gtg/transition_table.rb new file mode 100644 index 00000000..b2071ef9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/gtg/transition_table.rb @@ -0,0 +1,217 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "action_dispatch/journey/nfa/dot" + +module ActionDispatch + module Journey # :nodoc: + module GTG # :nodoc: + class TransitionTable # :nodoc: + include Journey::NFA::Dot + + attr_reader :memos + + DEFAULT_EXP = /[^.\/?]+/ + DEFAULT_EXP_ANCHORED = /\A#{DEFAULT_EXP}\Z/ + + def initialize + @stdparam_states = {} + @regexp_states = {} + @string_states = {} + @accepting = {} + @memos = Hash.new { |h, k| h[k] = [] } + end + + def add_accepting(state) + @accepting[state] = true + end + + def accepting_states + @accepting.keys + end + + def accepting?(state) + @accepting[state] + end + + def add_memo(idx, memo) + @memos[idx] << memo + end + + def memo(idx) + @memos[idx] + end + + def eclosure(t) + Array(t) + end + + def move(t, full_string, start_index, end_index) + return [] if t.empty? + + next_states = [] + + tok = full_string.slice(start_index, end_index - start_index) + token_matches_default_component = DEFAULT_EXP_ANCHORED.match?(tok) + + t.each { |s, previous_start| + if previous_start.nil? + # In the simple case of a "default" param regex do this fast-path and add all + # next states. + if token_matches_default_component && states = @stdparam_states[s] + states.each { |re, v| next_states << [v, nil].freeze if !v.nil? } + end + + # When we have a literal string, we can just pull the next state + if states = @string_states[s] + next_states << [states[tok], nil].freeze unless states[tok].nil? + end + end + + # For regexes that aren't the "default" style, they may potentially not be + # terminated by the first "token" [./?], so we need to continue to attempt to + # match this regexp as well as any successful paths that continue out of it. + # both paths could be valid. + if states = @regexp_states[s] + slice_start = if previous_start.nil? + start_index + else + previous_start + end + + slice_length = end_index - slice_start + curr_slice = full_string.slice(slice_start, slice_length) + + states.each { |re, v| + # if we match, we can try moving past this + next_states << [v, nil].freeze if !v.nil? && re.match?(curr_slice) + } + + # and regardless, we must continue accepting tokens and retrying this regexp. we + # need to remember where we started as well so we can take bigger slices. + next_states << [s, slice_start].freeze + end + } + + next_states + end + + def as_json(options = nil) + simple_regexp = Hash.new { |h, k| h[k] = {} } + + @regexp_states.each do |from, hash| + hash.each do |re, to| + simple_regexp[from][re.source] = to + end + end + + { + regexp_states: simple_regexp, + string_states: @string_states, + stdparam_states: @stdparam_states, + accepting: @accepting + } + end + + def to_svg + svg = IO.popen("dot -Tsvg", "w+") { |f| + f.write(to_dot) + f.close_write + f.readlines + } + 3.times { svg.shift } + svg.join.sub(/width="[^"]*"/, "").sub(/height="[^"]*"/, "") + end + + def visualizer(paths, title = "FSM") + viz_dir = File.join __dir__, "..", "visualizer" + fsm_js = File.read File.join(viz_dir, "fsm.js") + fsm_css = File.read File.join(viz_dir, "fsm.css") + erb = File.read File.join(viz_dir, "index.html.erb") + states = "function tt() { return #{to_json}; }" + + fun_routes = paths.sample(3).map do |ast| + ast.filter_map { |n| + case n + when Nodes::Symbol + case n.left + when ":id" then rand(100).to_s + when ":format" then %w{ xml json }.sample + else + "omg" + end + when Nodes::Terminal then n.symbol + else + nil + end + }.join + end + + stylesheets = [fsm_css] + svg = to_svg + javascripts = [states, fsm_js] + + fun_routes = fun_routes + stylesheets = stylesheets + svg = svg + javascripts = javascripts + + require "erb" + template = ERB.new erb + template.result(binding) + end + + def []=(from, to, sym) + to_mappings = states_hash_for(sym)[from] ||= {} + case sym + when Regexp + # we must match the whole string to a token boundary + if sym == DEFAULT_EXP + sym = DEFAULT_EXP_ANCHORED + else + sym = /\A#{sym}\Z/ + end + when Symbol + # account for symbols in the constraints the same as strings + sym = sym.to_s + end + to_mappings[sym] = to + end + + def states + ss = @string_states.keys + @string_states.values.flat_map(&:values) + ps = @stdparam_states.keys + @stdparam_states.values.flat_map(&:values) + rs = @regexp_states.keys + @regexp_states.values.flat_map(&:values) + (ss + ps + rs).uniq + end + + def transitions + @string_states.flat_map { |from, hash| + hash.map { |s, to| [from, s, to] } + } + @stdparam_states.flat_map { |from, hash| + hash.map { |s, to| [from, s, to] } + } + @regexp_states.flat_map { |from, hash| + hash.map { |s, to| [from, s, to] } + } + end + + private + def states_hash_for(sym) + case sym + when String, Symbol + @string_states + when Regexp + if sym == DEFAULT_EXP + @stdparam_states + else + @regexp_states + end + else + raise ArgumentError, "unknown symbol: %s" % sym.class + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/nfa/dot.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/nfa/dot.rb new file mode 100644 index 00000000..130cb1cc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/nfa/dot.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + module Journey # :nodoc: + module NFA # :nodoc: + module Dot # :nodoc: + def to_dot + edges = transitions.map { |from, sym, to| + " #{from} -> #{to} [label=\"#{sym || 'ε'}\"];" + } + + <<-eodot +digraph nfa { + rankdir=LR; + node [shape = doublecircle]; + #{accepting_states.join ' '}; + node [shape = circle]; +#{edges.join "\n"} +} + eodot + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/nodes/node.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/nodes/node.rb new file mode 100644 index 00000000..28343113 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/nodes/node.rb @@ -0,0 +1,208 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "action_dispatch/journey/visitors" + +module ActionDispatch + module Journey # :nodoc: + class Ast # :nodoc: + attr_reader :names, :path_params, :tree, :wildcard_options, :terminals + alias :root :tree + + def initialize(tree, formatted) + @tree = tree + @path_params = [] + @names = [] + @symbols = [] + @stars = [] + @terminals = [] + @wildcard_options = {} + + visit_tree(formatted) + end + + def requirements=(requirements) + # inject any regexp requirements for `star` nodes so they can be determined + # nullable, which requires knowing if the regex accepts an empty string. + (symbols + stars).each do |node| + re = requirements[node.to_sym] + node.regexp = re if re + end + end + + def route=(route) + terminals.each { |n| n.memo = route } + end + + def glob? + stars.any? + end + + private + attr_reader :symbols, :stars + + def visit_tree(formatted) + tree.each do |node| + if node.symbol? + path_params << node.to_sym + names << node.name + symbols << node + elsif node.star? + stars << node + + if formatted != false + # Add a constraint for wildcard route to make it non-greedy and match the + # optional format part of the route by default. + wildcard_options[node.name.to_sym] ||= /.+?/m + end + end + + if node.terminal? + terminals << node + end + end + end + end + + module Nodes # :nodoc: + class Node # :nodoc: + include Enumerable + + attr_accessor :left, :memo + + def initialize(left) + @left = left + @memo = nil + end + + def each(&block) + Visitors::Each::INSTANCE.accept(self, block) + end + + def to_s + Visitors::String::INSTANCE.accept(self, "") + end + + def to_dot + Visitors::Dot::INSTANCE.accept(self) + end + + def to_sym + name.to_sym + end + + def name + -left.tr("*:", "") + end + + def type + raise NotImplementedError + end + + def symbol?; false; end + def literal?; false; end + def terminal?; false; end + def star?; false; end + def cat?; false; end + def group?; false; end + end + + class Terminal < Node # :nodoc: + alias :symbol :left + def terminal?; true; end + end + + class Literal < Terminal # :nodoc: + def literal?; true; end + def type; :LITERAL; end + end + + class Dummy < Literal # :nodoc: + def initialize(x = Object.new) + super + end + + def literal?; false; end + end + + class Slash < Terminal # :nodoc: + def type; :SLASH; end + end + + class Dot < Terminal # :nodoc: + def type; :DOT; end + end + + class Symbol < Terminal # :nodoc: + attr_accessor :regexp + alias :symbol :regexp + attr_reader :name + + DEFAULT_EXP = /[^.\/?]+/ + GREEDY_EXP = /(.+)/ + def initialize(left, regexp = DEFAULT_EXP) + super(left) + @regexp = regexp + @name = -left.tr("*:", "") + end + + def type; :SYMBOL; end + def symbol?; true; end + end + + class Unary < Node # :nodoc: + def children; [left] end + end + + class Group < Unary # :nodoc: + def type; :GROUP; end + def group?; true; end + end + + class Star < Unary # :nodoc: + attr_accessor :regexp + + def initialize(left) + super(left) + + # By default wildcard routes are non-greedy and must match something. + @regexp = /.+?/m + end + + def star?; true; end + def type; :STAR; end + + def name + left.name.tr "*:", "" + end + end + + class Binary < Node # :nodoc: + attr_accessor :right + + def initialize(left, right) + super(left) + @right = right + end + + def children; [left, right] end + end + + class Cat < Binary # :nodoc: + def cat?; true; end + def type; :CAT; end + end + + class Or < Node # :nodoc: + attr_reader :children + + def initialize(children) + @children = children + end + + def type; :OR; end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/parser.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/parser.rb new file mode 100644 index 00000000..70520e13 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/parser.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require "action_dispatch/journey/scanner" +require "action_dispatch/journey/nodes/node" + +module ActionDispatch + module Journey # :nodoc: + class Parser # :nodoc: + include Journey::Nodes + + def self.parse(string) + new.parse string + end + + def initialize + @scanner = Scanner.new + @next_token = nil + end + + def parse(string) + @scanner.scan_setup(string) + advance_token + do_parse + end + + private + def advance_token + @next_token = @scanner.next_token + end + + def do_parse + parse_expressions + end + + def parse_expressions + node = parse_expression + + while @next_token + case @next_token + when :RPAREN + break + when :OR + node = parse_or(node) + else + node = Cat.new(node, parse_expressions) + end + end + + node + end + + def parse_or(lhs) + advance_token + node = parse_expression + Or.new([lhs, node]) + end + + def parse_expression + if @next_token == :STAR + parse_star + elsif @next_token == :LPAREN + parse_group + else + parse_terminal + end + end + + def parse_star + node = Star.new(Symbol.new(@scanner.last_string, Symbol::GREEDY_EXP)) + advance_token + node + end + + def parse_group + advance_token + node = parse_expressions + if @next_token == :RPAREN + node = Group.new(node) + advance_token + node + else + raise ArgumentError, "missing right parenthesis." + end + end + + def parse_terminal + node = case @next_token + when :SYMBOL + Symbol.new(@scanner.last_string) + when :LITERAL + Literal.new(@scanner.last_literal) + when :SLASH + Slash.new("/") + when :DOT + Dot.new(".") + end + + advance_token + node + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/path/pattern.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/path/pattern.rb new file mode 100644 index 00000000..291a9d21 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/path/pattern.rb @@ -0,0 +1,209 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + module Journey # :nodoc: + module Path # :nodoc: + class Pattern # :nodoc: + attr_reader :ast, :names, :requirements, :anchored, :spec + + def initialize(ast, requirements, separators, anchored) + @ast = ast + @spec = ast.root + @requirements = requirements + @separators = separators + @anchored = anchored + + @names = ast.names + @optional_names = nil + @required_names = nil + @re = nil + @offsets = nil + end + + def build_formatter + Visitors::FormatBuilder.new.accept(spec) + end + + def eager_load! + required_names + offsets + to_regexp + @ast = nil + end + + def requirements_anchored? + # each required param must not be surrounded by a literal, otherwise it isn't + # simple to chunk-match the url piecemeal + terminals = ast.terminals + + terminals.each_with_index { |s, index| + next if index < 1 + next if s.type == :DOT || s.type == :SLASH + + back = terminals[index - 1] + fwd = terminals[index + 1] + + # we also don't support this yet, constraints must be regexps + return false if s.symbol? && s.regexp.is_a?(Array) + + return false if back.literal? + return false if !fwd.nil? && fwd.literal? + } + + true + end + + def required_names + @required_names ||= names - optional_names + end + + def optional_names + @optional_names ||= spec.find_all(&:group?).flat_map { |group| + group.find_all(&:symbol?) + }.map(&:name).uniq + end + + class AnchoredRegexp < Journey::Visitors::Visitor # :nodoc: + def initialize(separator, matchers) + @separator = separator + @matchers = matchers + @separator_re = "([^#{separator}]+)" + super() + end + + def accept(node) + %r{\A#{visit node}\Z} + end + + def visit_CAT(node) + "#{visit(node.left)}#{visit(node.right)}" + end + + def visit_SYMBOL(node) + node = node.to_sym + + return @separator_re unless @matchers.key?(node) + + re = @matchers[node] + "(#{Regexp.union(re)})" + end + + def visit_GROUP(node) + "(?:#{visit node.left})?" + end + + def visit_LITERAL(node) + Regexp.escape(node.left) + end + alias :visit_DOT :visit_LITERAL + + def visit_SLASH(node) + node.left + end + + def visit_STAR(node) + re = @matchers[node.left.to_sym] + re ? "(#{re})" : "(.+)" + end + + def visit_OR(node) + children = node.children.map { |n| visit n } + "(?:#{children.join(?|)})" + end + end + + class UnanchoredRegexp < AnchoredRegexp # :nodoc: + def accept(node) + path = visit node + path == "/" ? %r{\A/} : %r{\A#{path}(?:\b|\Z|/)} + end + end + + class MatchData # :nodoc: + attr_reader :names + + def initialize(names, offsets, match) + @names = names + @offsets = offsets + @match = match + end + + def captures + Array.new(length - 1) { |i| self[i + 1] } + end + + def named_captures + @names.zip(captures).to_h + end + + def [](x) + idx = @offsets[x - 1] + x + @match[idx] + end + + def length + @offsets.length + end + + def post_match + @match.post_match + end + + def to_s + @match.to_s + end + end + + def match(other) + return unless match = to_regexp.match(other) + MatchData.new(names, offsets, match) + end + alias :=~ :match + + def match?(other) + to_regexp.match?(other) + end + + def source + to_regexp.source + end + + def to_regexp + @re ||= regexp_visitor.new(@separators, @requirements).accept spec + end + + def requirements_for_missing_keys_check + @requirements_for_missing_keys_check ||= requirements.transform_values do |regex| + /\A#{regex}\Z/ + end + end + + private + def regexp_visitor + @anchored ? AnchoredRegexp : UnanchoredRegexp + end + + def offsets + @offsets ||= begin + offsets = [0] + + spec.find_all(&:symbol?).each do |node| + node = node.to_sym + + if @requirements.key?(node) + re = /#{Regexp.union(@requirements[node])}|/ + offsets.push((re.match("").length - 1) + offsets.last) + else + offsets << offsets.last + end + end + + offsets + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/route.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/route.rb new file mode 100644 index 00000000..aed783f9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/route.rb @@ -0,0 +1,189 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + # :stopdoc: + module Journey + class Route + attr_reader :app, :path, :defaults, :name, :precedence, :constraints, + :internal, :scope_options, :ast, :source_location + + alias :conditions :constraints + + module VerbMatchers + VERBS = %w{ DELETE GET HEAD OPTIONS LINK PATCH POST PUT TRACE UNLINK } + VERBS.each do |v| + class_eval <<-eoc, __FILE__, __LINE__ + 1 + # frozen_string_literal: true + class #{v} + def self.verb; name.split("::").last; end + def self.call(req); req.#{v.downcase}?; end + end + eoc + end + + class Unknown + attr_reader :verb + + def initialize(verb) + @verb = verb + end + + def call(request); @verb == request.request_method; end + end + + class All + def self.call(_); true; end + def self.verb; ""; end + end + + VERB_TO_CLASS = VERBS.each_with_object(all: All) do |verb, hash| + klass = const_get verb + hash[verb] = klass + hash[verb.downcase] = klass + hash[verb.downcase.to_sym] = klass + end + end + + def self.verb_matcher(verb) + VerbMatchers::VERB_TO_CLASS.fetch(verb) do + VerbMatchers::Unknown.new verb.to_s.dasherize.upcase + end + end + + ## + # +path+ is a path constraint. + # `constraints` is a hash of constraints to be applied to this route. + def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {}, request_method_match: nil, precedence: 0, scope_options: {}, internal: false, source_location: nil) + @name = name + @app = app + @path = path + + @request_method_match = request_method_match + @constraints = constraints + @defaults = defaults + @required_defaults = nil + @_required_defaults = required_defaults + @required_parts = nil + @parts = nil + @precedence = precedence + @path_formatter = @path.build_formatter + @scope_options = scope_options + @internal = internal + @source_location = source_location + + @ast = @path.ast.root + @path.ast.route = self + end + + def eager_load! + path.eager_load! + parts + required_defaults + nil + end + + # Needed for `bin/rails routes`. Picks up succinctly defined requirements for a + # route, for example route + # + # get 'photo/:id', :controller => 'photos', :action => 'show', + # :id => /[A-Z]\d{5}/ + # + # will have {:controller=>"photos", :action=>"show", :[id=>/](A-Z){5}/} as + # requirements. + def requirements + @defaults.merge(path.requirements).delete_if { |_, v| + /.+?/m == v + } + end + + def segments + path.names + end + + def required_keys + required_parts + required_defaults.keys + end + + def score(supplied_keys) + path.required_names.each do |k| + return -1 unless supplied_keys.include?(k) + end + + (required_defaults.length * 2) + path.names.count { |k| supplied_keys.include?(k) } + end + + def parts + @parts ||= segments.map(&:to_sym) + end + alias :segment_keys :parts + + def format(path_options) + @path_formatter.evaluate path_options + end + + def required_parts + @required_parts ||= path.required_names.map(&:to_sym) + end + + def required_default?(key) + @_required_defaults.include?(key) + end + + def required_defaults + @required_defaults ||= @defaults.dup.delete_if do |k, _| + parts.include?(k) || !required_default?(k) + end + end + + def glob? + path.ast.glob? + end + + def dispatcher? + @app.dispatcher? + end + + def matches?(request) + match_verb(request) && + constraints.all? { |method, value| + case value + when Regexp, String + value === request.send(method).to_s + when Array + value.include?(request.send(method)) + when TrueClass + request.send(method).present? + when FalseClass + request.send(method).blank? + else + value === request.send(method) + end + } + end + + def ip + constraints[:ip] || // + end + + def requires_matching_verb? + !@request_method_match.all? { |x| x == VerbMatchers::All } + end + + def verb + verbs.join("|") + end + + private + def verbs + @request_method_match.map(&:verb) + end + + def match_verb(request) + @request_method_match.any? { |m| m.call request } + end + end + end + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/router.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/router.rb new file mode 100644 index 00000000..a4476dc9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/router.rb @@ -0,0 +1,151 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "action_dispatch/journey/router/utils" +require "action_dispatch/journey/routes" +require "action_dispatch/journey/formatter" + +before = $-w +$-w = false +require "action_dispatch/journey/parser" +$-w = before + +require "action_dispatch/journey/route" +require "action_dispatch/journey/path/pattern" + +module ActionDispatch + module Journey # :nodoc: + class Router # :nodoc: + attr_accessor :routes + + def initialize(routes) + @routes = routes + end + + def eager_load! + # Eagerly trigger the simulator's initialization so it doesn't happen during a + # request cycle. + simulator + nil + end + + def serve(req) + find_routes(req) do |match, parameters, route| + set_params = req.path_parameters + path_info = req.path_info + script_name = req.script_name + + unless route.path.anchored + req.script_name = (script_name.to_s + match.to_s).chomp("/") + req.path_info = match.post_match + req.path_info = "/" + req.path_info unless req.path_info.start_with? "/" + end + + tmp_params = set_params.merge route.defaults + parameters.each_pair { |key, val| + tmp_params[key] = val.force_encoding(::Encoding::UTF_8) + } + + req.path_parameters = tmp_params + req.route_uri_pattern = route.path.spec.to_s + + _, headers, _ = response = route.app.serve(req) + + if "pass" == headers[Constants::X_CASCADE] + req.script_name = script_name + req.path_info = path_info + req.path_parameters = set_params + next + end + + return response + end + + [404, { Constants::X_CASCADE => "pass" }, ["Not Found"]] + end + + def recognize(rails_req) + find_routes(rails_req) do |match, parameters, route| + unless route.path.anchored + rails_req.script_name = match.to_s + rails_req.path_info = match.post_match + rails_req.path_info = "/" + rails_req.path_info unless rails_req.path_info.start_with? "/" + end + + parameters = route.defaults.merge parameters + yield(route, parameters) + end + end + + def visualizer + tt = GTG::Builder.new(ast).transition_table + groups = partitioned_routes.first.map(&:ast).group_by(&:to_s) + asts = groups.values.map(&:first) + tt.visualizer(asts) + end + + private + def partitioned_routes + routes.partition { |r| + r.path.anchored && r.path.requirements_anchored? + } + end + + def ast + routes.ast + end + + def simulator + routes.simulator + end + + def custom_routes + routes.custom_routes + end + + def filter_routes(path) + return [] unless ast + simulator.memos(path) { [] } + end + + def find_routes(req) + path_info = req.path_info + routes = filter_routes(path_info).concat custom_routes.find_all { |r| + r.path.match?(path_info) + } + + if req.head? + routes = match_head_routes(routes, req) + else + routes.select! { |r| r.matches?(req) } + end + + routes.sort_by!(&:precedence) + + routes.each { |r| + match_data = r.path.match(path_info) + path_parameters = {} + match_data.names.each_with_index { |name, i| + val = match_data[i + 1] + path_parameters[name.to_sym] = Utils.unescape_uri(val) if val + } + yield [match_data, path_parameters, r] + } + end + + def match_head_routes(routes, req) + head_routes = routes.select { |r| r.requires_matching_verb? && r.matches?(req) } + return head_routes unless head_routes.empty? + + begin + req.request_method = "GET" + routes.select! { |r| r.matches?(req) } + routes + ensure + req.request_method = "HEAD" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/router/utils.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/router/utils.rb new file mode 100644 index 00000000..168119da --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/router/utils.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + module Journey # :nodoc: + class Router # :nodoc: + class Utils # :nodoc: + # Normalizes URI path. + # + # Strips off trailing slash and ensures there is a leading slash. Also converts + # downcase URL encoded string to uppercase. + # + # normalize_path("/foo") # => "/foo" + # normalize_path("/foo/") # => "/foo" + # normalize_path("foo") # => "/foo" + # normalize_path("") # => "/" + # normalize_path("/%ab") # => "/%AB" + def self.normalize_path(path) + path ||= "" + encoding = path.encoding + path = +"/#{path}" + path.squeeze!("/") + + unless path == "/" + path.delete_suffix!("/") + path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase } + end + + path.force_encoding(encoding) + end + + # URI path and fragment escaping https://tools.ietf.org/html/rfc3986 + class UriEncoder # :nodoc: + ENCODE = "%%%02X" + US_ASCII = Encoding::US_ASCII + UTF_8 = Encoding::UTF_8 + EMPTY = (+"").force_encoding(US_ASCII).freeze + DEC2HEX = (0..255).map { |i| (ENCODE % i).force_encoding(US_ASCII) } + + ALPHA = "a-zA-Z" + DIGIT = "0-9" + UNRESERVED = "#{ALPHA}#{DIGIT}\\-\\._~" + SUB_DELIMS = "!\\$&'\\(\\)\\*\\+,;=" + + ESCAPED = /%[a-zA-Z0-9]{2}/ + + FRAGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/?]/ + SEGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@]/ + PATH = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/]/ + + def escape_fragment(fragment) + escape(fragment, FRAGMENT) + end + + def escape_path(path) + escape(path, PATH) + end + + def escape_segment(segment) + escape(segment, SEGMENT) + end + + def unescape_uri(uri) + encoding = uri.encoding == US_ASCII ? UTF_8 : uri.encoding + uri.gsub(ESCAPED) { |match| [match[1, 2].hex].pack("C") }.force_encoding(encoding) + end + + private + def escape(component, pattern) + component.gsub(pattern) { |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII) + end + + def percent_encode(unsafe) + safe = EMPTY.dup + unsafe.each_byte { |b| safe << DEC2HEX[b] } + safe + end + end + + ENCODER = UriEncoder.new + + def self.escape_path(path) + ENCODER.escape_path(path.to_s) + end + + def self.escape_segment(segment) + ENCODER.escape_segment(segment.to_s) + end + + def self.escape_fragment(fragment) + ENCODER.escape_fragment(fragment.to_s) + end + + # Replaces any escaped sequences with their unescaped representations. + # + # uri = "/topics?title=Ruby%20on%20Rails" + # unescape_uri(uri) #=> "/topics?title=Ruby on Rails" + def self.unescape_uri(uri) + ENCODER.unescape_uri(uri) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/routes.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/routes.rb new file mode 100644 index 00000000..8f086430 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/routes.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + module Journey # :nodoc: + # The Routing table. Contains all routes for a system. Routes can be added to + # the table by calling Routes#add_route. + class Routes # :nodoc: + include Enumerable + + attr_reader :routes, :custom_routes, :anchored_routes + + def initialize(routes = []) + @routes = routes + @ast = nil + @anchored_routes = [] + @custom_routes = [] + @simulator = nil + end + + def empty? + routes.empty? + end + + def length + routes.length + end + alias :size :length + + def last + routes.last + end + + def each(&block) + routes.each(&block) + end + + def clear + routes.clear + anchored_routes.clear + custom_routes.clear + end + + def partition_route(route) + if route.path.anchored && route.path.requirements_anchored? + anchored_routes << route + else + custom_routes << route + end + end + + def ast + @ast ||= begin + nodes = anchored_routes.map(&:ast) + Nodes::Or.new(nodes) + end + end + + def simulator + @simulator ||= begin + gtg = GTG::Builder.new(ast).transition_table + GTG::Simulator.new(gtg) + end + end + + def add_route(name, mapping) + route = mapping.make_route name, routes.length + routes << route + partition_route(route) + clear_cache! + route + end + + private + def clear_cache! + @ast = nil + @simulator = nil + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/scanner.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/scanner.rb new file mode 100644 index 00000000..02fc2e11 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/scanner.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "strscan" + +module ActionDispatch + module Journey # :nodoc: + class Scanner # :nodoc: + STATIC_TOKENS = Array.new(150) + STATIC_TOKENS[".".ord] = :DOT + STATIC_TOKENS["/".ord] = :SLASH + STATIC_TOKENS["(".ord] = :LPAREN + STATIC_TOKENS[")".ord] = :RPAREN + STATIC_TOKENS["|".ord] = :OR + STATIC_TOKENS[":".ord] = :SYMBOL + STATIC_TOKENS["*".ord] = :STAR + STATIC_TOKENS.freeze + + class Scanner < StringScanner + unless method_defined?(:peek_byte) # https://github.com/ruby/strscan/pull/89 + def peek_byte + string.getbyte(pos) + end + end + end + + def initialize + @scanner = nil + @length = nil + end + + def scan_setup(str) + @scanner = Scanner.new(str) + end + + def next_token + return if @scanner.eos? + + until token = scan || @scanner.eos?; end + token + end + + def last_string + -@scanner.string.byteslice(@scanner.pos - @length, @length) + end + + def last_literal + last_str = @scanner.string.byteslice(@scanner.pos - @length, @length) + last_str.tr! "\\", "" + -last_str + end + + private + def scan + next_byte = @scanner.peek_byte + case + when (token = STATIC_TOKENS[next_byte]) && (token != :SYMBOL || next_byte_is_not_a_token?) + @scanner.pos += 1 + @length = @scanner.skip(/\w+/).to_i + 1 if token == :SYMBOL || token == :STAR + token + when @length = @scanner.skip(/(?:[\w%\-~!$&'*+,;=@]|\\[:()])+/) + :LITERAL + when @length = @scanner.skip(/./) + :LITERAL + end + end + + def next_byte_is_not_a_token? + !STATIC_TOKENS[@scanner.string.getbyte(@scanner.pos + 1)] + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/visitors.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/visitors.rb new file mode 100644 index 00000000..a5e33a56 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/visitors.rb @@ -0,0 +1,267 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + # :stopdoc: + module Journey + class Format + ESCAPE_PATH = ->(value) { Router::Utils.escape_path(value) } + ESCAPE_SEGMENT = ->(value) { Router::Utils.escape_segment(value) } + + Parameter = Struct.new(:name, :escaper) do + def escape(value); escaper.call value; end + end + + def self.required_path(symbol) + Parameter.new symbol, ESCAPE_PATH + end + + def self.required_segment(symbol) + Parameter.new symbol, ESCAPE_SEGMENT + end + + def initialize(parts) + @parts = parts + @children = [] + @parameters = [] + + parts.each_with_index do |object, i| + case object + when Journey::Format + @children << i + when Parameter + @parameters << i + end + end + end + + def evaluate(hash) + parts = @parts.dup + + @parameters.each do |index| + param = parts[index] + value = hash[param.name] + return "" if value.nil? + parts[index] = param.escape value + end + + @children.each { |index| parts[index] = parts[index].evaluate(hash) } + + parts.join + end + end + + module Visitors # :nodoc: + class Visitor # :nodoc: + DISPATCH_CACHE = {} + + def accept(node) + visit(node) + end + + private + def visit(node) + send(DISPATCH_CACHE[node.type], node) + end + + def binary(node) + visit(node.left) + visit(node.right) + end + def visit_CAT(n); binary(n); end + + def nary(node) + node.children.each { |c| visit(c) } + end + def visit_OR(n); nary(n); end + + def unary(node) + visit(node.left) + end + def visit_GROUP(n); unary(n); end + def visit_STAR(n); unary(n); end + + def terminal(node); end + def visit_LITERAL(n); terminal(n); end + def visit_SYMBOL(n); terminal(n); end + def visit_SLASH(n); terminal(n); end + def visit_DOT(n); terminal(n); end + + private_instance_methods(false).each do |pim| + next unless pim =~ /^visit_(.*)$/ + DISPATCH_CACHE[$1.to_sym] = pim + end + end + + class FunctionalVisitor # :nodoc: + DISPATCH_CACHE = {} + + def accept(node, seed) + visit(node, seed) + end + + def visit(node, seed) + send(DISPATCH_CACHE[node.type], node, seed) + end + + def binary(node, seed) + visit(node.right, visit(node.left, seed)) + end + def visit_CAT(n, seed); binary(n, seed); end + + def nary(node, seed) + node.children.inject(seed) { |s, c| visit(c, s) } + end + def visit_OR(n, seed); nary(n, seed); end + + def unary(node, seed) + visit(node.left, seed) + end + def visit_GROUP(n, seed); unary(n, seed); end + def visit_STAR(n, seed); unary(n, seed); end + + def terminal(node, seed); seed; end + def visit_LITERAL(n, seed); terminal(n, seed); end + def visit_SYMBOL(n, seed); terminal(n, seed); end + def visit_SLASH(n, seed); terminal(n, seed); end + def visit_DOT(n, seed); terminal(n, seed); end + + instance_methods(false).each do |pim| + next unless pim =~ /^visit_(.*)$/ + DISPATCH_CACHE[$1.to_sym] = pim + end + end + + class FormatBuilder < Visitor # :nodoc: + def accept(node); Journey::Format.new(super); end + def terminal(node); [node.left]; end + + def binary(node) + visit(node.left) + visit(node.right) + end + + def visit_GROUP(n); [Journey::Format.new(unary(n))]; end + + def visit_STAR(n) + [Journey::Format.required_path(n.left.to_sym)] + end + + def visit_SYMBOL(n) + symbol = n.to_sym + if symbol == :controller + [Journey::Format.required_path(symbol)] + else + [Journey::Format.required_segment(symbol)] + end + end + end + + # Loop through the requirements AST. + class Each < FunctionalVisitor # :nodoc: + def visit(node, block) + block.call(node) + super + end + + INSTANCE = new + end + + class String < FunctionalVisitor # :nodoc: + private + def binary(node, seed) + visit(node.right, visit(node.left, seed)) + end + + def nary(node, seed) + last_child = node.children.last + node.children.inject(seed) { |s, c| + string = visit(c, s) + string << "|" unless last_child == c + string + } + end + + def terminal(node, seed) + seed + node.left + end + + def visit_GROUP(node, seed) + visit(node.left, seed.dup << "(") << ")" + end + + INSTANCE = new + end + + class Dot < FunctionalVisitor # :nodoc: + def initialize + @nodes = [] + @edges = [] + end + + def accept(node, seed = [[], []]) + super + nodes, edges = seed + <<-eodot + digraph parse_tree { + size="8,5" + node [shape = none]; + edge [dir = none]; + #{nodes.join "\n"} + #{edges.join("\n")} + } + eodot + end + + private + def binary(node, seed) + seed.last.concat node.children.map { |c| + "#{node.object_id} -> #{c.object_id};" + } + super + end + + def nary(node, seed) + seed.last.concat node.children.map { |c| + "#{node.object_id} -> #{c.object_id};" + } + super + end + + def unary(node, seed) + seed.last << "#{node.object_id} -> #{node.left.object_id};" + super + end + + def visit_GROUP(node, seed) + seed.first << "#{node.object_id} [label=\"()\"];" + super + end + + def visit_CAT(node, seed) + seed.first << "#{node.object_id} [label=\"○\"];" + super + end + + def visit_STAR(node, seed) + seed.first << "#{node.object_id} [label=\"*\"];" + super + end + + def visit_OR(node, seed) + seed.first << "#{node.object_id} [label=\"|\"];" + super + end + + def terminal(node, seed) + value = node.left + + seed.first << "#{node.object_id} [label=\"#{value}\"];" + seed + end + INSTANCE = new + end + end + end + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/visualizer/fsm.css b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/visualizer/fsm.css new file mode 100644 index 00000000..403e16a7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/visualizer/fsm.css @@ -0,0 +1,30 @@ +body { + font-family: "Helvetica Neue", Helvetica, Arial, Sans-Serif; + margin: 0; +} + +h1 { + font-size: 2.0em; font-weight: bold; text-align: center; + color: white; background-color: black; + padding: 5px 0; + margin: 0 0 20px; +} + +h2 { + text-align: center; + display: none; + font-size: 0.5em; +} + +.clearfix {display: inline-block; } +.input { overflow: show;} +.instruction { color: #666; padding: 0 30px 20px; font-size: 0.9em} +.instruction p { padding: 0 0 5px; } +.instruction li { padding: 0 10px 5px; } + +.form { background: #EEE; padding: 20px 30px; border-radius: 5px; margin-left: auto; margin-right: auto; width: 500px; margin-bottom: 20px} +.form p, .form form { text-align: center } +.form form {padding: 0 10px 5px; } +.form .fun_routes { font-size: 0.9em;} +.form .fun_routes a { margin: 0 5px 0 0; } + diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/visualizer/fsm.js b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/visualizer/fsm.js new file mode 100644 index 00000000..445d020f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/visualizer/fsm.js @@ -0,0 +1,159 @@ +function tokenize(input, callback) { + while(input.length > 0) { + callback(input.match(/^[\/\.\?]|[^\/\.\?]+/)[0]); + input = input.replace(/^[\/\.\?]|[^\/\.\?]+/, ''); + } +} + +var graph = d3.select("#chart-2 svg"); +var svg_edges = {}; +var svg_nodes = {}; + +graph.selectAll("g.edge").each(function() { + var node = d3.select(this); + var index = node.select("title").text().split("->"); + var left = parseInt(index[0]); + var right = parseInt(index[1]); + + if(!svg_edges[left]) { svg_edges[left] = {} } + svg_edges[left][right] = node; +}); + +graph.selectAll("g.node").each(function() { + var node = d3.select(this); + var index = parseInt(node.select("title").text()); + svg_nodes[index] = node; +}); + +function reset_graph() { + for(var key in svg_edges) { + for(var mkey in svg_edges[key]) { + var node = svg_edges[key][mkey]; + var path = node.select("path"); + var arrow = node.select("polygon"); + path.style("stroke", "black"); + arrow.style("stroke", "black").style("fill", "black"); + } + } + + for(var key in svg_nodes) { + var node = svg_nodes[key]; + node.select('ellipse').style("fill", "white"); + node.select('polygon').style("fill", "white"); + } + return false; +} + +function highlight_edge(from, to) { + var node = svg_edges[from][to]; + var path = node.select("path"); + var arrow = node.select("polygon"); + + path + .transition().duration(500) + .style("stroke", "green"); + + arrow + .transition().duration(500) + .style("stroke", "green").style("fill", "green"); +} + +function highlight_state(index, color) { + if(!color) { color = "green"; } + + svg_nodes[index].select('ellipse') + .style("fill", "white") + .transition().duration(500) + .style("fill", color); +} + +function highlight_finish(index) { + svg_nodes[index].select('ellipse') + .style("fill", "while") + .transition().duration(500) + .style("fill", "blue"); +} + +function match(input) { + reset_graph(); + var table = tt(); + var states = [[0, null]]; + var regexp_states = table['regexp_states']; + var string_states = table['string_states']; + var stdparam_states = table['stdparam_states']; + var accepting = table['accepting']; + var default_re = new RegExp("^[^.\/?]+$"); + var start_index = 0; + + highlight_state(0); + + tokenize(input, function(token) { + var end_index = start_index + token.length; + + var new_states = []; + for(var key in states) { + var state_parts = states[key]; + var state = state_parts[0]; + var previous_start = state_parts[1]; + + if(previous_start == null) { + if(string_states[state] && string_states[state][token]) { + var new_state = string_states[state][token]; + highlight_edge(state, new_state); + highlight_state(new_state); + new_states.push([new_state, null]); + } + + if(stdparam_states[state] && default_re.test(token)) { + for(var key in stdparam_states[state]) { + var new_state = stdparam_states[state][key]; + highlight_edge(state, new_state); + highlight_state(new_state); + new_states.push([new_state, null]); + } + } + } + + if(regexp_states[state]) { + var slice_start = previous_start != null ? previous_start : start_index; + + for(var key in regexp_states[state]) { + var re = new RegExp("^" + key + "$"); + + var accumulation = input.slice(slice_start, end_index); + + if(re.test(accumulation)) { + var new_state = regexp_states[state][key]; + highlight_edge(state, new_state); + highlight_state(new_state); + new_states.push([new_state, null]); + } + + // retry the same regexp with the accumulated data either way + new_states.push([state, slice_start]); + } + } + } + + states = new_states; + start_index = end_index; + }); + + for(var key in states) { + var state_parts = states[key]; + var state = state_parts[0]; + var slice_start = state_parts[1]; + + // we must ignore ones that are still accepting more data + if (slice_start != null) continue; + + if(accepting[state]) { + highlight_finish(state); + } else { + highlight_state(state, "red"); + } + } + + return false; +} + diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/visualizer/index.html.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/visualizer/index.html.erb new file mode 100644 index 00000000..40d3073f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/journey/visualizer/index.html.erb @@ -0,0 +1,52 @@ + + + + <%= title %> + + + + + +
+

Routes FSM with NFA simulation

+
+

+ Type a route in to the box and click "simulate". +

+
+ + + +
+

+ Some fun routes to try: + <% fun_routes.each do |path| %> + + <%= path %> + + <% end %> +

+
+
+ <%= svg %> +
+
+

+ This is a FSM for a system that has the following routes: +

+
    + <% paths.each do |route| %> +
  • <%= route %>
  • + <% end %> +
+
+
+ <% javascripts.each do |js| %> + + <% end %> + + diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/log_subscriber.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/log_subscriber.rb new file mode 100644 index 00000000..c9d5d4ba --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/log_subscriber.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + class LogSubscriber < ActiveSupport::LogSubscriber + def redirect(event) + payload = event.payload + + info { "Redirected to #{payload[:location]}" } + + info do + status = payload[:status] + + message = +"Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms" + message << "\n\n" if defined?(Rails.env) && Rails.env.development? + + message + end + end + subscribe_log_level :redirect, :info + end +end + +ActionDispatch::LogSubscriber.attach_to :action_dispatch diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/actionable_exceptions.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/actionable_exceptions.rb new file mode 100644 index 00000000..03dabc73 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/actionable_exceptions.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "uri" +require "active_support/actionable_error" + +module ActionDispatch + class ActionableExceptions # :nodoc: + cattr_accessor :endpoint, default: "/rails/actions" + + def initialize(app) + @app = app + end + + def call(env) + request = ActionDispatch::Request.new(env) + return @app.call(env) unless actionable_request?(request) + + ActiveSupport::ActionableError.dispatch(request.params[:error].to_s.safe_constantize, request.params[:action]) + + redirect_to request.params[:location] + end + + private + def actionable_request?(request) + request.get_header("action_dispatch.show_detailed_exceptions") && request.post? && request.path == endpoint + end + + def redirect_to(location) + uri = URI.parse location + + if uri.relative? || uri.scheme == "http" || uri.scheme == "https" + body = "" + else + return [400, { Rack::CONTENT_TYPE => "text/plain; charset=utf-8" }, ["Invalid redirection URI"]] + end + + [302, { + Rack::CONTENT_TYPE => "text/html; charset=#{Response.default_charset}", + Rack::CONTENT_LENGTH => body.bytesize.to_s, + ActionDispatch::Constants::LOCATION => location, + }, [body]] + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/assume_ssl.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/assume_ssl.rb new file mode 100644 index 00000000..3569cc1f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/assume_ssl.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + # # Action Dispatch AssumeSSL + # + # When proxying through a load balancer that terminates SSL, the forwarded + # request will appear as though it's HTTP instead of HTTPS to the application. + # This makes redirects and cookie security target HTTP instead of HTTPS. This + # middleware makes the server assume that the proxy already terminated SSL, and + # that the request really is HTTPS. + class AssumeSSL + def initialize(app) + @app = app + end + + def call(env) + env["HTTPS"] = "on" + env["HTTP_X_FORWARDED_PORT"] = "443" + env["HTTP_X_FORWARDED_PROTO"] = "https" + env["rack.url_scheme"] = "https" + + @app.call(env) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/callbacks.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/callbacks.rb new file mode 100644 index 00000000..e4d2b083 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/callbacks.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + # # Action Dispatch Callbacks + # + # Provides callbacks to be executed before and after dispatching the request. + class Callbacks + include ActiveSupport::Callbacks + + define_callbacks :call + + class << self + def before(*args, &block) + set_callback(:call, :before, *args, &block) + end + + def after(*args, &block) + set_callback(:call, :after, *args, &block) + end + end + + def initialize(app) + @app = app + end + + def call(env) + error = nil + result = run_callbacks :call do + @app.call(env) + rescue => error + end + raise error if error + result + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/cookies.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/cookies.rb new file mode 100644 index 00000000..dbf07d52 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/cookies.rb @@ -0,0 +1,719 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/core_ext/hash/keys" +require "active_support/key_generator" +require "active_support/message_verifier" +require "active_support/json" +require "rack/utils" + +module ActionDispatch + module RequestCookieMethods + def cookie_jar + fetch_header("action_dispatch.cookies") do + self.cookie_jar = Cookies::CookieJar.build(self, cookies) + end + end + + # :stopdoc: + prepend Module.new { + def commit_cookie_jar! + cookie_jar.commit! + end + } + + def have_cookie_jar? + has_header? "action_dispatch.cookies" + end + + def cookie_jar=(jar) + set_header "action_dispatch.cookies", jar + end + + def key_generator + get_header Cookies::GENERATOR_KEY + end + + def signed_cookie_salt + get_header Cookies::SIGNED_COOKIE_SALT + end + + def encrypted_cookie_salt + get_header Cookies::ENCRYPTED_COOKIE_SALT + end + + def encrypted_signed_cookie_salt + get_header Cookies::ENCRYPTED_SIGNED_COOKIE_SALT + end + + def authenticated_encrypted_cookie_salt + get_header Cookies::AUTHENTICATED_ENCRYPTED_COOKIE_SALT + end + + def use_authenticated_cookie_encryption + get_header Cookies::USE_AUTHENTICATED_COOKIE_ENCRYPTION + end + + def encrypted_cookie_cipher + get_header Cookies::ENCRYPTED_COOKIE_CIPHER + end + + def signed_cookie_digest + get_header Cookies::SIGNED_COOKIE_DIGEST + end + + def secret_key_base + get_header Cookies::SECRET_KEY_BASE + end + + def cookies_serializer + get_header Cookies::COOKIES_SERIALIZER + end + + def cookies_same_site_protection + get_header(Cookies::COOKIES_SAME_SITE_PROTECTION)&.call(self) + end + + def cookies_digest + get_header Cookies::COOKIES_DIGEST + end + + def cookies_rotations + get_header Cookies::COOKIES_ROTATIONS + end + + def use_cookies_with_metadata + get_header Cookies::USE_COOKIES_WITH_METADATA + end + + # :startdoc: + end + + ActiveSupport.on_load(:action_dispatch_request) do + include RequestCookieMethods + end + + # Read and write data to cookies through ActionController::Cookies#cookies. + # + # When reading cookie data, the data is read from the HTTP request header, + # Cookie. When writing cookie data, the data is sent out in the HTTP response + # header, `Set-Cookie`. + # + # Examples of writing: + # + # # Sets a simple session cookie. + # # This cookie will be deleted when the user's browser is closed. + # cookies[:user_name] = "david" + # + # # Cookie values are String-based. Other data types need to be serialized. + # cookies[:lat_lon] = JSON.generate([47.68, -122.37]) + # + # # Sets a cookie that expires in 1 hour. + # cookies[:login] = { value: "XJ-122", expires: 1.hour } + # + # # Sets a cookie that expires at a specific time. + # cookies[:login] = { value: "XJ-122", expires: Time.utc(2020, 10, 15, 5) } + # + # # Sets a signed cookie, which prevents users from tampering with its value. + # cookies.signed[:user_id] = current_user.id + # # It can be read using the signed method. + # cookies.signed[:user_id] # => 123 + # + # # Sets an encrypted cookie value before sending it to the client which + # # prevent users from reading and tampering with its value. + # cookies.encrypted[:discount] = 45 + # # It can be read using the encrypted method. + # cookies.encrypted[:discount] # => 45 + # + # # Sets a "permanent" cookie (which expires in 20 years from now). + # cookies.permanent[:login] = "XJ-122" + # + # # You can also chain these methods: + # cookies.signed.permanent[:login] = "XJ-122" + # + # Examples of reading: + # + # cookies[:user_name] # => "david" + # cookies.size # => 2 + # JSON.parse(cookies[:lat_lon]) # => [47.68, -122.37] + # cookies.signed[:login] # => "XJ-122" + # cookies.encrypted[:discount] # => 45 + # + # Example for deleting: + # + # cookies.delete :user_name + # + # Please note that if you specify a `:domain` when setting a cookie, you must + # also specify the domain when deleting the cookie: + # + # cookies[:name] = { + # value: 'a yummy cookie', + # expires: 1.year, + # domain: 'domain.com' + # } + # + # cookies.delete(:name, domain: 'domain.com') + # + # The option symbols for setting cookies are: + # + # * `:value` - The cookie's value. + # * `:path` - The path for which this cookie applies. Defaults to the root of + # the application. + # * `:domain` - The domain for which this cookie applies so you can restrict + # to the domain level. If you use a schema like www.example.com and want to + # share session with user.example.com set `:domain` to `:all`. To support + # multiple domains, provide an array, and the first domain matching + # `request.host` will be used. Make sure to specify the `:domain` option + # with `:all` or `Array` again when deleting cookies. For more flexibility + # you can set the domain on a per-request basis by specifying `:domain` with + # a proc. + # + # domain: nil # Does not set cookie domain. (default) + # domain: :all # Allow the cookie for the top most level + # # domain and subdomains. + # domain: %w(.example.com .example.org) # Allow the cookie + # # for concrete domain names. + # domain: proc { Tenant.current.cookie_domain } # Set cookie domain dynamically + # domain: proc { |req| ".sub.#{req.host}" } # Set cookie domain dynamically based on request + # + # * `:tld_length` - When using `:domain => :all`, this option can be used to + # explicitly set the TLD length when using a short (<= 3 character) domain + # that is being interpreted as part of a TLD. For example, to share cookies + # between user1.lvh.me and user2.lvh.me, set `:tld_length` to 2. + # * `:expires` - The time at which this cookie expires, as a Time or + # ActiveSupport::Duration object. + # * `:secure` - Whether this cookie is only transmitted to HTTPS servers. + # Default is `false`. + # * `:httponly` - Whether this cookie is accessible via scripting or only + # HTTP. Defaults to `false`. + # * `:same_site` - The value of the `SameSite` cookie attribute, which + # determines how this cookie should be restricted in cross-site contexts. + # Possible values are `nil`, `:none`, `:lax`, and `:strict`. Defaults to + # `:lax`. + # + class Cookies + HTTP_HEADER = "Set-Cookie" + GENERATOR_KEY = "action_dispatch.key_generator" + SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt" + ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt" + ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt" + AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt" + USE_AUTHENTICATED_COOKIE_ENCRYPTION = "action_dispatch.use_authenticated_cookie_encryption" + ENCRYPTED_COOKIE_CIPHER = "action_dispatch.encrypted_cookie_cipher" + SIGNED_COOKIE_DIGEST = "action_dispatch.signed_cookie_digest" + SECRET_KEY_BASE = "action_dispatch.secret_key_base" + COOKIES_SERIALIZER = "action_dispatch.cookies_serializer" + COOKIES_DIGEST = "action_dispatch.cookies_digest" + COOKIES_ROTATIONS = "action_dispatch.cookies_rotations" + COOKIES_SAME_SITE_PROTECTION = "action_dispatch.cookies_same_site_protection" + USE_COOKIES_WITH_METADATA = "action_dispatch.use_cookies_with_metadata" + + # Cookies can typically store 4096 bytes. + MAX_COOKIE_SIZE = 4096 + + # Raised when storing more than 4K of session data. + CookieOverflow = Class.new StandardError + + # Include in a cookie jar to allow chaining, e.g. `cookies.permanent.signed`. + module ChainedCookieJars + # Returns a jar that'll automatically set the assigned cookies to have an + # expiration date 20 years from now. Example: + # + # cookies.permanent[:prefers_open_id] = true + # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT + # + # This jar is only meant for writing. You'll read permanent cookies through the + # regular accessor. + # + # This jar allows chaining with the signed jar as well, so you can set + # permanent, signed cookies. Examples: + # + # cookies.permanent.signed[:remember_me] = current_user.id + # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT + def permanent + @permanent ||= PermanentCookieJar.new(self) + end + + # Returns a jar that'll automatically generate a signed representation of cookie + # value and verify it when reading from the cookie again. This is useful for + # creating cookies with values that the user is not supposed to change. If a + # signed cookie was tampered with by the user (or a 3rd party), `nil` will be + # returned. + # + # This jar requires that you set a suitable secret for the verification on your + # app's `secret_key_base`. + # + # Example: + # + # cookies.signed[:discount] = 45 + # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/ + # + # cookies.signed[:discount] # => 45 + def signed + @signed ||= SignedKeyRotatingCookieJar.new(self) + end + + # Returns a jar that'll automatically encrypt cookie values before sending them + # to the client and will decrypt them for read. If the cookie was tampered with + # by the user (or a 3rd party), `nil` will be returned. + # + # If `config.action_dispatch.encrypted_cookie_salt` and + # `config.action_dispatch.encrypted_signed_cookie_salt` are both set, legacy + # cookies encrypted with HMAC AES-256-CBC will be transparently upgraded. + # + # This jar requires that you set a suitable secret for the verification on your + # app's `secret_key_base`. + # + # Example: + # + # cookies.encrypted[:discount] = 45 + # # => Set-Cookie: discount=DIQ7fw==--K3n//8vvnSbGq9dA--7Xh91HfLpwzbj1czhBiwOg==; path=/ + # + # cookies.encrypted[:discount] # => 45 + def encrypted + @encrypted ||= EncryptedKeyRotatingCookieJar.new(self) + end + + # Returns the `signed` or `encrypted` jar, preferring `encrypted` if + # `secret_key_base` is set. Used by ActionDispatch::Session::CookieStore to + # avoid the need to introduce new cookie stores. + def signed_or_encrypted + @signed_or_encrypted ||= + if request.secret_key_base.present? + encrypted + else + signed + end + end + + private + def upgrade_legacy_hmac_aes_cbc_cookies? + request.secret_key_base.present? && + request.encrypted_signed_cookie_salt.present? && + request.encrypted_cookie_salt.present? && + request.use_authenticated_cookie_encryption + end + + def prepare_upgrade_legacy_hmac_aes_cbc_cookies? + request.secret_key_base.present? && + request.authenticated_encrypted_cookie_salt.present? && + !request.use_authenticated_cookie_encryption + end + + def encrypted_cookie_cipher + request.encrypted_cookie_cipher || "aes-256-gcm" + end + + def signed_cookie_digest + request.signed_cookie_digest || "SHA1" + end + end + + class CookieJar # :nodoc: + include Enumerable, ChainedCookieJars + + def self.build(req, cookies) + jar = new(req) + jar.update(cookies) + jar + end + + attr_reader :request + + def initialize(request) + @set_cookies = {} + @delete_cookies = {} + @request = request + @cookies = {} + @committed = false + end + + def committed?; @committed; end + + def commit! + @committed = true + @set_cookies.freeze + @delete_cookies.freeze + end + + def each(&block) + @cookies.each(&block) + end + + # Returns the value of the cookie by `name`, or `nil` if no such cookie exists. + def [](name) + @cookies[name.to_s] + end + + def fetch(name, *args, &block) + @cookies.fetch(name.to_s, *args, &block) + end + + def key?(name) + @cookies.key?(name.to_s) + end + alias :has_key? :key? + + # Returns the cookies as Hash. + alias :to_hash :to_h + + def update(other_hash) + @cookies.update other_hash.stringify_keys + self + end + + def update_cookies_from_jar + request_jar = @request.cookie_jar.instance_variable_get(:@cookies) + set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) || @set_cookies.key?(k) } + + @cookies.update set_cookies if set_cookies + end + + def to_header + @cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; " + end + + # Sets the cookie named `name`. The second argument may be the cookie's value or + # a hash of options as documented above. + def []=(name, options) + if options.is_a?(Hash) + options.symbolize_keys! + value = options[:value] + else + value = options + options = { value: value } + end + + handle_options(options) + + if @cookies[name.to_s] != value || options[:expires] + @cookies[name.to_s] = value + @set_cookies[name.to_s] = options + @delete_cookies.delete(name.to_s) + end + + value + end + + # Removes the cookie on the client machine by setting the value to an empty + # string and the expiration date in the past. Like `[]=`, you can pass in an + # options hash to delete cookies with extra data such as a `:path`. + # + # Returns the value of the cookie, or `nil` if the cookie does not exist. + def delete(name, options = {}) + return unless @cookies.has_key? name.to_s + + options.symbolize_keys! + handle_options(options) + + value = @cookies.delete(name.to_s) + @delete_cookies[name.to_s] = options + value + end + + # Whether the given cookie is to be deleted by this CookieJar. Like `[]=`, you + # can pass in an options hash to test if a deletion applies to a specific + # `:path`, `:domain` etc. + def deleted?(name, options = {}) + options.symbolize_keys! + handle_options(options) + @delete_cookies[name.to_s] == options + end + + # Removes all cookies on the client machine by calling `delete` for each cookie. + def clear(options = {}) + @cookies.each_key { |k| delete(k, options) } + end + + def write(response) + @set_cookies.each do |name, value| + if write_cookie?(value) + response.set_cookie(name, value) + end + end + + @delete_cookies.each do |name, value| + response.delete_cookie(name, value) + end + end + + mattr_accessor :always_write_cookie, default: false + + private + def escape(string) + ::Rack::Utils.escape(string) + end + + def write_cookie?(cookie) + request.ssl? || !cookie[:secure] || always_write_cookie || request.host.end_with?(".onion") + end + + def handle_options(options) + if options[:expires].respond_to?(:from_now) + options[:expires] = options[:expires].from_now + end + + options[:path] ||= "/" + + unless options.key?(:same_site) + options[:same_site] = request.cookies_same_site_protection + end + + if options[:domain] == :all || options[:domain] == "all" + cookie_domain = "" + dot_splitted_host = request.host.split(".", -1) + + # Case where request.host is not an IP address or it's an invalid domain (ip + # confirms to the domain structure we expect so we explicitly check for ip) + if request.host.match?(/^[\d.]+$/) || dot_splitted_host.include?("") || dot_splitted_host.length == 1 + options[:domain] = nil + return + end + + # If there is a provided tld length then we use it otherwise default domain. + if options[:tld_length].present? + # Case where the tld_length provided is valid + if dot_splitted_host.length >= options[:tld_length] + cookie_domain = dot_splitted_host.last(options[:tld_length]).join(".") + end + # Case where tld_length is not provided + else + # Regular TLDs + if !(/\.[^.]{2,3}\.[^.]{2}\z/.match?(request.host)) + cookie_domain = dot_splitted_host.last(2).join(".") + # **.**, ***.** style TLDs like co.uk and com.au + else + cookie_domain = dot_splitted_host.last(3).join(".") + end + end + + options[:domain] = if cookie_domain.present? + cookie_domain + end + elsif options[:domain].is_a? Array + # If host matches one of the supplied domains. + options[:domain] = options[:domain].find do |domain| + domain = domain.delete_prefix(".") + request.host == domain || request.host.end_with?(".#{domain}") + end + elsif options[:domain].respond_to?(:call) + options[:domain] = options[:domain].call(request) + end + end + end + + class AbstractCookieJar # :nodoc: + include ChainedCookieJars + + def initialize(parent_jar) + @parent_jar = parent_jar + end + + def [](name) + if data = @parent_jar[name.to_s] + result = parse(name, data, purpose: "cookie.#{name}") + + if result.nil? + parse(name, data) + else + result + end + end + end + + def []=(name, options) + if options.is_a?(Hash) + options.symbolize_keys! + else + options = { value: options } + end + + commit(name, options) + @parent_jar[name] = options + end + + protected + def request; @parent_jar.request; end + + private + def expiry_options(options) + if options[:expires].respond_to?(:from_now) + { expires_in: options[:expires] } + else + { expires_at: options[:expires] } + end + end + + def cookie_metadata(name, options) + expiry_options(options).tap do |metadata| + metadata[:purpose] = "cookie.#{name}" if request.use_cookies_with_metadata + end + end + + def parse(name, data, purpose: nil); data; end + def commit(name, options); end + end + + class PermanentCookieJar < AbstractCookieJar # :nodoc: + private + def commit(name, options) + options[:expires] = 20.years.from_now + end + end + + module SerializedCookieJars # :nodoc: + SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer + + protected + def digest + request.cookies_digest || "SHA1" + end + + private + def serializer + @serializer ||= + case request.cookies_serializer + when nil + ActiveSupport::Messages::SerializerWithFallback[:marshal] + when :hybrid + ActiveSupport::Messages::SerializerWithFallback[:json_allow_marshal] + when Symbol + ActiveSupport::Messages::SerializerWithFallback[request.cookies_serializer] + else + request.cookies_serializer + end + end + + def reserialize?(dumped) + serializer.is_a?(ActiveSupport::Messages::SerializerWithFallback) && + serializer != ActiveSupport::Messages::SerializerWithFallback[:marshal] && + !serializer.dumped?(dumped) + end + + def parse(name, dumped, force_reserialize: false, **) + if dumped + begin + value = serializer.load(dumped) + rescue StandardError + return + end + + self[name] = { value: value } if force_reserialize || reserialize?(dumped) + + value + end + end + + def commit(name, options) + options[:value] = serializer.dump(options[:value]) + end + + def check_for_overflow!(name, options) + if options[:value].bytesize > MAX_COOKIE_SIZE + raise CookieOverflow, "#{name} cookie overflowed with size #{options[:value].bytesize} bytes" + end + end + end + + class SignedKeyRotatingCookieJar < AbstractCookieJar # :nodoc: + include SerializedCookieJars + + def initialize(parent_jar) + super + + secret = request.key_generator.generate_key(request.signed_cookie_salt) + @verifier = ActiveSupport::MessageVerifier.new(secret, digest: signed_cookie_digest, serializer: SERIALIZER) + + request.cookies_rotations.signed.each do |(*secrets)| + options = secrets.extract_options! + @verifier.rotate(*secrets, serializer: SERIALIZER, **options) + end + end + + private + def parse(name, signed_message, purpose: nil) + rotated = false + data = @verifier.verified(signed_message, purpose: purpose, on_rotation: -> { rotated = true }) + super(name, data, force_reserialize: rotated) + end + + def commit(name, options) + super + options[:value] = @verifier.generate(options[:value], **cookie_metadata(name, options)) + check_for_overflow!(name, options) + end + end + + class EncryptedKeyRotatingCookieJar < AbstractCookieJar # :nodoc: + include SerializedCookieJars + + def initialize(parent_jar) + super + + if request.use_authenticated_cookie_encryption + key_len = ActiveSupport::MessageEncryptor.key_len(encrypted_cookie_cipher) + secret = request.key_generator.generate_key(request.authenticated_encrypted_cookie_salt, key_len) + @encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: encrypted_cookie_cipher, serializer: SERIALIZER) + else + key_len = ActiveSupport::MessageEncryptor.key_len("aes-256-cbc") + secret = request.key_generator.generate_key(request.encrypted_cookie_salt, key_len) + sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt) + @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", serializer: SERIALIZER) + end + + request.cookies_rotations.encrypted.each do |(*secrets)| + options = secrets.extract_options! + @encryptor.rotate(*secrets, serializer: SERIALIZER, **options) + end + + if upgrade_legacy_hmac_aes_cbc_cookies? + legacy_cipher = "aes-256-cbc" + secret = request.key_generator.generate_key(request.encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len(legacy_cipher)) + sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt) + + @encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER) + elsif prepare_upgrade_legacy_hmac_aes_cbc_cookies? + future_cipher = encrypted_cookie_cipher + secret = request.key_generator.generate_key(request.authenticated_encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len(future_cipher)) + + @encryptor.rotate(secret, nil, cipher: future_cipher, serializer: SERIALIZER) + end + end + + private + def parse(name, encrypted_message, purpose: nil) + rotated = false + data = @encryptor.decrypt_and_verify(encrypted_message, purpose: purpose, on_rotation: -> { rotated = true }) + super(name, data, force_reserialize: rotated) + rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature + nil + end + + def commit(name, options) + super + options[:value] = @encryptor.encrypt_and_sign(options[:value], **cookie_metadata(name, options)) + check_for_overflow!(name, options) + end + end + + def initialize(app) + @app = app + end + + def call(env) + request = ActionDispatch::Request.new(env) + response = @app.call(env) + + if request.have_cookie_jar? + cookie_jar = request.cookie_jar + unless cookie_jar.committed? + response = Rack::Response[*response] + cookie_jar.write(response) + end + end + + response.to_a + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/debug_exceptions.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/debug_exceptions.rb new file mode 100644 index 00000000..baa7e3d2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/debug_exceptions.rb @@ -0,0 +1,206 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "action_dispatch/middleware/exception_wrapper" +require "action_dispatch/routing/inspector" + +require "action_view" + +module ActionDispatch + # # Action Dispatch DebugExceptions + # + # This middleware is responsible for logging exceptions and showing a debugging + # page in case the request is local. + class DebugExceptions + cattr_reader :interceptors, instance_accessor: false, default: [] + + def self.register_interceptor(object = nil, &block) + interceptor = object || block + interceptors << interceptor + end + + def initialize(app, routes_app = nil, response_format = :default, interceptors = self.class.interceptors) + @app = app + @routes_app = routes_app + @response_format = response_format + @interceptors = interceptors + end + + def call(env) + _, headers, body = response = @app.call(env) + + if headers[Constants::X_CASCADE] == "pass" + body.close if body.respond_to?(:close) + raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}" + end + + response + rescue Exception => exception + request = ActionDispatch::Request.new env + backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner") + wrapper = ExceptionWrapper.new(backtrace_cleaner, exception) + + invoke_interceptors(request, exception, wrapper) + raise exception unless wrapper.show?(request) + render_exception(request, exception, wrapper) + end + + private + def invoke_interceptors(request, exception, wrapper) + @interceptors.each do |interceptor| + interceptor.call(request, exception) + rescue Exception + log_error(request, wrapper) + end + end + + def render_exception(request, exception, wrapper) + log_error(request, wrapper) + + if request.get_header("action_dispatch.show_detailed_exceptions") + begin + content_type = request.formats.first + rescue ActionDispatch::Http::MimeNegotiation::InvalidType + content_type = Mime[:text] + end + + if api_request?(content_type) + render_for_api_request(content_type, wrapper) + else + render_for_browser_request(request, wrapper) + end + else + raise exception + end + end + + def render_for_browser_request(request, wrapper) + template = create_template(request, wrapper) + file = "rescues/#{wrapper.rescue_template}" + + if request.xhr? + body = template.render(template: file, layout: false, formats: [:text]) + format = "text/plain" + else + body = template.render(template: file, layout: "rescues/layout") + format = "text/html" + end + render(wrapper.status_code, body, format) + end + + def render_for_api_request(content_type, wrapper) + body = { + status: wrapper.status_code, + error: Rack::Utils::HTTP_STATUS_CODES.fetch( + wrapper.status_code, + Rack::Utils::HTTP_STATUS_CODES[500] + ), + exception: wrapper.exception_inspect, + traces: wrapper.traces + } + + to_format = "to_#{content_type.to_sym}" + + if content_type && body.respond_to?(to_format) + formatted_body = body.public_send(to_format) + format = content_type + else + formatted_body = body.to_json + format = Mime[:json] + end + + render(wrapper.status_code, formatted_body, format) + end + + def create_template(request, wrapper) + DebugView.new( + request: request, + exception_wrapper: wrapper, + # Everything should use the wrapper, but we need to pass `exception` for legacy + # code. + exception: wrapper.exception, + traces: wrapper.traces, + show_source_idx: wrapper.source_to_show_id, + trace_to_show: wrapper.trace_to_show, + routes_inspector: routes_inspector(wrapper), + source_extracts: wrapper.source_extracts, + ) + end + + def render(status, body, format) + [status, { Rack::CONTENT_TYPE => "#{format}; charset=#{Response.default_charset}", Rack::CONTENT_LENGTH => body.bytesize.to_s }, [body]] + end + + def log_error(request, wrapper) + logger = logger(request) + + return unless logger + return if !log_rescued_responses?(request) && wrapper.rescue_response? + + trace = wrapper.exception_trace + + message = [] + message << " " + if wrapper.has_cause? + message << "#{wrapper.exception_class_name} (#{wrapper.message})" + wrapper.wrapped_causes.each do |wrapped_cause| + message << "Caused by: #{wrapped_cause.exception_class_name} (#{wrapped_cause.message})" + end + + message << "\nInformation for: #{wrapper.exception_class_name} (#{wrapper.message}):" + else + message << "#{wrapper.exception_class_name} (#{wrapper.message}):" + end + + message.concat(wrapper.annotated_source_code) + message << " " + message.concat(trace) + + if wrapper.has_cause? + wrapper.wrapped_causes.each do |wrapped_cause| + message << "\nInformation for cause: #{wrapped_cause.exception_class_name} (#{wrapped_cause.message}):" + message.concat(wrapped_cause.annotated_source_code) + message << " " + message.concat(wrapped_cause.exception_trace) + end + end + + log_array(logger, message, request) + end + + def log_array(logger, lines, request) + return if lines.empty? + + level = request.get_header("action_dispatch.debug_exception_log_level") + + if logger.formatter && logger.formatter.respond_to?(:tags_text) + logger.add(level, lines.join("\n#{logger.formatter.tags_text}")) + else + logger.add(level, lines.join("\n")) + end + end + + def logger(request) + request.logger || ActionView::Base.logger || stderr_logger + end + + def stderr_logger + @stderr_logger ||= ActiveSupport::Logger.new($stderr) + end + + def routes_inspector(exception) + if @routes_app.respond_to?(:routes) && (exception.routing_error? || exception.template_error?) + ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes) + end + end + + def api_request?(content_type) + @response_format == :api && !content_type.html? + end + + def log_rescued_responses?(request) + request.get_header("action_dispatch.log_rescued_responses") + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/debug_locks.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/debug_locks.rb new file mode 100644 index 00000000..314d7bd2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/debug_locks.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + # # Action Dispatch DebugLocks + # + # This middleware can be used to diagnose deadlocks in the autoload interlock. + # + # To use it, insert it near the top of the middleware stack, using + # `config/application.rb`: + # + # config.middleware.insert_before Rack::Sendfile, ActionDispatch::DebugLocks + # + # After restarting the application and re-triggering the deadlock condition, the + # route `/rails/locks` will show a summary of all threads currently known to the + # interlock, which lock level they are holding or awaiting, and their current + # backtrace. + # + # Generally a deadlock will be caused by the interlock conflicting with some + # other external lock or blocking I/O call. These cannot be automatically + # identified, but should be visible in the displayed backtraces. + # + # NOTE: The formatting and content of this middleware's output is intended for + # human consumption, and should be expected to change between releases. + # + # This middleware exposes operational details of the server, with no access + # control. It should only be enabled when in use, and removed thereafter. + class DebugLocks + def initialize(app, path = "/rails/locks") + @app = app + @path = path + end + + def call(env) + req = ActionDispatch::Request.new env + + if req.get? + path = req.path_info.chomp("/") + if path == @path + return render_details(req) + end + end + + @app.call(env) + end + + private + def render_details(req) + threads = ActiveSupport::Dependencies.interlock.raw_state do |raw_threads| + # The Interlock itself comes to a complete halt as long as this block is + # executing. That gives us a more consistent picture of everything, but creates + # a pretty strong Observer Effect. + # + # Most directly, that means we need to do as little as possible in this block. + # More widely, it means this middleware should remain a strictly diagnostic tool + # (to be used when something has gone wrong), and not for any sort of general + # monitoring. + + raw_threads.each.with_index do |(thread, info), idx| + info[:index] = idx + info[:backtrace] = thread.backtrace + end + + raw_threads + end + + str = threads.map do |thread, info| + if info[:exclusive] + lock_state = +"Exclusive" + elsif info[:sharing] > 0 + lock_state = +"Sharing" + lock_state << " x#{info[:sharing]}" if info[:sharing] > 1 + else + lock_state = +"No lock" + end + + if info[:waiting] + lock_state << " (yielded share)" + end + + msg = +"Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n" + + if info[:sleeper] + msg << " Waiting in #{info[:sleeper]}" + msg << " to #{info[:purpose].to_s.inspect}" unless info[:purpose].nil? + msg << "\n" + + if info[:compatible] + compat = info[:compatible].map { |c| c == false ? "share" : c.to_s.inspect } + msg << " may be pre-empted for: #{compat.join(', ')}\n" + end + + blockers = threads.values.select { |binfo| blocked_by?(info, binfo, threads.values) } + msg << " blocked by: #{blockers.map { |i| i[:index] }.join(', ')}\n" if blockers.any? + end + + blockees = threads.values.select { |binfo| blocked_by?(binfo, info, threads.values) } + msg << " blocking: #{blockees.map { |i| i[:index] }.join(', ')}\n" if blockees.any? + + msg << "\n#{info[:backtrace].join("\n")}\n" if info[:backtrace] + end.join("\n\n---\n\n\n") + + [200, { Rack::CONTENT_TYPE => "text/plain; charset=#{ActionDispatch::Response.default_charset}", + Rack::CONTENT_LENGTH => str.size.to_s }, [str]] + end + + def blocked_by?(victim, blocker, all_threads) + return false if victim.equal?(blocker) + + case victim[:sleeper] + when :start_sharing + blocker[:exclusive] || + (!victim[:waiting] && blocker[:compatible] && !blocker[:compatible].include?(false)) + when :start_exclusive + blocker[:sharing] > 0 || + blocker[:exclusive] || + (blocker[:compatible] && !blocker[:compatible].include?(victim[:purpose])) + when :yield_shares + blocker[:exclusive] + when :stop_exclusive + blocker[:exclusive] || + victim[:compatible] && + victim[:compatible].include?(blocker[:purpose]) && + all_threads.all? { |other| !other[:compatible] || blocker.equal?(other) || other[:compatible].include?(blocker[:purpose]) } + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/debug_view.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/debug_view.rb new file mode 100644 index 00000000..883c5104 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/debug_view.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "pp" + +require "action_view" +require "action_view/base" + +module ActionDispatch + class DebugView < ActionView::Base # :nodoc: + RESCUES_TEMPLATE_PATHS = [File.expand_path("templates", __dir__)] + + def initialize(assigns) + paths = RESCUES_TEMPLATE_PATHS.dup + lookup_context = ActionView::LookupContext.new(paths) + super(lookup_context, assigns, nil) + end + + def compiled_method_container + self.class + end + + def debug_params(params) + clean_params = params.clone + clean_params.delete("action") + clean_params.delete("controller") + + if clean_params.empty? + "None" + else + PP.pp(clean_params, +"", 200) + end + end + + def debug_headers(headers) + if headers.present? + headers.inspect.gsub(",", ",\n") + else + "None" + end + end + + def debug_hash(object) + object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n") + end + + def render(*) + logger = ActionView::Base.logger + + if logger && logger.respond_to?(:silence) + logger.silence { super } + else + super + end + end + + def protect_against_forgery? + false + end + + def params_valid? + @request.parameters + rescue ActionController::BadRequest + false + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/exception_wrapper.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/exception_wrapper.rb new file mode 100644 index 00000000..79711557 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/exception_wrapper.rb @@ -0,0 +1,344 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/core_ext/module/attribute_accessors" +require "active_support/syntax_error_proxy" +require "active_support/core_ext/thread/backtrace/location" +require "rack/utils" + +module ActionDispatch + class ExceptionWrapper + cattr_accessor :rescue_responses, default: Hash.new(:internal_server_error).merge!( + "ActionController::RoutingError" => :not_found, + "AbstractController::ActionNotFound" => :not_found, + "ActionController::MethodNotAllowed" => :method_not_allowed, + "ActionController::UnknownHttpMethod" => :method_not_allowed, + "ActionController::NotImplemented" => :not_implemented, + "ActionController::UnknownFormat" => :not_acceptable, + "ActionDispatch::Http::MimeNegotiation::InvalidType" => :not_acceptable, + "ActionController::MissingExactTemplate" => :not_acceptable, + "ActionController::InvalidAuthenticityToken" => :unprocessable_entity, + "ActionController::InvalidCrossOriginRequest" => :unprocessable_entity, + "ActionDispatch::Http::Parameters::ParseError" => :bad_request, + "ActionController::BadRequest" => :bad_request, + "ActionController::ParameterMissing" => :bad_request, + "Rack::QueryParser::ParameterTypeError" => :bad_request, + "Rack::QueryParser::InvalidParameterError" => :bad_request + ) + + cattr_accessor :rescue_templates, default: Hash.new("diagnostics").merge!( + "ActionView::MissingTemplate" => "missing_template", + "ActionController::RoutingError" => "routing_error", + "AbstractController::ActionNotFound" => "unknown_action", + "ActiveRecord::StatementInvalid" => "invalid_statement", + "ActionView::Template::Error" => "template_error", + "ActionController::MissingExactTemplate" => "missing_exact_template", + ) + + cattr_accessor :wrapper_exceptions, default: [ + "ActionView::Template::Error" + ] + + cattr_accessor :silent_exceptions, default: [ + "ActionController::RoutingError", + "ActionDispatch::Http::MimeNegotiation::InvalidType" + ] + + attr_reader :backtrace_cleaner, :wrapped_causes, :exception_class_name, :exception + + def initialize(backtrace_cleaner, exception) + @backtrace_cleaner = backtrace_cleaner + @exception_class_name = exception.class.name + @wrapped_causes = wrapped_causes_for(exception, backtrace_cleaner) + @exception = exception + if exception.is_a?(SyntaxError) + @exception = ActiveSupport::SyntaxErrorProxy.new(exception) + end + @backtrace = build_backtrace + end + + def routing_error? + @exception.is_a?(ActionController::RoutingError) + end + + def template_error? + @exception.is_a?(ActionView::Template::Error) + end + + def sub_template_message + @exception.sub_template_message + end + + def has_cause? + @exception.cause + end + + def failures + @exception.failures + end + + def has_corrections? + @exception.respond_to?(:original_message) && @exception.respond_to?(:corrections) + end + + def original_message + @exception.original_message + end + + def corrections + @exception.corrections + end + + def file_name + @exception.file_name + end + + def line_number + @exception.line_number + end + + def actions + ActiveSupport::ActionableError.actions(@exception) + end + + def unwrapped_exception + if wrapper_exceptions.include?(@exception_class_name) + @exception.cause + else + @exception + end + end + + def annotated_source_code + if exception.respond_to?(:annotated_source_code) + exception.annotated_source_code + else + [] + end + end + + def rescue_template + @@rescue_templates[@exception_class_name] + end + + def status_code + self.class.status_code_for_exception(unwrapped_exception.class.name) + end + + def exception_trace + trace = application_trace + trace = framework_trace if trace.empty? && !silent_exceptions.include?(@exception_class_name) + trace + end + + def application_trace + clean_backtrace(:silent) + end + + def framework_trace + clean_backtrace(:noise) + end + + def full_trace + clean_backtrace(:all) + end + + def traces + application_trace_with_ids = [] + framework_trace_with_ids = [] + full_trace_with_ids = [] + + full_trace.each_with_index do |trace, idx| + trace_with_id = { + exception_object_id: @exception.object_id, + id: idx, + trace: trace + } + + if application_trace.include?(trace) + application_trace_with_ids << trace_with_id + else + framework_trace_with_ids << trace_with_id + end + + full_trace_with_ids << trace_with_id + end + + { + "Application Trace" => application_trace_with_ids, + "Framework Trace" => framework_trace_with_ids, + "Full Trace" => full_trace_with_ids + } + end + + def self.status_code_for_exception(class_name) + Rack::Utils.status_code(@@rescue_responses[class_name]) + end + + def show?(request) + # We're treating `nil` as "unset", and we want the default setting to be `:all`. + # This logic should be extracted to `env_config` and calculated once. + config = request.get_header("action_dispatch.show_exceptions") + + case config + when :none + false + when :rescuable + rescue_response? + else + true + end + end + + def rescue_response? + @@rescue_responses.key?(exception.class.name) + end + + def source_extracts + backtrace.map do |trace| + extract_source(trace) + end + end + + def trace_to_show + if traces["Application Trace"].empty? && rescue_template != "routing_error" + "Full Trace" + else + "Application Trace" + end + end + + def source_to_show_id + (traces[trace_to_show].first || {})[:id] + end + + def exception_name + exception.cause.class.to_s + end + + def message + exception.message + end + + def exception_inspect + exception.inspect + end + + def exception_id + exception.object_id + end + + private + class SourceMapLocation < DelegateClass(Thread::Backtrace::Location) # :nodoc: + def initialize(location, template) + super(location) + @template = template + end + + def spot(exc) + if RubyVM::AbstractSyntaxTree.respond_to?(:node_id_for_backtrace_location) && __getobj__.is_a?(Thread::Backtrace::Location) + location = @template.spot(__getobj__) + else + location = super + end + + if location + @template.translate_location(__getobj__, location) + end + end + end + + attr_reader :backtrace + + def build_backtrace + built_methods = {} + + ActionView::PathRegistry.all_resolvers.each do |resolver| + resolver.built_templates.each do |template| + built_methods[template.method_name] = template + end + end + + (@exception.backtrace_locations || []).map do |loc| + if built_methods.key?(loc.label.to_s) + thread_backtrace_location = if loc.respond_to?(:__getobj__) + loc.__getobj__ + else + loc + end + SourceMapLocation.new(thread_backtrace_location, built_methods[loc.label.to_s]) + else + loc + end + end + end + + def causes_for(exception) + return enum_for(__method__, exception) unless block_given? + + yield exception while exception = exception.cause + end + + def wrapped_causes_for(exception, backtrace_cleaner) + causes_for(exception).map { |cause| self.class.new(backtrace_cleaner, cause) } + end + + def clean_backtrace(*args) + if backtrace_cleaner + backtrace_cleaner.clean(backtrace, *args) + else + backtrace + end + end + + def extract_source(trace) + spot = trace.spot(@exception) + + if spot + line = spot[:first_lineno] + code = extract_source_fragment_lines(spot[:script_lines], line) + + if line == spot[:last_lineno] + code[line] = [ + code[line][0, spot[:first_column]], + code[line][spot[:first_column]...spot[:last_column]], + code[line][spot[:last_column]..-1], + ] + end + + return { + code: code, + line_number: line + } + end + + file, line_number = extract_file_and_line_number(trace) + + { + code: source_fragment(file, line_number), + line_number: line_number + } + end + + def extract_source_fragment_lines(source_lines, line) + start = [line - 3, 0].max + lines = source_lines.drop(start).take(6) + Hash[*(start + 1..(lines.count + start)).zip(lines).flatten] + end + + def source_fragment(path, line) + return unless Rails.respond_to?(:root) && Rails.root + full_path = Rails.root.join(path) + if File.exist?(full_path) + File.open(full_path, "r") do |file| + extract_source_fragment_lines(file.each_line, line) + end + end + end + + def extract_file_and_line_number(trace) + [trace.path, trace.lineno] + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/executor.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/executor.rb new file mode 100644 index 00000000..285f1dc4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/executor.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "rack/body_proxy" + +module ActionDispatch + class Executor + def initialize(app, executor) + @app, @executor = app, executor + end + + def call(env) + state = @executor.run!(reset: true) + begin + response = @app.call(env) + + if env["action_dispatch.report_exception"] + error = env["action_dispatch.exception"] + @executor.error_reporter.report(error, handled: false, source: "application.action_dispatch") + end + + returned = response << ::Rack::BodyProxy.new(response.pop) { state.complete! } + rescue Exception => error + request = ActionDispatch::Request.new env + backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner") + wrapper = ExceptionWrapper.new(backtrace_cleaner, error) + @executor.error_reporter.report(wrapper.unwrapped_exception, handled: false, source: "application.action_dispatch") + raise + ensure + state.complete! unless returned + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/flash.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/flash.rb new file mode 100644 index 00000000..6b642957 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/flash.rb @@ -0,0 +1,318 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/core_ext/hash/keys" + +module ActionDispatch + # # Action Dispatch Flash + # + # The flash provides a way to pass temporary primitive-types (String, Array, + # Hash) between actions. Anything you place in the flash will be exposed to the + # very next action and then cleared out. This is a great way of doing notices + # and alerts, such as a create action that sets `flash[:notice] = "Post + # successfully created"` before redirecting to a display action that can then + # expose the flash to its template. Actually, that exposure is automatically + # done. + # + # class PostsController < ActionController::Base + # def create + # # save post + # flash[:notice] = "Post successfully created" + # redirect_to @post + # end + # + # def show + # # doesn't need to assign the flash notice to the template, that's done automatically + # end + # end + # + # Then in `show.html.erb`: + # + # <% if flash[:notice] %> + #
<%= flash[:notice] %>
+ # <% end %> + # + # Since the `notice` and `alert` keys are a common idiom, convenience accessors + # are available: + # + # flash.alert = "You must be logged in" + # flash.notice = "Post successfully created" + # + # This example places a string in the flash. And of course, you can put as many + # as you like at a time too. If you want to pass non-primitive types, you will + # have to handle that in your application. Example: To show messages with links, + # you will have to use sanitize helper. + # + # Just remember: They'll be gone by the time the next action has been performed. + # + # See docs on the FlashHash class for more details about the flash. + class Flash + KEY = "action_dispatch.request.flash_hash" + + module RequestMethods + # Access the contents of the flash. Returns a ActionDispatch::Flash::FlashHash. + # + # See ActionDispatch::Flash for example usage. + def flash + flash = flash_hash + return flash if flash + self.flash = Flash::FlashHash.from_session_value(session["flash"]) + end + + def flash=(flash) + set_header Flash::KEY, flash + end + + def flash_hash # :nodoc: + get_header Flash::KEY + end + + def commit_flash # :nodoc: + return unless session.enabled? + + if flash_hash && (flash_hash.present? || session.key?("flash")) + session["flash"] = flash_hash.to_session_value + self.flash = flash_hash.dup + end + + if session.loaded? && session.key?("flash") && session["flash"].nil? + session.delete("flash") + end + end + + def reset_session # :nodoc: + super + self.flash = nil + end + end + + class FlashNow # :nodoc: + attr_accessor :flash + + def initialize(flash) + @flash = flash + end + + def []=(k, v) + k = k.to_s + @flash[k] = v + @flash.discard(k) + v + end + + def [](k) + @flash[k.to_s] + end + + # Convenience accessor for `flash.now[:alert]=`. + def alert=(message) + self[:alert] = message + end + + # Convenience accessor for `flash.now[:notice]=`. + def notice=(message) + self[:notice] = message + end + end + + class FlashHash + include Enumerable + + def self.from_session_value(value) # :nodoc: + case value + when FlashHash # Rails 3.1, 3.2 + flashes = value.instance_variable_get(:@flashes) + if discard = value.instance_variable_get(:@used) + flashes.except!(*discard) + end + new(flashes, flashes.keys) + when Hash # Rails 4.0 + flashes = value["flashes"] + if discard = value["discard"] + flashes.except!(*discard) + end + new(flashes, flashes.keys) + else + new + end + end + + # Builds a hash containing the flashes to keep for the next request. If there + # are none to keep, returns `nil`. + def to_session_value # :nodoc: + flashes_to_keep = @flashes.except(*@discard) + return nil if flashes_to_keep.empty? + { "discard" => [], "flashes" => flashes_to_keep } + end + + def initialize(flashes = {}, discard = []) # :nodoc: + @discard = Set.new(stringify_array(discard)) + @flashes = flashes.stringify_keys + @now = nil + end + + def initialize_copy(other) + if other.now_is_loaded? + @now = other.now.dup + @now.flash = self + end + super + end + + def []=(k, v) + k = k.to_s + @discard.delete k + @flashes[k] = v + end + + def [](k) + @flashes[k.to_s] + end + + def update(h) # :nodoc: + @discard.subtract stringify_array(h.keys) + @flashes.update h.stringify_keys + self + end + + def keys + @flashes.keys + end + + def key?(name) + @flashes.key? name.to_s + end + + # Immediately deletes the single flash entry. Use this method when you want + # remove the message within the current action. See also #discard. + def delete(key) + key = key.to_s + @discard.delete key + @flashes.delete key + self + end + + def to_hash + @flashes.dup + end + + def empty? + @flashes.empty? + end + + def clear + @discard.clear + @flashes.clear + end + + def each(&block) + @flashes.each(&block) + end + + alias :merge! :update + + def replace(h) # :nodoc: + @discard.clear + @flashes.replace h.stringify_keys + self + end + + # Sets a flash that will not be available to the next action, only to the + # current. + # + # flash.now[:message] = "Hello current action" + # + # This method enables you to use the flash as a central messaging system in your + # app. When you need to pass an object to the next action, you use the standard + # flash assign (`[]=`). When you need to pass an object to the current action, + # you use `now`, and your object will vanish when the current action is done. + # + # Entries set via `now` are accessed the same way as standard entries: + # `flash['my-key']`. + # + # Also, brings two convenience accessors: + # + # flash.now.alert = "Beware now!" + # # Equivalent to flash.now[:alert] = "Beware now!" + # + # flash.now.notice = "Good luck now!" + # # Equivalent to flash.now[:notice] = "Good luck now!" + def now + @now ||= FlashNow.new(self) + end + + # Keeps either the entire current flash or a specific flash entry available for + # the next action: + # + # flash.keep # keeps the entire flash + # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded + def keep(k = nil) + k = k.to_s if k + @discard.subtract Array(k || keys) + k ? self[k] : self + end + + # Marks the entire flash or a single flash entry to be discarded by the end of + # the current action: + # + # flash.discard # discard the entire flash at the end of the current action + # flash.discard(:warning) # discard only the "warning" entry at the end of the current action + # + # Use this method when you want to display the message in the current action but + # not in the next one. See also #delete. + def discard(k = nil) + k = k.to_s if k + @discard.merge Array(k || keys) + k ? self[k] : self + end + + # Mark for removal entries that were kept, and delete unkept ones. + # + # This method is called automatically by filters, so you generally don't need to + # care about it. + def sweep # :nodoc: + @discard.each { |k| @flashes.delete k } + @discard.replace @flashes.keys + end + + # Convenience accessor for `flash[:alert]`. + def alert + self[:alert] + end + + # Convenience accessor for `flash[:alert]=`. + def alert=(message) + self[:alert] = message + end + + # Convenience accessor for `flash[:notice]`. + def notice + self[:notice] + end + + # Convenience accessor for `flash[:notice]=`. + def notice=(message) + self[:notice] = message + end + + protected + def now_is_loaded? + @now + end + + private + def stringify_array(array) # :doc: + array.map do |item| + item.kind_of?(Symbol) ? item.to_s : item + end + end + end + + def self.new(app) app; end + end + + class Request + prepend Flash::RequestMethods + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/host_authorization.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/host_authorization.rb new file mode 100644 index 00000000..66749cfc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/host_authorization.rb @@ -0,0 +1,171 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + # # Action Dispatch HostAuthorization + # + # This middleware guards from DNS rebinding attacks by explicitly permitting the + # hosts a request can be sent to, and is passed the options set in + # `config.host_authorization`. + # + # Requests can opt-out of Host Authorization with `exclude`: + # + # config.host_authorization = { exclude: ->(request) { request.path =~ /healthcheck/ } } + # + # When a request comes to an unauthorized host, the `response_app` application + # will be executed and rendered. If no `response_app` is given, a default one + # will run. The default response app logs blocked host info with level 'error' + # and responds with `403 Forbidden`. The body of the response contains debug + # info if `config.consider_all_requests_local` is set to true, otherwise the + # body is empty. + class HostAuthorization + ALLOWED_HOSTS_IN_DEVELOPMENT = [".localhost", ".test", IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0")] + PORT_REGEX = /(?::\d+)/ # :nodoc: + SUBDOMAIN_REGEX = /(?:[a-z0-9-]+\.)/i # :nodoc: + IPV4_HOSTNAME = /(?\d+\.\d+\.\d+\.\d+)#{PORT_REGEX}?/ # :nodoc: + IPV6_HOSTNAME = /(?[a-f0-9]*:[a-f0-9.:]+)/i # :nodoc: + IPV6_HOSTNAME_WITH_PORT = /\[#{IPV6_HOSTNAME}\]#{PORT_REGEX}/i # :nodoc: + VALID_IP_HOSTNAME = Regexp.union( # :nodoc: + /\A#{IPV4_HOSTNAME}\z/, + /\A#{IPV6_HOSTNAME}\z/, + /\A#{IPV6_HOSTNAME_WITH_PORT}\z/, + ) + + class Permissions # :nodoc: + def initialize(hosts) + @hosts = sanitize_hosts(hosts) + end + + def empty? + @hosts.empty? + end + + def allows?(host) + @hosts.any? do |allowed| + if allowed.is_a?(IPAddr) + begin + allowed === extract_hostname(host) + rescue + # IPAddr#=== raises an error if you give it a hostname instead of IP. Treat + # similar errors as blocked access. + false + end + else + allowed === host + end + end + end + + private + def sanitize_hosts(hosts) + Array(hosts).map do |host| + case host + when Regexp then sanitize_regexp(host) + when String then sanitize_string(host) + else host + end + end + end + + def sanitize_regexp(host) + /\A#{host}#{PORT_REGEX}?\z/ + end + + def sanitize_string(host) + if host.start_with?(".") + /\A#{SUBDOMAIN_REGEX}?#{Regexp.escape(host[1..-1])}#{PORT_REGEX}?\z/i + else + /\A#{Regexp.escape host}#{PORT_REGEX}?\z/i + end + end + + def extract_hostname(host) + host.slice(VALID_IP_HOSTNAME, "host") || host + end + end + + class DefaultResponseApp # :nodoc: + RESPONSE_STATUS = 403 + + def call(env) + request = Request.new(env) + format = request.xhr? ? "text/plain" : "text/html" + + log_error(request) + response(format, response_body(request)) + end + + private + def response_body(request) + return "" unless request.get_header("action_dispatch.show_detailed_exceptions") + + template = DebugView.new(hosts: request.env["action_dispatch.blocked_hosts"]) + template.render(template: "rescues/blocked_host", layout: "rescues/layout") + end + + def response(format, body) + [RESPONSE_STATUS, + { Rack::CONTENT_TYPE => "#{format}; charset=#{Response.default_charset}", + Rack::CONTENT_LENGTH => body.bytesize.to_s }, + [body]] + end + + def log_error(request) + logger = available_logger(request) + + return unless logger + + logger.error("[#{self.class.name}] Blocked hosts: #{request.env["action_dispatch.blocked_hosts"].join(", ")}") + end + + def available_logger(request) + request.logger || ActionView::Base.logger + end + end + + def initialize(app, hosts, exclude: nil, response_app: nil) + @app = app + @permissions = Permissions.new(hosts) + @exclude = exclude + + @response_app = response_app || DefaultResponseApp.new + end + + def call(env) + return @app.call(env) if @permissions.empty? + + request = Request.new(env) + hosts = blocked_hosts(request) + + if hosts.empty? || excluded?(request) + mark_as_authorized(request) + @app.call(env) + else + env["action_dispatch.blocked_hosts"] = hosts + @response_app.call(env) + end + end + + private + def blocked_hosts(request) + hosts = [] + + origin_host = request.get_header("HTTP_HOST") + hosts << origin_host unless @permissions.allows?(origin_host) + + forwarded_host = request.x_forwarded_host&.split(/,\s?/)&.last + hosts << forwarded_host unless forwarded_host.blank? || @permissions.allows?(forwarded_host) + + hosts + end + + def excluded?(request) + @exclude && @exclude.call(request) + end + + def mark_as_authorized(request) + request.set_header("action_dispatch.authorized_host", request.host) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/public_exceptions.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/public_exceptions.rb new file mode 100644 index 00000000..621d82f1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/public_exceptions.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + # # Action Dispatch PublicExceptions + # + # When called, this middleware renders an error page. By default if an HTML + # response is expected it will render static error pages from the `/public` + # directory. For example when this middleware receives a 500 response it will + # render the template found in `/public/500.html`. If an internationalized + # locale is set, this middleware will attempt to render the template in + # `/public/500..html`. If an internationalized template is not found it + # will fall back on `/public/500.html`. + # + # When a request with a content type other than HTML is made, this middleware + # will attempt to convert error information into the appropriate response type. + class PublicExceptions + attr_accessor :public_path + + def initialize(public_path) + @public_path = public_path + end + + def call(env) + request = ActionDispatch::Request.new(env) + status = request.path_info[1..-1].to_i + begin + content_type = request.formats.first + rescue ActionDispatch::Http::MimeNegotiation::InvalidType + content_type = Mime[:text] + end + body = { status: status, error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) } + + render(status, content_type, body) + end + + private + def render(status, content_type, body) + format = "to_#{content_type.to_sym}" if content_type + if format && body.respond_to?(format) + render_format(status, content_type, body.public_send(format)) + else + render_html(status) + end + end + + def render_format(status, content_type, body) + [status, { Rack::CONTENT_TYPE => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}", + Rack::CONTENT_LENGTH => body.bytesize.to_s }, [body]] + end + + def render_html(status) + path = "#{public_path}/#{status}.#{I18n.locale}.html" + path = "#{public_path}/#{status}.html" unless (found = File.exist?(path)) + + if found || File.exist?(path) + render_format(status, "text/html", File.read(path)) + else + [404, { Constants::X_CASCADE => "pass" }, []] + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/reloader.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/reloader.rb new file mode 100644 index 00000000..f83c4b10 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/reloader.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + # # Action Dispatch Reloader + # + # ActionDispatch::Reloader wraps the request with callbacks provided by + # ActiveSupport::Reloader, intended to assist with code reloading during + # development. + # + # ActionDispatch::Reloader is included in the middleware stack only if reloading + # is enabled, which it is by the default in `development` mode. + class Reloader < Executor + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/remote_ip.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/remote_ip.rb new file mode 100644 index 00000000..d665a7fd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/remote_ip.rb @@ -0,0 +1,198 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "ipaddr" + +module ActionDispatch + # # Action Dispatch RemoteIp + # + # This middleware calculates the IP address of the remote client that is making + # the request. It does this by checking various headers that could contain the + # address, and then picking the last-set address that is not on the list of + # trusted IPs. This follows the precedent set by e.g. [the Tomcat + # server](https://issues.apache.org/bugzilla/show_bug.cgi?id=50453). A more + # detailed explanation of the algorithm is given at GetIp#calculate_ip. + # + # Some Rack servers concatenate repeated headers, like [HTTP RFC + # 2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2) requires. + # Some Rack servers simply drop preceding headers, and only report the value + # that was [given in the last + # header](https://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers). + # If you are behind multiple proxy servers (like NGINX to HAProxy to + # Unicorn) then you should test your Rack server to make sure your data is good. + # + # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING. This + # middleware assumes that there is at least one proxy sitting around and setting + # headers with the client's remote IP address. If you don't use a proxy, because + # you are hosted on e.g. Heroku without SSL, any client can claim to have any IP + # address by setting the `X-Forwarded-For` header. If you care about that, then + # you need to explicitly drop or ignore those headers sometime before this + # middleware runs. Alternatively, remove this middleware to avoid inadvertently + # relying on it. + class RemoteIp + class IpSpoofAttackError < StandardError; end + + # The default trusted IPs list simply includes IP addresses that are guaranteed + # by the IP specification to be private addresses. Those will not be the + # ultimate client IP in production, and so are discarded. See + # https://en.wikipedia.org/wiki/Private_network for details. + TRUSTED_PROXIES = [ + "127.0.0.0/8", # localhost IPv4 range, per RFC-3330 + "::1", # localhost IPv6 + "fc00::/7", # private IPv6 range fc00::/7 + "10.0.0.0/8", # private IPv4 range 10.x.x.x + "172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255 + "192.168.0.0/16", # private IPv4 range 192.168.x.x + ].map { |proxy| IPAddr.new(proxy) } + + attr_reader :check_ip, :proxies + + # Create a new `RemoteIp` middleware instance. + # + # The `ip_spoofing_check` option is on by default. When on, an exception is + # raised if it looks like the client is trying to lie about its own IP address. + # It makes sense to turn off this check on sites aimed at non-IP clients (like + # WAP devices), or behind proxies that set headers in an incorrect or confusing + # way (like AWS ELB). + # + # The `custom_proxies` argument can take an enumerable which will be used + # instead of `TRUSTED_PROXIES`. Any proxy setup will put the value you want in + # the middle (or at the beginning) of the `X-Forwarded-For` list, with your + # proxy servers after it. If your proxies aren't removed, pass them in via the + # `custom_proxies` parameter. That way, the middleware will ignore those IP + # addresses, and return the one that you want. + def initialize(app, ip_spoofing_check = true, custom_proxies = nil) + @app = app + @check_ip = ip_spoofing_check + @proxies = if custom_proxies.blank? + TRUSTED_PROXIES + elsif custom_proxies.respond_to?(:any?) + custom_proxies + else + raise(ArgumentError, <<~EOM) + Setting config.action_dispatch.trusted_proxies to a single value isn't + supported. Please set this to an enumerable instead. For + example, instead of: + + config.action_dispatch.trusted_proxies = IPAddr.new("10.0.0.0/8") + + Wrap the value in an Array: + + config.action_dispatch.trusted_proxies = [IPAddr.new("10.0.0.0/8")] + + Note that passing an enumerable will *replace* the default set of trusted proxies. + EOM + end + end + + # Since the IP address may not be needed, we store the object here without + # calculating the IP to keep from slowing down the majority of requests. For + # those requests that do need to know the IP, the GetIp#calculate_ip method will + # calculate the memoized client IP address. + def call(env) + req = ActionDispatch::Request.new env + req.remote_ip = GetIp.new(req, check_ip, proxies) + @app.call(req.env) + end + + # The GetIp class exists as a way to defer processing of the request data into + # an actual IP address. If the ActionDispatch::Request#remote_ip method is + # called, this class will calculate the value and then memoize it. + class GetIp + def initialize(req, check_ip, proxies) + @req = req + @check_ip = check_ip + @proxies = proxies + end + + # Sort through the various IP address headers, looking for the IP most likely to + # be the address of the actual remote client making this request. + # + # REMOTE_ADDR will be correct if the request is made directly against the Ruby + # process, on e.g. Heroku. When the request is proxied by another server like + # HAProxy or NGINX, the IP address that made the original request will be put in + # an `X-Forwarded-For` header. If there are multiple proxies, that header may + # contain a list of IPs. Other proxy services set the `Client-Ip` header + # instead, so we check that too. + # + # As discussed in [this post about Rails IP + # Spoofing](https://web.archive.org/web/20170626095448/https://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/), + # while the first IP in the list is likely to be the "originating" IP, it + # could also have been set by the client maliciously. + # + # In order to find the first address that is (probably) accurate, we take the + # list of IPs, remove known and trusted proxies, and then take the last address + # left, which was presumably set by one of those proxies. + def calculate_ip + # Set by the Rack web server, this is a single value. + remote_addr = ips_from(@req.remote_addr).last + + # Could be a CSV list and/or repeated headers that were concatenated. + client_ips = ips_from(@req.client_ip).reverse! + forwarded_ips = ips_from(@req.x_forwarded_for).reverse! + + # `Client-Ip` and `X-Forwarded-For` should not, generally, both be set. If they + # are both set, it means that either: + # + # 1) This request passed through two proxies with incompatible IP header + # conventions. + # + # 2) The client passed one of `Client-Ip` or `X-Forwarded-For` + # (whichever the proxy servers weren't using) themselves. + # + # Either way, there is no way for us to determine which header is the right one + # after the fact. Since we have no idea, if we are concerned about IP spoofing + # we need to give up and explode. (If you're not concerned about IP spoofing you + # can turn the `ip_spoofing_check` option off.) + should_check_ip = @check_ip && client_ips.last && forwarded_ips.last + if should_check_ip && !forwarded_ips.include?(client_ips.last) + # We don't know which came from the proxy, and which from the user + raise IpSpoofAttackError, "IP spoofing attack?! " \ + "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " \ + "HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}" + end + + # We assume these things about the IP headers: + # + # - X-Forwarded-For will be a list of IPs, one per proxy, or blank + # - Client-Ip is propagated from the outermost proxy, or is blank + # - REMOTE_ADDR will be the IP that made the request to Rack + ips = forwarded_ips + client_ips + ips.compact! + + # If every single IP option is in the trusted list, return the IP that's + # furthest away + filter_proxies(ips + [remote_addr]).first || ips.last || remote_addr + end + + # Memoizes the value returned by #calculate_ip and returns it for + # ActionDispatch::Request to use. + def to_s + @ip ||= calculate_ip + end + + private + def ips_from(header) # :doc: + return [] unless header + # Split the comma-separated list into an array of strings. + ips = header.strip.split(/[,\s]+/) + ips.select! do |ip| + # Only return IPs that are valid according to the IPAddr#new method. + range = IPAddr.new(ip).to_range + # We want to make sure nobody is sneaking a netmask in. + range.begin == range.end + rescue ArgumentError + nil + end + ips + end + + def filter_proxies(ips) # :doc: + ips.reject do |ip| + @proxies.any? { |proxy| proxy === ip } + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/request_id.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/request_id.rb new file mode 100644 index 00000000..7d2e3078 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/request_id.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "securerandom" +require "active_support/core_ext/string/access" + +module ActionDispatch + # # Action Dispatch RequestId + # + # Makes a unique request id available to the `action_dispatch.request_id` env + # variable (which is then accessible through ActionDispatch::Request#request_id + # or the alias ActionDispatch::Request#uuid) and sends the same id to the client + # via the `X-Request-Id` header. + # + # The unique request id is either based on the `X-Request-Id` header in the + # request, which would typically be generated by a firewall, load balancer, or + # the web server, or, if this header is not available, a random uuid. If the + # header is accepted from the outside world, we sanitize it to a max of 255 + # chars and alphanumeric and dashes only. + # + # The unique request id can be used to trace a request end-to-end and would + # typically end up being part of log files from multiple pieces of the stack. + class RequestId + def initialize(app, header:) + @app = app + @header = header + @env_header = "HTTP_#{header.upcase.tr("-", "_")}" + end + + def call(env) + req = ActionDispatch::Request.new env + req.request_id = make_request_id(req.get_header(@env_header)) + @app.call(env).tap { |_status, headers, _body| headers[@header] = req.request_id } + end + + private + def make_request_id(request_id) + if request_id.presence + request_id.gsub(/[^\w\-@]/, "").first(255) + else + internal_request_id + end + end + + def internal_request_id + SecureRandom.uuid + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/server_timing.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/server_timing.rb new file mode 100644 index 00000000..2ab43908 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/server_timing.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/notifications" + +module ActionDispatch + class ServerTiming + class Subscriber # :nodoc: + include Singleton + KEY = :action_dispatch_server_timing_events + + def initialize + @mutex = Mutex.new + end + + def call(event) + if events = ActiveSupport::IsolatedExecutionState[KEY] + events << event + end + end + + def collect_events + events = [] + ActiveSupport::IsolatedExecutionState[KEY] = events + yield + events + ensure + ActiveSupport::IsolatedExecutionState.delete(KEY) + end + + def ensure_subscribed + @mutex.synchronize do + # Subscribe to all events, except those beginning with "!" Ideally we would be + # more selective of what is being measured + @subscriber ||= ActiveSupport::Notifications.subscribe(/\A[^!]/, self) + end + end + + def unsubscribe + @mutex.synchronize do + ActiveSupport::Notifications.unsubscribe @subscriber + @subscriber = nil + end + end + end + + def self.unsubscribe # :nodoc: + Subscriber.instance.unsubscribe + end + + def initialize(app) + @app = app + @subscriber = Subscriber.instance + @subscriber.ensure_subscribed + end + + def call(env) + response = nil + events = @subscriber.collect_events do + response = @app.call(env) + end + + headers = response[1] + + header_info = events.group_by(&:name).map do |event_name, events_collection| + "%s;dur=%.2f" % [event_name, events_collection.sum(&:duration)] + end + + if headers[ActionDispatch::Constants::SERVER_TIMING].present? + header_info.prepend(headers[ActionDispatch::Constants::SERVER_TIMING]) + end + headers[ActionDispatch::Constants::SERVER_TIMING] = header_info.join(", ") + + response + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/session/abstract_store.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/session/abstract_store.rb new file mode 100644 index 00000000..39614fad --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/session/abstract_store.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "rack/utils" +require "rack/request" +require "rack/session/abstract/id" +require "action_dispatch/middleware/cookies" +require "action_dispatch/request/session" + +module ActionDispatch + module Session + class SessionRestoreError < StandardError # :nodoc: + def initialize + super("Session contains objects whose class definition isn't available.\n" \ + "Remember to require the classes for all objects kept in the session.\n" \ + "(Original exception: #{$!.message} [#{$!.class}])\n") + set_backtrace $!.backtrace + end + end + + module Compatibility + def initialize(app, options = {}) + options[:key] ||= "_session_id" + super + end + + def generate_sid + sid = SecureRandom.hex(16) + sid.encode!(Encoding::UTF_8) + sid + end + + private + def initialize_sid # :doc: + @default_options.delete(:sidbits) + @default_options.delete(:secure_random) + end + + def make_request(env) + ActionDispatch::Request.new env + end + end + + module StaleSessionCheck + def load_session(env) + stale_session_check! { super } + end + + def extract_session_id(env) + stale_session_check! { super } + end + + def stale_session_check! + yield + rescue ArgumentError => argument_error + if argument_error.message =~ %r{undefined class/module ([\w:]*\w)} + begin + # Note that the regexp does not allow $1 to end with a ':'. + $1.constantize + rescue LoadError, NameError + raise ActionDispatch::Session::SessionRestoreError + end + retry + else + raise + end + end + end + + module SessionObject # :nodoc: + def commit_session(req, res) + req.commit_csrf_token + super(req, res) + end + + def prepare_session(req) + Request::Session.create(self, req, @default_options) + end + + def loaded_session?(session) + !session.is_a?(Request::Session) || session.loaded? + end + end + + class AbstractStore < Rack::Session::Abstract::Persisted + include Compatibility + include StaleSessionCheck + include SessionObject + + private + def set_cookie(request, response, cookie) + request.cookie_jar[key] = cookie + end + end + + class AbstractSecureStore < Rack::Session::Abstract::PersistedSecure + include Compatibility + include StaleSessionCheck + include SessionObject + + def generate_sid + Rack::Session::SessionId.new(super) + end + + private + def set_cookie(request, response, cookie) + request.cookie_jar[key] = cookie + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/session/cache_store.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/session/cache_store.rb new file mode 100644 index 00000000..ecf94da9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/session/cache_store.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "action_dispatch/middleware/session/abstract_store" + +module ActionDispatch + module Session + # # Action Dispatch Session CacheStore + # + # A session store that uses an ActiveSupport::Cache::Store to store the + # sessions. This store is most useful if you don't store critical data in your + # sessions and you don't need them to live for extended periods of time. + # + # #### Options + # * `cache` - The cache to use. If it is not specified, `Rails.cache` + # will be used. + # * `expire_after` - The length of time a session will be stored before + # automatically expiring. By default, the `:expires_in` option of the cache + # is used. + # + class CacheStore < AbstractSecureStore + def initialize(app, options = {}) + @cache = options[:cache] || Rails.cache + options[:expire_after] ||= @cache.options[:expires_in] + super + end + + # Get a session from the cache. + def find_session(env, sid) + unless sid && (session = get_session_with_fallback(sid)) + sid, session = generate_sid, {} + end + [sid, session] + end + + # Set a session in the cache. + def write_session(env, sid, session, options) + key = cache_key(sid.private_id) + if session + @cache.write(key, session, expires_in: options[:expire_after]) + else + @cache.delete(key) + end + sid + end + + # Remove a session from the cache. + def delete_session(env, sid, options) + @cache.delete(cache_key(sid.private_id)) + @cache.delete(cache_key(sid.public_id)) + generate_sid + end + + private + # Turn the session id into a cache key. + def cache_key(id) + "_session_id:#{id}" + end + + def get_session_with_fallback(sid) + @cache.read(cache_key(sid.private_id)) || @cache.read(cache_key(sid.public_id)) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/session/cookie_store.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/session/cookie_store.rb new file mode 100644 index 00000000..c6ee1268 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/session/cookie_store.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/core_ext/hash/keys" +require "action_dispatch/middleware/session/abstract_store" +require "rack/session/cookie" + +module ActionDispatch + module Session + # # Action Dispatch Session CookieStore + # + # This cookie-based session store is the Rails default. It is dramatically + # faster than the alternatives. + # + # Sessions typically contain at most a user ID and flash message; both fit + # within the 4096 bytes cookie size limit. A `CookieOverflow` exception is + # raised if you attempt to store more than 4096 bytes of data. + # + # The cookie jar used for storage is automatically configured to be the best + # possible option given your application's configuration. + # + # Your cookies will be encrypted using your application's `secret_key_base`. + # This goes a step further than signed cookies in that encrypted cookies cannot + # be altered or read by users. This is the default starting in Rails 4. + # + # Configure your session store in an initializer: + # + # Rails.application.config.session_store :cookie_store, key: '_your_app_session' + # + # In the development and test environments your application's `secret_key_base` + # is generated by Rails and stored in a temporary file in + # `tmp/local_secret.txt`. In all other environments, it is stored encrypted in + # the `config/credentials.yml.enc` file. + # + # If your application was not updated to Rails 5.2 defaults, the + # `secret_key_base` will be found in the old `config/secrets.yml` file. + # + # Note that changing your `secret_key_base` will invalidate all existing + # session. Additionally, you should take care to make sure you are not relying + # on the ability to decode signed cookies generated by your app in external + # applications or JavaScript before changing it. + # + # Because CookieStore extends `Rack::Session::Abstract::Persisted`, many of the + # options described there can be used to customize the session cookie that is + # generated. For example: + # + # Rails.application.config.session_store :cookie_store, expire_after: 14.days + # + # would set the session cookie to expire automatically 14 days after creation. + # Other useful options include `:key`, `:secure`, `:httponly`, and `:same_site`. + class CookieStore < AbstractSecureStore + class SessionId < DelegateClass(Rack::Session::SessionId) + attr_reader :cookie_value + + def initialize(session_id, cookie_value = {}) + super(session_id) + @cookie_value = cookie_value + end + end + + DEFAULT_SAME_SITE = proc { |request| request.cookies_same_site_protection } # :nodoc: + + def initialize(app, options = {}) + options[:cookie_only] = true + options[:same_site] = DEFAULT_SAME_SITE if !options.key?(:same_site) + super + end + + def delete_session(req, session_id, options) + new_sid = generate_sid unless options[:drop] + # Reset hash and Assign the new session id + req.set_header("action_dispatch.request.unsigned_session_cookie", new_sid ? { "session_id" => new_sid.public_id } : {}) + new_sid + end + + def load_session(req) + stale_session_check! do + data = unpacked_cookie_data(req) + data = persistent_session_id!(data) + [Rack::Session::SessionId.new(data["session_id"]), data] + end + end + + private + def extract_session_id(req) + stale_session_check! do + sid = unpacked_cookie_data(req)["session_id"] + sid && Rack::Session::SessionId.new(sid) + end + end + + def unpacked_cookie_data(req) + req.fetch_header("action_dispatch.request.unsigned_session_cookie") do |k| + v = stale_session_check! do + if data = get_cookie(req) + data.stringify_keys! + end + data || {} + end + req.set_header k, v + end + end + + def persistent_session_id!(data, sid = nil) + data ||= {} + data["session_id"] ||= sid || generate_sid.public_id + data + end + + def write_session(req, sid, session_data, options) + session_data["session_id"] = sid.public_id + SessionId.new(sid, session_data) + end + + def set_cookie(request, session_id, cookie) + cookie_jar(request)[@key] = cookie + end + + def get_cookie(req) + cookie_jar(req)[@key] + end + + def cookie_jar(request) + request.cookie_jar.signed_or_encrypted + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/session/mem_cache_store.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/session/mem_cache_store.rb new file mode 100644 index 00000000..2c867995 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/session/mem_cache_store.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "action_dispatch/middleware/session/abstract_store" +begin + require "rack/session/dalli" +rescue LoadError => e + warn "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install" + raise e +end + +module ActionDispatch + module Session + # # Action Dispatch Session MemCacheStore + # + # A session store that uses MemCache to implement storage. + # + # #### Options + # * `expire_after` - The length of time a session will be stored before + # automatically expiring. + # + class MemCacheStore < Rack::Session::Dalli + include Compatibility + include StaleSessionCheck + include SessionObject + + def initialize(app, options = {}) + options[:expire_after] ||= options[:expires] + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/show_exceptions.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/show_exceptions.rb new file mode 100644 index 00000000..d07f4003 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/show_exceptions.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "action_dispatch/middleware/exception_wrapper" + +module ActionDispatch + # # Action Dispatch ShowExceptions + # + # This middleware rescues any exception returned by the application and calls an + # exceptions app that will wrap it in a format for the end user. + # + # The exceptions app should be passed as a parameter on initialization of + # `ShowExceptions`. Every time there is an exception, `ShowExceptions` will + # store the exception in `env["action_dispatch.exception"]`, rewrite the + # `PATH_INFO` to the exception status code, and call the Rack app. + # + # In Rails applications, the exceptions app can be configured with + # `config.exceptions_app`, which defaults to ActionDispatch::PublicExceptions. + # + # If the application returns a response with the `X-Cascade` header set to + # `"pass"`, this middleware will send an empty response as a result with the + # correct status code. If any exception happens inside the exceptions app, this + # middleware catches the exceptions and returns a failsafe response. + class ShowExceptions + def initialize(app, exceptions_app) + @app = app + @exceptions_app = exceptions_app + end + + def call(env) + @app.call(env) + rescue Exception => exception + request = ActionDispatch::Request.new env + backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner") + wrapper = ExceptionWrapper.new(backtrace_cleaner, exception) + request.set_header "action_dispatch.exception", wrapper.unwrapped_exception + request.set_header "action_dispatch.report_exception", !wrapper.rescue_response? + + if wrapper.show?(request) + render_exception(request.dup, wrapper) + else + raise exception + end + end + + private + def render_exception(request, wrapper) + status = wrapper.status_code + request.set_header "action_dispatch.original_path", request.path_info + request.set_header "action_dispatch.original_request_method", request.raw_request_method + fallback_to_html_format_if_invalid_mime_type(request) + request.path_info = "/#{status}" + request.request_method = "GET" + response = @exceptions_app.call(request.env) + response[1][Constants::X_CASCADE] == "pass" ? pass_response(status) : response + rescue Exception => failsafe_error + $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}" + + [500, { Rack::CONTENT_TYPE => "text/plain; charset=utf-8" }, + ["500 Internal Server Error\n" \ + "If you are the administrator of this website, then please read this web " \ + "application's log file and/or the web server's log file to find out what " \ + "went wrong."]] + end + + def fallback_to_html_format_if_invalid_mime_type(request) + # If the MIME type for the request is invalid then the @exceptions_app may not + # be able to handle it. To make it easier to handle, we switch to HTML. + begin + request.content_mime_type + rescue ActionDispatch::Http::MimeNegotiation::InvalidType + request.set_header "CONTENT_TYPE", "text/html" + end + + begin + request.formats + rescue ActionDispatch::Http::MimeNegotiation::InvalidType + request.set_header "HTTP_ACCEPT", "text/html" + end + end + + def pass_response(status) + [status, { Rack::CONTENT_TYPE => "text/html; charset=#{Response.default_charset}", + Rack::CONTENT_LENGTH => "0" }, []] + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/ssl.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/ssl.rb new file mode 100644 index 00000000..6a1146ad --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/ssl.rb @@ -0,0 +1,180 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + # # Action Dispatch SSL + # + # This middleware is added to the stack when `config.force_ssl = true`, and is + # passed the options set in `config.ssl_options`. It does three jobs to enforce + # secure HTTP requests: + # + # 1. **TLS redirect**: Permanently redirects `http://` requests to `https://` + # with the same URL host, path, etc. Enabled by default. Set + # `config.ssl_options` to modify the destination URL: + # + # config.ssl_options = { redirect: { host: "secure.widgets.com", port: 8080 }` + # + # Or set `redirect: false` to disable redirection. + # + # Requests can opt-out of redirection with `exclude`: + # + # config.ssl_options = { redirect: { exclude: -> request { request.path == "/up" } } } + # + # Cookies will not be flagged as secure for excluded requests. + # + # When proxying through a load balancer that terminates SSL, the forwarded + # request will appear as though it's HTTP instead of HTTPS to the application. + # This makes redirects and cookie security target HTTP instead of HTTPS. + # To make the server assume that the proxy already terminated SSL, and + # that the request really is HTTPS, set `config.assume_ssl` to `true`: + # + # config.assume_ssl = true + # + # 2. **Secure cookies**: Sets the `secure` flag on cookies to tell browsers + # they must not be sent along with `http://` requests. Enabled by default. + # Set `config.ssl_options` with `secure_cookies: false` to disable this + # feature. + # + # 3. **HTTP Strict Transport Security (HSTS)**: Tells the browser to remember + # this site as TLS-only and automatically redirect non-TLS requests. Enabled + # by default. Configure `config.ssl_options` with `hsts: false` to disable. + # + # Set `config.ssl_options` with `hsts: { ... }` to configure HSTS: + # + # * `expires`: How long, in seconds, these settings will stick. The + # minimum required to qualify for browser preload lists is 1 year. + # Defaults to 2 years (recommended). + # + # * `subdomains`: Set to `true` to tell the browser to apply these + # settings to all subdomains. This protects your cookies from + # interception by a vulnerable site on a subdomain. Defaults to `true`. + # + # * `preload`: Advertise that this site may be included in browsers' + # preloaded HSTS lists. HSTS protects your site on every visit *except + # the first visit* since it hasn't seen your HSTS header yet. To close + # this gap, browser vendors include a baked-in list of HSTS-enabled + # sites. Go to https://hstspreload.org to submit your site for + # inclusion. Defaults to `false`. + # + # + # To turn off HSTS, omitting the header is not enough. Browsers will + # remember the original HSTS directive until it expires. Instead, use the + # header to tell browsers to expire HSTS immediately. Setting `hsts: false` + # is a shortcut for `hsts: { expires: 0 }`. + # + class SSL + # :stopdoc: Default to 2 years as recommended on hstspreload.org. + HSTS_EXPIRES_IN = 63072000 + + PERMANENT_REDIRECT_REQUEST_METHODS = %w[GET HEAD] # :nodoc: + + def self.default_hsts_options + { expires: HSTS_EXPIRES_IN, subdomains: true, preload: false } + end + + def initialize(app, redirect: {}, hsts: {}, secure_cookies: true, ssl_default_redirect_status: nil) + @app = app + + @redirect = redirect + + @exclude = @redirect && @redirect[:exclude] || proc { !@redirect } + @secure_cookies = secure_cookies + + @hsts_header = build_hsts_header(normalize_hsts_options(hsts)) + @ssl_default_redirect_status = ssl_default_redirect_status + end + + def call(env) + request = Request.new env + + if request.ssl? + @app.call(env).tap do |status, headers, body| + set_hsts_header! headers + flag_cookies_as_secure! headers if @secure_cookies && !@exclude.call(request) + end + else + return redirect_to_https request unless @exclude.call(request) + @app.call(env) + end + end + + private + def set_hsts_header!(headers) + headers[Constants::STRICT_TRANSPORT_SECURITY] ||= @hsts_header + end + + def normalize_hsts_options(options) + case options + # Explicitly disabling HSTS clears the existing setting from browsers by setting + # expiry to 0. + when false + self.class.default_hsts_options.merge(expires: 0) + # Default to enabled, with default options. + when nil, true + self.class.default_hsts_options + else + self.class.default_hsts_options.merge(options) + end + end + + # https://tools.ietf.org/html/rfc6797#section-6.1 + def build_hsts_header(hsts) + value = +"max-age=#{hsts[:expires].to_i}" + value << "; includeSubDomains" if hsts[:subdomains] + value << "; preload" if hsts[:preload] + value + end + + def flag_cookies_as_secure!(headers) + cookies = headers[Rack::SET_COOKIE] + return unless cookies + + if Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3") + cookies = cookies.split("\n") + headers[Rack::SET_COOKIE] = cookies.map { |cookie| + if !/;\s*secure\s*(;|$)/i.match?(cookie) + "#{cookie}; secure" + else + cookie + end + }.join("\n") + else + headers[Rack::SET_COOKIE] = Array(cookies).map do |cookie| + if !/;\s*secure\s*(;|$)/i.match?(cookie) + "#{cookie}; secure" + else + cookie + end + end + end + end + + def redirect_to_https(request) + [ @redirect.fetch(:status, redirection_status(request)), + { Rack::CONTENT_TYPE => "text/html; charset=utf-8", + Constants::LOCATION => https_location_for(request) }, + (@redirect[:body] || []) ] + end + + def redirection_status(request) + if PERMANENT_REDIRECT_REQUEST_METHODS.include?(request.raw_request_method) + 301 # Issue a permanent redirect via a GET request. + elsif @ssl_default_redirect_status + @ssl_default_redirect_status + else + 307 # Issue a fresh request redirect to preserve the HTTP method. + end + end + + def https_location_for(request) + host = @redirect[:host] || request.host + port = @redirect[:port] || request.port + + location = +"https://#{host}" + location << ":#{port}" if port != 80 && port != 443 + location << request.fullpath + location + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/stack.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/stack.rb new file mode 100644 index 00000000..fa4a470a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/stack.rb @@ -0,0 +1,194 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/inflector/methods" +require "active_support/dependencies" + +module ActionDispatch + # # Action Dispatch MiddlewareStack + # + # Read more about [Rails middleware + # stack](https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack) + # in the guides. + class MiddlewareStack + class Middleware + attr_reader :args, :block, :klass + + def initialize(klass, args, block) + @klass = klass + @args = args + @block = block + end + + def name; klass.name; end + + def ==(middleware) + case middleware + when Middleware + klass == middleware.klass + when Module + klass == middleware + end + end + + def inspect + if klass.is_a?(Module) + klass.to_s + else + klass.class.to_s + end + end + + def build(app) + klass.new(app, *args, &block) + end + + def build_instrumented(app) + InstrumentationProxy.new(build(app), inspect) + end + end + + # This class is used to instrument the execution of a single middleware. It + # proxies the `call` method transparently and instruments the method call. + class InstrumentationProxy + EVENT_NAME = "process_middleware.action_dispatch" + + def initialize(middleware, class_name) + @middleware = middleware + + @payload = { + middleware: class_name, + } + end + + def call(env) + ActiveSupport::Notifications.instrument(EVENT_NAME, @payload) do + @middleware.call(env) + end + end + end + + include Enumerable + + attr_accessor :middlewares + + def initialize(*args) + @middlewares = [] + yield(self) if block_given? + end + + def each(&block) + @middlewares.each(&block) + end + + def size + middlewares.size + end + + def last + middlewares.last + end + + def [](i) + middlewares[i] + end + + def unshift(klass, *args, &block) + middlewares.unshift(build_middleware(klass, args, block)) + end + ruby2_keywords(:unshift) + + def initialize_copy(other) + self.middlewares = other.middlewares.dup + end + + def insert(index, klass, *args, &block) + index = assert_index(index, :before) + middlewares.insert(index, build_middleware(klass, args, block)) + end + ruby2_keywords(:insert) + + alias_method :insert_before, :insert + + def insert_after(index, *args, &block) + index = assert_index(index, :after) + insert(index + 1, *args, &block) + end + ruby2_keywords(:insert_after) + + def swap(target, *args, &block) + index = assert_index(target, :before) + insert(index, *args, &block) + middlewares.delete_at(index + 1) + end + ruby2_keywords(:swap) + + # Deletes a middleware from the middleware stack. + # + # Returns the array of middlewares not including the deleted item, or returns + # nil if the target is not found. + def delete(target) + middlewares.reject! { |m| m.name == target.name } + end + + # Deletes a middleware from the middleware stack. + # + # Returns the array of middlewares not including the deleted item, or raises + # `RuntimeError` if the target is not found. + def delete!(target) + delete(target) || (raise "No such middleware to remove: #{target.inspect}") + end + + def move(target, source) + source_index = assert_index(source, :before) + source_middleware = middlewares.delete_at(source_index) + + target_index = assert_index(target, :before) + middlewares.insert(target_index, source_middleware) + end + + alias_method :move_before, :move + + def move_after(target, source) + source_index = assert_index(source, :after) + source_middleware = middlewares.delete_at(source_index) + + target_index = assert_index(target, :after) + middlewares.insert(target_index + 1, source_middleware) + end + + def use(klass, *args, &block) + middlewares.push(build_middleware(klass, args, block)) + end + ruby2_keywords(:use) + + def build(app = nil, &block) + instrumenting = ActiveSupport::Notifications.notifier.listening?(InstrumentationProxy::EVENT_NAME) + middlewares.freeze.reverse.inject(app || block) do |a, e| + if instrumenting + e.build_instrumented(a) + else + e.build(a) + end + end + end + + private + def assert_index(index, where) + i = index.is_a?(Integer) ? index : index_of(index) + raise "No such middleware to insert #{where}: #{index.inspect}" unless i + i + end + + def build_middleware(klass, args, block) + Middleware.new(klass, args, block) + end + + def index_of(klass) + middlewares.index do |m| + m.name == klass.name + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/static.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/static.rb new file mode 100644 index 00000000..d2bf29c1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/static.rb @@ -0,0 +1,192 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "rack/utils" + +module ActionDispatch + # # Action Dispatch Static + # + # This middleware serves static files from disk, if available. If no file is + # found, it hands off to the main app. + # + # In Rails apps, this middleware is configured to serve assets from the + # `public/` directory. + # + # Only GET and HEAD requests are served. POST and other HTTP methods are handed + # off to the main app. + # + # Only files in the root directory are served; path traversal is denied. + class Static + def initialize(app, path, index: "index", headers: {}) + @app = app + @file_handler = FileHandler.new(path, index: index, headers: headers) + end + + def call(env) + @file_handler.attempt(env) || @app.call(env) + end + end + + # # Action Dispatch FileHandler + # + # This endpoint serves static files from disk using `Rack::Files`. + # + # URL paths are matched with static files according to expected conventions: + # `path`, `path`.html, `path`/index.html. + # + # Precompressed versions of these files are checked first. Brotli (.br) and gzip + # (.gz) files are supported. If `path`.br exists, this endpoint returns that + # file with a `content-encoding: br` header. + # + # If no matching file is found, this endpoint responds `404 Not Found`. + # + # Pass the `root` directory to search for matching files, an optional `index: + # "index"` to change the default `path`/index.html, and optional additional + # response headers. + class FileHandler + # `Accept-Encoding` value -> file extension + PRECOMPRESSED = { + "br" => ".br", + "gzip" => ".gz", + "identity" => nil + } + + def initialize(root, index: "index", headers: {}, precompressed: %i[ br gzip ], compressible_content_types: /\A(?:text\/|application\/javascript|image\/svg\+xml)/) + @root = root.chomp("/").b + @index = index + + @precompressed = Array(precompressed).map(&:to_s) | %w[ identity ] + @compressible_content_types = compressible_content_types + + @file_server = ::Rack::Files.new(@root, headers) + end + + def call(env) + attempt(env) || @file_server.call(env) + end + + def attempt(env) + request = Rack::Request.new env + + if request.get? || request.head? + if found = find_file(request.path_info, accept_encoding: request.accept_encoding) + serve request, *found + end + end + end + + private + def serve(request, filepath, content_headers) + original, request.path_info = + request.path_info, ::Rack::Utils.escape_path(filepath).b + + @file_server.call(request.env).tap do |status, headers, body| + # Omit content-encoding/type/etc headers for 304 Not Modified + if status != 304 + headers.update(content_headers) + end + end + ensure + request.path_info = original + end + + # Match a URI path to a static file to be served. + # + # Used by the `Static` class to negotiate a servable file in the `public/` + # directory (see Static#call). + # + # Checks for `path`, `path`.html, and `path`/index.html files, in that order, + # including .br and .gzip compressed extensions. + # + # If a matching file is found, the path and necessary response headers + # (Content-Type, Content-Encoding) are returned. + def find_file(path_info, accept_encoding:) + each_candidate_filepath(path_info) do |filepath, content_type| + if response = try_files(filepath, content_type, accept_encoding: accept_encoding) + return response + end + end + end + + def try_files(filepath, content_type, accept_encoding:) + headers = { Rack::CONTENT_TYPE => content_type } + + if compressible? content_type + try_precompressed_files filepath, headers, accept_encoding: accept_encoding + elsif file_readable? filepath + [ filepath, headers ] + end + end + + def try_precompressed_files(filepath, headers, accept_encoding:) + each_precompressed_filepath(filepath) do |content_encoding, precompressed_filepath| + if file_readable? precompressed_filepath + # Identity encoding is default, so we skip Accept-Encoding negotiation and + # needn't set Content-Encoding. + # + # Vary header is expected when we've found other available encodings that + # Accept-Encoding ruled out. + if content_encoding == "identity" + return precompressed_filepath, headers + else + headers[ActionDispatch::Constants::VARY] = "accept-encoding" + + if accept_encoding.any? { |enc, _| /\b#{content_encoding}\b/i.match?(enc) } + headers[ActionDispatch::Constants::CONTENT_ENCODING] = content_encoding + return precompressed_filepath, headers + end + end + end + end + end + + def file_readable?(path) + file_path = File.join(@root, path.b) + File.file?(file_path) && File.readable?(file_path) + end + + def compressible?(content_type) + @compressible_content_types.match?(content_type) + end + + def each_precompressed_filepath(filepath) + @precompressed.each do |content_encoding| + precompressed_ext = PRECOMPRESSED.fetch(content_encoding) + yield content_encoding, "#{filepath}#{precompressed_ext}" + end + + nil + end + + def each_candidate_filepath(path_info) + return unless path = clean_path(path_info) + + ext = ::File.extname(path) + content_type = ::Rack::Mime.mime_type(ext, nil) + yield path, content_type || "text/plain" + + # Tack on .html and /index.html only for paths that don't have an explicit, + # resolvable file extension. No need to check for foo.js.html and + # foo.js/index.html. + unless content_type + default_ext = ::ActionController::Base.default_static_extension + if ext != default_ext + default_content_type = ::Rack::Mime.mime_type(default_ext, "text/plain") + + yield "#{path}#{default_ext}", default_content_type + yield "#{path}/#{@index}#{default_ext}", default_content_type + end + end + + nil + end + + def clean_path(path_info) + path = ::Rack::Utils.unescape_path path_info.chomp("/") + if ::Rack::Utils.valid_path? path + ::Rack::Utils.clean_path_info path + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb new file mode 100644 index 00000000..81d41d00 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb @@ -0,0 +1,13 @@ +<% actions = exception_wrapper.actions %> + +<% if actions.any? %> +
+ <% actions.each do |action, _| %> + <%= button_to action, ActionDispatch::ActionableExceptions.endpoint, params: { + error: exception_wrapper.exception_class_name, + action: action, + location: request.path + } %> + <% end %> +
+<% end %> diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb new file mode 100644 index 00000000..e69de29b diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb new file mode 100644 index 00000000..fff22977 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb @@ -0,0 +1,22 @@ +<% if exception_wrapper.has_corrections? %> +
+ <%= simple_format h(exception_wrapper.original_message), { class: "message" }, wrapper_tag: "div" %> +
+ <% + # The 'did_you_mean' gem can raise exceptions when calling #corrections on + # the exception. If it does there are no corrections to show. + corrections = exception_wrapper.corrections rescue [] + %> + <% if corrections.any? %> + Did you mean? +
    + <% corrections.each do |correction| %> +
  • <%= h correction %>
  • + <% end %> +
+ <% end %> +<% else %> +
+ <%= simple_format h(exception_wrapper.message), { class: "message" }, wrapper_tag: "div" %> +
+<% end %> diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb new file mode 100644 index 00000000..d27a193a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb @@ -0,0 +1,17 @@ +

Request

+<% if params_valid? %> +

Parameters:

<%= debug_params(@request.filtered_parameters) %>
+<% end %> + +
+ + +
+ +
+ + +
+ +

Response

+

Headers:

<%= debug_headers(defined?(@response) ? @response.headers : {}) %>
diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb new file mode 100644 index 00000000..ca42a6fa --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb @@ -0,0 +1,23 @@ +<% + clean_params = params_valid? ? @request.filtered_parameters.clone : {} + clean_params.delete("action") + clean_params.delete("controller") + + request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n") + + def debug_hash(object) + object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n") + end unless self.class.method_defined?(:debug_hash) +%> + +Request parameters +<%= request_dump %> + +Session dump +<%= debug_hash @request.session %> + +Env dump +<%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %> + +Response headers +<%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %> diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_source.html.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_source.html.erb new file mode 100644 index 00000000..9f168357 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_source.html.erb @@ -0,0 +1,33 @@ +<% error_index = local_assigns[:error_index] || 0 %> + +<% source_extracts.each_with_index do |source_extract, index| %> + <% if source_extract[:code] %> +
" id="frame-source-<%= error_index %>-<%= index %>"> +
+ Extracted source (around line #<%= source_extract[:line_number] %>): +
+
+ + + + + +
+
+                <% source_extract[:code].each_key do |line_number| %>
+<%= line_number -%>
+                <% end %>
+              
+
+
+<% source_extract[:code].each do |line, source| -%>
+
"><% if source.is_a?(Array) -%><%= source[0] -%><%= source[1] -%><%= source[2] -%> +<% else -%> +<%= source -%> +<% end -%>
<% end -%> +
+
+
+
+ <% end %> +<% end %> diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_source.text.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_source.text.erb new file mode 100644 index 00000000..23a9c7ba --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_source.text.erb @@ -0,0 +1,8 @@ +<% @source_extracts.first(3).each do |source_extract| %> +<% if source_extract[:code] %> +Extracted source (around line #<%= source_extract[:line_number] %>): + +<% source_extract[:code].each do |line, source| -%> +<%= line == source_extract[:line_number] ? "*#{line}" : "##{line}" -%> <%= source -%><% end -%> +<% end %> +<% end %> diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb new file mode 100644 index 00000000..ee0bc10f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb @@ -0,0 +1,62 @@ +<% names = traces.keys %> +<% error_index = local_assigns[:error_index] || 0 %> + +

Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %>

+ +
+ <% names.each do |name| %> + <% + show = "show('#{name.gsub(/\s/, '-')}-#{error_index}');" + hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}-#{error_index}');"} + %> + <%= name %> <%= '|' unless names.last == name %> + <% end %> + + <% traces.each do |name, trace| %> +
" style="display: <%= (name == trace_to_show) ? 'block' : 'none' %>;"> + + <% trace.each do |frame| %> + + <%= frame[:trace] %> + +
+ <% end %> +
+
+ <% end %> + + +
diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb new file mode 100644 index 00000000..c0b53068 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb @@ -0,0 +1,9 @@ +Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %> + +<% @traces.each do |name, trace| %> +<% if trace.any? %> +<%= name %> +<%= trace.map { |t| t[:trace] }.join("\n") %> + +<% end %> +<% end %> diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb new file mode 100644 index 00000000..e4529473 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb @@ -0,0 +1,12 @@ +
+

Blocked hosts: <%= @hosts.join(", ") %>

+
+
+

To allow requests to these hosts, make sure they are valid hostnames (containing only numbers, letters, dashes and dots), then add the following to your environment configuration:

+
+  <% @hosts.each do |host| %>
+    config.hosts << "<%= host %>"
+  <% end %>
+  
+

For more details view: the Host Authorization guide

+
diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb new file mode 100644 index 00000000..bc5b5fe8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb @@ -0,0 +1,9 @@ +Blocked hosts: <%= @hosts.join(", ") %> + +To allow requests to these hosts, make sure they are valid hostnames (containing only numbers, letters, dashes and dots), then add the following to your environment configuration: + +<% @hosts.each do |host| %> + config.hosts << "<%= host %>" +<% end %> + +For more details on host authorization view: https://guides.rubyonrails.org/configuring.html#actiondispatch-hostauthorization diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb new file mode 100644 index 00000000..84cc8ab0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb @@ -0,0 +1,35 @@ +
+

+ <%= @exception_wrapper.exception_class_name %> + <% if params_valid? && @request.parameters['controller'] %> + in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %> + <% end %> +

+
+ +
+ <%= render "rescues/message_and_suggestions", exception: @exception, exception_wrapper: @exception_wrapper %> + <%= render "rescues/actions", exception: @exception, request: @request, exception_wrapper: @exception_wrapper %> + + <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx, error_index: 0 %> + <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show, error_index: 0 %> + + <% if @exception_wrapper.has_cause? %> +

Exception Causes

+ <% end %> + + <% @exception_wrapper.wrapped_causes.each.with_index(1) do |wrapper, index| %> + + + + <% end %> + + <%= render template: "rescues/_request_and_response" %> +
diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb new file mode 100644 index 00000000..52d0483e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb @@ -0,0 +1,9 @@ +<%= @exception_wrapper.exception_class_name %><% + if params_valid? && @request.parameters['controller'] +%> in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %> +<% end %> + +<%= @exception_wrapper.message %> +<%= render template: "rescues/_source" %> +<%= render template: "rescues/_trace" %> +<%= render template: "rescues/_request_and_response" %> diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb new file mode 100644 index 00000000..3743994e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb @@ -0,0 +1,24 @@ +
+

+ <%= @exception.class.to_s %> + <% if @request.parameters['controller'] %> + in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %> + <% end %> +

+
+ +
+

+ <%= h @exception.message %> + <% if defined?(ActiveStorage) && @exception.message.match?(%r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}}) %> +
To resolve this issue run: bin/rails active_storage:install + <% end %> + <% if defined?(ActionMailbox) && @exception.message.match?(%r{#{ActionMailbox::InboundEmail.table_name}}) %> +
To resolve this issue run: bin/rails action_mailbox:install + <% end %> +

+ + <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx %> + <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show %> + <%= render template: "rescues/_request_and_response" %> +
diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb new file mode 100644 index 00000000..d30facd3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb @@ -0,0 +1,16 @@ +<%= @exception.class.to_s %><% + if @request.parameters['controller'] +%> in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %> +<% end %> + +<%= @exception.message %> +<% if defined?(ActiveStorage) && @exception.message.match?(%r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}}) %> +To resolve this issue run: bin/rails active_storage:install +<% end %> +<% if defined?(ActionMailbox) && @exception.message.match?(%r{#{ActionMailbox::InboundEmail.table_name}}) %> +To resolve this issue run: bin/rails action_mailbox:install +<% end %> + +<%= render template: "rescues/_source" %> +<%= render template: "rescues/_trace" %> +<%= render template: "rescues/_request_and_response" %> diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/layout.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/layout.erb new file mode 100644 index 00000000..92fdee59 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/layout.erb @@ -0,0 +1,284 @@ + + + + + + + Action Controller: Exception caught + + + + + + + <%= yield %> + + + diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb new file mode 100644 index 00000000..e5563034 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb @@ -0,0 +1,23 @@ +
+

No view template for interactive request

+
+ +
+

<%= h @exception.message %>

+ +
+

+ NOTE: Rails usually expects a controller action to render a view template with the same name. +

+

+ For example, a <%= @exception.controller %>#<%= @exception.action_name %> action defined in app/controllers/<%= @exception.controller.controller_path %>_controller.rb should have a corresponding view template + in a file named app/views/<%= @exception.controller.controller_path %>/<%= @exception.action_name %>.html.erb. +

+

+ However, if this controller is an API endpoint responding with 204 (No Content), which does not require a view template because it doesn't serve an HTML response, then this error will occur when trying to access it with a browser. In this particular scenario, you can ignore this error. +

+

+ You can find more about view template rendering conventions in the Rails Guides on Layouts and Rendering in Rails. +

+
+
diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb new file mode 100644 index 00000000..fcdbe606 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb @@ -0,0 +1,3 @@ +Missing exact template + +<%= @exception.message %> diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb new file mode 100644 index 00000000..6081dcf3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb @@ -0,0 +1,11 @@ +
+

Template is missing

+
+ +
+

<%= h @exception_wrapper.message %>

+ + <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx %> + <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show %> + <%= render template: "rescues/_request_and_response" %> +
diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb new file mode 100644 index 00000000..ae62d9eb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb @@ -0,0 +1,3 @@ +Template is missing + +<%= @exception.message %> diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb new file mode 100644 index 00000000..25264a41 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb @@ -0,0 +1,32 @@ +
+

Routing Error

+
+
+

<%= h @exception_wrapper.message %>

+ <% unless @exception_wrapper.failures.empty? %> +

+

Failure reasons:

+
    + <% @exception_wrapper.failures.each do |route, reason| %> +
  1. <%= route.inspect.delete('\\') %> failed because <%= reason.downcase %>
  2. + <% end %> +
+

+ <% end %> + + <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show %> + + <% if @routes_inspector %> +

+ Routes +

+ +

+ Routes match in priority from top to bottom +

+ + <%= @routes_inspector.format(ActionDispatch::Routing::HtmlTableFormatter.new(self)) %> + <% end %> + + <%= render template: "rescues/_request_and_response" %> +
diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb new file mode 100644 index 00000000..f6e4dac1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb @@ -0,0 +1,11 @@ +Routing Error + +<%= @exception.message %> +<% unless @exception.failures.empty? %> +Failure reasons: +<% @exception.failures.each do |route, reason| %> + - <%= route.inspect.delete('\\') %> failed because <%= reason.downcase %> +<% end %> +<% end %> + +<%= render template: "rescues/_trace", format: :text %> diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb new file mode 100644 index 00000000..4ee6ad0c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb @@ -0,0 +1,20 @@ +
+

+ <%= @exception_wrapper.exception_name %> in + <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %> +

+
+ +
+

+ Showing <%= @exception_wrapper.file_name %> where line #<%= @exception_wrapper.line_number %> raised: +

+
<%= h @exception_wrapper.message %>
+ + <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx %> + +

<%= @exception_wrapper.sub_template_message %>

+ + <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show %> + <%= render template: "rescues/_request_and_response" %> +
diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb new file mode 100644 index 00000000..78d52acd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb @@ -0,0 +1,7 @@ +<%= @exception.cause.class.to_s %> in <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %> + +Showing <%= @exception.file_name %> where line #<%= @exception.line_number %> raised: +<%= @exception.message %> +<%= @exception.sub_template_message %> +<%= render template: "rescues/_trace", format: :text %> +<%= render template: "rescues/_request_and_response", format: :text %> diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb new file mode 100644 index 00000000..2c2d1a94 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb @@ -0,0 +1,6 @@ +
+

Unknown action

+
+
+ <%= render "rescues/message_and_suggestions", exception: @exception, exception_wrapper: @exception_wrapper %> +
diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb new file mode 100644 index 00000000..e7f0991f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb @@ -0,0 +1,3 @@ +Unknown action + +<%= @exception_wrapper.message %> diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/routes/_route.html.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/routes/_route.html.erb new file mode 100644 index 00000000..bcadd540 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/routes/_route.html.erb @@ -0,0 +1,19 @@ + + + <% if route[:name].present? %> + <%= route[:name] %>_path + <% end %> + + + <%= route[:verb] %> + + + <%= route[:path] %> + + + <%=simple_format route[:reqs] %> + + + <%=simple_format route[:source_location] %> + + diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/routes/_table.html.erb new file mode 100644 index 00000000..8b6b8df2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/middleware/templates/routes/_table.html.erb @@ -0,0 +1,232 @@ +<% content_for :style do %> + h2, p { + padding-left: 30px; + } + + #route_table { + margin: 0; + border-collapse: collapse; + word-wrap:break-word; + table-layout: fixed; + width:100%; + } + + #route_table thead tr { + border-bottom: 2px solid #ddd; + } + + #route_table th { + padding-left: 30px; + text-align: left; + } + + #route_table thead tr.bottom { + border-bottom: none; + } + + #route_table thead tr.bottom th { + padding: 10px 30px; + line-height: 15px; + } + + #route_table #search_container { + padding: 7px 30px; + } + + #route_table thead tr th input#search { + -webkit-appearance: textfield; + width:100%; + } + + #route_table thead th.http-verb { + width: 10%; + } + + #route_table tbody tr { + border-bottom: 1px solid #ddd; + } + + #route_table tbody tr:nth-child(odd) { + background: #f2f2f2; + } + + #route_table tbody.exact_matches, + #route_table tbody.fuzzy_matches { + background-color: LightGoldenRodYellow; + border-bottom: solid 2px SlateGrey; + } + + #route_table tbody.exact_matches tr, + #route_table tbody.fuzzy_matches tr { + background: none; + border-bottom: none; + } + + #route_table td { + padding: 4px 30px; + } + + @media (prefers-color-scheme: dark) { + #route_table tbody tr:nth-child(odd) { + background: #282828; + } + + #route_table tbody.exact_matches tr, + #route_table tbody.fuzzy_matches tr { + background: DarkSlateGrey; + } + } +<% end %> + + + + + + + + + + + + + + + + + + + + <%= yield %> + +
Helper + (<%= link_to "Path", "#", 'data-route-helper' => '_path', + title: "Returns a relative path (without the http or domain)" %> / + <%= link_to "Url", "#", 'data-route-helper' => '_url', + title: "Returns an absolute URL (with the http and domain)" %>) + HTTP VerbPathController#ActionSource Location
<%= search_field(:query, nil, id: 'search', placeholder: "Search") %>
+ + diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/railtie.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/railtie.rb new file mode 100644 index 00000000..d79f952d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/railtie.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "action_dispatch" +require "action_dispatch/log_subscriber" +require "active_support/messages/rotation_configuration" + +module ActionDispatch + class Railtie < Rails::Railtie # :nodoc: + config.action_dispatch = ActiveSupport::OrderedOptions.new + config.action_dispatch.x_sendfile_header = nil + config.action_dispatch.ip_spoofing_check = true + config.action_dispatch.show_exceptions = :all + config.action_dispatch.tld_length = 1 + config.action_dispatch.ignore_accept_header = false + config.action_dispatch.rescue_templates = {} + config.action_dispatch.rescue_responses = {} + config.action_dispatch.default_charset = nil + config.action_dispatch.rack_cache = false + config.action_dispatch.http_auth_salt = "http authentication" + config.action_dispatch.signed_cookie_salt = "signed cookie" + config.action_dispatch.encrypted_cookie_salt = "encrypted cookie" + config.action_dispatch.encrypted_signed_cookie_salt = "signed encrypted cookie" + config.action_dispatch.authenticated_encrypted_cookie_salt = "authenticated encrypted cookie" + config.action_dispatch.use_authenticated_cookie_encryption = false + config.action_dispatch.use_cookies_with_metadata = false + config.action_dispatch.perform_deep_munge = true + config.action_dispatch.request_id_header = ActionDispatch::Constants::X_REQUEST_ID + config.action_dispatch.log_rescued_responses = true + config.action_dispatch.debug_exception_log_level = :fatal + config.action_dispatch.strict_freshness = false + + config.action_dispatch.ignore_leading_brackets = nil + config.action_dispatch.strict_query_string_separator = nil + + config.action_dispatch.default_headers = { + "X-Frame-Options" => "SAMEORIGIN", + "X-XSS-Protection" => "1; mode=block", + "X-Content-Type-Options" => "nosniff", + "X-Download-Options" => "noopen", + "X-Permitted-Cross-Domain-Policies" => "none", + "Referrer-Policy" => "strict-origin-when-cross-origin" + } + + config.action_dispatch.cookies_rotations = ActiveSupport::Messages::RotationConfiguration.new + + config.eager_load_namespaces << ActionDispatch + + initializer "action_dispatch.deprecator", before: :load_environment_config do |app| + app.deprecators[:action_dispatch] = ActionDispatch.deprecator + end + + initializer "action_dispatch.configure" do |app| + ActionDispatch::Http::URL.secure_protocol = app.config.force_ssl + ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length + + ActionDispatch::ParamBuilder.ignore_leading_brackets = app.config.action_dispatch.ignore_leading_brackets + ActionDispatch::QueryParser.strict_query_string_separator = app.config.action_dispatch.strict_query_string_separator + + ActiveSupport.on_load(:action_dispatch_request) do + self.ignore_accept_header = app.config.action_dispatch.ignore_accept_header + ActionDispatch::Request::Utils.perform_deep_munge = app.config.action_dispatch.perform_deep_munge + end + + ActiveSupport.on_load(:action_dispatch_response) do + self.default_charset = app.config.action_dispatch.default_charset || app.config.encoding + self.default_headers = app.config.action_dispatch.default_headers + end + + ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses) + ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates) + + config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil? + ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie + + ActionDispatch::Routing::Mapper.route_source_locations = Rails.env.development? + + ActionDispatch::Http::Cache::Request.strict_freshness = app.config.action_dispatch.strict_freshness + ActionDispatch.test_app = app + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/request/session.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/request/session.rb new file mode 100644 index 00000000..06e61500 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/request/session.rb @@ -0,0 +1,284 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "rack/session/abstract/id" + +module ActionDispatch + class Request + # Session is responsible for lazily loading the session from store. + class Session # :nodoc: + DisabledSessionError = Class.new(StandardError) + ENV_SESSION_KEY = Rack::RACK_SESSION # :nodoc: + ENV_SESSION_OPTIONS_KEY = Rack::RACK_SESSION_OPTIONS # :nodoc: + + # Singleton object used to determine if an optional param wasn't specified. + Unspecified = Object.new + + # Creates a session hash, merging the properties of the previous session if any. + def self.create(store, req, default_options) + session_was = find req + session = Request::Session.new(store, req) + session.merge! session_was if session_was + + set(req, session) + Options.set(req, Request::Session::Options.new(store, default_options)) + session + end + + def self.disabled(req) + new(nil, req, enabled: false).tap do + Session::Options.set(req, Session::Options.new(nil, { id: nil })) + end + end + + def self.find(req) + req.get_header ENV_SESSION_KEY + end + + def self.set(req, session) + req.set_header ENV_SESSION_KEY, session + end + + def self.delete(req) + req.delete_header ENV_SESSION_KEY + end + + class Options # :nodoc: + def self.set(req, options) + req.set_header ENV_SESSION_OPTIONS_KEY, options + end + + def self.find(req) + req.get_header ENV_SESSION_OPTIONS_KEY + end + + def initialize(by, default_options) + @by = by + @delegate = default_options.dup + end + + def [](key) + @delegate[key] + end + + def id(req) + @delegate.fetch(:id) { + @by.send(:extract_session_id, req) + } + end + + def []=(k, v); @delegate[k] = v; end + def to_hash; @delegate.dup; end + def values_at(*args); @delegate.values_at(*args); end + end + + def initialize(by, req, enabled: true) + @by = by + @req = req + @delegate = {} + @loaded = false + @exists = nil # We haven't checked yet. + @enabled = enabled + @id_was = nil + @id_was_initialized = false + end + + def id + options.id(@req) + end + + def enabled? + @enabled + end + + def options + Options.find @req + end + + def destroy + clear + + if enabled? + options = self.options || {} + @by.send(:delete_session, @req, options.id(@req), options) + + # Load the new sid to be written with the response. + @loaded = false + load_for_write! + end + end + + # Returns value of the key stored in the session or `nil` if the given key is + # not found in the session. + def [](key) + load_for_read! + key = key.to_s + + if key == "session_id" + id&.public_id + else + @delegate[key] + end + end + + # Returns the nested value specified by the sequence of keys, returning `nil` if + # any intermediate step is `nil`. + def dig(*keys) + load_for_read! + keys = keys.map.with_index { |key, i| i.zero? ? key.to_s : key } + @delegate.dig(*keys) + end + + # Returns true if the session has the given key or false. + def has_key?(key) + load_for_read! + @delegate.key?(key.to_s) + end + alias :key? :has_key? + alias :include? :has_key? + + # Returns keys of the session as Array. + def keys + load_for_read! + @delegate.keys + end + + # Returns values of the session as Array. + def values + load_for_read! + @delegate.values + end + + # Writes given value to given key of the session. + def []=(key, value) + load_for_write! + @delegate[key.to_s] = value + end + alias store []= + + # Clears the session. + def clear + load_for_delete! + @delegate.clear + end + + # Returns the session as Hash. + def to_hash + load_for_read! + @delegate.dup.delete_if { |_, v| v.nil? } + end + alias :to_h :to_hash + + # Updates the session with given Hash. + # + # session.to_hash + # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2"} + # + # session.update({ "foo" => "bar" }) + # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"} + # + # session.to_hash + # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"} + def update(hash) + unless hash.respond_to?(:to_hash) + raise TypeError, "no implicit conversion of #{hash.class.name} into Hash" + end + + load_for_write! + @delegate.update hash.to_hash.stringify_keys + end + alias :merge! :update + + # Deletes given key from the session. + def delete(key) + load_for_delete! + @delegate.delete key.to_s + end + + # Returns value of the given key from the session, or raises `KeyError` if can't + # find the given key and no default value is set. Returns default value if + # specified. + # + # session.fetch(:foo) + # # => KeyError: key not found: "foo" + # + # session.fetch(:foo, :bar) + # # => :bar + # + # session.fetch(:foo) do + # :bar + # end + # # => :bar + def fetch(key, default = Unspecified, &block) + load_for_read! + if default == Unspecified + @delegate.fetch(key.to_s, &block) + else + @delegate.fetch(key.to_s, default, &block) + end + end + + def inspect + if loaded? + super + else + "#<#{self.class}:0x#{(object_id << 1).to_s(16)} not yet loaded>" + end + end + + def exists? + return false unless enabled? + return @exists unless @exists.nil? + @exists = @by.send(:session_exists?, @req) + end + + def loaded? + @loaded + end + + def empty? + load_for_read! + @delegate.empty? + end + + def each(&block) + to_hash.each(&block) + end + + def id_was + load_for_read! + @id_was + end + + private + def load_for_read! + load! if !loaded? && exists? + end + + def load_for_write! + if enabled? + load! unless loaded? + else + raise DisabledSessionError, "Your application has sessions disabled. To write to the session you must first configure a session store" + end + end + + def load_for_delete! + load! if enabled? && !loaded? + end + + def load! + if enabled? + @id_was_initialized = true unless exists? + id, session = @by.load_session @req + options[:id] = id + @delegate.replace(session.stringify_keys) + @id_was = id unless @id_was_initialized + end + @id_was_initialized = true + @loaded = true + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/request/utils.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/request/utils.rb new file mode 100644 index 00000000..c8ac5e23 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/request/utils.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/core_ext/hash/indifferent_access" + +module ActionDispatch + class Request + class Utils # :nodoc: + mattr_accessor :perform_deep_munge, default: true + + def self.each_param_value(params, &block) + case params + when Array + params.each { |element| each_param_value(element, &block) } + when Hash + params.each_value { |value| each_param_value(value, &block) } + when String + block.call params + end + end + + def self.normalize_encode_params(params) + if perform_deep_munge + NoNilParamEncoder.normalize_encode_params params + else + ParamEncoder.normalize_encode_params params + end + end + + def self.check_param_encoding(params) + case params + when Array + params.each { |element| check_param_encoding(element) } + when Hash + params.each_value { |value| check_param_encoding(value) } + when String + unless params.valid_encoding? + # Raise Rack::Utils::InvalidParameterError for consistency with Rack. + # ActionDispatch::Request#GET will re-raise as a BadRequest error. + raise Rack::Utils::InvalidParameterError, "Invalid encoding for parameter: #{params.scrub}" + end + end + end + + def self.set_binary_encoding(request, params, controller, action) + CustomParamEncoder.encode(request, params, controller, action) + end + + class ParamEncoder # :nodoc: + # Convert nested Hash to HashWithIndifferentAccess. + def self.normalize_encode_params(params) + case params + when Array + handle_array params + when Hash + if params.has_key?(:tempfile) + ActionDispatch::Http::UploadedFile.new(params) + else + hwia = ActiveSupport::HashWithIndifferentAccess.new + params.each_pair do |key, val| + hwia[key] = normalize_encode_params(val) + end + hwia + end + else + params + end + end + + def self.handle_array(params) + params.map! { |el| normalize_encode_params(el) } + end + end + + # Remove nils from the params hash. + class NoNilParamEncoder < ParamEncoder # :nodoc: + def self.handle_array(params) + list = super + list.compact! + list + end + end + + class CustomParamEncoder # :nodoc: + def self.encode_for_template(params, encoding_template) + return params unless encoding_template + params.except(:controller, :action).each do |key, value| + ActionDispatch::Request::Utils.each_param_value(value) do |param| + # If `param` is frozen, it comes from the router defaults + next if param.frozen? + + if encoding_template[key.to_s] + param.force_encoding(encoding_template[key.to_s]) + end + end + end + params + end + + def self.encode(request, params, controller, action) + encoding_template = action_encoding_template(request, controller, action) + encode_for_template(params, encoding_template) + end + + def self.action_encoding_template(request, controller, action) # :nodoc: + controller && controller.valid_encoding? && + request.controller_class_for(controller).action_encoding_template(action) + rescue MissingController + nil + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing.rb new file mode 100644 index 00000000..e1990a2d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing.rb @@ -0,0 +1,262 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + # The routing module provides URL rewriting in native Ruby. It's a way to + # redirect incoming requests to controllers and actions. This replaces + # mod_rewrite rules. Best of all, Rails' Routing works with any web server. + # Routes are defined in `config/routes.rb`. + # + # Think of creating routes as drawing a map for your requests. The map tells + # them where to go based on some predefined pattern: + # + # Rails.application.routes.draw do + # Pattern 1 tells some request to go to one place + # Pattern 2 tell them to go to another + # ... + # end + # + # The following symbols are special: + # + # :controller maps to your controller name + # :action maps to an action with your controllers + # + # Other names simply map to a parameter as in the case of `:id`. + # + # ## Resources + # + # Resource routing allows you to quickly declare all of the common routes for a + # given resourceful controller. Instead of declaring separate routes for your + # `index`, `show`, `new`, `edit`, `create`, `update`, and `destroy` actions, a + # resourceful route declares them in a single line of code: + # + # resources :photos + # + # Sometimes, you have a resource that clients always look up without referencing + # an ID. A common example, /profile always shows the profile of the currently + # logged in user. In this case, you can use a singular resource to map /profile + # (rather than /profile/:id) to the show action. + # + # resource :profile + # + # It's common to have resources that are logically children of other resources: + # + # resources :magazines do + # resources :ads + # end + # + # You may wish to organize groups of controllers under a namespace. Most + # commonly, you might group a number of administrative controllers under an + # `admin` namespace. You would place these controllers under the + # `app/controllers/admin` directory, and you can group them together in your + # router: + # + # namespace "admin" do + # resources :posts, :comments + # end + # + # Alternatively, you can add prefixes to your path without using a separate + # directory by using `scope`. `scope` takes additional options which apply to + # all enclosed routes. + # + # scope path: "/cpanel", as: 'admin' do + # resources :posts, :comments + # end + # + # For more, see Routing::Mapper::Resources#resources, + # Routing::Mapper::Scoping#namespace, and Routing::Mapper::Scoping#scope. + # + # ## Non-resourceful routes + # + # For routes that don't fit the `resources` mold, you can use the HTTP helper + # methods `get`, `post`, `patch`, `put` and `delete`. + # + # get 'post/:id', to: 'posts#show' + # post 'post/:id', to: 'posts#create_comment' + # + # Now, if you POST to `/posts/:id`, it will route to the `create_comment` + # action. A GET on the same URL will route to the `show` action. + # + # If your route needs to respond to more than one HTTP method (or all methods) + # then using the `:via` option on `match` is preferable. + # + # match 'post/:id', to: 'posts#show', via: [:get, :post] + # + # ## Named routes + # + # Routes can be named by passing an `:as` option, allowing for easy reference + # within your source as `name_of_route_url` for the full URL and + # `name_of_route_path` for the URI path. + # + # Example: + # + # # In config/routes.rb + # get '/login', to: 'accounts#login', as: 'login' + # + # # With render, redirect_to, tests, etc. + # redirect_to login_url + # + # Arguments can be passed as well. + # + # redirect_to show_item_path(id: 25) + # + # Use `root` as a shorthand to name a route for the root path "/". + # + # # In config/routes.rb + # root to: 'blogs#index' + # + # # would recognize http://www.example.com/ as + # params = { controller: 'blogs', action: 'index' } + # + # # and provide these named routes + # root_url # => 'http://www.example.com/' + # root_path # => '/' + # + # Note: when using `controller`, the route is simply named after the method you + # call on the block parameter rather than map. + # + # # In config/routes.rb + # controller :blog do + # get 'blog/show' => :list + # get 'blog/delete' => :delete + # get 'blog/edit' => :edit + # end + # + # # provides named routes for show, delete, and edit + # link_to @article.title, blog_show_path(id: @article.id) + # + # ## Pretty URLs + # + # Routes can generate pretty URLs. For example: + # + # get '/articles/:year/:month/:day', to: 'articles#find_by_id', constraints: { + # year: /\d{4}/, + # month: /\d{1,2}/, + # day: /\d{1,2}/ + # } + # + # Using the route above, the URL "http://localhost:3000/articles/2005/11/06" + # maps to + # + # params = {year: '2005', month: '11', day: '06'} + # + # ## Regular Expressions and parameters + # You can specify a regular expression to define a format for a parameter. + # + # controller 'geocode' do + # get 'geocode/:postalcode', to: :show, constraints: { + # postalcode: /\d{5}(-\d{4})?/ + # } + # end + # + # Constraints can include the 'ignorecase' and 'extended syntax' regular + # expression modifiers: + # + # controller 'geocode' do + # get 'geocode/:postalcode', to: :show, constraints: { + # postalcode: /hx\d\d\s\d[a-z]{2}/i + # } + # end + # + # controller 'geocode' do + # get 'geocode/:postalcode', to: :show, constraints: { + # postalcode: /# Postalcode format + # \d{5} #Prefix + # (-\d{4})? #Suffix + # /x + # } + # end + # + # Using the multiline modifier will raise an `ArgumentError`. Encoding regular + # expression modifiers are silently ignored. The match will always use the + # default encoding or ASCII. + # + # ## External redirects + # + # You can redirect any path to another path using the redirect helper in your + # router: + # + # get "/stories", to: redirect("/posts") + # + # ## Unicode character routes + # + # You can specify unicode character routes in your router: + # + # get "こんにちは", to: "welcome#index" + # + # ## Routing to Rack Applications + # + # Instead of a String, like `posts#index`, which corresponds to the index action + # in the PostsController, you can specify any Rack application as the endpoint + # for a matcher: + # + # get "/application.js", to: Sprockets + # + # ## Reloading routes + # + # You can reload routes if you feel you must: + # + # Rails.application.reload_routes! + # + # This will clear all named routes and reload config/routes.rb if the file has + # been modified from last load. To absolutely force reloading, use `reload!`. + # + # ## Testing Routes + # + # The two main methods for testing your routes: + # + # ### `assert_routing` + # + # def test_movie_route_properly_splits + # opts = {controller: "plugin", action: "checkout", id: "2"} + # assert_routing "plugin/checkout/2", opts + # end + # + # `assert_routing` lets you test whether or not the route properly resolves into + # options. + # + # ### `assert_recognizes` + # + # def test_route_has_options + # opts = {controller: "plugin", action: "show", id: "12"} + # assert_recognizes opts, "/plugins/show/12" + # end + # + # Note the subtle difference between the two: `assert_routing` tests that a URL + # fits options while `assert_recognizes` tests that a URL breaks into parameters + # properly. + # + # In tests you can simply pass the URL or named route to `get` or `post`. + # + # def send_to_jail + # get '/jail' + # assert_response :success + # end + # + # def goes_to_login + # get login_url + # #... + # end + # + # ## View a list of all your routes + # + # $ bin/rails routes + # + # Target a specific controller with `-c`, or grep routes using `-g`. Useful in + # conjunction with `--expanded` which displays routes vertically. + module Routing + extend ActiveSupport::Autoload + + autoload :Mapper + autoload :RouteSet + eager_autoload do + autoload :RoutesProxy + end + autoload :UrlFor + autoload :PolymorphicRoutes + + SEPARATORS = %w( / . ? ) # :nodoc: + HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] # :nodoc: + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/endpoint.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/endpoint.rb new file mode 100644 index 00000000..0202e66a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/endpoint.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + module Routing + class Endpoint # :nodoc: + def dispatcher?; false; end + def redirect?; false; end + def matches?(req); true; end + def app; self; end + def rack_app; app; end + + def engine? + rack_app.is_a?(Class) && rack_app < Rails::Engine + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/inspector.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/inspector.rb new file mode 100644 index 00000000..c06a0053 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/inspector.rb @@ -0,0 +1,323 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "delegate" +require "io/console/size" + +module ActionDispatch + module Routing + class RouteWrapper < SimpleDelegator # :nodoc: + def matches_filter?(filter, value) + return __getobj__.path.match(value) if filter == :exact_path_match + + value.match?(public_send(filter)) + end + + def endpoint + case + when app.dispatcher? + "#{controller}##{action}" + when rack_app.is_a?(Proc) + "Inline handler (Proc/Lambda)" + else + rack_app.inspect + end + end + + def constraints + requirements.except(:controller, :action) + end + + def rack_app + app.rack_app + end + + def path + super.spec.to_s + end + + def name + super.to_s + end + + def reqs + @reqs ||= begin + reqs = endpoint + reqs += " #{constraints}" unless constraints.empty? + reqs + end + end + + def controller + parts.include?(:controller) ? ":controller" : requirements[:controller] + end + + def action + parts.include?(:action) ? ":action" : requirements[:action] + end + + def internal? + internal + end + + def engine? + app.engine? + end + end + + ## + # This class is just used for displaying route information when someone + # executes `bin/rails routes` or looks at the RoutingError page. People should + # not use this class. + class RoutesInspector # :nodoc: + def initialize(routes) + @engines = {} + @routes = routes + end + + def format(formatter, filter = {}) + routes_to_display = filter_routes(normalize_filter(filter)) + routes = collect_routes(routes_to_display) + if routes.none? + formatter.no_routes(collect_routes(@routes), filter) + return formatter.result + end + + formatter.header routes + formatter.section routes + + @engines.each do |name, engine_routes| + formatter.section_title "Routes for #{name}" + formatter.section engine_routes + end + + formatter.result + end + + private + def normalize_filter(filter) + if filter[:controller] + { controller: /#{filter[:controller].underscore.sub(/_?controller\z/, "")}/ } + elsif filter[:grep] + grep_pattern = Regexp.new(filter[:grep]) + path = URI::RFC2396_PARSER.escape(filter[:grep]) + normalized_path = ("/" + path).squeeze("/") + + { + controller: grep_pattern, + action: grep_pattern, + verb: grep_pattern, + name: grep_pattern, + path: grep_pattern, + exact_path_match: normalized_path, + } + end + end + + def filter_routes(filter) + if filter + @routes.select do |route| + route_wrapper = RouteWrapper.new(route) + filter.any? { |filter_type, value| route_wrapper.matches_filter?(filter_type, value) } + end + else + @routes + end + end + + def collect_routes(routes) + routes.collect do |route| + RouteWrapper.new(route) + end.reject(&:internal?).collect do |route| + collect_engine_routes(route) + + { name: route.name, + verb: route.verb, + path: route.path, + reqs: route.reqs, + source_location: route.source_location } + end + end + + def collect_engine_routes(route) + name = route.endpoint + return unless route.engine? + return if @engines[name] + + routes = route.rack_app.routes + if routes.is_a?(ActionDispatch::Routing::RouteSet) + @engines[name] = collect_routes(routes.routes) + end + end + end + + module ConsoleFormatter + class Base + def initialize + @buffer = [] + end + + def result + @buffer.join("\n") + end + + def section_title(title) + end + + def section(routes) + end + + def header(routes) + end + + def no_routes(routes, filter) + @buffer << + if routes.none? + <<~MESSAGE + You don't have any routes defined! + + Please add some routes in config/routes.rb. + MESSAGE + elsif filter.key?(:controller) + "No routes were found for this controller." + elsif filter.key?(:grep) + "No routes were found for this grep pattern." + end + + @buffer << "For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html." + end + end + + class Sheet < Base + def section_title(title) + @buffer << "\n#{title}:" + end + + def section(routes) + @buffer << draw_section(routes) + end + + def header(routes) + @buffer << draw_header(routes) + end + + private + def draw_section(routes) + header_lengths = ["Prefix", "Verb", "URI Pattern"].map(&:length) + name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max) + + routes.map do |r| + "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}" + end + end + + def draw_header(routes) + name_width, verb_width, path_width = widths(routes) + + "#{"Prefix".rjust(name_width)} #{"Verb".ljust(verb_width)} #{"URI Pattern".ljust(path_width)} Controller#Action" + end + + def widths(routes) + [routes.map { |r| r[:name].length }.max || 0, + routes.map { |r| r[:verb].length }.max || 0, + routes.map { |r| r[:path].length }.max || 0] + end + end + + class Expanded < Base + def initialize(width: IO.console_size[1]) + @width = width + super() + end + + def section_title(title) + @buffer << "\n#{"[ #{title} ]"}" + end + + def section(routes) + @buffer << draw_expanded_section(routes) + end + + private + def draw_expanded_section(routes) + routes.map.each_with_index do |r, i| + route_rows = <<~MESSAGE.chomp + #{route_header(index: i + 1)} + Prefix | #{r[:name]} + Verb | #{r[:verb]} + URI | #{r[:path]} + Controller#Action | #{r[:reqs]} + MESSAGE + source_location = "\nSource Location | #{r[:source_location]}" + route_rows += source_location if r[:source_location].present? + route_rows + end + end + + def route_header(index:) + "--[ Route #{index} ]".ljust(@width, "-") + end + end + + class Unused < Sheet + def header(routes) + @buffer << <<~MSG + Found #{routes.count} unused #{"route".pluralize(routes.count)}: + MSG + + super + end + + def no_routes(routes, filter) + @buffer << + if filter.none? + "No unused routes found." + elsif filter.key?(:controller) + "No unused routes found for this controller." + elsif filter.key?(:grep) + "No unused routes found for this grep pattern." + end + end + end + end + + class HtmlTableFormatter + def initialize(view) + @view = view + @buffer = [] + end + + def section_title(title) + @buffer << %(#{title}) + end + + def section(routes) + @buffer << @view.render(partial: "routes/route", collection: routes) + end + + # The header is part of the HTML page, so we don't construct it here. + def header(routes) + end + + def no_routes(*) + @buffer << <<~MESSAGE +

You don't have any routes defined!

+ + MESSAGE + end + + def result + @view.raw @view.render(layout: "routes/table") { + @view.raw @buffer.join("\n") + } + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/mapper.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/mapper.rb new file mode 100644 index 00000000..7d6d3d69 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/mapper.rb @@ -0,0 +1,2394 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/enumerable" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/regexp" +require "action_dispatch/routing/redirection" +require "action_dispatch/routing/endpoint" + +module ActionDispatch + module Routing + class Mapper + class BacktraceCleaner < ActiveSupport::BacktraceCleaner # :nodoc: + def initialize + super + remove_silencers! + add_core_silencer + add_stdlib_silencer + end + end + + URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port] + + cattr_accessor :route_source_locations, instance_accessor: false, default: false + cattr_accessor :backtrace_cleaner, instance_accessor: false, default: BacktraceCleaner.new + + class Constraints < Routing::Endpoint # :nodoc: + attr_reader :app, :constraints + + SERVE = ->(app, req) { app.serve req } + CALL = ->(app, req) { app.call req.env } + + def initialize(app, constraints, strategy) + # Unwrap Constraints objects. I don't actually think it's possible to pass a + # Constraints object to this constructor, but there were multiple places that + # kept testing children of this object. I **think** they were just being + # defensive, but I have no idea. + if app.is_a?(self.class) + constraints += app.constraints + app = app.app + end + + @strategy = strategy + + @app, @constraints, = app, constraints + end + + def dispatcher?; @strategy == SERVE; end + + def matches?(req) + @constraints.all? do |constraint| + (constraint.respond_to?(:matches?) && constraint.matches?(req)) || + (constraint.respond_to?(:call) && constraint.call(*constraint_args(constraint, req))) + end + end + + def serve(req) + return [ 404, { Constants::X_CASCADE => "pass" }, [] ] unless matches?(req) + + @strategy.call @app, req + end + + private + def constraint_args(constraint, request) + arity = if constraint.respond_to?(:arity) + constraint.arity + else + constraint.method(:call).arity + end + + if arity < 1 + [] + elsif arity == 1 + [request] + else + [request.path_parameters, request] + end + end + end + + class Mapping # :nodoc: + ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} + OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z} + + attr_reader :path, :requirements, :defaults, :to, :default_controller, + :default_action, :required_defaults, :ast, :scope_options + + def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options) + scope_params = { + blocks: scope[:blocks] || [], + constraints: scope[:constraints] || {}, + defaults: (scope[:defaults] || {}).dup, + module: scope[:module], + options: scope[:options] || {} + } + + new set: set, ast: ast, controller: controller, default_action: default_action, + to: to, formatted: formatted, via: via, options_constraints: options_constraints, + anchor: anchor, scope_params: scope_params, options: scope_params[:options].merge(options) + end + + def self.check_via(via) + if via.empty? + msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \ + "If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \ + "If you want to expose your action to GET, use `get` in the router:\n" \ + " Instead of: match \"controller#action\"\n" \ + " Do: get \"controller#action\"" + raise ArgumentError, msg + end + via + end + + def self.normalize_path(path, format) + path = Mapper.normalize_path(path) + + if format == true + "#{path}.:format" + elsif optional_format?(path, format) + "#{path}(.:format)" + else + path + end + end + + def self.optional_format?(path, format) + format != false && !path.match?(OPTIONAL_FORMAT_REGEX) + end + + def initialize(set:, ast:, controller:, default_action:, to:, formatted:, via:, options_constraints:, anchor:, scope_params:, options:) + @defaults = scope_params[:defaults] + @set = set + @to = intern(to) + @default_controller = intern(controller) + @default_action = intern(default_action) + @anchor = anchor + @via = via + @internal = options.delete(:internal) + @scope_options = scope_params[:options] + ast = Journey::Ast.new(ast, formatted) + + options = ast.wildcard_options.merge!(options) + + options = normalize_options!(options, ast.path_params, scope_params[:module]) + + split_options = constraints(options, ast.path_params) + + constraints = scope_params[:constraints].merge Hash[split_options[:constraints] || []] + + if options_constraints.is_a?(Hash) + @defaults = Hash[options_constraints.find_all { |key, default| + URL_OPTIONS.include?(key) && (String === default || Integer === default) + }].merge @defaults + @blocks = scope_params[:blocks] + constraints.merge! options_constraints + else + @blocks = blocks(options_constraints) + end + + requirements, conditions = split_constraints ast.path_params, constraints + verify_regexp_requirements requirements, ast.wildcard_options + + formats = normalize_format(formatted) + + @requirements = formats[:requirements].merge Hash[requirements] + @conditions = Hash[conditions] + @defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options)) + + if ast.path_params.include?(:action) && !@requirements.key?(:action) + @defaults[:action] ||= "index" + end + + @required_defaults = (split_options[:required_defaults] || []).map(&:first) + + ast.requirements = @requirements + @path = Journey::Path::Pattern.new(ast, @requirements, JOINED_SEPARATORS, @anchor) + end + + JOINED_SEPARATORS = SEPARATORS.join # :nodoc: + + def make_route(name, precedence) + Journey::Route.new(name: name, app: application, path: path, constraints: conditions, + required_defaults: required_defaults, defaults: defaults, + request_method_match: request_method, precedence: precedence, + scope_options: scope_options, internal: @internal, source_location: route_source_location) + end + + def application + app(@blocks) + end + + def conditions + build_conditions @conditions, @set.request_class + end + + def build_conditions(current_conditions, request_class) + conditions = current_conditions.dup + + conditions.keep_if do |k, _| + request_class.public_method_defined?(k) + end + end + private :build_conditions + + def request_method + @via.map { |x| Journey::Route.verb_matcher(x) } + end + private :request_method + + private + def intern(object) + object.is_a?(String) ? -object : object + end + + def normalize_options!(options, path_params, modyoule) + if path_params.include?(:controller) + raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule + + # Add a default constraint for :controller path segments that matches namespaced + # controllers with default routes like :controller/:action/:id(.:format), e.g: + # GET /admin/products/show/1 + # # > { controller: 'admin/products', action: 'show', id: '1' } + options[:controller] ||= /.+?/ + end + + if to.respond_to?(:action) || to.respond_to?(:call) + options + else + if to.nil? + controller = default_controller + action = default_action + elsif to.is_a?(String) + if to.include?("#") + to_endpoint = to.split("#").map!(&:-@) + controller = to_endpoint[0] + action = to_endpoint[1] + else + controller = default_controller + action = to + end + else + raise ArgumentError, ":to must respond to `action` or `call`, or it must be a String that includes '#', or the controller should be implicit" + end + + controller = add_controller_module(controller, modyoule) + + options.merge! check_controller_and_action(path_params, controller, action) + end + end + + def split_constraints(path_params, constraints) + constraints.partition do |key, requirement| + path_params.include?(key) || key == :controller + end + end + + def normalize_format(formatted) + case formatted + when true + { requirements: { format: /.+/ }, + defaults: {} } + when Regexp + { requirements: { format: formatted }, + defaults: { format: nil } } + when String + { requirements: { format: Regexp.compile(formatted) }, + defaults: { format: formatted } } + else + { requirements: {}, defaults: {} } + end + end + + def verify_regexp_requirements(requirements, wildcard_options) + requirements.each do |requirement, regex| + next unless regex.is_a? Regexp + + if ANCHOR_CHARACTERS_REGEX.match?(regex.source) + raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" + end + + if regex.multiline? + next if wildcard_options.key?(requirement) + + raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{regex.inspect}" + end + end + end + + def normalize_defaults(options) + Hash[options.reject { |_, default| Regexp === default }] + end + + def app(blocks) + if to.respond_to?(:action) + Routing::RouteSet::StaticDispatcher.new to + elsif to.respond_to?(:call) + Constraints.new(to, blocks, Constraints::CALL) + elsif blocks.any? + Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE) + else + dispatcher(defaults.key?(:controller)) + end + end + + def check_controller_and_action(path_params, controller, action) + hash = check_part(:controller, controller, path_params, {}) do |part| + translate_controller(part) { + message = +"'#{part}' is not a supported controller name. This can lead to potential routing problems." + message << " See https://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use" + + raise ArgumentError, message + } + end + + check_part(:action, action, path_params, hash) { |part| + part.is_a?(Regexp) ? part : part.to_s + } + end + + def check_part(name, part, path_params, hash) + if part + hash[name] = yield(part) + else + unless path_params.include?(name) + message = "Missing :#{name} key on routes definition, please check your routes." + raise ArgumentError, message + end + end + hash + end + + def add_controller_module(controller, modyoule) + if modyoule && !controller.is_a?(Regexp) + if controller&.start_with?("/") + -controller[1..-1] + else + -[modyoule, controller].compact.join("/") + end + else + controller + end + end + + def translate_controller(controller) + return controller if Regexp === controller + return controller.to_s if /\A[a-z_0-9][a-z_0-9\/]*\z/.match?(controller) + + yield + end + + def blocks(callable_constraint) + unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?) + raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?" + end + [callable_constraint] + end + + def constraints(options, path_params) + options.group_by do |key, option| + if Regexp === option + :constraints + else + if path_params.include?(key) + :path_params + else + :required_defaults + end + end + end + end + + def dispatcher(raise_on_name_error) + Routing::RouteSet::Dispatcher.new raise_on_name_error + end + + def route_source_location + if Mapper.route_source_locations + action_dispatch_dir = File.expand_path("..", __dir__) + Thread.each_caller_location do |location| + next if location.path.start_with?(action_dispatch_dir) + + cleaned_path = Mapper.backtrace_cleaner.clean_frame(location.path) + next if cleaned_path.nil? + + return "#{cleaned_path}:#{location.lineno}" + end + nil + end + end + end + + # Invokes Journey::Router::Utils.normalize_path, then ensures that /(:locale) + # becomes (/:locale). Except for root cases, where the former is the correct + # one. + def self.normalize_path(path) + path = Journey::Router::Utils.normalize_path(path) + + # the path for a root URL at this point can be something like + # "/(/:locale)(/:platform)/(:browser)", and we would want + # "/(:locale)(/:platform)(/:browser)" reverse "/(", "/((" etc to "(/", "((/" etc + path.gsub!(%r{/(\(+)/?}, '\1/') + # if a path is all optional segments, change the leading "(/" back to "/(" so it + # evaluates to "/" when interpreted with no options. Unless, however, at least + # one secondary segment consists of a static part, ex. + # "(/:locale)(/pages/:page)" + path.sub!(%r{^(\(+)/}, '/\1') if %r{^(\(+[^)]+\))(\(+/:[^)]+\))*$}.match?(path) + path + end + + def self.normalize_name(name) + normalize_path(name)[1..-1].tr("/", "_") + end + + module Base + # Matches a URL pattern to one or more routes. + # + # You should not use the `match` method in your router without specifying an + # HTTP method. + # + # If you want to expose your action to both GET and POST, use: + # + # # sets :controller, :action, and :id in params + # match ':controller/:action/:id', via: [:get, :post] + # + # Note that `:controller`, `:action`, and `:id` are interpreted as URL query + # parameters and thus available through `params` in an action. + # + # If you want to expose your action to GET, use `get` in the router: + # + # Instead of: + # + # match ":controller/:action/:id" + # + # Do: + # + # get ":controller/:action/:id" + # + # Two of these symbols are special, `:controller` maps to the controller and + # `:action` to the controller's action. A pattern can also map wildcard segments + # (globs) to params: + # + # get 'songs/*category/:title', to: 'songs#show' + # + # # 'songs/rock/classic/stairway-to-heaven' sets + # # params[:category] = 'rock/classic' + # # params[:title] = 'stairway-to-heaven' + # + # To match a wildcard parameter, it must have a name assigned to it. Without a + # variable name to attach the glob parameter to, the route can't be parsed. + # + # When a pattern points to an internal route, the route's `:action` and + # `:controller` should be set in options or hash shorthand. Examples: + # + # match 'photos/:id', to: 'photos#show', via: :get + # match 'photos/:id', controller: 'photos', action: 'show', via: :get + # + # A pattern can also point to a `Rack` endpoint i.e. anything that responds to + # `call`: + # + # match 'photos/:id', to: -> (hash) { [200, {}, ["Coming soon"]] }, via: :get + # match 'photos/:id', to: PhotoRackApp, via: :get + # # Yes, controller actions are just rack endpoints + # match 'photos/:id', to: PhotosController.action(:show), via: :get + # + # Because requesting various HTTP verbs with a single action has security + # implications, you must either specify the actions in the via options or use + # one of the [HttpHelpers](rdoc-ref:HttpHelpers) instead `match` + # + # ### Options + # + # Any options not seen here are passed on as params with the URL. + # + # :controller + # : The route's controller. + # + # :action + # : The route's action. + # + # :param + # : Overrides the default resource identifier `:id` (name of the dynamic + # segment used to generate the routes). You can access that segment from + # your controller using `params[<:param>]`. In your router: + # + # resources :users, param: :name + # + # The `users` resource here will have the following routes generated for it: + # + # GET /users(.:format) + # POST /users(.:format) + # GET /users/new(.:format) + # GET /users/:name/edit(.:format) + # GET /users/:name(.:format) + # PATCH/PUT /users/:name(.:format) + # DELETE /users/:name(.:format) + # + # You can override `ActiveRecord::Base#to_param` of a related model to + # construct a URL: + # + # class User < ActiveRecord::Base + # def to_param + # name + # end + # end + # + # user = User.find_by(name: 'Phusion') + # user_path(user) # => "/users/Phusion" + # + # :path + # : The path prefix for the routes. + # + # :module + # : The namespace for :controller. + # + # match 'path', to: 'c#a', module: 'sekret', controller: 'posts', via: :get + # # => Sekret::PostsController + # + # See `Scoping#namespace` for its scope equivalent. + # + # :as + # : The name used to generate routing helpers. + # + # :via + # : Allowed HTTP verb(s) for route. + # + # match 'path', to: 'c#a', via: :get + # match 'path', to: 'c#a', via: [:get, :post] + # match 'path', to: 'c#a', via: :all + # + # :to + # : Points to a `Rack` endpoint. Can be an object that responds to `call` or a + # string representing a controller's action. + # + # match 'path', to: 'controller#action', via: :get + # match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get + # match 'path', to: RackApp, via: :get + # + # :on + # : Shorthand for wrapping routes in a specific RESTful context. Valid values + # are `:member`, `:collection`, and `:new`. Only use within `resource(s)` + # block. For example: + # + # resource :bar do + # match 'foo', to: 'c#a', on: :member, via: [:get, :post] + # end + # + # Is equivalent to: + # + # resource :bar do + # member do + # match 'foo', to: 'c#a', via: [:get, :post] + # end + # end + # + # :constraints + # : Constrains parameters with a hash of regular expressions or an object that + # responds to `matches?`. In addition, constraints other than path can also + # be specified with any object that responds to `===` (e.g. String, Array, + # Range, etc.). + # + # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get + # + # match 'json_only', constraints: { format: 'json' }, via: :get + # + # class PermitList + # def matches?(request) request.remote_ip == '1.2.3.4' end + # end + # match 'path', to: 'c#a', constraints: PermitList.new, via: :get + # + # See `Scoping#constraints` for more examples with its scope equivalent. + # + # :defaults + # : Sets defaults for parameters + # + # # Sets params[:format] to 'jpg' by default + # match 'path', to: 'c#a', defaults: { format: 'jpg' }, via: :get + # + # See `Scoping#defaults` for its scope equivalent. + # + # :anchor + # : Boolean to anchor a `match` pattern. Default is true. When set to false, + # the pattern matches any request prefixed with the given path. + # + # # Matches any request starting with 'path' + # match 'path', to: 'c#a', anchor: false, via: :get + # + # :format + # : Allows you to specify the default value for optional `format` segment or + # disable it by supplying `false`. + # + def match(path, options = nil) + end + + # Mount a Rack-based application to be used within the application. + # + # mount SomeRackApp, at: "some_route" + # + # For options, see `match`, as `mount` uses it internally. + # + # All mounted applications come with routing helpers to access them. These are + # named after the class specified, so for the above example the helper is either + # `some_rack_app_path` or `some_rack_app_url`. To customize this helper's name, + # use the `:as` option: + # + # mount(SomeRackApp, at: "some_route", as: "exciting") + # + # This will generate the `exciting_path` and `exciting_url` helpers which can be + # used to navigate to this mounted app. + def mount(app, options = nil) + if options + path = options.delete(:at) + elsif Hash === app + options = app + app, path = options.find { |k, _| k.respond_to?(:call) } + options.delete(app) if app + end + + raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call) + raise ArgumentError, <<~MSG unless path + Must be called with mount point + + mount SomeRackApp, at: "some_route" + or + mount(SomeRackApp => "some_route") + MSG + + rails_app = rails_app? app + options[:as] ||= app_name(app, rails_app) + + target_as = name_for_action(options[:as], path) + options[:via] ||= :all + + match(path, { to: app, anchor: false, format: false }.merge(options)) + + define_generate_prefix(app, target_as) if rails_app + self + end + + def default_url_options=(options) + @set.default_url_options = options + end + alias_method :default_url_options, :default_url_options= + + def with_default_scope(scope, &block) + scope(scope) do + instance_exec(&block) + end + end + + # Query if the following named route was already defined. + def has_named_route?(name) + @set.named_routes.key?(name) + end + + private + def rails_app?(app) + app.is_a?(Class) && app < Rails::Railtie + end + + def app_name(app, rails_app) + if rails_app + app.railtie_name + elsif app.is_a?(Class) + class_name = app.name + ActiveSupport::Inflector.underscore(class_name).tr("/", "_") + end + end + + def define_generate_prefix(app, name) + _route = @set.named_routes.get name + _routes = @set + _url_helpers = @set.url_helpers + + script_namer = ->(options) do + prefix_options = options.slice(*_route.segment_keys) + prefix_options[:script_name] = "" if options[:original_script_name] + + if options[:_recall] + prefix_options.reverse_merge!(options[:_recall].slice(*_route.segment_keys)) + end + + # We must actually delete prefix segment keys to avoid passing them to next + # url_for. + _route.segment_keys.each { |k| options.delete(k) } + _url_helpers.public_send("#{name}_path", prefix_options) + end + + app.routes.define_mounted_helper(name, script_namer) + + app.routes.extend Module.new { + def optimize_routes_generation?; false; end + + define_method :find_script_name do |options| + if options.key?(:script_name) && options[:script_name].present? + super(options) + else + script_namer.call(options) + end + end + } + end + end + + module HttpHelpers + # Define a route that only recognizes HTTP GET. For supported arguments, see + # [match](rdoc-ref:Base#match) + # + # get 'bacon', to: 'food#bacon' + def get(*args, &block) + map_method(:get, args, &block) + end + + # Define a route that only recognizes HTTP POST. For supported arguments, see + # [match](rdoc-ref:Base#match) + # + # post 'bacon', to: 'food#bacon' + def post(*args, &block) + map_method(:post, args, &block) + end + + # Define a route that only recognizes HTTP PATCH. For supported arguments, see + # [match](rdoc-ref:Base#match) + # + # patch 'bacon', to: 'food#bacon' + def patch(*args, &block) + map_method(:patch, args, &block) + end + + # Define a route that only recognizes HTTP PUT. For supported arguments, see + # [match](rdoc-ref:Base#match) + # + # put 'bacon', to: 'food#bacon' + def put(*args, &block) + map_method(:put, args, &block) + end + + # Define a route that only recognizes HTTP DELETE. For supported arguments, see + # [match](rdoc-ref:Base#match) + # + # delete 'broccoli', to: 'food#broccoli' + def delete(*args, &block) + map_method(:delete, args, &block) + end + + # Define a route that only recognizes HTTP OPTIONS. For supported arguments, see + # [match](rdoc-ref:Base#match) + # + # options 'carrots', to: 'food#carrots' + def options(*args, &block) + map_method(:options, args, &block) + end + + # Define a route that recognizes HTTP CONNECT (and GET) requests. More + # specifically this recognizes HTTP/1 protocol upgrade requests and HTTP/2 + # CONNECT requests with the protocol pseudo header. For supported arguments, + # see [match](rdoc-ref:Base#match) + # + # connect 'live', to: 'live#index' + def connect(*args, &block) + map_method([:get, :connect], args, &block) + end + + private + def map_method(method, args, &block) + options = args.extract_options! + options[:via] = method + match(*args, options, &block) + self + end + end + + # You may wish to organize groups of controllers under a namespace. Most + # commonly, you might group a number of administrative controllers under an + # `admin` namespace. You would place these controllers under the + # `app/controllers/admin` directory, and you can group them together in your + # router: + # + # namespace "admin" do + # resources :posts, :comments + # end + # + # This will create a number of routes for each of the posts and comments + # controller. For `Admin::PostsController`, Rails will create: + # + # GET /admin/posts + # GET /admin/posts/new + # POST /admin/posts + # GET /admin/posts/1 + # GET /admin/posts/1/edit + # PATCH/PUT /admin/posts/1 + # DELETE /admin/posts/1 + # + # If you want to route /posts (without the prefix /admin) to + # `Admin::PostsController`, you could use + # + # scope module: "admin" do + # resources :posts + # end + # + # or, for a single case + # + # resources :posts, module: "admin" + # + # If you want to route /admin/posts to `PostsController` (without the `Admin::` + # module prefix), you could use + # + # scope "/admin" do + # resources :posts + # end + # + # or, for a single case + # + # resources :posts, path: "/admin/posts" + # + # In each of these cases, the named routes remain the same as if you did not use + # scope. In the last case, the following paths map to `PostsController`: + # + # GET /admin/posts + # GET /admin/posts/new + # POST /admin/posts + # GET /admin/posts/1 + # GET /admin/posts/1/edit + # PATCH/PUT /admin/posts/1 + # DELETE /admin/posts/1 + module Scoping + # Scopes a set of routes to the given default options. + # + # Take the following route definition as an example: + # + # scope path: ":account_id", as: "account" do + # resources :projects + # end + # + # This generates helpers such as `account_projects_path`, just like `resources` + # does. The difference here being that the routes generated are like + # /:account_id/projects, rather than /accounts/:account_id/projects. + # + # ### Options + # + # Takes same options as `Base#match` and `Resources#resources`. + # + # # route /posts (without the prefix /admin) to Admin::PostsController + # scope module: "admin" do + # resources :posts + # end + # + # # prefix the posts resource's requests with '/admin' + # scope path: "/admin" do + # resources :posts + # end + # + # # prefix the routing helper name: sekret_posts_path instead of posts_path + # scope as: "sekret" do + # resources :posts + # end + def scope(*args) + options = args.extract_options!.dup + scope = {} + + options[:path] = args.flatten.join("/") if args.any? + options[:constraints] ||= {} + + unless nested_scope? + options[:shallow_path] ||= options[:path] if options.key?(:path) + options[:shallow_prefix] ||= options[:as] if options.key?(:as) + end + + if options[:constraints].is_a?(Hash) + defaults = options[:constraints].select do |k, v| + URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer)) + end + + options[:defaults] = defaults.merge(options[:defaults] || {}) + else + block, options[:constraints] = options[:constraints], {} + end + + if options.key?(:only) || options.key?(:except) + scope[:action_options] = { only: options.delete(:only), + except: options.delete(:except) } + end + + if options.key? :anchor + raise ArgumentError, "anchor is ignored unless passed to `match`" + end + + @scope.options.each do |option| + if option == :blocks + value = block + elsif option == :options + value = options + else + value = options.delete(option) { POISON } + end + + unless POISON == value + scope[option] = send("merge_#{option}_scope", @scope[option], value) + end + end + + @scope = @scope.new scope + yield + self + ensure + @scope = @scope.parent + end + + POISON = Object.new # :nodoc: + + # Scopes routes to a specific controller + # + # controller "food" do + # match "bacon", action: :bacon, via: :get + # end + def controller(controller) + @scope = @scope.new(controller: controller) + yield + ensure + @scope = @scope.parent + end + + # Scopes routes to a specific namespace. For example: + # + # namespace :admin do + # resources :posts + # end + # + # This generates the following routes: + # + # admin_posts GET /admin/posts(.:format) admin/posts#index + # admin_posts POST /admin/posts(.:format) admin/posts#create + # new_admin_post GET /admin/posts/new(.:format) admin/posts#new + # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit + # admin_post GET /admin/posts/:id(.:format) admin/posts#show + # admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update + # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy + # + # ### Options + # + # The `:path`, `:as`, `:module`, `:shallow_path`, and `:shallow_prefix` options + # all default to the name of the namespace. + # + # For options, see `Base#match`. For `:shallow_path` option, see + # `Resources#resources`. + # + # # accessible through /sekret/posts rather than /admin/posts + # namespace :admin, path: "sekret" do + # resources :posts + # end + # + # # maps to Sekret::PostsController rather than Admin::PostsController + # namespace :admin, module: "sekret" do + # resources :posts + # end + # + # # generates sekret_posts_path rather than admin_posts_path + # namespace :admin, as: "sekret" do + # resources :posts + # end + def namespace(path, options = {}, &block) + path = path.to_s + + defaults = { + module: path, + as: options.fetch(:as, path), + shallow_path: options.fetch(:path, path), + shallow_prefix: options.fetch(:as, path) + } + + path_scope(options.delete(:path) { path }) do + scope(defaults.merge!(options), &block) + end + end + + # ### Parameter Restriction + # Allows you to constrain the nested routes based on a set of rules. For + # instance, in order to change the routes to allow for a dot character in the + # `id` parameter: + # + # constraints(id: /\d+\.\d+/) do + # resources :posts + # end + # + # Now routes such as `/posts/1` will no longer be valid, but `/posts/1.1` will + # be. The `id` parameter must match the constraint passed in for this example. + # + # You may use this to also restrict other parameters: + # + # resources :posts do + # constraints(post_id: /\d+\.\d+/) do + # resources :comments + # end + # end + # + # ### Restricting based on IP + # + # Routes can also be constrained to an IP or a certain range of IP addresses: + # + # constraints(ip: /192\.168\.\d+\.\d+/) do + # resources :posts + # end + # + # Any user connecting from the 192.168.* range will be able to see this + # resource, where as any user connecting outside of this range will be told + # there is no such route. + # + # ### Dynamic request matching + # + # Requests to routes can be constrained based on specific criteria: + # + # constraints(-> (req) { /iPhone/.match?(req.env["HTTP_USER_AGENT"]) }) do + # resources :iphones + # end + # + # You are able to move this logic out into a class if it is too complex for + # routes. This class must have a `matches?` method defined on it which either + # returns `true` if the user should be given access to that route, or `false` if + # the user should not. + # + # class Iphone + # def self.matches?(request) + # /iPhone/.match?(request.env["HTTP_USER_AGENT"]) + # end + # end + # + # An expected place for this code would be `lib/constraints`. + # + # This class is then used like this: + # + # constraints(Iphone) do + # resources :iphones + # end + def constraints(constraints = {}, &block) + scope(constraints: constraints, &block) + end + + # Allows you to set default parameters for a route, such as this: + # + # defaults id: 'home' do + # match 'scoped_pages/(:id)', to: 'pages#show' + # end + # + # Using this, the `:id` parameter here will default to 'home'. + def defaults(defaults = {}) + @scope = @scope.new(defaults: merge_defaults_scope(@scope[:defaults], defaults)) + yield + ensure + @scope = @scope.parent + end + + private + def merge_path_scope(parent, child) + Mapper.normalize_path("#{parent}/#{child}") + end + + def merge_shallow_path_scope(parent, child) + Mapper.normalize_path("#{parent}/#{child}") + end + + def merge_as_scope(parent, child) + parent ? "#{parent}_#{child}" : child + end + + def merge_shallow_prefix_scope(parent, child) + parent ? "#{parent}_#{child}" : child + end + + def merge_module_scope(parent, child) + parent ? "#{parent}/#{child}" : child + end + + def merge_controller_scope(parent, child) + child + end + + def merge_action_scope(parent, child) + child + end + + def merge_via_scope(parent, child) + child + end + + def merge_format_scope(parent, child) + child + end + + def merge_path_names_scope(parent, child) + merge_options_scope(parent, child) + end + + def merge_constraints_scope(parent, child) + merge_options_scope(parent, child) + end + + def merge_defaults_scope(parent, child) + merge_options_scope(parent, child) + end + + def merge_blocks_scope(parent, child) + merged = parent ? parent.dup : [] + merged << child if child + merged + end + + def merge_options_scope(parent, child) + (parent || {}).merge(child) + end + + def merge_shallow_scope(parent, child) + child ? true : false + end + + def merge_to_scope(parent, child) + child + end + end + + # Resource routing allows you to quickly declare all of the common routes for a + # given resourceful controller. Instead of declaring separate routes for your + # `index`, `show`, `new`, `edit`, `create`, `update`, and `destroy` actions, a + # resourceful route declares them in a single line of code: + # + # resources :photos + # + # Sometimes, you have a resource that clients always look up without referencing + # an ID. A common example, /profile always shows the profile of the currently + # logged in user. In this case, you can use a singular resource to map /profile + # (rather than /profile/:id) to the show action. + # + # resource :profile + # + # It's common to have resources that are logically children of other resources: + # + # resources :magazines do + # resources :ads + # end + # + # You may wish to organize groups of controllers under a namespace. Most + # commonly, you might group a number of administrative controllers under an + # `admin` namespace. You would place these controllers under the + # `app/controllers/admin` directory, and you can group them together in your + # router: + # + # namespace "admin" do + # resources :posts, :comments + # end + # + # By default the `:id` parameter doesn't accept dots. If you need to use dots as + # part of the `:id` parameter add a constraint which overrides this restriction, + # e.g: + # + # resources :articles, id: /[^\/]+/ + # + # This allows any character other than a slash as part of your `:id`. + module Resources + # CANONICAL_ACTIONS holds all actions that does not need a prefix or a path + # appended since they fit properly in their scope level. + VALID_ON_OPTIONS = [:new, :collection, :member] + RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns] + CANONICAL_ACTIONS = %w(index create new show update destroy) + + class Resource # :nodoc: + class << self + def default_actions(api_only) + if api_only + [:index, :create, :show, :update, :destroy] + else + [:index, :create, :new, :show, :update, :destroy, :edit] + end + end + end + + attr_reader :controller, :path, :param + + def initialize(entities, api_only, shallow, options = {}) + if options[:param].to_s.include?(":") + raise ArgumentError, ":param option can't contain colons" + end + + valid_actions = self.class.default_actions(false) # ignore api_only for this validation + if invalid_actions = invalid_only_except_options(options, valid_actions).presence + error_prefix = "Route `resource#{"s" unless singleton?} :#{entities}`" + raise ArgumentError, "#{error_prefix} - :only and :except must include only #{valid_actions}, but also included #{invalid_actions}" + end + + @name = entities.to_s + @path = (options[:path] || @name).to_s + @controller = (options[:controller] || @name).to_s + @as = options[:as] + @param = (options[:param] || :id).to_sym + @options = options + @shallow = shallow + @api_only = api_only + @only = options.delete :only + @except = options.delete :except + end + + def default_actions + self.class.default_actions(@api_only) + end + + def actions + if @except + available_actions - Array(@except).map(&:to_sym) + else + available_actions + end + end + + def available_actions + if @only + Array(@only).map(&:to_sym) + else + default_actions + end + end + + def name + @as || @name + end + + def plural + @plural ||= name.to_s + end + + def singular + @singular ||= name.to_s.singularize + end + + alias :member_name :singular + + # Checks for uncountable plurals, and appends "_index" if the plural and + # singular form are the same. + def collection_name + singular == plural ? "#{plural}_index" : plural + end + + def resource_scope + controller + end + + alias :collection_scope :path + + def member_scope + "#{path}/:#{param}" + end + + alias :shallow_scope :member_scope + + def new_scope(new_path) + "#{path}/#{new_path}" + end + + def nested_param + :"#{singular}_#{param}" + end + + def nested_scope + "#{path}/:#{nested_param}" + end + + def shallow? + @shallow + end + + def singleton?; false; end + + private + def invalid_only_except_options(options, valid_actions) + options.values_at(:only, :except).flatten.compact.uniq.map(&:to_sym) - valid_actions + end + end + + class SingletonResource < Resource # :nodoc: + class << self + def default_actions(api_only) + if api_only + [:show, :create, :update, :destroy] + else + [:show, :create, :update, :destroy, :new, :edit] + end + end + end + + def initialize(entities, api_only, shallow, options) + super + @as = nil + @controller = (options[:controller] || plural).to_s + @as = options[:as] + end + + def default_actions + self.class.default_actions(@api_only) + end + + def plural + @plural ||= name.to_s.pluralize + end + + def singular + @singular ||= name.to_s + end + + alias :member_name :singular + alias :collection_name :singular + + alias :member_scope :path + alias :nested_scope :path + + def singleton?; true; end + end + + def resources_path_names(options) + @scope[:path_names].merge!(options) + end + + # Sometimes, you have a resource that clients always look up without referencing + # an ID. A common example, /profile always shows the profile of the currently + # logged in user. In this case, you can use a singular resource to map /profile + # (rather than /profile/:id) to the show action: + # + # resource :profile + # + # This creates six different routes in your application, all mapping to the + # `Profiles` controller (note that the controller is named after the plural): + # + # GET /profile/new + # GET /profile + # GET /profile/edit + # PATCH/PUT /profile + # DELETE /profile + # POST /profile + # + # If you want instances of a model to work with this resource via record + # identification (e.g. in `form_with` or `redirect_to`), you will need to call + # [resolve](rdoc-ref:CustomUrls#resolve): + # + # resource :profile + # resolve('Profile') { [:profile] } + # + # # Enables this to work with singular routes: + # form_with(model: @profile) {} + # + # ### Options + # Takes same options as [resources](rdoc-ref:#resources) + def resource(*resources, &block) + options = resources.extract_options!.dup + + if apply_common_behavior_for(:resource, resources, options, &block) + return self + end + + with_scope_level(:resource) do + options = apply_action_options :resource, options + resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], options)) do + yield if block_given? + + concerns(options[:concerns]) if options[:concerns] + + new do + get :new + end if parent_resource.actions.include?(:new) + + set_member_mappings_for_resource + + collection do + post :create + end if parent_resource.actions.include?(:create) + end + end + + self + end + + # In Rails, a resourceful route provides a mapping between HTTP verbs and URLs + # and controller actions. By convention, each action also maps to particular + # CRUD operations in a database. A single entry in the routing file, such as + # + # resources :photos + # + # creates seven different routes in your application, all mapping to the + # `Photos` controller: + # + # GET /photos + # GET /photos/new + # POST /photos + # GET /photos/:id + # GET /photos/:id/edit + # PATCH/PUT /photos/:id + # DELETE /photos/:id + # + # Resources can also be nested infinitely by using this block syntax: + # + # resources :photos do + # resources :comments + # end + # + # This generates the following comments routes: + # + # GET /photos/:photo_id/comments + # GET /photos/:photo_id/comments/new + # POST /photos/:photo_id/comments + # GET /photos/:photo_id/comments/:id + # GET /photos/:photo_id/comments/:id/edit + # PATCH/PUT /photos/:photo_id/comments/:id + # DELETE /photos/:photo_id/comments/:id + # + # ### Options + # Takes same options as [match](rdoc-ref:Base#match) as well as: + # + # :path_names + # : Allows you to change the segment component of the `edit` and `new` + # actions. Actions not specified are not changed. + # + # resources :posts, path_names: { new: "brand_new" } + # + # The above example will now change /posts/new to /posts/brand_new. + # + # :path + # : Allows you to change the path prefix for the resource. + # + # resources :posts, path: 'postings' + # + # The resource and all segments will now route to /postings instead of + # /posts. + # + # :only + # : Only generate routes for the given actions. + # + # resources :cows, only: :show + # resources :cows, only: [:show, :index] + # + # :except + # : Generate all routes except for the given actions. + # + # resources :cows, except: :show + # resources :cows, except: [:show, :index] + # + # :shallow + # : Generates shallow routes for nested resource(s). When placed on a parent + # resource, generates shallow routes for all nested resources. + # + # resources :posts, shallow: true do + # resources :comments + # end + # + # Is the same as: + # + # resources :posts do + # resources :comments, except: [:show, :edit, :update, :destroy] + # end + # resources :comments, only: [:show, :edit, :update, :destroy] + # + # This allows URLs for resources that otherwise would be deeply nested such + # as a comment on a blog post like `/posts/a-long-permalink/comments/1234` + # to be shortened to just `/comments/1234`. + # + # Set `shallow: false` on a child resource to ignore a parent's shallow + # parameter. + # + # :shallow_path + # : Prefixes nested shallow routes with the specified path. + # + # scope shallow_path: "sekret" do + # resources :posts do + # resources :comments, shallow: true + # end + # end + # + # The `comments` resource here will have the following routes generated for + # it: + # + # post_comments GET /posts/:post_id/comments(.:format) + # post_comments POST /posts/:post_id/comments(.:format) + # new_post_comment GET /posts/:post_id/comments/new(.:format) + # edit_comment GET /sekret/comments/:id/edit(.:format) + # comment GET /sekret/comments/:id(.:format) + # comment PATCH/PUT /sekret/comments/:id(.:format) + # comment DELETE /sekret/comments/:id(.:format) + # + # :shallow_prefix + # : Prefixes nested shallow route names with specified prefix. + # + # scope shallow_prefix: "sekret" do + # resources :posts do + # resources :comments, shallow: true + # end + # end + # + # The `comments` resource here will have the following routes generated for + # it: + # + # post_comments GET /posts/:post_id/comments(.:format) + # post_comments POST /posts/:post_id/comments(.:format) + # new_post_comment GET /posts/:post_id/comments/new(.:format) + # edit_sekret_comment GET /comments/:id/edit(.:format) + # sekret_comment GET /comments/:id(.:format) + # sekret_comment PATCH/PUT /comments/:id(.:format) + # sekret_comment DELETE /comments/:id(.:format) + # + # :format + # : Allows you to specify the default value for optional `format` segment or + # disable it by supplying `false`. + # + # :param + # : Allows you to override the default param name of `:id` in the URL. + # + # + # ### Examples + # + # # routes call Admin::PostsController + # resources :posts, module: "admin" + # + # # resource actions are at /admin/posts. + # resources :posts, path: "admin/posts" + def resources(*resources, &block) + options = resources.extract_options!.dup + + if apply_common_behavior_for(:resources, resources, options, &block) + return self + end + + with_scope_level(:resources) do + options = apply_action_options :resources, options + resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], options)) do + yield if block_given? + + concerns(options[:concerns]) if options[:concerns] + + collection do + get :index if parent_resource.actions.include?(:index) + post :create if parent_resource.actions.include?(:create) + end + + new do + get :new + end if parent_resource.actions.include?(:new) + + set_member_mappings_for_resource + end + end + + self + end + + # To add a route to the collection: + # + # resources :photos do + # collection do + # get 'search' + # end + # end + # + # This will enable Rails to recognize paths such as `/photos/search` with GET, + # and route to the search action of `PhotosController`. It will also create the + # `search_photos_url` and `search_photos_path` route helpers. + def collection(&block) + unless resource_scope? + raise ArgumentError, "can't use collection outside resource(s) scope" + end + + with_scope_level(:collection) do + path_scope(parent_resource.collection_scope, &block) + end + end + + # To add a member route, add a member block into the resource block: + # + # resources :photos do + # member do + # get 'preview' + # end + # end + # + # This will recognize `/photos/1/preview` with GET, and route to the preview + # action of `PhotosController`. It will also create the `preview_photo_url` and + # `preview_photo_path` helpers. + def member(&block) + unless resource_scope? + raise ArgumentError, "can't use member outside resource(s) scope" + end + + with_scope_level(:member) do + if shallow? + shallow_scope { + path_scope(parent_resource.member_scope, &block) + } + else + path_scope(parent_resource.member_scope, &block) + end + end + end + + def new(&block) + unless resource_scope? + raise ArgumentError, "can't use new outside resource(s) scope" + end + + with_scope_level(:new) do + path_scope(parent_resource.new_scope(action_path(:new)), &block) + end + end + + def nested(&block) + unless resource_scope? + raise ArgumentError, "can't use nested outside resource(s) scope" + end + + with_scope_level(:nested) do + if shallow? && shallow_nesting_depth >= 1 + shallow_scope do + path_scope(parent_resource.nested_scope) do + scope(nested_options, &block) + end + end + else + path_scope(parent_resource.nested_scope) do + scope(nested_options, &block) + end + end + end + end + + # See ActionDispatch::Routing::Mapper::Scoping#namespace. + def namespace(path, options = {}) + if resource_scope? + nested { super } + else + super + end + end + + def shallow + @scope = @scope.new(shallow: true) + yield + ensure + @scope = @scope.parent + end + + def shallow? + !parent_resource.singleton? && @scope[:shallow] + end + + # Loads another routes file with the given `name` located inside the + # `config/routes` directory. In that file, you can use the normal routing DSL, + # but *do not* surround it with a `Rails.application.routes.draw` block. + # + # # config/routes.rb + # Rails.application.routes.draw do + # draw :admin # Loads `config/routes/admin.rb` + # draw "third_party/some_gem" # Loads `config/routes/third_party/some_gem.rb` + # end + # + # # config/routes/admin.rb + # namespace :admin do + # resources :accounts + # end + # + # # config/routes/third_party/some_gem.rb + # mount SomeGem::Engine, at: "/some_gem" + # + # **CAUTION:** Use this feature with care. Having multiple routes files can + # negatively impact discoverability and readability. For most applications — + # even those with a few hundred routes — it's easier for developers to have a + # single routes file. + def draw(name) + path = @draw_paths.find do |_path| + File.exist? "#{_path}/#{name}.rb" + end + + unless path + msg = "Your router tried to #draw the external file #{name}.rb,\n" \ + "but the file was not found in:\n\n" + msg += @draw_paths.map { |_path| " * #{_path}" }.join("\n") + raise ArgumentError, msg + end + + route_path = "#{path}/#{name}.rb" + instance_eval(File.read(route_path), route_path.to_s) + end + + # Matches a URL pattern to one or more routes. For more information, see + # [match](rdoc-ref:Base#match). + # + # match 'path', to: 'controller#action', via: :post + # match 'path', 'otherpath', on: :member, via: :get + def match(path, *rest, &block) + if rest.empty? && Hash === path + options = path + path, to = options.find { |name, _value| name.is_a?(String) } + + raise ArgumentError, "Route path not specified" if path.nil? + + case to + when Symbol + options[:action] = to + when String + if to.include?("#") + options[:to] = to + else + options[:controller] = to + end + else + options[:to] = to + end + + options.delete(path) + paths = [path] + else + options = rest.pop || {} + paths = [path] + rest + end + + if options.key?(:defaults) + defaults(options.delete(:defaults)) { map_match(paths, options, &block) } + else + map_match(paths, options, &block) + end + end + + # You can specify what Rails should route "/" to with the root method: + # + # root to: 'pages#main' + # + # For options, see `match`, as `root` uses it internally. + # + # You can also pass a string which will expand + # + # root 'pages#main' + # + # You should put the root route at the top of `config/routes.rb`, because this + # means it will be matched first. As this is the most popular route of most + # Rails applications, this is beneficial. + def root(path, options = {}) + if path.is_a?(String) + options[:to] = path + elsif path.is_a?(Hash) && options.empty? + options = path + else + raise ArgumentError, "must be called with a path and/or options" + end + + if @scope.resources? + with_scope_level(:root) do + path_scope(parent_resource.path) do + match_root_route(options) + end + end + else + match_root_route(options) + end + end + + private + def parent_resource + @scope[:scope_level_resource] + end + + def apply_common_behavior_for(method, resources, options, &block) + if resources.length > 1 + resources.each { |r| public_send(method, r, options, &block) } + return true + end + + if options[:shallow] + options.delete(:shallow) + shallow do + public_send(method, resources.pop, options, &block) + end + return true + end + + if resource_scope? + nested { public_send(method, resources.pop, options, &block) } + return true + end + + options.keys.each do |k| + (options[:constraints] ||= {})[k] = options.delete(k) if options[k].is_a?(Regexp) + end + + scope_options = options.slice!(*RESOURCE_OPTIONS) + unless scope_options.empty? + scope(scope_options) do + public_send(method, resources.pop, options, &block) + end + return true + end + + false + end + + def apply_action_options(method, options) + return options if action_options? options + options.merge scope_action_options(method) + end + + def action_options?(options) + options[:only] || options[:except] + end + + def scope_action_options(method) + return {} unless @scope[:action_options] + + actions = applicable_actions_for(method) + @scope[:action_options].dup.tap do |options| + (options[:only] = Array(options[:only]) & actions) if options[:only] + (options[:except] = Array(options[:except]) & actions) if options[:except] + end + end + + def applicable_actions_for(method) + case method + when :resource + SingletonResource.default_actions(api_only?) + when :resources + Resource.default_actions(api_only?) + end + end + + def resource_scope? + @scope.resource_scope? + end + + def resource_method_scope? + @scope.resource_method_scope? + end + + def nested_scope? + @scope.nested? + end + + def with_scope_level(kind) # :doc: + @scope = @scope.new_level(kind) + yield + ensure + @scope = @scope.parent + end + + def resource_scope(resource, &block) + @scope = @scope.new(scope_level_resource: resource) + + controller(resource.resource_scope, &block) + ensure + @scope = @scope.parent + end + + def nested_options + options = { as: parent_resource.member_name } + options[:constraints] = { + parent_resource.nested_param => param_constraint + } if param_constraint? + + options + end + + def shallow_nesting_depth + @scope.find_all { |node| + node.frame[:scope_level_resource] + }.count { |node| node.frame[:scope_level_resource].shallow? } + end + + def param_constraint? + @scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp) + end + + def param_constraint + @scope[:constraints][parent_resource.param] + end + + def canonical_action?(action) + resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s) + end + + def shallow_scope + scope = { as: @scope[:shallow_prefix], + path: @scope[:shallow_path] } + @scope = @scope.new scope + + yield + ensure + @scope = @scope.parent + end + + def path_for_action(action, path) + return "#{@scope[:path]}/#{path}" if path + + if canonical_action?(action) + @scope[:path].to_s + else + "#{@scope[:path]}/#{action_path(action)}" + end + end + + def action_path(name) + @scope[:path_names][name.to_sym] || name + end + + def prefix_name_for_action(as, action) + if as + prefix = as + elsif !canonical_action?(action) + prefix = action + end + + if prefix && prefix != "/" && !prefix.empty? + Mapper.normalize_name prefix.to_s.tr("-", "_") + end + end + + def name_for_action(as, action) + prefix = prefix_name_for_action(as, action) + name_prefix = @scope[:as] + + if parent_resource + return nil unless as || action + + collection_name = parent_resource.collection_name + member_name = parent_resource.member_name + end + + action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name) + candidate = action_name.select(&:present?).join("_") + + unless candidate.empty? + # If a name was not explicitly given, we check if it is valid and return nil in + # case it isn't. Otherwise, we pass the invalid name forward so the underlying + # router engine treats it and raises an exception. + if as.nil? + candidate unless !candidate.match?(/\A[_a-z]/i) || has_named_route?(candidate) + else + candidate + end + end + end + + def set_member_mappings_for_resource # :doc: + member do + get :edit if parent_resource.actions.include?(:edit) + get :show if parent_resource.actions.include?(:show) + if parent_resource.actions.include?(:update) + patch :update + put :update + end + delete :destroy if parent_resource.actions.include?(:destroy) + end + end + + def api_only? # :doc: + @set.api_only? + end + + def path_scope(path) + @scope = @scope.new(path: merge_path_scope(@scope[:path], path)) + yield + ensure + @scope = @scope.parent + end + + def map_match(paths, options) + ActionDispatch.deprecator.warn(<<-MSG.squish) if paths.count > 1 + Mapping a route with multiple paths is deprecated and + will be removed in Rails 8.1. Please use multiple method calls instead. + MSG + + if (on = options[:on]) && !VALID_ON_OPTIONS.include?(on) + raise ArgumentError, "Unknown scope #{on.inspect} given to :on" + end + + if @scope[:to] + options[:to] ||= @scope[:to] + end + + if @scope[:controller] && @scope[:action] + options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}" + end + + controller = options.delete(:controller) || @scope[:controller] + option_path = options.delete :path + to = options.delete :to + via = Mapping.check_via Array(options.delete(:via) { + @scope[:via] + }) + formatted = options.delete(:format) { @scope[:format] } + anchor = options.delete(:anchor) { true } + options_constraints = options.delete(:constraints) || {} + + path_types = paths.group_by(&:class) + (path_types[String] || []).each do |_path| + route_options = options.dup + if _path && option_path + raise ArgumentError, "Ambiguous route definition. Both :path and the route path were specified as strings." + end + to = get_to_from_path(_path, to, route_options[:action]) + decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints) + end + + (path_types[Symbol] || []).each do |action| + route_options = options.dup + decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints) + end + + self + end + + def get_to_from_path(path, to, action) + return to if to || action + + path_without_format = path.sub(/\(\.:format\)$/, "") + if using_match_shorthand?(path_without_format) + path_without_format.delete_prefix("/").sub(%r{/([^/]*)$}, '#\1').tr("-", "_") + else + nil + end + end + + def using_match_shorthand?(path) + %r{^/?[-\w]+/[-\w/]+$}.match?(path) + end + + def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) + if on = options.delete(:on) + send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) } + else + case @scope.scope_level + when :resources + nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) } + when :resource + member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) } + else + add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints) + end + end + end + + def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints) + path = path_for_action(action, _path) + raise ArgumentError, "path is required" if path.blank? + + action = action.to_s + + default_action = options.delete(:action) || @scope[:action] + + if /^[\w\-\/]+$/.match?(action) + default_action ||= action.tr("-", "_") unless action.include?("/") + else + action = nil + end + + as = if !options.fetch(:as, true) # if it's set to nil or false + options.delete(:as) + else + name_for_action(options.delete(:as), action) + end + + path = Mapping.normalize_path URI::RFC2396_PARSER.escape(path), formatted + ast = Journey::Parser.parse path + + mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options) + @set.add_route(mapping, as) + end + + def match_root_route(options) + args = ["/", { as: :root, via: :get }.merge(options)] + match(*args) + end + end + + # Routing Concerns allow you to declare common routes that can be reused inside + # others resources and routes. + # + # concern :commentable do + # resources :comments + # end + # + # concern :image_attachable do + # resources :images, only: :index + # end + # + # These concerns are used in Resources routing: + # + # resources :messages, concerns: [:commentable, :image_attachable] + # + # or in a scope or namespace: + # + # namespace :posts do + # concerns :commentable + # end + module Concerns + # Define a routing concern using a name. + # + # Concerns may be defined inline, using a block, or handled by another object, + # by passing that object as the second parameter. + # + # The concern object, if supplied, should respond to `call`, which will receive + # two parameters: + # + # * The current mapper + # * A hash of options which the concern object may use + # + # Options may also be used by concerns defined in a block by accepting a block + # parameter. So, using a block, you might do something as simple as limit the + # actions available on certain resources, passing standard resource options + # through the concern: + # + # concern :commentable do |options| + # resources :comments, options + # end + # + # resources :posts, concerns: :commentable + # resources :archived_posts do + # # Don't allow comments on archived posts + # concerns :commentable, only: [:index, :show] + # end + # + # Or, using a callable object, you might implement something more specific to + # your application, which would be out of place in your routes file. + # + # # purchasable.rb + # class Purchasable + # def initialize(defaults = {}) + # @defaults = defaults + # end + # + # def call(mapper, options = {}) + # options = @defaults.merge(options) + # mapper.resources :purchases + # mapper.resources :receipts + # mapper.resources :returns if options[:returnable] + # end + # end + # + # # routes.rb + # concern :purchasable, Purchasable.new(returnable: true) + # + # resources :toys, concerns: :purchasable + # resources :electronics, concerns: :purchasable + # resources :pets do + # concerns :purchasable, returnable: false + # end + # + # Any routing helpers can be used inside a concern. If using a callable, they're + # accessible from the Mapper that's passed to `call`. + def concern(name, callable = nil, &block) + callable ||= lambda { |mapper, options| mapper.instance_exec(options, &block) } + @concerns[name] = callable + end + + # Use the named concerns + # + # resources :posts do + # concerns :commentable + # end + # + # Concerns also work in any routes helper that you want to use: + # + # namespace :posts do + # concerns :commentable + # end + def concerns(*args) + options = args.extract_options! + args.flatten.each do |name| + if concern = @concerns[name] + concern.call(self, options) + else + raise ArgumentError, "No concern named #{name} was found!" + end + end + end + end + + module CustomUrls + # Define custom URL helpers that will be added to the application's routes. This + # allows you to override and/or replace the default behavior of routing helpers, + # e.g: + # + # direct :homepage do + # "https://rubyonrails.org" + # end + # + # direct :commentable do |model| + # [ model, anchor: model.dom_id ] + # end + # + # direct :main do + # { controller: "pages", action: "index", subdomain: "www" } + # end + # + # The return value from the block passed to `direct` must be a valid set of + # arguments for `url_for` which will actually build the URL string. This can be + # one of the following: + # + # * A string, which is treated as a generated URL + # * A hash, e.g. `{ controller: "pages", action: "index" }` + # * An array, which is passed to `polymorphic_url` + # * An Active Model instance + # * An Active Model class + # + # + # NOTE: Other URL helpers can be called in the block but be careful not to + # invoke your custom URL helper again otherwise it will result in a stack + # overflow error. + # + # You can also specify default options that will be passed through to your URL + # helper definition, e.g: + # + # direct :browse, page: 1, size: 10 do |options| + # [ :products, options.merge(params.permit(:page, :size).to_h.symbolize_keys) ] + # end + # + # In this instance the `params` object comes from the context in which the block + # is executed, e.g. generating a URL inside a controller action or a view. If + # the block is executed where there isn't a `params` object such as this: + # + # Rails.application.routes.url_helpers.browse_path + # + # then it will raise a `NameError`. Because of this you need to be aware of the + # context in which you will use your custom URL helper when defining it. + # + # NOTE: The `direct` method can't be used inside of a scope block such as + # `namespace` or `scope` and will raise an error if it detects that it is. + def direct(name, options = {}, &block) + unless @scope.root? + raise RuntimeError, "The direct method can't be used inside a routes scope block" + end + + @set.add_url_helper(name, options, &block) + end + + # Define custom polymorphic mappings of models to URLs. This alters the behavior + # of `polymorphic_url` and consequently the behavior of `link_to`, `form_with` + # and `form_for` when passed a model instance, e.g: + # + # resource :basket + # + # resolve "Basket" do + # [:basket] + # end + # + # This will now generate "/basket" when a `Basket` instance is passed to + # `link_to`, `form_with` or `form_for` instead of the standard "/baskets/:id". + # + # NOTE: This custom behavior only applies to simple polymorphic URLs where a + # single model instance is passed and not more complicated forms, e.g: + # + # # config/routes.rb + # resource :profile + # namespace :admin do + # resources :users + # end + # + # resolve("User") { [:profile] } + # + # # app/views/application/_menu.html.erb + # link_to "Profile", @current_user + # link_to "Profile", [:admin, @current_user] + # + # The first `link_to` will generate "/profile" but the second will generate the + # standard polymorphic URL of "/admin/users/1". + # + # You can pass options to a polymorphic mapping - the arity for the block needs + # to be two as the instance is passed as the first argument, e.g: + # + # resolve "Basket", anchor: "items" do |basket, options| + # [:basket, options] + # end + # + # This generates the URL "/basket#items" because when the last item in an array + # passed to `polymorphic_url` is a hash then it's treated as options to the URL + # helper that gets called. + # + # NOTE: The `resolve` method can't be used inside of a scope block such as + # `namespace` or `scope` and will raise an error if it detects that it is. + def resolve(*args, &block) + unless @scope.root? + raise RuntimeError, "The resolve method can't be used inside a routes scope block" + end + + options = args.extract_options! + args = args.flatten(1) + + args.each do |klass| + @set.add_polymorphic_mapping(klass, options, &block) + end + end + end + + class Scope # :nodoc: + OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module, + :controller, :action, :path_names, :constraints, + :shallow, :blocks, :defaults, :via, :format, :options, :to] + + RESOURCE_SCOPES = [:resource, :resources] + RESOURCE_METHOD_SCOPES = [:collection, :member, :new] + + attr_reader :parent, :scope_level + + def initialize(hash, parent = ROOT, scope_level = nil) + @parent = parent + @hash = parent ? parent.frame.merge(hash) : hash + @scope_level = scope_level + end + + def nested? + scope_level == :nested + end + + def null? + @hash.nil? && @parent.nil? + end + + def root? + @parent == ROOT + end + + def resources? + scope_level == :resources + end + + def resource_method_scope? + RESOURCE_METHOD_SCOPES.include? scope_level + end + + def action_name(name_prefix, prefix, collection_name, member_name) + case scope_level + when :nested + [name_prefix, prefix] + when :collection + [prefix, name_prefix, collection_name] + when :new + [prefix, :new, name_prefix, member_name] + when :member + [prefix, name_prefix, member_name] + when :root + [name_prefix, collection_name, prefix] + else + [name_prefix, member_name, prefix] + end + end + + def resource_scope? + RESOURCE_SCOPES.include? scope_level + end + + def options + OPTIONS + end + + def new(hash) + self.class.new hash, self, scope_level + end + + def new_level(level) + self.class.new(frame, self, level) + end + + def [](key) + frame[key] + end + + def frame; @hash; end + + include Enumerable + + def each + node = self + until node.equal? ROOT + yield node + node = node.parent + end + end + + ROOT = Scope.new({}, nil) + end + + def initialize(set) # :nodoc: + @set = set + @draw_paths = set.draw_paths + @scope = Scope.new(path_names: @set.resources_path_names) + @concerns = {} + end + + include Base + include HttpHelpers + include Redirection + include Scoping + include Concerns + include Resources + include CustomUrls + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/polymorphic_routes.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/polymorphic_routes.rb new file mode 100644 index 00000000..02c8a84d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/polymorphic_routes.rb @@ -0,0 +1,363 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + module Routing + # # Action Dispatch Routing PolymorphicRoutes + # + # Polymorphic URL helpers are methods for smart resolution to a named route call + # when given an Active Record model instance. They are to be used in combination + # with ActionController::Resources. + # + # These methods are useful when you want to generate the correct URL or path to + # a RESTful resource without having to know the exact type of the record in + # question. + # + # Nested resources and/or namespaces are also supported, as illustrated in the + # example: + # + # polymorphic_url([:admin, @article, @comment]) + # + # results in: + # + # admin_article_comment_url(@article, @comment) + # + # ## Usage within the framework + # + # Polymorphic URL helpers are used in a number of places throughout the Rails + # framework: + # + # * `url_for`, so you can use it with a record as the argument, e.g. + # `url_for(@article)`; + # * ActionView::Helpers::FormHelper uses `polymorphic_path`, so you can write + # `form_with(model: @article)` without having to specify `:url` parameter for the + # form action; + # * `redirect_to` (which, in fact, uses `url_for`) so you can write + # `redirect_to(post)` in your controllers; + # * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly + # specify URLs for feed entries. + # + # + # ## Prefixed polymorphic helpers + # + # In addition to `polymorphic_url` and `polymorphic_path` methods, a number of + # prefixed helpers are available as a shorthand to `action: "..."` in options. + # Those are: + # + # * `edit_polymorphic_url`, `edit_polymorphic_path` + # * `new_polymorphic_url`, `new_polymorphic_path` + # + # + # Example usage: + # + # edit_polymorphic_path(@post) # => "/posts/1/edit" + # polymorphic_path(@post, format: :pdf) # => "/posts/1.pdf" + # + # ## Usage with mounted engines + # + # If you are using a mounted engine and you need to use a polymorphic_url + # pointing at the engine's routes, pass in the engine's route proxy as the first + # argument to the method. For example: + # + # polymorphic_url([blog, @post]) # calls blog.post_path(@post) + # form_with(model: [blog, @post]) # => "/blog/posts/1" + # + module PolymorphicRoutes + # Constructs a call to a named RESTful route for the given record and returns + # the resulting URL string. For example: + # + # # calls post_url(post) + # polymorphic_url(post) # => "http://example.com/posts/1" + # polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1" + # polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1" + # polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1" + # polymorphic_url(Comment) # => "http://example.com/comments" + # + # #### Options + # + # * `:action` - Specifies the action prefix for the named route: `:new` or + # `:edit`. Default is no prefix. + # * `:routing_type` - Allowed values are `:path` or `:url`. Default is `:url`. + # + # + # Also includes all the options from `url_for`. These include such things as + # `:anchor` or `:trailing_slash`. Example usage is given below: + # + # polymorphic_url([blog, post], anchor: 'my_anchor') + # # => "http://example.com/blogs/1/posts/1#my_anchor" + # polymorphic_url([blog, post], anchor: 'my_anchor', script_name: "/my_app") + # # => "http://example.com/my_app/blogs/1/posts/1#my_anchor" + # + # For all of these options, see the documentation for + # [url_for](rdoc-ref:ActionDispatch::Routing::UrlFor). + # + # #### Functionality + # + # # an Article record + # polymorphic_url(record) # same as article_url(record) + # + # # a Comment record + # polymorphic_url(record) # same as comment_url(record) + # + # # it recognizes new records and maps to the collection + # record = Comment.new + # polymorphic_url(record) # same as comments_url() + # + # # the class of a record will also map to the collection + # polymorphic_url(Comment) # same as comments_url() + # + def polymorphic_url(record_or_hash_or_array, options = {}) + if Hash === record_or_hash_or_array + options = record_or_hash_or_array.merge(options) + record = options.delete :id + return polymorphic_url record, options + end + + if mapping = polymorphic_mapping(record_or_hash_or_array) + return mapping.call(self, [record_or_hash_or_array, options], false) + end + + opts = options.dup + action = opts.delete :action + type = opts.delete(:routing_type) || :url + + HelperMethodBuilder.polymorphic_method self, + record_or_hash_or_array, + action, + type, + opts + end + + # Returns the path component of a URL for the given record. + def polymorphic_path(record_or_hash_or_array, options = {}) + if Hash === record_or_hash_or_array + options = record_or_hash_or_array.merge(options) + record = options.delete :id + return polymorphic_path record, options + end + + if mapping = polymorphic_mapping(record_or_hash_or_array) + return mapping.call(self, [record_or_hash_or_array, options], true) + end + + opts = options.dup + action = opts.delete :action + type = :path + + HelperMethodBuilder.polymorphic_method self, + record_or_hash_or_array, + action, + type, + opts + end + + %w(edit new).each do |action| + module_eval <<-EOT, __FILE__, __LINE__ + 1 + # frozen_string_literal: true + def #{action}_polymorphic_url(record_or_hash, options = {}) + polymorphic_url_for_action("#{action}", record_or_hash, options) + end + + def #{action}_polymorphic_path(record_or_hash, options = {}) + polymorphic_path_for_action("#{action}", record_or_hash, options) + end + EOT + end + + private + def polymorphic_url_for_action(action, record_or_hash, options) + polymorphic_url(record_or_hash, options.merge(action: action)) + end + + def polymorphic_path_for_action(action, record_or_hash, options) + polymorphic_path(record_or_hash, options.merge(action: action)) + end + + def polymorphic_mapping(record) + if record.respond_to?(:to_model) + _routes.polymorphic_mappings[record.to_model.model_name.name] + else + _routes.polymorphic_mappings[record.class.name] + end + end + + class HelperMethodBuilder # :nodoc: + CACHE = { path: {}, url: {} } + + def self.get(action, type) + type = type.to_sym + CACHE[type].fetch(action) { build action, type } + end + + def self.url; CACHE[:url][nil]; end + def self.path; CACHE[:path][nil]; end + + def self.build(action, type) + prefix = action ? "#{action}_" : "" + suffix = type + if action.to_s == "new" + HelperMethodBuilder.singular prefix, suffix + else + HelperMethodBuilder.plural prefix, suffix + end + end + + def self.singular(prefix, suffix) + new(->(name) { name.singular_route_key }, prefix, suffix) + end + + def self.plural(prefix, suffix) + new(->(name) { name.route_key }, prefix, suffix) + end + + def self.polymorphic_method(recipient, record_or_hash_or_array, action, type, options) + builder = get action, type + + case record_or_hash_or_array + when Array + record_or_hash_or_array = record_or_hash_or_array.compact + if record_or_hash_or_array.empty? + raise ArgumentError, "Nil location provided. Can't build URI." + end + if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy) + recipient = record_or_hash_or_array.shift + end + + method, args = builder.handle_list record_or_hash_or_array + when String, Symbol + method, args = builder.handle_string record_or_hash_or_array + when Class + method, args = builder.handle_class record_or_hash_or_array + + when nil + raise ArgumentError, "Nil location provided. Can't build URI." + else + method, args = builder.handle_model record_or_hash_or_array + end + + if options.empty? + recipient.public_send(method, *args) + else + recipient.public_send(method, *args, options) + end + end + + attr_reader :suffix, :prefix + + def initialize(key_strategy, prefix, suffix) + @key_strategy = key_strategy + @prefix = prefix + @suffix = suffix + end + + def handle_string(record) + [get_method_for_string(record), []] + end + + def handle_string_call(target, str) + target.public_send get_method_for_string str + end + + def handle_class(klass) + [get_method_for_class(klass), []] + end + + def handle_class_call(target, klass) + target.public_send get_method_for_class klass + end + + def handle_model(record) + args = [] + + model = record.to_model + named_route = if model.persisted? + args << model + get_method_for_string model.model_name.singular_route_key + else + get_method_for_class model + end + + [named_route, args] + end + + def handle_model_call(target, record) + if mapping = polymorphic_mapping(target, record) + mapping.call(target, [record], suffix == "path") + else + method, args = handle_model(record) + target.public_send(method, *args) + end + end + + def handle_list(list) + record_list = list.dup + record = record_list.pop + + args = [] + + route = record_list.map do |parent| + case parent + when Symbol + parent.to_s + when String + raise(ArgumentError, "Please use symbols for polymorphic route arguments.") + when Class + args << parent + parent.model_name.singular_route_key + else + args << parent.to_model + parent.to_model.model_name.singular_route_key + end + end + + route << + case record + when Symbol + record.to_s + when String + raise(ArgumentError, "Please use symbols for polymorphic route arguments.") + when Class + @key_strategy.call record.model_name + else + model = record.to_model + if model.persisted? + args << model + model.model_name.singular_route_key + else + @key_strategy.call model.model_name + end + end + + route << suffix + + named_route = prefix + route.join("_") + [named_route, args] + end + + private + def polymorphic_mapping(target, record) + if record.respond_to?(:to_model) + target._routes.polymorphic_mappings[record.to_model.model_name.name] + else + target._routes.polymorphic_mappings[record.class.name] + end + end + + def get_method_for_class(klass) + name = @key_strategy.call klass.model_name + get_method_for_string name + end + + def get_method_for_string(str) + "#{prefix}#{str}_#{suffix}" + end + + [nil, "new", "edit"].each do |action| + CACHE[:url][action] = build action, "url" + CACHE[:path][action] = build action, "path" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/redirection.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/redirection.rb new file mode 100644 index 00000000..83c9e58b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/redirection.rb @@ -0,0 +1,218 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/core_ext/array/extract_options" +require "rack/utils" +require "action_controller/metal/exceptions" +require "action_dispatch/routing/endpoint" + +module ActionDispatch + module Routing + class Redirect < Endpoint # :nodoc: + attr_reader :status, :block + + def initialize(status, block) + @status = status + @block = block + end + + def redirect?; true; end + + def call(env) + ActiveSupport::Notifications.instrument("redirect.action_dispatch") do |payload| + request = Request.new(env) + response = build_response(request) + + payload[:status] = @status + payload[:location] = response.headers["Location"] + payload[:request] = request + + response.to_a + end + end + + def build_response(req) + uri = URI.parse(path(req.path_parameters, req)) + + unless uri.host + if relative_path?(uri.path) + uri.path = "#{req.script_name}/#{uri.path}" + elsif uri.path.empty? + uri.path = req.script_name.empty? ? "/" : req.script_name + end + end + + uri.scheme ||= req.scheme + uri.host ||= req.host + uri.port ||= req.port unless req.standard_port? + + req.commit_flash + + body = "" + + headers = { + "Location" => uri.to_s, + "Content-Type" => "text/html; charset=#{ActionDispatch::Response.default_charset}", + "Content-Length" => body.length.to_s + } + + ActionDispatch::Response.new(status, headers, body) + end + + def path(params, request) + block.call params, request + end + + def inspect + "redirect(#{status})" + end + + private + def relative_path?(path) + path && !path.empty? && !path.start_with?("/") + end + + def escape(params) + params.transform_values { |v| Rack::Utils.escape(v) } + end + + def escape_fragment(params) + params.transform_values { |v| Journey::Router::Utils.escape_fragment(v) } + end + + def escape_path(params) + params.transform_values { |v| Journey::Router::Utils.escape_path(v) } + end + end + + class PathRedirect < Redirect + URL_PARTS = /\A([^?]+)?(\?[^#]+)?(#.+)?\z/ + + def path(params, request) + if block.match(URL_PARTS) + path = interpolation_required?($1, params) ? $1 % escape_path(params) : $1 + query = interpolation_required?($2, params) ? $2 % escape(params) : $2 + fragment = interpolation_required?($3, params) ? $3 % escape_fragment(params) : $3 + + "#{path}#{query}#{fragment}" + else + interpolation_required?(block, params) ? block % escape(params) : block + end + end + + def inspect + "redirect(#{status}, #{block})" + end + + private + def interpolation_required?(string, params) + !params.empty? && string && string.match(/%\{\w*\}/) + end + end + + class OptionRedirect < Redirect # :nodoc: + alias :options :block + + def path(params, request) + url_options = { + protocol: request.protocol, + host: request.host, + port: request.optional_port, + path: request.path, + params: request.query_parameters + }.merge! options + + if !params.empty? && url_options[:path].match(/%\{\w*\}/) + url_options[:path] = (url_options[:path] % escape_path(params)) + end + + unless options[:host] || options[:domain] + if relative_path?(url_options[:path]) + url_options[:path] = "/#{url_options[:path]}" + url_options[:script_name] = request.script_name + elsif url_options[:path].empty? + url_options[:path] = request.script_name.empty? ? "/" : "" + url_options[:script_name] = request.script_name + end + end + + ActionDispatch::Http::URL.url_for url_options + end + + def inspect + "redirect(#{status}, #{options.map { |k, v| "#{k}: #{v}" }.join(', ')})" + end + end + + module Redirection + # Redirect any path to another path: + # + # get "/stories" => redirect("/posts") + # + # This will redirect the user, while ignoring certain parts of the request, + # including query string, etc. `/stories`, `/stories?foo=bar`, etc all redirect + # to `/posts`. + # + # The redirect will use a `301 Moved Permanently` status code by default. This + # can be overridden with the `:status` option: + # + # get "/stories" => redirect("/posts", status: 307) + # + # You can also use interpolation in the supplied redirect argument: + # + # get 'docs/:article', to: redirect('/wiki/%{article}') + # + # Note that if you return a path without a leading slash then the URL is + # prefixed with the current SCRIPT_NAME environment variable. This is typically + # '/' but may be different in a mounted engine or where the application is + # deployed to a subdirectory of a website. + # + # Alternatively you can use one of the other syntaxes: + # + # The block version of redirect allows for the easy encapsulation of any logic + # associated with the redirect in question. Either the params and request are + # supplied as arguments, or just params, depending of how many arguments your + # block accepts. A string is required as a return value. + # + # get 'jokes/:number', to: redirect { |params, request| + # path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp") + # "http://#{request.host_with_port}/#{path}" + # } + # + # Note that the `do end` syntax for the redirect block wouldn't work, as Ruby + # would pass the block to `get` instead of `redirect`. Use `{ ... }` instead. + # + # The options version of redirect allows you to supply only the parts of the URL + # which need to change, it also supports interpolation of the path similar to + # the first example. + # + # get 'stores/:name', to: redirect(subdomain: 'stores', path: '/%{name}') + # get 'stores/:name(*all)', to: redirect(subdomain: 'stores', path: '/%{name}%{all}') + # get '/stories', to: redirect(path: '/posts') + # + # This will redirect the user, while changing only the specified parts of the + # request, for example the `path` option in the last example. `/stories`, + # `/stories?foo=bar`, redirect to `/posts` and `/posts?foo=bar` respectively. + # + # Finally, an object which responds to call can be supplied to redirect, + # allowing you to reuse common redirect routes. The call method must accept two + # arguments, params and request, and return a string. + # + # get 'accounts/:name' => redirect(SubdomainRedirector.new('api')) + # + def redirect(*args, &block) + options = args.extract_options! + status = options.delete(:status) || 301 + path = args.shift + + return OptionRedirect.new(status, options) if options.any? + return PathRedirect.new(status, path) if String === path + + block = path if path.respond_to? :call + raise ArgumentError, "redirection argument not supported" unless block + Redirect.new status, block + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/route_set.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/route_set.rb new file mode 100644 index 00000000..da8a6ad6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/route_set.rb @@ -0,0 +1,958 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "action_dispatch/journey" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/module/redefine_method" +require "active_support/core_ext/module/remove_method" +require "active_support/core_ext/array/extract_options" +require "action_controller/metal/exceptions" +require "action_dispatch/routing/endpoint" + +module ActionDispatch + module Routing + # The RouteSet contains a collection of Route instances, representing the routes + # typically defined in `config/routes.rb`. + class RouteSet + # Returns a Route matching the given requirements, or `nil` if none are found. + # + # This is intended for use by tools such as Language Servers. + # + # Given the routes are defined as: + # + # resources :posts + # + # Then the following will return the Route for the `show` action: + # + # Rails.application.routes.from_requirements(controller: "posts", action: "show") + def from_requirements(requirements) + routes.find { |route| route.requirements == requirements } + end + # :stopdoc: + + # Since the router holds references to many parts of the system like engines, + # controllers and the application itself, inspecting the route set can actually + # be really slow, therefore we default alias inspect to to_s. + alias inspect to_s + + class Dispatcher < Routing::Endpoint + def initialize(raise_on_name_error) + @raise_on_name_error = raise_on_name_error + end + + def dispatcher?; true; end + + def serve(req) + params = req.path_parameters + controller = controller req + res = controller.make_response! req + dispatch(controller, params[:action], req, res) + rescue ActionController::RoutingError + if @raise_on_name_error + raise + else + [404, { Constants::X_CASCADE => "pass" }, []] + end + end + + private + def controller(req) + req.controller_class + rescue NameError => e + raise ActionController::RoutingError, e.message, e.backtrace + end + + def dispatch(controller, action, req, res) + controller.dispatch(action, req, res) + end + end + + class StaticDispatcher < Dispatcher + def initialize(controller_class) + super(false) + @controller_class = controller_class + end + + private + def controller(_); @controller_class; end + end + + # A NamedRouteCollection instance is a collection of named routes, and also + # maintains an anonymous module that can be used to install helpers for the + # named routes. + class NamedRouteCollection + include Enumerable + attr_reader :routes, :url_helpers_module, :path_helpers_module + private :routes + + def initialize + @routes = {} + @path_helpers = Set.new + @url_helpers = Set.new + @url_helpers_module = Module.new + @path_helpers_module = Module.new + end + + def route_defined?(name) + key = name.to_sym + @path_helpers.include?(key) || @url_helpers.include?(key) + end + + def helper_names + @path_helpers.map(&:to_s) + @url_helpers.map(&:to_s) + end + + def clear! + @path_helpers.each do |helper| + @path_helpers_module.remove_method helper + end + + @url_helpers.each do |helper| + @url_helpers_module.remove_method helper + end + + @routes.clear + @path_helpers.clear + @url_helpers.clear + end + + def add(name, route) + key = name.to_sym + path_name = :"#{name}_path" + url_name = :"#{name}_url" + + if routes.key? key + @path_helpers_module.undef_method path_name + @url_helpers_module.undef_method url_name + end + routes[key] = route + + helper = UrlHelper.create(route, route.defaults, name) + define_url_helper @path_helpers_module, path_name, helper, PATH + define_url_helper @url_helpers_module, url_name, helper, UNKNOWN + + @path_helpers << path_name + @url_helpers << url_name + end + + def get(name) + routes[name.to_sym] + end + + def key?(name) + return unless name + routes.key? name.to_sym + end + + alias []= add + alias [] get + alias clear clear! + + def each(&block) + routes.each(&block) + self + end + + def names + routes.keys + end + + def length + routes.length + end + + # Given a `name`, defines name_path and name_url helpers. Used by 'direct', + # 'resolve', and 'polymorphic' route helpers. + def add_url_helper(name, defaults, &block) + helper = CustomUrlHelper.new(name, defaults, &block) + path_name = :"#{name}_path" + url_name = :"#{name}_url" + + @path_helpers_module.module_eval do + redefine_method(path_name) do |*args| + helper.call(self, args, true) + end + end + + @url_helpers_module.module_eval do + redefine_method(url_name) do |*args| + helper.call(self, args, false) + end + end + + @path_helpers << path_name + @url_helpers << url_name + + self + end + + class UrlHelper + def self.create(route, options, route_name) + if optimize_helper?(route) + OptimizedUrlHelper.new(route, options, route_name) + else + new(route, options, route_name) + end + end + + def self.optimize_helper?(route) + route.path.requirements.empty? && !route.glob? + end + + attr_reader :route_name + + class OptimizedUrlHelper < UrlHelper + attr_reader :arg_size + + def initialize(route, options, route_name) + super + @required_parts = @route.required_parts + @arg_size = @required_parts.size + end + + def call(t, method_name, args, inner_options, url_strategy) + if args.size == arg_size && !inner_options && optimize_routes_generation?(t) + options = t.url_options.merge @options + path = optimized_helper(args) + path << "/" if options[:trailing_slash] && !path.end_with?("/") + options[:path] = path + + original_script_name = options.delete(:original_script_name) + script_name = t._routes.find_script_name(options) + + if original_script_name + script_name = original_script_name + script_name + end + + options[:script_name] = script_name + + url_strategy.call options + else + super + end + end + + private + def optimized_helper(args) + params = parameterize_args(args) do + raise_generation_error(args) + end + + @route.format params + end + + def optimize_routes_generation?(t) + t.send(:optimize_routes_generation?) + end + + def parameterize_args(args) + params = {} + @arg_size.times { |i| + key = @required_parts[i] + value = args[i].to_param + yield key if value.nil? || value.empty? + params[key] = value + } + params + end + + def raise_generation_error(args) + missing_keys = [] + params = parameterize_args(args) { |missing_key| + missing_keys << missing_key + } + constraints = Hash[@route.requirements.merge(params).sort_by { |k, v| k.to_s }] + message = +"No route matches #{constraints.inspect}" + message << ", missing required keys: #{missing_keys.sort.inspect}" + + raise ActionController::UrlGenerationError, message + end + end + + def initialize(route, options, route_name) + @options = options + @segment_keys = route.segment_keys.uniq + @route = route + @route_name = route_name + end + + def call(t, method_name, args, inner_options, url_strategy) + controller_options = t.url_options + options = controller_options.merge @options + hash = handle_positional_args(controller_options, + inner_options || {}, + args, + options, + @segment_keys) + + t._routes.url_for(hash, route_name, url_strategy, method_name) + end + + def handle_positional_args(controller_options, inner_options, args, result, path_params) + if args.size > 0 + # take format into account + if path_params.include?(:format) + path_params_size = path_params.size - 1 + else + path_params_size = path_params.size + end + + if args.size < path_params_size + path_params -= controller_options.keys + path_params -= (result[:path_params] || {}).merge(result).keys + else + path_params = path_params.dup + end + inner_options.each_key do |key| + path_params.delete(key) + end + + args.each_with_index do |arg, index| + param = path_params[index] + result[param] = arg if param + end + end + + result.merge!(inner_options) + end + end + + private + # Create a URL helper allowing ordered parameters to be associated with + # corresponding dynamic segments, so you can do: + # + # foo_url(bar, baz, bang) + # + # Instead of: + # + # foo_url(bar: bar, baz: baz, bang: bang) + # + # Also allow options hash, so you can do: + # + # foo_url(bar, baz, bang, sort_by: 'baz') + # + def define_url_helper(mod, name, helper, url_strategy) + mod.define_method(name) do |*args| + last = args.last + options = \ + case last + when Hash + args.pop + when ActionController::Parameters + args.pop.to_h + end + helper.call(self, name, args, options, url_strategy) + end + end + end + + # strategy for building URLs to send to the client + PATH = ->(options) { ActionDispatch::Http::URL.path_for(options) } + UNKNOWN = ->(options) { ActionDispatch::Http::URL.url_for(options) } + + attr_accessor :formatter, :set, :named_routes, :router + attr_accessor :disable_clear_and_finalize, :resources_path_names + attr_accessor :default_url_options, :draw_paths + attr_reader :env_key, :polymorphic_mappings + + alias :routes :set + + def self.default_resources_path_names + { new: "new", edit: "edit" } + end + + def self.new_with_config(config) + route_set_config = DEFAULT_CONFIG.dup + + # engines apparently don't have this set + if config.respond_to? :relative_url_root + route_set_config.relative_url_root = config.relative_url_root + end + + if config.respond_to? :api_only + route_set_config.api_only = config.api_only + end + + if config.respond_to? :default_scope + route_set_config.default_scope = config.default_scope + end + + new route_set_config + end + + Config = Struct.new :relative_url_root, :api_only, :default_scope + + DEFAULT_CONFIG = Config.new(nil, false, nil) + + def initialize(config = DEFAULT_CONFIG.dup) + self.named_routes = NamedRouteCollection.new + self.resources_path_names = self.class.default_resources_path_names + self.default_url_options = {} + self.draw_paths = [] + + @config = config + @append = [] + @prepend = [] + @disable_clear_and_finalize = false + @finalized = false + @env_key = "ROUTES_#{object_id}_SCRIPT_NAME" + @default_env = nil + + @set = Journey::Routes.new + @router = Journey::Router.new @set + @formatter = Journey::Formatter.new self + @polymorphic_mappings = {} + end + + def eager_load! + router.eager_load! + routes.each(&:eager_load!) + formatter.eager_load! + nil + end + + def relative_url_root + @config.relative_url_root + end + + def api_only? + @config.api_only + end + + def default_scope + @config.default_scope + end + + def default_scope=(new_default_scope) + @config.default_scope = new_default_scope + end + + def request_class + ActionDispatch::Request + end + + def make_request(env) + request_class.new env + end + private :make_request + + def default_env + if default_url_options != @default_env&.[]("action_dispatch.routes.default_url_options") + url_options = default_url_options.dup.freeze + uri = URI(ActionDispatch::Http::URL.full_url_for(host: "example.org", **url_options)) + + @default_env = { + "action_dispatch.routes" => self, + "action_dispatch.routes.default_url_options" => url_options, + "HTTPS" => uri.scheme == "https" ? "on" : "off", + "rack.url_scheme" => uri.scheme, + "HTTP_HOST" => uri.port == uri.default_port ? uri.host : "#{uri.host}:#{uri.port}", + "SCRIPT_NAME" => uri.path.chomp("/"), + "rack.input" => "", + }.freeze + end + + @default_env + end + + def draw(&block) + clear! unless @disable_clear_and_finalize + eval_block(block) + finalize! unless @disable_clear_and_finalize + nil + end + + def append(&block) + @append << block + end + + def prepend(&block) + @prepend << block + end + + def eval_block(block) + mapper = Mapper.new(self) + if default_scope + mapper.with_default_scope(default_scope, &block) + else + mapper.instance_exec(&block) + end + end + private :eval_block + + def finalize! + return if @finalized + @append.each { |blk| eval_block(blk) } + @finalized = true + end + + def clear! + @finalized = false + named_routes.clear + set.clear + formatter.clear + @polymorphic_mappings.clear + @prepend.each { |blk| eval_block(blk) } + end + + module MountedHelpers + extend ActiveSupport::Concern + include UrlFor + end + + # Contains all the mounted helpers across different engines and the `main_app` + # helper for the application. You can include this in your classes if you want + # to access routes for other engines. + def mounted_helpers + MountedHelpers + end + + def define_mounted_helper(name, script_namer = nil) + return if MountedHelpers.method_defined?(name) + + routes = self + helpers = routes.url_helpers + + MountedHelpers.class_eval do + define_method "_#{name}" do + RoutesProxy.new(routes, _routes_context, helpers, script_namer) + end + end + + MountedHelpers.class_eval(<<-RUBY, __FILE__, __LINE__ + 1) + def #{name} + @_#{name} ||= _#{name} + end + RUBY + end + + def url_helpers(supports_path = true) + if supports_path + @url_helpers_with_paths ||= generate_url_helpers(true) + else + @url_helpers_without_paths ||= generate_url_helpers(false) + end + end + + def generate_url_helpers(supports_path) + routes = self + + Module.new do + extend ActiveSupport::Concern + include UrlFor + + # Define url_for in the singleton level so one can do: + # Rails.application.routes.url_helpers.url_for(args) + proxy_class = Class.new do + include UrlFor + include routes.named_routes.path_helpers_module + include routes.named_routes.url_helpers_module + + attr_reader :_routes + + def initialize(routes) + @_routes = routes + end + + def optimize_routes_generation? + @_routes.optimize_routes_generation? + end + end + + @_proxy = proxy_class.new(routes) + + class << self + def url_for(options) + @_proxy.url_for(options) + end + + def full_url_for(options) + @_proxy.full_url_for(options) + end + + def route_for(name, *args) + @_proxy.route_for(name, *args) + end + + def optimize_routes_generation? + @_proxy.optimize_routes_generation? + end + + def polymorphic_url(record_or_hash_or_array, options = {}) + @_proxy.polymorphic_url(record_or_hash_or_array, options) + end + + def polymorphic_path(record_or_hash_or_array, options = {}) + @_proxy.polymorphic_path(record_or_hash_or_array, options) + end + + def _routes; @_proxy._routes; end + def url_options; {}; end + end + + url_helpers = routes.named_routes.url_helpers_module + + # Make named_routes available in the module singleton as well, so one can do: + # Rails.application.routes.url_helpers.posts_path + extend url_helpers + + # Any class that includes this module will get all named routes... + include url_helpers + + if supports_path + path_helpers = routes.named_routes.path_helpers_module + + include path_helpers + extend path_helpers + end + + # plus a singleton class method called _routes ... + included do + redefine_singleton_method(:_routes) { routes } + end + + # And an instance method _routes. Note that UrlFor (included in this module) add + # extra conveniences for working with @_routes. + define_method(:_routes) { @_routes || routes } + + define_method(:_generate_paths_by_default) do + supports_path + end + + private :_generate_paths_by_default + + # If the module is included more than once (for example, in a subclass of an + # ancestor that includes the module), ensure that the `_routes` singleton and + # instance methods return the desired route set by including a new copy of the + # module (recursively if necessary). Note that this method is called for each + # inclusion, whereas the above `included` block is run only for the initial + # inclusion of each copy. + def self.included(base) + super + if base.respond_to?(:_routes) && !base._routes.equal?(@_proxy._routes) + @dup_for_reinclude ||= self.dup + base.include @dup_for_reinclude + end + end + end + end + + def empty? + routes.empty? + end + + def add_route(mapping, name) + raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i) + + if name && named_routes[name] + raise ArgumentError, "Invalid route name, already in use: '#{name}' \n" \ + "You may have defined two routes with the same name using the `:as` option, or " \ + "you may be overriding a route already defined by a resource with the same naming. " \ + "For the latter, you can restrict the routes created with `resources` as explained here: \n" \ + "https://guides.rubyonrails.org/routing.html#restricting-the-routes-created" + end + + route = @set.add_route(name, mapping) + named_routes[name] = route if name + + if route.segment_keys.include?(:controller) + ActionDispatch.deprecator.warn(<<-MSG.squish) + Using a dynamic :controller segment in a route is deprecated and + will be removed in Rails 8.1. + MSG + end + + if route.segment_keys.include?(:action) + ActionDispatch.deprecator.warn(<<-MSG.squish) + Using a dynamic :action segment in a route is deprecated and + will be removed in Rails 8.1. + MSG + end + + route + end + + def add_polymorphic_mapping(klass, options, &block) + @polymorphic_mappings[klass] = CustomUrlHelper.new(klass, options, &block) + end + + def add_url_helper(name, options, &block) + named_routes.add_url_helper(name, options, &block) + end + + class CustomUrlHelper + attr_reader :name, :defaults, :block + + def initialize(name, defaults, &block) + @name = name + @defaults = defaults + @block = block + end + + def call(t, args, only_path = false) + options = args.extract_options! + url = t.full_url_for(eval_block(t, args, options)) + + if only_path + "/" + url.partition(%r{(? e + raise ActionController::RoutingError, e.message + end + + req = make_request(env) + recognize_path_with_request(req, path, extras) + end + + def recognize_path_with_request(req, path, extras, raise_on_missing: true) + @router.recognize(req) do |route, params| + params.merge!(extras) + params.each do |key, value| + if value.is_a?(String) + value = value.dup.force_encoding(Encoding::BINARY) + params[key] = URI::RFC2396_PARSER.unescape(value) + end + end + req.path_parameters = params + app = route.app + if app.matches?(req) && app.dispatcher? + begin + req.controller_class + rescue NameError + raise ActionController::RoutingError, "A route matches #{path.inspect}, but references missing controller: #{params[:controller].camelize}Controller" + end + + return req.path_parameters + elsif app.matches?(req) && app.engine? + path_parameters = app.rack_app.routes.recognize_path_with_request(req, path, extras, raise_on_missing: false) + return path_parameters if path_parameters + end + end + + if raise_on_missing + raise ActionController::RoutingError, "No route matches #{path.inspect}" + end + end + end + # :startdoc: + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/routes_proxy.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/routes_proxy.rb new file mode 100644 index 00000000..fe9ba93c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/routes_proxy.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/core_ext/array/extract_options" + +module ActionDispatch + module Routing + class RoutesProxy # :nodoc: + include ActionDispatch::Routing::UrlFor + + attr_accessor :scope, :routes + alias :_routes :routes + + def initialize(routes, scope, helpers, script_namer = nil) + @routes, @scope = routes, scope + @helpers = helpers + @script_namer = script_namer + end + + def url_options + scope.send(:_with_routes, routes) do + scope.url_options + end + end + + private + def respond_to_missing?(method, _) + super || @helpers.respond_to?(method) + end + + def method_missing(method, *args) + if @helpers.respond_to?(method) + options = args.extract_options! + options = url_options.merge((options || {}).symbolize_keys) + + if @script_namer + options[:script_name] = merge_script_names( + options[:script_name], + @script_namer.call(options) + ) + end + + args << options + @helpers.public_send(method, *args) + else + super + end + end + + # Keeps the part of the script name provided by the global context via + # [ENV]("SCRIPT_NAME"), which `mount` doesn't know about since it depends on the + # specific request, but use our script name resolver for the mount point + # dependent part. + def merge_script_names(previous_script_name, new_script_name) + return new_script_name unless previous_script_name + + resolved_parts = new_script_name.count("/") + previous_parts = previous_script_name.count("/") + context_parts = previous_parts - resolved_parts + 1 + + (previous_script_name.split("/").slice(0, context_parts).join("/")) + new_script_name + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/url_for.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/url_for.rb new file mode 100644 index 00000000..43e89e3e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/routing/url_for.rb @@ -0,0 +1,244 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + module Routing + # # Action Dispatch Routing UrlFor + # + # In `config/routes.rb` you define URL-to-controller mappings, but the reverse + # is also possible: a URL can be generated from one of your routing definitions. + # URL generation functionality is centralized in this module. + # + # See ActionDispatch::Routing for general information about routing and + # `config/routes.rb`. + # + # **Tip:** If you need to generate URLs from your models or some other place, + # then ActionDispatch::Routing::UrlFor is what you're looking for. Read on for + # an introduction. In general, this module should not be included on its own, as + # it is usually included by `url_helpers` (as in + # `Rails.application.routes.url_helpers`). + # + # ## URL generation from parameters + # + # As you may know, some functions, such as `ActionController::Base#url_for` and + # ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set of + # parameters. For example, you've probably had the chance to write code like + # this in one of your views: + # + # <%= link_to('Click here', controller: 'users', + # action: 'new', message: 'Welcome!') %> + # # => Click here + # + # `link_to`, and all other functions that require URL generation functionality, + # actually use ActionDispatch::Routing::UrlFor under the hood. And in + # particular, they use the ActionDispatch::Routing::UrlFor#url_for method. One + # can generate the same path as the above example by using the following code: + # + # include ActionDispatch::Routing::UrlFor + # url_for(controller: 'users', + # action: 'new', + # message: 'Welcome!', + # only_path: true) + # # => "/users/new?message=Welcome%21" + # + # Notice the `only_path: true` part. This is because UrlFor has no information + # about the website hostname that your Rails app is serving. So if you want to + # include the hostname as well, then you must also pass the `:host` argument: + # + # include UrlFor + # url_for(controller: 'users', + # action: 'new', + # message: 'Welcome!', + # host: 'www.example.com') + # # => "http://www.example.com/users/new?message=Welcome%21" + # + # By default, all controllers and views have access to a special version of + # `url_for`, that already knows what the current hostname is. So if you use + # `url_for` in your controllers or your views, then you don't need to explicitly + # pass the `:host` argument. + # + # For convenience, mailers also include ActionDispatch::Routing::UrlFor. So + # within mailers, you can use url_for. However, mailers cannot access incoming + # web requests in order to derive hostname information, so you have to provide + # the `:host` option or set the default host using `default_url_options`. For + # more information on url_for in mailers see the ActionMailer::Base + # documentation. + # + # ## URL generation for named routes + # + # UrlFor also allows one to access methods that have been auto-generated from + # named routes. For example, suppose that you have a 'users' resource in your + # `config/routes.rb`: + # + # resources :users + # + # This generates, among other things, the method `users_path`. By default, this + # method is accessible from your controllers, views, and mailers. If you need to + # access this auto-generated method from other places (such as a model), then + # you can do that by including `Rails.application.routes.url_helpers` in your + # class: + # + # class User < ActiveRecord::Base + # include Rails.application.routes.url_helpers + # + # def base_uri + # user_path(self) + # end + # end + # + # User.find(1).base_uri # => "/users/1" + # + module UrlFor + extend ActiveSupport::Concern + include PolymorphicRoutes + + included do + unless method_defined?(:default_url_options) + # Including in a class uses an inheritable hash. Modules get a plain hash. + if respond_to?(:class_attribute) + class_attribute :default_url_options + else + mattr_writer :default_url_options + end + + self.default_url_options = {} + end + + include(*_url_for_modules) if respond_to?(:_url_for_modules) + end + + def initialize(...) + @_routes = nil + super + end + + # Hook overridden in controller to add request information with + # `default_url_options`. Application logic should not go into url_options. + def url_options + default_url_options + end + + # Generate a URL based on the options provided, `default_url_options`, and the + # routes defined in `config/routes.rb`. The following options are supported: + # + # * `:only_path` - If true, the relative URL is returned. Defaults to `false`. + # * `:protocol` - The protocol to connect to. Defaults to `"http"`. + # * `:host` - Specifies the host the link should be targeted at. If + # `:only_path` is false, this option must be provided either explicitly, or + # via `default_url_options`. + # * `:subdomain` - Specifies the subdomain of the link, using the `tld_length` + # to split the subdomain from the host. If false, removes all subdomains + # from the host part of the link. + # * `:domain` - Specifies the domain of the link, using the `tld_length` to + # split the domain from the host. + # * `:tld_length` - Number of labels the TLD id composed of, only used if + # `:subdomain` or `:domain` are supplied. Defaults to + # `ActionDispatch::Http::URL.tld_length`, which in turn defaults to 1. + # * `:port` - Optionally specify the port to connect to. + # * `:anchor` - An anchor name to be appended to the path. + # * `:params` - The query parameters to be appended to the path. + # * `:path_params` - The query parameters that will only be used for the named + # dynamic segments of path. If unused, they will be discarded. + # * `:trailing_slash` - If true, adds a trailing slash, as in + # `"/archive/2009/"`. + # * `:script_name` - Specifies application path relative to domain root. If + # provided, prepends application path. + # + # + # Any other key (`:controller`, `:action`, etc.) given to `url_for` is forwarded + # to the Routes module. + # + # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', port: '8080' + # # => 'http://somehost.org:8080/tasks/testing' + # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', anchor: 'ok', only_path: true + # # => '/tasks/testing#ok' + # url_for controller: 'tasks', action: 'testing', trailing_slash: true + # # => 'http://somehost.org/tasks/testing/' + # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', number: '33' + # # => 'http://somehost.org/tasks/testing?number=33' + # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', script_name: "/myapp" + # # => 'http://somehost.org/myapp/tasks/testing' + # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', script_name: "/myapp", only_path: true + # # => '/myapp/tasks/testing' + # + # Missing routes keys may be filled in from the current request's parameters + # (e.g. `:controller`, `:action`, `:id`, and any other parameters that are + # placed in the path). Given that the current action has been reached through + # `GET /users/1`: + # + # url_for(only_path: true) # => '/users/1' + # url_for(only_path: true, action: 'edit') # => '/users/1/edit' + # url_for(only_path: true, action: 'edit', id: 2) # => '/users/2/edit' + # + # Notice that no `:id` parameter was provided to the first `url_for` call and + # the helper used the one from the route's path. Any path parameter implicitly + # used by `url_for` can always be overwritten like shown on the last `url_for` + # calls. + def url_for(options = nil) + full_url_for(options) + end + + def full_url_for(options = nil) # :nodoc: + case options + when nil + _routes.url_for(url_options.symbolize_keys) + when Hash, ActionController::Parameters + route_name = options.delete :use_route + merged_url_options = options.to_h.symbolize_keys.reverse_merge!(url_options) + _routes.url_for(merged_url_options, route_name) + when String + options + when Symbol + HelperMethodBuilder.url.handle_string_call self, options + when Array + components = options.dup + polymorphic_url(components, components.extract_options!) + when Class + HelperMethodBuilder.url.handle_class_call self, options + else + HelperMethodBuilder.url.handle_model_call self, options + end + end + + # Allows calling direct or regular named route. + # + # resources :buckets + # + # direct :recordable do |recording| + # route_for(:bucket, recording.bucket) + # end + # + # direct :threadable do |threadable| + # route_for(:recordable, threadable.parent) + # end + # + # This maintains the context of the original caller on whether to return a path + # or full URL, e.g: + # + # threadable_path(threadable) # => "/buckets/1" + # threadable_url(threadable) # => "http://example.com/buckets/1" + # + def route_for(name, *args) + public_send(:"#{name}_url", *args) + end + + protected + def optimize_routes_generation? + _routes.optimize_routes_generation? && default_url_options.empty? + end + + private + def _with_routes(routes) # :doc: + old_routes, @_routes = @_routes, routes + yield + ensure + @_routes = old_routes + end + + def _routes_context # :doc: + self + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/system_test_case.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/system_test_case.rb new file mode 100644 index 00000000..c78131f1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/system_test_case.rb @@ -0,0 +1,206 @@ +# frozen_string_literal: true + +# :markup: markdown + +gem "capybara", ">= 3.26" + +require "capybara/dsl" +require "capybara/minitest" +require "action_controller" +require "action_dispatch/system_testing/driver" +require "action_dispatch/system_testing/browser" +require "action_dispatch/system_testing/server" +require "action_dispatch/system_testing/test_helpers/screenshot_helper" +require "action_dispatch/system_testing/test_helpers/setup_and_teardown" + +module ActionDispatch + # # System Testing + # + # System tests let you test applications in the browser. Because system tests + # use a real browser experience, you can test all of your JavaScript easily from + # your test suite. + # + # To create a system test in your application, extend your test class from + # `ApplicationSystemTestCase`. System tests use Capybara as a base and allow you + # to configure the settings through your `application_system_test_case.rb` file + # that is generated with a new application or scaffold. + # + # Here is an example system test: + # + # require "application_system_test_case" + # + # class Users::CreateTest < ApplicationSystemTestCase + # test "adding a new user" do + # visit users_path + # click_on 'New User' + # + # fill_in 'Name', with: 'Arya' + # click_on 'Create User' + # + # assert_text 'Arya' + # end + # end + # + # When generating an application or scaffold, an + # `application_system_test_case.rb` file will also be generated containing the + # base class for system testing. This is where you can change the driver, add + # Capybara settings, and other configuration for your system tests. + # + # require "test_helper" + # + # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + # driven_by :selenium, using: :chrome, screen_size: [1400, 1400] + # end + # + # By default, `ActionDispatch::SystemTestCase` is driven by the Selenium driver, + # with the Chrome browser, and a browser size of 1400x1400. + # + # Changing the driver configuration options is easy. Let's say you want to use + # the Firefox browser instead of Chrome. In your + # `application_system_test_case.rb` file add the following: + # + # require "test_helper" + # + # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + # driven_by :selenium, using: :firefox + # end + # + # `driven_by` has a required argument for the driver name. The keyword arguments + # are `:using` for the browser and `:screen_size` to change the size of the + # browser screen. These two options are not applicable for headless drivers and + # will be silently ignored if passed. + # + # Headless browsers such as headless Chrome and headless Firefox are also + # supported. You can use these browsers by setting the `:using` argument to + # `:headless_chrome` or `:headless_firefox`. + # + # To use a headless driver, like Cuprite, update your Gemfile to use Cuprite + # instead of Selenium and then declare the driver name in the + # `application_system_test_case.rb` file. In this case, you would leave out the + # `:using` option because the driver is headless, but you can still use + # `:screen_size` to change the size of the browser screen, also you can use + # `:options` to pass options supported by the driver. Please refer to your + # driver documentation to learn about supported options. + # + # require "test_helper" + # require "capybara/cuprite" + # + # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + # driven_by :cuprite, screen_size: [1400, 1400], options: + # { js_errors: true } + # end + # + # Some drivers require browser capabilities to be passed as a block instead of + # through the `options` hash. + # + # As an example, if you want to add mobile emulation on chrome, you'll have to + # create an instance of selenium's `Chrome::Options` object and add capabilities + # with a block. + # + # The block will be passed an instance of `::Options` where you can + # define the capabilities you want. Please refer to your driver documentation to + # learn about supported options. + # + # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + # driven_by :selenium, using: :chrome, screen_size: [1024, 768] do |driver_option| + # driver_option.add_emulation(device_name: 'iPhone 6') + # driver_option.add_extension('path/to/chrome_extension.crx') + # end + # end + # + # Because `ActionDispatch::SystemTestCase` is a shim between Capybara and Rails, + # any driver that is supported by Capybara is supported by system tests as long + # as you include the required gems and files. + class SystemTestCase < ActiveSupport::TestCase + include Capybara::DSL + include Capybara::Minitest::Assertions + include SystemTesting::TestHelpers::SetupAndTeardown + include SystemTesting::TestHelpers::ScreenshotHelper + + DEFAULT_HOST = "http://127.0.0.1" + + def initialize(*) # :nodoc: + super + self.class.driven_by(:selenium) unless self.class.driver? + self.class.driver.use + end + + def self.start_application # :nodoc: + Capybara.app = Rack::Builder.new do + map "/" do + run Rails.application + end + end + + SystemTesting::Server.new.run + end + + class_attribute :driver, instance_accessor: false + + # System Test configuration options + # + # The default settings are Selenium, using Chrome, with a screen size of + # 1400x1400. + # + # Examples: + # + # driven_by :cuprite + # + # driven_by :selenium, screen_size: [800, 800] + # + # driven_by :selenium, using: :chrome + # + # driven_by :selenium, using: :headless_chrome + # + # driven_by :selenium, using: :firefox + # + # driven_by :selenium, using: :headless_firefox + def self.driven_by(driver, using: :chrome, screen_size: [1400, 1400], options: {}, &capabilities) + driver_options = { using: using, screen_size: screen_size, options: options } + + self.driver = SystemTesting::Driver.new(driver, **driver_options, &capabilities) + end + + # Configuration for the System Test application server. + # + # By default this is localhost. This method allows the host and port to be specified manually. + def self.served_by(host:, port:) + Capybara.server_host = host + Capybara.server_port = port + end + + private + def url_helpers + @url_helpers ||= + if ActionDispatch.test_app + Class.new do + include ActionDispatch.test_app.routes.url_helpers + include ActionDispatch.test_app.routes.mounted_helpers + + def url_options + default_url_options.reverse_merge(host: app_host) + end + + def app_host + Capybara.app_host || Capybara.current_session.server_url || DEFAULT_HOST + end + end.new + end + end + + def method_missing(name, ...) + if url_helpers.respond_to?(name) + url_helpers.public_send(name, ...) + else + super + end + end + + def respond_to_missing?(name, include_private = false) + url_helpers.respond_to?(name) + end + end +end + +ActiveSupport.run_load_hooks :action_dispatch_system_test_case, ActionDispatch::SystemTestCase +ActionDispatch::SystemTestCase.start_application diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/system_testing/browser.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/system_testing/browser.rb new file mode 100644 index 00000000..e08e4876 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/system_testing/browser.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + module SystemTesting + class Browser # :nodoc: + attr_reader :name + + def initialize(name) + @name = name + end + + def type + case name + when :headless_chrome + :chrome + when :headless_firefox + :firefox + else + name + end + end + + def options + @options ||= + case type + when :chrome + default_chrome_options + when :firefox + default_firefox_options + end + end + + def configure + yield options if block_given? + end + + # driver_path is lazily initialized by default. Eagerly set it to avoid race + # conditions when using parallel tests. + def preload + case type + when :chrome + resolve_driver_path(::Selenium::WebDriver::Chrome) + when :firefox + resolve_driver_path(::Selenium::WebDriver::Firefox) + end + end + + private + def default_chrome_options + options = ::Selenium::WebDriver::Chrome::Options.new + options.add_argument("--disable-search-engine-choice-screen") + options.add_argument("--headless") if name == :headless_chrome + options.add_argument("--disable-gpu") if Gem.win_platform? + options + end + + def default_firefox_options + options = ::Selenium::WebDriver::Firefox::Options.new + options.add_argument("-headless") if name == :headless_firefox + options + end + + def resolve_driver_path(namespace) + # The path method has been deprecated in 4.20.0 + if Gem::Version.new(::Selenium::WebDriver::VERSION) >= Gem::Version.new("4.20.0") + namespace::Service.driver_path = ::Selenium::WebDriver::DriverFinder.new(options, namespace::Service.new).driver_path + else + namespace::Service.driver_path = ::Selenium::WebDriver::DriverFinder.path(options, namespace::Service) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/system_testing/driver.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/system_testing/driver.rb new file mode 100644 index 00000000..6389ffa3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/system_testing/driver.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + module SystemTesting + class Driver # :nodoc: + attr_reader :name + + def initialize(driver_type, **options, &capabilities) + @driver_type = driver_type + @screen_size = options[:screen_size] + @options = options[:options] || {} + @name = @options.delete(:name) || driver_type + @capabilities = capabilities + + if driver_type == :selenium + gem "selenium-webdriver", ">= 4.0.0" + require "selenium/webdriver" + @browser = Browser.new(options[:using]) + @browser.preload unless @options[:browser] == :remote + else + @browser = nil + end + end + + def use + register if registerable? + + setup + end + + private + def registerable? + [:selenium, :cuprite, :rack_test, :playwright].include?(@driver_type) + end + + def register + @browser&.configure(&@capabilities) + + Capybara.register_driver name do |app| + case @driver_type + when :selenium then register_selenium(app) + when :cuprite then register_cuprite(app) + when :rack_test then register_rack_test(app) + when :playwright then register_playwright(app) + end + end + end + + def browser_options + @options.merge(options: @browser.options).compact + end + + def register_selenium(app) + Capybara::Selenium::Driver.new(app, browser: @browser.type, **browser_options).tap do |driver| + driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size) + end + end + + def register_cuprite(app) + Capybara::Cuprite::Driver.new(app, @options.merge(window_size: @screen_size)) + end + + def register_rack_test(app) + Capybara::RackTest::Driver.new(app, respect_data_method: true, **@options) + end + + def register_playwright(app) + screen = { width: @screen_size[0], height: @screen_size[1] } if @screen_size + options = { + screen: screen, + viewport: screen, + **@options + }.compact + + Capybara::Playwright::Driver.new(app, **options) + end + + def setup + Capybara.current_driver = name + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/system_testing/server.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/system_testing/server.rb new file mode 100644 index 00000000..ae3e2e9c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/system_testing/server.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + module SystemTesting + class Server # :nodoc: + class << self + attr_accessor :silence_puma + end + + self.silence_puma = false + + def run + setup + end + + private + def setup + set_server + set_port + end + + def set_server + Capybara.server = :puma, { Silent: self.class.silence_puma } if Capybara.server == Capybara.servers[:default] + end + + def set_port + Capybara.always_include_port = true + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb new file mode 100644 index 00000000..5c1404c3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb @@ -0,0 +1,164 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + module SystemTesting + module TestHelpers + # Screenshot helper for system testing. + module ScreenshotHelper + # Takes a screenshot of the current page in the browser. + # + # `take_screenshot` can be used at any point in your system tests to take a + # screenshot of the current state. This can be useful for debugging or + # automating visual testing. You can take multiple screenshots per test to + # investigate changes at different points during your test. These will be named + # with a sequential prefix (or 'failed' for failing tests) + # + # The default screenshots directory is `tmp/screenshots` but you can set a + # different one with `Capybara.save_path` + # + # You can use the `html` argument or set the + # `RAILS_SYSTEM_TESTING_SCREENSHOT_HTML` environment variable to save the HTML + # from the page that is being screenshotted so you can investigate the elements + # on the page at the time of the screenshot + # + # You can use the `screenshot` argument or set the + # `RAILS_SYSTEM_TESTING_SCREENSHOT` environment variable to control the output. + # Possible values are: + # `simple` (default) + # : Only displays the screenshot path. This is the default value. + # + # `inline` + # : Display the screenshot in the terminal using the iTerm image protocol + # (https://iterm2.com/documentation-images.html). + # + # `artifact` + # : Display the screenshot in the terminal, using the terminal artifact + # format (https://buildkite.github.io/terminal-to-html/inline-images/). + # + # + def take_screenshot(html: false, screenshot: nil) + showing_html = html || html_from_env? + + increment_unique + save_html if showing_html + save_image + show display_image(html: showing_html, screenshot_output: screenshot) + end + + # Takes a screenshot of the current page in the browser if the test failed. + # + # `take_failed_screenshot` is called during system test teardown. + def take_failed_screenshot + return unless failed? && supports_screenshot? && Capybara::Session.instance_created? + + take_screenshot + metadata[:failure_screenshot_path] = relative_image_path if Minitest::Runnable.method_defined?(:metadata) + end + + private + attr_accessor :_screenshot_counter + + def html_from_env? + ENV["RAILS_SYSTEM_TESTING_SCREENSHOT_HTML"] == "1" + end + + def increment_unique + @_screenshot_counter ||= 0 + @_screenshot_counter += 1 + end + + def unique + failed? ? "failures" : (_screenshot_counter || 0).to_s + end + + def image_name + sanitized_method_name = method_name.gsub(/[^\w]+/, "-") + name = "#{unique}_#{sanitized_method_name}" + name[0...225] + end + + def image_path + absolute_image_path.to_s + end + + def html_path + absolute_html_path.to_s + end + + def absolute_path + Rails.root.join(screenshots_dir, image_name) + end + + def screenshots_dir + Capybara.save_path.presence || "tmp/screenshots" + end + + def absolute_image_path + "#{absolute_path}.png" + end + + def relative_image_path + "#{absolute_path.relative_path_from(Rails.root)}.png" + end + + def absolute_html_path + "#{absolute_path}.html" + end + + # rubocop:disable Lint/Debugger + def save_html + page.save_page(absolute_html_path) + end + + def save_image + page.save_screenshot(absolute_image_path) + end + # rubocop:enable Lint/Debugger + + def output_type + # Environment variables have priority + output_type = ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"] || ENV["CAPYBARA_INLINE_SCREENSHOT"] + + # Default to outputting a path to the screenshot + output_type ||= "simple" + + output_type + end + + def show(img) + puts img + end + + def display_image(html:, screenshot_output:) + message = +"[Screenshot Image]: #{image_path}\n" + message << +"[Screenshot HTML]: #{html_path}\n" if html + + case screenshot_output || output_type + when "artifact" + message << "\e]1338;url=artifact://#{absolute_image_path}\a\n" + when "inline" + name = inline_base64(File.basename(absolute_image_path)) + image = inline_base64(File.read(absolute_image_path)) + message << "\e]1337;File=name=#{name};height=400px;inline=1:#{image}\a\n" + end + + message + end + + def inline_base64(path) + Base64.strict_encode64(path) + end + + def failed? + !passed? && !skipped? + end + + def supports_screenshot? + Capybara.current_driver != :rack_test + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb new file mode 100644 index 00000000..f0eb3459 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + module SystemTesting + module TestHelpers + module SetupAndTeardown # :nodoc: + def before_teardown + take_failed_screenshot + ensure + super + end + + def after_teardown + Capybara.reset_sessions! + ensure + super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/assertion_response.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/assertion_response.rb new file mode 100644 index 00000000..bc81475b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/assertion_response.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + # This is a class that abstracts away an asserted response. It purposely does + # not inherit from Response because it doesn't need it. That means it does not + # have headers or a body. + class AssertionResponse + attr_reader :code, :name + + GENERIC_RESPONSE_CODES = { # :nodoc: + success: "2XX", + missing: "404", + redirect: "3XX", + error: "5XX" + } + + # Accepts a specific response status code as an Integer (404) or String ('404') + # or a response status range as a Symbol pseudo-code (:success, indicating any + # 200-299 status code). + def initialize(code_or_name) + if code_or_name.is_a?(Symbol) + @name = code_or_name + @code = code_from_name(code_or_name) + else + @name = name_from_code(code_or_name) + @code = code_or_name + end + + raise ArgumentError, "Invalid response name: #{name}" if @code.nil? + raise ArgumentError, "Invalid response code: #{code}" if @name.nil? + end + + def code_and_name + "#{code}: #{name}" + end + + private + def code_from_name(name) + GENERIC_RESPONSE_CODES[name] || Rack::Utils.status_code(name) + end + + def name_from_code(code) + GENERIC_RESPONSE_CODES.invert[code] || Rack::Utils::HTTP_STATUS_CODES[code] + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/assertions.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/assertions.rb new file mode 100644 index 00000000..e32858dd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/assertions.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "rails-dom-testing" +require "action_dispatch/testing/assertions/response" +require "action_dispatch/testing/assertions/routing" + +module ActionDispatch + module Assertions + extend ActiveSupport::Concern + + include ResponseAssertions + include RoutingAssertions + include Rails::Dom::Testing::Assertions + + def html_document + @html_document ||= if @response.media_type&.end_with?("xml") + Nokogiri::XML::Document.parse(@response.body) + else + Rails::Dom::Testing.html_document.parse(@response.body) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/assertions/response.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/assertions/response.rb new file mode 100644 index 00000000..d7598b7c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/assertions/response.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionDispatch + module Assertions + # A small suite of assertions that test responses from Rails applications. + module ResponseAssertions + RESPONSE_PREDICATES = { # :nodoc: + success: :successful?, + missing: :not_found?, + redirect: :redirection?, + error: :server_error?, + } + + # Asserts that the response is one of the following types: + # + # * `:success` - Status code was in the 200-299 range + # * `:redirect` - Status code was in the 300-399 range + # * `:missing` - Status code was 404 + # * `:error` - Status code was in the 500-599 range + # + # + # You can also pass an explicit status number like `assert_response(501)` or its + # symbolic equivalent `assert_response(:not_implemented)`. See + # `Rack::Utils::SYMBOL_TO_STATUS_CODE` for a full list. + # + # # Asserts that the response was a redirection + # assert_response :redirect + # + # # Asserts that the response code was status code 401 (unauthorized) + # assert_response 401 + def assert_response(type, message = nil) + message ||= generate_response_message(type) + + if RESPONSE_PREDICATES.key?(type) + assert @response.public_send(RESPONSE_PREDICATES[type]), message + else + assert_equal AssertionResponse.new(type).code, @response.response_code, message + end + end + + # Asserts that the response is a redirect to a URL matching the given options. + # + # # Asserts that the redirection was to the "index" action on the WeblogController + # assert_redirected_to controller: "weblog", action: "index" + # + # # Asserts that the redirection was to the named route login_url + # assert_redirected_to login_url + # + # # Asserts that the redirection was to the URL for @customer + # assert_redirected_to @customer + # + # # Asserts that the redirection matches the regular expression + # assert_redirected_to %r(\Ahttp://example.org) + # + # # Asserts that the redirection has the HTTP status code 301 (Moved + # # Permanently). + # assert_redirected_to "/some/path", status: :moved_permanently + def assert_redirected_to(url_options = {}, options = {}, message = nil) + options, message = {}, options unless options.is_a?(Hash) + + status = options[:status] || :redirect + assert_response(status, message) + return true if url_options === @response.location + + redirect_is = normalize_argument_to_redirection(@response.location) + redirect_expected = normalize_argument_to_redirection(url_options) + + message ||= "Expected response to be a redirect to <#{redirect_expected}> but was a redirect to <#{redirect_is}>" + assert_operator redirect_expected, :===, redirect_is, message + end + + private + # Proxy to to_param if the object will respond to it. + def parameterize(value) + value.respond_to?(:to_param) ? value.to_param : value + end + + def normalize_argument_to_redirection(fragment) + if Regexp === fragment + fragment + else + handle = @controller || ActionController::Redirecting + handle._compute_redirect_to_location(@request, fragment) + end + end + + def generate_response_message(expected, actual = @response.response_code) + lambda do + (+"Expected response to be a <#{code_with_name(expected)}>,"\ + " but was a <#{code_with_name(actual)}>"). + concat(location_if_redirected). + concat(exception_if_present). + concat(response_body_if_short) + end + end + + def response_body_if_short + return "" if @response.body.size > 500 + "\nResponse body: #{@response.body}" + end + + def exception_if_present + return "" unless ex = @request&.env&.[]("action_dispatch.exception") + "\n\nException while processing request: #{Minitest::UnexpectedError.new(ex).message}\n" + end + + def location_if_redirected + return "" unless @response.redirection? && @response.location.present? + location = normalize_argument_to_redirection(@response.location) + " redirect to <#{location}>" + end + + def code_with_name(code_or_name) + if RESPONSE_PREDICATES.value?("#{code_or_name}?".to_sym) + code_or_name = RESPONSE_PREDICATES.invert["#{code_or_name}?".to_sym] + end + + AssertionResponse.new(code_or_name).code_and_name + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/assertions/routing.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/assertions/routing.rb new file mode 100644 index 00000000..50c1f00e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/assertions/routing.rb @@ -0,0 +1,347 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "uri" +require "active_support/core_ext/hash/indifferent_access" +require "active_support/core_ext/string/access" +require "active_support/core_ext/module/redefine_method" +require "action_controller/metal/exceptions" + +module ActionDispatch + module Assertions + # Suite of assertions to test routes generated by Rails and the handling of + # requests made to them. + module RoutingAssertions + extend ActiveSupport::Concern + + module WithIntegrationRouting # :nodoc: + extend ActiveSupport::Concern + + module ClassMethods + def with_routing(&block) + old_routes = nil + old_routes_call_method = nil + old_integration_session = nil + + setup do + old_routes = app.routes + old_routes_call_method = old_routes.method(:call) + old_integration_session = integration_session + create_routes(&block) + end + + teardown do + reset_routes(old_routes, old_routes_call_method, old_integration_session) + end + end + end + + def with_routing(&block) + old_routes = app.routes + old_routes_call_method = old_routes.method(:call) + old_integration_session = integration_session + create_routes(&block) + ensure + reset_routes(old_routes, old_routes_call_method, old_integration_session) + end + + private + def create_routes + app = self.app + routes = ActionDispatch::Routing::RouteSet.new + + @original_routes ||= app.routes + @original_routes.singleton_class.redefine_method(:call, &routes.method(:call)) + + https = integration_session.https? + host = integration_session.host + + app.instance_variable_set(:@routes, routes) + @integration_session = Class.new(ActionDispatch::Integration::Session) do + include app.routes.url_helpers + include app.routes.mounted_helpers + end.new(app) + @integration_session.https! https + @integration_session.host! host + @routes = routes + + yield routes + end + + def reset_routes(old_routes, old_routes_call_method, old_integration_session) + app.instance_variable_set(:@routes, old_routes) + @original_routes.singleton_class.redefine_method(:call, &old_routes_call_method) + @integration_session = old_integration_session + @routes = old_routes + end + end + + module ClassMethods + # A helper to make it easier to test different route configurations. This method + # temporarily replaces @routes with a new RouteSet instance before each test. + # + # The new instance is yielded to the passed block. Typically the block will + # create some routes using `set.draw { match ... }`: + # + # with_routing do |set| + # set.draw do + # resources :users + # end + # end + # + def with_routing(&block) + old_routes, old_controller = nil + + setup do + old_routes, old_controller = @routes, @controller + create_routes(&block) + end + + teardown do + reset_routes(old_routes, old_controller) + end + end + end + + def setup # :nodoc: + @routes ||= nil + super + end + + # A helper to make it easier to test different route configurations. This method + # temporarily replaces @routes with a new RouteSet instance. + # + # The new instance is yielded to the passed block. Typically the block will + # create some routes using `set.draw { match ... }`: + # + # with_routing do |set| + # set.draw do + # resources :users + # end + # assert_equal "/users", users_path + # end + # + def with_routing(config = nil, &block) + old_routes, old_controller = @routes, @controller + create_routes(config, &block) + ensure + reset_routes(old_routes, old_controller) + end + + # Asserts that the routing of the given `path` was handled correctly and that + # the parsed options (given in the `expected_options` hash) match `path`. + # Basically, it asserts that Rails recognizes the route given by + # `expected_options`. + # + # Pass a hash in the second argument (`path`) to specify the request method. + # This is useful for routes requiring a specific HTTP method. The hash should + # contain a `:path` with the incoming request path and a `:method` containing + # the required HTTP verb. + # + # # Asserts that POSTing to /items will call the create action on ItemsController + # assert_recognizes({controller: 'items', action: 'create'}, {path: 'items', method: :post}) + # + # You can also pass in `extras` with a hash containing URL parameters that would + # normally be in the query string. This can be used to assert that values in the + # query string will end up in the params hash correctly. To test query strings + # you must use the extras argument because appending the query string on the + # path directly will not work. For example: + # + # # Asserts that a path of '/items/list/1?view=print' returns the correct options + # assert_recognizes({controller: 'items', action: 'list', id: '1', view: 'print'}, 'items/list/1', { view: "print" }) + # + # The `message` parameter allows you to pass in an error message that is + # displayed upon failure. + # + # # Check the default route (i.e., the index action) + # assert_recognizes({controller: 'items', action: 'index'}, 'items') + # + # # Test a specific action + # assert_recognizes({controller: 'items', action: 'list'}, 'items/list') + # + # # Test an action with a parameter + # assert_recognizes({controller: 'items', action: 'destroy', id: '1'}, 'items/destroy/1') + # + # # Test a custom route + # assert_recognizes({controller: 'items', action: 'show', id: '1'}, 'view/item1') + def assert_recognizes(expected_options, path, extras = {}, msg = nil) + if path.is_a?(Hash) && path[:method].to_s == "all" + [:get, :post, :put, :delete].each do |method| + assert_recognizes(expected_options, path.merge(method: method), extras, msg) + end + else + request = recognized_request_for(path, extras, msg) + + expected_options = expected_options.clone + + expected_options.stringify_keys! + + msg = message(msg, "") { + sprintf("The recognized options <%s> did not match <%s>, difference:", + request.path_parameters, expected_options) + } + + assert_equal(expected_options, request.path_parameters, msg) + end + end + + # Asserts that the provided options can be used to generate the provided path. + # This is the inverse of `assert_recognizes`. The `extras` parameter is used to + # tell the request the names and values of additional request parameters that + # would be in a query string. The `message` parameter allows you to specify a + # custom error message for assertion failures. + # + # The `defaults` parameter is unused. + # + # # Asserts that the default action is generated for a route with no action + # assert_generates "/items", controller: "items", action: "index" + # + # # Tests that the list action is properly routed + # assert_generates "/items/list", controller: "items", action: "list" + # + # # Tests the generation of a route with a parameter + # assert_generates "/items/list/1", { controller: "items", action: "list", id: "1" } + # + # # Asserts that the generated route gives us our custom route + # assert_generates "changesets/12", { controller: 'scm', action: 'show_diff', revision: "12" } + def assert_generates(expected_path, options, defaults = {}, extras = {}, message = nil) + if expected_path.include?("://") + fail_on(URI::InvalidURIError, message) do + uri = URI.parse(expected_path) + expected_path = uri.path.to_s.empty? ? "/" : uri.path + end + else + expected_path = "/#{expected_path}" unless expected_path.start_with?("/") + end + + options = options.clone + generated_path, query_string_keys = @routes.generate_extras(options, defaults) + found_extras = options.reject { |k, _| ! query_string_keys.include? k } + + msg = message || sprintf("found extras <%s>, not <%s>", found_extras, extras) + assert_equal(extras, found_extras, msg) + + msg = message || sprintf("The generated path <%s> did not match <%s>", generated_path, + expected_path) + assert_equal(expected_path, generated_path, msg) + end + + # Asserts that path and options match both ways; in other words, it verifies + # that `path` generates `options` and then that `options` generates `path`. This + # essentially combines `assert_recognizes` and `assert_generates` into one step. + # + # The `extras` hash allows you to specify options that would normally be + # provided as a query string to the action. The `message` parameter allows you + # to specify a custom error message to display upon failure. + # + # # Asserts a basic route: a controller with the default action (index) + # assert_routing '/home', controller: 'home', action: 'index' + # + # # Test a route generated with a specific controller, action, and parameter (id) + # assert_routing '/entries/show/23', controller: 'entries', action: 'show', id: 23 + # + # # Asserts a basic route (controller + default action), with an error message if it fails + # assert_routing '/store', { controller: 'store', action: 'index' }, {}, {}, 'Route for store index not generated properly' + # + # # Tests a route, providing a defaults hash + # assert_routing 'controller/action/9', {id: "9", item: "square"}, {controller: "controller", action: "action"}, {}, {item: "square"} + # + # # Tests a route with an HTTP method + # assert_routing({ method: 'put', path: '/product/321' }, { controller: "product", action: "update", id: "321" }) + def assert_routing(path, options, defaults = {}, extras = {}, message = nil) + assert_recognizes(options, path, extras, message) + + controller, default_controller = options[:controller], defaults[:controller] + if controller && controller.include?(?/) && default_controller && default_controller.include?(?/) + options[:controller] = "/#{controller}" + end + + generate_options = options.dup.delete_if { |k, _| defaults.key?(k) } + assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message) + end + + # ROUTES TODO: These assertions should really work in an integration context + def method_missing(selector, ...) + if @controller && @routes&.named_routes&.route_defined?(selector) + @controller.public_send(selector, ...) + else + super + end + end + + private + def create_routes(config = nil) + @routes = ActionDispatch::Routing::RouteSet.new(config || ActionDispatch::Routing::RouteSet::DEFAULT_CONFIG) + if @controller + @controller = @controller.clone + _routes = @routes + + @controller.singleton_class.include(_routes.url_helpers) + + if @controller.respond_to? :view_context_class + view_context_class = Class.new(@controller.view_context_class) do + include _routes.url_helpers + end + + custom_view_context = Module.new { + define_method(:view_context_class) do + view_context_class + end + } + @controller.extend(custom_view_context) + end + end + yield @routes + end + + def reset_routes(old_routes, old_controller) + @routes = old_routes + if @controller + @controller = old_controller + end + end + + # Recognizes the route for a given path. + def recognized_request_for(path, extras = {}, msg) + if path.is_a?(Hash) + method = path[:method] + path = path[:path] + else + method = :get + end + + controller = @controller if defined?(@controller) + request = ActionController::TestRequest.create controller&.class + + if path.include?("://") + fail_on(URI::InvalidURIError, msg) do + uri = URI.parse(path) + request.env["rack.url_scheme"] = uri.scheme || "http" + request.host = uri.host if uri.host + request.port = uri.port if uri.port + request.path = uri.path.to_s.empty? ? "/" : uri.path + end + else + path = "/#{path}" unless path.start_with?("/") + request.path = path + end + + request.request_method = method if method + + params = fail_on(ActionController::RoutingError, msg) do + @routes.recognize_path(path, method: method, extras: extras) + end + request.path_parameters = params.with_indifferent_access + + request + end + + def fail_on(exception_class, message) + yield + rescue exception_class => e + raise Minitest::Assertion, message || e.message + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/integration.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/integration.rb new file mode 100644 index 00000000..ff2cd676 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/integration.rb @@ -0,0 +1,704 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "stringio" +require "uri" +require "rack/test" +require "active_support/test_case" + +require "action_dispatch/testing/request_encoder" +require "action_dispatch/testing/test_helpers/page_dump_helper" + +module ActionDispatch + module Integration # :nodoc: + module RequestHelpers + # Performs a GET request with the given parameters. See + # ActionDispatch::Integration::Session#process for more details. + def get(path, **args) + process(:get, path, **args) + end + + # Performs a POST request with the given parameters. See + # ActionDispatch::Integration::Session#process for more details. + def post(path, **args) + process(:post, path, **args) + end + + # Performs a PATCH request with the given parameters. See + # ActionDispatch::Integration::Session#process for more details. + def patch(path, **args) + process(:patch, path, **args) + end + + # Performs a PUT request with the given parameters. See + # ActionDispatch::Integration::Session#process for more details. + def put(path, **args) + process(:put, path, **args) + end + + # Performs a DELETE request with the given parameters. See + # ActionDispatch::Integration::Session#process for more details. + def delete(path, **args) + process(:delete, path, **args) + end + + # Performs a HEAD request with the given parameters. See + # ActionDispatch::Integration::Session#process for more details. + def head(path, **args) + process(:head, path, **args) + end + + # Performs an OPTIONS request with the given parameters. See + # ActionDispatch::Integration::Session#process for more details. + def options(path, **args) + process(:options, path, **args) + end + + # Follow a single redirect response. If the last response was not a redirect, an + # exception will be raised. Otherwise, the redirect is performed on the location + # header. If the redirection is a 307 or 308 redirect, the same HTTP verb will + # be used when redirecting, otherwise a GET request will be performed. Any + # arguments are passed to the underlying request. + # + # The HTTP_REFERER header will be set to the previous url. + def follow_redirect!(headers: {}, **args) + raise "not a redirect! #{status} #{status_message}" unless redirect? + + method = + if [307, 308].include?(response.status) + request.method.downcase + else + :get + end + + if [ :HTTP_REFERER, "HTTP_REFERER" ].none? { |key| headers.key? key } + headers["HTTP_REFERER"] = request.url + end + + public_send(method, response.location, headers: headers, **args) + status + end + end + + # An instance of this class represents a set of requests and responses performed + # sequentially by a test process. Because you can instantiate multiple sessions + # and run them side-by-side, you can also mimic (to some limited extent) + # multiple simultaneous users interacting with your system. + # + # Typically, you will instantiate a new session using Runner#open_session, + # rather than instantiating a Session directly. + class Session + DEFAULT_HOST = "www.example.com" + + include Minitest::Assertions + include TestProcess, RequestHelpers, Assertions + + delegate :status, :status_message, :headers, :body, :redirect?, to: :response, allow_nil: true + delegate :path, to: :request, allow_nil: true + + # The hostname used in the last request. + def host + @host || DEFAULT_HOST + end + attr_writer :host + + # The remote_addr used in the last request. + attr_accessor :remote_addr + + # The Accept header to send. + attr_accessor :accept + + # A map of the cookies returned by the last response, and which will be sent + # with the next request. + def cookies + _mock_session.cookie_jar + end + + # A reference to the controller instance used by the last request. + attr_reader :controller + + # A reference to the request instance used by the last request. + attr_reader :request + + # A reference to the response instance used by the last request. + attr_reader :response + + # A running counter of the number of requests processed. + attr_accessor :request_count + + include ActionDispatch::Routing::UrlFor + + # Create and initialize a new Session instance. + def initialize(app) + super() + @app = app + + reset! + end + + def url_options + @url_options ||= default_url_options.dup.tap do |url_options| + url_options.reverse_merge!(controller.url_options) if controller.respond_to?(:url_options) + + if @app.respond_to?(:routes) + url_options.reverse_merge!(@app.routes.default_url_options) + end + + url_options.reverse_merge!(host: host, protocol: https? ? "https" : "http") + end + end + + # Resets the instance. This can be used to reset the state information in an + # existing session instance, so it can be used from a clean-slate condition. + # + # session.reset! + def reset! + @https = false + @controller = @request = @response = nil + @_mock_session = nil + @request_count = 0 + @url_options = nil + + self.host = DEFAULT_HOST + self.remote_addr = "127.0.0.1" + self.accept = "text/xml,application/xml,application/xhtml+xml," \ + "text/html;q=0.9,text/plain;q=0.8,image/png," \ + "*/*;q=0.5" + + unless defined? @named_routes_configured + # the helpers are made protected by default--we make them public for easier + # access during testing and troubleshooting. + @named_routes_configured = true + end + end + + # Specify whether or not the session should mimic a secure HTTPS request. + # + # session.https! + # session.https!(false) + def https!(flag = true) + @https = flag + end + + # Returns `true` if the session is mimicking a secure HTTPS request. + # + # if session.https? + # ... + # end + def https? + @https + end + + # Performs the actual request. + # + # * `method`: The HTTP method (GET, POST, PATCH, PUT, DELETE, HEAD, OPTIONS) + # as a symbol. + # * `path`: The URI (as a String) on which you want to perform the request. + # * `params`: The HTTP parameters that you want to pass. This may be `nil`, a + # Hash, or a String that is appropriately encoded + # (`application/x-www-form-urlencoded` or `multipart/form-data`). + # * `headers`: Additional headers to pass, as a Hash. The headers will be + # merged into the Rack env hash. + # * `env`: Additional env to pass, as a Hash. The headers will be merged into + # the Rack env hash. + # * `xhr`: Set to `true` if you want to make an Ajax request. Adds request + # headers characteristic of XMLHttpRequest e.g. HTTP_X_REQUESTED_WITH. The + # headers will be merged into the Rack env hash. + # * `as`: Used for encoding the request with different content type. Supports + # `:json` by default and will set the appropriate request headers. The + # headers will be merged into the Rack env hash. + # + # + # This method is rarely used directly. Use RequestHelpers#get, + # RequestHelpers#post, or other standard HTTP methods in integration tests. + # `#process` is only required when using a request method that doesn't have a + # method defined in the integration tests. + # + # This method returns the response status, after performing the request. + # Furthermore, if this method was called from an ActionDispatch::IntegrationTest + # object, then that object's `@response` instance variable will point to a + # Response object which one can use to inspect the details of the response. + # + # Example: + # process :get, '/author', params: { since: 201501011400 } + def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: nil) + request_encoder = RequestEncoder.encoder(as) + headers ||= {} + + if method == :get && as == :json && params + headers["X-Http-Method-Override"] = "GET" + method = :post + end + + if path.include?("://") + path = build_expanded_path(path) do |location| + https! URI::HTTPS === location if location.scheme + + if url_host = location.host + default = Rack::Request::DEFAULT_PORTS[location.scheme] + url_host += ":#{location.port}" if default != location.port + host! url_host + end + end + end + + hostname, port = host.split(":") + + request_env = { + :method => method, + :params => request_encoder.encode_params(params), + + "SERVER_NAME" => hostname, + "SERVER_PORT" => port || (https? ? "443" : "80"), + "HTTPS" => https? ? "on" : "off", + "rack.url_scheme" => https? ? "https" : "http", + + "REQUEST_URI" => path, + "HTTP_HOST" => host, + "REMOTE_ADDR" => remote_addr, + "HTTP_ACCEPT" => request_encoder.accept_header || accept + } + + if request_encoder.content_type + request_env["CONTENT_TYPE"] = request_encoder.content_type + end + + wrapped_headers = Http::Headers.from_hash({}) + wrapped_headers.merge!(headers) if headers + + if xhr + wrapped_headers["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest" + wrapped_headers["HTTP_ACCEPT"] ||= [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ") + end + + # This modifies the passed request_env directly. + if wrapped_headers.present? + Http::Headers.from_hash(request_env).merge!(wrapped_headers) + end + if env.present? + Http::Headers.from_hash(request_env).merge!(env) + end + + session = Rack::Test::Session.new(_mock_session) + + # NOTE: rack-test v0.5 doesn't build a default uri correctly Make sure requested + # path is always a full URI. + uri = build_full_uri(path, request_env) + + if method == :get && String === request_env[:params] + # rack-test will needlessly parse and rebuild a :params + # querystring, using Rack's query parser. At best that's a + # waste of time; at worst it can change the value. + + uri << "?" << request_env.delete(:params) + end + + session.request(uri, request_env) + + @request_count += 1 + @request = ActionDispatch::Request.new(session.last_request.env) + response = _mock_session.last_response + @response = ActionDispatch::TestResponse.from_response(response) + @response.request = @request + @html_document = nil + @url_options = nil + + @controller = @request.controller_instance + + response.status + end + + # Set the host name to use in the next request. + # + # session.host! "www.example.com" + alias :host! :host= + + private + def _mock_session + @_mock_session ||= Rack::MockSession.new(@app, host) + end + + def build_full_uri(path, env) + "#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{path}" + end + + def build_expanded_path(path) + location = URI.parse(path) + yield location if block_given? + path = location.path + location.query ? "#{path}?#{location.query}" : path + end + end + + module Runner + include ActionDispatch::Assertions + + APP_SESSIONS = {} + + attr_reader :app + attr_accessor :root_session # :nodoc: + + def initialize(*args, &blk) + super(*args, &blk) + @integration_session = nil + end + + def before_setup # :nodoc: + @app = nil + super + end + + def integration_session + @integration_session ||= create_session(app) + end + + # Reset the current session. This is useful for testing multiple sessions in a + # single test case. + def reset! + @integration_session = create_session(app) + end + + def create_session(app) + klass = APP_SESSIONS[app] ||= Class.new(Integration::Session) { + # If the app is a Rails app, make url_helpers available on the session. This + # makes app.url_for and app.foo_path available in the console. + if app.respond_to?(:routes) && app.routes.is_a?(ActionDispatch::Routing::RouteSet) + include app.routes.url_helpers + include app.routes.mounted_helpers + end + } + klass.new(app) + end + + def remove! # :nodoc: + @integration_session = nil + end + + %w(get post patch put head delete cookies assigns follow_redirect!).each do |method| + # reset the html_document variable, except for cookies/assigns calls + unless method == "cookies" || method == "assigns" + reset_html_document = "@html_document = nil" + end + + module_eval <<~RUBY, __FILE__, __LINE__ + 1 + def #{method}(...) + #{reset_html_document} + + result = integration_session.#{method}(...) + copy_session_variables! + result + end + RUBY + end + + # Open a new session instance. If a block is given, the new session is yielded + # to the block before being returned. + # + # session = open_session do |sess| + # sess.extend(CustomAssertions) + # end + # + # By default, a single session is automatically created for you, but you can use + # this method to open multiple sessions that ought to be tested simultaneously. + def open_session + dup.tap do |session| + session.reset! + session.root_session = self.root_session || self + yield session if block_given? + end + end + + def assertions # :nodoc: + root_session ? root_session.assertions : super + end + + def assertions=(assertions) # :nodoc: + root_session ? root_session.assertions = assertions : super + end + + # Copy the instance variables from the current session instance into the test + # instance. + def copy_session_variables! # :nodoc: + @controller = @integration_session.controller + @response = @integration_session.response + @request = @integration_session.request + end + + def default_url_options + integration_session.default_url_options + end + + def default_url_options=(options) + integration_session.default_url_options = options + end + + private + def respond_to_missing?(method, _) + integration_session.respond_to?(method) || super + end + + # Delegate unhandled messages to the current session instance. + def method_missing(method, ...) + if integration_session.respond_to?(method) + integration_session.public_send(method, ...).tap do + copy_session_variables! + end + else + super + end + end + end + end + + # An integration test spans multiple controllers and actions, tying them all + # together to ensure they work together as expected. It tests more completely + # than either unit or functional tests do, exercising the entire stack, from the + # dispatcher to the database. + # + # At its simplest, you simply extend `IntegrationTest` and write your tests + # using the Integration::RequestHelpers#get and/or + # Integration::RequestHelpers#post methods: + # + # require "test_helper" + # + # class ExampleTest < ActionDispatch::IntegrationTest + # fixtures :people + # + # def test_login + # # get the login page + # get "/login" + # assert_equal 200, status + # + # # post the login and follow through to the home page + # post "/login", params: { username: people(:jamis).username, + # password: people(:jamis).password } + # follow_redirect! + # assert_equal 200, status + # assert_equal "/home", path + # end + # end + # + # However, you can also have multiple session instances open per test, and even + # extend those instances with assertions and methods to create a very powerful + # testing DSL that is specific for your application. You can even reference any + # named routes you happen to have defined. + # + # require "test_helper" + # + # class AdvancedTest < ActionDispatch::IntegrationTest + # fixtures :people, :rooms + # + # def test_login_and_speak + # jamis, david = login(:jamis), login(:david) + # room = rooms(:office) + # + # jamis.enter(room) + # jamis.speak(room, "anybody home?") + # + # david.enter(room) + # david.speak(room, "hello!") + # end + # + # private + # + # module CustomAssertions + # def enter(room) + # # reference a named route, for maximum internal consistency! + # get(room_url(id: room.id)) + # assert(...) + # ... + # end + # + # def speak(room, message) + # post "/say/#{room.id}", xhr: true, params: { message: message } + # assert(...) + # ... + # end + # end + # + # def login(who) + # open_session do |sess| + # sess.extend(CustomAssertions) + # who = people(who) + # sess.post "/login", params: { username: who.username, + # password: who.password } + # assert(...) + # end + # end + # end + # + # Another longer example would be: + # + # A simple integration test that exercises multiple controllers: + # + # require "test_helper" + # + # class UserFlowsTest < ActionDispatch::IntegrationTest + # test "login and browse site" do + # # login via https + # https! + # get "/login" + # assert_response :success + # + # post "/login", params: { username: users(:david).username, password: users(:david).password } + # follow_redirect! + # assert_equal '/welcome', path + # assert_equal 'Welcome david!', flash[:notice] + # + # https!(false) + # get "/articles/all" + # assert_response :success + # assert_dom 'h1', 'Articles' + # end + # end + # + # As you can see the integration test involves multiple controllers and + # exercises the entire stack from database to dispatcher. In addition you can + # have multiple session instances open simultaneously in a test and extend those + # instances with assertion methods to create a very powerful testing DSL + # (domain-specific language) just for your application. + # + # Here's an example of multiple sessions and custom DSL in an integration test + # + # require "test_helper" + # + # class UserFlowsTest < ActionDispatch::IntegrationTest + # test "login and browse site" do + # # User david logs in + # david = login(:david) + # # User guest logs in + # guest = login(:guest) + # + # # Both are now available in different sessions + # assert_equal 'Welcome david!', david.flash[:notice] + # assert_equal 'Welcome guest!', guest.flash[:notice] + # + # # User david can browse site + # david.browses_site + # # User guest can browse site as well + # guest.browses_site + # + # # Continue with other assertions + # end + # + # private + # + # module CustomDsl + # def browses_site + # get "/products/all" + # assert_response :success + # assert_dom 'h1', 'Products' + # end + # end + # + # def login(user) + # open_session do |sess| + # sess.extend(CustomDsl) + # u = users(user) + # sess.https! + # sess.post "/login", params: { username: u.username, password: u.password } + # assert_equal '/welcome', sess.path + # sess.https!(false) + # end + # end + # end + # + # See the [request helpers documentation] + # (rdoc-ref:ActionDispatch::Integration::RequestHelpers) for help + # on how to use `get`, etc. + # + # ### Changing the request encoding + # + # You can also test your JSON API easily by setting what the request should be + # encoded as: + # + # require "test_helper" + # + # class ApiTest < ActionDispatch::IntegrationTest + # test "creates articles" do + # assert_difference -> { Article.count } do + # post articles_path, params: { article: { title: "Ahoy!" } }, as: :json + # end + # + # assert_response :success + # assert_equal({ id: Article.last.id, title: "Ahoy!" }, response.parsed_body) + # end + # end + # + # The `as` option passes an "application/json" Accept header (thereby setting + # the request format to JSON unless overridden), sets the content type to + # "application/json" and encodes the parameters as JSON. + # + # Calling TestResponse#parsed_body on the response parses the response body + # based on the last response MIME type. + # + # Out of the box, only `:json` is supported. But for any custom MIME types + # you've registered, you can add your own encoders with: + # + # ActionDispatch::IntegrationTest.register_encoder :wibble, + # param_encoder: -> params { params.to_wibble }, + # response_parser: -> body { body } + # + # Where `param_encoder` defines how the params should be encoded and + # `response_parser` defines how the response body should be parsed through + # TestResponse#parsed_body. + # + # Consult the [Rails Testing Guide](https://guides.rubyonrails.org/testing.html) + # for more. + + class IntegrationTest < ActiveSupport::TestCase + include TestProcess::FixtureFile + + module UrlOptions + extend ActiveSupport::Concern + def url_options + integration_session.url_options + end + end + + module Behavior + extend ActiveSupport::Concern + + include Integration::Runner + include ActionController::TemplateAssertions + include TestHelpers::PageDumpHelper + + included do + include ActionDispatch::Routing::UrlFor + include UrlOptions # don't let UrlFor override the url_options method + include ActionDispatch::Assertions::RoutingAssertions::WithIntegrationRouting + ActiveSupport.run_load_hooks(:action_dispatch_integration_test, self) + @@app = nil + end + + module ClassMethods + def app + if defined?(@@app) && @@app + @@app + else + ActionDispatch.test_app + end + end + + def app=(app) + @@app = app + end + + def register_encoder(*args, **options) + RequestEncoder.register_encoder(*args, **options) + end + end + + def app + super || self.class.app + end + + def document_root_element + html_document.root + end + end + + include Behavior + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/request_encoder.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/request_encoder.rb new file mode 100644 index 00000000..f1b6ad82 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/request_encoder.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "nokogiri" + +module ActionDispatch + class RequestEncoder # :nodoc: + class IdentityEncoder + def content_type; end + def accept_header; end + def encode_params(params); params; end + def response_parser; -> body { body }; end + end + + @encoders = { identity: IdentityEncoder.new } + + attr_reader :response_parser + + def initialize(mime_name, param_encoder, response_parser) + @mime = Mime[mime_name] + + unless @mime + raise ArgumentError, "Can't register a request encoder for " \ + "unregistered MIME Type: #{mime_name}. See `Mime::Type.register`." + end + + @response_parser = response_parser || -> body { body } + @param_encoder = param_encoder || :"to_#{@mime.symbol}".to_proc + end + + def content_type + @mime.to_s + end + + def accept_header + @mime.to_s + end + + def encode_params(params) + @param_encoder.call(params) if params + end + + def self.parser(content_type) + type = Mime::Type.lookup(content_type).ref if content_type + encoder(type).response_parser + end + + def self.encoder(name) + @encoders[name] || @encoders[:identity] + end + + def self.register_encoder(mime_name, param_encoder: nil, response_parser: nil) + @encoders[mime_name] = new(mime_name, param_encoder, response_parser) + end + + register_encoder :html, response_parser: -> body { Rails::Dom::Testing.html_document.parse(body) } + register_encoder :json, response_parser: -> body { JSON.parse(body, object_class: ActiveSupport::HashWithIndifferentAccess) } + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb new file mode 100644 index 00000000..5642145c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module ActionDispatch + module TestHelpers + module PageDumpHelper + class InvalidResponse < StandardError; end + + # Saves the content of response body to a file and tries to open it in your browser. + # Launchy must be present in your Gemfile for the page to open automatically. + def save_and_open_page(path = html_dump_default_path) + save_page(path).tap { |s_path| open_file(s_path) } + end + + private + def save_page(path = html_dump_default_path) + raise InvalidResponse.new("Response is a redirection!") if response.redirection? + path = Pathname.new(path) + path.dirname.mkpath + File.write(path, response.body) + path + end + + def open_file(path) + require "launchy" + Launchy.open(path) + rescue LoadError + warn "File saved to #{path}.\nPlease install the launchy gem to open the file automatically." + end + + def html_dump_default_path + Rails.root.join("tmp/html_dump", "#{method_name}_#{DateTime.current.to_i}.html").to_s + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/test_process.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/test_process.rb new file mode 100644 index 00000000..a017e8b3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/test_process.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "action_dispatch/middleware/cookies" +require "action_dispatch/middleware/flash" + +module ActionDispatch + module TestProcess + module FixtureFile + # Shortcut for + # `Rack::Test::UploadedFile.new(File.join(ActionDispatch::IntegrationTest.file_fixture_path, path), type)`: + # + # post :change_avatar, params: { avatar: file_fixture_upload('david.png', 'image/png') } + # + # Default fixture files location is `test/fixtures/files`. + # + # To upload binary files on Windows, pass `:binary` as the last parameter. This + # will not affect other platforms: + # + # post :change_avatar, params: { avatar: file_fixture_upload('david.png', 'image/png', :binary) } + def file_fixture_upload(path, mime_type = nil, binary = false) + if self.class.file_fixture_path && !File.exist?(path) + path = file_fixture(path) + end + + Rack::Test::UploadedFile.new(path, mime_type, binary) + end + alias_method :fixture_file_upload, :file_fixture_upload + end + + include FixtureFile + + def assigns(key = nil) + raise NoMethodError, + 'assigns has been extracted to a gem. To continue using it, + add `gem "rails-controller-testing"` to your Gemfile.' + end + + def session + @request.session + end + + def flash + @request.flash + end + + def cookies + @cookie_jar ||= Cookies::CookieJar.build(@request, @request.cookies) + end + + def redirect_to_url + @response.redirect_url + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/test_request.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/test_request.rb new file mode 100644 index 00000000..9609640c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/test_request.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "active_support/core_ext/hash/indifferent_access" +require "rack/utils" + +module ActionDispatch + class TestRequest < Request + DEFAULT_ENV = Rack::MockRequest.env_for("/", + "HTTP_HOST" => "test.host".b, + "REMOTE_ADDR" => "0.0.0.0".b, + "HTTP_USER_AGENT" => "Rails Testing".b, + ) + + # Create a new test request with default `env` values. + def self.create(env = {}) + env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application + env["rack.request.cookie_hash"] ||= {}.with_indifferent_access + new(default_env.merge(env)) + end + + def self.default_env + DEFAULT_ENV + end + private_class_method :default_env + + def request_method=(method) + super(method.to_s.upcase) + end + + def host=(host) + set_header("HTTP_HOST", host) + end + + def port=(number) + set_header("SERVER_PORT", number) + end + + def request_uri=(uri) + set_header("REQUEST_URI", uri) + end + + def path=(path) + set_header("PATH_INFO", path) + end + + def action=(action_name) + path_parameters[:action] = action_name.to_s + end + + def if_modified_since=(last_modified) + set_header("HTTP_IF_MODIFIED_SINCE", last_modified) + end + + def if_none_match=(etag) + set_header("HTTP_IF_NONE_MATCH", etag) + end + + def remote_addr=(addr) + set_header("REMOTE_ADDR", addr) + end + + def user_agent=(user_agent) + set_header("HTTP_USER_AGENT", user_agent) + end + + def accept=(mime_types) + delete_header("action_dispatch.request.accepts") + set_header("HTTP_ACCEPT", Array(mime_types).collect(&:to_s).join(",")) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/test_response.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/test_response.rb new file mode 100644 index 00000000..a5adf866 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_dispatch/testing/test_response.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +# :markup: markdown + +require "action_dispatch/testing/request_encoder" + +module ActionDispatch + # Integration test methods such as Integration::RequestHelpers#get and + # Integration::RequestHelpers#post return objects of class TestResponse, which + # represent the HTTP response results of the requested controller actions. + # + # See Response for more information on controller response objects. + class TestResponse < Response + def self.from_response(response) + new response.status, response.headers, response.body + end + + # Returns a parsed body depending on the response MIME type. When a parser + # corresponding to the MIME type is not found, it returns the raw body. + # + # #### Examples + # get "/posts" + # response.content_type # => "text/html; charset=utf-8" + # response.parsed_body.class # => Nokogiri::HTML5::Document + # response.parsed_body.to_html # => "\n\n..." + # + # assert_pattern { response.parsed_body.at("main") => { content: "Hello, world" } } + # + # response.parsed_body.at("main") => {name:, content:} + # assert_equal "main", name + # assert_equal "Some main content", content + # + # get "/posts.json" + # response.content_type # => "application/json; charset=utf-8" + # response.parsed_body.class # => Array + # response.parsed_body # => [{"id"=>42, "title"=>"Title"},... + # + # assert_pattern { response.parsed_body => [{ id: 42 }] } + # + # get "/posts/42.json" + # response.content_type # => "application/json; charset=utf-8" + # response.parsed_body.class # => ActiveSupport::HashWithIndifferentAccess + # response.parsed_body # => {"id"=>42, "title"=>"Title"} + # + # assert_pattern { response.parsed_body => [{ title: /title/i }] } + # + # response.parsed_body => {id:, title:} + # assert_equal 42, id + # assert_equal "Title", title + def parsed_body + @parsed_body ||= response_parser.call(body) + end + + def response_parser + @response_parser ||= RequestEncoder.parser(media_type) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_pack.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_pack.rb new file mode 100644 index 00000000..53cd40d1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_pack.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) David Heinemeier Hansson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +#++ + +# :markup: markdown + +require "action_pack/version" diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_pack/gem_version.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_pack/gem_version.rb new file mode 100644 index 00000000..6b5e4930 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_pack/gem_version.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionPack + # Returns the currently loaded version of Action Pack as a `Gem::Version`. + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 8 + MINOR = 0 + TINY = 2 + PRE = nil + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_pack/version.rb b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_pack/version.rb new file mode 100644 index 00000000..1d5cfac8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionpack-8.0.2/lib/action_pack/version.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# :markup: markdown + +require_relative "gem_version" + +module ActionPack + # Returns the currently loaded version of Action Pack as a `Gem::Version`. + def self.version + gem_version + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/CHANGELOG.md b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/CHANGELOG.md new file mode 100644 index 00000000..61ba2e75 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/CHANGELOG.md @@ -0,0 +1,107 @@ +## Rails 8.0.2 (March 12, 2025) ## + +* No changes. + + +## Rails 8.0.2 (March 12, 2025) ## + +* Respect `html_options[:form]` when `collection_checkboxes` generates the + hidden ``. + + *Riccardo Odone* + +* Layouts have access to local variables passed to `render`. + + This fixes #31680 which was a regression in Rails 5.1. + + *Mike Dalessio* + +* Argument errors related to strict locals in templates now raise an + `ActionView::StrictLocalsError`, and all other argument errors are reraised as-is. + + Previously, any `ArgumentError` raised during template rendering was swallowed during strict + local error handling, so that an `ArgumentError` unrelated to strict locals (e.g., a helper + method invoked with incorrect arguments) would be replaced by a similar `ArgumentError` with an + unrelated backtrace, making it difficult to debug templates. + + Now, any `ArgumentError` unrelated to strict locals is reraised, preserving the original + backtrace for developers. + + Also note that `ActionView::StrictLocalsError` is a subclass of `ArgumentError`, so any existing + code that rescues `ArgumentError` will continue to work. + + Fixes #52227. + + *Mike Dalessio* + +* Fix stack overflow error in dependency tracker when dealing with circular dependencies + + *Jean Boussier* + +## Rails 8.0.1 (December 13, 2024) ## + +* Fix a crash in ERB template error highlighting when the error occurs on a + line in the compiled template that is past the end of the source template. + + *Martin Emde* + +* Improve reliability of ERB template error highlighting. + Fix infinite loops and crashes in highlighting and + improve tolerance for alternate ERB handlers. + + *Martin Emde* + + +## Rails 8.0.0.1 (December 10, 2024) ## + +* No changes. + + +## Rails 8.0.0 (November 07, 2024) ## + +* No changes. + + +## Rails 8.0.0.rc2 (October 30, 2024) ## + +* No changes. + + +## Rails 8.0.0.rc1 (October 19, 2024) ## + +* Remove deprecated support to passing a content to void tag elements on the `tag` builder. + + *Rafael Mendonça França* + +* Remove deprecated support to passing `nil` to the `model:` argument of `form_with`. + + *Rafael Mendonça França* + + +## Rails 8.0.0.beta1 (September 26, 2024) ## + +* Enable DependencyTracker to evaluate renders with trailing interpolation. + + ```erb + <%= render "maintenance_tasks/runs/info/#{run.status}" %> + ``` + + Previously, the DependencyTracker would ignore this render, but now it will + mark all partials in the "maintenance_tasks/runs/info" folder as + dependencies. + + *Hartley McGuire* + +* Rename `text_area` methods into `textarea` + + Old names are still available as aliases. + + *Sean Doyle* + +* Rename `check_box*` methods into `checkbox*`. + + Old names are still available as aliases. + + *Jean Boussier* + +Please check [7-2-stable](https://github.com/rails/rails/blob/7-2-stable/actionview/CHANGELOG.md) for previous changes. diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/MIT-LICENSE b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/MIT-LICENSE new file mode 100644 index 00000000..7be9ac63 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/MIT-LICENSE @@ -0,0 +1,21 @@ +Copyright (c) David Heinemeier Hansson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/README.rdoc b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/README.rdoc new file mode 100644 index 00000000..498dbaaf --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/README.rdoc @@ -0,0 +1,40 @@ += Action View + +Action View is a framework for handling view template lookup and rendering, and provides +view helpers that assist when building HTML forms, Atom feeds and more. +Template formats that Action View handles are ERB (embedded Ruby, typically +used to inline short Ruby snippets inside HTML), and XML Builder. + +You can read more about Action View in the {Action View Overview}[https://guides.rubyonrails.org/action_view_overview.html] guide. + +== Download and installation + +The latest version of Action View can be installed with RubyGems: + + $ gem install actionview + +Source code can be downloaded as part of the \Rails project on GitHub: + +* https://github.com/rails/rails/tree/main/actionview + + +== License + +Action View is released under the MIT license: + +* https://opensource.org/licenses/MIT + + +== Support + +API documentation is at + +* https://api.rubyonrails.org + +Bug reports for the Ruby on \Rails project can be filed here: + +* https://github.com/rails/rails/issues + +Feature requests should be discussed on the rails-core mailing list here: + +* https://discuss.rubyonrails.org/c/rubyonrails-core diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/app/assets/javascripts/rails-ujs.esm.js b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/app/assets/javascripts/rails-ujs.esm.js new file mode 100644 index 00000000..a812dc60 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/app/assets/javascripts/rails-ujs.esm.js @@ -0,0 +1,686 @@ +/* +Unobtrusive JavaScript +https://github.com/rails/rails/blob/main/actionview/app/javascript +Released under the MIT license + */ +const linkClickSelector = "a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]"; + +const buttonClickSelector = { + selector: "button[data-remote]:not([form]), button[data-confirm]:not([form])", + exclude: "form button" +}; + +const inputChangeSelector = "select[data-remote], input[data-remote], textarea[data-remote]"; + +const formSubmitSelector = "form:not([data-turbo=true])"; + +const formInputClickSelector = "form:not([data-turbo=true]) input[type=submit], form:not([data-turbo=true]) input[type=image], form:not([data-turbo=true]) button[type=submit], form:not([data-turbo=true]) button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])"; + +const formDisableSelector = "input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled"; + +const formEnableSelector = "input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled"; + +const fileInputSelector = "input[name][type=file]:not([disabled])"; + +const linkDisableSelector = "a[data-disable-with], a[data-disable]"; + +const buttonDisableSelector = "button[data-remote][data-disable-with], button[data-remote][data-disable]"; + +let nonce = null; + +const loadCSPNonce = () => { + const metaTag = document.querySelector("meta[name=csp-nonce]"); + return nonce = metaTag && metaTag.content; +}; + +const cspNonce = () => nonce || loadCSPNonce(); + +const m = Element.prototype.matches || Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector; + +const matches = function(element, selector) { + if (selector.exclude) { + return m.call(element, selector.selector) && !m.call(element, selector.exclude); + } else { + return m.call(element, selector); + } +}; + +const EXPANDO = "_ujsData"; + +const getData = (element, key) => element[EXPANDO] ? element[EXPANDO][key] : undefined; + +const setData = function(element, key, value) { + if (!element[EXPANDO]) { + element[EXPANDO] = {}; + } + return element[EXPANDO][key] = value; +}; + +const $ = selector => Array.prototype.slice.call(document.querySelectorAll(selector)); + +const isContentEditable = function(element) { + var isEditable = false; + do { + if (element.isContentEditable) { + isEditable = true; + break; + } + element = element.parentElement; + } while (element); + return isEditable; +}; + +const csrfToken = () => { + const meta = document.querySelector("meta[name=csrf-token]"); + return meta && meta.content; +}; + +const csrfParam = () => { + const meta = document.querySelector("meta[name=csrf-param]"); + return meta && meta.content; +}; + +const CSRFProtection = xhr => { + const token = csrfToken(); + if (token) { + return xhr.setRequestHeader("X-CSRF-Token", token); + } +}; + +const refreshCSRFTokens = () => { + const token = csrfToken(); + const param = csrfParam(); + if (token && param) { + return $('form input[name="' + param + '"]').forEach((input => input.value = token)); + } +}; + +const AcceptHeaders = { + "*": "*/*", + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript", + script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" +}; + +const ajax = options => { + options = prepareOptions(options); + var xhr = createXHR(options, (function() { + const response = processResponse(xhr.response != null ? xhr.response : xhr.responseText, xhr.getResponseHeader("Content-Type")); + if (Math.floor(xhr.status / 100) === 2) { + if (typeof options.success === "function") { + options.success(response, xhr.statusText, xhr); + } + } else { + if (typeof options.error === "function") { + options.error(response, xhr.statusText, xhr); + } + } + return typeof options.complete === "function" ? options.complete(xhr, xhr.statusText) : undefined; + })); + if (options.beforeSend && !options.beforeSend(xhr, options)) { + return false; + } + if (xhr.readyState === XMLHttpRequest.OPENED) { + return xhr.send(options.data); + } +}; + +var prepareOptions = function(options) { + options.url = options.url || location.href; + options.type = options.type.toUpperCase(); + if (options.type === "GET" && options.data) { + if (options.url.indexOf("?") < 0) { + options.url += "?" + options.data; + } else { + options.url += "&" + options.data; + } + } + if (!(options.dataType in AcceptHeaders)) { + options.dataType = "*"; + } + options.accept = AcceptHeaders[options.dataType]; + if (options.dataType !== "*") { + options.accept += ", */*; q=0.01"; + } + return options; +}; + +var createXHR = function(options, done) { + const xhr = new XMLHttpRequest; + xhr.open(options.type, options.url, true); + xhr.setRequestHeader("Accept", options.accept); + if (typeof options.data === "string") { + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); + } + if (!options.crossDomain) { + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + CSRFProtection(xhr); + } + xhr.withCredentials = !!options.withCredentials; + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + return done(xhr); + } + }; + return xhr; +}; + +var processResponse = function(response, type) { + if (typeof response === "string" && typeof type === "string") { + if (type.match(/\bjson\b/)) { + try { + response = JSON.parse(response); + } catch (error) {} + } else if (type.match(/\b(?:java|ecma)script\b/)) { + const script = document.createElement("script"); + script.setAttribute("nonce", cspNonce()); + script.text = response; + document.head.appendChild(script).parentNode.removeChild(script); + } else if (type.match(/\b(xml|html|svg)\b/)) { + const parser = new DOMParser; + type = type.replace(/;.+/, ""); + try { + response = parser.parseFromString(response, type); + } catch (error1) {} + } + } + return response; +}; + +const href = element => element.href; + +const isCrossDomain = function(url) { + const originAnchor = document.createElement("a"); + originAnchor.href = location.href; + const urlAnchor = document.createElement("a"); + try { + urlAnchor.href = url; + return !((!urlAnchor.protocol || urlAnchor.protocol === ":") && !urlAnchor.host || originAnchor.protocol + "//" + originAnchor.host === urlAnchor.protocol + "//" + urlAnchor.host); + } catch (e) { + return true; + } +}; + +let preventDefault; + +let {CustomEvent: CustomEvent} = window; + +if (typeof CustomEvent !== "function") { + CustomEvent = function(event, params) { + const evt = document.createEvent("CustomEvent"); + evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); + return evt; + }; + CustomEvent.prototype = window.Event.prototype; + ({preventDefault: preventDefault} = CustomEvent.prototype); + CustomEvent.prototype.preventDefault = function() { + const result = preventDefault.call(this); + if (this.cancelable && !this.defaultPrevented) { + Object.defineProperty(this, "defaultPrevented", { + get() { + return true; + } + }); + } + return result; + }; +} + +const fire = (obj, name, data) => { + const event = new CustomEvent(name, { + bubbles: true, + cancelable: true, + detail: data + }); + obj.dispatchEvent(event); + return !event.defaultPrevented; +}; + +const stopEverything = e => { + fire(e.target, "ujs:everythingStopped"); + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); +}; + +const delegate = (element, selector, eventType, handler) => element.addEventListener(eventType, (function(e) { + let {target: target} = e; + while (!!(target instanceof Element) && !matches(target, selector)) { + target = target.parentNode; + } + if (target instanceof Element && handler.call(target, e) === false) { + e.preventDefault(); + e.stopPropagation(); + } +})); + +const toArray = e => Array.prototype.slice.call(e); + +const serializeElement = (element, additionalParam) => { + let inputs = [ element ]; + if (matches(element, "form")) { + inputs = toArray(element.elements); + } + const params = []; + inputs.forEach((function(input) { + if (!input.name || input.disabled) { + return; + } + if (matches(input, "fieldset[disabled] *")) { + return; + } + if (matches(input, "select")) { + toArray(input.options).forEach((function(option) { + if (option.selected) { + params.push({ + name: input.name, + value: option.value + }); + } + })); + } else if (input.checked || [ "radio", "checkbox", "submit" ].indexOf(input.type) === -1) { + params.push({ + name: input.name, + value: input.value + }); + } + })); + if (additionalParam) { + params.push(additionalParam); + } + return params.map((function(param) { + if (param.name) { + return `${encodeURIComponent(param.name)}=${encodeURIComponent(param.value)}`; + } else { + return param; + } + })).join("&"); +}; + +const formElements = (form, selector) => { + if (matches(form, "form")) { + return toArray(form.elements).filter((el => matches(el, selector))); + } else { + return toArray(form.querySelectorAll(selector)); + } +}; + +const handleConfirmWithRails = rails => function(e) { + if (!allowAction(this, rails)) { + stopEverything(e); + } +}; + +const confirm = (message, element) => window.confirm(message); + +var allowAction = function(element, rails) { + let callback; + const message = element.getAttribute("data-confirm"); + if (!message) { + return true; + } + let answer = false; + if (fire(element, "confirm")) { + try { + answer = rails.confirm(message, element); + } catch (error) {} + callback = fire(element, "confirm:complete", [ answer ]); + } + return answer && callback; +}; + +const handleDisabledElement = function(e) { + const element = this; + if (element.disabled) { + stopEverything(e); + } +}; + +const enableElement = e => { + let element; + if (e instanceof Event) { + if (isXhrRedirect(e)) { + return; + } + element = e.target; + } else { + element = e; + } + if (isContentEditable(element)) { + return; + } + if (matches(element, linkDisableSelector)) { + return enableLinkElement(element); + } else if (matches(element, buttonDisableSelector) || matches(element, formEnableSelector)) { + return enableFormElement(element); + } else if (matches(element, formSubmitSelector)) { + return enableFormElements(element); + } +}; + +const disableElement = e => { + const element = e instanceof Event ? e.target : e; + if (isContentEditable(element)) { + return; + } + if (matches(element, linkDisableSelector)) { + return disableLinkElement(element); + } else if (matches(element, buttonDisableSelector) || matches(element, formDisableSelector)) { + return disableFormElement(element); + } else if (matches(element, formSubmitSelector)) { + return disableFormElements(element); + } +}; + +var disableLinkElement = function(element) { + if (getData(element, "ujs:disabled")) { + return; + } + const replacement = element.getAttribute("data-disable-with"); + if (replacement != null) { + setData(element, "ujs:enable-with", element.innerHTML); + element.innerHTML = replacement; + } + element.addEventListener("click", stopEverything); + return setData(element, "ujs:disabled", true); +}; + +var enableLinkElement = function(element) { + const originalText = getData(element, "ujs:enable-with"); + if (originalText != null) { + element.innerHTML = originalText; + setData(element, "ujs:enable-with", null); + } + element.removeEventListener("click", stopEverything); + return setData(element, "ujs:disabled", null); +}; + +var disableFormElements = form => formElements(form, formDisableSelector).forEach(disableFormElement); + +var disableFormElement = function(element) { + if (getData(element, "ujs:disabled")) { + return; + } + const replacement = element.getAttribute("data-disable-with"); + if (replacement != null) { + if (matches(element, "button")) { + setData(element, "ujs:enable-with", element.innerHTML); + element.innerHTML = replacement; + } else { + setData(element, "ujs:enable-with", element.value); + element.value = replacement; + } + } + element.disabled = true; + return setData(element, "ujs:disabled", true); +}; + +var enableFormElements = form => formElements(form, formEnableSelector).forEach((element => enableFormElement(element))); + +var enableFormElement = function(element) { + const originalText = getData(element, "ujs:enable-with"); + if (originalText != null) { + if (matches(element, "button")) { + element.innerHTML = originalText; + } else { + element.value = originalText; + } + setData(element, "ujs:enable-with", null); + } + element.disabled = false; + return setData(element, "ujs:disabled", null); +}; + +var isXhrRedirect = function(event) { + const xhr = event.detail ? event.detail[0] : undefined; + return xhr && xhr.getResponseHeader("X-Xhr-Redirect"); +}; + +const handleMethodWithRails = rails => function(e) { + const link = this; + const method = link.getAttribute("data-method"); + if (!method) { + return; + } + if (isContentEditable(this)) { + return; + } + const href = rails.href(link); + const csrfToken$1 = csrfToken(); + const csrfParam$1 = csrfParam(); + const form = document.createElement("form"); + let formContent = ``; + if (csrfParam$1 && csrfToken$1 && !isCrossDomain(href)) { + formContent += ``; + } + formContent += ''; + form.method = "post"; + form.action = href; + form.target = link.target; + form.innerHTML = formContent; + form.style.display = "none"; + document.body.appendChild(form); + form.querySelector('[type="submit"]').click(); + stopEverything(e); +}; + +const isRemote = function(element) { + const value = element.getAttribute("data-remote"); + return value != null && value !== "false"; +}; + +const handleRemoteWithRails = rails => function(e) { + let data, method, url; + const element = this; + if (!isRemote(element)) { + return true; + } + if (!fire(element, "ajax:before")) { + fire(element, "ajax:stopped"); + return false; + } + if (isContentEditable(element)) { + fire(element, "ajax:stopped"); + return false; + } + const withCredentials = element.getAttribute("data-with-credentials"); + const dataType = element.getAttribute("data-type") || "script"; + if (matches(element, formSubmitSelector)) { + const button = getData(element, "ujs:submit-button"); + method = getData(element, "ujs:submit-button-formmethod") || element.getAttribute("method") || "get"; + url = getData(element, "ujs:submit-button-formaction") || element.getAttribute("action") || location.href; + if (method.toUpperCase() === "GET") { + url = url.replace(/\?.*$/, ""); + } + if (element.enctype === "multipart/form-data") { + data = new FormData(element); + if (button != null) { + data.append(button.name, button.value); + } + } else { + data = serializeElement(element, button); + } + setData(element, "ujs:submit-button", null); + setData(element, "ujs:submit-button-formmethod", null); + setData(element, "ujs:submit-button-formaction", null); + } else if (matches(element, buttonClickSelector) || matches(element, inputChangeSelector)) { + method = element.getAttribute("data-method"); + url = element.getAttribute("data-url"); + data = serializeElement(element, element.getAttribute("data-params")); + } else { + method = element.getAttribute("data-method"); + url = rails.href(element); + data = element.getAttribute("data-params"); + } + ajax({ + type: method || "GET", + url: url, + data: data, + dataType: dataType, + beforeSend(xhr, options) { + if (fire(element, "ajax:beforeSend", [ xhr, options ])) { + return fire(element, "ajax:send", [ xhr ]); + } else { + fire(element, "ajax:stopped"); + return false; + } + }, + success(...args) { + return fire(element, "ajax:success", args); + }, + error(...args) { + return fire(element, "ajax:error", args); + }, + complete(...args) { + return fire(element, "ajax:complete", args); + }, + crossDomain: isCrossDomain(url), + withCredentials: withCredentials != null && withCredentials !== "false" + }); + stopEverything(e); +}; + +const formSubmitButtonClick = function(e) { + const button = this; + const {form: form} = button; + if (!form) { + return; + } + if (button.name) { + setData(form, "ujs:submit-button", { + name: button.name, + value: button.value + }); + } + setData(form, "ujs:formnovalidate-button", button.formNoValidate); + setData(form, "ujs:submit-button-formaction", button.getAttribute("formaction")); + return setData(form, "ujs:submit-button-formmethod", button.getAttribute("formmethod")); +}; + +const preventInsignificantClick = function(e) { + const link = this; + const method = (link.getAttribute("data-method") || "GET").toUpperCase(); + const data = link.getAttribute("data-params"); + const metaClick = e.metaKey || e.ctrlKey; + const insignificantMetaClick = metaClick && method === "GET" && !data; + const nonPrimaryMouseClick = e.button != null && e.button !== 0; + if (nonPrimaryMouseClick || insignificantMetaClick) { + e.stopImmediatePropagation(); + } +}; + +const Rails = { + $: $, + ajax: ajax, + buttonClickSelector: buttonClickSelector, + buttonDisableSelector: buttonDisableSelector, + confirm: confirm, + cspNonce: cspNonce, + csrfToken: csrfToken, + csrfParam: csrfParam, + CSRFProtection: CSRFProtection, + delegate: delegate, + disableElement: disableElement, + enableElement: enableElement, + fileInputSelector: fileInputSelector, + fire: fire, + formElements: formElements, + formEnableSelector: formEnableSelector, + formDisableSelector: formDisableSelector, + formInputClickSelector: formInputClickSelector, + formSubmitButtonClick: formSubmitButtonClick, + formSubmitSelector: formSubmitSelector, + getData: getData, + handleDisabledElement: handleDisabledElement, + href: href, + inputChangeSelector: inputChangeSelector, + isCrossDomain: isCrossDomain, + linkClickSelector: linkClickSelector, + linkDisableSelector: linkDisableSelector, + loadCSPNonce: loadCSPNonce, + matches: matches, + preventInsignificantClick: preventInsignificantClick, + refreshCSRFTokens: refreshCSRFTokens, + serializeElement: serializeElement, + setData: setData, + stopEverything: stopEverything +}; + +const handleConfirm = handleConfirmWithRails(Rails); + +Rails.handleConfirm = handleConfirm; + +const handleMethod = handleMethodWithRails(Rails); + +Rails.handleMethod = handleMethod; + +const handleRemote = handleRemoteWithRails(Rails); + +Rails.handleRemote = handleRemote; + +const start = function() { + if (window._rails_loaded) { + throw new Error("rails-ujs has already been loaded!"); + } + window.addEventListener("pageshow", (function() { + $(formEnableSelector).forEach((function(el) { + if (getData(el, "ujs:disabled")) { + enableElement(el); + } + })); + $(linkDisableSelector).forEach((function(el) { + if (getData(el, "ujs:disabled")) { + enableElement(el); + } + })); + })); + delegate(document, linkDisableSelector, "ajax:complete", enableElement); + delegate(document, linkDisableSelector, "ajax:stopped", enableElement); + delegate(document, buttonDisableSelector, "ajax:complete", enableElement); + delegate(document, buttonDisableSelector, "ajax:stopped", enableElement); + delegate(document, linkClickSelector, "click", preventInsignificantClick); + delegate(document, linkClickSelector, "click", handleDisabledElement); + delegate(document, linkClickSelector, "click", handleConfirm); + delegate(document, linkClickSelector, "click", disableElement); + delegate(document, linkClickSelector, "click", handleRemote); + delegate(document, linkClickSelector, "click", handleMethod); + delegate(document, buttonClickSelector, "click", preventInsignificantClick); + delegate(document, buttonClickSelector, "click", handleDisabledElement); + delegate(document, buttonClickSelector, "click", handleConfirm); + delegate(document, buttonClickSelector, "click", disableElement); + delegate(document, buttonClickSelector, "click", handleRemote); + delegate(document, inputChangeSelector, "change", handleDisabledElement); + delegate(document, inputChangeSelector, "change", handleConfirm); + delegate(document, inputChangeSelector, "change", handleRemote); + delegate(document, formSubmitSelector, "submit", handleDisabledElement); + delegate(document, formSubmitSelector, "submit", handleConfirm); + delegate(document, formSubmitSelector, "submit", handleRemote); + delegate(document, formSubmitSelector, "submit", (e => setTimeout((() => disableElement(e)), 13))); + delegate(document, formSubmitSelector, "ajax:send", disableElement); + delegate(document, formSubmitSelector, "ajax:complete", enableElement); + delegate(document, formInputClickSelector, "click", preventInsignificantClick); + delegate(document, formInputClickSelector, "click", handleDisabledElement); + delegate(document, formInputClickSelector, "click", handleConfirm); + delegate(document, formInputClickSelector, "click", formSubmitButtonClick); + document.addEventListener("DOMContentLoaded", refreshCSRFTokens); + document.addEventListener("DOMContentLoaded", loadCSPNonce); + return window._rails_loaded = true; +}; + +Rails.start = start; + +if (typeof jQuery !== "undefined" && jQuery && jQuery.ajax) { + if (jQuery.rails) { + throw new Error("If you load both jquery_ujs and rails-ujs, use rails-ujs only."); + } + jQuery.rails = Rails; + jQuery.ajaxPrefilter((function(options, originalOptions, xhr) { + if (!options.crossDomain) { + return CSRFProtection(xhr); + } + })); +} + +export { Rails as default }; diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/app/assets/javascripts/rails-ujs.js b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/app/assets/javascripts/rails-ujs.js new file mode 100644 index 00000000..035a5210 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/app/assets/javascripts/rails-ujs.js @@ -0,0 +1,630 @@ +/* +Unobtrusive JavaScript +https://github.com/rails/rails/blob/main/actionview/app/javascript +Released under the MIT license + */ +(function(global, factory) { + typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory() : typeof define === "function" && define.amd ? define(factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, + global.Rails = factory()); +})(this, (function() { + "use strict"; + const linkClickSelector = "a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]"; + const buttonClickSelector = { + selector: "button[data-remote]:not([form]), button[data-confirm]:not([form])", + exclude: "form button" + }; + const inputChangeSelector = "select[data-remote], input[data-remote], textarea[data-remote]"; + const formSubmitSelector = "form:not([data-turbo=true])"; + const formInputClickSelector = "form:not([data-turbo=true]) input[type=submit], form:not([data-turbo=true]) input[type=image], form:not([data-turbo=true]) button[type=submit], form:not([data-turbo=true]) button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])"; + const formDisableSelector = "input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled"; + const formEnableSelector = "input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled"; + const fileInputSelector = "input[name][type=file]:not([disabled])"; + const linkDisableSelector = "a[data-disable-with], a[data-disable]"; + const buttonDisableSelector = "button[data-remote][data-disable-with], button[data-remote][data-disable]"; + let nonce = null; + const loadCSPNonce = () => { + const metaTag = document.querySelector("meta[name=csp-nonce]"); + return nonce = metaTag && metaTag.content; + }; + const cspNonce = () => nonce || loadCSPNonce(); + const m = Element.prototype.matches || Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector; + const matches = function(element, selector) { + if (selector.exclude) { + return m.call(element, selector.selector) && !m.call(element, selector.exclude); + } else { + return m.call(element, selector); + } + }; + const EXPANDO = "_ujsData"; + const getData = (element, key) => element[EXPANDO] ? element[EXPANDO][key] : undefined; + const setData = function(element, key, value) { + if (!element[EXPANDO]) { + element[EXPANDO] = {}; + } + return element[EXPANDO][key] = value; + }; + const $ = selector => Array.prototype.slice.call(document.querySelectorAll(selector)); + const isContentEditable = function(element) { + var isEditable = false; + do { + if (element.isContentEditable) { + isEditable = true; + break; + } + element = element.parentElement; + } while (element); + return isEditable; + }; + const csrfToken = () => { + const meta = document.querySelector("meta[name=csrf-token]"); + return meta && meta.content; + }; + const csrfParam = () => { + const meta = document.querySelector("meta[name=csrf-param]"); + return meta && meta.content; + }; + const CSRFProtection = xhr => { + const token = csrfToken(); + if (token) { + return xhr.setRequestHeader("X-CSRF-Token", token); + } + }; + const refreshCSRFTokens = () => { + const token = csrfToken(); + const param = csrfParam(); + if (token && param) { + return $('form input[name="' + param + '"]').forEach((input => input.value = token)); + } + }; + const AcceptHeaders = { + "*": "*/*", + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript", + script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" + }; + const ajax = options => { + options = prepareOptions(options); + var xhr = createXHR(options, (function() { + const response = processResponse(xhr.response != null ? xhr.response : xhr.responseText, xhr.getResponseHeader("Content-Type")); + if (Math.floor(xhr.status / 100) === 2) { + if (typeof options.success === "function") { + options.success(response, xhr.statusText, xhr); + } + } else { + if (typeof options.error === "function") { + options.error(response, xhr.statusText, xhr); + } + } + return typeof options.complete === "function" ? options.complete(xhr, xhr.statusText) : undefined; + })); + if (options.beforeSend && !options.beforeSend(xhr, options)) { + return false; + } + if (xhr.readyState === XMLHttpRequest.OPENED) { + return xhr.send(options.data); + } + }; + var prepareOptions = function(options) { + options.url = options.url || location.href; + options.type = options.type.toUpperCase(); + if (options.type === "GET" && options.data) { + if (options.url.indexOf("?") < 0) { + options.url += "?" + options.data; + } else { + options.url += "&" + options.data; + } + } + if (!(options.dataType in AcceptHeaders)) { + options.dataType = "*"; + } + options.accept = AcceptHeaders[options.dataType]; + if (options.dataType !== "*") { + options.accept += ", */*; q=0.01"; + } + return options; + }; + var createXHR = function(options, done) { + const xhr = new XMLHttpRequest; + xhr.open(options.type, options.url, true); + xhr.setRequestHeader("Accept", options.accept); + if (typeof options.data === "string") { + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); + } + if (!options.crossDomain) { + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + CSRFProtection(xhr); + } + xhr.withCredentials = !!options.withCredentials; + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + return done(xhr); + } + }; + return xhr; + }; + var processResponse = function(response, type) { + if (typeof response === "string" && typeof type === "string") { + if (type.match(/\bjson\b/)) { + try { + response = JSON.parse(response); + } catch (error) {} + } else if (type.match(/\b(?:java|ecma)script\b/)) { + const script = document.createElement("script"); + script.setAttribute("nonce", cspNonce()); + script.text = response; + document.head.appendChild(script).parentNode.removeChild(script); + } else if (type.match(/\b(xml|html|svg)\b/)) { + const parser = new DOMParser; + type = type.replace(/;.+/, ""); + try { + response = parser.parseFromString(response, type); + } catch (error1) {} + } + } + return response; + }; + const href = element => element.href; + const isCrossDomain = function(url) { + const originAnchor = document.createElement("a"); + originAnchor.href = location.href; + const urlAnchor = document.createElement("a"); + try { + urlAnchor.href = url; + return !((!urlAnchor.protocol || urlAnchor.protocol === ":") && !urlAnchor.host || originAnchor.protocol + "//" + originAnchor.host === urlAnchor.protocol + "//" + urlAnchor.host); + } catch (e) { + return true; + } + }; + let preventDefault; + let {CustomEvent: CustomEvent} = window; + if (typeof CustomEvent !== "function") { + CustomEvent = function(event, params) { + const evt = document.createEvent("CustomEvent"); + evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); + return evt; + }; + CustomEvent.prototype = window.Event.prototype; + ({preventDefault: preventDefault} = CustomEvent.prototype); + CustomEvent.prototype.preventDefault = function() { + const result = preventDefault.call(this); + if (this.cancelable && !this.defaultPrevented) { + Object.defineProperty(this, "defaultPrevented", { + get() { + return true; + } + }); + } + return result; + }; + } + const fire = (obj, name, data) => { + const event = new CustomEvent(name, { + bubbles: true, + cancelable: true, + detail: data + }); + obj.dispatchEvent(event); + return !event.defaultPrevented; + }; + const stopEverything = e => { + fire(e.target, "ujs:everythingStopped"); + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + }; + const delegate = (element, selector, eventType, handler) => element.addEventListener(eventType, (function(e) { + let {target: target} = e; + while (!!(target instanceof Element) && !matches(target, selector)) { + target = target.parentNode; + } + if (target instanceof Element && handler.call(target, e) === false) { + e.preventDefault(); + e.stopPropagation(); + } + })); + const toArray = e => Array.prototype.slice.call(e); + const serializeElement = (element, additionalParam) => { + let inputs = [ element ]; + if (matches(element, "form")) { + inputs = toArray(element.elements); + } + const params = []; + inputs.forEach((function(input) { + if (!input.name || input.disabled) { + return; + } + if (matches(input, "fieldset[disabled] *")) { + return; + } + if (matches(input, "select")) { + toArray(input.options).forEach((function(option) { + if (option.selected) { + params.push({ + name: input.name, + value: option.value + }); + } + })); + } else if (input.checked || [ "radio", "checkbox", "submit" ].indexOf(input.type) === -1) { + params.push({ + name: input.name, + value: input.value + }); + } + })); + if (additionalParam) { + params.push(additionalParam); + } + return params.map((function(param) { + if (param.name) { + return `${encodeURIComponent(param.name)}=${encodeURIComponent(param.value)}`; + } else { + return param; + } + })).join("&"); + }; + const formElements = (form, selector) => { + if (matches(form, "form")) { + return toArray(form.elements).filter((el => matches(el, selector))); + } else { + return toArray(form.querySelectorAll(selector)); + } + }; + const handleConfirmWithRails = rails => function(e) { + if (!allowAction(this, rails)) { + stopEverything(e); + } + }; + const confirm = (message, element) => window.confirm(message); + var allowAction = function(element, rails) { + let callback; + const message = element.getAttribute("data-confirm"); + if (!message) { + return true; + } + let answer = false; + if (fire(element, "confirm")) { + try { + answer = rails.confirm(message, element); + } catch (error) {} + callback = fire(element, "confirm:complete", [ answer ]); + } + return answer && callback; + }; + const handleDisabledElement = function(e) { + const element = this; + if (element.disabled) { + stopEverything(e); + } + }; + const enableElement = e => { + let element; + if (e instanceof Event) { + if (isXhrRedirect(e)) { + return; + } + element = e.target; + } else { + element = e; + } + if (isContentEditable(element)) { + return; + } + if (matches(element, linkDisableSelector)) { + return enableLinkElement(element); + } else if (matches(element, buttonDisableSelector) || matches(element, formEnableSelector)) { + return enableFormElement(element); + } else if (matches(element, formSubmitSelector)) { + return enableFormElements(element); + } + }; + const disableElement = e => { + const element = e instanceof Event ? e.target : e; + if (isContentEditable(element)) { + return; + } + if (matches(element, linkDisableSelector)) { + return disableLinkElement(element); + } else if (matches(element, buttonDisableSelector) || matches(element, formDisableSelector)) { + return disableFormElement(element); + } else if (matches(element, formSubmitSelector)) { + return disableFormElements(element); + } + }; + var disableLinkElement = function(element) { + if (getData(element, "ujs:disabled")) { + return; + } + const replacement = element.getAttribute("data-disable-with"); + if (replacement != null) { + setData(element, "ujs:enable-with", element.innerHTML); + element.innerHTML = replacement; + } + element.addEventListener("click", stopEverything); + return setData(element, "ujs:disabled", true); + }; + var enableLinkElement = function(element) { + const originalText = getData(element, "ujs:enable-with"); + if (originalText != null) { + element.innerHTML = originalText; + setData(element, "ujs:enable-with", null); + } + element.removeEventListener("click", stopEverything); + return setData(element, "ujs:disabled", null); + }; + var disableFormElements = form => formElements(form, formDisableSelector).forEach(disableFormElement); + var disableFormElement = function(element) { + if (getData(element, "ujs:disabled")) { + return; + } + const replacement = element.getAttribute("data-disable-with"); + if (replacement != null) { + if (matches(element, "button")) { + setData(element, "ujs:enable-with", element.innerHTML); + element.innerHTML = replacement; + } else { + setData(element, "ujs:enable-with", element.value); + element.value = replacement; + } + } + element.disabled = true; + return setData(element, "ujs:disabled", true); + }; + var enableFormElements = form => formElements(form, formEnableSelector).forEach((element => enableFormElement(element))); + var enableFormElement = function(element) { + const originalText = getData(element, "ujs:enable-with"); + if (originalText != null) { + if (matches(element, "button")) { + element.innerHTML = originalText; + } else { + element.value = originalText; + } + setData(element, "ujs:enable-with", null); + } + element.disabled = false; + return setData(element, "ujs:disabled", null); + }; + var isXhrRedirect = function(event) { + const xhr = event.detail ? event.detail[0] : undefined; + return xhr && xhr.getResponseHeader("X-Xhr-Redirect"); + }; + const handleMethodWithRails = rails => function(e) { + const link = this; + const method = link.getAttribute("data-method"); + if (!method) { + return; + } + if (isContentEditable(this)) { + return; + } + const href = rails.href(link); + const csrfToken$1 = csrfToken(); + const csrfParam$1 = csrfParam(); + const form = document.createElement("form"); + let formContent = ``; + if (csrfParam$1 && csrfToken$1 && !isCrossDomain(href)) { + formContent += ``; + } + formContent += ''; + form.method = "post"; + form.action = href; + form.target = link.target; + form.innerHTML = formContent; + form.style.display = "none"; + document.body.appendChild(form); + form.querySelector('[type="submit"]').click(); + stopEverything(e); + }; + const isRemote = function(element) { + const value = element.getAttribute("data-remote"); + return value != null && value !== "false"; + }; + const handleRemoteWithRails = rails => function(e) { + let data, method, url; + const element = this; + if (!isRemote(element)) { + return true; + } + if (!fire(element, "ajax:before")) { + fire(element, "ajax:stopped"); + return false; + } + if (isContentEditable(element)) { + fire(element, "ajax:stopped"); + return false; + } + const withCredentials = element.getAttribute("data-with-credentials"); + const dataType = element.getAttribute("data-type") || "script"; + if (matches(element, formSubmitSelector)) { + const button = getData(element, "ujs:submit-button"); + method = getData(element, "ujs:submit-button-formmethod") || element.getAttribute("method") || "get"; + url = getData(element, "ujs:submit-button-formaction") || element.getAttribute("action") || location.href; + if (method.toUpperCase() === "GET") { + url = url.replace(/\?.*$/, ""); + } + if (element.enctype === "multipart/form-data") { + data = new FormData(element); + if (button != null) { + data.append(button.name, button.value); + } + } else { + data = serializeElement(element, button); + } + setData(element, "ujs:submit-button", null); + setData(element, "ujs:submit-button-formmethod", null); + setData(element, "ujs:submit-button-formaction", null); + } else if (matches(element, buttonClickSelector) || matches(element, inputChangeSelector)) { + method = element.getAttribute("data-method"); + url = element.getAttribute("data-url"); + data = serializeElement(element, element.getAttribute("data-params")); + } else { + method = element.getAttribute("data-method"); + url = rails.href(element); + data = element.getAttribute("data-params"); + } + ajax({ + type: method || "GET", + url: url, + data: data, + dataType: dataType, + beforeSend(xhr, options) { + if (fire(element, "ajax:beforeSend", [ xhr, options ])) { + return fire(element, "ajax:send", [ xhr ]); + } else { + fire(element, "ajax:stopped"); + return false; + } + }, + success(...args) { + return fire(element, "ajax:success", args); + }, + error(...args) { + return fire(element, "ajax:error", args); + }, + complete(...args) { + return fire(element, "ajax:complete", args); + }, + crossDomain: isCrossDomain(url), + withCredentials: withCredentials != null && withCredentials !== "false" + }); + stopEverything(e); + }; + const formSubmitButtonClick = function(e) { + const button = this; + const {form: form} = button; + if (!form) { + return; + } + if (button.name) { + setData(form, "ujs:submit-button", { + name: button.name, + value: button.value + }); + } + setData(form, "ujs:formnovalidate-button", button.formNoValidate); + setData(form, "ujs:submit-button-formaction", button.getAttribute("formaction")); + return setData(form, "ujs:submit-button-formmethod", button.getAttribute("formmethod")); + }; + const preventInsignificantClick = function(e) { + const link = this; + const method = (link.getAttribute("data-method") || "GET").toUpperCase(); + const data = link.getAttribute("data-params"); + const metaClick = e.metaKey || e.ctrlKey; + const insignificantMetaClick = metaClick && method === "GET" && !data; + const nonPrimaryMouseClick = e.button != null && e.button !== 0; + if (nonPrimaryMouseClick || insignificantMetaClick) { + e.stopImmediatePropagation(); + } + }; + const Rails = { + $: $, + ajax: ajax, + buttonClickSelector: buttonClickSelector, + buttonDisableSelector: buttonDisableSelector, + confirm: confirm, + cspNonce: cspNonce, + csrfToken: csrfToken, + csrfParam: csrfParam, + CSRFProtection: CSRFProtection, + delegate: delegate, + disableElement: disableElement, + enableElement: enableElement, + fileInputSelector: fileInputSelector, + fire: fire, + formElements: formElements, + formEnableSelector: formEnableSelector, + formDisableSelector: formDisableSelector, + formInputClickSelector: formInputClickSelector, + formSubmitButtonClick: formSubmitButtonClick, + formSubmitSelector: formSubmitSelector, + getData: getData, + handleDisabledElement: handleDisabledElement, + href: href, + inputChangeSelector: inputChangeSelector, + isCrossDomain: isCrossDomain, + linkClickSelector: linkClickSelector, + linkDisableSelector: linkDisableSelector, + loadCSPNonce: loadCSPNonce, + matches: matches, + preventInsignificantClick: preventInsignificantClick, + refreshCSRFTokens: refreshCSRFTokens, + serializeElement: serializeElement, + setData: setData, + stopEverything: stopEverything + }; + const handleConfirm = handleConfirmWithRails(Rails); + Rails.handleConfirm = handleConfirm; + const handleMethod = handleMethodWithRails(Rails); + Rails.handleMethod = handleMethod; + const handleRemote = handleRemoteWithRails(Rails); + Rails.handleRemote = handleRemote; + const start = function() { + if (window._rails_loaded) { + throw new Error("rails-ujs has already been loaded!"); + } + window.addEventListener("pageshow", (function() { + $(formEnableSelector).forEach((function(el) { + if (getData(el, "ujs:disabled")) { + enableElement(el); + } + })); + $(linkDisableSelector).forEach((function(el) { + if (getData(el, "ujs:disabled")) { + enableElement(el); + } + })); + })); + delegate(document, linkDisableSelector, "ajax:complete", enableElement); + delegate(document, linkDisableSelector, "ajax:stopped", enableElement); + delegate(document, buttonDisableSelector, "ajax:complete", enableElement); + delegate(document, buttonDisableSelector, "ajax:stopped", enableElement); + delegate(document, linkClickSelector, "click", preventInsignificantClick); + delegate(document, linkClickSelector, "click", handleDisabledElement); + delegate(document, linkClickSelector, "click", handleConfirm); + delegate(document, linkClickSelector, "click", disableElement); + delegate(document, linkClickSelector, "click", handleRemote); + delegate(document, linkClickSelector, "click", handleMethod); + delegate(document, buttonClickSelector, "click", preventInsignificantClick); + delegate(document, buttonClickSelector, "click", handleDisabledElement); + delegate(document, buttonClickSelector, "click", handleConfirm); + delegate(document, buttonClickSelector, "click", disableElement); + delegate(document, buttonClickSelector, "click", handleRemote); + delegate(document, inputChangeSelector, "change", handleDisabledElement); + delegate(document, inputChangeSelector, "change", handleConfirm); + delegate(document, inputChangeSelector, "change", handleRemote); + delegate(document, formSubmitSelector, "submit", handleDisabledElement); + delegate(document, formSubmitSelector, "submit", handleConfirm); + delegate(document, formSubmitSelector, "submit", handleRemote); + delegate(document, formSubmitSelector, "submit", (e => setTimeout((() => disableElement(e)), 13))); + delegate(document, formSubmitSelector, "ajax:send", disableElement); + delegate(document, formSubmitSelector, "ajax:complete", enableElement); + delegate(document, formInputClickSelector, "click", preventInsignificantClick); + delegate(document, formInputClickSelector, "click", handleDisabledElement); + delegate(document, formInputClickSelector, "click", handleConfirm); + delegate(document, formInputClickSelector, "click", formSubmitButtonClick); + document.addEventListener("DOMContentLoaded", refreshCSRFTokens); + document.addEventListener("DOMContentLoaded", loadCSPNonce); + return window._rails_loaded = true; + }; + Rails.start = start; + if (typeof jQuery !== "undefined" && jQuery && jQuery.ajax) { + if (jQuery.rails) { + throw new Error("If you load both jquery_ujs and rails-ujs, use rails-ujs only."); + } + jQuery.rails = Rails; + jQuery.ajaxPrefilter((function(options, originalOptions, xhr) { + if (!options.crossDomain) { + return CSRFProtection(xhr); + } + })); + } + if (typeof exports !== "object" && typeof module === "undefined") { + window.Rails = Rails; + if (fire(document, "rails:attachBindings")) { + start(); + } + } + return Rails; +})); diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view.rb new file mode 100644 index 00000000..2fe9b3af --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) David Heinemeier Hansson +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ + +require "active_support" +require "active_support/rails" +require "action_view/version" +require "action_view/deprecator" + +# :include: ../README.rdoc +module ActionView + extend ActiveSupport::Autoload + + ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*' + + eager_autoload do + autoload :Base + autoload :Context + autoload :Digestor + autoload :Helpers + autoload :LookupContext + autoload :Layouts + autoload :PathRegistry + autoload :PathSet + autoload :RecordIdentifier + autoload :Rendering + autoload :RoutingUrlFor + autoload :Template + autoload :TemplateDetails + autoload :TemplatePath + autoload :UnboundTemplate + autoload :ViewPaths + + autoload_under "renderer" do + autoload :Renderer + autoload :AbstractRenderer + autoload :PartialRenderer + autoload :CollectionRenderer + autoload :ObjectRenderer + autoload :TemplateRenderer + autoload :StreamingTemplateRenderer + end + + autoload_at "action_view/template/resolver" do + autoload :Resolver + autoload :FileSystemResolver + end + + autoload_at "action_view/buffers" do + autoload :OutputBuffer + autoload :StreamingBuffer + end + + autoload_at "action_view/flows" do + autoload :OutputFlow + autoload :StreamingFlow + end + + autoload_at "action_view/template/error" do + autoload :MissingTemplate + autoload :ActionViewError + autoload :EncodingError + autoload :TemplateError + autoload :SyntaxErrorInTemplate + autoload :WrongEncodingError + end + end + + autoload :CacheExpiry + autoload :TestCase + + def self.eager_load! + super + ActionView::Helpers.eager_load! + ActionView::Template.eager_load! + end +end + +require "active_support/core_ext/string/output_safety" + +ActiveSupport.on_load(:i18n) do + I18n.load_path << File.expand_path("action_view/locale/en.yml", __dir__) +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/base.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/base.rb new file mode 100644 index 00000000..59ae9bd8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/base.rb @@ -0,0 +1,313 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attr_internal" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/ordered_options" +require "action_view/log_subscriber" +require "action_view/helpers" +require "action_view/context" +require "action_view/template" +require "action_view/lookup_context" + +module ActionView # :nodoc: + # = Action View \Base + # + # Action View templates can be written in several ways. + # If the template file has a .erb extension, then it uses the erubi[https://rubygems.org/gems/erubi] + # template system which can embed Ruby into an HTML document. + # If the template file has a .builder extension, then Jim Weirich's Builder::XmlMarkup library is used. + # + # == ERB + # + # You trigger ERB by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the + # following loop for names: + # + # Names of all the people + # <% @people.each do |person| %> + # Name: <%= person.name %>
+ # <% end %> + # + # The loop is set up in regular embedding tags <% %>, and the name is written using the output embedding tag <%= %>. Note that this + # is not just a usage suggestion. Regular output functions like print or puts won't work with ERB templates. So this would be wrong: + # + # <%# WRONG %> + # Hi, Mr. <% puts "Frodo" %> + # + # If you absolutely must write from within a function use +concat+. + # + # When on a line that only contains whitespaces except for the tag, <% %> suppresses leading and trailing whitespace, + # including the trailing newline. <% %> and <%- -%> are the same. + # Note however that <%= %> and <%= -%> are different: only the latter removes trailing whitespaces. + # + # === Using sub templates + # + # Using sub templates allows you to sidestep tedious replication and extract common display structures in shared templates. The + # classic example is the use of a header and footer (even though the Action Pack-way would be to use Layouts): + # + # <%= render "application/header" %> + # Something really specific and terrific + # <%= render "application/footer" %> + # + # As you see, we use the output embeddings for the render methods. The render call itself will just return a string holding the + # result of the rendering. The output embedding writes it to the current template. + # + # But you don't have to restrict yourself to static includes. Templates can share variables amongst themselves by using instance + # variables defined using the regular embedding tags. Like this: + # + # <% @page_title = "A Wonderful Hello" %> + # <%= render "application/header" %> + # + # Now the header can pick up on the @page_title variable and use it for outputting a title tag: + # + # <%= @page_title %> + # + # === Passing local variables to sub templates + # + # You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values: + # + # <%= render "application/header", { headline: "Welcome", person: person } %> + # + # These can now be accessed in application/header with: + # + # Headline: <%= headline %> + # First name: <%= person.first_name %> + # + # The local variables passed to sub templates can be accessed as a hash using the local_assigns hash. This lets you access the + # variables as: + # + # Headline: <%= local_assigns[:headline] %> + # + # This is useful in cases where you aren't sure if the local variable has been assigned. Alternatively, you could also use + # defined? headline to first check if the variable has been assigned before using it. + # + # By default, templates will accept any locals as keyword arguments. To restrict what locals a template accepts, add a locals: magic comment: + # + # <%# locals: (headline:) %> + # + # Headline: <%= headline %> + # + # In cases where the local variables are optional, declare the keyword argument with a default value: + # + # <%# locals: (headline: nil) %> + # + # <% unless headline.nil? %> + # Headline: <%= headline %> + # <% end %> + # + # Read more about strict locals in {Action View Overview}[https://guides.rubyonrails.org/action_view_overview.html#strict-locals] + # in the guides. + # + # === Template caching + # + # By default, \Rails will compile each template to a method in order to render it. When you alter a template, + # \Rails will check the file's modification time and recompile it in development mode. + # + # == Builder + # + # Builder templates are a more programmatic alternative to ERB. They are especially useful for generating XML content. An XmlMarkup object + # named +xml+ is automatically made available to templates with a .builder extension. + # + # Here are some basic examples: + # + # xml.em("emphasized") # => emphasized + # xml.em { xml.b("emph & bold") } # => emph & bold + # xml.a("A Link", "href" => "http://onestepback.org") # => A Link + # xml.target("name" => "compile", "option" => "fast") # => + # # NOTE: order of attributes is not specified. + # + # Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following: + # + # xml.div do + # xml.h1(@person.name) + # xml.p(@person.bio) + # end + # + # would produce something like: + # + #
+ #

David Heinemeier Hansson

+ #

A product of Danish Design during the Winter of '79...

+ #
+ # + # Here is a full-length RSS example actually used on Basecamp: + # + # xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do + # xml.channel do + # xml.title(@feed_title) + # xml.link(@url) + # xml.description "Basecamp: Recent items" + # xml.language "en-us" + # xml.ttl "40" + # + # @recent_items.each do |item| + # xml.item do + # xml.title(item_title(item)) + # xml.description(item_description(item)) if item_description(item) + # xml.pubDate(item_pubDate(item)) + # xml.guid(@person.firm.account.url + @recent_items.url(item)) + # xml.link(@person.firm.account.url + @recent_items.url(item)) + # + # xml.tag!("dc:creator", item.author_name) if item_has_creator?(item) + # end + # end + # end + # end + # + # For more information on Builder please consult the {source code}[https://github.com/rails/builder]. + class Base + include Helpers, ::ERB::Util, Context + + # Specify the proc used to decorate input tags that refer to attributes with errors. + cattr_accessor :field_error_proc, default: Proc.new { |html_tag, instance| content_tag :div, html_tag, class: "field_with_errors" } + + # How to complete the streaming when an exception occurs. + # This is our best guess: first try to close the attribute, then the tag. + cattr_accessor :streaming_completion_on_exception, default: %(">) + + # Specify whether rendering within namespaced controllers should prefix + # the partial paths for ActiveModel objects with the namespace. + # (e.g., an Admin::PostsController would render @post using /admin/posts/_post.erb) + class_attribute :prefix_partial_path_with_controller_namespace, default: true + + # Specify default_formats that can be rendered. + cattr_accessor :default_formats + + # Specify whether submit_tag should automatically disable on click + cattr_accessor :automatically_disable_submit_tag, default: true + + # Annotate rendered view with file names + cattr_accessor :annotate_rendered_view_with_filenames, default: false + + class_attribute :_routes + class_attribute :logger + + class << self + delegate :erb_trim_mode=, to: "ActionView::Template::Handlers::ERB" + + def cache_template_loading + ActionView::Resolver.caching? + end + + def cache_template_loading=(value) + ActionView::Resolver.caching = value + end + + def xss_safe? # :nodoc: + true + end + + def with_empty_template_cache # :nodoc: + subclass = Class.new(self) { + # We can't implement these as self.class because subclasses will + # share the same template cache as superclasses, so "changed?" won't work + # correctly. + define_method(:compiled_method_container) { subclass } + define_singleton_method(:compiled_method_container) { subclass } + + def inspect + "#" + end + } + end + + def changed?(other) # :nodoc: + compiled_method_container != other.compiled_method_container + end + end + + attr_reader :view_renderer, :lookup_context + attr_internal :config, :assigns + + delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, to: :lookup_context + + def assign(new_assigns) # :nodoc: + @_assigns = new_assigns + new_assigns.each { |key, value| instance_variable_set("@#{key}", value) } + end + + # :stopdoc: + + def self.empty + with_view_paths([]) + end + + def self.with_view_paths(view_paths, assigns = {}, controller = nil) + with_context ActionView::LookupContext.new(view_paths), assigns, controller + end + + def self.with_context(context, assigns = {}, controller = nil) + new context, assigns, controller + end + + # :startdoc: + + def initialize(lookup_context, assigns, controller) # :nodoc: + @_config = ActiveSupport::InheritableOptions.new + + @lookup_context = lookup_context + + @view_renderer = ActionView::Renderer.new @lookup_context + @current_template = nil + + assign_controller(controller) + _prepare_context + + super() + + # Assigns must be called last to minimize the number of shapes + assign(assigns) + end + + def _run(method, template, locals, buffer, add_to_stack: true, has_strict_locals: false, &block) + _old_output_buffer, _old_virtual_path, _old_template = @output_buffer, @virtual_path, @current_template + @current_template = template if add_to_stack + @output_buffer = buffer + + if has_strict_locals + begin + public_send(method, locals, buffer, **locals, &block) + rescue ArgumentError => argument_error + public_send_line = __LINE__ - 2 + frame = argument_error.backtrace_locations[1] + if frame.path == __FILE__ && frame.lineno == public_send_line + raise StrictLocalsError.new(argument_error, @current_template) + end + raise + end + else + public_send(method, locals, buffer, &block) + end + ensure + @output_buffer, @virtual_path, @current_template = _old_output_buffer, _old_virtual_path, _old_template + end + + def compiled_method_container + raise NotImplementedError, <<~msg.squish + Subclasses of ActionView::Base must implement `compiled_method_container` + or use the class method `with_empty_template_cache` for constructing + an ActionView::Base subclass that has an empty cache. + msg + end + + def in_rendering_context(options) + old_view_renderer = @view_renderer + old_lookup_context = @lookup_context + + if !lookup_context.html_fallback_for_js && options[:formats] + formats = Array(options[:formats]) + if formats == [:js] + formats << :html + end + @lookup_context = lookup_context.with_prepended_formats(formats) + @view_renderer = ActionView::Renderer.new @lookup_context + end + + yield @view_renderer + ensure + @view_renderer = old_view_renderer + @lookup_context = old_lookup_context + end + + ActiveSupport.run_load_hooks(:action_view, self) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/buffers.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/buffers.rb new file mode 100644 index 00000000..90ddcc81 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/buffers.rb @@ -0,0 +1,165 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/output_safety" + +module ActionView + # Used as a buffer for views + # + # The main difference between this and ActiveSupport::SafeBuffer + # is for the methods `<<` and `safe_expr_append=` the inputs are + # checked for nil before they are assigned and `to_s` is called on + # the input. For example: + # + # obuf = ActionView::OutputBuffer.new "hello" + # obuf << 5 + # puts obuf # => "hello5" + # + # sbuf = ActiveSupport::SafeBuffer.new "hello" + # sbuf << 5 + # puts sbuf # => "hello\u0005" + # + class OutputBuffer # :nodoc: + def initialize(buffer = "") + @raw_buffer = String.new(buffer) + @raw_buffer.encode! + end + + delegate :length, :empty?, :blank?, :encoding, :encode!, :force_encoding, to: :@raw_buffer + + def to_s + @raw_buffer.html_safe + end + alias_method :html_safe, :to_s + + def to_str + @raw_buffer.dup + end + + def html_safe? + true + end + + def <<(value) + unless value.nil? + value = value.to_s + @raw_buffer << if value.html_safe? + value + else + CGI.escapeHTML(value) + end + end + self + end + alias :concat :<< + alias :append= :<< + + def safe_concat(value) + @raw_buffer << value + self + end + alias :safe_append= :safe_concat + + def safe_expr_append=(val) + return self if val.nil? + @raw_buffer << val.to_s + self + end + + def initialize_copy(other) + @raw_buffer = other.to_str + end + + def capture(*args) + new_buffer = +"" + old_buffer, @raw_buffer = @raw_buffer, new_buffer + yield(*args) + new_buffer.html_safe + ensure + @raw_buffer = old_buffer + end + + def ==(other) + other.class == self.class && @raw_buffer == other.to_str + end + + def raw + RawOutputBuffer.new(self) + end + + attr_reader :raw_buffer + end + + class RawOutputBuffer # :nodoc: + def initialize(buffer) + @buffer = buffer + end + + def <<(value) + unless value.nil? + @buffer.raw_buffer << value.to_s + end + end + + def raw + self + end + end + + class StreamingBuffer # :nodoc: + def initialize(block) + @block = block + end + + def <<(value) + value = value.to_s + value = ERB::Util.h(value) unless value.html_safe? + @block.call(value) + end + alias :concat :<< + alias :append= :<< + + def safe_concat(value) + @block.call(value.to_s) + end + alias :safe_append= :safe_concat + + def capture + buffer = +"" + old_block, @block = @block, ->(value) { buffer << value } + yield + buffer.html_safe + ensure + @block = old_block + end + + def html_safe? + true + end + + def html_safe + self + end + + def raw + RawStreamingBuffer.new(self) + end + + attr_reader :block + end + + class RawStreamingBuffer # :nodoc: + def initialize(buffer) + @buffer = buffer + end + + def <<(value) + unless value.nil? + @buffer.block.call(value.to_s) + end + end + + def raw + self + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/cache_expiry.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/cache_expiry.rb new file mode 100644 index 00000000..d166d70f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/cache_expiry.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module ActionView + module CacheExpiry # :nodoc: all + class ViewReloader + def initialize(watcher:, &block) + @mutex = Mutex.new + @watcher_class = watcher + @watched_dirs = nil + @watcher = nil + @previous_change = false + + ActionView::PathRegistry.file_system_resolver_hooks << method(:rebuild_watcher) + end + + def updated? + build_watcher unless @watcher + @previous_change || @watcher.updated? + end + + def execute + return unless @watcher + + watcher = nil + @mutex.synchronize do + @previous_change = false + watcher = @watcher + end + watcher.execute + end + + private + def reload! + ActionView::LookupContext::DetailsKey.clear + end + + def build_watcher + @mutex.synchronize do + old_watcher = @watcher + + if @watched_dirs != dirs_to_watch + @watched_dirs = dirs_to_watch + new_watcher = @watcher_class.new([], @watched_dirs) do + reload! + end + @watcher = new_watcher + + # We must check the old watcher after initializing the new one to + # ensure we don't miss any events + @previous_change ||= old_watcher&.updated? + end + end + end + + def rebuild_watcher + return unless @watcher + build_watcher + end + + def dirs_to_watch + all_view_paths.uniq.sort + end + + def all_view_paths + ActionView::PathRegistry.all_file_system_resolvers.map(&:path) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/context.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/context.rb new file mode 100644 index 00000000..776db583 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/context.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module ActionView + # = Action View Context + # + # Action View contexts are supplied to Action Controller to render a template. + # The default Action View context is ActionView::Base. + # + # In order to work with Action Controller, a Context must just include this + # module. The initialization of the variables used by the context + # (@output_buffer, @view_flow, and @virtual_path) is responsibility of the + # object that includes this module (although you can call _prepare_context + # defined below). + module Context + attr_accessor :output_buffer, :view_flow + + # Prepares the context by setting the appropriate instance variables. + def _prepare_context + @view_flow = OutputFlow.new + @output_buffer = ActionView::OutputBuffer.new + @virtual_path = nil + end + + # Encapsulates the interaction with the view flow so it + # returns the correct buffer on +yield+. This is usually + # overwritten by helpers to add more behavior. + def _layout_for(name = nil) + name ||= :layout + view_flow.get(name).html_safe + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/dependency_tracker.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/dependency_tracker.rb new file mode 100644 index 00000000..dd54c5f6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/dependency_tracker.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require "concurrent/map" +require "action_view/path_set" +require "action_view/render_parser" + +module ActionView + class DependencyTracker # :nodoc: + extend ActiveSupport::Autoload + + autoload :ERBTracker + autoload :RubyTracker + autoload :WildcardResolver + + @trackers = Concurrent::Map.new + + def self.find_dependencies(name, template, view_paths = nil) + tracker = @trackers[template.handler] + return [] unless tracker + + tracker.call(name, template, view_paths) + end + + def self.register_tracker(extension, tracker) + handler = Template.handler_for_extension(extension) + if tracker.respond_to?(:supports_view_paths?) + @trackers[handler] = tracker + else + @trackers[handler] = lambda { |name, template, _| + tracker.call(name, template) + } + end + end + + def self.remove_tracker(handler) + @trackers.delete(handler) + end + + register_tracker :erb, ERBTracker + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/dependency_tracker/erb_tracker.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/dependency_tracker/erb_tracker.rb new file mode 100644 index 00000000..d805fcc5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/dependency_tracker/erb_tracker.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true + +module ActionView + class DependencyTracker # :nodoc: + class ERBTracker # :nodoc: + EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/ + + # A valid ruby identifier - suitable for class, method and specially variable names + IDENTIFIER = / + [[:alpha:]_] # at least one uppercase letter, lowercase letter or underscore + [[:word:]]* # followed by optional letters, numbers or underscores + /x + + # Any kind of variable name. e.g. @instance, @@class, $global or local. + # Possibly following a method call chain + VARIABLE_OR_METHOD_CHAIN = / + (?:\$|@{1,2})? # optional global, instance or class variable indicator + (?:#{IDENTIFIER}\.)* # followed by an optional chain of zero-argument method calls + (?#{IDENTIFIER}) # and a final valid identifier, captured as DYNAMIC + /x + + # A simple string literal. e.g. "School's out!" + STRING = / + (?['"]) # an opening quote + (?.*?) # with anything inside, captured as STATIC + \k # and a matching closing quote + /x + + # Part of any hash containing the :partial key + PARTIAL_HASH_KEY = / + (?:\bpartial:|:partial\s*=>) # partial key in either old or new style hash syntax + \s* # followed by optional spaces + /x + + # Part of any hash containing the :layout key + LAYOUT_HASH_KEY = / + (?:\blayout:|:layout\s*=>) # layout key in either old or new style hash syntax + \s* # followed by optional spaces + /x + + # Matches: + # partial: "comments/comment", collection: @all_comments => "comments/comment" + # (object: @single_comment, partial: "comments/comment") => "comments/comment" + # + # "comments/comments" + # 'comments/comments' + # ('comments/comments') + # + # (@topic) => "topics/topic" + # topics => "topics/topic" + # (message.topics) => "topics/topic" + RENDER_ARGUMENTS = /\A + (?:\s*\(?\s*) # optional opening paren surrounded by spaces + (?:.*?#{PARTIAL_HASH_KEY}|#{LAYOUT_HASH_KEY})? # optional hash, up to the partial or layout key declaration + (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest + /xm + + LAYOUT_DEPENDENCY = /\A + (?:\s*\(?\s*) # optional opening paren surrounded by spaces + (?:.*?#{LAYOUT_HASH_KEY}) # check if the line has layout key declaration + (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest + /xm + + def self.supports_view_paths? # :nodoc: + true + end + + def self.call(name, template, view_paths = nil) + new(name, template, view_paths).dependencies + end + + def initialize(name, template, view_paths = nil) + @name, @template, @view_paths = name, template, view_paths + end + + def dependencies + WildcardResolver.new(@view_paths, render_dependencies + explicit_dependencies).resolve + end + + attr_reader :name, :template + private :name, :template + + private + def source + template.source + end + + def directory + name.split("/")[0..-2].join("/") + end + + def render_dependencies + dependencies = [] + render_calls = source.split(/\brender\b/).drop(1) + + render_calls.each do |arguments| + add_dependencies(dependencies, arguments, LAYOUT_DEPENDENCY) + add_dependencies(dependencies, arguments, RENDER_ARGUMENTS) + end + + dependencies + end + + def add_dependencies(render_dependencies, arguments, pattern) + arguments.scan(pattern) do + match = Regexp.last_match + add_dynamic_dependency(render_dependencies, match[:dynamic]) + add_static_dependency(render_dependencies, match[:static], match[:quote]) + end + end + + def add_dynamic_dependency(dependencies, dependency) + if dependency + dependencies << "#{dependency.pluralize}/#{dependency.singularize}" + end + end + + def add_static_dependency(dependencies, dependency, quote_type) + if quote_type == '"' && dependency.include?('#{') + scanner = StringScanner.new(dependency) + + wildcard_dependency = +"" + + while !scanner.eos? + if scanner.scan_until(/\#{/) + unmatched_brackets = 1 + wildcard_dependency << scanner.pre_match + + while unmatched_brackets > 0 && !scanner.eos? + found = scanner.scan_until(/[{}]/) + return unless found + + case scanner.matched + when "{" + unmatched_brackets += 1 + when "}" + unmatched_brackets -= 1 + end + end + + wildcard_dependency << "*" + else + wildcard_dependency << scanner.rest + scanner.terminate + end + end + + dependencies << wildcard_dependency + elsif dependency + if dependency.include?("/") + dependencies << dependency + else + dependencies << "#{directory}/#{dependency}" + end + end + end + + def explicit_dependencies + source.scan(EXPLICIT_DEPENDENCY).flatten.uniq + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/dependency_tracker/ruby_tracker.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/dependency_tracker/ruby_tracker.rb new file mode 100644 index 00000000..72a547c3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/dependency_tracker/ruby_tracker.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module ActionView + class DependencyTracker # :nodoc: + class RubyTracker # :nodoc: + EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/ + + def self.call(name, template, view_paths = nil) + new(name, template, view_paths).dependencies + end + + def dependencies + WildcardResolver.new(view_paths, render_dependencies + explicit_dependencies).resolve + end + + def self.supports_view_paths? # :nodoc: + true + end + + def initialize(name, template, view_paths = nil, parser_class: RenderParser::Default) + @name, @template, @view_paths = name, template, view_paths + @parser_class = parser_class + end + + private + attr_reader :template, :name, :view_paths + + def render_dependencies + return [] unless template.source.include?("render") + + compiled_source = template.handler.call(template, template.source) + + @parser_class.new(@name, compiled_source).render_calls.filter_map do |render_call| + render_call.gsub(%r|/_|, "/") + end + end + + def explicit_dependencies + template.source.scan(EXPLICIT_DEPENDENCY).flatten.uniq + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/dependency_tracker/wildcard_resolver.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/dependency_tracker/wildcard_resolver.rb new file mode 100644 index 00000000..443f034f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/dependency_tracker/wildcard_resolver.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module ActionView + class DependencyTracker # :nodoc: + class WildcardResolver # :nodoc: + def initialize(view_paths, dependencies) + @view_paths = view_paths + + @wildcard_dependencies, @explicit_dependencies = + dependencies.partition { |dependency| dependency.end_with?("/*") } + end + + def resolve + return explicit_dependencies.uniq if !view_paths || wildcard_dependencies.empty? + + (explicit_dependencies + resolved_wildcard_dependencies).uniq + end + + private + attr_reader :explicit_dependencies, :wildcard_dependencies, :view_paths + + def resolved_wildcard_dependencies + # Remove trailing "/*" + prefixes = wildcard_dependencies.map { |query| query[0..-3] } + + view_paths.flat_map(&:all_template_paths).uniq.filter_map { |path| + path.to_s if prefixes.include?(path.prefix) + }.sort + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/deprecator.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/deprecator.rb new file mode 100644 index 00000000..c75be658 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/deprecator.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module ActionView + def self.deprecator # :nodoc: + @deprecator ||= ActiveSupport::Deprecation.new + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/digestor.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/digestor.rb new file mode 100644 index 00000000..d27453bb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/digestor.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true + +require "action_view/dependency_tracker" + +module ActionView + class Digestor + @@digest_mutex = Mutex.new + + class << self + # Supported options: + # + # * name - Template name + # * format - Template format + # * +finder+ - An instance of ActionView::LookupContext + # * dependencies - An array of dependent views + def digest(name:, format: nil, finder:, dependencies: nil) + if dependencies.nil? || dependencies.empty? + cache_key = "#{name}.#{format}" + else + dependencies_suffix = dependencies.flatten.tap(&:compact!).join(".") + cache_key = "#{name}.#{format}.#{dependencies_suffix}" + end + + # this is a correctly done double-checked locking idiom + # (Concurrent::Map's lookups have volatile semantics) + finder.digest_cache[cache_key] || @@digest_mutex.synchronize do + finder.digest_cache.fetch(cache_key) do # re-check under lock + path = TemplatePath.parse(name) + root = tree(path.to_s, finder, path.partial?) + dependencies.each do |injected_dep| + root.children << Injected.new(injected_dep, nil, nil) + end if dependencies + finder.digest_cache[cache_key] = root.digest(finder) + end + end + end + + def logger + ActionView::Base.logger || NullLogger + end + + # Create a dependency tree for template named +name+. + def tree(name, finder, partial = false, seen = {}) + logical_name = name.gsub(%r|/_|, "/") + interpolated = name.include?("#") + + path = TemplatePath.parse(name) + + if !interpolated && (template = find_template(finder, path.name, [path.prefix], partial, [])) + if node = seen[template.identifier] # handle cycles in the tree + node + else + node = seen[template.identifier] = Node.create(name, logical_name, template, partial) + + deps = DependencyTracker.find_dependencies(name, template, finder.view_paths) + deps.uniq { |n| n.gsub(%r|/_|, "/") }.each do |dep_file| + node.children << tree(dep_file, finder, true, seen) + end + node + end + else + unless interpolated # Dynamic template partial names can never be tracked + logger.error " Couldn't find template for digesting: #{name}" + end + + seen[name] ||= Missing.new(name, logical_name, nil) + end + end + + private + def find_template(finder, name, prefixes, partial, keys) + finder.disable_cache do + finder.find_all(name, prefixes, partial, keys).first + end + end + end + + class Node + attr_reader :name, :logical_name, :template, :children + + def self.create(name, logical_name, template, partial) + klass = partial ? Partial : Node + klass.new(name, logical_name, template, []) + end + + def initialize(name, logical_name, template, children = []) + @name = name + @logical_name = logical_name + @template = template + @children = children + end + + def digest(finder, stack = []) + ActiveSupport::Digest.hexdigest("#{template.source}-#{dependency_digest(finder, stack)}") + end + + def dependency_digest(finder, stack) + children.map do |node| + if stack.include?(node) + false + else + finder.digest_cache[node.name] ||= begin + stack.push node + node.digest(finder, stack).tap { stack.pop } + end + end + end.join("-") + end + + def to_dep_map(seen = Set.new.compare_by_identity) + if seen.add?(self) + children.any? ? { name => children.map { |c| c.to_dep_map(seen) } } : name + else # the tree has a cycle + name + end + end + end + + class Partial < Node; end + + class Missing < Node + def digest(finder, _ = []) "" end + end + + class Injected < Node + def digest(finder, _ = []) name end + end + + class NullLogger + def self.debug(_); end + def self.error(_); end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/flows.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/flows.rb new file mode 100644 index 00000000..1e5fe74a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/flows.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/output_safety" + +module ActionView + class OutputFlow # :nodoc: + attr_reader :content + + def initialize + @content = Hash.new { |h, k| h[k] = ActiveSupport::SafeBuffer.new } + end + + # Called by _layout_for to read stored values. + def get(key) + @content[key] + end + + # Called by each renderer object to set the layout contents. + def set(key, value) + @content[key] = ActiveSupport::SafeBuffer.new(value.to_s) + end + + # Called by content_for + def append(key, value) + @content[key] << value.to_s + end + alias_method :append!, :append + end + + class StreamingFlow < OutputFlow # :nodoc: + def initialize(view, fiber) + @view = view + @parent = nil + @child = view.output_buffer + @content = view.view_flow.content + @fiber = fiber + @root = Fiber.current.object_id + end + + # Try to get stored content. If the content + # is not available and we're inside the layout fiber, + # then it will begin waiting for the given key and yield. + def get(key) + return super if @content.key?(key) + + if inside_fiber? + view = @view + + begin + @waiting_for = key + view.output_buffer, @parent = @child, view.output_buffer + Fiber.yield + ensure + @waiting_for = nil + view.output_buffer, @child = @parent, view.output_buffer + end + end + + super + end + + # Appends the contents for the given key. This is called + # by providing and resuming back to the fiber, + # if that's the key it's waiting for. + def append!(key, value) + super + @fiber.resume if @waiting_for == key + end + + private + def inside_fiber? + Fiber.current.object_id != @root + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/gem_version.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/gem_version.rb new file mode 100644 index 00000000..17c39ef2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/gem_version.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActionView + # Returns the currently loaded version of Action View as a +Gem::Version+. + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 8 + MINOR = 0 + TINY = 2 + PRE = nil + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers.rb new file mode 100644 index 00000000..dc060c78 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require "active_support/benchmarkable" +require "action_view/helpers/capture_helper" +require "action_view/helpers/output_safety_helper" +require "action_view/helpers/tag_helper" +require "action_view/helpers/url_helper" +require "action_view/helpers/sanitize_helper" +require "action_view/helpers/text_helper" +require "action_view/helpers/active_model_helper" +require "action_view/helpers/asset_tag_helper" +require "action_view/helpers/asset_url_helper" +require "action_view/helpers/atom_feed_helper" +require "action_view/helpers/cache_helper" +require "action_view/helpers/content_exfiltration_prevention_helper" +require "action_view/helpers/controller_helper" +require "action_view/helpers/csp_helper" +require "action_view/helpers/csrf_helper" +require "action_view/helpers/date_helper" +require "action_view/helpers/debug_helper" +require "action_view/helpers/form_tag_helper" +require "action_view/helpers/form_helper" +require "action_view/helpers/form_options_helper" +require "action_view/helpers/javascript_helper" +require "action_view/helpers/number_helper" +require "action_view/helpers/rendering_helper" +require "action_view/helpers/translation_helper" + +module ActionView # :nodoc: + module Helpers # :nodoc: + extend ActiveSupport::Autoload + + autoload :Tags + + def self.eager_load! + super + Tags.eager_load! + end + + extend ActiveSupport::Concern + + include ActiveSupport::Benchmarkable + include ActiveModelHelper + include AssetTagHelper + include AssetUrlHelper + include AtomFeedHelper + include CacheHelper + include CaptureHelper + include ContentExfiltrationPreventionHelper + include ControllerHelper + include CspHelper + include CsrfHelper + include DateHelper + include DebugHelper + include FormHelper + include FormOptionsHelper + include FormTagHelper + include JavaScriptHelper + include NumberHelper + include OutputSafetyHelper + include RenderingHelper + include SanitizeHelper + include TagHelper + include TextHelper + include TranslationHelper + include UrlHelper + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/active_model_helper.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/active_model_helper.rb new file mode 100644 index 00000000..bf3079dc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/active_model_helper.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/enumerable" + +module ActionView + module Helpers # :nodoc: + module ActiveModelHelper + end + + # = Active \Model Instance Tag \Helpers + module ActiveModelInstanceTag + def object + @active_model_object ||= begin + object = super + object.respond_to?(:to_model) ? object.to_model : object + end + end + + def content_tag(type, options, *) + select_markup_helper?(type) ? super : error_wrapping(super) + end + + def tag(type, options, *) + tag_generate_errors?(options) ? error_wrapping(super) : super + end + + def error_wrapping(html_tag) + if object_has_errors? + @template_object.instance_exec(html_tag, self, &Base.field_error_proc) + else + html_tag + end + end + + def error_message + object.errors[@method_name] + end + + private + def object_has_errors? + object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present? + end + + def select_markup_helper?(type) + ["optgroup", "option"].include?(type) + end + + def tag_generate_errors?(options) + options["type"] != "hidden" + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/asset_tag_helper.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/asset_tag_helper.rb new file mode 100644 index 00000000..f555676f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/asset_tag_helper.rb @@ -0,0 +1,680 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/object/inclusion" +require "action_view/helpers/asset_url_helper" +require "action_view/helpers/tag_helper" + +module ActionView + module Helpers # :nodoc: + # = Action View Asset Tag \Helpers + # + # This module provides methods for generating HTML that links views to assets such + # as images, JavaScripts, stylesheets, and feeds. These methods do not verify + # the assets exist before linking to them: + # + # image_tag("rails.png") + # # => + # stylesheet_link_tag("application") + # # => + module AssetTagHelper + include AssetUrlHelper + include TagHelper + + mattr_accessor :image_loading + mattr_accessor :image_decoding + mattr_accessor :preload_links_header + mattr_accessor :apply_stylesheet_media_default + + # Returns an HTML script tag for each of the +sources+ provided. + # + # Sources may be paths to JavaScript files. Relative paths are assumed to be relative + # to assets/javascripts, full paths are assumed to be relative to the document + # root. Relative paths are idiomatic, use absolute paths only when needed. + # + # When passing paths, the ".js" extension is optional. If you do not want ".js" + # appended to the path extname: false can be set on the options. + # + # You can modify the HTML attributes of the script tag by passing a hash as the + # last argument. + # + # When the Asset Pipeline is enabled, you can pass the name of your manifest as + # source, and include other JavaScript or CoffeeScript files inside the manifest. + # + # If the server supports HTTP Early Hints, and the +defer+ option is not + # enabled, \Rails will push a 103 Early Hints response that links + # to the assets. + # + # ==== Options + # + # When the last parameter is a hash you can add HTML attributes using that + # parameter. This includes but is not limited to the following options: + # + # * :extname - Append an extension to the generated URL unless the extension + # already exists. This only applies for relative URLs. + # * :protocol - Sets the protocol of the generated URL. This option only + # applies when a relative URL and +host+ options are provided. + # * :host - When a relative URL is provided the host is added to the + # that path. + # * :skip_pipeline - This option is used to bypass the asset pipeline + # when it is set to true. + # * :nonce - When set to true, adds an automatic nonce value if + # you have Content Security Policy enabled. + # * :async - When set to +true+, adds the +async+ HTML + # attribute, allowing the script to be fetched in parallel to be parsed + # and evaluated as soon as possible. + # * :defer - When set to +true+, adds the +defer+ HTML + # attribute, which indicates to the browser that the script is meant to + # be executed after the document has been parsed. Additionally, prevents + # sending the Preload Links header. + # * :nopush - Specify if the use of server push is not desired + # for the script. Defaults to +true+. + # + # Any other specified options will be treated as HTML attributes for the + # +script+ tag. + # + # For more information regarding how the :async and :defer + # options affect the + # + # javascript_include_tag "xmlhr", host: "localhost", protocol: "https" + # # => + # + # javascript_include_tag "template.jst", extname: false + # # => + # + # javascript_include_tag "xmlhr.js" + # # => + # + # javascript_include_tag "common.javascript", "/elsewhere/cools" + # # => + # # + # + # javascript_include_tag "http://www.example.com/xmlhr" + # # => + # + # javascript_include_tag "http://www.example.com/xmlhr.js" + # # => + # + # javascript_include_tag "http://www.example.com/xmlhr.js", nonce: true + # # => + # + # javascript_include_tag "http://www.example.com/xmlhr.js", async: true + # # => + # + # javascript_include_tag "http://www.example.com/xmlhr.js", defer: true + # # => + def javascript_include_tag(*sources) + options = sources.extract_options!.stringify_keys + path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys + preload_links = [] + use_preload_links_header = options["preload_links_header"].nil? ? preload_links_header : options.delete("preload_links_header") + nopush = options["nopush"].nil? ? true : options.delete("nopush") + crossorigin = options.delete("crossorigin") + crossorigin = "anonymous" if crossorigin == true + integrity = options["integrity"] + rel = options["type"] == "module" ? "modulepreload" : "preload" + + sources_tags = sources.uniq.map { |source| + href = path_to_javascript(source, path_options) + if use_preload_links_header && !options["defer"] && href.present? && !href.start_with?("data:") + preload_link = "<#{href}>; rel=#{rel}; as=script" + preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil? + preload_link += "; integrity=#{integrity}" unless integrity.nil? + preload_link += "; nopush" if nopush + preload_links << preload_link + end + tag_options = { + "src" => href, + "crossorigin" => crossorigin + }.merge!(options) + if tag_options["nonce"] == true + tag_options["nonce"] = content_security_policy_nonce + end + content_tag("script", "", tag_options) + }.join("\n").html_safe + + if use_preload_links_header + send_preload_links_header(preload_links) + end + + sources_tags + end + + # Returns a stylesheet link tag for the sources specified as arguments. + # + # When passing paths, the .css extension is optional. + # If you don't specify an extension, .css will be appended automatically. + # If you do not want .css appended to the path, + # set extname: false in the options. + # You can modify the link attributes by passing a hash as the last argument. + # + # If the server supports HTTP Early Hints, \Rails will push a 103 Early + # Hints response that links to the assets. + # + # ==== Options + # + # * :extname - Append an extension to the generated URL unless the extension + # already exists. This only applies for relative URLs. + # * :protocol - Sets the protocol of the generated URL. This option only + # applies when a relative URL and +host+ options are provided. + # * :host - When a relative URL is provided the host is added to the + # that path. + # * :skip_pipeline - This option is used to bypass the asset pipeline + # when it is set to true. + # * :nonce - When set to true, adds an automatic nonce value if + # you have Content Security Policy enabled. + # * :nopush - Specify if the use of server push is not desired + # for the stylesheet. Defaults to +true+. + # + # ==== Examples + # + # stylesheet_link_tag "style" + # # => + # + # stylesheet_link_tag "style.css" + # # => + # + # stylesheet_link_tag "http://www.example.com/style.css" + # # => + # + # stylesheet_link_tag "style.less", extname: false, skip_pipeline: true, rel: "stylesheet/less" + # # => + # + # stylesheet_link_tag "style", media: "all" + # # => + # + # stylesheet_link_tag "style", media: "print" + # # => + # + # stylesheet_link_tag "random.styles", "/css/stylish" + # # => + # # + # + # stylesheet_link_tag "style", nonce: true + # # => + def stylesheet_link_tag(*sources) + options = sources.extract_options!.stringify_keys + path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys + use_preload_links_header = options["preload_links_header"].nil? ? preload_links_header : options.delete("preload_links_header") + preload_links = [] + crossorigin = options.delete("crossorigin") + crossorigin = "anonymous" if crossorigin == true + nopush = options["nopush"].nil? ? true : options.delete("nopush") + integrity = options["integrity"] + + sources_tags = sources.uniq.map { |source| + href = path_to_stylesheet(source, path_options) + if use_preload_links_header && href.present? && !href.start_with?("data:") + preload_link = "<#{href}>; rel=preload; as=style" + preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil? + preload_link += "; integrity=#{integrity}" unless integrity.nil? + preload_link += "; nopush" if nopush + preload_links << preload_link + end + tag_options = { + "rel" => "stylesheet", + "crossorigin" => crossorigin, + "href" => href + }.merge!(options) + if tag_options["nonce"] == true + tag_options["nonce"] = content_security_policy_nonce + end + + if apply_stylesheet_media_default && tag_options["media"].blank? + tag_options["media"] = "screen" + end + + tag(:link, tag_options) + }.join("\n").html_safe + + if use_preload_links_header + send_preload_links_header(preload_links) + end + + sources_tags + end + + # Returns a link tag that browsers and feed readers can use to auto-detect + # an RSS, Atom, or JSON feed. The +type+ can be :rss (default), + # :atom, or :json. Control the link options in url_for format + # using the +url_options+. You can modify the LINK tag itself in +tag_options+. + # + # ==== Options + # + # * :rel - Specify the relation of this link, defaults to "alternate" + # * :type - Override the auto-generated mime type + # * :title - Specify the title of the link, defaults to the +type+ + # + # ==== Examples + # + # auto_discovery_link_tag + # # => + # auto_discovery_link_tag(:atom) + # # => + # auto_discovery_link_tag(:json) + # # => + # auto_discovery_link_tag(:rss, {action: "feed"}) + # # => + # auto_discovery_link_tag(:rss, {action: "feed"}, {title: "My RSS"}) + # # => + # auto_discovery_link_tag(:rss, {controller: "news", action: "feed"}) + # # => + # auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "Example RSS"}) + # # => + def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {}) + if !(type == :rss || type == :atom || type == :json) && tag_options[:type].blank? + raise ArgumentError.new("You should pass :type tag_option key explicitly, because you have passed #{type} type other than :rss, :atom, or :json.") + end + + tag( + "link", + "rel" => tag_options[:rel] || "alternate", + "type" => tag_options[:type] || Template::Types[type].to_s, + "title" => tag_options[:title] || type.to_s.upcase, + "href" => url_options.is_a?(Hash) ? url_for(url_options.merge(only_path: false)) : url_options + ) + end + + # Returns a link tag for a favicon managed by the asset pipeline. + # + # If a page has no link like the one generated by this helper, browsers + # ask for /favicon.ico automatically, and cache the file if the + # request succeeds. If the favicon changes it is hard to get it updated. + # + # To have better control applications may let the asset pipeline manage + # their favicon storing the file under app/assets/images, and + # using this helper to generate its corresponding link tag. + # + # The helper gets the name of the favicon file as first argument, which + # defaults to "favicon.ico", and also supports +:rel+ and +:type+ options + # to override their defaults, "icon" and "image/x-icon" + # respectively: + # + # favicon_link_tag + # # => + # + # favicon_link_tag 'myicon.ico' + # # => + # + # Mobile Safari looks for a different link tag, pointing to an image that + # will be used if you add the page to the home screen of an iOS device. + # The following call would generate such a tag: + # + # favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png' + # # => + def favicon_link_tag(source = "favicon.ico", options = {}) + tag("link", { + rel: "icon", + type: "image/x-icon", + href: path_to_image(source, skip_pipeline: options.delete(:skip_pipeline)) + }.merge!(options.symbolize_keys)) + end + + # Returns a link tag that browsers can use to preload the +source+. + # The +source+ can be the path of a resource managed by asset pipeline, + # a full path, or an URI. + # + # ==== Options + # + # * :type - Override the auto-generated mime type, defaults to the mime type for +source+ extension. + # * :as - Override the auto-generated value for as attribute, calculated using +source+ extension and mime type. + # * :crossorigin - Specify the crossorigin attribute, required to load cross-origin resources. + # * :nopush - Specify if the use of server push is not desired for the resource. Defaults to +false+. + # * :integrity - Specify the integrity attribute. + # + # ==== Examples + # + # preload_link_tag("custom_theme.css") + # # => + # + # preload_link_tag("/videos/video.webm") + # # => + # + # preload_link_tag(post_path(format: :json), as: "fetch") + # # => + # + # preload_link_tag("worker.js", as: "worker") + # # => + # + # preload_link_tag("//example.com/font.woff2") + # # => + # + # preload_link_tag("//example.com/font.woff2", crossorigin: "use-credentials") + # # => + # + # preload_link_tag("/media/audio.ogg", nopush: true) + # # => + # + def preload_link_tag(source, options = {}) + href = path_to_asset(source, skip_pipeline: options.delete(:skip_pipeline)) + extname = File.extname(source).downcase.delete(".") + mime_type = options.delete(:type) || Template::Types[extname]&.to_s + as_type = options.delete(:as) || resolve_link_as(extname, mime_type) + crossorigin = options.delete(:crossorigin) + crossorigin = "anonymous" if crossorigin == true || (crossorigin.blank? && as_type == "font") + integrity = options[:integrity] + nopush = options.delete(:nopush) || false + rel = mime_type == "module" ? "modulepreload" : "preload" + + link_tag = tag.link( + rel: rel, + href: href, + as: as_type, + type: mime_type, + crossorigin: crossorigin, + **options.symbolize_keys) + + preload_link = "<#{href}>; rel=#{rel}; as=#{as_type}" + preload_link += "; type=#{mime_type}" if mime_type + preload_link += "; crossorigin=#{crossorigin}" if crossorigin + preload_link += "; integrity=#{integrity}" if integrity + preload_link += "; nopush" if nopush + + send_preload_links_header([preload_link]) + + link_tag + end + + # Returns an HTML image tag for the +source+. The +source+ can be a full + # path, a file, or an Active Storage attachment. + # + # ==== Options + # + # You can add HTML attributes using the +options+. The +options+ supports + # additional keys for convenience and conformance: + # + # * :size - Supplied as "#{width}x#{height}" or "#{number}", so "30x45" becomes + # width="30" height="45", and "50" becomes width="50" height="50". + # :size will be ignored if the value is not in the correct format. + # * :srcset - If supplied as a hash or array of [source, descriptor] + # pairs, each image path will be expanded before the list is formatted as a string. + # + # ==== Examples + # + # Assets (images that are part of your app): + # + # image_tag("icon") + # # => + # image_tag("icon.png") + # # => + # image_tag("icon.png", size: "16x10", alt: "Edit Entry") + # # => Edit Entry + # image_tag("/icons/icon.gif", size: "16") + # # => + # image_tag("/icons/icon.gif", height: '32', width: '32') + # # => + # image_tag("/icons/icon.gif", class: "menu_icon") + # # => + # image_tag("/icons/icon.gif", data: { title: 'Rails Application' }) + # # => + # image_tag("icon.png", srcset: { "icon_2x.png" => "2x", "icon_4x.png" => "4x" }) + # # => + # image_tag("pic.jpg", srcset: [["pic_1024.jpg", "1024w"], ["pic_1980.jpg", "1980w"]], sizes: "100vw") + # # => + # + # Active Storage blobs (images that are uploaded by the users of your app): + # + # image_tag(user.avatar) + # # => + # image_tag(user.avatar.variant(resize_to_limit: [100, 100])) + # # => + # image_tag(user.avatar.variant(resize_to_limit: [100, 100]), size: '100') + # # => + def image_tag(source, options = {}) + options = options.symbolize_keys + check_for_image_tag_errors(options) + skip_pipeline = options.delete(:skip_pipeline) + + options[:src] = resolve_asset_source("image", source, skip_pipeline) + + if options[:srcset] && !options[:srcset].is_a?(String) + options[:srcset] = options[:srcset].map do |src_path, size| + src_path = path_to_image(src_path, skip_pipeline: skip_pipeline) + "#{src_path} #{size}" + end.join(", ") + end + + options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size] + + options[:loading] ||= image_loading if image_loading + options[:decoding] ||= image_decoding if image_decoding + + tag("img", options) + end + + # Returns an HTML picture tag for the +sources+. If +sources+ is a string, + # a single picture tag will be returned. If +sources+ is an array, a picture + # tag with nested source tags for each source will be returned. The + # +sources+ can be full paths, files that exist in your public images + # directory, or Active Storage attachments. Since the picture tag requires + # an img tag, the last element you provide will be used for the img tag. + # For complete control over the picture tag, a block can be passed, which + # will populate the contents of the tag accordingly. + # + # ==== Options + # + # When the last parameter is a hash you can add HTML attributes using that + # parameter. Apart from all the HTML supported options, the following are supported: + # + # * :image - Hash of options that are passed directly to the +image_tag+ helper. + # + # ==== Examples + # + # picture_tag("picture.webp") + # # => + # picture_tag("gold.png", :image => { :size => "20" }) + # # => + # picture_tag("gold.png", :image => { :size => "45x70" }) + # # => + # picture_tag("picture.webp", "picture.png") + # # => + # picture_tag("picture.webp", "picture.png", :image => { alt: "Image" }) + # # => Image + # picture_tag(["picture.webp", "picture.png"], :image => { alt: "Image" }) + # # => Image + # picture_tag(:class => "my-class") { tag(:source, :srcset => image_path("picture.webp")) + image_tag("picture.png", :alt => "Image") } + # # => Image + # picture_tag { tag(:source, :srcset => image_path("picture-small.webp"), :media => "(min-width: 600px)") + tag(:source, :srcset => image_path("picture-big.webp")) + image_tag("picture.png", :alt => "Image") } + # # => Image + # + # Active Storage blobs (images that are uploaded by the users of your app): + # + # picture_tag(user.profile_picture) + # # => + def picture_tag(*sources, &block) + sources.flatten! + options = sources.extract_options!.symbolize_keys + image_options = options.delete(:image) || {} + skip_pipeline = options.delete(:skip_pipeline) + + content_tag("picture", options) do + if block.present? + capture(&block).html_safe + elsif sources.size <= 1 + image_tag(sources.last, image_options) + else + source_tags = sources.map do |source| + tag("source", + srcset: resolve_asset_source("image", source, skip_pipeline), + type: Template::Types[File.extname(source)[1..]]&.to_s) + end + safe_join(source_tags << image_tag(sources.last, image_options)) + end + end + end + + # Returns an HTML video tag for the +sources+. If +sources+ is a string, + # a single video tag will be returned. If +sources+ is an array, a video + # tag with nested source tags for each source will be returned. The + # +sources+ can be full paths, files that exist in your public videos + # directory, or Active Storage attachments. + # + # ==== Options + # + # When the last parameter is a hash you can add HTML attributes using that + # parameter. The following options are supported: + # + # * :poster - Set an image (like a screenshot) to be shown + # before the video loads. The path is calculated like the +src+ of +image_tag+. + # * :size - Supplied as "#{width}x#{height}" or "#{number}", so "30x45" becomes + # width="30" height="45", and "50" becomes width="50" height="50". + # :size will be ignored if the value is not in the correct format. + # * :poster_skip_pipeline will bypass the asset pipeline when using + # the :poster option instead using an asset in the public folder. + # + # ==== Examples + # + # video_tag("trailer") + # # => + # video_tag("trailer.ogg") + # # => + # video_tag("trailer.ogg", controls: true, preload: 'none') + # # => + # video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png") + # # => + # video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png", poster_skip_pipeline: true) + # # => + # video_tag("/trailers/hd.avi", size: "16x16") + # # => + # video_tag("/trailers/hd.avi", size: "16") + # # => + # video_tag("/trailers/hd.avi", height: '32', width: '32') + # # => + # video_tag("trailer.ogg", "trailer.flv") + # # => + # video_tag(["trailer.ogg", "trailer.flv"]) + # # => + # video_tag(["trailer.ogg", "trailer.flv"], size: "160x120") + # # => + # + # Active Storage blobs (videos that are uploaded by the users of your app): + # + # video_tag(user.intro_video) + # # => + def video_tag(*sources) + options = sources.extract_options!.symbolize_keys + public_poster_folder = options.delete(:poster_skip_pipeline) + sources << options + multiple_sources_tag_builder("video", sources) do |tag_options| + tag_options[:poster] = path_to_image(tag_options[:poster], skip_pipeline: public_poster_folder) if tag_options[:poster] + tag_options[:width], tag_options[:height] = extract_dimensions(tag_options.delete(:size)) if tag_options[:size] + end + end + + # Returns an HTML audio tag for the +sources+. If +sources+ is a string, + # a single audio tag will be returned. If +sources+ is an array, an audio + # tag with nested source tags for each source will be returned. The + # +sources+ can be full paths, files that exist in your public audios + # directory, or Active Storage attachments. + # + # When the last parameter is a hash you can add HTML attributes using that + # parameter. + # + # audio_tag("sound") + # # => + # audio_tag("sound.wav") + # # => + # audio_tag("sound.wav", autoplay: true, controls: true) + # # => + # audio_tag("sound.wav", "sound.mid") + # # => + # + # Active Storage blobs (audios that are uploaded by the users of your app): + # + # audio_tag(user.name_pronunciation_audio) + # # => + def audio_tag(*sources) + multiple_sources_tag_builder("audio", sources) + end + + private + def multiple_sources_tag_builder(type, sources) + options = sources.extract_options!.symbolize_keys + skip_pipeline = options.delete(:skip_pipeline) + sources.flatten! + + yield options if block_given? + + if sources.size > 1 + content_tag(type, options) do + safe_join sources.map { |source| tag("source", src: resolve_asset_source(type, source, skip_pipeline)) } + end + else + options[:src] = resolve_asset_source(type, sources.first, skip_pipeline) + content_tag(type, nil, options) + end + end + + def resolve_asset_source(asset_type, source, skip_pipeline) + if source.is_a?(Symbol) || source.is_a?(String) + path_to_asset(source, type: asset_type.to_sym, skip_pipeline: skip_pipeline) + else + polymorphic_url(source) + end + rescue NoMethodError => e + raise ArgumentError, "Can't resolve #{asset_type} into URL: #{e}" + end + + def extract_dimensions(size) + size = size.to_s + if /\A\d+(?:\.\d+)?x\d+(?:\.\d+)?\z/.match?(size) + size.split("x") + elsif /\A\d+(?:\.\d+)?\z/.match?(size) + [size, size] + end + end + + def check_for_image_tag_errors(options) + if options[:size] && (options[:height] || options[:width]) + raise ArgumentError, "Cannot pass a :size option with a :height or :width option" + end + end + + def resolve_link_as(extname, mime_type) + case extname + when "js" then "script" + when "css" then "style" + when "vtt" then "track" + else + mime_type.to_s.split("/").first.presence_in(%w(audio video font image)) + end + end + + # Some HTTP client and proxies have a 4kiB header limit, but more importantly + # including preload links has diminishing returns so it's best to not go overboard + MAX_HEADER_SIZE = 1_000 # :nodoc: + + def send_preload_links_header(preload_links, max_header_size: MAX_HEADER_SIZE) + return if preload_links.empty? + response_present = respond_to?(:response) && response + return if response_present && response.sending? + + if respond_to?(:request) && request + request.send_early_hints("link" => preload_links.join(",")) + end + + if response_present + header = +response.headers["link"].to_s + preload_links.each do |link| + break if header.bytesize + link.bytesize > max_header_size + + if header.empty? + header << link + else + header << "," << link + end + end + + response.headers["link"] = header + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/asset_url_helper.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/asset_url_helper.rb new file mode 100644 index 00000000..d1b4347e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/asset_url_helper.rb @@ -0,0 +1,473 @@ +# frozen_string_literal: true + +require "zlib" + +module ActionView + module Helpers # :nodoc: + # = Action View Asset URL \Helpers + # + # This module provides methods for generating asset paths and + # URLs. + # + # image_path("rails.png") + # # => "/assets/rails.png" + # + # image_url("rails.png") + # # => "http://www.example.com/assets/rails.png" + # + # === Using asset hosts + # + # By default, \Rails links to these assets on the current host in the public + # folder, but you can direct \Rails to link to assets from a dedicated asset + # server by setting ActionController::Base.asset_host in the application + # configuration, typically in config/environments/production.rb. + # For example, you'd define assets.example.com to be your asset + # host this way, inside the configure block of your environment-specific + # configuration files or config/application.rb: + # + # config.action_controller.asset_host = "assets.example.com" + # + # Helpers take that into account: + # + # image_tag("rails.png") + # # => + # stylesheet_link_tag("application") + # # => + # + # Browsers open a limited number of simultaneous connections to a single + # host. The exact number varies by browser and version. This limit may cause + # some asset downloads to wait for previous assets to finish before they can + # begin. You can use the %d wildcard in the +asset_host+ to + # distribute the requests over four hosts. For example, + # assets%d.example.com will spread the asset requests over + # "assets0.example.com", ..., "assets3.example.com". + # + # image_tag("rails.png") + # # => + # stylesheet_link_tag("application") + # # => + # + # This may improve the asset loading performance of your application. + # It is also possible the combination of additional connection overhead + # (DNS, SSL) and the overall browser connection limits may result in this + # solution being slower. You should be sure to measure your actual + # performance across targeted browsers both before and after this change. + # + # To implement the corresponding hosts you can either set up four actual + # hosts or use wildcard DNS to CNAME the wildcard to a single asset host. + # You can read more about setting up your DNS CNAME records from your ISP. + # + # Note: This is purely a browser performance optimization and is not meant + # for server load balancing. See https://www.die.net/musings/page_load_time/ + # for background and https://www.browserscope.org/?category=network for + # connection limit data. + # + # Alternatively, you can exert more control over the asset host by setting + # +asset_host+ to a proc like this: + # + # ActionController::Base.asset_host = Proc.new { |source| + # "http://assets#{OpenSSL::Digest::SHA256.hexdigest(source).to_i(16) % 2 + 1}.example.com" + # } + # image_tag("rails.png") + # # => + # stylesheet_link_tag("application") + # # => + # + # The example above generates "http://assets1.example.com" and + # "http://assets2.example.com". This option is useful for example if + # you need fewer/more than four hosts, custom host names, etc. + # + # As you see the proc takes a +source+ parameter. That's a string with the + # absolute path of the asset, for example "/assets/rails.png". + # + # ActionController::Base.asset_host = Proc.new { |source| + # if source.end_with?('.css') + # "http://stylesheets.example.com" + # else + # "http://assets.example.com" + # end + # } + # image_tag("rails.png") + # # => + # stylesheet_link_tag("application") + # # => + # + # Alternatively you may ask for a second parameter +request+. That one is + # particularly useful for serving assets from an SSL-protected page. The + # example proc below disables asset hosting for HTTPS connections, while + # still sending assets for plain HTTP requests from asset hosts. If you don't + # have SSL certificates for each of the asset hosts this technique allows you + # to avoid warnings in the client about mixed media. + # Note that the +request+ parameter might not be supplied, e.g. when the assets + # are precompiled with the command bin/rails assets:precompile. Make sure to use a + # +Proc+ instead of a lambda, since a +Proc+ allows missing parameters and sets them + # to +nil+. + # + # config.action_controller.asset_host = Proc.new { |source, request| + # if request && request.ssl? + # "#{request.protocol}#{request.host_with_port}" + # else + # "#{request.protocol}assets.example.com" + # end + # } + # + # You can also implement a custom asset host object that responds to +call+ + # and takes either one or two parameters just like the proc. + # + # config.action_controller.asset_host = AssetHostingWithMinimumSsl.new( + # "http://asset%d.example.com", "https://asset1.example.com" + # ) + # + module AssetUrlHelper + URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}i + + # This is the entry point for all assets. + # When using an asset pipeline gem (e.g. propshaft or sprockets-rails), the + # behavior is "enhanced". You can bypass the asset pipeline by passing in + # skip_pipeline: true to the options. + # + # All other asset *_path helpers delegate through this method. + # + # === With the asset pipeline + # + # All options passed to +asset_path+ will be passed to +compute_asset_path+ + # which is implemented by asset pipeline gems. + # + # asset_path("application.js") # => "/assets/application-60aa4fdc5cea14baf5400fba1abf4f2a46a5166bad4772b1effe341570f07de9.js" + # asset_path('application.js', host: 'example.com') # => "//example.com/assets/application.js" + # asset_path("application.js", host: 'example.com', protocol: 'https') # => "https://example.com/assets/application.js" + # + # === Without the asset pipeline (skip_pipeline: true) + # + # Accepts a type option that can specify the asset's extension. No error + # checking is done to verify the source passed into +asset_path+ is valid + # and that the file exists on disk. + # + # asset_path("application.js", skip_pipeline: true) # => "application.js" + # asset_path("filedoesnotexist.png", skip_pipeline: true) # => "filedoesnotexist.png" + # asset_path("application", type: :javascript, skip_pipeline: true) # => "/javascripts/application.js" + # asset_path("application", type: :stylesheet, skip_pipeline: true) # => "/stylesheets/application.css" + # + # === Options applying to all assets + # + # Below lists scenarios that apply to +asset_path+ whether or not you're + # using the asset pipeline. + # + # - All fully qualified URLs are returned immediately. This bypasses the + # asset pipeline and all other behavior described. + # + # asset_path("http://www.example.com/js/xmlhr.js") # => "http://www.example.com/js/xmlhr.js" + # + # - All assets that begin with a forward slash are assumed to be full + # URLs and will not be expanded. This will bypass the asset pipeline. + # + # asset_path("/foo.png") # => "/foo.png" + # + # - All blank strings will be returned immediately. This bypasses the + # asset pipeline and all other behavior described. + # + # asset_path("") # => "" + # + # - If config.relative_url_root is specified, all assets will have that + # root prepended. + # + # Rails.application.config.relative_url_root = "bar" + # asset_path("foo.js", skip_pipeline: true) # => "bar/foo.js" + # + # - A different asset host can be specified via config.action_controller.asset_host + # this is commonly used in conjunction with a CDN. + # + # Rails.application.config.action_controller.asset_host = "assets.example.com" + # asset_path("foo.js", skip_pipeline: true) # => "http://assets.example.com/foo.js" + # + # - An extension name can be specified manually with extname. + # + # asset_path("foo", skip_pipeline: true, extname: ".js") # => "/foo.js" + # asset_path("foo.css", skip_pipeline: true, extname: ".js") # => "/foo.css.js" + def asset_path(source, options = {}) + raise ArgumentError, "nil is not a valid asset source" if source.nil? + + source = source.to_s + return "" if source.blank? + return source if URI_REGEXP.match?(source) + + tail, source = source[/([?#].+)$/], source.sub(/([?#].+)$/, "") + + if extname = compute_asset_extname(source, options) + source = "#{source}#{extname}" + end + + unless source.start_with?(?/) + if options[:skip_pipeline] + source = public_compute_asset_path(source, options) + else + source = compute_asset_path(source, options) + end + end + + relative_url_root = defined?(config.relative_url_root) && config.relative_url_root + if relative_url_root + source = File.join(relative_url_root, source) unless source.start_with?("#{relative_url_root}/") + end + + if host = compute_asset_host(source, options) + source = File.join(host, source) + end + + "#{source}#{tail}" + end + alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with an asset_path named route + + # Computes the full URL to an asset in the public directory. This + # will use +asset_path+ internally, so most of their behaviors + # will be the same. If +:host+ options is set, it overwrites global + # +config.action_controller.asset_host+ setting. + # + # All other options provided are forwarded to +asset_path+ call. + # + # asset_url "application.js" # => http://example.com/assets/application.js + # asset_url "application.js", host: "http://cdn.example.com" # => http://cdn.example.com/assets/application.js + # + def asset_url(source, options = {}) + path_to_asset(source, options.merge(protocol: :request)) + end + alias_method :url_to_asset, :asset_url # aliased to avoid conflicts with an asset_url named route + + ASSET_EXTENSIONS = { + javascript: ".js", + stylesheet: ".css" + } + + # Compute extname to append to asset path. Returns +nil+ if + # nothing should be added. + def compute_asset_extname(source, options = {}) + return if options[:extname] == false + extname = options[:extname] || ASSET_EXTENSIONS[options[:type]] + if extname && File.extname(source) != extname + extname + else + nil + end + end + + # Maps asset types to public directory. + ASSET_PUBLIC_DIRECTORIES = { + audio: "/audios", + font: "/fonts", + image: "/images", + javascript: "/javascripts", + stylesheet: "/stylesheets", + video: "/videos" + } + + # Computes asset path to public directory. Plugins and + # extensions can override this method to point to custom assets + # or generate digested paths or query strings. + def compute_asset_path(source, options = {}) + dir = ASSET_PUBLIC_DIRECTORIES[options[:type]] || "" + File.join(dir, source) + end + alias :public_compute_asset_path :compute_asset_path + + # Pick an asset host for this source. Returns +nil+ if no host is set, + # the host if no wildcard is set, the host interpolated with the + # numbers 0-3 if it contains %d (the number is the source hash mod 4), + # or the value returned from invoking call on an object responding to call + # (proc or otherwise). + def compute_asset_host(source = "", options = {}) + request = self.request if respond_to?(:request) + host = options[:host] + host ||= config.asset_host if defined? config.asset_host + + if host + if host.respond_to?(:call) + arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity + args = [source] + args << request if request && (arity > 1 || arity < 0) + host = host.call(*args) + elsif host.include?("%d") + host = host % (Zlib.crc32(source) % 4) + end + end + + host ||= request.base_url if request && options[:protocol] == :request + return unless host + + if URI_REGEXP.match?(host) + host + else + protocol = options[:protocol] || config.default_asset_host_protocol || (request ? :request : :relative) + case protocol + when :relative + "//#{host}" + when :request + "#{request.protocol}#{host}" + else + "#{protocol}://#{host}" + end + end + end + + # Computes the path to a JavaScript asset in the public javascripts directory. + # If the +source+ filename has no extension, .js will be appended (except for explicit URIs) + # Full paths from the document root will be passed through. + # Used internally by +javascript_include_tag+ to build the script path. + # + # javascript_path "xmlhr" # => /assets/xmlhr.js + # javascript_path "dir/xmlhr.js" # => /assets/dir/xmlhr.js + # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js + # javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr + # javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js + def javascript_path(source, options = {}) + path_to_asset(source, { type: :javascript }.merge!(options)) + end + alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route + + # Computes the full URL to a JavaScript asset in the public javascripts directory. + # This will use +javascript_path+ internally, so most of their behaviors will be the same. + # Since +javascript_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+ + # options is set, it overwrites global +config.action_controller.asset_host+ setting. + # + # javascript_url "js/xmlhr.js", host: "http://stage.example.com" # => http://stage.example.com/assets/js/xmlhr.js + # + def javascript_url(source, options = {}) + url_to_asset(source, { type: :javascript }.merge!(options)) + end + alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route + + # Computes the path to a stylesheet asset in the public stylesheets directory. + # If the +source+ filename has no extension, .css will be appended (except for explicit URIs). + # Full paths from the document root will be passed through. + # Used internally by +stylesheet_link_tag+ to build the stylesheet path. + # + # stylesheet_path "style" # => /assets/style.css + # stylesheet_path "dir/style.css" # => /assets/dir/style.css + # stylesheet_path "/dir/style.css" # => /dir/style.css + # stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style + # stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css + def stylesheet_path(source, options = {}) + path_to_asset(source, { type: :stylesheet }.merge!(options)) + end + alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route + + # Computes the full URL to a stylesheet asset in the public stylesheets directory. + # This will use +stylesheet_path+ internally, so most of their behaviors will be the same. + # Since +stylesheet_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+ + # options is set, it overwrites global +config.action_controller.asset_host+ setting. + # + # stylesheet_url "css/style.css", host: "http://stage.example.com" # => http://stage.example.com/assets/css/style.css + # + def stylesheet_url(source, options = {}) + url_to_asset(source, { type: :stylesheet }.merge!(options)) + end + alias_method :url_to_stylesheet, :stylesheet_url # aliased to avoid conflicts with a stylesheet_url named route + + # Computes the path to an image asset. + # Full paths from the document root will be passed through. + # Used internally by +image_tag+ to build the image path: + # + # image_path("edit") # => "/assets/edit" + # image_path("edit.png") # => "/assets/edit.png" + # image_path("icons/edit.png") # => "/assets/icons/edit.png" + # image_path("/icons/edit.png") # => "/icons/edit.png" + # image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png" + # + # If you have images as application resources this method may conflict with their named routes. + # The alias +path_to_image+ is provided to avoid that. \Rails uses the alias internally, and + # plugin authors are encouraged to do so. + def image_path(source, options = {}) + path_to_asset(source, { type: :image }.merge!(options)) + end + alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route + + # Computes the full URL to an image asset. + # This will use +image_path+ internally, so most of their behaviors will be the same. + # Since +image_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+ + # options is set, it overwrites global +config.action_controller.asset_host+ setting. + # + # image_url "edit.png", host: "http://stage.example.com" # => http://stage.example.com/assets/edit.png + # + def image_url(source, options = {}) + url_to_asset(source, { type: :image }.merge!(options)) + end + alias_method :url_to_image, :image_url # aliased to avoid conflicts with an image_url named route + + # Computes the path to a video asset in the public videos directory. + # Full paths from the document root will be passed through. + # Used internally by +video_tag+ to build the video path. + # + # video_path("hd") # => /videos/hd + # video_path("hd.avi") # => /videos/hd.avi + # video_path("trailers/hd.avi") # => /videos/trailers/hd.avi + # video_path("/trailers/hd.avi") # => /trailers/hd.avi + # video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi + def video_path(source, options = {}) + path_to_asset(source, { type: :video }.merge!(options)) + end + alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route + + # Computes the full URL to a video asset in the public videos directory. + # This will use +video_path+ internally, so most of their behaviors will be the same. + # Since +video_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+ + # options is set, it overwrites global +config.action_controller.asset_host+ setting. + # + # video_url "hd.avi", host: "http://stage.example.com" # => http://stage.example.com/videos/hd.avi + # + def video_url(source, options = {}) + url_to_asset(source, { type: :video }.merge!(options)) + end + alias_method :url_to_video, :video_url # aliased to avoid conflicts with a video_url named route + + # Computes the path to an audio asset in the public audios directory. + # Full paths from the document root will be passed through. + # Used internally by +audio_tag+ to build the audio path. + # + # audio_path("horse") # => /audios/horse + # audio_path("horse.wav") # => /audios/horse.wav + # audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav + # audio_path("/sounds/horse.wav") # => /sounds/horse.wav + # audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav + def audio_path(source, options = {}) + path_to_asset(source, { type: :audio }.merge!(options)) + end + alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route + + # Computes the full URL to an audio asset in the public audios directory. + # This will use +audio_path+ internally, so most of their behaviors will be the same. + # Since +audio_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+ + # options is set, it overwrites global +config.action_controller.asset_host+ setting. + # + # audio_url "horse.wav", host: "http://stage.example.com" # => http://stage.example.com/audios/horse.wav + # + def audio_url(source, options = {}) + url_to_asset(source, { type: :audio }.merge!(options)) + end + alias_method :url_to_audio, :audio_url # aliased to avoid conflicts with an audio_url named route + + # Computes the path to a font asset. + # Full paths from the document root will be passed through. + # + # font_path("font") # => /fonts/font + # font_path("font.ttf") # => /fonts/font.ttf + # font_path("dir/font.ttf") # => /fonts/dir/font.ttf + # font_path("/dir/font.ttf") # => /dir/font.ttf + # font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf + def font_path(source, options = {}) + path_to_asset(source, { type: :font }.merge!(options)) + end + alias_method :path_to_font, :font_path # aliased to avoid conflicts with a font_path named route + + # Computes the full URL to a font asset. + # This will use +font_path+ internally, so most of their behaviors will be the same. + # Since +font_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+ + # options is set, it overwrites global +config.action_controller.asset_host+ setting. + # + # font_url "font.ttf", host: "http://stage.example.com" # => http://stage.example.com/fonts/font.ttf + # + def font_url(source, options = {}) + url_to_asset(source, { type: :font }.merge!(options)) + end + alias_method :url_to_font, :font_url # aliased to avoid conflicts with a font_url named route + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/atom_feed_helper.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/atom_feed_helper.rb new file mode 100644 index 00000000..66e3216d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/atom_feed_helper.rb @@ -0,0 +1,203 @@ +# frozen_string_literal: true + +module ActionView + module Helpers # :nodoc: + # = Action View Atom Feed \Helpers + module AtomFeedHelper + # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other + # template languages). + # + # Full usage example: + # + # config/routes.rb: + # Rails.application.routes.draw do + # resources :posts + # root to: "posts#index" + # end + # + # app/controllers/posts_controller.rb: + # class PostsController < ApplicationController + # # GET /posts.html + # # GET /posts.atom + # def index + # @posts = Post.all + # + # respond_to do |format| + # format.html + # format.atom + # end + # end + # end + # + # app/views/posts/index.atom.builder: + # atom_feed do |feed| + # feed.title("My great blog!") + # feed.updated(@posts[0].created_at) if @posts.length > 0 + # + # @posts.each do |post| + # feed.entry(post) do |entry| + # entry.title(post.title) + # entry.content(post.body, type: 'html') + # + # entry.author do |author| + # author.name("DHH") + # end + # end + # end + # end + # + # The options for atom_feed are: + # + # * :language: Defaults to "en-US". + # * :root_url: The HTML alternative that this feed is doubling for. Defaults to / on the current host. + # * :url: The URL for this feed. Defaults to the current URL. + # * :id: The id for this feed. Defaults to "tag:localhost,2005:/posts", in this case. + # * :schema_date: The date at which the tag scheme for the feed was first used. A good default is the year you + # created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified, + # 2005 is used (as an "I don't care" value). + # * :instruct: Hash of XML processing instructions in the form {target => {attribute => value, }} or {target => [{attribute => value, }, ]} + # + # Other namespaces can be added to the root element: + # + # app/views/posts/index.atom.builder: + # atom_feed({'xmlns:app' => 'http://www.w3.org/2007/app', + # 'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed| + # feed.title("My great blog!") + # feed.updated((@posts.first.created_at)) + # feed.tag!('openSearch:totalResults', 10) + # + # @posts.each do |post| + # feed.entry(post) do |entry| + # entry.title(post.title) + # entry.content(post.body, type: 'html') + # entry.tag!('app:edited', Time.now) + # + # entry.author do |author| + # author.name("DHH") + # end + # end + # end + # end + # + # The Atom spec defines five elements (content rights title subtitle + # summary) which may directly contain XHTML content if type: 'xhtml' + # is specified as an attribute. If so, this helper will take care of + # the enclosing div and XHTML namespace declaration. Example usage: + # + # entry.summary type: 'xhtml' do |xhtml| + # xhtml.p pluralize(order.line_items.count, "line item") + # xhtml.p "Shipped to #{order.address}" + # xhtml.p "Paid by #{order.pay_type}" + # end + # + # + # atom_feed yields an +AtomFeedBuilder+ instance. Nested elements yield + # an +AtomBuilder+ instance. + def atom_feed(options = {}, &block) + if options[:schema_date] + options[:schema_date] = options[:schema_date].strftime("%Y-%m-%d") if options[:schema_date].respond_to?(:strftime) + else + options[:schema_date] = "2005" # The Atom spec copyright date + end + + xml = options.delete(:xml) || block.binding.local_variable_get(:xml) + xml.instruct! + if options[:instruct] + options[:instruct].each do |target, attrs| + if attrs.respond_to?(:keys) + xml.instruct!(target, attrs) + elsif attrs.respond_to?(:each) + attrs.each { |attr_group| xml.instruct!(target, attr_group) } + end + end + end + + feed_opts = { "xml:lang" => options[:language] || "en-US", "xmlns" => "http://www.w3.org/2005/Atom" } + feed_opts.merge!(options).select! { |k, _| k.start_with?("xml") } + + xml.feed(feed_opts) do + xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}") + xml.link(rel: "alternate", type: "text/html", href: options[:root_url] || (request.protocol + request.host_with_port)) + xml.link(rel: "self", type: "application/atom+xml", href: options[:url] || request.url) + + yield AtomFeedBuilder.new(xml, self, options) + end + end + + class AtomBuilder # :nodoc: + XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set + + def initialize(xml) + @xml = xml + end + + private + # Delegate to XML Builder, first wrapping the element in an XHTML + # namespaced div element if the method and arguments indicate + # that an xhtml_block? is desired. + def method_missing(method, *arguments, &block) + if xhtml_block?(method, arguments) + @xml.__send__(method, *arguments) do + @xml.div(xmlns: "http://www.w3.org/1999/xhtml") do |xhtml| + block.call(xhtml) + end + end + else + @xml.__send__(method, *arguments, &block) + end + end + + # True if the method name matches one of the five elements defined + # in the Atom spec as potentially containing XHTML content and + # if type: 'xhtml' is, in fact, specified. + def xhtml_block?(method, arguments) + if XHTML_TAG_NAMES.include?(method.to_s) + last = arguments.last + last.is_a?(Hash) && last[:type].to_s == "xhtml" + end + end + end + + class AtomFeedBuilder < AtomBuilder # :nodoc: + def initialize(xml, view, feed_options = {}) + @xml, @view, @feed_options = xml, view, feed_options + end + + # Accepts a Date or Time object and inserts it in the proper format. If +nil+ is passed, current time in UTC is used. + def updated(date_or_time = nil) + @xml.updated((date_or_time || Time.now.utc).xmlschema) + end + + # Creates an entry tag for a specific record and prefills the id using class and id. + # + # Options: + # + # * :published: Time first published. Defaults to the created_at attribute on the record if one such exists. + # * :updated: Time of update. Defaults to the updated_at attribute on the record if one such exists. + # * :url: The URL for this entry or +false+ or +nil+ for not having a link tag. Defaults to the +polymorphic_url+ for the record. + # * :id: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}" + # * :type: The TYPE for this entry. Defaults to "text/html". + def entry(record, options = {}) + @xml.entry do + @xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}") + + if options[:published] || (record.respond_to?(:created_at) && record.created_at) + @xml.published((options[:published] || record.created_at).xmlschema) + end + + if options[:updated] || (record.respond_to?(:updated_at) && record.updated_at) + @xml.updated((options[:updated] || record.updated_at).xmlschema) + end + + type = options.fetch(:type, "text/html") + + url = options.fetch(:url) { @view.polymorphic_url(record) } + @xml.link(rel: "alternate", type: type, href: url) if url + + yield AtomBuilder.new(@xml) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/cache_helper.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/cache_helper.rb new file mode 100644 index 00000000..15a31e92 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/cache_helper.rb @@ -0,0 +1,315 @@ +# frozen_string_literal: true + +module ActionView + module Helpers # :nodoc: + # = Action View Cache \Helpers + module CacheHelper + class UncacheableFragmentError < StandardError; end + + # This helper exposes a method for caching fragments of a view + # rather than an entire action or page. This technique is useful + # caching pieces like menus, lists of new topics, static HTML + # fragments, and so on. This method takes a block that contains + # the content you wish to cache. + # + # The best way to use this is by doing recyclable key-based cache expiration + # on top of a cache store like Memcached or Redis that'll automatically + # kick out old entries. + # + # When using this method, you list the cache dependency as the name of the cache, like so: + # + # <% cache project do %> + # All the topics on this project + # <%= render project.topics %> + # <% end %> + # + # This approach will assume that when a new topic is added, you'll touch + # the project. The cache key generated from this call will be something like: + # + # views/template/action:7a1156131a6928cb0026877f8b749ac9/projects/123 + # ^template path ^template tree digest ^class ^id + # + # This cache key is stable, but it's combined with a cache version derived from the project + # record. When the project updated_at is touched, the #cache_version changes, even + # if the key stays stable. This means that unlike a traditional key-based cache expiration + # approach, you won't be generating cache trash, unused keys, simply because the dependent + # record is updated. + # + # If your template cache depends on multiple sources (try to avoid this to keep things simple), + # you can name all these dependencies as part of an array: + # + # <% cache [ project, current_user ] do %> + # All the topics on this project + # <%= render project.topics %> + # <% end %> + # + # This will include both records as part of the cache key and updating either of them will + # expire the cache. + # + # ==== \Template digest + # + # The template digest that's added to the cache key is computed by taking an MD5 of the + # contents of the entire template file. This ensures that your caches will automatically + # expire when you change the template file. + # + # Note that the MD5 is taken of the entire template file, not just what's within the + # cache do/end call. So it's possible that changing something outside of that call will + # still expire the cache. + # + # Additionally, the digestor will automatically look through your template file for + # explicit and implicit dependencies, and include those as part of the digest. + # + # The digestor can be bypassed by passing skip_digest: true as an option to the cache call: + # + # <% cache project, skip_digest: true do %> + # All the topics on this project + # <%= render project.topics %> + # <% end %> + # + # ==== Implicit dependencies + # + # Most template dependencies can be derived from calls to render in the template itself. + # Here are some examples of render calls that Cache Digests knows how to decode: + # + # render partial: "comments/comment", collection: commentable.comments + # render "comments/comments" + # render 'comments/comments' + # render('comments/comments') + # + # render "header" # translates to render("comments/header") + # + # render(@topic) # translates to render("topics/topic") + # render(topics) # translates to render("topics/topic") + # render(message.topics) # translates to render("topics/topic") + # + # It's not possible to derive all render calls like that, though. + # Here are a few examples of things that can't be derived: + # + # render group_of_attachments + # render @project.documents.where(published: true).order('created_at') + # + # You will have to rewrite those to the explicit form: + # + # render partial: 'attachments/attachment', collection: group_of_attachments + # render partial: 'documents/document', collection: @project.documents.where(published: true).order('created_at') + # + # One last type of dependency can be determined implicitly: + # + # render "maintenance_tasks/runs/info/#{run.status}" + # + # Because the value passed to render ends in interpolation, Action View + # will mark all partials within the "maintenance_tasks/runs/info" folder as + # dependencies. + # + # === Explicit dependencies + # + # Sometimes you'll have template dependencies that can't be derived at all. This is typically + # the case when you have template rendering that happens in helpers. Here's an example: + # + # <%= render_sortable_todolists @project.todolists %> + # + # You'll need to use a special comment format to call those out: + # + # <%# Template Dependency: todolists/todolist %> + # <%= render_sortable_todolists @project.todolists %> + # + # In some cases, like a single table inheritance setup, you might have + # a bunch of explicit dependencies. Instead of writing every template out, + # you can use a wildcard to match any template in a directory: + # + # <%# Template Dependency: events/* %> + # <%= render_categorizable_events @person.events %> + # + # This marks every template in the directory as a dependency. To find those + # templates, the wildcard path must be absolutely defined from app/views or paths + # otherwise added with +prepend_view_path+ or +append_view_path+. + # This way the wildcard for app/views/recordings/events would be recordings/events/* etc. + # + # The pattern used to match explicit dependencies is /# Template Dependency: (\S+)/, + # so it's important that you type it out just so. + # You can only declare one template dependency per line. + # + # === External dependencies + # + # If you use a helper method, for example, inside a cached block and + # you then update that helper, you'll have to bump the cache as well. + # It doesn't really matter how you do it, but the MD5 of the template file + # must change. One recommendation is to simply be explicit in a comment, like: + # + # <%# Helper Dependency Updated: May 6, 2012 at 6pm %> + # <%= some_helper_method(person) %> + # + # Now all you have to do is change that timestamp when the helper method changes. + # + # === Collection Caching + # + # When rendering a collection of objects that each use the same partial, a :cached + # option can be passed. + # + # For collections rendered such: + # + # <%= render partial: 'projects/project', collection: @projects, cached: true %> + # + # The cached: true will make Action View's rendering read several templates + # from cache at once instead of one call per template. + # + # Templates in the collection not already cached are written to cache. + # + # Works great alongside individual template fragment caching. + # For instance if the template the collection renders is cached like: + # + # # projects/_project.html.erb + # <% cache project do %> + # <%# ... %> + # <% end %> + # + # Any collection renders will find those cached templates when attempting + # to read multiple templates at once. + # + # If your collection cache depends on multiple sources (try to avoid this to keep things simple), + # you can name all these dependencies as part of a block that returns an array: + # + # <%= render partial: 'projects/project', collection: @projects, cached: -> project { [ project, current_user ] } %> + # + # This will include both records as part of the cache key and updating either of them will + # expire the cache. + def cache(name = {}, options = {}, &block) + if controller.respond_to?(:perform_caching) && controller.perform_caching + CachingRegistry.track_caching do + name_options = options.slice(:skip_digest) + safe_concat(fragment_for(cache_fragment_name(name, **name_options), options, &block)) + end + else + yield + end + + nil + end + + # Returns whether the current view fragment is within a +cache+ block. + # + # Useful when certain fragments aren't cacheable: + # + # <% cache project do %> + # <% raise StandardError, "Caching private data!" if caching? %> + # <% end %> + def caching? + CachingRegistry.caching? + end + + # Raises UncacheableFragmentError when called from within a +cache+ block. + # + # Useful to denote helper methods that can't participate in fragment caching: + # + # def project_name_with_time(project) + # uncacheable! + # "#{project.name} - #{Time.now}" + # end + # + # # Which will then raise if used within a `cache` block: + # <% cache project do %> + # <%= project_name_with_time(project) %> + # <% end %> + def uncacheable! + raise UncacheableFragmentError, "can't be fragment cached" if caching? + end + + # Cache fragments of a view if +condition+ is true + # + # <% cache_if admin?, project do %> + # All the topics on this project + # <%= render project.topics %> + # <% end %> + def cache_if(condition, name = {}, options = {}, &block) + if condition + cache(name, options, &block) + else + yield + end + + nil + end + + # Cache fragments of a view unless +condition+ is true + # + # <% cache_unless admin?, project do %> + # All the topics on this project + # <%= render project.topics %> + # <% end %> + def cache_unless(condition, name = {}, options = {}, &block) + cache_if !condition, name, options, &block + end + + # This helper returns the name of a cache key for a given fragment cache + # call. By supplying skip_digest: true to cache, the digestion of cache + # fragments can be manually bypassed. This is useful when cache fragments + # cannot be manually expired unless you know the exact key which is the + # case when using memcached. + def cache_fragment_name(name = {}, skip_digest: nil, digest_path: nil) + if skip_digest + name + else + fragment_name_with_digest(name, digest_path) + end + end + + def digest_path_from_template(template) # :nodoc: + digest = Digestor.digest(name: template.virtual_path, format: template.format, finder: lookup_context, dependencies: view_cache_dependencies) + + if digest.present? + "#{template.virtual_path}:#{digest}" + else + template.virtual_path + end + end + + private + def fragment_name_with_digest(name, digest_path) + name = controller.url_for(name).split("://").last if name.is_a?(Hash) + + if @current_template&.virtual_path || digest_path + digest_path ||= digest_path_from_template(@current_template) + [ digest_path, name ] + else + name + end + end + + def fragment_for(name = {}, options = nil, &block) + if content = read_fragment_for(name, options) + @view_renderer.cache_hits[@current_template&.virtual_path] = :hit if defined?(@view_renderer) + content + else + @view_renderer.cache_hits[@current_template&.virtual_path] = :miss if defined?(@view_renderer) + write_fragment_for(name, options, &block) + end + end + + def read_fragment_for(name, options) + controller.read_fragment(name, options) + end + + def write_fragment_for(name, options, &block) + fragment = output_buffer.capture(&block) + controller.write_fragment(name, fragment, options) + end + + module CachingRegistry # :nodoc: + extend self + + def caching? + ActiveSupport::IsolatedExecutionState[:action_view_caching] ||= false + end + + def track_caching + caching_was = ActiveSupport::IsolatedExecutionState[:action_view_caching] + ActiveSupport::IsolatedExecutionState[:action_view_caching] = true + + yield + ensure + ActiveSupport::IsolatedExecutionState[:action_view_caching] = caching_was + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/capture_helper.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/capture_helper.rb new file mode 100644 index 00000000..0b0c8055 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/capture_helper.rb @@ -0,0 +1,236 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/output_safety" + +module ActionView + module Helpers # :nodoc: + # = Action View Capture \Helpers + # + # \CaptureHelper exposes methods to let you extract generated markup which + # can be used in other parts of a template or layout file. + # + # It provides a method to capture blocks into variables through #capture and + # a way to capture a block of markup for use in a layout through #content_for. + # + # As well as provides a method when using streaming responses through #provide. + # See ActionController::Streaming for more information. + module CaptureHelper + # The capture method extracts part of a template as a string object. + # You can then use this object anywhere in your templates, layout, or helpers. + # + # The capture method can be used in \ERB templates... + # + # <% @greeting = capture do %> + # Welcome to my shiny new web page! The date and time is + # <%= Time.now %> + # <% end %> + # + # ...and Builder (RXML) templates. + # + # @timestamp = capture do + # "The current timestamp is #{Time.now}." + # end + # + # You can then use that variable anywhere else. For example: + # + # + # <%= @greeting %> + # + # <%= @greeting %> + # + # + # + # The return of capture is the string generated by the block. For Example: + # + # @greeting # => "Welcome to my shiny new web page! The date and time is 2018-09-06 11:09:16 -0500" + # + def capture(*args, &block) + value = nil + @output_buffer ||= ActionView::OutputBuffer.new + buffer = @output_buffer.capture { value = yield(*args) } + + string = if @output_buffer.equal?(value) + buffer + else + buffer.presence || value + end + + case string + when OutputBuffer + string.to_s + when ActiveSupport::SafeBuffer + string + when String + ERB::Util.html_escape(string) + end + end + + # Calling content_for stores a block of markup in an identifier for later use. + # In order to access this stored content in other templates, helper modules + # or the layout, you would pass the identifier as an argument to content_for. + # + # Note: yield can still be used to retrieve the stored content, but calling + # yield doesn't work in helper modules, while content_for does. + # + # <% content_for :not_authorized do %> + # alert('You are not authorized to do that!') + # <% end %> + # + # You can then use content_for :not_authorized anywhere in your templates. + # + # <%= content_for :not_authorized if current_user.nil? %> + # + # This is equivalent to: + # + # <%= yield :not_authorized if current_user.nil? %> + # + # content_for, however, can also be used in helper modules. + # + # module StorageHelper + # def stored_content + # content_for(:storage) || "Your storage is empty" + # end + # end + # + # This helper works just like normal helpers. + # + # <%= stored_content %> + # + # You can also use the yield syntax alongside an existing call to + # yield in a layout. For example: + # + # <%# This is the layout %> + # + # + # My Website + # <%= yield :script %> + # + # + # <%= yield %> + # + # + # + # And now, we'll create a view that has a content_for call that + # creates the script identifier. + # + # <%# This is our view %> + # Please login! + # + # <% content_for :script do %> + # + # <% end %> + # + # Then, in another view, you could to do something like this: + # + # <%= link_to 'Logout', action: 'logout', remote: true %> + # + # <% content_for :script do %> + # <%= javascript_include_tag :defaults %> + # <% end %> + # + # That will place +script+ tags for your default set of JavaScript files on the page; + # this technique is useful if you'll only be using these scripts in a few views. + # + # Note that content_for concatenates (default) the blocks it is given for a particular + # identifier in order. For example: + # + # <% content_for :navigation do %> + #
  • <%= link_to 'Home', action: 'index' %>
  • + # <% end %> + # + # And in another place: + # + # <% content_for :navigation do %> + #
  • <%= link_to 'Login', action: 'login' %>
  • + # <% end %> + # + # Then, in another template or layout, this code would render both links in order: + # + #
      <%= content_for :navigation %>
    + # + # If the flush parameter is +true+ content_for replaces the blocks it is given. For example: + # + # <% content_for :navigation do %> + #
  • <%= link_to 'Home', action: 'index' %>
  • + # <% end %> + # + # <%# Add some other content, or use a different template: %> + # + # <% content_for :navigation, flush: true do %> + #
  • <%= link_to 'Login', action: 'login' %>
  • + # <% end %> + # + # Then, in another template or layout, this code would render only the last link: + # + #
      <%= content_for :navigation %>
    + # + # Lastly, simple content can be passed as a parameter: + # + # <% content_for :script, javascript_include_tag(:defaults) %> + # + # WARNING: content_for is ignored in caches. So you shouldn't use it for elements that will be fragment cached. + def content_for(name, content = nil, options = {}, &block) + if content || block_given? + if block_given? + options = content if content + content = capture(&block) + end + if content + options[:flush] ? @view_flow.set(name, content) : @view_flow.append(name, content) + end + nil + else + @view_flow.get(name).presence + end + end + + # The same as +content_for+ but when used with streaming flushes + # straight back to the layout. In other words, if you want to + # concatenate several times to the same buffer when rendering a given + # template, you should use +content_for+, if not, use +provide+ to tell + # the layout to stop looking for more contents. + # + # See ActionController::Streaming for more information. + def provide(name, content = nil, &block) + content = capture(&block) if block_given? + result = @view_flow.append!(name, content) if content + result unless content + end + + # content_for? checks whether any content has been captured yet using content_for. + # + # Useful to render parts of your layout differently based on what is in your views. + # + # <%# This is the layout %> + # + # + # My Website + # <%= yield :script %> + # + # + # <%= yield %> + # <%= yield :right_col %> + # + # + def content_for?(name) + @view_flow.get(name).present? + end + + # Use an alternate output buffer for the duration of the block. + # Defaults to a new empty string. + def with_output_buffer(buf = nil) # :nodoc: + unless buf + buf = ActionView::OutputBuffer.new + if output_buffer && output_buffer.respond_to?(:encoding) + buf.force_encoding(output_buffer.encoding) + end + end + self.output_buffer, old_buffer = buf, output_buffer + yield + output_buffer + ensure + self.output_buffer = old_buffer + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/content_exfiltration_prevention_helper.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/content_exfiltration_prevention_helper.rb new file mode 100644 index 00000000..7f70bc37 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/content_exfiltration_prevention_helper.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module ContentExfiltrationPreventionHelper + mattr_accessor :prepend_content_exfiltration_prevention, default: false + + # Close any open attributes before each form tag. This prevents attackers from + # injecting partial tags that could leak markup offsite. + # + # For example, an attacker might inject: + # + # ).html_safe.freeze + + # Close any open tags that support CDATA (textarea, xmp) before each form tag. + # This prevents attackers from injecting unclosed tags that could capture + # form contents. + # + # For example, an attacker might inject: + # + #
    or + # the end of the document would be captured by the attacker's + # -->".html_safe.freeze + + # Close any open option tags before each form tag. This prevents attackers + # from injecting unclosed options that could leak markup offsite. + # + # For example, an attacker might inject: + # + # or the + # end of the document would be captured by the attacker's + # . By closing any open option tags, we ensure that form + # contents are never exfiltrated. + CLOSE_OPTION_TAG = "".html_safe.freeze + + # Close any open form tags before each new form tag. This prevents attackers + # from injecting unclosed forms that could leak markup offsite. + # + # For example, an attacker might inject: + # + # + # + # The form elements following this tag, up until the next + # would be captured by the attacker's
    . By closing any open + # form tags, we ensure that form contents are never exfiltrated. + CLOSE_FORM_TAG = "
    ".html_safe.freeze + + CONTENT_EXFILTRATION_PREVENTION_MARKUP = (CLOSE_QUOTES_COMMENT + CLOSE_CDATA_COMMENT + CLOSE_OPTION_TAG + CLOSE_FORM_TAG).freeze + + def prevent_content_exfiltration(html) + if prepend_content_exfiltration_prevention + CONTENT_EXFILTRATION_PREVENTION_MARKUP + html + else + html + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/controller_helper.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/controller_helper.rb new file mode 100644 index 00000000..38aa015a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/controller_helper.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attr_internal" + +module ActionView + module Helpers # :nodoc: + # = Action View Controller \Helpers + # + # This module keeps all methods and behavior in ActionView + # that simply delegates to the controller. + module ControllerHelper # :nodoc: + attr_internal :controller, :request + + CONTROLLER_DELEGATES = [:request_forgery_protection_token, :params, + :session, :cookies, :response, :headers, :flash, :action_name, + :controller_name, :controller_path] + + delegate(*CONTROLLER_DELEGATES, to: :controller) + + def assign_controller(controller) + if @_controller = controller + @_request = controller.request if controller.respond_to?(:request) + @_config = controller.config.inheritable_copy if controller.respond_to?(:config) + @_default_form_builder = controller.default_form_builder if controller.respond_to?(:default_form_builder) + else + @_request ||= nil + @_config ||= nil + @_default_form_builder ||= nil + end + end + + def logger + controller.logger if controller.respond_to?(:logger) + end + + def respond_to?(method_name, include_private = false) + return controller.respond_to?(method_name) if CONTROLLER_DELEGATES.include?(method_name.to_sym) + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/csp_helper.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/csp_helper.rb new file mode 100644 index 00000000..1434ca62 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/csp_helper.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module ActionView + module Helpers # :nodoc: + # = Action View CSP \Helpers + module CspHelper + # Returns a meta tag "csp-nonce" with the per-session nonce value + # for allowing inline + # + # +html_options+ may be a hash of attributes for the \ + # + # Instead of passing the content as an argument, you can also use a block + # in which case, you pass your +html_options+ as the first parameter. + # + # <%= javascript_tag type: 'application/javascript' do -%> + # alert('All is good') + # <% end -%> + # + # If you have a content security policy enabled then you can add an automatic + # nonce value by passing nonce: true as part of +html_options+. Example: + # + # <%= javascript_tag nonce: true do -%> + # alert('All is good') + # <% end -%> + def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block) + content = + if block_given? + html_options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash) + capture(&block) + else + content_or_options_with_block + end + + if html_options[:nonce] == true + html_options[:nonce] = content_security_policy_nonce + end + + content_tag("script", javascript_cdata_section(content), html_options) + end + + def javascript_cdata_section(content) # :nodoc: + "\n//#{cdata_section("\n#{content}\n//")}\n".html_safe + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/number_helper.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/number_helper.rb new file mode 100644 index 00000000..eebb1f4e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/number_helper.rb @@ -0,0 +1,165 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/string/output_safety" +require "active_support/number_helper" + +module ActionView + module Helpers # :nodoc: + # = Action View Number \Helpers + # + # Provides methods for converting numbers into formatted strings. + # Methods are provided for phone numbers, currency, percentage, + # precision, positional notation, file size, and pretty printing. + # + # Most methods expect a +number+ argument, and will return it + # unchanged if can't be converted into a valid number. + module NumberHelper + # Raised when argument +number+ param given to the helpers is invalid and + # the option +:raise+ is set to +true+. + class InvalidNumberError < StandardError + attr_accessor :number + def initialize(number) + @number = number + end + end + + # Delegates to ActiveSupport::NumberHelper#number_to_phone. + # + # Additionally, supports a +:raise+ option that will cause + # InvalidNumberError to be raised if +number+ is not a valid number: + # + # number_to_phone("12x34") # => "12x34" + # number_to_phone("12x34", raise: true) # => InvalidNumberError + # + def number_to_phone(number, options = {}) + return unless number + options = options.symbolize_keys + + parse_float(number, true) if options.delete(:raise) + ERB::Util.html_escape(ActiveSupport::NumberHelper.number_to_phone(number, options)) + end + + # Delegates to ActiveSupport::NumberHelper#number_to_currency. + # + # Additionally, supports a +:raise+ option that will cause + # InvalidNumberError to be raised if +number+ is not a valid number: + # + # number_to_currency("12x34") # => "$12x34" + # number_to_currency("12x34", raise: true) # => InvalidNumberError + # + def number_to_currency(number, options = {}) + delegate_number_helper_method(:number_to_currency, number, options) + end + + # Delegates to ActiveSupport::NumberHelper#number_to_percentage. + # + # Additionally, supports a +:raise+ option that will cause + # InvalidNumberError to be raised if +number+ is not a valid number: + # + # number_to_percentage("99x") # => "99x%" + # number_to_percentage("99x", raise: true) # => InvalidNumberError + # + def number_to_percentage(number, options = {}) + delegate_number_helper_method(:number_to_percentage, number, options) + end + + # Delegates to ActiveSupport::NumberHelper#number_to_delimited. + # + # Additionally, supports a +:raise+ option that will cause + # InvalidNumberError to be raised if +number+ is not a valid number: + # + # number_with_delimiter("12x34") # => "12x34" + # number_with_delimiter("12x34", raise: true) # => InvalidNumberError + # + def number_with_delimiter(number, options = {}) + delegate_number_helper_method(:number_to_delimited, number, options) + end + + # Delegates to ActiveSupport::NumberHelper#number_to_rounded. + # + # Additionally, supports a +:raise+ option that will cause + # InvalidNumberError to be raised if +number+ is not a valid number: + # + # number_with_precision("12x34") # => "12x34" + # number_with_precision("12x34", raise: true) # => InvalidNumberError + # + def number_with_precision(number, options = {}) + delegate_number_helper_method(:number_to_rounded, number, options) + end + + # Delegates to ActiveSupport::NumberHelper#number_to_human_size. + # + # Additionally, supports a +:raise+ option that will cause + # InvalidNumberError to be raised if +number+ is not a valid number: + # + # number_to_human_size("12x34") # => "12x34" + # number_to_human_size("12x34", raise: true) # => InvalidNumberError + # + def number_to_human_size(number, options = {}) + delegate_number_helper_method(:number_to_human_size, number, options) + end + + # Delegates to ActiveSupport::NumberHelper#number_to_human. + # + # Additionally, supports a +:raise+ option that will cause + # InvalidNumberError to be raised if +number+ is not a valid number: + # + # number_to_human("12x34") # => "12x34" + # number_to_human("12x34", raise: true) # => InvalidNumberError + # + def number_to_human(number, options = {}) + delegate_number_helper_method(:number_to_human, number, options) + end + + private + def delegate_number_helper_method(method, number, options) + return unless number + options = escape_unsafe_options(options.symbolize_keys) + + wrap_with_output_safety_handling(number, options.delete(:raise)) { + ActiveSupport::NumberHelper.public_send(method, number, options) + } + end + + def escape_unsafe_options(options) + options[:format] = ERB::Util.html_escape(options[:format]) if options[:format] + options[:negative_format] = ERB::Util.html_escape(options[:negative_format]) if options[:negative_format] + options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] + options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] + options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe? + options[:units] = escape_units(options[:units]) if options[:units] && Hash === options[:units] + options + end + + def escape_units(units) + units.transform_values do |v| + ERB::Util.html_escape(v) + end + end + + def wrap_with_output_safety_handling(number, raise_on_invalid, &block) + valid_float = valid_float?(number) + raise InvalidNumberError, number if raise_on_invalid && !valid_float + + formatted_number = yield + + if valid_float || number.html_safe? + formatted_number.html_safe + else + formatted_number + end + end + + def valid_float?(number) + !parse_float(number, false).nil? + end + + def parse_float(number, raise_error) + result = Float(number, exception: false) + raise InvalidNumberError, number if result.nil? && raise_error + result + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/output_safety_helper.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/output_safety_helper.rb new file mode 100644 index 00000000..6f6314e6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/output_safety_helper.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/output_safety" + +module ActionView # :nodoc: + module Helpers # :nodoc: + # = Action View Raw Output \Helpers + module OutputSafetyHelper + # This method outputs without escaping a string. Since escaping tags is + # now default, this can be used when you don't want \Rails to automatically + # escape tags. This is not recommended if the data is coming from the user's + # input. + # + # For example: + # + # raw @user.name + # # => 'Jimmy Tables' + def raw(stringish) + stringish.to_s.html_safe + end + + # This method returns an HTML safe string similar to what Array#join + # would return. The array is flattened, and all items, including + # the supplied separator, are HTML escaped unless they are HTML + # safe, and the returned string is marked as HTML safe. + # + # safe_join([tag.p("foo"), "

    bar

    "], "
    ") + # # => "

    foo

    <br><p>bar</p>" + # + # safe_join([tag.p("foo"), tag.p("bar")], tag.br) + # # => "

    foo


    bar

    " + # + def safe_join(array, sep = $,) + sep = ERB::Util.unwrapped_html_escape(sep) + + array.flatten.map! { |i| ERB::Util.unwrapped_html_escape(i) }.join(sep).html_safe + end + + # Converts the array to a comma-separated sentence where the last element is + # joined by the connector word. This is the html_safe-aware version of + # ActiveSupport's Array#to_sentence. + def to_sentence(array, options = {}) + options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale) + + default_connectors = { + words_connector: ", ", + two_words_connector: " and ", + last_word_connector: ", and " + } + if defined?(I18n) + i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {}) + default_connectors.merge!(i18n_connectors) + end + options = default_connectors.merge!(options) + + case array.length + when 0 + "".html_safe + when 1 + ERB::Util.html_escape(array[0]) + when 2 + safe_join([array[0], array[1]], options[:two_words_connector]) + else + safe_join([safe_join(array[0...-1], options[:words_connector]), options[:last_word_connector], array[-1]], nil) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/rendering_helper.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/rendering_helper.rb new file mode 100644 index 00000000..ca886c87 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/rendering_helper.rb @@ -0,0 +1,218 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActionView + module Helpers # :nodoc: + # # Action View Rendering Helpers + # + # Implements methods that allow rendering from a view context. In order to use + # this module, all you need is to implement view_renderer that returns an + # ActionView::Renderer object. + module RenderingHelper + # Renders a template and returns the result. + # + # Pass the template to render as the first argument. This is shorthand + # syntax for partial rendering, so the template filename should be + # prefixed with an underscore. The partial renderer looks for the partial + # template in the directory of the calling template first. + # + # <% # app/views/posts/new.html.erb %> + # <%= render "form" %> + # # => renders app/views/posts/_form.html.erb + # + # Use the complete view path to render a partial from another directory. + # + # <% # app/views/posts/show.html.erb %> + # <%= render "comments/form" %> + # # => renders app/views/comments/_form.html.erb + # + # Without the rendering mode, the second argument can be a Hash of local + # variable assignments for the template. + # + # <% # app/views/posts/new.html.erb %> + # <%= render "form", post: Post.new %> + # # => renders app/views/posts/_form.html.erb + # + # If the first argument responds to `render_in`, the template will be rendered + # by calling `render_in` with the current view context. + # + # class Greeting + # def render_in(view_context) + # view_context.render html: "

    Hello, World

    " + # end + # + # def format + # :html + # end + # end + # + # <%= render Greeting.new %> + # # => "

    Hello, World

    " + # + # #### Rendering Mode + # + # Pass the rendering mode as first argument to override it. + # + # `:partial` + # : See ActionView::PartialRenderer for details. + # + # <%= render partial: "form", locals: { post: Post.new } %> + # # => renders app/views/posts/_form.html.erb + # + # `:file` + # : Renders the contents of a file. This option should **not** be used with + # unsanitized user input. + # + # <%= render file: "/path/to/some/file" %> + # # => renders /path/to/some/file + # + # `:inline` + # : Renders an ERB template string. + # + # <% name = "World" %> + # <%= render inline: "

    Hello, <%= name %>!

    " %> + # # => renders "

    Hello, World!

    " + # + # `:body` + # : Renders the provided text, and sets the format as `:text`. + # + # <%= render body: "Hello, World!" %> + # # => renders "Hello, World!" + # + # `:plain` + # : Renders the provided text, and sets the format as `:text`. + # + # <%= render plain: "Hello, World!" %> + # # => renders "Hello, World!" + # + # `:html` + # : Renders the provided HTML string, and sets the format as + # `:html`. If the string is not `html_safe?`, performs HTML escaping on + # the string before rendering. + # + # <%= render html: "

    Hello, World!

    ".html_safe %> + # # => renders "

    Hello, World!

    " + # + # <%= render html: "

    Hello, World!

    " %> + # # => renders "<h1>Hello, World!</h1>" + # + # `:renderable` + # : Renders the provided object by calling `render_in` with the current view + # context. The format is determined by calling `format` on the + # renderable if it responds to `format`, falling back to `:html` by + # default. + # + # <%= render renderable: Greeting.new %> + # # => renders "

    Hello, World

    " + # + # + # #### Options + # + # `:locals` + # : Hash of local variable assignments for the template. + # + # <%= render inline: "

    Hello, <%= name %>!

    ", locals: { name: "World" } %> + # # => renders "

    Hello, World!

    " + # + # `:formats` + # : Override the current format to render a template for a different format. + # + # <% # app/views/posts/show.html.erb %> + # <%= render template: "posts/content", formats: [:text] %> + # # => renders app/views/posts/content.text.erb + # + # `:variants` + # : Render a template for a different variant. + # + # <% # app/views/posts/show.html.erb %> + # <%= render template: "posts/content", variants: [:tablet] %> + # # => renders app/views/posts/content.html+tablet.erb + # + # `:handlers` + # : Render a template for a different handler. + # + # <% # app/views/posts/show.html.erb %> + # <%= render template: "posts/content", handlers: [:builder] %> + # # => renders app/views/posts/content.html.builder + def render(options = {}, locals = {}, &block) + case options + when Hash + in_rendering_context(options) do |renderer| + if block_given? + view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block) + else + view_renderer.render(self, options) + end + end + else + if options.respond_to?(:render_in) + options.render_in(self, &block) + else + view_renderer.render_partial(self, partial: options, locals: locals, &block) + end + end + end + + # Overrides _layout_for in the context object so it supports the case a block is + # passed to a partial. Returns the contents that are yielded to a layout, given + # a name or a block. + # + # You can think of a layout as a method that is called with a block. If the user + # calls `yield :some_name`, the block, by default, returns + # `content_for(:some_name)`. If the user calls simply `yield`, the default block + # returns `content_for(:layout)`. + # + # The user can override this default by passing a block to the layout: + # + # # The template + # <%= render layout: "my_layout" do %> + # Content + # <% end %> + # + # # The layout + # + # <%= yield %> + # + # + # In this case, instead of the default block, which would return `content_for(:layout)`, + # this method returns the block that was passed in to `render :layout`, and the response + # would be + # + # + # Content + # + # + # Finally, the block can take block arguments, which can be passed in by + # `yield`: + # + # # The template + # <%= render layout: "my_layout" do |customer| %> + # Hello <%= customer.name %> + # <% end %> + # + # # The layout + # + # <%= yield Struct.new(:name).new("David") %> + # + # + # In this case, the layout would receive the block passed into `render :layout`, + # and the struct specified would be passed into the block as an argument. The result + # would be + # + # + # Hello David + # + # + def _layout_for(*args, &block) + name = args.first + + if block && !name.is_a?(Symbol) + capture(*args, &block) + else + super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/sanitize_helper.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/sanitize_helper.rb new file mode 100644 index 00000000..da3f4934 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/sanitize_helper.rb @@ -0,0 +1,207 @@ +# frozen_string_literal: true + +require "rails-html-sanitizer" + +module ActionView + module Helpers # :nodoc: + # = Action View Sanitize \Helpers + # + # The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements. + # These helper methods extend Action View making them callable within your template files. + module SanitizeHelper + mattr_accessor :sanitizer_vendor, default: Rails::HTML4::Sanitizer + + extend ActiveSupport::Concern + + # Sanitizes HTML input, stripping all but known-safe tags and attributes. + # + # It also strips +href+ / +src+ attributes with unsafe protocols like +javascript:+, while + # also protecting against attempts to use Unicode, ASCII, and hex character references to work + # around these protocol filters. + # + # The default sanitizer is +Rails::HTML5::SafeListSanitizer+. See {Rails HTML + # Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information. + # + # Custom sanitization rules can also be provided. + # + # Warning: Adding disallowed tags or attributes to the allowlists may introduce + # vulnerabilities into your application. Please rely on the default allowlists whenever + # possible, because they are curated to maintain security and safety. If you think that the + # default allowlists should be expanded, please {open an issue on the rails-html-sanitizer + # project}[https://github.com/rails/rails-html-sanitizer/issues]. + # + # Please note that sanitizing user-provided text does not guarantee that the + # resulting markup is valid or even well-formed. + # + # ==== Options + # + # [+:tags+] + # An array of allowed tags. + # + # [+:attributes+] + # An array of allowed attributes. + # + # [+:scrubber+] + # A {Rails::HTML scrubber}[https://github.com/rails/rails-html-sanitizer] + # or {Loofah::Scrubber}[https://github.com/flavorjones/loofah] object that + # defines custom sanitization rules. A custom scrubber takes precedence over + # custom tags and attributes. + # + # ==== Examples + # + # ===== Normal use + # + # <%= sanitize @comment.body %> + # + # ===== Providing custom lists of permitted tags and attributes + # + # <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %> + # + # ===== Providing a custom +Rails::HTML+ scrubber + # + # class CommentScrubber < Rails::HTML::PermitScrubber + # def initialize + # super + # self.tags = %w( form script comment blockquote ) + # self.attributes = %w( style ) + # end + # + # def skip_node?(node) + # node.text? + # end + # end + # + # + # + # <%= sanitize @comment.body, scrubber: CommentScrubber.new %> + # + # See {Rails HTML Sanitizer}[https://github.com/rails/rails-html-sanitizer] for + # documentation about +Rails::HTML+ scrubbers. + # + # ===== Providing a custom +Loofah::Scrubber+ + # + # scrubber = Loofah::Scrubber.new do |node| + # node.remove if node.name == 'script' + # end + # + # + # + # <%= sanitize @comment.body, scrubber: scrubber %> + # + # See {Loofah's documentation}[https://github.com/flavorjones/loofah] for more + # information about defining custom +Loofah::Scrubber+ objects. + # + # ==== Global Configuration + # + # To set the default allowed tags or attributes across your application: + # + # # In config/application.rb + # config.action_view.sanitized_allowed_tags = ['strong', 'em', 'a'] + # config.action_view.sanitized_allowed_attributes = ['href', 'title'] + # + # The default, starting in \Rails 7.1, is to use an HTML5 parser for sanitization (if it is + # available, see NOTE below). If you wish to revert back to the previous HTML4 behavior, you + # can do so by setting the following in your application configuration: + # + # # In config/application.rb + # config.action_view.sanitizer_vendor = Rails::HTML4::Sanitizer + # + # Or, if you're upgrading from a previous version of \Rails and wish to opt into the HTML5 + # behavior: + # + # # In config/application.rb + # config.action_view.sanitizer_vendor = Rails::HTML5::Sanitizer + # + # NOTE: +Rails::HTML5::Sanitizer+ is not supported on JRuby, so on JRuby platforms \Rails will + # fall back to using +Rails::HTML4::Sanitizer+. + def sanitize(html, options = {}) + self.class.safe_list_sanitizer.sanitize(html, options)&.html_safe + end + + # Sanitizes a block of CSS code. Used by #sanitize when it comes across a style attribute. + def sanitize_css(style) + self.class.safe_list_sanitizer.sanitize_css(style) + end + + # Strips all HTML tags from +html+, including comments and special characters. + # + # strip_tags("Strip these tags!") + # # => Strip these tags! + # + # strip_tags("Bold no more! See more here...") + # # => Bold no more! See more here... + # + # strip_tags("
    Welcome to my website!
    ") + # # => Welcome to my website! + # + # strip_tags("> A quote from Smith & Wesson") + # # => > A quote from Smith & Wesson + def strip_tags(html) + self.class.full_sanitizer.sanitize(html)&.html_safe + end + + # Strips all link tags from +html+ leaving just the link text. + # + # strip_links('Ruby on Rails') + # # => Ruby on Rails + # + # strip_links('Please e-mail me at me@email.com.') + # # => Please e-mail me at me@email.com. + # + # strip_links('Blog: Visit.') + # # => Blog: Visit. + # + # strip_links('<malformed & link') + # # => <malformed & link + def strip_links(html) + self.class.link_sanitizer.sanitize(html) + end + + module ClassMethods # :nodoc: + attr_writer :full_sanitizer, :link_sanitizer, :safe_list_sanitizer + + def sanitizer_vendor + ActionView::Helpers::SanitizeHelper.sanitizer_vendor + end + + def sanitized_allowed_tags + sanitizer_vendor.safe_list_sanitizer.allowed_tags + end + + def sanitized_allowed_attributes + sanitizer_vendor.safe_list_sanitizer.allowed_attributes + end + + # Gets the Rails::HTML::FullSanitizer instance used by +strip_tags+. Replace with + # any object that responds to +sanitize+. + # + # class Application < Rails::Application + # config.action_view.full_sanitizer = MySpecialSanitizer.new + # end + def full_sanitizer + @full_sanitizer ||= sanitizer_vendor.full_sanitizer.new + end + + # Gets the Rails::HTML::LinkSanitizer instance used by +strip_links+. + # Replace with any object that responds to +sanitize+. + # + # class Application < Rails::Application + # config.action_view.link_sanitizer = MySpecialSanitizer.new + # end + def link_sanitizer + @link_sanitizer ||= sanitizer_vendor.link_sanitizer.new + end + + # Gets the Rails::HTML::SafeListSanitizer instance used by sanitize and +sanitize_css+. + # Replace with any object that responds to +sanitize+. + # + # class Application < Rails::Application + # config.action_view.safe_list_sanitizer = MySpecialSanitizer.new + # end + def safe_list_sanitizer + @safe_list_sanitizer ||= sanitizer_vendor.safe_list_sanitizer.new + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tag_helper.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tag_helper.rb new file mode 100644 index 00000000..57a18d28 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tag_helper.rb @@ -0,0 +1,605 @@ +# frozen_string_literal: true + +require "active_support/code_generator" +require "active_support/core_ext/enumerable" +require "active_support/core_ext/string/output_safety" +require "active_support/core_ext/string/inflections" +require "action_view/helpers/capture_helper" +require "action_view/helpers/output_safety_helper" + +module ActionView + module Helpers # :nodoc: + # = Action View Tag \Helpers + # + # Provides methods to generate HTML tags programmatically both as a modern + # HTML5 compliant builder style and legacy XHTML compliant tags. + module TagHelper + include CaptureHelper + include OutputSafetyHelper + + BOOLEAN_ATTRIBUTES = %w(allowfullscreen allowpaymentrequest async autofocus + autoplay checked compact controls declare default + defaultchecked defaultmuted defaultselected defer + disabled enabled formnovalidate hidden indeterminate + inert ismap itemscope loop multiple muted nohref + nomodule noresize noshade novalidate nowrap open + pauseonexit playsinline readonly required reversed + scoped seamless selected sortable truespeed + typemustmatch visible).to_set + + BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym)) + BOOLEAN_ATTRIBUTES.freeze + + ARIA_PREFIXES = ["aria", :aria].to_set.freeze + DATA_PREFIXES = ["data", :data].to_set.freeze + + TAG_TYPES = {} + TAG_TYPES.merge! BOOLEAN_ATTRIBUTES.index_with(:boolean) + TAG_TYPES.merge! DATA_PREFIXES.index_with(:data) + TAG_TYPES.merge! ARIA_PREFIXES.index_with(:aria) + TAG_TYPES.freeze + + PRE_CONTENT_STRINGS = Hash.new { "" } + PRE_CONTENT_STRINGS[:textarea] = "\n" + PRE_CONTENT_STRINGS["textarea"] = "\n" + + class TagBuilder # :nodoc: + include CaptureHelper + include OutputSafetyHelper + + def self.define_element(name, code_generator:, method_name: name) + return if method_defined?(name) + + code_generator.class_eval do |batch| + batch << "\n" << + "def #{method_name}(content = nil, escape: true, **options, &block)" << + " tag_string(#{name.inspect}, content, options, escape: escape, &block)" << + "end" + end + end + + def self.define_void_element(name, code_generator:, method_name: name) + code_generator.class_eval do |batch| + batch << "\n" << + "def #{method_name}(escape: true, **options, &block)" << + " self_closing_tag_string(#{name.inspect}, options, escape, '>')" << + "end" + end + end + + def self.define_self_closing_element(name, code_generator:, method_name: name) + code_generator.class_eval do |batch| + batch << "\n" << + "def #{method_name}(content = nil, escape: true, **options, &block)" << + " if content || block" << + " tag_string(#{name.inspect}, content, options, escape: escape, &block)" << + " else" << + " self_closing_tag_string(#{name.inspect}, options, escape)" << + " end" << + "end" + end + end + + ActiveSupport::CodeGenerator.batch(self, __FILE__, __LINE__) do |code_generator| + define_void_element :area, code_generator: code_generator + define_void_element :base, code_generator: code_generator + define_void_element :br, code_generator: code_generator + define_void_element :col, code_generator: code_generator + define_void_element :embed, code_generator: code_generator + define_void_element :hr, code_generator: code_generator + define_void_element :img, code_generator: code_generator + define_void_element :input, code_generator: code_generator + define_void_element :keygen, code_generator: code_generator + define_void_element :link, code_generator: code_generator + define_void_element :meta, code_generator: code_generator + define_void_element :source, code_generator: code_generator + define_void_element :track, code_generator: code_generator + define_void_element :wbr, code_generator: code_generator + + define_self_closing_element :animate, code_generator: code_generator + define_self_closing_element :animateMotion, code_generator: code_generator, method_name: :animate_motion + define_self_closing_element :animateTransform, code_generator: code_generator, method_name: :animate_transform + define_self_closing_element :circle, code_generator: code_generator + define_self_closing_element :ellipse, code_generator: code_generator + define_self_closing_element :line, code_generator: code_generator + define_self_closing_element :path, code_generator: code_generator + define_self_closing_element :polygon, code_generator: code_generator + define_self_closing_element :polyline, code_generator: code_generator + define_self_closing_element :rect, code_generator: code_generator + define_self_closing_element :set, code_generator: code_generator + define_self_closing_element :stop, code_generator: code_generator + define_self_closing_element :use, code_generator: code_generator + define_self_closing_element :view, code_generator: code_generator + + define_element :a, code_generator: code_generator + define_element :abbr, code_generator: code_generator + define_element :address, code_generator: code_generator + define_element :article, code_generator: code_generator + define_element :aside, code_generator: code_generator + define_element :audio, code_generator: code_generator + define_element :b, code_generator: code_generator + define_element :bdi, code_generator: code_generator + define_element :bdo, code_generator: code_generator + define_element :blockquote, code_generator: code_generator + define_element :body, code_generator: code_generator + define_element :button, code_generator: code_generator + define_element :canvas, code_generator: code_generator + define_element :caption, code_generator: code_generator + define_element :cite, code_generator: code_generator + define_element :code, code_generator: code_generator + define_element :colgroup, code_generator: code_generator + define_element :data, code_generator: code_generator + define_element :datalist, code_generator: code_generator + define_element :dd, code_generator: code_generator + define_element :del, code_generator: code_generator + define_element :details, code_generator: code_generator + define_element :dfn, code_generator: code_generator + define_element :dialog, code_generator: code_generator + define_element :div, code_generator: code_generator + define_element :dl, code_generator: code_generator + define_element :dt, code_generator: code_generator + define_element :em, code_generator: code_generator + define_element :fieldset, code_generator: code_generator + define_element :figcaption, code_generator: code_generator + define_element :figure, code_generator: code_generator + define_element :footer, code_generator: code_generator + define_element :form, code_generator: code_generator + define_element :h1, code_generator: code_generator + define_element :h2, code_generator: code_generator + define_element :h3, code_generator: code_generator + define_element :h4, code_generator: code_generator + define_element :h5, code_generator: code_generator + define_element :h6, code_generator: code_generator + define_element :head, code_generator: code_generator + define_element :header, code_generator: code_generator + define_element :hgroup, code_generator: code_generator + define_element :html, code_generator: code_generator + define_element :i, code_generator: code_generator + define_element :iframe, code_generator: code_generator + define_element :ins, code_generator: code_generator + define_element :kbd, code_generator: code_generator + define_element :label, code_generator: code_generator + define_element :legend, code_generator: code_generator + define_element :li, code_generator: code_generator + define_element :main, code_generator: code_generator + define_element :map, code_generator: code_generator + define_element :mark, code_generator: code_generator + define_element :menu, code_generator: code_generator + define_element :meter, code_generator: code_generator + define_element :nav, code_generator: code_generator + define_element :noscript, code_generator: code_generator + define_element :object, code_generator: code_generator + define_element :ol, code_generator: code_generator + define_element :optgroup, code_generator: code_generator + define_element :option, code_generator: code_generator + define_element :output, code_generator: code_generator + define_element :p, code_generator: code_generator + define_element :picture, code_generator: code_generator + define_element :portal, code_generator: code_generator + define_element :pre, code_generator: code_generator + define_element :progress, code_generator: code_generator + define_element :q, code_generator: code_generator + define_element :rp, code_generator: code_generator + define_element :rt, code_generator: code_generator + define_element :ruby, code_generator: code_generator + define_element :s, code_generator: code_generator + define_element :samp, code_generator: code_generator + define_element :script, code_generator: code_generator + define_element :search, code_generator: code_generator + define_element :section, code_generator: code_generator + define_element :select, code_generator: code_generator + define_element :slot, code_generator: code_generator + define_element :small, code_generator: code_generator + define_element :span, code_generator: code_generator + define_element :strong, code_generator: code_generator + define_element :style, code_generator: code_generator + define_element :sub, code_generator: code_generator + define_element :summary, code_generator: code_generator + define_element :sup, code_generator: code_generator + define_element :table, code_generator: code_generator + define_element :tbody, code_generator: code_generator + define_element :td, code_generator: code_generator + define_element :template, code_generator: code_generator + define_element :textarea, code_generator: code_generator + define_element :tfoot, code_generator: code_generator + define_element :th, code_generator: code_generator + define_element :thead, code_generator: code_generator + define_element :time, code_generator: code_generator + define_element :title, code_generator: code_generator + define_element :tr, code_generator: code_generator + define_element :u, code_generator: code_generator + define_element :ul, code_generator: code_generator + define_element :var, code_generator: code_generator + define_element :video, code_generator: code_generator + end + + def initialize(view_context) + @view_context = view_context + end + + # Transforms a Hash into HTML Attributes, ready to be interpolated into + # ERB. + # + # > + # # => + def attributes(attributes) + tag_options(attributes.to_h).to_s.strip.html_safe + end + + def tag_string(name, content = nil, options, escape: true, &block) + content = @view_context.capture(self, &block) if block + + content_tag_string(name, content, options, escape) + end + + def self_closing_tag_string(name, options, escape = true, tag_suffix = " />") + "<#{name}#{tag_options(options, escape)}#{tag_suffix}".html_safe + end + + def content_tag_string(name, content, options, escape = true) + tag_options = tag_options(options, escape) if options + + if escape && content.present? + content = ERB::Util.unwrapped_html_escape(content) + end + "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}".html_safe + end + + def tag_options(options, escape = true) + return if options.blank? + output = +"" + sep = " " + options.each_pair do |key, value| + type = TAG_TYPES[key] + if type == :data && value.is_a?(Hash) + value.each_pair do |k, v| + next if v.nil? + output << sep + output << prefix_tag_option(key, k, v, escape) + end + elsif type == :aria && value.is_a?(Hash) + value.each_pair do |k, v| + next if v.nil? + + case v + when Array, Hash + tokens = TagHelper.build_tag_values(v) + next if tokens.none? + + v = safe_join(tokens, " ") + else + v = v.to_s + end + + output << sep + output << prefix_tag_option(key, k, v, escape) + end + elsif type == :boolean + if value + output << sep + output << boolean_tag_option(key) + end + elsif !value.nil? + output << sep + output << tag_option(key, value, escape) + end + end + output unless output.empty? + end + + def boolean_tag_option(key) + %(#{key}="#{key}") + end + + def tag_option(key, value, escape) + key = ERB::Util.xml_name_escape(key) if escape + + case value + when Array, Hash + value = TagHelper.build_tag_values(value) if key.to_s == "class" + value = escape ? safe_join(value, " ") : value.join(" ") + when Regexp + value = escape ? ERB::Util.unwrapped_html_escape(value.source) : value.source + else + value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s + end + value = value.gsub('"', """) if value.include?('"') + + %(#{key}="#{value}") + end + + private + def prefix_tag_option(prefix, key, value, escape) + key = "#{prefix}-#{key.to_s.dasherize}" + unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal) + value = value.to_json + end + tag_option(key, value, escape) + end + + def respond_to_missing?(*args) + true + end + + def method_missing(called, *args, escape: true, **options, &block) + name = called.name.dasherize + + TagHelper.ensure_valid_html5_tag_name(name) + + tag_string(name, *args, options, escape: escape, &block) + end + end + + # Returns an HTML tag. + # + # === Building HTML tags + # + # Builds HTML5 compliant tags with a tag proxy. Every tag can be built with: + # + # tag.(optional content, options) + # + # where tag name can be e.g. br, div, section, article, or any tag really. + # + # ==== Passing content + # + # Tags can pass content to embed within it: + # + # tag.h1 'All titles fit to print' # =>

    All titles fit to print

    + # + # tag.div tag.p('Hello world!') # =>

    Hello world!

    + # + # Content can also be captured with a block, which is useful in templates: + # + # <%= tag.p do %> + # The next great American novel starts here. + # <% end %> + # # =>

    The next great American novel starts here.

    + # + # ==== Options + # + # Use symbol keyed options to add attributes to the generated tag. + # + # tag.section class: %w( kitties puppies ) + # # =>
    + # + # tag.section id: dom_id(@post) + # # =>
    + # + # Pass +true+ for any attributes that can render with no values, like +disabled+ and +readonly+. + # + # tag.input type: 'text', disabled: true + # # => + # + # HTML5 data-* and aria-* attributes can be set with a + # single +data+ or +aria+ key pointing to a hash of sub-attributes. + # + # To play nicely with JavaScript conventions, sub-attributes are dasherized. + # + # tag.article data: { user_id: 123 } + # # =>
    + # + # Thus data-user-id can be accessed as dataset.userId. + # + # Data attribute values are encoded to JSON, with the exception of strings, symbols, and + # BigDecimals. + # This may come in handy when using jQuery's HTML5-aware .data() + # from 1.4.3. + # + # tag.div data: { city_state: %w( Chicago IL ) } + # # =>
    + # + # The generated tag names and attributes are escaped by default. This can be disabled using + # +escape+. + # + # tag.img src: 'open & shut.png' + # # => + # + # tag.img src: 'open & shut.png', escape: false + # # => + # + # The tag builder respects + # {HTML5 void elements}[https://www.w3.org/TR/html5/syntax.html#void-elements] + # if no content is passed, and omits closing tags for those elements. + # + # # A standard element: + # tag.div # =>
    + # + # # A void element: + # tag.br # =>
    + # + # Note that when using the block form options should be wrapped in + # parenthesis. + # + # <%= tag.a(href: "/about", class: "font-bold") do %> + # About the author + # <% end %> + # # => About the author + # + # === Building HTML attributes + # + # Transforms a Hash into HTML attributes, ready to be interpolated into + # ERB. Includes or omits boolean attributes based on their truthiness. + # Transforms keys nested within + # aria: or data: objects into aria- and data- + # prefixed attributes: + # + # > + # # => + # + # + # # => + # + # === Legacy syntax + # + # The following format is for legacy syntax support. It will be deprecated in future versions of \Rails. + # + # tag(name, options = nil, open = false, escape = true) + # + # It returns an empty HTML tag of type +name+ which by default is XHTML + # compliant. Set +open+ to true to create an open tag compatible + # with HTML 4.0 and below. Add HTML attributes by passing an attributes + # hash to +options+. Set +escape+ to false to disable attribute value + # escaping. + # + # ==== Options + # + # You can use symbols or strings for the attribute names. + # + # Use +true+ with boolean attributes that can render with no value, like + # +disabled+ and +readonly+. + # + # HTML5 data-* attributes can be set with a single +data+ key + # pointing to a hash of sub-attributes. + # + # ==== Examples + # + # tag("br") + # # =>
    + # + # tag("br", nil, true) + # # =>
    + # + # tag("input", type: 'text', disabled: true) + # # => + # + # tag("input", type: 'text', class: ["strong", "highlight"]) + # # => + # + # tag("img", src: "open & shut.png") + # # => + # + # tag("img", { src: "open & shut.png" }, false, false) + # # => + # + # tag("div", data: { name: 'Stephen', city_state: %w(Chicago IL) }) + # # =>
    + # + # tag("div", class: { highlight: current_user.admin? }) + # # =>
    + def tag(name = nil, options = nil, open = false, escape = true) + if name.nil? + tag_builder + else + ensure_valid_html5_tag_name(name) + "<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe + end + end + + # Returns an HTML block tag of type +name+ surrounding the +content+. Add + # HTML attributes by passing an attributes hash to +options+. + # Instead of passing the content as an argument, you can also use a block + # in which case, you pass your +options+ as the second parameter. + # Set escape to false to disable escaping. + # Note: this is legacy syntax, see +tag+ method description for details. + # + # ==== Options + # The +options+ hash can be used with attributes with no value like (disabled and + # readonly), which you can give a value of true in the +options+ hash. You can use + # symbols or strings for the attribute names. + # + # ==== Examples + # content_tag(:p, "Hello world!") + # # =>

    Hello world!

    + # content_tag(:div, content_tag(:p, "Hello world!"), class: "strong") + # # =>

    Hello world!

    + # content_tag(:div, "Hello world!", class: ["strong", "highlight"]) + # # =>
    Hello world!
    + # content_tag(:div, "Hello world!", class: ["strong", { highlight: current_user.admin? }]) + # # =>
    Hello world!
    + # content_tag("select", options, multiple: true) + # # => + # + # <%= content_tag :div, class: "strong" do -%> + # Hello world! + # <% end -%> + # # =>
    Hello world!
    + def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block) + ensure_valid_html5_tag_name(name) + + if block_given? + options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash) + tag_builder.content_tag_string(name, capture(&block), options, escape) + else + tag_builder.content_tag_string(name, content_or_options_with_block, options, escape) + end + end + + # Returns a string of tokens built from +args+. + # + # ==== Examples + # token_list("foo", "bar") + # # => "foo bar" + # token_list("foo", "foo bar") + # # => "foo bar" + # token_list({ foo: true, bar: false }) + # # => "foo" + # token_list(nil, false, 123, "", "foo", { bar: true }) + # # => "123 foo bar" + def token_list(*args) + tokens = build_tag_values(*args).flat_map { |value| CGI.unescape_html(value.to_s).split(/\s+/) }.uniq + + safe_join(tokens, " ") + end + alias_method :class_names, :token_list + + # Returns a CDATA section with the given +content+. CDATA sections + # are used to escape blocks of text containing characters which would + # otherwise be recognized as markup. CDATA sections begin with the string + # and end with (and may not contain) the string ]]>. + # + # cdata_section("") + # # => ]]> + # + # cdata_section(File.read("hello_world.txt")) + # # => + # + # cdata_section("hello]]>world") + # # => world]]> + def cdata_section(content) + splitted = content.to_s.gsub(/\]\]>/, "]]]]>") + "".html_safe + end + + # Returns an escaped version of +html+ without affecting existing escaped entities. + # + # escape_once("1 < 2 & 3") + # # => "1 < 2 & 3" + # + # escape_once("<< Accept & Checkout") + # # => "<< Accept & Checkout" + def escape_once(html) + ERB::Util.html_escape_once(html) + end + + private + def ensure_valid_html5_tag_name(name) + raise ArgumentError, "Invalid HTML5 tag name: #{name.inspect}" unless /\A[a-zA-Z][^\s\/>]*\z/.match?(name) + end + module_function :ensure_valid_html5_tag_name + + def build_tag_values(*args) + tag_values = [] + + args.each do |tag_value| + case tag_value + when Hash + tag_value.each do |key, val| + tag_values << key.to_s if val && key.present? + end + when Array + tag_values.concat build_tag_values(*tag_value) + else + tag_values << tag_value.to_s if tag_value.present? + end + end + + tag_values + end + module_function :build_tag_values + + def tag_builder + @tag_builder ||= TagBuilder.new(self) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags.rb new file mode 100644 index 00000000..00643883 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module ActionView + module Helpers # :nodoc: + module Tags # :nodoc: + extend ActiveSupport::Autoload + + autoload :SelectRenderer + + eager_autoload do + autoload :Base + autoload :Translator + autoload :CheckBox + autoload :CollectionCheckBoxes + autoload :CollectionRadioButtons + autoload :CollectionSelect + autoload :ColorField + autoload :DateField + autoload :DateSelect + autoload :DatetimeField + autoload :DatetimeLocalField + autoload :DatetimeSelect + autoload :EmailField + autoload :FileField + autoload :GroupedCollectionSelect + autoload :HiddenField + autoload :Label + autoload :MonthField + autoload :NumberField + autoload :PasswordField + autoload :RadioButton + autoload :RangeField + autoload :SearchField + autoload :Select + autoload :TelField + autoload :TextArea + autoload :TextField + autoload :TimeField + autoload :TimeSelect + autoload :TimeZoneSelect + autoload :UrlField + autoload :WeekField + autoload :WeekdaySelect + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/base.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/base.rb new file mode 100644 index 00000000..02225aad --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/base.rb @@ -0,0 +1,138 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class Base # :nodoc: + include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper + + attr_reader :object + + def initialize(object_name, method_name, template_object, options = {}) + @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup + @template_object = template_object + + @object_name.sub!(/\[\]$/, "") || @object_name.sub!(/\[\]\]$/, "]") + @object = retrieve_object(options.delete(:object)) + @skip_default_ids = options.delete(:skip_default_ids) + @allow_method_names_outside_object = options.delete(:allow_method_names_outside_object) + @options = options + + if Regexp.last_match + @generate_indexed_names = true + @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) + else + @generate_indexed_names = false + @auto_index = nil + end + end + + # This is what child classes implement. + def render + raise NotImplementedError, "Subclasses must implement a render method" + end + + private + def value + return unless object + + if @allow_method_names_outside_object + object.public_send @method_name if object.respond_to?(@method_name) + else + object.public_send @method_name + end + end + + def value_before_type_cast + return unless object + + method_before_type_cast = @method_name + "_before_type_cast" + + if value_came_from_user? && object.respond_to?(method_before_type_cast) + object.public_send(method_before_type_cast) + else + value + end + end + + def value_came_from_user? + method_name = "#{@method_name}_came_from_user?" + !object.respond_to?(method_name) || object.public_send(method_name) + end + + def retrieve_object(object) + if object + object + elsif @template_object.instance_variable_defined?("@#{@object_name}") + @template_object.instance_variable_get("@#{@object_name}") + end + rescue NameError + # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil. + nil + end + + def retrieve_autoindex(pre_match) + object = self.object || @template_object.instance_variable_get("@#{pre_match}") + if object && object.respond_to?(:to_param) + object.to_param + else + raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}" + end + end + + def add_default_name_and_id_for_value(tag_value, options) + if tag_value.nil? + add_default_name_and_id(options) + else + specified_id = options["id"] + add_default_name_and_id(options) + + if specified_id.blank? && options["id"].present? + options["id"] += "_#{sanitized_value(tag_value)}" + end + end + end + + def add_default_name_and_id(options) + index = name_and_id_index(options) + options["name"] = options.fetch("name") { tag_name(options["multiple"], index) } + + if generate_ids? + options["id"] = options.fetch("id") { tag_id(index, options.delete("namespace")) } + if namespace = options.delete("namespace") + options["id"] = options["id"] ? "#{namespace}_#{options['id']}" : namespace + end + end + end + + def tag_name(multiple = false, index = nil) + @template_object.field_name(@object_name, sanitized_method_name, multiple: multiple, index: index) + end + + def tag_id(index = nil, namespace = nil) + @template_object.field_id(@object_name, @method_name, index: index, namespace: namespace) + end + + def sanitized_method_name + @sanitized_method_name ||= @method_name.delete_suffix("?") + end + + def sanitized_value(value) + value.to_s.gsub(/[\s.]/, "_").gsub(/[^-[[:word:]]]/, "").downcase + end + + def name_and_id_index(options) + if options.key?("index") + options.delete("index") || "" + elsif @generate_indexed_names + @auto_index || "" + end + end + + def generate_ids? + !@skip_default_ids + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/check_box.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/check_box.rb new file mode 100644 index 00000000..cf347b5e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/check_box.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require "action_view/helpers/tags/checkable" + +module ActionView + module Helpers + module Tags # :nodoc: + class CheckBox < Base # :nodoc: + include Checkable + + def initialize(object_name, method_name, template_object, checked_value, unchecked_value, options) + @checked_value = checked_value + @unchecked_value = unchecked_value + super(object_name, method_name, template_object, options) + end + + def render + options = @options.stringify_keys + options["type"] = "checkbox" + options["value"] = @checked_value + options["checked"] = "checked" if input_checked?(options) + + if options["multiple"] + add_default_name_and_id_for_value(@checked_value, options) + options.delete("multiple") + else + add_default_name_and_id(options) + end + + include_hidden = options.delete("include_hidden") { true } + checkbox = tag("input", options) + + if include_hidden + hidden = hidden_field_for_checkbox(options) + hidden + checkbox + else + checkbox + end + end + + private + def checked?(value) + case value + when TrueClass, FalseClass + value == !!@checked_value + when NilClass + false + when String + value == @checked_value + else + if value.respond_to?(:include?) + value.include?(@checked_value) + else + value.to_i == @checked_value.to_i + end + end + end + + def hidden_field_for_checkbox(options) + @unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value, "autocomplete" => "off")) : "".html_safe + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/checkable.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/checkable.rb new file mode 100644 index 00000000..776fefe7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/checkable.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + module Checkable # :nodoc: + def input_checked?(options) + if options.has_key?("checked") + checked = options.delete "checked" + checked == true || checked == "checked" + else + checked?(value) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/collection_check_boxes.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/collection_check_boxes.rb new file mode 100644 index 00000000..776f485d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/collection_check_boxes.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "action_view/helpers/tags/collection_helpers" + +module ActionView + module Helpers + module Tags # :nodoc: + class CollectionCheckBoxes < Base # :nodoc: + include CollectionHelpers + include FormOptionsHelper + + class CheckBoxBuilder < Builder # :nodoc: + def checkbox(extra_html_options = {}) + html_options = extra_html_options.merge(@input_html_options) + html_options[:multiple] = true + html_options[:skip_default_ids] = false + @template_object.checkbox(@object_name, @method_name, html_options, @value, nil) + end + alias_method :check_box, :checkbox + end + + def render(&block) + render_collection_for(CheckBoxBuilder, &block) + end + + private + def render_component(builder) + builder.checkbox + builder.label + end + + def hidden_field_name + "#{super}[]" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/collection_helpers.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/collection_helpers.rb new file mode 100644 index 00000000..c2a6a0ea --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/collection_helpers.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + module CollectionHelpers # :nodoc: + class Builder # :nodoc: + attr_reader :object, :text, :value + + def initialize(template_object, object_name, method_name, object, + sanitized_attribute_name, text, value, input_html_options) + @template_object = template_object + @object_name = object_name + @method_name = method_name + @object = object + @sanitized_attribute_name = sanitized_attribute_name + @text = text + @value = value + @input_html_options = input_html_options + end + + def label(label_html_options = {}, &block) + html_options = @input_html_options.slice(:index, :namespace).merge(label_html_options) + html_options[:for] ||= @input_html_options[:id] if @input_html_options[:id] + + @template_object.label(@object_name, @sanitized_attribute_name, @text, html_options, &block) + end + end + + def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options) + @collection = collection + @value_method = value_method + @text_method = text_method + @html_options = html_options + + super(object_name, method_name, template_object, options) + end + + private + def instantiate_builder(builder_class, item, value, text, html_options) + builder_class.new(@template_object, @object_name, @method_name, item, + sanitize_attribute_name(value), text, value, html_options) + end + + # Generate default options for collection helpers, such as :checked and + # :disabled. + def default_html_options_for_collection(item, value) + html_options = @html_options.dup + + [:checked, :selected, :disabled, :readonly].each do |option| + current_value = @options[option] + next if current_value.nil? + + accept = if current_value.respond_to?(:call) + current_value.call(item) + else + Array(current_value).map(&:to_s).include?(value.to_s) + end + + if accept + html_options[option] = true + elsif option == :checked + html_options[option] = false + end + end + + html_options[:object] = @object + html_options + end + + def sanitize_attribute_name(value) + "#{sanitized_method_name}_#{sanitized_value(value)}" + end + + def render_collection + @collection.map do |item| + value = value_for_collection(item, @value_method) + text = value_for_collection(item, @text_method) + default_html_options = default_html_options_for_collection(item, value) + additional_html_options = option_html_attributes(item) + + yield item, value, text, default_html_options.merge(additional_html_options) + end.join.html_safe + end + + def render_collection_for(builder_class, &block) + options = @options.stringify_keys + rendered_collection = render_collection do |item, value, text, default_html_options| + builder = instantiate_builder(builder_class, item, value, text, default_html_options) + + if block_given? + @template_object.capture(builder, &block) + else + render_component(builder) + end + end + + # Prepend a hidden field to make sure something will be sent back to the + # server if all radio buttons are unchecked. + if options.fetch("include_hidden", true) + hidden_field + rendered_collection + else + rendered_collection + end + end + + def hidden_field + hidden_name = @html_options[:name] || hidden_field_name + options = { id: nil, form: @html_options[:form] } + @template_object.hidden_field_tag(hidden_name, "", options) + end + + def hidden_field_name + "#{tag_name(false, @options[:index])}" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/collection_radio_buttons.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/collection_radio_buttons.rb new file mode 100644 index 00000000..865d9247 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/collection_radio_buttons.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "action_view/helpers/tags/collection_helpers" + +module ActionView + module Helpers + module Tags # :nodoc: + class CollectionRadioButtons < Base # :nodoc: + include CollectionHelpers + include FormOptionsHelper + + class RadioButtonBuilder < Builder # :nodoc: + def radio_button(extra_html_options = {}) + html_options = extra_html_options.merge(@input_html_options) + html_options[:skip_default_ids] = false + @template_object.radio_button(@object_name, @method_name, @value, html_options) + end + end + + def render(&block) + render_collection_for(RadioButtonBuilder, &block) + end + + private + def render_component(builder) + builder.radio_button + builder.label + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/collection_select.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/collection_select.rb new file mode 100644 index 00000000..289d3e07 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/collection_select.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class CollectionSelect < Base # :nodoc: + include SelectRenderer + include FormOptionsHelper + + def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options) + @collection = collection + @value_method = value_method + @text_method = text_method + @html_options = html_options + + super(object_name, method_name, template_object, options) + end + + def render + option_tags_options = { + selected: @options.fetch(:selected) { value }, + disabled: @options[:disabled] + } + + select_content_tag( + options_from_collection_for_select(@collection, @value_method, @text_method, option_tags_options), + @options, @html_options + ) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/color_field.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/color_field.rb new file mode 100644 index 00000000..69b19080 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/color_field.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class ColorField < TextField # :nodoc: + def render + options = @options.stringify_keys + options["value"] ||= validate_color_string(value) + @options = options + super + end + + private + def validate_color_string(string) + regex = /#[0-9a-fA-F]{6}/ + if regex.match?(string) + string.downcase + else + "#000000" + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/date_field.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/date_field.rb new file mode 100644 index 00000000..720b669a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/date_field.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class DateField < DatetimeField # :nodoc: + private + def format_datetime(value) + value&.strftime("%Y-%m-%d") + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/date_select.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/date_select.rb new file mode 100644 index 00000000..4574d1ff --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/date_select.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require "active_support/core_ext/time/calculations" + +module ActionView + module Helpers + module Tags # :nodoc: + class DateSelect < Base # :nodoc: + include SelectRenderer + + def initialize(object_name, method_name, template_object, options, html_options) + @html_options = html_options + + super(object_name, method_name, template_object, options) + end + + def render + error_wrapping(datetime_selector(@options, @html_options).public_send("select_#{select_type}").html_safe) + end + + class << self + def select_type + @select_type ||= name.split("::").last.sub("Select", "").downcase + end + end + + private + def select_type + self.class.select_type + end + + def datetime_selector(options, html_options) + datetime = options.fetch(:selected) { value || default_datetime(options) } + @auto_index ||= nil + + options = options.dup + options[:field_name] = @method_name + options[:include_position] = true + options[:prefix] ||= @object_name + options[:index] = @auto_index if @auto_index && !options.has_key?(:index) + + DateTimeSelector.new(datetime, options, html_options) + end + + def default_datetime(options) + return if options[:include_blank] || options[:prompt] + + case options[:default] + when nil + Time.current + when Date, Time + options[:default] + else + default = options[:default].dup + + # Rename :minute and :second to :min and :sec + default[:min] ||= default[:minute] + default[:sec] ||= default[:second] + + time = Time.current + + [:year, :month, :day, :hour, :min, :sec].each do |key| + default[key] ||= time.public_send(key) + end + + Time.utc( + default[:year], default[:month], default[:day], + default[:hour], default[:min], default[:sec] + ) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/datetime_field.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/datetime_field.rb new file mode 100644 index 00000000..2d9803f2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/datetime_field.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class DatetimeField < TextField # :nodoc: + def render + options = @options.stringify_keys + options["value"] = datetime_value(options["value"] || value) + options["min"] = format_datetime(parse_datetime(options["min"])) + options["max"] = format_datetime(parse_datetime(options["max"])) + @options = options + super + end + + private + def datetime_value(value) + if value.is_a?(String) + value + else + format_datetime(value) + end + end + + def format_datetime(value) + raise NotImplementedError + end + + def parse_datetime(value) + if value.is_a?(String) + DateTime.parse(value) rescue nil + else + value + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/datetime_local_field.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/datetime_local_field.rb new file mode 100644 index 00000000..d5ece652 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/datetime_local_field.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class DatetimeLocalField < DatetimeField # :nodoc: + def initialize(object_name, method_name, template_object, options = {}) + @include_seconds = options.delete(:include_seconds) { true } + super + end + + class << self + def field_type + @field_type ||= "datetime-local" + end + end + + private + def format_datetime(value) + if @include_seconds + value&.strftime("%Y-%m-%dT%T") + else + value&.strftime("%Y-%m-%dT%H:%M") + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/datetime_select.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/datetime_select.rb new file mode 100644 index 00000000..dc557093 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/datetime_select.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class DatetimeSelect < DateSelect # :nodoc: + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/email_field.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/email_field.rb new file mode 100644 index 00000000..0c3b9224 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/email_field.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class EmailField < TextField # :nodoc: + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/file_field.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/file_field.rb new file mode 100644 index 00000000..236078ad --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/file_field.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class FileField < TextField # :nodoc: + def render + include_hidden = @options.delete(:include_hidden) + options = @options.stringify_keys + add_default_name_and_id(options) + + if options["multiple"] && include_hidden + hidden_field_for_multiple_file(options) + super + else + super + end + end + + private + def hidden_field_for_multiple_file(options) + tag("input", "name" => options["name"], "type" => "hidden", "value" => "", "autocomplete" => "off") + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/grouped_collection_select.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/grouped_collection_select.rb new file mode 100644 index 00000000..6dd7fe87 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/grouped_collection_select.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class GroupedCollectionSelect < Base # :nodoc: + include SelectRenderer + include FormOptionsHelper + + def initialize(object_name, method_name, template_object, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options) + @collection = collection + @group_method = group_method + @group_label_method = group_label_method + @option_key_method = option_key_method + @option_value_method = option_value_method + @html_options = html_options + + super(object_name, method_name, template_object, options) + end + + def render + option_tags_options = { + selected: @options.fetch(:selected) { value }, + disabled: @options[:disabled] + } + + select_content_tag( + option_groups_from_collection_for_select(@collection, @group_method, @group_label_method, @option_key_method, @option_value_method, option_tags_options), @options, @html_options + ) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/hidden_field.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/hidden_field.rb new file mode 100644 index 00000000..0984846d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/hidden_field.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class HiddenField < TextField # :nodoc: + def render + @options[:autocomplete] = "off" + super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/label.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/label.rb new file mode 100644 index 00000000..157fca05 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/label.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class Label < Base # :nodoc: + class LabelBuilder # :nodoc: + attr_reader :object + + def initialize(template_object, object_name, method_name, object, tag_value) + @template_object = template_object + @object_name = object_name + @method_name = method_name + @object = object + @tag_value = tag_value + end + + def translation + method_and_value = @tag_value.present? ? "#{@method_name}.#{@tag_value}" : @method_name + + content ||= Translator + .new(object, @object_name, method_and_value, scope: "helpers.label") + .translate + content ||= @method_name.humanize + + content + end + + def to_s + translation + end + end + + def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil) + options ||= {} + + content_is_options = content_or_options.is_a?(Hash) + if content_is_options + options.merge! content_or_options + @content = nil + else + @content = content_or_options + end + + super(object_name, method_name, template_object, options) + end + + def render(&block) + options = @options.stringify_keys + tag_value = options.delete("value") + name_and_id = options.dup + + if name_and_id["for"] + name_and_id["id"] = name_and_id["for"] + else + name_and_id.delete("id") + end + + add_default_name_and_id_for_value(tag_value, name_and_id) + options.delete("index") + options.delete("namespace") + options["for"] = name_and_id["id"] unless options.key?("for") + + builder = LabelBuilder.new(@template_object, @object_name, @method_name, @object, tag_value) + + content = if block_given? + @template_object.capture(builder, &block) + elsif @content.present? + @content.to_s + else + render_component(builder) + end + + label_tag(name_and_id["id"], content, options) + end + + private + def render_component(builder) + builder.translation + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/month_field.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/month_field.rb new file mode 100644 index 00000000..d652ef41 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/month_field.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class MonthField < DatetimeField # :nodoc: + private + def format_datetime(value) + value&.strftime("%Y-%m") + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/number_field.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/number_field.rb new file mode 100644 index 00000000..41c69642 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/number_field.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class NumberField < TextField # :nodoc: + def render + options = @options.stringify_keys + + if range = options.delete("in") || options.delete("within") + options.update("min" => range.min, "max" => range.max) + end + + @options = options + super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/password_field.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/password_field.rb new file mode 100644 index 00000000..9f10f523 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/password_field.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class PasswordField < TextField # :nodoc: + def render + @options = { value: nil }.merge!(@options) + super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/placeholderable.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/placeholderable.rb new file mode 100644 index 00000000..e9f7601e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/placeholderable.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + module Placeholderable # :nodoc: + def initialize(*) + super + + if tag_value = @options[:placeholder] + placeholder = tag_value if tag_value.is_a?(String) + method_and_value = tag_value.is_a?(TrueClass) ? @method_name : "#{@method_name}.#{tag_value}" + + placeholder ||= Tags::Translator + .new(object, @object_name, method_and_value, scope: "helpers.placeholder") + .translate + placeholder ||= @method_name.humanize + @options[:placeholder] = placeholder + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/radio_button.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/radio_button.rb new file mode 100644 index 00000000..4ce6c9f6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/radio_button.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "action_view/helpers/tags/checkable" + +module ActionView + module Helpers + module Tags # :nodoc: + class RadioButton < Base # :nodoc: + include Checkable + + def initialize(object_name, method_name, template_object, tag_value, options) + @tag_value = tag_value + super(object_name, method_name, template_object, options) + end + + def render + options = @options.stringify_keys + options["type"] = "radio" + options["value"] = @tag_value + options["checked"] = "checked" if input_checked?(options) + add_default_name_and_id_for_value(@tag_value, options) + tag("input", options) + end + + private + def checked?(value) + value.to_s == @tag_value.to_s + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/range_field.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/range_field.rb new file mode 100644 index 00000000..66d1bbac --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/range_field.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class RangeField < NumberField # :nodoc: + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/search_field.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/search_field.rb new file mode 100644 index 00000000..f2093489 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/search_field.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class SearchField < TextField # :nodoc: + def render + options = @options.stringify_keys + + if options["autosave"] + if options["autosave"] == true + options["autosave"] = request.host.split(".").reverse.join(".") + end + options["results"] ||= 10 + end + + if options["onsearch"] + options["incremental"] = true unless options.has_key?("incremental") + end + + @options = options + super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/select.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/select.rb new file mode 100644 index 00000000..0e3de4af --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/select.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class Select < Base # :nodoc: + include SelectRenderer + include FormOptionsHelper + + def initialize(object_name, method_name, template_object, choices, options, html_options) + @choices = block_given? ? template_object.capture { yield || "" } : choices + @choices = @choices.to_a if @choices.is_a?(Range) + + @html_options = html_options + + super(object_name, method_name, template_object, options) + end + + def render + option_tags_options = { + selected: @options.fetch(:selected) { value.nil? ? "" : value }, + disabled: @options[:disabled] + } + + option_tags = if grouped_choices? + grouped_options_for_select(@choices, option_tags_options) + else + options_for_select(@choices, option_tags_options) + end + + select_content_tag(option_tags, @options, @html_options) + end + + private + # Grouped choices look like this: + # + # [nil, []] + # { nil => [] } + def grouped_choices? + !@choices.blank? && @choices.first.respond_to?(:second) && Array === @choices.first.second + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/select_renderer.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/select_renderer.rb new file mode 100644 index 00000000..0c80694e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/select_renderer.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + module SelectRenderer # :nodoc: + private + def select_content_tag(option_tags, options, html_options) + html_options = html_options.stringify_keys + [:required, :multiple, :size].each do |prop| + html_options[prop.to_s] = options.delete(prop) if options.key?(prop) && !html_options.key?(prop.to_s) + end + + add_default_name_and_id(html_options) + + if placeholder_required?(html_options) + raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false + options[:include_blank] ||= true unless options[:prompt] + end + + value = options.fetch(:selected) { value() } + select = content_tag("select", add_options(option_tags, options, value), html_options) + + if html_options["multiple"] && options.fetch(:include_hidden, true) + tag("input", disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "", autocomplete: "off") + select + else + select + end + end + + def placeholder_required?(html_options) + # See https://html.spec.whatwg.org/multipage/forms.html#attr-select-required + html_options["required"] && !html_options["multiple"] && html_options.fetch("size", 1).to_i == 1 + end + + def add_options(option_tags, options, value = nil) + if options[:include_blank] + content = (options[:include_blank] if options[:include_blank].is_a?(String)) + label = (" " unless content) + option_tags = tag_builder.content_tag_string("option", content, value: "", label: label) + "\n" + option_tags + end + + if value.blank? && options[:prompt] + tag_options = { value: "" }.tap do |prompt_opts| + prompt_opts[:disabled] = true if options[:disabled] == "" + prompt_opts[:selected] = true if options[:selected] == "" + end + option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags + end + + option_tags + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/tel_field.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/tel_field.rb new file mode 100644 index 00000000..ab1caaac --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/tel_field.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class TelField < TextField # :nodoc: + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/text_area.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/text_area.rb new file mode 100644 index 00000000..4519082f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/text_area.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "action_view/helpers/tags/placeholderable" + +module ActionView + module Helpers + module Tags # :nodoc: + class TextArea < Base # :nodoc: + include Placeholderable + + def render + options = @options.stringify_keys + add_default_name_and_id(options) + + if size = options.delete("size") + options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split) + end + + content_tag("textarea", options.delete("value") { value_before_type_cast }, options) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/text_field.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/text_field.rb new file mode 100644 index 00000000..c579e9e7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/text_field.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require "action_view/helpers/tags/placeholderable" + +module ActionView + module Helpers + module Tags # :nodoc: + class TextField < Base # :nodoc: + include Placeholderable + + def render + options = @options.stringify_keys + options["size"] = options["maxlength"] unless options.key?("size") + options["type"] ||= field_type + options["value"] = options.fetch("value") { value_before_type_cast } unless field_type == "file" + add_default_name_and_id(options) + tag("input", options) + end + + class << self + def field_type + @field_type ||= name.split("::").last.sub("Field", "").downcase + end + end + + private + def field_type + self.class.field_type + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/time_field.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/time_field.rb new file mode 100644 index 00000000..cd3578ef --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/time_field.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class TimeField < DatetimeField # :nodoc: + def initialize(object_name, method_name, template_object, options = {}) + @include_seconds = options.delete(:include_seconds) { true } + super + end + + private + def format_datetime(value) + if @include_seconds + value&.strftime("%T.%L") + else + value&.strftime("%H:%M") + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/time_select.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/time_select.rb new file mode 100644 index 00000000..ba3dcb64 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/time_select.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class TimeSelect < DateSelect # :nodoc: + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/time_zone_select.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/time_zone_select.rb new file mode 100644 index 00000000..37d647e6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/time_zone_select.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class TimeZoneSelect < Base # :nodoc: + include SelectRenderer + include FormOptionsHelper + + def initialize(object_name, method_name, template_object, priority_zones, options, html_options) + @priority_zones = priority_zones + @html_options = html_options + + super(object_name, method_name, template_object, options) + end + + def render + select_content_tag( + time_zone_options_for_select(value || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options + ) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/translator.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/translator.rb new file mode 100644 index 00000000..e81ca3ae --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/translator.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class Translator # :nodoc: + def initialize(object, object_name, method_and_value, scope:) + @object_name = object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1') + @method_and_value = method_and_value + @scope = scope + @model = object.respond_to?(:to_model) ? object.to_model : nil + end + + def translate + translated_attribute = I18n.t("#{object_name}.#{method_and_value}", default: i18n_default, scope: scope).presence + translated_attribute || human_attribute_name + end + + private + attr_reader :object_name, :method_and_value, :scope, :model + + def i18n_default + if model + key = model.model_name.i18n_key + ["#{key}.#{method_and_value}".to_sym, ""] + else + "" + end + end + + def human_attribute_name + if model && model.class.respond_to?(:human_attribute_name) + model.class.human_attribute_name(method_and_value) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/url_field.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/url_field.rb new file mode 100644 index 00000000..395fec67 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/url_field.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class UrlField < TextField # :nodoc: + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/week_field.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/week_field.rb new file mode 100644 index 00000000..51203006 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/week_field.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class WeekField < DatetimeField # :nodoc: + private + def format_datetime(value) + value&.strftime("%Y-W%V") + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/weekday_select.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/weekday_select.rb new file mode 100644 index 00000000..82a60b91 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/tags/weekday_select.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class WeekdaySelect < Base # :nodoc: + include SelectRenderer + include FormOptionsHelper + + def initialize(object_name, method_name, template_object, options, html_options) + @html_options = html_options + + super(object_name, method_name, template_object, options) + end + + def render + select_content_tag( + weekday_options_for_select( + value || @options[:selected], + index_as_value: @options.fetch(:index_as_value, false), + day_format: @options.fetch(:day_format, :day_names), + beginning_of_week: @options.fetch(:beginning_of_week, Date.beginning_of_week) + ), + @options, + @html_options + ) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/text_helper.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/text_helper.rb new file mode 100644 index 00000000..bf837d52 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/text_helper.rb @@ -0,0 +1,568 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/filters" +require "active_support/core_ext/string/access" +require "active_support/core_ext/array/extract_options" +require "action_view/helpers/sanitize_helper" +require "action_view/helpers/tag_helper" +require "action_view/helpers/output_safety_helper" + +module ActionView + module Helpers # :nodoc: + # = Action View Text \Helpers + # + # The TextHelper module provides a set of methods for filtering, formatting + # and transforming strings, which can reduce the amount of inline Ruby code in + # your views. These helper methods extend Action View making them callable + # within your template files. + # + # ==== Sanitization + # + # Most text helpers that generate HTML output sanitize the given input by default, + # but do not escape it. This means HTML tags will appear in the page but all malicious + # code will be removed. Let's look at some examples using the +simple_format+ method: + # + # simple_format('Example') + # # => "

    Example

    " + # + # simple_format('Example') + # # => "

    Example

    " + # + # If you want to escape all content, you should invoke the +h+ method before + # calling the text helper. + # + # simple_format h('Example') + # # => "

    <a href=\"http://example.com/\">Example</a>

    " + module TextHelper + extend ActiveSupport::Concern + + include SanitizeHelper + include TagHelper + include OutputSafetyHelper + + # The preferred method of outputting text in your views is to use the + # <%= "text" %> eRuby syntax. The regular +puts+ and +print+ methods + # do not operate as expected in an eRuby code block. If you absolutely must + # output text within a non-output code block (i.e., <% %>), you + # can use the +concat+ method. + # + # <% concat "hello" %> is equivalent to <%= "hello" %> + # + # <% + # unless signed_in? + # concat link_to("Sign In", action: :sign_in) + # end + # %> + # + # is equivalent to + # + # <% unless signed_in? %> + # <%= link_to "Sign In", action: :sign_in %> + # <% end %> + # + def concat(string) + output_buffer << string + end + + def safe_concat(string) + output_buffer.respond_to?(:safe_concat) ? output_buffer.safe_concat(string) : concat(string) + end + + # Truncates +text+ if it is longer than a specified +:length+. If +text+ + # is truncated, an omission marker will be appended to the result for a + # total length not exceeding +:length+. + # + # You can also pass a block to render and append extra content after the + # omission marker when +text+ is truncated. However, this content _can_ + # cause the total length to exceed +:length+ characters. + # + # The result will be escaped unless escape: false is specified. + # In any case, the result will be marked HTML-safe. Care should be taken + # if +text+ might contain HTML tags or entities, because truncation could + # produce invalid HTML, such as unbalanced or incomplete tags. + # + # ==== Options + # + # [+:length+] + # The maximum number of characters that should be returned, excluding + # any extra content from the block. Defaults to 30. + # + # [+:omission+] + # The string to append after truncating. Defaults to "...". + # + # [+:separator+] + # A string or regexp used to find a breaking point at which to truncate. + # By default, truncation can occur at any character in +text+. + # + # [+:escape+] + # Whether to escape the result. Defaults to true. + # + # ==== Examples + # + # truncate("Once upon a time in a world far far away") + # # => "Once upon a time in a world..." + # + # truncate("Once upon a time in a world far far away", length: 17) + # # => "Once upon a ti..." + # + # truncate("Once upon a time in a world far far away", length: 17, separator: ' ') + # # => "Once upon a..." + # + # truncate("And they found that many people were sleeping better.", length: 25, omission: '... (continued)') + # # => "And they f... (continued)" + # + # truncate("

    Once upon a time in a world far far away

    ") + # # => "<p>Once upon a time in a wo..." + # + # truncate("

    Once upon a time in a world far far away

    ", escape: false) + # # => "

    Once upon a time in a wo..." + # + # truncate("Once upon a time in a world far far away") { link_to "Continue", "#" } + # # => "Once upon a time in a world...Continue" + def truncate(text, options = {}, &block) + if text + length = options.fetch(:length, 30) + + content = text.truncate(length, options) + content = options[:escape] == false ? content.html_safe : ERB::Util.html_escape(content) + content << capture(&block) if block_given? && text.length > length + content + end + end + + # Highlights occurrences of +phrases+ in +text+ by formatting them with a + # highlighter string. +phrases+ can be one or more strings or regular + # expressions. The result will be marked HTML safe. By default, +text+ is + # sanitized before highlighting to prevent possible XSS attacks. + # + # If a block is specified, it will be used instead of the highlighter + # string. Each occurrence of a phrase will be passed to the block, and its + # return value will be inserted into the final result. + # + # ==== Options + # + # [+:highlighter+] + # The highlighter string. Uses \1 as the placeholder for a + # phrase, similar to +String#sub+. Defaults to "\1". + # This option is ignored if a block is specified. + # + # [+:sanitize+] + # Whether to sanitize +text+ before highlighting. Defaults to true. + # + # ==== Examples + # + # highlight('You searched for: rails', 'rails') + # # => "You searched for: rails" + # + # highlight('You searched for: rails', /for|rails/) + # # => "You searched for: rails" + # + # highlight('You searched for: ruby, rails, dhh', 'actionpack') + # # => "You searched for: ruby, rails, dhh" + # + # highlight('You searched for: rails', ['for', 'rails'], highlighter: '\1') + # # => "You searched for: rails" + # + # highlight('You searched for: rails', 'rails', highlighter: '\1') + # # => "You searched for: rails" + # + # highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match)) } + # # => "You searched for: rails" + # + # highlight('ruby on rails', 'rails', sanitize: false) + # # => "ruby on rails" + def highlight(text, phrases, options = {}, &block) + text = sanitize(text) if options.fetch(:sanitize, true) + + if text.blank? || phrases.blank? + text || "" + else + patterns = Array(phrases).map { |phrase| Regexp === phrase ? phrase : Regexp.escape(phrase) } + pattern = /(#{patterns.join("|")})/i + highlighter = options.fetch(:highlighter, '\1') unless block + + text.scan(/<[^>]*|[^<]+/).each do |segment| + if !segment.start_with?("<") + if block + segment.gsub!(pattern, &block) + else + segment.gsub!(pattern, highlighter) + end + end + end.join + end.html_safe + end + + # Extracts the first occurrence of +phrase+ plus surrounding text from + # +text+. An omission marker is prepended / appended if the start / end of + # the result does not coincide with the start / end of +text+. The result + # is always stripped in any case. Returns +nil+ if +phrase+ isn't found. + # + # ==== Options + # + # [+:radius+] + # The number of characters (or tokens — see +:separator+ option) around + # +phrase+ to include in the result. Defaults to 100. + # + # [+:omission+] + # The marker to prepend / append when the start / end of the excerpt + # does not coincide with the start / end of +text+. Defaults to + # "...". + # + # [+:separator+] + # The separator between tokens to count for +:radius+. Defaults to + # "", which treats each character as a token. + # + # ==== Examples + # + # excerpt('This is an example', 'an', radius: 5) + # # => "...s is an exam..." + # + # excerpt('This is an example', 'is', radius: 5) + # # => "This is a..." + # + # excerpt('This is an example', 'is') + # # => "This is an example" + # + # excerpt('This next thing is an example', 'ex', radius: 2) + # # => "...next..." + # + # excerpt('This is also an example', 'an', radius: 8, omission: ' ') + # # => " is also an example" + # + # excerpt('This is a very beautiful morning', 'very', separator: ' ', radius: 1) + # # => "...a very beautiful..." + def excerpt(text, phrase, options = {}) + return unless text && phrase + + separator = options.fetch(:separator, nil) || "" + case phrase + when Regexp + regex = phrase + else + regex = /#{Regexp.escape(phrase)}/i + end + + return unless matches = text.match(regex) + phrase = matches[0] + + unless separator.empty? + text.split(separator).each do |value| + if value.match?(regex) + phrase = value + break + end + end + end + + first_part, second_part = text.split(phrase, 2) + + prefix, first_part = cut_excerpt_part(:first, first_part, separator, options) + postfix, second_part = cut_excerpt_part(:second, second_part, separator, options) + + affix = [first_part, separator, phrase, separator, second_part].join.strip + [prefix, affix, postfix].join + end + + # Attempts to pluralize the +singular+ word unless +count+ is 1. If + # +plural+ is supplied, it will use that when count is > 1, otherwise + # it will use the Inflector to determine the plural form for the given locale, + # which defaults to +I18n.locale+. + # + # The word will be pluralized using rules defined for the locale + # (you must define your own inflection rules for languages other than English). + # See ActiveSupport::Inflector.pluralize + # + # pluralize(1, 'person') + # # => "1 person" + # + # pluralize(2, 'person') + # # => "2 people" + # + # pluralize(3, 'person', plural: 'users') + # # => "3 users" + # + # pluralize(0, 'person') + # # => "0 people" + # + # pluralize(2, 'Person', locale: :de) + # # => "2 Personen" + def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale) + word = if count == 1 || count.to_s.match?(/^1(\.0+)?$/) + singular + else + plural || singular.pluralize(locale) + end + + "#{count || 0} #{word}" + end + + # Wraps the +text+ into lines no longer than +line_width+ width. This method + # breaks on the first whitespace character that does not exceed +line_width+ + # (which is 80 by default). + # + # word_wrap('Once upon a time') + # # => "Once upon a time" + # + # word_wrap('Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding a successor to the throne turned out to be more trouble than anyone could have imagined...') + # # => "Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\na successor to the throne turned out to be more trouble than anyone could have\nimagined..." + # + # word_wrap('Once upon a time', line_width: 8) + # # => "Once\nupon a\ntime" + # + # word_wrap('Once upon a time', line_width: 1) + # # => "Once\nupon\na\ntime" + # + # You can also specify a custom +break_sequence+ ("\n" by default): + # + # word_wrap('Once upon a time', line_width: 1, break_sequence: "\r\n") + # # => "Once\r\nupon\r\na\r\ntime" + def word_wrap(text, line_width: 80, break_sequence: "\n") + return +"" if text.empty? + + # Match up to `line_width` characters, followed by one of + # (1) non-newline whitespace plus an optional newline + # (2) the end of the string, ignoring any trailing newlines + # (3) a newline + # + # -OR- + # + # Match an empty line + pattern = /(.{1,#{line_width}})(?:[^\S\n]+\n?|\n*\Z|\n)|\n/ + + text.gsub(pattern, "\\1#{break_sequence}").chomp!(break_sequence) + end + + # Returns +text+ transformed into HTML using simple formatting rules. + # Two or more consecutive newlines (\n\n or \r\n\r\n) are + # considered a paragraph and wrapped in

    tags. One newline + # (\n or \r\n) is considered a linebreak and a + #
    tag is appended. This method does not remove the + # newlines from the +text+. + # + # You can pass any HTML attributes into html_options. These + # will be added to all created paragraphs. + # + # ==== Options + # * :sanitize - If +false+, does not sanitize +text+. + # * :sanitize_options - Any extra options you want appended to the sanitize. + # * :wrapper_tag - String representing the wrapper tag, defaults to "p" + # + # ==== Examples + # my_text = "Here is some basic text...\n...with a line break." + # + # simple_format(my_text) + # # => "

    Here is some basic text...\n
    ...with a line break.

    " + # + # simple_format(my_text, {}, wrapper_tag: "div") + # # => "
    Here is some basic text...\n
    ...with a line break.
    " + # + # more_text = "We want to put a paragraph...\n\n...right there." + # + # simple_format(more_text) + # # => "

    We want to put a paragraph...

    \n\n

    ...right there.

    " + # + # simple_format("Look ma! A class!", class: 'description') + # # => "

    Look ma! A class!

    " + # + # simple_format("Unblinkable.") + # # => "

    Unblinkable.

    " + # + # simple_format("Blinkable! It's true.", {}, sanitize: false) + # # => "

    Blinkable! It's true.

    " + # + # simple_format("Continue", {}, { sanitize_options: { attributes: %w[target href] } }) + # # => "

    Continue

    " + def simple_format(text, html_options = {}, options = {}) + wrapper_tag = options[:wrapper_tag] || "p" + + text = sanitize(text, options.fetch(:sanitize_options, {})) if options.fetch(:sanitize, true) + paragraphs = split_paragraphs(text) + + if paragraphs.empty? + content_tag(wrapper_tag, nil, html_options) + else + paragraphs.map! { |paragraph| + content_tag(wrapper_tag, raw(paragraph), html_options) + }.join("\n\n").html_safe + end + end + + # Creates a Cycle object whose +to_s+ method cycles through elements of an + # array every time it is called. This can be used for example, to alternate + # classes for table rows. You can use named cycles to allow nesting in loops. + # Passing a Hash as the last parameter with a :name key will create a + # named cycle. The default name for a cycle without a +:name+ key is + # "default". You can manually reset a cycle by calling reset_cycle + # and passing the name of the cycle. The current cycle string can be obtained + # anytime using the current_cycle method. + # + # <%# Alternate CSS classes for even and odd numbers... %> + # <% @items = [1,2,3,4] %> + # + # <% @items.each do |item| %> + # "> + # + # + # <% end %> + #
    <%= item %>
    + # + # + # <%# Cycle CSS classes for rows, and text colors for values within each row %> + # <% @items = [ + # { first: "Robert", middle: "Daniel", last: "James" }, + # { first: "Emily", middle: "Shannon", maiden: "Pike", last: "Hicks" }, + # { first: "June", middle: "Dae", last: "Jones" }, + # ] %> + # <% @items.each do |item| %> + # "> + # + # <% item.values.each do |value| %> + # <%# Create a named cycle "colors" %> + # "> + # <%= value %> + # + # <% end %> + # <% reset_cycle("colors") %> + # + # + # <% end %> + def cycle(first_value, *values) + options = values.extract_options! + name = options.fetch(:name, "default") + + values.unshift(*first_value) + + cycle = get_cycle(name) + unless cycle && cycle.values == values + cycle = set_cycle(name, Cycle.new(*values)) + end + cycle.to_s + end + + # Returns the current cycle string after a cycle has been started. Useful + # for complex table highlighting or any other design need which requires + # the current cycle string in more than one place. + # + # <%# Alternate background colors %> + # <% @items = [1,2,3,4] %> + # <% @items.each do |item| %> + #
    "> + # <%= item %> + #
    + # <% end %> + def current_cycle(name = "default") + cycle = get_cycle(name) + cycle.current_value if cycle + end + + # Resets a cycle so that it starts from the first element the next time + # it is called. Pass in +name+ to reset a named cycle. + # + # <%# Alternate CSS classes for even and odd numbers... %> + # <% @items = [[1,2,3,4], [5,6,3], [3,4,5,6,7,4]] %> + # + # <% @items.each do |item| %> + # "> + # <% item.each do |value| %> + # "> + # <%= value %> + # + # <% end %> + # + # <% reset_cycle("colors") %> + # + # <% end %> + #
    + def reset_cycle(name = "default") + cycle = get_cycle(name) + cycle.reset if cycle + end + + class Cycle # :nodoc: + attr_reader :values + + def initialize(first_value, *values) + @values = values.unshift(first_value) + reset + end + + def reset + @index = 0 + end + + def current_value + @values[previous_index].to_s + end + + def to_s + value = @values[@index].to_s + @index = next_index + value + end + + private + def next_index + step_index(1) + end + + def previous_index + step_index(-1) + end + + def step_index(n) + (@index + n) % @values.size + end + end + + private + # The cycle helpers need to store the cycles in a place that is + # guaranteed to be reset every time a page is rendered, so it + # uses an instance variable of ActionView::Base. + def get_cycle(name) + @_cycles = Hash.new unless defined?(@_cycles) + @_cycles[name] + end + + def set_cycle(name, cycle_object) + @_cycles = Hash.new unless defined?(@_cycles) + @_cycles[name] = cycle_object + end + + def split_paragraphs(text) + return [] if text.blank? + + text.to_str.gsub(/\r\n?/, "\n").split(/\n\n+/).map! do |t| + t.gsub!(/([^\n]\n)(?=[^\n])/, '\1
    ') || t + end + end + + def cut_excerpt_part(part_position, part, separator, options) + return "", "" unless part + + radius = options.fetch(:radius, 100) + omission = options.fetch(:omission, "...") + + if separator != "" + part = part.split(separator) + part.delete("") + end + + affix = part.length > radius ? omission : "" + + part = + if part_position == :first + part.last(radius) + else + part.first(radius) + end + + if separator != "" + part = part.join(separator) + end + + return affix, part + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/translation_helper.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/translation_helper.rb new file mode 100644 index 00000000..97154971 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/translation_helper.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true + +require "action_view/helpers/tag_helper" +require "active_support/html_safe_translation" + +module ActionView + module Helpers # :nodoc: + # = Action View Translation \Helpers + module TranslationHelper + extend ActiveSupport::Concern + + include TagHelper + + # Specify whether an error should be raised for missing translations. + singleton_class.attr_accessor :raise_on_missing_translations + + included do + mattr_accessor :debug_missing_translation, default: true + end + + # Delegates to I18n#translate but also performs three additional + # functions. + # + # First, it will ensure that any thrown +MissingTranslation+ messages will + # be rendered as inline spans that: + # + # * Have a translation-missing class applied + # * Contain the missing key as the value of the +title+ attribute + # * Have a titleized version of the last key segment as text + # + # For example, the value returned for the missing translation key + # "blog.post.title" will be: + # + # Title + # + # This allows for views to display rather reasonable strings while still + # giving developers a way to find missing translations. + # + # If you would prefer missing translations to raise an error, you can + # opt out of span-wrapping behavior globally by setting + # config.i18n.raise_on_missing_translations = true or + # individually by passing raise: true as an option to + # translate. + # + # Second, if the key starts with a period translate will scope + # the key by the current partial. Calling translate(".foo") from + # the people/index.html.erb template is equivalent to calling + # translate("people.index.foo"). This makes it less + # repetitive to translate many keys within the same partial and provides + # a convention to scope keys consistently. + # + # Third, the translation will be marked as html_safe if the key + # has the suffix "_html" or the last element of the key is "html". Calling + # translate("footer_html") or translate("footer.html") + # will return an HTML safe string that won't be escaped by other HTML + # helper methods. This naming convention helps to identify translations + # that include HTML tags so that you know what kind of output to expect + # when you call translate in a template and translators know which keys + # they can provide HTML values for. + # + # To access the translated text along with the fully resolved + # translation key, translate accepts a block: + # + # <%= translate(".relative_key") do |translation, resolved_key| %> + # <%= translation %> + # <% end %> + # + # This enables annotate translated text to be aware of the scope it was + # resolved against. + # + def translate(key, **options) + return key.map { |k| translate(k, **options) } if key.is_a?(Array) + key = key&.to_s unless key.is_a?(Symbol) + + alternatives = if options.key?(:default) + options[:default].is_a?(Array) ? options.delete(:default).compact : [options.delete(:default)] + end + + options[:raise] = true if options[:raise].nil? && TranslationHelper.raise_on_missing_translations + default = MISSING_TRANSLATION + + translation = while key || alternatives.present? + if alternatives.blank? && !options[:raise].nil? + default = NO_DEFAULT # let I18n handle missing translation + end + + key = scope_key_by_partial(key) + + translated = ActiveSupport::HtmlSafeTranslation.translate(key, **options, default: default) + + break translated unless translated == MISSING_TRANSLATION + + if alternatives.present? && !alternatives.first.is_a?(Symbol) + break alternatives.first && I18n.translate(nil, **options, default: alternatives) + end + + first_key ||= key + key = alternatives&.shift + end + + if key.nil? && !first_key.nil? + translation = missing_translation(first_key, options) + key = first_key + end + + block_given? ? yield(translation, key) : translation + end + alias :t :translate + + # Delegates to I18n.localize with no additional functionality. + # + # See https://www.rubydoc.info/gems/i18n/I18n/Backend/Base:localize + # for more information. + def localize(object, **options) + I18n.localize(object, **options) + end + alias :l :localize + + private + MISSING_TRANSLATION = -(2**60) + private_constant :MISSING_TRANSLATION + + NO_DEFAULT = [].freeze + private_constant :NO_DEFAULT + + def scope_key_by_partial(key) + if key&.start_with?(".") + if @virtual_path + @_scope_key_by_partial_cache ||= {} + @_scope_key_by_partial_cache[@virtual_path] ||= @virtual_path.gsub(%r{/_?}, ".") + "#{@_scope_key_by_partial_cache[@virtual_path]}#{key}" + else + raise "Cannot use t(#{key.inspect}) shortcut because path is not available" + end + else + key + end + end + + def missing_translation(key, options) + keys = I18n.normalize_keys(options[:locale] || I18n.locale, key, options[:scope]) + + title = +"translation missing: #{keys.join(".")}" + + options.each do |name, value| + unless name == :scope + title << ", " << name.to_s << ": " << ERB::Util.html_escape(value) + end + end + + if ActionView::Base.debug_missing_translation + content_tag("span", keys.last.to_s.titleize, class: "translation_missing", title: title) + else + title + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/url_helper.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/url_helper.rb new file mode 100644 index 00000000..cfeff968 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/helpers/url_helper.rb @@ -0,0 +1,812 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/access" +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/string/output_safety" +require "action_view/helpers/content_exfiltration_prevention_helper" +require "action_view/helpers/tag_helper" + +module ActionView + module Helpers # :nodoc: + # = Action View URL \Helpers + # + # Provides a set of methods for making links and getting URLs that + # depend on the routing subsystem (see ActionDispatch::Routing). + # This allows you to use the same format for links in views + # and controllers. + module UrlHelper + # This helper may be included in any class that includes the + # URL helpers of a routes (routes.url_helpers). Some methods + # provided here will only work in the context of a request + # (link_to_unless_current, for instance), which must be provided + # as a method called #request on the context. + BUTTON_TAG_METHOD_VERBS = %w{patch put delete} + extend ActiveSupport::Concern + + include TagHelper + include ContentExfiltrationPreventionHelper + + module ClassMethods + def _url_for_modules + ActionView::RoutingUrlFor + end + end + + mattr_accessor :button_to_generates_button_tag, default: false + + # Basic implementation of url_for to allow use helpers without routes existence + def url_for(options = nil) # :nodoc: + case options + when String + options + when :back + _back_url + else + raise ArgumentError, "arguments passed to url_for can't be handled. Please require " \ + "routes or provide your own implementation" + end + end + + def _back_url # :nodoc: + _filtered_referrer || "javascript:history.back()" + end + private :_back_url + + def _filtered_referrer # :nodoc: + if controller.respond_to?(:request) + referrer = controller.request.env["HTTP_REFERER"] + if referrer && URI(referrer).scheme != "javascript" + referrer + end + end + rescue URI::InvalidURIError + end + private :_filtered_referrer + + # Creates an anchor element of the given +name+ using a URL created by the set of +options+. + # See the valid options in the documentation for +url_for+. It's also possible to + # pass a \String instead of an options hash, which generates an anchor element that uses the + # value of the \String as the href for the link. Using a :back \Symbol instead + # of an options hash will generate a link to the referrer (a JavaScript back link + # will be used in place of a referrer if none exists). If +nil+ is passed as the name + # the value of the link itself will become the name. + # + # ==== Signatures + # + # link_to(body, url, html_options = {}) + # # url is a String; you can use URL helpers like + # # posts_path + # + # link_to(body, url_options = {}, html_options = {}) + # # url_options, except :method, is passed to url_for + # + # link_to(options = {}, html_options = {}) do + # # name + # end + # + # link_to(url, html_options = {}) do + # # name + # end + # + # link_to(active_record_model) + # + # ==== Options + # * :data - This option can be used to add custom data attributes. + # + # ==== Examples + # + # Because it relies on +url_for+, +link_to+ supports both older-style controller/action/id arguments + # and newer RESTful routes. Current \Rails style favors RESTful routes whenever possible, so base + # your application on resources and use + # + # link_to "Profile", profile_path(@profile) + # # => Profile + # + # or the even pithier + # + # link_to "Profile", @profile + # # => Profile + # + # in place of the older more verbose, non-resource-oriented + # + # link_to "Profile", controller: "profiles", action: "show", id: @profile + # # => Profile + # + # Similarly, + # + # link_to "Profiles", profiles_path + # # => Profiles + # + # is better than + # + # link_to "Profiles", controller: "profiles" + # # => Profiles + # + # When name is +nil+ the href is presented instead + # + # link_to nil, "http://example.com" + # # => http://www.example.com + # + # More concise yet, when +name+ is an Active Record model that defines a + # +to_s+ method returning a default value or a model instance attribute + # + # link_to @profile + # # => Eileen + # + # You can use a block as well if your link target is hard to fit into the name parameter. ERB example: + # + # <%= link_to(@profile) do %> + # <%= @profile.name %> -- Check it out! + # <% end %> + # # => + # David -- Check it out! + # + # + # Classes and ids for CSS are easy to produce: + # + # link_to "Articles", articles_path, id: "news", class: "article" + # # => Articles + # + # Be careful when using the older argument style, as an extra literal hash is needed: + # + # link_to "Articles", { controller: "articles" }, id: "news", class: "article" + # # => Articles + # + # Leaving the hash off gives the wrong link: + # + # link_to "WRONG!", controller: "articles", id: "news", class: "article" + # # => WRONG! + # + # +link_to+ can also produce links with anchors or query strings: + # + # link_to "Comment wall", profile_path(@profile, anchor: "wall") + # # => Comment wall + # + # link_to "Ruby on Rails search", controller: "searches", query: "ruby on rails" + # # => Ruby on Rails search + # + # link_to "Nonsense search", searches_path(foo: "bar", baz: "quux") + # # => Nonsense search + # + # You can set any link attributes such as target, rel, type: + # + # link_to "External link", "http://www.rubyonrails.org/", target: "_blank", rel: "nofollow" + # # => External link + # + # ==== Turbo + # + # Rails 7 ships with Turbo enabled by default. Turbo provides the following +:data+ options: + # + # * turbo_method: symbol of HTTP verb - Performs a Turbo link visit + # with the given HTTP verb. Forms are recommended when performing non-+GET+ requests. + # Only use data-turbo-method where a form is not possible. + # + # * turbo_confirm: "question?" - Adds a confirmation dialog to the link with the + # given value. + # + # {Consult the Turbo Handbook for more information on the options + # above.}[https://turbo.hotwired.dev/handbook/drive#performing-visits-with-a-different-method] + # + # ===== \Examples + # + # link_to "Delete profile", @profile, data: { turbo_method: :delete } + # # => Delete profile + # + # link_to "Visit Other Site", "https://rubyonrails.org/", data: { turbo_confirm: "Are you sure?" } + # # => Visit Other Site + # + def link_to(name = nil, options = nil, html_options = nil, &block) + html_options, options, name = options, name, block if block_given? + options ||= {} + + html_options = convert_options_to_data_attributes(options, html_options) + + url = url_target(name, options) + html_options["href"] ||= url + + content_tag("a", name || url, html_options, &block) + end + + # Generates a form containing a single button that submits to the URL created + # by the set of +options+. This is the safest method to ensure links that + # cause changes to your data are not triggered by search bots or accelerators. + # + # You can control the form and button behavior with +html_options+. Most + # values in +html_options+ are passed through to the button element. For + # example, passing a +:class+ option within +html_options+ will set the + # class attribute of the button element. + # + # The class attribute of the form element can be set by passing a + # +:form_class+ option within +html_options+. It defaults to + # "button_to" to allow styling of the form and its children. + # + # The form submits a POST request by default if the object is not persisted; + # conversely, if the object is persisted, it will submit a PATCH request. + # To specify a different HTTP verb use the +:method+ option within +html_options+. + # + # If the HTML button generated from +button_to+ does not work with your layout, you can + # consider using the +link_to+ method with the +data-turbo-method+ + # attribute as described in the +link_to+ documentation. + # + # ==== Options + # The +options+ hash accepts the same options as +url_for+. To generate a + #
    element without an [action] attribute, pass + # false: + # + # <%= button_to "New", false %> + # # => " + # # + # # + # #
    " + # + # Most values in +html_options+ are passed through to the button element, + # but there are a few special options: + # + # * :method - \Symbol of HTTP verb. Supported verbs are :post, :get, + # :delete, :patch, and :put. By default it will be :post. + # * :disabled - If set to true, it will generate a disabled button. + # * :data - This option can be used to add custom data attributes. + # * :form - This hash will be form attributes + # * :form_class - This controls the class of the form within which the submit button will + # be placed + # * :params - \Hash of parameters to be rendered as hidden fields within the form. + # + # ==== Examples + # <%= button_to "New", action: "new" %> + # # => "
    + # # + # # + # #
    " + # + # <%= button_to "New", new_article_path %> + # # => "
    + # # + # # + # #
    " + # + # <%= button_to "New", new_article_path, params: { time: Time.now } %> + # # => "
    + # # + # # + # # + # #
    " + # + # <%= button_to [:make_happy, @user] do %> + # Make happy <%= @user.name %> + # <% end %> + # # => "
    + # # + # # + # #
    " + # + # <%= button_to "New", { action: "new" }, form_class: "new-thing" %> + # # => "
    + # # + # # + # #
    " + # + # <%= button_to "Create", { action: "create" }, form: { "data-type" => "json" } %> + # # => "
    + # # + # # + # #
    " + # + def button_to(name = nil, options = nil, html_options = nil, &block) + html_options, options = options, name if block_given? + html_options ||= {} + html_options = html_options.stringify_keys + + url = + case options + when FalseClass then nil + else url_for(options) + end + + remote = html_options.delete("remote") + params = html_options.delete("params") + + authenticity_token = html_options.delete("authenticity_token") + + method = (html_options.delete("method").presence || method_for_options(options)).to_s + method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : "".html_safe + + form_method = method == "get" ? "get" : "post" + form_options = html_options.delete("form") || {} + form_options[:class] ||= html_options.delete("form_class") || "button_to" + form_options[:method] = form_method + form_options[:action] = url + form_options[:'data-remote'] = true if remote + + request_token_tag = if form_method == "post" + request_method = method.empty? ? "post" : method + token_tag(authenticity_token, form_options: { action: url, method: request_method }) + else + "" + end + + html_options = convert_options_to_data_attributes(options, html_options) + html_options["type"] = "submit" + + button = if block_given? + content_tag("button", html_options, &block) + elsif button_to_generates_button_tag + content_tag("button", name || url, html_options, &block) + else + html_options["value"] = name || url + tag("input", html_options) + end + + inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag) + if params + to_form_params(params).each do |param| + inner_tags.safe_concat tag(:input, type: "hidden", name: param[:name], value: param[:value], + autocomplete: "off") + end + end + html = content_tag("form", inner_tags, form_options) + prevent_content_exfiltration(html) + end + + # Creates a link tag of the given +name+ using a URL created by the set of + # +options+ unless the current request URI is the same as the links, in + # which case only the name is returned (or the given block is yielded, if + # one exists). You can give +link_to_unless_current+ a block which will + # specialize the default behavior (e.g., show a "Start Here" link rather + # than the link's text). + # + # ==== Examples + # Let's say you have a navigation menu... + # + # + # + # If in the "about" action, it will render... + # + # + # + # ...but if in the "index" action, it will render: + # + # + # + # The implicit block given to +link_to_unless_current+ is evaluated if the current + # action is the action given. So, if we had a comments page and wanted to render a + # "Go Back" link instead of a link to the comments page, we could do something like this... + # + # <%= + # link_to_unless_current("Comment", { controller: "comments", action: "new" }) do + # link_to("Go back", { controller: "posts", action: "index" }) + # end + # %> + def link_to_unless_current(name, options = {}, html_options = {}, &block) + link_to_unless current_page?(options), name, options, html_options, &block + end + + # Creates a link tag of the given +name+ using a URL created by the set of + # +options+ unless +condition+ is true, in which case only the name is + # returned. To specialize the default behavior (i.e., show a login link rather + # than just the plaintext link text), you can pass a block that + # accepts the name or the full argument list for +link_to_unless+. + # + # ==== Examples + # <%= link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) %> + # # If the user is logged in... + # # => Reply + # + # <%= + # link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) do |name| + # link_to(name, { controller: "accounts", action: "signup" }) + # end + # %> + # # If the user is logged in... + # # => Reply + # # If not... + # # => Reply + def link_to_unless(condition, name, options = {}, html_options = {}, &block) + link_to_if !condition, name, options, html_options, &block + end + + # Creates a link tag of the given +name+ using a URL created by the set of + # +options+ if +condition+ is true, otherwise only the name is + # returned. To specialize the default behavior, you can pass a block that + # accepts the name or the full argument list for +link_to_if+. + # + # ==== Examples + # <%= link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) %> + # # If the user isn't logged in... + # # => Login + # + # <%= + # link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) do + # link_to(@current_user.login, { controller: "accounts", action: "show", id: @current_user }) + # end + # %> + # # If the user isn't logged in... + # # => Login + # # If they are logged in... + # # => my_username + def link_to_if(condition, name, options = {}, html_options = {}, &block) + if condition + link_to(name, options, html_options) + else + if block_given? + block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block) + else + ERB::Util.html_escape(name) + end + end + end + + # Creates a mailto link tag to the specified +email_address+, which is + # also used as the name of the link unless +name+ is specified. Additional + # HTML attributes for the link can be passed in +html_options+. + # + # +mail_to+ has several methods for customizing the email itself by + # passing special keys to +html_options+. + # + # ==== Options + # * :subject - Preset the subject line of the email. + # * :body - Preset the body of the email. + # * :cc - Carbon Copy additional recipients on the email. + # * :bcc - Blind Carbon Copy additional recipients on the email. + # * :reply_to - Preset the +Reply-To+ field of the email. + # + # ==== Obfuscation + # Prior to \Rails 4.0, +mail_to+ provided options for encoding the address + # in order to hinder email harvesters. To take advantage of these options, + # install the +actionview-encoded_mail_to+ gem. + # + # ==== Examples + # mail_to "me@domain.com" + # # => me@domain.com + # + # mail_to "me@domain.com", "My email" + # # => My email + # + # mail_to "me@domain.com", cc: "ccaddress@domain.com", + # subject: "This is an example email" + # # => me@domain.com + # + # You can use a block as well if your link target is hard to fit into the name parameter. ERB example: + # + # <%= mail_to "me@domain.com" do %> + # Email me: me@domain.com + # <% end %> + # # => + # Email me: me@domain.com + # + def mail_to(email_address, name = nil, html_options = {}, &block) + html_options, name = name, nil if name.is_a?(Hash) + html_options = (html_options || {}).stringify_keys + + extras = %w{ cc bcc body subject reply_to }.map! { |item| + option = html_options.delete(item).presence || next + "#{item.dasherize}=#{ERB::Util.url_encode(option)}" + }.compact + extras = extras.empty? ? "" : "?" + extras.join("&") + + encoded_email_address = ERB::Util.url_encode(email_address).gsub("%40", "@") + html_options["href"] = "mailto:#{encoded_email_address}#{extras}" + + content_tag("a", name || email_address, html_options, &block) + end + + # True if the current request URI was generated by the given +options+. + # + # ==== Examples + # Let's say we're in the http://www.example.com/shop/checkout?order=desc&page=1 action. + # + # current_page?(action: 'process') + # # => false + # + # current_page?(action: 'checkout') + # # => true + # + # current_page?(controller: 'library', action: 'checkout') + # # => false + # + # current_page?(controller: 'shop', action: 'checkout') + # # => true + # + # current_page?(controller: 'shop', action: 'checkout', order: 'asc') + # # => false + # + # current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '1') + # # => true + # + # current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '2') + # # => false + # + # current_page?('http://www.example.com/shop/checkout') + # # => true + # + # current_page?('http://www.example.com/shop/checkout', check_parameters: true) + # # => false + # + # current_page?('/shop/checkout') + # # => true + # + # current_page?('http://www.example.com/shop/checkout?order=desc&page=1') + # # => true + # + # Let's say we're in the http://www.example.com/products action with method POST in case of invalid product. + # + # current_page?(controller: 'product', action: 'index') + # # => false + # + # We can also pass in the symbol arguments instead of strings. + # + def current_page?(options = nil, check_parameters: false, **options_as_kwargs) + unless request + raise "You cannot use helpers that need to determine the current " \ + "page unless your view context provides a Request object " \ + "in a #request method" + end + + return false unless request.get? || request.head? + + options ||= options_as_kwargs + check_parameters ||= options.is_a?(Hash) && options.delete(:check_parameters) + url_string = URI::RFC2396_PARSER.unescape(url_for(options)).force_encoding(Encoding::BINARY) + + # We ignore any extra parameters in the request_uri if the + # submitted URL doesn't have any either. This lets the function + # work with things like ?order=asc + # the behavior can be disabled with check_parameters: true + request_uri = url_string.index("?") || check_parameters ? request.fullpath : request.path + request_uri = URI::RFC2396_PARSER.unescape(request_uri).force_encoding(Encoding::BINARY) + + if %r{^\w+://}.match?(url_string) + request_uri = +"#{request.protocol}#{request.host_with_port}#{request_uri}" + end + + remove_trailing_slash!(url_string) + remove_trailing_slash!(request_uri) + + url_string == request_uri + end + + # Creates an SMS anchor link tag to the specified +phone_number+. When the + # link is clicked, the default SMS messaging app is opened ready to send a + # message to the linked phone number. If the +body+ option is specified, + # the contents of the message will be preset to +body+. + # + # If +name+ is not specified, +phone_number+ will be used as the name of + # the link. + # + # A +country_code+ option is supported, which prepends a plus sign and the + # given country code to the linked phone number. For example, + # country_code: "01" will prepend +01 to the linked + # phone number. + # + # Additional HTML attributes for the link can be passed via +html_options+. + # + # ==== Options + # * :country_code - Prepend the country code to the phone number. + # * :body - Preset the body of the message. + # + # ==== Examples + # sms_to "5155555785" + # # => 5155555785 + # + # sms_to "5155555785", country_code: "01" + # # => 5155555785 + # + # sms_to "5155555785", "Text me" + # # => Text me + # + # sms_to "5155555785", body: "I have a question about your product." + # # => 5155555785 + # + # You can use a block as well if your link target is hard to fit into the name parameter. \ERB example: + # + # <%= sms_to "5155555785" do %> + # Text me: + # <% end %> + # # => + # Text me: + # + def sms_to(phone_number, name = nil, html_options = {}, &block) + html_options, name = name, nil if name.is_a?(Hash) + html_options = (html_options || {}).stringify_keys + + country_code = html_options.delete("country_code").presence + country_code = country_code ? "+#{ERB::Util.url_encode(country_code)}" : "" + + body = html_options.delete("body").presence + body = body ? "?&body=#{ERB::Util.url_encode(body)}" : "" + + encoded_phone_number = ERB::Util.url_encode(phone_number) + html_options["href"] = "sms:#{country_code}#{encoded_phone_number};#{body}" + + content_tag("a", name || phone_number, html_options, &block) + end + + # Creates a TEL anchor link tag to the specified +phone_number+. When the + # link is clicked, the default app to make phone calls is opened and + # prepopulated with the phone number. + # + # If +name+ is not specified, +phone_number+ will be used as the name of + # the link. + # + # A +country_code+ option is supported, which prepends a plus sign and the + # given country code to the linked phone number. For example, + # country_code: "01" will prepend +01 to the linked + # phone number. + # + # Additional HTML attributes for the link can be passed via +html_options+. + # + # ==== Options + # * :country_code - Prepends the country code to the phone number + # + # ==== Examples + # phone_to "1234567890" + # # => 1234567890 + # + # phone_to "1234567890", "Phone me" + # # => Phone me + # + # phone_to "1234567890", country_code: "01" + # # => 1234567890 + # + # You can use a block as well if your link target is hard to fit into the name parameter. \ERB example: + # + # <%= phone_to "1234567890" do %> + # Phone me: + # <% end %> + # # => + # Phone me: + # + def phone_to(phone_number, name = nil, html_options = {}, &block) + html_options, name = name, nil if name.is_a?(Hash) + html_options = (html_options || {}).stringify_keys + + country_code = html_options.delete("country_code").presence + country_code = country_code.nil? ? "" : "+#{ERB::Util.url_encode(country_code)}" + + encoded_phone_number = ERB::Util.url_encode(phone_number) + html_options["href"] = "tel:#{country_code}#{encoded_phone_number}" + + content_tag("a", name || phone_number, html_options, &block) + end + + private + def convert_options_to_data_attributes(options, html_options) + if html_options + html_options = html_options.stringify_keys + html_options["data-remote"] = "true" if link_to_remote_options?(options) || link_to_remote_options?(html_options) + + method = html_options.delete("method") + + add_method_to_attributes!(html_options, method) if method + + html_options + else + link_to_remote_options?(options) ? { "data-remote" => "true" } : {} + end + end + + def url_target(name, options) + if name.respond_to?(:model_name) && options.is_a?(Hash) && options.empty? + url_for(name) + else + url_for(options) + end + end + + def link_to_remote_options?(options) + if options.is_a?(Hash) + options.delete("remote") || options.delete(:remote) + end + end + + def add_method_to_attributes!(html_options, method) + if method_not_get_method?(method) && !html_options["rel"].to_s.include?("nofollow") + if html_options["rel"].blank? + html_options["rel"] = "nofollow" + else + html_options["rel"] = "#{html_options["rel"]} nofollow" + end + end + html_options["data-method"] = method + end + + def method_for_options(options) + if options.is_a?(Array) + method_for_options(options.last) + elsif options.respond_to?(:persisted?) + options.persisted? ? :patch : :post + elsif options.respond_to?(:to_model) + method_for_options(options.to_model) + end + end + + STRINGIFIED_COMMON_METHODS = { + get: "get", + delete: "delete", + patch: "patch", + post: "post", + put: "put", + }.freeze + + def method_not_get_method?(method) + return false unless method + (STRINGIFIED_COMMON_METHODS[method] || method.to_s.downcase) != "get" + end + + def token_tag(token = nil, form_options: {}) + if token != false && defined?(protect_against_forgery?) && protect_against_forgery? + token = + if token == true || token.nil? + form_authenticity_token(form_options: form_options.merge(authenticity_token: token)) + else + token + end + tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token, autocomplete: "off") + else + "" + end + end + + def method_tag(method) + tag("input", type: "hidden", name: "_method", value: method.to_s, autocomplete: "off") + end + + # Returns an array of hashes each containing :name and :value keys + # suitable for use as the names and values of form input fields: + # + # to_form_params(name: 'David', nationality: 'Danish') + # # => [{name: 'name', value: 'David'}, {name: 'nationality', value: 'Danish'}] + # + # to_form_params(country: { name: 'Denmark' }) + # # => [{name: 'country[name]', value: 'Denmark'}] + # + # to_form_params(countries: ['Denmark', 'Sweden']}) + # # => [{name: 'countries[]', value: 'Denmark'}, {name: 'countries[]', value: 'Sweden'}] + # + # An optional namespace can be passed to enclose key names: + # + # to_form_params({ name: 'Denmark' }, 'country') + # # => [{name: 'country[name]', value: 'Denmark'}] + def to_form_params(attribute, namespace = nil) + attribute = if attribute.respond_to?(:permitted?) + attribute.to_h + else + attribute + end + + params = [] + case attribute + when Hash + attribute.each do |key, value| + prefix = namespace ? "#{namespace}[#{key}]" : key + params.push(*to_form_params(value, prefix)) + end + when Array + array_prefix = "#{namespace}[]" + attribute.each do |value| + params.push(*to_form_params(value, array_prefix)) + end + else + params << { name: namespace.to_s, value: attribute.to_param } + end + + params.sort_by { |pair| pair[:name] } + end + + def remove_trailing_slash!(url_string) + trailing_index = (url_string.index("?") || 0) - 1 + url_string[trailing_index] = "" if url_string[trailing_index] == "/" + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/layouts.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/layouts.rb new file mode 100644 index 00000000..6f1d0e88 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/layouts.rb @@ -0,0 +1,434 @@ +# frozen_string_literal: true + +require "action_view/rendering" +require "active_support/core_ext/module/redefine_method" + +module ActionView + # = Action View \Layouts + # + # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in + # repeated setups. The inclusion pattern has pages that look like this: + # + # <%= render "application/header" %> + # Hello World + # <%= render "application/footer" %> + # + # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose + # and if you ever want to change the structure of these two includes, you'll have to change all the templates. + # + # With layouts, you can flip it around and have the common structure know where to insert changing content. This means + # that the header and footer are only mentioned in one place, like this: + # + # // The header part of this layout + # <%= yield %> + # // The footer part of this layout + # + # And then you have content pages that look like this: + # + # hello world + # + # At rendering time, the content page is computed and then inserted in the layout, like this: + # + # // The header part of this layout + # hello world + # // The footer part of this layout + # + # == Accessing shared variables + # + # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with + # references that won't materialize before rendering time: + # + #

    <%= @page_title %>

    + # <%= yield %> + # + # ...and content pages that fulfill these references _at_ rendering time: + # + # <% @page_title = "Welcome" %> + # Off-world colonies offers you a chance to start a new life + # + # The result after rendering is: + # + #

    Welcome

    + # Off-world colonies offers you a chance to start a new life + # + # == Layout assignment + # + # You can either specify a layout declaratively (using the #layout class method) or give + # it the same name as your controller, and place it in app/views/layouts. + # If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance. + # + # For instance, if you have PostsController and a template named app/views/layouts/posts.html.erb, + # that template will be used for all actions in PostsController and controllers inheriting + # from PostsController. + # + # If you use a module, for instance Weblog::PostsController, you will need a template named + # app/views/layouts/weblog/posts.html.erb. + # + # Since all your controllers inherit from ApplicationController, they will use + # app/views/layouts/application.html.erb if no other layout is specified + # or provided. + # + # == Inheritance Examples + # + # class BankController < ActionController::Base + # # bank.html.erb exists + # + # class ExchangeController < BankController + # # exchange.html.erb exists + # + # class CurrencyController < BankController + # + # class InformationController < BankController + # layout "information" + # + # class TellerController < InformationController + # # teller.html.erb exists + # + # class EmployeeController < InformationController + # # employee.html.erb exists + # layout nil + # + # class VaultController < BankController + # layout :access_level_layout + # + # class TillController < BankController + # layout false + # + # In these examples, we have three implicit lookup scenarios: + # * The +BankController+ uses the "bank" layout. + # * The +ExchangeController+ uses the "exchange" layout. + # * The +CurrencyController+ inherits the layout from BankController. + # + # However, when a layout is explicitly set, the explicitly set layout wins: + # * The +InformationController+ uses the "information" layout, explicitly set. + # * The +TellerController+ also uses the "information" layout, because the parent explicitly set it. + # * The +EmployeeController+ uses the "employee" layout, because it set the layout to +nil+, resetting the parent configuration. + # * The +VaultController+ chooses a layout dynamically by calling the access_level_layout method. + # * The +TillController+ does not use a layout at all. + # + # == Types of layouts + # + # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes + # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can + # be done either by specifying a method reference as a symbol or using an inline method (as a proc). + # + # The method reference is the preferred approach to variable layouts and is used like this: + # + # class WeblogController < ActionController::Base + # layout :writers_and_readers + # + # def index + # # fetching posts + # end + # + # private + # def writers_and_readers + # logged_in? ? "writer_layout" : "reader_layout" + # end + # end + # + # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing + # is logged in or not. + # + # If you want to use an inline method, such as a proc, do something like this: + # + # class WeblogController < ActionController::Base + # layout proc { |controller| controller.logged_in? ? "writer_layout" : "reader_layout" } + # end + # + # If an argument isn't given to the proc, it's evaluated in the context of + # the current controller anyway. + # + # class WeblogController < ActionController::Base + # layout proc { logged_in? ? "writer_layout" : "reader_layout" } + # end + # + # Of course, the most common way of specifying a layout is still just as a plain template name: + # + # class WeblogController < ActionController::Base + # layout "weblog_standard" + # end + # + # The template will be looked always in app/views/layouts/ folder. But you can point + # layouts folder direct also. layout "layouts/demo" is the same as layout "demo". + # + # Setting the layout to +nil+ forces it to be looked up in the filesystem and falls back to the parent behavior if none exists. + # Setting it to +nil+ is useful to re-enable template lookup overriding a previous configuration set in the parent: + # + # class ApplicationController < ActionController::Base + # layout "application" + # end + # + # class PostsController < ApplicationController + # # Will use "application" layout + # end + # + # class CommentsController < ApplicationController + # # Will search for "comments" layout and fall back to "application" layout + # layout nil + # end + # + # == Conditional layouts + # + # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering + # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The + # :only and :except options can be passed to the layout call. For example: + # + # class WeblogController < ActionController::Base + # layout "weblog_standard", except: :rss + # + # # ... + # + # end + # + # This will assign "weblog_standard" as the WeblogController's layout for all actions except for the +rss+ action, which will + # be rendered directly, without wrapping a layout around the rendered view. + # + # Both the :only and :except condition can accept an arbitrary number of method references, so + # except: [ :rss, :text_only ] is valid, as is except: :rss. + # + # == Using a different layout in the action render call + # + # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above. + # Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller. + # You can do this by passing a :layout option to the render call. For example: + # + # class WeblogController < ActionController::Base + # layout "weblog_standard" + # + # def help + # render action: "help", layout: "help" + # end + # end + # + # This will override the controller-wide "weblog_standard" layout, and will render the help action with the "help" layout instead. + module Layouts + extend ActiveSupport::Concern + + include ActionView::Rendering + + included do + class_attribute :_layout, instance_accessor: false + class_attribute :_layout_conditions, instance_accessor: false, instance_reader: true, default: {} + + _write_layout_method + end + + module ClassMethods + def inherited(klass) # :nodoc: + super + klass._write_layout_method + end + + # This module is mixed in if layout conditions are provided. This means + # that if no layout conditions are used, this method is not used + module LayoutConditions # :nodoc: + private + # Determines whether the current action has a layout definition by + # checking the action name against the :only and :except conditions + # set by the layout method. + # + # ==== Returns + # * Boolean - True if the action has a layout definition, false otherwise. + def _conditional_layout? + return unless super + + conditions = _layout_conditions + + if only = conditions[:only] + only.include?(action_name) + elsif except = conditions[:except] + !except.include?(action_name) + else + true + end + end + end + + # Specify the layout to use for this class. + # + # If the specified layout is a: + # String:: the String is the template name + # Symbol:: call the method specified by the symbol + # Proc:: call the passed Proc + # false:: There is no layout + # true:: raise an ArgumentError + # nil:: Force default layout behavior with inheritance + # + # Return value of +Proc+ and +Symbol+ arguments should be +String+, +false+, +true+, or +nil+ + # with the same meaning as described above. + # + # ==== Parameters + # + # * layout - The layout to use. + # + # ==== Options (conditions) + # + # * +:only+ - A list of actions to apply this layout to. + # * +:except+ - Apply this layout to all actions but this one. + def layout(layout, conditions = {}) + include LayoutConditions unless conditions.empty? + + conditions.each { |k, v| conditions[k] = Array(v).map(&:to_s) } + self._layout_conditions = conditions + + self._layout = layout + _write_layout_method + end + + # Creates a _layout method to be called by _default_layout . + # + # If a layout is not explicitly mentioned then look for a layout with the controller's name. + # if nothing is found then try same procedure to find super class's layout. + def _write_layout_method # :nodoc: + silence_redefinition_of_method(:_layout) + + prefixes = /\blayouts/.match?(_implied_layout_name) ? [] : ["layouts"] + default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}, false, keys, { formats: formats }).first || super" + name_clause = if name + default_behavior + else + <<-RUBY + super + RUBY + end + + layout_definition = \ + case _layout + when String + _layout.inspect + when Symbol + <<-RUBY + #{_layout}.tap do |layout| + return #{default_behavior} if layout.nil? + unless layout.is_a?(String) || !layout + raise ArgumentError, "Your layout method :#{_layout} returned \#{layout}. It " \ + "should have returned a String, false, or nil" + end + end + RUBY + when Proc + define_method :_layout_from_proc, &_layout + private :_layout_from_proc + <<-RUBY + result = _layout_from_proc(#{_layout.arity == 0 ? '' : 'self'}) + return #{default_behavior} if result.nil? + result + RUBY + when false + nil + when true + raise ArgumentError, "Layouts must be specified as a String, Symbol, Proc, false, or nil" + when nil + name_clause + end + + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + # frozen_string_literal: true + def _layout(lookup_context, formats, keys) + if _conditional_layout? + #{layout_definition} + else + #{name_clause} + end + end + private :_layout + RUBY + end + + private + # If no layout is supplied, look for a template named the return + # value of this method. + # + # ==== Returns + # * String - A template name + def _implied_layout_name + controller_path + end + end + + def _process_render_template_options(options) # :nodoc: + super + + if _include_layout?(options) + layout = options.delete(:layout) { :default } + options[:layout] = _layout_for_option(layout) + end + end + + attr_internal_writer :action_has_layout + + def initialize(*) # :nodoc: + @_action_has_layout = true + super + end + + # Controls whether an action should be rendered using a layout. + # If you want to disable any layout settings for the + # current action so that it is rendered without a layout then + # either override this method in your controller to return false + # for that action or set the action_has_layout attribute + # to false before rendering. + def action_has_layout? + @_action_has_layout + end + + private + def _conditional_layout? + true + end + + # This will be overwritten by _write_layout_method + def _layout(*); end + + # Determine the layout for a given name, taking into account the name type. + # + # ==== Parameters + # * name - The name of the template + def _layout_for_option(name) + case name + when String then _normalize_layout(name) + when Proc then name + when true then Proc.new { |lookup_context, formats, keys| _default_layout(lookup_context, formats, keys, true) } + when :default then Proc.new { |lookup_context, formats, keys| _default_layout(lookup_context, formats, keys, false) } + when false, nil then nil + else + raise ArgumentError, + "String, Proc, :default, true, or false, expected for `layout'; you passed #{name.inspect}" + end + end + + def _normalize_layout(value) + value.is_a?(String) && !value.match?(/\blayouts/) ? "layouts/#{value}" : value + end + + # Returns the default layout for this controller. + # Optionally raises an exception if the layout could not be found. + # + # ==== Parameters + # * formats - The formats accepted to this layout + # * require_layout - If set to +true+ and layout is not found, + # an +ArgumentError+ exception is raised (defaults to +false+) + # + # ==== Returns + # * template - The template object for the default layout (or +nil+) + def _default_layout(lookup_context, formats, keys, require_layout = false) + begin + value = _layout(lookup_context, formats, keys) if action_has_layout? + rescue NameError => e + raise e, "Could not render layout: #{e.message}" + end + + if require_layout && action_has_layout? && !value + raise ArgumentError, + "There was no default layout for #{self.class} in #{view_paths.inspect}" + end + + _normalize_layout(value) + end + + def _include_layout?(options) + !options.keys.intersect?([:body, :plain, :html, :inline, :partial]) || options.key?(:layout) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/locale/en.yml b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/locale/en.yml new file mode 100644 index 00000000..8a56f147 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/locale/en.yml @@ -0,0 +1,56 @@ +"en": + # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() + datetime: + distance_in_words: + half_a_minute: "half a minute" + less_than_x_seconds: + one: "less than 1 second" + other: "less than %{count} seconds" + x_seconds: + one: "1 second" + other: "%{count} seconds" + less_than_x_minutes: + one: "less than a minute" + other: "less than %{count} minutes" + x_minutes: + one: "1 minute" + other: "%{count} minutes" + about_x_hours: + one: "about 1 hour" + other: "about %{count} hours" + x_days: + one: "1 day" + other: "%{count} days" + about_x_months: + one: "about 1 month" + other: "about %{count} months" + x_months: + one: "1 month" + other: "%{count} months" + about_x_years: + one: "about 1 year" + other: "about %{count} years" + over_x_years: + one: "over 1 year" + other: "over %{count} years" + almost_x_years: + one: "almost 1 year" + other: "almost %{count} years" + prompts: + year: "Year" + month: "Month" + day: "Day" + hour: "Hour" + minute: "Minute" + second: "Seconds" + + helpers: + select: + # Default value for :prompt => true in FormOptionsHelper + prompt: "Please select" + + # Default translation keys for submit and button FormHelper + submit: + create: 'Create %{model}' + update: 'Update %{model}' + submit: 'Save %{model}' diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/log_subscriber.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/log_subscriber.rb new file mode 100644 index 00000000..3582aee4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/log_subscriber.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +require "active_support/log_subscriber" + +module ActionView + # = Action View Log Subscriber + # + # Provides functionality so that \Rails can output logs from Action View. + class LogSubscriber < ActiveSupport::LogSubscriber + VIEWS_PATTERN = /^app\/views\// + + def initialize + @root = nil + super + end + + def render_template(event) + info do + message = +" Rendered #{from_rails_root(event.payload[:identifier])}" + message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout] + message << " (Duration: #{event.duration.round(1)}ms | GC: #{event.gc_time.round(1)}ms)" + end + end + subscribe_log_level :render_template, :debug + + def render_partial(event) + debug do + message = +" Rendered #{from_rails_root(event.payload[:identifier])}" + message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout] + message << " (Duration: #{event.duration.round(1)}ms | GC: #{event.gc_time.round(1)}ms)" + message << " #{cache_message(event.payload)}" unless event.payload[:cache_hit].nil? + message + end + end + subscribe_log_level :render_partial, :debug + + def render_layout(event) + info do + message = +" Rendered layout #{from_rails_root(event.payload[:identifier])}" + message << " (Duration: #{event.duration.round(1)}ms | GC: #{event.gc_time.round(1)}ms)" + end + end + subscribe_log_level :render_layout, :info + + def render_collection(event) + identifier = event.payload[:identifier] || "templates" + + debug do + message = +" Rendered collection of #{from_rails_root(identifier)}" + message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout] + message << " #{render_count(event.payload)} (Duration: #{event.duration.round(1)}ms | GC: #{event.gc_time.round(1)}ms)" + message + end + end + subscribe_log_level :render_collection, :debug + + module Utils # :nodoc: + def logger + ActionView::Base.logger + end + + private + def from_rails_root(string) + string = string.sub(rails_root, "") + string.sub!(VIEWS_PATTERN, "") + string + end + + def rails_root # :doc: + @root ||= "#{Rails.root}/" + end + end + + include Utils + + class Start # :nodoc: + include Utils + + def start(name, id, payload) + return unless logger + logger.debug do + qualifier = + if name == "render_template.action_view" + "" + elsif name == "render_layout.action_view" + "layout " + end + + return unless qualifier + + message = +" Rendering #{qualifier}#{from_rails_root(payload[:identifier])}" + message << " within #{from_rails_root(payload[:layout])}" if payload[:layout] + message + end + end + + def finish(name, id, payload) + end + + def silenced?(_) + logger.nil? || !logger.debug? + end + end + + def self.attach_to(*) + ActiveSupport::Notifications.subscribe("render_template.action_view", ActionView::LogSubscriber::Start.new) + ActiveSupport::Notifications.subscribe("render_layout.action_view", ActionView::LogSubscriber::Start.new) + + super + end + + private + def render_count(payload) # :doc: + if payload[:cache_hits] + "[#{payload[:cache_hits]} / #{payload[:count]} cache hits]" + else + "[#{payload[:count]} times]" + end + end + + def cache_message(payload) # :doc: + case payload[:cache_hit] + when :hit + "[cache hit]" + when :miss + "[cache miss]" + end + end + end +end + +ActionView::LogSubscriber.attach_to :action_view diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/lookup_context.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/lookup_context.rb new file mode 100644 index 00000000..04e0207a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/lookup_context.rb @@ -0,0 +1,299 @@ +# frozen_string_literal: true + +require "concurrent/map" +require "active_support/core_ext/module/attribute_accessors" +require "action_view/template/resolver" + +module ActionView + # = Action View Lookup Context + # + # LookupContext is the object responsible for holding all information + # required for looking up templates, i.e. view paths and details. + # LookupContext is also responsible for generating a key, given to + # view paths, used in the resolver cache lookup. Since this key is generated + # only once during the request, it speeds up all cache accesses. + class LookupContext # :nodoc: + attr_accessor :prefixes + + singleton_class.attr_accessor :registered_details + self.registered_details = [] + + def self.register_detail(name, &block) + registered_details << name + Accessors::DEFAULT_PROCS[name] = block + + Accessors.define_method(:"default_#{name}", &block) + Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1 + def #{name} + @details[:#{name}] || [] + end + + def #{name}=(value) + value = value.present? ? Array(value) : default_#{name} + _set_detail(:#{name}, value) if value != @details[:#{name}] + end + METHOD + end + + # Holds accessors for the registered details. + module Accessors # :nodoc: + DEFAULT_PROCS = {} + end + + register_detail(:locale) do + locales = [I18n.locale] + locales.concat(I18n.fallbacks[I18n.locale]) if I18n.respond_to? :fallbacks + locales << I18n.default_locale + locales.uniq! + locales + end + register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] } + register_detail(:variants) { [] } + register_detail(:handlers) { Template::Handlers.extensions } + + class DetailsKey # :nodoc: + alias :eql? :equal? + + @details_keys = Concurrent::Map.new + @digest_cache = Concurrent::Map.new + @view_context_mutex = Mutex.new + + def self.digest_cache(details) + @digest_cache[details_cache_key(details)] ||= Concurrent::Map.new + end + + def self.details_cache_key(details) + @details_keys.fetch(details) do + if formats = details[:formats] + unless Template::Types.valid_symbols?(formats) + details = details.dup + details[:formats] &= Template::Types.symbols + end + end + @details_keys[details] ||= TemplateDetails::Requested.new(**details) + end + end + + def self.clear + ActionView::PathRegistry.all_resolvers.each do |resolver| + resolver.clear_cache + end + @view_context_class = nil + @details_keys.clear + @digest_cache.clear + end + + def self.digest_caches + @digest_cache.values + end + + def self.view_context_class + @view_context_mutex.synchronize do + @view_context_class ||= ActionView::Base.with_empty_template_cache + end + end + end + + # Add caching behavior on top of Details. + module DetailsCache + attr_accessor :cache + + # Calculate the details key. Remove the handlers from calculation to improve performance + # since the user cannot modify it explicitly. + def details_key # :nodoc: + @details_key ||= DetailsKey.details_cache_key(@details) if @cache + end + + # Temporary skip passing the details_key forward. + def disable_cache + old_value, @cache = @cache, false + yield + ensure + @cache = old_value + end + + private + def _set_detail(key, value) # :doc: + @details = @details.dup if @digest_cache || @details_key + @digest_cache = nil + @details_key = nil + @details[key] = value + end + end + + # Helpers related to template lookup using the lookup context information. + module ViewPaths + attr_reader :view_paths, :html_fallback_for_js + + def find(name, prefixes = [], partial = false, keys = [], options = {}) + name, prefixes = normalize_name(name, prefixes) + details, details_key = detail_args_for(options) + @view_paths.find(name, prefixes, partial, details, details_key, keys) + end + alias :find_template :find + + def find_all(name, prefixes = [], partial = false, keys = [], options = {}) + name, prefixes = normalize_name(name, prefixes) + details, details_key = detail_args_for(options) + @view_paths.find_all(name, prefixes, partial, details, details_key, keys) + end + + def exists?(name, prefixes = [], partial = false, keys = [], **options) + name, prefixes = normalize_name(name, prefixes) + details, details_key = detail_args_for(options) + @view_paths.exists?(name, prefixes, partial, details, details_key, keys) + end + alias :template_exists? :exists? + + def any?(name, prefixes = [], partial = false) + name, prefixes = normalize_name(name, prefixes) + details, details_key = detail_args_for_any + @view_paths.exists?(name, prefixes, partial, details, details_key, []) + end + alias :any_templates? :any? + + def append_view_paths(paths) + @view_paths = build_view_paths(@view_paths.to_a + paths) + end + + def prepend_view_paths(paths) + @view_paths = build_view_paths(paths + @view_paths.to_a) + end + + private + # Whenever setting view paths, makes a copy so that we can manipulate them in + # instance objects as we wish. + def build_view_paths(paths) + if ActionView::PathSet === paths + paths + else + ActionView::PathSet.new(Array(paths)) + end + end + + # Compute details hash and key according to user options (e.g. passed from #render). + def detail_args_for(options) # :doc: + return @details, details_key if options.empty? # most common path. + user_details = @details.merge(options) + + if @cache + details_key = DetailsKey.details_cache_key(user_details) + else + details_key = nil + end + + [user_details, details_key] + end + + def detail_args_for_any + @detail_args_for_any ||= begin + details = {} + + LookupContext.registered_details.each do |k| + if k == :variants + details[k] = :any + else + details[k] = Accessors::DEFAULT_PROCS[k].call + end + end + + if @cache + [details, DetailsKey.details_cache_key(details)] + else + [details, nil] + end + end + end + + # Fix when prefix is specified as part of the template name + def normalize_name(name, prefixes) + name = name.to_s + idx = name.rindex("/") + return name, prefixes.presence || [""] unless idx + + path_prefix = name[0, idx] + path_prefix = path_prefix.from(1) if path_prefix.start_with?("/") + name = name.from(idx + 1) + + if !prefixes || prefixes.empty? + prefixes = [path_prefix] + else + prefixes = prefixes.map { |p| "#{p}/#{path_prefix}" } + end + + return name, prefixes + end + end + + include Accessors + include DetailsCache + include ViewPaths + + def initialize(view_paths, details = {}, prefixes = []) + @details_key = nil + @digest_cache = nil + @cache = true + @prefixes = prefixes + + @details = initialize_details({}, details) + @view_paths = build_view_paths(view_paths) + end + + def digest_cache + @digest_cache ||= DetailsKey.digest_cache(@details) + end + + def with_prepended_formats(formats) + details = @details.dup + details[:formats] = formats + + self.class.new(@view_paths, details, @prefixes) + end + + def initialize_details(target, details) + LookupContext.registered_details.each do |k| + target[k] = details[k] || Accessors::DEFAULT_PROCS[k].call + end + target + end + private :initialize_details + + # Override formats= to expand ["*/*"] values and automatically + # add :html as fallback to :js. + def formats=(values) + if values + values = values.dup + values.concat(default_formats) if values.delete "*/*" + values.uniq! + + unless Template::Types.valid_symbols?(values) + invalid_values = values - Template::Types.symbols + raise ArgumentError, "Invalid formats: #{invalid_values.map(&:inspect).join(", ")}" + end + + if (values.length == 1) && (values[0] == :js) + values << :html + @html_fallback_for_js = true + end + end + super(values) + end + + # Override locale to return a symbol instead of array. + def locale + @details[:locale].first + end + + # Overload locale= to also set the I18n.locale. If the current I18n.config object responds + # to original_config, it means that it has a copy of the original I18n configuration and it's + # acting as proxy, which we need to skip. + def locale=(value) + if value + config = I18n.config.respond_to?(:original_config) ? I18n.config.original_config : I18n.config + config.locale = value + end + + super(default_locale) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/model_naming.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/model_naming.rb new file mode 100644 index 00000000..76d5b3e3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/model_naming.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module ActionView + module ModelNaming # :nodoc: + # Converts the given object to an Active Model compliant one. + def convert_to_model(object) + object.respond_to?(:to_model) ? object.to_model : object + end + + def model_name_from_record_or_class(record_or_class) + convert_to_model(record_or_class).model_name + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/path_registry.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/path_registry.rb new file mode 100644 index 00000000..bcabb56e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/path_registry.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module ActionView # :nodoc: + module PathRegistry # :nodoc: + @view_paths_by_class = {} + @file_system_resolvers = {} + @file_system_resolver_mutex = Mutex.new + @file_system_resolver_hooks = [] + + class << self + attr_reader :file_system_resolver_hooks + end + + def self.get_view_paths(klass) + @view_paths_by_class[klass] || get_view_paths(klass.superclass) + end + + def self.set_view_paths(klass, paths) + @view_paths_by_class[klass] = paths + end + + def self.cast_file_system_resolvers(paths) + paths = Array(paths) + + @file_system_resolver_mutex.synchronize do + built_resolver = false + paths = paths.map do |path| + case path + when String, Pathname + path = File.expand_path(path) + @file_system_resolvers[path] ||= + begin + built_resolver = true + FileSystemResolver.new(path) + end + else + path + end + end + + file_system_resolver_hooks.each(&:call) if built_resolver + end + + paths + end + + def self.all_resolvers + resolvers = [all_file_system_resolvers] + resolvers.concat @view_paths_by_class.values.map(&:to_a) + resolvers.flatten.uniq + end + + def self.all_file_system_resolvers + @file_system_resolvers.values + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/path_set.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/path_set.rb new file mode 100644 index 00000000..b6e6deb4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/path_set.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +module ActionView # :nodoc: + # = Action View PathSet + # + # This class is used to store and access paths in Action View. A number of + # operations are defined so that you can search among the paths in this + # set and also perform operations on other +PathSet+ objects. + # + # A +LookupContext+ will use a +PathSet+ to store the paths in its context. + class PathSet # :nodoc: + include Enumerable + + attr_reader :paths + + delegate :[], :include?, :size, :each, to: :paths + + def initialize(paths = []) + @paths = typecast(paths).freeze + end + + def initialize_copy(other) + @paths = other.paths.dup.freeze + self + end + + def to_ary + paths.dup + end + + def compact + PathSet.new paths.compact + end + + def +(other) + array = Array === other ? other : other.paths + PathSet.new(paths + array) + end + + def find(path, prefixes, partial, details, details_key, locals) + find_all(path, prefixes, partial, details, details_key, locals).first || + raise(MissingTemplate.new(self, path, prefixes, partial, details, details_key, locals)) + end + + def find_all(path, prefixes, partial, details, details_key, locals) + search_combinations(prefixes) do |resolver, prefix| + templates = resolver.find_all(path, prefix, partial, details, details_key, locals) + return templates unless templates.empty? + end + [] + end + + def exists?(path, prefixes, partial, details, details_key, locals) + find_all(path, prefixes, partial, details, details_key, locals).any? + end + + private + def search_combinations(prefixes) + prefixes = Array(prefixes) + prefixes.each do |prefix| + paths.each do |resolver| + yield resolver, prefix + end + end + end + + def typecast(paths) + paths.map do |path| + case path + when Pathname, String + # This path should only be reached by "direct" users of + # ActionView::Base (not using the ViewPaths or Renderer modules). + # We can't cache/de-dup the file system resolver in this case as we + # don't know which compiled_method_container we'll be rendering to. + FileSystemResolver.new(path) + when Resolver + path + else + raise TypeError, "#{path.inspect} is not a valid path: must be a String, Pathname, or Resolver" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/railtie.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/railtie.rb new file mode 100644 index 00000000..0a99fe66 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/railtie.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +require "action_view" +require "rails" + +module ActionView + # = Action View Railtie + class Railtie < Rails::Engine # :nodoc: + config.action_view = ActiveSupport::OrderedOptions.new + config.action_view.embed_authenticity_token_in_remote_forms = nil + config.action_view.debug_missing_translation = true + config.action_view.default_enforce_utf8 = nil + config.action_view.image_loading = nil + config.action_view.image_decoding = nil + config.action_view.apply_stylesheet_media_default = true + config.action_view.prepend_content_exfiltration_prevention = false + + config.eager_load_namespaces << ActionView + + config.after_initialize do |app| + ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = + app.config.action_view.delete(:embed_authenticity_token_in_remote_forms) + end + + config.after_initialize do |app| + form_with_generates_remote_forms = app.config.action_view.delete(:form_with_generates_remote_forms) + ActionView::Helpers::FormHelper.form_with_generates_remote_forms = form_with_generates_remote_forms + end + + config.after_initialize do |app| + form_with_generates_ids = app.config.action_view.delete(:form_with_generates_ids) + unless form_with_generates_ids.nil? + ActionView::Helpers::FormHelper.form_with_generates_ids = form_with_generates_ids + end + end + + config.after_initialize do |app| + default_enforce_utf8 = app.config.action_view.delete(:default_enforce_utf8) + unless default_enforce_utf8.nil? + ActionView::Helpers::FormTagHelper.default_enforce_utf8 = default_enforce_utf8 + end + end + + config.after_initialize do |app| + prepend_content_exfiltration_prevention = app.config.action_view.delete(:prepend_content_exfiltration_prevention) + ActionView::Helpers::ContentExfiltrationPreventionHelper.prepend_content_exfiltration_prevention = prepend_content_exfiltration_prevention + end + + config.after_initialize do |app| + if klass = app.config.action_view.delete(:sanitizer_vendor) + ActionView::Helpers::SanitizeHelper.sanitizer_vendor = klass + end + end + + config.after_initialize do |app| + button_to_generates_button_tag = app.config.action_view.delete(:button_to_generates_button_tag) + unless button_to_generates_button_tag.nil? + ActionView::Helpers::UrlHelper.button_to_generates_button_tag = button_to_generates_button_tag + end + end + + config.after_initialize do |app| + frozen_string_literal = app.config.action_view.delete(:frozen_string_literal) + ActionView::Template.frozen_string_literal = frozen_string_literal + end + + config.after_initialize do |app| + ActionView::Helpers::AssetTagHelper.image_loading = app.config.action_view.delete(:image_loading) + ActionView::Helpers::AssetTagHelper.image_decoding = app.config.action_view.delete(:image_decoding) + ActionView::Helpers::AssetTagHelper.preload_links_header = app.config.action_view.delete(:preload_links_header) + ActionView::Helpers::AssetTagHelper.apply_stylesheet_media_default = app.config.action_view.delete(:apply_stylesheet_media_default) + end + + config.after_initialize do |app| + ActiveSupport.on_load(:action_view) do + app.config.action_view.each do |k, v| + send "#{k}=", v + end + end + end + + initializer "action_view.deprecator", before: :load_environment_config do |app| + app.deprecators[:action_view] = ActionView.deprecator + end + + initializer "action_view.logger" do + ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger } + end + + initializer "action_view.caching" do |app| + ActiveSupport.on_load(:action_view) do + if app.config.action_view.cache_template_loading.nil? + ActionView::Resolver.caching = !app.config.reloading_enabled? + end + end + end + + initializer "action_view.setup_action_pack" do |app| + ActiveSupport.on_load(:action_controller) do + ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor) + end + end + + initializer "action_view.collection_caching", after: "action_controller.set_configs" do |app| + PartialRenderer.collection_cache = app.config.action_controller.cache_store + end + + config.after_initialize do |app| + enable_caching = if app.config.action_view.cache_template_loading.nil? + !app.config.reloading_enabled? + else + app.config.action_view.cache_template_loading + end + + unless enable_caching + view_reloader = ActionView::CacheExpiry::ViewReloader.new(watcher: app.config.file_watcher) + + app.reloaders << view_reloader + app.reloader.to_run do + require_unload_lock! + view_reloader.execute + end + end + end + + rake_tasks do |app| + unless app.config.api_only + load "action_view/tasks/cache_digests.rake" + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/record_identifier.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/record_identifier.rb new file mode 100644 index 00000000..6796997c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/record_identifier.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module" +require "action_view/model_naming" + +module ActionView + # = Action View \Record \Identifier + # + # RecordIdentifier encapsulates methods used by various ActionView helpers + # to associate records with DOM elements. + # + # Consider for example the following code that form of post: + # + # <%= form_with(model: post) do |f| %> + # <%= f.text_field :body %> + # <% end %> + # + # When +post+ is a new, unsaved ActiveRecord::Base instance, the resulting HTML + # is: + # + #
    + # + #
    + # + # When +post+ is a persisted ActiveRecord::Base instance, the resulting HTML + # is: + # + #
    + # + #
    + # + # In both cases, the +id+ and +class+ of the wrapping DOM element are + # automatically generated, following naming conventions encapsulated by the + # RecordIdentifier methods #dom_id and #dom_class: + # + # dom_id(Post) # => "new_post" + # dom_class(Post) # => "post" + # dom_id(Post.new) # => "new_post" + # dom_class(Post.new) # => "post" + # dom_id(Post.find 42) # => "post_42" + # dom_class(Post.find 42) # => "post" + # + # Note that these methods do not strictly require +Post+ to be a subclass of + # ActiveRecord::Base. + # Any +Post+ class will work as long as its instances respond to +to_key+ + # and +model_name+, given that +model_name+ responds to +param_key+. + # For instance: + # + # class Post + # attr_accessor :to_key + # + # def model_name + # OpenStruct.new param_key: 'post' + # end + # + # def self.find(id) + # new.tap { |post| post.to_key = [id] } + # end + # end + module RecordIdentifier + extend self + extend ModelNaming + + include ModelNaming + + JOIN = "_" + NEW = "new" + + # The DOM class convention is to use the singular form of an object or class. + # + # dom_class(post) # => "post" + # dom_class(Person) # => "person" + # + # If you need to address multiple instances of the same class in the same view, you can prefix the dom_class: + # + # dom_class(post, :edit) # => "edit_post" + # dom_class(Person, :edit) # => "edit_person" + def dom_class(record_or_class, prefix = nil) + singular = model_name_from_record_or_class(record_or_class).param_key + prefix ? "#{prefix}#{JOIN}#{singular}" : singular + end + + # The DOM id convention is to use the singular form of an object or class with the id following an underscore. + # If no id is found, prefix with "new_" instead. + # + # dom_id(Post.find(45)) # => "post_45" + # dom_id(Post) # => "new_post" + # + # If you need to address multiple instances of the same class in the same view, you can prefix the dom_id: + # + # dom_id(Post.find(45), :edit) # => "edit_post_45" + # dom_id(Post, :custom) # => "custom_post" + def dom_id(record_or_class, prefix = nil) + raise ArgumentError, "dom_id must be passed a record_or_class as the first argument, you passed #{record_or_class.inspect}" unless record_or_class + + record_id = record_key_for_dom_id(record_or_class) unless record_or_class.is_a?(Class) + if record_id + "#{dom_class(record_or_class, prefix)}#{JOIN}#{record_id}" + else + dom_class(record_or_class, prefix || NEW) + end + end + + private + # Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id. + # This can be overwritten to customize the default generated string representation if desired. + # If you need to read back a key from a dom_id in order to query for the underlying database record, + # you should write a helper like 'person_record_from_dom_id' that will extract the key either based + # on the default implementation (which just joins all key attributes with '_') or on your own + # overwritten version of the method. By default, this implementation passes the key string through a + # method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to + # make sure yourself that your dom ids are valid, in case you override this method. + def record_key_for_dom_id(record) # :doc: + key = convert_to_model(record).to_key + key && key.all? ? key.join(JOIN) : nil + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/render_parser.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/render_parser.rb new file mode 100644 index 00000000..2f74cf38 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/render_parser.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module ActionView + module RenderParser # :nodoc: + ALL_KNOWN_KEYS = [:partial, :template, :layout, :formats, :locals, :object, :collection, :as, :status, :content_type, :location, :spacer_template] + RENDER_TYPE_KEYS = [:partial, :template, :layout] + + class Base # :nodoc: + def initialize(name, code) + @name = name + @code = code + end + + private + def directory + File.dirname(@name) + end + + def partial_to_virtual_path(render_type, partial_path) + if render_type == :partial || render_type == :layout + partial_path.gsub(%r{(/|^)([^/]*)\z}, '\1_\2') + else + partial_path + end + end + end + + # Check if prism is available. If it is, use it. Otherwise, use ripper. + begin + require "prism" + rescue LoadError + require "ripper" + require_relative "render_parser/ripper_render_parser" + Default = RipperRenderParser + else + require_relative "render_parser/prism_render_parser" + Default = PrismRenderParser + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/render_parser/prism_render_parser.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/render_parser/prism_render_parser.rb new file mode 100644 index 00000000..57f5d302 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/render_parser/prism_render_parser.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +module ActionView + module RenderParser + class PrismRenderParser < Base # :nodoc: + def render_calls + queue = [Prism.parse(@code).value] + templates = [] + + while (node = queue.shift) + queue.concat(node.compact_child_nodes) + next unless node.is_a?(Prism::CallNode) + + options = render_call_options(node) + next unless options + + render_type = (options.keys & RENDER_TYPE_KEYS)[0] + template, object_template = render_call_template(options[render_type]) + next unless template + + if options.key?(:object) || options.key?(:collection) || object_template + next if options.key?(:object) && options.key?(:collection) + next unless options.key?(:partial) + end + + if options[:spacer_template].is_a?(Prism::StringNode) + templates << partial_to_virtual_path(:partial, options[:spacer_template].unescaped) + end + + templates << partial_to_virtual_path(render_type, template) + + if render_type != :layout && options[:layout].is_a?(Prism::StringNode) + templates << partial_to_virtual_path(:layout, options[:layout].unescaped) + end + end + + templates + end + + private + # Accept a call node and return a hash of options for the render call. + # If it doesn't match the expected format, return nil. + def render_call_options(node) + # We are only looking for calls to render or render_to_string. + name = node.name.to_sym + return if name != :render && name != :render_to_string + + # We are only looking for calls with arguments. + arguments = node.arguments + return unless arguments + + arguments = arguments.arguments + length = arguments.length + + # Get rid of any parentheses to get directly to the contents. + arguments.map! do |argument| + current = argument + + while current.is_a?(Prism::ParenthesesNode) && + current.body.is_a?(Prism::StatementsNode) && + current.body.body.length == 1 + current = current.body.body.first + end + + current + end + + # We are only looking for arguments that are either a string with an + # array of locals or a keyword hash with symbol keys. + options = + if (length == 1 || length == 2) && !arguments[0].is_a?(Prism::KeywordHashNode) + { partial: arguments[0], locals: arguments[1] } + elsif length == 1 && + arguments[0].is_a?(Prism::KeywordHashNode) && + arguments[0].elements.all? do |element| + element.is_a?(Prism::AssocNode) && element.key.is_a?(Prism::SymbolNode) + end + arguments[0].elements.to_h do |element| + [element.key.unescaped.to_sym, element.value] + end + end + + return unless options + + # Here we validate that the options have the keys we expect. + keys = options.keys + return if !keys.intersect?(RENDER_TYPE_KEYS) + return if (keys - ALL_KNOWN_KEYS).any? + + # Finally, we can return a valid set of options. + options + end + + # Accept the node that is being passed in the position of the template + # and return the template name and whether or not it is an object + # template. + def render_call_template(node) + object_template = false + template = + case node.type + when :string_node + path = node.unescaped + path.include?("/") ? path : "#{directory}/#{path}" + when :interpolated_string_node + node.parts.map do |node| + case node.type + when :string_node + node.unescaped + when :embedded_statements_node + "*" + else + return + end + end.join("") + else + dependency = + case node.type + when :class_variable_read_node + node.slice[2..] + when :instance_variable_read_node + node.slice[1..] + when :global_variable_read_node + node.slice[1..] + when :local_variable_read_node + node.slice + when :call_node + node.name.to_s + else + return + end + + "#{dependency.pluralize}/#{dependency.singularize}" + end + + [template, object_template] + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/render_parser/ripper_render_parser.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/render_parser/ripper_render_parser.rb new file mode 100644 index 00000000..f8dac744 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/render_parser/ripper_render_parser.rb @@ -0,0 +1,350 @@ +# frozen_string_literal: true + +module ActionView + module RenderParser + class RipperRenderParser < Base # :nodoc: + class Node < ::Array # :nodoc: + attr_reader :type + + def initialize(type, arr, opts = {}) + @type = type + super(arr) + end + + def children + to_a + end + + def inspect + typeinfo = type && type != :list ? ":" + type.to_s + ", " : "" + "s(" + typeinfo + map(&:inspect).join(", ") + ")" + end + + def fcall? + type == :command || type == :fcall + end + + def fcall_named?(name) + fcall? && + self[0].type == :@ident && + self[0][0] == name + end + + def argument_nodes + raise unless fcall? + return [] if self[1].nil? + if self[1].last == false || self[1].last.type == :vcall + self[1][0...-1] + else + self[1][0..-1] + end + end + + def string? + type == :string_literal + end + + def variable_reference? + type == :var_ref + end + + def vcall? + type == :vcall + end + + def call? + type == :call + end + + def variable_name + self[0][0] + end + + def call_method_name + self[2].first + end + + def to_string + raise unless string? + + # s(:string_literal, s(:string_content, map)) + self[0].map do |node| + case node.type + when :@tstring_content + node[0] + when :string_embexpr + "*" + end + end.join("") + end + + def hash? + type == :bare_assoc_hash || type == :hash + end + + def to_hash + if type == :bare_assoc_hash + hash_from_body(self[0]) + elsif type == :hash && self[0] == nil + {} + elsif type == :hash && self[0].type == :assoclist_from_args + hash_from_body(self[0][0]) + end + end + + def hash_from_body(body) + body.to_h do |hash_node| + return nil if hash_node.type != :assoc_new + + [hash_node[0], hash_node[1]] + end + end + + def symbol? + type == :@label || type == :symbol_literal + end + + def to_symbol + if type == :@label && self[0] =~ /\A(.+):\z/ + $1.to_sym + elsif type == :symbol_literal && self[0].type == :symbol && self[0][0].type == :@ident + self[0][0][0].to_sym + else + raise "not a symbol?: #{self.inspect}" + end + end + end + + class NodeParser < ::Ripper # :nodoc: + PARSER_EVENTS.each do |event| + arity = PARSER_EVENT_TABLE[event] + if arity == 0 && event.to_s.end_with?("_new") + module_eval(<<-eof, __FILE__, __LINE__ + 1) + def on_#{event}(*args) + Node.new(:list, args, lineno: lineno(), column: column()) + end + eof + elsif event.to_s.match?(/_add(_.+)?\z/) + module_eval(<<-eof, __FILE__, __LINE__ + 1) + begin; undef on_#{event}; rescue NameError; end + def on_#{event}(list, item) + list.push(item) + list + end + eof + else + module_eval(<<-eof, __FILE__, __LINE__ + 1) + begin; undef on_#{event}; rescue NameError; end + def on_#{event}(*args) + Node.new(:#{event}, args, lineno: lineno(), column: column()) + end + eof + end + end + + SCANNER_EVENTS.each do |event| + module_eval(<<-End, __FILE__, __LINE__ + 1) + def on_#{event}(tok) + Node.new(:@#{event}, [tok], lineno: lineno(), column: column()) + end + End + end + end + + class RenderCallExtractor < NodeParser # :nodoc: + attr_reader :render_calls + + METHODS_TO_PARSE = %w(render render_to_string) + + def initialize(*args) + super + + @render_calls = [] + end + + private + def on_fcall(name, *args) + on_render_call(super) + end + + def on_command(name, *args) + on_render_call(super) + end + + def on_render_call(node) + METHODS_TO_PARSE.each do |method| + if node.fcall_named?(method) + @render_calls << [method, node] + return node + end + end + node + end + + def on_arg_paren(content) + content + end + + def on_paren(content) + content.size == 1 ? content.first : content + end + end + + def render_calls + parser = RenderCallExtractor.new(@code) + parser.parse + + parser.render_calls.group_by(&:first).to_h do |method, nodes| + [ method.to_sym, nodes.collect { |v| v[1] } ] + end.map do |method, nodes| + nodes.map { |n| parse_render(n) } + end.flatten.compact + end + + private + def resolve_path_directory(path) + if path.include?("/") + path + else + "#{directory}/#{path}" + end + end + + # Convert + # render("foo", ...) + # into either + # render(template: "foo", ...) + # or + # render(partial: "foo", ...) + def normalize_args(string, options_hash) + if options_hash + { partial: string, locals: options_hash } + else + { partial: string } + end + end + + def parse_render(node) + node = node.argument_nodes + + if (node.length == 1 || node.length == 2) && !node[0].hash? + if node.length == 1 + options = normalize_args(node[0], nil) + elsif node.length == 2 + options = normalize_args(node[0], node[1]) + end + + return nil unless options + + parse_render_from_options(options) + elsif node.length == 1 && node[0].hash? + options = parse_hash_to_symbols(node[0]) + + return nil unless options + + parse_render_from_options(options) + else + nil + end + end + + def parse_hash(node) + node.hash? && node.to_hash + end + + def parse_hash_to_symbols(node) + hash = parse_hash(node) + + return unless hash + + hash.transform_keys do |key_node| + key = parse_sym(key_node) + + return unless key + + key + end + end + + def parse_render_from_options(options_hash) + renders = [] + keys = options_hash.keys + + if (keys & RENDER_TYPE_KEYS).size < 1 + # Must have at least one of render keys + return nil + end + + if (keys - ALL_KNOWN_KEYS).any? + # de-opt in case of unknown option + return nil + end + + render_type = (keys & RENDER_TYPE_KEYS)[0] + + node = options_hash[render_type] + + if node.string? + template = resolve_path_directory(node.to_string) + else + if node.variable_reference? + dependency = node.variable_name.sub(/\A(?:\$|@{1,2})/, "") + elsif node.vcall? + dependency = node.variable_name + elsif node.call? + dependency = node.call_method_name + else + return + end + + object_template = true + template = "#{dependency.pluralize}/#{dependency.singularize}" + end + + return unless template + + if spacer_template = render_template_with_spacer?(options_hash) + virtual_path = partial_to_virtual_path(:partial, spacer_template) + renders << virtual_path + end + + if options_hash.key?(:object) || options_hash.key?(:collection) || object_template + return nil if options_hash.key?(:object) && options_hash.key?(:collection) + return nil unless options_hash.key?(:partial) + end + + virtual_path = partial_to_virtual_path(render_type, template) + renders << virtual_path + + # Support for rendering multiple templates (i.e. a partial with a layout) + if layout_template = render_template_with_layout?(render_type, options_hash) + virtual_path = partial_to_virtual_path(:layout, layout_template) + + renders << virtual_path + end + + renders + end + + def parse_str(node) + node.string? && node.to_string + end + + def parse_sym(node) + node.symbol? && node.to_symbol + end + + def render_template_with_layout?(render_type, options_hash) + if render_type != :layout && options_hash.key?(:layout) + parse_str(options_hash[:layout]) + end + end + + def render_template_with_spacer?(options_hash) + if options_hash.key?(:spacer_template) + parse_str(options_hash[:spacer_template]) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/abstract_renderer.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/abstract_renderer.rb new file mode 100644 index 00000000..e5702654 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/abstract_renderer.rb @@ -0,0 +1,186 @@ +# frozen_string_literal: true + +require "concurrent/map" + +module ActionView + # This class defines the interface for a renderer. Each class that + # subclasses +AbstractRenderer+ is used by the base +Renderer+ class to + # render a specific type of object. + # + # The base +Renderer+ class uses its +render+ method to delegate to the + # renderers. These currently consist of + # + # PartialRenderer - Used for rendering partials + # TemplateRenderer - Used for rendering other types of templates + # StreamingTemplateRenderer - Used for streaming + # + # Whenever the +render+ method is called on the base +Renderer+ class, a new + # renderer object of the correct type is created, and the +render+ method on + # that new object is called in turn. This abstracts the set up and rendering + # into a separate classes for partials and templates. + class AbstractRenderer # :nodoc: + delegate :template_exists?, :any_templates?, :formats, to: :@lookup_context + + def initialize(lookup_context) + @lookup_context = lookup_context + end + + def render + raise NotImplementedError + end + + module ObjectRendering # :nodoc: + PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k| + h.compute_if_absent(k) { Concurrent::Map.new } + end + + def initialize(lookup_context, options) + super + @context_prefix = lookup_context.prefixes.first + end + + private + def local_variable(path) + if as = @options[:as] + raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s) + as.to_sym + else + base = path.end_with?("/") ? "" : File.basename(path) + raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/ + $1.to_sym + end + end + + IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \ + "make sure your partial name starts with underscore." + + OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " \ + "make sure it starts with lowercase letter, " \ + "and is followed by any combination of letters, numbers and underscores." + + def raise_invalid_identifier(path) + raise ArgumentError, IDENTIFIER_ERROR_MESSAGE % path + end + + def raise_invalid_option_as(as) + raise ArgumentError, OPTION_AS_ERROR_MESSAGE % as + end + + # Obtains the path to where the object's partial is located. If the object + # responds to +to_partial_path+, then +to_partial_path+ will be called and + # will provide the path. If the object does not respond to +to_partial_path+, + # then an +ArgumentError+ is raised. + # + # If +prefix_partial_path_with_controller_namespace+ is true, then this + # method will prefix the partial paths with a namespace. + def partial_path(object, view) + object = object.to_model if object.respond_to?(:to_model) + + path = if object.respond_to?(:to_partial_path) + object.to_partial_path + else + raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement #to_partial_path.") + end + + if view.prefix_partial_path_with_controller_namespace + PREFIXED_PARTIAL_NAMES[@context_prefix][path] ||= merge_prefix_into_object_path(@context_prefix, path.dup) + else + path + end + end + + def merge_prefix_into_object_path(prefix, object_path) + if prefix.include?(?/) && object_path.include?(?/) + prefixes = [] + prefix_array = File.dirname(prefix).split("/") + object_path_array = object_path.split("/")[0..-3] # skip model dir & partial + + prefix_array.each_with_index do |dir, index| + break if dir == object_path_array[index] + prefixes << dir + end + + (prefixes << object_path).join("/") + else + object_path + end + end + end + + class RenderedCollection # :nodoc: + def self.empty(format) + EmptyCollection.new format + end + + attr_reader :rendered_templates + + def initialize(rendered_templates, spacer) + @rendered_templates = rendered_templates + @spacer = spacer + end + + def body + @rendered_templates.map(&:body).join(@spacer.body).html_safe + end + + def format + rendered_templates.first.format + end + + class EmptyCollection + attr_reader :format + + def initialize(format) + @format = format + end + + def body; nil; end + end + end + + class RenderedTemplate # :nodoc: + attr_reader :body, :template + + def initialize(body, template) + @body = body + @template = template + end + + def format + template.format + end + + EMPTY_SPACER = Struct.new(:body).new + end + + private + NO_DETAILS = {}.freeze + + def extract_details(options) # :doc: + details = nil + LookupContext.registered_details.each do |key| + value = options[key] + + if value + (details ||= {})[key] = Array(value) + end + end + details || NO_DETAILS + end + + def prepend_formats(formats) # :doc: + formats = Array(formats) + return if formats.empty? || @lookup_context.html_fallback_for_js + + @lookup_context.formats = formats | @lookup_context.formats + end + + def build_rendered_template(content, template) + RenderedTemplate.new content, template + end + + def build_rendered_collection(templates, spacer) + RenderedCollection.new templates, spacer + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/collection_renderer.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/collection_renderer.rb new file mode 100644 index 00000000..64e3f866 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/collection_renderer.rb @@ -0,0 +1,204 @@ +# frozen_string_literal: true + +require "action_view/renderer/partial_renderer" + +module ActionView + class PartialIteration + # The number of iterations that will be done by the partial. + attr_reader :size + + # The current iteration of the partial. + attr_reader :index + + def initialize(size) + @size = size + @index = 0 + end + + # Check if this is the first iteration of the partial. + def first? + index == 0 + end + + # Check if this is the last iteration of the partial. + def last? + index == size - 1 + end + + def iterate! # :nodoc: + @index += 1 + end + end + + class CollectionRenderer < PartialRenderer # :nodoc: + include ObjectRendering + + class CollectionIterator # :nodoc: + include Enumerable + + def initialize(collection) + @collection = collection + end + + def each(&blk) + @collection.each(&blk) + end + + def size + @collection.size + end + + def length + @collection.respond_to?(:length) ? @collection.length : size + end + + def preload! + # no-op + end + end + + class SameCollectionIterator < CollectionIterator # :nodoc: + def initialize(collection, path, variables) + super(collection) + @path = path + @variables = variables + end + + def from_collection(collection) + self.class.new(collection, @path, @variables) + end + + def each_with_info + return enum_for(:each_with_info) unless block_given? + variables = [@path] + @variables + @collection.each { |o| yield(o, variables) } + end + end + + class PreloadCollectionIterator < SameCollectionIterator # :nodoc: + def initialize(collection, path, variables, relation) + super(collection, path, variables) + relation.skip_preloading! unless relation.loaded? + @relation = relation + end + + def from_collection(collection) + self.class.new(collection, @path, @variables, @relation) + end + + def each_with_info + return super unless block_given? + preload! + super + end + + def preload! + @relation.preload_associations(@collection) + end + end + + class MixedCollectionIterator < CollectionIterator # :nodoc: + def initialize(collection, paths) + super(collection) + @paths = paths + end + + def each_with_info + return enum_for(:each_with_info) unless block_given? + @collection.each_with_index { |o, i| yield(o, @paths[i]) } + end + end + + def render_collection_with_partial(collection, partial, context, block) + iter_vars = retrieve_variable(partial) + + collection = if collection.respond_to?(:preload_associations) + PreloadCollectionIterator.new(collection, partial, iter_vars, collection) + else + SameCollectionIterator.new(collection, partial, iter_vars) + end + + template = find_template(partial, @locals.keys + iter_vars) + + layout = if !block && (layout = @options[:layout]) + find_template(layout.to_s, @locals.keys + iter_vars) + end + + render_collection(collection, context, partial, template, layout, block) + end + + def render_collection_derive_partial(collection, context, block) + paths = collection.map { |o| partial_path(o, context) } + + if paths.uniq.length == 1 + # Homogeneous + render_collection_with_partial(collection, paths.first, context, block) + else + if @options[:cached] + raise NotImplementedError, "render caching requires a template. Please specify a partial when rendering" + end + + paths.map! { |path| retrieve_variable(path).unshift(path) } + collection = MixedCollectionIterator.new(collection, paths) + render_collection(collection, context, nil, nil, nil, block) + end + end + + private + def retrieve_variable(path) + variable = local_variable(path) + [variable, :"#{variable}_counter", :"#{variable}_iteration"] + end + + def render_collection(collection, view, path, template, layout, block) + identifier = (template && template.identifier) || path + ActiveSupport::Notifications.instrument( + "render_collection.action_view", + identifier: identifier, + layout: layout && layout.virtual_path, + count: collection.length + ) do |payload| + spacer = if @options.key?(:spacer_template) + spacer_template = find_template(@options[:spacer_template], @locals.keys) + build_rendered_template(spacer_template.render(view, @locals), spacer_template) + else + RenderedTemplate::EMPTY_SPACER + end + + collection_body = if template + cache_collection_render(payload, view, template, collection) do |filtered_collection| + collection_with_template(view, template, layout, filtered_collection) + end + else + collection_with_template(view, nil, layout, collection) + end + + return RenderedCollection.empty(@lookup_context.formats.first) if collection_body.empty? + + build_rendered_collection(collection_body, spacer) + end + end + + def collection_with_template(view, template, layout, collection) + locals = @locals + cache = {} + + partial_iteration = PartialIteration.new(collection.size) + + collection.each_with_info.map do |object, (path, as, counter, iteration)| + index = partial_iteration.index + + locals[as] = object + locals[counter] = index + locals[iteration] = partial_iteration + + _template = (cache[path] ||= (template || find_template(path, @locals.keys + [as, counter, iteration]))) + + content = _template.render(view, locals, implicit_locals: [counter, iteration]) + content = layout.render(view, locals) { content } if layout + partial_iteration.iterate! + build_rendered_template(content, _template) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/object_renderer.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/object_renderer.rb new file mode 100644 index 00000000..12c5bb59 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/object_renderer.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module ActionView + class ObjectRenderer < PartialRenderer # :nodoc: + include ObjectRendering + + def initialize(lookup_context, options) + super + @object = nil + @local_name = nil + end + + def render_object_with_partial(object, partial, context, block) + @object = object + @local_name = local_variable(partial) + render(partial, context, block) + end + + def render_object_derive_partial(object, context, block) + path = partial_path(object, context) + render_object_with_partial(object, path, context, block) + end + + private + def template_keys(path) + super + [@local_name] + end + + def render_partial_template(view, locals, template, layout, block) + locals[@local_name || template.variable] = @object + super(view, locals, template, layout, block) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/partial_renderer.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/partial_renderer.rb new file mode 100644 index 00000000..8f138264 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/partial_renderer.rb @@ -0,0 +1,267 @@ +# frozen_string_literal: true + +require "action_view/renderer/partial_renderer/collection_caching" + +module ActionView + # = Action View Partials + # + # There's also a convenience method for rendering sub templates within the current controller that depends on a + # single object (we call this kind of sub templates for partials). It relies on the fact that partials should + # follow the naming convention of being prefixed with an underscore -- as to separate them from regular + # templates that could be rendered on their own. + # + # In a template for Advertiser#account: + # + # <%= render partial: "account" %> + # + # This would render "advertiser/_account.html.erb". + # + # In another template for Advertiser#buy, we could have: + # + # <%= render partial: "account", locals: { account: @buyer } %> + # + # <% @advertisements.each do |ad| %> + # <%= render partial: "ad", locals: { ad: ad } %> + # <% end %> + # + # This would first render advertiser/_account.html.erb with @buyer passed in as the local variable +account+, then + # render advertiser/_ad.html.erb and pass the local variable +ad+ to the template for display. + # + # == The +:as+ and +:object+ options + # + # By default ActionView::PartialRenderer doesn't have any local variables. + # The :object option can be used to pass an object to the partial. For instance: + # + # <%= render partial: "account", object: @buyer %> + # + # would provide the @buyer object to the partial, available under the local variable +account+ and is + # equivalent to: + # + # <%= render partial: "account", locals: { account: @buyer } %> + # + # With the :as option we can specify a different name for said local variable. For example, if we + # wanted it to be +user+ instead of +account+ we'd do: + # + # <%= render partial: "account", object: @buyer, as: 'user' %> + # + # This is equivalent to + # + # <%= render partial: "account", locals: { user: @buyer } %> + # + # == \Rendering a collection of partials + # + # The example of partial use describes a familiar pattern where a template needs to iterate over an array and + # render a sub template for each of the elements. This pattern has been implemented as a single method that + # accepts an array and renders a partial by the same name as the elements contained within. So the three-lined + # example in "Using partials" can be rewritten with a single line: + # + # <%= render partial: "ad", collection: @advertisements %> + # + # This will render advertiser/_ad.html.erb and pass the local variable +ad+ to the template for display. An + # iteration object will automatically be made available to the template with a name of the form + # +partial_name_iteration+. The iteration object has knowledge about which index the current object has in + # the collection and the total size of the collection. The iteration object also has two convenience methods, + # +first?+ and +last?+. In the case of the example above, the template would be fed +ad_iteration+. + # For backwards compatibility the +partial_name_counter+ is still present and is mapped to the iteration's + # +index+ method. + # + # The :as option may be used when rendering partials. + # + # You can specify a partial to be rendered between elements via the :spacer_template option. + # The following example will render advertiser/_ad_divider.html.erb between each ad partial: + # + # <%= render partial: "ad", collection: @advertisements, spacer_template: "ad_divider" %> + # + # If the given :collection is +nil+ or empty, render will return +nil+. This will allow you + # to specify a text which will be displayed instead by using this form: + # + # <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %> + # + # == \Rendering shared partials + # + # Two controllers can share a set of partials and render them like this: + # + # <%= render partial: "advertisement/ad", locals: { ad: @advertisement } %> + # + # This will render the partial advertisement/_ad.html.erb regardless of which controller this is being called from. + # + # == \Rendering objects that respond to +to_partial_path+ + # + # Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work + # and pick the proper path by checking +to_partial_path+ method. + # + # # @account.to_partial_path returns 'accounts/account', so it can be used to replace: + # # <%= render partial: "accounts/account", locals: { account: @account} %> + # <%= render partial: @account %> + # + # # @posts is an array of Post instances, so every post record returns 'posts/post' on #to_partial_path, + # # that's why we can replace: + # # <%= render partial: "posts/post", collection: @posts %> + # <%= render partial: @posts %> + # + # == \Rendering the default case + # + # If you're not going to be using any of the options like collections or layouts, you can also use the short-hand + # defaults of render to render partials. Examples: + # + # # Instead of <%= render partial: "account" %> + # <%= render "account" %> + # + # # Instead of <%= render partial: "account", locals: { account: @buyer } %> + # <%= render "account", account: @buyer %> + # + # # @account.to_partial_path returns 'accounts/account', so it can be used to replace: + # # <%= render partial: "accounts/account", locals: { account: @account} %> + # <%= render @account %> + # + # # @posts is an array of Post instances, so every post record returns 'posts/post' on #to_partial_path, + # # that's why we can replace: + # # <%= render partial: "posts/post", collection: @posts %> + # <%= render @posts %> + # + # == \Rendering partials with layouts + # + # Partials can have their own layouts applied to them. These layouts are different than the ones that are + # specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types + # of users: + # + # <%# app/views/users/index.html.erb %> + # Here's the administrator: + # <%= render partial: "user", layout: "administrator", locals: { user: administrator } %> + # + # Here's the editor: + # <%= render partial: "user", layout: "editor", locals: { user: editor } %> + # + # <%# app/views/users/_user.html.erb %> + # Name: <%= user.name %> + # + # <%# app/views/users/_administrator.html.erb %> + #
    + # Budget: $<%= user.budget %> + # <%= yield %> + #
    + # + # <%# app/views/users/_editor.html.erb %> + #
    + # Deadline: <%= user.deadline %> + # <%= yield %> + #
    + # + # ...this will return: + # + # Here's the administrator: + #
    + # Budget: $<%= user.budget %> + # Name: <%= user.name %> + #
    + # + # Here's the editor: + #
    + # Deadline: <%= user.deadline %> + # Name: <%= user.name %> + #
    + # + # If a collection is given, the layout will be rendered once for each item in + # the collection. For example, these two snippets have the same output: + # + # <%# app/views/users/_user.html.erb %> + # Name: <%= user.name %> + # + # <%# app/views/users/index.html.erb %> + # <%# This does not use layouts %> + #
      + # <% users.each do |user| -%> + #
    • + # <%= render partial: "user", locals: { user: user } %> + #
    • + # <% end -%> + #
    + # + # <%# app/views/users/_li_layout.html.erb %> + #
  • + # <%= yield %> + #
  • + # + # <%# app/views/users/index.html.erb %> + #
      + # <%= render partial: "user", layout: "li_layout", collection: users %> + #
    + # + # Given two users whose names are Alice and Bob, these snippets return: + # + #
      + #
    • + # Name: Alice + #
    • + #
    • + # Name: Bob + #
    • + #
    + # + # The current object being rendered, as well as the object_counter, will be + # available as local variables inside the layout template under the same names + # as available in the partial. + # + # You can also apply a layout to a block within any template: + # + # <%# app/views/users/_chief.html.erb %> + # <%= render(layout: "administrator", locals: { user: chief }) do %> + # Title: <%= chief.title %> + # <% end %> + # + # ...this will return: + # + #
    + # Budget: $<%= user.budget %> + # Title: <%= chief.name %> + #
    + # + # As you can see, the :locals hash is shared between both the partial and its layout. + class PartialRenderer < AbstractRenderer + include CollectionCaching + + def initialize(lookup_context, options) + super(lookup_context) + @options = options + @locals = @options[:locals] || {} + @details = extract_details(@options) + end + + def render(partial, context, block) + template = find_template(partial, template_keys(partial)) + + if !block && (layout = @options[:layout]) + layout = find_template(layout.to_s, template_keys(partial)) + end + + render_partial_template(context, @locals, template, layout, block) + end + + private + def template_keys(_) + @locals.keys + end + + def render_partial_template(view, locals, template, layout, block) + ActiveSupport::Notifications.instrument( + "render_partial.action_view", + identifier: template.identifier, + layout: layout && layout.virtual_path, + locals: locals + ) do |payload| + content = template.render(view, locals, add_to_stack: !block) do |*name| + view._layout_for(*name, &block) + end + + content = layout.render(view, locals) { content } if layout + payload[:cache_hit] = view.view_renderer.cache_hits[template.virtual_path] + build_rendered_template(content, template) + end + end + + def find_template(path, locals) + prefixes = path.include?(?/) ? [] : @lookup_context.prefixes + @lookup_context.find_template(path, prefixes, true, locals, @details) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/partial_renderer/collection_caching.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/partial_renderer/collection_caching.rb new file mode 100644 index 00000000..6ee720aa --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/partial_renderer/collection_caching.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" + +module ActionView + module CollectionCaching # :nodoc: + extend ActiveSupport::Concern + + included do + # Fallback cache store if Action View is used without Rails. + # Otherwise overridden in Railtie to use Rails.cache. + mattr_accessor :collection_cache, default: ActiveSupport::Cache::MemoryStore.new + end + + private + def will_cache?(options, view) + options[:cached] && view.controller.respond_to?(:perform_caching) && view.controller.perform_caching + end + + def cache_collection_render(instrumentation_payload, view, template, collection) + return yield(collection) unless will_cache?(@options, view) + + collection_iterator = collection + + # Result is a hash with the key represents the + # key used for cache lookup and the value is the item + # on which the partial is being rendered + keyed_collection, ordered_keys = collection_by_cache_keys(view, template, collection) + + # Pull all partials from cache + # Result is a hash, key matches the entry in + # `keyed_collection` where the cache was retrieved and the + # value is the value that was present in the cache + cached_partials = collection_cache.read_multi(*keyed_collection.keys) + instrumentation_payload[:cache_hits] = cached_partials.size + + # Extract the items for the keys that are not found + collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values + + rendered_partials = collection.empty? ? [] : yield(collection_iterator.from_collection(collection)) + + index = 0 + keyed_partials = fetch_or_cache_partial(cached_partials, template, order_by: keyed_collection.each_key) do + # This block is called once + # for every cache miss while preserving order. + rendered_partials[index].tap { index += 1 } + end + + ordered_keys.map do |key| + keyed_partials[key] + end + end + + def callable_cache_key? + @options[:cached].respond_to?(:call) + end + + def collection_by_cache_keys(view, template, collection) + seed = callable_cache_key? ? @options[:cached] : ->(i) { i } + + digest_path = view.digest_path_from_template(template) + collection.preload! if callable_cache_key? + + collection.each_with_object([{}, []]) do |item, (hash, ordered_keys)| + key = expanded_cache_key(seed.call(item), view, template, digest_path) + ordered_keys << key + hash[key] = item + end + end + + def expanded_cache_key(key, view, template, digest_path) + key = view.combined_fragment_cache_key(view.cache_fragment_name(key, digest_path: digest_path)) + key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0. + end + + # `order_by` is an enumerable object containing keys of the cache, + # all keys are passed in whether found already or not. + # + # `cached_partials` is a hash. If the value exists + # it represents the rendered partial from the cache + # otherwise `Hash#fetch` will take the value of its block. + # + # This method expects a block that will return the rendered + # partial. An example is to render all results + # for each element that was not found in the cache and store it as an array. + # Order it so that the first empty cache element in `cached_partials` + # corresponds to the first element in `rendered_partials`. + # + # If the partial is not already cached it will also be + # written back to the underlying cache store. + def fetch_or_cache_partial(cached_partials, template, order_by:) + entries_to_write = {} + + keyed_partials = order_by.index_with do |cache_key| + if content = cached_partials[cache_key] + build_rendered_template(content, template) + else + rendered_partial = yield + body = rendered_partial.body + + # We want to cache buffers as raw strings. This both improve performance and + # avoid creating forward compatibility issues with the internal representation + # of these two types. + if body.is_a?(ActionView::OutputBuffer) || body.is_a?(ActiveSupport::SafeBuffer) + body = body.to_str + end + + entries_to_write[cache_key] = body + rendered_partial + end + end + + unless entries_to_write.empty? + collection_cache.write_multi(entries_to_write) + end + + keyed_partials + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/renderer.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/renderer.rb new file mode 100644 index 00000000..df5138bb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/renderer.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +module ActionView + # = Action View \Renderer + # + # This is the main entry point for rendering. It basically delegates + # to other objects like TemplateRenderer and PartialRenderer which + # actually renders the template. + # + # The Renderer will parse the options from the +render+ or +render_body+ + # method and render a partial or a template based on the options. The + # +TemplateRenderer+ and +PartialRenderer+ objects are wrappers which do all + # the setup and logic necessary to render a view and a new object is created + # each time +render+ is called. + class Renderer + attr_accessor :lookup_context + + def initialize(lookup_context) + @lookup_context = lookup_context + end + + # Main render entry point shared by Action View and Action Controller. + def render(context, options) + render_to_object(context, options).body + end + + def render_to_object(context, options) # :nodoc: + if options.key?(:partial) + render_partial_to_object(context, options) + else + render_template_to_object(context, options) + end + end + + # Render but returns a valid Rack body. If fibers are defined, we return + # a streaming body that renders the template piece by piece. + # + # Note that partials are not supported to be rendered with streaming, + # so in such cases, we just wrap them in an array. + def render_body(context, options) + if options.key?(:partial) + [render_partial(context, options)] + else + StreamingTemplateRenderer.new(@lookup_context).render(context, options) + end + end + + def render_partial(context, options, &block) # :nodoc: + render_partial_to_object(context, options, &block).body + end + + def cache_hits # :nodoc: + @cache_hits ||= {} + end + + private + def render_template_to_object(context, options) + TemplateRenderer.new(@lookup_context).render(context, options) + end + + def render_partial_to_object(context, options, &block) + partial = options[:partial] + if String === partial + collection = collection_from_options(options) + + if collection + # Collection + Partial + renderer = CollectionRenderer.new(@lookup_context, options) + renderer.render_collection_with_partial(collection, partial, context, block) + else + if options.key?(:object) + # Object + Partial + renderer = ObjectRenderer.new(@lookup_context, options) + renderer.render_object_with_partial(options[:object], partial, context, block) + else + # Partial + renderer = PartialRenderer.new(@lookup_context, options) + renderer.render(partial, context, block) + end + end + else + collection = collection_from_object(partial) || collection_from_options(options) + + if collection + # Collection + Derived Partial + renderer = CollectionRenderer.new(@lookup_context, options) + renderer.render_collection_derive_partial(collection, context, block) + else + # Object + Derived Partial + renderer = ObjectRenderer.new(@lookup_context, options) + renderer.render_object_derive_partial(partial, context, block) + end + end + end + + def collection_from_options(options) + if options.key?(:collection) + collection = options[:collection] + collection || [] + end + end + + def collection_from_object(object) + object if object.respond_to?(:to_ary) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/streaming_template_renderer.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/streaming_template_renderer.rb new file mode 100644 index 00000000..10b8f2ee --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/streaming_template_renderer.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + + +module ActionView + # == TODO + # + # * Support streaming from child templates, partials and so on. + # * Rack::Cache needs to support streaming bodies + class StreamingTemplateRenderer < TemplateRenderer # :nodoc: + # A valid Rack::Body (i.e. it responds to each). + # It is initialized with a block that, when called, starts + # rendering the template. + class Body # :nodoc: + def initialize(&start) + @start = start + end + + def each(&block) + begin + @start.call(block) + rescue Exception => exception + log_error(exception) + block.call ActionView::Base.streaming_completion_on_exception + end + self + end + + private + # This is the same logging logic as in ShowExceptions middleware. + def log_error(exception) + logger = ActionView::Base.logger + return unless logger + + message = +"\n#{exception.class} (#{exception.message}):\n" + message << exception.annotated_source_code.to_s if exception.respond_to?(:annotated_source_code) + message << " " << exception.backtrace.join("\n ") + logger.fatal("#{message}\n\n") + end + end + + # For streaming, instead of rendering a given a template, we return a Body + # object that responds to each. This object is initialized with a block + # that knows how to render the template. + def render_template(view, template, layout_name = nil, locals = {}) # :nodoc: + return [super.body] unless layout_name && template.supports_streaming? + + locals ||= {} + layout = find_layout(layout_name, locals.keys, [formats.first]) + + Body.new do |buffer| + delayed_render(buffer, template, layout, view, locals) + end + end + + private + def delayed_render(buffer, template, layout, view, locals) + # Wrap the given buffer in the StreamingBuffer and pass it to the + # underlying template handler. Now, every time something is concatenated + # to the buffer, it is not appended to an array, but streamed straight + # to the client. + output = ActionView::StreamingBuffer.new(buffer) + yielder = lambda { |*name| view._layout_for(*name) } + + ActiveSupport::Notifications.instrument( + "render_template.action_view", + identifier: template.identifier, + layout: layout && layout.virtual_path, + locals: locals + ) do + outer_config = I18n.config + fiber = Fiber.new do + I18n.config = outer_config + if layout + layout.render(view, locals, output, &yielder) + else + # If you don't have a layout, just render the thing + # and concatenate the final result. This is the same + # as a layout with just <%= yield %> + output.safe_concat view._layout_for + end + end + + # Set the view flow to support streaming. It will be aware + # when to stop rendering the layout because it needs to search + # something in the template and vice-versa. + view.view_flow = StreamingFlow.new(view, fiber) + + # Yo! Start the fiber! + fiber.resume + + # If the fiber is still alive, it means we need something + # from the template, so start rendering it. If not, it means + # the layout exited without requiring anything from the template. + if fiber.alive? + content = template.render(view, locals, &yielder) + + # Once rendering the template is done, sets its content in the :layout key. + view.view_flow.set(:layout, content) + + # In case the layout continues yielding, we need to resume + # the fiber until all yields are handled. + fiber.resume while fiber.alive? + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/template_renderer.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/template_renderer.rb new file mode 100644 index 00000000..1f9aba7d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/renderer/template_renderer.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +module ActionView + class TemplateRenderer < AbstractRenderer # :nodoc: + def render(context, options) + @details = extract_details(options) + template = determine_template(options) + + prepend_formats(template.format) + + render_template(context, template, options[:layout], options[:locals] || {}) + end + + private + # Determine the template to be rendered using the given options. + def determine_template(options) + keys = options.has_key?(:locals) ? options[:locals].keys : [] + + if options.key?(:body) + Template::Text.new(options[:body]) + elsif options.key?(:plain) + Template::Text.new(options[:plain]) + elsif options.key?(:html) + Template::HTML.new(options[:html], formats.first) + elsif options.key?(:file) + if File.exist?(options[:file]) + Template::RawFile.new(options[:file]) + else + if Pathname.new(options[:file]).absolute? + raise ArgumentError, "File #{options[:file]} does not exist" + else + raise ArgumentError, "`render file:` should be given the absolute path to a file. '#{options[:file]}' was given instead" + end + end + elsif options.key?(:inline) + handler = Template.handler_for_extension(options[:type] || "erb") + format = if handler.respond_to?(:default_format) + handler.default_format + else + @lookup_context.formats.first + end + Template::Inline.new(options[:inline], "inline template", handler, locals: keys, format: format) + elsif options.key?(:renderable) + Template::Renderable.new(options[:renderable]) + elsif options.key?(:template) + if options[:template].respond_to?(:render) + options[:template] + else + @lookup_context.find_template(options[:template], options[:prefixes], false, keys, @details) + end + else + raise ArgumentError, "You invoked render but did not give any of :body, :file, :html, :inline, :partial, :plain, :renderable, or :template option." + end + end + + # Renders the given template. A string representing the layout can be + # supplied as well. + def render_template(view, template, layout_name, locals) + render_with_layout(view, template, layout_name, locals) do |layout| + ActiveSupport::Notifications.instrument( + "render_template.action_view", + identifier: template.identifier, + layout: layout && layout.virtual_path, + locals: locals + ) do + template.render(view, locals) { |*name| view._layout_for(*name) } + end + end + end + + def render_with_layout(view, template, path, locals) + layout = path && find_layout(path, locals.keys, [formats.first]) + + body = if layout + ActiveSupport::Notifications.instrument("render_layout.action_view", identifier: layout.identifier) do + view.view_flow.set(:layout, yield(layout)) + layout.render(view, locals) { |*name| view._layout_for(*name) } + end + else + yield + end + build_rendered_template(body, template) + end + + # This is the method which actually finds the layout using details in the lookup + # context object. If no layout is found, it checks if at least a layout with + # the given name exists across all details before raising the error. + def find_layout(layout, keys, formats) + resolve_layout(layout, keys, formats) + end + + def resolve_layout(layout, keys, formats) + details = @details.dup + details[:formats] = formats + + case layout + when String + begin + if layout.start_with?("/") + raise ArgumentError, "Rendering layouts from an absolute path is not supported." + else + @lookup_context.find_template(layout, nil, false, keys, details) + end + rescue ActionView::MissingTemplate + all_details = @details.merge(formats: @lookup_context.default_formats) + raise unless template_exists?(layout, nil, false, keys, **all_details) + end + when Proc + resolve_layout(layout.call(@lookup_context, formats, keys), keys, formats) + else + layout + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/rendering.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/rendering.rb new file mode 100644 index 00000000..415436ac --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/rendering.rb @@ -0,0 +1,189 @@ +# frozen_string_literal: true + +require "action_view/view_paths" + +module ActionView + # This is a class to fix I18n global state. Whenever you provide I18n.locale during a request, + # it will trigger the lookup_context and consequently expire the cache. + class I18nProxy < ::I18n::Config # :nodoc: + attr_reader :original_config, :lookup_context + + def initialize(original_config, lookup_context) + original_config = original_config.original_config if original_config.respond_to?(:original_config) + @original_config = original_config + @lookup_context = lookup_context + end + + def locale + @original_config.locale + end + + def locale=(value) + @lookup_context.locale = value + end + end + + module Rendering + extend ActiveSupport::Concern + include ActionView::ViewPaths + + attr_internal_reader :rendered_format + + def initialize + @_rendered_format = nil + super + end + + # Override process to set up I18n proxy. + def process(...) # :nodoc: + old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context) + super + ensure + I18n.config = old_config + end + + module ClassMethods + def _routes + end + + def _helpers + end + + def inherit_view_context_class? + superclass.respond_to?(:view_context_class) && + supports_path? == superclass.supports_path? && + _routes.equal?(superclass._routes) && + _helpers.equal?(superclass._helpers) + end + + def build_view_context_class(klass, supports_path, routes, helpers) + if inherit_view_context_class? + return superclass.view_context_class + end + + Class.new(klass) do + if routes + include routes.url_helpers(supports_path) + include routes.mounted_helpers + end + + if helpers + include helpers + end + end + end + + def eager_load! + super + view_context_class + nil + end + + def view_context_class + klass = ActionView::LookupContext::DetailsKey.view_context_class + + @view_context_class ||= build_view_context_class(klass, supports_path?, _routes, _helpers) + + if klass.changed?(@view_context_class) + @view_context_class = build_view_context_class(klass, supports_path?, _routes, _helpers) + end + + @view_context_class + end + end + + def view_context_class + self.class.view_context_class + end + + # An instance of a view class. The default view class is ActionView::Base. + # + # The view class must have the following methods: + # + # * View.new(lookup_context, assigns, controller) — Create a new + # ActionView instance for a controller and we can also pass the arguments. + # + # * View#render(option) — Returns String with the rendered template. + # + # Override this method in a module to change the default behavior. + def view_context + view_context_class.new(lookup_context, view_assigns, self) + end + + # Returns an object that is able to render templates. + def view_renderer # :nodoc: + # Lifespan: Per controller + @_view_renderer ||= ActionView::Renderer.new(lookup_context) + end + + def render_to_body(options = {}) + _process_options(options) + _process_render_template_options(options) + _render_template(options) + end + + private + # Find and render a template based on the options given. + def _render_template(options) + variant = options.delete(:variant) + assigns = options.delete(:assigns) + context = view_context + + context.assign assigns if assigns + lookup_context.variants = variant if variant + + rendered_template = context.in_rendering_context(options) do |renderer| + renderer.render_to_object(context, options) + end + + rendered_format = rendered_template.format || lookup_context.formats.first + @_rendered_format = Template::Types[rendered_format] + + rendered_template.body + end + + # Assign the rendered format to look up context. + def _process_format(format) + super + lookup_context.formats = [format.to_sym] if format.to_sym + end + + # Normalize args by converting render "foo" to render action: "foo" and + # render "foo/bar" to render template: "foo/bar". + def _normalize_args(action = nil, options = {}) + options = super(action, options) + case action + when NilClass + when Hash + options = action + when String, Symbol + action = action.to_s + key = action.include?(?/) ? :template : :action + options[key] = action + else + if action.respond_to?(:permitted?) && action.permitted? + options = action + elsif action.respond_to?(:render_in) + options[:renderable] = action + else + options[:partial] = action + end + end + + options + end + + # Normalize options. + def _process_render_template_options(options) + if options[:partial] == true + options[:partial] = action_name + end + + if !options.keys.intersect?([:partial, :file, :template]) + options[:prefixes] ||= _prefixes + end + + options[:template] ||= (options[:action] || action_name).to_s + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/routing_url_for.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/routing_url_for.rb new file mode 100644 index 00000000..5de43700 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/routing_url_for.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +require "action_dispatch/routing/polymorphic_routes" + +module ActionView + module RoutingUrlFor + # Returns the URL for the set of +options+ provided. This takes the + # same options as +url_for+ in Action Controller (see the + # documentation for ActionDispatch::Routing::UrlFor#url_for). Note that by default + # :only_path is true so you'll get the relative "/controller/action" + # instead of the fully qualified URL like "http://example.com/controller/action". + # + # ==== Options + # * :anchor - Specifies the anchor name to be appended to the path. + # * :only_path - If true, returns the relative URL (omitting the protocol, host name, and port) (true by default unless :host is specified). + # * :trailing_slash - If true, adds a trailing slash, as in "/archive/2005/". Note that this + # is currently not recommended since it breaks caching. + # * :host - Overrides the default (current) host if provided. + # * :protocol - Overrides the default (current) protocol if provided. + # * :user - Inline HTTP authentication (only plucked out if :password is also present). + # * :password - Inline HTTP authentication (only plucked out if :user is also present). + # + # ==== Relying on named routes + # + # Passing a record (like an Active Record) instead of a hash as the options parameter will + # trigger the named route for that record. The lookup will happen on the name of the class. So passing a + # Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as + # +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route). + # + # ==== Implicit Controller Namespacing + # + # Controllers passed in using the +:controller+ option will retain their namespace unless it is an absolute one. + # + # ==== Examples + # <%= url_for(action: 'index') %> + # # => /blogs/ + # + # <%= url_for(action: 'find', controller: 'books') %> + # # => /books/find + # + # <%= url_for(action: 'login', controller: 'members', only_path: false, protocol: 'https') %> + # # => https://www.example.com/members/login/ + # + # <%= url_for(action: 'play', anchor: 'player') %> + # # => /messages/play/#player + # + # <%= url_for(action: 'jump', anchor: 'tax&ship') %> + # # => /testing/jump/#tax&ship + # + # <%= url_for(Workshop) %> + # # => /workshops + # + # <%= url_for(Workshop.new) %> + # # relies on Workshop answering a persisted? call (and in this case returning false) + # # => /workshops + # + # <%= url_for(@workshop) %> + # # calls @workshop.to_param which by default returns the id + # # => /workshops/5 + # + # # to_param can be re-defined in a model to provide different URL names: + # # => /workshops/1-workshop-name + # + # <%= url_for("http://www.example.com") %> + # # => http://www.example.com + # + # <%= url_for(:back) %> + # # if request.env["HTTP_REFERER"] is set to "http://www.example.com" + # # => http://www.example.com + # + # <%= url_for(:back) %> + # # if request.env["HTTP_REFERER"] is not set or is blank + # # => javascript:history.back() + # + # <%= url_for(action: 'index', controller: 'users') %> + # # Assuming an "admin" namespace + # # => /admin/users + # + # <%= url_for(action: 'index', controller: '/users') %> + # # Specify absolute path with beginning slash + # # => /users + def url_for(options = nil) + case options + when String + options + when nil + super(only_path: _generate_paths_by_default) + when Hash + options = options.symbolize_keys + ensure_only_path_option(options) + + super(options) + when ActionController::Parameters + ensure_only_path_option(options) + + super(options) + when :back + _back_url + when Array + components = options.dup + options = components.extract_options! + ensure_only_path_option(options) + + if options[:only_path] + polymorphic_path(components, options) + else + polymorphic_url(components, options) + end + else + method = _generate_paths_by_default ? :path : :url + builder = ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.public_send(method) + + case options + when Symbol + builder.handle_string_call(self, options) + when Class + builder.handle_class_call(self, options) + else + builder.handle_model_call(self, options) + end + end + end + + def url_options # :nodoc: + return super unless controller.respond_to?(:url_options) + controller.url_options + end + + private + def _routes_context + controller + end + + def optimize_routes_generation? + controller.respond_to?(:optimize_routes_generation?, true) ? + controller.optimize_routes_generation? : super + end + + def _generate_paths_by_default + true + end + + def ensure_only_path_option(options) + unless options.key?(:only_path) + options[:only_path] = _generate_paths_by_default unless options[:host] + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/tasks/cache_digests.rake b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/tasks/cache_digests.rake new file mode 100644 index 00000000..dd8e94bd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/tasks/cache_digests.rake @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +namespace :cache_digests do + desc "Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)" + task nested_dependencies: :environment do + abort "You must provide TEMPLATE for the task to run" unless ENV["TEMPLATE"].present? + puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:to_dep_map) + end + + desc "Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)" + task dependencies: :environment do + abort "You must provide TEMPLATE for the task to run" unless ENV["TEMPLATE"].present? + puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:name) + end + + class CacheDigests + def self.template_name + ENV["TEMPLATE"].split(".", 2).first + end + + def self.finder + ApplicationController.new.lookup_context + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template.rb new file mode 100644 index 00000000..b2cdf21c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template.rb @@ -0,0 +1,590 @@ +# frozen_string_literal: true + +require "delegate" + +module ActionView + # = Action View \Template + class Template + extend ActiveSupport::Autoload + + STRICT_LOCALS_REGEX = /\#\s+locals:\s+\((.*)\)/ + + # === Encodings in ActionView::Template + # + # ActionView::Template is one of a few sources of potential + # encoding issues in \Rails. This is because the source for + # templates are usually read from disk, and Ruby (like most + # encoding-aware programming languages) assumes that the + # String retrieved through File IO is encoded in the + # default_external encoding. In \Rails, the default + # default_external encoding is UTF-8. + # + # As a result, if a user saves their template as ISO-8859-1 + # (for instance, using a non-Unicode-aware text editor), + # and uses characters outside of the ASCII range, their + # users will see diamonds with question marks in them in + # the browser. + # + # For the rest of this documentation, when we say "UTF-8", + # we mean "UTF-8 or whatever the default_internal encoding + # is set to". By default, it will be UTF-8. + # + # To mitigate this problem, we use a few strategies: + # 1. If the source is not valid UTF-8, we raise an exception + # when the template is compiled to alert the user + # to the problem. + # 2. The user can specify the encoding using Ruby-style + # encoding comments in any template engine. If such + # a comment is supplied, \Rails will apply that encoding + # to the resulting compiled source returned by the + # template handler. + # 3. In all cases, we transcode the resulting String to + # the UTF-8. + # + # This means that other parts of \Rails can always assume + # that templates are encoded in UTF-8, even if the original + # source of the template was not UTF-8. + # + # From a user's perspective, the easiest thing to do is + # to save your templates as UTF-8. If you do this, you + # do not need to do anything else for things to "just work". + # + # === Instructions for template handlers + # + # The easiest thing for you to do is to simply ignore + # encodings. \Rails will hand you the template source + # as the default_internal (generally UTF-8), raising + # an exception for the user before sending the template + # to you if it could not determine the original encoding. + # + # For the greatest simplicity, you can support only + # UTF-8 as the default_internal. This means + # that from the perspective of your handler, the + # entire pipeline is just UTF-8. + # + # === Advanced: Handlers with alternate metadata sources + # + # If you want to provide an alternate mechanism for + # specifying encodings (like ERB does via <%# encoding: ... %>), + # you may indicate that you will handle encodings yourself + # by implementing handles_encoding? on your handler. + # + # If you do, \Rails will not try to encode the String + # into the default_internal, passing you the unaltered + # bytes tagged with the assumed encoding (from + # default_external). + # + # In this case, make sure you return a String from + # your handler encoded in the default_internal. Since + # you are handling out-of-band metadata, you are + # also responsible for alerting the user to any + # problems with converting the user's data to + # the default_internal. + # + # To do so, simply raise +WrongEncodingError+ as follows: + # + # raise WrongEncodingError.new( + # problematic_string, + # expected_encoding + # ) + + ## + # :method: local_assigns + # + # Returns a hash with the defined local variables. + # + # Given this sub template rendering: + # + # <%= render "application/header", { headline: "Welcome", person: person } %> + # + # You can use +local_assigns+ in the sub templates to access the local variables: + # + # local_assigns[:headline] # => "Welcome" + # + # Each key in +local_assigns+ is available as a partial-local variable: + # + # local_assigns[:headline] # => "Welcome" + # headline # => "Welcome" + # + # Since +local_assigns+ is a +Hash+, it's compatible with Ruby 3.1's pattern + # matching assignment operator: + # + # local_assigns => { headline:, **options } + # headline # => "Welcome" + # options # => {} + # + # Pattern matching assignment also supports variable renaming: + # + # local_assigns => { headline: title } + # title # => "Welcome" + # + # If a template refers to a variable that isn't passed into the view as part + # of the locals: { ... } Hash, the template will raise an + # +ActionView::Template::Error+: + # + # <%# => raises ActionView::Template::Error %> + # <% alerts.each do |alert| %> + #

    <%= alert %>

    + # <% end %> + # + # Since +local_assigns+ returns a +Hash+ instance, you can conditionally + # read a variable, then fall back to a default value when + # the key isn't part of the locals: { ... } options: + # + # <% local_assigns.fetch(:alerts, []).each do |alert| %> + #

    <%= alert %>

    + # <% end %> + # + # Combining Ruby 3.1's pattern matching assignment with calls to + # +Hash#with_defaults+ enables compact partial-local variable + # assignments: + # + # <% local_assigns.with_defaults(alerts: []) => { headline:, alerts: } %> + # + #

    <%= headline %>

    + # + # <% alerts.each do |alert| %> + #

    <%= alert %>

    + # <% end %> + # + # By default, templates will accept any locals as keyword arguments + # and make them available to local_assigns. To restrict what + # local_assigns a template will accept, add a locals: magic comment: + # + # <%# locals: (headline:, alerts: []) %> + # + #

    <%= headline %>

    + # + # <% alerts.each do |alert| %> + #

    <%= alert %>

    + # <% end %> + # + # Read more about strict locals in {Action View Overview}[https://guides.rubyonrails.org/action_view_overview.html#strict-locals] + # in the guides. + + eager_autoload do + autoload :Error + autoload :RawFile + autoload :Renderable + autoload :Handlers + autoload :HTML + autoload :Inline + autoload :Types + autoload :Sources + autoload :Text + autoload :Types + end + + extend Template::Handlers + + singleton_class.attr_accessor :frozen_string_literal + @frozen_string_literal = false + + class << self # :nodoc: + def mime_types_implementation=(implementation) + # This method isn't thread-safe, but it's not supposed + # to be called after initialization + if self::Types != implementation + remove_const(:Types) + const_set(:Types, implementation) + end + end + end + + attr_reader :identifier, :handler + attr_reader :variable, :format, :variant, :virtual_path + + NONE = Object.new + + def initialize(source, identifier, handler, locals:, format: nil, variant: nil, virtual_path: nil) + @source = source.dup + @identifier = identifier + @handler = handler + @compiled = false + @locals = locals + @virtual_path = virtual_path + + @variable = if @virtual_path + base = @virtual_path.end_with?("/") ? "" : ::File.basename(@virtual_path) + base =~ /\A_?(.*?)(?:\.\w+)*\z/ + $1.to_sym + end + + @format = format + @variant = variant + @compile_mutex = Mutex.new + @strict_locals = NONE + @strict_local_keys = nil + @type = nil + end + + # The locals this template has been or will be compiled for, or nil if this + # is a strict locals template. + def locals + if strict_locals? + nil + else + @locals + end + end + + def spot(location) # :nodoc: + node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(location) + found = + if RubyVM::InstructionSequence.compile("").to_a[4][:parser] == :prism + require "prism" + + if Prism::VERSION >= "1.0.0" + result = Prism.parse(compiled_source).value + result.breadth_first_search { |node| node.node_id == node_id } + end + else + node = RubyVM::AbstractSyntaxTree.parse(compiled_source, keep_script_lines: true) + find_node_by_id(node, node_id) + end + + ErrorHighlight.spot(found) if found + end + + # Translate an error location returned by ErrorHighlight to the correct + # source location inside the template. + def translate_location(backtrace_location, spot) + if handler.respond_to?(:translate_location) + handler.translate_location(spot, backtrace_location, encode!) || spot + else + spot + end + end + + # Returns whether the underlying handler supports streaming. If so, + # a streaming buffer *may* be passed when it starts rendering. + def supports_streaming? + handler.respond_to?(:supports_streaming?) && handler.supports_streaming? + end + + # Render a template. If the template was not compiled yet, it is done + # exactly before rendering. + # + # This method is instrumented as "!render_template.action_view". Notice that + # we use a bang in this instrumentation because you don't want to + # consume this in production. This is only slow if it's being listened to. + def render(view, locals, buffer = nil, implicit_locals: [], add_to_stack: true, &block) + instrument_render_template do + compile!(view) + + if strict_locals? && @strict_local_keys && !implicit_locals.empty? + locals_to_ignore = implicit_locals - @strict_local_keys + locals.except!(*locals_to_ignore) + end + + if buffer + view._run(method_name, self, locals, buffer, add_to_stack: add_to_stack, has_strict_locals: strict_locals?, &block) + nil + else + result = view._run(method_name, self, locals, OutputBuffer.new, add_to_stack: add_to_stack, has_strict_locals: strict_locals?, &block) + result.is_a?(OutputBuffer) ? result.to_s : result + end + end + rescue => e + handle_render_error(view, e) + end + + def type + @type ||= Types[format] + end + + def short_identifier + @short_identifier ||= defined?(Rails.root) ? identifier.delete_prefix("#{Rails.root}/") : identifier + end + + def inspect + "#<#{self.class.name} #{short_identifier} locals=#{locals.inspect}>" + end + + def source + @source.to_s + end + + LEADING_ENCODING_REGEXP = /\A#{ENCODING_FLAG}/ + private_constant :LEADING_ENCODING_REGEXP + + # This method is responsible for properly setting the encoding of the + # source. Until this point, we assume that the source is BINARY data. + # If no additional information is supplied, we assume the encoding is + # the same as Encoding.default_external. + # + # The user can also specify the encoding via a comment on the first + # line of the template (# encoding: NAME-OF-ENCODING). This will work + # with any template engine, as we process out the encoding comment + # before passing the source on to the template engine, leaving a + # blank line in its stead. + def encode! + source = self.source + + return source unless source.encoding == Encoding::BINARY + + # Look for # encoding: *. If we find one, we'll encode the + # String in that encoding, otherwise, we'll use the + # default external encoding. + if source.sub!(LEADING_ENCODING_REGEXP, "") + encoding = magic_encoding = $1 + else + encoding = Encoding.default_external + end + + # Tag the source with the default external encoding + # or the encoding specified in the file + source.force_encoding(encoding) + + # If the user didn't specify an encoding, and the handler + # handles encodings, we simply pass the String as is to + # the handler (with the default_external tag) + if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding? + source + # Otherwise, if the String is valid in the encoding, + # encode immediately to default_internal. This means + # that if a handler doesn't handle encodings, it will + # always get Strings in the default_internal + elsif source.valid_encoding? + source.encode! + # Otherwise, since the String is invalid in the encoding + # specified, raise an exception + else + raise WrongEncodingError.new(source, encoding) + end + end + + # This method is responsible for marking a template as having strict locals + # which means the template can only accept the locals defined in a magic + # comment. For example, if your template accepts the locals +title+ and + # +comment_count+, add the following to your template file: + # + # <%# locals: (title: "Default title", comment_count: 0) %> + # + # Strict locals are useful for validating template arguments and for + # specifying defaults. + def strict_locals! + if @strict_locals == NONE + self.source.sub!(STRICT_LOCALS_REGEX, "") + @strict_locals = $1 + + return if @strict_locals.nil? # Magic comment not found + + @strict_locals = "**nil" if @strict_locals.blank? + end + + @strict_locals + end + + # Returns whether a template is using strict locals. + def strict_locals? + strict_locals! + end + + # Exceptions are marshalled when using the parallel test runner with DRb, so we need + # to ensure that references to the template object can be marshalled as well. This means forgoing + # the marshalling of the compiler mutex and instantiating that again on unmarshalling. + def marshal_dump # :nodoc: + [ @source, @identifier, @handler, @compiled, @locals, @virtual_path, @format, @variant ] + end + + def marshal_load(array) # :nodoc: + @source, @identifier, @handler, @compiled, @locals, @virtual_path, @format, @variant = *array + @compile_mutex = Mutex.new + end + + def method_name # :nodoc: + @method_name ||= begin + m = +"_#{identifier_method_name}__#{@identifier.hash}_#{__id__}" + m.tr!("-", "_") + m + end + end + + private + def find_node_by_id(node, node_id) + return node if node.node_id == node_id + + node.children.grep(node.class).each do |child| + found = find_node_by_id(child, node_id) + return found if found + end + + false + end + + # Compile a template. This method ensures a template is compiled + # just once and removes the source after it is compiled. + def compile!(view) + return if @compiled + + # Templates can be used concurrently in threaded environments + # so compilation and any instance variable modification must + # be synchronized + @compile_mutex.synchronize do + # Any thread holding this lock will be compiling the template needed + # by the threads waiting. So re-check the @compiled flag to avoid + # re-compilation + return if @compiled + + mod = view.compiled_method_container + + instrument("!compile_template") do + compile(mod) + end + + @compiled = true + end + end + + # This method compiles the source of the template. The compilation of templates + # involves setting strict_locals! if applicable, encoding the template, and setting + # frozen string literal. + def compiled_source + set_strict_locals = strict_locals! + source = encode! + code = @handler.call(self, source) + + method_arguments = + if set_strict_locals + if set_strict_locals.include?("&") + "local_assigns, output_buffer, #{set_strict_locals}" + else + "local_assigns, output_buffer, #{set_strict_locals}, &_" + end + else + "local_assigns, output_buffer, &_" + end + + # Make sure that the resulting String to be eval'd is in the + # encoding of the code + source = +<<-end_src + def #{method_name}(#{method_arguments}) + @virtual_path = #{@virtual_path.inspect};#{locals_code};#{code} + end + end_src + + # Make sure the source is in the encoding of the returned code + source.force_encoding(code.encoding) + + # In case we get back a String from a handler that is not in + # BINARY or the default_internal, encode it to the default_internal + source.encode! + + # Now, validate that the source we got back from the template + # handler is valid in the default_internal. This is for handlers + # that handle encoding but screw up + unless source.valid_encoding? + raise WrongEncodingError.new(source, Encoding.default_internal) + end + + if Template.frozen_string_literal + "# frozen_string_literal: true\n#{source}" + else + source + end + end + + # Among other things, this method is responsible for properly setting + # the encoding of the compiled template. + # + # If the template engine handles encodings, we send the encoded + # String to the engine without further processing. This allows + # the template engine to support additional mechanisms for + # specifying the encoding. For instance, ERB supports <%# encoding: %> + # + # Otherwise, after we figure out the correct encoding, we then + # encode the source into Encoding.default_internal. + # In general, this means that templates will be UTF-8 inside of Rails, + # regardless of the original source encoding. + def compile(mod) + begin + mod.module_eval(compiled_source, identifier, offset) + rescue SyntaxError + # Account for when code in the template is not syntactically valid; e.g. if we're using + # ERB and the user writes <%= foo( %>, attempting to call a helper `foo` and interpolate + # the result into the template, but missing an end parenthesis. + raise SyntaxErrorInTemplate.new(self, encode!) + end + + return unless strict_locals? + + parameters = mod.instance_method(method_name).parameters + parameters -= [[:req, :local_assigns], [:req, :output_buffer]] + + # Check compiled method parameters to ensure that only kwargs + # were provided as strict locals, preventing `locals: (foo, *foo)` etc + # and allowing `locals: (foo:)`. + non_kwarg_parameters = parameters.select do |parameter| + ![:keyreq, :key, :keyrest, :nokey].include?(parameter[0]) + end + + non_kwarg_parameters.pop if non_kwarg_parameters.last == %i(block _) + + unless non_kwarg_parameters.empty? + mod.undef_method(method_name) + + raise ArgumentError.new( + "#{non_kwarg_parameters.map { |_, name| "`#{name}`" }.to_sentence} set as non-keyword " \ + "#{'argument'.pluralize(non_kwarg_parameters.length)} for #{short_identifier}. " \ + "Locals can only be set as keyword arguments." + ) + end + + unless parameters.any? { |type, _| type == :keyrest } + parameters.map!(&:last) + parameters.sort! + @strict_local_keys = parameters.freeze + end + end + + def offset + if Template.frozen_string_literal + -1 + else + 0 + end + end + + def handle_render_error(view, e) + if e.is_a?(Template::Error) + e.sub_template_of(self) + raise e + else + raise Template::Error.new(self) + end + end + + RUBY_RESERVED_KEYWORDS = ::ActiveSupport::Delegation::RUBY_RESERVED_KEYWORDS + private_constant :RUBY_RESERVED_KEYWORDS + + def locals_code + return "" if strict_locals? + + # Only locals with valid variable names get set directly. Others will + # still be available in local_assigns. + locals = @locals - RUBY_RESERVED_KEYWORDS + + locals = locals.grep(/\A(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/) + + # Assign for the same variable is to suppress unused variable warning + locals.each_with_object(+"") { |key, code| code << "#{key} = local_assigns[:#{key}]; #{key} = #{key};" } + end + + def identifier_method_name + short_identifier.tr("^a-z_", "_") + end + + def instrument(action, &block) # :doc: + ActiveSupport::Notifications.instrument("#{action}.action_view", instrument_payload, &block) + end + + def instrument_render_template(&block) + ActiveSupport::Notifications.instrument("!render_template.action_view", instrument_payload, &block) + end + + def instrument_payload + { virtual_path: @virtual_path, identifier: @identifier } + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/error.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/error.rb new file mode 100644 index 00000000..3bb757bb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/error.rb @@ -0,0 +1,275 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" +require "active_support/syntax_error_proxy" + +module ActionView + # = Action View Errors + class ActionViewError < StandardError # :nodoc: + end + + class EncodingError < StandardError # :nodoc: + end + + class WrongEncodingError < EncodingError # :nodoc: + def initialize(string, encoding) + @string, @encoding = string, encoding + end + + def message + @string.force_encoding(Encoding::ASCII_8BIT) + "Your template was not saved as valid #{@encoding}. Please " \ + "either specify #{@encoding} as the encoding for your template " \ + "in your text editor, or mark the template with its " \ + "encoding by inserting the following as the first line " \ + "of the template:\n\n# encoding: .\n\n" \ + "The source of your template was:\n\n#{@string}" + end + end + + class StrictLocalsError < ArgumentError # :nodoc: + def initialize(argument_error, template) + message = argument_error.message. + gsub("unknown keyword:", "unknown local:"). + gsub("missing keyword:", "missing local:"). + gsub("no keywords accepted", "no locals accepted"). + concat(" for #{template.short_identifier}") + super(message) + end + end + + class MissingTemplate < ActionViewError # :nodoc: + attr_reader :path, :paths, :prefixes, :partial + + def initialize(paths, path, prefixes, partial, details, *) + if partial && path.present? + path = path.sub(%r{([^/]+)$}, "_\\1") + end + + @path = path + @paths = paths + @prefixes = Array(prefixes) + @partial = partial + template_type = if partial + "partial" + elsif /layouts/i.match?(path) + "layout" + else + "template" + end + + searched_paths = @prefixes.map { |prefix| [prefix, path].join("/") } + + out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}.\n\nSearched in:\n" + out += paths.compact.map { |p| " * #{p.to_s.inspect}\n" }.join + super out + end + + if defined?(DidYouMean::Correctable) && defined?(DidYouMean::Jaro) + include DidYouMean::Correctable + + class Results # :nodoc: + Result = Struct.new(:path, :score) + + def initialize(size) + @size = size + @results = [] + end + + def to_a + @results.map(&:path) + end + + def should_record?(score) + if @results.size < @size + true + else + score < @results.last.score + end + end + + def add(path, score) + if should_record?(score) + @results << Result.new(path, score) + @results.sort_by!(&:score) + @results.pop if @results.size > @size + end + end + end + + # Apps may have thousands of candidate templates so we attempt to + # generate the suggestions as efficiently as possible. + # First we split templates into prefixes and basenames, so that those can + # be matched separately. + def corrections + candidates = paths.flat_map(&:all_template_paths).uniq + + if partial + candidates.select!(&:partial?) + else + candidates.reject!(&:partial?) + end + + # Group by possible prefixes + files_by_dir = candidates.group_by(&:prefix) + files_by_dir.transform_values! do |files| + files.map do |file| + # Remove prefix + File.basename(file.to_s) + end + end + + # No suggestions if there's an exact match, but wrong details + if prefixes.any? { |prefix| files_by_dir[prefix]&.include?(path) } + return [] + end + + cached_distance = Hash.new do |h, args| + h[args] = -DidYouMean::Jaro.distance(*args) + end + + results = Results.new(6) + + files_by_dir.keys.index_with do |dirname| + prefixes.map do |prefix| + cached_distance[[prefix, dirname]] + end.min + end.sort_by(&:last).each do |dirname, dirweight| + # If our directory's score makes it impossible to find a better match + # we can prune this search branch. + next unless results.should_record?(dirweight - 1.0) + + files = files_by_dir[dirname] + + files.each do |file| + fileweight = cached_distance[[path, file]] + score = dirweight + fileweight + + results.add(File.join(dirname, file), score) + end + end + + if partial + results.to_a.map { |res| res.sub(%r{_([^/]+)\z}, "\\1") } + else + results.to_a + end + end + end + end + + class Template + # The Template::Error exception is raised when the compilation or rendering of the template + # fails. This exception then gathers a bunch of intimate details and uses it to report a + # precise exception message. + class Error < ActionViewError # :nodoc: + SOURCE_CODE_RADIUS = 3 + + # Override to prevent #cause resetting during re-raise. + attr_reader :cause + + attr_reader :template + + def initialize(template) + super($!.message) + @cause = $! + if @cause.is_a?(SyntaxError) + @cause = ActiveSupport::SyntaxErrorProxy.new(@cause) + end + @template, @sub_templates = template, nil + end + + def backtrace + @cause.backtrace + end + + def backtrace_locations + @cause.backtrace_locations + end + + def file_name + @template.identifier + end + + def sub_template_message + if @sub_templates + "Trace of template inclusion: " + + @sub_templates.collect(&:inspect).join(", ") + else + "" + end + end + + def source_extract(indentation = 0) + return [] unless num = line_number + num = num.to_i + + source_code = @template.encode!.split("\n") + + start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max + end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min + + indent = end_on_line.to_s.size + indentation + return [] unless source_code = source_code[start_on_line..end_on_line] + + formatted_code_for(source_code, start_on_line, indent) + end + + def sub_template_of(template_path) + @sub_templates ||= [] + @sub_templates << template_path + end + + def line_number + @line_number ||= + if file_name + regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/ + $1 if message =~ regexp || backtrace.find { |line| line =~ regexp } + end + end + + def annotated_source_code + source_extract(4) + end + + private + def source_location + if line_number + "on line ##{line_number} of " + else + "in " + end + file_name + end + + def formatted_code_for(source_code, line_counter, indent) + indent_template = "%#{indent}s: %s" + source_code.map do |line| + line_counter += 1 + indent_template % [line_counter, line] + end + end + end + end + + TemplateError = Template::Error + + class SyntaxErrorInTemplate < TemplateError # :nodoc: + def initialize(template, offending_code_string) + @offending_code_string = offending_code_string + super(template) + end + + def message + <<~MESSAGE + Encountered a syntax error while rendering template: check #{@offending_code_string} + MESSAGE + end + + def annotated_source_code + @offending_code_string.split("\n").map.with_index(1) { |line, index| + indentation = " " * 4 + "#{index}:#{indentation}#{line}" + } + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/handlers.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/handlers.rb new file mode 100644 index 00000000..931a412e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/handlers.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module ActionView # :nodoc: + class Template # :nodoc: + # = Action View Template Handlers + module Handlers # :nodoc: + autoload :Raw, "action_view/template/handlers/raw" + autoload :ERB, "action_view/template/handlers/erb" + autoload :Html, "action_view/template/handlers/html" + autoload :Builder, "action_view/template/handlers/builder" + + def self.extended(base) + base.register_default_template_handler :raw, Raw.new + base.register_template_handler :erb, ERB.new + base.register_template_handler :html, Html.new + base.register_template_handler :builder, Builder.new + base.register_template_handler :ruby, lambda { |_, source| source } + end + + @@template_handlers = {} + @@default_template_handlers = nil + + def self.extensions + @@template_extensions ||= @@template_handlers.keys + end + + # Register an object that knows how to handle template files with the given + # extensions. This can be used to implement new template types. + # The handler must respond to +:call+, which will be passed the template + # and should return the rendered template as a String. + def register_template_handler(*extensions, handler) + raise(ArgumentError, "Extension is required") if extensions.empty? + extensions.each do |extension| + @@template_handlers[extension.to_sym] = handler + end + @@template_extensions = nil + end + + # Opposite to register_template_handler. + def unregister_template_handler(*extensions) + extensions.each do |extension| + handler = @@template_handlers.delete extension.to_sym + @@default_template_handlers = nil if @@default_template_handlers == handler + end + @@template_extensions = nil + end + + def template_handler_extensions + @@template_handlers.keys.map(&:to_s).sort + end + + def registered_template_handler(extension) + extension && @@template_handlers[extension.to_sym] + end + + def register_default_template_handler(extension, klass) + register_template_handler(extension, klass) + @@default_template_handlers = klass + end + + def handler_for_extension(extension) + registered_template_handler(extension) || @@default_template_handlers + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/handlers/builder.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/handlers/builder.rb new file mode 100644 index 00000000..4ae350fb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/handlers/builder.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module ActionView + module Template::Handlers + class Builder + class_attribute :default_format, default: :xml + + def call(template, source) + require_engine + # the double assignment is to silence "assigned but unused variable" warnings + "xml = xml = ::Builder::XmlMarkup.new(indent: 2, target: output_buffer.raw);" \ + "#{source};" \ + "output_buffer.to_s" + end + + private + def require_engine # :doc: + @required ||= begin + require "builder" + true + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/handlers/erb.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/handlers/erb.rb new file mode 100644 index 00000000..286e331f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/handlers/erb.rb @@ -0,0 +1,165 @@ +# frozen_string_literal: true + +require "strscan" +require "active_support/core_ext/erb/util" + +module ActionView + class Template + module Handlers + class ERB + autoload :Erubi, "action_view/template/handlers/erb/erubi" + + # Specify trim mode for the ERB compiler. Defaults to '-'. + # See ERB documentation for suitable values. + class_attribute :erb_trim_mode, default: "-" + + # Default implementation used. + class_attribute :erb_implementation, default: Erubi + + # Do not escape templates of these mime types. + class_attribute :escape_ignore_list, default: ["text/plain"] + + # Strip trailing newlines from rendered output + class_attribute :strip_trailing_newlines, default: false + + ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*") + + LocationParsingError = Class.new(StandardError) # :nodoc: + + def self.call(template, source) + new.call(template, source) + end + + def supports_streaming? + true + end + + def handles_encoding? + true + end + + # Translate an error location returned by ErrorHighlight to the correct + # source location inside the template. + def translate_location(spot, backtrace_location, source) + # Tokenize the source line + source_lines = source.lines + return nil if source_lines.size < backtrace_location.lineno + tokens = ::ERB::Util.tokenize(source_lines[backtrace_location.lineno - 1]) + new_first_column = find_offset(spot[:snippet], tokens, spot[:first_column]) + lineno_delta = spot[:first_lineno] - backtrace_location.lineno + spot[:first_lineno] -= lineno_delta + spot[:last_lineno] -= lineno_delta + + column_delta = spot[:first_column] - new_first_column + spot[:first_column] -= column_delta + spot[:last_column] -= column_delta + spot[:script_lines] = source_lines + + spot + rescue NotImplementedError, LocationParsingError + nil + end + + def call(template, source) + # First, convert to BINARY, so in case the encoding is + # wrong, we can still find an encoding tag + # (<%# encoding %>) inside the String using a regular + # expression + template_source = source.b + + erb = template_source.gsub(ENCODING_TAG, "") + encoding = $2 + + erb.force_encoding valid_encoding(source.dup, encoding) + + # Always make sure we return a String in the default_internal + erb.encode! + + # Strip trailing newlines from the template if enabled + erb.chomp! if strip_trailing_newlines + + options = { + escape: (self.class.escape_ignore_list.include? template.type), + trim: (self.class.erb_trim_mode == "-") + } + + if ActionView::Base.annotate_rendered_view_with_filenames && template.format == :html + options[:preamble] = "@output_buffer.safe_append='';" + options[:postamble] = "@output_buffer.safe_append='';@output_buffer" + end + + self.class.erb_implementation.new(erb, options).src + end + + private + def valid_encoding(string, encoding) + # If a magic encoding comment was found, tag the + # String with this encoding. This is for a case + # where the original String was assumed to be, + # for instance, UTF-8, but a magic comment + # proved otherwise + string.force_encoding(encoding) if encoding + + # If the String is valid, return the encoding we found + return string.encoding if string.valid_encoding? + + # Otherwise, raise an exception + raise WrongEncodingError.new(string, string.encoding) + end + + # Find which token in the source template spans the byte range that + # contains the error_column, then return the offset compared to the + # original source template. + # + # Iterate consecutive pairs of CODE or TEXT tokens, requiring + # a match of the first token before matching either token. + # + # For example, if we want to find tokens A, B, C, we do the following: + # 1. Find a match for A: test error_column or advance scanner. + # 2. Find a match for B or A: + # a. If B: start over with next token set (B, C). + # b. If A: test error_column or advance scanner. + # c. Otherwise: Advance 1 byte + # + # Prioritize matching the next token over the current token once + # a match for the current token has been found. This is to prevent + # the current token from looping past the next token if they both + # match (i.e. if the current token is a single space character). + def find_offset(compiled, source_tokens, error_column) + compiled = StringScanner.new(compiled) + offset_source_tokens(source_tokens).each_cons(2) do |(name, str, offset), (_, next_str, _)| + matched_str = false + + until compiled.eos? + if matched_str && next_str && compiled.match?(next_str) + break + elsif compiled.match?(str) + matched_str = true + + if name == :CODE && compiled.pos <= error_column && compiled.pos + str.bytesize >= error_column + return error_column - compiled.pos + offset + end + + compiled.pos += str.bytesize + else + compiled.pos += 1 + end + end + end + + raise LocationParsingError, "Couldn't find code snippet" + end + + def offset_source_tokens(source_tokens) + source_offset = 0 + with_offset = source_tokens.filter_map do |(name, str)| + result = [name, str, source_offset] if name == :CODE || name == :TEXT + source_offset += str.bytesize + result + end + with_offset << [:EOS, nil, source_offset] + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/handlers/erb/erubi.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/handlers/erb/erubi.rb new file mode 100644 index 00000000..b26a38ac --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/handlers/erb/erubi.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require "erubi" + +module ActionView + class Template + module Handlers + class ERB + class Erubi < ::Erubi::Engine + # :nodoc: all + def initialize(input, properties = {}) + @newline_pending = 0 + + # Dup properties so that we don't modify argument + properties = Hash[properties] + + properties[:bufvar] ||= "@output_buffer" + properties[:preamble] ||= "" + properties[:postamble] ||= "#{properties[:bufvar]}" + + # Tell Eruby that whether template will be compiled with `frozen_string_literal: true` + properties[:freeze_template_literals] = !Template.frozen_string_literal + + properties[:escapefunc] = "" + + super + end + + private + def add_text(text) + return if text.empty? + + if text == "\n" + @newline_pending += 1 + else + with_buffer do + src << ".safe_append='" + src << "\n" * @newline_pending if @newline_pending > 0 + src << text.gsub(/['\\]/, '\\\\\&') << @text_end + end + @newline_pending = 0 + end + end + + BLOCK_EXPR = /((\s|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/ + + def add_expression(indicator, code) + flush_newline_if_pending(src) + + with_buffer do + if (indicator == "==") || @escape + src << ".safe_expr_append=" + else + src << ".append=" + end + + if BLOCK_EXPR.match?(code) + src << " " << code + else + src << "(" << code << ")" + end + end + end + + def add_code(code) + flush_newline_if_pending(src) + super + end + + def add_postamble(_) + flush_newline_if_pending(src) + super + end + + def flush_newline_if_pending(src) + if @newline_pending > 0 + with_buffer { src << ".safe_append='#{"\n" * @newline_pending}" << @text_end } + @newline_pending = 0 + end + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/handlers/html.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/handlers/html.rb new file mode 100644 index 00000000..65857d85 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/handlers/html.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module ActionView + module Template::Handlers + class Html < Raw + def call(template, source) + "ActionView::OutputBuffer.new #{super}" + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/handlers/raw.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/handlers/raw.rb new file mode 100644 index 00000000..57ebb169 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/handlers/raw.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module ActionView + module Template::Handlers + class Raw + def call(template, source) + "#{source.inspect}.html_safe;" + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/html.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/html.rb new file mode 100644 index 00000000..0d0529b8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/html.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module ActionView # :nodoc: + class Template # :nodoc: + # = Action View HTML Template + class HTML # :nodoc: + attr_reader :type + + def initialize(string, type) + @string = string.to_s + @type = type + end + + def identifier + "html template" + end + + alias_method :inspect, :identifier + + def to_str + ERB::Util.h(@string) + end + + def render(*args) + to_str + end + + def format + @type + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/inline.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/inline.rb new file mode 100644 index 00000000..8a826a40 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/inline.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module ActionView # :nodoc: + class Template # :nodoc: + class Inline < Template # :nodoc: + # This finalizer is needed (and exactly with a proc inside another proc) + # otherwise templates leak in development. + Finalizer = proc do |method_name, mod| # :nodoc: + proc do + mod.module_eval do + remove_possible_method method_name + end + end + end + + def compile(mod) + super + ObjectSpace.define_finalizer(self, Finalizer[method_name, mod]) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/raw_file.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/raw_file.rb new file mode 100644 index 00000000..47ce9186 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/raw_file.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module ActionView # :nodoc: + class Template # :nodoc: + # = Action View RawFile Template + class RawFile # :nodoc: + attr_accessor :type, :format + + def initialize(filename) + @filename = filename.to_s + extname = ::File.extname(filename).delete(".") + @type = Template::Types[extname] || Template::Types[:text] + @format = @type.symbol + end + + def identifier + @filename + end + + def render(*args) + ::File.read(@filename) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/renderable.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/renderable.rb new file mode 100644 index 00000000..7800b498 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/renderable.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module ActionView + class Template + # = Action View Renderable Template for objects that respond to #render_in + class Renderable # :nodoc: + def initialize(renderable) + @renderable = renderable + end + + def identifier + @renderable.class.name + end + + def render(context, *args) + @renderable.render_in(context) + rescue NoMethodError + if !@renderable.respond_to?(:render_in) + raise ArgumentError, "'#{@renderable.inspect}' is not a renderable object. It must implement #render_in." + else + raise + end + end + + def format + @renderable.try(:format) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/resolver.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/resolver.rb new file mode 100644 index 00000000..f07f97d5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/resolver.rb @@ -0,0 +1,212 @@ +# frozen_string_literal: true + +require "pathname" +require "active_support/core_ext/class" +require "active_support/core_ext/module/attribute_accessors" +require "action_view/template" +require "concurrent/map" + +module ActionView + # = Action View Resolver + class Resolver + class PathParser # :nodoc: + ParsedPath = Struct.new(:path, :details) + + def build_path_regex + handlers = Regexp.union(Template::Handlers.extensions.map(&:to_s)) + formats = Regexp.union(Template::Types.symbols.map(&:to_s)) + available_locales = I18n.available_locales.map(&:to_s) + regular_locales = [/[a-z]{2}(?:[-_][A-Z]{2})?/] + locales = Regexp.union(available_locales + regular_locales) + variants = "[^.]*" + + %r{ + \A + (?:(?.*)/)? + (?_)? + (?.*?) + (?:\.(?#{locales}))?? + (?:\.(?#{formats}))?? + (?:\+(?#{variants}))?? + (?:\.(?#{handlers}))? + \z + }x + end + + def parse(path) + @regex ||= build_path_regex + match = @regex.match(path) + path = TemplatePath.build(match[:action], match[:prefix] || "", !!match[:partial]) + details = TemplateDetails.new( + match[:locale]&.to_sym, + match[:handler]&.to_sym, + match[:format]&.to_sym, + match[:variant]&.to_sym + ) + ParsedPath.new(path, details) + end + end + + cattr_accessor :caching, default: true + + class << self + alias :caching? :caching + end + + def clear_cache + end + + # Normalizes the arguments and passes it on to find_templates. + def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = []) + _find_all(name, prefix, partial, details, key, locals) + end + + def built_templates # :nodoc: + # Used for error pages + [] + end + + def all_template_paths # :nodoc: + # Not implemented by default + [] + end + + private + def _find_all(name, prefix, partial, details, key, locals) + find_templates(name, prefix, partial, details, locals) + end + + delegate :caching?, to: :class + + # This is what child classes implement. No defaults are needed + # because Resolver guarantees that the arguments are present and + # normalized. + def find_templates(name, prefix, partial, details, locals = []) + raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, locals = []) method" + end + end + + # A resolver that loads files from the filesystem. + class FileSystemResolver < Resolver + attr_reader :path + + def initialize(path) + raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver) + @unbound_templates = Concurrent::Map.new + @path_parser = PathParser.new + @path = File.expand_path(path) + super() + end + + def clear_cache + @unbound_templates.clear + @path_parser = PathParser.new + super + end + + def to_s + @path.to_s + end + alias :to_path :to_s + + def eql?(resolver) + self.class.equal?(resolver.class) && to_path == resolver.to_path + end + alias :== :eql? + + def all_template_paths # :nodoc: + paths = template_glob("**/*") + paths.map do |filename| + filename.from(@path.size + 1).remove(/\.[^\/]*\z/) + end.uniq.map do |filename| + TemplatePath.parse(filename) + end + end + + def built_templates # :nodoc: + @unbound_templates.values.flatten.flat_map(&:built_templates) + end + + private + def _find_all(name, prefix, partial, details, key, locals) + requested_details = key || TemplateDetails::Requested.new(**details) + cache = key ? @unbound_templates : Concurrent::Map.new + + unbound_templates = + cache.compute_if_absent(TemplatePath.virtual(name, prefix, partial)) do + path = TemplatePath.build(name, prefix, partial) + unbound_templates_from_path(path) + end + + filter_and_sort_by_details(unbound_templates, requested_details).map do |unbound_template| + unbound_template.bind_locals(locals) + end + end + + def source_for_template(template) + Template::Sources::File.new(template) + end + + def build_unbound_template(template) + parsed = @path_parser.parse(template.from(@path.size + 1)) + details = parsed.details + source = source_for_template(template) + + UnboundTemplate.new( + source, + template, + details: details, + virtual_path: parsed.path.virtual, + ) + end + + def unbound_templates_from_path(path) + if path.name.include?(".") + return [] + end + + # Instead of checking for every possible path, as our other globs would + # do, scan the directory for files with the right prefix. + paths = template_glob("#{escape_entry(path.to_s)}*") + + paths.map do |path| + build_unbound_template(path) + end.select do |template| + # Select for exact virtual path match, including case sensitivity + template.virtual_path == path.virtual + end + end + + def filter_and_sort_by_details(templates, requested_details) + filtered_templates = templates.select do |template| + template.details.matches?(requested_details) + end + + if filtered_templates.count > 1 + filtered_templates.sort_by! do |template| + template.details.sort_key_for(requested_details) + end + end + + filtered_templates + end + + # Safe glob within @path + def template_glob(glob) + query = File.join(escape_entry(@path), glob) + path_with_slash = File.join(@path, "") + + Dir.glob(query).filter_map do |filename| + filename = File.expand_path(filename) + next if File.directory?(filename) + next unless filename.start_with?(path_with_slash) + + filename + end + end + + def escape_entry(entry) + entry.gsub(/[*?{}\[\]]/, '\\\\\\&') + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/sources.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/sources.rb new file mode 100644 index 00000000..8b895357 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/sources.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module ActionView + class Template + module Sources + extend ActiveSupport::Autoload + + eager_autoload do + autoload :File + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/sources/file.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/sources/file.rb new file mode 100644 index 00000000..2d3a7dec --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/sources/file.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActionView + class Template + module Sources + class File + def initialize(filename) + @filename = filename + end + + def to_s + ::File.binread @filename + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/text.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/text.rb new file mode 100644 index 00000000..c91cd6d8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/text.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module ActionView # :nodoc: + class Template # :nodoc: + # = Action View Text Template + class Text # :nodoc: + attr_accessor :type + + def initialize(string) + @string = string.to_s + end + + def identifier + "text template" + end + + alias_method :inspect, :identifier + + def to_str + @string + end + + def render(*args) + to_str + end + + def format + :text + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/types.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/types.rb new file mode 100644 index 00000000..fdddb20a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template/types.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" + +module ActionView + class Template # :nodoc: + # SimpleType is mostly just a stub implementation for when Action View + # is used without Action Dispatch. + class SimpleType # :nodoc: + @symbols = [ :html, :text, :js, :css, :xml, :json ] + class << self + attr_reader :symbols + + def [](type) + if type.is_a?(self) + type + else + new(type) + end + end + + def valid_symbols?(symbols) # :nodoc + symbols.all? { |s| @symbols.include?(s) } + end + end + + attr_reader :symbol + + def initialize(symbol) + @symbol = symbol.to_sym + end + + def to_s + @symbol.to_s + end + alias to_str to_s + + def ref + @symbol + end + alias to_sym ref + + def ==(type) + @symbol == type.to_sym unless type.blank? + end + end + + Types = SimpleType # :nodoc: + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template_details.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template_details.rb new file mode 100644 index 00000000..0f74fb65 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template_details.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module ActionView + class TemplateDetails # :nodoc: + class Requested + attr_reader :locale, :handlers, :formats, :variants + attr_reader :locale_idx, :handlers_idx, :formats_idx, :variants_idx + + ANY_HASH = Hash.new(1).merge(nil => 0).freeze + + def initialize(locale:, handlers:, formats:, variants:) + @locale = locale + @handlers = handlers + @formats = formats + @variants = variants + + @locale_idx = build_idx_hash(locale) + @handlers_idx = build_idx_hash(handlers) + @formats_idx = build_idx_hash(formats) + if variants == :any + @variants_idx = ANY_HASH + else + @variants_idx = build_idx_hash(variants) + end + end + + private + def build_idx_hash(arr) + [*arr, nil].each_with_index.to_h.freeze + end + end + + attr_reader :locale, :handler, :format, :variant + + def initialize(locale, handler, format, variant) + @locale = locale + @handler = handler + @format = format + @variant = variant + end + + def matches?(requested) + requested.formats_idx[@format] && + requested.locale_idx[@locale] && + requested.variants_idx[@variant] && + requested.handlers_idx[@handler] + end + + def sort_key_for(requested) + [ + requested.formats_idx[@format], + requested.locale_idx[@locale], + requested.variants_idx[@variant], + requested.handlers_idx[@handler] + ] + end + + def handler_class + Template.handler_for_extension(handler) + end + + def format_or_default + format || handler_class.try(:default_format) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template_path.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template_path.rb new file mode 100644 index 00000000..993210cc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/template_path.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module ActionView + # = Action View \TemplatePath + # + # Represents a template path within ActionView's lookup and rendering system, + # like "users/show" + # + # TemplatePath makes it convenient to convert between separate name, prefix, + # partial arguments and the virtual path. + class TemplatePath + attr_reader :name, :prefix, :partial, :virtual + alias_method :partial?, :partial + alias_method :virtual_path, :virtual + + # Convert name, prefix, and partial into a virtual path string + def self.virtual(name, prefix, partial) + if prefix.empty? + "#{partial ? "_" : ""}#{name}" + elsif partial + "#{prefix}/_#{name}" + else + "#{prefix}/#{name}" + end + end + + # Build a TemplatePath form a virtual path + def self.parse(virtual) + if nameidx = virtual.rindex("/") + prefix = virtual[0, nameidx] + name = virtual.from(nameidx + 1) + prefix = prefix[1..] if prefix.start_with?("/") + else + prefix = "" + name = virtual + end + partial = name.start_with?("_") + name = name[1..] if partial + new name, prefix, partial, virtual + end + + # Convert name, prefix, and partial into a TemplatePath + def self.build(name, prefix, partial) + new name, prefix, partial, virtual(name, prefix, partial) + end + + def initialize(name, prefix, partial, virtual) + @name = name + @prefix = prefix + @partial = partial + @virtual = virtual + end + + alias :to_str :virtual + alias :to_s :virtual + + def hash # :nodoc: + @virtual.hash + end + + def eql?(other) # :nodoc: + @virtual == other.virtual + end + alias :== :eql? # :nodoc: + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/test_case.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/test_case.rb new file mode 100644 index 00000000..eb31c676 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/test_case.rb @@ -0,0 +1,449 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/redefine_method" +require "action_controller" +require "action_controller/test_case" +require "action_view" + +require "rails-dom-testing" + +module ActionView + # = Action View Test Case + # + # Read more about ActionView::TestCase in {Testing Rails Applications}[https://guides.rubyonrails.org/testing.html#testing-view-partials] + # in the guides. + class TestCase < ActiveSupport::TestCase + class TestController < ActionController::Base + include ActionDispatch::TestProcess + + attr_accessor :request, :response, :params + + class << self + # Overrides AbstractController::Base#controller_path + attr_accessor :controller_path + end + + def controller_path=(path) + self.class.controller_path = path + end + + def self.controller_name + "test" + end + + def initialize + super + self.class.controller_path = "" + @request = ActionController::TestRequest.create(self.class) + @response = ActionDispatch::TestResponse.new + + @request.env.delete("PATH_INFO") + @params = ActionController::Parameters.new + end + end + + module Behavior + extend ActiveSupport::Concern + + include ActionDispatch::Assertions, ActionDispatch::TestProcess + include Rails::Dom::Testing::Assertions + include ActionController::TemplateAssertions + include ActionView::Context + + include ActionDispatch::Routing::PolymorphicRoutes + + include AbstractController::Helpers + include ActionView::Helpers + include ActionView::RecordIdentifier + include ActionView::RoutingUrlFor + + include ActiveSupport::Testing::ConstantLookup + + delegate :lookup_context, to: :controller + attr_accessor :controller, :request, :output_buffer, :rendered + + module ClassMethods + def inherited(descendant) # :nodoc: + super + + descendant_content_class = content_class.dup + + if descendant_content_class.respond_to?(:set_temporary_name) + descendant_content_class.set_temporary_name("rendered_content") + end + + descendant.content_class = descendant_content_class + end + + # Register a callable to parse rendered content for a given template + # format. + # + # Each registered parser will also define a +#rendered.[FORMAT]+ helper + # method, where +[FORMAT]+ corresponds to the value of the + # +format+ argument. + # + # By default, ActionView::TestCase defines parsers for: + # + # * +:html+ - returns an instance of +Nokogiri::XML::Node+ + # * +:json+ - returns an instance of ActiveSupport::HashWithIndifferentAccess + # + # These pre-registered parsers also define corresponding helpers: + # + # * +:html+ - defines +rendered.html+ + # * +:json+ - defines +rendered.json+ + # + # ==== Parameters + # + # [+format+] + # The name (as a +Symbol+) of the format used to render the content. + # + # [+callable+] + # The parser. A callable object that accepts the rendered string as + # its sole argument. Alternatively, the parser can be specified as a + # block. + # + # ==== Examples + # + # test "renders HTML" do + # article = Article.create!(title: "Hello, world") + # + # render partial: "articles/article", locals: { article: article } + # + # assert_pattern { rendered.html.at("main h1") => { content: "Hello, world" } } + # end + # + # test "renders JSON" do + # article = Article.create!(title: "Hello, world") + # + # render formats: :json, partial: "articles/article", locals: { article: article } + # + # assert_pattern { rendered.json => { title: "Hello, world" } } + # end + # + # To parse the rendered content into RSS, register a call to +RSS::Parser.parse+: + # + # register_parser :rss, -> rendered { RSS::Parser.parse(rendered) } + # + # test "renders RSS" do + # article = Article.create!(title: "Hello, world") + # + # render formats: :rss, partial: article + # + # assert_equal "Hello, world", rendered.rss.items.last.title + # end + # + # To parse the rendered content into a +Capybara::Simple::Node+, + # re-register an +:html+ parser with a call to +Capybara.string+: + # + # register_parser :html, -> rendered { Capybara.string(rendered) } + # + # test "renders HTML" do + # article = Article.create!(title: "Hello, world") + # + # render partial: article + # + # rendered.html.assert_css "h1", text: "Hello, world" + # end + # + def register_parser(format, callable = nil, &block) + parser = callable || block || :itself.to_proc + content_class.redefine_method(format) do + parser.call(to_s) + end + end + + def tests(helper_class) + case helper_class + when String, Symbol + self.helper_class = "#{helper_class.to_s.underscore}_helper".camelize.safe_constantize + when Module + self.helper_class = helper_class + end + end + + def determine_default_helper_class(name) + determine_constant_from_test_name(name) do |constant| + Module === constant && !(Class === constant) + end + end + + def helper_method(*methods) + # Almost a duplicate from ActionController::Helpers + methods.flatten.each do |method| + _helpers_for_modification.module_eval <<~end_eval, __FILE__, __LINE__ + 1 + def #{method}(...) # def current_user(...) + _test_case.send(:'#{method}', ...) # _test_case.send(:'current_user', ...) + end # end + end_eval + end + end + + attr_writer :helper_class + + def helper_class + @helper_class ||= determine_default_helper_class(name) + end + + def new(*) + include_helper_modules! + super + end + + private + def include_helper_modules! + helper(helper_class) if helper_class + include _helpers + end + end + + included do + class_attribute :content_class, instance_accessor: false, default: RenderedViewContent + + setup :setup_with_controller + + register_parser :html, -> rendered { Rails::Dom::Testing.html_document_fragment.parse(rendered) } + register_parser :json, -> rendered { JSON.parse(rendered, object_class: ActiveSupport::HashWithIndifferentAccess) } + + ActiveSupport.run_load_hooks(:action_view_test_case, self) + + helper do + def protect_against_forgery? + false + end + + def _test_case + controller._test_case + end + end + end + + def setup_with_controller + controller_class = Class.new(ActionView::TestCase::TestController) + @controller = controller_class.new + @request = @controller.request + @view_flow = ActionView::OutputFlow.new + @output_buffer = ActionView::OutputBuffer.new + @rendered = self.class.content_class.new(+"") + + test_case_instance = self + controller_class.define_method(:_test_case) { test_case_instance } + end + + def config + @controller.config if @controller.respond_to?(:config) + end + + def render(options = {}, local_assigns = {}, &block) + view.assign(view_assigns) + @rendered << output = view.render(options, local_assigns, &block) + output + end + + def rendered_views + @_rendered_views ||= RenderedViewsCollection.new + end + + ## + # :method: rendered + # + # Returns the content rendered by the last +render+ call. + # + # The returned object behaves like a string but also exposes a number of methods + # that allows you to parse the content string in formats registered using + # .register_parser. + # + # By default includes the following parsers: + # + # +.html+ + # + # Parse the rendered content String into HTML. By default, this means + # a Nokogiri::XML::Node. + # + # test "renders HTML" do + # article = Article.create!(title: "Hello, world") + # + # render partial: "articles/article", locals: { article: article } + # + # assert_pattern { rendered.html.at("main h1") => { content: "Hello, world" } } + # end + # + # To parse the rendered content into a Capybara::Simple::Node, + # re-register an :html parser with a call to + # Capybara.string: + # + # register_parser :html, -> rendered { Capybara.string(rendered) } + # + # test "renders HTML" do + # article = Article.create!(title: "Hello, world") + # + # render partial: article + # + # rendered.html.assert_css "h1", text: "Hello, world" + # end + # + # +.json+ + # + # Parse the rendered content String into JSON. By default, this means + # a ActiveSupport::HashWithIndifferentAccess. + # + # test "renders JSON" do + # article = Article.create!(title: "Hello, world") + # + # render formats: :json, partial: "articles/article", locals: { article: article } + # + # assert_pattern { rendered.json => { title: "Hello, world" } } + # end + + def _routes + @controller._routes if @controller.respond_to?(:_routes) + end + + class RenderedViewContent < String # :nodoc: + end + + # Need to experiment if this priority is the best one: rendered => output_buffer + class RenderedViewsCollection + def initialize + @rendered_views ||= Hash.new { |hash, key| hash[key] = [] } + end + + def add(view, locals) + @rendered_views[view] ||= [] + @rendered_views[view] << locals + end + + def locals_for(view) + @rendered_views[view] + end + + def rendered_views + @rendered_views.keys + end + + def view_rendered?(view, expected_locals) + locals_for(view).any? do |actual_locals| + expected_locals.all? { |key, value| value == actual_locals[key] } + end + end + end + + private + # Need to experiment if this priority is the best one: rendered => output_buffer + def document_root_element + Rails::Dom::Testing.html_document.parse(@rendered.blank? ? @output_buffer.to_str : @rendered).root + end + + module Locals + attr_accessor :rendered_views + + def render(options = {}, local_assigns = {}) + case options + when Hash + if block_given? + rendered_views.add options[:layout], options[:locals] + elsif options.key?(:partial) + rendered_views.add options[:partial], options[:locals] + end + else + rendered_views.add options, local_assigns + end + + super + end + end + + # The instance of ActionView::Base that is used by +render+. + def view + @view ||= begin + view = @controller.view_context + view.singleton_class.include(_helpers) + view.extend(Locals) + view.rendered_views = rendered_views + view.output_buffer = output_buffer + view + end + end + + alias_method :_view, :view + + INTERNAL_IVARS = [ + :@NAME, + :@failures, + :@assertions, + :@__io__, + :@_assertion_wrapped, + :@_assertions, + :@_result, + :@_routes, + :@controller, + :@_controller, + :@_request, + :@_config, + :@_default_form_builder, + :@_layouts, + :@_files, + :@_rendered_views, + :@method_name, + :@output_buffer, + :@_partials, + :@passed, + :@rendered, + :@request, + :@routes, + :@tagged_logger, + :@_templates, + :@options, + :@test_passed, + :@view, + :@view_context_class, + :@view_flow, + :@_subscribers, + :@html_document, + ] + + def _user_defined_ivars + instance_variables - INTERNAL_IVARS + end + + # Returns a Hash of instance variables and their values, as defined by + # the user in the test case, which are then assigned to the view being + # rendered. This is generally intended for internal use and extension + # frameworks. + def view_assigns + Hash[_user_defined_ivars.map do |ivar| + [ivar[1..-1].to_sym, instance_variable_get(ivar)] + end] + end + + def method_missing(selector, ...) + begin + routes = @controller.respond_to?(:_routes) && @controller._routes + rescue + # Don't call routes, if there is an error on _routes call + end + + if routes && + (routes.named_routes.route_defined?(selector) || + routes.mounted_helpers.method_defined?(selector)) + @controller.__send__(selector, ...) + else + super + end + end + + def respond_to_missing?(name, include_private = false) + begin + routes = @controller.respond_to?(:_routes) && @controller._routes + rescue + # Don't call routes, if there is an error on _routes call + end + + routes && + (routes.named_routes.route_defined?(name) || + routes.mounted_helpers.method_defined?(name)) + end + end + + include Behavior + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/testing/resolvers.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/testing/resolvers.rb new file mode 100644 index 00000000..17a66b02 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/testing/resolvers.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require "action_view/template/resolver" + +module ActionView # :nodoc: + # Use FixtureResolver in your tests to simulate the presence of files on the + # file system. This is used internally by Rails' own test suite, and is + # useful for testing extensions that have no way of knowing what the file + # system will look like at runtime. + class FixtureResolver < FileSystemResolver + def initialize(hash = {}) + super("") + @hash = hash + @path = "" + end + + def data + @hash + end + + def to_s + @hash.keys.join(", ") + end + + private + def template_glob(glob) + @hash.keys.filter_map do |path| + "/#{path}" if File.fnmatch(glob, path) + end + end + + def source_for_template(template) + @hash[template.from(1)] + end + end + + class NullResolver < Resolver + def find_templates(name, prefix, partial, details, locals = []) + path = TemplatePath.build(name, prefix, partial) + handler = ActionView::Template::Handlers::Raw + [ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, virtual_path: path.virtual, format: nil, variant: nil, locals: locals)] + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/unbound_template.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/unbound_template.rb new file mode 100644 index 00000000..b9e28e2e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/unbound_template.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require "concurrent/map" + +module ActionView + class UnboundTemplate + attr_reader :virtual_path, :details + delegate :locale, :format, :variant, :handler, to: :@details + + def initialize(source, identifier, details:, virtual_path:) + @source = source + @identifier = identifier + @details = details + @virtual_path = virtual_path + + @templates = Concurrent::Map.new(initial_capacity: 2) + @write_lock = Mutex.new + end + + def bind_locals(locals) + unless template = @templates[locals] + @write_lock.synchronize do + normalized_locals = normalize_locals(locals) + + # We need ||=, both to dedup on the normalized locals and to check + # while holding the lock. + template = (@templates[normalized_locals] ||= build_template(normalized_locals)) + + if template.strict_locals? + # Under strict locals, we only need one template. + # This replaces the @templates Concurrent::Map with a hash which + # returns this template for every key. + @templates = Hash.new(template).freeze + else + # This may have already been assigned, but we've already de-dup'd so + # reassignment is fine. + @templates[locals.dup] = template + end + end + end + template + end + + def built_templates # :nodoc: + @templates.values + end + + private + def build_template(locals) + Template.new( + @source, + @identifier, + details.handler_class, + + format: details.format_or_default, + variant: variant&.to_s, + virtual_path: @virtual_path, + + locals: locals.map(&:to_s) + ) + end + + def normalize_locals(locals) + locals.map(&:to_sym).sort!.freeze + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/version.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/version.rb new file mode 100644 index 00000000..359067e5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative "gem_version" + +module ActionView + # Returns the currently loaded version of Action View as a +Gem::Version+. + def self.version + gem_version + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/view_paths.rb b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/view_paths.rb new file mode 100644 index 00000000..ba6ec262 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/actionview-8.0.2/lib/action_view/view_paths.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +module ActionView + module ViewPaths + extend ActiveSupport::Concern + + included do + ActionView::PathRegistry.set_view_paths(self, ActionView::PathSet.new.freeze) + end + + delegate :template_exists?, :any_templates?, :view_paths, :formats, :formats=, + :locale, :locale=, to: :lookup_context + + module ClassMethods + def _view_paths + ActionView::PathRegistry.get_view_paths(self) + end + + def _view_paths=(paths) + ActionView::PathRegistry.set_view_paths(self, paths) + end + + def _prefixes # :nodoc: + @_prefixes ||= begin + return local_prefixes if superclass.abstract? + + local_prefixes + superclass._prefixes + end + end + + def _build_view_paths(paths) # :nodoc: + return paths if ActionView::PathSet === paths + + paths = ActionView::PathRegistry.cast_file_system_resolvers(paths) + ActionView::PathSet.new(paths) + end + + # Append a path to the list of view paths for this controller. + # + # ==== Parameters + # * path - If a String is provided, it gets converted into + # the default view path. You may also provide a custom view path + # (see ActionView::PathSet for more information) + def append_view_path(path) + self._view_paths = view_paths + _build_view_paths(path) + end + + # Prepend a path to the list of view paths for this controller. + # + # ==== Parameters + # * path - If a String is provided, it gets converted into + # the default view path. You may also provide a custom view path + # (see ActionView::PathSet for more information) + def prepend_view_path(path) + self._view_paths = _build_view_paths(path) + view_paths + end + + # A list of all of the default view paths for this controller. + def view_paths + _view_paths + end + + # Set the view paths. + # + # ==== Parameters + # * paths - If a PathSet is provided, use that; + # otherwise, process the parameter into a PathSet. + def view_paths=(paths) + self._view_paths = _build_view_paths(paths) + end + + private + # Override this method in your controller if you want to change paths prefixes for finding views. + # Prefixes defined here will still be added to parents' ._prefixes. + def local_prefixes + [controller_path] + end + end + + # The prefixes used in render "foo" shortcuts. + def _prefixes # :nodoc: + self.class._prefixes + end + + # LookupContext is the object responsible for holding all + # information required for looking up templates, i.e. view paths and + # details. Check ActionView::LookupContext for more information. + def lookup_context + @_lookup_context ||= + ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes) + end + + def details_for_lookup + {} + end + + # Append a path to the list of view paths for the current LookupContext. + # + # ==== Parameters + # * path - If a String is provided, it gets converted into + # the default view path. You may also provide a custom view path + # (see ActionView::PathSet for more information) + def append_view_path(path) + lookup_context.append_view_paths(self.class._build_view_paths(path)) + end + + # Prepend a path to the list of view paths for the current LookupContext. + # + # ==== Parameters + # * path - If a String is provided, it gets converted into + # the default view path. You may also provide a custom view path + # (see ActionView::PathSet for more information) + def prepend_view_path(path) + lookup_context.prepend_view_paths(self.class._build_view_paths(path)) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/CHANGELOG.md b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/CHANGELOG.md new file mode 100644 index 00000000..46d1f90e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/CHANGELOG.md @@ -0,0 +1,120 @@ +## Rails 8.0.2 (March 12, 2025) ## + +* No changes. + + +## Rails 8.0.2 (March 12, 2025) ## + +* No changes. + + +## Rails 8.0.1 (December 13, 2024) ## + +* No changes. + + +## Rails 8.0.0.1 (December 10, 2024) ## + +* No changes. + + +## Rails 8.0.0 (November 07, 2024) ## + +* No changes. + + +## Rails 8.0.0.rc2 (October 30, 2024) ## + +* No changes. + + +## Rails 8.0.0.rc1 (October 19, 2024) ## + +* Add `:except_on` option for validations. Grants the ability to _skip_ validations in specified contexts. + + ```ruby + class User < ApplicationRecord + #... + validates :birthday, presence: { except_on: :admin } + #... + end + + user = User.new(attributes except birthday) + user.save(context: :admin) + ``` + + *Drew Bragg* + +## Rails 8.0.0.beta1 (September 26, 2024) ## + +* Make `ActiveModel::Serialization#read_attribute_for_serialization` public + + *Sean Doyle* + +* Add a default token generator for password reset tokens when using `has_secure_password`. + + ```ruby + class User < ApplicationRecord + has_secure_password + end + + user = User.create!(name: "david", password: "123", password_confirmation: "123") + token = user.password_reset_token + User.find_by_password_reset_token(token) # returns user + + # 16 minutes later... + User.find_by_password_reset_token(token) # returns nil + + # raises ActiveSupport::MessageVerifier::InvalidSignature since the token is expired + User.find_by_password_reset_token!(token) + ``` + + *DHH* + +* Add a load hook `active_model_translation` for `ActiveModel::Translation`. + + *Shouichi Kamiya* + +* Add `raise_on_missing_translations` option to `ActiveModel::Translation`. + When the option is set, `human_attribute_name` raises an error if a translation of the given attribute is missing. + + ```ruby + # ActiveModel::Translation.raise_on_missing_translations = false + Post.human_attribute_name("title") + => "Title" + + # ActiveModel::Translation.raise_on_missing_translations = true + Post.human_attribute_name("title") + => Translation missing. Options considered were: (I18n::MissingTranslationData) + - en.activerecord.attributes.post.title + - en.attributes.title + + raise exception.respond_to?(:to_exception) ? exception.to_exception : exception + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ``` + + *Shouichi Kamiya* + +* Introduce `ActiveModel::AttributeAssignment#attribute_writer_missing` + + Provide instances with an opportunity to gracefully handle assigning to an + unknown attribute: + + ```ruby + class Rectangle + include ActiveModel::AttributeAssignment + + attr_accessor :length, :width + + def attribute_writer_missing(name, value) + Rails.logger.warn "Tried to assign to unknown attribute #{name}" + end + end + + rectangle = Rectangle.new + rectangle.assign_attributes(height: 10) # => Logs "Tried to assign to unknown attribute 'height'" + ``` + + *Sean Doyle* + +Please check [7-2-stable](https://github.com/rails/rails/blob/7-2-stable/activemodel/CHANGELOG.md) for previous changes. diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/MIT-LICENSE b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/MIT-LICENSE new file mode 100644 index 00000000..7be9ac63 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/MIT-LICENSE @@ -0,0 +1,21 @@ +Copyright (c) David Heinemeier Hansson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/README.rdoc b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/README.rdoc new file mode 100644 index 00000000..9e4395fb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/README.rdoc @@ -0,0 +1,266 @@ += Active Model -- model interfaces for \Rails + +Active Model provides a known set of interfaces for usage in model classes. +They allow for Action Pack helpers to interact with non-Active Record models, +for example. Active Model also helps with building custom ORMs for use outside of +the \Rails framework. + +You can read more about Active Model in the {Active Model Basics}[https://guides.rubyonrails.org/active_model_basics.html] guide. + +Prior to \Rails 3.0, if a plugin or gem developer wanted to have an object +interact with Action Pack helpers, it was required to either copy chunks of +code from \Rails, or monkey patch entire helpers to make them handle objects +that did not exactly conform to the Active Record interface. This would result +in code duplication and fragile applications that broke on upgrades. Active +Model solves this by defining an explicit API. You can read more about the +API in +ActiveModel::Lint::Tests+. + +Active Model provides a default module that implements the basic API required +to integrate with Action Pack out of the box: ActiveModel::API. + + class Person + include ActiveModel::API + + attr_accessor :name, :age + validates_presence_of :name + end + + person = Person.new(name: 'bob', age: '18') + person.name # => 'bob' + person.age # => '18' + person.valid? # => true + +It includes model name introspections, conversions, translations and +validations, resulting in a class suitable to be used with Action Pack. +See ActiveModel::API for more examples. + +Active Model also provides the following functionality to have ORM-like +behavior out of the box: + +* Add attribute magic to objects + + class Person + include ActiveModel::AttributeMethods + + attribute_method_prefix 'clear_' + define_attribute_methods :name, :age + + attr_accessor :name, :age + + def clear_attribute(attr) + send("#{attr}=", nil) + end + end + + person = Person.new + person.clear_name + person.clear_age + + {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/AttributeMethods.html] + +* Callbacks for certain operations + + class Person + extend ActiveModel::Callbacks + define_model_callbacks :create + + def create + run_callbacks :create do + # Your create action methods here + end + end + end + + This generates +before_create+, +around_create+ and +after_create+ + class methods that wrap your create method. + + {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Callbacks.html] + +* Tracking value changes + + class Person + include ActiveModel::Dirty + + define_attribute_methods :name + + def name + @name + end + + def name=(val) + name_will_change! unless val == @name + @name = val + end + + def save + # do persistence work + changes_applied + end + end + + person = Person.new + person.name # => nil + person.changed? # => false + person.name = 'bob' + person.changed? # => true + person.changed # => ['name'] + person.changes # => { 'name' => [nil, 'bob'] } + person.save + person.name = 'robert' + person.save + person.previous_changes # => {'name' => ['bob, 'robert']} + + {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Dirty.html] + +* Adding +errors+ interface to objects + + Exposing error messages allows objects to interact with Action Pack + helpers seamlessly. + + class Person + + def initialize + @errors = ActiveModel::Errors.new(self) + end + + attr_accessor :name + attr_reader :errors + + def validate! + errors.add(:name, "cannot be nil") if name.nil? + end + + def self.human_attribute_name(attr, options = {}) + "Name" + end + end + + person = Person.new + person.name = nil + person.validate! + person.errors.full_messages + # => ["Name cannot be nil"] + + {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Errors.html] + +* Model name introspection + + class NamedPerson + extend ActiveModel::Naming + end + + NamedPerson.model_name.name # => "NamedPerson" + NamedPerson.model_name.human # => "Named person" + + {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Naming.html] + +* Making objects serializable + + ActiveModel::Serialization provides a standard interface for your object + to provide +to_json+ serialization. + + class SerialPerson + include ActiveModel::Serialization + + attr_accessor :name + + def attributes + {'name' => name} + end + end + + s = SerialPerson.new + s.serializable_hash # => {"name"=>nil} + + class SerialPerson + include ActiveModel::Serializers::JSON + end + + s = SerialPerson.new + s.to_json # => "{\"name\":null}" + + {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Serialization.html] + +* Internationalization (i18n) support + + class Person + extend ActiveModel::Translation + end + + Person.human_attribute_name('my_attribute') + # => "My attribute" + + {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Translation.html] + +* Validation support + + class Person + include ActiveModel::Validations + + attr_accessor :first_name, :last_name + + validates_each :first_name, :last_name do |record, attr, value| + record.errors.add attr, "starts with z." if value.start_with?("z") + end + end + + person = Person.new + person.first_name = 'zoolander' + person.valid? # => false + + {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Validations.html] + +* Custom validators + + class HasNameValidator < ActiveModel::Validator + def validate(record) + record.errors.add(:name, "must exist") if record.name.blank? + end + end + + class ValidatorPerson + include ActiveModel::Validations + validates_with HasNameValidator + attr_accessor :name + end + + p = ValidatorPerson.new + p.valid? # => false + p.errors.full_messages # => ["Name must exist"] + p.name = "Bob" + p.valid? # => true + + {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Validator.html] + + +== Download and installation + +The latest version of Active Model can be installed with RubyGems: + + $ gem install activemodel + +Source code can be downloaded as part of the \Rails project on GitHub + +* https://github.com/rails/rails/tree/main/activemodel + + +== License + +Active Model is released under the MIT license: + +* https://opensource.org/licenses/MIT + + +== Support + +API documentation is at: + +* https://api.rubyonrails.org + +Bug reports for the Ruby on \Rails project can be filed here: + +* https://github.com/rails/rails/issues + +Feature requests should be discussed on the rails-core mailing list here: + +* https://discuss.rubyonrails.org/c/rubyonrails-core diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model.rb new file mode 100644 index 00000000..b3439fcc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) David Heinemeier Hansson +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ + +require "active_support" +require "active_support/rails" +require "active_model/version" +require "active_model/deprecator" + +# :include: ../README.rdoc +module ActiveModel + extend ActiveSupport::Autoload + + autoload :Access + autoload :API + autoload :Attribute + autoload :Attributes + autoload :AttributeAssignment + autoload :AttributeMethods + autoload :AttributeRegistration + autoload :BlockValidator, "active_model/validator" + autoload :Callbacks + autoload :Conversion + autoload :Dirty + autoload :EachValidator, "active_model/validator" + autoload :ForbiddenAttributesProtection + autoload :Lint + autoload :Model + autoload :Name, "active_model/naming" + autoload :Naming + autoload :SecurePassword + autoload :Serialization + autoload :Translation + autoload :Type + autoload :Validations + autoload :Validator + + eager_autoload do + autoload :Errors + autoload :Error + autoload :RangeError, "active_model/errors" + autoload :StrictValidationFailed, "active_model/errors" + autoload :UnknownAttributeError, "active_model/errors" + autoload :ValidationError, "active_model/validations" + end + + module Serializers + extend ActiveSupport::Autoload + + eager_autoload do + autoload :JSON + end + end + + def self.eager_load! + super + ActiveModel::Serializers.eager_load! + end +end + +ActiveSupport.on_load(:i18n) do + I18n.load_path << File.expand_path("active_model/locale/en.yml", __dir__) +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/access.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/access.rb new file mode 100644 index 00000000..9ee83f5c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/access.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" +require "active_support/core_ext/hash/indifferent_access" + +module ActiveModel + module Access # :nodoc: + def slice(*methods) + methods.flatten.index_with { |method| public_send(method) }.with_indifferent_access + end + + def values_at(*methods) + methods.flatten.map! { |method| public_send(method) } + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/api.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/api.rb new file mode 100644 index 00000000..f1e88de4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/api.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +module ActiveModel + # = Active \Model \API + # + # Includes the required interface for an object to interact with + # Action Pack and Action View, using different Active \Model modules. + # It includes model name introspections, conversions, translations, and + # validations. Besides that, it allows you to initialize the object with a + # hash of attributes, pretty much like Active Record does. + # + # A minimal implementation could be: + # + # class Person + # include ActiveModel::API + # attr_accessor :name, :age + # end + # + # person = Person.new(name: 'bob', age: '18') + # person.name # => "bob" + # person.age # => "18" + # + # Note that, by default, +ActiveModel::API+ implements #persisted? + # to return +false+, which is the most common case. You may want to override + # it in your class to simulate a different scenario: + # + # class Person + # include ActiveModel::API + # attr_accessor :id, :name + # + # def persisted? + # self.id.present? + # end + # end + # + # person = Person.new(id: 1, name: 'bob') + # person.persisted? # => true + # + # Also, if for some reason you need to run code on initialize ( ::new ), make + # sure you call +super+ if you want the attributes hash initialization to + # happen. + # + # class Person + # include ActiveModel::API + # attr_accessor :id, :name, :omg + # + # def initialize(attributes={}) + # super + # @omg ||= true + # end + # end + # + # person = Person.new(id: 1, name: 'bob') + # person.omg # => true + # + # For more detailed information on other functionalities available, please + # refer to the specific modules included in +ActiveModel::API+ + # (see below). + module API + extend ActiveSupport::Concern + include ActiveModel::AttributeAssignment + include ActiveModel::Validations + include ActiveModel::Conversion + + included do + extend ActiveModel::Naming + extend ActiveModel::Translation + end + + # Initializes a new model with the given +params+. + # + # class Person + # include ActiveModel::API + # attr_accessor :name, :age + # end + # + # person = Person.new(name: 'bob', age: '18') + # person.name # => "bob" + # person.age # => "18" + def initialize(attributes = {}) + assign_attributes(attributes) if attributes + + super() + end + + # Indicates if the model is persisted. Default is +false+. + # + # class Person + # include ActiveModel::API + # attr_accessor :id, :name + # end + # + # person = Person.new(id: 1, name: 'bob') + # person.persisted? # => false + def persisted? + false + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute.rb new file mode 100644 index 00000000..1de3e118 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute.rb @@ -0,0 +1,277 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/duplicable" + +module ActiveModel + class Attribute # :nodoc: + class << self + def from_database(name, value_before_type_cast, type, value = nil) + FromDatabase.new(name, value_before_type_cast, type, nil, value) + end + + def from_user(name, value_before_type_cast, type, original_attribute = nil) + FromUser.new(name, value_before_type_cast, type, original_attribute) + end + + def with_cast_value(name, value_before_type_cast, type) + WithCastValue.new(name, value_before_type_cast, type) + end + + def null(name) + Null.new(name) + end + + def uninitialized(name, type) + Uninitialized.new(name, type) + end + end + + attr_reader :name, :value_before_type_cast, :type + + # This method should not be called directly. + # Use #from_database or #from_user + def initialize(name, value_before_type_cast, type, original_attribute = nil, value = nil) + @name = name + @value_before_type_cast = value_before_type_cast + @type = type + @original_attribute = original_attribute + @value = value unless value.nil? + end + + def value(&_) + # `defined?` is cheaper than `||=` when we get back falsy values + @value = type_cast(value_before_type_cast) unless defined?(@value) + @value + end + + def original_value + if assigned? + original_attribute.original_value + else + type_cast(value_before_type_cast) + end + end + + def value_for_database + if !defined?(@value_for_database) || type.changed_in_place?(@value_for_database, value) + @value_for_database = _value_for_database + end + @value_for_database + end + + def serializable?(&block) + type.serializable?(value, &block) + end + + def changed? + changed_from_assignment? || changed_in_place? + end + + def changed_in_place? + has_been_read? && type.changed_in_place?(original_value_for_database, value) + end + + def forgetting_assignment + with_value_from_database(value_for_database) + end + + def with_value_from_user(value) + type.assert_valid_value(value) + self.class.from_user(name, value, type, original_attribute || self) + end + + def with_value_from_database(value) + self.class.from_database(name, value, type) + end + + def with_cast_value(value) + self.class.with_cast_value(name, value, type) + end + + def with_type(type) + if changed_in_place? + with_value_from_user(value).with_type(type) + else + self.class.new(name, value_before_type_cast, type, original_attribute) + end + end + + def type_cast(*) + raise NotImplementedError + end + + def initialized? + true + end + + def came_from_user? + false + end + + def has_been_read? + defined?(@value) + end + + def ==(other) + self.class == other.class && + name == other.name && + value_before_type_cast == other.value_before_type_cast && + type == other.type + end + alias eql? == + + def hash + [self.class, name, value_before_type_cast, type].hash + end + + def init_with(coder) + @name = coder["name"] + @value_before_type_cast = coder["value_before_type_cast"] + @type = coder["type"] + @original_attribute = coder["original_attribute"] + @value = coder["value"] if coder.map.key?("value") + end + + def encode_with(coder) + coder["name"] = name + coder["value_before_type_cast"] = value_before_type_cast unless value_before_type_cast.nil? + coder["type"] = type if type + coder["original_attribute"] = original_attribute if original_attribute + coder["value"] = value if defined?(@value) + end + + def original_value_for_database + if assigned? + original_attribute.original_value_for_database + else + _original_value_for_database + end + end + + private + attr_reader :original_attribute + alias :assigned? :original_attribute + + def initialize_dup(other) + if @value&.duplicable? + @value = @value.dup + end + end + + def changed_from_assignment? + assigned? && type.changed?(original_value, value, value_before_type_cast) + end + + def _value_for_database + type.serialize(value) + end + + def _original_value_for_database + type.serialize(original_value) + end + + class FromDatabase < Attribute # :nodoc: + def type_cast(value) + type.deserialize(value) + end + + def forgetting_assignment + # If this attribute was not persisted (with a `value_for_database` + # that might differ from `value_before_type_cast`) and `value` has not + # changed in place, we can use the existing `value_before_type_cast` + # to avoid deserialize / cast / serialize calls from computing the new + # attribute's `value_before_type_cast`. + if !defined?(@value_for_database) && !changed_in_place? + with_value_from_database(value_before_type_cast) + else + super + end + end + + private + def _original_value_for_database + value_before_type_cast + end + end + + class FromUser < Attribute # :nodoc: + def type_cast(value) + type.cast(value) + end + + def came_from_user? + !type.value_constructed_by_mass_assignment?(value_before_type_cast) + end + + private + def _value_for_database + Type::SerializeCastValue.serialize(type, value) + end + end + + class WithCastValue < Attribute # :nodoc: + def type_cast(value) + value + end + + def changed_in_place? + false + end + end + + class Null < Attribute # :nodoc: + def initialize(name) + super(name, nil, Type.default_value) + end + + def type_cast(*) + nil + end + + def with_type(type) + self.class.with_cast_value(name, nil, type) + end + + def with_value_from_database(value) + raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`" + end + alias_method :with_value_from_user, :with_value_from_database + alias_method :with_cast_value, :with_value_from_database + end + + class Uninitialized < Attribute # :nodoc: + UNINITIALIZED_ORIGINAL_VALUE = Object.new + + def initialize(name, type) + super(name, nil, type) + end + + def value + if block_given? + yield name + end + end + + def original_value + UNINITIALIZED_ORIGINAL_VALUE + end + + def value_for_database + end + + def initialized? + false + end + + def forgetting_assignment + dup + end + + def with_type(type) + self.class.new(name, type) + end + end + + private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute/user_provided_default.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute/user_provided_default.rb new file mode 100644 index 00000000..a201c3f8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute/user_provided_default.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "active_model/attribute" + +module ActiveModel + class Attribute # :nodoc: + def with_user_default(value) + UserProvidedDefault.new(name, value, type, self.is_a?(FromDatabase) ? self : original_attribute) + end + + class UserProvidedDefault < FromUser # :nodoc: + def initialize(name, value, type, database_default) + @user_provided_value = value + super(name, value, type, database_default) + end + + def value_before_type_cast + if user_provided_value.is_a?(Proc) + @memoized_value_before_type_cast ||= user_provided_value.call + else + @user_provided_value + end + end + + def with_type(type) + self.class.new(name, user_provided_value, type, original_attribute) + end + + def marshal_dump + result = [ + name, + value_before_type_cast, + type, + original_attribute, + ] + result << value if defined?(@value) + result + end + + def marshal_load(values) + name, user_provided_value, type, original_attribute, value = values + @name = name + @user_provided_value = user_provided_value + @type = type + @original_attribute = original_attribute + if values.length == 5 + @value = value + end + end + + private + attr_reader :user_provided_value + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_assignment.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_assignment.rb new file mode 100644 index 00000000..db746b1c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_assignment.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/keys" + +module ActiveModel + module AttributeAssignment + include ActiveModel::ForbiddenAttributesProtection + + # Allows you to set all the attributes by passing in a hash of attributes with + # keys matching the attribute names. + # + # If the passed hash responds to permitted? method and the return value + # of this method is +false+ an ActiveModel::ForbiddenAttributesError + # exception is raised. + # + # class Cat + # include ActiveModel::AttributeAssignment + # attr_accessor :name, :status + # end + # + # cat = Cat.new + # cat.assign_attributes(name: "Gorby", status: "yawning") + # cat.name # => 'Gorby' + # cat.status # => 'yawning' + # cat.assign_attributes(status: "sleeping") + # cat.name # => 'Gorby' + # cat.status # => 'sleeping' + def assign_attributes(new_attributes) + unless new_attributes.respond_to?(:each_pair) + raise ArgumentError, "When assigning attributes, you must pass a hash as an argument, #{new_attributes.class} passed." + end + return if new_attributes.empty? + + _assign_attributes(sanitize_for_mass_assignment(new_attributes)) + end + + alias attributes= assign_attributes + + # Like `BasicObject#method_missing`, `#attribute_writer_missing` is invoked + # when `#assign_attributes` is passed an unknown attribute name. + # + # By default, `#attribute_writer_missing` raises an UnknownAttributeError. + # + # class Rectangle + # include ActiveModel::AttributeAssignment + # + # attr_accessor :length, :width + # + # def attribute_writer_missing(name, value) + # Rails.logger.warn "Tried to assign to unknown attribute #{name}" + # end + # end + # + # rectangle = Rectangle.new + # rectangle.assign_attributes(height: 10) # => Logs "Tried to assign to unknown attribute 'height'" + def attribute_writer_missing(name, value) + raise UnknownAttributeError.new(self, name) + end + + private + def _assign_attributes(attributes) + attributes.each_pair do |k, v| + _assign_attribute(k, v) + end + end + + def _assign_attribute(k, v) + setter = :"#{k}=" + public_send(setter, v) + rescue NoMethodError + if respond_to?(setter) + raise + else + attribute_writer_missing(k.to_s, v) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_methods.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_methods.rb new file mode 100644 index 00000000..2ed632e5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_methods.rb @@ -0,0 +1,592 @@ +# frozen_string_literal: true + +require "concurrent/map" + +module ActiveModel + # Raised when an attribute is not defined. + # + # class User < ActiveRecord::Base + # has_many :pets + # end + # + # user = User.first + # user.pets.select(:id).first.user_id + # # => ActiveModel::MissingAttributeError: missing attribute 'user_id' for Pet + class MissingAttributeError < NoMethodError + end + + # = Active \Model \Attribute \Methods + # + # Provides a way to add prefixes and suffixes to your methods as + # well as handling the creation of ActiveRecord::Base - like + # class methods such as +table_name+. + # + # The requirements to implement +ActiveModel::AttributeMethods+ are to: + # + # * include ActiveModel::AttributeMethods in your class. + # * Call each of its methods you want to add, such as +attribute_method_suffix+ + # or +attribute_method_prefix+. + # * Call +define_attribute_methods+ after the other methods are called. + # * Define the various generic +_attribute+ methods that you have declared. + # * Define an +attributes+ method which returns a hash with each + # attribute name in your model as hash key and the attribute value as hash value. + # Hash keys must be strings. + # + # A minimal implementation could be: + # + # class Person + # include ActiveModel::AttributeMethods + # + # attribute_method_affix prefix: 'reset_', suffix: '_to_default!' + # attribute_method_suffix '_contrived?' + # attribute_method_prefix 'clear_' + # define_attribute_methods :name + # + # attr_accessor :name + # + # def attributes + # { 'name' => @name } + # end + # + # private + # def attribute_contrived?(attr) + # true + # end + # + # def clear_attribute(attr) + # send("#{attr}=", nil) + # end + # + # def reset_attribute_to_default!(attr) + # send("#{attr}=", 'Default Name') + # end + # end + module AttributeMethods + extend ActiveSupport::Concern + + NAME_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/ + CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/ + + included do + class_attribute :attribute_aliases, instance_writer: false, default: {} + class_attribute :attribute_method_patterns, instance_writer: false, default: [ ClassMethods::AttributeMethodPattern.new ] + end + + module ClassMethods + # Declares a method available for all attributes with the given prefix. + # Uses +method_missing+ and respond_to? to rewrite the method. + # + # #{prefix}#{attr}(*args, &block) + # + # to + # + # #{prefix}attribute(#{attr}, *args, &block) + # + # An instance method #{prefix}attribute must exist and accept + # at least the +attr+ argument. + # + # class Person + # include ActiveModel::AttributeMethods + # + # attr_accessor :name + # attribute_method_prefix 'clear_' + # define_attribute_methods :name + # + # private + # def clear_attribute(attr) + # send("#{attr}=", nil) + # end + # end + # + # person = Person.new + # person.name = 'Bob' + # person.name # => "Bob" + # person.clear_name + # person.name # => nil + def attribute_method_prefix(*prefixes, parameters: nil) + self.attribute_method_patterns += prefixes.map! { |prefix| AttributeMethodPattern.new(prefix: prefix, parameters: parameters) } + undefine_attribute_methods + end + + # Declares a method available for all attributes with the given suffix. + # Uses +method_missing+ and respond_to? to rewrite the method. + # + # #{attr}#{suffix}(*args, &block) + # + # to + # + # attribute#{suffix}(#{attr}, *args, &block) + # + # An attribute#{suffix} instance method must exist and accept at + # least the +attr+ argument. + # + # class Person + # include ActiveModel::AttributeMethods + # + # attr_accessor :name + # attribute_method_suffix '_short?' + # define_attribute_methods :name + # + # private + # def attribute_short?(attr) + # send(attr).length < 5 + # end + # end + # + # person = Person.new + # person.name = 'Bob' + # person.name # => "Bob" + # person.name_short? # => true + def attribute_method_suffix(*suffixes, parameters: nil) + self.attribute_method_patterns += suffixes.map! { |suffix| AttributeMethodPattern.new(suffix: suffix, parameters: parameters) } + undefine_attribute_methods + end + + # Declares a method available for all attributes with the given prefix + # and suffix. Uses +method_missing+ and respond_to? to rewrite + # the method. + # + # #{prefix}#{attr}#{suffix}(*args, &block) + # + # to + # + # #{prefix}attribute#{suffix}(#{attr}, *args, &block) + # + # An #{prefix}attribute#{suffix} instance method must exist and + # accept at least the +attr+ argument. + # + # class Person + # include ActiveModel::AttributeMethods + # + # attr_accessor :name + # attribute_method_affix prefix: 'reset_', suffix: '_to_default!' + # define_attribute_methods :name + # + # private + # def reset_attribute_to_default!(attr) + # send("#{attr}=", 'Default Name') + # end + # end + # + # person = Person.new + # person.name # => 'Gem' + # person.reset_name_to_default! + # person.name # => 'Default Name' + def attribute_method_affix(*affixes) + self.attribute_method_patterns += affixes.map! { |affix| AttributeMethodPattern.new(**affix) } + undefine_attribute_methods + end + + # Allows you to make aliases for attributes. + # + # class Person + # include ActiveModel::AttributeMethods + # + # attr_accessor :name + # attribute_method_suffix '_short?' + # define_attribute_methods :name + # + # alias_attribute :nickname, :name + # + # private + # def attribute_short?(attr) + # send(attr).length < 5 + # end + # end + # + # person = Person.new + # person.name = 'Bob' + # person.name # => "Bob" + # person.nickname # => "Bob" + # person.name_short? # => true + # person.nickname_short? # => true + def alias_attribute(new_name, old_name) + old_name = old_name.to_s + new_name = new_name.to_s + self.attribute_aliases = attribute_aliases.merge(new_name => old_name) + aliases_by_attribute_name[old_name] << new_name + eagerly_generate_alias_attribute_methods(new_name, old_name) + end + + def eagerly_generate_alias_attribute_methods(new_name, old_name) # :nodoc: + ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator| + generate_alias_attribute_methods(code_generator, new_name, old_name) + end + end + + def generate_alias_attribute_methods(code_generator, new_name, old_name) # :nodoc: + ActiveSupport::CodeGenerator.batch(code_generator, __FILE__, __LINE__) do |owner| + attribute_method_patterns.each do |pattern| + alias_attribute_method_definition(code_generator, pattern, new_name, old_name) + end + attribute_method_patterns_cache.clear + end + end + + def alias_attribute_method_definition(code_generator, pattern, new_name, old_name) # :nodoc: + method_name = pattern.method_name(new_name).to_s + target_name = pattern.method_name(old_name).to_s + parameters = pattern.parameters + + mangled_name = build_mangled_name(target_name) + + call_args = [] + call_args << parameters if parameters + + define_call(code_generator, method_name, target_name, mangled_name, parameters, call_args, namespace: :alias_attribute, as: method_name) + end + + # Is +new_name+ an alias? + def attribute_alias?(new_name) + attribute_aliases.key? new_name.to_s + end + + # Returns the original name for the alias +name+ + def attribute_alias(name) + attribute_aliases[name.to_s] + end + + # Declares the attributes that should be prefixed and suffixed by + # +ActiveModel::AttributeMethods+. + # + # To use, pass attribute names (as strings or symbols). Be sure to declare + # +define_attribute_methods+ after you define any prefix, suffix, or affix + # methods, or they will not hook in. + # + # class Person + # include ActiveModel::AttributeMethods + # + # attr_accessor :name, :age, :address + # attribute_method_prefix 'clear_' + # + # # Call to define_attribute_methods must appear after the + # # attribute_method_prefix, attribute_method_suffix or + # # attribute_method_affix declarations. + # define_attribute_methods :name, :age, :address + # + # private + # def clear_attribute(attr) + # send("#{attr}=", nil) + # end + # end + def define_attribute_methods(*attr_names) + ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner| + attr_names.flatten.each do |attr_name| + define_attribute_method(attr_name, _owner: owner) + aliases_by_attribute_name[attr_name.to_s].each do |aliased_name| + generate_alias_attribute_methods owner, aliased_name, attr_name + end + end + end + end + + # Declares an attribute that should be prefixed and suffixed by + # +ActiveModel::AttributeMethods+. + # + # To use, pass an attribute name (as string or symbol). Be sure to declare + # +define_attribute_method+ after you define any prefix, suffix or affix + # method, or they will not hook in. + # + # class Person + # include ActiveModel::AttributeMethods + # + # attr_accessor :name + # attribute_method_suffix '_short?' + # + # # Call to define_attribute_method must appear after the + # # attribute_method_prefix, attribute_method_suffix or + # # attribute_method_affix declarations. + # define_attribute_method :name + # + # private + # def attribute_short?(attr) + # send(attr).length < 5 + # end + # end + # + # person = Person.new + # person.name = 'Bob' + # person.name # => "Bob" + # person.name_short? # => true + def define_attribute_method(attr_name, _owner: generated_attribute_methods, as: attr_name) + ActiveSupport::CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner| + attribute_method_patterns.each do |pattern| + define_attribute_method_pattern(pattern, attr_name, owner: owner, as: as) + end + attribute_method_patterns_cache.clear + end + end + + def define_attribute_method_pattern(pattern, attr_name, owner:, as:, override: false) # :nodoc: + canonical_method_name = pattern.method_name(attr_name) + public_method_name = pattern.method_name(as) + + # If defining a regular attribute method, we don't override methods that are explicitly + # defined in parent classes. + if instance_method_already_implemented?(public_method_name) + # However, for `alias_attribute`, we always define the method. + # We check for override second because `instance_method_already_implemented?` + # also check for dangerous methods. + return unless override + end + + generate_method = "define_method_#{pattern.proxy_target}" + + if respond_to?(generate_method, true) + send(generate_method, attr_name.to_s, owner: owner, as: as) + else + define_proxy_call( + owner, + canonical_method_name, + pattern.proxy_target, + pattern.parameters, + attr_name.to_s, + namespace: :active_model_proxy, + as: public_method_name + ) + end + end + + # Removes all the previously dynamically defined methods from the class, including alias attribute methods. + # + # class Person + # include ActiveModel::AttributeMethods + # + # attr_accessor :name + # attribute_method_suffix '_short?' + # define_attribute_method :name + # alias_attribute :first_name, :name + # + # private + # def attribute_short?(attr) + # send(attr).length < 5 + # end + # end + # + # person = Person.new + # person.name = 'Bob' + # person.first_name # => "Bob" + # person.name_short? # => true + # + # Person.undefine_attribute_methods + # + # person.name_short? # => NoMethodError + # person.first_name # => NoMethodError + def undefine_attribute_methods + generated_attribute_methods.module_eval do + undef_method(*instance_methods) + end + attribute_method_patterns_cache.clear + end + + def aliases_by_attribute_name # :nodoc: + @aliases_by_attribute_name ||= Hash.new { |h, k| h[k] = [] } + end + + private + def inherited(base) # :nodoc: + super + base.class_eval do + @attribute_method_patterns_cache = nil + @aliases_by_attribute_name = nil + @generated_attribute_methods = nil + end + end + + def resolve_attribute_name(name) + attribute_aliases.fetch(super, &:itself) + end + + def generated_attribute_methods + @generated_attribute_methods ||= Module.new.tap { |mod| include mod } + end + + def instance_method_already_implemented?(method_name) + generated_attribute_methods.method_defined?(method_name) + end + + # The methods +method_missing+ and +respond_to?+ of this module are + # invoked often in a typical rails, both of which invoke the method + # +matched_attribute_method+. The latter method iterates through an + # array doing regular expression matches, which results in a lot of + # object creations. Most of the time it returns a +nil+ match. As the + # match result is always the same given a +method_name+, this cache is + # used to alleviate the GC, which ultimately also speeds up the app + # significantly (in our case our test suite finishes 10% faster with + # this cache). + def attribute_method_patterns_cache + @attribute_method_patterns_cache ||= Concurrent::Map.new(initial_capacity: 4) + end + + def attribute_method_patterns_matching(method_name) + attribute_method_patterns_cache.compute_if_absent(method_name) do + attribute_method_patterns.filter_map { |pattern| pattern.match(method_name) } + end + end + + # Define a method `name` in `mod` that dispatches to `send` + # using the given `extra` args. This falls back on `send` + # if the called name cannot be compiled. + def define_proxy_call(code_generator, name, proxy_target, parameters, *call_args, namespace:, as: name) + mangled_name = build_mangled_name(name) + + call_args.map!(&:inspect) + call_args << parameters if parameters + + # We have to use a different namespace for every target method, because + # if someone defines an attribute that look like an attribute method we could clash, e.g. + # attribute :title_was + # attribute :title + namespace = :"#{namespace}_#{proxy_target}" + + define_call(code_generator, name, proxy_target, mangled_name, parameters, call_args, namespace: namespace, as: as) + end + + def build_mangled_name(name) + mangled_name = name + + unless NAME_COMPILABLE_REGEXP.match?(name) + mangled_name = :"__temp__#{name.unpack1("h*")}" + end + + mangled_name + end + + def define_call(code_generator, name, target_name, mangled_name, parameters, call_args, namespace:, as:) + code_generator.define_cached_method(mangled_name, as: as, namespace: namespace) do |batch| + body = if CALL_COMPILABLE_REGEXP.match?(target_name) + "self.#{target_name}(#{call_args.join(", ")})" + else + call_args.unshift(":'#{target_name}'") + "send(#{call_args.join(", ")})" + end + + batch << + "def #{mangled_name}(#{parameters || ''})" << + body << + "end" + end + end + + class AttributeMethodPattern # :nodoc: + attr_reader :prefix, :suffix, :proxy_target, :parameters + + AttributeMethod = Struct.new(:proxy_target, :attr_name) + + def initialize(prefix: "", suffix: "", parameters: nil) + @prefix = prefix + @suffix = suffix + @parameters = parameters.nil? ? "..." : parameters + @regex = /\A(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})\z/ + @proxy_target = "#{@prefix}attribute#{@suffix}" + @method_name = "#{prefix}%s#{suffix}" + end + + def match(method_name) + if @regex =~ method_name + AttributeMethod.new(proxy_target, $1) + end + end + + def method_name(attr_name) + @method_name % attr_name + end + end + end + + # Allows access to the object attributes, which are held in the hash + # returned by attributes, as though they were first-class + # methods. So a +Person+ class with a +name+ attribute can for example use + # Person#name and Person#name= and never directly use + # the attributes hash -- except for multiple assignments with + # ActiveRecord::Base#attributes=. + # + # It's also possible to instantiate related objects, so a Client + # class belonging to the +clients+ table with a +master_id+ foreign key + # can instantiate master through Client#master. + def method_missing(method, ...) + if respond_to_without_attributes?(method, true) + super + else + match = matched_attribute_method(method.name) + match ? attribute_missing(match, ...) : super + end + end + + # +attribute_missing+ is like +method_missing+, but for attributes. When + # +method_missing+ is called we check to see if there is a matching + # attribute method. If so, we tell +attribute_missing+ to dispatch the + # attribute. This method can be overloaded to customize the behavior. + def attribute_missing(match, ...) + __send__(match.proxy_target, match.attr_name, ...) + end + + # A +Person+ instance with a +name+ attribute can ask + # person.respond_to?(:name), person.respond_to?(:name=), + # and person.respond_to?(:name?) which will all return +true+. + alias :respond_to_without_attributes? :respond_to? + def respond_to?(method, include_private_methods = false) + if super + true + elsif !include_private_methods && super(method, true) + # If we're here then we haven't found among non-private methods + # but found among all methods. Which means that the given method is private. + false + else + !matched_attribute_method(method.to_s).nil? + end + end + + private + def attribute_method?(attr_name) + respond_to_without_attributes?(:attributes) && attributes.include?(attr_name) + end + + # Returns a struct representing the matching attribute method. + # The struct's attributes are prefix, base and suffix. + def matched_attribute_method(method_name) + matches = self.class.send(:attribute_method_patterns_matching, method_name) + matches.detect { |match| attribute_method?(match.attr_name) } + end + + def missing_attribute(attr_name, stack) + raise ActiveModel::MissingAttributeError, "missing attribute '#{attr_name}' for #{self.class}", stack + end + + def _read_attribute(attr) + __send__(attr) + end + + module AttrNames # :nodoc: + DEF_SAFE_NAME = /\A[a-zA-Z_]\w*\z/ + + # We want to generate the methods via module_eval rather than + # define_method, because define_method is slower on dispatch. + # + # But sometimes the database might return columns with + # characters that are not allowed in normal method names (like + # 'my_column(omg)'. So to work around this we first define with + # the __temp__ identifier, and then use alias method to rename + # it to what we want. + # + # We are also defining a constant to hold the frozen string of + # the attribute name. Using a constant means that we do not have + # to allocate an object on each call to the attribute method. + # Making it frozen means that it doesn't get duped when used to + # key the @attributes in read_attribute. + def self.define_attribute_accessor_method(owner, attr_name, writer: false) + method_name = "#{attr_name}#{'=' if writer}" + if attr_name.ascii_only? && DEF_SAFE_NAME.match?(attr_name) + yield method_name, "'#{attr_name}'" + else + safe_name = attr_name.unpack1("h*") + const_name = "ATTR_#{safe_name}" + const_set(const_name, attr_name) unless const_defined?(const_name) + temp_method_name = "__temp__#{safe_name}#{'=' if writer}" + attr_name_expr = "::ActiveModel::AttributeMethods::AttrNames::#{const_name}" + yield temp_method_name, attr_name_expr + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_mutation_tracker.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_mutation_tracker.rb new file mode 100644 index 00000000..a683a94f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_mutation_tracker.rb @@ -0,0 +1,189 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/indifferent_access" +require "active_support/core_ext/object/duplicable" + +module ActiveModel + class AttributeMutationTracker # :nodoc: + OPTION_NOT_GIVEN = Object.new + + def initialize(attributes) + @attributes = attributes + end + + def changed_attribute_names + attr_names.select { |attr_name| changed?(attr_name) } + end + + def changed_values + attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result| + if changed?(attr_name) + result[attr_name] = original_value(attr_name) + end + end + end + + def changes + attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result| + if change = change_to_attribute(attr_name) + result.merge!(attr_name => change) + end + end + end + + def change_to_attribute(attr_name) + if changed?(attr_name) + [original_value(attr_name), fetch_value(attr_name)] + end + end + + def any_changes? + attr_names.any? { |attr| changed?(attr) } + end + + def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) + attribute_changed?(attr_name) && + (OPTION_NOT_GIVEN == from || original_value(attr_name) == type_cast(attr_name, from)) && + (OPTION_NOT_GIVEN == to || fetch_value(attr_name) == type_cast(attr_name, to)) + end + + def changed_in_place?(attr_name) + attributes[attr_name].changed_in_place? + end + + def forget_change(attr_name) + attributes[attr_name] = attributes[attr_name].forgetting_assignment + forced_changes.delete(attr_name) + end + + def original_value(attr_name) + attributes[attr_name].original_value + end + + def force_change(attr_name) + forced_changes[attr_name] = fetch_value(attr_name) + end + + private + attr_reader :attributes + + def forced_changes + @forced_changes ||= {} + end + + def attr_names + attributes.keys + end + + def attribute_changed?(attr_name) + forced_changes.include?(attr_name) || !!attributes[attr_name].changed? + end + + def fetch_value(attr_name) + attributes.fetch_value(attr_name) + end + + def type_cast(attr_name, value) + attributes[attr_name].type_cast(value) + end + end + + class ForcedMutationTracker < AttributeMutationTracker # :nodoc: + def initialize(attributes) + super + @finalized_changes = nil + end + + def changed_in_place?(attr_name) + false + end + + def change_to_attribute(attr_name) + if finalized_changes&.include?(attr_name) + finalized_changes[attr_name].dup + else + super + end + end + + def forget_change(attr_name) + forced_changes.delete(attr_name) + end + + def original_value(attr_name) + if changed?(attr_name) + forced_changes[attr_name] + else + fetch_value(attr_name) + end + end + + def force_change(attr_name) + forced_changes[attr_name] = clone_value(attr_name) unless attribute_changed?(attr_name) + end + + def finalize_changes + @finalized_changes = changes + end + + private + attr_reader :finalized_changes + + def attr_names + forced_changes.keys + end + + def attribute_changed?(attr_name) + forced_changes.include?(attr_name) + end + + def fetch_value(attr_name) + attributes.send(:_read_attribute, attr_name) + end + + def clone_value(attr_name) + value = fetch_value(attr_name) + value.duplicable? ? value.clone : value + rescue TypeError, NoMethodError + value + end + + def type_cast(attr_name, value) + value + end + end + + class NullMutationTracker # :nodoc: + include Singleton + + def changed_attribute_names + [] + end + + def changed_values + {} + end + + def changes + {} + end + + def change_to_attribute(attr_name) + end + + def any_changes? + false + end + + def changed?(attr_name, **) + false + end + + def changed_in_place?(attr_name) + false + end + + def original_value(attr_name) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_registration.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_registration.rb new file mode 100644 index 00000000..3159b021 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_registration.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +require "active_support/core_ext/class/subclasses" +require "active_model/attribute_set" +require "active_model/attribute/user_provided_default" + +module ActiveModel + module AttributeRegistration # :nodoc: + extend ActiveSupport::Concern + + module ClassMethods # :nodoc: + def attribute(name, type = nil, default: (no_default = true), **options) + name = resolve_attribute_name(name) + type = resolve_type_name(type, **options) if type.is_a?(Symbol) + type = hook_attribute_type(name, type) if type + + pending_attribute_modifications << PendingType.new(name, type) if type || no_default + pending_attribute_modifications << PendingDefault.new(name, default) unless no_default + + reset_default_attributes + end + + def decorate_attributes(names = nil, &decorator) # :nodoc: + names = names&.map { |name| resolve_attribute_name(name) } + + pending_attribute_modifications << PendingDecorator.new(names, decorator) + + reset_default_attributes + end + + def _default_attributes # :nodoc: + @default_attributes ||= AttributeSet.new({}).tap do |attribute_set| + apply_pending_attribute_modifications(attribute_set) + end + end + + def attribute_types # :nodoc: + @attribute_types ||= _default_attributes.cast_types.tap do |hash| + hash.default = Type.default_value + end + end + + def type_for_attribute(attribute_name, &block) + attribute_name = resolve_attribute_name(attribute_name) + + if block + attribute_types.fetch(attribute_name, &block) + else + attribute_types[attribute_name] + end + end + + private + PendingType = Struct.new(:name, :type) do # :nodoc: + def apply_to(attribute_set) + attribute = attribute_set[name] + attribute_set[name] = attribute.with_type(type || attribute.type) + end + end + + PendingDefault = Struct.new(:name, :default) do # :nodoc: + def apply_to(attribute_set) + attribute_set[name] = attribute_set[name].with_user_default(default) + end + end + + PendingDecorator = Struct.new(:names, :decorator) do # :nodoc: + def apply_to(attribute_set) + (names || attribute_set.keys).each do |name| + attribute = attribute_set[name] + type = decorator.call(name, attribute.type) + attribute_set[name] = attribute.with_type(type) if type + end + end + end + + def pending_attribute_modifications + @pending_attribute_modifications ||= [] + end + + def apply_pending_attribute_modifications(attribute_set) + if superclass.respond_to?(:apply_pending_attribute_modifications, true) + superclass.send(:apply_pending_attribute_modifications, attribute_set) + end + + pending_attribute_modifications.each do |modification| + modification.apply_to(attribute_set) + end + end + + def reset_default_attributes + reset_default_attributes! + subclasses.each { |subclass| subclass.send(:reset_default_attributes) } + end + + def reset_default_attributes! + @default_attributes = nil + @attribute_types = nil + end + + def resolve_attribute_name(name) + name.to_s + end + + def resolve_type_name(name, **options) + Type.lookup(name, **options) + end + + # Hook for other modules to override. The attribute type is passed + # through this method immediately after it is resolved, before any type + # decorations are applied. + def hook_attribute_type(attribute, type) + type + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_set.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_set.rb new file mode 100644 index 00000000..3d418ee9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_set.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" +require "active_support/core_ext/object/deep_dup" +require "active_model/attribute_set/builder" +require "active_model/attribute_set/yaml_encoder" + +module ActiveModel + class AttributeSet # :nodoc: + delegate :each_value, :fetch, :except, to: :attributes + + def initialize(attributes) + @attributes = attributes + end + + def [](name) + @attributes[name] || default_attribute(name) + end + + def []=(name, value) + @attributes[name] = value + end + + def cast_types + attributes.transform_values(&:type) + end + + def values_before_type_cast + attributes.transform_values(&:value_before_type_cast) + end + + def values_for_database + attributes.transform_values(&:value_for_database) + end + + def to_hash + keys.index_with { |name| self[name].value } + end + alias :to_h :to_hash + + def key?(name) + attributes.key?(name) && self[name].initialized? + end + alias :include? :key? + + def keys + attributes.each_key.select { |name| self[name].initialized? } + end + + def fetch_value(name, &block) + self[name].value(&block) + end + + def write_from_database(name, value) + @attributes[name] = self[name].with_value_from_database(value) + end + + def write_from_user(name, value) + raise FrozenError, "can't modify frozen attributes" if frozen? + @attributes[name] = self[name].with_value_from_user(value) + value + end + + def write_cast_value(name, value) + @attributes[name] = self[name].with_cast_value(value) + end + + def freeze + attributes.freeze + super + end + + def deep_dup + AttributeSet.new(attributes.transform_values(&:deep_dup)) + end + + def initialize_dup(_) + @attributes = @attributes.dup + super + end + + def initialize_clone(_) + @attributes = @attributes.clone + super + end + + def reset(key) + if key?(key) + write_from_database(key, nil) + end + end + + def accessed + attributes.each_key.select { |name| self[name].has_been_read? } + end + + def map(&block) + new_attributes = attributes.transform_values(&block) + AttributeSet.new(new_attributes) + end + + def reverse_merge!(target_attributes) + attributes.reverse_merge!(target_attributes.attributes) && self + end + + def ==(other) + other.is_a?(AttributeSet) && attributes == other.send(:attributes) + end + + protected + attr_reader :attributes + + private + def default_attribute(name) + Attribute.null(name) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_set/builder.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_set/builder.rb new file mode 100644 index 00000000..36628005 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_set/builder.rb @@ -0,0 +1,182 @@ +# frozen_string_literal: true + +require "active_model/attribute" + +module ActiveModel + class AttributeSet # :nodoc: + class Builder # :nodoc: + attr_reader :types, :default_attributes + + def initialize(types, default_attributes = {}) + @types = types + @default_attributes = default_attributes + end + + def build_from_database(values = {}, additional_types = {}) + LazyAttributeSet.new(values, types, additional_types, default_attributes) + end + end + end + + class LazyAttributeSet < AttributeSet # :nodoc: + def initialize(values, types, additional_types, default_attributes, attributes = {}) + super(attributes) + @values = values + @types = types + @additional_types = additional_types + @default_attributes = default_attributes + @casted_values = {} + @materialized = false + end + + def key?(name) + (values.key?(name) || types.key?(name) || @attributes.key?(name)) && self[name].initialized? + end + + def keys + keys = values.keys | types.keys | @attributes.keys + keys.keep_if { |name| self[name].initialized? } + end + + def fetch_value(name, &block) + if attr = @attributes[name] + return attr.value(&block) + end + + @casted_values.fetch(name) do + value_present = true + value = values.fetch(name) { value_present = false } + + if value_present + type = additional_types.fetch(name, types[name]) + @casted_values[name] = type.deserialize(value) + else + attr = default_attribute(name, value_present, value) + attr.value(&block) + end + end + end + + protected + def attributes + unless @materialized + values.each_key { |key| self[key] } + types.each_key { |key| self[key] } + @materialized = true + end + @attributes + end + + private + attr_reader :values, :types, :additional_types, :default_attributes + + def default_attribute( + name, + value_present = true, + value = values.fetch(name) { value_present = false } + ) + type = additional_types.fetch(name, types[name]) + + if value_present + @attributes[name] = Attribute.from_database(name, value, type, @casted_values[name]) + elsif types.key?(name) + if attr = default_attributes[name] + @attributes[name] = attr.dup + else + @attributes[name] = Attribute.uninitialized(name, type) + end + else + Attribute.null(name) + end + end + end + + class LazyAttributeHash # :nodoc: + delegate :transform_values, :each_value, :fetch, :except, to: :materialize + + def initialize(types, values, additional_types, default_attributes, delegate_hash = {}) + @types = types + @values = values + @additional_types = additional_types + @materialized = false + @delegate_hash = delegate_hash + @default_attributes = default_attributes + end + + def key?(key) + delegate_hash.key?(key) || values.key?(key) || types.key?(key) + end + + def [](key) + delegate_hash[key] || assign_default_value(key) + end + + def []=(key, value) + delegate_hash[key] = value + end + + def deep_dup + dup.tap do |copy| + copy.instance_variable_set(:@delegate_hash, delegate_hash.transform_values(&:dup)) + end + end + + def initialize_dup(_) + @delegate_hash = Hash[delegate_hash] + super + end + + def each_key(&block) + keys = types.keys | values.keys | delegate_hash.keys + keys.each(&block) + end + + def ==(other) + if other.is_a?(LazyAttributeHash) + materialize == other.materialize + else + materialize == other + end + end + + def marshal_dump + [@types, @values, @additional_types, @default_attributes, @delegate_hash] + end + + def marshal_load(values) + initialize(*values) + end + + protected + def materialize + unless @materialized + values.each_key { |key| self[key] } + types.each_key { |key| self[key] } + unless frozen? + @materialized = true + end + end + delegate_hash + end + + private + attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes + + def assign_default_value(name) + type = additional_types.fetch(name, types[name]) + value_present = true + value = values.fetch(name) { value_present = false } + + if value_present + delegate_hash[name] = Attribute.from_database(name, value, type) + elsif types.key?(name) + attr = default_attributes[name] + if attr + delegate_hash[name] = attr.dup + else + delegate_hash[name] = Attribute.uninitialized(name, type) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_set/yaml_encoder.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_set/yaml_encoder.rb new file mode 100644 index 00000000..ea1efc16 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attribute_set/yaml_encoder.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module ActiveModel + class AttributeSet + # Attempts to do more intelligent YAML dumping of an + # ActiveModel::AttributeSet to reduce the size of the resulting string + class YAMLEncoder # :nodoc: + def initialize(default_types) + @default_types = default_types + end + + def encode(attribute_set, coder) + coder["concise_attributes"] = attribute_set.each_value.map do |attr| + if attr.type.equal?(default_types[attr.name]) + attr.with_type(nil) + else + attr + end + end + end + + def decode(coder) + if coder["attributes"] + coder["attributes"] + else + attributes_hash = Hash[coder["concise_attributes"].map do |attr| + if attr.type.nil? + attr = attr.with_type(default_types[attr.name]) + end + [attr.name, attr] + end] + AttributeSet.new(attributes_hash) + end + end + + private + attr_reader :default_types + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attributes.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attributes.rb new file mode 100644 index 00000000..134f1206 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/attributes.rb @@ -0,0 +1,165 @@ +# frozen_string_literal: true + +module ActiveModel + # = Active \Model \Attributes + # + # The Attributes module allows models to define attributes beyond simple Ruby + # readers and writers. Similar to Active Record attributes, which are + # typically inferred from the database schema, Active Model Attributes are + # aware of data types, can have default values, and can handle casting and + # serialization. + # + # To use Attributes, include the module in your model class and define your + # attributes using the +attribute+ macro. It accepts a name, a type, a default + # value, and any other options supported by the attribute type. + # + # ==== Examples + # + # class Person + # include ActiveModel::Attributes + # + # attribute :name, :string + # attribute :active, :boolean, default: true + # end + # + # person = Person.new + # person.name = "Volmer" + # + # person.name # => "Volmer" + # person.active # => true + module Attributes + extend ActiveSupport::Concern + include ActiveModel::AttributeRegistration + include ActiveModel::AttributeMethods + + included do + attribute_method_suffix "=", parameters: "value" + end + + module ClassMethods + ## + # :call-seq: attribute(name, cast_type = nil, default: nil, **options) + # + # Defines a model attribute. In addition to the attribute name, a cast + # type and default value may be specified, as well as any options + # supported by the given cast type. + # + # class Person + # include ActiveModel::Attributes + # + # attribute :name, :string + # attribute :active, :boolean, default: true + # end + # + # person = Person.new + # person.name = "Volmer" + # + # person.name # => "Volmer" + # person.active # => true + def attribute(name, ...) + super + define_attribute_method(name) + end + + # Returns an array of attribute names as strings. + # + # class Person + # include ActiveModel::Attributes + # + # attribute :name, :string + # attribute :age, :integer + # end + # + # Person.attribute_names # => ["name", "age"] + def attribute_names + attribute_types.keys + end + + ## + # :method: type_for_attribute + # :call-seq: type_for_attribute(attribute_name, &block) + # + # Returns the type of the specified attribute after applying any + # modifiers. This method is the only valid source of information for + # anything related to the types of a model's attributes. The return value + # of this method will implement the interface described by + # ActiveModel::Type::Value (though the object itself may not subclass it). + #-- + # Implemented by ActiveModel::AttributeRegistration::ClassMethods#type_for_attribute. + + ## + private + def define_method_attribute=(canonical_name, owner:, as: canonical_name) + ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method( + owner, canonical_name, writer: true, + ) do |temp_method_name, attr_name_expr| + owner.define_cached_method(temp_method_name, as: "#{as}=", namespace: :active_model) do |batch| + batch << + "def #{temp_method_name}(value)" << + " _write_attribute(#{attr_name_expr}, value)" << + "end" + end + end + end + end + + def initialize(*) # :nodoc: + @attributes = self.class._default_attributes.deep_dup + super + end + + def initialize_dup(other) # :nodoc: + @attributes = @attributes.deep_dup + super + end + + # Returns a hash of all the attributes with their names as keys and the + # values of the attributes as values. + # + # class Person + # include ActiveModel::Attributes + # + # attribute :name, :string + # attribute :age, :integer + # end + # + # person = Person.new + # person.name = "Francesco" + # person.age = 22 + # + # person.attributes # => { "name" => "Francesco", "age" => 22} + def attributes + @attributes.to_hash + end + + # Returns an array of attribute names as strings. + # + # class Person + # include ActiveModel::Attributes + # + # attribute :name, :string + # attribute :age, :integer + # end + # + # person = Person.new + # person.attribute_names # => ["name", "age"] + def attribute_names + @attributes.keys + end + + def freeze # :nodoc: + @attributes = @attributes.clone.freeze unless frozen? + super + end + + private + def _write_attribute(attr_name, value) + @attributes.write_from_user(attr_name, value) + end + alias :attribute= :_write_attribute + + def attribute(attr_name) + @attributes.fetch_value(attr_name) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/callbacks.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/callbacks.rb new file mode 100644 index 00000000..3c0d4a54 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/callbacks.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/hash/keys" + +module ActiveModel + # = Active \Model \Callbacks + # + # Provides an interface for any class to have Active Record like callbacks. + # + # Like the Active Record methods, the callback chain is aborted as soon as + # one of the methods throws +:abort+. + # + # First, extend +ActiveModel::Callbacks+ from the class you are creating: + # + # class MyModel + # extend ActiveModel::Callbacks + # end + # + # Then define a list of methods that you want callbacks attached to: + # + # define_model_callbacks :create, :update + # + # This will provide all three standard callbacks (before, around and after) + # for both the :create and :update methods. To implement, + # you need to wrap the methods you want callbacks on in a block so that the + # callbacks get a chance to fire: + # + # def create + # run_callbacks :create do + # # Your create action methods here + # end + # end + # + # Then in your class, you can use the +before_create+, +after_create+, and + # +around_create+ methods, just as you would in an Active Record model. + # + # before_create :action_before_create + # + # def action_before_create + # # Your code here + # end + # + # When defining an around callback remember to yield to the block, otherwise + # it won't be executed: + # + # around_create :log_status + # + # def log_status + # puts 'going to call the block...' + # yield + # puts 'block successfully called.' + # end + # + # You can choose to have only specific callbacks by passing a hash to the + # +define_model_callbacks+ method. + # + # define_model_callbacks :create, only: [:after, :before] + # + # Would only create the +after_create+ and +before_create+ callback methods in + # your class. + # + # NOTE: Defining the same callback multiple times will overwrite previous callback definitions. + # + module Callbacks + def self.extended(base) # :nodoc: + base.class_eval do + include ActiveSupport::Callbacks + end + end + + # +define_model_callbacks+ accepts the same options +define_callbacks+ does, + # in case you want to overwrite a default. Besides that, it also accepts an + # :only option, where you can choose if you want all types (before, + # around or after) or just some. + # + # define_model_callbacks :initialize, only: :after + # + # Note, the only: hash will apply to all callbacks defined + # on that method call. To get around this you can call the +define_model_callbacks+ + # method as many times as you need. + # + # define_model_callbacks :create, only: :after + # define_model_callbacks :update, only: :before + # define_model_callbacks :destroy, only: :around + # + # Would create +after_create+, +before_update+, and +around_destroy+ methods + # only. + # + # You can pass in a class to before_, after_ and around_, + # in which case the callback will call that class's _ method + # passing the object that the callback is being called on. + # + # class MyModel + # extend ActiveModel::Callbacks + # define_model_callbacks :create + # + # before_create AnotherClass + # end + # + # class AnotherClass + # def self.before_create( obj ) + # # obj is the MyModel instance that the callback is being called on + # end + # end + # + # NOTE: +method_name+ passed to +define_model_callbacks+ must not end with + # !, ? or =. + def define_model_callbacks(*callbacks) + options = callbacks.extract_options! + options = { + skip_after_callbacks_if_terminated: true, + scope: [:kind, :name], + only: [:before, :around, :after] + }.merge!(options) + + types = Array(options.delete(:only)) + + callbacks.each do |callback| + define_callbacks(callback, options) + + types.each do |type| + send("_define_#{type}_model_callback", self, callback) + end + end + end + + private + def _define_before_model_callback(klass, callback) + klass.define_singleton_method("before_#{callback}") do |*args, **options, &block| + options.assert_valid_keys(:if, :unless, :prepend) + set_callback(:"#{callback}", :before, *args, options, &block) + end + end + + def _define_around_model_callback(klass, callback) + klass.define_singleton_method("around_#{callback}") do |*args, **options, &block| + options.assert_valid_keys(:if, :unless, :prepend) + set_callback(:"#{callback}", :around, *args, options, &block) + end + end + + def _define_after_model_callback(klass, callback) + klass.define_singleton_method("after_#{callback}") do |*args, **options, &block| + options.assert_valid_keys(:if, :unless, :prepend) + options[:prepend] = true + conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v| + v != false + } + options[:if] = Array(options[:if]) + [conditional] + set_callback(:"#{callback}", :after, *args, options, &block) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/conversion.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/conversion.rb new file mode 100644 index 00000000..771126f8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/conversion.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +module ActiveModel + # = Active \Model \Conversion + # + # Handles default conversions: #to_model, #to_key, #to_param, and #to_partial_path. + # + # Let's take for example this non-persisted object. + # + # class ContactMessage + # include ActiveModel::Conversion + # + # # ContactMessage are never persisted in the DB + # def persisted? + # false + # end + # end + # + # cm = ContactMessage.new + # cm.to_model == cm # => true + # cm.to_key # => nil + # cm.to_param # => nil + # cm.to_partial_path # => "contact_messages/contact_message" + module Conversion + extend ActiveSupport::Concern + + included do + ## + # :singleton-method: + # + # Accepts a string that will be used as a delimiter of object's key values in the `to_param` method. + class_attribute :param_delimiter, instance_reader: false, default: "-" + end + + # If your object is already designed to implement all of the \Active \Model + # you can use the default :to_model implementation, which simply + # returns +self+. + # + # class Person + # include ActiveModel::Conversion + # end + # + # person = Person.new + # person.to_model == person # => true + # + # If your model does not act like an \Active \Model object, then you should + # define :to_model yourself returning a proxy object that wraps + # your object with \Active \Model compliant methods. + def to_model + self + end + + # Returns an Array of all key attributes if any of the attributes is set, whether or not + # the object is persisted. Returns +nil+ if there are no key attributes. + # + # class Person + # include ActiveModel::Conversion + # attr_accessor :id + # + # def initialize(id) + # @id = id + # end + # end + # + # person = Person.new(1) + # person.to_key # => [1] + def to_key + key = respond_to?(:id) && id + key ? Array(key) : nil + end + + # Returns a +string+ representing the object's key suitable for use in URLs, + # or +nil+ if persisted? is +false+. + # + # class Person + # include ActiveModel::Conversion + # attr_accessor :id + # + # def initialize(id) + # @id = id + # end + # + # def persisted? + # true + # end + # end + # + # person = Person.new(1) + # person.to_param # => "1" + def to_param + (persisted? && (key = to_key) && key.all?) ? key.join(self.class.param_delimiter) : nil + end + + # Returns a +string+ identifying the path associated with the object. + # ActionPack uses this to find a suitable partial to represent the object. + # + # class Person + # include ActiveModel::Conversion + # end + # + # person = Person.new + # person.to_partial_path # => "people/person" + def to_partial_path + self.class._to_partial_path + end + + module ClassMethods # :nodoc: + # Provide a class level cache for #to_partial_path. This is an + # internal method and should not be accessed directly. + def _to_partial_path # :nodoc: + @_to_partial_path ||= if respond_to?(:model_name) + "#{model_name.collection}/#{model_name.element}" + else + element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name)) + collection = ActiveSupport::Inflector.tableize(name) + "#{collection}/#{element}" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/deprecator.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/deprecator.rb new file mode 100644 index 00000000..5c6cf353 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/deprecator.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module ActiveModel + def self.deprecator # :nodoc: + @deprecator ||= ActiveSupport::Deprecation.new + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/dirty.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/dirty.rb new file mode 100644 index 00000000..32e60c35 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/dirty.rb @@ -0,0 +1,422 @@ +# frozen_string_literal: true + +require "active_model/attribute_mutation_tracker" + +module ActiveModel + # = Active \Model \Dirty + # + # Provides a way to track changes in your object in the same way as + # Active Record does. + # + # The requirements for implementing ActiveModel::Dirty are: + # + # * include ActiveModel::Dirty in your object. + # * Call define_attribute_methods passing each method you want to + # track. + # * Call *_will_change! before each change to the tracked attribute. + # * Call changes_applied after the changes are persisted. + # * Call clear_changes_information when you want to reset the changes + # information. + # * Call restore_attributes when you want to restore previous data. + # + # A minimal implementation could be: + # + # class Person + # include ActiveModel::Dirty + # + # define_attribute_methods :name + # + # def initialize + # @name = nil + # end + # + # def name + # @name + # end + # + # def name=(val) + # name_will_change! unless val == @name + # @name = val + # end + # + # def save + # # do persistence work + # + # changes_applied + # end + # + # def reload! + # # get the values from the persistence layer + # + # clear_changes_information + # end + # + # def rollback! + # restore_attributes + # end + # end + # + # A newly instantiated +Person+ object is unchanged: + # + # person = Person.new + # person.changed? # => false + # + # Change the name: + # + # person.name = 'Bob' + # person.changed? # => true + # person.name_changed? # => true + # person.name_changed?(from: nil, to: "Bob") # => true + # person.name_was # => nil + # person.name_change # => [nil, "Bob"] + # person.name = 'Bill' + # person.name_change # => [nil, "Bill"] + # + # Save the changes: + # + # person.save + # person.changed? # => false + # person.name_changed? # => false + # + # Reset the changes: + # + # person.previous_changes # => {"name" => [nil, "Bill"]} + # person.name_previously_changed? # => true + # person.name_previously_changed?(from: nil, to: "Bill") # => true + # person.name_previous_change # => [nil, "Bill"] + # person.name_previously_was # => nil + # person.reload! + # person.previous_changes # => {} + # + # Rollback the changes: + # + # person.name = "Uncle Bob" + # person.rollback! + # person.name # => "Bill" + # person.name_changed? # => false + # + # Assigning the same value leaves the attribute unchanged: + # + # person.name = 'Bill' + # person.name_changed? # => false + # person.name_change # => nil + # + # Which attributes have changed? + # + # person.name = 'Bob' + # person.changed # => ["name"] + # person.changes # => {"name" => ["Bill", "Bob"]} + # + # If an attribute is modified in-place then make use of + # {*_will_change!}[rdoc-label:method-i-2A_will_change-21] to mark that the attribute is changing. + # Otherwise \Active \Model can't track changes to in-place attributes. Note + # that Active Record can detect in-place modifications automatically. You do + # not need to call *_will_change! on Active Record models. + # + # person.name_will_change! + # person.name_change # => ["Bill", "Bill"] + # person.name << 'y' + # person.name_change # => ["Bill", "Billy"] + # + # Methods can be invoked as +name_changed?+ or by passing an argument to the + # generic method attribute_changed?("name"). + module Dirty + extend ActiveSupport::Concern + include ActiveModel::AttributeMethods + + included do + ## + # :method: *_previously_changed? + # + # :call-seq: *_previously_changed?(**options) + # + # This method is generated for each attribute. + # + # Returns true if the attribute previously had unsaved changes. + # + # person = Person.new + # person.name = 'Britanny' + # person.save + # person.name_previously_changed? # => true + # person.name_previously_changed?(from: nil, to: 'Britanny') # => true + + ## + # :method: *_changed? + # + # This method is generated for each attribute. + # + # Returns true if the attribute has unsaved changes. + # + # person = Person.new + # person.name = 'Andrew' + # person.name_changed? # => true + + ## + # :method: *_change + # + # This method is generated for each attribute. + # + # Returns the old and the new value of the attribute. + # + # person = Person.new + # person.name = 'Nick' + # person.name_change # => [nil, 'Nick'] + + ## + # :method: *_will_change! + # + # This method is generated for each attribute. + # + # If an attribute is modified in-place then make use of + # *_will_change! to mark that the attribute is changing. + # Otherwise Active Model can’t track changes to in-place attributes. Note + # that Active Record can detect in-place modifications automatically. You + # do not need to call *_will_change! on Active Record + # models. + # + # person = Person.new('Sandy') + # person.name_will_change! + # person.name_change # => ['Sandy', 'Sandy'] + + ## + # :method: *_was + # + # This method is generated for each attribute. + # + # Returns the old value of the attribute. + # + # person = Person.new(name: 'Steph') + # person.name = 'Stephanie' + # person.name_was # => 'Steph' + + ## + # :method: *_previous_change + # + # This method is generated for each attribute. + # + # Returns the old and the new value of the attribute before the last save. + # + # person = Person.new + # person.name = 'Emmanuel' + # person.save + # person.name_previous_change # => [nil, 'Emmanuel'] + + ## + # :method: *_previously_was + # + # This method is generated for each attribute. + # + # Returns the old value of the attribute before the last save. + # + # person = Person.new + # person.name = 'Sage' + # person.save + # person.name_previously_was # => nil + + ## + # :method: restore_*! + # + # This method is generated for each attribute. + # + # Restores the attribute to the old value. + # + # person = Person.new + # person.name = 'Amanda' + # person.restore_name! + # person.name # => nil + + ## + # :method: clear_*_change + # + # This method is generated for each attribute. + # + # Clears all dirty data of the attribute: current changes and previous changes. + # + # person = Person.new(name: 'Chris') + # person.name = 'Jason' + # person.name_change # => ['Chris', 'Jason'] + # person.clear_name_change + # person.name_change # => nil + + attribute_method_suffix "_previously_changed?", "_changed?", parameters: "**options" + attribute_method_suffix "_change", "_will_change!", "_was", parameters: false + attribute_method_suffix "_previous_change", "_previously_was", parameters: false + attribute_method_affix prefix: "restore_", suffix: "!", parameters: false + attribute_method_affix prefix: "clear_", suffix: "_change", parameters: false + end + + def initialize_dup(other) # :nodoc: + super + @mutations_from_database = nil + end + + def init_attributes(other) # :nodoc: + attrs = super + if other.persisted? && self.class.respond_to?(:_default_attributes) + self.class._default_attributes.map do |attr| + attr.with_value_from_user(attrs.fetch_value(attr.name)) + end + else + attrs + end + end + + def as_json(options = {}) # :nodoc: + except = [*options[:except], "mutations_from_database", "mutations_before_last_save"] + options = options.merge except: except + super(options) + end + + # Clears dirty data and moves +changes+ to +previous_changes+ and + # +mutations_from_database+ to +mutations_before_last_save+ respectively. + def changes_applied + unless defined?(@attributes) + mutations_from_database.finalize_changes + end + @mutations_before_last_save = mutations_from_database + forget_attribute_assignments + @mutations_from_database = nil + end + + # Returns +true+ if any of the attributes has unsaved changes, +false+ otherwise. + # + # person.changed? # => false + # person.name = 'bob' + # person.changed? # => true + def changed? + mutations_from_database.any_changes? + end + + # Returns an array with the name of the attributes with unsaved changes. + # + # person.changed # => [] + # person.name = 'bob' + # person.changed # => ["name"] + def changed + mutations_from_database.changed_attribute_names + end + + # Dispatch target for {*_changed?}[rdoc-label:method-i-2A_changed-3F] attribute methods. + def attribute_changed?(attr_name, **options) + mutations_from_database.changed?(attr_name.to_s, **options) + end + + # Dispatch target for {*_was}[rdoc-label:method-i-2A_was] attribute methods. + def attribute_was(attr_name) + mutations_from_database.original_value(attr_name.to_s) + end + + # Dispatch target for {*_previously_changed?}[rdoc-label:method-i-2A_previously_changed-3F] attribute methods. + def attribute_previously_changed?(attr_name, **options) + mutations_before_last_save.changed?(attr_name.to_s, **options) + end + + # Dispatch target for {*_previously_was}[rdoc-label:method-i-2A_previously_was] attribute methods. + def attribute_previously_was(attr_name) + mutations_before_last_save.original_value(attr_name.to_s) + end + + # Restore all previous data of the provided attributes. + def restore_attributes(attr_names = changed) + attr_names.each { |attr_name| restore_attribute!(attr_name) } + end + + # Clears all dirty data: current changes and previous changes. + def clear_changes_information + @mutations_before_last_save = nil + forget_attribute_assignments + @mutations_from_database = nil + end + + def clear_attribute_changes(attr_names) + attr_names.each do |attr_name| + clear_attribute_change(attr_name) + end + end + + # Returns a hash of the attributes with unsaved changes indicating their original + # values like attr => original value. + # + # person.name # => "bob" + # person.name = 'robert' + # person.changed_attributes # => {"name" => "bob"} + def changed_attributes + mutations_from_database.changed_values + end + + # Returns a hash of changed attributes indicating their original + # and new values like attr => [original value, new value]. + # + # person.changes # => {} + # person.name = 'bob' + # person.changes # => { "name" => ["bill", "bob"] } + def changes + mutations_from_database.changes + end + + # Returns a hash of attributes that were changed before the model was saved. + # + # person.name # => "bob" + # person.name = 'robert' + # person.save + # person.previous_changes # => {"name" => ["bob", "robert"]} + def previous_changes + mutations_before_last_save.changes + end + + def attribute_changed_in_place?(attr_name) # :nodoc: + mutations_from_database.changed_in_place?(attr_name.to_s) + end + + private + def init_internals + super + @mutations_before_last_save = nil + @mutations_from_database = nil + end + + def clear_attribute_change(attr_name) + mutations_from_database.forget_change(attr_name.to_s) + end + + def mutations_from_database + @mutations_from_database ||= if defined?(@attributes) + ActiveModel::AttributeMutationTracker.new(@attributes) + else + ActiveModel::ForcedMutationTracker.new(self) + end + end + + def forget_attribute_assignments + @attributes = @attributes.map(&:forgetting_assignment) if defined?(@attributes) + end + + def mutations_before_last_save + @mutations_before_last_save ||= ActiveModel::NullMutationTracker.instance + end + + # Dispatch target for *_change attribute methods. + def attribute_change(attr_name) + mutations_from_database.change_to_attribute(attr_name.to_s) + end + + # Dispatch target for *_previous_change attribute methods. + def attribute_previous_change(attr_name) + mutations_before_last_save.change_to_attribute(attr_name.to_s) + end + + # Dispatch target for *_will_change! attribute methods. + def attribute_will_change!(attr_name) + mutations_from_database.force_change(attr_name.to_s) + end + + # Dispatch target for restore_*! attribute methods. + def restore_attribute!(attr_name) + attr_name = attr_name.to_s + if attribute_changed?(attr_name) + __send__("#{attr_name}=", attribute_was(attr_name)) + clear_attribute_change(attr_name) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/error.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/error.rb new file mode 100644 index 00000000..51124a77 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/error.rb @@ -0,0 +1,208 @@ +# frozen_string_literal: true + +require "active_support/core_ext/class/attribute" + +module ActiveModel + # = Active \Model \Error + # + # Represents one single error + class Error + CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict] + MESSAGE_OPTIONS = [:message] + + class_attribute :i18n_customize_full_message, default: false + + def self.full_message(attribute, message, base) # :nodoc: + return message if attribute == :base + + base_class = base.class + attribute = attribute.to_s + + if i18n_customize_full_message && base_class.respond_to?(:i18n_scope) + attribute = attribute.remove(/\[\d+\]/) + parts = attribute.split(".") + attribute_name = parts.pop + namespace = parts.join("/") unless parts.empty? + attributes_scope = "#{base_class.i18n_scope}.errors.models" + + if namespace + defaults = base_class.lookup_ancestors.map do |klass| + [ + :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.attributes.#{attribute_name}.format", + :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.format", + ] + end + else + defaults = base_class.lookup_ancestors.map do |klass| + [ + :"#{attributes_scope}.#{klass.model_name.i18n_key}.attributes.#{attribute_name}.format", + :"#{attributes_scope}.#{klass.model_name.i18n_key}.format", + ] + end + end + + defaults.flatten! + else + defaults = [] + end + + defaults << :"errors.format" + defaults << "%{attribute} %{message}" + + attr_name = attribute.remove(/\.base\z/).tr(".", "_").humanize + attr_name = base_class.human_attribute_name(attribute, { + default: attr_name, + base: base, + }) + + I18n.t(defaults.shift, + default: defaults, + attribute: attr_name, + message: message) + end + + def self.generate_message(attribute, type, base, options) # :nodoc: + type = options.delete(:message) if options[:message].is_a?(Symbol) + value = (attribute != :base ? base.read_attribute_for_validation(attribute) : nil) + + options = { + model: base.model_name.human, + attribute: base.class.human_attribute_name(attribute, { base: base }), + value: value, + object: base + }.merge!(options) + + if base.class.respond_to?(:i18n_scope) + i18n_scope = base.class.i18n_scope.to_s + attribute = attribute.to_s.remove(/\[\d+\]/) + + defaults = base.class.lookup_ancestors.flat_map do |klass| + [ :"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}", + :"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ] + end + defaults << :"#{i18n_scope}.errors.messages.#{type}" + + catch(:exception) do + translation = I18n.translate(defaults.first, **options.merge(default: defaults.drop(1), throw: true)) + return translation unless translation.nil? + end unless options[:message] + else + defaults = [] + end + + defaults << :"errors.attributes.#{attribute}.#{type}" + defaults << :"errors.messages.#{type}" + + key = defaults.shift + defaults = options.delete(:message) if options[:message] + options[:default] = defaults + + I18n.translate(key, **options) + end + + def initialize(base, attribute, type = :invalid, **options) + @base = base + @attribute = attribute + @raw_type = type + @type = type || :invalid + @options = options + end + + def initialize_dup(other) # :nodoc: + @attribute = @attribute.dup + @raw_type = @raw_type.dup + @type = @type.dup + @options = @options.deep_dup + end + + # The object which the error belongs to + attr_reader :base + # The attribute of +base+ which the error belongs to + attr_reader :attribute + # The type of error, defaults to +:invalid+ unless specified + attr_reader :type + # The raw value provided as the second parameter when calling + # errors#add + attr_reader :raw_type + # The options provided when calling errors#add + attr_reader :options + + # Returns the error message. + # + # error = ActiveModel::Error.new(person, :name, :too_short, count: 5) + # error.message + # # => "is too short (minimum is 5 characters)" + def message + case raw_type + when Symbol + self.class.generate_message(attribute, raw_type, @base, options.except(*CALLBACKS_OPTIONS)) + else + raw_type + end + end + + # Returns the error details. + # + # error = ActiveModel::Error.new(person, :name, :too_short, count: 5) + # error.details + # # => { error: :too_short, count: 5 } + def details + { error: raw_type }.merge(options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS)) + end + alias_method :detail, :details + + # Returns the full error message. + # + # error = ActiveModel::Error.new(person, :name, :too_short, count: 5) + # error.full_message + # # => "Name is too short (minimum is 5 characters)" + def full_message + self.class.full_message(attribute, message, @base) + end + + # See if error matches provided +attribute+, +type+, and +options+. + # + # Omitted params are not checked for a match. + def match?(attribute, type = nil, **options) + if @attribute != attribute || (type && @type != type) + return false + end + + options.each do |key, value| + if @options[key] != value + return false + end + end + + true + end + + # See if error matches provided +attribute+, +type+, and +options+ exactly. + # + # All params must be equal to Error's own attributes to be considered a + # strict match. + def strict_match?(attribute, type, **options) + return false unless match?(attribute, type) + + options == @options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS) + end + + def ==(other) # :nodoc: + other.is_a?(self.class) && attributes_for_hash == other.attributes_for_hash + end + alias eql? == + + def hash # :nodoc: + attributes_for_hash.hash + end + + def inspect # :nodoc: + "#<#{self.class.name} attribute=#{@attribute}, type=#{@type}, options=#{@options.inspect}>" + end + + protected + def attributes_for_hash + [@base, @attribute, @raw_type, @options.except(*CALLBACKS_OPTIONS)] + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/errors.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/errors.rb new file mode 100644 index 00000000..35980069 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/errors.rb @@ -0,0 +1,547 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/conversions" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/object/deep_dup" +require "active_model/error" +require "active_model/nested_error" +require "forwardable" + +module ActiveModel + # = Active \Model \Errors + # + # Provides error related functionalities you can include in your object + # for handling error messages and interacting with Action View helpers. + # + # A minimal implementation could be: + # + # class Person + # # Required dependency for ActiveModel::Errors + # extend ActiveModel::Naming + # + # def initialize + # @errors = ActiveModel::Errors.new(self) + # end + # + # attr_accessor :name + # attr_reader :errors + # + # def validate! + # errors.add(:name, :blank, message: "cannot be nil") if name.nil? + # end + # + # # The following methods are needed to be minimally implemented + # + # def read_attribute_for_validation(attr) + # send(attr) + # end + # + # def self.human_attribute_name(attr, options = {}) + # attr + # end + # + # def self.lookup_ancestors + # [self] + # end + # end + # + # The last three methods are required in your object for +Errors+ to be + # able to generate error messages correctly and also handle multiple + # languages. Of course, if you extend your object with ActiveModel::Translation + # you will not need to implement the last two. Likewise, using + # ActiveModel::Validations will handle the validation related methods + # for you. + # + # The above allows you to do: + # + # person = Person.new + # person.validate! # => ["cannot be nil"] + # person.errors.full_messages # => ["name cannot be nil"] + # # etc.. + class Errors + include Enumerable + + extend Forwardable + + ## + # :method: each + # + # :call-seq: each(&block) + # + # Iterates through each error object. + # + # person.errors.add(:name, :too_short, count: 2) + # person.errors.each do |error| + # # Will yield <#ActiveModel::Error attribute=name, type=too_short, + # options={:count=>3}> + # end + + ## + # :method: clear + # + # :call-seq: clear + # + # Clears all errors. Clearing the errors does not, however, make the model + # valid. The next time the validations are run (for example, via + # ActiveRecord::Validations#valid?), the errors collection will be filled + # again if any validations fail. + + ## + # :method: empty? + # + # :call-seq: empty? + # + # Returns true if there are no errors. + + ## + # :method: size + # + # :call-seq: size + # + # Returns number of errors. + + def_delegators :@errors, :each, :clear, :empty?, :size, :uniq! + + # The actual array of +Error+ objects + # This method is aliased to objects. + attr_reader :errors + alias :objects :errors + + # Pass in the instance of the object that is using the errors object. + # + # class Person + # def initialize + # @errors = ActiveModel::Errors.new(self) + # end + # end + def initialize(base) + @base = base + @errors = [] + end + + def initialize_dup(other) # :nodoc: + @errors = other.errors.deep_dup + super + end + + # Copies the errors from other. + # For copying errors but keep @base as is. + # + # ==== Parameters + # + # * +other+ - The ActiveModel::Errors instance. + # + # ==== Examples + # + # person.errors.copy!(other) + # + def copy!(other) # :nodoc: + @errors = other.errors.deep_dup + @errors.each { |error| + error.instance_variable_set(:@base, @base) + } + end + + # Imports one error. + # Imported errors are wrapped as a NestedError, + # providing access to original error object. + # If attribute or type needs to be overridden, use +override_options+. + # + # ==== Options + # + # * +:attribute+ - Override the attribute the error belongs to. + # * +:type+ - Override type of the error. + def import(error, override_options = {}) + [:attribute, :type].each do |key| + if override_options.key?(key) + override_options[key] = override_options[key].to_sym + end + end + @errors.append(NestedError.new(@base, error, override_options)) + end + + # Merges the errors from other, + # each Error wrapped as NestedError. + # + # ==== Parameters + # + # * +other+ - The ActiveModel::Errors instance. + # + # ==== Examples + # + # person.errors.merge!(other) + # + def merge!(other) + return errors if equal?(other) + + other.errors.each { |error| + import(error) + } + end + + # Search for errors matching +attribute+, +type+, or +options+. + # + # Only supplied params will be matched. + # + # person.errors.where(:name) # => all name errors. + # person.errors.where(:name, :too_short) # => all name errors being too short + # person.errors.where(:name, :too_short, minimum: 2) # => all name errors being too short and minimum is 2 + def where(attribute, type = nil, **options) + attribute, type, options = normalize_arguments(attribute, type, **options) + @errors.select { |error| + error.match?(attribute, type, **options) + } + end + + # Returns +true+ if the error messages include an error for the given key + # +attribute+, +false+ otherwise. + # + # person.errors.messages # => {:name=>["cannot be nil"]} + # person.errors.include?(:name) # => true + # person.errors.include?(:age) # => false + def include?(attribute) + @errors.any? { |error| + error.match?(attribute.to_sym) + } + end + alias :has_key? :include? + alias :key? :include? + + # Delete messages for +key+. Returns the deleted messages. + # + # person.errors[:name] # => ["cannot be nil"] + # person.errors.delete(:name) # => ["cannot be nil"] + # person.errors[:name] # => [] + def delete(attribute, type = nil, **options) + attribute, type, options = normalize_arguments(attribute, type, **options) + matches = where(attribute, type, **options) + matches.each do |error| + @errors.delete(error) + end + matches.map(&:message).presence + end + + # When passed a symbol or a name of a method, returns an array of errors + # for the method. + # + # person.errors[:name] # => ["cannot be nil"] + # person.errors['name'] # => ["cannot be nil"] + def [](attribute) + messages_for(attribute) + end + + # Returns all error attribute names + # + # person.errors.messages # => {:name=>["cannot be nil", "must be specified"]} + # person.errors.attribute_names # => [:name] + def attribute_names + @errors.map(&:attribute).uniq.freeze + end + + # Returns a Hash that can be used as the JSON representation for this + # object. You can pass the :full_messages option. This determines + # if the JSON object should contain full messages or not (false by default). + # + # person.errors.as_json # => {:name=>["cannot be nil"]} + # person.errors.as_json(full_messages: true) # => {:name=>["name cannot be nil"]} + def as_json(options = nil) + to_hash(options && options[:full_messages]) + end + + # Returns a Hash of attributes with their error messages. If +full_messages+ + # is +true+, it will contain full messages (see +full_message+). + # + # person.errors.to_hash # => {:name=>["cannot be nil"]} + # person.errors.to_hash(true) # => {:name=>["name cannot be nil"]} + def to_hash(full_messages = false) + message_method = full_messages ? :full_message : :message + group_by_attribute.transform_values do |errors| + errors.map(&message_method) + end + end + + undef :to_h + + EMPTY_ARRAY = [].freeze # :nodoc: + + # Returns a Hash of attributes with an array of their error messages. + def messages + hash = to_hash + hash.default = EMPTY_ARRAY + hash.freeze + hash + end + + # Returns a Hash of attributes with an array of their error details. + def details + hash = group_by_attribute.transform_values do |errors| + errors.map(&:details) + end + hash.default = EMPTY_ARRAY + hash.freeze + hash + end + + # Returns a Hash of attributes with an array of their Error objects. + # + # person.errors.group_by_attribute + # # => {:name=>[<#ActiveModel::Error>, <#ActiveModel::Error>]} + def group_by_attribute + @errors.group_by(&:attribute) + end + + # Adds a new error of +type+ on +attribute+. + # More than one error can be added to the same +attribute+. + # If no +type+ is supplied, :invalid is assumed. + # + # person.errors.add(:name) + # # Adds <#ActiveModel::Error attribute=name, type=invalid> + # person.errors.add(:name, :not_implemented, message: "must be implemented") + # # Adds <#ActiveModel::Error attribute=name, type=not_implemented, + # options={:message=>"must be implemented"}> + # + # person.errors.messages + # # => {:name=>["is invalid", "must be implemented"]} + # + # If +type+ is a string, it will be used as error message. + # + # If +type+ is a symbol, it will be translated using the appropriate + # scope (see +generate_message+). + # + # person.errors.add(:name, :blank) + # person.errors.messages + # # => {:name=>["can't be blank"]} + # + # person.errors.add(:name, :too_long, count: 25) + # person.errors.messages + # # => ["is too long (maximum is 25 characters)"] + # + # If +type+ is a proc, it will be called, allowing for things like + # Time.now to be used within an error. + # + # If the :strict option is set to +true+, it will raise + # ActiveModel::StrictValidationFailed instead of adding the error. + # :strict option can also be set to any other exception. + # + # person.errors.add(:name, :invalid, strict: true) + # # => ActiveModel::StrictValidationFailed: Name is invalid + # person.errors.add(:name, :invalid, strict: NameIsInvalid) + # # => NameIsInvalid: Name is invalid + # + # person.errors.messages # => {} + # + # +attribute+ should be set to :base if the error is not + # directly associated with a single attribute. + # + # person.errors.add(:base, :name_or_email_blank, + # message: "either name or email must be present") + # person.errors.messages + # # => {:base=>["either name or email must be present"]} + # person.errors.details + # # => {:base=>[{error: :name_or_email_blank}]} + def add(attribute, type = :invalid, **options) + attribute, type, options = normalize_arguments(attribute, type, **options) + error = Error.new(@base, attribute, type, **options) + + if exception = options[:strict] + exception = ActiveModel::StrictValidationFailed if exception == true + raise exception, error.full_message + end + + @errors.append(error) + + error + end + + # Returns +true+ if an error matches provided +attribute+ and +type+, + # or +false+ otherwise. +type+ is treated the same as for +add+. + # + # person.errors.add :name, :blank + # person.errors.added? :name, :blank # => true + # person.errors.added? :name, "can't be blank" # => true + # + # If the error requires options, then it returns +true+ with + # the correct options, or +false+ with incorrect or missing options. + # + # person.errors.add :name, :too_long, count: 25 + # person.errors.added? :name, :too_long, count: 25 # => true + # person.errors.added? :name, "is too long (maximum is 25 characters)" # => true + # person.errors.added? :name, :too_long, count: 24 # => false + # person.errors.added? :name, :too_long # => false + # person.errors.added? :name, "is too long" # => false + def added?(attribute, type = :invalid, options = {}) + attribute, type, options = normalize_arguments(attribute, type, **options) + + if type.is_a? Symbol + @errors.any? { |error| + error.strict_match?(attribute, type, **options) + } + else + messages_for(attribute).include?(type) + end + end + + # Returns +true+ if an error on the attribute with the given type is + # present, or +false+ otherwise. +type+ is treated the same as for +add+. + # + # person.errors.add :age + # person.errors.add :name, :too_long, count: 25 + # person.errors.of_kind? :age # => true + # person.errors.of_kind? :name # => false + # person.errors.of_kind? :name, :too_long # => true + # person.errors.of_kind? :name, "is too long (maximum is 25 characters)" # => true + # person.errors.of_kind? :name, :not_too_long # => false + # person.errors.of_kind? :name, "is too long" # => false + def of_kind?(attribute, type = :invalid) + attribute, type = normalize_arguments(attribute, type) + + if type.is_a? Symbol + !where(attribute, type).empty? + else + messages_for(attribute).include?(type) + end + end + + # Returns all the full error messages in an array. + # + # class Person + # validates_presence_of :name, :address, :email + # validates_length_of :name, in: 5..30 + # end + # + # person = Person.create(address: '123 First St.') + # person.errors.full_messages + # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"] + def full_messages + @errors.map(&:full_message) + end + alias :to_a :full_messages + + # Returns all the full error messages for a given attribute in an array. + # + # class Person + # validates_presence_of :name, :email + # validates_length_of :name, in: 5..30 + # end + # + # person = Person.create() + # person.errors.full_messages_for(:name) + # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"] + def full_messages_for(attribute) + where(attribute).map(&:full_message).freeze + end + + # Returns all the error messages for a given attribute in an array. + # + # class Person + # validates_presence_of :name, :email + # validates_length_of :name, in: 5..30 + # end + # + # person = Person.create() + # person.errors.messages_for(:name) + # # => ["is too short (minimum is 5 characters)", "can't be blank"] + def messages_for(attribute) + where(attribute).map(&:message) + end + + # Returns a full message for a given attribute. + # + # person.errors.full_message(:name, 'is invalid') # => "Name is invalid" + def full_message(attribute, message) + Error.full_message(attribute, message, @base) + end + + # Translates an error message in its default scope + # (activemodel.errors.messages). + # + # Error messages are first looked up in activemodel.errors.models.MODEL.attributes.ATTRIBUTE.MESSAGE, + # if it's not there, it's looked up in activemodel.errors.models.MODEL.MESSAGE and if + # that is not there also, it returns the translation of the default message + # (e.g. activemodel.errors.messages.MESSAGE). The translated model + # name, translated attribute name, and the value are available for + # interpolation. + # + # When using inheritance in your models, it will check all the inherited + # models too, but only if the model itself hasn't been found. Say you have + # class Admin < User; end and you wanted the translation for + # the :blank error message for the title attribute, + # it looks for these translations: + # + # * activemodel.errors.models.admin.attributes.title.blank + # * activemodel.errors.models.admin.blank + # * activemodel.errors.models.user.attributes.title.blank + # * activemodel.errors.models.user.blank + # * any default you provided through the +options+ hash (in the activemodel.errors scope) + # * activemodel.errors.messages.blank + # * errors.attributes.title.blank + # * errors.messages.blank + def generate_message(attribute, type = :invalid, options = {}) + Error.generate_message(attribute, type, @base, options) + end + + def inspect # :nodoc: + inspection = @errors.inspect + + "#<#{self.class.name} #{inspection}>" + end + + private + def normalize_arguments(attribute, type, **options) + # Evaluate proc first + if type.respond_to?(:call) + type = type.call(@base, options) + end + + [attribute.to_sym, type, options] + end + end + + # = Active \Model \StrictValidationFailed + # + # Raised when a validation cannot be corrected by end users and are considered + # exceptional. + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :name + # + # validates_presence_of :name, strict: true + # end + # + # person = Person.new + # person.name = nil + # person.valid? + # # => ActiveModel::StrictValidationFailed: Name can't be blank + class StrictValidationFailed < StandardError + end + + # = Active \Model \RangeError + # + # Raised when attribute values are out of range. + class RangeError < ::RangeError + end + + # = Active \Model \UnknownAttributeError + # + # Raised when unknown attributes are supplied via mass assignment. + # + # class Person + # include ActiveModel::AttributeAssignment + # include ActiveModel::Validations + # end + # + # person = Person.new + # person.assign_attributes(name: 'Gorby') + # # => ActiveModel::UnknownAttributeError: unknown attribute 'name' for Person. + class UnknownAttributeError < NoMethodError + attr_reader :record, :attribute + + def initialize(record, attribute) + @record = record + @attribute = attribute + super("unknown attribute '#{attribute}' for #{@record.class}.") + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/forbidden_attributes_protection.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/forbidden_attributes_protection.rb new file mode 100644 index 00000000..e9d43ca6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/forbidden_attributes_protection.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module ActiveModel + # = Active \Model \ForbiddenAttributesError + # + # Raised when forbidden attributes are used for mass assignment. + # + # class Person < ActiveRecord::Base + # end + # + # params = ActionController::Parameters.new(name: 'Bob') + # Person.new(params) + # # => ActiveModel::ForbiddenAttributesError + # + # params.permit! + # Person.new(params) + # # => # + class ForbiddenAttributesError < StandardError + end + + module ForbiddenAttributesProtection # :nodoc: + private + def sanitize_for_mass_assignment(attributes) + if attributes.respond_to?(:permitted?) + raise ActiveModel::ForbiddenAttributesError if !attributes.permitted? + attributes.to_h + else + attributes + end + end + alias :sanitize_forbidden_attributes :sanitize_for_mass_assignment + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/gem_version.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/gem_version.rb new file mode 100644 index 00000000..aa84db60 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/gem_version.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveModel + # Returns the currently loaded version of \Active \Model as a +Gem::Version+. + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 8 + MINOR = 0 + TINY = 2 + PRE = nil + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/lint.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/lint.rb new file mode 100644 index 00000000..09527cdd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/lint.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +module ActiveModel + module Lint + # == Active \Model \Lint \Tests + # + # You can test whether an object is compliant with the Active \Model API by + # including +ActiveModel::Lint::Tests+ in your TestCase. It will + # include tests that tell you whether your object is fully compliant, + # or if not, which aspects of the API are not implemented. + # + # Note an object is not required to implement all APIs in order to work + # with Action Pack. This module only intends to provide guidance in case + # you want all features out of the box. + # + # These tests do not attempt to determine the semantic correctness of the + # returned values. For instance, you could implement valid? to + # always return +true+, and the tests would pass. It is up to you to ensure + # that the values are semantically meaningful. + # + # Objects you pass in are expected to return a compliant object from a call + # to to_model. It is perfectly fine for to_model to return + # +self+. + module Tests + # Passes if the object's model responds to to_key and if calling + # this method returns +nil+ when the object is not persisted. + # Fails otherwise. + # + # to_key returns an Enumerable of all (primary) key attributes + # of the model, and is used to a generate unique DOM id for the object. + def test_to_key + assert_respond_to model, :to_key + def model.persisted?() false end + assert model.to_key.nil?, "to_key should return nil when `persisted?` returns false" + end + + # Passes if the object's model responds to to_param and if + # calling this method returns +nil+ when the object is not persisted. + # Fails otherwise. + # + # to_param is used to represent the object's key in URLs. + # Implementers can decide to either raise an exception or provide a + # default in case the record uses a composite primary key. There are no + # tests for this behavior in lint because it doesn't make sense to force + # any of the possible implementation strategies on the implementer. + def test_to_param + assert_respond_to model, :to_param + def model.to_key() [1] end + def model.persisted?() false end + assert model.to_param.nil?, "to_param should return nil when `persisted?` returns false" + end + + # Passes if the object's model responds to to_partial_path and if + # calling this method returns a string. Fails otherwise. + # + # to_partial_path is used for looking up partials. For example, + # a BlogPost model might return "blog_posts/blog_post". + def test_to_partial_path + assert_respond_to model, :to_partial_path + assert_kind_of String, model.to_partial_path + end + + # Passes if the object's model responds to persisted? and if + # calling this method returns either +true+ or +false+. Fails otherwise. + # + # persisted? is used when calculating the URL for an object. + # If the object is not persisted, a form for that object, for instance, + # will route to the create action. If it is persisted, a form for the + # object will route to the update action. + def test_persisted? + assert_respond_to model, :persisted? + assert_boolean model.persisted?, "persisted?" + end + + # Passes if the object's model responds to model_name both as + # an instance method and as a class method, and if calling this method + # returns a string with some convenience methods: :human, + # :singular and :plural. + # + # Check ActiveModel::Naming for more information. + def test_model_naming + assert_respond_to model.class, :model_name + model_name = model.class.model_name + assert_respond_to model_name, :to_str + assert_respond_to model_name.human, :to_str + assert_respond_to model_name.singular, :to_str + assert_respond_to model_name.plural, :to_str + + assert_respond_to model, :model_name + assert_equal model.model_name, model.class.model_name + end + + # Passes if the object's model responds to errors and if calling + # [](attribute) on the result of this method returns an array. + # Fails otherwise. + # + # errors[attribute] is used to retrieve the errors of a model + # for a given attribute. If errors are present, the method should return + # an array of strings that are the errors for the attribute in question. + # If localization is used, the strings should be localized for the current + # locale. If no error is present, the method should return an empty array. + def test_errors_aref + assert_respond_to model, :errors + assert_equal [], model.errors[:hello], "errors#[] should return an empty Array" + end + + private + def model + assert_respond_to @model, :to_model + @model.to_model + end + + def assert_boolean(result, name) + assert result == true || result == false, "#{name} should be a boolean" + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/locale/en.yml b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/locale/en.yml new file mode 100644 index 00000000..c6a03635 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/locale/en.yml @@ -0,0 +1,38 @@ +en: + errors: + # The default format to use in full error messages. + format: "%{attribute} %{message}" + + # The values :model, :attribute and :value are always available for interpolation + # The value :count is available when applicable. Can be used for pluralization. + messages: + model_invalid: "Validation failed: %{errors}" + inclusion: "is not included in the list" + exclusion: "is reserved" + invalid: "is invalid" + confirmation: "doesn't match %{attribute}" + accepted: "must be accepted" + empty: "can't be empty" + blank: "can't be blank" + present: "must be blank" + too_long: + one: "is too long (maximum is 1 character)" + other: "is too long (maximum is %{count} characters)" + password_too_long: "is too long" + too_short: + one: "is too short (minimum is 1 character)" + other: "is too short (minimum is %{count} characters)" + wrong_length: + one: "is the wrong length (should be 1 character)" + other: "is the wrong length (should be %{count} characters)" + not_a_number: "is not a number" + not_an_integer: "must be an integer" + greater_than: "must be greater than %{count}" + greater_than_or_equal_to: "must be greater than or equal to %{count}" + equal_to: "must be equal to %{count}" + less_than: "must be less than %{count}" + less_than_or_equal_to: "must be less than or equal to %{count}" + other_than: "must be other than %{count}" + in: "must be in %{count}" + odd: "must be odd" + even: "must be even" diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/model.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/model.rb new file mode 100644 index 00000000..86df469b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/model.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module ActiveModel + # = Active \Model \Basic \Model + # + # Allows implementing models similar to ActiveRecord::Base. + # Includes ActiveModel::API for the required interface for an + # object to interact with Action Pack and Action View, but can be + # extended with other functionalities. + # + # A minimal implementation could be: + # + # class Person + # include ActiveModel::Model + # attr_accessor :name, :age + # end + # + # person = Person.new(name: 'bob', age: '18') + # person.name # => "bob" + # person.age # => "18" + # + # If for some reason you need to run code on initialize, make + # sure you call +super+ if you want the attributes hash initialization to + # happen. + # + # class Person + # include ActiveModel::Model + # attr_accessor :id, :name, :omg + # + # def initialize(attributes={}) + # super + # @omg ||= true + # end + # end + # + # person = Person.new(id: 1, name: 'bob') + # person.omg # => true + # + # For more detailed information on other functionalities available, please + # refer to the specific modules included in +ActiveModel::Model+ + # (see below). + module Model + extend ActiveSupport::Concern + include ActiveModel::API + include ActiveModel::Access + + ## + # :method: slice + # + # :call-seq: slice(*methods) + # + # Returns a hash of the given methods with their names as keys and returned + # values as values. + # + # person = Person.new(id: 1, name: "bob") + # person.slice(:id, :name) + # => { "id" => 1, "name" => "bob" } + # + #-- + # Implemented by ActiveModel::Access#slice. + + ## + # :method: values_at + # + # :call-seq: values_at(*methods) + # + # Returns an array of the values returned by the given methods. + # + # person = Person.new(id: 1, name: "bob") + # person.values_at(:id, :name) + # => [1, "bob"] + # + #-- + # Implemented by ActiveModel::Access#values_at. + end + + ActiveSupport.run_load_hooks(:active_model, Model) +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/naming.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/naming.rb new file mode 100644 index 00000000..ee42a22f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/naming.rb @@ -0,0 +1,359 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/except" +require "active_support/core_ext/module/introspection" +require "active_support/core_ext/module/redefine_method" +require "active_support/core_ext/module/delegation" + +module ActiveModel + class Name + include Comparable + + attr_accessor :singular, :plural, :element, :collection, + :singular_route_key, :route_key, :param_key, :i18n_key, + :name + + alias_method :cache_key, :collection + + ## + # :method: == + # + # :call-seq: + # ==(other) + # + # Equivalent to String#==. Returns +true+ if the class name and + # +other+ are equal, otherwise +false+. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name == 'BlogPost' # => true + # BlogPost.model_name == 'Blog Post' # => false + + ## + # :method: === + # + # :call-seq: + # ===(other) + # + # Equivalent to #==. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name === 'BlogPost' # => true + # BlogPost.model_name === 'Blog Post' # => false + + ## + # :method: <=> + # + # :call-seq: + # <=>(other) + # + # Equivalent to String#<=>. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name <=> 'BlogPost' # => 0 + # BlogPost.model_name <=> 'Blog' # => 1 + # BlogPost.model_name <=> 'BlogPosts' # => -1 + + ## + # :method: =~ + # + # :call-seq: + # =~(regexp) + # + # Equivalent to String#=~. Match the class name against the given + # regexp. Returns the position where the match starts or +nil+ if there is + # no match. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name =~ /Post/ # => 4 + # BlogPost.model_name =~ /\d/ # => nil + + ## + # :method: !~ + # + # :call-seq: + # !~(regexp) + # + # Equivalent to String#!~. Match the class name against the given + # regexp. Returns +true+ if there is no match, otherwise +false+. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name !~ /Post/ # => false + # BlogPost.model_name !~ /\d/ # => true + + ## + # :method: eql? + # + # :call-seq: + # eql?(other) + # + # Equivalent to String#eql?. Returns +true+ if the class name and + # +other+ have the same length and content, otherwise +false+. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name.eql?('BlogPost') # => true + # BlogPost.model_name.eql?('Blog Post') # => false + + ## + # :method: match? + # + # :call-seq: + # match?(regexp) + # + # Equivalent to String#match?. Match the class name against the + # given regexp. Returns +true+ if there is a match, otherwise +false+. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name.match?(/Post/) # => true + # BlogPost.model_name.match?(/\d/) # => false + + ## + # :method: to_s + # + # :call-seq: + # to_s() + # + # Returns the class name. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name.to_s # => "BlogPost" + + ## + # :method: to_str + # + # :call-seq: + # to_str() + # + # Equivalent to +to_s+. + delegate :==, :===, :<=>, :=~, :"!~", :eql?, :match?, :to_s, + :to_str, :as_json, to: :name + + # Returns a new ActiveModel::Name instance. By default, the +namespace+ + # and +name+ option will take the namespace and name of the given class + # respectively. + # Use +locale+ argument for singularize and pluralize model name. + # + # module Foo + # class Bar + # end + # end + # + # ActiveModel::Name.new(Foo::Bar).to_s + # # => "Foo::Bar" + def initialize(klass, namespace = nil, name = nil, locale = :en) + @name = name || klass.name + + raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank? + + @unnamespaced = @name.delete_prefix("#{namespace.name}::") if namespace + @klass = klass + @singular = _singularize(@name) + @plural = ActiveSupport::Inflector.pluralize(@singular, locale) + @uncountable = @plural == @singular + @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(@name)) + @human = ActiveSupport::Inflector.humanize(@element) + @collection = ActiveSupport::Inflector.tableize(@name) + @param_key = (namespace ? _singularize(@unnamespaced) : @singular) + @i18n_key = @name.underscore.to_sym + + @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key, locale) : @plural.dup) + @singular_route_key = ActiveSupport::Inflector.singularize(@route_key, locale) + @route_key << "_index" if @uncountable + end + + # Transform the model name into a more human format, using I18n. By default, + # it will underscore then humanize the class name. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name.human # => "Blog post" + # + # Specify +options+ with additional translating options. + def human(options = {}) + return @human if i18n_keys.empty? || i18n_scope.empty? + + key, *defaults = i18n_keys + defaults << options[:default] if options[:default] + defaults << MISSING_TRANSLATION + + translation = I18n.translate(key, scope: i18n_scope, count: 1, **options, default: defaults) + translation = @human if translation == MISSING_TRANSLATION + translation + end + + def uncountable? + @uncountable + end + + private + MISSING_TRANSLATION = -(2**60) # :nodoc: + + def _singularize(string) + ActiveSupport::Inflector.underscore(string).tr("/", "_") + end + + def i18n_keys + @i18n_keys ||= if @klass.respond_to?(:lookup_ancestors) + @klass.lookup_ancestors.map { |klass| klass.model_name.i18n_key } + else + [] + end + end + + def i18n_scope + @i18n_scope ||= @klass.respond_to?(:i18n_scope) ? [@klass.i18n_scope, :models] : [] + end + end + + # = Active \Model \Naming + # + # Creates a +model_name+ method on your object. + # + # To implement, just extend ActiveModel::Naming in your object: + # + # class BookCover + # extend ActiveModel::Naming + # end + # + # BookCover.model_name.name # => "BookCover" + # BookCover.model_name.human # => "Book cover" + # + # BookCover.model_name.i18n_key # => :book_cover + # BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover" + # + # Providing the functionality that ActiveModel::Naming provides in your object + # is required to pass the \Active \Model Lint test. So either extending the + # provided method below, or rolling your own is required. + module Naming + def self.extended(base) # :nodoc: + base.silence_redefinition_of_method :model_name + base.delegate :model_name, to: :class + end + + # Returns an ActiveModel::Name object for module. It can be + # used to retrieve all kinds of naming-related information + # (See ActiveModel::Name for more information). + # + # class Person + # extend ActiveModel::Naming + # end + # + # Person.model_name.name # => "Person" + # Person.model_name.class # => ActiveModel::Name + # Person.model_name.singular # => "person" + # Person.model_name.plural # => "people" + def model_name + @_model_name ||= begin + namespace = module_parents.detect do |n| + n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming? + end + ActiveModel::Name.new(self, namespace) + end + end + + # Returns the plural class name of a record or class. + # + # ActiveModel::Naming.plural(post) # => "posts" + # ActiveModel::Naming.plural(Highrise::Person) # => "highrise_people" + def self.plural(record_or_class) + model_name_from_record_or_class(record_or_class).plural + end + + # Returns the singular class name of a record or class. + # + # ActiveModel::Naming.singular(post) # => "post" + # ActiveModel::Naming.singular(Highrise::Person) # => "highrise_person" + def self.singular(record_or_class) + model_name_from_record_or_class(record_or_class).singular + end + + # Identifies whether the class name of a record or class is uncountable. + # + # ActiveModel::Naming.uncountable?(Sheep) # => true + # ActiveModel::Naming.uncountable?(Post) # => false + def self.uncountable?(record_or_class) + model_name_from_record_or_class(record_or_class).uncountable? + end + + # Returns string to use while generating route names. It differs for + # namespaced models regarding whether it's inside isolated engine. + # + # # For isolated engine: + # ActiveModel::Naming.singular_route_key(Blog::Post) # => "post" + # + # # For shared engine: + # ActiveModel::Naming.singular_route_key(Blog::Post) # => "blog_post" + def self.singular_route_key(record_or_class) + model_name_from_record_or_class(record_or_class).singular_route_key + end + + # Returns string to use while generating route names. It differs for + # namespaced models regarding whether it's inside isolated engine. + # + # # For isolated engine: + # ActiveModel::Naming.route_key(Blog::Post) # => "posts" + # + # # For shared engine: + # ActiveModel::Naming.route_key(Blog::Post) # => "blog_posts" + # + # The route key also considers if the noun is uncountable and, in + # such cases, automatically appends _index. + def self.route_key(record_or_class) + model_name_from_record_or_class(record_or_class).route_key + end + + # Returns string to use for params names. It differs for + # namespaced models regarding whether it's inside isolated engine. + # + # # For isolated engine: + # ActiveModel::Naming.param_key(Blog::Post) # => "post" + # + # # For shared engine: + # ActiveModel::Naming.param_key(Blog::Post) # => "blog_post" + def self.param_key(record_or_class) + model_name_from_record_or_class(record_or_class).param_key + end + + def self.model_name_from_record_or_class(record_or_class) # :nodoc: + if record_or_class.respond_to?(:to_model) + record_or_class.to_model.model_name + else + record_or_class.model_name + end + end + private_class_method :model_name_from_record_or_class + + private + def inherited(base) + super + base.class_eval do + @_model_name = nil + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/nested_error.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/nested_error.rb new file mode 100644 index 00000000..60d40930 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/nested_error.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "active_model/error" +require "forwardable" + +module ActiveModel + class NestedError < Error + def initialize(base, inner_error, override_options = {}) + @base = base + @inner_error = inner_error + @attribute = override_options.fetch(:attribute) { inner_error.attribute } + @type = override_options.fetch(:type) { inner_error.type } + @raw_type = inner_error.raw_type + @options = inner_error.options + end + + attr_reader :inner_error + + extend Forwardable + def_delegators :@inner_error, :message + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/railtie.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/railtie.rb new file mode 100644 index 00000000..619965fc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/railtie.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "active_model" +require "rails" + +module ActiveModel + class Railtie < Rails::Railtie # :nodoc: + config.eager_load_namespaces << ActiveModel + + config.active_model = ActiveSupport::OrderedOptions.new + + initializer "active_model.deprecator", before: :load_environment_config do |app| + app.deprecators[:active_model] = ActiveModel.deprecator + end + + initializer "active_model.secure_password" do + ActiveModel::SecurePassword.min_cost = Rails.env.test? + end + + initializer "active_model.i18n_customize_full_message" do + ActiveModel::Error.i18n_customize_full_message = config.active_model.delete(:i18n_customize_full_message) || false + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/secure_password.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/secure_password.rb new file mode 100644 index 00000000..c3fd7a1b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/secure_password.rb @@ -0,0 +1,231 @@ +# frozen_string_literal: true + +module ActiveModel + module SecurePassword + extend ActiveSupport::Concern + + # BCrypt hash function can handle maximum 72 bytes, and if we pass + # password of length more than 72 bytes it ignores extra characters. + # Hence need to put a restriction on password length. + MAX_PASSWORD_LENGTH_ALLOWED = 72 + + class << self + attr_accessor :min_cost # :nodoc: + end + self.min_cost = false + + module ClassMethods + # Adds methods to set and authenticate against a BCrypt password. + # This mechanism requires you to have a +XXX_digest+ attribute, + # where +XXX+ is the attribute name of your desired password. + # + # The following validations are added automatically: + # * Password must be present on creation + # * Password length should be less than or equal to 72 bytes + # * Confirmation of password (using a +XXX_confirmation+ attribute) + # + # If confirmation validation is not needed, simply leave out the + # value for +XXX_confirmation+ (i.e. don't provide a form field for + # it). When this attribute has a +nil+ value, the validation will not be + # triggered. + # + # Additionally, a +XXX_challenge+ attribute is created. When set to a + # value other than +nil+, it will validate against the currently persisted + # password. This validation relies on dirty tracking, as provided by + # ActiveModel::Dirty; if dirty tracking methods are not defined, this + # validation will fail. + # + # All of the above validations can be omitted by passing + # validations: false as an argument. This allows complete + # customizability of validation behavior. + # + # Finally, a password reset token that's valid for 15 minutes after issue + # is automatically configured when +reset_token+ is set to true (which it is by default) + # and the object responds to +generates_token_for+ (which Active Records do). + # + # To use +has_secure_password+, add bcrypt (~> 3.1.7) to your Gemfile: + # + # gem "bcrypt", "~> 3.1.7" + # + # ==== Examples + # + # ===== Using Active Record (which automatically includes ActiveModel::SecurePassword) + # + # # Schema: User(name:string, password_digest:string, recovery_password_digest:string) + # class User < ActiveRecord::Base + # has_secure_password + # has_secure_password :recovery_password, validations: false + # end + # + # user = User.new(name: "david", password: "", password_confirmation: "nomatch") + # + # user.save # => false, password required + # user.password = "vr00m" + # user.save # => false, confirmation doesn't match + # user.password_confirmation = "vr00m" + # user.save # => true + # + # user.authenticate("notright") # => false + # user.authenticate("vr00m") # => user + # User.find_by(name: "david")&.authenticate("notright") # => false + # User.find_by(name: "david")&.authenticate("vr00m") # => user + # + # user.recovery_password = "42password" + # user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uXkTG65HR.qpW.bNhEjFP3ftli3o5DQC" + # user.save # => true + # + # user.authenticate_recovery_password("42password") # => user + # + # user.update(password: "pwn3d", password_challenge: "") # => false, challenge doesn't authenticate + # user.update(password: "nohack4u", password_challenge: "vr00m") # => true + # + # user.authenticate("vr00m") # => false, old password + # user.authenticate("nohack4u") # => user + # + # ===== Conditionally requiring a password + # + # class Account + # include ActiveModel::SecurePassword + # + # attr_accessor :is_guest, :password_digest + # + # has_secure_password + # + # def errors + # super.tap { |errors| errors.delete(:password, :blank) if is_guest } + # end + # end + # + # account = Account.new + # account.valid? # => false, password required + # + # account.is_guest = true + # account.valid? # => true + # + # ===== Using the password reset token + # + # user = User.create!(name: "david", password: "123", password_confirmation: "123") + # token = user.password_reset_token + # User.find_by_password_reset_token(token) # returns user + # + # # 16 minutes later... + # User.find_by_password_reset_token(token) # returns nil + # + # # raises ActiveSupport::MessageVerifier::InvalidSignature since the token is expired + # User.find_by_password_reset_token!(token) + def has_secure_password(attribute = :password, validations: true, reset_token: true) + # Load bcrypt gem only when has_secure_password is used. + # This is to avoid ActiveModel (and by extension the entire framework) + # being dependent on a binary library. + begin + require "bcrypt" + rescue LoadError + warn "You don't have bcrypt installed in your application. Please add it to your Gemfile and run bundle install." + raise + end + + include InstanceMethodsOnActivation.new(attribute, reset_token: reset_token) + + if validations + include ActiveModel::Validations + + # This ensures the model has a password by checking whether the password_digest + # is present, so that this works with both new and existing records. However, + # when there is an error, the message is added to the password attribute instead + # so that the error message will make sense to the end-user. + validate do |record| + record.errors.add(attribute, :blank) unless record.public_send("#{attribute}_digest").present? + end + + validate do |record| + if challenge = record.public_send(:"#{attribute}_challenge") + digest_was = record.public_send(:"#{attribute}_digest_was") if record.respond_to?(:"#{attribute}_digest_was") + + unless digest_was.present? && BCrypt::Password.new(digest_was).is_password?(challenge) + record.errors.add(:"#{attribute}_challenge") + end + end + end + + # Validates that the password does not exceed the maximum allowed bytes for BCrypt (72 bytes). + validate do |record| + password_value = record.public_send(attribute) + if password_value.present? && password_value.bytesize > ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED + record.errors.add(attribute, :password_too_long) + end + end + + validates_confirmation_of attribute, allow_blank: true + end + + # Only generate tokens for records that are capable of doing so (Active Records, not vanilla Active Models) + if reset_token && respond_to?(:generates_token_for) + generates_token_for :"#{attribute}_reset", expires_in: 15.minutes do + public_send(:"#{attribute}_salt")&.last(10) + end + + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + silence_redefinition_of_method :find_by_#{attribute}_reset_token + def self.find_by_#{attribute}_reset_token(token) + find_by_token_for(:#{attribute}_reset, token) + end + + silence_redefinition_of_method :find_by_#{attribute}_reset_token! + def self.find_by_#{attribute}_reset_token!(token) + find_by_token_for!(:#{attribute}_reset, token) + end + RUBY + end + end + end + + class InstanceMethodsOnActivation < Module + def initialize(attribute, reset_token:) + attr_reader attribute + + define_method("#{attribute}=") do |unencrypted_password| + if unencrypted_password.nil? + instance_variable_set("@#{attribute}", nil) + self.public_send("#{attribute}_digest=", nil) + elsif !unencrypted_password.empty? + instance_variable_set("@#{attribute}", unencrypted_password) + cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost + self.public_send("#{attribute}_digest=", BCrypt::Password.create(unencrypted_password, cost: cost)) + end + end + + attr_accessor :"#{attribute}_confirmation", :"#{attribute}_challenge" + + # Returns +self+ if the password is correct, otherwise +false+. + # + # class User < ActiveRecord::Base + # has_secure_password validations: false + # end + # + # user = User.new(name: 'david', password: 'mUc3m00RsqyRe') + # user.save + # user.authenticate_password('notright') # => false + # user.authenticate_password('mUc3m00RsqyRe') # => user + define_method("authenticate_#{attribute}") do |unencrypted_password| + attribute_digest = public_send("#{attribute}_digest") + attribute_digest.present? && BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self + end + + # Returns the salt, a small chunk of random data added to the password before it's hashed. + define_method("#{attribute}_salt") do + attribute_digest = public_send("#{attribute}_digest") + attribute_digest.present? ? BCrypt::Password.new(attribute_digest).salt : nil + end + + alias_method :authenticate, :authenticate_password if attribute == :password + + if reset_token + # Returns the class-level configured reset token for the password. + define_method("#{attribute}_reset_token") do + generate_token_for(:"#{attribute}_reset") + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/serialization.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/serialization.rb new file mode 100644 index 00000000..8e2dadd8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/serialization.rb @@ -0,0 +1,198 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" + +module ActiveModel + # = Active \Model \Serialization + # + # Provides a basic serialization to a serializable_hash for your objects. + # + # A minimal implementation could be: + # + # class Person + # include ActiveModel::Serialization + # + # attr_accessor :name + # + # def attributes + # {'name' => nil} + # end + # end + # + # Which would provide you with: + # + # person = Person.new + # person.serializable_hash # => {"name"=>nil} + # person.name = "Bob" + # person.serializable_hash # => {"name"=>"Bob"} + # + # An +attributes+ hash must be defined and should contain any attributes you + # need to be serialized. Attributes must be strings, not symbols. + # When called, serializable hash will use instance methods that match the name + # of the attributes hash's keys. In order to override this behavior, override + # the +read_attribute_for_serialization+ method. + # + # ActiveModel::Serializers::JSON module automatically includes + # the +ActiveModel::Serialization+ module, so there is no need to + # explicitly include +ActiveModel::Serialization+. + # + # A minimal implementation including JSON would be: + # + # class Person + # include ActiveModel::Serializers::JSON + # + # attr_accessor :name + # + # def attributes + # {'name' => nil} + # end + # end + # + # Which would provide you with: + # + # person = Person.new + # person.serializable_hash # => {"name"=>nil} + # person.as_json # => {"name"=>nil} + # person.to_json # => "{\"name\":null}" + # + # person.name = "Bob" + # person.serializable_hash # => {"name"=>"Bob"} + # person.as_json # => {"name"=>"Bob"} + # person.to_json # => "{\"name\":\"Bob\"}" + # + # Valid options are :only, :except, :methods and + # :include. The following are all valid examples: + # + # person.serializable_hash(only: 'name') + # person.serializable_hash(include: :address) + # person.serializable_hash(include: { address: { only: 'city' }}) + module Serialization + # Returns a serialized hash of your object. + # + # class Person + # include ActiveModel::Serialization + # + # attr_accessor :name, :age + # + # def attributes + # {'name' => nil, 'age' => nil} + # end + # + # def capitalized_name + # name.capitalize + # end + # end + # + # person = Person.new + # person.name = 'bob' + # person.age = 22 + # person.serializable_hash # => {"name"=>"bob", "age"=>22} + # person.serializable_hash(only: :name) # => {"name"=>"bob"} + # person.serializable_hash(except: :name) # => {"age"=>22} + # person.serializable_hash(methods: :capitalized_name) + # # => {"name"=>"bob", "age"=>22, "capitalized_name"=>"Bob"} + # + # Example with :include option + # + # class User + # include ActiveModel::Serializers::JSON + # attr_accessor :name, :notes # Emulate has_many :notes + # def attributes + # {'name' => nil} + # end + # end + # + # class Note + # include ActiveModel::Serializers::JSON + # attr_accessor :title, :text + # def attributes + # {'title' => nil, 'text' => nil} + # end + # end + # + # note = Note.new + # note.title = 'Battle of Austerlitz' + # note.text = 'Some text here' + # + # user = User.new + # user.name = 'Napoleon' + # user.notes = [note] + # + # user.serializable_hash + # # => {"name" => "Napoleon"} + # user.serializable_hash(include: { notes: { only: 'title' }}) + # # => {"name" => "Napoleon", "notes" => [{"title"=>"Battle of Austerlitz"}]} + def serializable_hash(options = nil) + attribute_names = attribute_names_for_serialization + + return serializable_attributes(attribute_names) if options.blank? + + if only = options[:only] + attribute_names = Array(only).map(&:to_s) & attribute_names + elsif except = options[:except] + attribute_names -= Array(except).map(&:to_s) + end + + hash = serializable_attributes(attribute_names) + + Array(options[:methods]).each { |m| hash[m.to_s] = send(m) } + + serializable_add_includes(options) do |association, records, opts| + hash[association.to_s] = if records.respond_to?(:to_ary) + records.to_ary.map { |a| a.serializable_hash(opts) } + else + records.serializable_hash(opts) + end + end + + hash + end + + # Hook method defining how an attribute value should be retrieved for + # serialization. By default this is assumed to be an instance named after + # the attribute. Override this method in subclasses should you need to + # retrieve the value for a given attribute differently: + # + # class MyClass + # include ActiveModel::Serialization + # + # def initialize(data = {}) + # @data = data + # end + # + # def read_attribute_for_serialization(key) + # @data[key] + # end + # end + alias :read_attribute_for_serialization :send + + private + def attribute_names_for_serialization + attributes.keys + end + + def serializable_attributes(attribute_names) + attribute_names.index_with { |n| read_attribute_for_serialization(n) } + end + + # Add associations specified via the :include option. + # + # Expects a block that takes as arguments: + # +association+ - name of the association + # +records+ - the association record(s) to be serialized + # +opts+ - options for the association records + def serializable_add_includes(options = {}) # :nodoc: + return unless includes = options[:include] + + unless includes.is_a?(Hash) + includes = Hash[Array(includes).flat_map { |n| n.is_a?(Hash) ? n.to_a : [[n, {}]] }] + end + + includes.each do |association, opts| + if records = send(association) + yield association, records, opts + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/serializers/json.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/serializers/json.rb new file mode 100644 index 00000000..c6cff11e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/serializers/json.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +require "active_support/json" + +module ActiveModel + module Serializers + # = Active \Model \JSON \Serializer + module JSON + extend ActiveSupport::Concern + include ActiveModel::Serialization + + included do + extend ActiveModel::Naming + + class_attribute :include_root_in_json, instance_writer: false, default: false + end + + # Returns a hash representing the model. Some configuration can be + # passed through +options+. + # + # The option include_root_in_json controls the top-level behavior + # of +as_json+. If +true+, +as_json+ will emit a single root node named + # after the object's type. The default value for include_root_in_json + # option is +false+. + # + # user = User.find(1) + # user.as_json + # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16, + # # "created_at" => "2006-08-01T17:27:133.000Z", "awesome" => true} + # + # ActiveRecord::Base.include_root_in_json = true + # + # user.as_json + # # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16, + # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true } } + # + # This behavior can also be achieved by setting the :root option + # to +true+ as in: + # + # user = User.find(1) + # user.as_json(root: true) + # # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16, + # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true } } + # + # If you prefer, :root may also be set to a custom string key instead as in: + # + # user = User.find(1) + # user.as_json(root: "author") + # # => { "author" => { "id" => 1, "name" => "Konata Izumi", "age" => 16, + # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true } } + # + # Without any +options+, the returned Hash will include all the model's + # attributes. + # + # user = User.find(1) + # user.as_json + # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16, + # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true} + # + # The :only and :except options can be used to limit + # the attributes included, and work similar to the +attributes+ method. + # + # user.as_json(only: [:id, :name]) + # # => { "id" => 1, "name" => "Konata Izumi" } + # + # user.as_json(except: [:id, :created_at, :age]) + # # => { "name" => "Konata Izumi", "awesome" => true } + # + # To include the result of some method calls on the model use :methods: + # + # user.as_json(methods: :permalink) + # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16, + # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true, + # # "permalink" => "1-konata-izumi" } + # + # To include associations use :include: + # + # user.as_json(include: :posts) + # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16, + # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true, + # # "posts" => [ { "id" => 1, "author_id" => 1, "title" => "Welcome to the weblog" }, + # # { "id" => 2, "author_id" => 1, "title" => "So I was thinking" } ] } + # + # Second level and higher order associations work as well: + # + # user.as_json(include: { posts: { + # include: { comments: { + # only: :body } }, + # only: :title } }) + # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16, + # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true, + # # "posts" => [ { "comments" => [ { "body" => "1st post!" }, { "body" => "Second!" } ], + # # "title" => "Welcome to the weblog" }, + # # { "comments" => [ { "body" => "Don't think too hard" } ], + # # "title" => "So I was thinking" } ] } + def as_json(options = nil) + root = if options && options.key?(:root) + options[:root] + else + include_root_in_json + end + + hash = serializable_hash(options).as_json + if root + root = model_name.element if root == true + { root => hash } + else + hash + end + end + + # Sets the model +attributes+ from a JSON string. Returns +self+. + # + # class Person + # include ActiveModel::Serializers::JSON + # + # attr_accessor :name, :age, :awesome + # + # def attributes=(hash) + # hash.each do |key, value| + # send("#{key}=", value) + # end + # end + # + # def attributes + # instance_values + # end + # end + # + # json = { name: 'bob', age: 22, awesome:true }.to_json + # person = Person.new + # person.from_json(json) # => # + # person.name # => "bob" + # person.age # => 22 + # person.awesome # => true + # + # The default value for +include_root+ is +false+. You can change it to + # +true+ if the given JSON string includes a single root node. + # + # json = { person: { name: 'bob', age: 22, awesome:true } }.to_json + # person = Person.new + # person.from_json(json, true) # => # + # person.name # => "bob" + # person.age # => 22 + # person.awesome # => true + def from_json(json, include_root = include_root_in_json) + hash = ActiveSupport::JSON.decode(json) + hash = hash.values.first if include_root + self.attributes = hash + self + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/translation.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/translation.rb new file mode 100644 index 00000000..0a913005 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/translation.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +module ActiveModel + # = Active \Model \Translation + # + # Provides integration between your object and the \Rails internationalization + # (i18n) framework. + # + # A minimal implementation could be: + # + # class TranslatedPerson + # extend ActiveModel::Translation + # end + # + # TranslatedPerson.human_attribute_name('my_attribute') + # # => "My attribute" + # + # This also provides the required class methods for hooking into the + # \Rails internationalization API, including being able to define a + # class-based +i18n_scope+ and +lookup_ancestors+ to find translations in + # parent classes. + module Translation + include ActiveModel::Naming + + singleton_class.attr_accessor :raise_on_missing_translations + + # Returns the +i18n_scope+ for the class. Override if you want custom lookup. + def i18n_scope + :activemodel + end + + # When localizing a string, it goes through the lookup returned by this + # method, which is used in ActiveModel::Name#human, + # ActiveModel::Errors#full_messages and + # ActiveModel::Translation#human_attribute_name. + def lookup_ancestors + ancestors.select { |x| x.respond_to?(:model_name) } + end + + MISSING_TRANSLATION = -(2**60) # :nodoc: + + # Transforms attribute names into a more human format, such as "First name" + # instead of "first_name". + # + # Person.human_attribute_name("first_name") # => "First name" + # + # Specify +options+ with additional translating options. + def human_attribute_name(attribute, options = {}) + attribute = attribute.to_s + + if attribute.include?(".") + namespace, _, attribute = attribute.rpartition(".") + namespace.tr!(".", "/") + + if attribute.present? + key = "#{namespace}.#{attribute}" + separator = "/" + else + key = namespace + separator = "." + end + + defaults = lookup_ancestors.map do |klass| + :"#{i18n_scope}.attributes.#{klass.model_name.i18n_key}#{separator}#{key}" + end + defaults << :"#{i18n_scope}.attributes.#{key}" + defaults << :"attributes.#{key}" + else + defaults = lookup_ancestors.map do |klass| + :"#{i18n_scope}.attributes.#{klass.model_name.i18n_key}.#{attribute}" + end + end + + raise_on_missing = options.fetch(:raise, Translation.raise_on_missing_translations) + + defaults << :"attributes.#{attribute}" + defaults << options[:default] if options[:default] + defaults << MISSING_TRANSLATION unless raise_on_missing + + translation = I18n.translate(defaults.shift, count: 1, raise: raise_on_missing, **options, default: defaults) + if translation == MISSING_TRANSLATION + translation = attribute.present? ? attribute.humanize : namespace.humanize + end + translation + end + end + + ActiveSupport.run_load_hooks(:active_model_translation, Translation) +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type.rb new file mode 100644 index 00000000..97bcc6a5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "active_model/type/helpers" +require "active_model/type/serialize_cast_value" +require "active_model/type/value" + +require "active_model/type/big_integer" +require "active_model/type/binary" +require "active_model/type/boolean" +require "active_model/type/date" +require "active_model/type/date_time" +require "active_model/type/decimal" +require "active_model/type/float" +require "active_model/type/immutable_string" +require "active_model/type/integer" +require "active_model/type/string" +require "active_model/type/time" + +require "active_model/type/registry" + +module ActiveModel + module Type + @registry = Registry.new + + class << self + attr_accessor :registry # :nodoc: + + # Add a new type to the registry, allowing it to be referenced as a + # symbol by {attribute}[rdoc-ref:Attributes::ClassMethods#attribute]. + def register(type_name, klass = nil, &block) + registry.register(type_name, klass, &block) + end + + def lookup(...) # :nodoc: + registry.lookup(...) + end + + def default_value # :nodoc: + @default_value ||= Value.new + end + end + + register(:big_integer, Type::BigInteger) + register(:binary, Type::Binary) + register(:boolean, Type::Boolean) + register(:date, Type::Date) + register(:datetime, Type::DateTime) + register(:decimal, Type::Decimal) + register(:float, Type::Float) + register(:immutable_string, Type::ImmutableString) + register(:integer, Type::Integer) + register(:string, Type::String) + register(:time, Type::Time) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/big_integer.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/big_integer.rb new file mode 100644 index 00000000..8683585b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/big_integer.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require "active_model/type/integer" + +module ActiveModel + module Type + # = Active Model \BigInteger \Type + # + # Attribute type for integers that can be serialized to an unlimited number + # of bytes. This type is registered under the +:big_integer+ key. + # + # class Person + # include ActiveModel::Attributes + # + # attribute :id, :big_integer + # end + # + # person = Person.new + # person.id = "18_000_000_000" + # + # person.id # => 18000000000 + # + # All casting and serialization are performed in the same way as the + # standard ActiveModel::Type::Integer type. + class BigInteger < Integer + def serialize_cast_value(value) # :nodoc: + value + end + + private + def max_value + ::Float::INFINITY + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/binary.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/binary.rb new file mode 100644 index 00000000..a5baf23c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/binary.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + # = Active Model \Binary \Type + # + # Attribute type for representation of binary data. This type is registered + # under the +:binary+ key. + # + # Non-string values are coerced to strings using their +to_s+ method. + class Binary < Value + def type + :binary + end + + def binary? + true + end + + def cast(value) + if value.is_a?(Data) + value.to_s + else + value = super + value = value.b if ::String === value && value.encoding != Encoding::BINARY + value + end + end + + def serialize(value) + return if value.nil? + Data.new(super) + end + + def changed_in_place?(raw_old_value, value) + old_value = deserialize(raw_old_value) + old_value != value + end + + class Data # :nodoc: + def initialize(value) + value = value.to_s + value = value.b unless value.encoding == Encoding::BINARY + @value = value + end + + def to_s + @value + end + alias_method :to_str, :to_s + + def hex + @value.unpack1("H*") + end + + def ==(other) + other == to_s || super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/boolean.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/boolean.rb new file mode 100644 index 00000000..0a07c5e8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/boolean.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + # = Active Model \Boolean \Type + # + # A class that behaves like a boolean type, including rules for coercion of + # user input. + # + # - "false", "f", "0", +0+ or any other value in + # +FALSE_VALUES+ will be coerced to +false+. + # - Empty strings are coerced to +nil+. + # - All other values will be coerced to +true+. + class Boolean < Value + FALSE_VALUES = [ + false, 0, + "0", :"0", + "f", :f, + "F", :F, + "false", :false, + "FALSE", :FALSE, + "off", :off, + "OFF", :OFF, + ].to_set.freeze + + def type # :nodoc: + :boolean + end + + def serialize(value) # :nodoc: + cast(value) + end + + def serialize_cast_value(value) # :nodoc: + value + end + + private + def cast_value(value) + if value == "" + nil + else + !FALSE_VALUES.include?(value) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/date.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/date.rb new file mode 100644 index 00000000..613b1d8d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/date.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + # = Active Model \Date \Type + # + # Attribute type for date representation. It is registered under the + # +:date+ key. + # + # class Person + # include ActiveModel::Attributes + # + # attribute :birthday, :date + # end + # + # person = Person.new + # person.birthday = "1989-07-13" + # + # person.birthday.class # => Date + # person.birthday.year # => 1989 + # person.birthday.month # => 7 + # person.birthday.day # => 13 + # + # String values are parsed using the ISO 8601 date format. Any other values + # are cast using their +to_date+ method, if it exists. + class Date < Value + include Helpers::Timezone + include Helpers::AcceptsMultiparameterTime.new + + def type + :date + end + + def type_cast_for_schema(value) + value.to_fs(:db).inspect + end + + private + def cast_value(value) + if value.is_a?(::String) + return if value.empty? + fast_string_to_date(value) || fallback_string_to_date(value) + elsif value.respond_to?(:to_date) + value.to_date + else + value + end + end + + ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/ + def fast_string_to_date(string) + if string =~ ISO_DATE + new_date $1.to_i, $2.to_i, $3.to_i + end + end + + def fallback_string_to_date(string) + parts = begin + ::Date._parse(string, false) + rescue ArgumentError + end + + new_date(*parts.values_at(:year, :mon, :mday)) if parts + end + + def new_date(year, mon, mday) + unless year.nil? || (year == 0 && mon == 0 && mday == 0) + ::Date.new(year, mon, mday) rescue nil + end + end + + def value_from_multiparameter_assignment(*) + time = super + time && new_date(time.year, time.mon, time.mday) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/date_time.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/date_time.rb new file mode 100644 index 00000000..e85dc67a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/date_time.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + # = Active Model \DateTime \Type + # + # Attribute type to represent dates and times. It is registered under the + # +:datetime+ key. + # + # class Event + # include ActiveModel::Attributes + # + # attribute :start, :datetime + # end + # + # event = Event.new + # event.start = "Wed, 04 Sep 2013 03:00:00 EAT" + # + # event.start.class # => Time + # event.start.year # => 2013 + # event.start.month # => 9 + # event.start.day # => 4 + # event.start.hour # => 3 + # event.start.min # => 0 + # event.start.sec # => 0 + # event.start.zone # => "EAT" + # + # String values are parsed using the ISO 8601 datetime format. Partial + # time-only formats are also accepted. + # + # event.start = "06:07:08+09:00" + # event.start.utc # => 1999-12-31 21:07:08 UTC + # + # The degree of sub-second precision can be customized when declaring an + # attribute: + # + # class Event + # include ActiveModel::Attributes + # + # attribute :start, :datetime, precision: 4 + # end + class DateTime < Value + include Helpers::Timezone + include Helpers::AcceptsMultiparameterTime.new( + defaults: { 4 => 0, 5 => 0 } + ) + include Helpers::TimeValue + + def type + :datetime + end + + private + def cast_value(value) + return apply_seconds_precision(value) unless value.is_a?(::String) + return if value.empty? + + fast_string_to_time(value) || fallback_string_to_time(value) + end + + # '0.123456' -> 123456 + # '1.123456' -> 123456 + def microseconds(time) + time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0 + end + + def fallback_string_to_time(string) + time_hash = begin + ::Date._parse(string) + rescue ArgumentError + end + return unless time_hash + + time_hash[:sec_fraction] = microseconds(time_hash) + + new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset)) + end + + def value_from_multiparameter_assignment(values_hash) + missing_parameters = [1, 2, 3].delete_if { |key| values_hash.key?(key) } + unless missing_parameters.empty? + raise ArgumentError, "Provided hash #{values_hash} doesn't contain necessary keys: #{missing_parameters}" + end + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/decimal.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/decimal.rb new file mode 100644 index 00000000..b75e9972 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/decimal.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +require "bigdecimal/util" + +module ActiveModel + module Type + # = Active Model \Decimal \Type + # + # Attribute type for decimal, high-precision floating point numeric + # representation. It is registered under the +:decimal+ key. + # + # class BagOfCoffee + # include ActiveModel::Attributes + # + # attribute :weight, :decimal + # end + # + # Numeric instances are converted to BigDecimal instances. Any other objects + # are cast using their +to_d+ method, except for blank strings, which are + # cast to +nil+. If a +to_d+ method is not defined, the object is converted + # to a string using +to_s+, which is then cast using +to_d+. + # + # bag = BagOfCoffee.new + # + # bag.weight = 0.01 + # bag.weight # => 0.1e-1 + # + # bag.weight = "0.01" + # bag.weight # => 0.1e-1 + # + # bag.weight = "" + # bag.weight # => nil + # + # bag.weight = :arbitrary + # bag.weight # => nil (the result of `.to_s.to_d`) + # + # Decimal precision defaults to 18, and can be customized when declaring an + # attribute: + # + # class BagOfCoffee + # include ActiveModel::Attributes + # + # attribute :weight, :decimal, precision: 24 + # end + class Decimal < Value + include Helpers::Numeric + BIGDECIMAL_PRECISION = 18 + + def type + :decimal + end + + def type_cast_for_schema(value) + value.to_s.inspect + end + + private + def cast_value(value) + casted_value = \ + case value + when ::Float + convert_float_to_big_decimal(value) + when ::Numeric + BigDecimal(value, precision || BIGDECIMAL_PRECISION) + when ::String + begin + value.to_d + rescue ArgumentError + BigDecimal(0) + end + else + if value.respond_to?(:to_d) + value.to_d + else + cast_value(value.to_s) + end + end + + apply_scale(casted_value) + end + + def convert_float_to_big_decimal(value) + if precision + BigDecimal(apply_scale(value), float_precision) + else + value.to_d + end + end + + def float_precision + if precision.to_i > ::Float::DIG + 1 + ::Float::DIG + 1 + else + precision.to_i + end + end + + def apply_scale(value) + if scale + value.round(scale) + else + value + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/float.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/float.rb new file mode 100644 index 00000000..62ddd26d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/float.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/try" + +module ActiveModel + module Type + # = Active Model \Float \Type + # + # Attribute type for floating point numeric values. It is registered under + # the +:float+ key. + # + # class BagOfCoffee + # include ActiveModel::Attributes + # + # attribute :weight, :float + # end + # + # bag = BagOfCoffee.new + # + # bag.weight = "0.25" + # bag.weight # => 0.25 + # + # bag.weight = "" + # bag.weight # => nil + # + # bag.weight = "NaN" + # bag.weight # => Float::NAN + # + # Values are cast using their +to_f+ method, except for the following + # strings: + # + # - Blank strings are cast to +nil+. + # - "Infinity" is cast to +Float::INFINITY+. + # - "-Infinity" is cast to -Float::INFINITY. + # - "NaN" is cast to +Float::NAN+. + class Float < Value + include Helpers::Numeric + + def type + :float + end + + def type_cast_for_schema(value) + return "::Float::NAN" if value.try(:nan?) + case value + when ::Float::INFINITY then "::Float::INFINITY" + when -::Float::INFINITY then "-::Float::INFINITY" + else super + end + end + + private + def cast_value(value) + case value + when ::Float then value + when "Infinity" then ::Float::INFINITY + when "-Infinity" then -::Float::INFINITY + when "NaN" then ::Float::NAN + else value.to_f + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/helpers.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/helpers.rb new file mode 100644 index 00000000..20145d5f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/helpers.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "active_model/type/helpers/accepts_multiparameter_time" +require "active_model/type/helpers/numeric" +require "active_model/type/helpers/mutable" +require "active_model/type/helpers/time_value" +require "active_model/type/helpers/timezone" diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/helpers/accepts_multiparameter_time.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/helpers/accepts_multiparameter_time.rb new file mode 100644 index 00000000..0494907a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/helpers/accepts_multiparameter_time.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + module Helpers # :nodoc: all + class AcceptsMultiparameterTime < Module + module InstanceMethods + def serialize(value) + serialize_cast_value(cast(value)) + end + + def serialize_cast_value(value) + value + end + + def cast(value) + if value.is_a?(Hash) + value_from_multiparameter_assignment(value) + else + super(value) + end + end + + def assert_valid_value(value) + if value.is_a?(Hash) + value_from_multiparameter_assignment(value) + else + super(value) + end + end + + def value_constructed_by_mass_assignment?(value) + value.is_a?(Hash) + end + end + + def initialize(defaults: {}) + include InstanceMethods + + define_method(:value_from_multiparameter_assignment) do |values_hash| + defaults.each do |k, v| + values_hash[k] ||= v + end + return unless values_hash[1] && values_hash[2] && values_hash[3] + values = values_hash.sort.map!(&:last) + ::Time.public_send(default_timezone, *values) + end + private :value_from_multiparameter_assignment + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/helpers/mutable.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/helpers/mutable.rb new file mode 100644 index 00000000..ef1d8e15 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/helpers/mutable.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + module Helpers # :nodoc: all + module Mutable + def cast(value) + deserialize(serialize(value)) + end + + # +raw_old_value+ will be the `_before_type_cast` version of the + # value (likely a string). +new_value+ will be the current, type + # cast value. + def changed_in_place?(raw_old_value, new_value) + raw_old_value != serialize(new_value) + end + + def mutable? # :nodoc: + true + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/helpers/numeric.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/helpers/numeric.rb new file mode 100644 index 00000000..6b3d2fb6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/helpers/numeric.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + module Helpers # :nodoc: all + module Numeric + def serialize(value) + cast(value) + end + + def serialize_cast_value(value) + value + end + + def cast(value) + # Checks whether the value is numeric. Spaceship operator + # will return nil if value is not numeric. + value = if value <=> 0 + value + else + case value + when true then 1 + when false then 0 + else value.presence + end + end + + super(value) + end + + def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc: + (super || number_to_non_number?(old_value, new_value_before_type_cast)) && + !equal_nan?(old_value, new_value_before_type_cast) + end + + private + def equal_nan?(old_value, new_value) + (old_value.is_a?(::Float) || old_value.is_a?(BigDecimal)) && + old_value.nan? && + old_value.instance_of?(new_value.class) && + new_value.nan? + end + + def number_to_non_number?(old_value, new_value_before_type_cast) + old_value != nil && !new_value_before_type_cast.is_a?(::Numeric) && + non_numeric_string?(new_value_before_type_cast.to_s) + end + + def non_numeric_string?(value) + # 'wibble'.to_i will give zero, we want to make sure + # that we aren't marking int zero to string zero as + # changed. + !NUMERIC_REGEX.match?(value) + end + + NUMERIC_REGEX = /\A\s*[+-]?\d/ + private_constant :NUMERIC_REGEX + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/helpers/time_value.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/helpers/time_value.rb new file mode 100644 index 00000000..7b4df886 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/helpers/time_value.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/zones" +require "active_support/core_ext/time/zones" + +module ActiveModel + module Type + module Helpers # :nodoc: all + module TimeValue + def serialize_cast_value(value) + value = apply_seconds_precision(value) + + if value.acts_like?(:time) + if is_utc? + value = value.getutc if !value.utc? + else + value = value.getlocal + end + end + + value + end + + def apply_seconds_precision(value) + return value unless precision && value.respond_to?(:nsec) + + number_of_insignificant_digits = 9 - precision + round_power = 10**number_of_insignificant_digits + rounded_off_nsec = value.nsec % round_power + + if rounded_off_nsec > 0 + value.change(nsec: value.nsec - rounded_off_nsec) + else + value + end + end + + def type_cast_for_schema(value) + value.to_fs(:db).inspect + end + + def user_input_in_time_zone(value) + value.in_time_zone + end + + private + def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil) + # Treat 0000-00-00 00:00:00 as nil. + return if year.nil? || (year == 0 && mon == 0 && mday == 0) + + if offset + time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil + return unless time + + time -= offset unless offset == 0 + is_utc? ? time : time.getlocal + elsif is_utc? + ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil + else + ::Time.local(year, mon, mday, hour, min, sec, microsec) rescue nil + end + end + + ISO_DATETIME = / + \A + (\d{4})-(\d\d)-(\d\d)(?:T|\s) # 2020-06-20T + (\d\d):(\d\d):(\d\d)(?:\.(\d{1,6})\d*)? # 10:20:30.123456 + (?:(Z(?=\z)|[+-]\d\d)(?::?(\d\d))?)? # +09:00 + \z + /x + + if Time.new(2000, 1, 1, 0, 0, 0, "-00:00").yday != 1 # Early 3.2.x had a bug + # BUG: Wrapping the Time object with Time.at because Time.new with `in:` in Ruby 3.2.0 + # used to return an invalid Time object + # see: https://bugs.ruby-lang.org/issues/19292 + def fast_string_to_time(string) + return unless string.include?("-") # Time.new("1234") # => 1234-01-01 00:00:00 + + if is_utc? + ::Time.at(::Time.new(string, in: "UTC")) + else + ::Time.new(string) + end + rescue ArgumentError + nil + end + else + def fast_string_to_time(string) + return unless string.include?("-") # Time.new("1234") # => 1234-01-01 00:00:00 + + if is_utc? + ::Time.new(string, in: "UTC") + else + ::Time.new(string) + end + rescue ArgumentError + nil + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/helpers/timezone.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/helpers/timezone.rb new file mode 100644 index 00000000..c589e033 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/helpers/timezone.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "active_support/core_ext/time/zones" + +module ActiveModel + module Type + module Helpers # :nodoc: all + module Timezone + def is_utc? + if default = ::Time.zone_default + default.name == "UTC" + else + true + end + end + + def default_timezone + is_utc? ? :utc : :local + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/immutable_string.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/immutable_string.rb new file mode 100644 index 00000000..a8350a87 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/immutable_string.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + # = Active Model \ImmutableString \Type + # + # Attribute type to represent immutable strings. It casts incoming values to + # frozen strings. + # + # class Person + # include ActiveModel::Attributes + # + # attribute :name, :immutable_string + # end + # + # person = Person.new + # person.name = 1 + # + # person.name # => "1" + # person.name.frozen? # => true + # + # Values are coerced to strings using their +to_s+ method. Boolean values + # are treated differently, however: +true+ will be cast to "t" and + # +false+ will be cast to "f". These strings can be customized when + # declaring an attribute: + # + # class Person + # include ActiveModel::Attributes + # + # attribute :active, :immutable_string, true: "aye", false: "nay" + # end + # + # person = Person.new + # person.active = true + # + # person.active # => "aye" + class ImmutableString < Value + def initialize(**args) + @true = -(args.delete(:true)&.to_s || "t") + @false = -(args.delete(:false)&.to_s || "f") + super + end + + def type + :string + end + + def serialize(value) + case value + when ::Numeric, ::Symbol, ActiveSupport::Duration then value.to_s + when true then @true + when false then @false + else super + end + end + + def serialize_cast_value(value) # :nodoc: + value + end + + private + def cast_value(value) + case value + when true then @true + when false then @false + else value.to_s.freeze + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/integer.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/integer.rb new file mode 100644 index 00000000..0c4bd03f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/integer.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + # = Active Model \Integer \Type + # + # Attribute type for integer representation. This type is registered under + # the +:integer+ key. + # + # class Person + # include ActiveModel::Attributes + # + # attribute :age, :integer + # end + # + # Values are cast using their +to_i+ method, except for blank strings, which + # are cast to +nil+. If a +to_i+ method is not defined or raises an error, + # the value will be cast to +nil+. + # + # person = Person.new + # + # person.age = "18" + # person.age # => 18 + # + # person.age = "" + # person.age # => nil + # + # person.age = :not_an_integer + # person.age # => nil (because Symbol does not define #to_i) + # + # Serialization also works under the same principle. Non-numeric strings are + # serialized as +nil+, for example. + # + # Serialization also validates that the integer can be stored using a + # limited number of bytes. If it cannot, an ActiveModel::RangeError will be + # raised. The default limit is 4 bytes, and can be customized when declaring + # an attribute: + # + # class Person + # include ActiveModel::Attributes + # + # attribute :age, :integer, limit: 6 + # end + class Integer < Value + include Helpers::Numeric + + # Column storage size in bytes. + # 4 bytes means an integer as opposed to smallint etc. + DEFAULT_LIMIT = 4 + + def initialize(**) + super + @range = min_value...max_value + end + + def type + :integer + end + + def deserialize(value) + return if value.blank? + value.to_i + end + + def serialize(value) + return if value.is_a?(::String) && non_numeric_string?(value) + ensure_in_range(super) + end + + def serialize_cast_value(value) # :nodoc: + ensure_in_range(value) + end + + def serializable?(value) + cast_value = cast(value) + in_range?(cast_value) || begin + yield cast_value if block_given? + false + end + end + + private + attr_reader :range + + def in_range?(value) + !value || range.member?(value) + end + + def cast_value(value) + value.to_i rescue nil + end + + def ensure_in_range(value) + unless in_range?(value) + raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit} bytes" + end + value + end + + def max_value + 1 << (_limit * 8 - 1) # 8 bits per byte with one bit for sign + end + + def min_value + -max_value + end + + def _limit + limit || DEFAULT_LIMIT + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/registry.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/registry.rb new file mode 100644 index 00000000..6ce50201 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/registry.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class Registry # :nodoc: + def initialize + @registrations = {} + end + + def initialize_copy(other) + @registrations = @registrations.dup + super + end + + def register(type_name, klass = nil, &block) + unless block_given? + block = proc { |_, *args| klass.new(*args) } + block.ruby2_keywords if block.respond_to?(:ruby2_keywords) + end + registrations[type_name] = block + end + + def lookup(symbol, ...) + registration = registrations[symbol] + + if registration + registration.call(symbol, ...) + else + raise ArgumentError, "Unknown type #{symbol.inspect}" + end + end + + private + attr_reader :registrations + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/serialize_cast_value.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/serialize_cast_value.rb new file mode 100644 index 00000000..ec30fb1f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/serialize_cast_value.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + module SerializeCastValue # :nodoc: + extend ActiveSupport::Concern + + module ClassMethods + def serialize_cast_value_compatible? + return @serialize_cast_value_compatible if defined?(@serialize_cast_value_compatible) + @serialize_cast_value_compatible = ancestors.index(instance_method(:serialize_cast_value).owner) <= ancestors.index(instance_method(:serialize).owner) + end + end + + module DefaultImplementation + def serialize_cast_value(value) + value + end + end + + def self.included(klass) + klass.include DefaultImplementation unless klass.method_defined?(:serialize_cast_value) + end + + def self.serialize(type, value) + # Using `type.equal?(type.itself_if_...)` is a performant way to also + # ensure that `type` is not just a DelegateClass instance (e.g. + # ActiveRecord::Type::Serialized) unintentionally delegating + # SerializeCastValue methods. + if type.equal?((type.itself_if_serialize_cast_value_compatible rescue nil)) + type.serialize_cast_value(value) + else + type.serialize(value) + end + end + + def itself_if_serialize_cast_value_compatible + self if self.class.serialize_cast_value_compatible? + end + + def initialize(...) + super + self.class.serialize_cast_value_compatible? # eagerly compute + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/string.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/string.rb new file mode 100644 index 00000000..1f263c50 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/string.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "active_model/type/immutable_string" + +module ActiveModel + module Type + # = Active Model \String \Type + # + # Attribute type for strings. It is registered under the +:string+ key. + # + # This class is a specialization of ActiveModel::Type::ImmutableString. It + # performs coercion in the same way, and can be configured in the same way. + # However, it accounts for mutable strings, so dirty tracking can properly + # check if a string has changed. + class String < ImmutableString + def changed_in_place?(raw_old_value, new_value) + if new_value.is_a?(::String) + raw_old_value != new_value + end + end + + def to_immutable_string + ImmutableString.new( + true: @true, + false: @false, + limit: limit, + precision: precision, + scale: scale, + ) + end + + private + def cast_value(value) + case value + when ::String then ::String.new(value) + when true then @true + when false then @false + else value.to_s + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/time.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/time.rb new file mode 100644 index 00000000..8a80f9d1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/time.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + # = Active Model \Time \Type + # + # Attribute type for time of day representation. It is registered under the + # +:time+ key. + # + # class Event + # include ActiveModel::Attributes + # + # attribute :start, :time + # end + # + # String values are parsed using the ISO 8601 datetime format, but are + # normalized to have a date of 2000-01-01 and be in the UTC time zone. + # + # event = Event.new + # event.start = "2004-10-25T01:23:45-06:00" + # + # event.start.class # => Time + # event.start # => 2000-01-01 07:23:45 UTC + # + # Partial time-only formats are also accepted. + # + # event.start = "00:01:02+03:00" + # event.start # => 1999-12-31 21:01:02 UTC + # + # The degree of sub-second precision can be customized when declaring an + # attribute: + # + # class Event + # include ActiveModel::Attributes + # + # attribute :start, :time, precision: 4 + # end + class Time < Value + include Helpers::Timezone + include Helpers::AcceptsMultiparameterTime.new( + defaults: { 1 => 2000, 2 => 1, 3 => 1, 4 => 0, 5 => 0 } + ) + include Helpers::TimeValue + + def type + :time + end + + def user_input_in_time_zone(value) + return unless value.present? + + case value + when ::String + value = "2000-01-01 #{value}" + time_hash = begin + ::Date._parse(value) + rescue ArgumentError + end + + return if time_hash.nil? || time_hash[:hour].nil? + when ::Time + value = value.change(year: 2000, day: 1, month: 1) + end + + super(value) + end + + private + def cast_value(value) + return apply_seconds_precision(value) unless value.is_a?(::String) + return if value.blank? + + dummy_time_value = value.sub(/\A\d{4}-\d\d-\d\d(?:T|\s)|/, "2000-01-01 ") + + fast_string_to_time(dummy_time_value) || begin + time_hash = begin + ::Date._parse(dummy_time_value) + rescue ArgumentError + end + + return if time_hash.nil? || time_hash[:hour].nil? + new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset)) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/value.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/value.rb new file mode 100644 index 00000000..0aa1ffa3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/type/value.rb @@ -0,0 +1,157 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + # = Active Model \Value \Type + # + # The base class for all attribute types. This class also serves as the + # default type for attributes that do not specify a type. + class Value + include SerializeCastValue + attr_reader :precision, :scale, :limit + + # Initializes a type with three basic configuration settings: precision, + # limit, and scale. The Value base class does not define behavior for + # these settings. It uses them for equality comparison and hash key + # generation only. + def initialize(precision: nil, limit: nil, scale: nil) + super() + @precision = precision + @scale = scale + @limit = limit + end + + # Returns true if this type can convert +value+ to a type that is usable + # by the database. For example a boolean type can return +true+ if the + # value parameter is a Ruby boolean, but may return +false+ if the value + # parameter is some other object. + def serializable?(value, &_) + true + end + + # Returns the unique type name as a Symbol. Subclasses should override + # this method. + def type + end + + # Converts a value from database input to the appropriate ruby type. The + # return value of this method will be returned from + # ActiveRecord::AttributeMethods::Read#read_attribute. The default + # implementation just calls Value#cast. + # + # +value+ The raw input, as provided from the database. + def deserialize(value) + cast(value) + end + + # Type casts a value from user input (e.g. from a setter). This value may + # be a string from the form builder, or a ruby object passed to a setter. + # There is currently no way to differentiate between which source it came + # from. + # + # The return value of this method will be returned from + # ActiveRecord::AttributeMethods::Read#read_attribute. See also: + # Value#cast_value. + # + # +value+ The raw input, as provided to the attribute setter. + def cast(value) + cast_value(value) unless value.nil? + end + + # Casts a value from the ruby type to a type that the database knows how + # to understand. The returned value from this method should be a + # +String+, +Numeric+, +Date+, +Time+, +Symbol+, +true+, +false+, or + # +nil+. + def serialize(value) + value + end + + # Type casts a value for schema dumping. This method is private, as we are + # hoping to remove it entirely. + def type_cast_for_schema(value) # :nodoc: + value.inspect + end + + # These predicates are not documented, as I need to look further into + # their use, and see if they can be removed entirely. + def binary? # :nodoc: + false + end + + # Determines whether a value has changed for dirty checking. +old_value+ + # and +new_value+ will always be type-cast. Types should not need to + # override this method. + def changed?(old_value, new_value, _new_value_before_type_cast) + old_value != new_value + end + + # Determines whether the mutable value has been modified since it was + # read. Returns +false+ by default. If your type returns an object + # which could be mutated, you should override this method. You will need + # to either: + # + # - pass +new_value+ to Value#serialize and compare it to + # +raw_old_value+ + # + # or + # + # - pass +raw_old_value+ to Value#deserialize and compare it to + # +new_value+ + # + # +raw_old_value+ The original value, before being passed to + # +deserialize+. + # + # +new_value+ The current value, after type casting. + def changed_in_place?(raw_old_value, new_value) + false + end + + def value_constructed_by_mass_assignment?(_value) # :nodoc: + false + end + + def force_equality?(_value) # :nodoc: + false + end + + def map(value, &) # :nodoc: + value + end + + def ==(other) + self.class == other.class && + precision == other.precision && + scale == other.scale && + limit == other.limit + end + alias eql? == + + def hash + [self.class, precision, scale, limit].hash + end + + def assert_valid_value(_) + end + + def serialized? # :nodoc: + false + end + + def mutable? # :nodoc: + false + end + + def as_json(*) + raise NoMethodError + end + + private + # Convenience method for types which do not need separate type casting + # behavior for user and database inputs. Called by Value#cast for + # values except +nil+. + def cast_value(value) # :doc: + value + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations.rb new file mode 100644 index 00000000..fe75c7da --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations.rb @@ -0,0 +1,508 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/extract_options" + +module ActiveModel + # = Active \Model \Validations + # + # Provides a full validation framework to your objects. + # + # A minimal implementation could be: + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :first_name, :last_name + # + # validates_each :first_name, :last_name do |record, attr, value| + # record.errors.add attr, "starts with z." if value.start_with?("z") + # end + # end + # + # Which provides you with the full standard validation stack that you + # know from Active Record: + # + # person = Person.new + # person.valid? # => true + # person.invalid? # => false + # + # person.first_name = 'zoolander' + # person.valid? # => false + # person.invalid? # => true + # person.errors.messages # => {first_name:["starts with z."]} + # + # Note that +ActiveModel::Validations+ automatically adds an +errors+ + # method to your instances initialized with a new ActiveModel::Errors + # object, so there is no need for you to do this manually. + module Validations + extend ActiveSupport::Concern + + included do + extend ActiveModel::Naming + extend ActiveModel::Callbacks + extend ActiveModel::Translation + + extend HelperMethods + include HelperMethods + + define_callbacks :validate, scope: :name + + class_attribute :_validators, instance_writer: false, default: Hash.new { |h, k| h[k] = [] } + end + + module ClassMethods + # Validates each attribute against a block. + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :first_name, :last_name + # + # validates_each :first_name, :last_name, allow_blank: true do |record, attr, value| + # record.errors.add attr, "starts with z." if value.start_with?("z") + # end + # end + # + # Options: + # * :on - Specifies the contexts where this validation is active. + # Runs in all validation contexts by default +nil+. You can pass a symbol + # or an array of symbols. (e.g. on: :create or + # on: :custom_validation_context or + # on: [:create, :custom_validation_context]) + # * :except_on - Specifies the contexts where this validation is not active. + # Runs in all validation contexts by default +nil+. You can pass a symbol + # or an array of symbols. (e.g. except: :create or + # except_on: :custom_validation_context or + # except_on: [:create, :custom_validation_context]) + # * :allow_nil - Skip validation if attribute is +nil+. + # * :allow_blank - Skip validation if attribute is blank. + # * :if - Specifies a method, proc, or string to call to determine + # if the validation should occur (e.g. if: :allow_validation, + # or if: Proc.new { |user| user.signup_step > 2 }). The method, + # proc or string should return or evaluate to a +true+ or +false+ value. + # * :unless - Specifies a method, proc, or string to call to + # determine if the validation should not occur (e.g. unless: :skip_validation, + # or unless: Proc.new { |user| user.signup_step <= 2 }). The + # method, proc, or string should return or evaluate to a +true+ or +false+ + # value. + def validates_each(*attr_names, &block) + validates_with BlockValidator, _merge_attributes(attr_names), &block + end + + VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend, :except_on].freeze # :nodoc: + + # Adds a validation method or block to the class. This is useful when + # overriding the +validate+ instance method becomes too unwieldy and + # you're looking for more descriptive declaration of your validations. + # + # This can be done with a symbol pointing to a method: + # + # class Comment + # include ActiveModel::Validations + # + # validate :must_be_friends + # + # def must_be_friends + # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee) + # end + # end + # + # With a block which is passed with the current record to be validated: + # + # class Comment + # include ActiveModel::Validations + # + # validate do |comment| + # comment.must_be_friends + # end + # + # def must_be_friends + # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee) + # end + # end + # + # Or with a block where +self+ points to the current record to be validated: + # + # class Comment + # include ActiveModel::Validations + # + # validate do + # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee) + # end + # end + # + # Note that the return value of validation methods is not relevant. + # It's not possible to halt the validate callback chain. + # + # Options: + # * :on - Specifies the contexts where this validation is active. + # Runs in all validation contexts by default +nil+. You can pass a symbol + # or an array of symbols. (e.g. on: :create or + # on: :custom_validation_context or + # on: [:create, :custom_validation_context]) + # * :except_on - Specifies the contexts where this validation is not active. + # Runs in all validation contexts by default +nil+. You can pass a symbol + # or an array of symbols. (e.g. except: :create or + # except_on: :custom_validation_context or + # except_on: [:create, :custom_validation_context]) + # * :if - Specifies a method, proc or string to call to determine + # if the validation should occur (e.g. if: :allow_validation, + # or if: Proc.new { |user| user.signup_step > 2 }). The method, + # proc or string should return or evaluate to a +true+ or +false+ value. + # * :unless - Specifies a method, proc, or string to call to + # determine if the validation should not occur (e.g. unless: :skip_validation, + # or unless: Proc.new { |user| user.signup_step <= 2 }). The + # method, proc, or string should return or evaluate to a +true+ or +false+ + # value. + # + # NOTE: Calling +validate+ multiple times on the same method will overwrite previous definitions. + # + def validate(*args, &block) + options = args.extract_options! + + if args.all?(Symbol) + options.each_key do |k| + unless VALID_OPTIONS_FOR_VALIDATE.include?(k) + raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{VALID_OPTIONS_FOR_VALIDATE.map(&:inspect).join(', ')}. Perhaps you meant to call `validates` instead of `validate`?") + end + end + end + + if options.key?(:on) + options = options.merge(if: [predicate_for_validation_context(options[:on]), *options[:if]]) + end + + if options.key?(:except_on) + options = options.dup + options[:except_on] = Array(options[:except_on]) + options[:unless] = [ + ->(o) { options[:except_on].intersect?(Array(o.validation_context)) }, + *options[:unless] + ] + end + + set_callback(:validate, *args, options, &block) + end + + # List all validators that are being used to validate the model using + # +validates_with+ method. + # + # class Person + # include ActiveModel::Validations + # + # validates_with MyValidator + # validates_with OtherValidator, on: :create + # validates_with StrictValidator, strict: true + # end + # + # Person.validators + # # => [ + # # #, + # # #, + # # # + # # ] + def validators + _validators.values.flatten.uniq + end + + # Clears all of the validators and validations. + # + # Note that this will clear anything that is being used to validate + # the model for both the +validates_with+ and +validate+ methods. + # It clears the validators that are created with an invocation of + # +validates_with+ and the callbacks that are set by an invocation + # of +validate+. + # + # class Person + # include ActiveModel::Validations + # + # validates_with MyValidator + # validates_with OtherValidator, on: :create + # validates_with StrictValidator, strict: true + # validate :cannot_be_robot + # + # def cannot_be_robot + # errors.add(:base, 'A person cannot be a robot') if person_is_robot + # end + # end + # + # Person.validators + # # => [ + # # #, + # # #, + # # # + # # ] + # + # If one runs Person.clear_validators! and then checks to see what + # validators this class has, you would obtain: + # + # Person.validators # => [] + # + # Also, the callback set by validate :cannot_be_robot will be erased + # so that: + # + # Person._validate_callbacks.empty? # => true + # + def clear_validators! + reset_callbacks(:validate) + _validators.clear + end + + # List all validators that are being used to validate a specific attribute. + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :name, :age + # + # validates_presence_of :name + # validates_inclusion_of :age, in: 0..99 + # end + # + # Person.validators_on(:name) + # # => [ + # # #, + # # ] + def validators_on(*attributes) + attributes.flat_map do |attribute| + _validators[attribute.to_sym] + end + end + + # Returns +true+ if +attribute+ is an attribute method, +false+ otherwise. + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :name + # end + # + # User.attribute_method?(:name) # => true + # User.attribute_method?(:age) # => false + def attribute_method?(attribute) + method_defined?(attribute) + end + + # Copy validators on inheritance. + def inherited(base) # :nodoc: + dup = _validators.dup + base._validators = dup.each { |k, v| dup[k] = v.dup } + super + end + + private + @@predicates_for_validation_contexts = {} + + def predicate_for_validation_context(context) + context = context.is_a?(Array) ? context.sort : Array(context) + + @@predicates_for_validation_contexts[context] ||= -> (model) do + if model.validation_context.is_a?(Array) + model.validation_context.any? { |model_context| context.include?(model_context) } + else + context.include?(model.validation_context) + end + end + end + end + + # Clean the +Errors+ object if instance is duped. + def initialize_dup(other) # :nodoc: + @errors = nil + super + end + + # Returns the +Errors+ object that holds all information about attribute + # error messages. + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :name + # validates_presence_of :name + # end + # + # person = Person.new + # person.valid? # => false + # person.errors # => # + def errors + @errors ||= Errors.new(self) + end + + # Runs all the specified validations and returns +true+ if no errors were + # added otherwise +false+. + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :name + # validates_presence_of :name + # end + # + # person = Person.new + # person.name = '' + # person.valid? # => false + # person.name = 'david' + # person.valid? # => true + # + # Context can optionally be supplied to define which callbacks to test + # against (the context is defined on the validations using :on). + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :name + # validates_presence_of :name, on: :new + # end + # + # person = Person.new + # person.valid? # => true + # person.valid?(:new) # => false + def valid?(context = nil) + current_context = validation_context + context_for_validation.context = context + errors.clear + run_validations! + ensure + context_for_validation.context = current_context + end + + alias_method :validate, :valid? + + def freeze + errors + context_for_validation + + super + end + + # Performs the opposite of valid?. Returns +true+ if errors were + # added, +false+ otherwise. + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :name + # validates_presence_of :name + # end + # + # person = Person.new + # person.name = '' + # person.invalid? # => true + # person.name = 'david' + # person.invalid? # => false + # + # Context can optionally be supplied to define which callbacks to test + # against (the context is defined on the validations using :on). + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :name + # validates_presence_of :name, on: :new + # end + # + # person = Person.new + # person.invalid? # => false + # person.invalid?(:new) # => true + def invalid?(context = nil) + !valid?(context) + end + + # Runs all the validations within the specified context. Returns +true+ if + # no errors are found, raises +ValidationError+ otherwise. + # + # Validations with no :on option will run no matter the context. Validations with + # some :on option will only run in the specified context. + def validate!(context = nil) + valid?(context) || raise_validation_error + end + + # Hook method defining how an attribute value should be retrieved. By default + # this is assumed to be an instance named after the attribute. Override this + # method in subclasses should you need to retrieve the value for a given + # attribute differently: + # + # class MyClass + # include ActiveModel::Validations + # + # def initialize(data = {}) + # @data = data + # end + # + # def read_attribute_for_validation(key) + # @data[key] + # end + # end + alias :read_attribute_for_validation :send + + # Returns the context when running validations. + # + # This is useful when running validations except a certain context (opposite to the +on+ option). + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :name + # validates :name, presence: true, if: -> { validation_context != :custom } + # end + # + # person = Person.new + # person.valid? #=> false + # person.valid?(:new) #=> false + # person.valid?(:custom) #=> true + def validation_context + context_for_validation.context + end + + private + def validation_context=(context) + context_for_validation.context = context + end + + def context_for_validation + @context_for_validation ||= ValidationContext.new + end + + def init_internals + super + @errors = nil + @context_for_validation = nil + end + + def run_validations! + _run_validate_callbacks + errors.empty? + end + + def raise_validation_error # :doc: + raise(ValidationError.new(self)) + end + end + + # = Active \Model \ValidationError + # + # Raised by validate! when the model is invalid. Use the + # +model+ method to retrieve the record which did not validate. + # + # begin + # complex_operation_that_internally_calls_validate! + # rescue ActiveModel::ValidationError => invalid + # puts invalid.model.errors + # end + class ValidationError < StandardError + attr_reader :model + + def initialize(model) + @model = model + errors = @model.errors.full_messages.join(", ") + super(I18n.t(:"#{@model.class.i18n_scope}.errors.messages.model_invalid", errors: errors, default: :"errors.messages.model_invalid")) + end + end + + class ValidationContext # :nodoc: + attr_accessor :context + end +end + +Dir[File.expand_path("validations/*.rb", __dir__)].each { |file| require file } diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/absence.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/absence.rb new file mode 100644 index 00000000..f2bfd624 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/absence.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + # == \Active \Model Absence Validator + class AbsenceValidator < EachValidator # :nodoc: + def validate_each(record, attr_name, value) + record.errors.add(attr_name, :present, **options) if value.present? + end + end + + module HelperMethods + # Validates that the specified attributes are blank (as defined by + # Object#present?). + # + # class Person < ActiveRecord::Base + # validates_absence_of :first_name + # end + # + # The first_name attribute must be in the object and it must be blank. + # + # Configuration options: + # * :message - A custom error message (default is: "must be blank"). + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. + # See ActiveModel::Validations::ClassMethods#validates for more information. + def validates_absence_of(*attr_names) + validates_with AbsenceValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/acceptance.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/acceptance.rb new file mode 100644 index 00000000..9c35acb2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/acceptance.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + class AcceptanceValidator < EachValidator # :nodoc: + def initialize(options) + super({ allow_nil: true, accept: ["1", true] }.merge!(options)) + setup!(options[:class]) + end + + def validate_each(record, attribute, value) + unless acceptable_option?(value) + record.errors.add(attribute, :accepted, **options.except(:accept, :allow_nil)) + end + end + + private + def setup!(klass) + define_attributes = LazilyDefineAttributes.new(attributes) + klass.include(define_attributes) unless klass.included_modules.include?(define_attributes) + end + + def acceptable_option?(value) + Array(options[:accept]).include?(value) + end + + class LazilyDefineAttributes < Module # :nodoc: + def initialize(attributes) + @attributes = attributes.map(&:to_s) + end + + def included(klass) + @lock = Mutex.new + mod = self + + define_method(:respond_to_missing?) do |method_name, include_private = false| + mod.define_on(klass) + super(method_name, include_private) || mod.matches?(method_name) + end + + define_method(:method_missing) do |method_name, *args, &block| + mod.define_on(klass) + if mod.matches?(method_name) + send(method_name, *args, &block) + else + super(method_name, *args, &block) + end + end + end + + def matches?(method_name) + attr_name = method_name.to_s.chomp("=") + attributes.any? { |name| name == attr_name } + end + + def define_on(klass) + @lock&.synchronize do + return unless @lock + + attr_readers = attributes.reject { |name| klass.attribute_method?(name) } + attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") } + + attr_reader(*attr_readers) + attr_writer(*attr_writers) + + remove_method :respond_to_missing? + remove_method :method_missing + + @lock = nil + end + end + + def ==(other) + self.class == other.class && attributes == other.attributes + end + + protected + attr_reader :attributes + end + end + + module HelperMethods + # Encapsulates the pattern of wanting to validate the acceptance of a + # terms of service check box (or similar agreement). + # + # class Person < ActiveRecord::Base + # validates_acceptance_of :terms_of_service + # validates_acceptance_of :eula, message: 'must be abided' + # end + # + # If the database column does not exist, the +terms_of_service+ attribute + # is entirely virtual. This check is performed only if +terms_of_service+ + # is not +nil+. + # + # Configuration options: + # * :message - A custom error message (default is: "must be + # accepted"). + # * :accept - Specifies a value that is considered accepted. + # Also accepts an array of possible values. The default value is + # an array ["1", true], which makes it easy to relate to an HTML + # checkbox. This should be set to, or include, +true+ if you are validating + # a database column, since the attribute is typecast from "1" to +true+ + # before validation. + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. + # See ActiveModel::Validations::ClassMethods#validates for more information. + def validates_acceptance_of(*attr_names) + validates_with AcceptanceValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/callbacks.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/callbacks.rb new file mode 100644 index 00000000..85d816d8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/callbacks.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + # = Active \Model \Validation \Callbacks + # + # Provides an interface for any class to have ClassMethods#before_validation and + # ClassMethods#after_validation callbacks. + # + # First, include +ActiveModel::Validations::Callbacks+ from the class you are + # creating: + # + # class MyModel + # include ActiveModel::Validations::Callbacks + # + # before_validation :do_stuff_before_validation + # after_validation :do_stuff_after_validation + # end + # + # Like other before_* callbacks if +before_validation+ throws + # +:abort+ then valid? will not be called. + module Callbacks + extend ActiveSupport::Concern + + included do + include ActiveSupport::Callbacks + define_callbacks :validation, + skip_after_callbacks_if_terminated: true, + scope: [:kind, :name] + end + + module ClassMethods + # Defines a callback that will get called right before validation. + # + # class Person + # include ActiveModel::Validations + # include ActiveModel::Validations::Callbacks + # + # attr_accessor :name + # + # validates_length_of :name, maximum: 6 + # + # before_validation :remove_whitespaces + # + # private + # def remove_whitespaces + # name.strip! + # end + # end + # + # person = Person.new + # person.name = ' bob ' + # person.valid? # => true + # person.name # => "bob" + def before_validation(*args, &block) + options = args.extract_options! + + set_options_for_callback(options) + + set_callback(:validation, :before, *args, options, &block) + end + + # Defines a callback that will get called right after validation. + # + # class Person + # include ActiveModel::Validations + # include ActiveModel::Validations::Callbacks + # + # attr_accessor :name, :status + # + # validates_presence_of :name + # + # after_validation :set_status + # + # private + # def set_status + # self.status = errors.empty? + # end + # end + # + # person = Person.new + # person.name = '' + # person.valid? # => false + # person.status # => false + # person.name = 'bob' + # person.valid? # => true + # person.status # => true + def after_validation(*args, &block) + options = args.extract_options! + options = options.dup + options[:prepend] = true + + set_options_for_callback(options) + + set_callback(:validation, :after, *args, options, &block) + end + + private + def set_options_for_callback(options) + if options.key?(:on) + options[:on] = Array(options[:on]) + options[:if] = [ + ->(o) { + options[:on].intersect?(Array(o.validation_context)) + }, + *options[:if] + ] + end + end + end + + private + # Override run_validations! to include callbacks. + def run_validations! + _run_validation_callbacks { super } + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/clusivity.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/clusivity.rb new file mode 100644 index 00000000..125c8560 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/clusivity.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "active_model/validations/resolve_value" +require "active_support/core_ext/range" + +module ActiveModel + module Validations + module Clusivity # :nodoc: + include ResolveValue + + ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " \ + "and must be supplied as the :in (or :within) option of the configuration hash" + + def check_validity! + unless delimiter.respond_to?(:include?) || delimiter.respond_to?(:call) || delimiter.respond_to?(:to_sym) + raise ArgumentError, ERROR_MESSAGE + end + end + + private + def include?(record, value) + members = resolve_value(record, delimiter) + + if value.is_a?(Array) + value.all? { |v| members.public_send(inclusion_method(members), v) } + else + members.public_send(inclusion_method(members), value) + end + end + + def delimiter + @delimiter ||= options[:in] || options[:within] + end + + # After Ruby 2.2, Range#include? on non-number-or-time-ish ranges checks all + # possible values in the range for equality, which is slower but more accurate. + # Range#cover? uses the previous logic of comparing a value with the range + # endpoints, which is fast but is only accurate on Numeric, Time, Date, + # or DateTime ranges. + def inclusion_method(enumerable) + if enumerable.is_a? Range + case enumerable.begin || enumerable.end + when Numeric, Time, DateTime, Date + :cover? + else + :include? + end + else + :include? + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/comparability.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/comparability.rb new file mode 100644 index 00000000..848c5069 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/comparability.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + module Comparability # :nodoc: + COMPARE_CHECKS = { greater_than: :>, greater_than_or_equal_to: :>=, + equal_to: :==, less_than: :<, less_than_or_equal_to: :<=, + other_than: :!= }.freeze + + def error_options(value, option_value) + options.except(*COMPARE_CHECKS.keys).merge!( + count: option_value, + value: value + ) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/comparison.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/comparison.rb new file mode 100644 index 00000000..193b9e0a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/comparison.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +require "active_model/validations/comparability" +require "active_model/validations/resolve_value" + +module ActiveModel + module Validations + class ComparisonValidator < EachValidator # :nodoc: + include Comparability + include ResolveValue + + def check_validity! + unless options.keys.intersect?(COMPARE_CHECKS.keys) + raise ArgumentError, "Expected one of :greater_than, :greater_than_or_equal_to, "\ + ":equal_to, :less_than, :less_than_or_equal_to, or :other_than option to be supplied." + end + end + + def validate_each(record, attr_name, value) + options.slice(*COMPARE_CHECKS.keys).each do |option, raw_option_value| + option_value = resolve_value(record, raw_option_value) + + if value.nil? || value.blank? + return record.errors.add(attr_name, :blank, **error_options(value, option_value)) + end + + unless value.public_send(COMPARE_CHECKS[option], option_value) + record.errors.add(attr_name, option, **error_options(value, option_value)) + end + rescue ArgumentError => e + record.errors.add(attr_name, e.message) + end + end + end + + module HelperMethods + # Validates the value of a specified attribute fulfills all + # defined comparisons with another value, proc, or attribute. + # + # class Person < ActiveRecord::Base + # validates_comparison_of :value, greater_than: 'the sum of its parts' + # end + # + # Configuration options: + # * :message - A custom error message (default is: "failed comparison"). + # * :greater_than - Specifies the value must be greater than the + # supplied value. The default error message for this option is _"must be + # greater than %{count}"_. + # * :greater_than_or_equal_to - Specifies the value must be + # greater than or equal to the supplied value. The default error message + # for this option is _"must be greater than or equal to %{count}"_. + # * :equal_to - Specifies the value must be equal to the supplied + # value. The default error message for this option is _"must be equal to + # %{count}"_. + # * :less_than - Specifies the value must be less than the + # supplied value. The default error message for this option is _"must be + # less than %{count}"_. + # * :less_than_or_equal_to - Specifies the value must be less + # than or equal to the supplied value. The default error message for + # this option is _"must be less than or equal to %{count}"_. + # * :other_than - Specifies the value must not be equal to the + # supplied value. The default error message for this option is _"must be + # other than %{count}"_. + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ . + # See ActiveModel::Validations::ClassMethods#validates for more information. + # + # The validator requires at least one of the following checks to be supplied. + # Each will accept a proc, value, or a symbol which corresponds to a method: + # + # * :greater_than + # * :greater_than_or_equal_to + # * :equal_to + # * :less_than + # * :less_than_or_equal_to + # * :other_than + # + # For example: + # + # class Person < ActiveRecord::Base + # validates_comparison_of :birth_date, less_than_or_equal_to: -> { Date.today } + # validates_comparison_of :preferred_name, other_than: :given_name, allow_nil: true + # end + def validates_comparison_of(*attr_names) + validates_with ComparisonValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/confirmation.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/confirmation.rb new file mode 100644 index 00000000..b92e3bb1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/confirmation.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + class ConfirmationValidator < EachValidator # :nodoc: + def initialize(options) + super({ case_sensitive: true }.merge!(options)) + setup!(options[:class]) + end + + def validate_each(record, attribute, value) + unless (confirmed = record.public_send("#{attribute}_confirmation")).nil? + unless confirmation_value_equal?(record, attribute, value, confirmed) + human_attribute_name = record.class.human_attribute_name(attribute) + record.errors.add(:"#{attribute}_confirmation", :confirmation, **options.except(:case_sensitive).merge!(attribute: human_attribute_name)) + end + end + end + + private + def setup!(klass) + klass.attr_reader(*attributes.filter_map do |attribute| + :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation") + end) + + klass.attr_writer(*attributes.filter_map do |attribute| + :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=") + end) + end + + def confirmation_value_equal?(record, attribute, value, confirmed) + if !options[:case_sensitive] && value.is_a?(String) + value.casecmp(confirmed) == 0 + else + value == confirmed + end + end + end + + module HelperMethods + # Encapsulates the pattern of wanting to validate a password or email + # address field with a confirmation. + # + # Model: + # class Person < ActiveRecord::Base + # validates_confirmation_of :user_name, :password + # validates_confirmation_of :email_address, + # message: 'should match confirmation' + # end + # + # View: + # <%= password_field "person", "password" %> + # <%= password_field "person", "password_confirmation" %> + # + # The added +password_confirmation+ attribute is virtual; it exists only + # as an in-memory attribute for validating the password. To achieve this, + # the validation adds accessors to the model for the confirmation + # attribute. + # + # NOTE: This check is performed only if +password_confirmation+ is not + # +nil+. To require confirmation, make sure to add a presence check for + # the confirmation attribute: + # + # validates_presence_of :password_confirmation, if: :password_changed? + # + # Configuration options: + # * :message - A custom error message (default is: "doesn't match + # %{translated_attribute_name}"). + # * :case_sensitive - Looks for an exact match. Ignored by + # non-text columns (+true+ by default). + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. + # See ActiveModel::Validations::ClassMethods#validates for more information. + def validates_confirmation_of(*attr_names) + validates_with ConfirmationValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/exclusion.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/exclusion.rb new file mode 100644 index 00000000..028f28e4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/exclusion.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "active_model/validations/clusivity" + +module ActiveModel + module Validations + class ExclusionValidator < EachValidator # :nodoc: + include Clusivity + + def validate_each(record, attribute, value) + if include?(record, value) + record.errors.add(attribute, :exclusion, **options.except(:in, :within).merge!(value: value)) + end + end + end + + module HelperMethods + # Validates that the value of the specified attribute is not in a + # particular enumerable object. + # + # class Person < ActiveRecord::Base + # validates_exclusion_of :username, in: %w( admin superuser ), message: "You don't belong here" + # validates_exclusion_of :age, in: 30..60, message: 'This site is only for under 30 and over 60' + # validates_exclusion_of :format, in: %w( mov avi ), message: "extension %{value} is not allowed" + # validates_exclusion_of :password, in: ->(person) { [person.username, person.first_name] }, + # message: 'should not be the same as your username or first name' + # validates_exclusion_of :karma, in: :reserved_karmas + # end + # + # Configuration options: + # * :in - An enumerable object of items that the value shouldn't + # be part of. This can be supplied as a proc, lambda, or symbol which returns an + # enumerable. If the enumerable is a numerical, time, or datetime range the test + # is performed with Range#cover?, otherwise with include?. When + # using a proc or lambda the instance under validation is passed as an argument. + # * :within - A synonym(or alias) for :in + # Range#cover?, otherwise with include?. + # * :message - Specifies a custom error message (default is: "is + # reserved"). + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. + # See ActiveModel::Validations::ClassMethods#validates for more information. + def validates_exclusion_of(*attr_names) + validates_with ExclusionValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/format.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/format.rb new file mode 100644 index 00000000..7e8f8e5d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/format.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require "active_model/validations/resolve_value" + +module ActiveModel + module Validations + class FormatValidator < EachValidator # :nodoc: + include ResolveValue + + def validate_each(record, attribute, value) + if options[:with] + regexp = resolve_value(record, options[:with]) + record_error(record, attribute, :with, value) unless regexp.match?(value.to_s) + elsif options[:without] + regexp = resolve_value(record, options[:without]) + record_error(record, attribute, :without, value) if regexp.match?(value.to_s) + end + end + + def check_validity! + unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or" + raise ArgumentError, "Either :with or :without must be supplied (but not both)" + end + + check_options_validity :with + check_options_validity :without + end + + private + def record_error(record, attribute, name, value) + record.errors.add(attribute, :invalid, **options.except(name).merge!(value: value)) + end + + def check_options_validity(name) + if option = options[name] + if option.is_a?(Regexp) + if options[:multiline] != true && regexp_using_multiline_anchors?(option) + raise ArgumentError, "The provided regular expression is using multiline anchors (^ or $), " \ + "which may present a security risk. Did you mean to use \\A and \\z, or forgot to add the " \ + ":multiline => true option?" + end + elsif !option.respond_to?(:call) + raise ArgumentError, "A regular expression or a proc or lambda must be supplied as :#{name}" + end + end + end + + def regexp_using_multiline_anchors?(regexp) + source = regexp.source + source.start_with?("^") || (source.end_with?("$") && !source.end_with?("\\$")) + end + end + + module HelperMethods + # Validates whether the value of the specified attribute is of the correct + # form, going by the regular expression provided. You can require that the + # attribute matches the regular expression: + # + # class Person < ActiveRecord::Base + # validates_format_of :email, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create + # end + # + # Alternatively, you can require that the specified attribute does _not_ + # match the regular expression: + # + # class Person < ActiveRecord::Base + # validates_format_of :email, without: /NOSPAM/ + # end + # + # You can also provide a proc or lambda which will determine the regular + # expression that will be used to validate the attribute. + # + # class Person < ActiveRecord::Base + # # Admin can have number as a first letter in their screen name + # validates_format_of :screen_name, + # with: ->(person) { person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\z/i : /\A[a-z][a-z0-9_\-]*\z/i } + # end + # + # Note: use \A and \z to match the start and end of the + # string, ^ and $ match the start/end of a line. + # + # Due to frequent misuse of ^ and $, you need to pass + # the multiline: true option in case you use any of these two + # anchors in the provided regular expression. In most cases, you should be + # using \A and \z. + # + # You must pass either :with or :without as an option. + # In addition, both must be a regular expression or a proc or lambda, or + # else an exception will be raised. + # + # Configuration options: + # * :message - A custom error message (default is: "is invalid"). + # * :with - Regular expression that if the attribute matches will + # result in a successful validation. This can be provided as a proc or + # lambda returning regular expression which will be called at runtime. + # * :without - Regular expression that if the attribute does not + # match will result in a successful validation. This can be provided as + # a proc or lambda returning regular expression which will be called at + # runtime. + # * :multiline - Set to true if your regular expression contains + # anchors that match the beginning or end of lines as opposed to the + # beginning or end of the string. These anchors are ^ and $. + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. + # See ActiveModel::Validations::ClassMethods#validates for more information. + def validates_format_of(*attr_names) + validates_with FormatValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/helper_methods.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/helper_methods.rb new file mode 100644 index 00000000..730173f2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/helper_methods.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + module HelperMethods # :nodoc: + private + def _merge_attributes(attr_names) + options = attr_names.extract_options!.symbolize_keys + attr_names.flatten! + options[:attributes] = attr_names + options + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/inclusion.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/inclusion.rb new file mode 100644 index 00000000..30536d98 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/inclusion.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "active_model/validations/clusivity" + +module ActiveModel + module Validations + class InclusionValidator < EachValidator # :nodoc: + include Clusivity + + def validate_each(record, attribute, value) + unless include?(record, value) + record.errors.add(attribute, :inclusion, **options.except(:in, :within).merge!(value: value)) + end + end + end + + module HelperMethods + # Validates whether the value of the specified attribute is available in a + # particular enumerable object. + # + # class Person < ActiveRecord::Base + # validates_inclusion_of :role, in: %w( admin contributor ) + # validates_inclusion_of :age, in: 0..99 + # validates_inclusion_of :format, in: %w( jpg gif png ), message: "extension %{value} is not included in the list" + # validates_inclusion_of :states, in: ->(person) { STATES[person.country] } + # validates_inclusion_of :karma, in: :available_karmas + # end + # + # Configuration options: + # * :in - An enumerable object of available items. This can be + # supplied as a proc, lambda, or symbol which returns an enumerable. If the + # enumerable is a numerical, time, or datetime range the test is performed + # with Range#cover?, otherwise with include?. When using + # a proc or lambda the instance under validation is passed as an argument. + # * :within - A synonym(or alias) for :in + # * :message - Specifies a custom error message (default is: "is + # not included in the list"). + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. + # See ActiveModel::Validations::ClassMethods#validates for more information. + def validates_inclusion_of(*attr_names) + validates_with InclusionValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/length.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/length.rb new file mode 100644 index 00000000..22a621c8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/length.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +require "active_model/validations/resolve_value" + +module ActiveModel + module Validations + class LengthValidator < EachValidator # :nodoc: + include ResolveValue + + MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze + CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze + + RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :too_short, :too_long] + + def initialize(options) + if range = (options.delete(:in) || options.delete(:within)) + raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range) + options[:minimum] = range.min if range.begin + options[:maximum] = (range.exclude_end? ? range.end - 1 : range.end) if range.end + end + + if options[:allow_blank] == false && options[:minimum].nil? && options[:is].nil? + options[:minimum] = 1 + end + + super + end + + def check_validity! + keys = CHECKS.keys & options.keys + + if keys.empty? + raise ArgumentError, "Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option." + end + + keys.each do |key| + value = options[key] + + unless (value.is_a?(Integer) && value >= 0) || + value == Float::INFINITY || value == -Float::INFINITY || + value.is_a?(Symbol) || value.is_a?(Proc) + raise ArgumentError, ":#{key} must be a non-negative Integer, Infinity, Symbol, or Proc" + end + end + end + + def validate_each(record, attribute, value) + value_length = value.respond_to?(:length) ? value.length : value.to_s.length + errors_options = options.except(*RESERVED_OPTIONS) + + CHECKS.each do |key, validity_check| + next unless check_value = options[key] + + if !value.nil? || skip_nil_check?(key) + check_value = resolve_value(record, check_value) + next if value_length.public_send(validity_check, check_value) + end + + errors_options[:count] = check_value + + default_message = options[MESSAGES[key]] + errors_options[:message] ||= default_message if default_message + + record.errors.add(attribute, MESSAGES[key], **errors_options) + end + end + + private + def skip_nil_check?(key) + key == :maximum && options[:allow_nil].nil? && options[:allow_blank].nil? + end + end + + module HelperMethods + # Validates that the specified attributes match the length restrictions + # supplied. Only one constraint option can be used at a time apart from + # +:minimum+ and +:maximum+ that can be combined together: + # + # class Person < ActiveRecord::Base + # validates_length_of :first_name, maximum: 30 + # validates_length_of :last_name, maximum: 30, message: "less than 30 if you don't mind" + # validates_length_of :fax, in: 7..32, allow_nil: true + # validates_length_of :phone, in: 7..32, allow_blank: true + # validates_length_of :user_name, within: 6..20, too_long: 'pick a shorter name', too_short: 'pick a longer name' + # validates_length_of :zip_code, minimum: 5, too_short: 'please enter at least 5 characters' + # validates_length_of :smurf_leader, is: 4, message: "papa is spelled with 4 characters... don't play me." + # validates_length_of :words_in_essay, minimum: 100, too_short: 'Your essay must be at least 100 words.' + # + # private + # def words_in_essay + # essay.scan(/\w+/) + # end + # end + # + # Constraint options: + # + # * :minimum - The minimum size of the attribute. + # * :maximum - The maximum size of the attribute. Allows +nil+ by + # default if not used with +:minimum+. + # * :is - The exact size of the attribute. + # * :within - A range specifying the minimum and maximum size of + # the attribute. + # * :in - A synonym (or alias) for :within. + # + # Other options: + # + # * :allow_nil - Attribute may be +nil+; skip validation. + # * :allow_blank - Attribute may be blank; skip validation. + # * :too_long - The error message if the attribute goes over the + # maximum (default is: "is too long (maximum is %{count} characters)"). + # * :too_short - The error message if the attribute goes under the + # minimum (default is: "is too short (minimum is %{count} characters)"). + # * :wrong_length - The error message if using the :is + # method and the attribute is the wrong size (default is: "is the wrong + # length (should be %{count} characters)"). + # * :message - The error message to use for a :minimum, + # :maximum, or :is violation. An alias of the appropriate + # too_long/too_short/wrong_length message. + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+, and +:strict+. + # See ActiveModel::Validations::ClassMethods#validates for more information. + def validates_length_of(*attr_names) + validates_with LengthValidator, _merge_attributes(attr_names) + end + + alias_method :validates_size_of, :validates_length_of + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/numericality.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/numericality.rb new file mode 100644 index 00000000..5b171520 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/numericality.rb @@ -0,0 +1,222 @@ +# frozen_string_literal: true + +require "active_model/validations/comparability" +require "active_model/validations/resolve_value" +require "bigdecimal/util" + +module ActiveModel + module Validations + class NumericalityValidator < EachValidator # :nodoc: + include Comparability + include ResolveValue + + RANGE_CHECKS = { in: :in? } + NUMBER_CHECKS = { odd: :odd?, even: :even? } + + RESERVED_OPTIONS = COMPARE_CHECKS.keys + NUMBER_CHECKS.keys + RANGE_CHECKS.keys + [:only_integer, :only_numeric] + + INTEGER_REGEX = /\A[+-]?\d+\z/ + + HEXADECIMAL_REGEX = /\A[+-]?0[xX]/ + + def check_validity! + options.slice(*COMPARE_CHECKS.keys).each do |option, value| + unless value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol) + raise ArgumentError, ":#{option} must be a number, a symbol or a proc" + end + end + + options.slice(*RANGE_CHECKS.keys).each do |option, value| + unless value.is_a?(Range) + raise ArgumentError, ":#{option} must be a range" + end + end + end + + def validate_each(record, attr_name, value, precision: Float::DIG, scale: nil) + unless is_number?(value, precision, scale) + record.errors.add(attr_name, :not_a_number, **filtered_options(value)) + return + end + + if allow_only_integer?(record) && !is_integer?(value) + record.errors.add(attr_name, :not_an_integer, **filtered_options(value)) + return + end + + value = parse_as_number(value, precision, scale) + + options.slice(*RESERVED_OPTIONS).each do |option, option_value| + if NUMBER_CHECKS.include?(option) + unless value.to_i.public_send(NUMBER_CHECKS[option]) + record.errors.add(attr_name, option, **filtered_options(value)) + end + elsif RANGE_CHECKS.include?(option) + unless value.public_send(RANGE_CHECKS[option], option_value) + record.errors.add(attr_name, option, **filtered_options(value).merge!(count: option_value)) + end + elsif COMPARE_CHECKS.include?(option) + option_value = option_as_number(record, option_value, precision, scale) + unless value.public_send(COMPARE_CHECKS[option], option_value) + record.errors.add(attr_name, option, **filtered_options(value).merge!(count: option_value)) + end + end + end + end + + private + def option_as_number(record, option_value, precision, scale) + parse_as_number(resolve_value(record, option_value), precision, scale) + end + + def parse_as_number(raw_value, precision, scale) + if raw_value.is_a?(Float) + parse_float(raw_value, precision, scale) + elsif raw_value.is_a?(BigDecimal) + round(raw_value, scale) + elsif raw_value.is_a?(Numeric) + raw_value + elsif is_integer?(raw_value) + raw_value.to_i + elsif !is_hexadecimal_literal?(raw_value) + parse_float(Kernel.Float(raw_value), precision, scale) + end + end + + def parse_float(raw_value, precision, scale) + round(raw_value, scale).to_d(precision) + end + + def round(raw_value, scale) + scale ? raw_value.round(scale) : raw_value + end + + def is_number?(raw_value, precision, scale) + if options[:only_numeric] && !raw_value.is_a?(Numeric) + return false + end + + !parse_as_number(raw_value, precision, scale).nil? + rescue ArgumentError, TypeError + false + end + + def is_integer?(raw_value) + INTEGER_REGEX.match?(raw_value.to_s) + end + + def is_hexadecimal_literal?(raw_value) + HEXADECIMAL_REGEX.match?(raw_value.to_s) + end + + def filtered_options(value) + filtered = options.except(*RESERVED_OPTIONS) + filtered[:value] = value + filtered + end + + def allow_only_integer?(record) + resolve_value(record, options[:only_integer]) + end + + def prepare_value_for_validation(value, record, attr_name) + return value if record_attribute_changed_in_place?(record, attr_name) + + came_from_user = :"#{attr_name}_came_from_user?" + + if record.respond_to?(came_from_user) + if record.public_send(came_from_user) + raw_value = record.public_send(:"#{attr_name}_before_type_cast") + elsif record.respond_to?(:read_attribute) + raw_value = record.read_attribute(attr_name) + end + else + before_type_cast = :"#{attr_name}_before_type_cast" + if record.respond_to?(before_type_cast) + raw_value = record.public_send(before_type_cast) + end + end + + raw_value || value + end + + def record_attribute_changed_in_place?(record, attr_name) + record.respond_to?(:attribute_changed_in_place?) && + record.attribute_changed_in_place?(attr_name.to_s) + end + end + + module HelperMethods + # Validates whether the value of the specified attribute is numeric by + # trying to convert it to a float with +Kernel.Float+ (if + # only_integer is +false+) or applying it to the regular + # expression /\A[\+\-]?\d+\z/ (if only_integer is set to + # +true+). Precision of +Kernel.Float+ values are guaranteed up to 15 + # digits. + # + # class Person < ActiveRecord::Base + # validates_numericality_of :value, on: :create + # end + # + # Configuration options: + # * :message - A custom error message (default is: "is not a number"). + # * :only_integer - Specifies whether the value has to be an + # integer (default is +false+). + # * :only_numeric - Specifies whether the value has to be an + # instance of Numeric (default is +false+). The default behavior is to + # attempt parsing the value if it is a String. + # * :allow_nil - Skip validation if attribute is +nil+ (default is + # +false+). Notice that for Integer and Float columns empty strings are + # converted to +nil+. + # * :greater_than - Specifies the value must be greater than the + # supplied value. The default error message for this option is _"must be + # greater than %{count}"_. + # * :greater_than_or_equal_to - Specifies the value must be + # greater than or equal the supplied value. The default error message + # for this option is _"must be greater than or equal to %{count}"_. + # * :equal_to - Specifies the value must be equal to the supplied + # value. The default error message for this option is _"must be equal to + # %{count}"_. + # * :less_than - Specifies the value must be less than the + # supplied value. The default error message for this option is _"must be + # less than %{count}"_. + # * :less_than_or_equal_to - Specifies the value must be less + # than or equal the supplied value. The default error message for this + # option is _"must be less than or equal to %{count}"_. + # * :other_than - Specifies the value must be other than the + # supplied value. The default error message for this option is _"must be + # other than %{count}"_. + # * :odd - Specifies the value must be an odd number. The default + # error message for this option is _"must be odd"_. + # * :even - Specifies the value must be an even number. The + # default error message for this option is _"must be even"_. + # * :in - Check that the value is within a range. The default + # error message for this option is _"must be in %{count}"_. + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ . + # See ActiveModel::Validations::ClassMethods#validates for more information. + # + # The following checks can also be supplied with a proc or a symbol which + # corresponds to a method: + # + # * :greater_than + # * :greater_than_or_equal_to + # * :equal_to + # * :less_than + # * :less_than_or_equal_to + # * :only_integer + # * :other_than + # + # For example: + # + # class Person < ActiveRecord::Base + # validates_numericality_of :width, less_than: ->(person) { person.height } + # validates_numericality_of :width, greater_than: :minimum_weight + # end + def validates_numericality_of(*attr_names) + validates_with NumericalityValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/presence.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/presence.rb new file mode 100644 index 00000000..533e4dc2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/presence.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + class PresenceValidator < EachValidator # :nodoc: + def validate_each(record, attr_name, value) + record.errors.add(attr_name, :blank, **options) if value.blank? + end + end + + module HelperMethods + # Validates that the specified attributes are not blank (as defined by + # Object#blank?). + # + # class Person < ActiveRecord::Base + # validates_presence_of :first_name + # end + # + # The first_name attribute must be in the object and it cannot be blank. + # + # If you want to validate the presence of a boolean field (where the real + # values are +true+ and +false+), you will want to use + # validates_inclusion_of :field_name, in: [true, false]. + # + # This is due to the way Object#blank? handles boolean values: + # false.blank? # => true. + # + # Configuration options: + # * :message - A custom error message (default is: "can't be blank"). + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. + # See ActiveModel::Validations::ClassMethods#validates for more information. + def validates_presence_of(*attr_names) + validates_with PresenceValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/resolve_value.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/resolve_value.rb new file mode 100644 index 00000000..f0438f4a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/resolve_value.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + module ResolveValue # :nodoc: + def resolve_value(record, value) + case value + when Proc + if value.arity == 0 + value.call + else + value.call(record) + end + when Symbol + record.send(value) + else + if value.respond_to?(:call) + value.call(record) + else + value + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/validates.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/validates.rb new file mode 100644 index 00000000..e095f667 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/validates.rb @@ -0,0 +1,180 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/slice" + +module ActiveModel + module Validations + module ClassMethods + # This method is a shortcut to all default validators and any custom + # validator classes ending in 'Validator'. Note that \Rails default + # validators can be overridden inside specific classes by creating + # custom validator classes in their place such as PresenceValidator. + # + # Examples of using the default Rails validators: + # + # validates :username, absence: true + # validates :terms, acceptance: true + # validates :password, confirmation: true + # validates :username, exclusion: { in: %w(admin superuser) } + # validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create } + # validates :age, inclusion: { in: 0..9 } + # validates :first_name, length: { maximum: 30 } + # validates :age, numericality: true + # validates :username, presence: true + # + # The power of the +validates+ method comes when using custom validators + # and default validators in one call for a given attribute. + # + # class EmailValidator < ActiveModel::EachValidator + # def validate_each(record, attribute, value) + # record.errors.add attribute, (options[:message] || "is not an email") unless + # /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i.match?(value) + # end + # end + # + # class Person + # include ActiveModel::Validations + # attr_accessor :name, :email + # + # validates :name, presence: true, length: { maximum: 100 } + # validates :email, presence: true, email: true + # end + # + # Validator classes may also exist within the class being validated + # allowing custom modules of validators to be included as needed. + # + # class Film + # include ActiveModel::Validations + # + # class TitleValidator < ActiveModel::EachValidator + # def validate_each(record, attribute, value) + # record.errors.add attribute, "must start with 'the'" unless /\Athe/i.match?(value) + # end + # end + # + # validates :name, title: true + # end + # + # Additionally validator classes may be in another namespace and still + # used within any class. + # + # validates :name, :'film/title' => true + # + # The validators hash can also handle regular expressions, ranges, arrays + # and strings in shortcut form. + # + # validates :email, format: /@/ + # validates :role, inclusion: %w(admin contributor) + # validates :password, length: 6..20 + # + # When using shortcut form, ranges and arrays are passed to your + # validator's initializer as options[:in] while other types + # including regular expressions and strings are passed as options[:with]. + # + # There is also a list of options that could be used along with validators: + # + # * :on - Specifies the contexts where this validation is active. + # Runs in all validation contexts by default +nil+. You can pass a symbol + # or an array of symbols. (e.g. on: :create or + # on: :custom_validation_context or + # on: [:create, :custom_validation_context]) + # * :except_on - Specifies the contexts where this validation is not active. + # Runs in all validation contexts by default +nil+. You can pass a symbol + # or an array of symbols. (e.g. except: :create or + # except_on: :custom_validation_context or + # except_on: [:create, :custom_validation_context]) + # * :if - Specifies a method, proc or string to call to determine + # if the validation should occur (e.g. if: :allow_validation, + # or if: Proc.new { |user| user.signup_step > 2 }). The method, + # proc or string should return or evaluate to a +true+ or +false+ value. + # * :unless - Specifies a method, proc, or string to call to determine + # if the validation should not occur (e.g. unless: :skip_validation, + # or unless: Proc.new { |user| user.signup_step <= 2 }). The + # method, proc, or string should return or evaluate to a +true+ or + # +false+ value. + # * :allow_nil - Skip validation if the attribute is +nil+. + # * :allow_blank - Skip validation if the attribute is blank. + # * :strict - If the :strict option is set to true + # will raise ActiveModel::StrictValidationFailed instead of adding the error. + # :strict option can also be set to any other exception. + # + # Example: + # + # validates :password, presence: true, confirmation: true, if: :password_required? + # validates :token, length: { is: 24 }, strict: TokenLengthException + # + # + # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+, +:strict+ + # and +:message+ can be given to one specific validator, as a hash: + # + # validates :password, presence: { if: :password_required?, message: 'is forgotten.' }, confirmation: true + def validates(*attributes) + defaults = attributes.extract_options!.dup + validations = defaults.slice!(*_validates_default_keys) + + raise ArgumentError, "You need to supply at least one attribute" if attributes.empty? + raise ArgumentError, "You need to supply at least one validation" if validations.empty? + + defaults[:attributes] = attributes + + validations.each do |key, options| + key = "#{key.to_s.camelize}Validator" + + begin + validator = const_get(key) + rescue NameError + raise ArgumentError, "Unknown validator: '#{key}'" + end + + next unless options + + validates_with(validator, defaults.merge(_parse_validates_options(options))) + end + end + + # This method is used to define validations that cannot be corrected by end + # users and are considered exceptional. So each validator defined with bang + # or :strict option set to true will always raise + # ActiveModel::StrictValidationFailed instead of adding error + # when validation fails. See validates for more information about + # the validation itself. + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :name + # validates! :name, presence: true + # end + # + # person = Person.new + # person.name = '' + # person.valid? + # # => ActiveModel::StrictValidationFailed: Name can't be blank + def validates!(*attributes) + options = attributes.extract_options! + options[:strict] = true + validates(*(attributes << options)) + end + + private + # When creating custom validators, it might be useful to be able to specify + # additional default keys. This can be done by overwriting this method. + def _validates_default_keys + [:if, :unless, :on, :allow_blank, :allow_nil, :strict, :except_on] + end + + def _parse_validates_options(options) + case options + when TrueClass + {} + when Hash + options + when Range, Array + { in: options } + else + { with: options } + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/with.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/with.rb new file mode 100644 index 00000000..8e183ce8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validations/with.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/extract_options" + +module ActiveModel + module Validations + class WithValidator < EachValidator # :nodoc: + def validate_each(record, attr, val) + method_name = options[:with] + + if record.method(method_name).arity == 0 + record.send method_name + else + record.send method_name, attr + end + end + end + + module ClassMethods + # Passes the record off to the class or classes specified and allows them + # to add errors based on more complex conditions. + # + # class Person + # include ActiveModel::Validations + # validates_with MyValidator + # end + # + # class MyValidator < ActiveModel::Validator + # def validate(record) + # if some_complex_logic + # record.errors.add :base, 'This record is invalid' + # end + # end + # + # private + # def some_complex_logic + # # ... + # end + # end + # + # You may also pass it multiple classes, like so: + # + # class Person + # include ActiveModel::Validations + # validates_with MyValidator, MyOtherValidator, on: :create + # end + # + # There is no default error message for +validates_with+. You must + # manually add errors to the record's errors collection in the validator + # class. + # + # To implement the validate method, you must have a +record+ parameter + # defined, which is the record to be validated. + # + # Configuration options: + # * :on - Specifies the contexts where this validation is active. + # Runs in all validation contexts by default +nil+. You can pass a symbol + # or an array of symbols. (e.g. on: :create or + # on: :custom_validation_context or + # on: [:create, :custom_validation_context]) + # * :if - Specifies a method, proc, or string to call to determine + # if the validation should occur (e.g. if: :allow_validation, + # or if: Proc.new { |user| user.signup_step > 2 }). + # The method, proc, or string should return or evaluate to a +true+ or + # +false+ value. + # * :unless - Specifies a method, proc, or string to call to + # determine if the validation should not occur + # (e.g. unless: :skip_validation, or + # unless: Proc.new { |user| user.signup_step <= 2 }). + # The method, proc, or string should return or evaluate to a +true+ or + # +false+ value. + # * :strict - Specifies whether validation should be strict. + # See ActiveModel::Validations#validates! for more information. + # + # If you pass any additional configuration options, they will be passed + # to the class and available as +options+: + # + # class Person + # include ActiveModel::Validations + # validates_with MyValidator, my_custom_key: 'my custom value' + # end + # + # class MyValidator < ActiveModel::Validator + # def validate(record) + # options[:my_custom_key] # => "my custom value" + # end + # end + def validates_with(*args, &block) + options = args.extract_options! + options[:class] = self + + args.each do |klass| + validator = klass.new(options.dup, &block) + + if validator.respond_to?(:attributes) && !validator.attributes.empty? + validator.attributes.each do |attribute| + _validators[attribute.to_sym] << validator + end + else + _validators[nil] << validator + end + + validate(validator, options) + end + end + end + + # Passes the record off to the class or classes specified and allows them + # to add errors based on more complex conditions. + # + # class Person + # include ActiveModel::Validations + # + # validate :instance_validations + # + # def instance_validations + # validates_with MyValidator + # end + # end + # + # Please consult the class method documentation for more information on + # creating your own validator. + # + # You may also pass it multiple classes, like so: + # + # class Person + # include ActiveModel::Validations + # + # validate :instance_validations, on: :create + # + # def instance_validations + # validates_with MyValidator, MyOtherValidator + # end + # end + # + # Standard configuration options (:on, :if and + # :unless), which are available on the class version of + # +validates_with+, should instead be placed on the +validates+ method + # as these are applied and tested in the callback. + # + # If you pass any additional configuration options, they will be passed + # to the class and available as +options+, please refer to the + # class version of this method for more information. + def validates_with(*args, &block) + options = args.extract_options! + options[:class] = self.class + + args.each do |klass| + validator = klass.new(options.dup, &block) + validator.validate(self) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validator.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validator.rb new file mode 100644 index 00000000..7516300b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/validator.rb @@ -0,0 +1,190 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/anonymous" + +module ActiveModel + # = Active \Model \Validator + # + # A simple base class that can be used along with + # ActiveModel::Validations::ClassMethods.validates_with + # + # class Person + # include ActiveModel::Validations + # validates_with MyValidator + # end + # + # class MyValidator < ActiveModel::Validator + # def validate(record) + # if some_complex_logic + # record.errors.add(:base, "This record is invalid") + # end + # end + # + # private + # def some_complex_logic + # # ... + # end + # end + # + # Any class that inherits from \ActiveModel::Validator must implement a method + # called +validate+ which accepts a +record+. + # + # class Person + # include ActiveModel::Validations + # validates_with MyValidator + # end + # + # class MyValidator < ActiveModel::Validator + # def validate(record) + # record # => The person instance being validated + # options # => Any non-standard options passed to validates_with + # end + # end + # + # To cause a validation error, you must add to the +record+'s errors directly + # from within the validators message. + # + # class MyValidator < ActiveModel::Validator + # def validate(record) + # record.errors.add :base, "This is some custom error message" + # record.errors.add :first_name, "This is some complex validation" + # # etc... + # end + # end + # + # To add behavior to the initialize method, use the following signature: + # + # class MyValidator < ActiveModel::Validator + # def initialize(options) + # super + # @my_custom_field = options[:field_name] || :first_name + # end + # end + # + # Note that the validator is initialized only once for the whole application + # life cycle, and not on each validation run. + # + # The easiest way to add custom validators for validating individual attributes + # is with the convenient ActiveModel::EachValidator class. + # + # class TitleValidator < ActiveModel::EachValidator + # def validate_each(record, attribute, value) + # record.errors.add attribute, 'must be Mr., Mrs., or Dr.' unless %w(Mr. Mrs. Dr.).include?(value) + # end + # end + # + # This can now be used in combination with the +validates+ method. + # See ActiveModel::Validations::ClassMethods#validates for more on this. + # + # class Person + # include ActiveModel::Validations + # attr_accessor :title + # + # validates :title, presence: true, title: true + # end + # + # It can be useful to access the class that is using that validator when there are prerequisites such + # as an +attr_accessor+ being present. This class is accessible via options[:class] in the constructor. + # To set up your validator override the constructor. + # + # class MyValidator < ActiveModel::Validator + # def initialize(options={}) + # super + # options[:class].attr_accessor :custom_attribute + # end + # end + class Validator + attr_reader :options + + # Returns the kind of the validator. + # + # PresenceValidator.kind # => :presence + # AcceptanceValidator.kind # => :acceptance + def self.kind + @kind ||= name.split("::").last.underscore.chomp("_validator").to_sym unless anonymous? + end + + # Accepts options that will be made available through the +options+ reader. + def initialize(options = {}) + @options = options.except(:class).freeze + end + + # Returns the kind for this validator. + # + # PresenceValidator.new(attributes: [:username]).kind # => :presence + # AcceptanceValidator.new(attributes: [:terms]).kind # => :acceptance + def kind + self.class.kind + end + + # Override this method in subclasses with validation logic, adding errors + # to the records +errors+ array where necessary. + def validate(record) + raise NotImplementedError, "Subclasses must implement a validate(record) method." + end + end + + # = Active \Model \EachValidator + # + # +EachValidator+ is a validator which iterates through the attributes given + # in the options hash invoking the validate_each method passing in the + # record, attribute, and value. + # + # All \Active \Model validations are built on top of this validator. + class EachValidator < Validator + attr_reader :attributes + + # Returns a new validator instance. All options will be available via the + # +options+ reader, however the :attributes option will be removed + # and instead be made available through the +attributes+ reader. + def initialize(options) + @attributes = Array(options.delete(:attributes)) + raise ArgumentError, ":attributes cannot be blank" if @attributes.empty? + super + check_validity! + end + + # Performs validation on the supplied record. By default this will call + # +validate_each+ to determine validity therefore subclasses should + # override +validate_each+ with validation logic. + def validate(record) + attributes.each do |attribute| + value = record.read_attribute_for_validation(attribute) + next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank]) + value = prepare_value_for_validation(value, record, attribute) + validate_each(record, attribute, value) + end + end + + # Override this method in subclasses with the validation logic, adding + # errors to the records +errors+ array where necessary. + def validate_each(record, attribute, value) + raise NotImplementedError, "Subclasses must implement a validate_each(record, attribute, value) method" + end + + # Hook method that gets called by the initializer allowing verification + # that the arguments supplied are valid. You could for example raise an + # +ArgumentError+ when invalid options are supplied. + def check_validity! + end + + private + def prepare_value_for_validation(value, record, attr_name) + value + end + end + + # +BlockValidator+ is a special +EachValidator+ which receives a block on initialization + # and call this block for each attribute being validated. +validates_each+ uses this validator. + class BlockValidator < EachValidator # :nodoc: + def initialize(options, &block) + @block = block + super + end + + private + def validate_each(record, attribute, value) + @block.call(record, attribute, value) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/version.rb b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/version.rb new file mode 100644 index 00000000..8fa74853 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activemodel-8.0.2/lib/active_model/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative "gem_version" + +module ActiveModel + # Returns the currently loaded version of \Active \Model as a +Gem::Version+. + def self.version + gem_version + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/CHANGELOG.md b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/CHANGELOG.md new file mode 100644 index 00000000..14abbd3b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/CHANGELOG.md @@ -0,0 +1,667 @@ +## Rails 8.0.2 (March 12, 2025) ## + +* No changes. + + +## Rails 8.0.2 (March 12, 2025) ## + +* Fix inverting `rename_enum_value` when `:from`/`:to` are provided. + + *fatkodima* + +* Prevent persisting invalid record. + + *Edouard Chin* + +* Fix inverting `drop_table` without options. + + *fatkodima* + +* Fix count with group by qualified name on loaded relation. + + *Ryuta Kamizono* + +* Fix `sum` with qualified name on loaded relation. + + *Chris Gunther* + +* The SQLite3 adapter quotes non-finite Numeric values like "Infinity" and "NaN". + + *Mike Dalessio* + +* Handle libpq returning a database version of 0 on no/bad connection in `PostgreSQLAdapter`. + + Before, this version would be cached and an error would be raised during connection configuration when + comparing it with the minimum required version for the adapter. This meant that the connection could + never be successfully configured on subsequent reconnection attempts. + + Now, this is treated as a connection failure consistent with libpq, raising a `ActiveRecord::ConnectionFailed` + and ensuring the version isn't cached, which allows the version to be retrieved on the next connection attempt. + + *Joshua Young*, *Rian McGuire* + +* Fix error handling during connection configuration. + + Active Record wasn't properly handling errors during the connection configuration phase. + This could lead to a partially configured connection being used, resulting in various exceptions, + the most common being with the PostgreSQLAdapter raising `undefined method `key?' for nil` + or `TypeError: wrong argument type nil (expected PG::TypeMap)`. + + *Jean Boussier* + +* Fix a case where a non-retryable query could be marked retryable. + + *Hartley McGuire* + +* Handle circular references when autosaving associations. + + *zzak* + +* PoolConfig no longer keeps a reference to the connection class. + + Keeping a reference to the class caused subtle issues when combined with reloading in + development. Fixes #54343. + + *Mike Dalessio* + +* Fix SQL notifications sometimes not sent when using async queries. + + ```ruby + Post.async_count + ActiveSupport::Notifications.subscribed(->(*) { "Will never reach here" }) do + Post.count + end + ``` + + In rare circumstances and under the right race condition, Active Support notifications + would no longer be dispatched after using an asynchronous query. + This is now fixed. + + *Edouard Chin* + +* Fix support for PostgreSQL enum types with commas in their name. + + *Arthur Hess* + +* Fix inserts on MySQL with no RETURNING support for a table with multiple auto populated columns. + + *Nikita Vasilevsky* + +* Fix joining on a scoped association with string joins and bind parameters. + + ```ruby + class Instructor < ActiveRecord::Base + has_many :instructor_roles, -> { active } + end + + class InstructorRole < ActiveRecord::Base + scope :active, -> { + joins("JOIN students ON instructor_roles.student_id = students.id") + .where(students { status: 1 }) + } + end + + Instructor.joins(:instructor_roles).first + ``` + + The above example would result in `ActiveRecord::StatementInvalid` because the + `active` scope bind parameters would be lost. + + *Jean Boussier* + +* Fix a potential race condition with system tests and transactional fixtures. + + *Sjoerd Lagarde* + +* Fix autosave associations to no longer validated unmodified associated records. + + Active Record was incorrectly performing validation on associated record that + weren't created nor modified as part of the transaction: + + ```ruby + Post.create!(author: User.find(1)) # Fail if user is invalid + ``` + + *Jean Boussier* + +* Remember when a database connection has recently been verified (for + two seconds, by default), to avoid repeated reverifications during a + single request. + + This should recreate a similar rate of verification as in Rails 7.1, + where connections are leased for the duration of a request, and thus + only verified once. + + *Matthew Draper* + + +## Rails 8.0.1 (December 13, 2024) ## + +* Fix removing foreign keys with :restrict action for MySQL. + + *fatkodima* + +* Fix a race condition in `ActiveRecord::Base#method_missing` when lazily defining attributes. + + If multiple thread were concurrently triggering attribute definition on the same model, + it could result in a `NoMethodError` being raised. + + *Jean Boussier* + +* Fix MySQL default functions getting dropped when changing a column's nullability. + + *Bastian Bartmann* + +* Fix `add_unique_constraint`/`add_check_constraint`/`add_foreign_key` to be revertible when given invalid options. + + *fatkodima* + +* Fix asynchronous destroying of polymorphic `belongs_to` associations. + + *fatkodima* + +* Fix `insert_all` to not update existing records. + + *fatkodima* + +* `NOT VALID` constraints should not dump in `create_table`. + + *Ryuta Kamizono* + +* Fix finding by nil composite primary key association. + + *fatkodima* + +* Properly reset composite primary key configuration when setting a primary key. + + *fatkodima* + +* Fix Mysql2Adapter support for prepared statements + + Using prepared statements with MySQL could result in a `NoMethodError` exception. + + *Jean Boussier*, *Leo Arnold*, *zzak* + +* Fix parsing of SQLite foreign key names when they contain non-ASCII characters + + *Zacharias Knudsen* + +* Fix parsing of MySQL 8.0.16+ CHECK constraints when they contain new lines. + + *Steve Hill* + +* Ensure normalized attribute queries use `IS NULL` consistently for `nil` and normalized `nil` values. + + *Joshua Young* + +* Fix `sum` when performing a grouped calculation. + + `User.group(:friendly).sum` no longer worked. This is fixed. + + *Edouard Chin* + +* Restore back the ability to pass only database name to `DATABASE_URL`. + + *fatkodima* + + +## Rails 8.0.0.1 (December 10, 2024) ## + +* No changes. + + +## Rails 8.0.0 (November 07, 2024) ## + +* Fix support for `query_cache: false` in `database.yml`. + + `query_cache: false` would no longer entirely disable the Active Record query cache. + + *zzak* + + +## Rails 8.0.0.rc2 (October 30, 2024) ## + +* NULLS NOT DISTINCT works with UNIQUE CONSTRAINT as well as UNIQUE INDEX. + + *Ryuta Kamizono* + +* The `db:prepare` task no longer loads seeds when a non-primary database is created. + + Previously, the `db:prepare` task would load seeds whenever a new database + is created, leading to potential loss of data if a database is added to an + existing environment. + + Introduces a new database config property `seeds` to control whether seeds + are loaded during `db:prepare` which defaults to `true` for primary database + configs and `false` otherwise. + + Fixes #53348. + + *Mike Dalessio* + +* `PG::UnableToSend: no connection to the server` is now retryable as a connection-related exception + + *Kazuma Watanabe* + +* Fix strict loading propagation even if statement cache is not used. + + *Ryuta Kamizono* + +* Allow `rename_enum` accepts two from/to name arguments as `rename_table` does so. + + *Ryuta Kamizono* + + +## Rails 8.0.0.rc1 (October 19, 2024) ## + +* Remove deprecated support to setting `ENV["SCHEMA_CACHE"]`. + + *Rafael Mendonça França* + +* Remove deprecated support to passing a database name to `cache_dump_filename`. + + *Rafael Mendonça França* + +* Remove deprecated `ActiveRecord::ConnectionAdapters::ConnectionPool#connection`. + + *Rafael Mendonça França* + +* Remove deprecated `config.active_record.sqlite3_deprecated_warning`. + + *Rafael Mendonça França* + +* Remove deprecated `config.active_record.warn_on_records_fetched_greater_than`. + + *Rafael Mendonça França* + +* Remove deprecated support for defining `enum` with keyword arguments. + + *Rafael Mendonça França* + +* Remove deprecated support to finding database adapters that aren't registered to Active Record. + + *Rafael Mendonça França* + +* Remove deprecated `config.active_record.allow_deprecated_singular_associations_name`. + + *Rafael Mendonça França* + +* Remove deprecated `config.active_record.commit_transaction_on_non_local_return`. + + *Rafael Mendonça França* + +* Fix incorrect SQL query when passing an empty hash to `ActiveRecord::Base.insert`. + + *David Stosik* + +* Allow to save records with polymorphic join tables that have `inverse_of` + specified. + + *Markus Doits* + +* Fix association scopes applying on the incorrect join when using a polymorphic `has_many through:`. + + *Joshua Young* + +* Allow `ActiveRecord::Base#pluck` to accept hash arguments with symbol and string values. + + ```ruby + Post.joins(:comments).pluck(:id, comments: :id) + Post.joins(:comments).pluck("id", "comments" => "id") + ``` + + *Joshua Young* + +* Make Float distinguish between `float4` and `float8` in PostgreSQL. + + Fixes #52742 + + *Ryota Kitazawa*, *Takayuki Nagatomi* + + +## Rails 8.0.0.beta1 (September 26, 2024) ## + +* Allow `drop_table` to accept an array of table names. + + This will let you to drop multiple tables in a single call. + + ```ruby + ActiveRecord::Base.lease_connection.drop_table(:users, :posts) + ``` + + *Gabriel Sobrinho* + +* Add support for PostgreSQL `IF NOT EXISTS` via the `:if_not_exists` option + on the `add_enum_value` method. + + *Ariel Rzezak* + +* When running `db:migrate` on a fresh database, load the databases schemas before running migrations. + + *Andrew Novoselac*, *Marek Kasztelnik* + +* Fix an issue where `.left_outer_joins` used with multiple associations that have + the same child association but different parents does not join all parents. + + Previously, using `.left_outer_joins` with the same child association would only join one of the parents. + + Now it will correctly join both parents. + + Fixes #41498. + + *Garrett Blehm* + +* Deprecate `unsigned_float` and `unsigned_decimal` short-hand column methods. + + As of MySQL 8.0.17, the UNSIGNED attribute is deprecated for columns of type FLOAT, DOUBLE, + and DECIMAL. Consider using a simple CHECK constraint instead for such columns. + + https://dev.mysql.com/doc/refman/8.0/en/numeric-type-syntax.html + + *Ryuta Kamizono* + +* Drop MySQL 5.5 support. + + MySQL 5.5 is the only version that does not support datetime with precision, + which we have supported in the core. Now we support MySQL 5.6.4 or later, which + is the first version to support datetime with precision. + + *Ryuta Kamizono* + +* Make Active Record asynchronous queries compatible with transactional fixtures. + + Previously transactional fixtures would disable asynchronous queries, because transactional + fixtures impose all queries use the same connection. + + Now asynchronous queries will use the connection pinned by transactional fixtures, and behave + much closer to production. + + *Jean Boussier* + +* Deserialize binary data before decrypting + + This ensures that we call `PG::Connection.unescape_bytea` on PostgreSQL before decryption. + + *Donal McBreen* + +* Ensure `ActiveRecord::Encryption.config` is always ready before access. + + Previously, `ActiveRecord::Encryption` configuration was deferred until `ActiveRecord::Base` + was loaded. Therefore, accessing `ActiveRecord::Encryption.config` properties before + `ActiveRecord::Base` was loaded would give incorrect results. + + `ActiveRecord::Encryption` now has its own loading hook so that its configuration is set as + soon as needed. + + When `ActiveRecord::Base` is loaded, even lazily, it in turn triggers the loading of + `ActiveRecord::Encryption`, thus preserving the original behavior of having its config ready + before any use of `ActiveRecord::Base`. + + *Maxime Réty* + +* Add `TimeZoneConverter#==` method, so objects will be properly compared by + their type, scale, limit & precision. + + Address #52699. + + *Ruy Rocha* + +* Add support for SQLite3 full-text-search and other virtual tables. + + Previously, adding sqlite3 virtual tables messed up `schema.rb`. + + Now, virtual tables can safely be added using `create_virtual_table`. + + *Zacharias Knudsen* + +* Support use of alternative database interfaces via the `database_cli` ActiveRecord configuration option. + + ```ruby + Rails.application.configure do + config.active_record.database_cli = { postgresql: "pgcli" } + end + ``` + + *T S Vallender* + +* Add support for dumping table inheritance and native partitioning table definitions for PostgeSQL adapter + + *Justin Talbott* + +* Add support for `ActiveRecord::Point` type casts using `Hash` values + + This allows `ActiveRecord::Point` to be cast or serialized from a hash + with `:x` and `:y` keys of numeric values, mirroring the functionality of + existing casts for string and array values. Both string and symbol keys are + supported. + + ```ruby + class PostgresqlPoint < ActiveRecord::Base + attribute :x, :point + attribute :y, :point + attribute :z, :point + end + + val = PostgresqlPoint.new({ + x: '(12.34, -43.21)', + y: [12.34, '-43.21'], + z: {x: '12.34', y: -43.21} + }) + ActiveRecord::Point.new(12.32, -43.21) == val.x == val.y == val.z + ``` + + *Stephen Drew* + +* Replace `SQLite3::Database#busy_timeout` with `#busy_handler_timeout=`. + + Provides a non-GVL-blocking, fair retry interval busy handler implementation. + + *Stephen Margheim* + +* SQLite3Adapter: Translate `SQLite3::BusyException` into `ActiveRecord::StatementTimeout`. + + *Matthew Nguyen* + +* Include schema name in `enable_extension` statements in `db/schema.rb`. + + The schema dumper will now include the schema name in generated + `enable_extension` statements if they differ from the current schema. + + For example, if you have a migration: + + ```ruby + enable_extension "heroku_ext.pgcrypto" + enable_extension "pg_stat_statements" + ``` + + then the generated schema dump will also contain: + + ```ruby + enable_extension "heroku_ext.pgcrypto" + enable_extension "pg_stat_statements" + ``` + + *Tony Novak* + +* Fix `ActiveRecord::Encryption::EncryptedAttributeType#type` to return + actual cast type. + + *Vasiliy Ermolovich* + +* SQLite3Adapter: Bulk insert fixtures. + + Previously one insert command was executed for each fixture, now they are + aggregated in a single bulk insert command. + + *Lázaro Nixon* + +* PostgreSQLAdapter: Allow `disable_extension` to be called with schema-qualified name. + + For parity with `enable_extension`, the `disable_extension` method can be called with a schema-qualified + name (e.g. `disable_extension "myschema.pgcrypto"`). Note that PostgreSQL's `DROP EXTENSION` does not + actually take a schema name (unlike `CREATE EXTENSION`), so the resulting SQL statement will only name + the extension, e.g. `DROP EXTENSION IF EXISTS "pgcrypto"`. + + *Tony Novak* + +* Make `create_schema` / `drop_schema` reversible in migrations. + + Previously, `create_schema` and `drop_schema` were irreversible migration operations. + + *Tony Novak* + +* Support batching using custom columns. + + ```ruby + Product.in_batches(cursor: [:shop_id, :id]) do |relation| + # do something with relation + end + ``` + + *fatkodima* + +* Use SQLite `IMMEDIATE` transactions when possible. + + Transactions run against the SQLite3 adapter default to IMMEDIATE mode to improve concurrency support and avoid busy exceptions. + + *Stephen Margheim* + +* Raise specific exception when a connection is not defined. + + The new `ConnectionNotDefined` exception provides connection name, shard and role accessors indicating the details of the connection that was requested. + + *Hana Harencarova*, *Matthew Draper* + +* Delete the deprecated constant `ActiveRecord::ImmutableRelation`. + + *Xavier Noria* + +* Fix duplicate callback execution when child autosaves parent with `has_one` and `belongs_to`. + + Before, persisting a new child record with a new associated parent record would run `before_validation`, + `after_validation`, `before_save` and `after_save` callbacks twice. + + Now, these callbacks are only executed once as expected. + + *Joshua Young* + +* `ActiveRecord::Encryption::Encryptor` now supports a `:compressor` option to customize the compression algorithm used. + + ```ruby + module ZstdCompressor + def self.deflate(data) + Zstd.compress(data) + end + + def self.inflate(data) + Zstd.decompress(data) + end + end + + class User + encrypts :name, compressor: ZstdCompressor + end + ``` + + You disable compression by passing `compress: false`. + + ```ruby + class User + encrypts :name, compress: false + end + ``` + + *heka1024* + +* Add condensed `#inspect` for `ConnectionPool`, `AbstractAdapter`, and + `DatabaseConfig`. + + *Hartley McGuire* + +* Add `.shard_keys`, `.sharded?`, & `.connected_to_all_shards` methods. + + ```ruby + class ShardedBase < ActiveRecord::Base + self.abstract_class = true + + connects_to shards: { + shard_one: { writing: :shard_one }, + shard_two: { writing: :shard_two } + } + end + + class ShardedModel < ShardedBase + end + + ShardedModel.shard_keys => [:shard_one, :shard_two] + ShardedModel.sharded? => true + ShardedBase.connected_to_all_shards { ShardedModel.current_shard } => [:shard_one, :shard_two] + ``` + + *Nony Dutton* + +* Add a `filter` option to `in_order_of` to prioritize certain values in the sorting without filtering the results + by these values. + + *Igor Depolli* + +* Fix an issue where the IDs reader method did not return expected results + for preloaded associations in models using composite primary keys. + + *Jay Ang* + +* Allow to configure `strict_loading_mode` globally or within a model. + + Defaults to `:all`, can be changed to `:n_plus_one_only`. + + *Garen Torikian* + +* Add `ActiveRecord::Relation#readonly?`. + + Reflects if the relation has been marked as readonly. + + *Theodor Tonum* + +* Improve `ActiveRecord::Store` to raise a descriptive exception if the column is not either + structured (e.g., PostgreSQL +hstore+/+json+, or MySQL +json+) or declared serializable via + `ActiveRecord.store`. + + Previously, a `NoMethodError` would be raised when the accessor was read or written: + + NoMethodError: undefined method `accessor' for an instance of ActiveRecord::Type::Text + + Now, a descriptive `ConfigurationError` is raised: + + ActiveRecord::ConfigurationError: the column 'metadata' has not been configured as a store. + Please make sure the column is declared serializable via 'ActiveRecord.store' or, if your + database supports it, use a structured column type like hstore or json. + + *Mike Dalessio* + +* Fix inference of association model on nested models with the same demodularized name. + + E.g. with the following setup: + + ```ruby + class Nested::Post < ApplicationRecord + has_one :post, through: :other + end + ``` + + Before, `#post` would infer the model as `Nested::Post`, but now it correctly infers `Post`. + + *Joshua Young* + +* Add public method for checking if a table is ignored by the schema cache. + + Previously, an application would need to reimplement `ignored_table?` from the schema cache class to check if a table was set to be ignored. This adds a public method to support this and updates the schema cache to use that directly. + + ```ruby + ActiveRecord.schema_cache_ignored_tables = ["developers"] + ActiveRecord.schema_cache_ignored_table?("developers") + => true + ``` + + *Eileen M. Uchitelle* + +Please check [7-2-stable](https://github.com/rails/rails/blob/7-2-stable/activerecord/CHANGELOG.md) for previous changes. diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/MIT-LICENSE b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/MIT-LICENSE new file mode 100644 index 00000000..5b861091 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/MIT-LICENSE @@ -0,0 +1,22 @@ +Copyright (c) David Heinemeier Hansson + +Arel originally copyright (c) 2007-2016 Nick Kallen, Bryan Helmkamp, Emilio Tagua, Aaron Patterson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/README.rdoc b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/README.rdoc new file mode 100644 index 00000000..712f9eda --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/README.rdoc @@ -0,0 +1,219 @@ += Active Record -- Object-relational mapping in \Rails + +Active Record connects classes to relational database tables to establish an +almost zero-configuration persistence layer for applications. The library +provides a base class that, when subclassed, sets up a mapping between the new +class and an existing table in the database. In the context of an application, +these classes are commonly referred to as *models*. Models can also be +connected to other models; this is done by defining *associations*. + +Active Record relies heavily on naming in that it uses class and association +names to establish mappings between respective database tables and foreign key +columns. Although these mappings can be defined explicitly, it's recommended +to follow naming conventions, especially when getting started with the +library. + +You can read more about Active Record in the {Active Record Basics}[https://guides.rubyonrails.org/active_record_basics.html] guide. + +A short rundown of some of the major features: + +* Automated mapping between classes and tables, attributes and columns. + + class Product < ActiveRecord::Base + end + + The Product class is automatically mapped to the table named "products", + which might look like this: + + CREATE TABLE products ( + id bigint NOT NULL auto_increment, + name varchar(255), + PRIMARY KEY (id) + ); + + This would also define the following accessors: Product#name and + Product#name=(new_name). + + {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Base.html] + +* Associations between objects defined by simple class methods. + + class Firm < ActiveRecord::Base + has_many :clients + has_one :account + belongs_to :conglomerate + end + + {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html] + + +* Aggregations of value objects. + + class Account < ActiveRecord::Base + composed_of :balance, class_name: 'Money', + mapping: %w(balance amount) + composed_of :address, + mapping: [%w(address_street street), %w(address_city city)] + end + + {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html] + + +* Validation rules that can differ for new or existing objects. + + class Account < ActiveRecord::Base + validates :subdomain, :name, :email_address, :password, presence: true + validates :subdomain, uniqueness: true + validates :terms_of_service, acceptance: true, on: :create + validates :password, :email_address, confirmation: true, on: :create + end + + {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Validations.html] + + +* Callbacks available for the entire life cycle (instantiation, saving, destroying, validating, etc.). + + class Person < ActiveRecord::Base + before_destroy :invalidate_payment_plan + # the `invalidate_payment_plan` method gets called just before Person#destroy + end + + {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html] + + +* Inheritance hierarchies. + + class Company < ActiveRecord::Base; end + class Firm < Company; end + class Client < Company; end + class PriorityClient < Client; end + + {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Base.html] + + +* Transactions. + + # Database transaction + Account.transaction do + david.withdrawal(100) + mary.deposit(100) + end + + {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html] + + +* Reflections on columns, associations, and aggregations. + + reflection = Firm.reflect_on_association(:clients) + reflection.klass # => Client (class) + Firm.columns # Returns an array of column descriptors for the firms table + + {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Reflection/ClassMethods.html] + + +* Database abstraction through simple adapters. + + # connect to SQLite3 + ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: 'dbfile.sqlite3') + + # connect to MySQL with authentication + ActiveRecord::Base.establish_connection( + adapter: 'mysql2', + host: 'localhost', + username: 'me', + password: 'secret', + database: 'activerecord' + ) + + {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Base.html] and read about the built-in support for + MySQL[https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Mysql2Adapter.html], + PostgreSQL[https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], and + SQLite3[https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html]. + + +* Logging support for Log4r[https://github.com/colbygk/log4r] and Logger[https://docs.ruby-lang.org/en/master/Logger.html]. + + ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT) + ActiveRecord::Base.logger = Log4r::Logger.new('Application Log') + + +* Database agnostic schema management with Migrations. + + class AddSystemSettings < ActiveRecord::Migration[8.0] + def up + create_table :system_settings do |t| + t.string :name + t.string :label + t.text :value + t.string :type + t.integer :position + end + + SystemSetting.create name: 'notice', label: 'Use notice?', value: 1 + end + + def down + drop_table :system_settings + end + end + + {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Migration.html] + + +== Philosophy + +Active Record is an implementation of the object-relational mapping (ORM) +pattern[https://www.martinfowler.com/eaaCatalog/activeRecord.html] by the same +name described by Martin Fowler: + +>>> + "An object that wraps a row in a database table or view, + encapsulates the database access, and adds domain logic on that data." + +Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is +object-relational mapping. The prime directive for this mapping has been to minimize +the amount of code needed to build a real-world domain model. This is made possible +by relying on a number of conventions that make it easy for Active Record to infer +complex relations and structures from a minimal amount of explicit direction. + +Convention over Configuration: +* No XML files! +* Lots of reflection and run-time extension +* Magic is not inherently a bad word + +Admit the Database: +* Lets you drop down to SQL for odd cases and performance +* Doesn't attempt to duplicate or replace data definitions + + +== Download and installation + +The latest version of Active Record can be installed with RubyGems: + + $ gem install activerecord + +Source code can be downloaded as part of the \Rails project on GitHub: + +* https://github.com/rails/rails/tree/main/activerecord + + +== License + +Active Record is released under the MIT license: + +* https://opensource.org/licenses/MIT + + +== Support + +API documentation is at: + +* https://api.rubyonrails.org + +Bug reports for the Ruby on \Rails project can be filed here: + +* https://github.com/rails/rails/issues + +Feature requests should be discussed on the rails-core mailing list here: + +* https://discuss.rubyonrails.org/c/rubyonrails-core diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/examples/performance.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/examples/performance.rb new file mode 100644 index 00000000..4e3fe2fb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/examples/performance.rb @@ -0,0 +1,185 @@ +# frozen_string_literal: true + +require "active_record" +require "benchmark/ips" + +TIME = (ENV["BENCHMARK_TIME"] || 20).to_i +RECORDS = (ENV["BENCHMARK_RECORDS"] || TIME * 1000).to_i + +conn = { adapter: "sqlite3", database: ":memory:" } + +ActiveRecord::Base.establish_connection(conn) + +class User < ActiveRecord::Base + connection.create_table :users, force: true do |t| + t.string :name, :email + t.timestamps + end + + has_many :exhibits +end + +class Exhibit < ActiveRecord::Base + connection.create_table :exhibits, force: true do |t| + t.belongs_to :user + t.string :name + t.text :notes + t.timestamps + end + + belongs_to :user + + def look; attributes end + def feel; look; user.name end + + def self.with_name + where("name IS NOT NULL") + end + + def self.with_notes + where("notes IS NOT NULL") + end + + def self.look(exhibits) exhibits.each(&:look) end + def self.feel(exhibits) exhibits.each(&:feel) end +end + +def progress_bar(int); print "." if (int % 100).zero? ; end + +puts "Generating data..." + +module ActiveRecord + class Faker + LOREM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse non aliquet diam. Curabitur vel urna metus, quis malesuada elit. + Integer consequat tincidunt felis. Etiam non erat dolor. Vivamus imperdiet nibh sit amet diam eleifend id posuere diam malesuada. Mauris at accumsan sem. + Donec id lorem neque. Fusce erat lorem, ornare eu congue vitae, malesuada quis neque. Maecenas vel urna a velit pretium fermentum. Donec tortor enim, + tempor venenatis egestas a, tempor sed ipsum. Ut arcu justo, faucibus non imperdiet ac, interdum at diam. Pellentesque ipsum enim, venenatis ut iaculis vitae, + varius vitae sem. Sed rutrum quam ac elit euismod bibendum. Donec ultricies ultricies magna, at lacinia libero mollis aliquam. Sed ac arcu in tortor elementum + tincidunt vel interdum sem. Curabitur eget erat arcu. Praesent eget eros leo. Nam magna enim, sollicitudin vehicula scelerisque in, vulputate ut libero. + Praesent varius tincidunt commodo".split + + def self.name + LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join " " + end + + def self.email + LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join("@") + ".com" + end + end +end + +# pre-compute the insert statements and fake data compilation, +# so the benchmarks below show the actual runtime for the execute +# method, minus the setup steps + +# Using the same paragraph for all exhibits because it is very slow +# to generate unique paragraphs for all exhibits. +notes = ActiveRecord::Faker::LOREM.join " " +today = Date.today + +puts "Inserting #{RECORDS} users and exhibits..." +RECORDS.times do |record| + user = User.create( + created_at: today, + name: ActiveRecord::Faker.name, + email: ActiveRecord::Faker.email + ) + + Exhibit.create( + created_at: today, + name: ActiveRecord::Faker.name, + user: user, + notes: notes + ) + progress_bar(record) +end +puts "Done!\n" + +Benchmark.ips(TIME) do |x| + ar_obj = Exhibit.find(1) + attrs = { name: "sam" } + attrs_first = { name: "sam" } + attrs_second = { name: "tom" } + exhibit = { + name: ActiveRecord::Faker.name, + notes: notes, + created_at: Date.today + } + + x.report("Model#id") do + ar_obj.id + end + + x.report "Model.new (instantiation)" do + Exhibit.new + end + + x.report "Model.new (setting attributes)" do + Exhibit.new(attrs) + end + + x.report "Model.first" do + Exhibit.first.look + end + + x.report "Model.take" do + Exhibit.take + end + + x.report("Model.all limit(100)") do + Exhibit.look Exhibit.limit(100) + end + + x.report("Model.all take(100)") do + Exhibit.look Exhibit.take(100) + end + + x.report "Model.all limit(100) with relationship" do + Exhibit.feel Exhibit.limit(100).includes(:user) + end + + x.report "Model.all limit(10,000)" do + Exhibit.look Exhibit.limit(10000) + end + + x.report "Model.named_scope" do + Exhibit.limit(10).with_name.with_notes + end + + x.report "Model.create" do + Exhibit.create(exhibit) + end + + x.report "Resource#attributes=" do + e = Exhibit.new(attrs_first) + e.attributes = attrs_second + end + + x.report "Resource#update" do + Exhibit.first.update(name: "bob") + end + + x.report "Resource#destroy" do + Exhibit.first.destroy + end + + x.report "Model.transaction" do + Exhibit.transaction { Exhibit.new } + end + + x.report "Model.find(id)" do + User.find(1) + end + + x.report "Model.find_by_sql" do + Exhibit.find_by_sql("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}").first + end + + x.report "Model.log" do + Exhibit.lease_connection.send(:log, "hello", "world") { } + end + + x.report "AR.execute(query)" do + ActiveRecord::Base.lease_connection.execute("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}") + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/examples/simple.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/examples/simple.rb new file mode 100644 index 00000000..280b786d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/examples/simple.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "active_record" + +class Person < ActiveRecord::Base + establish_connection adapter: "sqlite3", database: "foobar.db" + connection.create_table table_name, force: true do |t| + t.string :name + end +end + +bob = Person.create!(name: "bob") +puts Person.all.inspect +bob.destroy +puts Person.all.inspect diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record.rb new file mode 100644 index 00000000..2ef82883 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record.rb @@ -0,0 +1,573 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) David Heinemeier Hansson +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ + +require "active_support" +require "active_support/rails" +require "active_support/ordered_options" +require "active_model" +require "arel" +require "yaml" +require "zlib" + +require "active_record/version" +require "active_record/deprecator" +require "active_model/attribute_set" +require "active_record/errors" + +# :include: ../README.rdoc +module ActiveRecord + extend ActiveSupport::Autoload + + autoload :Base + autoload :Callbacks + autoload :ConnectionHandling + autoload :Core + autoload :CounterCache + autoload :DelegatedType + autoload :DestroyAssociationAsyncJob + autoload :DynamicMatchers + autoload :Encryption + autoload :Enum + autoload :Explain + autoload :FixtureSet, "active_record/fixtures" + autoload :Inheritance + autoload :Integration + autoload :InternalMetadata + autoload :LogSubscriber + autoload :Marshalling + autoload :Migration + autoload :Migrator, "active_record/migration" + autoload :ModelSchema + autoload :NestedAttributes + autoload :NoTouching + autoload :Normalization + autoload :Persistence + autoload :QueryCache + autoload :QueryLogs + autoload :Querying + autoload :ReadonlyAttributes + autoload :RecordInvalid, "active_record/validations" + autoload :Reflection + autoload :RuntimeRegistry + autoload :Sanitization + autoload :Schema + autoload :SchemaDumper + autoload :SchemaMigration + autoload :Scoping + autoload :SecurePassword + autoload :SecureToken + autoload :Serialization + autoload :SignedId + autoload :Store + autoload :Suppressor + autoload :TestDatabases + autoload :TestFixtures, "active_record/fixtures" + autoload :Timestamp + autoload :TokenFor + autoload :TouchLater + autoload :Transaction + autoload :Transactions + autoload :Translation + autoload :Validations + + eager_autoload do + autoload :Aggregations + autoload :AssociationRelation + autoload :Associations + autoload :AsynchronousQueriesTracker + autoload :AttributeAssignment + autoload :AttributeMethods + autoload :AutosaveAssociation + autoload :ConnectionAdapters + autoload :DisableJoinsAssociationRelation + autoload :FutureResult + autoload :LegacyYamlAdapter + autoload :Promise + autoload :Relation + autoload :Result + autoload :StatementCache + autoload :TableMetadata + autoload :Type + + autoload_under "relation" do + autoload :Batches + autoload :Calculations + autoload :Delegation + autoload :FinderMethods + autoload :PredicateBuilder + autoload :QueryMethods + autoload :SpawnMethods + end + end + + module Coders + autoload :ColumnSerializer, "active_record/coders/column_serializer" + autoload :JSON, "active_record/coders/json" + autoload :YAMLColumn, "active_record/coders/yaml_column" + end + + module AttributeMethods + extend ActiveSupport::Autoload + + autoload :CompositePrimaryKey + + eager_autoload do + autoload :BeforeTypeCast + autoload :Dirty + autoload :PrimaryKey + autoload :Query + autoload :Read + autoload :Serialization + autoload :TimeZoneConversion + autoload :Write + end + end + + module Locking + extend ActiveSupport::Autoload + + eager_autoload do + autoload :Optimistic + autoload :Pessimistic + end + end + + module Scoping + extend ActiveSupport::Autoload + + eager_autoload do + autoload :Default + autoload :Named + end + end + + module Middleware + extend ActiveSupport::Autoload + + autoload :DatabaseSelector + autoload :ShardSelector + end + + module Tasks + extend ActiveSupport::Autoload + + autoload :DatabaseTasks + autoload :MySQLDatabaseTasks, "active_record/tasks/mysql_database_tasks" + autoload :PostgreSQLDatabaseTasks, "active_record/tasks/postgresql_database_tasks" + autoload :SQLiteDatabaseTasks, "active_record/tasks/sqlite_database_tasks" + end + + singleton_class.attr_accessor :disable_prepared_statements + self.disable_prepared_statements = false + + ## + # :singleton-method: lazily_load_schema_cache + # Lazily load the schema cache. This option will load the schema cache + # when a connection is established rather than on boot. + singleton_class.attr_accessor :lazily_load_schema_cache + self.lazily_load_schema_cache = false + + ## + # :singleton-method: schema_cache_ignored_tables + # A list of tables or regex's to match tables to ignore when + # dumping the schema cache. For example if this is set to +[/^_/]+ + # the schema cache will not dump tables named with an underscore. + singleton_class.attr_accessor :schema_cache_ignored_tables + self.schema_cache_ignored_tables = [] + + # Checks to see if the +table_name+ is ignored by checking + # against the +schema_cache_ignored_tables+ option. + # + # ActiveRecord.schema_cache_ignored_table?(:developers) + # + def self.schema_cache_ignored_table?(table_name) + ActiveRecord.schema_cache_ignored_tables.any? do |ignored| + ignored === table_name + end + end + + singleton_class.attr_accessor :database_cli + self.database_cli = { postgresql: "psql", mysql: %w[mysql mysql5], sqlite: "sqlite3" } + + singleton_class.attr_reader :default_timezone + + # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling + # dates and times from the database. This is set to :utc by default. + def self.default_timezone=(default_timezone) + unless %i(local utc).include?(default_timezone) + raise ArgumentError, "default_timezone must be either :utc (default) or :local." + end + + @default_timezone = default_timezone + end + + self.default_timezone = :utc + + ## + # :singleton-method: db_warnings_action + # The action to take when database query produces warning. + # Must be one of :ignore, :log, :raise, :report, or a custom proc. + # The default is :ignore. + singleton_class.attr_reader :db_warnings_action + + def self.db_warnings_action=(action) + @db_warnings_action = + case action + when :ignore + nil + when :log + ->(warning) do + warning_message = "[#{warning.class}] #{warning.message}" + warning_message += " (#{warning.code})" if warning.code + ActiveRecord::Base.logger.warn(warning_message) + end + when :raise + ->(warning) { raise warning } + when :report + ->(warning) { Rails.error.report(warning, handled: true) } + when Proc + action + else + raise ArgumentError, "db_warnings_action must be one of :ignore, :log, :raise, :report, or a custom proc." + end + end + + self.db_warnings_action = :ignore + + ## + # :singleton-method: db_warnings_ignore + # Specify allowlist of database warnings. + singleton_class.attr_accessor :db_warnings_ignore + self.db_warnings_ignore = [] + + singleton_class.attr_accessor :writing_role + self.writing_role = :writing + + singleton_class.attr_accessor :reading_role + self.reading_role = :reading + + ## + # :singleton-method: async_query_executor + # Sets the async_query_executor for an application. By default the thread pool executor + # set to +nil+ which will not run queries in the background. Applications must configure + # a thread pool executor to use this feature. Options are: + # + # * nil - Does not initialize a thread pool executor. Any async calls will be + # run in the foreground. + # * :global_thread_pool - Initializes a single +Concurrent::ThreadPoolExecutor+ + # that uses the +async_query_concurrency+ for the +max_threads+ value. + # * :multi_thread_pool - Initializes a +Concurrent::ThreadPoolExecutor+ for each + # database connection. The initializer values are defined in the configuration hash. + singleton_class.attr_accessor :async_query_executor + self.async_query_executor = nil + + def self.global_thread_pool_async_query_executor # :nodoc: + concurrency = global_executor_concurrency || 4 + @global_thread_pool_async_query_executor ||= Concurrent::ThreadPoolExecutor.new( + min_threads: 0, + max_threads: concurrency, + max_queue: concurrency * 4, + fallback_policy: :caller_runs + ) + end + + # Set the +global_executor_concurrency+. This configuration value can only be used + # with the global thread pool async query executor. + def self.global_executor_concurrency=(global_executor_concurrency) + if self.async_query_executor.nil? || self.async_query_executor == :multi_thread_pool + raise ArgumentError, "`global_executor_concurrency` cannot be set when the executor is nil or set to `:multi_thread_pool`. For multiple thread pools, please set the concurrency in your database configuration." + end + + @global_executor_concurrency = global_executor_concurrency + end + + def self.global_executor_concurrency # :nodoc: + @global_executor_concurrency ||= nil + end + + @permanent_connection_checkout = true + singleton_class.attr_reader :permanent_connection_checkout + + # Defines whether +ActiveRecord::Base.connection+ is allowed, deprecated, or entirely disallowed. + def self.permanent_connection_checkout=(value) + unless [true, :deprecated, :disallowed].include?(value) + raise ArgumentError, "permanent_connection_checkout must be one of: `true`, `:deprecated` or `:disallowed`" + end + @permanent_connection_checkout = value + end + + singleton_class.attr_accessor :index_nested_attribute_errors + self.index_nested_attribute_errors = false + + ## + # :singleton-method: verbose_query_logs + # + # Specifies if the methods calling database queries should be logged below + # their relevant queries. Defaults to false. + singleton_class.attr_accessor :verbose_query_logs + self.verbose_query_logs = false + + ## + # :singleton-method: queues + # + # Specifies the names of the queues used by background jobs. + singleton_class.attr_accessor :queues + self.queues = {} + + singleton_class.attr_accessor :maintain_test_schema + self.maintain_test_schema = nil + + singleton_class.attr_accessor :raise_on_assign_to_attr_readonly + self.raise_on_assign_to_attr_readonly = false + + singleton_class.attr_accessor :belongs_to_required_validates_foreign_key + self.belongs_to_required_validates_foreign_key = true + + singleton_class.attr_accessor :before_committed_on_all_records + self.before_committed_on_all_records = false + + singleton_class.attr_accessor :run_after_transaction_callbacks_in_order_defined + self.run_after_transaction_callbacks_in_order_defined = false + + singleton_class.attr_accessor :application_record_class + self.application_record_class = nil + + ## + # :singleton-method: action_on_strict_loading_violation + # Set the application to log or raise when an association violates strict loading. + # Defaults to :raise. + singleton_class.attr_accessor :action_on_strict_loading_violation + self.action_on_strict_loading_violation = :raise + + ## + # :singleton-method: schema_format + # Specifies the format to use when dumping the database schema with Rails' + # Rakefile. If :sql, the schema is dumped as (potentially database- + # specific) SQL statements. If :ruby, the schema is dumped as an + # ActiveRecord::Schema file which can be loaded into any database that + # supports migrations. Use :ruby if you want to have different database + # adapters for, e.g., your development and test environments. + singleton_class.attr_accessor :schema_format + self.schema_format = :ruby + + ## + # :singleton-method: error_on_ignored_order + # Specifies if an error should be raised if the query has an order being + # ignored when doing batch queries. Useful in applications where the + # scope being ignored is error-worthy, rather than a warning. + singleton_class.attr_accessor :error_on_ignored_order + self.error_on_ignored_order = false + + ## + # :singleton-method: timestamped_migrations + # Specify whether or not to use timestamps for migration versions + singleton_class.attr_accessor :timestamped_migrations + self.timestamped_migrations = true + + ## + # :singleton-method: validate_migration_timestamps + # Specify whether or not to validate migration timestamps. When set, an error + # will be raised if a timestamp is more than a day ahead of the timestamp + # associated with the current time. +timestamped_migrations+ must be set to true. + singleton_class.attr_accessor :validate_migration_timestamps + self.validate_migration_timestamps = false + + ## + # :singleton-method: migration_strategy + # Specify strategy to use for executing migrations. + singleton_class.attr_accessor :migration_strategy + self.migration_strategy = Migration::DefaultStrategy + + ## + # :singleton-method: dump_schema_after_migration + # Specify whether schema dump should happen at the end of the + # bin/rails db:migrate command. This is true by default, which is useful for the + # development environment. This should ideally be false in the production + # environment where dumping schema is rarely needed. + singleton_class.attr_accessor :dump_schema_after_migration + self.dump_schema_after_migration = true + + ## + # :singleton-method: dump_schemas + # Specifies which database schemas to dump when calling db:schema:dump. + # If the value is :schema_search_path (the default), any schemas listed in + # schema_search_path are dumped. Use :all to dump all schemas regardless + # of schema_search_path, or a string of comma separated schemas for a + # custom list. + singleton_class.attr_accessor :dump_schemas + self.dump_schemas = :schema_search_path + + ## + # :singleton-method: verify_foreign_keys_for_fixtures + # If true, Rails will verify all foreign keys in the database after loading fixtures. + # An error will be raised if there are any foreign key violations, indicating incorrectly + # written fixtures. + # Supported by PostgreSQL and SQLite. + singleton_class.attr_accessor :verify_foreign_keys_for_fixtures + self.verify_foreign_keys_for_fixtures = false + + singleton_class.attr_accessor :query_transformers + self.query_transformers = [] + + ## + # :singleton-method: use_yaml_unsafe_load + # Application configurable boolean that instructs the YAML Coder to use + # an unsafe load if set to true. + singleton_class.attr_accessor :use_yaml_unsafe_load + self.use_yaml_unsafe_load = false + + ## + # :singleton-method: raise_int_wider_than_64bit + # Application configurable boolean that denotes whether or not to raise + # an exception when the PostgreSQLAdapter is provided with an integer that + # is wider than signed 64bit representation + singleton_class.attr_accessor :raise_int_wider_than_64bit + self.raise_int_wider_than_64bit = true + + ## + # :singleton-method: yaml_column_permitted_classes + # Application configurable array that provides additional permitted classes + # to Psych safe_load in the YAML Coder + singleton_class.attr_accessor :yaml_column_permitted_classes + self.yaml_column_permitted_classes = [Symbol] + + ## + # :singleton-method: generate_secure_token_on + # Controls when to generate a value for has_secure_token + # declarations. Defaults to :create. + singleton_class.attr_accessor :generate_secure_token_on + self.generate_secure_token_on = :create + + def self.marshalling_format_version + Marshalling.format_version + end + + def self.marshalling_format_version=(value) + Marshalling.format_version = value + end + + ## + # :singleton-method: protocol_adapters + # Provides a mapping between database protocols/DBMSs and the + # underlying database adapter to be used. This is used only by the + # DATABASE_URL environment variable. + # + # == Example + # + # DATABASE_URL="mysql://myuser:mypass@localhost/somedatabase" + # + # The above URL specifies that MySQL is the desired protocol/DBMS, and the + # application configuration can then decide which adapter to use. For this example + # the default mapping is from mysql to mysql2, but :trilogy + # is also supported. + # + # ActiveRecord.protocol_adapters.mysql = "mysql2" + # + # The protocols names are arbitrary, and external database adapters can be + # registered and set here. + singleton_class.attr_accessor :protocol_adapters + self.protocol_adapters = ActiveSupport::InheritableOptions.new( + { + sqlite: "sqlite3", + mysql: "mysql2", + postgres: "postgresql", + } + ) + + def self.eager_load! + super + ActiveRecord::Locking.eager_load! + ActiveRecord::Scoping.eager_load! + ActiveRecord::Associations.eager_load! + ActiveRecord::AttributeMethods.eager_load! + ActiveRecord::ConnectionAdapters.eager_load! + ActiveRecord::Encryption.eager_load! + end + + # Explicitly closes all database connections in all pools. + def self.disconnect_all! + ConnectionAdapters::PoolConfig.disconnect_all! + end + + # Registers a block to be called after all the current transactions have been + # committed. + # + # If there is no currently open transaction, the block is called immediately. + # + # If there are multiple nested transactions, the block is called after the outermost one + # has been committed, + # + # If any of the currently open transactions is rolled back, the block is never called. + # + # If multiple transactions are open across multiple databases, the block will be invoked + # if and once all of them have been committed. But note that nesting transactions across + # two distinct databases is a sharding anti-pattern that comes with a world of hurts. + def self.after_all_transactions_commit(&block) + open_transactions = all_open_transactions + + if open_transactions.empty? + yield + elsif open_transactions.size == 1 + open_transactions.first.after_commit(&block) + else + count = open_transactions.size + callback = -> do + count -= 1 + block.call if count.zero? + end + open_transactions.each do |t| + t.after_commit(&callback) + end + open_transactions = nil # rubocop:disable Lint/UselessAssignment avoid holding it in the closure + end + end + + def self.all_open_transactions # :nodoc: + open_transactions = [] + Base.connection_handler.each_connection_pool do |pool| + if active_connection = pool.active_connection + current_transaction = active_connection.current_transaction + + if current_transaction.open? && current_transaction.joinable? && !current_transaction.state.invalidated? + open_transactions << current_transaction + end + end + end + open_transactions + end +end + +ActiveSupport.on_load(:active_record) do + Arel::Table.engine = self +end + +ActiveSupport.on_load(:i18n) do + I18n.load_path << File.expand_path("active_record/locale/en.yml", __dir__) +end + +YAML.load_tags["!ruby/object:ActiveRecord::AttributeSet"] = "ActiveModel::AttributeSet" +YAML.load_tags["!ruby/object:ActiveRecord::Attribute::FromDatabase"] = "ActiveModel::Attribute::FromDatabase" +YAML.load_tags["!ruby/object:ActiveRecord::LazyAttributeHash"] = "ActiveModel::LazyAttributeHash" +YAML.load_tags["!ruby/object:ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlString"] = "ActiveRecord::Type::String" diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/aggregations.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/aggregations.rb new file mode 100644 index 00000000..64f91f3b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/aggregations.rb @@ -0,0 +1,287 @@ +# frozen_string_literal: true + +module ActiveRecord + # See ActiveRecord::Aggregations::ClassMethods for documentation + module Aggregations + def initialize_dup(*) # :nodoc: + @aggregation_cache = @aggregation_cache.dup + super + end + + def reload(*) # :nodoc: + clear_aggregation_cache + super + end + + private + def clear_aggregation_cache + @aggregation_cache.clear if persisted? + end + + def init_internals + super + @aggregation_cache = {} + end + + # = Active Record \Aggregations + # + # Active Record implements aggregation through a macro-like class method called #composed_of + # for representing attributes as value objects. It expresses relationships like "Account [is] + # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call + # to the macro adds a description of how the value objects are created from the attributes of + # the entity object (when the entity is initialized either as a new object or from finding an + # existing object) and how it can be turned back into attributes (when the entity is saved to + # the database). + # + # class Customer < ActiveRecord::Base + # composed_of :balance, class_name: "Money", mapping: { balance: :amount } + # composed_of :address, mapping: { address_street: :street, address_city: :city } + # end + # + # The customer class now has the following methods to manipulate the value objects: + # * Customer#balance, Customer#balance=(money) + # * Customer#address, Customer#address=(address) + # + # These methods will operate with value objects like the ones described below: + # + # class Money + # include Comparable + # attr_reader :amount, :currency + # EXCHANGE_RATES = { "USD_TO_DKK" => 6 } + # + # def initialize(amount, currency = "USD") + # @amount, @currency = amount, currency + # end + # + # def exchange_to(other_currency) + # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor + # Money.new(exchanged_amount, other_currency) + # end + # + # def ==(other_money) + # amount == other_money.amount && currency == other_money.currency + # end + # + # def <=>(other_money) + # if currency == other_money.currency + # amount <=> other_money.amount + # else + # amount <=> other_money.exchange_to(currency).amount + # end + # end + # end + # + # class Address + # attr_reader :street, :city + # def initialize(street, city) + # @street, @city = street, city + # end + # + # def close_to?(other_address) + # city == other_address.city + # end + # + # def ==(other_address) + # city == other_address.city && street == other_address.street + # end + # end + # + # Now it's possible to access attributes from the database through the value objects instead. If + # you choose to name the composition the same as the attribute's name, it will be the only way to + # access that attribute. That's the case with our +balance+ attribute. You interact with the value + # objects just like you would with any other attribute: + # + # customer.balance = Money.new(20) # sets the Money value object and the attribute + # customer.balance # => Money value object + # customer.balance.exchange_to("DKK") # => Money.new(120, "DKK") + # customer.balance > Money.new(10) # => true + # customer.balance == Money.new(20) # => true + # customer.balance < Money.new(5) # => false + # + # Value objects can also be composed of multiple attributes, such as the case of Address. The order + # of the mappings will determine the order of the parameters. + # + # customer.address_street = "Hyancintvej" + # customer.address_city = "Copenhagen" + # customer.address # => Address.new("Hyancintvej", "Copenhagen") + # + # customer.address = Address.new("May Street", "Chicago") + # customer.address_street # => "May Street" + # customer.address_city # => "Chicago" + # + # == Writing value objects + # + # Value objects are immutable and interchangeable objects that represent a given value, such as + # a Money object representing $5. Two Money objects both representing $5 should be equal (through + # methods such as == and <=> from Comparable if ranking makes sense). This is + # unlike entity objects where equality is determined by identity. An entity class such as Customer can + # easily have two different objects that both have an address on Hyancintvej. Entity identity is + # determined by object or relational unique identifiers (such as primary keys). Normal + # ActiveRecord::Base classes are entity objects. + # + # It's also important to treat the value objects as immutable. Don't allow the Money object to have + # its amount changed after creation. Create a new Money object with the new value instead. The + # Money#exchange_to method is an example of this. It returns a new value object instead of changing + # its own values. Active Record won't persist value objects that have been changed through means + # other than the writer method. + # + # The immutable requirement is enforced by Active Record by freezing any object assigned as a value + # object. Attempting to change it afterwards will result in a +RuntimeError+. + # + # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not + # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable + # + # == Custom constructors and converters + # + # By default value objects are initialized by calling the new constructor of the value + # class passing each of the mapped attributes, in the order specified by the :mapping + # option, as arguments. If the value class doesn't support this convention then #composed_of allows + # a custom constructor to be specified. + # + # When a new value is assigned to the value object, the default assumption is that the new value + # is an instance of the value class. Specifying a custom converter allows the new value to be automatically + # converted to an instance of value class if necessary. + # + # For example, the +NetworkResource+ model has +network_address+ and +cidr_range+ attributes that should be + # aggregated using the +NetAddr::CIDR+ value class (https://www.rubydoc.info/gems/netaddr/1.5.0/NetAddr/CIDR). + # The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter. + # New values can be assigned to the value object using either another +NetAddr::CIDR+ object, a string + # or an array. The :constructor and :converter options can be used to meet + # these requirements: + # + # class NetworkResource < ActiveRecord::Base + # composed_of :cidr, + # class_name: 'NetAddr::CIDR', + # mapping: { network_address: :network, cidr_range: :bits }, + # allow_nil: true, + # constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") }, + # converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) } + # end + # + # # This calls the :constructor + # network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24) + # + # # These assignments will both use the :converter + # network_resource.cidr = [ '192.168.2.1', 8 ] + # network_resource.cidr = '192.168.0.1/24' + # + # # This assignment won't use the :converter as the value is already an instance of the value class + # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8') + # + # # Saving and then reloading will use the :constructor on reload + # network_resource.save + # network_resource.reload + # + # == Finding records by a value object + # + # Once a #composed_of relationship is specified for a model, records can be loaded from the database + # by specifying an instance of the value object in the conditions hash. The following example + # finds all customers with +address_street+ equal to "May Street" and +address_city+ equal to "Chicago": + # + # Customer.where(address: Address.new("May Street", "Chicago")) + # + module ClassMethods + # Adds reader and writer methods for manipulating a value object: + # composed_of :address adds address and address=(new_address) methods. + # + # Options are: + # * :class_name - Specifies the class name of the association. Use it only if that name + # can't be inferred from the part id. So composed_of :address will by default be linked + # to the Address class, but if the real class name is +CompanyAddress+, you'll have to specify it + # with this option. + # * :mapping - Specifies the mapping of entity attributes to attributes of the value + # object. Each mapping is represented as a key-value pair where the key is the name of the + # entity attribute and the value is the name of the attribute in the value object. The + # order in which mappings are defined determines the order in which attributes are sent to the + # value class constructor. The mapping can be written as a hash or as an array of pairs. + # * :allow_nil - Specifies that the value object will not be instantiated when all mapped + # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all + # mapped attributes. + # This defaults to +false+. + # * :constructor - A symbol specifying the name of the constructor method or a Proc that + # is called to initialize the value object. The constructor is passed all of the mapped attributes, + # in the order that they are defined in the :mapping option, as arguments and uses them + # to instantiate a :class_name object. + # The default is :new. + # * :converter - A symbol specifying the name of a class method of :class_name + # or a Proc that is called when a new value is assigned to the value object. The converter is + # passed the single value that is used in the assignment and is only called if the new value is + # not an instance of :class_name. If :allow_nil is set to true, the converter + # can return +nil+ to skip the assignment. + # + # Option examples: + # composed_of :temperature, mapping: { reading: :celsius } + # composed_of :balance, class_name: "Money", mapping: { balance: :amount } + # composed_of :address, mapping: { address_street: :street, address_city: :city } + # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ] + # composed_of :gps_location + # composed_of :gps_location, allow_nil: true + # composed_of :ip_address, + # class_name: 'IPAddr', + # mapping: { ip: :to_i }, + # constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) }, + # converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) } + # + def composed_of(part_id, options = {}) + options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter) + + unless self < Aggregations + include Aggregations + end + + name = part_id.id2name + class_name = options[:class_name] || name.camelize + mapping = options[:mapping] || [ name, name ] + mapping = [ mapping ] unless mapping.first.is_a?(Array) + allow_nil = options[:allow_nil] || false + constructor = options[:constructor] || :new + converter = options[:converter] + + reader_method(name, class_name, mapping, allow_nil, constructor) + writer_method(name, class_name, mapping, allow_nil, converter) + + reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self) + Reflection.add_aggregate_reflection self, part_id, reflection + end + + private + def reader_method(name, class_name, mapping, allow_nil, constructor) + define_method(name) do + if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? { |key, _| !read_attribute(key).nil? }) + attrs = mapping.collect { |key, _| read_attribute(key) } + object = constructor.respond_to?(:call) ? + constructor.call(*attrs) : + class_name.constantize.send(constructor, *attrs) + @aggregation_cache[name] = object.freeze + end + @aggregation_cache[name] + end + end + + def writer_method(name, class_name, mapping, allow_nil, converter) + define_method("#{name}=") do |part| + klass = class_name.constantize + + unless part.is_a?(klass) || converter.nil? || part.nil? + part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part) + end + + hash_from_multiparameter_assignment = part.is_a?(Hash) && + part.keys.all?(Integer) + if hash_from_multiparameter_assignment + raise ArgumentError unless part.size == part.each_key.max + part = klass.new(*part.sort.map(&:last)) + end + + if part.nil? && allow_nil + mapping.each { |key, _| write_attribute(key, nil) } + @aggregation_cache[name] = nil + else + mapping.each { |key, value| write_attribute(key, part.send(value)) } + @aggregation_cache[name] = part.dup.freeze + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/association_relation.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/association_relation.rb new file mode 100644 index 00000000..a74184a4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/association_relation.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module ActiveRecord + class AssociationRelation < Relation # :nodoc: + def initialize(klass, association, **) + super(klass) + @association = association + end + + def proxy_association + @association + end + + def ==(other) + other == records + end + + %w(insert insert_all insert! insert_all! upsert upsert_all).each do |method| + class_eval <<~RUBY, __FILE__, __LINE__ + 1 + def #{method}(attributes, **kwargs) + if @association.reflection.through_reflection? + raise ArgumentError, "Bulk insert or upsert is currently not supported for has_many through association" + end + + super + end + RUBY + end + + private + def _new(attributes, &block) + @association.build(attributes, &block) + end + + def _create(attributes, &block) + @association.create(attributes, &block) + end + + def _create!(attributes, &block) + @association.create!(attributes, &block) + end + + def exec_queries + super do |record| + @association.set_inverse_instance_from_queries(record) + @association.set_strict_loading(record) + yield record if block_given? + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations.rb new file mode 100644 index 00000000..0d85a063 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations.rb @@ -0,0 +1,1909 @@ +# frozen_string_literal: true + +module ActiveRecord + # See ActiveRecord::Associations::ClassMethods for documentation. + module Associations # :nodoc: + extend ActiveSupport::Autoload + extend ActiveSupport::Concern + + # These classes will be loaded when associations are created. + # So there is no need to eager load them. + autoload :Association + autoload :SingularAssociation + autoload :CollectionAssociation + autoload :ForeignAssociation + autoload :CollectionProxy + autoload :ThroughAssociation + + module Builder # :nodoc: + autoload :Association, "active_record/associations/builder/association" + autoload :SingularAssociation, "active_record/associations/builder/singular_association" + autoload :CollectionAssociation, "active_record/associations/builder/collection_association" + + autoload :BelongsTo, "active_record/associations/builder/belongs_to" + autoload :HasOne, "active_record/associations/builder/has_one" + autoload :HasMany, "active_record/associations/builder/has_many" + autoload :HasAndBelongsToMany, "active_record/associations/builder/has_and_belongs_to_many" + end + + eager_autoload do + autoload :BelongsToAssociation + autoload :BelongsToPolymorphicAssociation + autoload :HasManyAssociation + autoload :HasManyThroughAssociation + autoload :HasOneAssociation + autoload :HasOneThroughAssociation + + autoload :Preloader + autoload :JoinDependency + autoload :AssociationScope + autoload :DisableJoinsAssociationScope + autoload :AliasTracker + end + + def self.eager_load! + super + Preloader.eager_load! + JoinDependency.eager_load! + end + + # Returns the association instance for the given name, instantiating it if it doesn't already exist + def association(name) # :nodoc: + association = association_instance_get(name) + + if association.nil? + unless reflection = self.class._reflect_on_association(name) + raise AssociationNotFoundError.new(self, name) + end + association = reflection.association_class.new(self, reflection) + association_instance_set(name, association) + end + + association + end + + def association_cached?(name) # :nodoc: + @association_cache.key?(name) + end + + def initialize_dup(*) # :nodoc: + @association_cache = {} + super + end + + private + def init_internals + super + @association_cache = {} + end + + # Returns the specified association instance if it exists, +nil+ otherwise. + def association_instance_get(name) + @association_cache[name] + end + + # Set the specified association instance. + def association_instance_set(name, association) + @association_cache[name] = association + end + + # = Active Record \Associations + # + # \Associations are a set of macro-like class methods for tying objects together through + # foreign keys. They express relationships like "Project has one Project Manager" + # or "Project belongs to a Portfolio". Each macro adds a number of methods to the + # class which are specialized according to the collection or association symbol and the + # options hash. It works much the same way as Ruby's own attr* + # methods. + # + # class Project < ActiveRecord::Base + # belongs_to :portfolio + # has_one :project_manager + # has_many :milestones + # has_and_belongs_to_many :categories + # end + # + # The project class now has the following methods (and more) to ease the traversal and + # manipulation of its relationships: + # + # project = Project.first + # project.portfolio + # project.portfolio = Portfolio.first + # project.reload_portfolio + # + # project.project_manager + # project.project_manager = ProjectManager.first + # project.reload_project_manager + # + # project.milestones.empty? + # project.milestones.size + # project.milestones + # project.milestones << Milestone.first + # project.milestones.delete(Milestone.first) + # project.milestones.destroy(Milestone.first) + # project.milestones.find(Milestone.first.id) + # project.milestones.build + # project.milestones.create + # + # project.categories.empty? + # project.categories.size + # project.categories + # project.categories << Category.first + # project.categories.delete(category1) + # project.categories.destroy(category1) + # + # === A word of warning + # + # Don't create associations that have the same name as {instance methods}[rdoc-ref:ActiveRecord::Core] of + # +ActiveRecord::Base+. Since the association adds a method with that name to + # its model, using an association with the same name as one provided by +ActiveRecord::Base+ will override the method inherited through +ActiveRecord::Base+ and will break things. + # For instance, +attributes+ and +connection+ would be bad choices for association names, because those names already exist in the list of +ActiveRecord::Base+ instance methods. + # + # == Auto-generated methods + # See also "Instance Public methods" below ( from #belongs_to ) for more details. + # + # === Singular associations (one-to-one) + # | | belongs_to | + # generated methods | belongs_to | :polymorphic | has_one + # ----------------------------------+------------+--------------+--------- + # other | X | X | X + # other=(other) | X | X | X + # build_other(attributes={}) | X | | X + # create_other(attributes={}) | X | | X + # create_other!(attributes={}) | X | | X + # reload_other | X | X | X + # other_changed? | X | X | + # other_previously_changed? | X | X | + # + # === Collection associations (one-to-many / many-to-many) + # | | | has_many + # generated methods | habtm | has_many | :through + # ----------------------------------+-------+----------+---------- + # others | X | X | X + # others=(other,other,...) | X | X | X + # other_ids | X | X | X + # other_ids=(id,id,...) | X | X | X + # others<< | X | X | X + # others.push | X | X | X + # others.concat | X | X | X + # others.build(attributes={}) | X | X | X + # others.create(attributes={}) | X | X | X + # others.create!(attributes={}) | X | X | X + # others.size | X | X | X + # others.length | X | X | X + # others.count | X | X | X + # others.sum(*args) | X | X | X + # others.empty? | X | X | X + # others.clear | X | X | X + # others.delete(other,other,...) | X | X | X + # others.delete_all | X | X | X + # others.destroy(other,other,...) | X | X | X + # others.destroy_all | X | X | X + # others.find(*args) | X | X | X + # others.exists? | X | X | X + # others.distinct | X | X | X + # others.reset | X | X | X + # others.reload | X | X | X + # + # === Overriding generated methods + # + # Association methods are generated in a module included into the model + # class, making overrides easy. The original generated method can thus be + # called with +super+: + # + # class Car < ActiveRecord::Base + # belongs_to :owner + # belongs_to :old_owner + # + # def owner=(new_owner) + # self.old_owner = self.owner + # super + # end + # end + # + # The association methods module is included immediately after the + # generated attributes methods module, meaning an association will + # override the methods for an attribute with the same name. + # + # == Cardinality and associations + # + # Active Record associations can be used to describe one-to-one, one-to-many, and many-to-many + # relationships between models. Each model uses an association to describe its role in + # the relation. The #belongs_to association is always used in the model that has + # the foreign key. + # + # === One-to-one + # + # Use #has_one in the base, and #belongs_to in the associated model. + # + # class Employee < ActiveRecord::Base + # has_one :office + # end + # class Office < ActiveRecord::Base + # belongs_to :employee # foreign key - employee_id + # end + # + # === One-to-many + # + # Use #has_many in the base, and #belongs_to in the associated model. + # + # class Manager < ActiveRecord::Base + # has_many :employees + # end + # class Employee < ActiveRecord::Base + # belongs_to :manager # foreign key - manager_id + # end + # + # === Many-to-many + # + # There are two ways to build a many-to-many relationship. + # + # The first way uses a #has_many association with the :through option and a join model, so + # there are two stages of associations. + # + # class Assignment < ActiveRecord::Base + # belongs_to :programmer # foreign key - programmer_id + # belongs_to :project # foreign key - project_id + # end + # class Programmer < ActiveRecord::Base + # has_many :assignments + # has_many :projects, through: :assignments + # end + # class Project < ActiveRecord::Base + # has_many :assignments + # has_many :programmers, through: :assignments + # end + # + # For the second way, use #has_and_belongs_to_many in both models. This requires a join table + # that has no corresponding model or primary key. + # + # class Programmer < ActiveRecord::Base + # has_and_belongs_to_many :projects # foreign keys in the join table + # end + # class Project < ActiveRecord::Base + # has_and_belongs_to_many :programmers # foreign keys in the join table + # end + # + # Choosing which way to build a many-to-many relationship is not always simple. + # If you need to work with the relationship model as its own entity, + # use #has_many :through. Use #has_and_belongs_to_many when working with legacy schemas or when + # you never work directly with the relationship itself. + # + # == Is it a #belongs_to or #has_one association? + # + # Both express a 1-1 relationship. The difference is mostly where to place the foreign + # key, which goes on the table for the class declaring the #belongs_to relationship. + # + # class User < ActiveRecord::Base + # # I reference an account. + # belongs_to :account + # end + # + # class Account < ActiveRecord::Base + # # One user references me. + # has_one :user + # end + # + # The tables for these classes could look something like: + # + # CREATE TABLE users ( + # id bigint NOT NULL auto_increment, + # account_id bigint default NULL, + # name varchar default NULL, + # PRIMARY KEY (id) + # ) + # + # CREATE TABLE accounts ( + # id bigint NOT NULL auto_increment, + # name varchar default NULL, + # PRIMARY KEY (id) + # ) + # + # == Unsaved objects and associations + # + # You can manipulate objects and associations before they are saved to the database, but + # there is some special behavior you should be aware of, mostly involving the saving of + # associated objects. + # + # You can set the :autosave option on a #has_one, #belongs_to, + # #has_many, or #has_and_belongs_to_many association. Setting it + # to +true+ will _always_ save the members, whereas setting it to +false+ will + # _never_ save the members. More details about :autosave option is available at + # AutosaveAssociation. + # + # === One-to-one associations + # + # * Assigning an object to a #has_one association automatically saves that object and + # the object being replaced (if there is one), in order to update their foreign + # keys - except if the parent object is unsaved (new_record? == true). + # * If either of these saves fail (due to one of the objects being invalid), an + # ActiveRecord::RecordNotSaved exception is raised and the assignment is + # cancelled. + # * If you wish to assign an object to a #has_one association without saving it, + # use the #build_association method (documented below). The object being + # replaced will still be saved to update its foreign key. + # * Assigning an object to a #belongs_to association does not save the object, since + # the foreign key field belongs on the parent. It does not save the parent either. + # + # === Collections + # + # * Adding an object to a collection (#has_many or #has_and_belongs_to_many) automatically + # saves that object, except if the parent object (the owner of the collection) is not yet + # stored in the database. + # * If saving any of the objects being added to a collection (via push or similar) + # fails, then push returns +false+. + # * If saving fails while replacing the collection (via association=), an + # ActiveRecord::RecordNotSaved exception is raised and the assignment is + # cancelled. + # * You can add an object to a collection without automatically saving it by using the + # collection.build method (documented below). + # * All unsaved (new_record? == true) members of the collection are automatically + # saved when the parent is saved. + # + # == Customizing the query + # + # \Associations are built from Relation objects, and you can use the Relation syntax + # to customize them. For example, to add a condition: + # + # class Blog < ActiveRecord::Base + # has_many :published_posts, -> { where(published: true) }, class_name: 'Post' + # end + # + # Inside the -> { ... } block you can use all of the usual Relation methods. + # + # === Accessing the owner object + # + # Sometimes it is useful to have access to the owner object when building the query. The owner + # is passed as a parameter to the block. For example, the following association would find all + # events that occur on the user's birthday: + # + # class User < ActiveRecord::Base + # has_many :birthday_events, ->(user) { where(starts_on: user.birthday) }, class_name: 'Event' + # end + # + # Note: Joining or eager loading such associations is not possible because + # those operations happen before instance creation. Such associations + # _can_ be preloaded, but doing so will perform N+1 queries because there + # will be a different scope for each record (similar to preloading + # polymorphic scopes). + # + # == Association callbacks + # + # Similar to the normal callbacks that hook into the life cycle of an Active Record object, + # you can also define callbacks that get triggered when you add an object to or remove an + # object from an association collection. + # + # class Firm < ActiveRecord::Base + # has_many :clients, + # dependent: :destroy, + # after_add: :congratulate_client, + # after_remove: :log_after_remove + # + # def congratulate_client(client) + # # ... + # end + # + # def log_after_remove(client) + # # ... + # end + # end + # + # Callbacks can be defined in three ways: + # + # 1. A symbol that references a method defined on the class with the + # associated collection. For example, after_add: :congratulate_client + # invokes Firm#congratulate_client(client). + # 2. A callable with a signature that accepts both the record with the + # associated collection and the record being added or removed. For + # example, after_add: ->(firm, client) { ... }. + # 3. An object that responds to the callback name. For example, passing + # after_add: CallbackObject.new invokes CallbackObject#after_add(firm, + # client). + # + # It's possible to stack callbacks by passing them as an array. Example: + # + # class CallbackObject + # def after_add(firm, client) + # firm.log << "after_adding #{client.id}" + # end + # end + # + # class Firm < ActiveRecord::Base + # has_many :clients, + # dependent: :destroy, + # after_add: [ + # :congratulate_client, + # -> (firm, client) { firm.log << "after_adding #{client.id}" }, + # CallbackObject.new + # ], + # after_remove: :log_after_remove + # end + # + # Possible callbacks are: +before_add+, +after_add+, +before_remove+, and +after_remove+. + # + # If any of the +before_add+ callbacks throw an exception, the object will not be + # added to the collection. + # + # Similarly, if any of the +before_remove+ callbacks throw an exception, the object + # will not be removed from the collection. + # + # Note: To trigger remove callbacks, you must use +destroy+ / +destroy_all+ methods. For example: + # + # * firm.clients.destroy(client) + # * firm.clients.destroy(*clients) + # * firm.clients.destroy_all + # + # +delete+ / +delete_all+ methods like the following do *not* trigger remove callbacks: + # + # * firm.clients.delete(client) + # * firm.clients.delete(*clients) + # * firm.clients.delete_all + # + # == Association extensions + # + # The proxy objects that control the access to associations can be extended through anonymous + # modules. This is especially beneficial for adding new finders, creators, and other + # factory-type methods that are only used as part of this association. + # + # class Account < ActiveRecord::Base + # has_many :people do + # def find_or_create_by_name(name) + # first_name, last_name = name.split(" ", 2) + # find_or_create_by(first_name: first_name, last_name: last_name) + # end + # end + # end + # + # person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson") + # person.first_name # => "David" + # person.last_name # => "Heinemeier Hansson" + # + # If you need to share the same extensions between many associations, you can use a named + # extension module. + # + # module FindOrCreateByNameExtension + # def find_or_create_by_name(name) + # first_name, last_name = name.split(" ", 2) + # find_or_create_by(first_name: first_name, last_name: last_name) + # end + # end + # + # class Account < ActiveRecord::Base + # has_many :people, -> { extending FindOrCreateByNameExtension } + # end + # + # class Company < ActiveRecord::Base + # has_many :people, -> { extending FindOrCreateByNameExtension } + # end + # + # Some extensions can only be made to work with knowledge of the association's internals. + # Extensions can access relevant state using the following methods (where +items+ is the + # name of the association): + # + # * record.association(:items).owner - Returns the object the association is part of. + # * record.association(:items).reflection - Returns the reflection object that describes the association. + # * record.association(:items).target - Returns the associated object for #belongs_to and #has_one, or + # the collection of associated objects for #has_many and #has_and_belongs_to_many. + # + # However, inside the actual extension code, you will not have access to the record as + # above. In this case, you can access proxy_association. For example, + # record.association(:items) and record.items.proxy_association will return + # the same object, allowing you to make calls like proxy_association.owner inside + # association extensions. + # + # == Association Join Models + # + # Has Many associations can be configured with the :through option to use an + # explicit join model to retrieve the data. This operates similarly to a + # #has_and_belongs_to_many association. The advantage is that you're able to add validations, + # callbacks, and extra attributes on the join model. Consider the following schema: + # + # class Author < ActiveRecord::Base + # has_many :authorships + # has_many :books, through: :authorships + # end + # + # class Authorship < ActiveRecord::Base + # belongs_to :author + # belongs_to :book + # end + # + # @author = Author.first + # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to + # @author.books # selects all books by using the Authorship join model + # + # You can also go through a #has_many association on the join model: + # + # class Firm < ActiveRecord::Base + # has_many :clients + # has_many :invoices, through: :clients + # end + # + # class Client < ActiveRecord::Base + # belongs_to :firm + # has_many :invoices + # end + # + # class Invoice < ActiveRecord::Base + # belongs_to :client + # end + # + # @firm = Firm.first + # @firm.clients.flat_map { |c| c.invoices } # select all invoices for all clients of the firm + # @firm.invoices # selects all invoices by going through the Client join model + # + # Similarly you can go through a #has_one association on the join model: + # + # class Group < ActiveRecord::Base + # has_many :users + # has_many :avatars, through: :users + # end + # + # class User < ActiveRecord::Base + # belongs_to :group + # has_one :avatar + # end + # + # class Avatar < ActiveRecord::Base + # belongs_to :user + # end + # + # @group = Group.first + # @group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group + # @group.avatars # selects all avatars by going through the User join model. + # + # An important caveat with going through #has_one or #has_many associations on the + # join model is that these associations are *read-only*. For example, the following + # would not work following the previous example: + # + # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around + # @group.avatars.delete(@group.avatars.last) # so would this + # + # === Setting Inverses + # + # If you are using a #belongs_to on the join model, it is a good idea to set the + # :inverse_of option on the #belongs_to, which will mean that the following example + # works correctly (where tags is a #has_many :through association): + # + # @post = Post.first + # @tag = @post.tags.build name: "ruby" + # @tag.save + # + # The last line ought to save the through record (a Tagging). This will only work if the + # :inverse_of is set: + # + # class Tagging < ActiveRecord::Base + # belongs_to :post + # belongs_to :tag, inverse_of: :taggings + # end + # + # If you do not set the :inverse_of record, the association will + # do its best to match itself up with the correct inverse. Automatic + # inverse detection only works on #has_many, #has_one, and + # #belongs_to associations. + # + # :foreign_key and :through options on the associations + # will also prevent the association's inverse from being found automatically, + # as will a custom scopes in some cases. See further details in the + # {Active Record Associations guide}[https://guides.rubyonrails.org/association_basics.html#bi-directional-associations]. + # + # The automatic guessing of the inverse association uses a heuristic based + # on the name of the class, so it may not work for all associations, + # especially the ones with non-standard names. + # + # You can turn off the automatic detection of inverse associations by setting + # the :inverse_of option to false like so: + # + # class Tagging < ActiveRecord::Base + # belongs_to :tag, inverse_of: false + # end + # + # == Nested \Associations + # + # You can actually specify *any* association with the :through option, including an + # association which has a :through option itself. For example: + # + # class Author < ActiveRecord::Base + # has_many :posts + # has_many :comments, through: :posts + # has_many :commenters, through: :comments + # end + # + # class Post < ActiveRecord::Base + # has_many :comments + # end + # + # class Comment < ActiveRecord::Base + # belongs_to :commenter + # end + # + # @author = Author.first + # @author.commenters # => People who commented on posts written by the author + # + # An equivalent way of setting up this association this would be: + # + # class Author < ActiveRecord::Base + # has_many :posts + # has_many :commenters, through: :posts + # end + # + # class Post < ActiveRecord::Base + # has_many :comments + # has_many :commenters, through: :comments + # end + # + # class Comment < ActiveRecord::Base + # belongs_to :commenter + # end + # + # When using a nested association, you will not be able to modify the association because there + # is not enough information to know what modification to make. For example, if you tried to + # add a Commenter in the example above, there would be no way to tell how to set up the + # intermediate Post and Comment objects. + # + # == Polymorphic \Associations + # + # Polymorphic associations on models are not restricted on what types of models they + # can be associated with. Rather, they specify an interface that a #has_many association + # must adhere to. + # + # class Asset < ActiveRecord::Base + # belongs_to :attachable, polymorphic: true + # end + # + # class Post < ActiveRecord::Base + # has_many :assets, as: :attachable # The :as option specifies the polymorphic interface to use. + # end + # + # @asset.attachable = @post + # + # This works by using a type column in addition to a foreign key to specify the associated + # record. In the Asset example, you'd need an +attachable_id+ integer column and an + # +attachable_type+ string column. + # + # Using polymorphic associations in combination with single table inheritance (STI) is + # a little tricky. In order for the associations to work as expected, ensure that you + # store the base model for the STI models in the type column of the polymorphic + # association. To continue with the asset example above, suppose there are guest posts + # and member posts that use the posts table for STI. In this case, there must be a +type+ + # column in the posts table. + # + # Note: The attachable_type= method is being called when assigning an +attachable+. + # The +class_name+ of the +attachable+ is passed as a String. + # + # class Asset < ActiveRecord::Base + # belongs_to :attachable, polymorphic: true + # + # def attachable_type=(class_name) + # super(class_name.constantize.base_class.to_s) + # end + # end + # + # class Post < ActiveRecord::Base + # # because we store "Post" in attachable_type now dependent: :destroy will work + # has_many :assets, as: :attachable, dependent: :destroy + # end + # + # class GuestPost < Post + # end + # + # class MemberPost < Post + # end + # + # == Caching + # + # All of the methods are built on a simple caching principle that will keep the result + # of the last query around unless specifically instructed not to. The cache is even + # shared across methods to make it even cheaper to use the macro-added methods without + # worrying too much about performance at the first go. + # + # project.milestones # fetches milestones from the database + # project.milestones.size # uses the milestone cache + # project.milestones.empty? # uses the milestone cache + # project.milestones.reload.size # fetches milestones from the database + # project.milestones # uses the milestone cache + # + # == Eager loading of associations + # + # Eager loading is a way to find objects of a certain class and a number of named associations. + # It is one of the easiest ways to prevent the dreaded N+1 problem in which fetching 100 + # posts that each need to display their author triggers 101 database queries. Through the + # use of eager loading, the number of queries will be reduced from 101 to 2. + # + # class Post < ActiveRecord::Base + # belongs_to :author + # has_many :comments + # end + # + # Consider the following loop using the class above: + # + # Post.all.each do |post| + # puts "Post: " + post.title + # puts "Written by: " + post.author.name + # puts "Last comment on: " + post.comments.first.created_on + # end + # + # To iterate over these one hundred posts, we'll generate 201 database queries. Let's + # first just optimize it for retrieving the author: + # + # Post.includes(:author).each do |post| + # + # This references the name of the #belongs_to association that also used the :author + # symbol. After loading the posts, +find+ will collect the +author_id+ from each one and load + # all of the referenced authors with one query. Doing so will cut down the number of queries + # from 201 to 102. + # + # We can improve upon the situation further by referencing both associations in the finder with: + # + # Post.includes(:author, :comments).each do |post| + # + # This will load all comments with a single query. This reduces the total number of queries + # to 3. In general, the number of queries will be 1 plus the number of associations + # named (except if some of the associations are polymorphic #belongs_to - see below). + # + # To include a deep hierarchy of associations, use a hash: + # + # Post.includes(:author, { comments: { author: :gravatar } }).each do |post| + # + # The above code will load all the comments and all of their associated + # authors and gravatars. You can mix and match any combination of symbols, + # arrays, and hashes to retrieve the associations you want to load. + # + # All of this power shouldn't fool you into thinking that you can pull out huge amounts + # of data with no performance penalty just because you've reduced the number of queries. + # The database still needs to send all the data to Active Record and it still needs to + # be processed. So it's no catch-all for performance problems, but it's a great way to + # cut down on the number of queries in a situation as the one described above. + # + # Since only one table is loaded at a time, conditions or orders cannot reference tables + # other than the main one. If this is the case, Active Record falls back to the previously + # used LEFT OUTER JOIN based strategy. For example: + # + # Post.includes([:author, :comments]).where(['comments.approved = ?', true]) + # + # This will result in a single SQL query with joins along the lines of: + # LEFT OUTER JOIN comments ON comments.post_id = posts.id and + # LEFT OUTER JOIN authors ON authors.id = posts.author_id. Note that using conditions + # like this can have unintended consequences. + # In the above example, posts with no approved comments are not returned at all because + # the conditions apply to the SQL statement as a whole and not just to the association. + # + # You must disambiguate column references for this fallback to happen, for example + # order: "author.name DESC" will work but order: "name DESC" will not. + # + # If you want to load all posts (including posts with no approved comments), then write + # your own LEFT OUTER JOIN query using ON: + # + # Post.joins("LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = '1'") + # + # In this case, it is usually more natural to include an association which has conditions defined on it: + # + # class Post < ActiveRecord::Base + # has_many :approved_comments, -> { where(approved: true) }, class_name: 'Comment' + # end + # + # Post.includes(:approved_comments) + # + # This will load posts and eager load the +approved_comments+ association, which contains + # only those comments that have been approved. + # + # If you eager load an association with a specified :limit option, it will be ignored, + # returning all the associated objects: + # + # class Picture < ActiveRecord::Base + # has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment' + # end + # + # Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments. + # + # Eager loading is supported with polymorphic associations. + # + # class Address < ActiveRecord::Base + # belongs_to :addressable, polymorphic: true + # end + # + # A call that tries to eager load the addressable model + # + # Address.includes(:addressable) + # + # This will execute one query to load the addresses and load the addressables with one + # query per addressable type. + # For example, if all the addressables are either of class Person or Company, then a total + # of 3 queries will be executed. The list of addressable types to load is determined on + # the back of the addresses loaded. This is not supported if Active Record has to fall back + # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. + # The reason is that the parent model's type is a column value so its corresponding table + # name cannot be put in the +FROM+/+JOIN+ clauses of that query. + # + # == Table Aliasing + # + # Active Record uses table aliasing in the case that a table is referenced multiple times + # in a join. If a table is referenced only once, the standard table name is used. The + # second time, the table is aliased as #{reflection_name}_#{parent_table_name}. + # Indexes are appended for any more successive uses of the table name. + # + # Post.joins(:comments) + # # SELECT ... FROM posts INNER JOIN comments ON ... + # Post.joins(:special_comments) # STI + # # SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment' + # Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name + # # SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts + # + # Acts as tree example: + # + # TreeMixin.joins(:children) + # # SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... + # TreeMixin.joins(children: :parent) + # # SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... + # # INNER JOIN parents_mixins ... + # TreeMixin.joins(children: {parent: :children}) + # # SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... + # # INNER JOIN parents_mixins ... + # # INNER JOIN mixins childrens_mixins_2 + # + # Has and Belongs to Many join tables use the same idea, but add a _join suffix: + # + # Post.joins(:categories) + # # SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... + # Post.joins(categories: :posts) + # # SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... + # # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories + # Post.joins(categories: {posts: :categories}) + # # SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... + # # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories + # # INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2 + # + # If you wish to specify your own custom joins using ActiveRecord::QueryMethods#joins method, those table + # names will take precedence over the eager associations: + # + # Post.joins(:comments).joins("inner join comments ...") + # # SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ... + # Post.joins(:comments, :special_comments).joins("inner join comments ...") + # # SELECT ... FROM posts INNER JOIN comments comments_posts ON ... + # # INNER JOIN comments special_comments_posts ... + # # INNER JOIN comments ... + # + # Table aliases are automatically truncated according to the maximum length of table identifiers + # according to the specific database. + # + # == Modules + # + # By default, associations will look for objects within the current module scope. Consider: + # + # module MyApplication + # module Business + # class Firm < ActiveRecord::Base + # has_many :clients + # end + # + # class Client < ActiveRecord::Base; end + # end + # end + # + # When Firm#clients is called, it will in turn call + # MyApplication::Business::Client.find_all_by_firm_id(firm.id). + # If you want to associate with a class in another module scope, this can be done by + # specifying the complete class name. + # + # module MyApplication + # module Business + # class Firm < ActiveRecord::Base; end + # end + # + # module Billing + # class Account < ActiveRecord::Base + # belongs_to :firm, class_name: "MyApplication::Business::Firm" + # end + # end + # end + # + # == Bi-directional associations + # + # When you specify an association, there is usually an association on the associated model + # that specifies the same relationship in reverse. For example, with the following models: + # + # class Dungeon < ActiveRecord::Base + # has_many :traps + # has_one :evil_wizard + # end + # + # class Trap < ActiveRecord::Base + # belongs_to :dungeon + # end + # + # class EvilWizard < ActiveRecord::Base + # belongs_to :dungeon + # end + # + # The +traps+ association on +Dungeon+ and the +dungeon+ association on +Trap+ are + # the inverse of each other, and the inverse of the +dungeon+ association on +EvilWizard+ + # is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default, + # Active Record can guess the inverse of the association based on the name + # of the class. The result is the following: + # + # d = Dungeon.first + # t = d.traps.first + # d.object_id == t.dungeon.object_id # => true + # + # The +Dungeon+ instances +d+ and t.dungeon in the above example refer to + # the same in-memory instance since the association matches the name of the class. + # The result would be the same if we added +:inverse_of+ to our model definitions: + # + # class Dungeon < ActiveRecord::Base + # has_many :traps, inverse_of: :dungeon + # has_one :evil_wizard, inverse_of: :dungeon + # end + # + # class Trap < ActiveRecord::Base + # belongs_to :dungeon, inverse_of: :traps + # end + # + # class EvilWizard < ActiveRecord::Base + # belongs_to :dungeon, inverse_of: :evil_wizard + # end + # + # For more information, see the documentation for the +:inverse_of+ option and the + # {Active Record Associations guide}[https://guides.rubyonrails.org/association_basics.html#bi-directional-associations]. + # + # == Deleting from associations + # + # === Dependent associations + # + # #has_many, #has_one, and #belongs_to associations support the :dependent option. + # This allows you to specify that associated records should be deleted when the owner is + # deleted. + # + # For example: + # + # class Author + # has_many :posts, dependent: :destroy + # end + # Author.find(1).destroy # => Will destroy all of the author's posts, too + # + # The :dependent option can have different values which specify how the deletion + # is done. For more information, see the documentation for this option on the different + # specific association types. When no option is given, the behavior is to do nothing + # with the associated records when destroying a record. + # + # Note that :dependent is implemented using \Rails' callback + # system, which works by processing callbacks in order. Therefore, other + # callbacks declared either before or after the :dependent option + # can affect what it does. + # + # Note that :dependent option is ignored for #has_one :through associations. + # + # === Delete or destroy? + # + # #has_many and #has_and_belongs_to_many associations have the methods destroy, + # delete, destroy_all and delete_all. + # + # For #has_and_belongs_to_many, delete and destroy are the same: they + # cause the records in the join table to be removed. + # + # For #has_many, destroy and destroy_all will always call the destroy method of the + # record(s) being removed so that callbacks are run. However delete and delete_all will either + # do the deletion according to the strategy specified by the :dependent option, or + # if no :dependent option is given, then it will follow the default strategy. + # The default strategy is to do nothing (leave the foreign keys with the parent ids set), except for + # #has_many :through, where the default strategy is delete_all (delete + # the join records, without running their callbacks). + # + # There is also a clear method which is the same as delete_all, except that + # it returns the association rather than the records which have been deleted. + # + # === What gets deleted? + # + # There is a potential pitfall here: #has_and_belongs_to_many and #has_many :through + # associations have records in join tables, as well as the associated records. So when we + # call one of these deletion methods, what exactly should be deleted? + # + # The answer is that it is assumed that deletion on an association is about removing the + # link between the owner and the associated object(s), rather than necessarily the + # associated objects themselves. So with #has_and_belongs_to_many and #has_many + # :through, the join records will be deleted, but the associated records won't. + # + # This makes sense if you think about it: if you were to call post.tags.delete(Tag.find_by(name: 'food')) + # you would want the 'food' tag to be unlinked from the post, rather than for the tag itself + # to be removed from the database. + # + # However, there are examples where this strategy doesn't make sense. For example, suppose + # a person has many projects, and each project has many tasks. If we deleted one of a person's + # tasks, we would probably not want the project to be deleted. In this scenario, the delete method + # won't actually work: it can only be used if the association on the join model is a + # #belongs_to. In other situations you are expected to perform operations directly on + # either the associated records or the :through association. + # + # With a regular #has_many there is no distinction between the "associated records" + # and the "link", so there is only one choice for what gets deleted. + # + # With #has_and_belongs_to_many and #has_many :through, if you want to delete the + # associated records themselves, you can always do something along the lines of + # person.tasks.each(&:destroy). + # + # == Type safety with ActiveRecord::AssociationTypeMismatch + # + # If you attempt to assign an object to an association that doesn't match the inferred + # or specified :class_name, you'll get an ActiveRecord::AssociationTypeMismatch. + # + # == Options + # + # All of the association macros can be specialized through options. This makes cases + # more complex than the simple and guessable ones possible. + module ClassMethods + # Specifies a one-to-many association. The following methods for retrieval and query of + # collections of associated objects will be added: + # + # +collection+ is a placeholder for the symbol passed as the +name+ argument, so + # has_many :clients would add among others clients.empty?. + # + # [collection] + # Returns a Relation of all the associated objects. + # An empty Relation is returned if none are found. + # [collection<<(object, ...)] + # Adds one or more objects to the collection by setting their foreign keys to the collection's primary key. + # Note that this operation instantly fires update SQL without waiting for the save or update call on the + # parent object, unless the parent object is a new record. + # This will also run validations and callbacks of associated object(s). + # [collection.delete(object, ...)] + # Removes one or more objects from the collection by setting their foreign keys to +NULL+. + # Objects will be in addition destroyed if they're associated with dependent: :destroy, + # and deleted if they're associated with dependent: :delete_all. + # + # If the :through option is used, then the join records are deleted (rather than + # nullified) by default, but you can specify dependent: :destroy or + # dependent: :nullify to override this. + # [collection.destroy(object, ...)] + # Removes one or more objects from the collection by running destroy on + # each record, regardless of any dependent option, ensuring callbacks are run. + # + # If the :through option is used, then the join records are destroyed + # instead, not the objects themselves. + # [collection=objects] + # Replaces the collections content by deleting and adding objects as appropriate. If the :through + # option is true callbacks in the join models are triggered except destroy callbacks, since deletion is + # direct by default. You can specify dependent: :destroy or + # dependent: :nullify to override this. + # [collection_singular_ids] + # Returns an array of the associated objects' ids + # [collection_singular_ids=ids] + # Replace the collection with the objects identified by the primary keys in +ids+. This + # method loads the models and calls collection=. See above. + # [collection.clear] + # Removes every object from the collection. This destroys the associated objects if they + # are associated with dependent: :destroy, deletes them directly from the + # database if dependent: :delete_all, otherwise sets their foreign keys to +NULL+. + # If the :through option is true no destroy callbacks are invoked on the join models. + # Join models are directly deleted. + # [collection.empty?] + # Returns +true+ if there are no associated objects. + # [collection.size] + # Returns the number of associated objects. + # [collection.find(...)] + # Finds an associated object according to the same rules as ActiveRecord::FinderMethods#find. + # [collection.exists?(...)] + # Checks whether an associated object with the given conditions exists. + # Uses the same rules as ActiveRecord::FinderMethods#exists?. + # [collection.build(attributes = {}, ...)] + # Returns one or more new objects of the collection type that have been instantiated + # with +attributes+ and linked to this object through a foreign key, but have not yet + # been saved. + # [collection.create(attributes = {})] + # Returns a new object of the collection type that has been instantiated + # with +attributes+, linked to this object through a foreign key, and that has already + # been saved (if it passed the validation). *Note*: This only works if the base model + # already exists in the DB, not if it is a new (unsaved) record! + # [collection.create!(attributes = {})] + # Does the same as collection.create, but raises ActiveRecord::RecordInvalid + # if the record is invalid. + # [collection.reload] + # Returns a Relation of all of the associated objects, forcing a database read. + # An empty Relation is returned if none are found. + # + # ==== Example + # + # class Firm < ActiveRecord::Base + # has_many :clients + # end + # + # Declaring has_many :clients adds the following methods (and more): + # + # firm = Firm.find(2) + # client = Client.find(6) + # + # firm.clients # similar to Client.where(firm_id: 2) + # firm.clients << client + # firm.clients.delete(client) + # firm.clients.destroy(client) + # firm.clients = [client] + # firm.client_ids + # firm.client_ids = [6] + # firm.clients.clear + # firm.clients.empty? # similar to firm.clients.size == 0 + # firm.clients.size # similar to Client.count "firm_id = 2" + # firm.clients.find # similar to Client.where(firm_id: 2).find(6) + # firm.clients.exists?(name: 'ACME') # similar to Client.exists?(name: 'ACME', firm_id: 2) + # firm.clients.build # similar to Client.new(firm_id: 2) + # firm.clients.create # similar to Client.create(firm_id: 2) + # firm.clients.create! # similar to Client.create!(firm_id: 2) + # firm.clients.reload + # + # The declaration can also include an +options+ hash to specialize the behavior of the association. + # + # ==== Scopes + # + # You can pass a second argument +scope+ as a callable (i.e. proc or + # lambda) to retrieve a specific set of records or customize the generated + # query when you access the associated collection. + # + # Scope examples: + # has_many :comments, -> { where(author_id: 1) } + # has_many :employees, -> { joins(:address) } + # has_many :posts, ->(blog) { where("max_post_length > ?", blog.max_post_length) } + # + # ==== Extensions + # + # The +extension+ argument allows you to pass a block into a has_many + # association. This is useful for adding new finders, creators, and other + # factory-type methods to be used as part of the association. + # + # Extension examples: + # has_many :employees do + # def find_or_create_by_name(name) + # first_name, last_name = name.split(" ", 2) + # find_or_create_by(first_name: first_name, last_name: last_name) + # end + # end + # + # ==== Options + # [+:class_name+] + # Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So has_many :products will by default be linked + # to the +Product+ class, but if the real class name is +SpecialProduct+, you'll have to + # specify it with this option. + # [+:foreign_key+] + # Specify the foreign key used for the association. By default this is guessed to be the name + # of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_many + # association will use "person_id" as the default :foreign_key. + # + # Setting the :foreign_key option prevents automatic detection of the association's + # inverse, so it is generally a good idea to set the :inverse_of option as well. + # [+:foreign_type+] + # Specify the column used to store the associated object's type, if this is a polymorphic + # association. By default this is guessed to be the name of the polymorphic association + # specified on "as" option with a "_type" suffix. So a class that defines a + # has_many :tags, as: :taggable association will use "taggable_type" as the + # default :foreign_type. + # [+:primary_key+] + # Specify the name of the column to use as the primary key for the association. By default this is +id+. + # [+:dependent+] + # Controls what happens to the associated objects when + # their owner is destroyed. Note that these are implemented as + # callbacks, and \Rails executes callbacks in order. Therefore, other + # similar callbacks may affect the :dependent behavior, and the + # :dependent behavior may affect other callbacks. + # + # * nil do nothing (default). + # * :destroy causes all the associated objects to also be destroyed. + # * :destroy_async destroys all the associated objects in a background job. WARNING: Do not use + # this option if the association is backed by foreign key constraints in your database. The foreign key + # constraint actions will occur inside the same transaction that deletes its owner. + # * :delete_all causes all the associated objects to be deleted directly from the database (so callbacks will not be executed). + # * :nullify causes the foreign keys to be set to +NULL+. Polymorphic type will also be nullified + # on polymorphic associations. Callbacks are not executed. + # * :restrict_with_exception causes an ActiveRecord::DeleteRestrictionError exception to be raised if there are any associated records. + # * :restrict_with_error causes an error to be added to the owner if there are any associated objects. + # + # If using with the :through option, the association on the join model must be + # a #belongs_to, and the records which get deleted are the join records, rather than + # the associated records. + # + # If using dependent: :destroy on a scoped association, only the scoped objects are destroyed. + # For example, if a Post model defines + # has_many :comments, -> { where published: true }, dependent: :destroy and destroy is + # called on a post, only published comments are destroyed. This means that any unpublished comments in the + # database would still contain a foreign key pointing to the now deleted post. + # [+:counter_cache+] + # This option can be used to configure a custom named :counter_cache. You only need this option, + # when you customized the name of your :counter_cache on the #belongs_to association. + # [+:as+] + # Specifies a polymorphic interface (See #belongs_to). + # [+:through+] + # Specifies an association through which to perform the query. This can be any other type + # of association, including other :through associations. Options for :class_name, + # :primary_key and :foreign_key are ignored, as the association uses the + # source reflection. + # + # If the association on the join model is a #belongs_to, the collection can be modified + # and the records on the :through model will be automatically created and removed + # as appropriate. Otherwise, the collection is read-only, so you should manipulate the + # :through association directly. + # + # If you are going to modify the association (rather than just read from it), then it is + # a good idea to set the :inverse_of option on the source association on the + # join model. This allows associated records to be built which will automatically create + # the appropriate join model records when they are saved. See + # {Association Join Models}[rdoc-ref:Associations::ClassMethods@Association+Join+Models] + # and {Setting Inverses}[rdoc-ref:Associations::ClassMethods@Setting+Inverses] for + # more detail. + # + # [+:disable_joins+] + # Specifies whether joins should be skipped for an association. If set to true, two or more queries + # will be generated. Note that in some cases, if order or limit is applied, it will be done in-memory + # due to database limitations. This option is only applicable on has_many :through associations as + # +has_many+ alone do not perform a join. + # [+:source+] + # Specifies the source association name used by #has_many :through queries. + # Only use it if the name cannot be inferred from the association. + # has_many :subscribers, through: :subscriptions will look for either :subscribers or + # :subscriber on Subscription, unless a :source is given. + # [+:source_type+] + # Specifies type of the source association used by #has_many :through queries where the source + # association is a polymorphic #belongs_to. + # [+:validate+] + # When set to +true+, validates new objects added to association when saving the parent object. +true+ by default. + # If you want to ensure associated objects are revalidated on every update, use +validates_associated+. + # [+:autosave+] + # If true, always save the associated objects or destroy them if marked for destruction, + # when saving the parent object. If false, never save or destroy the associated objects. + # By default, only save associated objects that are new records. This option is implemented as a + # +before_save+ callback. Because callbacks are run in the order they are defined, associated objects + # may need to be explicitly saved in any user-defined +before_save+ callbacks. + # + # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets + # :autosave to true. + # [+:inverse_of+] + # Specifies the name of the #belongs_to association on the associated object + # that is the inverse of this #has_many association. + # See {Bi-directional associations}[rdoc-ref:Associations::ClassMethods@Bi-directional+associations] + # for more detail. + # [+:extend+] + # Specifies a module or array of modules that will be extended into the association object returned. + # Useful for defining methods on associations, especially when they should be shared between multiple + # association objects. + # [+:strict_loading+] + # When set to +true+, enforces strict loading every time the associated record is loaded through this + # association. + # [+:ensuring_owner_was+] + # Specifies an instance method to be called on the owner. The method must return true in order for the + # associated records to be deleted in a background job. + # [+:query_constraints+] + # Serves as a composite foreign key. Defines the list of columns to be used to query the associated object. + # This is an optional option. By default Rails will attempt to derive the value automatically. + # When the value is set the Array size must match associated model's primary key or +query_constraints+ size. + # [+:index_errors+] + # Allows differentiation of multiple validation errors from the association records, by including + # an index in the error attribute name, e.g. +roles[2].level+. + # When set to +true+, the index is based on association order, i.e. database order, with yet to be + # persisted new records placed at the end. + # When set to +:nested_attributes_order+, the index is based on the record order received by + # nested attributes setter, when accepts_nested_attributes_for is used. + # [:before_add] + # Defines an {association callback}[rdoc-ref:Associations::ClassMethods@Association+callbacks] that gets triggered before an object is added to the association collection. + # [:after_add] + # Defines an {association callback}[rdoc-ref:Associations::ClassMethods@Association+callbacks] that gets triggered after an object is added to the association collection. + # [:before_remove] + # Defines an {association callback}[rdoc-ref:Associations::ClassMethods@Association+callbacks] that gets triggered before an object is removed from the association collection. + # [:after_remove] + # Defines an {association callback}[rdoc-ref:Associations::ClassMethods@Association+callbacks] that gets triggered after an object is removed from the association collection. + # + # Option examples: + # has_many :comments, -> { order("posted_on") } + # has_many :comments, -> { includes(:author) } + # has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person" + # has_many :tracks, -> { order("position") }, dependent: :destroy + # has_many :comments, dependent: :nullify + # has_many :tags, as: :taggable + # has_many :reports, -> { readonly } + # has_many :subscribers, through: :subscriptions, source: :user + # has_many :subscribers, through: :subscriptions, disable_joins: true + # has_many :comments, strict_loading: true + # has_many :comments, query_constraints: [:blog_id, :post_id] + # has_many :comments, index_errors: :nested_attributes_order + def has_many(name, scope = nil, **options, &extension) + reflection = Builder::HasMany.build(self, name, scope, options, &extension) + Reflection.add_reflection self, name, reflection + end + + # Specifies a one-to-one association with another class. This method + # should only be used if the other class contains the foreign key. If + # the current class contains the foreign key, then you should use + # #belongs_to instead. See {Is it a belongs_to or has_one + # association?}[rdoc-ref:Associations::ClassMethods@Is+it+a+-23belongs_to+or+-23has_one+association-3F] + # for more detail on when to use #has_one and when to use #belongs_to. + # + # The following methods for retrieval and query of a single associated object will be added: + # + # +association+ is a placeholder for the symbol passed as the +name+ argument, so + # has_one :manager would add among others manager.nil?. + # + # [association] + # Returns the associated object. +nil+ is returned if none is found. + # [association=(associate)] + # Assigns the associate object, extracts the primary key, sets it as the foreign key, + # and saves the associate object. To avoid database inconsistencies, permanently deletes an existing + # associated object when assigning a new one, even if the new one isn't saved to database. + # [build_association(attributes = {})] + # Returns a new object of the associated type that has been instantiated + # with +attributes+ and linked to this object through a foreign key, but has not + # yet been saved. + # [create_association(attributes = {})] + # Returns a new object of the associated type that has been instantiated + # with +attributes+, linked to this object through a foreign key, and that + # has already been saved (if it passed the validation). + # [create_association!(attributes = {})] + # Does the same as create_association, but raises ActiveRecord::RecordInvalid + # if the record is invalid. + # [reload_association] + # Returns the associated object, forcing a database read. + # [reset_association] + # Unloads the associated object. The next access will query it from the database. + # + # ==== Example + # + # class Account < ActiveRecord::Base + # has_one :beneficiary + # end + # + # Declaring has_one :beneficiary adds the following methods (and more): + # + # account = Account.find(5) + # beneficiary = Beneficiary.find(8) + # + # account.beneficiary # similar to Beneficiary.find_by(account_id: 5) + # account.beneficiary = beneficiary # similar to beneficiary.update(account_id: 5) + # account.build_beneficiary # similar to Beneficiary.new(account_id: 5) + # account.create_beneficiary # similar to Beneficiary.create(account_id: 5) + # account.create_beneficiary! # similar to Beneficiary.create!(account_id: 5) + # account.reload_beneficiary + # account.reset_beneficiary + # + # ==== Scopes + # + # You can pass a second argument +scope+ as a callable (i.e. proc or + # lambda) to retrieve a specific record or customize the generated query + # when you access the associated object. + # + # Scope examples: + # has_one :author, -> { where(comment_id: 1) } + # has_one :employer, -> { joins(:company) } + # has_one :latest_post, ->(blog) { where("created_at > ?", blog.enabled_at) } + # + # ==== Options + # + # The declaration can also include an +options+ hash to specialize the behavior of the association. + # + # Options are: + # [+:class_name+] + # Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So has_one :manager will by default be linked to the Manager class, but + # if the real class name is Person, you'll have to specify it with this option. + # [+:dependent+] + # Controls what happens to the associated object when + # its owner is destroyed: + # + # * nil do nothing (default). + # * :destroy causes the associated object to also be destroyed + # * :destroy_async causes the associated object to be destroyed in a background job. WARNING: Do not use + # this option if the association is backed by foreign key constraints in your database. The foreign key + # constraint actions will occur inside the same transaction that deletes its owner. + # * :delete causes the associated object to be deleted directly from the database (so callbacks will not execute) + # * :nullify causes the foreign key to be set to +NULL+. Polymorphic type column is also nullified + # on polymorphic associations. Callbacks are not executed. + # * :restrict_with_exception causes an ActiveRecord::DeleteRestrictionError exception to be raised if there is an associated record + # * :restrict_with_error causes an error to be added to the owner if there is an associated object + # + # Note that :dependent option is ignored when using :through option. + # [+:foreign_key+] + # Specify the foreign key used for the association. By default this is guessed to be the name + # of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_one association + # will use "person_id" as the default :foreign_key. + # + # Setting the :foreign_key option prevents automatic detection of the association's + # inverse, so it is generally a good idea to set the :inverse_of option as well. + # [+:foreign_type+] + # Specify the column used to store the associated object's type, if this is a polymorphic + # association. By default this is guessed to be the name of the polymorphic association + # specified on "as" option with a "_type" suffix. So a class that defines a + # has_one :tag, as: :taggable association will use "taggable_type" as the + # default :foreign_type. + # [+:primary_key+] + # Specify the method that returns the primary key used for the association. By default this is +id+. + # [+:as+] + # Specifies a polymorphic interface (See #belongs_to). + # [+:through+] + # Specifies a Join Model through which to perform the query. Options for :class_name, + # :primary_key, and :foreign_key are ignored, as the association uses the + # source reflection. You can only use a :through query through a #has_one + # or #belongs_to association on the join model. + # + # If the association on the join model is a #belongs_to, the collection can be modified + # and the records on the :through model will be automatically created and removed + # as appropriate. Otherwise, the collection is read-only, so you should manipulate the + # :through association directly. + # + # If you are going to modify the association (rather than just read from it), then it is + # a good idea to set the :inverse_of option on the source association on the + # join model. This allows associated records to be built which will automatically create + # the appropriate join model records when they are saved. See + # {Association Join Models}[rdoc-ref:Associations::ClassMethods@Association+Join+Models] + # and {Setting Inverses}[rdoc-ref:Associations::ClassMethods@Setting+Inverses] for + # more detail. + # [+:disable_joins+] + # Specifies whether joins should be skipped for an association. If set to true, two or more queries + # will be generated. Note that in some cases, if order or limit is applied, it will be done in-memory + # due to database limitations. This option is only applicable on has_one :through associations as + # +has_one+ alone does not perform a join. + # [+:source+] + # Specifies the source association name used by #has_one :through queries. + # Only use it if the name cannot be inferred from the association. + # has_one :favorite, through: :favorites will look for a + # :favorite on Favorite, unless a :source is given. + # [+:source_type+] + # Specifies type of the source association used by #has_one :through queries where the source + # association is a polymorphic #belongs_to. + # [+:validate+] + # When set to +true+, validates new objects added to association when saving the parent object. +false+ by default. + # If you want to ensure associated objects are revalidated on every update, use +validates_associated+. + # [+:autosave+] + # If +true+, always saves the associated object or destroys it if marked for destruction, + # when saving the parent object. + # If +false+, never save or destroy the associated object. + # + # By default, only saves the associated object if it's a new record. Setting this option + # to +true+ also enables validations on the associated object unless explicitly disabled + # with validate: false. This is because saving an object with invalid associated + # objects would fail, so any associated objects will go through validation checks. + # + # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets + # :autosave to true. + # [+:touch+] + # If true, the associated object will be touched (the +updated_at+ / +updated_on+ attributes set to current time) + # when this record is either saved or destroyed. If you specify a symbol, that attribute + # will be updated with the current time in addition to the +updated_at+ / +updated_on+ attribute. + # Please note that no validation will be performed when touching, and only the +after_touch+, + # +after_commit+, and +after_rollback+ callbacks will be executed. + # [+:inverse_of+] + # Specifies the name of the #belongs_to association on the associated object + # that is the inverse of this #has_one association. + # See {Bi-directional associations}[rdoc-ref:Associations::ClassMethods@Bi-directional+associations] + # for more detail. + # [+:required+] + # When set to +true+, the association will also have its presence validated. + # This will validate the association itself, not the id. You can use + # +:inverse_of+ to avoid an extra query during validation. + # [+:strict_loading+] + # Enforces strict loading every time the associated record is loaded through this association. + # [+:ensuring_owner_was+] + # Specifies an instance method to be called on the owner. The method must return true in order for the + # associated records to be deleted in a background job. + # [+:query_constraints+] + # Serves as a composite foreign key. Defines the list of columns to be used to query the associated object. + # This is an optional option. By default Rails will attempt to derive the value automatically. + # When the value is set the Array size must match associated model's primary key or +query_constraints+ size. + # + # Option examples: + # has_one :credit_card, dependent: :destroy # destroys the associated credit card + # has_one :credit_card, dependent: :nullify # updates the associated records foreign + # # key value to NULL rather than destroying it + # has_one :last_comment, -> { order('posted_on') }, class_name: "Comment" + # has_one :project_manager, -> { where(role: 'project_manager') }, class_name: "Person" + # has_one :attachment, as: :attachable + # has_one :boss, -> { readonly } + # has_one :club, through: :membership + # has_one :club, through: :membership, disable_joins: true + # has_one :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable + # has_one :credit_card, required: true + # has_one :credit_card, strict_loading: true + # has_one :employment_record_book, query_constraints: [:organization_id, :employee_id] + def has_one(name, scope = nil, **options) + reflection = Builder::HasOne.build(self, name, scope, options) + Reflection.add_reflection self, name, reflection + end + + # Specifies a one-to-one association with another class. This method + # should only be used if this class contains the foreign key. If the + # other class contains the foreign key, then you should use #has_one + # instead. See {Is it a belongs_to or has_one + # association?}[rdoc-ref:Associations::ClassMethods@Is+it+a+-23belongs_to+or+-23has_one+association-3F] + # for more detail on when to use #has_one and when to use #belongs_to. + # + # Methods will be added for retrieval and query for a single associated object, for which + # this object holds an id: + # + # +association+ is a placeholder for the symbol passed as the +name+ argument, so + # belongs_to :author would add among others author.nil?. + # + # [association] + # Returns the associated object. +nil+ is returned if none is found. + # [association=(associate)] + # Assigns the associate object, extracts the primary key, and sets it as the foreign key. + # No modification or deletion of existing records takes place. + # [build_association(attributes = {})] + # Returns a new object of the associated type that has been instantiated + # with +attributes+ and linked to this object through a foreign key, but has not yet been saved. + # [create_association(attributes = {})] + # Returns a new object of the associated type that has been instantiated + # with +attributes+, linked to this object through a foreign key, and that + # has already been saved (if it passed the validation). + # [create_association!(attributes = {})] + # Does the same as create_association, but raises ActiveRecord::RecordInvalid + # if the record is invalid. + # [reload_association] + # Returns the associated object, forcing a database read. + # [reset_association] + # Unloads the associated object. The next access will query it from the database. + # [association_changed?] + # Returns true if a new associate object has been assigned and the next save will update the foreign key. + # [association_previously_changed?] + # Returns true if the previous save updated the association to reference a new associate object. + # + # ==== Example + # + # class Post < ActiveRecord::Base + # belongs_to :author + # end + # + # Declaring belongs_to :author adds the following methods (and more): + # + # post = Post.find(7) + # author = Author.find(19) + # + # post.author # similar to Author.find(post.author_id) + # post.author = author # similar to post.author_id = author.id + # post.build_author # similar to post.author = Author.new + # post.create_author # similar to post.author = Author.new; post.author.save; post.author + # post.create_author! # similar to post.author = Author.new; post.author.save!; post.author + # post.reload_author + # post.reset_author + # post.author_changed? + # post.author_previously_changed? + # + # ==== Scopes + # + # You can pass a second argument +scope+ as a callable (i.e. proc or + # lambda) to retrieve a specific record or customize the generated query + # when you access the associated object. + # + # Scope examples: + # belongs_to :firm, -> { where(id: 2) } + # belongs_to :user, -> { joins(:friends) } + # belongs_to :level, ->(game) { where("game_level > ?", game.current_level) } + # + # ==== Options + # + # The declaration can also include an +options+ hash to specialize the behavior of the association. + # + # [+:class_name+] + # Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So belongs_to :author will by default be linked to the Author class, but + # if the real class name is Person, you'll have to specify it with this option. + # [+:foreign_key+] + # Specify the foreign key used for the association. By default this is guessed to be the name + # of the association with an "_id" suffix. So a class that defines a belongs_to :person + # association will use "person_id" as the default :foreign_key. Similarly, + # belongs_to :favorite_person, class_name: "Person" will use a foreign key + # of "favorite_person_id". + # + # Setting the :foreign_key option prevents automatic detection of the association's + # inverse, so it is generally a good idea to set the :inverse_of option as well. + # [+:foreign_type+] + # Specify the column used to store the associated object's type, if this is a polymorphic + # association. By default this is guessed to be the name of the association with a "_type" + # suffix. So a class that defines a belongs_to :taggable, polymorphic: true + # association will use "taggable_type" as the default :foreign_type. + # [+:primary_key+] + # Specify the method that returns the primary key of associated object used for the association. + # By default this is +id+. + # [+:dependent+] + # If set to :destroy, the associated object is destroyed when this object is. If set to + # :delete, the associated object is deleted *without* calling its destroy method. If set to + # :destroy_async, the associated object is scheduled to be destroyed in a background job. + # This option should not be specified when #belongs_to is used in conjunction with + # a #has_many relationship on another class because of the potential to leave + # orphaned records behind. + # [+:counter_cache+] + # Caches the number of belonging objects on the associate class through the use of CounterCache::ClassMethods#increment_counter + # and CounterCache::ClassMethods#decrement_counter. The counter cache is incremented when an object of this + # class is created and decremented when it's destroyed. This requires that a column + # named #{table_name}_count (such as +comments_count+ for a belonging Comment class) + # is used on the associate class (such as a Post class) - that is the migration for + # #{table_name}_count is created on the associate class (such that Post.comments_count will + # return the count cached). You can also specify a custom counter + # cache column by providing a column name instead of a +true+/+false+ value to this + # option (e.g., counter_cache: :my_custom_counter.) + # + # Starting to use counter caches on existing large tables can be troublesome, because the column + # values must be backfilled separately of the column addition (to not lock the table for too long) + # and before the use of +:counter_cache+ (otherwise methods like +size+/+any?+/etc, which use + # counter caches internally, can produce incorrect results). To safely backfill the values while keeping + # counter cache columns updated with the child records creation/removal and to avoid the mentioned methods + # use the possibly incorrect counter cache column values and always get the results from the database, + # use counter_cache: { active: false }. + # If you also need to specify a custom column name, use counter_cache: { active: false, column: :my_custom_counter }. + # + # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute + # to the +attr_readonly+ list in the associated classes (e.g. class Post; attr_readonly :comments_count; end). + # [+:polymorphic+] + # Specify this association is a polymorphic association by passing +true+. + # Note: Since polymorphic associations rely on storing class names in the database, make sure to update the class names in the + # *_type polymorphic type column of the corresponding rows. + # [+:validate+] + # When set to +true+, validates new objects added to association when saving the parent object. +false+ by default. + # If you want to ensure associated objects are revalidated on every update, use +validates_associated+. + # [+:autosave+] + # If true, always save the associated object or destroy it if marked for destruction, when + # saving the parent object. + # If false, never save or destroy the associated object. + # By default, only save the associated object if it's a new record. + # + # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for + # sets :autosave to true. + # [+:touch+] + # If true, the associated object will be touched (the +updated_at+ / +updated_on+ attributes set to current time) + # when this record is either saved or destroyed. If you specify a symbol, that attribute + # will be updated with the current time in addition to the +updated_at+ / +updated_on+ attribute. + # Please note that no validation will be performed when touching, and only the +after_touch+, + # +after_commit+, and +after_rollback+ callbacks will be executed. + # [+:inverse_of+] + # Specifies the name of the #has_one or #has_many association on the associated + # object that is the inverse of this #belongs_to association. + # See {Bi-directional associations}[rdoc-ref:Associations::ClassMethods@Bi-directional+associations] + # for more detail. + # [+:optional+] + # When set to +true+, the association will not have its presence validated. + # [+:required+] + # When set to +true+, the association will also have its presence validated. + # This will validate the association itself, not the id. You can use + # +:inverse_of+ to avoid an extra query during validation. + # NOTE: required is set to true by default and is deprecated. If + # you don't want to have association presence validated, use optional: true. + # [+:default+] + # Provide a callable (i.e. proc or lambda) to specify that the association should + # be initialized with a particular record before validation. + # Please note that callable won't be executed if the record exists. + # [+:strict_loading+] + # Enforces strict loading every time the associated record is loaded through this association. + # [+:ensuring_owner_was+] + # Specifies an instance method to be called on the owner. The method must return true in order for the + # associated records to be deleted in a background job. + # [+:query_constraints+] + # Serves as a composite foreign key. Defines the list of columns to be used to query the associated object. + # This is an optional option. By default Rails will attempt to derive the value automatically. + # When the value is set the Array size must match associated model's primary key or +query_constraints+ size. + # + # Option examples: + # belongs_to :firm, foreign_key: "client_of" + # belongs_to :person, primary_key: "name", foreign_key: "person_name" + # belongs_to :author, class_name: "Person", foreign_key: "author_id" + # belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count }, + # class_name: "Coupon", foreign_key: "coupon_id" + # belongs_to :attachable, polymorphic: true + # belongs_to :project, -> { readonly } + # belongs_to :post, counter_cache: true + # belongs_to :comment, touch: true + # belongs_to :company, touch: :employees_last_updated_at + # belongs_to :user, optional: true + # belongs_to :account, default: -> { company.account } + # belongs_to :account, strict_loading: true + # belongs_to :note, query_constraints: [:organization_id, :note_id] + def belongs_to(name, scope = nil, **options) + reflection = Builder::BelongsTo.build(self, name, scope, options) + Reflection.add_reflection self, name, reflection + end + + # Specifies a many-to-many relationship with another class. This associates two classes via an + # intermediate join table. Unless the join table is explicitly specified as an option, it is + # guessed using the lexical order of the class names. So a join between Developer and Project + # will give the default join table name of "developers_projects" because "D" precedes "P" alphabetically. + # Note that this precedence is calculated using the < operator for String. This + # means that if the strings are of different lengths, and the strings are equal when compared + # up to the shortest length, then the longer string is considered of higher + # lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" + # to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", + # but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the + # custom :join_table option if you need to. + # If your tables share a common prefix, it will only appear once at the beginning. For example, + # the tables "catalog_categories" and "catalog_products" generate a join table name of "catalog_categories_products". + # + # The join table should not have a primary key or a model associated with it. You must manually generate the + # join table with a migration such as this: + # + # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[8.0] + # def change + # create_join_table :developers, :projects + # end + # end + # + # It's also a good idea to add indexes to each of those columns to speed up the joins process. + # However, in MySQL it is advised to add a compound index for both of the columns as MySQL only + # uses one index per table during the lookup. + # + # Adds the following methods for retrieval and query: + # + # +collection+ is a placeholder for the symbol passed as the +name+ argument, so + # has_and_belongs_to_many :categories would add among others categories.empty?. + # + # [collection] + # Returns a Relation of all the associated objects. + # An empty Relation is returned if none are found. + # [collection<<(object, ...)] + # Adds one or more objects to the collection by creating associations in the join table + # (collection.push and collection.concat are aliases to this method). + # Note that this operation instantly fires update SQL without waiting for the save or update call on the + # parent object, unless the parent object is a new record. + # [collection.delete(object, ...)] + # Removes one or more objects from the collection by removing their associations from the join table. + # This does not destroy the objects. + # [collection.destroy(object, ...)] + # Removes one or more objects from the collection by running destroy on each association in the join table, overriding any dependent option. + # This does not destroy the objects. + # [collection=objects] + # Replaces the collection's content by deleting and adding objects as appropriate. + # [collection_singular_ids] + # Returns an array of the associated objects' ids. + # [collection_singular_ids=ids] + # Replace the collection by the objects identified by the primary keys in +ids+. + # [collection.clear] + # Removes every object from the collection. This does not destroy the objects. + # [collection.empty?] + # Returns +true+ if there are no associated objects. + # [collection.size] + # Returns the number of associated objects. + # [collection.find(id)] + # Finds an associated object responding to the +id+ and that + # meets the condition that it has to be associated with this object. + # Uses the same rules as ActiveRecord::FinderMethods#find. + # [collection.exists?(...)] + # Checks whether an associated object with the given conditions exists. + # Uses the same rules as ActiveRecord::FinderMethods#exists?. + # [collection.build(attributes = {})] + # Returns a new object of the collection type that has been instantiated + # with +attributes+ and linked to this object through the join table, but has not yet been saved. + # [collection.create(attributes = {})] + # Returns a new object of the collection type that has been instantiated + # with +attributes+, linked to this object through the join table, and that has already been + # saved (if it passed the validation). + # [collection.reload] + # Returns a Relation of all of the associated objects, forcing a database read. + # An empty Relation is returned if none are found. + # + # ==== Example + # + # class Developer < ActiveRecord::Base + # has_and_belongs_to_many :projects + # end + # + # Declaring has_and_belongs_to_many :projects adds the following methods (and more): + # + # developer = Developer.find(11) + # project = Project.find(9) + # + # developer.projects + # developer.projects << project + # developer.projects.delete(project) + # developer.projects.destroy(project) + # developer.projects = [project] + # developer.project_ids + # developer.project_ids = [9] + # developer.projects.clear + # developer.projects.empty? + # developer.projects.size + # developer.projects.find(9) + # developer.projects.exists?(9) + # developer.projects.build # similar to Project.new(developer_id: 11) + # developer.projects.create # similar to Project.create(developer_id: 11) + # developer.projects.reload + # + # The declaration may include an +options+ hash to specialize the behavior of the association. + # + # ==== Scopes + # + # You can pass a second argument +scope+ as a callable (i.e. proc or + # lambda) to retrieve a specific set of records or customize the generated + # query when you access the associated collection. + # + # Scope examples: + # has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) } + # has_and_belongs_to_many :categories, ->(post) { + # where("default_category = ?", post.default_category) + # } + # + # ==== Extensions + # + # The +extension+ argument allows you to pass a block into a + # has_and_belongs_to_many association. This is useful for adding new + # finders, creators, and other factory-type methods to be used as part of + # the association. + # + # Extension examples: + # has_and_belongs_to_many :contractors do + # def find_or_create_by_name(name) + # first_name, last_name = name.split(" ", 2) + # find_or_create_by(first_name: first_name, last_name: last_name) + # end + # end + # + # ==== Options + # + # [+:class_name+] + # Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So has_and_belongs_to_many :projects will by default be linked to the + # Project class, but if the real class name is SuperProject, you'll have to specify it with this option. + # [+:join_table+] + # Specify the name of the join table if the default based on lexical order isn't what you want. + # WARNING: If you're overwriting the table name of either class, the +table_name+ method + # MUST be declared underneath any #has_and_belongs_to_many declaration in order to work. + # [+:foreign_key+] + # Specify the foreign key used for the association. By default this is guessed to be the name + # of this class in lower-case and "_id" suffixed. So a Person class that makes + # a #has_and_belongs_to_many association to Project will use "person_id" as the + # default :foreign_key. + # + # Setting the :foreign_key option prevents automatic detection of the association's + # inverse, so it is generally a good idea to set the :inverse_of option as well. + # [+:association_foreign_key+] + # Specify the foreign key used for the association on the receiving side of the association. + # By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed. + # So if a Person class makes a #has_and_belongs_to_many association to Project, + # the association will use "project_id" as the default :association_foreign_key. + # [+:validate+] + # When set to +true+, validates new objects added to association when saving the parent object. +true+ by default. + # If you want to ensure associated objects are revalidated on every update, use +validates_associated+. + # [+:autosave+] + # If true, always save the associated objects or destroy them if marked for destruction, when + # saving the parent object. + # If false, never save or destroy the associated objects. + # By default, only save associated objects that are new records. + # + # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets + # :autosave to true. + # [+:strict_loading+] + # Enforces strict loading every time an associated record is loaded through this association. + # + # Option examples: + # has_and_belongs_to_many :projects + # has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) } + # has_and_belongs_to_many :nations, class_name: "Country" + # has_and_belongs_to_many :categories, join_table: "prods_cats" + # has_and_belongs_to_many :categories, -> { readonly } + # has_and_belongs_to_many :categories, strict_loading: true + def has_and_belongs_to_many(name, scope = nil, **options, &extension) + habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self) + + builder = Builder::HasAndBelongsToMany.new name, self, options + + join_model = builder.through_model + + const_set join_model.name, join_model + private_constant join_model.name + + middle_reflection = builder.middle_reflection join_model + + Builder::HasMany.define_callbacks self, middle_reflection + Reflection.add_reflection self, middle_reflection.name, middle_reflection + middle_reflection.parent_reflection = habtm_reflection + + include Module.new { + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def destroy_associations + association(:#{middle_reflection.name}).delete_all(:delete_all) + association(:#{name}).reset + super + end + RUBY + } + + hm_options = {} + hm_options[:through] = middle_reflection.name + hm_options[:source] = join_model.right_reflection.name + + [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend, :strict_loading].each do |k| + hm_options[k] = options[k] if options.key? k + end + + has_many name, scope, **hm_options, &extension + _reflections[name].parent_reflection = habtm_reflection + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/alias_tracker.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/alias_tracker.rb new file mode 100644 index 00000000..bddd795d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/alias_tracker.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/conversions" + +module ActiveRecord + module Associations + # Keeps track of table aliases for ActiveRecord::Associations::JoinDependency + class AliasTracker # :nodoc: + def self.create(pool, initial_table, joins, aliases = nil) + pool.with_connection do |connection| + if joins.empty? + aliases ||= Hash.new(0) + elsif aliases + default_proc = aliases.default_proc || proc { 0 } + aliases.default_proc = proc { |h, k| + h[k] = initial_count_for(connection, k, joins) + default_proc.call(h, k) + } + else + aliases = Hash.new { |h, k| + h[k] = initial_count_for(connection, k, joins) + } + end + aliases[initial_table] = 1 + new(connection.table_alias_length, aliases) + end + end + + def self.initial_count_for(connection, name, table_joins) + quoted_name_escaped = nil + name_escaped = nil + + counts = table_joins.map do |join| + if join.is_a?(Arel::Nodes::StringJoin) + # quoted_name_escaped should be case ignored as some database adapters (Oracle) return quoted name in uppercase + quoted_name_escaped ||= Regexp.escape(connection.quote_table_name(name)) + name_escaped ||= Regexp.escape(name) + + # Table names + table aliases + join.left.scan( + /JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name_escaped}|#{name_escaped})\sON/i + ).size + elsif join.is_a?(Arel::Nodes::Join) + join.left.name == name ? 1 : 0 + else + raise ArgumentError, "joins list should be initialized by list of Arel::Nodes::Join" + end + end + + counts.sum + end + + # table_joins is an array of arel joins which might conflict with the aliases we assign here + def initialize(table_alias_length, aliases) + @aliases = aliases + @table_alias_length = table_alias_length + end + + def aliased_table_for(arel_table, table_name = nil) + table_name ||= arel_table.name + + if aliases[table_name] == 0 + # If it's zero, we can have our table_name + aliases[table_name] = 1 + arel_table = arel_table.alias(table_name) if arel_table.name != table_name + else + # Otherwise, we need to use an alias + aliased_name = table_alias_for(yield) + + # Update the count + count = aliases[aliased_name] += 1 + + aliased_name = "#{truncate(aliased_name)}_#{count}" if count > 1 + + arel_table = arel_table.alias(aliased_name) + end + + arel_table + end + + attr_reader :aliases + + private + def table_alias_for(table_name) + table_name[0...@table_alias_length].tr(".", "_") + end + + def truncate(name) + name.slice(0, @table_alias_length - 2) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/association.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/association.rb new file mode 100644 index 00000000..2c2a601d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/association.rb @@ -0,0 +1,421 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # = Active Record Associations + # + # This is the root class of all associations ('+ Foo' signifies an included module Foo): + # + # Association + # SingularAssociation + # HasOneAssociation + ForeignAssociation + # HasOneThroughAssociation + ThroughAssociation + # BelongsToAssociation + # BelongsToPolymorphicAssociation + # CollectionAssociation + # HasManyAssociation + ForeignAssociation + # HasManyThroughAssociation + ThroughAssociation + # + # Associations in Active Record are middlemen between the object that + # holds the association, known as the owner, and the associated + # result set, known as the target. Association metadata is available in + # reflection, which is an instance of +ActiveRecord::Reflection::AssociationReflection+. + # + # For example, given + # + # class Blog < ActiveRecord::Base + # has_many :posts + # end + # + # blog = Blog.first + # + # The association of blog.posts has the object +blog+ as its + # owner, the collection of its posts as target, and + # the reflection object represents a :has_many macro. + class Association # :nodoc: + attr_accessor :owner + attr_reader :reflection, :disable_joins + + delegate :options, to: :reflection + + def initialize(owner, reflection) + reflection.check_validity! + + @owner, @reflection = owner, reflection + @disable_joins = @reflection.options[:disable_joins] || false + + reset + reset_scope + + @skip_strict_loading = nil + end + + def target + if @target.is_a?(Promise) + @target = @target.value + end + @target + end + + # Resets the \loaded flag to +false+ and sets the \target to +nil+. + def reset + @loaded = false + @stale_state = nil + end + + def reset_negative_cache # :nodoc: + reset if loaded? && target.nil? + end + + # Reloads the \target and returns +self+ on success. + # The QueryCache is cleared if +force+ is true. + def reload(force = false) + klass.connection_pool.clear_query_cache if force && klass + reset + reset_scope + load_target + self unless target.nil? + end + + # Has the \target been already \loaded? + def loaded? + @loaded + end + + # Asserts the \target has been loaded setting the \loaded flag to +true+. + def loaded! + @loaded = true + @stale_state = stale_state + end + + # The target is stale if the target no longer points to the record(s) that the + # relevant foreign_key(s) refers to. If stale, the association accessor method + # on the owner will reload the target. It's up to subclasses to implement the + # stale_state method if relevant. + # + # Note that if the target has not been loaded, it is not considered stale. + def stale_target? + loaded? && @stale_state != stale_state + end + + # Sets the target of this association to \target, and the \loaded flag to +true+. + def target=(target) + @target = target + loaded! + end + + def scope + if disable_joins + DisableJoinsAssociationScope.create.scope(self) + elsif (scope = klass.current_scope) && scope.try(:proxy_association) == self + scope.spawn + elsif scope = klass.global_current_scope + target_scope.merge!(association_scope).merge!(scope) + else + target_scope.merge!(association_scope) + end + end + + def reset_scope + @association_scope = nil + end + + def set_strict_loading(record) + if owner.strict_loading_n_plus_one_only? && reflection.macro == :has_many + record.strict_loading! + else + record.strict_loading!(false, mode: owner.strict_loading_mode) + end + end + + # Set the inverse association, if possible + def set_inverse_instance(record) + if inverse = inverse_association_for(record) + inverse.inversed_from(owner) + end + record + end + + def set_inverse_instance_from_queries(record) + if inverse = inverse_association_for(record) + inverse.inversed_from_queries(owner) + end + record + end + + # Remove the inverse association, if possible + def remove_inverse_instance(record) + if inverse = inverse_association_for(record) + inverse.inversed_from(nil) + end + end + + def inversed_from(record) + self.target = record + end + + def inversed_from_queries(record) + if inversable?(record) + self.target = record + end + end + + # Returns the class of the target. belongs_to polymorphic overrides this to look at the + # polymorphic_type field on the owner. + def klass + reflection.klass + end + + def extensions + extensions = klass.default_extensions | reflection.extensions + + if reflection.scope + extensions |= reflection.scope_for(klass.unscoped, owner).extensions + end + + extensions + end + + # Loads the \target if needed and returns it. + # + # This method is abstract in the sense that it relies on +find_target+, + # which is expected to be provided by descendants. + # + # If the \target is already \loaded it is just returned. Thus, you can call + # +load_target+ unconditionally to get the \target. + # + # ActiveRecord::RecordNotFound is rescued within the method, and it is + # not reraised. The proxy is \reset and +nil+ is the return value. + def load_target + @target = find_target(async: false) if (@stale_state && stale_target?) || find_target? + + loaded! unless loaded? + target + rescue ActiveRecord::RecordNotFound + reset + end + + def async_load_target # :nodoc: + @target = find_target(async: true) if (@stale_state && stale_target?) || find_target? + + loaded! unless loaded? + nil + end + + # We can't dump @reflection and @through_reflection since it contains the scope proc + def marshal_dump + ivars = (instance_variables - [:@reflection, :@through_reflection]).map { |name| [name, instance_variable_get(name)] } + [@reflection.name, ivars] + end + + def marshal_load(data) + reflection_name, ivars = data + ivars.each { |name, val| instance_variable_set(name, val) } + @reflection = @owner.class._reflect_on_association(reflection_name) + end + + def initialize_attributes(record, except_from_scope_attributes = nil) # :nodoc: + except_from_scope_attributes ||= {} + skip_assign = [reflection.foreign_key, reflection.type].compact + assigned_keys = record.changed_attribute_names_to_save + assigned_keys += except_from_scope_attributes.keys.map(&:to_s) + attributes = scope_for_create.except!(*(assigned_keys - skip_assign)) + record.send(:_assign_attributes, attributes) if attributes.any? + set_inverse_instance(record) + end + + def create(attributes = nil, &block) + _create_record(attributes, &block) + end + + def create!(attributes = nil, &block) + _create_record(attributes, true, &block) + end + + # Whether the association represent a single record + # or a collection of records. + def collection? + false + end + + private + # Reader and writer methods call this so that consistent errors are presented + # when the association target class does not exist. + def ensure_klass_exists! + klass + end + + def find_target(async: false) + if violates_strict_loading? + Base.strict_loading_violation!(owner: owner.class, reflection: reflection) + end + + scope = self.scope + if skip_statement_cache?(scope) + if async + return scope.load_async.then(&:to_a) + else + return scope.to_a + end + end + + sc = reflection.association_scope_cache(klass, owner) do |params| + as = AssociationScope.create { params.bind } + target_scope.merge!(as.scope(self)) + end + + binds = AssociationScope.get_bind_values(owner, reflection.chain) + klass.with_connection do |c| + sc.execute(binds, c, async: async) do |record| + set_inverse_instance(record) + set_strict_loading(record) + end + end + end + + def skip_strict_loading(&block) + skip_strict_loading_was = @skip_strict_loading + @skip_strict_loading = true + yield + ensure + @skip_strict_loading = skip_strict_loading_was + end + + def violates_strict_loading? + return if @skip_strict_loading + + return unless owner.validation_context.nil? + + return reflection.strict_loading? if reflection.options.key?(:strict_loading) + + owner.strict_loading? && !owner.strict_loading_n_plus_one_only? + end + + # The scope for this association. + # + # Note that the association_scope is merged into the target_scope only when the + # scope method is called. This is because at that point the call may be surrounded + # by scope.scoping { ... } or unscoped { ... } etc, which affects the scope which + # actually gets built. + def association_scope + if klass + @association_scope ||= if disable_joins + DisableJoinsAssociationScope.scope(self) + else + AssociationScope.scope(self) + end + end + end + + # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the + # through association's scope) + def target_scope + AssociationRelation.create(klass, self).merge!(klass.scope_for_association) + end + + def scope_for_create + scope.scope_for_create + end + + def find_target? + !loaded? && (!owner.new_record? || foreign_key_present?) && klass + end + + # Returns true if there is a foreign key present on the owner which + # references the target. This is used to determine whether we can load + # the target if the owner is currently a new record (and therefore + # without a key). If the owner is a new record then foreign_key must + # be present in order to load target. + # + # Currently implemented by belongs_to (vanilla and polymorphic) and + # has_one/has_many :through associations which go through a belongs_to. + def foreign_key_present? + false + end + + # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of + # the kind of the class of the associated objects. Meant to be used as + # a safety check when you are about to assign an associated record. + def raise_on_type_mismatch!(record) + unless record.is_a?(reflection.klass) + fresh_class = reflection.class_name.safe_constantize + unless fresh_class && record.is_a?(fresh_class) + message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, "\ + "got #{record.inspect} which is an instance of #{record.class}(##{record.class.object_id})" + raise ActiveRecord::AssociationTypeMismatch, message + end + end + end + + def inverse_association_for(record) + if invertible_for?(record) + record.association(inverse_reflection_for(record).name) + end + end + + # Can be redefined by subclasses, notably polymorphic belongs_to + # The record parameter is necessary to support polymorphic inverses as we must check for + # the association in the specific class of the record. + def inverse_reflection_for(record) + reflection.inverse_of + end + + # Returns true if inverse association on the given record needs to be set. + # This method is redefined by subclasses. + def invertible_for?(record) + foreign_key_for?(record) && inverse_reflection_for(record) + end + + # Returns true if record contains the foreign_key + def foreign_key_for?(record) + foreign_key = Array(reflection.foreign_key) + foreign_key.all? { |key| record._has_attribute?(key) } + end + + # This should be implemented to return the values of the relevant key(s) on the owner, + # so that when stale_state is different from the value stored on the last find_target, + # the target is stale. + # + # This is only relevant to certain associations, which is why it returns +nil+ by default. + def stale_state + end + + def build_record(attributes) + reflection.build_association(attributes) do |record| + initialize_attributes(record, attributes) + yield(record) if block_given? + end + end + + # Returns true if statement cache should be skipped on the association reader. + def skip_statement_cache?(scope) + reflection.has_scope? || + scope.eager_loading? || + klass.scope_attributes? || + reflection.source_reflection.active_record.default_scopes.any? + end + + def enqueue_destroy_association(options) + job_class = owner.class.destroy_association_async_job + + if job_class + owner._after_commit_jobs.push([job_class, options]) + end + end + + def inversable?(record) + record && + ((!record.persisted? || !owner.persisted?) || matches_foreign_key?(record)) + end + + def matches_foreign_key?(record) + if foreign_key_for?(record) + record.read_attribute(reflection.foreign_key) == owner.id || + (foreign_key_for?(owner) && owner.read_attribute(reflection.foreign_key) == record.id) + else + owner.read_attribute(reflection.foreign_key) == record.id + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/association_scope.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/association_scope.rb new file mode 100644 index 00000000..e16d2a74 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/association_scope.rb @@ -0,0 +1,175 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + class AssociationScope # :nodoc: + def self.scope(association) + INSTANCE.scope(association) + end + + def self.create(&block) + block ||= lambda { |val| val } + new(block) + end + + def initialize(value_transformation) + @value_transformation = value_transformation + end + + INSTANCE = create + + def scope(association) + klass = association.klass + reflection = association.reflection + scope = klass.unscoped + owner = association.owner + chain = get_chain(reflection, association, scope.alias_tracker) + + scope.extending! reflection.extensions + scope = add_constraints(scope, owner, chain) + scope.limit!(1) unless reflection.collection? + scope + end + + def self.get_bind_values(owner, chain) + binds = [] + last_reflection = chain.last + + binds.push(*last_reflection.join_id_for(owner)) + if last_reflection.type + binds << owner.class.polymorphic_name + end + + chain.each_cons(2).each do |reflection, next_reflection| + if reflection.type + binds << next_reflection.klass.polymorphic_name + end + end + binds + end + + private + attr_reader :value_transformation + + def join(table, constraint) + Arel::Nodes::LeadingJoin.new(table, Arel::Nodes::On.new(constraint)) + end + + def last_chain_scope(scope, reflection, owner) + primary_key = Array(reflection.join_primary_key) + foreign_key = Array(reflection.join_foreign_key) + + table = reflection.aliased_table + primary_key_foreign_key_pairs = primary_key.zip(foreign_key) + primary_key_foreign_key_pairs.each do |join_key, foreign_key| + value = transform_value(owner._read_attribute(foreign_key)) + scope = apply_scope(scope, table, join_key, value) + end + + if reflection.type + polymorphic_type = transform_value(owner.class.polymorphic_name) + scope = apply_scope(scope, table, reflection.type, polymorphic_type) + end + + scope + end + + def transform_value(value) + value_transformation.call(value) + end + + def next_chain_scope(scope, reflection, next_reflection) + primary_key = Array(reflection.join_primary_key) + foreign_key = Array(reflection.join_foreign_key) + + table = reflection.aliased_table + foreign_table = next_reflection.aliased_table + + primary_key_foreign_key_pairs = primary_key.zip(foreign_key) + constraints = primary_key_foreign_key_pairs.map do |join_primary_key, foreign_key| + table[join_primary_key].eq(foreign_table[foreign_key]) + end.inject(&:and) + + if reflection.type + value = transform_value(next_reflection.klass.polymorphic_name) + scope = apply_scope(scope, table, reflection.type, value) + end + + scope.joins!(join(foreign_table, constraints)) + end + + class ReflectionProxy < SimpleDelegator # :nodoc: + attr_reader :aliased_table + + def initialize(reflection, aliased_table) + super(reflection) + @aliased_table = aliased_table + end + + def all_includes; nil; end + end + + def get_chain(reflection, association, tracker) + name = reflection.name + chain = [Reflection::RuntimeReflection.new(reflection, association)] + reflection.chain.drop(1).each do |refl| + aliased_table = tracker.aliased_table_for(refl.klass.arel_table) do + refl.alias_candidate(name) + end + chain << ReflectionProxy.new(refl, aliased_table) + end + chain + end + + def add_constraints(scope, owner, chain) + scope = last_chain_scope(scope, chain.last, owner) + + chain.each_cons(2) do |reflection, next_reflection| + scope = next_chain_scope(scope, reflection, next_reflection) + end + + chain_head = chain.first + chain.reverse_each do |reflection| + reflection.constraints.each do |scope_chain_item| + item = eval_scope(reflection, scope_chain_item, owner) + + if scope_chain_item == chain_head.scope + scope.merge! item.except(:where, :includes, :unscope, :order) + elsif !item.references_values.empty? + scope.merge! item.only(:joins, :left_outer_joins) + + associations = item.eager_load_values | item.includes_values + + unless associations.empty? + scope.joins! item.construct_join_dependency(associations, Arel::Nodes::OuterJoin) + end + end + + reflection.all_includes do + scope.includes_values |= item.includes_values + end + + scope.unscope!(*item.unscope_values) + scope.where_clause += item.where_clause + scope.order_values = item.order_values | scope.order_values + end + end + + scope + end + + def apply_scope(scope, table, key, value) + if scope.table == table + scope.where!(key => value) + else + scope.where!(table.name => { key => value }) + end + end + + def eval_scope(reflection, scope, owner) + relation = reflection.build_scope(reflection.aliased_table) + relation.instance_exec(owner, &scope) || relation + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/belongs_to_association.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/belongs_to_association.rb new file mode 100644 index 00000000..9ed2db07 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/belongs_to_association.rb @@ -0,0 +1,169 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # = Active Record Belongs To Association + class BelongsToAssociation < SingularAssociation # :nodoc: + def handle_dependency + return unless load_target + + case options[:dependent] + when :destroy + raise ActiveRecord::Rollback unless target.destroy + when :destroy_async + if reflection.foreign_key.is_a?(Array) + primary_key_column = reflection.active_record_primary_key + id = reflection.foreign_key.map { |col| owner.public_send(col) } + else + primary_key_column = reflection.active_record_primary_key + id = owner.public_send(reflection.foreign_key) + end + + association_class = if reflection.polymorphic? + owner.public_send(reflection.foreign_type) + else + reflection.klass + end + + enqueue_destroy_association( + owner_model_name: owner.class.to_s, + owner_id: owner.id, + association_class: association_class.to_s, + association_ids: [id], + association_primary_key_column: primary_key_column, + ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil) + ) + else + target.public_send(options[:dependent]) + end + end + + def inversed_from(record) + replace_keys(record) + super + end + + def default(&block) + writer(owner.instance_exec(&block)) if reader.nil? + end + + def reset + super + @updated = false + end + + def updated? + @updated + end + + def decrement_counters + update_counters(-1) + end + + def increment_counters + update_counters(1) + end + + def decrement_counters_before_last_save + if reflection.polymorphic? + model_type_was = owner.attribute_before_last_save(reflection.foreign_type) + model_was = owner.class.polymorphic_class_for(model_type_was) if model_type_was + else + model_was = klass + end + + foreign_key_was = owner.attribute_before_last_save(reflection.foreign_key) + + if foreign_key_was && model_was < ActiveRecord::Base + update_counters_via_scope(model_was, foreign_key_was, -1) + end + end + + def target_changed? + owner.attribute_changed?(reflection.foreign_key) || (!foreign_key_present? && target&.new_record?) + end + + def target_previously_changed? + owner.attribute_previously_changed?(reflection.foreign_key) + end + + def saved_change_to_target? + owner.saved_change_to_attribute?(reflection.foreign_key) + end + + private + def replace(record) + if record + raise_on_type_mismatch!(record) + set_inverse_instance(record) + @updated = true + elsif target + remove_inverse_instance(target) + end + + replace_keys(record, force: true) + + self.target = record + end + + def update_counters(by) + if require_counter_update? && foreign_key_present? + if target && !stale_target? + target.increment!(reflection.counter_cache_column, by, touch: reflection.options[:touch]) + else + update_counters_via_scope(klass, owner._read_attribute(reflection.foreign_key), by) + end + end + end + + def update_counters_via_scope(klass, foreign_key, by) + scope = klass.unscoped.where!(primary_key(klass) => foreign_key) + scope.update_counters(reflection.counter_cache_column => by, touch: reflection.options[:touch]) + end + + def find_target? + !loaded? && foreign_key_present? && klass + end + + def require_counter_update? + reflection.counter_cache_column && owner.persisted? + end + + def replace_keys(record, force: false) + reflection_fk = reflection.foreign_key + if reflection_fk.is_a?(Array) + target_key_values = record ? Array(primary_key(record.class)).map { |key| record._read_attribute(key) } : [] + + if force || reflection_fk.map { |fk| owner._read_attribute(fk) } != target_key_values + reflection_fk.each_with_index do |key, index| + owner[key] = target_key_values[index] + end + end + else + target_key_value = record ? record._read_attribute(primary_key(record.class)) : nil + + if force || owner._read_attribute(reflection_fk) != target_key_value + owner[reflection_fk] = target_key_value + end + end + end + + def primary_key(klass) + reflection.association_primary_key(klass) + end + + def foreign_key_present? + Array(reflection.foreign_key).all? { |fk| owner._read_attribute(fk) } + end + + def invertible_for?(record) + inverse = inverse_reflection_for(record) + inverse && (inverse.has_one? || inverse.klass.has_many_inversing) + end + + def stale_state + owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) } + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/belongs_to_polymorphic_association.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/belongs_to_polymorphic_association.rb new file mode 100644 index 00000000..a6e5ee3b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/belongs_to_polymorphic_association.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # = Active Record Belongs To Polymorphic Association + class BelongsToPolymorphicAssociation < BelongsToAssociation # :nodoc: + def klass + type = owner[reflection.foreign_type] + type.presence && owner.class.polymorphic_class_for(type) + end + + def target_changed? + super || owner.attribute_changed?(reflection.foreign_type) + end + + def target_previously_changed? + super || owner.attribute_previously_changed?(reflection.foreign_type) + end + + def saved_change_to_target? + super || owner.saved_change_to_attribute?(reflection.foreign_type) + end + + private + def replace_keys(record, force: false) + super + + target_type = record ? record.class.polymorphic_name : nil + + if force || owner._read_attribute(reflection.foreign_type) != target_type + owner[reflection.foreign_type] = target_type + end + end + + def inverse_reflection_for(record) + reflection.polymorphic_inverse_of(record.class) + end + + def raise_on_type_mismatch!(record) + # A polymorphic association cannot have a type mismatch, by definition + end + + def stale_state + if foreign_key = super + [foreign_key, owner[reflection.foreign_type]] + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/association.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/association.rb new file mode 100644 index 00000000..0f80a2e8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/association.rb @@ -0,0 +1,170 @@ +# frozen_string_literal: true + +# This is the parent Association class which defines the variables +# used by all associations. +# +# The hierarchy is defined as follows: +# Association +# - SingularAssociation +# - BelongsToAssociation +# - HasOneAssociation +# - CollectionAssociation +# - HasManyAssociation + +module ActiveRecord::Associations::Builder # :nodoc: + class Association # :nodoc: + class << self + attr_accessor :extensions + end + self.extensions = [] + + VALID_OPTIONS = [ + :class_name, :anonymous_class, :primary_key, :foreign_key, :dependent, :validate, :inverse_of, :strict_loading, :query_constraints + ].freeze # :nodoc: + + def self.build(model, name, scope, options, &block) + if model.dangerous_attribute_method?(name) + raise ArgumentError, "You tried to define an association named #{name} on the model #{model.name}, but " \ + "this will conflict with a method #{name} already defined by Active Record. " \ + "Please choose a different association name." + end + + reflection = create_reflection(model, name, scope, options, &block) + define_accessors(model, reflection) + define_callbacks(model, reflection) + define_validations(model, reflection) + define_change_tracking_methods(model, reflection) + reflection + end + + def self.create_reflection(model, name, scope, options, &block) + raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol) + + validate_options(options) + + extension = define_extensions(model, name, &block) + options[:extend] = [*options[:extend], extension] if extension + + scope = build_scope(scope) + + ActiveRecord::Reflection.create(macro, name, scope, options, model) + end + + def self.build_scope(scope) + if scope && scope.arity == 0 + proc { instance_exec(&scope) } + else + scope + end + end + + def self.macro + raise NotImplementedError + end + + def self.valid_options(options) + VALID_OPTIONS + Association.extensions.flat_map(&:valid_options) + end + + def self.validate_options(options) + options.assert_valid_keys(valid_options(options)) + end + + def self.define_extensions(model, name) + # noop + end + + def self.define_callbacks(model, reflection) + if dependent = reflection.options[:dependent] + check_dependent_options(dependent, model) + add_destroy_callbacks(model, reflection) + add_after_commit_jobs_callback(model, dependent) + end + + Association.extensions.each do |extension| + extension.build(model, reflection) + end + end + + # Defines the setter and getter methods for the association + # class Post < ActiveRecord::Base + # has_many :comments + # end + # + # Post.first.comments and Post.first.comments= methods are defined by this method... + def self.define_accessors(model, reflection) + mixin = model.generated_association_methods + name = reflection.name + define_readers(mixin, name) + define_writers(mixin, name) + end + + def self.define_readers(mixin, name) + mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{name} + association(:#{name}).reader + end + CODE + end + + def self.define_writers(mixin, name) + mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{name}=(value) + association(:#{name}).writer(value) + end + CODE + end + + def self.define_validations(model, reflection) + # noop + end + + def self.define_change_tracking_methods(model, reflection) + # noop + end + + def self.valid_dependent_options + raise NotImplementedError + end + + def self.check_dependent_options(dependent, model) + if dependent == :destroy_async && !model.destroy_association_async_job + err_message = "A valid destroy_association_async_job is required to use `dependent: :destroy_async` on associations" + raise ActiveRecord::ConfigurationError, err_message + end + unless valid_dependent_options.include?(dependent) + raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}" + end + end + + def self.add_destroy_callbacks(model, reflection) + name = reflection.name + model.before_destroy(->(o) { o.association(name).handle_dependency }) + end + + def self.add_after_commit_jobs_callback(model, dependent) + if dependent == :destroy_async + mixin = model.generated_association_methods + + unless mixin.method_defined?(:_after_commit_jobs) + model.after_commit(-> do + _after_commit_jobs.each do |job_class, job_arguments| + job_class.perform_later(**job_arguments) + end + end) + + mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def _after_commit_jobs + @_after_commit_jobs ||= [] + end + CODE + end + end + end + + private_class_method :build_scope, :macro, :valid_options, :validate_options, :define_extensions, + :define_callbacks, :define_accessors, :define_readers, :define_writers, :define_validations, + :define_change_tracking_methods, :valid_dependent_options, :check_dependent_options, + :add_destroy_callbacks, :add_after_commit_jobs_callback + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/belongs_to.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/belongs_to.rb new file mode 100644 index 00000000..aa5a7c3d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/belongs_to.rb @@ -0,0 +1,160 @@ +# frozen_string_literal: true + +module ActiveRecord::Associations::Builder # :nodoc: + class BelongsTo < SingularAssociation # :nodoc: + def self.macro + :belongs_to + end + + def self.valid_options(options) + valid = super + [:polymorphic, :counter_cache, :optional, :default] + valid += [:foreign_type] if options[:polymorphic] + valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async + valid + end + + def self.valid_dependent_options + [:destroy, :delete, :destroy_async] + end + + def self.define_callbacks(model, reflection) + super + add_counter_cache_callbacks(model, reflection) if reflection.options[:counter_cache] + add_touch_callbacks(model, reflection) if reflection.options[:touch] + add_default_callbacks(model, reflection) if reflection.options[:default] + end + + def self.add_counter_cache_callbacks(model, reflection) + cache_column = reflection.counter_cache_column + + model.after_update lambda { |record| + association = association(reflection.name) + + if association.saved_change_to_target? + association.increment_counters + association.decrement_counters_before_last_save + end + } + + klass = reflection.class_name.safe_constantize + klass._counter_cache_columns |= [cache_column] if klass && klass.respond_to?(:_counter_cache_columns) + model.counter_cached_association_names |= [reflection.name] + end + + def self.touch_record(o, changes, foreign_key, name, touch) # :nodoc: + old_foreign_id = changes[foreign_key] && changes[foreign_key].first + + if old_foreign_id + association = o.association(name) + reflection = association.reflection + if reflection.polymorphic? + foreign_type = reflection.foreign_type + klass = changes[foreign_type] && changes[foreign_type].first || o.public_send(foreign_type) + klass = o.class.polymorphic_class_for(klass) + else + klass = association.klass + end + primary_key = reflection.association_primary_key(klass) + old_record = klass.find_by(primary_key => old_foreign_id) + + if old_record + if touch != true + old_record.touch_later(touch) + else + old_record.touch_later + end + end + end + + record = o.public_send name + if record && record.persisted? + if touch != true + record.touch_later(touch) + else + record.touch_later + end + end + end + + def self.add_touch_callbacks(model, reflection) + foreign_key = reflection.foreign_key + name = reflection.name + touch = reflection.options[:touch] + + callback = lambda { |changes_method| lambda { |record| + BelongsTo.touch_record(record, record.send(changes_method), foreign_key, name, touch) + }} + + if reflection.counter_cache_column + touch_callback = callback.(:saved_changes) + update_callback = lambda { |record| + instance_exec(record, &touch_callback) unless association(reflection.name).saved_change_to_target? + } + model.after_update update_callback, if: :saved_changes? + else + model.after_create callback.(:saved_changes), if: :saved_changes? + model.after_update callback.(:saved_changes), if: :saved_changes? + model.after_destroy callback.(:changes_to_save) + end + + model.after_touch callback.(:changes_to_save) + end + + def self.add_default_callbacks(model, reflection) + model.before_validation lambda { |o| + o.association(reflection.name).default(&reflection.options[:default]) + } + end + + def self.add_destroy_callbacks(model, reflection) + model.after_destroy lambda { |o| o.association(reflection.name).handle_dependency } + end + + def self.define_validations(model, reflection) + if reflection.options.key?(:required) + reflection.options[:optional] = !reflection.options.delete(:required) + end + + if reflection.options[:optional].nil? + required = model.belongs_to_required_by_default + else + required = !reflection.options[:optional] + end + + super + + if required + if ActiveRecord.belongs_to_required_validates_foreign_key + model.validates_presence_of reflection.name, message: :required + else + condition = lambda { |record| + foreign_key = reflection.foreign_key + foreign_type = reflection.foreign_type + + record.read_attribute(foreign_key).nil? || + record.attribute_changed?(foreign_key) || + (reflection.polymorphic? && (record.read_attribute(foreign_type).nil? || record.attribute_changed?(foreign_type))) + } + + model.validates_presence_of reflection.name, message: :required, if: condition + end + end + end + + def self.define_change_tracking_methods(model, reflection) + model.generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{reflection.name}_changed? + association(:#{reflection.name}).target_changed? + end + + def #{reflection.name}_previously_changed? + association(:#{reflection.name}).target_previously_changed? + end + CODE + end + + private_class_method :macro, :valid_options, :valid_dependent_options, :define_callbacks, + :define_validations, :define_change_tracking_methods, :add_counter_cache_callbacks, + :add_touch_callbacks, :add_default_callbacks, :add_destroy_callbacks + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/collection_association.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/collection_association.rb new file mode 100644 index 00000000..391b2e4d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/collection_association.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require "active_record/associations" + +module ActiveRecord::Associations::Builder # :nodoc: + class CollectionAssociation < Association # :nodoc: + CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove] + + def self.valid_options(options) + super + [:before_add, :after_add, :before_remove, :after_remove, :extend] + end + + def self.define_callbacks(model, reflection) + super + name = reflection.name + options = reflection.options + CALLBACKS.each { |callback_name| + define_callback(model, callback_name, name, options) + } + end + + def self.define_extensions(model, name, &block) + if block_given? + extension_module_name = "#{name.to_s.camelize}AssociationExtension" + extension = Module.new(&block) + model.const_set(extension_module_name, extension) + end + end + + def self.define_callback(model, callback_name, name, options) + full_callback_name = "#{callback_name}_for_#{name}" + + callback_values = Array(options[callback_name.to_sym]) + method_defined = model.respond_to?(full_callback_name) + + # If there are no callbacks, we must also check if a superclass had + # previously defined this association + return if callback_values.empty? && !method_defined + + unless method_defined + model.class_attribute(full_callback_name, instance_accessor: false, instance_predicate: false) + end + + callbacks = callback_values.map do |callback| + case callback + when Symbol + ->(method, owner, record) { owner.send(callback, record) } + when Proc + ->(method, owner, record) { callback.call(owner, record) } + else + ->(method, owner, record) { callback.send(method, owner, record) } + end + end + model.send "#{full_callback_name}=", callbacks + end + + # Defines the setter and getter methods for the collection_singular_ids. + def self.define_readers(mixin, name) + super + + mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{name.to_s.singularize}_ids + association(:#{name}).ids_reader + end + CODE + end + + def self.define_writers(mixin, name) + super + + mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{name.to_s.singularize}_ids=(ids) + association(:#{name}).ids_writer(ids) + end + CODE + end + + private_class_method :valid_options, :define_callback, :define_extensions, :define_readers, :define_writers + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/has_and_belongs_to_many.rb new file mode 100644 index 00000000..c08fa48f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/has_and_belongs_to_many.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +module ActiveRecord::Associations::Builder # :nodoc: + class HasAndBelongsToMany # :nodoc: + attr_reader :lhs_model, :association_name, :options + + def initialize(association_name, lhs_model, options) + @association_name = association_name + @lhs_model = lhs_model + @options = options + end + + def through_model + join_model = Class.new(ActiveRecord::Base) { + class << self + attr_accessor :left_model + attr_accessor :name + attr_accessor :table_name_resolver + attr_accessor :left_reflection + attr_accessor :right_reflection + end + + @table_name = nil + def self.table_name + # Table name needs to be resolved lazily + # because RHS class might not have been loaded + @table_name ||= table_name_resolver.call + end + + def self.compute_type(class_name) + left_model.compute_type class_name + end + + def self.add_left_association(name, options) + belongs_to name, required: false, **options + self.left_reflection = _reflect_on_association(name) + end + + def self.add_right_association(name, options) + rhs_name = name.to_s.singularize.to_sym + belongs_to rhs_name, required: false, **options + self.right_reflection = _reflect_on_association(rhs_name) + end + + def self.connection_pool + left_model.connection_pool + end + } + + join_model.name = "HABTM_#{association_name.to_s.camelize}" + join_model.table_name_resolver = -> { table_name } + join_model.left_model = lhs_model + + join_model.add_left_association :left_side, anonymous_class: lhs_model + join_model.add_right_association association_name, belongs_to_options(options) + join_model + end + + def middle_reflection(join_model) + middle_name = [lhs_model.name.downcase.pluralize, + association_name.to_s].sort.join("_").gsub("::", "_").to_sym + middle_options = middle_options join_model + + HasMany.create_reflection(lhs_model, + middle_name, + nil, + middle_options) + end + + private + def middle_options(join_model) + middle_options = {} + middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}" + if options.key? :foreign_key + middle_options[:foreign_key] = options[:foreign_key] + end + middle_options + end + + def table_name + if options[:join_table] + options[:join_table].to_s + else + class_name = options.fetch(:class_name) { + association_name.to_s.camelize.singularize + } + klass = lhs_model.send(:compute_type, class_name.to_s) + [lhs_model.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_") + end + end + + def belongs_to_options(options) + rhs_options = {} + + if options.key? :class_name + rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key + rhs_options[:class_name] = options[:class_name] + end + + if options.key? :association_foreign_key + rhs_options[:foreign_key] = options[:association_foreign_key] + end + + rhs_options + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/has_many.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/has_many.rb new file mode 100644 index 00000000..dfc3da0f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/has_many.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module ActiveRecord::Associations::Builder # :nodoc: + class HasMany < CollectionAssociation # :nodoc: + def self.macro + :has_many + end + + def self.valid_options(options) + valid = super + [:counter_cache, :join_table, :index_errors, :as, :through] + valid += [:foreign_type] if options[:as] + valid += [:source, :source_type, :disable_joins] if options[:through] + valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async + valid + end + + def self.valid_dependent_options + [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception, :destroy_async] + end + + private_class_method :macro, :valid_options, :valid_dependent_options + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/has_one.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/has_one.rb new file mode 100644 index 00000000..73e0fa38 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/has_one.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module ActiveRecord::Associations::Builder # :nodoc: + class HasOne < SingularAssociation # :nodoc: + def self.macro + :has_one + end + + def self.valid_options(options) + valid = super + [:as, :through] + valid += [:foreign_type] if options[:as] + valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async + valid += [:source, :source_type, :disable_joins] if options[:through] + valid + end + + def self.valid_dependent_options + [:destroy, :destroy_async, :delete, :nullify, :restrict_with_error, :restrict_with_exception] + end + + def self.define_callbacks(model, reflection) + super + add_touch_callbacks(model, reflection) if reflection.options[:touch] + end + + def self.add_destroy_callbacks(model, reflection) + super unless reflection.options[:through] + end + + def self.define_validations(model, reflection) + super + if reflection.options[:required] + model.validates_presence_of reflection.name, message: :required + end + end + + def self.touch_record(record, name, touch) + instance = record.send(name) + + if instance&.persisted? + touch != true ? + instance.touch(touch) : instance.touch + end + end + + def self.add_touch_callbacks(model, reflection) + name = reflection.name + touch = reflection.options[:touch] + + callback = -> (record) { HasOne.touch_record(record, name, touch) } + model.after_create callback, if: :saved_changes? + model.after_create_commit { association(name).reset_negative_cache } + model.after_update callback, if: :saved_changes? + model.after_destroy callback + model.after_touch callback + end + + private_class_method :macro, :valid_options, :valid_dependent_options, :add_destroy_callbacks, + :define_callbacks, :define_validations, :add_touch_callbacks + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/singular_association.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/singular_association.rb new file mode 100644 index 00000000..b66ca141 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/builder/singular_association.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +# This class is inherited by the has_one and belongs_to association classes + +module ActiveRecord::Associations::Builder # :nodoc: + class SingularAssociation < Association # :nodoc: + def self.valid_options(options) + super + [:required, :touch] + end + + def self.define_accessors(model, reflection) + super + mixin = model.generated_association_methods + name = reflection.name + + define_constructors(mixin, name) unless reflection.polymorphic? + + mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def reload_#{name} + association(:#{name}).force_reload_reader + end + + def reset_#{name} + association(:#{name}).reset + end + CODE + end + + # Defines the (build|create)_association methods for belongs_to or has_one association + def self.define_constructors(mixin, name) + mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def build_#{name}(*args, &block) + association(:#{name}).build(*args, &block) + end + + def create_#{name}(*args, &block) + association(:#{name}).create(*args, &block) + end + + def create_#{name}!(*args, &block) + association(:#{name}).create!(*args, &block) + end + CODE + end + + private_class_method :valid_options, :define_accessors, :define_constructors + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/collection_association.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/collection_association.rb new file mode 100644 index 00000000..bfef699e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/collection_association.rb @@ -0,0 +1,535 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" + +module ActiveRecord + module Associations + # = Active Record Association Collection + # + # CollectionAssociation is an abstract class that provides common stuff to + # ease the implementation of association proxies that represent + # collections. See the class hierarchy in Association. + # + # CollectionAssociation: + # HasManyAssociation => has_many + # HasManyThroughAssociation + ThroughAssociation => has_many :through + # + # The CollectionAssociation class provides common methods to the collections + # defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with + # the :through association option. + # + # You need to be careful with assumptions regarding the target: The proxy + # does not fetch records from the database until it needs them, but new + # ones created with +build+ are added to the target. So, the target may be + # non-empty and still lack children waiting to be read from the database. + # If you look directly to the database you cannot assume that's the entire + # collection because new records may have been added to the target, etc. + # + # If you need to work on all current children, new and existing records, + # +load_target+ and the +loaded+ flag are your friends. + class CollectionAssociation < Association # :nodoc: + attr_accessor :nested_attributes_target + + # Implements the reader method, e.g. foo.items for Foo.has_many :items + def reader + ensure_klass_exists! + + if stale_target? + reload + end + + @proxy ||= CollectionProxy.create(klass, self) + @proxy.reset_scope + end + + # Implements the writer method, e.g. foo.items= for Foo.has_many :items + def writer(records) + replace(records) + end + + # Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items + def ids_reader + if loaded? + target.pluck(*reflection.association_primary_key) + elsif !target.empty? + load_target.pluck(*reflection.association_primary_key) + else + @association_ids ||= scope.pluck(*reflection.association_primary_key) + end + end + + # Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items + def ids_writer(ids) + primary_key = reflection.association_primary_key + pk_type = klass.type_for_attribute(primary_key) + ids = Array(ids).compact_blank + ids.map! { |id| pk_type.cast(id) } + + records = if klass.composite_primary_key? + klass.where(primary_key => ids).index_by do |record| + primary_key.map { |primary_key| record._read_attribute(primary_key) } + end + else + klass.where(primary_key => ids).index_by do |record| + record._read_attribute(primary_key) + end + end.values_at(*ids).compact + + if records.size != ids.size + found_ids = records.map { |record| record._read_attribute(primary_key) } + not_found_ids = ids - found_ids + klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, primary_key, not_found_ids) + else + replace(records) + end + end + + def reset + super + @target = [] + @replaced_or_added_targets = Set.new.compare_by_identity + @association_ids = nil + end + + def find(*args) + if options[:inverse_of] && loaded? + args_flatten = args.flatten + model = scope.model + + if args_flatten.blank? + error_message = "Couldn't find #{model.name} without an ID" + raise RecordNotFound.new(error_message, model.name, model.primary_key, args) + end + + result = find_by_scan(*args) + + result_size = Array(result).size + if !result || result_size != args_flatten.size + scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size) + else + result + end + else + scope.find(*args) + end + end + + def build(attributes = nil, &block) + if attributes.is_a?(Array) + attributes.collect { |attr| build(attr, &block) } + else + add_to_target(build_record(attributes, &block), replace: true) + end + end + + # Add +records+ to this association. Since +<<+ flattens its argument list + # and inserts each record, +push+ and +concat+ behave identically. + def concat(*records) + records = records.flatten + if owner.new_record? + skip_strict_loading { load_target } + concat_records(records) + else + transaction { concat_records(records) } + end + end + + # Removes all records from the association without calling callbacks + # on the associated records. It honors the +:dependent+ option. However + # if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+ + # deletion strategy for the association is applied. + # + # You can force a particular deletion strategy by passing a parameter. + # + # Example: + # + # @author.books.delete_all(:nullify) + # @author.books.delete_all(:delete_all) + # + # See delete for more info. + def delete_all(dependent = nil) + if dependent && ![:nullify, :delete_all].include?(dependent) + raise ArgumentError, "Valid values are :nullify or :delete_all" + end + + dependent = if dependent + dependent + elsif options[:dependent] == :destroy + :delete_all + else + options[:dependent] + end + + delete_or_nullify_all_records(dependent).tap do + reset + loaded! + end + end + + # Destroy all the records from this association. + # + # See destroy for more info. + def destroy_all + destroy(load_target).tap do + reset + loaded! + end + end + + # Removes +records+ from this association calling +before_remove+ and + # +after_remove+ callbacks. + # + # This method is abstract in the sense that +delete_records+ has to be + # provided by descendants. Note this method does not imply the records + # are actually removed from the database, that depends precisely on + # +delete_records+. They are in any case removed from the collection. + def delete(*records) + delete_or_destroy(records, options[:dependent]) + end + + # Deletes the +records+ and removes them from this association calling + # +before_remove+, +after_remove+, +before_destroy+ and +after_destroy+ callbacks. + # + # Note that this method removes records from the database ignoring the + # +:dependent+ option. + def destroy(*records) + delete_or_destroy(records, :destroy) + end + + # Returns the size of the collection by executing a SELECT COUNT(*) + # query if the collection hasn't been loaded, and calling + # collection.size if it has. + # + # If the collection has been already loaded +size+ and +length+ are + # equivalent. If not and you are going to need the records anyway + # +length+ will take one less query. Otherwise +size+ is more efficient. + # + # This method is abstract in the sense that it relies on + # +count_records+, which is a method descendants have to provide. + def size + if !find_target? || loaded? + target.size + elsif @association_ids + @association_ids.size + elsif !association_scope.group_values.empty? + load_target.size + elsif !association_scope.distinct_value && !target.empty? + unsaved_records = target.select(&:new_record?) + unsaved_records.size + count_records + else + count_records + end + end + + # Returns true if the collection is empty. + # + # If the collection has been loaded + # it is equivalent to collection.size.zero?. If the + # collection has not been loaded, it is equivalent to + # !collection.exists?. If the collection has not already been + # loaded and you are going to fetch the records anyway it is better to + # check collection.length.zero?. + def empty? + if loaded? || @association_ids || reflection.has_active_cached_counter? + size.zero? + else + target.empty? && !scope.exists? + end + end + + # Replace this collection with +other_array+. This will perform a diff + # and delete/add only records that have changed. + def replace(other_array) + other_array.each { |val| raise_on_type_mismatch!(val) } + original_target = skip_strict_loading { load_target }.dup + + if owner.new_record? + replace_records(other_array, original_target) + else + replace_common_records_in_memory(other_array, original_target) + if other_array != original_target + transaction { replace_records(other_array, original_target) } + else + other_array + end + end + end + + def include?(record) + klass = reflection.klass + return false unless record.is_a?(klass) + + if record.new_record? + include_in_memory?(record) + elsif loaded? + target.include?(record) + else + record_id = klass.composite_primary_key? ? klass.primary_key.zip(record.id).to_h : record.id + scope.exists?(record_id) + end + end + + def load_target + if find_target? + @target = merge_target_lists(find_target, target) + end + + loaded! + target + end + + def add_to_target(record, skip_callbacks: false, replace: false, &block) + replace_on_target(record, skip_callbacks, replace: replace || association_scope.distinct_value, &block) + end + + def target=(record) + return super unless reflection.klass.has_many_inversing + + case record + when nil + # It's not possible to remove the record from the inverse association. + when Array + super + else + replace_on_target(record, true, replace: true, inversing: true) + end + end + + def scope + scope = super + scope.none! if null_scope? + scope + end + + def null_scope? + owner.new_record? && !foreign_key_present? + end + + def find_from_target? + loaded? || + (owner.strict_loading? && owner.strict_loading_all?) || + reflection.strict_loading? || + owner.new_record? || + target.any? { |record| record.new_record? || record.changed? } + end + + def collection? + true + end + + private + def transaction(&block) + reflection.klass.transaction(&block) + end + + # We have some records loaded from the database (persisted) and some that are + # in-memory (memory). The same record may be represented in the persisted array + # and in the memory array. + # + # So the task of this method is to merge them according to the following rules: + # + # * The final array must not have duplicates + # * The order of the persisted array is to be preserved + # * Any changes made to attributes on objects in the memory array are to be preserved + # * Otherwise, attributes should have the value found in the database + def merge_target_lists(persisted, memory) + return persisted if memory.empty? + + persisted.map! do |record| + if mem_record = memory.delete(record) + + ((record.attribute_names & mem_record.attribute_names) - mem_record.changed_attribute_names_to_save - mem_record.class._attr_readonly).each do |name| + mem_record._write_attribute(name, record[name]) + end + + mem_record + else + record + end + end + + persisted + memory.reject(&:persisted?) + end + + def _create_record(attributes, raise = false, &block) + unless owner.persisted? + raise ActiveRecord::RecordNotSaved.new("You cannot call create unless the parent is saved", owner) + end + + if attributes.is_a?(Array) + attributes.collect { |attr| _create_record(attr, raise, &block) } + else + record = build_record(attributes, &block) + transaction do + result = nil + add_to_target(record) do + result = insert_record(record, true, raise) { + @_was_loaded = loaded? + } + end + raise ActiveRecord::Rollback unless result + end + record + end + end + + # Do the relevant stuff to insert the given record into the association collection. + def insert_record(record, validate = true, raise = false, &block) + if raise + record.save!(validate: validate, &block) + else + record.save(validate: validate, &block) + end + end + + def delete_or_destroy(records, method) + return if records.empty? + records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) } + records = records.flatten + records.each { |record| raise_on_type_mismatch!(record) } + existing_records = records.reject(&:new_record?) + + if existing_records.empty? + remove_records(existing_records, records, method) + else + transaction { remove_records(existing_records, records, method) } + end + end + + def remove_records(existing_records, records, method) + catch(:abort) do + records.each { |record| callback(:before_remove, record) } + end || return + + delete_records(existing_records, method) if existing_records.any? + @target -= records + @association_ids = nil + + records.each { |record| callback(:after_remove, record) } + end + + # Delete the given records from the association, + # using one of the methods +:destroy+, +:delete_all+ + # or +:nullify+ (or +nil+, in which case a default is used). + def delete_records(records, method) + raise NotImplementedError + end + + def replace_records(new_target, original_target) + delete(difference(target, new_target)) + + unless concat(difference(new_target, target)) + @target = original_target + raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \ + "new records could not be saved." + end + + target + end + + def replace_common_records_in_memory(new_target, original_target) + common_records = intersection(new_target, original_target) + common_records.each do |record| + skip_callbacks = true + replace_on_target(record, skip_callbacks, replace: true) + end + end + + def concat_records(records, raise = false) + result = true + + records.each do |record| + raise_on_type_mismatch!(record) + add_to_target(record) do + unless owner.new_record? + result &&= insert_record(record, true, raise) { + @_was_loaded = loaded? + } + end + end + end + + raise ActiveRecord::Rollback unless result + + records + end + + def replace_on_target(record, skip_callbacks, replace:, inversing: false) + if replace && (!record.new_record? || @replaced_or_added_targets.include?(record)) + index = @target.index(record) + end + + catch(:abort) do + callback(:before_add, record) + end || return unless skip_callbacks + + set_inverse_instance(record) + + @_was_loaded = true + + yield(record) if block_given? + + if !index && @replaced_or_added_targets.include?(record) + index = @target.index(record) + end + + @replaced_or_added_targets << record if inversing || index || record.new_record? + + if index + target[index] = record + elsif @_was_loaded || !loaded? + @association_ids = nil + target << record + end + + callback(:after_add, record) unless skip_callbacks + + record + ensure + @_was_loaded = nil + end + + def callback(method, record) + callbacks_for(method).each do |callback| + callback.call(method, owner, record) + end + end + + def callbacks_for(callback_name) + full_callback_name = "#{callback_name}_for_#{reflection.name}" + if owner.class.respond_to?(full_callback_name) + owner.class.send(full_callback_name) + else + [] + end + end + + def include_in_memory?(record) + if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection) + assoc = owner.association(reflection.through_reflection.name) + assoc.reader.any? { |source| + target_reflection = source.send(reflection.source_reflection.name) + target_reflection.respond_to?(:include?) ? target_reflection.include?(record) : target_reflection == record + } || target.include?(record) + else + target.include?(record) + end + end + + # If the :inverse_of option has been + # specified, then #find scans the entire collection. + def find_by_scan(*args) + expects_array = args.first.kind_of?(Array) + ids = args.flatten.compact.map(&:to_s).uniq + + if ids.size == 1 + id = ids.first + record = load_target.detect { |r| id == r.id.to_s } + expects_array ? [ record ] : record + else + load_target.select { |r| ids.include?(r.id.to_s) } + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/collection_proxy.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/collection_proxy.rb new file mode 100644 index 00000000..e5a84910 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/collection_proxy.rb @@ -0,0 +1,1163 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # = Active Record Collection Proxy + # + # Collection proxies in Active Record are middlemen between an + # association, and its target result set. + # + # For example, given + # + # class Blog < ActiveRecord::Base + # has_many :posts + # end + # + # blog = Blog.first + # + # The collection proxy returned by blog.posts is built from a + # :has_many association, and delegates to a collection + # of posts as the target. + # + # This class delegates unknown methods to the association's + # relation class via a delegate cache. + # + # The target result set is not loaded until needed. For example, + # + # blog.posts.count + # + # is computed directly through SQL and does not trigger by itself the + # instantiation of the actual post records. + class CollectionProxy < Relation + def initialize(klass, association, **) # :nodoc: + @association = association + super klass + + extensions = association.extensions + extend(*extensions) if extensions.any? + end + + def target + @association.target + end + + def load_target + @association.load_target + end + + # Returns +true+ if the association has been loaded, otherwise +false+. + # + # person.pets.loaded? # => false + # person.pets.records + # person.pets.loaded? # => true + def loaded? + @association.loaded? + end + alias :loaded :loaded? + + ## + # :method: select + # + # :call-seq: + # select(*fields, &block) + # + # Works in two ways. + # + # *First:* Specify a subset of fields to be selected from the result set. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.select(:name) + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.select(:id, :name) + # # => [ + # # #, + # # #, + # # # + # # ] + # + # Be careful because this also means you're initializing a model + # object with only the fields that you've selected. If you attempt + # to access a field except +id+ that is not in the initialized record you'll + # receive: + # + # person.pets.select(:name).first.person_id + # # => ActiveModel::MissingAttributeError: missing attribute 'person_id' for Pet + # + # *Second:* You can pass a block so it can be used just like Array#select. + # This builds an array of objects from the database for the scope, + # converting them into an array and iterating through them using + # Array#select. + # + # person.pets.select { |pet| /oo/.match?(pet.name) } + # # => [ + # # #, + # # # + # # ] + + # Finds an object in the collection responding to the +id+. Uses the same + # rules as ActiveRecord::FinderMethods.find. Returns ActiveRecord::RecordNotFound + # error if the object cannot be found. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.find(1) # => # + # person.pets.find(4) # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=4 + # + # person.pets.find(2) { |pet| pet.name.downcase! } + # # => # + # + # person.pets.find(2, 3) + # # => [ + # # #, + # # # + # # ] + def find(*args) + return super if block_given? + @association.find(*args) + end + + ## + # :method: first + # + # :call-seq: + # first(limit = nil) + # + # Returns the first record, or the first +n+ records, from the collection. + # If the collection is empty, the first form returns +nil+, and the second + # form returns an empty array. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.first # => # + # + # person.pets.first(2) + # # => [ + # # #, + # # # + # # ] + # + # another_person_without.pets # => [] + # another_person_without.pets.first # => nil + # another_person_without.pets.first(3) # => [] + + ## + # :method: second + # + # :call-seq: + # second() + # + # Same as #first except returns only the second record. + + ## + # :method: third + # + # :call-seq: + # third() + # + # Same as #first except returns only the third record. + + ## + # :method: fourth + # + # :call-seq: + # fourth() + # + # Same as #first except returns only the fourth record. + + ## + # :method: fifth + # + # :call-seq: + # fifth() + # + # Same as #first except returns only the fifth record. + + ## + # :method: forty_two + # + # :call-seq: + # forty_two() + # + # Same as #first except returns only the forty second record. + # Also known as accessing "the reddit". + + ## + # :method: third_to_last + # + # :call-seq: + # third_to_last() + # + # Same as #last except returns only the third-to-last record. + + ## + # :method: second_to_last + # + # :call-seq: + # second_to_last() + # + # Same as #last except returns only the second-to-last record. + + # Returns the last record, or the last +n+ records, from the collection. + # If the collection is empty, the first form returns +nil+, and the second + # form returns an empty array. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.last # => # + # + # person.pets.last(2) + # # => [ + # # #, + # # # + # # ] + # + # another_person_without.pets # => [] + # another_person_without.pets.last # => nil + # another_person_without.pets.last(3) # => [] + def last(limit = nil) + load_target if find_from_target? + super + end + + # Gives a record (or N records if a parameter is supplied) from the collection + # using the same rules as ActiveRecord::FinderMethods.take. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.take # => # + # + # person.pets.take(2) + # # => [ + # # #, + # # # + # # ] + # + # another_person_without.pets # => [] + # another_person_without.pets.take # => nil + # another_person_without.pets.take(2) # => [] + def take(limit = nil) + load_target if find_from_target? + super + end + + # Returns a new object of the collection type that has been instantiated + # with +attributes+ and linked to this object, but have not yet been saved. + # You can pass an array of attributes hashes, this will return an array + # with the new objects. + # + # class Person + # has_many :pets + # end + # + # person.pets.build + # # => # + # + # person.pets.build(name: 'Fancy-Fancy') + # # => # + # + # person.pets.build([{name: 'Spook'}, {name: 'Choo-Choo'}, {name: 'Brain'}]) + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.size # => 5 # size of the collection + # person.pets.count # => 0 # count from database + def build(attributes = {}, &block) + @association.build(attributes, &block) + end + alias_method :new, :build + + # Returns a new object of the collection type that has been instantiated with + # attributes, linked to this object and that has already been saved (if it + # passes the validations). + # + # class Person + # has_many :pets + # end + # + # person.pets.create(name: 'Fancy-Fancy') + # # => # + # + # person.pets.create([{name: 'Spook'}, {name: 'Choo-Choo'}]) + # # => [ + # # #, + # # # + # # ] + # + # person.pets.size # => 3 + # person.pets.count # => 3 + # + # person.pets.find(1, 2, 3) + # # => [ + # # #, + # # #, + # # # + # # ] + def create(attributes = {}, &block) + @association.create(attributes, &block) + end + + # Like #create, except that if the record is invalid, raises an exception. + # + # class Person + # has_many :pets + # end + # + # class Pet + # validates :name, presence: true + # end + # + # person.pets.create!(name: nil) + # # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank + def create!(attributes = {}, &block) + @association.create!(attributes, &block) + end + + # Replaces this collection with +other_array+. This will perform a diff + # and delete/add only records that have changed. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [#] + # + # other_pets = [Pet.new(name: 'Puff', group: 'celebrities')] + # + # person.pets.replace(other_pets) + # + # person.pets + # # => [#] + # + # If the supplied array has an incorrect association type, it raises + # an ActiveRecord::AssociationTypeMismatch error: + # + # person.pets.replace(["doo", "ggie", "gaga"]) + # # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String + def replace(other_array) + @association.replace(other_array) + end + + # Deletes all the records from the collection according to the strategy + # specified by the +:dependent+ option. If no +:dependent+ option is given, + # then it will follow the default strategy. + # + # For has_many :through associations, the default deletion strategy is + # +:delete_all+. + # + # For +has_many+ associations, the default deletion strategy is +:nullify+. + # This sets the foreign keys to +NULL+. + # + # class Person < ActiveRecord::Base + # has_many :pets # dependent: :nullify option by default + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.delete_all + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.size # => 0 + # person.pets # => [] + # + # Pet.find(1, 2, 3) + # # => [ + # # #, + # # #, + # # # + # # ] + # + # Both +has_many+ and has_many :through dependencies default to the + # +:delete_all+ strategy if the +:dependent+ option is set to +:destroy+. + # Records are not instantiated and callbacks will not be fired. + # + # class Person < ActiveRecord::Base + # has_many :pets, dependent: :destroy + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.delete_all + # + # Pet.find(1, 2, 3) + # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3) + # + # If it is set to :delete_all, all the objects are deleted + # *without* calling their +destroy+ method. + # + # class Person < ActiveRecord::Base + # has_many :pets, dependent: :delete_all + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.delete_all + # + # Pet.find(1, 2, 3) + # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3) + def delete_all(dependent = nil) + @association.delete_all(dependent).tap { reset_scope } + end + + # Deletes the records of the collection directly from the database + # ignoring the +:dependent+ option. Records are instantiated and it + # invokes +before_remove+, +after_remove+, +before_destroy+, and + # +after_destroy+ callbacks. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.destroy_all + # + # person.pets.size # => 0 + # person.pets # => [] + # + # Pet.find(1) # => Couldn't find Pet with id=1 + def destroy_all + @association.destroy_all.tap { reset_scope } + end + + # Deletes the +records+ supplied from the collection according to the strategy + # specified by the +:dependent+ option. If no +:dependent+ option is given, + # then it will follow the default strategy. Returns an array with the + # deleted records. + # + # For has_many :through associations, the default deletion strategy is + # +:delete_all+. + # + # For +has_many+ associations, the default deletion strategy is +:nullify+. + # This sets the foreign keys to +NULL+. + # + # class Person < ActiveRecord::Base + # has_many :pets # dependent: :nullify option by default + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.delete(Pet.find(1)) + # # => [#] + # + # person.pets.size # => 2 + # person.pets + # # => [ + # # #, + # # # + # # ] + # + # Pet.find(1) + # # => # + # + # If it is set to :destroy all the +records+ are removed by calling + # their +destroy+ method. See +destroy+ for more information. + # + # class Person < ActiveRecord::Base + # has_many :pets, dependent: :destroy + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.delete(Pet.find(1), Pet.find(3)) + # # => [ + # # #, + # # # + # # ] + # + # person.pets.size # => 1 + # person.pets + # # => [#] + # + # Pet.find(1, 3) + # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 3) + # + # If it is set to :delete_all, all the +records+ are deleted + # *without* calling their +destroy+ method. + # + # class Person < ActiveRecord::Base + # has_many :pets, dependent: :delete_all + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.delete(Pet.find(1)) + # # => [#] + # + # person.pets.size # => 2 + # person.pets + # # => [ + # # #, + # # # + # # ] + # + # Pet.find(1) + # # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=1 + # + # You can pass +Integer+ or +String+ values, it finds the records + # responding to the +id+ and executes delete on them. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.delete("1") + # # => [#] + # + # person.pets.delete(2, 3) + # # => [ + # # #, + # # # + # # ] + def delete(*records) + @association.delete(*records).tap { reset_scope } + end + + # Destroys the +records+ supplied and removes them from the collection. + # This method will _always_ remove record from the database ignoring + # the +:dependent+ option. Returns an array with the removed records. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.destroy(Pet.find(1)) + # # => [#] + # + # person.pets.size # => 2 + # person.pets + # # => [ + # # #, + # # # + # # ] + # + # person.pets.destroy(Pet.find(2), Pet.find(3)) + # # => [ + # # #, + # # # + # # ] + # + # person.pets.size # => 0 + # person.pets # => [] + # + # Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3) + # + # You can pass +Integer+ or +String+ values, it finds the records + # responding to the +id+ and then deletes them from the database. + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.destroy("4") + # # => # + # + # person.pets.size # => 2 + # person.pets + # # => [ + # # #, + # # # + # # ] + # + # person.pets.destroy(5, 6) + # # => [ + # # #, + # # # + # # ] + # + # person.pets.size # => 0 + # person.pets # => [] + # + # Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (4, 5, 6) + def destroy(*records) + @association.destroy(*records).tap { reset_scope } + end + + ## + # :method: distinct + # + # :call-seq: + # distinct(value = true) + # + # Specifies whether the records should be unique or not. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.select(:name) + # # => [ + # # #, + # # # + # # ] + # + # person.pets.select(:name).distinct + # # => [#] + # + # person.pets.select(:name).distinct.distinct(false) + # # => [ + # # #, + # # # + # # ] + + #-- + def calculate(operation, column_name) + null_scope? ? scope.calculate(operation, column_name) : super + end + + def pluck(*column_names) + null_scope? ? scope.pluck(*column_names) : super + end + + ## + # :method: count + # + # :call-seq: + # count(column_name = nil, &block) + # + # Count all records. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # # This will perform the count using SQL. + # person.pets.count # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # Passing a block will select all of a person's pets in SQL and then + # perform the count using Ruby. + # + # person.pets.count { |pet| pet.name.include?('-') } # => 2 + + # Returns the size of the collection. If the collection hasn't been loaded, + # it executes a SELECT COUNT(*) query. Else it calls collection.size. + # + # If the collection has been already loaded +size+ and +length+ are + # equivalent. If not and you are going to need the records anyway + # +length+ will take one less query. Otherwise +size+ is more efficient. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.size # => 3 + # # executes something like SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" = 1 + # + # person.pets # This will execute a SELECT * FROM query + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.size # => 3 + # # Because the collection is already loaded, this will behave like + # # collection.size and no SQL count query is executed. + def size + @association.size + end + + ## + # :method: length + # + # :call-seq: + # length() + # + # Returns the size of the collection calling +size+ on the target. + # If the collection has been already loaded, +length+ and +size+ are + # equivalent. If not and you are going to need the records anyway this + # method will take one less query. Otherwise +size+ is more efficient. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.length # => 3 + # # executes something like SELECT "pets".* FROM "pets" WHERE "pets"."person_id" = 1 + # + # # Because the collection is loaded, you can + # # call the collection with no additional queries: + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + + # Returns +true+ if the collection is empty. If the collection has been + # loaded it is equivalent + # to collection.size.zero?. If the collection has not been loaded, + # it is equivalent to !collection.exists?. If the collection has + # not already been loaded and you are going to fetch the records anyway it + # is better to check collection.load.empty?. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.count # => 1 + # person.pets.empty? # => false + # + # person.pets.delete_all + # + # person.pets.count # => 0 + # person.pets.empty? # => true + def empty? + @association.empty? + end + + ## + # :method: any? + # + # :call-seq: + # any?() + # + # Returns +true+ if the collection is not empty. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.count # => 0 + # person.pets.any? # => false + # + # person.pets << Pet.new(name: 'Snoop') + # person.pets.count # => 1 + # person.pets.any? # => true + # + # Calling it without a block when the collection is not yet + # loaded is equivalent to collection.exists?. + # If you're going to load the collection anyway, it is better + # to call collection.load.any? to avoid an extra query. + # + # You can also pass a +block+ to define criteria. The behavior + # is the same, it returns true if the collection based on the + # criteria is not empty. + # + # person.pets + # # => [#] + # + # person.pets.any? do |pet| + # pet.group == 'cats' + # end + # # => false + # + # person.pets.any? do |pet| + # pet.group == 'dogs' + # end + # # => true + + ## + # :method: many? + # + # :call-seq: + # many?() + # + # Returns true if the collection has more than one record. + # Equivalent to collection.size > 1. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.count # => 1 + # person.pets.many? # => false + # + # person.pets << Pet.new(name: 'Snoopy') + # person.pets.count # => 2 + # person.pets.many? # => true + # + # You can also pass a +block+ to define criteria. The + # behavior is the same, it returns true if the collection + # based on the criteria has more than one record. + # + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.many? do |pet| + # pet.group == 'dogs' + # end + # # => false + # + # person.pets.many? do |pet| + # pet.group == 'cats' + # end + # # => true + + # Returns +true+ if the given +record+ is present in the collection. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets # => [#] + # + # person.pets.include?(Pet.find(20)) # => true + # person.pets.include?(Pet.find(21)) # => false + def include?(record) + !!@association.include?(record) + end + + # Returns the association object for the collection. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.proxy_association + # # => # + # + # Returns the same object as person.association(:pets), + # allowing you to make calls like person.pets.proxy_association.owner. + # + # See Associations::ClassMethods@Association+extensions for more. + def proxy_association + @association + end + + # Returns a Relation object for the records in this association + def scope + @scope ||= @association.scope + end + + # Equivalent to Array#==. Returns +true+ if the two arrays + # contain the same number of elements and if each element is equal + # to the corresponding element in the +other+ array, otherwise returns + # +false+. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [ + # # #, + # # # + # # ] + # + # other = person.pets.to_ary + # + # person.pets == other + # # => true + # + # + # Note that unpersisted records can still be seen as equal: + # + # other = [Pet.new(id: 1), Pet.new(id: 2)] + # + # person.pets == other + # # => true + def ==(other) + load_target == other + end + + ## + # :method: to_ary + # + # :call-seq: + # to_ary() + # + # Returns a new array of objects from the collection. If the collection + # hasn't been loaded, it fetches the records from the database. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # other_pets = person.pets.to_ary + # # => [ + # # #, + # # #, + # # # + # # ] + # + # other_pets.replace([Pet.new(name: 'BooGoo')]) + # + # other_pets + # # => [#] + # + # person.pets + # # This is not affected by replace + # # => [ + # # #, + # # #, + # # # + # # ] + + def records # :nodoc: + load_target + end + + # Adds one or more +records+ to the collection by setting their foreign keys + # to the association's primary key. Since << flattens its argument list and + # inserts each record, +push+ and +concat+ behave identically. Returns +self+ + # so several appends may be chained together. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.size # => 0 + # person.pets << Pet.new(name: 'Fancy-Fancy') + # person.pets << [Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo')] + # person.pets.size # => 3 + # + # person.id # => 1 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + def <<(*records) + proxy_association.concat(records) && self + end + alias_method :push, :<< + alias_method :append, :<< + alias_method :concat, :<< + + def prepend(*args) # :nodoc: + raise NoMethodError, "prepend on association is not defined. Please use <<, push or append" + end + + # Equivalent to +delete_all+. The difference is that returns +self+, instead + # of an array with the deleted objects, so methods can be chained. See + # +delete_all+ for more information. + # Note that because +delete_all+ removes records by directly + # running an SQL query into the database, the +updated_at+ column of + # the object is not changed. + def clear + delete_all + self + end + + # Reloads the collection from the database. Returns +self+. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets # fetches pets from the database + # # => [#] + # + # person.pets # uses the pets cache + # # => [#] + # + # person.pets.reload # fetches pets from the database + # # => [#] + def reload + proxy_association.reload(true) + reset_scope + end + + # Unloads the association. Returns +self+. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets # fetches pets from the database + # # => [#] + # + # person.pets # uses the pets cache + # # => [#] + # + # person.pets.reset # clears the pets cache + # + # person.pets # fetches pets from the database + # # => [#] + def reset + proxy_association.reset + proxy_association.reset_scope + reset_scope + end + + def reset_scope # :nodoc: + @offsets = @take = nil + @scope = nil + self + end + + def inspect # :nodoc: + load_target if find_from_target? + super + end + + def pretty_print(pp) # :nodoc: + load_target if find_from_target? + super + end + + delegate_methods = [ + QueryMethods, + SpawnMethods, + ].flat_map { |klass| + klass.public_instance_methods(false) + } - self.public_instance_methods(false) - [:select] + [ + :scoping, :values, :insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all, :load_async + ] + + delegate(*delegate_methods, to: :scope) + + private + def find_nth_with_limit(index, limit) + load_target if find_from_target? + super + end + + def find_nth_from_last(index) + load_target if find_from_target? + super + end + + def null_scope? + @association.null_scope? + end + + def find_from_target? + @association.find_from_target? + end + + def exec_queries + load_target + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/disable_joins_association_scope.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/disable_joins_association_scope.rb new file mode 100644 index 00000000..fa40884f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/disable_joins_association_scope.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + class DisableJoinsAssociationScope < AssociationScope # :nodoc: + def scope(association) + source_reflection = association.reflection + owner = association.owner + unscoped = association.klass.unscoped + reverse_chain = get_chain(source_reflection, association, unscoped.alias_tracker).reverse + + last_reflection, last_ordered, last_join_ids = last_scope_chain(reverse_chain, owner) + + add_constraints(last_reflection, last_reflection.join_primary_key, last_join_ids, owner, last_ordered) + end + + private + def last_scope_chain(reverse_chain, owner) + first_item = reverse_chain.shift + first_scope = [first_item, false, [owner._read_attribute(first_item.join_foreign_key)]] + + reverse_chain.inject(first_scope) do |(reflection, ordered, join_ids), next_reflection| + key = reflection.join_primary_key + records = add_constraints(reflection, key, join_ids, owner, ordered) + foreign_key = next_reflection.join_foreign_key + record_ids = records.pluck(foreign_key) + records_ordered = records && records.order_values.any? + + [next_reflection, records_ordered, record_ids] + end + end + + def add_constraints(reflection, key, join_ids, owner, ordered) + scope = reflection.build_scope(reflection.aliased_table).where(key => join_ids) + + relation = reflection.klass.scope_for_association + scope.merge!( + relation.except(:select, :create_with, :includes, :preload, :eager_load, :joins, :left_outer_joins) + ) + + scope = reflection.constraints.inject(scope) do |memo, scope_chain_item| + item = eval_scope(reflection, scope_chain_item, owner) + scope.unscope!(*item.unscope_values) + scope.where_clause += item.where_clause + scope.order_values = item.order_values | scope.order_values + scope + end + + if scope.order_values.empty? && ordered + split_scope = DisableJoinsAssociationRelation.create(scope.model, key, join_ids) + split_scope.where_clause += scope.where_clause + split_scope + else + scope + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/errors.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/errors.rb new file mode 100644 index 00000000..8bb0bd2a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/errors.rb @@ -0,0 +1,265 @@ +# frozen_string_literal: true + +module ActiveRecord + class AssociationNotFoundError < ConfigurationError # :nodoc: + attr_reader :record, :association_name + + def initialize(record = nil, association_name = nil) + @record = record + @association_name = association_name + if record && association_name + super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?") + else + super("Association was not found.") + end + end + + if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker) + include DidYouMean::Correctable + + def corrections + if record && association_name + @corrections ||= begin + maybe_these = record.class.reflections.keys + DidYouMean::SpellChecker.new(dictionary: maybe_these).correct(association_name) + end + else + [] + end + end + end + end + + class InverseOfAssociationNotFoundError < ActiveRecordError # :nodoc: + attr_reader :reflection, :associated_class + + def initialize(reflection = nil, associated_class = nil) + if reflection + @reflection = reflection + @associated_class = associated_class.nil? ? reflection.klass : associated_class + super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})") + else + super("Could not find the inverse association.") + end + end + + if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker) + include DidYouMean::Correctable + + def corrections + if reflection && associated_class + @corrections ||= begin + maybe_these = associated_class.reflections.keys + DidYouMean::SpellChecker.new(dictionary: maybe_these).correct(reflection.options[:inverse_of].to_s) + end + else + [] + end + end + end + end + + class InverseOfAssociationRecursiveError < ActiveRecordError # :nodoc: + attr_reader :reflection + def initialize(reflection = nil) + if reflection + @reflection = reflection + super("Inverse association #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{reflection.class_name}) is recursive.") + else + super("Inverse association is recursive.") + end + end + end + + class HasManyThroughAssociationNotFoundError < ActiveRecordError # :nodoc: + attr_reader :owner_class, :reflection + + def initialize(owner_class = nil, reflection = nil) + if owner_class && reflection + @owner_class = owner_class + @reflection = reflection + super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class.name}") + else + super("Could not find the association.") + end + end + + if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker) + include DidYouMean::Correctable + + def corrections + if owner_class && reflection + @corrections ||= begin + maybe_these = owner_class.reflections.keys + maybe_these -= [reflection.name.to_s] # remove failing reflection + DidYouMean::SpellChecker.new(dictionary: maybe_these).correct(reflection.options[:through].to_s) + end + else + [] + end + end + end + end + + class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError # :nodoc: + def initialize(owner_class_name = nil, reflection = nil, source_reflection = nil) + if owner_class_name && reflection && source_reflection + super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}' without 'source_type'. Try adding 'source_type: \"#{reflection.name.to_s.classify}\"' to 'has_many :through' definition.") + else + super("Cannot have a has_many :through association.") + end + end + end + + class HasManyThroughAssociationPolymorphicThroughError < ActiveRecordError # :nodoc: + def initialize(owner_class_name = nil, reflection = nil) + if owner_class_name && reflection + super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.") + else + super("Cannot have a has_many :through association.") + end + end + end + + class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError # :nodoc: + def initialize(owner_class_name = nil, reflection = nil, source_reflection = nil) + if owner_class_name && reflection && source_reflection + super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.") + else + super("Cannot have a has_many :through association.") + end + end + end + + class HasOneThroughCantAssociateThroughCollection < ActiveRecordError # :nodoc: + def initialize(owner_class_name = nil, reflection = nil, through_reflection = nil) + if owner_class_name && reflection && through_reflection + super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' where the :through association '#{owner_class_name}##{through_reflection.name}' is a collection. Specify a has_one or belongs_to association in the :through option instead.") + else + super("Cannot have a has_one :through association.") + end + end + end + + class HasOneAssociationPolymorphicThroughError < ActiveRecordError # :nodoc: + def initialize(owner_class_name = nil, reflection = nil) + if owner_class_name && reflection + super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.") + else + super("Cannot have a has_one :through association.") + end + end + end + + class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError # :nodoc: + def initialize(reflection = nil) + if reflection + through_reflection = reflection.through_reflection + source_reflection_names = reflection.source_reflection_names + source_associations = reflection.through_reflection.klass._reflections.keys + super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => '. Is it one of #{source_associations.to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')}?") + else + super("Could not find the source association(s).") + end + end + end + + class HasManyThroughOrderError < ActiveRecordError # :nodoc: + def initialize(owner_class_name = nil, reflection = nil, through_reflection = nil) + if owner_class_name && reflection && through_reflection + super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through '#{owner_class_name}##{through_reflection.name}' before the through association is defined.") + else + super("Cannot have a has_many :through association before the through association is defined.") + end + end + end + + class ThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError # :nodoc: + def initialize(owner = nil, reflection = nil) + if owner && reflection + super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.") + else + super("Cannot modify association.") + end + end + end + + class CompositePrimaryKeyMismatchError < ActiveRecordError # :nodoc: + attr_reader :reflection + + def initialize(reflection = nil) + if reflection + if reflection.has_one? || reflection.collection? + super("Association #{reflection.active_record}##{reflection.name} primary key #{reflection.active_record_primary_key} doesn't match with foreign key #{reflection.foreign_key}. Please specify query_constraints, or primary_key and foreign_key values.") + else + super("Association #{reflection.active_record}##{reflection.name} primary key #{reflection.association_primary_key} doesn't match with foreign key #{reflection.foreign_key}. Please specify query_constraints, or primary_key and foreign_key values.") + end + else + super("Association primary key doesn't match with foreign key.") + end + end + end + + class AmbiguousSourceReflectionForThroughAssociation < ActiveRecordError # :nodoc: + def initialize(klass, macro, association_name, options, possible_sources) + example_options = options.dup + example_options[:source] = possible_sources.first + + super("Ambiguous source reflection for through association. Please " \ + "specify a :source directive on your declaration like:\n" \ + "\n" \ + " class #{klass} < ActiveRecord::Base\n" \ + " #{macro} :#{association_name}, #{example_options}\n" \ + " end" + ) + end + end + + class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection # :nodoc: + end + + class HasOneThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection # :nodoc: + end + + class ThroughNestedAssociationsAreReadonly < ActiveRecordError # :nodoc: + def initialize(owner = nil, reflection = nil) + if owner && reflection + super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.") + else + super("Through nested associations are read-only.") + end + end + end + + class HasManyThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly # :nodoc: + end + + class HasOneThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly # :nodoc: + end + + # This error is raised when trying to eager load a polymorphic association using a JOIN. + # Eager loading polymorphic associations is only possible with + # {ActiveRecord::Relation#preload}[rdoc-ref:QueryMethods#preload]. + class EagerLoadPolymorphicError < ActiveRecordError + def initialize(reflection = nil) + if reflection + super("Cannot eagerly load the polymorphic association #{reflection.name.inspect}") + else + super("Eager load polymorphic error.") + end + end + end + + # This error is raised when trying to destroy a parent instance in N:1 or 1:1 associations + # (has_many, has_one) when there is at least 1 child associated instance. + # ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project + class DeleteRestrictionError < ActiveRecordError # :nodoc: + def initialize(name = nil) + if name + super("Cannot delete record because of dependent #{name}") + else + super("Delete restriction error.") + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/foreign_association.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/foreign_association.rb new file mode 100644 index 00000000..37cd9d74 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/foreign_association.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module ActiveRecord::Associations + module ForeignAssociation # :nodoc: + def foreign_key_present? + if reflection.klass.primary_key + owner.attribute_present?(reflection.active_record_primary_key) + else + false + end + end + + def nullified_owner_attributes + Hash.new.tap do |attrs| + Array(reflection.foreign_key).each { |foreign_key| attrs[foreign_key] = nil } + attrs[reflection.type] = nil if reflection.type.present? + end + end + + private + # Sets the owner attributes on the given record + def set_owner_attributes(record) + return if options[:through] + + primary_key_attribute_names = Array(reflection.join_primary_key) + foreign_key_attribute_names = Array(reflection.join_foreign_key) + + primary_key_foreign_key_pairs = primary_key_attribute_names.zip(foreign_key_attribute_names) + + primary_key_foreign_key_pairs.each do |primary_key, foreign_key| + value = owner._read_attribute(foreign_key) + record._write_attribute(primary_key, value) + end + + if reflection.type + record._write_attribute(reflection.type, owner.class.polymorphic_name) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/has_many_association.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/has_many_association.rb new file mode 100644 index 00000000..f8dbc95b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/has_many_association.rb @@ -0,0 +1,167 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # = Active Record Has Many Association + # + # This is the proxy that handles a has many association. + # + # If the association has a :through option further specialization + # is provided by its child HasManyThroughAssociation. + class HasManyAssociation < CollectionAssociation # :nodoc: + include ForeignAssociation + + def handle_dependency + case options[:dependent] + when :restrict_with_exception + raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty? + + when :restrict_with_error + unless empty? + record = owner.class.human_attribute_name(reflection.name).downcase + owner.errors.add(:base, :'restrict_dependent_destroy.has_many', record: record) + throw(:abort) + end + + when :destroy + # No point in executing the counter update since we're going to destroy the parent anyway + load_target.each { |t| t.destroyed_by_association = reflection } + destroy_all + when :destroy_async + load_target.each do |t| + t.destroyed_by_association = reflection + end + + unless target.empty? + association_class = target.first.class + if association_class.query_constraints_list + primary_key_column = association_class.query_constraints_list + ids = target.collect { |assoc| primary_key_column.map { |col| assoc.public_send(col) } } + else + primary_key_column = association_class.primary_key + ids = target.collect { |assoc| assoc.public_send(primary_key_column) } + end + + ids.each_slice(owner.class.destroy_association_async_batch_size || ids.size) do |ids_batch| + enqueue_destroy_association( + owner_model_name: owner.class.to_s, + owner_id: owner.id, + association_class: reflection.klass.to_s, + association_ids: ids_batch, + association_primary_key_column: primary_key_column, + ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil) + ) + end + end + else + delete_all + end + end + + def insert_record(record, validate = true, raise = false) + set_owner_attributes(record) + super + end + + private + # Returns the number of records in this collection. + # + # If the association has a counter cache it gets that value. Otherwise + # it will attempt to do a count via SQL, bounded to :limit if + # there's one. Some configuration options like :group make it impossible + # to do an SQL count, in those cases the array count will be used. + # + # That does not depend on whether the collection has already been loaded + # or not. The +size+ method is the one that takes the loaded flag into + # account and delegates to +count_records+ if needed. + # + # If the collection is empty the target is set to an empty array and + # the loaded flag is set to true as well. + def count_records + count = if reflection.has_active_cached_counter? + owner.read_attribute(reflection.counter_cache_column).to_i + else + scope.count(:all) + end + + # If there's nothing in the database, @target should only contain new + # records or be an empty array. This is a documented side-effect of + # the method that may avoid an extra SELECT. + if count == 0 + target.select!(&:new_record?) + loaded! + end + + [association_scope.limit_value, count].compact.min + end + + def update_counter(difference, reflection = reflection()) + if reflection.has_cached_counter? + owner.increment!(reflection.counter_cache_column, difference) + end + end + + def update_counter_in_memory(difference, reflection = reflection()) + if reflection.counter_must_be_updated_by_has_many? + counter = reflection.counter_cache_column + owner.increment(counter, difference) + owner.send(:"clear_#{counter}_change") + end + end + + def delete_count(method, scope) + if method == :delete_all + scope.delete_all + else + scope.update_all(nullified_owner_attributes) + end + end + + def delete_or_nullify_all_records(method) + count = delete_count(method, scope) + update_counter(-count) + count + end + + # Deletes the records according to the :dependent option. + def delete_records(records, method) + if method == :destroy + records.each(&:destroy!) + update_counter(-records.length) unless reflection.inverse_updates_counter_cache? + else + query_constraints = reflection.klass.composite_query_constraints_list + values = records.map { |r| query_constraints.map { |col| r._read_attribute(col) } } + scope = self.scope.where(query_constraints => values) + update_counter(-delete_count(method, scope)) + end + end + + def concat_records(records, *) + update_counter_if_success(super, records.length) + end + + def _create_record(attributes, *) + if attributes.is_a?(Array) + super + else + update_counter_if_success(super, 1) + end + end + + def update_counter_if_success(saved_successfully, difference) + if saved_successfully + update_counter_in_memory(difference) + end + saved_successfully + end + + def difference(a, b) + a - b + end + + def intersection(a, b) + a & b + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/has_many_through_association.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/has_many_through_association.rb new file mode 100644 index 00000000..7b8e0008 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/has_many_through_association.rb @@ -0,0 +1,238 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # = Active Record Has Many Through Association + class HasManyThroughAssociation < HasManyAssociation # :nodoc: + include ThroughAssociation + + def initialize(owner, reflection) + super + @through_records = {}.compare_by_identity + end + + def concat(*records) + unless owner.new_record? + records.flatten.each do |record| + raise_on_type_mismatch!(record) + end + end + + super + end + + def insert_record(record, validate = true, raise = false) + ensure_not_nested + + if record.new_record? || record.has_changes_to_save? + return unless super + end + + save_through_record(record) + + record + end + + private + def concat_records(records) + ensure_not_nested + + records = super(records, true) + + if owner.new_record? && records + records.flatten.each do |record| + build_through_record(record) + end + end + + records + end + + # The through record (built with build_record) is temporarily cached + # so that it may be reused if insert_record is subsequently called. + # + # However, after insert_record has been called, the cache is cleared in + # order to allow multiple instances of the same record in an association. + def build_through_record(record) + @through_records[record] ||= begin + ensure_mutable + + attributes = through_scope_attributes + attributes[source_reflection.name] = record + + through_association.build(attributes).tap do |new_record| + new_record.send("#{source_reflection.foreign_type}=", options[:source_type]) if options[:source_type] + end + end + end + + attr_reader :through_scope + + def through_scope_attributes + scope = through_scope || self.scope + attributes = scope.where_values_hash(through_association.reflection.klass.table_name) + except_keys = [ + *Array(through_association.reflection.foreign_key), + through_association.reflection.klass.inheritance_column + ] + attributes.except!(*except_keys) + end + + def save_through_record(record) + association = build_through_record(record) + if association.changed? + association.save! + end + ensure + @through_records.delete(record) + end + + def build_record(attributes) + ensure_not_nested + + @through_scope = scope + record = super + + inverse = + if source_reflection.polymorphic? + source_reflection.polymorphic_inverse_of(record.class) + else + source_reflection.inverse_of + end + + if inverse + if inverse.collection? + record.send(inverse.name) << build_through_record(record) + elsif inverse.has_one? + record.send("#{inverse.name}=", build_through_record(record)) + end + end + + record + ensure + @through_scope = nil + end + + def remove_records(existing_records, records, method) + super + delete_through_records(records) + end + + def target_reflection_has_associated_record? + !(through_reflection.belongs_to? && Array(through_reflection.foreign_key).all? { |foreign_key_column| owner[foreign_key_column].blank? }) + end + + def update_through_counter?(method) + case method + when :destroy + !through_reflection.inverse_updates_counter_cache? + when :nullify + false + else + true + end + end + + def delete_or_nullify_all_records(method) + delete_records(load_target, method) + end + + def delete_records(records, method) + ensure_not_nested + + scope = through_association.scope + scope.where! construct_join_attributes(*records) + scope = scope.where(through_scope_attributes) + + case method + when :destroy + if scope.model.primary_key + count = scope.destroy_all.count(&:destroyed?) + else + scope.each(&:_run_destroy_callbacks) + count = scope.delete_all + end + when :nullify + count = scope.update_all(source_reflection.foreign_key => nil) + else + count = scope.delete_all + end + + delete_through_records(records) + + if source_reflection.options[:counter_cache] && method != :destroy + counter = source_reflection.counter_cache_column + klass.decrement_counter counter, records.map(&:id) + end + + if through_reflection.collection? && update_through_counter?(method) + update_counter(-count, through_reflection) + else + update_counter(-count) + end + + count + end + + def difference(a, b) + distribution = distribution(b) + + a.reject { |record| mark_occurrence(distribution, record) } + end + + def intersection(a, b) + distribution = distribution(b) + + a.select { |record| mark_occurrence(distribution, record) } + end + + def mark_occurrence(distribution, record) + distribution[record] > 0 && distribution[record] -= 1 + end + + def distribution(array) + array.each_with_object(Hash.new(0)) do |record, distribution| + distribution[record] += 1 + end + end + + def through_records_for(record) + attributes = construct_join_attributes(record) + candidates = Array.wrap(through_association.target) + candidates.find_all do |c| + attributes.all? do |key, value| + c.public_send(key) == value + end + end + end + + def delete_through_records(records) + records.each do |record| + through_records = through_records_for(record) + + if through_reflection.collection? + through_records.each { |r| through_association.target.delete(r) } + else + if through_records.include?(through_association.target) + through_association.target = nil + end + end + + @through_records.delete(record) + end + end + + def find_target(async: false) + raise NotImplementedError, "No async loading for HasManyThroughAssociation yet" if async + return [] unless target_reflection_has_associated_record? + return scope.to_a if disable_joins + super + end + + # NOTE - not sure that we can actually cope with inverses here + def invertible_for?(record) + false + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/has_one_association.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/has_one_association.rb new file mode 100644 index 00000000..5e1b523d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/has_one_association.rb @@ -0,0 +1,142 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # = Active Record Has One Association + class HasOneAssociation < SingularAssociation # :nodoc: + include ForeignAssociation + + def handle_dependency + case options[:dependent] + when :restrict_with_exception + raise ActiveRecord::DeleteRestrictionError.new(reflection.name) if load_target + + when :restrict_with_error + if load_target + record = owner.class.human_attribute_name(reflection.name).downcase + owner.errors.add(:base, :'restrict_dependent_destroy.has_one', record: record) + throw(:abort) + end + + else + delete + end + end + + def delete(method = options[:dependent]) + if load_target + case method + when :delete + target.delete + when :destroy + target.destroyed_by_association = reflection + target.destroy + throw(:abort) unless target.destroyed? + when :destroy_async + if target.class.query_constraints_list + primary_key_column = target.class.query_constraints_list + id = primary_key_column.map { |col| target.public_send(col) } + else + primary_key_column = target.class.primary_key + id = target.public_send(primary_key_column) + end + + enqueue_destroy_association( + owner_model_name: owner.class.to_s, + owner_id: owner.id, + association_class: reflection.klass.to_s, + association_ids: [id], + association_primary_key_column: primary_key_column, + ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil) + ) + when :nullify + target.update_columns(nullified_owner_attributes) if target.persisted? + end + end + end + + private + def replace(record, save = true) + raise_on_type_mismatch!(record) if record + + return target unless load_target || record + + assigning_another_record = target != record + if assigning_another_record || record.has_changes_to_save? + save &&= owner.persisted? + + transaction_if(save) do + remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record + + if record + set_owner_attributes(record) + set_inverse_instance(record) + + if save && !record.save + nullify_owner_attributes(record) + set_owner_attributes(target) if target + raise RecordNotSaved.new("Failed to save the new associated #{reflection.name}.", record) + end + end + end + end + + self.target = record + end + + # The reason that the save param for replace is false, if for create (not just build), + # is because the setting of the foreign keys is actually handled by the scoping when + # the record is instantiated, and so they are set straight away and do not need to be + # updated within replace. + def set_new_record(record) + replace(record, false) + end + + def remove_target!(method) + case method + when :delete + target.delete + when :destroy + target.destroyed_by_association = reflection + if target.persisted? + target.destroy + end + else + nullify_owner_attributes(target) + remove_inverse_instance(target) + + if target.persisted? && owner.persisted? && !target.save + set_owner_attributes(target) + raise RecordNotSaved.new( + "Failed to remove the existing associated #{reflection.name}. " \ + "The record failed to save after its foreign key was set to nil.", + target + ) + end + end + end + + def nullify_owner_attributes(record) + Array(reflection.foreign_key).each do |foreign_key_column| + record[foreign_key_column] = nil unless foreign_key_column.in?(Array(record.class.primary_key)) + end + end + + def transaction_if(value, &block) + if value + reflection.klass.transaction(&block) + else + yield + end + end + + def _create_record(attributes, raise_error = false, &block) + unless owner.persisted? + raise ActiveRecord::RecordNotSaved.new("You cannot call create unless the parent is saved", owner) + end + + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/has_one_through_association.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/has_one_through_association.rb new file mode 100644 index 00000000..e0a760cc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/has_one_through_association.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # = Active Record Has One Through Association + class HasOneThroughAssociation < HasOneAssociation # :nodoc: + include ThroughAssociation + + private + def replace(record, save = true) + create_through_record(record, save) + self.target = record + end + + def create_through_record(record, save) + ensure_not_nested + + through_proxy = through_association + through_record = through_proxy.load_target + + if through_record && !record + through_record.destroy + elsif record + attributes = construct_join_attributes(record) + + if through_record && through_record.destroyed? + through_record = through_proxy.tap(&:reload).target + end + + if through_record + if through_record.new_record? + through_record.assign_attributes(attributes) + else + through_record.update(attributes) + end + elsif owner.new_record? || !save + through_proxy.build(attributes) + else + through_proxy.create(attributes) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/join_dependency.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/join_dependency.rb new file mode 100644 index 00000000..975f2035 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/join_dependency.rb @@ -0,0 +1,301 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + class JoinDependency # :nodoc: + extend ActiveSupport::Autoload + + eager_autoload do + autoload :JoinBase + autoload :JoinAssociation + end + + class Aliases # :nodoc: + def initialize(tables) + @tables = tables + @alias_cache = tables.each_with_object({}) { |table, h| + h[table.node] = table.columns.each_with_object({}) { |column, i| + i[column.name] = column.alias + } + } + @columns_cache = tables.each_with_object({}) { |table, h| + h[table.node] = table.columns + } + end + + def columns + @tables.flat_map(&:column_aliases) + end + + def column_aliases(node) + @columns_cache[node] + end + + def column_alias(node, column) + @alias_cache[node][column] + end + + Table = Struct.new(:node, :columns) do # :nodoc: + def column_aliases + t = node.table + columns.map { |column| t[column.name].as(column.alias) } + end + end + Column = Struct.new(:name, :alias) + end + + def self.make_tree(associations) + hash = {} + walk_tree associations, hash + hash + end + + def self.walk_tree(associations, hash) + case associations + when Symbol, String + hash[associations.to_sym] ||= {} + when Array + associations.each do |assoc| + walk_tree assoc, hash + end + when Hash + associations.each do |k, v| + cache = hash[k] ||= {} + walk_tree v, cache if v + end + else + raise ConfigurationError, associations.inspect + end + end + + def initialize(base, table, associations, join_type) + tree = self.class.make_tree associations + @join_root = JoinBase.new(base, table, build(tree, base)) + @join_type = join_type + end + + def base_klass + join_root.base_klass + end + + def reflections + join_root.drop(1).map!(&:reflection) + end + + def join_constraints(joins_to_add, alias_tracker, references) + @alias_tracker = alias_tracker + @joined_tables = {} + @references = {} + + references.each do |table_name| + @references[table_name.to_sym] = table_name if table_name.is_a?(Arel::Nodes::SqlLiteral) + end unless references.empty? + + joins = make_join_constraints(join_root, join_type) + + joins.concat joins_to_add.flat_map { |oj| + if join_root.match? oj.join_root + walk(join_root, oj.join_root, oj.join_type) + else + make_join_constraints(oj.join_root, oj.join_type) + end + } + end + + def instantiate(result_set, strict_loading_value, &block) + primary_key = aliases.column_alias(join_root, join_root.primary_key) + + seen = Hash.new { |i, parent| + i[parent] = Hash.new { |j, child_class| + j[child_class] = {} + } + }.compare_by_identity + + model_cache = Hash.new { |h, klass| h[klass] = {} } + parents = model_cache[join_root] + + column_aliases = aliases.column_aliases(join_root) + column_names = [] + + result_set.columns.each do |name| + column_names << name unless /\At\d+_r\d+\z/.match?(name) + end + + if column_names.empty? + column_types = {} + else + column_types = result_set.column_types + unless column_types.empty? + attribute_types = join_root.attribute_types + column_types = column_types.slice(*column_names).delete_if { |k, _| attribute_types.key?(k) } + end + column_aliases += column_names.map! { |name| Aliases::Column.new(name, name) } + end + + message_bus = ActiveSupport::Notifications.instrumenter + + payload = { + record_count: result_set.length, + class_name: join_root.base_klass.name + } + + message_bus.instrument("instantiation.active_record", payload) do + result_set.each { |row_hash| + parent_key = primary_key ? row_hash[primary_key] : row_hash + parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, column_types, &block) + construct(parent, join_root, row_hash, seen, model_cache, strict_loading_value) + } + end + + parents.values + end + + def apply_column_aliases(relation) + @join_root_alias = relation.select_values.empty? + relation._select!(-> { aliases.columns }) + end + + def each(&block) + join_root.each(&block) + end + + protected + attr_reader :join_root, :join_type + + private + attr_reader :alias_tracker, :join_root_alias + + def aliases + @aliases ||= Aliases.new join_root.each_with_index.map { |join_part, i| + column_names = if join_part == join_root && !join_root_alias + primary_key = join_root.primary_key + primary_key ? [primary_key] : [] + else + join_part.column_names + end + + columns = column_names.each_with_index.map { |column_name, j| + Aliases::Column.new column_name, "t#{i}_r#{j}" + } + Aliases::Table.new(join_part, columns) + } + end + + def make_join_constraints(join_root, join_type) + join_root.children.flat_map do |child| + make_constraints(join_root, child, join_type) + end + end + + def make_constraints(parent, child, join_type) + foreign_table = parent.table + foreign_klass = parent.base_klass + child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) do |reflection, remaining_reflection_chain| + table, terminated = @joined_tables[remaining_reflection_chain] + root = reflection == child.reflection + + if table && (!root || !terminated) + @joined_tables[remaining_reflection_chain] = [table, root] if root + next table, true + end + + table_name = @references[reflection.name.to_sym]&.to_s + + table = alias_tracker.aliased_table_for(reflection.klass.arel_table, table_name) do + name = reflection.alias_candidate(parent.table_name) + root ? name : "#{name}_join" + end + + @joined_tables[remaining_reflection_chain] ||= [table, root] if join_type == Arel::Nodes::OuterJoin + table + end.concat child.children.flat_map { |c| make_constraints(child, c, join_type) } + end + + def walk(left, right, join_type) + intersection, missing = right.children.map { |node1| + [left.children.find { |node2| node1.match? node2 }, node1] + }.partition(&:first) + + joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r, join_type) } + joins.concat missing.flat_map { |_, n| make_constraints(left, n, join_type) } + end + + def find_reflection(klass, name) + klass._reflect_on_association(name) || + raise(ConfigurationError, "Can't join '#{klass.name}' to association named '#{name}'; perhaps you misspelled it?") + end + + def build(associations, base_klass) + associations.map do |name, right| + reflection = find_reflection base_klass, name + reflection.check_validity! + reflection.check_eager_loadable! + + if reflection.polymorphic? + raise EagerLoadPolymorphicError.new(reflection) + end + + JoinAssociation.new(reflection, build(right, reflection.klass)) + end + end + + def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value) + return if ar_parent.nil? + + parent.children.each do |node| + if node.reflection.collection? + other = ar_parent.association(node.reflection.name) + other.loaded! + elsif ar_parent.association_cached?(node.reflection.name) + model = ar_parent.association(node.reflection.name).target + construct(model, node, row, seen, model_cache, strict_loading_value) + next + end + + if node.primary_key + keys = Array(node.primary_key).map { |column| aliases.column_alias(node, column) } + id = keys.map { |key| row[key] } + else + keys = Array(node.reflection.join_primary_key).map { |column| aliases.column_alias(node, column.to_s) } + id = keys.map { nil } # Avoid id-based model caching. + end + + if keys.any? { |key| row[key].nil? } + nil_association = ar_parent.association(node.reflection.name) + nil_association.loaded! + next + end + + unless model = seen[ar_parent][node][id] + model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value) + seen[ar_parent][node][id] = model if id + end + + construct(model, node, row, seen, model_cache, strict_loading_value) + end + end + + def construct_model(record, node, row, model_cache, id, strict_loading_value) + other = record.association(node.reflection.name) + + unless model = model_cache[node][id] + model = node.instantiate(row, aliases.column_aliases(node)) do |m| + m.strict_loading! if strict_loading_value + other.set_inverse_instance(m) + end + model_cache[node][id] = model if id + end + + if node.reflection.collection? + other.target.push(model) + else + other.target = model + end + + model.readonly! if node.readonly? + model.strict_loading! if node.strict_loading? + model + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/join_dependency/join_association.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/join_dependency/join_association.rb new file mode 100644 index 00000000..9102b6ac --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/join_dependency/join_association.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +require "active_record/associations/join_dependency/join_part" +require "active_support/core_ext/array/extract" + +module ActiveRecord + module Associations + class JoinDependency # :nodoc: + class JoinAssociation < JoinPart # :nodoc: + attr_reader :reflection, :tables + attr_accessor :table + + def initialize(reflection, children) + super(reflection.klass, children) + + @reflection = reflection + end + + def match?(other) + return true if self == other + super && reflection == other.reflection + end + + def join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) + joins = [] + chain = [] + + reflection_chain = reflection.chain + reflection_chain.each_with_index do |reflection, index| + table, terminated = yield reflection, reflection_chain[index..] + @table ||= table + + if terminated + foreign_table, foreign_klass = table, reflection.klass + break + end + + chain << [reflection, table] + end + + # The chain starts with the target table, but we want to end with it here (makes + # more sense in this context), so we reverse + chain.reverse_each do |reflection, table| + klass = reflection.klass + + scope = reflection.join_scope(table, foreign_table, foreign_klass) + + unless scope.references_values.empty? + associations = scope.eager_load_values | scope.includes_values + + unless associations.empty? + scope.joins! scope.construct_join_dependency(associations, Arel::Nodes::OuterJoin) + end + end + + arel = scope.arel(alias_tracker.aliases) + nodes = arel.constraints.first + + if nodes.is_a?(Arel::Nodes::And) + others = nodes.children.extract! do |node| + !Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name } + end + end + + joins << join_type.new(table, Arel::Nodes::On.new(nodes)) + + if others && !others.empty? + joins.concat arel.join_sources + append_constraints(joins.last, others) + end + + # The current table in this iteration becomes the foreign table in the next + foreign_table, foreign_klass = table, klass + end + + joins + end + + def readonly? + return @readonly if defined?(@readonly) + + @readonly = reflection.scope && reflection.scope_for(base_klass.unscoped).readonly_value + end + + def strict_loading? + return @strict_loading if defined?(@strict_loading) + + @strict_loading = reflection.scope && reflection.scope_for(base_klass.unscoped).strict_loading_value + end + + private + def append_constraints(join, constraints) + if join.is_a?(Arel::Nodes::StringJoin) + join_string = Arel::Nodes::And.new(constraints.unshift join.left) + join.left = join_string + else + right = join.right + right.expr = Arel::Nodes::And.new(constraints.unshift right.expr) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/join_dependency/join_base.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/join_dependency/join_base.rb new file mode 100644 index 00000000..988b4e8f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/join_dependency/join_base.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "active_record/associations/join_dependency/join_part" + +module ActiveRecord + module Associations + class JoinDependency # :nodoc: + class JoinBase < JoinPart # :nodoc: + attr_reader :table + + def initialize(base_klass, table, children) + super(base_klass, children) + @table = table + end + + def match?(other) + return true if self == other + super && base_klass == other.base_klass + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/join_dependency/join_part.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/join_dependency/join_part.rb new file mode 100644 index 00000000..3ffa079c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/join_dependency/join_part.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + class JoinDependency # :nodoc: + # A JoinPart represents a part of a JoinDependency. It is inherited + # by JoinBase and JoinAssociation. A JoinBase represents the Active Record which + # everything else is being joined onto. A JoinAssociation represents an association which + # is joining to the base. A JoinAssociation may result in more than one actual join + # operations (for example a has_and_belongs_to_many JoinAssociation would result in + # two; one for the join table and one for the target table). + class JoinPart # :nodoc: + include Enumerable + + # The Active Record class which this join part is associated 'about'; for a JoinBase + # this is the actual base model, for a JoinAssociation this is the target model of the + # association. + attr_reader :base_klass, :children + + delegate :table_name, :column_names, :primary_key, :attribute_types, to: :base_klass + + def initialize(base_klass, children) + @base_klass = base_klass + @children = children + end + + def match?(other) + self.class == other.class + end + + def each(&block) + yield self + children.each { |child| child.each(&block) } + end + + def each_children(&block) + children.each do |child| + yield self, child + child.each_children(&block) + end + end + + # An Arel::Table for the active_record + def table + raise NotImplementedError + end + + def extract_record(row, column_names_with_alias) + # This code is performance critical as it is called per row. + # see: https://github.com/rails/rails/pull/12185 + hash = {} + + index = 0 + length = column_names_with_alias.length + + while index < length + column = column_names_with_alias[index] + hash[column.name] = row[column.alias] + index += 1 + end + + hash + end + + def instantiate(row, aliases, column_types = {}, &block) + base_klass.instantiate(extract_record(row, aliases), column_types, &block) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/nested_error.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/nested_error.rb new file mode 100644 index 00000000..cb2cf5a2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/nested_error.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +# Validation error class to wrap association records' errors, +# with index_errors support. +module ActiveRecord + module Associations + class NestedError < ::ActiveModel::NestedError + def initialize(association, inner_error) + @base = association.owner + @association = association + @inner_error = inner_error + super(@base, inner_error, { attribute: compute_attribute(inner_error) }) + end + + private + attr_reader :association + + def compute_attribute(inner_error) + association_name = association.reflection.name + + if association.collection? && index_errors_setting && index + "#{association_name}[#{index}].#{inner_error.attribute}".to_sym + else + "#{association_name}.#{inner_error.attribute}".to_sym + end + end + + def index_errors_setting + @index_errors_setting ||= + association.options.fetch(:index_errors, ActiveRecord.index_nested_attribute_errors) + end + + def index + @index ||= ordered_records&.find_index(inner_error.base) + end + + def ordered_records + case index_errors_setting + when true # default is association order + association.target + when :nested_attributes_order + association.nested_attributes_target + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/preloader.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/preloader.rb new file mode 100644 index 00000000..b35c8e8b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/preloader.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" + +module ActiveRecord + module Associations + # = Active Record \Preloader + # + # Implements the details of eager loading of Active Record associations. + # + # Suppose that you have the following two Active Record models: + # + # class Author < ActiveRecord::Base + # # columns: name, age + # has_many :books + # end + # + # class Book < ActiveRecord::Base + # # columns: title, sales, author_id + # end + # + # When you load an author with all associated books Active Record will make + # multiple queries like this: + # + # Author.includes(:books).where(name: ['bell hooks', 'Homer']).to_a + # + # # SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer') + # # SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5) + # + # Active Record saves the ids of the records from the first query to use in + # the second. Depending on the number of associations involved there can be + # arbitrarily many SQL queries made. + # + # However, if there is a WHERE clause that spans across tables Active + # Record will fall back to a slightly more resource-intensive single query: + # + # Author.includes(:books).where(books: {title: 'Illiad'}).to_a + # # SELECT `authors`.`id` AS t0_r0, `authors`.`name` AS t0_r1, `authors`.`age` AS t0_r2, + # # `books`.`id` AS t1_r0, `books`.`title` AS t1_r1, `books`.`sales` AS t1_r2 + # # FROM `authors` + # # LEFT OUTER JOIN `books` ON `authors`.`id` = `books`.`author_id` + # # WHERE `books`.`title` = 'Illiad' + # + # This could result in many rows that contain redundant data and it performs poorly at scale + # and is therefore only used when necessary. + class Preloader # :nodoc: + extend ActiveSupport::Autoload + + eager_autoload do + autoload :Association, "active_record/associations/preloader/association" + autoload :Batch, "active_record/associations/preloader/batch" + autoload :Branch, "active_record/associations/preloader/branch" + autoload :ThroughAssociation, "active_record/associations/preloader/through_association" + end + + attr_reader :records, :associations, :scope, :associate_by_default + + # Eager loads the named associations for the given Active Record record(s). + # + # In this description, 'association name' shall refer to the name passed + # to an association creation method. For example, a model that specifies + # belongs_to :author, has_many :buyers has association + # names +:author+ and +:buyers+. + # + # == Parameters + # +records+ is an array of ActiveRecord::Base. This array needs not be flat, + # i.e. +records+ itself may also contain arrays of records. In any case, + # +preload_associations+ will preload all associations records by + # flattening +records+. + # + # +associations+ specifies one or more associations that you want to + # preload. It may be: + # - a Symbol or a String which specifies a single association name. For + # example, specifying +:books+ allows this method to preload all books + # for an Author. + # - an Array which specifies multiple association names. This array + # is processed recursively. For example, specifying [:avatar, :books] + # allows this method to preload an author's avatar as well as all of their + # books. + # - a Hash which specifies multiple association names, as well as + # association names for the to-be-preloaded association objects. For + # example, specifying { author: :avatar } will preload a + # book's author, as well as that author's avatar. + # + # +:associations+ has the same format as the arguments to + # ActiveRecord::QueryMethods#includes. So +associations+ could look like + # this: + # + # :books + # [ :books, :author ] + # { author: :avatar } + # [ :books, { author: :avatar } ] + # + # +available_records+ is an array of ActiveRecord::Base. The Preloader + # will try to use the objects in this array to preload the requested + # associations before querying the database. This can save database + # queries by reusing in-memory objects. The optimization is only applied + # to single associations (i.e. :belongs_to, :has_one) with no scopes. + def initialize(records:, associations:, scope: nil, available_records: [], associate_by_default: true) + @records = records + @associations = associations + @scope = scope + @available_records = available_records || [] + @associate_by_default = associate_by_default + + @tree = Branch.new( + parent: nil, + association: nil, + children: @associations, + associate_by_default: @associate_by_default, + scope: @scope + ) + @tree.preloaded_records = @records + end + + def empty? + associations.nil? || records.length == 0 + end + + def call + Batch.new([self], available_records: @available_records).call + + loaders + end + + def branches + @tree.children + end + + def loaders + branches.flat_map(&:loaders) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/preloader/association.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/preloader/association.rb new file mode 100644 index 00000000..84f4f09a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/preloader/association.rb @@ -0,0 +1,316 @@ +# frozen_string_literal: true + +# :enddoc: + +module ActiveRecord + module Associations + class Preloader + class Association # :nodoc: + class LoaderQuery + attr_reader :scope, :association_key_name + + def initialize(scope, association_key_name) + @scope = scope + @association_key_name = association_key_name + end + + def eql?(other) + association_key_name == other.association_key_name && + scope.table_name == other.scope.table_name && + scope.model.connection_specification_name == other.scope.model.connection_specification_name && + scope.values_for_queries == other.scope.values_for_queries + end + + def hash + [association_key_name, scope.model.table_name, scope.model.connection_specification_name, scope.values_for_queries].hash + end + + def records_for(loaders) + LoaderRecords.new(loaders, self).records + end + + def load_records_in_batch(loaders) + raw_records = records_for(loaders) + + loaders.each do |loader| + loader.load_records(raw_records) + loader.run + end + end + + def load_records_for_keys(keys, &block) + return [] if keys.empty? + + if association_key_name.is_a?(Array) + query_constraints = Hash.new { |hsh, key| hsh[key] = Set.new } + + keys.each_with_object(query_constraints) do |values_set, constraints| + association_key_name.zip(values_set).each do |key_name, value| + constraints[key_name] << value + end + end + + scope.where(query_constraints) + else + scope.where(association_key_name => keys) + end.load(&block) + end + end + + class LoaderRecords + def initialize(loaders, loader_query) + @loader_query = loader_query + @loaders = loaders + @keys_to_load = Set.new + @already_loaded_records_by_key = {} + + populate_keys_to_load_and_already_loaded_records + end + + def records + load_records + already_loaded_records + end + + private + attr_reader :loader_query, :loaders, :keys_to_load, :already_loaded_records_by_key + + def populate_keys_to_load_and_already_loaded_records + loaders.each do |loader| + loader.owners_by_key.each do |key, owners| + if loaded_owner = owners.find { |owner| loader.loaded?(owner) } + already_loaded_records_by_key[key] = loader.target_for(loaded_owner) + else + keys_to_load << key + end + end + end + + @keys_to_load.subtract(already_loaded_records_by_key.keys) + end + + def load_records + loader_query.load_records_for_keys(keys_to_load) do |record| + loaders.each { |l| l.set_inverse(record) } + end + end + + def already_loaded_records + already_loaded_records_by_key.values.flatten + end + end + + attr_reader :klass + + def initialize(klass, owners, reflection, preload_scope, reflection_scope, associate_by_default) + @klass = klass + @owners = owners.uniq(&:__id__) + @reflection = reflection + @preload_scope = preload_scope + @reflection_scope = reflection_scope + @associate = associate_by_default || !preload_scope || preload_scope.empty_scope? + @model = owners.first && owners.first.class + @run = false + end + + def table_name + @klass.table_name + end + + def future_classes + if run? + [] + else + [@klass] + end + end + + def runnable_loaders + [self] + end + + def run? + @run + end + + def run + return self if run? + @run = true + + records = records_by_owner + + owners.each do |owner| + associate_records_to_owner(owner, records[owner] || []) + end if @associate + + self + end + + def records_by_owner + load_records unless defined?(@records_by_owner) + + @records_by_owner + end + + def preloaded_records + load_records unless defined?(@preloaded_records) + + @preloaded_records + end + + # The name of the key on the associated records + def association_key_name + reflection.join_primary_key(klass) + end + + def loader_query + LoaderQuery.new(scope, association_key_name) + end + + def owners_by_key + @owners_by_key ||= owners.each_with_object({}) do |owner, result| + key = derive_key(owner, owner_key_name) + (result[key] ||= []) << owner if key + end + end + + def loaded?(owner) + owner.association(reflection.name).loaded? + end + + def target_for(owner) + Array.wrap(owner.association(reflection.name).target) + end + + def scope + @scope ||= build_scope + end + + def set_inverse(record) + if owners = owners_by_key[derive_key(record, association_key_name)] + # Processing only the first owner + # because the record is modified but not an owner + association = owners.first.association(reflection.name) + association.set_inverse_instance(record) + end + end + + def load_records(raw_records = nil) + # owners can be duplicated when a relation has a collection association join + # #compare_by_identity makes such owners different hash keys + @records_by_owner = {}.compare_by_identity + raw_records ||= loader_query.records_for([self]) + @preloaded_records = raw_records.select do |record| + assignments = false + + owners_by_key[derive_key(record, association_key_name)]&.each do |owner| + entries = (@records_by_owner[owner] ||= []) + + if reflection.collection? || entries.empty? + entries << record + assignments = true + end + end + + assignments + end + end + + def associate_records_from_unscoped(unscoped_records) + return if unscoped_records.nil? || unscoped_records.empty? + return if !reflection_scope.empty_scope? + return if preload_scope && !preload_scope.empty_scope? + return if reflection.collection? + + unscoped_records.select { |r| r[association_key_name].present? }.each do |record| + owners = owners_by_key[derive_key(record, association_key_name)] + owners&.each_with_index do |owner, i| + association = owner.association(reflection.name) + association.target = record + + if i == 0 # Set inverse on first owner + association.set_inverse_instance(record) + end + end + end + end + + private + attr_reader :owners, :reflection, :preload_scope, :model + + # The name of the key on the model which declares the association + def owner_key_name + reflection.join_foreign_key + end + + def associate_records_to_owner(owner, records) + return if loaded?(owner) + + association = owner.association(reflection.name) + + if reflection.collection? + not_persisted_records = association.target.reject(&:persisted?) + association.target = records + not_persisted_records + else + association.target = records.first + end + end + + def key_conversion_required? + unless defined?(@key_conversion_required) + @key_conversion_required = (association_key_type != owner_key_type) + end + + @key_conversion_required + end + + def derive_key(owner, key) + if key.is_a?(Array) + key.map { |k| convert_key(owner._read_attribute(k)) } + else + convert_key(owner._read_attribute(key)) + end + end + + def convert_key(key) + if key_conversion_required? + key.to_s + else + key + end + end + + def association_key_type + @klass.type_for_attribute(association_key_name).type + end + + def owner_key_type + @model.type_for_attribute(owner_key_name).type + end + + def reflection_scope + @reflection_scope ||= reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(klass.unscoped, &:merge!) + end + + def build_scope + scope = klass.scope_for_association + + if reflection.type && !reflection.through_reflection? + scope.where!(reflection.type => model.polymorphic_name) + end + + scope.merge!(reflection_scope) unless reflection_scope.empty_scope? + + if preload_scope && !preload_scope.empty_scope? + scope.merge!(preload_scope) + end + + cascade_strict_loading(scope) + end + + def cascade_strict_loading(scope) + preload_scope&.strict_loading_value ? scope.strict_loading : scope + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/preloader/batch.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/preloader/batch.rb new file mode 100644 index 00000000..a0048d0f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/preloader/batch.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + class Preloader + class Batch # :nodoc: + def initialize(preloaders, available_records:) + @preloaders = preloaders.reject(&:empty?) + @available_records = available_records.flatten.group_by { |r| r.class.base_class } + end + + def call + branches = @preloaders.flat_map(&:branches) + until branches.empty? + loaders = branches.flat_map(&:runnable_loaders) + + loaders.each { |loader| loader.associate_records_from_unscoped(@available_records[loader.klass.base_class]) } + + if loaders.any? + future_tables = branches.flat_map do |branch| + branch.future_classes - branch.runnable_loaders.map(&:klass) + end.map(&:table_name).uniq + + target_loaders = loaders.reject { |l| future_tables.include?(l.table_name) } + target_loaders = loaders if target_loaders.empty? + + group_and_load_similar(target_loaders) + target_loaders.each(&:run) + end + + finished, in_progress = branches.partition(&:done?) + + branches = in_progress + finished.flat_map(&:children) + end + end + + private + attr_reader :loaders + + def group_and_load_similar(loaders) + loaders.grep_v(ThroughAssociation).group_by(&:loader_query).each_pair do |query, similar_loaders| + query.load_records_in_batch(similar_loaders) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/preloader/branch.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/preloader/branch.rb new file mode 100644 index 00000000..13bac93d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/preloader/branch.rb @@ -0,0 +1,153 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + class Preloader + class Branch # :nodoc: + attr_reader :association, :children, :parent + attr_reader :scope, :associate_by_default + attr_writer :preloaded_records + + def initialize(association:, children:, parent:, associate_by_default:, scope:) + @association = if association + begin + @association = association.to_sym + rescue NoMethodError + raise ArgumentError, "Association names must be Symbol or String, got: #{association.class.name}" + end + end + @parent = parent + @scope = scope + @associate_by_default = associate_by_default + + @children = build_children(children) + @loaders = nil + end + + def future_classes + (immediate_future_classes + children.flat_map(&:future_classes)).uniq + end + + def immediate_future_classes + if parent.done? + loaders.flat_map(&:future_classes).uniq + else + likely_reflections.reject(&:polymorphic?).flat_map do |reflection| + reflection. + chain. + map(&:klass) + end.uniq + end + end + + def target_classes + if done? + preloaded_records.map(&:klass).uniq + elsif parent.done? + loaders.map(&:klass).uniq + else + likely_reflections.reject(&:polymorphic?).map(&:klass).uniq + end + end + + def likely_reflections + parent_classes = parent.target_classes + parent_classes.filter_map do |parent_klass| + parent_klass._reflect_on_association(@association) + end + end + + def root? + parent.nil? + end + + def source_records + @parent.preloaded_records + end + + def preloaded_records + @preloaded_records ||= loaders.flat_map(&:preloaded_records) + end + + def done? + root? || (@loaders && @loaders.all?(&:run?)) + end + + def runnable_loaders + loaders.flat_map(&:runnable_loaders).reject(&:run?) + end + + def grouped_records + h = {} + polymorphic_parent = !root? && parent.polymorphic? + source_records.each do |record| + reflection = record.class._reflect_on_association(association) + next if polymorphic_parent && !reflection || !record.association(association).klass + (h[reflection] ||= []) << record + end + h + end + + def preloaders_for_reflection(reflection, reflection_records) + reflection_records.group_by do |record| + klass = record.association(association).klass + + if reflection.scope && reflection.scope.arity != 0 + # For instance dependent scopes, the scope is potentially + # different for each record. To allow this we'll group each + # object separately into its own preloader + reflection_scope = reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass, record).inject(&:merge!) + end + + [klass, reflection_scope] + end.map do |(rhs_klass, reflection_scope), rs| + preloader_for(reflection).new(rhs_klass, rs, reflection, scope, reflection_scope, associate_by_default) + end + end + + def polymorphic? + return false if root? + return @polymorphic if defined?(@polymorphic) + + @polymorphic = source_records.any? do |record| + reflection = record.class._reflect_on_association(association) + reflection && reflection.options[:polymorphic] + end + end + + def loaders + @loaders ||= + grouped_records.flat_map do |reflection, reflection_records| + preloaders_for_reflection(reflection, reflection_records) + end + end + + private + def build_children(children) + Array.wrap(children).flat_map { |association| + Array(association).flat_map { |parent, child| + Branch.new( + parent: self, + association: parent, + children: child, + associate_by_default: associate_by_default, + scope: scope + ) + } + } + end + + # Returns a class containing the logic needed to load preload the data + # and attach it to a relation. The class returned implements a `run` method + # that accepts a preloader. + def preloader_for(reflection) + if reflection.options[:through] + ThroughAssociation + else + Association + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/preloader/through_association.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/preloader/through_association.rb new file mode 100644 index 00000000..50d90315 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/preloader/through_association.rb @@ -0,0 +1,150 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + class Preloader + class ThroughAssociation < Association # :nodoc: + def preloaded_records + @preloaded_records ||= source_preloaders.flat_map(&:preloaded_records) + end + + def records_by_owner + @records_by_owner ||= owners.each_with_object({}) do |owner, result| + if loaded?(owner) + result[owner] = target_for(owner) + next + end + + through_records = through_records_by_owner[owner] || [] + + if owners.first.association(through_reflection.name).loaded? + if source_type = reflection.options[:source_type] + through_records = through_records.select do |record| + record[reflection.foreign_type] == source_type + end + end + end + + records = through_records.flat_map do |record| + source_records_by_owner[record] + end + + records.compact! + records.sort_by! { |rhs| preload_index[rhs] } if scope.order_values.any? + records.uniq! if scope.distinct_value + result[owner] = records + end + end + + def runnable_loaders + if data_available? + [self] + elsif through_preloaders.all?(&:run?) + source_preloaders.flat_map(&:runnable_loaders) + else + through_preloaders.flat_map(&:runnable_loaders) + end + end + + def future_classes + if run? + [] + elsif through_preloaders.all?(&:run?) + source_preloaders.flat_map(&:future_classes).uniq + else + through_classes = through_preloaders.flat_map(&:future_classes) + source_classes = source_reflection. + chain. + reject { |reflection| reflection.respond_to?(:polymorphic?) && reflection.polymorphic? }. + map(&:klass) + (through_classes + source_classes).uniq + end + end + + private + def data_available? + owners.all? { |owner| loaded?(owner) } || + through_preloaders.all?(&:run?) && source_preloaders.all?(&:run?) + end + + def source_preloaders + @source_preloaders ||= ActiveRecord::Associations::Preloader.new(records: middle_records, associations: source_reflection.name, scope: scope, associate_by_default: false).loaders + end + + def middle_records + through_records_by_owner.values.flatten + end + + def through_preloaders + @through_preloaders ||= ActiveRecord::Associations::Preloader.new(records: owners, associations: through_reflection.name, scope: through_scope, associate_by_default: false).loaders + end + + def through_reflection + reflection.through_reflection + end + + def source_reflection + reflection.source_reflection + end + + def source_records_by_owner + @source_records_by_owner ||= source_preloaders.map(&:records_by_owner).reduce(:merge) + end + + def through_records_by_owner + @through_records_by_owner ||= through_preloaders.map(&:records_by_owner).reduce(:merge) + end + + def preload_index + @preload_index ||= preloaded_records.each_with_object({}).with_index do |(record, result), index| + result[record] = index + end + end + + def through_scope + scope = through_reflection.klass.unscoped + options = reflection.options + + return scope if options[:disable_joins] + + values = reflection_scope.values + if annotations = values[:annotate] + scope.annotate!(*annotations) + end + + if options[:source_type] + scope.where! reflection.foreign_type => options[:source_type] + elsif !reflection_scope.where_clause.empty? + scope.where_clause = reflection_scope.where_clause + + if includes = values[:includes] + scope.includes!(source_reflection.name => includes) + else + scope.includes!(source_reflection.name) + end + + if values[:references] && !values[:references].empty? + scope.references_values |= values[:references] + else + scope.references!(source_reflection.table_name) + end + + if joins = values[:joins] + scope.joins!(source_reflection.name => joins) + end + + if left_outer_joins = values[:left_outer_joins] + scope.left_outer_joins!(source_reflection.name => left_outer_joins) + end + + if scope.eager_loading? && order_values = values[:order] + scope = scope.order(order_values) + end + end + + cascade_strict_loading(scope) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/singular_association.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/singular_association.rb new file mode 100644 index 00000000..66fa00e1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/singular_association.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + class SingularAssociation < Association # :nodoc: + # Implements the reader method, e.g. foo.bar for Foo.has_one :bar + def reader + ensure_klass_exists! + + if !loaded? || stale_target? + reload + end + + target + end + + # Resets the \loaded flag to +false+ and sets the \target to +nil+. + def reset + super + @target = nil + @future_target = nil + end + + # Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar + def writer(record) + replace(record) + end + + def build(attributes = nil, &block) + record = build_record(attributes, &block) + set_new_record(record) + record + end + + # Implements the reload reader method, e.g. foo.reload_bar for + # Foo.has_one :bar + def force_reload_reader + reload(true) + target + end + + private + def scope_for_create + super.except!(*Array(klass.primary_key)) + end + + def find_target(async: false) + if disable_joins + if async + scope.load_async.then(&:first) + else + scope.first + end + else + super.then(&:first) + end + end + + def replace(record) + raise NotImplementedError, "Subclasses must implement a replace(record) method" + end + + def set_new_record(record) + replace(record) + end + + def _create_record(attributes, raise_error = false, &block) + record = build_record(attributes, &block) + saved = record.save + set_new_record(record) + raise RecordInvalid.new(record) if !saved && raise_error + record + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/through_association.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/through_association.rb new file mode 100644 index 00000000..7389ecc7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/associations/through_association.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # = Active Record Through Association + module ThroughAssociation # :nodoc: + delegate :source_reflection, to: :reflection + + private + def transaction(&block) + through_reflection.klass.transaction(&block) + end + + def through_reflection + @through_reflection ||= begin + refl = reflection.through_reflection + + while refl.through_reflection? + refl = refl.through_reflection + end + + refl + end + end + + def through_association + @through_association ||= owner.association(through_reflection.name) + end + + # We merge in these scopes for two reasons: + # + # 1. To get the default_scope conditions for any of the other reflections in the chain + # 2. To get the type conditions for any STI models in the chain + def target_scope + scope = super + reflection.chain.drop(1).each do |reflection| + relation = reflection.klass.scope_for_association + scope.merge!( + relation.except(:select, :create_with, :includes, :preload, :eager_load, :joins, :left_outer_joins) + ) + end + scope + end + + # Construct attributes for :through pointing to owner and associate. This is used by the + # methods which create and delete records on the association. + # + # We only support indirectly modifying through associations which have a belongs_to source. + # This is the "has_many :tags, through: :taggings" situation, where the join model + # typically has a belongs_to on both side. In other words, associations which could also + # be represented as has_and_belongs_to_many associations. + # + # We do not support creating/deleting records on the association where the source has + # some other type, because this opens up a whole can of worms, and in basically any + # situation it is more natural for the user to just create or modify their join records + # directly as required. + def construct_join_attributes(*records) + ensure_mutable + + association_primary_key = source_reflection.association_primary_key(reflection.klass) + + if Array(association_primary_key) == reflection.klass.composite_query_constraints_list && !options[:source_type] + join_attributes = { source_reflection.name => records } + else + assoc_pk_values = records.map { |record| record._read_attribute(association_primary_key) } + join_attributes = { source_reflection.foreign_key => assoc_pk_values } + end + + if options[:source_type] + join_attributes[source_reflection.foreign_type] = [ options[:source_type] ] + end + + if records.count == 1 + join_attributes.transform_values!(&:first) + else + join_attributes + end + end + + # Note: this does not capture all cases, for example it would be impractical + # to try to properly support stale-checking for nested associations. + def stale_state + if through_reflection.belongs_to? + Array(through_reflection.foreign_key).filter_map do |foreign_key_column| + owner[foreign_key_column] + end.presence + end + end + + def foreign_key_present? + through_reflection.belongs_to? && Array(through_reflection.foreign_key).all? do |foreign_key_column| + !owner[foreign_key_column].nil? + end + end + + def ensure_mutable + unless source_reflection.belongs_to? + if reflection.has_one? + raise HasOneThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection) + else + raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection) + end + end + end + + def ensure_not_nested + if reflection.nested? + if reflection.has_one? + raise HasOneThroughNestedAssociationsAreReadonly.new(owner, reflection) + else + raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection) + end + end + end + + def build_record(attributes) + if source_reflection.collection? + inverse = source_reflection.inverse_of + target = through_association.target + + if inverse && target && !target.is_a?(Array) + Array(target.id).zip(Array(inverse.foreign_key)).map do |primary_key_value, foreign_key_column| + attributes[foreign_key_column] = primary_key_value + end + end + end + + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/asynchronous_queries_tracker.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/asynchronous_queries_tracker.rb new file mode 100644 index 00000000..322adb59 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/asynchronous_queries_tracker.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require "concurrent/atomic/atomic_boolean" +require "concurrent/atomic/read_write_lock" + +module ActiveRecord + class AsynchronousQueriesTracker # :nodoc: + class Session # :nodoc: + def initialize + @active = Concurrent::AtomicBoolean.new(true) + @lock = Concurrent::ReadWriteLock.new + end + + def active? + @active.true? + end + + def synchronize(&block) + @lock.with_read_lock(&block) + end + + def finalize(wait = false) + @active.make_false + if wait + # Wait until all thread with a read lock are done + @lock.with_write_lock { } + end + end + end + + class << self + def install_executor_hooks(executor = ActiveSupport::Executor) + executor.register_hook(self) + end + + def run + ActiveRecord::Base.asynchronous_queries_tracker.tap(&:start_session) + end + + def complete(asynchronous_queries_tracker) + asynchronous_queries_tracker.finalize_session + end + end + + def initialize + @stack = [] + end + + def current_session + @stack.last or raise ActiveRecordError, "Can't perform asynchronous queries without a query session" + end + + def start_session + session = Session.new + @stack << session + end + + def finalize_session(wait = false) + session = @stack.pop + session&.finalize(wait) + self + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_assignment.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_assignment.rb new file mode 100644 index 00000000..bd246578 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_assignment.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeAssignment + private + def _assign_attributes(attributes) + multi_parameter_attributes = nested_parameter_attributes = nil + + attributes.each do |k, v| + key = k.to_s + + if key.include?("(") + (multi_parameter_attributes ||= {})[key] = v + elsif v.is_a?(Hash) + (nested_parameter_attributes ||= {})[key] = v + else + _assign_attribute(key, v) + end + end + + assign_nested_parameter_attributes(nested_parameter_attributes) if nested_parameter_attributes + assign_multiparameter_attributes(multi_parameter_attributes) if multi_parameter_attributes + end + + # Assign any deferred nested attributes after the base attributes have been set. + def assign_nested_parameter_attributes(pairs) + pairs.each { |k, v| _assign_attribute(k, v) } + end + + # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done + # by calling new on the column type or aggregation type (through composed_of) object with these parameters. + # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate + # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the + # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and + # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+. + def assign_multiparameter_attributes(pairs) + execute_callstack_for_multiparameter_attributes( + extract_callstack_for_multiparameter_attributes(pairs) + ) + end + + def execute_callstack_for_multiparameter_attributes(callstack) + errors = [] + callstack.each do |name, values_with_empty_parameters| + if values_with_empty_parameters.each_value.all?(NilClass) + values = nil + else + values = values_with_empty_parameters + end + send("#{name}=", values) + rescue => ex + errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name) + end + unless errors.empty? + error_descriptions = errors.map(&:message).join(",") + raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]" + end + end + + def extract_callstack_for_multiparameter_attributes(pairs) + attributes = {} + + pairs.each do |(multiparameter_name, value)| + attribute_name = multiparameter_name.split("(").first + attributes[attribute_name] ||= {} + + parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value) + attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value + end + + attributes + end + + def type_cast_attribute_value(multiparameter_name, value) + multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value + end + + def find_parameter_position(multiparameter_name) + multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods.rb new file mode 100644 index 00000000..e5179bf7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods.rb @@ -0,0 +1,547 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" + +module ActiveRecord + # = Active Record Attribute Methods + module AttributeMethods + extend ActiveSupport::Concern + include ActiveModel::AttributeMethods + + included do + initialize_generated_modules + include Read + include Write + include BeforeTypeCast + include Query + include PrimaryKey + include TimeZoneConversion + include Dirty + include Serialization + end + + RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name superclass) + + class GeneratedAttributeMethods < Module # :nodoc: + LOCK = Monitor.new + end + + class << self + def dangerous_attribute_methods # :nodoc: + @dangerous_attribute_methods ||= ( + Base.instance_methods + + Base.private_instance_methods - + Base.superclass.instance_methods - + Base.superclass.private_instance_methods + + %i[__id__ dup freeze frozen? hash class clone] + ).map { |m| -m.to_s }.to_set.freeze + end + end + + module ClassMethods + def initialize_generated_modules # :nodoc: + @generated_attribute_methods = const_set(:GeneratedAttributeMethods, GeneratedAttributeMethods.new) + private_constant :GeneratedAttributeMethods + @attribute_methods_generated = false + @alias_attributes_mass_generated = false + include @generated_attribute_methods + + super + end + + # Allows you to make aliases for attributes. + # + # class Person < ActiveRecord::Base + # alias_attribute :nickname, :name + # end + # + # person = Person.create(name: 'Bob') + # person.name # => "Bob" + # person.nickname # => "Bob" + # + # The alias can also be used for querying: + # + # Person.where(nickname: "Bob") + # # SELECT "people".* FROM "people" WHERE "people"."name" = "Bob" + def alias_attribute(new_name, old_name) + super + + if @alias_attributes_mass_generated + ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator| + generate_alias_attribute_methods(code_generator, new_name, old_name) + end + end + end + + def eagerly_generate_alias_attribute_methods(_new_name, _old_name) # :nodoc: + # alias attributes in Active Record are lazily generated + end + + def generate_alias_attribute_methods(code_generator, new_name, old_name) # :nodoc: + attribute_method_patterns.each do |pattern| + alias_attribute_method_definition(code_generator, pattern, new_name, old_name) + end + attribute_method_patterns_cache.clear + end + + def alias_attribute_method_definition(code_generator, pattern, new_name, old_name) # :nodoc: + old_name = old_name.to_s + + if !abstract_class? && !has_attribute?(old_name) + raise ArgumentError, "#{self.name} model aliases `#{old_name}`, but `#{old_name}` is not an attribute. " \ + "Use `alias_method :#{new_name}, :#{old_name}` or define the method manually." + else + define_attribute_method_pattern(pattern, old_name, owner: code_generator, as: new_name, override: true) + end + end + + def attribute_methods_generated? # :nodoc: + @attribute_methods_generated + end + + # Generates all the attribute related methods for columns in the database + # accessors, mutators and query methods. + def define_attribute_methods # :nodoc: + return false if @attribute_methods_generated + # Use a mutex; we don't want two threads simultaneously trying to define + # attribute methods. + GeneratedAttributeMethods::LOCK.synchronize do + return false if @attribute_methods_generated + + superclass.define_attribute_methods unless base_class? + + unless abstract_class? + load_schema + super(attribute_names) + alias_attribute :id_value, :id if _has_attribute?("id") + end + + generate_alias_attributes + + @attribute_methods_generated = true + end + + true + end + + def generate_alias_attributes # :nodoc: + superclass.generate_alias_attributes unless superclass == Base + + return if @alias_attributes_mass_generated + + ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator| + aliases_by_attribute_name.each do |old_name, new_names| + new_names.each do |new_name| + generate_alias_attribute_methods(code_generator, new_name, old_name) + end + end + end + + @alias_attributes_mass_generated = true + end + + def undefine_attribute_methods # :nodoc: + GeneratedAttributeMethods::LOCK.synchronize do + super if @attribute_methods_generated + @attribute_methods_generated = false + @alias_attributes_mass_generated = false + end + end + + # Raises an ActiveRecord::DangerousAttributeError exception when an + # \Active \Record method is defined in the model, otherwise +false+. + # + # class Person < ActiveRecord::Base + # def save + # 'already defined by Active Record' + # end + # end + # + # Person.instance_method_already_implemented?(:save) + # # => ActiveRecord::DangerousAttributeError: save is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name. + # + # Person.instance_method_already_implemented?(:name) + # # => false + def instance_method_already_implemented?(method_name) + if dangerous_attribute_method?(method_name) + raise DangerousAttributeError, "#{method_name} is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name." + end + + if superclass == Base + super + else + # If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass + # defines its own attribute method, then we don't want to override that. + defined = method_defined_within?(method_name, superclass, Base) && + ! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods) + defined || super + end + end + + # A method name is 'dangerous' if it is already (re)defined by Active Record, but + # not by any ancestors. (So 'puts' is not dangerous but 'save' is.) + def dangerous_attribute_method?(name) # :nodoc: + ::ActiveRecord::AttributeMethods.dangerous_attribute_methods.include?(name.to_s) + end + + def method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc: + if klass.method_defined?(name) || klass.private_method_defined?(name) + if superklass.method_defined?(name) || superklass.private_method_defined?(name) + klass.instance_method(name).owner != superklass.instance_method(name).owner + else + true + end + else + false + end + end + + # A class method is 'dangerous' if it is already (re)defined by Active Record, but + # not by any ancestors. (So 'puts' is not dangerous but 'new' is.) + def dangerous_class_method?(method_name) + return true if RESTRICTED_CLASS_METHODS.include?(method_name.to_s) + + if Base.respond_to?(method_name, true) + if Object.respond_to?(method_name, true) + Base.method(method_name).owner != Object.method(method_name).owner + else + true + end + else + false + end + end + + # Returns +true+ if +attribute+ is an attribute method and table exists, + # +false+ otherwise. + # + # class Person < ActiveRecord::Base + # end + # + # Person.attribute_method?('name') # => true + # Person.attribute_method?(:age=) # => true + # Person.attribute_method?(:nothing) # => false + def attribute_method?(attribute) + super || (table_exists? && column_names.include?(attribute.to_s.delete_suffix("="))) + end + + # Returns an array of column names as strings if it's not an abstract class and + # table exists. Otherwise it returns an empty array. + # + # class Person < ActiveRecord::Base + # end + # + # Person.attribute_names + # # => ["id", "created_at", "updated_at", "name", "age"] + def attribute_names + @attribute_names ||= if !abstract_class? && table_exists? + attribute_types.keys + else + [] + end.freeze + end + + # Returns true if the given attribute exists, otherwise false. + # + # class Person < ActiveRecord::Base + # alias_attribute :new_name, :name + # end + # + # Person.has_attribute?('name') # => true + # Person.has_attribute?('new_name') # => true + # Person.has_attribute?(:age) # => true + # Person.has_attribute?(:nothing) # => false + def has_attribute?(attr_name) + attr_name = attr_name.to_s + attr_name = attribute_aliases[attr_name] || attr_name + attribute_types.key?(attr_name) + end + + def _has_attribute?(attr_name) # :nodoc: + attribute_types.key?(attr_name) + end + + private + def inherited(child_class) + super + child_class.initialize_generated_modules + child_class.class_eval do + @alias_attributes_mass_generated = false + @attribute_names = nil + end + end + end + + # A Person object with a name attribute can ask person.respond_to?(:name), + # person.respond_to?(:name=), and person.respond_to?(:name?) + # which will all return +true+. It also defines the attribute methods if they have + # not been generated. + # + # class Person < ActiveRecord::Base + # end + # + # person = Person.new + # person.respond_to?(:name) # => true + # person.respond_to?(:name=) # => true + # person.respond_to?(:name?) # => true + # person.respond_to?('age') # => true + # person.respond_to?('age=') # => true + # person.respond_to?('age?') # => true + # person.respond_to?(:nothing) # => false + def respond_to?(name, include_private = false) + return false unless super + + # If the result is true then check for the select case. + # For queries selecting a subset of columns, return false for unselected columns. + if @attributes + if name = self.class.symbol_column_to_string(name.to_sym) + return _has_attribute?(name) + end + end + + true + end + + # Returns +true+ if the given attribute is in the attributes hash, otherwise +false+. + # + # class Person < ActiveRecord::Base + # alias_attribute :new_name, :name + # end + # + # person = Person.new + # person.has_attribute?(:name) # => true + # person.has_attribute?(:new_name) # => true + # person.has_attribute?('age') # => true + # person.has_attribute?(:nothing) # => false + def has_attribute?(attr_name) + attr_name = attr_name.to_s + attr_name = self.class.attribute_aliases[attr_name] || attr_name + @attributes.key?(attr_name) + end + + def _has_attribute?(attr_name) # :nodoc: + @attributes.key?(attr_name) + end + + # Returns an array of names for the attributes available on this object. + # + # class Person < ActiveRecord::Base + # end + # + # person = Person.new + # person.attribute_names + # # => ["id", "created_at", "updated_at", "name", "age"] + def attribute_names + @attributes.keys + end + + # Returns a hash of all the attributes with their names as keys and the values of the attributes as values. + # + # class Person < ActiveRecord::Base + # end + # + # person = Person.create(name: 'Francesco', age: 22) + # person.attributes + # # => {"id"=>3, "created_at"=>Sun, 21 Oct 2012 04:53:04, "updated_at"=>Sun, 21 Oct 2012 04:53:04, "name"=>"Francesco", "age"=>22} + def attributes + @attributes.to_hash + end + + # Returns an #inspect-like string for the value of the + # attribute +attr_name+. String attributes are truncated up to 50 + # characters. Other attributes return the value of #inspect + # without modification. + # + # person = Person.create!(name: 'David Heinemeier Hansson ' * 3) + # + # person.attribute_for_inspect(:name) + # # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\"" + # + # person.attribute_for_inspect(:created_at) + # # => "\"2012-10-22 00:15:07.000000000 +0000\"" + # + # person.attribute_for_inspect(:tag_ids) + # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]" + def attribute_for_inspect(attr_name) + attr_name = attr_name.to_s + attr_name = self.class.attribute_aliases[attr_name] || attr_name + value = _read_attribute(attr_name) + format_for_inspect(attr_name, value) + end + + # Returns +true+ if the specified +attribute+ has been set by the user or by a + # database load and is neither +nil+ nor empty? (the latter only applies + # to objects that respond to empty?, most notably Strings). Otherwise, +false+. + # Note that it always returns +true+ with boolean attributes. + # + # class Task < ActiveRecord::Base + # end + # + # task = Task.new(title: '', is_done: false) + # task.attribute_present?(:title) # => false + # task.attribute_present?(:is_done) # => true + # task.title = 'Buy milk' + # task.is_done = true + # task.attribute_present?(:title) # => true + # task.attribute_present?(:is_done) # => true + def attribute_present?(attr_name) + attr_name = attr_name.to_s + attr_name = self.class.attribute_aliases[attr_name] || attr_name + value = _read_attribute(attr_name) + !value.nil? && !(value.respond_to?(:empty?) && value.empty?) + end + + # Returns the value of the attribute identified by +attr_name+ after it has + # been type cast. (For information about specific type casting behavior, see + # the types under ActiveModel::Type.) + # + # class Person < ActiveRecord::Base + # belongs_to :organization + # end + # + # person = Person.new(name: "Francesco", date_of_birth: "2004-12-12") + # person[:name] # => "Francesco" + # person[:date_of_birth] # => Date.new(2004, 12, 12) + # person[:organization_id] # => nil + # + # Raises ActiveModel::MissingAttributeError if the attribute is missing. + # Note, however, that the +id+ attribute will never be considered missing. + # + # person = Person.select(:name).first + # person[:name] # => "Francesco" + # person[:date_of_birth] # => ActiveModel::MissingAttributeError: missing attribute 'date_of_birth' for Person + # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute 'organization_id' for Person + # person[:id] # => nil + def [](attr_name) + read_attribute(attr_name) { |n| missing_attribute(n, caller) } + end + + # Updates the attribute identified by +attr_name+ using the specified + # +value+. The attribute value will be type cast upon being read. + # + # class Person < ActiveRecord::Base + # end + # + # person = Person.new + # person[:date_of_birth] = "2004-12-12" + # person[:date_of_birth] # => Date.new(2004, 12, 12) + def []=(attr_name, value) + write_attribute(attr_name, value) + end + + # Returns the name of all database fields which have been read from this + # model. This can be useful in development mode to determine which fields + # need to be selected. For performance critical pages, selecting only the + # required fields can be an easy performance win (assuming you aren't using + # all of the fields on the model). + # + # For example: + # + # class PostsController < ActionController::Base + # after_action :print_accessed_fields, only: :index + # + # def index + # @posts = Post.all + # end + # + # private + # def print_accessed_fields + # p @posts.first.accessed_fields + # end + # end + # + # Which allows you to quickly change your code to: + # + # class PostsController < ActionController::Base + # def index + # @posts = Post.select(:id, :title, :author_id, :updated_at) + # end + # end + def accessed_fields + @attributes.accessed + end + + private + def respond_to_missing?(name, include_private = false) + if self.class.define_attribute_methods + # Some methods weren't defined yet. + return true if self.class.method_defined?(name) + return true if include_private && self.class.private_method_defined?(name) + end + + super + end + + def method_missing(name, ...) + # We can't know whether some method was defined or not because + # multiple thread might be concurrently be in this code path. + # So the first one would define the methods and the others would + # appear to already have them. + self.class.define_attribute_methods + + # So in all cases we must behave as if the method was just defined. + method = begin + self.class.public_instance_method(name) + rescue NameError + nil + end + + # The method might be explicitly defined in the model, but call a generated + # method with super. So we must resume the call chain at the right step. + method = method.super_method while method && !method.owner.is_a?(GeneratedAttributeMethods) + if method + method.bind_call(self, ...) + else + super + end + end + + def attribute_method?(attr_name) + @attributes&.key?(attr_name) + end + + def attributes_with_values(attribute_names) + attribute_names.index_with { |name| @attributes[name] } + end + + # Filters the primary keys, readonly attributes and virtual columns from the attribute names. + def attributes_for_update(attribute_names) + attribute_names &= self.class.column_names + attribute_names.delete_if do |name| + self.class.readonly_attribute?(name) || + self.class.counter_cache_column?(name) || + column_for_attribute(name).virtual? + end + end + + # Filters out the virtual columns and also primary keys, from the attribute names, when the primary + # key is to be generated (e.g. the id attribute has no value). + def attributes_for_create(attribute_names) + attribute_names &= self.class.column_names + attribute_names.delete_if do |name| + (pk_attribute?(name) && id.nil?) || + column_for_attribute(name).virtual? + end + end + + def format_for_inspect(name, value) + if value.nil? + value.inspect + else + inspected_value = if value.is_a?(String) && value.length > 50 + "#{value[0, 50]}...".inspect + elsif value.is_a?(Date) || value.is_a?(Time) + %("#{value.to_fs(:inspect)}") + else + value.inspect + end + + inspection_filter.filter_param(name, inspected_value) + end + end + + def pk_attribute?(name) + name == @primary_key + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/before_type_cast.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/before_type_cast.rb new file mode 100644 index 00000000..32841493 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/before_type_cast.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + # = Active Record Attribute Methods Before Type Cast + # + # ActiveRecord::AttributeMethods::BeforeTypeCast provides a way to + # read the value of the attributes before typecasting and deserialization. + # + # class Task < ActiveRecord::Base + # end + # + # task = Task.new(id: '1', completed_on: '2012-10-21') + # task.id # => 1 + # task.completed_on # => Sun, 21 Oct 2012 + # + # task.attributes_before_type_cast + # # => {"id"=>"1", "completed_on"=>"2012-10-21", ... } + # task.read_attribute_before_type_cast('id') # => "1" + # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21" + # + # In addition to #read_attribute_before_type_cast and #attributes_before_type_cast, + # it declares a method for all attributes with the *_before_type_cast + # suffix. + # + # task.id_before_type_cast # => "1" + # task.completed_on_before_type_cast # => "2012-10-21" + module BeforeTypeCast + extend ActiveSupport::Concern + + included do + attribute_method_suffix "_before_type_cast", "_for_database", parameters: false + attribute_method_suffix "_came_from_user?", parameters: false + end + + # Returns the value of the attribute identified by +attr_name+ before + # typecasting and deserialization. + # + # class Task < ActiveRecord::Base + # end + # + # task = Task.new(id: '1', completed_on: '2012-10-21') + # task.read_attribute('id') # => 1 + # task.read_attribute_before_type_cast('id') # => '1' + # task.read_attribute('completed_on') # => Sun, 21 Oct 2012 + # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21" + # task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21" + def read_attribute_before_type_cast(attr_name) + name = attr_name.to_s + name = self.class.attribute_aliases[name] || name + + attribute_before_type_cast(name) + end + + # Returns the value of the attribute identified by +attr_name+ after + # serialization. + # + # class Book < ActiveRecord::Base + # enum :status, { draft: 1, published: 2 } + # end + # + # book = Book.new(status: "published") + # book.read_attribute(:status) # => "published" + # book.read_attribute_for_database(:status) # => 2 + def read_attribute_for_database(attr_name) + name = attr_name.to_s + name = self.class.attribute_aliases[name] || name + + attribute_for_database(name) + end + + # Returns a hash of attributes before typecasting and deserialization. + # + # class Task < ActiveRecord::Base + # end + # + # task = Task.new(title: nil, is_done: true, completed_on: '2012-10-21') + # task.attributes + # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>Sun, 21 Oct 2012, "created_at"=>nil, "updated_at"=>nil} + # task.attributes_before_type_cast + # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil} + def attributes_before_type_cast + @attributes.values_before_type_cast + end + + # Returns a hash of attributes for assignment to the database. + def attributes_for_database + @attributes.values_for_database + end + + private + # Dispatch target for *_before_type_cast attribute methods. + def attribute_before_type_cast(attr_name) + @attributes[attr_name].value_before_type_cast + end + + def attribute_for_database(attr_name) + @attributes[attr_name].value_for_database + end + + def attribute_came_from_user?(attr_name) + @attributes[attr_name].came_from_user? + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/composite_primary_key.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/composite_primary_key.rb new file mode 100644 index 00000000..1c608ae2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/composite_primary_key.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + module CompositePrimaryKey # :nodoc: + # Returns the primary key column's value. If the primary key is composite, + # returns an array of the primary key column values. + def id + if self.class.composite_primary_key? + @primary_key.map { |pk| _read_attribute(pk) } + else + super + end + end + + def primary_key_values_present? # :nodoc: + if self.class.composite_primary_key? + id.all? + else + super + end + end + + # Sets the primary key column's value. If the primary key is composite, + # raises TypeError when the set value not enumerable. + def id=(value) + if self.class.composite_primary_key? + raise TypeError, "Expected value matching #{self.class.primary_key.inspect}, got #{value.inspect}." unless value.is_a?(Enumerable) + @primary_key.zip(value) { |attr, value| _write_attribute(attr, value) } + else + super + end + end + + # Queries the primary key column's value. If the primary key is composite, + # all primary key column values must be queryable. + def id? + if self.class.composite_primary_key? + @primary_key.all? { |col| _query_attribute(col) } + else + super + end + end + + # Returns the primary key column's value before type cast. If the primary key is composite, + # returns an array of primary key column values before type cast. + def id_before_type_cast + if self.class.composite_primary_key? + @primary_key.map { |col| attribute_before_type_cast(col) } + else + super + end + end + + # Returns the primary key column's previous value. If the primary key is composite, + # returns an array of primary key column previous values. + def id_was + if self.class.composite_primary_key? + @primary_key.map { |col| attribute_was(col) } + else + super + end + end + + # Returns the primary key column's value from the database. If the primary key is composite, + # returns an array of primary key column values from database. + def id_in_database + if self.class.composite_primary_key? + @primary_key.map { |col| attribute_in_database(col) } + else + super + end + end + + def id_for_database # :nodoc: + if self.class.composite_primary_key? + @primary_key.map { |col| @attributes[col].value_for_database } + else + super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/dirty.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/dirty.rb new file mode 100644 index 00000000..4d436a52 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/dirty.rb @@ -0,0 +1,262 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" + +module ActiveRecord + module AttributeMethods + # = Active Record Attribute Methods \Dirty + # + # Provides a way to track changes in your Active Record models. It adds all + # methods from ActiveModel::Dirty and adds database-specific methods. + # + # A newly created +Person+ object is unchanged: + # + # class Person < ActiveRecord::Base + # end + # + # person = Person.create(name: "Allison") + # person.changed? # => false + # + # Change the name: + # + # person.name = 'Alice' + # person.name_in_database # => "Allison" + # person.will_save_change_to_name? # => true + # person.name_change_to_be_saved # => ["Allison", "Alice"] + # person.changes_to_save # => {"name"=>["Allison", "Alice"]} + # + # Save the changes: + # + # person.save + # person.name_in_database # => "Alice" + # person.saved_change_to_name? # => true + # person.saved_change_to_name # => ["Allison", "Alice"] + # person.name_before_last_save # => "Allison" + # + # Similar to ActiveModel::Dirty, methods can be invoked as + # +saved_change_to_name?+ or by passing an argument to the generic method + # saved_change_to_attribute?("name"). + module Dirty + extend ActiveSupport::Concern + + include ActiveModel::Dirty + + included do + if self < ::ActiveRecord::Timestamp + raise "You cannot include Dirty after Timestamp" + end + + class_attribute :partial_updates, instance_writer: false, default: true + class_attribute :partial_inserts, instance_writer: false, default: true + + # Attribute methods for "changed in last call to save?" + attribute_method_affix(prefix: "saved_change_to_", suffix: "?", parameters: "**options") + attribute_method_prefix("saved_change_to_", parameters: false) + attribute_method_suffix("_before_last_save", parameters: false) + + # Attribute methods for "will change if I call save?" + attribute_method_affix(prefix: "will_save_change_to_", suffix: "?", parameters: "**options") + attribute_method_suffix("_change_to_be_saved", "_in_database", parameters: false) + end + + # reload the record and clears changed attributes. + def reload(*) + super.tap do + @mutations_before_last_save = nil + @mutations_from_database = nil + end + end + + # Did this attribute change when we last saved? + # + # This method is useful in after callbacks to determine if an attribute + # was changed during the save that triggered the callbacks to run. It can + # be invoked as +saved_change_to_name?+ instead of + # saved_change_to_attribute?("name"). + # + # ==== Options + # + # [+from+] + # When specified, this method will return false unless the original + # value is equal to the given value. + # + # [+to+] + # When specified, this method will return false unless the value will be + # changed to the given value. + def saved_change_to_attribute?(attr_name, **options) + mutations_before_last_save.changed?(attr_name.to_s, **options) + end + + # Returns the change to an attribute during the last save. If the + # attribute was changed, the result will be an array containing the + # original value and the saved value. + # + # This method is useful in after callbacks, to see the change in an + # attribute during the save that triggered the callbacks to run. It can be + # invoked as +saved_change_to_name+ instead of + # saved_change_to_attribute("name"). + def saved_change_to_attribute(attr_name) + mutations_before_last_save.change_to_attribute(attr_name.to_s) + end + + # Returns the original value of an attribute before the last save. + # + # This method is useful in after callbacks to get the original value of an + # attribute before the save that triggered the callbacks to run. It can be + # invoked as +name_before_last_save+ instead of + # attribute_before_last_save("name"). + def attribute_before_last_save(attr_name) + mutations_before_last_save.original_value(attr_name.to_s) + end + + # Did the last call to +save+ have any changes to change? + def saved_changes? + mutations_before_last_save.any_changes? + end + + # Returns a hash containing all the changes that were just saved. + def saved_changes + mutations_before_last_save.changes + end + + # Will this attribute change the next time we save? + # + # This method is useful in validations and before callbacks to determine + # if the next call to +save+ will change a particular attribute. It can be + # invoked as +will_save_change_to_name?+ instead of + # will_save_change_to_attribute?("name"). + # + # ==== Options + # + # [+from+] + # When specified, this method will return false unless the original + # value is equal to the given value. + # + # [+to+] + # When specified, this method will return false unless the value will be + # changed to the given value. + def will_save_change_to_attribute?(attr_name, **options) + mutations_from_database.changed?(attr_name.to_s, **options) + end + + # Returns the change to an attribute that will be persisted during the + # next save. + # + # This method is useful in validations and before callbacks, to see the + # change to an attribute that will occur when the record is saved. It can + # be invoked as +name_change_to_be_saved+ instead of + # attribute_change_to_be_saved("name"). + # + # If the attribute will change, the result will be an array containing the + # original value and the new value about to be saved. + def attribute_change_to_be_saved(attr_name) + mutations_from_database.change_to_attribute(attr_name.to_s) + end + + # Returns the value of an attribute in the database, as opposed to the + # in-memory value that will be persisted the next time the record is + # saved. + # + # This method is useful in validations and before callbacks, to see the + # original value of an attribute prior to any changes about to be + # saved. It can be invoked as +name_in_database+ instead of + # attribute_in_database("name"). + def attribute_in_database(attr_name) + mutations_from_database.original_value(attr_name.to_s) + end + + # Will the next call to +save+ have any changes to persist? + def has_changes_to_save? + mutations_from_database.any_changes? + end + + # Returns a hash containing all the changes that will be persisted during + # the next save. + def changes_to_save + mutations_from_database.changes + end + + # Returns an array of the names of any attributes that will change when + # the record is next saved. + def changed_attribute_names_to_save + mutations_from_database.changed_attribute_names + end + + # Returns a hash of the attributes that will change when the record is + # next saved. + # + # The hash keys are the attribute names, and the hash values are the + # original attribute values in the database (as opposed to the in-memory + # values about to be saved). + def attributes_in_database + mutations_from_database.changed_values + end + + private + def init_internals + super + @mutations_before_last_save = nil + @mutations_from_database = nil + @_touch_attr_names = nil + @_skip_dirty_tracking = nil + end + + def _touch_row(attribute_names, time) + @_touch_attr_names = Set.new(attribute_names) + + affected_rows = super + + if @_skip_dirty_tracking ||= false + clear_attribute_changes(@_touch_attr_names) + return affected_rows + end + + changes = {} + @attributes.keys.each do |attr_name| + next if @_touch_attr_names.include?(attr_name) + + if attribute_changed?(attr_name) + changes[attr_name] = _read_attribute(attr_name) + _write_attribute(attr_name, attribute_was(attr_name)) + clear_attribute_change(attr_name) + end + end + + changes_applied + changes.each { |attr_name, value| _write_attribute(attr_name, value) } + + affected_rows + ensure + @_touch_attr_names, @_skip_dirty_tracking = nil, nil + end + + def _update_record(attribute_names = attribute_names_for_partial_updates) + affected_rows = super + changes_applied + affected_rows + end + + def _create_record(attribute_names = attribute_names_for_partial_inserts) + id = super + changes_applied + id + end + + def attribute_names_for_partial_updates + partial_updates? ? changed_attribute_names_to_save : attribute_names + end + + def attribute_names_for_partial_inserts + if partial_inserts? + changed_attribute_names_to_save + else + attribute_names.reject do |attr_name| + if column_for_attribute(attr_name).auto_populated? + !attribute_changed?(attr_name) + end + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/primary_key.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/primary_key.rb new file mode 100644 index 00000000..8291d687 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/primary_key.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + # = Active Record Attribute Methods Primary Key + module PrimaryKey + extend ActiveSupport::Concern + + # Returns this record's primary key value wrapped in an array if one is + # available. + def to_key + key = id + Array(key) if key + end + + # Returns the primary key column's value. If the primary key is composite, + # returns an array of the primary key column values. + def id + _read_attribute(@primary_key) + end + + def primary_key_values_present? # :nodoc: + !!id + end + + # Sets the primary key column's value. If the primary key is composite, + # raises TypeError when the set value not enumerable. + def id=(value) + _write_attribute(@primary_key, value) + end + + # Queries the primary key column's value. If the primary key is composite, + # all primary key column values must be queryable. + def id? + _query_attribute(@primary_key) + end + + # Returns the primary key column's value before type cast. If the primary key is composite, + # returns an array of primary key column values before type cast. + def id_before_type_cast + attribute_before_type_cast(@primary_key) + end + + # Returns the primary key column's previous value. If the primary key is composite, + # returns an array of primary key column previous values. + def id_was + attribute_was(@primary_key) + end + + # Returns the primary key column's value from the database. If the primary key is composite, + # returns an array of primary key column values from database. + def id_in_database + attribute_in_database(@primary_key) + end + + def id_for_database # :nodoc: + @attributes[@primary_key].value_for_database + end + + private + def attribute_method?(attr_name) + attr_name == "id" || super + end + + module ClassMethods + ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database id_for_database).to_set + PRIMARY_KEY_NOT_SET = BasicObject.new + + def instance_method_already_implemented?(method_name) + super || primary_key && ID_ATTRIBUTE_METHODS.include?(method_name) + end + + def dangerous_attribute_method?(method_name) + super && !ID_ATTRIBUTE_METHODS.include?(method_name) + end + + # Defines the primary key field -- can be overridden in subclasses. + # Overwriting will negate any effect of the +primary_key_prefix_type+ + # setting, though. + def primary_key + reset_primary_key if PRIMARY_KEY_NOT_SET.equal?(@primary_key) + @primary_key + end + + def composite_primary_key? # :nodoc: + reset_primary_key if PRIMARY_KEY_NOT_SET.equal?(@primary_key) + @composite_primary_key + end + + # Returns a quoted version of the primary key name. + def quoted_primary_key + adapter_class.quote_column_name(primary_key) + end + + def reset_primary_key # :nodoc: + if base_class? + self.primary_key = get_primary_key(base_class.name) + else + self.primary_key = base_class.primary_key + end + end + + def get_primary_key(base_name) # :nodoc: + if base_name && primary_key_prefix_type == :table_name + base_name.foreign_key(false) + elsif base_name && primary_key_prefix_type == :table_name_with_underscore + base_name.foreign_key + elsif ActiveRecord::Base != self && table_exists? + schema_cache.primary_keys(table_name) + else + "id" + end + end + + # Sets the name of the primary key column. + # + # class Project < ActiveRecord::Base + # self.primary_key = 'sysid' + # end + # + # You can also define the #primary_key method yourself: + # + # class Project < ActiveRecord::Base + # def self.primary_key + # 'foo_' + super + # end + # end + # + # Project.primary_key # => "foo_id" + def primary_key=(value) + @primary_key = if value.is_a?(Array) + include CompositePrimaryKey + @primary_key = value.map { |v| -v.to_s }.freeze + elsif value + -value.to_s + end + + @composite_primary_key = value.is_a?(Array) + @attributes_builder = nil + end + + private + def inherited(base) + super + base.class_eval do + @primary_key = PRIMARY_KEY_NOT_SET + @composite_primary_key = false + @attributes_builder = nil + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/query.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/query.rb new file mode 100644 index 00000000..aa371ab4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/query.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + # = Active Record Attribute Methods \Query + module Query + extend ActiveSupport::Concern + + included do + attribute_method_suffix "?", parameters: false + end + + def query_attribute(attr_name) + value = self.public_send(attr_name) + + query_cast_attribute(attr_name, value) + end + + def _query_attribute(attr_name) # :nodoc: + value = self._read_attribute(attr_name.to_s) + + query_cast_attribute(attr_name, value) + end + + alias :attribute? :query_attribute + private :attribute? + + private + def query_cast_attribute(attr_name, value) + case value + when true then true + when false, nil then false + else + if !type_for_attribute(attr_name) { false } + if Numeric === value || !value.match?(/[^0-9]/) + !value.to_i.zero? + else + return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value) + !value.blank? + end + elsif value.respond_to?(:zero?) + !value.zero? + else + !value.blank? + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/read.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/read.rb new file mode 100644 index 00000000..3fa0befc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/read.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + # = Active Record Attribute Methods \Read + module Read + extend ActiveSupport::Concern + + module ClassMethods # :nodoc: + private + def define_method_attribute(canonical_name, owner:, as: canonical_name) + ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method( + owner, canonical_name + ) do |temp_method_name, attr_name_expr| + owner.define_cached_method(temp_method_name, as: as, namespace: :active_record) do |batch| + batch << + "def #{temp_method_name}" << + " _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" << + "end" + end + end + end + end + + # Returns the value of the attribute identified by +attr_name+ after it + # has been type cast. For example, a date attribute will cast "2004-12-12" + # to Date.new(2004, 12, 12). (For information about specific type + # casting behavior, see the types under ActiveModel::Type.) + def read_attribute(attr_name, &block) + name = attr_name.to_s + name = self.class.attribute_aliases[name] || name + + @attributes.fetch_value(name, &block) + end + + # This method exists to avoid the expensive primary_key check internally, without + # breaking compatibility with the read_attribute API + def _read_attribute(attr_name, &block) # :nodoc: + @attributes.fetch_value(attr_name, &block) + end + + alias :attribute :_read_attribute + private :attribute + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/serialization.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/serialization.rb new file mode 100644 index 00000000..569a9072 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/serialization.rb @@ -0,0 +1,232 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + # = Active Record Attribute Methods \Serialization + module Serialization + extend ActiveSupport::Concern + + class ColumnNotSerializableError < StandardError + def initialize(name, type) + super <<~EOS + Column `#{name}` of type #{type.class} does not support `serialize` feature. + Usually it means that you are trying to use `serialize` + on a column that already implements serialization natively. + EOS + end + end + + included do + class_attribute :default_column_serializer, instance_accessor: false, default: Coders::YAMLColumn + end + + module ClassMethods + # If you have an attribute that needs to be saved to the database as a + # serialized object, and retrieved by deserializing into the same object, + # then specify the name of that attribute using this method and serialization + # will be handled automatically. + # + # The serialization format may be YAML, JSON, or any custom format using a + # custom coder class. + # + # Keep in mind that database adapters handle certain serialization tasks + # for you. For instance: +json+ and +jsonb+ types in PostgreSQL will be + # converted between JSON object/array syntax and Ruby +Hash+ or +Array+ + # objects transparently. There is no need to use #serialize in this + # case. + # + # For more complex cases, such as conversion to or from your application + # domain objects, consider using the ActiveRecord::Attributes API. + # + # ==== Parameters + # + # * +attr_name+ - The name of the attribute to serialize. + # * +coder+ The serializer implementation to use, e.g. +JSON+. + # * The attribute value will be serialized + # using the coder's dump(value) method, and will be + # deserialized using the coder's load(string) method. The + # +dump+ method may return +nil+ to serialize the value as +NULL+. + # * +type+ - Optional. What the type of the serialized object should be. + # * Attempting to serialize another type will raise an + # ActiveRecord::SerializationTypeMismatch error. + # * If the column is +NULL+ or starting from a new record, the default value + # will set to +type.new+ + # * +yaml+ - Optional. Yaml specific options. The allowed config is: + # * +:permitted_classes+ - +Array+ with the permitted classes. + # * +:unsafe_load+ - Unsafely load YAML blobs, allow YAML to load any class. + # + # ==== Options + # + # * +:default+ - The default value to use when no value is provided. If + # this option is not passed, the previous default value (if any) will + # be used. Otherwise, the default will be +nil+. + # + # ==== Choosing a serializer + # + # While any serialization format can be used, it is recommended to carefully + # evaluate the properties of a serializer before using it, as migrating to + # another format later on can be difficult. + # + # ===== Avoid accepting arbitrary types + # + # When serializing data in a column, it is heavily recommended to make sure + # only expected types will be serialized. For instance some serializer like + # +Marshal+ or +YAML+ are capable of serializing almost any Ruby object. + # + # This can lead to unexpected types being serialized, and it is important + # that type serialization remains backward and forward compatible as long + # as some database records still contain these serialized types. + # + # class Address + # def initialize(line, city, country) + # @line, @city, @country = line, city, country + # end + # end + # + # In the above example, if any of the +Address+ attributes is renamed, + # instances that were persisted before the change will be loaded with the + # old attributes. This problem is even worse when the serialized type comes + # from a dependency which doesn't expect to be serialized this way and may + # change its internal representation without notice. + # + # As such, it is heavily recommended to instead convert these objects into + # primitives of the serialization format, for example: + # + # class Address + # attr_reader :line, :city, :country + # + # def self.load(payload) + # data = YAML.safe_load(payload) + # new(data["line"], data["city"], data["country"]) + # end + # + # def self.dump(address) + # YAML.safe_dump( + # "line" => address.line, + # "city" => address.city, + # "country" => address.country, + # ) + # end + # + # def initialize(line, city, country) + # @line, @city, @country = line, city, country + # end + # end + # + # class User < ActiveRecord::Base + # serialize :address, coder: Address + # end + # + # This pattern allows to be more deliberate about what is serialized, and + # to evolve the format in a backward compatible way. + # + # ===== Ensure serialization stability + # + # Some serialization methods may accept some types they don't support by + # silently casting them to other types. This can cause bugs when the + # data is deserialized. + # + # For instance the +JSON+ serializer provided in the standard library will + # silently cast unsupported types to +String+: + # + # >> JSON.parse(JSON.dump(Struct.new(:foo))) + # => "#" + # + # ==== Examples + # + # ===== Serialize the +preferences+ attribute using YAML + # + # class User < ActiveRecord::Base + # serialize :preferences, coder: YAML + # end + # + # ===== Serialize the +preferences+ attribute using JSON + # + # class User < ActiveRecord::Base + # serialize :preferences, coder: JSON + # end + # + # ===== Serialize the +preferences+ +Hash+ using YAML + # + # class User < ActiveRecord::Base + # serialize :preferences, type: Hash, coder: YAML + # end + # + # ===== Serializes +preferences+ to YAML, permitting select classes + # + # class User < ActiveRecord::Base + # serialize :preferences, coder: YAML, yaml: { permitted_classes: [Symbol, Time] } + # end + # + # ===== Serialize the +preferences+ attribute using a custom coder + # + # class Rot13JSON + # def self.rot13(string) + # string.tr("a-zA-Z", "n-za-mN-ZA-M") + # end + # + # # Serializes an attribute value to a string that will be stored in the database. + # def self.dump(value) + # rot13(ActiveSupport::JSON.dump(value)) + # end + # + # # Deserializes a string from the database to an attribute value. + # def self.load(string) + # ActiveSupport::JSON.load(rot13(string)) + # end + # end + # + # class User < ActiveRecord::Base + # serialize :preferences, coder: Rot13JSON + # end + # + def serialize(attr_name, coder: nil, type: Object, yaml: {}, **options) + coder ||= default_column_serializer + unless coder + raise ArgumentError, <<~MSG.squish + missing keyword: :coder + + If no default coder is configured, a coder must be provided to `serialize`. + MSG + end + + column_serializer = build_column_serializer(attr_name, coder, type, yaml) + + attribute(attr_name, **options) + + decorate_attributes([attr_name]) do |attr_name, cast_type| + if type_incompatible_with_serialize?(cast_type, coder, type) + raise ColumnNotSerializableError.new(attr_name, cast_type) + end + + cast_type = cast_type.subtype if Type::Serialized === cast_type + Type::Serialized.new(cast_type, column_serializer) + end + end + + private + def build_column_serializer(attr_name, coder, type, yaml = nil) + # When ::JSON is used, force it to go through the Active Support JSON encoder + # to ensure special objects (e.g. Active Record models) are dumped correctly + # using the #as_json hook. + coder = Coders::JSON if coder == ::JSON + + if coder == ::YAML || coder == Coders::YAMLColumn + Coders::YAMLColumn.new(attr_name, type, **(yaml || {})) + elsif coder.respond_to?(:new) && !coder.respond_to?(:load) + coder.new(attr_name, type) + elsif type && type != Object + Coders::ColumnSerializer.new(attr_name, coder, type) + else + coder + end + end + + def type_incompatible_with_serialize?(cast_type, coder, type) + cast_type.is_a?(ActiveRecord::Type::Json) && coder == ::JSON || + cast_type.respond_to?(:type_cast_array, true) && type == ::Array + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/time_zone_conversion.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/time_zone_conversion.rb new file mode 100644 index 00000000..01865e2d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/try" + +module ActiveRecord + module AttributeMethods + module TimeZoneConversion + class TimeZoneConverter < DelegateClass(Type::Value) # :nodoc: + def self.new(subtype) + self === subtype ? subtype : super + end + + def deserialize(value) + convert_time_to_time_zone(super) + end + + def cast(value) + return if value.nil? + + if value.is_a?(Hash) + set_time_zone_without_conversion(super) + elsif value.respond_to?(:in_time_zone) + begin + super(user_input_in_time_zone(value)) || super + rescue ArgumentError + nil + end + elsif value.respond_to?(:infinite?) && value.infinite? + value + else + map(super) { |v| cast(v) } + end + end + + def ==(other) + other.is_a?(self.class) && __getobj__ == other.__getobj__ + end + + private + def convert_time_to_time_zone(value) + return if value.nil? + + if value.acts_like?(:time) + value.in_time_zone + elsif value.respond_to?(:infinite?) && value.infinite? + value + else + map(value) { |v| convert_time_to_time_zone(v) } + end + end + + def set_time_zone_without_conversion(value) + ::Time.zone.local_to_utc(value).try(:in_time_zone) if value + end + end + + extend ActiveSupport::Concern + + included do + class_attribute :time_zone_aware_attributes, instance_writer: false, default: false + class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false, default: [] + class_attribute :time_zone_aware_types, instance_writer: false, default: [ :datetime, :time ] + end + + module ClassMethods # :nodoc: + private + def hook_attribute_type(name, cast_type) + if create_time_zone_conversion_attribute?(name, cast_type) + cast_type = TimeZoneConverter.new(cast_type) + end + + super + end + + def create_time_zone_conversion_attribute?(name, cast_type) + enabled_for_column = time_zone_aware_attributes && + !skip_time_zone_conversion_for_attributes.include?(name.to_sym) + + enabled_for_column && time_zone_aware_types.include?(cast_type.type) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/write.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/write.rb new file mode 100644 index 00000000..4c545ed8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attribute_methods/write.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + # = Active Record Attribute Methods \Write + module Write + extend ActiveSupport::Concern + + included do + attribute_method_suffix "=", parameters: "value" + end + + module ClassMethods # :nodoc: + private + def define_method_attribute=(canonical_name, owner:, as: canonical_name) + ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method( + owner, canonical_name, writer: true, + ) do |temp_method_name, attr_name_expr| + owner.define_cached_method(temp_method_name, as: "#{as}=", namespace: :active_record) do |batch| + batch << + "def #{temp_method_name}(value)" << + " _write_attribute(#{attr_name_expr}, value)" << + "end" + end + end + end + end + + # Updates the attribute identified by +attr_name+ using the specified + # +value+. The attribute value will be type cast upon being read. + def write_attribute(attr_name, value) + name = attr_name.to_s + name = self.class.attribute_aliases[name] || name + + name = @primary_key if name == "id" && @primary_key + @attributes.write_from_user(name, value) + end + + # This method exists to avoid the expensive primary_key check internally, without + # breaking compatibility with the write_attribute API + def _write_attribute(attr_name, value) # :nodoc: + @attributes.write_from_user(attr_name, value) + end + + alias :attribute= :_write_attribute + private :attribute= + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attributes.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attributes.rb new file mode 100644 index 00000000..580085da --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/attributes.rb @@ -0,0 +1,306 @@ +# frozen_string_literal: true + +require "active_model/attribute/user_provided_default" + +module ActiveRecord + # See ActiveRecord::Attributes::ClassMethods for documentation + module Attributes + extend ActiveSupport::Concern + include ActiveModel::AttributeRegistration + + # = Active Record \Attributes + module ClassMethods + # :method: attribute + # :call-seq: attribute(name, cast_type = nil, **options) + # + # Defines an attribute with a type on this model. It will override the + # type of existing attributes if needed. This allows control over how + # values are converted to and from SQL when assigned to a model. It also + # changes the behavior of values passed to + # {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where]. This will let you use + # your domain objects across much of Active Record, without having to + # rely on implementation details or monkey patching. + # + # +name+ The name of the methods to define attribute methods for, and the + # column which this will persist to. + # + # +cast_type+ A symbol such as +:string+ or +:integer+, or a type object + # to be used for this attribute. If this parameter is not passed, the previously + # defined type (if any) will be used. + # Otherwise, the type will be ActiveModel::Type::Value. + # See the examples below for more information about providing custom type objects. + # + # ==== Options + # + # The following options are accepted: + # + # +default+ The default value to use when no value is provided. If this option + # is not passed, the previously defined default value (if any) on the superclass or in the schema will be used. + # Otherwise, the default will be +nil+. + # + # +array+ (PostgreSQL only) specifies that the type should be an array (see the + # examples below). + # + # +range+ (PostgreSQL only) specifies that the type should be a range (see the + # examples below). + # + # When using a symbol for +cast_type+, extra options are forwarded to the + # constructor of the type object. + # + # ==== Examples + # + # The type detected by Active Record can be overridden. + # + # # db/schema.rb + # create_table :store_listings, force: true do |t| + # t.decimal :price_in_cents + # end + # + # # app/models/store_listing.rb + # class StoreListing < ActiveRecord::Base + # end + # + # store_listing = StoreListing.new(price_in_cents: '10.1') + # + # # before + # store_listing.price_in_cents # => BigDecimal(10.1) + # + # class StoreListing < ActiveRecord::Base + # attribute :price_in_cents, :integer + # end + # + # # after + # store_listing.price_in_cents # => 10 + # + # A default can also be provided. + # + # # db/schema.rb + # create_table :store_listings, force: true do |t| + # t.string :my_string, default: "original default" + # end + # + # StoreListing.new.my_string # => "original default" + # + # # app/models/store_listing.rb + # class StoreListing < ActiveRecord::Base + # attribute :my_string, :string, default: "new default" + # end + # + # StoreListing.new.my_string # => "new default" + # + # class Product < ActiveRecord::Base + # attribute :my_default_proc, :datetime, default: -> { Time.now } + # end + # + # Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600 + # sleep 1 + # Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600 + # + # \Attributes do not need to be backed by a database column. + # + # # app/models/my_model.rb + # class MyModel < ActiveRecord::Base + # attribute :my_string, :string + # attribute :my_int_array, :integer, array: true + # attribute :my_float_range, :float, range: true + # end + # + # model = MyModel.new( + # my_string: "string", + # my_int_array: ["1", "2", "3"], + # my_float_range: "[1,3.5]", + # ) + # model.attributes + # # => + # { + # my_string: "string", + # my_int_array: [1, 2, 3], + # my_float_range: 1.0..3.5 + # } + # + # Passing options to the type constructor + # + # # app/models/my_model.rb + # class MyModel < ActiveRecord::Base + # attribute :small_int, :integer, limit: 2 + # end + # + # MyModel.create(small_int: 65537) + # # => Error: 65537 is out of range for the limit of two bytes + # + # ==== Creating Custom Types + # + # Users may also define their own custom types, as long as they respond + # to the methods defined on the value type. The method +deserialize+ or + # +cast+ will be called on your type object, with raw input from the + # database or from your controllers. See ActiveModel::Type::Value for the + # expected API. It is recommended that your type objects inherit from an + # existing type, or from ActiveRecord::Type::Value + # + # class PriceType < ActiveRecord::Type::Integer + # def cast(value) + # if !value.kind_of?(Numeric) && value.include?('$') + # price_in_dollars = value.gsub(/\$/, '').to_f + # super(price_in_dollars * 100) + # else + # super + # end + # end + # end + # + # # config/initializers/types.rb + # ActiveRecord::Type.register(:price, PriceType) + # + # # app/models/store_listing.rb + # class StoreListing < ActiveRecord::Base + # attribute :price_in_cents, :price + # end + # + # store_listing = StoreListing.new(price_in_cents: '$10.00') + # store_listing.price_in_cents # => 1000 + # + # For more details on creating custom types, see the documentation for + # ActiveModel::Type::Value. For more details on registering your types + # to be referenced by a symbol, see ActiveRecord::Type.register. You can + # also pass a type object directly, in place of a symbol. + # + # ==== \Querying + # + # When {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where] is called, it will + # use the type defined by the model class to convert the value to SQL, + # calling +serialize+ on your type object. For example: + # + # class Money < Struct.new(:amount, :currency) + # end + # + # class PriceType < ActiveRecord::Type::Value + # def initialize(currency_converter:) + # @currency_converter = currency_converter + # end + # + # # value will be the result of #deserialize or + # # #cast. Assumed to be an instance of Money in + # # this case. + # def serialize(value) + # value_in_bitcoins = @currency_converter.convert_to_bitcoins(value) + # value_in_bitcoins.amount + # end + # end + # + # # config/initializers/types.rb + # ActiveRecord::Type.register(:price, PriceType) + # + # # app/models/product.rb + # class Product < ActiveRecord::Base + # currency_converter = ConversionRatesFromTheInternet.new + # attribute :price_in_bitcoins, :price, currency_converter: currency_converter + # end + # + # Product.where(price_in_bitcoins: Money.new(5, "USD")) + # # SELECT * FROM products WHERE price_in_bitcoins = 0.02230 + # + # Product.where(price_in_bitcoins: Money.new(5, "GBP")) + # # SELECT * FROM products WHERE price_in_bitcoins = 0.03412 + # + # ==== Dirty Tracking + # + # The type of an attribute is given the opportunity to change how dirty + # tracking is performed. The methods +changed?+ and +changed_in_place?+ + # will be called from ActiveModel::Dirty. See the documentation for those + # methods in ActiveModel::Type::Value for more details. + # + #-- + # Implemented by ActiveModel::AttributeRegistration#attribute. + + # This API only accepts type objects, and will do its work immediately instead of + # waiting for the schema to load. While this method + # is provided so it can be used by plugin authors, application code + # should probably use ClassMethods#attribute. + # + # +name+ The name of the attribute being defined. Expected to be a +String+. + # + # +cast_type+ The type object to use for this attribute. + # + # +default+ The default value to use when no value is provided. If this option + # is not passed, the previous default value (if any) will be used. + # Otherwise, the default will be +nil+. A proc can also be passed, and + # will be called once each time a new value is needed. + # + # +user_provided_default+ Whether the default value should be cast using + # +cast+ or +deserialize+. + def define_attribute( + name, + cast_type, + default: NO_DEFAULT_PROVIDED, + user_provided_default: true + ) + attribute_types[name] = cast_type + define_default_attribute(name, default, cast_type, from_user: user_provided_default) + end + + def _default_attributes # :nodoc: + @default_attributes ||= begin + attributes_hash = with_connection do |connection| + columns_hash.transform_values do |column| + ActiveModel::Attribute.from_database(column.name, column.default, type_for_column(connection, column)) + end + end + + attribute_set = ActiveModel::AttributeSet.new(attributes_hash) + apply_pending_attribute_modifications(attribute_set) + attribute_set + end + end + + ## + # :method: type_for_attribute + # :call-seq: type_for_attribute(attribute_name, &block) + # + # See ActiveModel::Attributes::ClassMethods#type_for_attribute. + # + # This method will access the database and load the model's schema if + # necessary. + #-- + # Implemented by ActiveModel::AttributeRegistration::ClassMethods#type_for_attribute. + + ## + protected + def reload_schema_from_cache(*) + reset_default_attributes! + super + end + + private + NO_DEFAULT_PROVIDED = Object.new # :nodoc: + private_constant :NO_DEFAULT_PROVIDED + + def define_default_attribute(name, value, type, from_user:) + if value == NO_DEFAULT_PROVIDED + default_attribute = _default_attributes[name].with_type(type) + elsif from_user + default_attribute = ActiveModel::Attribute::UserProvidedDefault.new( + name, + value, + type, + _default_attributes.fetch(name.to_s) { nil }, + ) + else + default_attribute = ActiveModel::Attribute.from_database(name, value, type) + end + _default_attributes[name] = default_attribute + end + + def reset_default_attributes + reload_schema_from_cache + end + + def resolve_type_name(name, **options) + Type.lookup(name, **options, adapter: Type.adapter_name_from(self)) + end + + def type_for_column(connection, column) + hook_attribute_type(column.name, super) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/autosave_association.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/autosave_association.rb new file mode 100644 index 00000000..e1dbbd5a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/autosave_association.rb @@ -0,0 +1,596 @@ +# frozen_string_literal: true + +require "active_record/associations/nested_error" + +module ActiveRecord + # = Active Record Autosave Association + # + # AutosaveAssociation is a module that takes care of automatically saving + # associated records when their parent is saved. In addition to saving, it + # also destroys any associated records that were marked for destruction. + # (See #mark_for_destruction and #marked_for_destruction?). + # + # Saving of the parent, its associations, and the destruction of marked + # associations, all happen inside a transaction. This should never leave the + # database in an inconsistent state. + # + # If validations for any of the associations fail, their error messages will + # be applied to the parent. + # + # Note that it also means that associations marked for destruction won't + # be destroyed directly. They will however still be marked for destruction. + # + # Note that autosave: false is not same as not declaring :autosave. + # When the :autosave option is not present then new association records are + # saved but the updated association records are not saved. + # + # == Validation + # + # Child records are validated unless :validate is +false+. + # + # == \Callbacks + # + # Association with autosave option defines several callbacks on your + # model (around_save, before_save, after_create, after_update). Please note that + # callbacks are executed in the order they were defined in + # model. You should avoid modifying the association content before + # autosave callbacks are executed. Placing your callbacks after + # associations is usually a good practice. + # + # === One-to-one Example + # + # class Post < ActiveRecord::Base + # has_one :author, autosave: true + # end + # + # Saving changes to the parent and its associated model can now be performed + # automatically _and_ atomically: + # + # post = Post.find(1) + # post.title # => "The current global position of migrating ducks" + # post.author.name # => "alloy" + # + # post.title = "On the migration of ducks" + # post.author.name = "Eloy Duran" + # + # post.save + # post.reload + # post.title # => "On the migration of ducks" + # post.author.name # => "Eloy Duran" + # + # Destroying an associated model, as part of the parent's save action, is as + # simple as marking it for destruction: + # + # post.author.mark_for_destruction + # post.author.marked_for_destruction? # => true + # + # Note that the model is _not_ yet removed from the database: + # + # id = post.author.id + # Author.find_by(id: id).nil? # => false + # + # post.save + # post.reload.author # => nil + # + # Now it _is_ removed from the database: + # + # Author.find_by(id: id).nil? # => true + # + # === One-to-many Example + # + # When :autosave is not declared new children are saved when their parent is saved: + # + # class Post < ActiveRecord::Base + # has_many :comments # :autosave option is not declared + # end + # + # post = Post.new(title: 'ruby rocks') + # post.comments.build(body: 'hello world') + # post.save # => saves both post and comment + # + # post = Post.create(title: 'ruby rocks') + # post.comments.build(body: 'hello world') + # post.save # => saves both post and comment + # + # post = Post.create(title: 'ruby rocks') + # comment = post.comments.create(body: 'hello world') + # comment.body = 'hi everyone' + # post.save # => saves post, but not comment + # + # When :autosave is true all children are saved, no matter whether they + # are new records or not: + # + # class Post < ActiveRecord::Base + # has_many :comments, autosave: true + # end + # + # post = Post.create(title: 'ruby rocks') + # comment = post.comments.create(body: 'hello world') + # comment.body = 'hi everyone' + # post.comments.build(body: "good morning.") + # post.save # => saves post and both comments. + # + # Destroying one of the associated models as part of the parent's save action + # is as simple as marking it for destruction: + # + # post.comments # => [#, # + # post.comments[1].mark_for_destruction + # post.comments[1].marked_for_destruction? # => true + # post.comments.length # => 2 + # + # Note that the model is _not_ yet removed from the database: + # + # id = post.comments.last.id + # Comment.find_by(id: id).nil? # => false + # + # post.save + # post.reload.comments.length # => 1 + # + # Now it _is_ removed from the database: + # + # Comment.find_by(id: id).nil? # => true + # + # === Caveats + # + # Note that autosave will only trigger for already-persisted association records + # if the records themselves have been changed. This is to protect against + # SystemStackError caused by circular association validations. The one + # exception is if a custom validation context is used, in which case the validations + # will always fire on the associated records. + module AutosaveAssociation + extend ActiveSupport::Concern + + module AssociationBuilderExtension # :nodoc: + def self.build(model, reflection) + model.send(:add_autosave_association_callbacks, reflection) + end + + def self.valid_options + [ :autosave ] + end + end + + included do + Associations::Builder::Association.extensions << AssociationBuilderExtension + end + + module ClassMethods # :nodoc: + private + def define_non_cyclic_method(name, &block) + return if method_defined?(name, false) + + define_method(name) do |*args| + result = true; @_already_called ||= {} + # Loop prevention for validation of associations + unless @_already_called[name] + begin + @_already_called[name] = true + result = instance_eval(&block) + ensure + @_already_called[name] = false + end + end + + result + end + end + + # Adds validation and save callbacks for the association as specified by + # the +reflection+. + # + # For performance reasons, we don't check whether to validate at runtime. + # However the validation and callback methods are lazy and those methods + # get created when they are invoked for the very first time. However, + # this can change, for instance, when using nested attributes, which is + # called _after_ the association has been defined. Since we don't want + # the callbacks to get defined multiple times, there are guards that + # check if the save or validation methods have already been defined + # before actually defining them. + def add_autosave_association_callbacks(reflection) + save_method = :"autosave_associated_records_for_#{reflection.name}" + + if reflection.collection? + around_save :around_save_collection_association + + define_non_cyclic_method(save_method) { save_collection_association(reflection) } + # Doesn't use after_save as that would save associations added in after_create/after_update twice + after_create save_method + after_update save_method + elsif reflection.has_one? + define_non_cyclic_method(save_method) { save_has_one_association(reflection) } + # Configures two callbacks instead of a single after_save so that + # the model may rely on their execution order relative to its + # own callbacks. + # + # For example, given that after_creates run before after_saves, if + # we configured instead an after_save there would be no way to fire + # a custom after_create callback after the child association gets + # created. + after_create save_method + after_update save_method + else + define_non_cyclic_method(save_method) { throw(:abort) if save_belongs_to_association(reflection) == false } + before_save save_method + end + + define_autosave_validation_callbacks(reflection) + end + + def define_autosave_validation_callbacks(reflection) + validation_method = :"validate_associated_records_for_#{reflection.name}" + if reflection.validate? && !method_defined?(validation_method) + if reflection.collection? + method = :validate_collection_association + elsif reflection.has_one? + method = :validate_has_one_association + else + method = :validate_belongs_to_association + end + + define_non_cyclic_method(validation_method) { send(method, reflection) } + validate validation_method + after_validation :_ensure_no_duplicate_errors + end + end + end + + # Reloads the attributes of the object as usual and clears marked_for_destruction flag. + def reload(options = nil) + @marked_for_destruction = false + @destroyed_by_association = nil + super + end + + # Marks this record to be destroyed as part of the parent's save transaction. + # This does _not_ actually destroy the record instantly, rather child record will be destroyed + # when parent.save is called. + # + # Only useful if the :autosave option on the parent is enabled for this associated model. + def mark_for_destruction + @marked_for_destruction = true + end + + # Returns whether or not this record will be destroyed as part of the parent's save transaction. + # + # Only useful if the :autosave option on the parent is enabled for this associated model. + def marked_for_destruction? + @marked_for_destruction + end + + # Records the association that is being destroyed and destroying this + # record in the process. + def destroyed_by_association=(reflection) + @destroyed_by_association = reflection + end + + # Returns the association for the parent being destroyed. + # + # Used to avoid updating the counter cache unnecessarily. + def destroyed_by_association + @destroyed_by_association + end + + # Returns whether or not this record has been changed in any way (including whether + # any of its nested autosave associations are likewise changed) + def changed_for_autosave? + new_record? || has_changes_to_save? || marked_for_destruction? || nested_records_changed_for_autosave? + end + + def validating_belongs_to_for?(association) + @validating_belongs_to_for ||= {} + @validating_belongs_to_for[association] + end + + def autosaving_belongs_to_for?(association) + @autosaving_belongs_to_for ||= {} + @autosaving_belongs_to_for[association] + end + + private + def init_internals + super + @_already_called = nil + end + + # Returns the record for an association collection that should be validated + # or saved. If +autosave+ is +false+ only new records will be returned, + # unless the parent is/was a new record itself. + def associated_records_to_validate_or_save(association, new_record, autosave) + if new_record || custom_validation_context? + association && association.target + elsif autosave + association.target.find_all(&:changed_for_autosave?) + else + association.target.find_all(&:new_record?) + end + end + + # Go through nested autosave associations that are loaded in memory (without loading + # any new ones), and return true if any are changed for autosave. + # Returns false if already called to prevent an infinite loop. + def nested_records_changed_for_autosave? + @_nested_records_changed_for_autosave_already_called ||= false + return false if @_nested_records_changed_for_autosave_already_called + begin + @_nested_records_changed_for_autosave_already_called = true + self.class._reflections.values.any? do |reflection| + if reflection.options[:autosave] + association = association_instance_get(reflection.name) + association && Array.wrap(association.target).any?(&:changed_for_autosave?) + end + end + ensure + @_nested_records_changed_for_autosave_already_called = false + end + end + + # Validate the association if :validate or :autosave is + # turned on for the has_one association. + def validate_has_one_association(reflection) + association = association_instance_get(reflection.name) + record = association && association.reader + return unless record && (record.changed_for_autosave? || custom_validation_context?) + + inverse_association = reflection.inverse_of && record.association(reflection.inverse_of.name) + return if inverse_association && (record.validating_belongs_to_for?(inverse_association) || + record.autosaving_belongs_to_for?(inverse_association)) + + association_valid?(association, record) + end + + # Validate the association if :validate or :autosave is + # turned on for the belongs_to association. + def validate_belongs_to_association(reflection) + association = association_instance_get(reflection.name) + record = association && association.reader + return unless record && (record.changed_for_autosave? || custom_validation_context?) + + begin + @validating_belongs_to_for ||= {} + @validating_belongs_to_for[association] = true + association_valid?(association, record) + ensure + @validating_belongs_to_for[association] = false + end + end + + # Validate the associated records if :validate or + # :autosave is turned on for the association specified by + # +reflection+. + def validate_collection_association(reflection) + if association = association_instance_get(reflection.name) + if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave]) + records.each { |record| association_valid?(association, record) } + end + end + end + + # Returns whether or not the association is valid and applies any errors to + # the parent, self, if it wasn't. Skips any :autosave + # enabled records if they're marked_for_destruction? or destroyed. + def association_valid?(association, record) + return true if record.destroyed? || (association.options[:autosave] && record.marked_for_destruction?) + + context = validation_context if custom_validation_context? + return true if record.valid?(context) + + if record.changed? || record.new_record? || context + associated_errors = record.errors.objects + else + # If there are existing invalid records in the DB, we should still be able to reference them. + # Unless a record (no matter where in the association chain) is invalid and is being changed. + associated_errors = record.errors.objects.select { |error| error.is_a?(Associations::NestedError) } + end + + if association.options[:autosave] + return if equal?(record) + + associated_errors.each { |error| + errors.objects.append( + Associations::NestedError.new(association, error) + ) + } + elsif associated_errors.any? + errors.add(association.reflection.name) + end + + errors.any? + end + + # Is used as an around_save callback to check while saving a collection + # association whether or not the parent was a new record before saving. + def around_save_collection_association + previously_new_record_before_save = (@new_record_before_save ||= false) + @new_record_before_save = !previously_new_record_before_save && new_record? + + yield + ensure + @new_record_before_save = previously_new_record_before_save + end + + # Saves any new associated records, or all loaded autosave associations if + # :autosave is enabled on the association. + # + # In addition, it destroys all children that were marked for destruction + # with #mark_for_destruction. + # + # This all happens inside a transaction, _if_ the Transactions module is included into + # ActiveRecord::Base after the AutosaveAssociation module, which it does by default. + def save_collection_association(reflection) + if association = association_instance_get(reflection.name) + autosave = reflection.options[:autosave] + + # By saving the instance variable in a local variable, + # we make the whole callback re-entrant. + new_record_before_save = @new_record_before_save + + # reconstruct the scope now that we know the owner's id + association.reset_scope + + if records = associated_records_to_validate_or_save(association, new_record_before_save, autosave) + if autosave + records_to_destroy = records.select(&:marked_for_destruction?) + records_to_destroy.each { |record| association.destroy(record) } + records -= records_to_destroy + end + + records.each do |record| + next if record.destroyed? + + saved = true + + if autosave != false && (new_record_before_save || record.new_record?) + association.set_inverse_instance(record) + + if autosave + saved = association.insert_record(record, false) + elsif !reflection.nested? + association_saved = association.insert_record(record) + + if reflection.validate? + errors.add(reflection.name) unless association_saved + saved = association_saved + end + end + elsif autosave + saved = record.save(validate: false) + end + + raise(RecordInvalid.new(association.owner)) unless saved + end + end + end + end + + # Saves the associated record if it's new or :autosave is enabled + # on the association. + # + # In addition, it will destroy the association if it was marked for + # destruction with #mark_for_destruction. + # + # This all happens inside a transaction, _if_ the Transactions module is included into + # ActiveRecord::Base after the AutosaveAssociation module, which it does by default. + def save_has_one_association(reflection) + association = association_instance_get(reflection.name) + return unless association && association.loaded? + + record = association.load_target + return unless record && !record.destroyed? + + autosave = reflection.options[:autosave] + + if autosave && record.marked_for_destruction? + record.destroy + elsif autosave != false + primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s) + primary_key_value = primary_key.map { |key| _read_attribute(key) } + return unless (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, primary_key_value) + + unless reflection.through_reflection + foreign_key = Array(reflection.foreign_key) + primary_key_foreign_key_pairs = primary_key.zip(foreign_key) + + primary_key_foreign_key_pairs.each do |primary_key, foreign_key| + association_id = _read_attribute(primary_key) + record[foreign_key] = association_id unless record[foreign_key] == association_id + end + association.set_inverse_instance(record) + end + + inverse_association = reflection.inverse_of && record.association(reflection.inverse_of.name) + return if inverse_association && record.autosaving_belongs_to_for?(inverse_association) + + saved = record.save(validate: !autosave) + raise ActiveRecord::Rollback if !saved && autosave + saved + end + end + + # If the record is new or it has changed, returns true. + def _record_changed?(reflection, record, key) + record.new_record? || + (association_foreign_key_changed?(reflection, record, key) || + inverse_polymorphic_association_changed?(reflection, record)) || + record.will_save_change_to_attribute?(reflection.foreign_key) + end + + def association_foreign_key_changed?(reflection, record, key) + return false if reflection.through_reflection? + + foreign_key = Array(reflection.foreign_key) + return false unless foreign_key.all? { |key| record._has_attribute?(key) } + + foreign_key.map { |key| record._read_attribute(key) } != Array(key) + end + + def inverse_polymorphic_association_changed?(reflection, record) + return false unless reflection.inverse_of&.polymorphic? + + class_name = record._read_attribute(reflection.inverse_of.foreign_type) + reflection.active_record != record.class.polymorphic_class_for(class_name) + end + + # Saves the associated record if it's new or :autosave is enabled. + # + # In addition, it will destroy the association if it was marked for destruction. + def save_belongs_to_association(reflection) + association = association_instance_get(reflection.name) + return unless association && association.loaded? && !association.stale_target? + + record = association.load_target + if record && !record.destroyed? + autosave = reflection.options[:autosave] + + if autosave && record.marked_for_destruction? + foreign_key = Array(reflection.foreign_key) + foreign_key.each { |key| self[key] = nil } + record.destroy + elsif autosave != false + saved = if record.new_record? || (autosave && record.changed_for_autosave?) + begin + @autosaving_belongs_to_for ||= {} + @autosaving_belongs_to_for[association] = true + record.save(validate: !autosave) + ensure + @autosaving_belongs_to_for[association] = false + end + end + + if association.updated? + primary_key = Array(compute_primary_key(reflection, record)).map(&:to_s) + foreign_key = Array(reflection.foreign_key) + + primary_key_foreign_key_pairs = primary_key.zip(foreign_key) + primary_key_foreign_key_pairs.each do |primary_key, foreign_key| + association_id = record._read_attribute(primary_key) + self[foreign_key] = association_id unless self[foreign_key] == association_id + end + association.loaded! + end + + saved if autosave + end + end + end + + def compute_primary_key(reflection, record) + if primary_key_options = reflection.options[:primary_key] + primary_key_options + elsif reflection.options[:query_constraints] && (query_constraints = record.class.query_constraints_list) + query_constraints + elsif record.class.has_query_constraints? && !reflection.options[:foreign_key] + record.class.query_constraints_list + elsif record.class.composite_primary_key? + # If record has composite primary key of shape [:, :id], infer primary_key as :id + primary_key = record.class.primary_key + primary_key.include?("id") ? "id" : primary_key + else + record.class.primary_key + end + end + + def _ensure_no_duplicate_errors + errors.uniq! + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/base.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/base.rb new file mode 100644 index 00000000..c4a38f8d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/base.rb @@ -0,0 +1,338 @@ +# frozen_string_literal: true + +require "active_support/benchmarkable" +require "active_support/dependencies" +require "active_support/descendants_tracker" +require "active_support/time" +require "active_support/core_ext/class/subclasses" +require "active_record/log_subscriber" +require "active_record/explain_subscriber" +require "active_record/relation/delegation" +require "active_record/attributes" +require "active_record/type_caster" +require "active_record/database_configurations" + +module ActiveRecord # :nodoc: + # = Active Record + # + # Active Record objects don't specify their attributes directly, but rather infer them from + # the table definition with which they're linked. Adding, removing, and changing attributes + # and their type is done directly in the database. Any change is instantly reflected in the + # Active Record objects. The mapping that binds a given Active Record class to a certain + # database table will happen automatically in most common cases, but can be overwritten for the uncommon ones. + # + # See the mapping rules in table_name and the full example in link:files/activerecord/README_rdoc.html for more insight. + # + # == Creation + # + # Active Records accept constructor parameters either in a hash or as a block. The hash + # method is especially useful when you're receiving the data from somewhere else, like an + # HTTP request. It works like this: + # + # user = User.new(name: "David", occupation: "Code Artist") + # user.name # => "David" + # + # You can also use block initialization: + # + # user = User.new do |u| + # u.name = "David" + # u.occupation = "Code Artist" + # end + # + # And of course you can just create a bare object and specify the attributes after the fact: + # + # user = User.new + # user.name = "David" + # user.occupation = "Code Artist" + # + # == Conditions + # + # Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement. + # The array form is to be used when the condition input is tainted and requires sanitization. The string form can + # be used for statements that don't involve tainted data. The hash form works much like the array form, except + # only equality and range is possible. Examples: + # + # class User < ActiveRecord::Base + # def self.authenticate_unsafely(user_name, password) + # where("user_name = '#{user_name}' AND password = '#{password}'").first + # end + # + # def self.authenticate_safely(user_name, password) + # where("user_name = ? AND password = ?", user_name, password).first + # end + # + # def self.authenticate_safely_simply(user_name, password) + # where(user_name: user_name, password: password).first + # end + # end + # + # The authenticate_unsafely method inserts the parameters directly into the query + # and is thus susceptible to SQL-injection attacks if the user_name and +password+ + # parameters come directly from an HTTP request. The authenticate_safely and + # authenticate_safely_simply both will sanitize the user_name and +password+ + # before inserting them in the query, which will ensure that an attacker can't escape the + # query and fake the login (or worse). + # + # When using multiple parameters in the conditions, it can easily become hard to read exactly + # what the fourth or fifth question mark is supposed to represent. In those cases, you can + # resort to named bind variables instead. That's done by replacing the question marks with + # symbols and supplying a hash with values for the matching symbol keys: + # + # Company.where( + # "id = :id AND name = :name AND division = :division AND created_at > :accounting_date", + # { id: 3, name: "37signals", division: "First", accounting_date: '2005-01-01' } + # ).first + # + # Similarly, a simple hash without a statement will generate conditions based on equality with the SQL AND + # operator. For instance: + # + # Student.where(first_name: "Harvey", status: 1) + # Student.where(params[:student]) + # + # A range may be used in the hash to use the SQL BETWEEN operator: + # + # Student.where(grade: 9..12) + # + # An array may be used in the hash to use the SQL IN operator: + # + # Student.where(grade: [9,11,12]) + # + # When joining tables, nested hashes or keys written in the form 'table_name.column_name' + # can be used to qualify the table name of a particular condition. For instance: + # + # Student.joins(:schools).where(schools: { category: 'public' }) + # Student.joins(:schools).where('schools.category' => 'public' ) + # + # == Overwriting default accessors + # + # All column values are automatically available through basic accessors on the Active Record + # object, but sometimes you want to specialize this behavior. This can be done by overwriting + # the default accessors (using the same name as the attribute) and calling + # +super+ to actually change things. + # + # class Song < ActiveRecord::Base + # # Uses an integer of seconds to hold the length of the song + # + # def length=(minutes) + # super(minutes.to_i * 60) + # end + # + # def length + # super / 60 + # end + # end + # + # == Attribute query methods + # + # In addition to the basic accessors, query methods are also automatically available on the Active Record object. + # Query methods allow you to test whether an attribute value is present. + # Additionally, when dealing with numeric values, a query method will return false if the value is zero. + # + # For example, an Active Record User with the name attribute has a name? method that you can call + # to determine whether the user has a name: + # + # user = User.new(name: "David") + # user.name? # => true + # + # anonymous = User.new(name: "") + # anonymous.name? # => false + # + # Query methods will also respect any overrides of default accessors: + # + # class User + # # Has admin boolean column + # def admin + # false + # end + # end + # + # user.update(admin: true) + # + # user.read_attribute(:admin) # => true, gets the column value + # user[:admin] # => true, also gets the column value + # + # user.admin # => false, due to the getter override + # user.admin? # => false, due to the getter override + # + # == Accessing attributes before they have been typecasted + # + # Sometimes you want to be able to read the raw attribute data without having the column-determined + # typecast run its course first. That can be done by using the _before_type_cast + # accessors that all attributes have. For example, if your Account model has a balance attribute, + # you can call account.balance_before_type_cast or account.id_before_type_cast. + # + # This is especially useful in validation situations where the user might supply a string for an + # integer field and you want to display the original string back in an error message. Accessing the + # attribute normally would typecast the string to 0, which isn't what you want. + # + # == Dynamic attribute-based finders + # + # Dynamic attribute-based finders are a mildly deprecated way of getting (and/or creating) objects + # by simple queries without turning to SQL. They work by appending the name of an attribute + # to find_by_ like Person.find_by_user_name. + # Instead of writing Person.find_by(user_name: user_name), you can use + # Person.find_by_user_name(user_name). + # + # It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an + # ActiveRecord::RecordNotFound error if they do not return any records, + # like Person.find_by_last_name!. + # + # It's also possible to use multiple attributes in the same find_by_ by separating them with + # "_and_". + # + # Person.find_by(user_name: user_name, password: password) + # Person.find_by_user_name_and_password(user_name, password) # with dynamic finder + # + # It's even possible to call these dynamic finder methods on relations and named scopes. + # + # Payment.order("created_on").find_by_amount(50) + # + # == Saving arrays, hashes, and other non-mappable objects in text columns + # + # Active Record can serialize any object in text columns using YAML. To do so, you must + # specify this with a call to the class method + # {serialize}[rdoc-ref:AttributeMethods::Serialization::ClassMethods#serialize]. + # This makes it possible to store arrays, hashes, and other non-mappable objects without doing + # any additional work. + # + # class User < ActiveRecord::Base + # serialize :preferences + # end + # + # user = User.create(preferences: { "background" => "black", "display" => large }) + # User.find(user.id).preferences # => { "background" => "black", "display" => large } + # + # You can also specify a class option as the second parameter that'll raise an exception + # if a serialized object is retrieved as a descendant of a class not in the hierarchy. + # + # class User < ActiveRecord::Base + # serialize :preferences, Hash + # end + # + # user = User.create(preferences: %w( one two three )) + # User.find(user.id).preferences # raises SerializationTypeMismatch + # + # When you specify a class option, the default value for that attribute will be a new + # instance of that class. + # + # class User < ActiveRecord::Base + # serialize :preferences, OpenStruct + # end + # + # user = User.new + # user.preferences.theme_color = "red" + # + # + # == Single table inheritance + # + # Active Record allows inheritance by storing the name of the class in a + # column that is named "type" by default. See ActiveRecord::Inheritance for + # more details. + # + # == Connection to multiple databases in different models + # + # Connections are usually created through + # {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] and retrieved + # by ActiveRecord::Base.lease_connection. All classes inheriting from ActiveRecord::Base will use this + # connection. But you can also set a class-specific connection. For example, if Course is an + # ActiveRecord::Base, but resides in a different database, you can just say Course.establish_connection + # and Course and all of its subclasses will use this connection instead. + # + # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is + # a hash indexed by the class. If a connection is requested, the + # {ActiveRecord::Base.retrieve_connection}[rdoc-ref:ConnectionHandling#retrieve_connection] method + # will go up the class-hierarchy until a connection is found in the connection pool. + # + # == Exceptions + # + # * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record. + # * AdapterNotSpecified - The configuration hash used in + # {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] + # didn't include an :adapter key. + # * AdapterNotFound - The :adapter key used in + # {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] + # specified a non-existent adapter + # (or a bad spelling of an existing one). + # * AssociationTypeMismatch - The object assigned to the association wasn't of the type + # specified in the association definition. + # * AttributeAssignmentError - An error occurred while doing a mass assignment through the + # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method. + # You can inspect the +attribute+ property of the exception object to determine which attribute + # triggered the error. + # * ConnectionNotEstablished - No connection has been established. + # Use {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] before querying. + # * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the + # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method. + # The +errors+ property of this exception contains an array of + # AttributeAssignmentError + # objects that should be inspected to determine which attributes triggered the errors. + # * RecordInvalid - raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and + # {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!] + # when the record is invalid. + # * RecordNotFound - No record responded to the {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] method. + # Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions. + # Some {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] calls do not raise this exception to signal + # nothing was found, please check its documentation for further details. + # * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter. + # * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message. + # + # *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level). + # So it's possible to assign a logger to the class through Base.logger= which will then be used by all + # instances in the current object space. + class Base + include ActiveModel::API + + extend ActiveSupport::Benchmarkable + extend ActiveSupport::DescendantsTracker + + extend ConnectionHandling + extend QueryCache::ClassMethods + extend Querying + extend Translation + extend DynamicMatchers + extend DelegatedType + extend Explain + extend Enum + extend Delegation::DelegateCache + extend Aggregations::ClassMethods + + include Core + include Persistence + include ReadonlyAttributes + include ModelSchema + include Inheritance + include Scoping + include Sanitization + include AttributeAssignment + include Integration + include Validations + include CounterCache + include Attributes + include Locking::Optimistic + include Locking::Pessimistic + include Encryption::EncryptableRecord + include AttributeMethods + include Callbacks + include Timestamp + include Associations + include SecurePassword + include AutosaveAssociation + include NestedAttributes + include Transactions + include TouchLater + include NoTouching + include Reflection + include Serialization + include Store + include SecureToken + include TokenFor + include SignedId + include Suppressor + include Normalization + include Marshalling::Methods + + self.param_delimiter = "_" + end + + ActiveSupport.run_load_hooks(:active_record, Base) +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/callbacks.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/callbacks.rb new file mode 100644 index 00000000..6e9c68b7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/callbacks.rb @@ -0,0 +1,452 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record \Callbacks + # + # \Callbacks are hooks into the life cycle of an Active Record object that allow you to trigger logic + # before or after a change in the object state. This can be used to make sure that associated and + # dependent objects are deleted when {ActiveRecord::Base#destroy}[rdoc-ref:Persistence#destroy] is called (by overwriting +before_destroy+) or + # to massage attributes before they're validated (by overwriting +before_validation+). + # As an example of the callbacks initiated, consider the {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] call for a new record: + # + # * (-) save + # * (-) valid + # * (1) before_validation + # * (-) validate + # * (2) after_validation + # * (3) before_save + # * (4) before_create + # * (-) create + # * (5) after_create + # * (6) after_save + # * (7) after_commit + # + # Also, an after_rollback callback can be configured to be triggered whenever a rollback is issued. + # Check out ActiveRecord::Transactions for more details about after_commit and + # after_rollback. + # + # Additionally, an after_touch callback is triggered whenever an + # object is touched. + # + # Lastly an after_find and after_initialize callback is triggered for each object that + # is found and instantiated by a finder, with after_initialize being triggered after new objects + # are instantiated as well. + # + # There are nineteen callbacks in total, which give a lot of control over how to react and prepare for each state in the + # Active Record life cycle. The sequence for calling {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] for an existing record is similar, + # except that each _create callback is replaced by the corresponding _update callback. + # + # Examples: + # class CreditCard < ActiveRecord::Base + # # Strip everything but digits, so the user can specify "555 234 34" or + # # "5552-3434" and both will mean "55523434" + # before_validation(on: :create) do + # self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number") + # end + # end + # + # class Subscription < ActiveRecord::Base + # before_create :record_signup + # + # private + # def record_signup + # self.signed_up_on = Date.today + # end + # end + # + # class Firm < ActiveRecord::Base + # # Disables access to the system, for associated clients and people when the firm is destroyed + # before_destroy { |record| Person.where(firm_id: record.id).update_all(access: 'disabled') } + # before_destroy { |record| Client.where(client_of: record.id).update_all(access: 'disabled') } + # end + # + # == Inheritable callback queues + # + # Besides the overwritable callback methods, it's also possible to register callbacks through the + # use of the callback macros. Their main advantage is that the macros add behavior into a callback + # queue that is kept intact through an inheritance hierarchy. + # + # class Topic < ActiveRecord::Base + # before_destroy :destroy_author + # end + # + # class Reply < Topic + # before_destroy :destroy_readers + # end + # + # When Topic#destroy is run only +destroy_author+ is called. When Reply#destroy is + # run, both +destroy_author+ and +destroy_readers+ are called. + # + # *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the + # callbacks before specifying the associations. Otherwise, you might trigger the loading of a + # child before the parent has registered the callbacks and they won't be inherited. + # + # == Types of callbacks + # + # There are three types of callbacks accepted by the callback macros: method references (symbol), callback objects, + # inline methods (using a proc). \Method references and callback objects are the recommended approaches, + # inline methods using a proc are sometimes appropriate (such as for creating mix-ins). + # + # The method reference callbacks work by specifying a protected or private method available in the object, like this: + # + # class Topic < ActiveRecord::Base + # before_destroy :delete_parents + # + # private + # def delete_parents + # self.class.delete_by(parent_id: id) + # end + # end + # + # The callback objects have methods named after the callback called with the record as the only parameter, such as: + # + # class BankAccount < ActiveRecord::Base + # before_save EncryptionWrapper.new + # after_save EncryptionWrapper.new + # after_initialize EncryptionWrapper.new + # end + # + # class EncryptionWrapper + # def before_save(record) + # record.credit_card_number = encrypt(record.credit_card_number) + # end + # + # def after_save(record) + # record.credit_card_number = decrypt(record.credit_card_number) + # end + # + # alias_method :after_initialize, :after_save + # + # private + # def encrypt(value) + # # Secrecy is committed + # end + # + # def decrypt(value) + # # Secrecy is unveiled + # end + # end + # + # So you specify the object you want to be messaged on a given callback. When that callback is triggered, the object has + # a method by the name of the callback messaged. You can make these callbacks more flexible by passing in other + # initialization data such as the name of the attribute to work with: + # + # class BankAccount < ActiveRecord::Base + # before_save EncryptionWrapper.new("credit_card_number") + # after_save EncryptionWrapper.new("credit_card_number") + # after_initialize EncryptionWrapper.new("credit_card_number") + # end + # + # class EncryptionWrapper + # def initialize(attribute) + # @attribute = attribute + # end + # + # def before_save(record) + # record.send("#{@attribute}=", encrypt(record.send("#{@attribute}"))) + # end + # + # def after_save(record) + # record.send("#{@attribute}=", decrypt(record.send("#{@attribute}"))) + # end + # + # alias_method :after_initialize, :after_save + # + # private + # def encrypt(value) + # # Secrecy is committed + # end + # + # def decrypt(value) + # # Secrecy is unveiled + # end + # end + # + # == before_validation* returning statements + # + # If the +before_validation+ callback throws +:abort+, the process will be + # aborted and {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] will return +false+. + # If {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] is called it will raise an ActiveRecord::RecordInvalid exception. + # Nothing will be appended to the errors object. + # + # == Canceling callbacks + # + # If a before_* callback throws +:abort+, all the later callbacks and + # the associated action are cancelled. + # \Callbacks are generally run in the order they are defined, with the exception of callbacks defined as + # methods on the model, which are called last. + # + # == Ordering callbacks + # + # Sometimes application code requires that callbacks execute in a specific order. For example, a +before_destroy+ + # callback (+log_children+ in this case) should be executed before records in the +children+ association are destroyed by the + # dependent: :destroy option. + # + # Let's look at the code below: + # + # class Topic < ActiveRecord::Base + # has_many :children, dependent: :destroy + # + # before_destroy :log_children + # + # private + # def log_children + # # Child processing + # end + # end + # + # In this case, the problem is that when the +before_destroy+ callback is executed, records in the +children+ association no + # longer exist because the {ActiveRecord::Base#destroy}[rdoc-ref:Persistence#destroy] callback was executed first. + # You can use the +prepend+ option on the +before_destroy+ callback to avoid this. + # + # class Topic < ActiveRecord::Base + # has_many :children, dependent: :destroy + # + # before_destroy :log_children, prepend: true + # + # private + # def log_children + # # Child processing + # end + # end + # + # This way, the +before_destroy+ is executed before the dependent: :destroy is called, and the data is still available. + # + # Also, there are cases when you want several callbacks of the same type to + # be executed in order. + # + # For example: + # + # class Topic < ActiveRecord::Base + # has_many :children + # + # after_save :log_children + # after_save :do_something_else + # + # private + # def log_children + # # Child processing + # end + # + # def do_something_else + # # Something else + # end + # end + # + # In this case the +log_children+ is executed before +do_something_else+. + # This applies to all non-transactional callbacks, and to +before_commit+. + # + # For transactional +after_+ callbacks (+after_commit+, +after_rollback+, etc), the order + # can be set via configuration. + # + # config.active_record.run_after_transaction_callbacks_in_order_defined = false + # + # When set to +true+ (the default from \Rails 7.1), callbacks are executed in the order they + # are defined, just like the example above. When set to +false+, the order is reversed, so + # +do_something_else+ is executed before +log_children+. + # + # == \Transactions + # + # The entire callback chain of a {#save}[rdoc-ref:Persistence#save], {#save!}[rdoc-ref:Persistence#save!], + # or {#destroy}[rdoc-ref:Persistence#destroy] call runs within a transaction. That includes after_* hooks. + # If everything goes fine a +COMMIT+ is executed once the chain has been completed. + # + # If a before_* callback cancels the action a +ROLLBACK+ is issued. You + # can also trigger a +ROLLBACK+ raising an exception in any of the callbacks, + # including after_* hooks. Note, however, that in that case the client + # needs to be aware of it because an ordinary {#save}[rdoc-ref:Persistence#save] will raise such exception + # instead of quietly returning +false+. + # + # == Debugging callbacks + # + # The callback chain is accessible via the _*_callbacks method on an object. Active Model \Callbacks support + # :before, :after and :around as values for the kind property. The kind property + # defines what part of the chain the callback runs in. + # + # To find all callbacks in the +before_save+ callback chain: + # + # Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) } + # + # Returns an array of callback objects that form the +before_save+ chain. + # + # To further check if the before_save chain contains a proc defined as rest_when_dead use the filter property of the callback object: + # + # Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:rest_when_dead) + # + # Returns true or false depending on whether the proc is contained in the +before_save+ callback chain on a Topic model. + # + module Callbacks + extend ActiveSupport::Concern + + CALLBACKS = [ + :after_initialize, :after_find, :after_touch, :before_validation, :after_validation, + :before_save, :around_save, :after_save, :before_create, :around_create, + :after_create, :before_update, :around_update, :after_update, + :before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback + ] + + module ClassMethods + include ActiveModel::Callbacks + + ## + # :method: after_initialize + # + # :call-seq: after_initialize(*args, &block) + # + # Registers a callback to be called after a record is instantiated. See + # ActiveRecord::Callbacks for more information. + + ## + # :method: after_find + # + # :call-seq: after_find(*args, &block) + # + # Registers a callback to be called after a record is instantiated + # via a finder. See ActiveRecord::Callbacks for more information. + + ## + # :method: after_touch + # + # :call-seq: after_touch(*args, &block) + # + # Registers a callback to be called after a record is touched. See + # ActiveRecord::Callbacks for more information. + + ## + # :method: before_save + # + # :call-seq: before_save(*args, &block) + # + # Registers a callback to be called before a record is saved. See + # ActiveRecord::Callbacks for more information. + + ## + # :method: around_save + # + # :call-seq: around_save(*args, &block) + # + # Registers a callback to be called around the save of a record. See + # ActiveRecord::Callbacks for more information. + + ## + # :method: after_save + # + # :call-seq: after_save(*args, &block) + # + # Registers a callback to be called after a record is saved. See + # ActiveRecord::Callbacks for more information. + + ## + # :method: before_create + # + # :call-seq: before_create(*args, &block) + # + # Registers a callback to be called before a record is created. See + # ActiveRecord::Callbacks for more information. + + ## + # :method: around_create + # + # :call-seq: around_create(*args, &block) + # + # Registers a callback to be called around the creation of a record. See + # ActiveRecord::Callbacks for more information. + + ## + # :method: after_create + # + # :call-seq: after_create(*args, &block) + # + # Registers a callback to be called after a record is created. See + # ActiveRecord::Callbacks for more information. + + ## + # :method: before_update + # + # :call-seq: before_update(*args, &block) + # + # Registers a callback to be called before a record is updated. See + # ActiveRecord::Callbacks for more information. + + ## + # :method: around_update + # + # :call-seq: around_update(*args, &block) + # + # Registers a callback to be called around the update of a record. See + # ActiveRecord::Callbacks for more information. + + ## + # :method: after_update + # + # :call-seq: after_update(*args, &block) + # + # Registers a callback to be called after a record is updated. See + # ActiveRecord::Callbacks for more information. + + ## + # :method: before_destroy + # + # :call-seq: before_destroy(*args, &block) + # + # Registers a callback to be called before a record is destroyed. See + # ActiveRecord::Callbacks for more information. + + ## + # :method: around_destroy + # + # :call-seq: around_destroy(*args, &block) + # + # Registers a callback to be called around the destruction of a record. + # See ActiveRecord::Callbacks for more information. + + ## + # :method: after_destroy + # + # :call-seq: after_destroy(*args, &block) + # + # Registers a callback to be called after a record is destroyed. See + # ActiveRecord::Callbacks for more information. + end + + included do + include ActiveModel::Validations::Callbacks + + define_model_callbacks :initialize, :find, :touch, only: :after + define_model_callbacks :save, :create, :update, :destroy + end + + def destroy # :nodoc: + @_destroy_callback_already_called ||= false + return true if @_destroy_callback_already_called + @_destroy_callback_already_called = true + _run_destroy_callbacks { super } + rescue RecordNotDestroyed => e + @_association_destroy_exception = e + false + ensure + @_destroy_callback_already_called = false + end + + def touch(*, **) # :nodoc: + _run_touch_callbacks { super } + end + + def increment!(attribute, by = 1, touch: nil) # :nodoc: + touch ? _run_touch_callbacks { super } : super + end + + private + def create_or_update(**) + _run_save_callbacks { super } + end + + def _create_record + _run_create_callbacks { super } + end + + def _update_record + _run_update_callbacks { record_update_timestamps { super } } + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/coders/column_serializer.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/coders/column_serializer.rb new file mode 100644 index 00000000..e8ee6950 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/coders/column_serializer.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module ActiveRecord + module Coders # :nodoc: + class ColumnSerializer # :nodoc: + attr_reader :object_class + attr_reader :coder + + def initialize(attr_name, coder, object_class = Object) + @attr_name = attr_name + @object_class = object_class + @coder = coder + check_arity_of_constructor + end + + def init_with(coder) # :nodoc: + @attr_name = coder["attr_name"] + @object_class = coder["object_class"] + @coder = coder["coder"] + end + + def dump(object) + return if object.nil? + + assert_valid_value(object, action: "dump") + coder.dump(object) + end + + def load(payload) + if payload.nil? + if @object_class != ::Object + return @object_class.new + end + return nil + end + + object = coder.load(payload) + + assert_valid_value(object, action: "load") + object ||= object_class.new if object_class != Object + + object + end + + # Public because it's called by Type::Serialized + def assert_valid_value(object, action:) + unless object.nil? || object_class === object + raise SerializationTypeMismatch, + "can't #{action} `#{@attr_name}`: was supposed to be a #{object_class}, but was a #{object.class}. -- #{object.inspect}" + end + end + + private + def check_arity_of_constructor + load(nil) + rescue ArgumentError + raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor." + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/coders/json.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/coders/json.rb new file mode 100644 index 00000000..9fa7d1f9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/coders/json.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveRecord + module Coders # :nodoc: + module JSON # :nodoc: + def self.dump(obj) + ActiveSupport::JSON.encode(obj) + end + + def self.load(json) + ActiveSupport::JSON.decode(json) unless json.blank? + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/coders/yaml_column.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/coders/yaml_column.rb new file mode 100644 index 00000000..a1fcc292 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/coders/yaml_column.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require "yaml" + +module ActiveRecord + module Coders # :nodoc: + class YAMLColumn < ColumnSerializer # :nodoc: + class SafeCoder + def initialize(permitted_classes: [], unsafe_load: nil) + @permitted_classes = permitted_classes + @unsafe_load = unsafe_load + end + + if Gem::Version.new(Psych::VERSION) >= Gem::Version.new("5.1") + def dump(object) + if @unsafe_load.nil? ? ActiveRecord.use_yaml_unsafe_load : @unsafe_load + ::YAML.dump(object) + else + ::YAML.safe_dump( + object, + permitted_classes: @permitted_classes + ActiveRecord.yaml_column_permitted_classes, + aliases: true, + ) + end + end + else + def dump(object) + YAML.dump(object) + end + end + + if YAML.respond_to?(:unsafe_load) + def load(payload) + if @unsafe_load.nil? ? ActiveRecord.use_yaml_unsafe_load : @unsafe_load + YAML.unsafe_load(payload) + else + YAML.safe_load( + payload, + permitted_classes: @permitted_classes + ActiveRecord.yaml_column_permitted_classes, + aliases: true, + ) + end + end + else + def load(payload) + if @unsafe_load.nil? ? ActiveRecord.use_yaml_unsafe_load : @unsafe_load + YAML.load(payload) + else + YAML.safe_load( + payload, + permitted_classes: @permitted_classes + ActiveRecord.yaml_column_permitted_classes, + aliases: true, + ) + end + end + end + end + + def initialize(attr_name, object_class = Object, permitted_classes: [], unsafe_load: nil) + super( + attr_name, + SafeCoder.new(permitted_classes: permitted_classes || [], unsafe_load: unsafe_load), + object_class, + ) + check_arity_of_constructor + end + + def init_with(coder) # :nodoc: + unless coder["coder"] + permitted_classes = coder["permitted_classes"] || [] + unsafe_load = coder["unsafe_load"] || false + coder["coder"] = SafeCoder.new(permitted_classes: permitted_classes, unsafe_load: unsafe_load) + end + super(coder) + end + + def coder + # This is to retain forward compatibility when loading records serialized with Marshal + # from a previous version of Rails. + @coder ||= begin + permitted_classes = defined?(@permitted_classes) ? @permitted_classes : [] + unsafe_load = defined?(@unsafe_load) && @unsafe_load.nil? + SafeCoder.new(permitted_classes: permitted_classes, unsafe_load: unsafe_load) + end + end + + private + def check_arity_of_constructor + load(nil) + rescue ArgumentError + raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor." + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters.rb new file mode 100644 index 00000000..d4be1c08 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/filters" + +module ActiveRecord + module ConnectionAdapters + extend ActiveSupport::Autoload + + @adapters = {} + + class << self + # Registers a custom database adapter. + # + # Can also be used to define aliases. + # + # == Example + # + # ActiveRecord::ConnectionAdapters.register("megadb", "MegaDB::ActiveRecordAdapter", "mega_db/active_record_adapter") + # + # ActiveRecord::ConnectionAdapters.register("mysql", "ActiveRecord::ConnectionAdapters::TrilogyAdapter", "active_record/connection_adapters/trilogy_adapter") + # + def register(name, class_name, path = class_name.underscore) + @adapters[name.to_s] = [class_name, path] + end + + def resolve(adapter_name) # :nodoc: + # Require the adapter itself and give useful feedback about + # 1. Missing adapter gems. + # 2. Incorrectly registered adapters. + # 3. Adapter gems' missing dependencies. + class_name, path_to_adapter = @adapters[adapter_name.to_s] + + unless class_name + raise AdapterNotFound, <<~MSG.squish + Database configuration specifies nonexistent '#{adapter_name}' adapter. + Available adapters are: #{@adapters.keys.sort.join(", ")}. + Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary + adapter gem to your Gemfile if it's not in the list of available adapters. + MSG + end + + unless Object.const_defined?(class_name) + begin + require path_to_adapter + rescue LoadError => error + # We couldn't require the adapter itself. + if error.path == path_to_adapter + # We can assume here that a non-builtin adapter was specified and the path + # registered by the adapter gem is incorrect. + raise LoadError, "Error loading the '#{adapter_name}' Active Record adapter. Ensure that the path registered by the adapter gem is correct. #{error.message}", error.backtrace + else + # Bubbled up from the adapter require. Prefix the exception message + # with some guidance about how to address it and reraise. + raise LoadError, "Error loading the '#{adapter_name}' Active Record adapter. Missing a gem it depends on? #{error.message}", error.backtrace + end + end + end + + begin + Object.const_get(class_name) + rescue NameError => error + raise AdapterNotFound, "Could not load the #{class_name} Active Record adapter (#{error.message})." + end + end + end + + register "sqlite3", "ActiveRecord::ConnectionAdapters::SQLite3Adapter", "active_record/connection_adapters/sqlite3_adapter" + register "mysql2", "ActiveRecord::ConnectionAdapters::Mysql2Adapter", "active_record/connection_adapters/mysql2_adapter" + register "trilogy", "ActiveRecord::ConnectionAdapters::TrilogyAdapter", "active_record/connection_adapters/trilogy_adapter" + register "postgresql", "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter", "active_record/connection_adapters/postgresql_adapter" + + eager_autoload do + autoload :AbstractAdapter + end + + autoload :Column + autoload :PoolConfig + autoload :PoolManager + autoload :SchemaCache + autoload :BoundSchemaReflection, "active_record/connection_adapters/schema_cache" + autoload :SchemaReflection, "active_record/connection_adapters/schema_cache" + autoload :Deduplicable + + autoload_at "active_record/connection_adapters/abstract/schema_definitions" do + autoload :IndexDefinition + autoload :ColumnDefinition + autoload :ChangeColumnDefinition + autoload :ChangeColumnDefaultDefinition + autoload :ForeignKeyDefinition + autoload :CheckConstraintDefinition + autoload :TableDefinition + autoload :Table + autoload :AlterTable + autoload :ReferenceDefinition + end + + autoload_under "abstract" do + autoload :SchemaStatements + autoload :DatabaseStatements + autoload :DatabaseLimits + autoload :Quoting + autoload :ConnectionHandler + autoload :QueryCache + autoload :Savepoints + end + + autoload_at "active_record/connection_adapters/abstract/connection_pool" do + autoload :ConnectionPool + autoload :NullPool + end + + autoload_at "active_record/connection_adapters/abstract/transaction" do + autoload :TransactionManager + autoload :NullTransaction + autoload :RealTransaction + autoload :SavepointTransaction + autoload :TransactionState + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/connection_handler.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/connection_handler.rb new file mode 100644 index 00000000..7c969707 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/connection_handler.rb @@ -0,0 +1,293 @@ +# frozen_string_literal: true + +require "concurrent/map" + +module ActiveRecord + module ConnectionAdapters + # = Active Record Connection Handler + # + # ConnectionHandler is a collection of ConnectionPool objects. It is used + # for keeping separate connection pools that connect to different databases. + # + # For example, suppose that you have 5 models, with the following hierarchy: + # + # class Author < ActiveRecord::Base + # end + # + # class BankAccount < ActiveRecord::Base + # end + # + # class Book < ActiveRecord::Base + # establish_connection :library_db + # end + # + # class ScaryBook < Book + # end + # + # class GoodBook < Book + # end + # + # And a database.yml that looked like this: + # + # development: + # database: my_application + # host: localhost + # + # library_db: + # database: library + # host: some.library.org + # + # Your primary database in the development environment is "my_application" + # but the Book model connects to a separate database called "library_db" + # (this can even be a database on a different machine). + # + # Book, ScaryBook, and GoodBook will all use the same connection pool to + # "library_db" while Author, BankAccount, and any other models you create + # will use the default connection pool to "my_application". + # + # The various connection pools are managed by a single instance of + # ConnectionHandler accessible via ActiveRecord::Base.connection_handler. + # All Active Record models use this handler to determine the connection pool that they + # should use. + # + # The ConnectionHandler class is not coupled with the Active models, as it has no knowledge + # about the model. The model needs to pass a connection specification name to the handler, + # in order to look up the correct connection pool. + class ConnectionHandler + class ConnectionDescriptor # :nodoc: + def initialize(name, primary = false) + @name = name + @primary = primary + end + + def name + primary_class? ? "ActiveRecord::Base" : @name + end + + def primary_class? + @primary + end + + def current_preventing_writes + ActiveRecord::Base.preventing_writes?(@name) + end + end + + def initialize + # These caches are keyed by pool_config.connection_name (PoolConfig#connection_name). + @connection_name_to_pool_manager = Concurrent::Map.new(initial_capacity: 2) + end + + def prevent_writes # :nodoc: + ActiveSupport::IsolatedExecutionState[:active_record_prevent_writes] + end + + def prevent_writes=(prevent_writes) # :nodoc: + ActiveSupport::IsolatedExecutionState[:active_record_prevent_writes] = prevent_writes + end + + def connection_pool_names # :nodoc: + connection_name_to_pool_manager.keys + end + + # Returns the pools for a connection handler and given role. If +:all+ is passed, + # all pools belonging to the connection handler will be returned. + def connection_pool_list(role = nil) + if role.nil? || role == :all + connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs.map(&:pool) } + else + connection_name_to_pool_manager.values.flat_map { |m| m.pool_configs(role).map(&:pool) } + end + end + alias :connection_pools :connection_pool_list + + def each_connection_pool(role = nil, &block) # :nodoc: + role = nil if role == :all + return enum_for(__method__, role) unless block_given? + + connection_name_to_pool_manager.each_value do |manager| + manager.each_pool_config(role) do |pool_config| + yield pool_config.pool + end + end + end + + def establish_connection(config, owner_name: Base, role: Base.current_role, shard: Base.current_shard, clobber: false) + owner_name = determine_owner_name(owner_name, config) + + pool_config = resolve_pool_config(config, owner_name, role, shard) + db_config = pool_config.db_config + + pool_manager = set_pool_manager(pool_config.connection_descriptor) + + # If there is an existing pool with the same values as the pool_config + # don't remove the connection. Connections should only be removed if we are + # establishing a connection on a class that is already connected to a different + # configuration. + existing_pool_config = pool_manager.get_pool_config(role, shard) + + if !clobber && existing_pool_config && existing_pool_config.db_config == db_config + # Update the pool_config's connection class if it differs. This is used + # for ensuring that ActiveRecord::Base and the primary_abstract_class use + # the same pool. Without this granular swapping will not work correctly. + if owner_name.primary_class? && (existing_pool_config.connection_descriptor != owner_name) + existing_pool_config.connection_descriptor = owner_name + end + + existing_pool_config.pool + else + disconnect_pool_from_pool_manager(pool_manager, role, shard) + pool_manager.set_pool_config(role, shard, pool_config) + + payload = { + connection_name: pool_config.connection_descriptor.name, + role: role, + shard: shard, + config: db_config.configuration_hash + } + + ActiveSupport::Notifications.instrumenter.instrument("!connection.active_record", payload) do + pool_config.pool + end + end + end + + # Returns true if there are any active connections among the connection + # pools that the ConnectionHandler is managing. + def active_connections?(role = nil) + each_connection_pool(role).any?(&:active_connection?) + end + + # Returns any connections in use by the current thread back to the pool, + # and also returns connections to the pool cached by threads that are no + # longer alive. + def clear_active_connections!(role = nil) + each_connection_pool(role).each do |pool| + pool.release_connection + pool.disable_query_cache! + end + end + + # Clears the cache which maps classes. + # + # See ConnectionPool#clear_reloadable_connections! for details. + def clear_reloadable_connections!(role = nil) + each_connection_pool(role).each(&:clear_reloadable_connections!) + end + + def clear_all_connections!(role = nil) + each_connection_pool(role).each(&:disconnect!) + end + + # Disconnects all currently idle connections. + # + # See ConnectionPool#flush! for details. + def flush_idle_connections!(role = nil) + each_connection_pool(role).each(&:flush!) + end + + # Locate the connection of the nearest super class. This can be an + # active or defined connection: if it is the latter, it will be + # opened and set as the active connection for the class it was defined + # for (not necessarily the current class). + def retrieve_connection(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) # :nodoc: + pool = retrieve_connection_pool(connection_name, role: role, shard: shard, strict: true) + pool.lease_connection + end + + # Returns true if a connection that's accessible to this class has + # already been opened. + def connected?(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) + pool = retrieve_connection_pool(connection_name, role: role, shard: shard) + pool && pool.connected? + end + + def remove_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard) + if pool_manager = get_pool_manager(connection_name) + disconnect_pool_from_pool_manager(pool_manager, role, shard) + end + end + + # Retrieving the connection pool happens a lot, so we cache it in @connection_name_to_pool_manager. + # This makes retrieving the connection pool O(1) once the process is warm. + # When a connection is established or removed, we invalidate the cache. + def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard, strict: false) + pool_manager = get_pool_manager(connection_name) + pool = pool_manager&.get_pool_config(role, shard)&.pool + + if strict && !pool + selector = [ + ("'#{shard}' shard" unless shard == ActiveRecord::Base.default_shard), + ("'#{role}' role" unless role == ActiveRecord::Base.default_role), + ].compact.join(" and ") + + selector = [ + (connection_name unless connection_name == "ActiveRecord::Base"), + selector.presence, + ].compact.join(" with ") + + selector = " for #{selector}" if selector.present? + + message = "No database connection defined#{selector}." + + raise ConnectionNotDefined.new(message, connection_name: connection_name, shard: shard, role: role) + end + + pool + end + + private + attr_reader :connection_name_to_pool_manager + + # Returns the pool manager for a connection name / identifier. + def get_pool_manager(connection_name) + connection_name_to_pool_manager[connection_name] + end + + # Get the existing pool manager or initialize and assign a new one. + def set_pool_manager(connection_descriptor) + connection_name_to_pool_manager[connection_descriptor.name] ||= PoolManager.new + end + + def pool_managers + connection_name_to_pool_manager.values + end + + def disconnect_pool_from_pool_manager(pool_manager, role, shard) + pool_config = pool_manager.remove_pool_config(role, shard) + + if pool_config + pool_config.disconnect! + pool_config.db_config + end + end + + # Returns an instance of PoolConfig for a given adapter. + # Accepts a hash one layer deep that contains all connection information. + # + # == Example + # + # config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } } + # pool_config = Base.configurations.resolve_pool_config(:production) + # pool_config.db_config.configuration_hash + # # => { host: "localhost", database: "foo", adapter: "sqlite3" } + # + def resolve_pool_config(config, connection_name, role, shard) + db_config = Base.configurations.resolve(config) + db_config.validate! + raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.adapter + ConnectionAdapters::PoolConfig.new(connection_name, db_config, role, shard) + end + + def determine_owner_name(owner_name, config) + if owner_name.is_a?(String) || owner_name.is_a?(Symbol) + ConnectionDescriptor.new(owner_name.to_s) + elsif config.is_a?(Symbol) + ConnectionDescriptor.new(config.to_s) + else + owner_name + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/connection_pool.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/connection_pool.rb new file mode 100644 index 00000000..afbf80e2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -0,0 +1,954 @@ +# frozen_string_literal: true + +require "concurrent/map" +require "monitor" + +require "active_record/connection_adapters/abstract/connection_pool/queue" +require "active_record/connection_adapters/abstract/connection_pool/reaper" + +module ActiveRecord + module ConnectionAdapters + module AbstractPool # :nodoc: + end + + class NullPool # :nodoc: + include ConnectionAdapters::AbstractPool + + class NullConfig # :nodoc: + def method_missing(...) + nil + end + end + NULL_CONFIG = NullConfig.new # :nodoc: + + def initialize + super() + @mutex = Mutex.new + @server_version = nil + end + + def server_version(connection) # :nodoc: + @server_version || @mutex.synchronize { @server_version ||= connection.get_database_version } + end + + def schema_reflection + SchemaReflection.new(nil) + end + + def schema_cache; end + def connection_descriptor; end + def checkin(_); end + def remove(_); end + def async_executor; end + + def db_config + NULL_CONFIG + end + + def dirties_query_cache + true + end + end + + # = Active Record Connection Pool + # + # Connection pool base class for managing Active Record database + # connections. + # + # == Introduction + # + # A connection pool synchronizes thread access to a limited number of + # database connections. The basic idea is that each thread checks out a + # database connection from the pool, uses that connection, and checks the + # connection back in. ConnectionPool is completely thread-safe, and will + # ensure that a connection cannot be used by two threads at the same time, + # as long as ConnectionPool's contract is correctly followed. It will also + # handle cases in which there are more threads than connections: if all + # connections have been checked out, and a thread tries to checkout a + # connection anyway, then ConnectionPool will wait until some other thread + # has checked in a connection, or the +checkout_timeout+ has expired. + # + # == Obtaining (checking out) a connection + # + # Connections can be obtained and used from a connection pool in several + # ways: + # + # 1. Simply use {ActiveRecord::Base.lease_connection}[rdoc-ref:ConnectionHandling#lease_connection]. + # When you're done with the connection(s) and wish it to be returned to the pool, you call + # {ActiveRecord::Base.connection_handler.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!]. + # This is the default behavior for Active Record when used in conjunction with + # Action Pack's request handling cycle. + # 2. Manually check out a connection from the pool with + # {ActiveRecord::Base.connection_pool.checkout}[rdoc-ref:#checkout]. You are responsible for + # returning this connection to the pool when finished by calling + # {ActiveRecord::Base.connection_pool.checkin(connection)}[rdoc-ref:#checkin]. + # 3. Use {ActiveRecord::Base.connection_pool.with_connection(&block)}[rdoc-ref:#with_connection], which + # obtains a connection, yields it as the sole argument to the block, + # and returns it to the pool after the block completes. + # + # Connections in the pool are actually AbstractAdapter objects (or objects + # compatible with AbstractAdapter's interface). + # + # While a thread has a connection checked out from the pool using one of the + # above three methods, that connection will automatically be the one used + # by ActiveRecord queries executing on that thread. It is not required to + # explicitly pass the checked out connection to \Rails models or queries, for + # example. + # + # == Options + # + # There are several connection-pooling-related options that you can add to + # your database connection configuration: + # + # * +pool+: maximum number of connections the pool may manage (default 5). + # * +idle_timeout+: number of seconds that a connection will be kept + # unused in the pool before it is automatically disconnected (default + # 300 seconds). Set this to zero to keep connections forever. + # * +checkout_timeout+: number of seconds to wait for a connection to + # become available before giving up and raising a timeout error (default + # 5 seconds). + # + #-- + # Synchronization policy: + # * all public methods can be called outside +synchronize+ + # * access to these instance variables needs to be in +synchronize+: + # * @connections + # * @now_connecting + # * private methods that require being called in a +synchronize+ blocks + # are now explicitly documented + class ConnectionPool + # Prior to 3.3.5, WeakKeyMap had a use after free bug + # https://bugs.ruby-lang.org/issues/20688 + if ObjectSpace.const_defined?(:WeakKeyMap) && RUBY_VERSION >= "3.3.5" + WeakThreadKeyMap = ObjectSpace::WeakKeyMap + else + class WeakThreadKeyMap # :nodoc: + # FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap + # but it currently cause GC crashes: https://github.com/byroot/rails/pull/3 + def initialize + @map = {} + end + + def clear + @map.clear + end + + def [](key) + @map[key] + end + + def []=(key, value) + @map.select! { |c, _| c&.alive? } + @map[key] = value + end + end + end + + class Lease # :nodoc: + attr_accessor :connection, :sticky + + def initialize + @connection = nil + @sticky = nil + end + + def release + conn = @connection + @connection = nil + @sticky = nil + conn + end + + def clear(connection) + if @connection == connection + @connection = nil + @sticky = nil + true + else + false + end + end + end + + class LeaseRegistry # :nodoc: + def initialize + @mutex = Mutex.new + @map = WeakThreadKeyMap.new + end + + def [](context) + @mutex.synchronize do + @map[context] ||= Lease.new + end + end + + def clear + @mutex.synchronize do + @map.clear + end + end + end + + module ExecutorHooks # :nodoc: + class << self + def run + # noop + end + + def complete(_) + ActiveRecord::Base.connection_handler.each_connection_pool do |pool| + if (connection = pool.active_connection?) + transaction = connection.current_transaction + if transaction.closed? || !transaction.joinable? + pool.release_connection + end + end + end + end + end + end + + class << self + def install_executor_hooks(executor = ActiveSupport::Executor) + executor.register_hook(ExecutorHooks) + end + end + + include MonitorMixin + prepend QueryCache::ConnectionPoolConfiguration + include ConnectionAdapters::AbstractPool + + attr_accessor :automatic_reconnect, :checkout_timeout + attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard + + delegate :schema_reflection, :server_version, to: :pool_config + + # Creates a new ConnectionPool object. +pool_config+ is a PoolConfig + # object which describes database connection information (e.g. adapter, + # host name, username, password, etc), as well as the maximum size for + # this ConnectionPool. + # + # The default ConnectionPool maximum size is 5. + def initialize(pool_config) + super() + + @pool_config = pool_config + @db_config = pool_config.db_config + @role = pool_config.role + @shard = pool_config.shard + + @checkout_timeout = db_config.checkout_timeout + @idle_timeout = db_config.idle_timeout + @size = db_config.pool + + # This variable tracks the cache of threads mapped to reserved connections, with the + # sole purpose of speeding up the +connection+ method. It is not the authoritative + # registry of which thread owns which connection. Connection ownership is tracked by + # the +connection.owner+ attr on each +connection+ instance. + # The invariant works like this: if there is mapping of thread => conn, + # then that +thread+ does indeed own that +conn+. However, an absence of such + # mapping does not mean that the +thread+ doesn't own the said connection. In + # that case +conn.owner+ attr should be consulted. + # Access and modification of @leases does not require + # synchronization. + @leases = LeaseRegistry.new + + @connections = [] + @automatic_reconnect = true + + # Connection pool allows for concurrent (outside the main +synchronize+ section) + # establishment of new connections. This variable tracks the number of threads + # currently in the process of independently establishing connections to the DB. + @now_connecting = 0 + + @threads_blocking_new_connections = 0 + + @available = ConnectionLeasingQueue.new self + @pinned_connection = nil + @pinned_connections_depth = 0 + + @async_executor = build_async_executor + + @schema_cache = nil + + @reaper = Reaper.new(self, db_config.reaping_frequency) + @reaper.run + end + + def inspect # :nodoc: + name_field = " name=#{db_config.name.inspect}" unless db_config.name == "primary" + shard_field = " shard=#{@shard.inspect}" unless @shard == :default + + "#<#{self.class.name} env_name=#{db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>" + end + + def schema_cache + @schema_cache ||= BoundSchemaReflection.new(schema_reflection, self) + end + + def schema_reflection=(schema_reflection) + pool_config.schema_reflection = schema_reflection + @schema_cache = nil + end + + def migration_context # :nodoc: + MigrationContext.new(migrations_paths, schema_migration, internal_metadata) + end + + def migrations_paths # :nodoc: + db_config.migrations_paths || Migrator.migrations_paths + end + + def schema_migration # :nodoc: + SchemaMigration.new(self) + end + + def internal_metadata # :nodoc: + InternalMetadata.new(self) + end + + # Retrieve the connection associated with the current thread, or call + # #checkout to obtain one if necessary. + # + # #lease_connection can be called any number of times; the connection is + # held in a cache keyed by a thread. + def lease_connection + lease = connection_lease + lease.sticky = true + lease.connection ||= checkout + end + + def permanent_lease? # :nodoc: + connection_lease.sticky.nil? + end + + def pin_connection!(lock_thread) # :nodoc: + @pinned_connection ||= (connection_lease&.connection || checkout) + @pinned_connections_depth += 1 + + # Any leased connection must be in @connections otherwise + # some methods like #connected? won't behave correctly + unless @connections.include?(@pinned_connection) + @connections << @pinned_connection + end + + @pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread + @pinned_connection.verify! # eagerly validate the connection + @pinned_connection.begin_transaction joinable: false, _lazy: false + end + + def unpin_connection! # :nodoc: + raise "There isn't a pinned connection #{object_id}" unless @pinned_connection + + clean = true + @pinned_connection.lock.synchronize do + @pinned_connections_depth -= 1 + connection = @pinned_connection + @pinned_connection = nil if @pinned_connections_depth.zero? + + if connection.transaction_open? + connection.rollback_transaction + else + # Something committed or rolled back the transaction + clean = false + connection.reset! + end + + if @pinned_connection.nil? + connection.steal! + connection.lock_thread = nil + checkin(connection) + end + end + + clean + end + + def connection_descriptor # :nodoc: + pool_config.connection_descriptor + end + + # Returns true if there is an open connection being used for the current thread. + # + # This method only works for connections that have been obtained through + # #lease_connection or #with_connection methods. Connections obtained through + # #checkout will not be detected by #active_connection? + def active_connection? + connection_lease.connection + end + alias_method :active_connection, :active_connection? # :nodoc: + + # Signal that the thread is finished with the current connection. + # #release_connection releases the connection-thread association + # and returns the connection to the pool. + # + # This method only works for connections that have been obtained through + # #lease_connection or #with_connection methods, connections obtained through + # #checkout will not be automatically released. + def release_connection(existing_lease = nil) + if conn = connection_lease.release + checkin conn + return true + end + false + end + + # Yields a connection from the connection pool to the block. If no connection + # is already checked out by the current thread, a connection will be checked + # out from the pool, yielded to the block, and then returned to the pool when + # the block is finished. If a connection has already been checked out on the + # current thread, such as via #lease_connection or #with_connection, that existing + # connection will be the one yielded and it will not be returned to the pool + # automatically at the end of the block; it is expected that such an existing + # connection will be properly returned to the pool by the code that checked + # it out. + def with_connection(prevent_permanent_checkout: false) + lease = connection_lease + sticky_was = lease.sticky + lease.sticky = false if prevent_permanent_checkout + + if lease.connection + begin + yield lease.connection + ensure + lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was + end + else + begin + yield lease.connection = checkout + ensure + lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was + release_connection(lease) unless lease.sticky + end + end + end + + # Returns true if a connection has already been opened. + def connected? + synchronize { @connections.any?(&:connected?) } + end + + # Returns an array containing the connections currently in the pool. + # Access to the array does not require synchronization on the pool because + # the array is newly created and not retained by the pool. + # + # However; this method bypasses the ConnectionPool's thread-safe connection + # access pattern. A returned connection may be owned by another thread, + # unowned, or by happen-stance owned by the calling thread. + # + # Calling methods on a connection without ownership is subject to the + # thread-safety guarantees of the underlying method. Many of the methods + # on connection adapter classes are inherently multi-thread unsafe. + def connections + synchronize { @connections.dup } + end + + # Disconnects all connections in the pool, and clears the pool. + # + # Raises: + # - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all + # connections in the pool within a timeout interval (default duration is + # spec.db_config.checkout_timeout * 2 seconds). + def disconnect(raise_on_acquisition_timeout = true) + with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do + synchronize do + @connections.each do |conn| + if conn.in_use? + conn.steal! + checkin conn + end + conn.disconnect! + end + @connections = [] + @leases.clear + @available.clear + end + end + end + + # Disconnects all connections in the pool, and clears the pool. + # + # The pool first tries to gain ownership of all connections. If unable to + # do so within a timeout interval (default duration is + # spec.db_config.checkout_timeout * 2 seconds), then the pool is forcefully + # disconnected without any regard for other connection owning threads. + def disconnect! + disconnect(false) + end + + # Discards all connections in the pool (even if they're currently + # leased!), along with the pool itself. Any further interaction with the + # pool (except #spec and #schema_cache) is undefined. + # + # See AbstractAdapter#discard! + def discard! # :nodoc: + synchronize do + return if self.discarded? + @connections.each do |conn| + conn.discard! + end + @connections = @available = @leases = nil + end + end + + def discarded? # :nodoc: + @connections.nil? + end + + # Clears the cache which maps classes and re-connects connections that + # require reloading. + # + # Raises: + # - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all + # connections in the pool within a timeout interval (default duration is + # spec.db_config.checkout_timeout * 2 seconds). + def clear_reloadable_connections(raise_on_acquisition_timeout = true) + with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do + synchronize do + @connections.each do |conn| + if conn.in_use? + conn.steal! + checkin conn + end + conn.disconnect! if conn.requires_reloading? + end + @connections.delete_if(&:requires_reloading?) + @available.clear + end + end + end + + # Clears the cache which maps classes and re-connects connections that + # require reloading. + # + # The pool first tries to gain ownership of all connections. If unable to + # do so within a timeout interval (default duration is + # spec.db_config.checkout_timeout * 2 seconds), then the pool forcefully + # clears the cache and reloads connections without any regard for other + # connection owning threads. + def clear_reloadable_connections! + clear_reloadable_connections(false) + end + + # Check-out a database connection from the pool, indicating that you want + # to use it. You should call #checkin when you no longer need this. + # + # This is done by either returning and leasing existing connection, or by + # creating a new connection and leasing it. + # + # If all connections are leased and the pool is at capacity (meaning the + # number of currently leased connections is greater than or equal to the + # size limit set), an ActiveRecord::ConnectionTimeoutError exception will be raised. + # + # Returns: an AbstractAdapter object. + # + # Raises: + # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool. + def checkout(checkout_timeout = @checkout_timeout) + return checkout_and_verify(acquire_connection(checkout_timeout)) unless @pinned_connection + + @pinned_connection.lock.synchronize do + synchronize do + # The pinned connection may have been cleaned up before we synchronized, so check if it is still present + if @pinned_connection + @pinned_connection.verify! + + # Any leased connection must be in @connections otherwise + # some methods like #connected? won't behave correctly + unless @connections.include?(@pinned_connection) + @connections << @pinned_connection + end + + @pinned_connection + else + checkout_and_verify(acquire_connection(checkout_timeout)) + end + end + end + end + + # Check-in a database connection back into the pool, indicating that you + # no longer need this connection. + # + # +conn+: an AbstractAdapter object, which was obtained by earlier by + # calling #checkout on this pool. + def checkin(conn) + return if @pinned_connection.equal?(conn) + + conn.lock.synchronize do + synchronize do + connection_lease.clear(conn) + + conn._run_checkin_callbacks do + conn.expire + end + + @available.add conn + end + end + end + + # Remove a connection from the connection pool. The connection will + # remain open and active but will no longer be managed by this pool. + def remove(conn) + needs_new_connection = false + + synchronize do + remove_connection_from_thread_cache conn + + @connections.delete conn + @available.delete conn + + # @available.any_waiting? => true means that prior to removing this + # conn, the pool was at its max size (@connections.size == @size). + # This would mean that any threads stuck waiting in the queue wouldn't + # know they could checkout_new_connection, so let's do it for them. + # Because condition-wait loop is encapsulated in the Queue class + # (that in turn is oblivious to ConnectionPool implementation), threads + # that are "stuck" there are helpless. They have no way of creating + # new connections and are completely reliant on us feeding available + # connections into the Queue. + needs_new_connection = @available.any_waiting? + end + + # This is intentionally done outside of the synchronized section as we + # would like not to hold the main mutex while checking out new connections. + # Thus there is some chance that needs_new_connection information is now + # stale, we can live with that (bulk_make_new_connections will make + # sure not to exceed the pool's @size limit). + bulk_make_new_connections(1) if needs_new_connection + end + + # Recover lost connections for the pool. A lost connection can occur if + # a programmer forgets to checkin a connection at the end of a thread + # or a thread dies unexpectedly. + def reap + stale_connections = synchronize do + return if self.discarded? + @connections.select do |conn| + conn.in_use? && !conn.owner.alive? + end.each do |conn| + conn.steal! + end + end + + stale_connections.each do |conn| + if conn.active? + conn.reset! + checkin conn + else + remove conn + end + end + end + + # Disconnect all connections that have been idle for at least + # +minimum_idle+ seconds. Connections currently checked out, or that were + # checked in less than +minimum_idle+ seconds ago, are unaffected. + def flush(minimum_idle = @idle_timeout) + return if minimum_idle.nil? + + idle_connections = synchronize do + return if self.discarded? + @connections.select do |conn| + !conn.in_use? && conn.seconds_idle >= minimum_idle + end.each do |conn| + conn.lease + + @available.delete conn + @connections.delete conn + end + end + + idle_connections.each do |conn| + conn.disconnect! + end + end + + # Disconnect all currently idle connections. Connections currently checked + # out are unaffected. + def flush! + reap + flush(-1) + end + + def num_waiting_in_queue # :nodoc: + @available.num_waiting + end + + # Returns the connection pool's usage statistic. + # + # ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 } + def stat + synchronize do + { + size: size, + connections: @connections.size, + busy: @connections.count { |c| c.in_use? && c.owner.alive? }, + dead: @connections.count { |c| c.in_use? && !c.owner.alive? }, + idle: @connections.count { |c| !c.in_use? }, + waiting: num_waiting_in_queue, + checkout_timeout: checkout_timeout + } + end + end + + def schedule_query(future_result) # :nodoc: + @async_executor.post { future_result.execute_or_skip } + Thread.pass + end + + def new_connection # :nodoc: + connection = db_config.new_connection + connection.pool = self + connection + rescue ConnectionNotEstablished => ex + raise ex.set_pool(self) + end + + private + def connection_lease + @leases[ActiveSupport::IsolatedExecutionState.context] + end + + def build_async_executor + case ActiveRecord.async_query_executor + when :multi_thread_pool + if @db_config.max_threads > 0 + Concurrent::ThreadPoolExecutor.new( + min_threads: @db_config.min_threads, + max_threads: @db_config.max_threads, + max_queue: @db_config.max_queue, + fallback_policy: :caller_runs + ) + end + when :global_thread_pool + ActiveRecord.global_thread_pool_async_query_executor + end + end + + #-- + # this is unfortunately not concurrent + def bulk_make_new_connections(num_new_conns_needed) + num_new_conns_needed.times do + # try_to_checkout_new_connection will not exceed pool's @size limit + if new_conn = try_to_checkout_new_connection + # make the new_conn available to the starving threads stuck @available Queue + checkin(new_conn) + end + end + end + + # Take control of all existing connections so a "group" action such as + # reload/disconnect can be performed safely. It is no longer enough to + # wrap it in +synchronize+ because some pool's actions are allowed + # to be performed outside of the main +synchronize+ block. + def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true) + with_new_connections_blocked do + attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout) + yield + end + end + + def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true) + collected_conns = synchronize do + reap # No need to wait for dead owners + + # account for our own connections + @connections.select { |conn| conn.owner == ActiveSupport::IsolatedExecutionState.context } + end + + newly_checked_out = [] + timeout_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (@checkout_timeout * 2) + + @available.with_a_bias_for(ActiveSupport::IsolatedExecutionState.context) do + loop do + synchronize do + return if collected_conns.size == @connections.size && @now_connecting == 0 + + remaining_timeout = timeout_time - Process.clock_gettime(Process::CLOCK_MONOTONIC) + remaining_timeout = 0 if remaining_timeout < 0 + conn = checkout_for_exclusive_access(remaining_timeout) + collected_conns << conn + newly_checked_out << conn + end + end + end + rescue ExclusiveConnectionTimeoutError + # raise_on_acquisition_timeout == false means we are directed to ignore any + # timeouts and are expected to just give up: we've obtained as many connections + # as possible, note that in a case like that we don't return any of the + # +newly_checked_out+ connections. + + if raise_on_acquisition_timeout + release_newly_checked_out = true + raise + end + rescue Exception # if something else went wrong + # this can't be a "naked" rescue, because we have should return conns + # even for non-StandardErrors + release_newly_checked_out = true + raise + ensure + if release_newly_checked_out && newly_checked_out + # releasing only those conns that were checked out in this method, conns + # checked outside this method (before it was called) are not for us to release + newly_checked_out.each { |conn| checkin(conn) } + end + end + + #-- + # Must be called in a synchronize block. + def checkout_for_exclusive_access(checkout_timeout) + checkout(checkout_timeout) + rescue ConnectionTimeoutError + # this block can't be easily moved into attempt_to_checkout_all_existing_connections's + # rescue block, because doing so would put it outside of synchronize section, without + # being in a critical section thread_report might become inaccurate + msg = +"could not obtain ownership of all database connections in #{checkout_timeout} seconds" + + thread_report = [] + @connections.each do |conn| + unless conn.owner == ActiveSupport::IsolatedExecutionState.context + thread_report << "#{conn} is owned by #{conn.owner}" + end + end + + msg << " (#{thread_report.join(', ')})" if thread_report.any? + + raise ExclusiveConnectionTimeoutError.new(msg, connection_pool: self) + end + + def with_new_connections_blocked + synchronize do + @threads_blocking_new_connections += 1 + end + + yield + ensure + num_new_conns_required = 0 + + synchronize do + @threads_blocking_new_connections -= 1 + + if @threads_blocking_new_connections.zero? + @available.clear + + num_new_conns_required = num_waiting_in_queue + + @connections.each do |conn| + next if conn.in_use? + + @available.add conn + num_new_conns_required -= 1 + end + end + end + + bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0 + end + + # Acquire a connection by one of 1) immediately removing one + # from the queue of available connections, 2) creating a new + # connection if the pool is not at capacity, 3) waiting on the + # queue for a connection to become available. + # + # Raises: + # - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired + # + #-- + # Implementation detail: the connection returned by +acquire_connection+ + # will already be "+connection.lease+ -ed" to the current thread. + def acquire_connection(checkout_timeout) + # NOTE: we rely on @available.poll and +try_to_checkout_new_connection+ to + # +conn.lease+ the returned connection (and to do this in a +synchronized+ + # section). This is not the cleanest implementation, as ideally we would + # synchronize { conn.lease } in this method, but by leaving it to @available.poll + # and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections + # of the said methods and avoid an additional +synchronize+ overhead. + if conn = @available.poll || try_to_checkout_new_connection + conn + else + reap + # Retry after reaping, which may return an available connection, + # remove an inactive connection, or both + if conn = @available.poll || try_to_checkout_new_connection + conn + else + @available.poll(checkout_timeout) + end + end + rescue ConnectionTimeoutError => ex + raise ex.set_pool(self) + end + + #-- + # if owner_thread param is omitted, this must be called in synchronize block + def remove_connection_from_thread_cache(conn, owner_thread = conn.owner) + if owner_thread + @leases[owner_thread].clear(conn) + end + end + alias_method :release, :remove_connection_from_thread_cache + + # If the pool is not at a @size limit, establish new connection. Connecting + # to the DB is done outside main synchronized section. + #-- + # Implementation constraint: a newly established connection returned by this + # method must be in the +.leased+ state. + def try_to_checkout_new_connection + # first in synchronized section check if establishing new conns is allowed + # and increment @now_connecting, to prevent overstepping this pool's @size + # constraint + do_checkout = synchronize do + if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @size + @now_connecting += 1 + end + end + if do_checkout + begin + # if successfully incremented @now_connecting establish new connection + # outside of synchronized section + conn = checkout_new_connection + ensure + synchronize do + if conn + adopt_connection(conn) + # returned conn needs to be already leased + conn.lease + end + @now_connecting -= 1 + end + end + end + end + + def adopt_connection(conn) + conn.pool = self + @connections << conn + + # We just created the first connection, it's time to load the schema + # cache if that wasn't eagerly done before + if @schema_cache.nil? && ActiveRecord.lazily_load_schema_cache + schema_cache.load! + end + end + + def checkout_new_connection + raise ConnectionNotEstablished unless @automatic_reconnect + new_connection + end + + def checkout_and_verify(c) + c._run_checkout_callbacks do + c.clean! + end + c + rescue Exception + remove c + c.disconnect! + raise + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb new file mode 100644 index 00000000..8f8f9d53 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb @@ -0,0 +1,210 @@ +# frozen_string_literal: true + +require "monitor" + +module ActiveRecord + module ConnectionAdapters + class ConnectionPool + # = Active Record Connection Pool \Queue + # + # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool + # with which it shares a Monitor. + class Queue + def initialize(lock = Monitor.new) + @lock = lock + @cond = @lock.new_cond + @num_waiting = 0 + @queue = [] + end + + # Test if any threads are currently waiting on the queue. + def any_waiting? + synchronize do + @num_waiting > 0 + end + end + + # Returns the number of threads currently waiting on this + # queue. + def num_waiting + synchronize do + @num_waiting + end + end + + # Add +element+ to the queue. Never blocks. + def add(element) + synchronize do + @queue.push element + @cond.signal + end + end + + # If +element+ is in the queue, remove and return it, or +nil+. + def delete(element) + synchronize do + @queue.delete(element) + end + end + + # Remove all elements from the queue. + def clear + synchronize do + @queue.clear + end + end + + # Remove the head of the queue. + # + # If +timeout+ is not given, remove and return the head of the + # queue if the number of available elements is strictly + # greater than the number of threads currently waiting (that + # is, don't jump ahead in line). Otherwise, return +nil+. + # + # If +timeout+ is given, block if there is no element + # available, waiting up to +timeout+ seconds for an element to + # become available. + # + # Raises: + # - ActiveRecord::ConnectionTimeoutError if +timeout+ is given and no element + # becomes available within +timeout+ seconds, + def poll(timeout = nil) + synchronize { internal_poll(timeout) } + end + + private + def internal_poll(timeout) + no_wait_poll || (timeout && wait_poll(timeout)) + end + + def synchronize(&block) + @lock.synchronize(&block) + end + + # Test if the queue currently contains any elements. + def any? + !@queue.empty? + end + + # A thread can remove an element from the queue without + # waiting if and only if the number of currently available + # connections is strictly greater than the number of waiting + # threads. + def can_remove_no_wait? + @queue.size > @num_waiting + end + + # Removes and returns the head of the queue if possible, or +nil+. + def remove + @queue.pop + end + + # Remove and return the head of the queue if the number of + # available elements is strictly greater than the number of + # threads currently waiting. Otherwise, return +nil+. + def no_wait_poll + remove if can_remove_no_wait? + end + + # Waits on the queue up to +timeout+ seconds, then removes and + # returns the head of the queue. + def wait_poll(timeout) + @num_waiting += 1 + + t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC) + elapsed = 0 + loop do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @cond.wait(timeout - elapsed) + end + + return remove if any? + + elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0 + if elapsed >= timeout + msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" % + [timeout, elapsed] + raise ConnectionTimeoutError, msg + end + end + ensure + @num_waiting -= 1 + end + end + + # Adds the ability to turn a basic fair FIFO queue into one + # biased to some thread. + module BiasableQueue # :nodoc: + class BiasedConditionVariable # :nodoc: + # semantics of condition variables guarantee that +broadcast+, +broadcast_on_biased+, + # +signal+ and +wait+ methods are only called while holding a lock + def initialize(lock, other_cond, preferred_thread) + @real_cond = lock.new_cond + @other_cond = other_cond + @preferred_thread = preferred_thread + @num_waiting_on_real_cond = 0 + end + + def broadcast + broadcast_on_biased + @other_cond.broadcast + end + + def broadcast_on_biased + @num_waiting_on_real_cond = 0 + @real_cond.broadcast + end + + def signal + if @num_waiting_on_real_cond > 0 + @num_waiting_on_real_cond -= 1 + @real_cond + else + @other_cond + end.signal + end + + def wait(timeout) + if Thread.current == @preferred_thread + @num_waiting_on_real_cond += 1 + @real_cond + else + @other_cond + end.wait(timeout) + end + end + + def with_a_bias_for(thread) + previous_cond = nil + new_cond = nil + synchronize do + previous_cond = @cond + @cond = new_cond = BiasedConditionVariable.new(@lock, @cond, thread) + end + yield + ensure + synchronize do + @cond = previous_cond if previous_cond + new_cond.broadcast_on_biased if new_cond # wake up any remaining sleepers + end + end + end + + # Connections must be leased while holding the main pool mutex. This is + # an internal subclass that also +.leases+ returned connections while + # still in queue's critical section (queue synchronizes with the same + # @lock as the main pool) so that a returned connection is already + # leased and there is no need to re-enter synchronized block. + class ConnectionLeasingQueue < Queue # :nodoc: + include BiasableQueue + + private + def internal_poll(timeout) + conn = super + conn.lease if conn + conn + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb new file mode 100644 index 00000000..44fa0b1b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require "weakref" + +module ActiveRecord + module ConnectionAdapters + class ConnectionPool + # = Active Record Connection Pool \Reaper + # + # Every +frequency+ seconds, the reaper will call +reap+ and +flush+ on + # +pool+. A reaper instantiated with a zero frequency will never reap + # the connection pool. + # + # Configure the frequency by setting +reaping_frequency+ in your database + # YAML file (default 60 seconds). + class Reaper + attr_reader :pool, :frequency + + def initialize(pool, frequency) + @pool = pool + @frequency = frequency + end + + @mutex = Mutex.new + @pools = {} + @threads = {} + + class << self + def register_pool(pool, frequency) # :nodoc: + @mutex.synchronize do + unless @threads[frequency]&.alive? + @threads[frequency] = spawn_thread(frequency) + end + @pools[frequency] ||= [] + @pools[frequency] << WeakRef.new(pool) + end + end + + private + def spawn_thread(frequency) + Thread.new(frequency) do |t| + # Advise multi-threaded app servers to ignore this thread for + # the purposes of fork safety warnings + Thread.current.thread_variable_set(:fork_safe, true) + Thread.current.name = "AR Pool Reaper" + running = true + while running + sleep t + @mutex.synchronize do + @pools[frequency].select! do |pool| + pool.weakref_alive? && !pool.discarded? + end + + @pools[frequency].each do |p| + p.reap + p.flush + rescue WeakRef::RefError + end + + if @pools[frequency].empty? + @pools.delete(frequency) + @threads.delete(frequency) + running = false + end + end + end + end + end + end + + def run + return unless frequency && frequency > 0 + self.class.register_pool(pool, frequency) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/database_limits.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/database_limits.rb new file mode 100644 index 00000000..be58e98c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/database_limits.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters # :nodoc: + module DatabaseLimits + def max_identifier_length # :nodoc: + 64 + end + + # Returns the maximum length of a table name. + def table_name_length + max_identifier_length + end + + # Returns the maximum length of a table alias. + def table_alias_length + max_identifier_length + end + + # Returns the maximum length of an index name. + def index_name_length + max_identifier_length + end + + private + def bind_params_length + 65535 + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/database_statements.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/database_statements.rb new file mode 100644 index 00000000..4541f615 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -0,0 +1,747 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters # :nodoc: + module DatabaseStatements + def initialize + super + reset_transaction + end + + # Converts an arel AST to SQL + def to_sql(arel_or_sql_string, binds = []) + sql, _ = to_sql_and_binds(arel_or_sql_string, binds) + sql + end + + def to_sql_and_binds(arel_or_sql_string, binds = [], preparable = nil, allow_retry = false) # :nodoc: + # Arel::TreeManager -> Arel::Node + if arel_or_sql_string.respond_to?(:ast) + arel_or_sql_string = arel_or_sql_string.ast + end + + if Arel.arel_node?(arel_or_sql_string) && !(String === arel_or_sql_string) + unless binds.empty? + raise "Passing bind parameters with an arel AST is forbidden. " \ + "The values must be stored on the AST directly" + end + + collector = collector() + collector.retryable = true + + if prepared_statements + collector.preparable = true + sql, binds = visitor.compile(arel_or_sql_string, collector) + + if binds.length > bind_params_length + unprepared_statement do + return to_sql_and_binds(arel_or_sql_string) + end + end + preparable = collector.preparable + else + sql = visitor.compile(arel_or_sql_string, collector) + end + allow_retry = collector.retryable + [sql.freeze, binds, preparable, allow_retry] + else + arel_or_sql_string = arel_or_sql_string.dup.freeze unless arel_or_sql_string.frozen? + [arel_or_sql_string, binds, preparable, allow_retry] + end + end + private :to_sql_and_binds + + # This is used in the StatementCache object. It returns an object that + # can be used to query the database repeatedly. + def cacheable_query(klass, arel) # :nodoc: + if prepared_statements + sql, binds = visitor.compile(arel.ast, collector) + query = klass.query(sql) + else + collector = klass.partial_query_collector + parts, binds = visitor.compile(arel.ast, collector) + query = klass.partial_query(parts) + end + [query, binds] + end + + # Returns an ActiveRecord::Result instance. + def select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false) + arel = arel_from_relation(arel) + sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable, allow_retry) + + select(sql, name, binds, + prepare: prepared_statements && preparable, + async: async && FutureResult::SelectAll, + allow_retry: allow_retry + ) + rescue ::RangeError + ActiveRecord::Result.empty(async: async) + end + + # Returns a record hash with the column names as keys and column values + # as values. + def select_one(arel, name = nil, binds = [], async: false) + select_all(arel, name, binds, async: async).then(&:first) + end + + # Returns a single value from a record + def select_value(arel, name = nil, binds = [], async: false) + select_rows(arel, name, binds, async: async).then { |rows| single_value_from_rows(rows) } + end + + # Returns an array of the values of the first column in a select: + # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3] + def select_values(arel, name = nil, binds = []) + select_rows(arel, name, binds).map(&:first) + end + + # Returns an array of arrays containing the field values. + # Order is the same as that returned by +columns+. + def select_rows(arel, name = nil, binds = [], async: false) + select_all(arel, name, binds, async: async).then(&:rows) + end + + def query_value(...) # :nodoc: + single_value_from_rows(query(...)) + end + + def query_values(...) # :nodoc: + query(...).map(&:first) + end + + def query(...) # :nodoc: + internal_exec_query(...).rows + end + + # Determines whether the SQL statement is a write query. + def write_query?(sql) + raise NotImplementedError + end + + # Executes the SQL statement in the context of this connection and returns + # the raw result from the connection adapter. + # + # Setting +allow_retry+ to true causes the db to reconnect and retry + # executing the SQL statement in case of a connection-related exception. + # This option should only be enabled for known idempotent queries. + # + # Note: the query is assumed to have side effects and the query cache + # will be cleared. If the query is read-only, consider using #select_all + # instead. + # + # Note: depending on your database connector, the result returned by this + # method may be manually memory managed. Consider using #exec_query + # wrapper instead. + def execute(sql, name = nil, allow_retry: false) + internal_execute(sql, name, allow_retry: allow_retry) + end + + # Executes +sql+ statement in the context of this connection using + # +binds+ as the bind substitutes. +name+ is logged along with + # the executed +sql+ statement. + # + # Note: the query is assumed to have side effects and the query cache + # will be cleared. If the query is read-only, consider using #select_all + # instead. + def exec_query(sql, name = "SQL", binds = [], prepare: false) + internal_exec_query(sql, name, binds, prepare: prepare) + end + + # Executes insert +sql+ statement in the context of this connection using + # +binds+ as the bind substitutes. +name+ is logged along with + # the executed +sql+ statement. + # Some adapters support the `returning` keyword argument which allows to control the result of the query: + # `nil` is the default value and maintains default behavior. If an array of column names is passed - + # the result will contain values of the specified columns from the inserted row. + def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) + sql, binds = sql_for_insert(sql, pk, binds, returning) + internal_exec_query(sql, name, binds) + end + + # Executes delete +sql+ statement in the context of this connection using + # +binds+ as the bind substitutes. +name+ is logged along with + # the executed +sql+ statement. + def exec_delete(sql, name = nil, binds = []) + affected_rows(internal_execute(sql, name, binds)) + end + + # Executes update +sql+ statement in the context of this connection using + # +binds+ as the bind substitutes. +name+ is logged along with + # the executed +sql+ statement. + def exec_update(sql, name = nil, binds = []) + affected_rows(internal_execute(sql, name, binds)) + end + + def exec_insert_all(sql, name) # :nodoc: + internal_exec_query(sql, name) + end + + def explain(arel, binds = [], options = []) # :nodoc: + raise NotImplementedError + end + + # Executes an INSERT query and returns the new record's ID + # + # +id_value+ will be returned unless the value is +nil+, in + # which case the database will attempt to calculate the last inserted + # id and return that value. + # + # If the next id was calculated in advance (as in Oracle), it should be + # passed in as +id_value+. + # Some adapters support the `returning` keyword argument which allows defining the return value of the method: + # `nil` is the default value and maintains default behavior. If an array of column names is passed - + # an array of is returned from the method representing values of the specified columns from the inserted row. + def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [], returning: nil) + sql, binds = to_sql_and_binds(arel, binds) + value = exec_insert(sql, name, binds, pk, sequence_name, returning: returning) + + return returning_column_values(value) unless returning.nil? + + id_value || last_inserted_id(value) + end + alias create insert + + # Executes the update statement and returns the number of rows affected. + def update(arel, name = nil, binds = []) + sql, binds = to_sql_and_binds(arel, binds) + exec_update(sql, name, binds) + end + + # Executes the delete statement and returns the number of rows affected. + def delete(arel, name = nil, binds = []) + sql, binds = to_sql_and_binds(arel, binds) + exec_delete(sql, name, binds) + end + + # Executes the truncate statement. + def truncate(table_name, name = nil) + execute(build_truncate_statement(table_name), name) + end + + def truncate_tables(*table_names) # :nodoc: + table_names -= [pool.schema_migration.table_name, pool.internal_metadata.table_name] + + return if table_names.empty? + + disable_referential_integrity do + statements = build_truncate_statements(table_names) + execute_batch(statements, "Truncate Tables") + end + end + + # Runs the given block in a database transaction, and returns the result + # of the block. + # + # == Transaction callbacks + # + # #transaction yields an ActiveRecord::Transaction object on which it is + # possible to register callback: + # + # ActiveRecord::Base.transaction do |transaction| + # transaction.before_commit { puts "before commit!" } + # transaction.after_commit { puts "after commit!" } + # transaction.after_rollback { puts "after rollback!" } + # end + # + # == Nested transactions support + # + # #transaction calls can be nested. By default, this makes all database + # statements in the nested transaction block become part of the parent + # transaction. For example, the following behavior may be surprising: + # + # ActiveRecord::Base.transaction do + # Post.create(title: 'first') + # ActiveRecord::Base.transaction do + # Post.create(title: 'second') + # raise ActiveRecord::Rollback + # end + # end + # + # This creates both "first" and "second" posts. Reason is the + # ActiveRecord::Rollback exception in the nested block does not issue a + # ROLLBACK. Since these exceptions are captured in transaction blocks, + # the parent block does not see it and the real transaction is committed. + # + # Most databases don't support true nested transactions. At the time of + # writing, the only database that supports true nested transactions that + # we're aware of, is MS-SQL. + # + # In order to get around this problem, #transaction will emulate the effect + # of nested transactions, by using savepoints: + # https://dev.mysql.com/doc/refman/en/savepoint.html. + # + # It is safe to call this method if a database transaction is already open, + # i.e. if #transaction is called within another #transaction block. In case + # of a nested call, #transaction will behave as follows: + # + # - The block will be run without doing anything. All database statements + # that happen within the block are effectively appended to the already + # open database transaction. + # - However, if +:requires_new+ is set, the block will be wrapped in a + # database savepoint acting as a sub-transaction. + # + # In order to get a ROLLBACK for the nested transaction you may ask for a + # real sub-transaction by passing requires_new: true. + # If anything goes wrong, the database rolls back to the beginning of + # the sub-transaction without rolling back the parent transaction. + # If we add it to the previous example: + # + # ActiveRecord::Base.transaction do + # Post.create(title: 'first') + # ActiveRecord::Base.transaction(requires_new: true) do + # Post.create(title: 'second') + # raise ActiveRecord::Rollback + # end + # end + # + # only post with title "first" is created. + # + # See ActiveRecord::Transactions to learn more. + # + # === Caveats + # + # MySQL doesn't support DDL transactions. If you perform a DDL operation, + # then any created savepoints will be automatically released. For example, + # if you've created a savepoint, then you execute a CREATE TABLE statement, + # then the savepoint that was created will be automatically released. + # + # This means that, on MySQL, you shouldn't execute DDL operations inside + # a #transaction call that you know might create a savepoint. Otherwise, + # #transaction will raise exceptions when it tries to release the + # already-automatically-released savepoints: + # + # Model.lease_connection.transaction do # BEGIN + # Model.lease_connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1 + # Model.lease_connection.create_table(...) + # # active_record_1 now automatically released + # end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error! + # end + # + # == Transaction isolation + # + # If your database supports setting the isolation level for a transaction, you can set + # it like so: + # + # Post.transaction(isolation: :serializable) do + # # ... + # end + # + # Valid isolation levels are: + # + # * :read_uncommitted + # * :read_committed + # * :repeatable_read + # * :serializable + # + # You should consult the documentation for your database to understand the + # semantics of these different levels: + # + # * https://www.postgresql.org/docs/current/static/transaction-iso.html + # * https://dev.mysql.com/doc/refman/en/set-transaction.html + # + # An ActiveRecord::TransactionIsolationError will be raised if: + # + # * The adapter does not support setting the isolation level + # * You are joining an existing open transaction + # * You are creating a nested (savepoint) transaction + # + # The mysql2, trilogy, and postgresql adapters support setting the transaction + # isolation level. + # :args: (requires_new: nil, isolation: nil, &block) + def transaction(requires_new: nil, isolation: nil, joinable: true, &block) + if !requires_new && current_transaction.joinable? + if isolation + raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction" + end + yield current_transaction.user_transaction + else + within_new_transaction(isolation: isolation, joinable: joinable, &block) + end + rescue ActiveRecord::Rollback + # rollbacks are silently swallowed + end + + attr_reader :transaction_manager # :nodoc: + + delegate :within_new_transaction, :open_transactions, :current_transaction, :begin_transaction, + :commit_transaction, :rollback_transaction, :materialize_transactions, + :disable_lazy_transactions!, :enable_lazy_transactions!, :dirty_current_transaction, + to: :transaction_manager + + def mark_transaction_written_if_write(sql) # :nodoc: + transaction = current_transaction + if transaction.open? + transaction.written ||= write_query?(sql) + end + end + + def transaction_open? + current_transaction.open? + end + + def reset_transaction(restore: false) # :nodoc: + # Store the existing transaction state to the side + old_state = @transaction_manager if restore && @transaction_manager&.restorable? + + @transaction_manager = ConnectionAdapters::TransactionManager.new(self) + + if block_given? + # Reconfigure the connection without any transaction state in the way + result = yield + + # Now the connection's fully established, we can swap back + if old_state + @transaction_manager = old_state + @transaction_manager.restore_transactions + end + + result + end + end + + # Register a record with the current transaction so that its after_commit and after_rollback callbacks + # can be called. + def add_transaction_record(record, ensure_finalize = true) + current_transaction.add_record(record, ensure_finalize) + end + + # Begins the transaction (and turns off auto-committing). + def begin_db_transaction() end + + def begin_deferred_transaction(isolation_level = nil) # :nodoc: + if isolation_level + begin_isolated_db_transaction(isolation_level) + else + begin_db_transaction + end + end + + def transaction_isolation_levels + { + read_uncommitted: "READ UNCOMMITTED", + read_committed: "READ COMMITTED", + repeatable_read: "REPEATABLE READ", + serializable: "SERIALIZABLE" + } + end + + # Begins the transaction with the isolation level set. Raises an error by + # default; adapters that support setting the isolation level should implement + # this method. + def begin_isolated_db_transaction(isolation) + raise ActiveRecord::TransactionIsolationError, "adapter does not support setting transaction isolation" + end + + # Hook point called after an isolated DB transaction is committed + # or rolled back. + # Most adapters don't need to implement anything because the isolation + # level is set on a per transaction basis. + # But some databases like SQLite set it on a per connection level + # and need to explicitly reset it after commit or rollback. + def reset_isolation_level + end + + # Commits the transaction (and turns on auto-committing). + def commit_db_transaction() end + + # Rolls back the transaction (and turns on auto-committing). Must be + # done if the transaction block raises an exception or returns false. + def rollback_db_transaction + exec_rollback_db_transaction + rescue ActiveRecord::ConnectionNotEstablished, ActiveRecord::ConnectionFailed + # Connection's gone; that counts as a rollback + end + + def exec_rollback_db_transaction() end # :nodoc: + + def restart_db_transaction + exec_restart_db_transaction + end + + def exec_restart_db_transaction() end # :nodoc: + + def rollback_to_savepoint(name = nil) + exec_rollback_to_savepoint(name) + end + + def default_sequence_name(table, column) + nil + end + + # Set the sequence to the max value of the table's column. + def reset_sequence!(table, column, sequence = nil) + # Do nothing by default. Implement for PostgreSQL, Oracle, ... + end + + # Inserts the given fixture into the table. Overridden in adapters that require + # something beyond a simple insert (e.g. Oracle). + # Most of adapters should implement +insert_fixtures_set+ that leverages bulk SQL insert. + # We keep this method to provide fallback + # for databases like SQLite that do not support bulk inserts. + def insert_fixture(fixture, table_name) + execute(build_fixture_sql(Array.wrap(fixture), table_name), "Fixture Insert") + end + + def insert_fixtures_set(fixture_set, tables_to_delete = []) + fixture_inserts = build_fixture_statements(fixture_set) + table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name(table)}" } + statements = table_deletes + fixture_inserts + + transaction(requires_new: true) do + disable_referential_integrity do + execute_batch(statements, "Fixtures Load") + end + end + end + + def empty_insert_statement_value(primary_key = nil) + "DEFAULT VALUES" + end + + # Sanitizes the given LIMIT parameter in order to prevent SQL injection. + # + # The +limit+ may be anything that can evaluate to a string via #to_s. It + # should look like an integer, or an Arel SQL literal. + # + # Returns Integer and Arel::Nodes::SqlLiteral limits as is. + def sanitize_limit(limit) + if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral) + limit + else + Integer(limit) + end + end + + # Fixture value is quoted by Arel, however scalar values + # are not quotable. In this case we want to convert + # the column value to YAML. + def with_yaml_fallback(value) # :nodoc: + if value.is_a?(Hash) || value.is_a?(Array) + YAML.dump(value) + else + value + end + end + + # This is a safe default, even if not high precision on all databases + HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP", retryable: true).freeze # :nodoc: + private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP + + # Returns an Arel SQL literal for the CURRENT_TIMESTAMP for usage with + # arbitrary precision date/time columns. + # + # Adapters supporting datetime with precision should override this to + # provide as much precision as is available. + def high_precision_current_timestamp + HIGH_PRECISION_CURRENT_TIMESTAMP + end + + # Same as raw_execute but returns an ActiveRecord::Result object. + def raw_exec_query(...) # :nodoc: + cast_result(raw_execute(...)) + end + + # Execute a query and returns an ActiveRecord::Result + def internal_exec_query(...) # :nodoc: + cast_result(internal_execute(...)) + end + + private + # Lowest level way to execute a query. Doesn't check for illegal writes, doesn't annotate queries, yields a native result object. + def raw_execute(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, batch: false) + type_casted_binds = type_casted_binds(binds) + log(sql, name, binds, type_casted_binds, async: async) do |notification_payload| + with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn| + perform_query(conn, sql, binds, type_casted_binds, prepare: prepare, notification_payload: notification_payload, batch: batch) + end + end + end + + def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch:) + raise NotImplementedError + end + + # Receive a native adapter result object and returns an ActiveRecord::Result object. + def cast_result(raw_result) + raise NotImplementedError + end + + def affected_rows(raw_result) + raise NotImplementedError + end + + def preprocess_query(sql) + check_if_write_query(sql) + mark_transaction_written_if_write(sql) + + # We call tranformers after the write checks so we don't add extra parsing work. + # This means we assume no transformer whille change a read for a write + # but it would be insane to do such a thing. + ActiveRecord.query_transformers.each do |transformer| + sql = transformer.call(sql, self) + end + + sql + end + + # Same as #internal_exec_query, but yields a native adapter result + def internal_execute(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, &block) + sql = preprocess_query(sql) + raw_execute(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions, &block) + end + + def execute_batch(statements, name = nil, **kwargs) + statements.each do |statement| + raw_execute(statement, name, **kwargs) + end + end + + DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze + private_constant :DEFAULT_INSERT_VALUE + + def default_insert_value(column) + DEFAULT_INSERT_VALUE + end + + def build_fixture_sql(fixtures, table_name) + columns = schema_cache.columns_hash(table_name).reject { |_, column| supports_virtual_columns? && column.virtual? } + + values_list = fixtures.map do |fixture| + fixture = fixture.stringify_keys + + unknown_columns = fixture.keys - columns.keys + if unknown_columns.any? + raise Fixture::FixtureError, %(table "#{table_name}" has no columns named #{unknown_columns.map(&:inspect).join(', ')}.) + end + + columns.map do |name, column| + if fixture.key?(name) + type = lookup_cast_type_from_column(column) + with_yaml_fallback(type.serialize(fixture[name])) + else + default_insert_value(column) + end + end + end + + table = Arel::Table.new(table_name) + manager = Arel::InsertManager.new(table) + + if values_list.size == 1 + values = values_list.shift + new_values = [] + columns.each_key.with_index { |column, i| + unless values[i].equal?(DEFAULT_INSERT_VALUE) + new_values << values[i] + manager.columns << table[column] + end + } + values_list << new_values + else + columns.each_key { |column| manager.columns << table[column] } + end + + manager.values = manager.create_values_list(values_list) + visitor.compile(manager.ast) + end + + def build_fixture_statements(fixture_set) + fixture_set.filter_map do |table_name, fixtures| + next if fixtures.empty? + build_fixture_sql(fixtures, table_name) + end + end + + def build_truncate_statement(table_name) + "TRUNCATE TABLE #{quote_table_name(table_name)}" + end + + def build_truncate_statements(table_names) + table_names.map do |table_name| + build_truncate_statement(table_name) + end + end + + def combine_multi_statements(total_sql) + total_sql.join(";\n") + end + + # Returns an ActiveRecord::Result instance. + def select(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false) + if async && async_enabled? + if current_transaction.joinable? + raise AsynchronousQueryInsideTransactionError, "Asynchronous queries are not allowed inside transactions" + end + + # We make sure to run query transformers on the original thread + sql = preprocess_query(sql) + future_result = async.new( + pool, + sql, + name, + binds, + prepare: prepare, + ) + if supports_concurrent_connections? && !current_transaction.joinable? + future_result.schedule!(ActiveRecord::Base.asynchronous_queries_session) + else + future_result.execute!(self) + end + future_result + else + result = internal_exec_query(sql, name, binds, prepare: prepare, allow_retry: allow_retry) + if async + FutureResult.wrap(result) + else + result + end + end + end + + def sql_for_insert(sql, pk, binds, returning) # :nodoc: + if supports_insert_returning? + if pk.nil? + # Extract the table from the insert sql. Yuck. + table_ref = extract_table_ref_from_insert_sql(sql) + pk = primary_key(table_ref) if table_ref + end + + returning_columns = returning || Array(pk) + + returning_columns_statement = returning_columns.map { |c| quote_column_name(c) }.join(", ") + sql = "#{sql} RETURNING #{returning_columns_statement}" if returning_columns.any? + end + + [sql, binds] + end + + def last_inserted_id(result) + single_value_from_rows(result.rows) + end + + def returning_column_values(result) + [last_inserted_id(result)] + end + + def single_value_from_rows(rows) + row = rows.first + row && row.first + end + + def arel_from_relation(relation) + if relation.is_a?(Relation) + relation.arel + else + relation + end + end + + def extract_table_ref_from_insert_sql(sql) + if sql =~ /into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im + $1.delete('"').strip + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/query_cache.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/query_cache.rb new file mode 100644 index 00000000..dfa4577b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -0,0 +1,321 @@ +# frozen_string_literal: true + +require "concurrent/map" +require "concurrent/atomic/atomic_fixnum" + +module ActiveRecord + module ConnectionAdapters # :nodoc: + module QueryCache + DEFAULT_SIZE = 100 # :nodoc: + + class << self + def included(base) # :nodoc: + dirties_query_cache base, :exec_query, :execute, :create, :insert, :update, :delete, :truncate, + :truncate_tables, :rollback_to_savepoint, :rollback_db_transaction, :restart_db_transaction, + :exec_insert_all + + base.set_callback :checkin, :after, :unset_query_cache! + end + + def dirties_query_cache(base, *method_names) + method_names.each do |method_name| + base.class_eval <<-end_code, __FILE__, __LINE__ + 1 + def #{method_name}(...) + if pool.dirties_query_cache + ActiveRecord::Base.clear_query_caches_for_current_thread + end + super + end + end_code + end + end + end + + class Store # :nodoc: + attr_accessor :enabled, :dirties + alias_method :enabled?, :enabled + alias_method :dirties?, :dirties + + def initialize(version, max_size) + @version = version + @current_version = version.value + @map = {} + @max_size = max_size + @enabled = false + @dirties = true + end + + def size + check_version + @map.size + end + + def empty? + check_version + @map.empty? + end + + def [](key) + check_version + return unless @enabled + + if entry = @map.delete(key) + @map[key] = entry + end + end + + def compute_if_absent(key) + check_version + + return yield unless @enabled + + if entry = @map.delete(key) + return @map[key] = entry + end + + if @max_size && @map.size >= @max_size + @map.shift # evict the oldest entry + end + + @map[key] ||= yield + end + + def clear + @map.clear + self + end + + private + def check_version + if @current_version != @version.value + @map.clear + @current_version = @version.value + end + end + end + + class QueryCacheRegistry # :nodoc: + def initialize + @mutex = Mutex.new + @map = ConnectionPool::WeakThreadKeyMap.new + end + + def compute_if_absent(context) + @map[context] || @mutex.synchronize do + @map[context] ||= yield + end + end + + def clear + @map.synchronize do + @map.clear + end + end + end + + module ConnectionPoolConfiguration # :nodoc: + def initialize(...) + super + @query_cache_version = Concurrent::AtomicFixnum.new + @thread_query_caches = QueryCacheRegistry.new + @query_cache_max_size = \ + case query_cache = db_config&.query_cache + when 0, false + nil + when Integer + query_cache + when nil + DEFAULT_SIZE + end + end + + def checkout_and_verify(connection) + super + connection.query_cache ||= query_cache + connection + end + + # Disable the query cache within the block. + def disable_query_cache(dirties: true) + cache = query_cache + old_enabled, cache.enabled, old_dirties, cache.dirties = cache.enabled, false, cache.dirties, dirties + begin + yield + ensure + cache.enabled, cache.dirties = old_enabled, old_dirties + end + end + + def enable_query_cache + cache = query_cache + old_enabled, cache.enabled, old_dirties, cache.dirties = cache.enabled, true, cache.dirties, true + begin + yield + ensure + cache.enabled, cache.dirties = old_enabled, old_dirties + end + end + + def enable_query_cache! + query_cache.enabled = true + query_cache.dirties = true + end + + def disable_query_cache! + query_cache.enabled = false + query_cache.dirties = true + end + + def query_cache_enabled + query_cache.enabled + end + + def dirties_query_cache + query_cache.dirties + end + + def clear_query_cache + if @pinned_connection + # With transactional fixtures, and especially systems test + # another thread may use the same connection, but with a different + # query cache. So we must clear them all. + @query_cache_version.increment + end + query_cache.clear + end + + def query_cache + @thread_query_caches.compute_if_absent(ActiveSupport::IsolatedExecutionState.context) do + Store.new(@query_cache_version, @query_cache_max_size) + end + end + end + + attr_accessor :query_cache + + def initialize(*) + super + @query_cache = nil + end + + def query_cache_enabled + @query_cache&.enabled? + end + + # Enable the query cache within the block. + def cache(&block) + pool.enable_query_cache(&block) + end + + def enable_query_cache! + pool.enable_query_cache! + end + + # Disable the query cache within the block. + # + # Set dirties: false to prevent query caches on all connections from being cleared by write operations. + # (By default, write operations dirty all connections' query caches in case they are replicas whose cache would now be outdated.) + def uncached(dirties: true, &block) + pool.disable_query_cache(dirties: dirties, &block) + end + + def disable_query_cache! + pool.disable_query_cache! + end + + # Clears the query cache. + # + # One reason you may wish to call this method explicitly is between queries + # that ask the database to randomize results. Otherwise the cache would see + # the same SQL query and repeatedly return the same result each time, silently + # undermining the randomness you were expecting. + def clear_query_cache + pool.clear_query_cache + end + + def select_all(arel, name = nil, binds = [], preparable: nil, async: false, allow_retry: false) # :nodoc: + arel = arel_from_relation(arel) + + # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. + # Such queries should not be cached. + if @query_cache&.enabled? && !(arel.respond_to?(:locked) && arel.locked) + sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable) + + if async + result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry) + FutureResult.wrap(result) + else + cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry) } + end + else + super + end + end + + private + def unset_query_cache! + @query_cache = nil + end + + def lookup_sql_cache(sql, name, binds) + key = binds.empty? ? sql : [sql, binds] + + result = nil + @lock.synchronize do + result = @query_cache[key] + end + + if result + ActiveSupport::Notifications.instrument( + "sql.active_record", + cache_notification_info_result(sql, name, binds, result) + ) + end + + result + end + + def cache_sql(sql, name, binds) + key = binds.empty? ? sql : [sql, binds] + result = nil + hit = true + + @lock.synchronize do + result = @query_cache.compute_if_absent(key) do + hit = false + yield + end + end + + if hit + ActiveSupport::Notifications.instrument( + "sql.active_record", + cache_notification_info_result(sql, name, binds, result) + ) + end + + result.dup + end + + def cache_notification_info_result(sql, name, binds, result) + payload = cache_notification_info(sql, name, binds) + payload[:row_count] = result.length + payload + end + + # Database adapters can override this method to + # provide custom cache information. + def cache_notification_info(sql, name, binds) + { + sql: sql, + binds: binds, + type_casted_binds: -> { type_casted_binds(binds) }, + name: name, + connection: self, + transaction: current_transaction.user_transaction.presence, + cached: true + } + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/quoting.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/quoting.rb new file mode 100644 index 00000000..5d6c02ce --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/quoting.rb @@ -0,0 +1,239 @@ +# frozen_string_literal: true + +require "active_support/core_ext/big_decimal/conversions" +require "active_support/multibyte/chars" + +module ActiveRecord + module ConnectionAdapters # :nodoc: + # = Active Record Connection Adapters \Quoting + module Quoting + extend ActiveSupport::Concern + + module ClassMethods # :nodoc: + # Regexp for column names (with or without a table name prefix). + # Matches the following: + # + # "#{table_name}.#{column_name}" + # "#{column_name}" + def column_name_matcher + / + \A + ( + (?: + # table_name.column_name | function(one or no argument) + ((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\)) + ) + (?:(?:\s+AS)?\s+\w+)? + ) + (?:\s*,\s*\g<1>)* + \z + /ix + end + + # Regexp for column names with order (with or without a table name prefix, + # with or without various order modifiers). Matches the following: + # + # "#{table_name}.#{column_name}" + # "#{table_name}.#{column_name} #{direction}" + # "#{table_name}.#{column_name} #{direction} NULLS FIRST" + # "#{table_name}.#{column_name} NULLS LAST" + # "#{column_name}" + # "#{column_name} #{direction}" + # "#{column_name} #{direction} NULLS FIRST" + # "#{column_name} NULLS LAST" + def column_name_with_order_matcher + / + \A + ( + (?: + # table_name.column_name | function(one or no argument) + ((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\)) + ) + (?:\s+ASC|\s+DESC)? + (?:\s+NULLS\s+(?:FIRST|LAST))? + ) + (?:\s*,\s*\g<1>)* + \z + /ix + end + + # Quotes the column name. Must be implemented by subclasses + def quote_column_name(column_name) + raise NotImplementedError + end + + # Quotes the table name. Defaults to column name quoting. + def quote_table_name(table_name) + quote_column_name(table_name) + end + end + + # Quotes the column value to help prevent + # {SQL injection attacks}[https://en.wikipedia.org/wiki/SQL_injection]. + def quote(value) + case value + when String, Symbol, ActiveSupport::Multibyte::Chars + "'#{quote_string(value.to_s)}'" + when true then quoted_true + when false then quoted_false + when nil then "NULL" + # BigDecimals need to be put in a non-normalized form and quoted. + when BigDecimal then value.to_s("F") + when Numeric then value.to_s + when Type::Binary::Data then quoted_binary(value) + when Type::Time::Value then "'#{quoted_time(value)}'" + when Date, Time then "'#{quoted_date(value)}'" + when Class then "'#{value}'" + else raise TypeError, "can't quote #{value.class.name}" + end + end + + # Cast a +value+ to a type that the database understands. For example, + # SQLite does not understand dates, so this method will convert a Date + # to a String. + def type_cast(value) + case value + when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data + value.to_s + when true then unquoted_true + when false then unquoted_false + # BigDecimals need to be put in a non-normalized form and quoted. + when BigDecimal then value.to_s("F") + when nil, Numeric, String then value + when Type::Time::Value then quoted_time(value) + when Date, Time then quoted_date(value) + else raise TypeError, "can't cast #{value.class.name}" + end + end + + # Cast a value to be used as a bound parameter of unknown type. For example, + # MySQL might perform dangerous castings when comparing a string to a number, + # so this method will cast numbers to string. + def cast_bound_value(value) # :nodoc: + value + end + + # If you are having to call this function, you are likely doing something + # wrong. The column does not have sufficient type information if the user + # provided a custom type on the class level either explicitly (via + # Attributes::ClassMethods#attribute) or implicitly (via + # AttributeMethods::Serialization::ClassMethods#serialize, +time_zone_aware_attributes+). + # In almost all cases, the sql type should only be used to change quoting behavior, when the primitive to + # represent the type doesn't sufficiently reflect the differences + # (varchar vs binary) for example. The type used to get this primitive + # should have been provided before reaching the connection adapter. + def lookup_cast_type_from_column(column) # :nodoc: + lookup_cast_type(column.sql_type) + end + + # Quotes a string, escaping any ' (single quote) and \ (backslash) + # characters. + def quote_string(s) + s.gsub("\\", '\&\&').gsub("'", "''") # ' (for ruby-mode) + end + + # Quotes the column name. + def quote_column_name(column_name) + self.class.quote_column_name(column_name) + end + + # Quotes the table name. + def quote_table_name(table_name) + self.class.quote_table_name(table_name) + end + + # Override to return the quoted table name for assignment. Defaults to + # table quoting. + # + # This works for MySQL where table.column can be used to + # resolve ambiguity. + # + # We override this in the sqlite3 and postgresql adapters to use only + # the column name (as per syntax requirements). + def quote_table_name_for_assignment(table, attr) + quote_table_name("#{table}.#{attr}") + end + + def quote_default_expression(value, column) # :nodoc: + if value.is_a?(Proc) + value.call + else + value = lookup_cast_type(column.sql_type).serialize(value) + quote(value) + end + end + + def quoted_true + "TRUE" + end + + def unquoted_true + true + end + + def quoted_false + "FALSE" + end + + def unquoted_false + false + end + + # Quote date/time values for use in SQL input. Includes microseconds + # if the value is a Time responding to usec. + def quoted_date(value) + if value.acts_like?(:time) + if default_timezone == :utc + value = value.getutc if !value.utc? + else + value = value.getlocal + end + end + + result = value.to_fs(:db) + if value.respond_to?(:usec) && value.usec > 0 + result << "." << sprintf("%06d", value.usec) + else + result + end + end + + def quoted_time(value) # :nodoc: + value = value.change(year: 2000, month: 1, day: 1) + quoted_date(value).sub(/\A\d\d\d\d-\d\d-\d\d /, "") + end + + def quoted_binary(value) # :nodoc: + "'#{quote_string(value.to_s)}'" + end + + def sanitize_as_sql_comment(value) # :nodoc: + # Sanitize a string to appear within a SQL comment + # For compatibility, this also surrounding "/*+", "/*", and "*/" + # charcacters, possibly with single surrounding space. + # Then follows that by replacing any internal "*/" or "/ *" with + # "* /" or "/ *" + comment = value.to_s.dup + comment.gsub!(%r{\A\s*/\*\+?\s?|\s?\*/\s*\Z}, "") + comment.gsub!("*/", "* /") + comment.gsub!("/*", "/ *") + comment + end + + private + def type_casted_binds(binds) + binds&.map do |value| + if ActiveModel::Attribute === value + type_cast(value.value_for_database) + else + type_cast(value) + end + end + end + + def lookup_cast_type(sql_type) + type_map.lookup(sql_type) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/savepoints.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/savepoints.rb new file mode 100644 index 00000000..fbfe923f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/savepoints.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + # = Active Record Connection Adapters \Savepoints + module Savepoints + def current_savepoint_name + current_transaction.savepoint_name + end + + def create_savepoint(name = current_savepoint_name) + internal_execute("SAVEPOINT #{name}", "TRANSACTION") + end + + def exec_rollback_to_savepoint(name = current_savepoint_name) + internal_execute("ROLLBACK TO SAVEPOINT #{name}", "TRANSACTION") + end + + def release_savepoint(name = current_savepoint_name) + internal_execute("RELEASE SAVEPOINT #{name}", "TRANSACTION") + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/schema_creation.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/schema_creation.rb new file mode 100644 index 00000000..6abcaad8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -0,0 +1,189 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + class SchemaCreation # :nodoc: + def initialize(conn) + @conn = conn + @cache = {} + end + + def accept(o) + m = @cache[o.class] ||= "visit_#{o.class.name.split('::').last}" + send m, o + end + + delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql, + :options_include_default?, :supports_indexes_in_create?, :use_foreign_keys?, + :quoted_columns_for_index, :supports_partial_index?, :supports_check_constraints?, + :supports_index_include?, :supports_exclusion_constraints?, :supports_unique_constraints?, + :supports_nulls_not_distinct?, + to: :@conn, private: true + + private + def visit_AlterTable(o) + sql = +"ALTER TABLE #{quote_table_name(o.name)} " + sql << o.adds.map { |col| accept col }.join(" ") + sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(" ") + sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(" ") + sql << o.check_constraint_adds.map { |con| visit_AddCheckConstraint con }.join(" ") + sql << o.check_constraint_drops.map { |con| visit_DropCheckConstraint con }.join(" ") + sql << o.constraint_drops.map { |con| visit_DropConstraint con }.join(" ") + end + + def visit_ColumnDefinition(o) + o.sql_type = type_to_sql(o.type, **o.options) + column_sql = +"#{quote_column_name(o.name)} #{o.sql_type}" + add_column_options!(column_sql, column_options(o)) unless o.type == :primary_key + column_sql + end + + def visit_AddColumnDefinition(o) + +"ADD #{accept(o.column)}" + end + + def visit_TableDefinition(o) + create_sql = +"CREATE#{table_modifier_in_create(o)} TABLE " + create_sql << "IF NOT EXISTS " if o.if_not_exists + create_sql << "#{quote_table_name(o.name)} " + + statements = o.columns.map { |c| accept c } + statements << accept(o.primary_keys) if o.primary_keys + + if supports_indexes_in_create? + statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) }) + end + + if use_foreign_keys? + statements.concat(o.foreign_keys.map { |fk| accept fk }) + end + + if supports_check_constraints? + statements.concat(o.check_constraints.map { |chk| accept chk }) + end + + if supports_exclusion_constraints? + statements.concat(o.exclusion_constraints.map { |exc| accept exc }) + end + + if supports_unique_constraints? + statements.concat(o.unique_constraints.map { |exc| accept exc }) + end + + create_sql << "(#{statements.join(', ')})" if statements.present? + add_table_options!(create_sql, o) + create_sql << " AS #{to_sql(o.as)}" if o.as + create_sql + end + + def visit_PrimaryKeyDefinition(o) + "PRIMARY KEY (#{o.name.map { |name| quote_column_name(name) }.join(', ')})" + end + + def visit_ForeignKeyDefinition(o) + quoted_columns = Array(o.column).map { |c| quote_column_name(c) } + quoted_primary_keys = Array(o.primary_key).map { |c| quote_column_name(c) } + sql = +<<~SQL + CONSTRAINT #{quote_column_name(o.name)} + FOREIGN KEY (#{quoted_columns.join(", ")}) + REFERENCES #{quote_table_name(o.to_table)} (#{quoted_primary_keys.join(", ")}) + SQL + sql << " #{action_sql('DELETE', o.on_delete)}" if o.on_delete + sql << " #{action_sql('UPDATE', o.on_update)}" if o.on_update + sql + end + + def visit_AddForeignKey(o) + "ADD #{accept(o)}" + end + + def visit_DropConstraint(name) + "DROP CONSTRAINT #{quote_column_name(name)}" + end + alias :visit_DropForeignKey :visit_DropConstraint + alias :visit_DropCheckConstraint :visit_DropConstraint + + def visit_CreateIndexDefinition(o) + index = o.index + + sql = ["CREATE"] + sql << "UNIQUE" if index.unique + sql << "INDEX" + sql << o.algorithm if o.algorithm + sql << "IF NOT EXISTS" if o.if_not_exists + sql << index.type if index.type + sql << "#{quote_column_name(index.name)} ON #{quote_table_name(index.table)}" + sql << "USING #{index.using}" if supports_index_using? && index.using + sql << "(#{quoted_columns(index)})" + sql << "INCLUDE (#{quoted_include_columns(index.include)})" if supports_index_include? && index.include + sql << "NULLS NOT DISTINCT" if supports_nulls_not_distinct? && index.nulls_not_distinct + sql << "WHERE #{index.where}" if supports_partial_index? && index.where + + sql.join(" ") + end + + def visit_CheckConstraintDefinition(o) + "CONSTRAINT #{o.name} CHECK (#{o.expression})" + end + + def visit_AddCheckConstraint(o) + "ADD #{accept(o)}" + end + + def quoted_columns(o) + String === o.columns ? o.columns : quoted_columns_for_index(o.columns, o.column_options) + end + + def supports_index_using? + true + end + + def add_table_options!(create_sql, o) + create_sql << " #{o.options}" if o.options + create_sql + end + + def column_options(o) + o.options.merge(column: o) + end + + def add_column_options!(sql, options) + sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options) + # must explicitly check for :null to allow change_column to work on migrations + if options[:null] == false + sql << " NOT NULL" + end + if options[:auto_increment] == true + sql << " AUTO_INCREMENT" + end + if options[:primary_key] == true + sql << " PRIMARY KEY" + end + sql + end + + def to_sql(sql) + sql = sql.to_sql if sql.respond_to?(:to_sql) + sql + end + + # Returns any SQL string to go between CREATE and TABLE. May be nil. + def table_modifier_in_create(o) + " TEMPORARY" if o.temporary + end + + def action_sql(action, dependency) + case dependency + when :nullify then "ON #{action} SET NULL" + when :cascade then "ON #{action} CASCADE" + when :restrict then "ON #{action} RESTRICT" + else + raise ArgumentError, <<~MSG + '#{dependency}' is not supported for :on_update or :on_delete. + Supported values are: :nullify, :cascade, :restrict + MSG + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/schema_definitions.rb new file mode 100644 index 00000000..3888fefa --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -0,0 +1,970 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters # :nodoc: + # Abstract representation of an index definition on a table. Instances of + # this type are typically created and returned by methods in database + # adapters. e.g. ActiveRecord::ConnectionAdapters::MySQL::SchemaStatements#indexes + class IndexDefinition # :nodoc: + attr_reader :table, :name, :unique, :columns, :lengths, :orders, :opclasses, :where, :type, :using, :include, :nulls_not_distinct, :comment, :valid + + def initialize( + table, name, + unique = false, + columns = [], + lengths: {}, + orders: {}, + opclasses: {}, + where: nil, + type: nil, + using: nil, + include: nil, + nulls_not_distinct: nil, + comment: nil, + valid: true + ) + @table = table + @name = name + @unique = unique + @columns = columns + @lengths = concise_options(lengths) + @orders = concise_options(orders) + @opclasses = concise_options(opclasses) + @where = where + @type = type + @using = using + @include = include + @nulls_not_distinct = nulls_not_distinct + @comment = comment + @valid = valid + end + + def valid? + @valid + end + + def column_options + { + length: lengths, + order: orders, + opclass: opclasses, + } + end + + def defined_for?(columns = nil, name: nil, unique: nil, valid: nil, include: nil, nulls_not_distinct: nil, **options) + columns = options[:column] if columns.blank? + (columns.nil? || Array(self.columns) == Array(columns).map(&:to_s)) && + (name.nil? || self.name == name.to_s) && + (unique.nil? || self.unique == unique) && + (valid.nil? || self.valid == valid) && + (include.nil? || Array(self.include) == Array(include).map(&:to_s)) && + (nulls_not_distinct.nil? || self.nulls_not_distinct == nulls_not_distinct) + end + + private + def concise_options(options) + if columns.size == options.size && options.values.uniq.size == 1 + options.values.first + else + options + end + end + end + + # Abstract representation of a column definition. Instances of this type + # are typically created by methods in TableDefinition, and added to the + # +columns+ attribute of said TableDefinition object, in order to be used + # for generating a number of table creation or table changing SQL statements. + ColumnDefinition = Struct.new(:name, :type, :options, :sql_type) do # :nodoc: + self::OPTION_NAMES = [ + :limit, + :precision, + :scale, + :default, + :null, + :collation, + :comment, + :primary_key, + :if_exists, + :if_not_exists + ] + + def primary_key? + options[:primary_key] + end + + (self::OPTION_NAMES - [:primary_key]).each do |option_name| + module_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{option_name} + options[:#{option_name}] + end + + def #{option_name}=(value) + options[:#{option_name}] = value + end + CODE + end + + def aliased_types(name, fallback) + "timestamp" == name ? :datetime : fallback + end + end + + AddColumnDefinition = Struct.new(:column) # :nodoc: + + ChangeColumnDefinition = Struct.new(:column, :name) # :nodoc: + + ChangeColumnDefaultDefinition = Struct.new(:column, :default) # :nodoc: + + CreateIndexDefinition = Struct.new(:index, :algorithm, :if_not_exists) # :nodoc: + + PrimaryKeyDefinition = Struct.new(:name) # :nodoc: + + ForeignKeyDefinition = Struct.new(:from_table, :to_table, :options) do # :nodoc: + def name + options[:name] + end + + def column + options[:column] + end + + def primary_key + options[:primary_key] || default_primary_key + end + + def on_delete + options[:on_delete] + end + + def on_update + options[:on_update] + end + + def deferrable + options[:deferrable] + end + + def custom_primary_key? + options[:primary_key] != default_primary_key + end + + def validate? + options.fetch(:validate, true) + end + alias validated? validate? + + def export_name_on_schema_dump? + !ActiveRecord::SchemaDumper.fk_ignore_pattern.match?(name) if name + end + + def defined_for?(to_table: nil, validate: nil, **options) + options = options.slice(*self.options.keys) + + (to_table.nil? || to_table.to_s == self.to_table) && + (validate.nil? || validate == self.options.fetch(:validate, validate)) && + options.all? { |k, v| Array(self.options[k]).map(&:to_s) == Array(v).map(&:to_s) } + end + + private + def default_primary_key + "id" + end + end + + CheckConstraintDefinition = Struct.new(:table_name, :expression, :options) do + def name + options[:name] + end + + def validate? + options.fetch(:validate, true) + end + alias validated? validate? + + def export_name_on_schema_dump? + !ActiveRecord::SchemaDumper.chk_ignore_pattern.match?(name) if name + end + + def defined_for?(name:, expression: nil, validate: nil, **options) + options = options.slice(*self.options.keys) + + self.name == name.to_s && + (validate.nil? || validate == self.options.fetch(:validate, validate)) && + options.all? { |k, v| self.options[k].to_s == v.to_s } + end + end + + class ReferenceDefinition # :nodoc: + def initialize( + name, + polymorphic: false, + index: true, + foreign_key: false, + type: :bigint, + **options + ) + @name = name + @polymorphic = polymorphic + @index = index + @foreign_key = foreign_key + @type = type + @options = options + + if polymorphic && foreign_key + raise ArgumentError, "Cannot add a foreign key to a polymorphic relation" + end + end + + def add(table_name, connection) + columns.each do |name, type, options| + connection.add_column(table_name, name, type, **options) + end + + if index + connection.add_index(table_name, column_names, **index_options(table_name)) + end + + if foreign_key + connection.add_foreign_key(table_name, foreign_table_name, **foreign_key_options) + end + end + + def add_to(table) + columns.each do |name, type, options| + table.column(name, type, **options) + end + + if index + table.index(column_names, **index_options(table.name)) + end + + if foreign_key + table.foreign_key(foreign_table_name, **foreign_key_options) + end + end + + private + attr_reader :name, :polymorphic, :index, :foreign_key, :type, :options + + def as_options(value) + value.is_a?(Hash) ? value : {} + end + + def conditional_options + options.slice(:if_exists, :if_not_exists) + end + + def polymorphic_options + as_options(polymorphic).merge(conditional_options).merge(options.slice(:null, :first, :after)) + end + + def polymorphic_index_name(table_name) + "index_#{table_name}_on_#{name}" + end + + def index_options(table_name) + index_options = as_options(index).merge(conditional_options) + + # legacy reference index names are used on versions 6.0 and earlier + return index_options if options[:_uses_legacy_reference_index_name] + + index_options[:name] ||= polymorphic_index_name(table_name) if polymorphic + index_options + end + + def foreign_key_options + as_options(foreign_key).merge(column: column_name, **conditional_options) + end + + def columns + result = [[column_name, type, options]] + if polymorphic + result.unshift(["#{name}_type", :string, polymorphic_options]) + end + result + end + + def column_name + "#{name}_id" + end + + def column_names + columns.map(&:first) + end + + def foreign_table_name + foreign_key_options.fetch(:to_table) do + Base.pluralize_table_names ? name.to_s.pluralize : name + end + end + end + + module ColumnMethods + extend ActiveSupport::Concern + + # Appends a primary key definition to the table definition. + # Can be called multiple times, but this is probably not a good idea. + def primary_key(name, type = :primary_key, **options) + column(name, type, **options.merge(primary_key: true)) + end + + ## + # :method: column + # :call-seq: column(name, type, **options) + # + # Appends a column or columns of a specified type. + # + # t.string(:goat) + # t.string(:goat, :sheep) + # + # See TableDefinition#column + + included do + define_column_methods :bigint, :binary, :boolean, :date, :datetime, :decimal, + :float, :integer, :json, :string, :text, :time, :timestamp, :virtual + + alias :blob :binary + alias :numeric :decimal + end + + class_methods do + def define_column_methods(*column_types) # :nodoc: + column_types.each do |column_type| + module_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{column_type}(*names, **options) + raise ArgumentError, "Missing column name(s) for #{column_type}" if names.empty? + names.each { |name| column(name, :#{column_type}, **options) } + end + RUBY + end + end + private :define_column_methods + end + end + + # = Active Record Connection Adapters \Table \Definition + # + # Represents the schema of an SQL table in an abstract way. This class + # provides methods for manipulating the schema representation. + # + # Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table] + # is actually of this type: + # + # class SomeMigration < ActiveRecord::Migration[8.0] + # def up + # create_table :foo do |t| + # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition" + # end + # end + # + # def down + # ... + # end + # end + # + class TableDefinition + include ColumnMethods + + attr_reader :name, :temporary, :if_not_exists, :options, :as, :comment, :indexes, :foreign_keys, :check_constraints + + def initialize( + conn, + name, + temporary: false, + if_not_exists: false, + options: nil, + as: nil, + comment: nil, + ** + ) + @conn = conn + @columns_hash = {} + @indexes = [] + @foreign_keys = [] + @primary_keys = nil + @check_constraints = [] + @temporary = temporary + @if_not_exists = if_not_exists + @options = options + @as = as + @name = name + @comment = comment + end + + def set_primary_key(table_name, id, primary_key, **options) + if id && !as + pk = primary_key || Base.get_primary_key(table_name.to_s.singularize) + + if id.is_a?(Hash) + options.merge!(id.except(:type)) + id = id.fetch(:type, :primary_key) + end + + if pk.is_a?(Array) + primary_keys(pk) + else + primary_key(pk, id, **options) + end + end + end + + def primary_keys(name = nil) # :nodoc: + @primary_keys = PrimaryKeyDefinition.new(name) if name + @primary_keys + end + + # Returns an array of ColumnDefinition objects for the columns of the table. + def columns; @columns_hash.values; end + + # Returns a ColumnDefinition for the column with name +name+. + def [](name) + @columns_hash[name.to_s] + end + + # Instantiates a new column for the table. + # See {connection.add_column}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_column] + # for available options. + # + # Additional options are: + # * :index - + # Create an index for the column. Can be either true or an options hash. + # + # This method returns self. + # + # == Examples + # + # # Assuming `td` is an instance of TableDefinition + # td.column(:granted, :boolean, index: true) + # + # == Short-hand examples + # + # Instead of calling #column directly, you can also work with the short-hand definitions for the default types. + # They use the type as the method name instead of as a parameter and allow for multiple columns to be defined + # in a single statement. + # + # What can be written like this with the regular calls to column: + # + # create_table :products do |t| + # t.column :shop_id, :integer + # t.column :creator_id, :integer + # t.column :item_number, :string + # t.column :name, :string, default: "Untitled" + # t.column :value, :string, default: "Untitled" + # t.column :created_at, :datetime + # t.column :updated_at, :datetime + # end + # add_index :products, :item_number + # + # can also be written as follows using the short-hand: + # + # create_table :products do |t| + # t.integer :shop_id, :creator_id + # t.string :item_number, index: true + # t.string :name, :value, default: "Untitled" + # t.timestamps null: false + # end + # + # There's a short-hand method for each of the type values declared at the top. And then there's + # TableDefinition#timestamps that'll add +created_at+ and +updated_at+ as datetimes. + # + # TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type + # column if the :polymorphic option is supplied. If :polymorphic is a hash of + # options, these will be used when creating the _type column. The :index option + # will also create an index, similar to calling {add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index]. + # So what can be written like this: + # + # create_table :taggings do |t| + # t.integer :tag_id, :tagger_id, :taggable_id + # t.string :tagger_type + # t.string :taggable_type, default: 'Photo' + # end + # add_index :taggings, :tag_id, name: 'index_taggings_on_tag_id' + # add_index :taggings, [:tagger_id, :tagger_type] + # + # Can also be written as follows using references: + # + # create_table :taggings do |t| + # t.references :tag, index: { name: 'index_taggings_on_tag_id' } + # t.references :tagger, polymorphic: true + # t.references :taggable, polymorphic: { default: 'Photo' }, index: false + # end + def column(name, type, index: nil, **options) + name = name.to_s + type = type.to_sym if type + + raise_on_duplicate_column(name) + @columns_hash[name] = new_column_definition(name, type, **options) + + if index + index_options = index.is_a?(Hash) ? index : {} + index(name, **index_options) + end + + self + end + + # remove the column +name+ from the table. + # remove_column(:account_id) + def remove_column(name) + @columns_hash.delete name.to_s + end + + # Adds index options to the indexes hash, keyed by column name + # This is primarily used to track indexes that need to be created after the table + # + # index(:account_id, name: 'index_projects_on_account_id') + def index(column_name, **options) + indexes << [column_name, options] + end + + def foreign_key(to_table, **options) + foreign_keys << new_foreign_key_definition(to_table, options) + end + + def check_constraint(expression, **options) + check_constraints << new_check_constraint_definition(expression, options) + end + + # Appends :datetime columns :created_at and + # :updated_at to the table. See {connection.add_timestamps}[rdoc-ref:SchemaStatements#add_timestamps] + # + # t.timestamps null: false + def timestamps(**options) + options[:null] = false if options[:null].nil? + + if !options.key?(:precision) && @conn.supports_datetime_with_precision? + options[:precision] = 6 + end + + column(:created_at, :datetime, **options) + column(:updated_at, :datetime, **options) + end + + # Adds a reference. + # + # t.references(:user) + # t.belongs_to(:supplier, foreign_key: true) + # t.belongs_to(:supplier, foreign_key: true, type: :integer) + # + # See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use. + def references(*args, **options) + args.each do |ref_name| + ReferenceDefinition.new(ref_name, **options).add_to(self) + end + end + alias :belongs_to :references + + def new_column_definition(name, type, **options) # :nodoc: + if integer_like_primary_key?(type, options) + type = integer_like_primary_key_type(type, options) + end + type = aliased_types(type.to_s, type) + + if @conn.supports_datetime_with_precision? + if type == :datetime && !options.key?(:precision) + options[:precision] = 6 + end + end + + options[:primary_key] ||= type == :primary_key + options[:null] = false if options[:primary_key] + create_column_definition(name, type, options) + end + + def new_foreign_key_definition(to_table, options) # :nodoc: + prefix = ActiveRecord::Base.table_name_prefix + suffix = ActiveRecord::Base.table_name_suffix + to_table = "#{prefix}#{to_table}#{suffix}" + options = @conn.foreign_key_options(name, to_table, options) + ForeignKeyDefinition.new(name, to_table, options) + end + + def new_check_constraint_definition(expression, options) # :nodoc: + options = @conn.check_constraint_options(name, expression, options) + CheckConstraintDefinition.new(name, expression, options) + end + + private + def valid_column_definition_options + @conn.valid_column_definition_options + end + + def create_column_definition(name, type, options) + unless options[:_skip_validate_options] + options.except(:_uses_legacy_reference_index_name, :_skip_validate_options).assert_valid_keys(valid_column_definition_options) + end + + ColumnDefinition.new(name, type, options) + end + + def aliased_types(name, fallback) + "timestamp" == name ? :datetime : fallback + end + + def integer_like_primary_key?(type, options) + options[:primary_key] && [:integer, :bigint].include?(type) && !options.key?(:default) + end + + def integer_like_primary_key_type(type, options) + type + end + + def raise_on_duplicate_column(name) + if @columns_hash[name] + if @columns_hash[name].primary_key? + raise ArgumentError, "you can't redefine the primary key column '#{name}' on '#{@name}'. To define a custom primary key, pass { id: false } to create_table." + else + raise ArgumentError, "you can't define an already defined column '#{name}' on '#{@name}'." + end + end + end + end + + class AlterTable # :nodoc: + attr_reader :adds + attr_reader :foreign_key_adds, :foreign_key_drops + attr_reader :check_constraint_adds, :check_constraint_drops + attr_reader :constraint_drops + + def initialize(td) + @td = td + @adds = [] + @foreign_key_adds = [] + @foreign_key_drops = [] + @check_constraint_adds = [] + @check_constraint_drops = [] + @constraint_drops = [] + end + + def name; @td.name; end + + def add_foreign_key(to_table, options) + @foreign_key_adds << @td.new_foreign_key_definition(to_table, options) + end + + def drop_foreign_key(name) + @foreign_key_drops << name + end + + def add_check_constraint(expression, options) + @check_constraint_adds << @td.new_check_constraint_definition(expression, options) + end + + def drop_check_constraint(constraint_name) + @check_constraint_drops << constraint_name + end + + def drop_constraint(constraint_name) + @constraint_drops << constraint_name + end + + def add_column(name, type, **options) + name = name.to_s + type = type.to_sym + @adds << AddColumnDefinition.new(@td.new_column_definition(name, type, **options)) + end + end + + # = Active Record Connection Adapters \Table + # + # Represents an SQL table in an abstract way for updating a table. + # Also see TableDefinition and {connection.create_table}[rdoc-ref:SchemaStatements#create_table] + # + # Available transformations are: + # + # change_table :table do |t| + # t.primary_key + # t.column + # t.index + # t.rename_index + # t.timestamps + # t.change + # t.change_default + # t.change_null + # t.rename + # t.references + # t.belongs_to + # t.check_constraint + # t.string + # t.text + # t.integer + # t.bigint + # t.float + # t.decimal + # t.numeric + # t.datetime + # t.timestamp + # t.time + # t.date + # t.binary + # t.blob + # t.boolean + # t.foreign_key + # t.json + # t.virtual + # t.remove + # t.remove_foreign_key + # t.remove_references + # t.remove_belongs_to + # t.remove_index + # t.remove_check_constraint + # t.remove_timestamps + # end + # + class Table + include ColumnMethods + + attr_reader :name + + def initialize(table_name, base) + @name = table_name + @base = base + end + + # Adds a new column to the named table. + # + # t.column(:name, :string) + # + # See TableDefinition#column for details of the options you can use. + def column(column_name, type, index: nil, **options) + raise_on_if_exist_options(options) + @base.add_column(name, column_name, type, **options) + if index + index_options = index.is_a?(Hash) ? index : {} + index(column_name, **index_options) + end + end + + # Checks to see if a column exists. + # + # t.string(:name) unless t.column_exists?(:name, :string) + # + # See {connection.column_exists?}[rdoc-ref:SchemaStatements#column_exists?] + def column_exists?(column_name, type = nil, **options) + @base.column_exists?(name, column_name, type, **options) + end + + # Adds a new index to the table. +column_name+ can be a single Symbol, or + # an Array of Symbols. + # + # t.index(:name) + # t.index([:branch_id, :party_id], unique: true) + # t.index([:branch_id, :party_id], unique: true, name: 'by_branch_party') + # + # See {connection.add_index}[rdoc-ref:SchemaStatements#add_index] for details of the options you can use. + def index(column_name, **options) + raise_on_if_exist_options(options) + @base.add_index(name, column_name, **options) + end + + # Checks to see if an index exists. + # + # unless t.index_exists?(:branch_id) + # t.index(:branch_id) + # end + # + # See {connection.index_exists?}[rdoc-ref:SchemaStatements#index_exists?] + def index_exists?(column_name, **options) + @base.index_exists?(name, column_name, **options) + end + + # Renames the given index on the table. + # + # t.rename_index(:user_id, :account_id) + # + # See {connection.rename_index}[rdoc-ref:SchemaStatements#rename_index] + def rename_index(index_name, new_index_name) + @base.rename_index(name, index_name, new_index_name) + end + + # Adds timestamps (+created_at+ and +updated_at+) columns to the table. + # + # t.timestamps(null: false) + # + # See {connection.add_timestamps}[rdoc-ref:SchemaStatements#add_timestamps] + def timestamps(**options) + raise_on_if_exist_options(options) + @base.add_timestamps(name, **options) + end + + # Changes the column's definition according to the new options. + # + # t.change(:name, :string, limit: 80) + # t.change(:description, :text) + # + # See TableDefinition#column for details of the options you can use. + def change(column_name, type, **options) + raise_on_if_exist_options(options) + @base.change_column(name, column_name, type, **options) + end + + # Sets a new default value for a column. + # + # t.change_default(:qualification, 'new') + # t.change_default(:authorized, 1) + # t.change_default(:status, from: nil, to: "draft") + # + # See {connection.change_column_default}[rdoc-ref:SchemaStatements#change_column_default] + def change_default(column_name, default_or_changes) + @base.change_column_default(name, column_name, default_or_changes) + end + + # Sets or removes a NOT NULL constraint on a column. + # + # t.change_null(:qualification, true) + # t.change_null(:qualification, false, 0) + # + # See {connection.change_column_null}[rdoc-ref:SchemaStatements#change_column_null] + def change_null(column_name, null, default = nil) + @base.change_column_null(name, column_name, null, default) + end + + # Removes the column(s) from the table definition. + # + # t.remove(:qualification) + # t.remove(:qualification, :experience) + # + # See {connection.remove_columns}[rdoc-ref:SchemaStatements#remove_columns] + def remove(*column_names, **options) + raise_on_if_exist_options(options) + @base.remove_columns(name, *column_names, **options) + end + + # Removes the given index from the table. + # + # t.remove_index(:branch_id) + # t.remove_index(column: [:branch_id, :party_id]) + # t.remove_index(name: :by_branch_party) + # t.remove_index(:branch_id, name: :by_branch_party) + # + # See {connection.remove_index}[rdoc-ref:SchemaStatements#remove_index] + def remove_index(column_name = nil, **options) + raise_on_if_exist_options(options) + @base.remove_index(name, column_name, **options) + end + + # Removes the timestamp columns (+created_at+ and +updated_at+) from the table. + # + # t.remove_timestamps + # + # See {connection.remove_timestamps}[rdoc-ref:SchemaStatements#remove_timestamps] + def remove_timestamps(**options) + @base.remove_timestamps(name, **options) + end + + # Renames a column. + # + # t.rename(:description, :name) + # + # See {connection.rename_column}[rdoc-ref:SchemaStatements#rename_column] + def rename(column_name, new_column_name) + @base.rename_column(name, column_name, new_column_name) + end + + # Adds a reference. + # + # t.references(:user) + # t.belongs_to(:supplier, foreign_key: true) + # + # See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use. + def references(*args, **options) + raise_on_if_exist_options(options) + args.each do |ref_name| + @base.add_reference(name, ref_name, **options) + end + end + alias :belongs_to :references + + # Removes a reference. Optionally removes a +type+ column. + # + # t.remove_references(:user) + # t.remove_belongs_to(:supplier, polymorphic: true) + # + # See {connection.remove_reference}[rdoc-ref:SchemaStatements#remove_reference] + def remove_references(*args, **options) + raise_on_if_exist_options(options) + args.each do |ref_name| + @base.remove_reference(name, ref_name, **options) + end + end + alias :remove_belongs_to :remove_references + + # Adds a foreign key to the table using a supplied table name. + # + # t.foreign_key(:authors) + # t.foreign_key(:authors, column: :author_id, primary_key: "id") + # + # See {connection.add_foreign_key}[rdoc-ref:SchemaStatements#add_foreign_key] + def foreign_key(*args, **options) + raise_on_if_exist_options(options) + @base.add_foreign_key(name, *args, **options) + end + + # Removes the given foreign key from the table. + # + # t.remove_foreign_key(:authors) + # t.remove_foreign_key(column: :author_id) + # + # See {connection.remove_foreign_key}[rdoc-ref:SchemaStatements#remove_foreign_key] + def remove_foreign_key(*args, **options) + raise_on_if_exist_options(options) + @base.remove_foreign_key(name, *args, **options) + end + + # Checks to see if a foreign key exists. + # + # t.foreign_key(:authors) unless t.foreign_key_exists?(:authors) + # + # See {connection.foreign_key_exists?}[rdoc-ref:SchemaStatements#foreign_key_exists?] + def foreign_key_exists?(*args, **options) + @base.foreign_key_exists?(name, *args, **options) + end + + # Adds a check constraint. + # + # t.check_constraint("price > 0", name: "price_check") + # + # See {connection.add_check_constraint}[rdoc-ref:SchemaStatements#add_check_constraint] + def check_constraint(*args, **options) + @base.add_check_constraint(name, *args, **options) + end + + # Removes the given check constraint from the table. + # + # t.remove_check_constraint(name: "price_check") + # + # See {connection.remove_check_constraint}[rdoc-ref:SchemaStatements#remove_check_constraint] + def remove_check_constraint(*args, **options) + @base.remove_check_constraint(name, *args, **options) + end + + # Checks if a check_constraint exists on a table. + # + # unless t.check_constraint_exists?(name: "price_check") + # t.check_constraint("price > 0", name: "price_check") + # end + # + # See {connection.check_constraint_exists?}[rdoc-ref:SchemaStatements#check_constraint_exists?] + def check_constraint_exists?(*args, **options) + @base.check_constraint_exists?(name, *args, **options) + end + + private + def raise_on_if_exist_options(options) + unrecognized_option = options.keys.find do |key| + key == :if_exists || key == :if_not_exists + end + if unrecognized_option + conditional = unrecognized_option == :if_exists ? "if" : "unless" + message = <<~TXT + Option #{unrecognized_option} will be ignored. If you are calling an expression like + `t.column(.., #{unrecognized_option}: true)` from inside a change_table block, try a + conditional clause instead, as in `t.column(..) #{conditional} t.column_exists?(..)` + TXT + raise ArgumentError.new(message) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/schema_dumper.rb new file mode 100644 index 00000000..193e7079 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters # :nodoc: + class SchemaDumper < SchemaDumper # :nodoc: + DEFAULT_DATETIME_PRECISION = 6 # :nodoc: + + def self.create(connection, options) + new(connection, options) + end + + private + def column_spec(column) + [schema_type_with_virtual(column), prepare_column_options(column)] + end + + def column_spec_for_primary_key(column) + spec = {} + spec[:id] = schema_type(column).inspect unless default_primary_key?(column) + spec.merge!(prepare_column_options(column).except!(:null)) + spec[:default] ||= "nil" if explicit_primary_key_default?(column) + spec + end + + def prepare_column_options(column) + spec = {} + spec[:limit] = schema_limit(column) + spec[:precision] = schema_precision(column) + spec[:scale] = schema_scale(column) + spec[:default] = schema_default(column) + spec[:null] = "false" unless column.null + spec[:collation] = schema_collation(column) + spec[:comment] = column.comment.inspect if column.comment.present? + spec.compact! + spec + end + + def default_primary_key?(column) + schema_type(column) == :bigint + end + + def explicit_primary_key_default?(column) + false + end + + def schema_type_with_virtual(column) + if @connection.supports_virtual_columns? && column.virtual? + :virtual + else + schema_type(column) + end + end + + def schema_type(column) + if column.bigint? + :bigint + else + column.type + end + end + + def schema_limit(column) + limit = column.limit unless column.bigint? + limit.inspect if limit && limit != @connection.native_database_types[column.type][:limit] + end + + def schema_precision(column) + if column.type == :datetime + case column.precision + when nil + "nil" + when DEFAULT_DATETIME_PRECISION + nil + else + column.precision.inspect + end + elsif column.precision + column.precision.inspect + end + end + + def schema_scale(column) + column.scale.inspect if column.scale + end + + def schema_default(column) + return unless column.has_default? + type = @connection.lookup_cast_type_from_column(column) + default = type.deserialize(column.default) + if default.nil? + schema_expression(column) + else + type.type_cast_for_schema(default) + end + end + + def schema_expression(column) + "-> { #{column.default_function.inspect} }" if column.default_function + end + + def schema_collation(column) + column.collation.inspect if column.collation + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/schema_statements.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/schema_statements.rb new file mode 100644 index 00000000..a31ddaa0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -0,0 +1,1899 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/access" +require "openssl" + +module ActiveRecord + module ConnectionAdapters # :nodoc: + module SchemaStatements + include ActiveRecord::Migration::JoinTable + + # Returns a hash of mappings from the abstract data types to the native + # database types. See TableDefinition#column for details on the recognized + # abstract data types. + def native_database_types + {} + end + + def table_options(table_name) + nil + end + + # Returns the table comment that's stored in database metadata. + def table_comment(table_name) + nil + end + + # Truncates a table alias according to the limits of the current adapter. + def table_alias_for(table_name) + table_name[0...table_alias_length].tr(".", "_") + end + + # Returns the relation names usable to back Active Record models. + # For most adapters this means all #tables and #views. + def data_sources + query_values(data_source_sql, "SCHEMA") + rescue NotImplementedError + tables | views + end + + # Checks to see if the data source +name+ exists on the database. + # + # data_source_exists?(:ebooks) + # + def data_source_exists?(name) + query_values(data_source_sql(name), "SCHEMA").any? if name.present? + rescue NotImplementedError + data_sources.include?(name.to_s) + end + + # Returns an array of table names defined in the database. + def tables + query_values(data_source_sql(type: "BASE TABLE"), "SCHEMA") + end + + # Checks to see if the table +table_name+ exists on the database. + # + # table_exists?(:developers) + # + def table_exists?(table_name) + query_values(data_source_sql(table_name, type: "BASE TABLE"), "SCHEMA").any? if table_name.present? + rescue NotImplementedError + tables.include?(table_name.to_s) + end + + # Returns an array of view names defined in the database. + def views + query_values(data_source_sql(type: "VIEW"), "SCHEMA") + end + + # Checks to see if the view +view_name+ exists on the database. + # + # view_exists?(:ebooks) + # + def view_exists?(view_name) + query_values(data_source_sql(view_name, type: "VIEW"), "SCHEMA").any? if view_name.present? + rescue NotImplementedError + views.include?(view_name.to_s) + end + + # Returns an array of indexes for the given table. + def indexes(table_name) + raise NotImplementedError, "#indexes is not implemented" + end + + # Checks to see if an index exists on a table for a given index definition. + # + # # Check an index exists + # index_exists?(:suppliers, :company_id) + # + # # Check an index on multiple columns exists + # index_exists?(:suppliers, [:company_id, :company_type]) + # + # # Check a unique index exists + # index_exists?(:suppliers, :company_id, unique: true) + # + # # Check an index with a custom name exists + # index_exists?(:suppliers, :company_id, name: "idx_company_id") + # + # # Check a valid index exists (PostgreSQL only) + # index_exists?(:suppliers, :company_id, valid: true) + # + def index_exists?(table_name, column_name, **options) + indexes(table_name).any? { |i| i.defined_for?(column_name, **options) } + end + + # Returns an array of +Column+ objects for the table specified by +table_name+. + def columns(table_name) + table_name = table_name.to_s + definitions = column_definitions(table_name) + definitions.map do |field| + new_column_from_field(table_name, field, definitions) + end + end + + # Checks to see if a column exists in a given table. + # + # # Check a column exists + # column_exists?(:suppliers, :name) + # + # # Check a column exists of a particular type + # # + # # This works for standard non-casted types (eg. string) but is unreliable + # # for types that may get cast to something else (eg. char, bigint). + # column_exists?(:suppliers, :name, :string) + # + # # Check a column exists with a specific definition + # column_exists?(:suppliers, :name, :string, limit: 100) + # column_exists?(:suppliers, :name, :string, default: 'default') + # column_exists?(:suppliers, :name, :string, null: false) + # column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2) + # + def column_exists?(table_name, column_name, type = nil, **options) + column_name = column_name.to_s + checks = [] + checks << lambda { |c| c.name == column_name } + checks << lambda { |c| c.type == type.to_sym rescue nil } if type + column_options_keys.each do |attr| + checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr) + end + + columns(table_name).any? { |c| checks.all? { |check| check[c] } } + end + + # Returns just a table's primary key + def primary_key(table_name) + pk = primary_keys(table_name) + pk = pk.first unless pk.size > 1 + pk + end + + # Creates a new table with the name +table_name+. +table_name+ may either + # be a String or a Symbol. + # + # There are two ways to work with #create_table. You can use the block + # form or the regular form, like this: + # + # === Block form + # + # # create_table() passes a TableDefinition object to the block. + # # This form will not only create the table, but also columns for the + # # table. + # + # create_table(:suppliers) do |t| + # t.column :name, :string, limit: 60 + # # Other fields here + # end + # + # === Block form, with shorthand + # + # # You can also use the column types as method calls, rather than calling the column method. + # create_table(:suppliers) do |t| + # t.string :name, limit: 60 + # # Other fields here + # end + # + # === Regular form + # + # # Creates a table called 'suppliers' with no columns. + # create_table(:suppliers) + # # Add a column to 'suppliers'. + # add_column(:suppliers, :name, :string, {limit: 60}) + # + # The +options+ hash can include the following keys: + # [:id] + # Whether to automatically add a primary key column. Defaults to true. + # Join tables for {ActiveRecord::Base.has_and_belongs_to_many}[rdoc-ref:Associations::ClassMethods#has_and_belongs_to_many] should set it to false. + # + # A Symbol can be used to specify the type of the generated primary key column. + # [:primary_key] + # The name of the primary key, if one is to be added automatically. + # Defaults to +id+. If :id is false, then this option is ignored. + # + # If an array is passed, a composite primary key will be created. + # + # Note that Active Record models will automatically detect their + # primary key. This can be avoided by using + # {self.primary_key=}[rdoc-ref:AttributeMethods::PrimaryKey::ClassMethods#primary_key=] on the model + # to define the key explicitly. + # + # [:options] + # Any extra options you want appended to the table definition. + # [:temporary] + # Make a temporary table. + # [:force] + # Set to true to drop the table before creating it. + # Set to +:cascade+ to drop dependent objects as well. + # Defaults to false. + # [:if_not_exists] + # Set to true to avoid raising an error when the table already exists. + # Defaults to false. + # [:as] + # SQL to use to generate the table. When this option is used, the block is + # ignored, as are the :id and :primary_key options. + # + # ====== Add a backend specific option to the generated SQL (MySQL) + # + # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4') + # + # generates: + # + # CREATE TABLE suppliers ( + # id bigint auto_increment PRIMARY KEY + # ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + # + # ====== Rename the primary key column + # + # create_table(:objects, primary_key: 'guid') do |t| + # t.column :name, :string, limit: 80 + # end + # + # generates: + # + # CREATE TABLE objects ( + # guid bigint auto_increment PRIMARY KEY, + # name varchar(80) + # ) + # + # ====== Change the primary key column type + # + # create_table(:tags, id: :string) do |t| + # t.column :label, :string + # end + # + # generates: + # + # CREATE TABLE tags ( + # id varchar PRIMARY KEY, + # label varchar + # ) + # + # ====== Create a composite primary key + # + # create_table(:orders, primary_key: [:product_id, :client_id]) do |t| + # t.belongs_to :product + # t.belongs_to :client + # end + # + # generates: + # + # CREATE TABLE orders ( + # product_id bigint NOT NULL, + # client_id bigint NOT NULL + # ); + # + # ALTER TABLE ONLY "orders" + # ADD CONSTRAINT orders_pkey PRIMARY KEY (product_id, client_id); + # + # ====== Do not add a primary key column + # + # create_table(:categories_suppliers, id: false) do |t| + # t.column :category_id, :bigint + # t.column :supplier_id, :bigint + # end + # + # generates: + # + # CREATE TABLE categories_suppliers ( + # category_id bigint, + # supplier_id bigint + # ) + # + # ====== Create a temporary table based on a query + # + # create_table(:long_query, temporary: true, + # as: "SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id") + # + # generates: + # + # CREATE TEMPORARY TABLE long_query AS + # SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id + # + # See also TableDefinition#column for details on how to create columns. + def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options, &block) + validate_create_table_options!(options) + validate_table_length!(table_name) unless options[:_uses_legacy_table_name] + + if force && options.key?(:if_not_exists) + raise ArgumentError, "Options `:force` and `:if_not_exists` cannot be used simultaneously." + end + + td = build_create_table_definition(table_name, id: id, primary_key: primary_key, force: force, **options, &block) + + if force + drop_table(table_name, force: force, if_exists: true) + else + schema_cache.clear_data_source_cache!(table_name.to_s) + end + + result = execute schema_creation.accept(td) + + unless supports_indexes_in_create? + td.indexes.each do |column_name, index_options| + add_index(table_name, column_name, **index_options, if_not_exists: td.if_not_exists) + end + end + + if supports_comments? && !supports_comments_in_create? + if table_comment = td.comment.presence + change_table_comment(table_name, table_comment) + end + + td.columns.each do |column| + change_column_comment(table_name, column.name, column.comment) if column.comment.present? + end + end + + result + end + + # Returns a TableDefinition object containing information about the table that would be created + # if the same arguments were passed to #create_table. See #create_table for information about + # passing a +table_name+, and other additional options that can be passed. + def build_create_table_definition(table_name, id: :primary_key, primary_key: nil, force: nil, **options) + table_definition = create_table_definition(table_name, **options.extract!(*valid_table_definition_options, :_skip_validate_options)) + table_definition.set_primary_key(table_name, id, primary_key, **options.extract!(*valid_primary_key_options, :_skip_validate_options)) + + yield table_definition if block_given? + + table_definition + end + + # Creates a new join table with the name created using the lexical order of the first two + # arguments. These arguments can be a String or a Symbol. + # + # # Creates a table called 'assemblies_parts' with no id. + # create_join_table(:assemblies, :parts) + # + # # Creates a table called 'paper_boxes_papers' with no id. + # create_join_table('papers', 'paper_boxes') + # + # A duplicate prefix is combined into a single prefix. This is useful for + # namespaced models like Music::Artist and Music::Record: + # + # # Creates a table called 'music_artists_records' with no id. + # create_join_table('music_artists', 'music_records') + # + # You can pass an +options+ hash which can include the following keys: + # [:table_name] + # Sets the table name, overriding the default. + # [:column_options] + # Any extra options you want appended to the columns definition. + # [:options] + # Any extra options you want appended to the table definition. + # [:temporary] + # Make a temporary table. + # [:force] + # Set to true to drop the table before creating it. + # Defaults to false. + # + # Note that #create_join_table does not create any indices by default; you can use + # its block form to do so yourself: + # + # create_join_table :products, :categories do |t| + # t.index :product_id + # t.index :category_id + # end + # + # ====== Add a backend specific option to the generated SQL (MySQL) + # + # create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8') + # + # generates: + # + # CREATE TABLE assemblies_parts ( + # assembly_id bigint NOT NULL, + # part_id bigint NOT NULL, + # ) ENGINE=InnoDB DEFAULT CHARSET=utf8 + # + def create_join_table(table_1, table_2, column_options: {}, **options) + join_table_name = find_join_table_name(table_1, table_2, options) + + column_options.reverse_merge!(null: false, index: false) + + t1_ref, t2_ref = [table_1, table_2].map { |t| reference_name_for_table(t) } + + create_table(join_table_name, **options.merge!(id: false)) do |td| + td.references t1_ref, **column_options + td.references t2_ref, **column_options + yield td if block_given? + end + end + + # Builds a TableDefinition object for a join table. + # + # This definition object contains information about the table that would be created + # if the same arguments were passed to #create_join_table. See #create_join_table for + # information about what arguments should be passed. + def build_create_join_table_definition(table_1, table_2, column_options: {}, **options) # :nodoc: + join_table_name = find_join_table_name(table_1, table_2, options) + column_options.reverse_merge!(null: false, index: false) + + t1_ref, t2_ref = [table_1, table_2].map { |t| reference_name_for_table(t) } + + build_create_table_definition(join_table_name, **options.merge!(id: false)) do |td| + td.references t1_ref, **column_options + td.references t2_ref, **column_options + yield td if block_given? + end + end + + # Drops the join table specified by the given arguments. + # See #create_join_table and #drop_table for details. + # + # Although this command ignores the block if one is given, it can be helpful + # to provide one in a migration's +change+ method so it can be reverted. + # In that case, the block will be used by #create_join_table. + def drop_join_table(table_1, table_2, **options) + join_table_name = find_join_table_name(table_1, table_2, options) + drop_table(join_table_name, **options) + end + + # A block for changing columns in +table+. + # + # # change_table() yields a Table instance + # change_table(:suppliers) do |t| + # t.column :name, :string, limit: 60 + # # Other column alterations here + # end + # + # The +options+ hash can include the following keys: + # [:bulk] + # Set this to true to make this a bulk alter query, such as + # + # ALTER TABLE `users` ADD COLUMN age INT, ADD COLUMN birthdate DATETIME ... + # + # Defaults to false. + # + # Only supported on the MySQL and PostgreSQL adapter, ignored elsewhere. + # + # ====== Add a column + # + # change_table(:suppliers) do |t| + # t.column :name, :string, limit: 60 + # end + # + # ====== Change type of a column + # + # change_table(:suppliers) do |t| + # t.change :metadata, :json + # end + # + # ====== Add 2 integer columns + # + # change_table(:suppliers) do |t| + # t.integer :width, :height, null: false, default: 0 + # end + # + # ====== Add created_at/updated_at columns + # + # change_table(:suppliers) do |t| + # t.timestamps + # end + # + # ====== Add a foreign key column + # + # change_table(:suppliers) do |t| + # t.references :company + # end + # + # Creates a company_id(bigint) column. + # + # ====== Add a polymorphic foreign key column + # + # change_table(:suppliers) do |t| + # t.belongs_to :company, polymorphic: true + # end + # + # Creates company_type(varchar) and company_id(bigint) columns. + # + # ====== Remove a column + # + # change_table(:suppliers) do |t| + # t.remove :company + # end + # + # ====== Remove several columns + # + # change_table(:suppliers) do |t| + # t.remove :company_id + # t.remove :width, :height + # end + # + # ====== Remove an index + # + # change_table(:suppliers) do |t| + # t.remove_index :company_id + # end + # + # See also Table for details on all of the various column transformations. + def change_table(table_name, base = self, **options) + if supports_bulk_alter? && options[:bulk] + recorder = ActiveRecord::Migration::CommandRecorder.new(self) + yield update_table_definition(table_name, recorder) + bulk_change_table(table_name, recorder.commands) + else + yield update_table_definition(table_name, base) + end + end + + # Renames a table. + # + # rename_table('octopuses', 'octopi') + # + def rename_table(table_name, new_name, **) + raise NotImplementedError, "rename_table is not implemented" + end + + # Drops a table or tables from the database. + # + # [:force] + # Set to +:cascade+ to drop dependent objects as well. + # Defaults to false. + # [:if_exists] + # Set to +true+ to only drop the table if it exists. + # Defaults to false. + # + # Although this command ignores most +options+ and the block if one is given, + # it can be helpful to provide these in a migration's +change+ method so it can be reverted. + # In that case, +options+ and the block will be used by #create_table except if you provide more than one table which is not supported. + def drop_table(*table_names, **options) + table_names.each do |table_name| + schema_cache.clear_data_source_cache!(table_name.to_s) + execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}" + end + end + + # Add a new +type+ column named +column_name+ to +table_name+. + # + # See {ActiveRecord::ConnectionAdapters::TableDefinition.column}[rdoc-ref:ActiveRecord::ConnectionAdapters::TableDefinition#column]. + # + # The +type+ parameter is normally one of the migrations native types, + # which is one of the following: + # :primary_key, :string, :text, + # :integer, :bigint, :float, :decimal, :numeric, + # :datetime, :time, :date, + # :binary, :blob, :boolean. + # + # You may use a type not in this list as long as it is supported by your + # database (for example, "polygon" in MySQL), but this will not be database + # agnostic and should usually be avoided. + # + # Available options are (none of these exists by default): + # * :comment - + # Specifies the comment for the column. This option is ignored by some backends. + # * :collation - + # Specifies the collation for a :string or :text column. + # If not specified, the column will have the same collation as the table. + # * :default - + # The column's default value. Use +nil+ for +NULL+. + # * :limit - + # Requests a maximum column length. This is the number of characters for a :string column + # and number of bytes for :text, :binary, :blob, and :integer columns. + # This option is ignored by some backends. + # * :null - + # Allows or disallows +NULL+ values in the column. + # * :precision - + # Specifies the precision for the :decimal, :numeric, + # :datetime, and :time columns. + # * :scale - + # Specifies the scale for the :decimal and :numeric columns. + # * :if_not_exists - + # Specifies if the column already exists to not try to re-add it. This will avoid + # duplicate column errors. + # + # Note: The precision is the total number of significant digits, + # and the scale is the number of digits that can be stored following + # the decimal point. For example, the number 123.45 has a precision of 5 + # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can + # range from -999.99 to 999.99. + # + # Please be aware of different RDBMS implementations behavior with + # :decimal columns: + # * The SQL standard says the default scale should be 0, :scale <= + # :precision, and makes no comments about the requirements of + # :precision. + # * MySQL: :precision [1..65], :scale [0..30]. + # Default is (10,0). + # * PostgreSQL: :precision [1..infinity], + # :scale [0..infinity]. No default. + # * SQLite3: No restrictions on :precision and :scale, + # but the maximum supported :precision is 16. No default. + # * Oracle: :precision [1..38], :scale [-84..127]. + # Default is (38,0). + # * SqlServer: :precision [1..38], :scale [0..38]. + # Default (38,0). + # + # == Examples + # + # add_column(:users, :picture, :binary, limit: 2.megabytes) + # # ALTER TABLE "users" ADD "picture" blob(2097152) + # + # add_column(:articles, :status, :string, limit: 20, default: 'draft', null: false) + # # ALTER TABLE "articles" ADD "status" varchar(20) DEFAULT 'draft' NOT NULL + # + # add_column(:answers, :bill_gates_money, :decimal, precision: 15, scale: 2) + # # ALTER TABLE "answers" ADD "bill_gates_money" decimal(15,2) + # + # add_column(:measurements, :sensor_reading, :decimal, precision: 30, scale: 20) + # # ALTER TABLE "measurements" ADD "sensor_reading" decimal(30,20) + # + # # While :scale defaults to zero on most databases, it + # # probably wouldn't hurt to include it. + # add_column(:measurements, :huge_integer, :decimal, precision: 30) + # # ALTER TABLE "measurements" ADD "huge_integer" decimal(30) + # + # # Defines a column that stores an array of a type. + # add_column(:users, :skills, :text, array: true) + # # ALTER TABLE "users" ADD "skills" text[] + # + # # Defines a column with a database-specific type. + # add_column(:shapes, :triangle, 'polygon') + # # ALTER TABLE "shapes" ADD "triangle" polygon + # + # # Ignores the method call if the column exists + # add_column(:shapes, :triangle, 'polygon', if_not_exists: true) + def add_column(table_name, column_name, type, **options) + add_column_def = build_add_column_definition(table_name, column_name, type, **options) + return unless add_column_def + + execute schema_creation.accept(add_column_def) + end + + def add_columns(table_name, *column_names, type:, **options) # :nodoc: + column_names.each do |column_name| + add_column(table_name, column_name, type, **options) + end + end + + # Builds an AlterTable object for adding a column to a table. + # + # This definition object contains information about the column that would be created + # if the same arguments were passed to #add_column. See #add_column for information about + # passing a +table_name+, +column_name+, +type+ and other options that can be passed. + def build_add_column_definition(table_name, column_name, type, **options) # :nodoc: + return if options[:if_not_exists] == true && column_exists?(table_name, column_name) + + if supports_datetime_with_precision? + if type == :datetime && !options.key?(:precision) + options[:precision] = 6 + end + end + + alter_table = create_alter_table(table_name) + alter_table.add_column(column_name, type, **options) + alter_table + end + + # Removes the given columns from the table definition. + # + # remove_columns(:suppliers, :qualification, :experience) + # + # +type+ and other column options can be passed to make migration reversible. + # + # remove_columns(:suppliers, :qualification, :experience, type: :string, null: false) + def remove_columns(table_name, *column_names, type: nil, **options) + if column_names.empty? + raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") + end + + remove_column_fragments = remove_columns_for_alter(table_name, *column_names, type: type, **options) + execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_fragments.join(', ')}" + end + + # Removes the column from the table definition. + # + # remove_column(:suppliers, :qualification) + # + # The +type+ and +options+ parameters will be ignored if present. It can be helpful + # to provide these in a migration's +change+ method so it can be reverted. + # In that case, +type+ and +options+ will be used by #add_column. + # Depending on the database you're using, indexes using this column may be + # automatically removed or modified to remove this column from the index. + # + # If the options provided include an +if_exists+ key, it will be used to check if the + # column does not exist. This will silently ignore the migration rather than raising + # if the column was already removed. + # + # remove_column(:suppliers, :qualification, if_exists: true) + def remove_column(table_name, column_name, type = nil, **options) + return if options[:if_exists] == true && !column_exists?(table_name, column_name) + + execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, **options)}" + end + + # Changes the column's definition according to the new options. + # See TableDefinition#column for details of the options you can use. + # + # change_column(:suppliers, :name, :string, limit: 80) + # change_column(:accounts, :description, :text) + # + def change_column(table_name, column_name, type, **options) + raise NotImplementedError, "change_column is not implemented" + end + + # Sets a new default value for a column: + # + # change_column_default(:suppliers, :qualification, 'new') + # change_column_default(:accounts, :authorized, 1) + # + # Setting the default to +nil+ effectively drops the default: + # + # change_column_default(:users, :email, nil) + # + # Passing a hash containing +:from+ and +:to+ will make this change + # reversible in migration: + # + # change_column_default(:posts, :state, from: nil, to: "draft") + # + def change_column_default(table_name, column_name, default_or_changes) + raise NotImplementedError, "change_column_default is not implemented" + end + + # Builds a ChangeColumnDefaultDefinition object. + # + # This definition object contains information about the column change that would occur + # if the same arguments were passed to #change_column_default. See #change_column_default for + # information about passing a +table_name+, +column_name+, +type+ and other options that can be passed. + def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc: + raise NotImplementedError, "build_change_column_default_definition is not implemented" + end + + # Sets or removes a NOT NULL constraint on a column. The +null+ flag + # indicates whether the value can be +NULL+. For example + # + # change_column_null(:users, :nickname, false) + # + # says nicknames cannot be +NULL+ (adds the constraint), whereas + # + # change_column_null(:users, :nickname, true) + # + # allows them to be +NULL+ (drops the constraint). + # + # The method accepts an optional fourth argument to replace existing + # NULLs with some other value. Use that one when enabling the + # constraint if needed, since otherwise those rows would not be valid. + # + # Please note the fourth argument does not set a column's default. + def change_column_null(table_name, column_name, null, default = nil) + raise NotImplementedError, "change_column_null is not implemented" + end + + # Renames a column. + # + # rename_column(:suppliers, :description, :name) + # + def rename_column(table_name, column_name, new_column_name) + raise NotImplementedError, "rename_column is not implemented" + end + + # Adds a new index to the table. +column_name+ can be a single Symbol, or + # an Array of Symbols. + # + # The index will be named after the table and the column name(s), unless + # you pass :name as an option. + # + # ====== Creating a simple index + # + # add_index(:suppliers, :name) + # + # generates: + # + # CREATE INDEX index_suppliers_on_name ON suppliers(name) + # + # ====== Creating a index which already exists + # + # add_index(:suppliers, :name, if_not_exists: true) + # + # generates: + # + # CREATE INDEX IF NOT EXISTS index_suppliers_on_name ON suppliers(name) + # + # Note: Not supported by MySQL. + # + # ====== Creating a unique index + # + # add_index(:accounts, [:branch_id, :party_id], unique: true) + # + # generates: + # + # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) + # + # ====== Creating a named index + # + # add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party') + # + # generates: + # + # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id) + # + # ====== Creating an index with specific key length + # + # add_index(:accounts, :name, name: 'by_name', length: 10) + # + # generates: + # + # CREATE INDEX by_name ON accounts(name(10)) + # + # ====== Creating an index with specific key lengths for multiple keys + # + # add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15}) + # + # generates: + # + # CREATE INDEX by_name_surname ON accounts(name(10), surname(15)) + # + # Note: only supported by MySQL + # + # ====== Creating an index with a sort order (desc or asc, asc is the default) + # + # add_index(:accounts, [:branch_id, :party_id, :surname], name: 'by_branch_desc_party', order: {branch_id: :desc, party_id: :asc}) + # + # generates: + # + # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname) + # + # Note: MySQL only supports index order from 8.0.1 onwards (earlier versions accepted the syntax but ignored it). + # + # ====== Creating a partial index + # + # add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active") + # + # generates: + # + # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active + # + # Note: Partial indexes are only supported for PostgreSQL and SQLite. + # + # ====== Creating an index that includes additional columns + # + # add_index(:accounts, :branch_id, include: :party_id) + # + # generates: + # + # CREATE INDEX index_accounts_on_branch_id ON accounts USING btree(branch_id) INCLUDE (party_id) + # + # Note: only supported by PostgreSQL. + # + # ====== Creating an index where NULLs are treated equally + # + # add_index(:people, :last_name, nulls_not_distinct: true) + # + # generates: + # + # CREATE INDEX index_people_on_last_name ON people (last_name) NULLS NOT DISTINCT + # + # Note: only supported by PostgreSQL version 15.0.0 and greater. + # + # ====== Creating an index with a specific method + # + # add_index(:developers, :name, using: 'btree') + # + # generates: + # + # CREATE INDEX index_developers_on_name ON developers USING btree (name) -- PostgreSQL + # CREATE INDEX index_developers_on_name USING btree ON developers (name) -- MySQL + # + # Note: only supported by PostgreSQL and MySQL + # + # ====== Creating an index with a specific operator class + # + # add_index(:developers, :name, using: 'gist', opclass: :gist_trgm_ops) + # # CREATE INDEX developers_on_name ON developers USING gist (name gist_trgm_ops) -- PostgreSQL + # + # add_index(:developers, [:name, :city], using: 'gist', opclass: { city: :gist_trgm_ops }) + # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL + # + # add_index(:developers, [:name, :city], using: 'gist', opclass: :gist_trgm_ops) + # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL + # + # Note: only supported by PostgreSQL + # + # ====== Creating an index with a specific type + # + # add_index(:developers, :name, type: :fulltext) + # + # generates: + # + # CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL + # + # Note: only supported by MySQL. + # + # ====== Creating an index with a specific algorithm + # + # add_index(:developers, :name, algorithm: :concurrently) + # # CREATE INDEX CONCURRENTLY developers_on_name on developers (name) -- PostgreSQL + # + # add_index(:developers, :name, algorithm: :inplace) + # # CREATE INDEX `index_developers_on_name` ON `developers` (`name`) ALGORITHM = INPLACE -- MySQL + # + # Note: only supported by PostgreSQL and MySQL. + # + # Concurrently adding an index is not supported in a transaction. + # + # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration]. + def add_index(table_name, column_name, **options) + create_index = build_create_index_definition(table_name, column_name, **options) + execute schema_creation.accept(create_index) + end + + # Builds a CreateIndexDefinition object. + # + # This definition object contains information about the index that would be created + # if the same arguments were passed to #add_index. See #add_index for information about + # passing a +table_name+, +column_name+, and other additional options that can be passed. + def build_create_index_definition(table_name, column_name, **options) # :nodoc: + index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options) + CreateIndexDefinition.new(index, algorithm, if_not_exists) + end + + # Removes the given index from the table. + # + # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists. + # + # remove_index :accounts, :branch_id + # + # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists. + # + # remove_index :accounts, column: :branch_id + # + # Removes the index on +branch_id+ and +party_id+ in the +accounts+ table if exactly one such index exists. + # + # remove_index :accounts, column: [:branch_id, :party_id] + # + # Removes the index named +by_branch_party+ in the +accounts+ table. + # + # remove_index :accounts, name: :by_branch_party + # + # Removes the index on +branch_id+ named +by_branch_party+ in the +accounts+ table. + # + # remove_index :accounts, :branch_id, name: :by_branch_party + # + # Checks if the index exists before trying to remove it. Will silently ignore indexes that + # don't exist. + # + # remove_index :accounts, if_exists: true + # + # Removes the index named +by_branch_party+ in the +accounts+ table +concurrently+. + # + # remove_index :accounts, name: :by_branch_party, algorithm: :concurrently + # + # Note: only supported by PostgreSQL. + # + # Concurrently removing an index is not supported in a transaction. + # + # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration]. + def remove_index(table_name, column_name = nil, **options) + return if options[:if_exists] && !index_exists?(table_name, column_name, **options) + + index_name = index_name_for_remove(table_name, column_name, options) + + execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}" + end + + # Renames an index. + # + # Rename the +index_people_on_last_name+ index to +index_users_on_last_name+: + # + # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name' + # + def rename_index(table_name, old_name, new_name) + old_name = old_name.to_s + new_name = new_name.to_s + validate_index_length!(table_name, new_name) + + # this is a naive implementation; some DBs may support this more efficiently (PostgreSQL, for instance) + old_index_def = indexes(table_name).detect { |i| i.name == old_name } + return unless old_index_def + add_index(table_name, old_index_def.columns, name: new_name, unique: old_index_def.unique) + remove_index(table_name, name: old_name) + end + + def index_name(table_name, options) # :nodoc: + if Hash === options + if options[:column] + if options[:_uses_legacy_index_name] + "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}" + else + generate_index_name(table_name, options[:column]) + end + elsif options[:name] + options[:name] + else + raise ArgumentError, "You must specify the index name" + end + else + index_name(table_name, index_name_options(options)) + end + end + + # Verifies the existence of an index with a given name. + def index_name_exists?(table_name, index_name) + index_name = index_name.to_s + indexes(table_name).detect { |i| i.name == index_name } + end + + # Adds a reference. The reference column is a bigint by default, + # the :type option can be used to specify a different type. + # Optionally adds a +_type+ column, if :polymorphic option is provided. + # + # The +options+ hash can include the following keys: + # [:type] + # The reference column type. Defaults to +:bigint+. + # [:index] + # Add an appropriate index. Defaults to true. + # See #add_index for usage of this option. + # [:foreign_key] + # Add an appropriate foreign key constraint. Defaults to false, pass true + # to add. In case the join table can't be inferred from the association + # pass :to_table with the appropriate table name. + # [:polymorphic] + # Whether an additional +_type+ column should be added. Defaults to false. + # [:null] + # Whether the column allows nulls. Defaults to true. + # + # ====== Create a user_id bigint column without an index + # + # add_reference(:products, :user, index: false) + # + # ====== Create a user_id string column + # + # add_reference(:products, :user, type: :string) + # + # ====== Create supplier_id, supplier_type columns + # + # add_reference(:products, :supplier, polymorphic: true) + # + # ====== Create a supplier_id column with a unique index + # + # add_reference(:products, :supplier, index: { unique: true }) + # + # ====== Create a supplier_id column with a named index + # + # add_reference(:products, :supplier, index: { name: "my_supplier_index" }) + # + # ====== Create a supplier_id column and appropriate foreign key + # + # add_reference(:products, :supplier, foreign_key: true) + # + # ====== Create a supplier_id column and a foreign key to the firms table + # + # add_reference(:products, :supplier, foreign_key: { to_table: :firms }) + # + def add_reference(table_name, ref_name, **options) + ReferenceDefinition.new(ref_name, **options).add(table_name, self) + end + alias :add_belongs_to :add_reference + + # Removes the reference(s). Also removes a +type+ column if one exists. + # + # ====== Remove the reference + # + # remove_reference(:products, :user, index: false) + # + # ====== Remove polymorphic reference + # + # remove_reference(:products, :supplier, polymorphic: true) + # + # ====== Remove the reference with a foreign key + # + # remove_reference(:products, :user, foreign_key: true) + # + def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options) + conditional_options = options.slice(:if_exists, :if_not_exists) + + if foreign_key + reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name + if foreign_key.is_a?(Hash) + foreign_key_options = foreign_key.merge(conditional_options) + else + foreign_key_options = { to_table: reference_name, **conditional_options } + end + foreign_key_options[:column] ||= "#{ref_name}_id" + remove_foreign_key(table_name, **foreign_key_options) + end + + remove_column(table_name, "#{ref_name}_id", **conditional_options) + remove_column(table_name, "#{ref_name}_type", **conditional_options) if polymorphic + end + alias :remove_belongs_to :remove_reference + + # Returns an array of foreign keys for the given table. + # The foreign keys are represented as ForeignKeyDefinition objects. + def foreign_keys(table_name) + raise NotImplementedError, "foreign_keys is not implemented" + end + + # Adds a new foreign key. +from_table+ is the table with the key column, + # +to_table+ contains the referenced primary key. + # + # The foreign key will be named after the following pattern: fk_rails_. + # +identifier+ is a 10 character long string which is deterministically generated from the + # +from_table+ and +column+. A custom name can be specified with the :name option. + # + # ====== Creating a simple foreign key + # + # add_foreign_key :articles, :authors + # + # generates: + # + # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id") + # + # ====== Creating a foreign key, ignoring method call if the foreign key exists + # + # add_foreign_key(:articles, :authors, if_not_exists: true) + # + # ====== Creating a foreign key on a specific column + # + # add_foreign_key :articles, :users, column: :author_id, primary_key: "lng_id" + # + # generates: + # + # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id") + # + # ====== Creating a composite foreign key + # + # Assuming "carts" table has "(shop_id, user_id)" as a primary key. + # + # add_foreign_key :orders, :carts, primary_key: [:shop_id, :user_id] + # + # generates: + # + # ALTER TABLE "orders" ADD CONSTRAINT fk_rails_6f5e4cb3a4 FOREIGN KEY ("cart_shop_id", "cart_user_id") REFERENCES "carts" ("shop_id", "user_id") + # + # ====== Creating a cascading foreign key + # + # add_foreign_key :articles, :authors, on_delete: :cascade + # + # generates: + # + # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE + # + # The +options+ hash can include the following keys: + # [:column] + # The foreign key column name on +from_table+. Defaults to to_table.singularize + "_id". + # Pass an array to create a composite foreign key. + # [:primary_key] + # The primary key column name on +to_table+. Defaults to +id+. + # Pass an array to create a composite foreign key. + # [:name] + # The constraint name. Defaults to fk_rails_. + # [:on_delete] + # Action that happens ON DELETE. Valid values are +:nullify+, +:cascade+, and +:restrict+ + # [:on_update] + # Action that happens ON UPDATE. Valid values are +:nullify+, +:cascade+, and +:restrict+ + # [:if_not_exists] + # Specifies if the foreign key already exists to not try to re-add it. This will avoid + # duplicate column errors. + # [:validate] + # (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+. + # [:deferrable] + # (PostgreSQL only) Specify whether or not the foreign key should be deferrable. Valid values are booleans or + # +:deferred+ or +:immediate+ to specify the default behavior. Defaults to +false+. + def add_foreign_key(from_table, to_table, **options) + return unless use_foreign_keys? + return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table, **options.slice(:column)) + + options = foreign_key_options(from_table, to_table, options) + at = create_alter_table from_table + at.add_foreign_key to_table, options + + execute schema_creation.accept(at) + end + + # Removes the given foreign key from the table. Any option parameters provided + # will be used to re-add the foreign key in case of a migration rollback. + # It is recommended that you provide any options used when creating the foreign + # key so that the migration can be reverted properly. + # + # Removes the foreign key on +accounts.branch_id+. + # + # remove_foreign_key :accounts, :branches + # + # Removes the foreign key on +accounts.owner_id+. + # + # remove_foreign_key :accounts, column: :owner_id + # + # Removes the foreign key on +accounts.owner_id+. + # + # remove_foreign_key :accounts, to_table: :owners + # + # Removes the foreign key named +special_fk_name+ on the +accounts+ table. + # + # remove_foreign_key :accounts, name: :special_fk_name + # + # Checks if the foreign key exists before trying to remove it. Will silently ignore indexes that + # don't exist. + # + # remove_foreign_key :accounts, :branches, if_exists: true + # + # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key + # with an addition of + # [:to_table] + # The name of the table that contains the referenced primary key. + def remove_foreign_key(from_table, to_table = nil, **options) + return unless use_foreign_keys? + return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table) + + fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name + + at = create_alter_table from_table + at.drop_foreign_key fk_name_to_delete + + execute schema_creation.accept(at) + end + + # Checks to see if a foreign key exists on a table for a given foreign key definition. + # + # # Checks to see if a foreign key exists. + # foreign_key_exists?(:accounts, :branches) + # + # # Checks to see if a foreign key on a specified column exists. + # foreign_key_exists?(:accounts, column: :owner_id) + # + # # Checks to see if a foreign key with a custom name exists. + # foreign_key_exists?(:accounts, name: "special_fk_name") + # + def foreign_key_exists?(from_table, to_table = nil, **options) + foreign_key_for(from_table, to_table: to_table, **options).present? + end + + def foreign_key_column_for(table_name, column_name) # :nodoc: + name = strip_table_name_prefix_and_suffix(table_name) + "#{name.singularize}_#{column_name}" + end + + def foreign_key_options(from_table, to_table, options) # :nodoc: + options = options.dup + + if options[:primary_key].is_a?(Array) + options[:column] ||= options[:primary_key].map do |pk_column| + foreign_key_column_for(to_table, pk_column) + end + else + options[:column] ||= foreign_key_column_for(to_table, "id") + end + + options[:name] ||= foreign_key_name(from_table, options) + + if options[:column].is_a?(Array) || options[:primary_key].is_a?(Array) + if Array(options[:primary_key]).size != Array(options[:column]).size + raise ArgumentError, <<~MSG.squish + For composite primary keys, specify :column and :primary_key, where + :column must reference all the :primary_key columns from #{to_table.inspect} + MSG + end + end + + options + end + + # Returns an array of check constraints for the given table. + # The check constraints are represented as CheckConstraintDefinition objects. + def check_constraints(table_name) + raise NotImplementedError + end + + # Adds a new check constraint to the table. +expression+ is a String + # representation of verifiable boolean condition. + # + # add_check_constraint :products, "price > 0", name: "price_check" + # + # generates: + # + # ALTER TABLE "products" ADD CONSTRAINT price_check CHECK (price > 0) + # + # The +options+ hash can include the following keys: + # [:name] + # The constraint name. Defaults to chk_rails_. + # [:if_not_exists] + # Silently ignore if the constraint already exists, rather than raise an error. + # [:validate] + # (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+. + def add_check_constraint(table_name, expression, if_not_exists: false, **options) + return unless supports_check_constraints? + + options = check_constraint_options(table_name, expression, options) + return if if_not_exists && check_constraint_exists?(table_name, **options) + + at = create_alter_table(table_name) + at.add_check_constraint(expression, options) + + execute schema_creation.accept(at) + end + + def check_constraint_options(table_name, expression, options) # :nodoc: + options = options.dup + options[:name] ||= check_constraint_name(table_name, expression: expression, **options) + options + end + + # Removes the given check constraint from the table. Removing a check constraint + # that does not exist will raise an error. + # + # remove_check_constraint :products, name: "price_check" + # + # To silently ignore a non-existent check constraint rather than raise an error, + # use the +if_exists+ option. + # + # remove_check_constraint :products, name: "price_check", if_exists: true + # + # The +expression+ parameter will be ignored if present. It can be helpful + # to provide this in a migration's +change+ method so it can be reverted. + # In that case, +expression+ will be used by #add_check_constraint. + def remove_check_constraint(table_name, expression = nil, if_exists: false, **options) + return unless supports_check_constraints? + + return if if_exists && !check_constraint_exists?(table_name, **options) + + chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name + + at = create_alter_table(table_name) + at.drop_check_constraint(chk_name_to_delete) + + execute schema_creation.accept(at) + end + + # Checks to see if a check constraint exists on a table for a given check constraint definition. + # + # check_constraint_exists?(:products, name: "price_check") + # + def check_constraint_exists?(table_name, **options) + if !options.key?(:name) && !options.key?(:expression) + raise ArgumentError, "At least one of :name or :expression must be supplied" + end + check_constraint_for(table_name, **options).present? + end + + def remove_constraint(table_name, constraint_name) # :nodoc: + at = create_alter_table(table_name) + at.drop_constraint(constraint_name) + + execute schema_creation.accept(at) + end + + def dump_schema_information # :nodoc: + versions = pool.schema_migration.versions + insert_versions_sql(versions) if versions.any? + end + + def internal_string_options_for_primary_key # :nodoc: + { primary_key: true } + end + + def assume_migrated_upto_version(version) + version = version.to_i + sm_table = quote_table_name(pool.schema_migration.table_name) + + migration_context = pool.migration_context + migrated = migration_context.get_all_versions + versions = migration_context.migrations.map(&:version) + + unless migrated.include?(version) + execute "INSERT INTO #{sm_table} (version) VALUES (#{quote(version)})" + end + + inserting = (versions - migrated).select { |v| v < version } + if inserting.any? + if (duplicate = inserting.detect { |v| inserting.count(v) > 1 }) + raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict." + end + execute insert_versions_sql(inserting) + end + end + + def type_to_sql(type, limit: nil, precision: nil, scale: nil, **) # :nodoc: + type = type.to_sym if type + if native = native_database_types[type] + column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup + + if type == :decimal # ignore limit, use precision and scale + scale ||= native[:scale] + + if precision ||= native[:precision] + if scale + column_type_sql << "(#{precision},#{scale})" + else + column_type_sql << "(#{precision})" + end + elsif scale + raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified" + end + + elsif [:datetime, :timestamp, :time, :interval].include?(type) && precision ||= native[:precision] + if (0..6) === precision + column_type_sql << "(#{precision})" + else + raise ArgumentError, "No #{native[:name]} type has precision of #{precision}. The allowed range of precision is from 0 to 6" + end + elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit]) + column_type_sql << "(#{limit})" + end + + column_type_sql + else + type.to_s + end + end + + # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT. + # PostgreSQL, MySQL, and Oracle override this for custom DISTINCT syntax - they + # require the order columns appear in the SELECT. + # + # columns_for_distinct("posts.id", ["posts.created_at desc"]) + # + def columns_for_distinct(columns, orders) # :nodoc: + columns + end + + def distinct_relation_for_primary_key(relation) # :nodoc: + primary_key_columns = Array(relation.primary_key).map do |column| + visitor.compile(relation.table[column]) + end + + values = columns_for_distinct( + primary_key_columns, + relation.order_values + ) + + limited = relation.reselect(values).distinct! + limited_ids = select_rows(limited.arel, "SQL").map do |results| + results.last(Array(relation.primary_key).length) # ignores order values for MySQL and PostgreSQL + end + + if limited_ids.empty? + relation.none! + else + relation.where!(**Array(relation.primary_key).zip(limited_ids.transpose).to_h) + end + + relation.limit_value = relation.offset_value = nil + relation + end + + # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+. + # Additional options (like +:null+) are forwarded to #add_column. + # + # add_timestamps(:suppliers, null: true) + # + def add_timestamps(table_name, **options) + fragments = add_timestamps_for_alter(table_name, **options) + execute "ALTER TABLE #{quote_table_name(table_name)} #{fragments.join(', ')}" + end + + # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition. + # + # remove_timestamps(:suppliers) + # + def remove_timestamps(table_name, **options) + remove_columns table_name, :updated_at, :created_at + end + + def update_table_definition(table_name, base) # :nodoc: + Table.new(table_name, base) + end + + def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc: + options.assert_valid_keys(:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm, :include, :nulls_not_distinct) + + column_names = index_column_names(column_name) + + index_name = name&.to_s + index_name ||= index_name(table_name, column_names) + + validate_index_length!(table_name, index_name, internal) + + index = IndexDefinition.new( + table_name, index_name, + options[:unique], + column_names, + lengths: options[:length] || {}, + orders: options[:order] || {}, + opclasses: options[:opclass] || {}, + where: options[:where], + type: options[:type], + using: options[:using], + include: options[:include], + nulls_not_distinct: options[:nulls_not_distinct], + comment: options[:comment] + ) + + [index, index_algorithm(options[:algorithm]), if_not_exists] + end + + def index_algorithm(algorithm) # :nodoc: + index_algorithms.fetch(algorithm) do + raise ArgumentError, "Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}" + end if algorithm + end + + def quoted_columns_for_index(column_names, options) # :nodoc: + quoted_columns = column_names.each_with_object({}) do |name, result| + result[name.to_sym] = quote_column_name(name).dup + end + add_options_for_index_columns(quoted_columns, **options).values.join(", ") + end + + def options_include_default?(options) + options.include?(:default) && !(options[:null] == false && options[:default].nil?) + end + + # Changes the comment for a table or removes it if +nil+. + # + # Passing a hash containing +:from+ and +:to+ will make this change + # reversible in migration: + # + # change_table_comment(:posts, from: "old_comment", to: "new_comment") + def change_table_comment(table_name, comment_or_changes) + raise NotImplementedError, "#{self.class} does not support changing table comments" + end + + # Changes the comment for a column or removes it if +nil+. + # + # Passing a hash containing +:from+ and +:to+ will make this change + # reversible in migration: + # + # change_column_comment(:posts, :state, from: "old_comment", to: "new_comment") + def change_column_comment(table_name, column_name, comment_or_changes) + raise NotImplementedError, "#{self.class} does not support changing column comments" + end + + def create_schema_dumper(options) # :nodoc: + SchemaDumper.create(self, options) + end + + def use_foreign_keys? + supports_foreign_keys? && foreign_keys_enabled? + end + + # Returns an instance of SchemaCreation, which can be used to visit a schema definition + # object and return DDL. + def schema_creation # :nodoc: + SchemaCreation.new(self) + end + + def bulk_change_table(table_name, operations) # :nodoc: + sql_fragments = [] + non_combinable_operations = [] + + operations.each do |command, args| + table, arguments = args.shift, args + method = :"#{command}_for_alter" + + if respond_to?(method, true) + sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) } + sql_fragments.concat(sqls) + non_combinable_operations.concat(procs) + else + execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty? + non_combinable_operations.each(&:call) + sql_fragments = [] + non_combinable_operations = [] + send(command, table, *arguments) + end + end + + execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty? + non_combinable_operations.each(&:call) + end + + def valid_table_definition_options # :nodoc: + [:temporary, :if_not_exists, :options, :as, :comment, :charset, :collation] + end + + def valid_column_definition_options # :nodoc: + ColumnDefinition::OPTION_NAMES + end + + def valid_primary_key_options # :nodoc: + [:limit, :default, :precision] + end + + # Returns the maximum length of an index name in bytes. + def max_index_name_size + 62 + end + + private + def generate_index_name(table_name, column) + name = "index_#{table_name}_on_#{Array(column) * '_and_'}" + return name if name.bytesize <= max_index_name_size + + # Fallback to short version, add hash to ensure uniqueness + hashed_identifier = "_" + OpenSSL::Digest::SHA256.hexdigest(name).first(10) + name = "idx_on_#{Array(column) * '_'}" + + short_limit = max_index_name_size - hashed_identifier.bytesize + short_name = name.mb_chars.limit(short_limit).to_s + + "#{short_name}#{hashed_identifier}" + end + + def validate_change_column_null_argument!(value) + unless value == true || value == false + raise ArgumentError, "change_column_null expects a boolean value (true for NULL, false for NOT NULL). Got: #{value.inspect}" + end + end + + def column_options_keys + [:limit, :precision, :scale, :default, :null, :collation, :comment] + end + + def add_index_sort_order(quoted_columns, **options) + orders = options_for_index_columns(options[:order]) + quoted_columns.each do |name, column| + column << " #{orders[name].upcase}" if orders[name].present? + end + end + + def options_for_index_columns(options) + if options.is_a?(Hash) + options.symbolize_keys + else + Hash.new { |hash, column| hash[column] = options } + end + end + + # Overridden by the MySQL adapter for supporting index lengths and by + # the PostgreSQL adapter for supporting operator classes. + def add_options_for_index_columns(quoted_columns, **options) + if supports_index_sort_order? + quoted_columns = add_index_sort_order(quoted_columns, **options) + end + + quoted_columns + end + + def index_name_for_remove(table_name, column_name, options) + return options[:name] if can_remove_index_by_name?(column_name, options) + + checks = [] + + if !options.key?(:name) && expression_column_name?(column_name) + options[:name] = index_name(table_name, column_name) + column_names = [] + else + column_names = index_column_names(column_name || options[:column]) + end + + checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name) + + if column_names.present? && !(options.key?(:name) && expression_column_name?(column_names)) + checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) } + end + + raise ArgumentError, "No name or columns specified" if checks.none? + + matching_indexes = indexes(table_name).select { |i| checks.all? { |check| check[i] } } + + if matching_indexes.count > 1 + raise ArgumentError, "Multiple indexes found on #{table_name} columns #{column_names}. " \ + "Specify an index name from #{matching_indexes.map(&:name).join(', ')}" + elsif matching_indexes.none? + raise ArgumentError, "No indexes found on #{table_name} with the options provided." + else + matching_indexes.first.name + end + end + + def rename_table_indexes(table_name, new_name, **options) + indexes(new_name).each do |index| + generated_index_name = index_name(table_name, column: index.columns, **options) + if generated_index_name == index.name + rename_index new_name, generated_index_name, index_name(new_name, column: index.columns, **options) + end + end + end + + def rename_column_indexes(table_name, column_name, new_column_name) + column_name, new_column_name = column_name.to_s, new_column_name.to_s + indexes(table_name).each do |index| + next unless index.columns.include?(new_column_name) + old_columns = index.columns.dup + old_columns[old_columns.index(new_column_name)] = column_name + generated_index_name = index_name(table_name, column: old_columns) + if generated_index_name == index.name + rename_index table_name, generated_index_name, index_name(table_name, column: index.columns) + end + end + end + + def create_table_definition(name, **options) + TableDefinition.new(self, name, **options) + end + + def create_alter_table(name) + AlterTable.new create_table_definition(name) + end + + def validate_create_table_options!(options) + unless options[:_skip_validate_options] + options + .except(:_uses_legacy_table_name, :_skip_validate_options) + .assert_valid_keys(valid_table_definition_options, valid_primary_key_options) + end + end + + def fetch_type_metadata(sql_type) + cast_type = lookup_cast_type(sql_type) + SqlTypeMetadata.new( + sql_type: sql_type, + type: cast_type.type, + limit: cast_type.limit, + precision: cast_type.precision, + scale: cast_type.scale, + ) + end + + def index_column_names(column_names) + if expression_column_name?(column_names) + column_names + else + Array(column_names) + end + end + + def index_name_options(column_names) + if expression_column_name?(column_names) + column_names = column_names.scan(/\w+/).join("_") + end + + { column: column_names } + end + + # Try to identify whether the given column name is an expression + def expression_column_name?(column_name) + column_name.is_a?(String) && /\W/.match?(column_name) + end + + def strip_table_name_prefix_and_suffix(table_name) + prefix = Base.table_name_prefix + suffix = Base.table_name_suffix + table_name.to_s =~ /#{prefix}(.+)#{suffix}/ ? $1 : table_name.to_s + end + + def foreign_key_name(table_name, options) + options.fetch(:name) do + columns = Array(options.fetch(:column)).map(&:to_s) + identifier = "#{table_name}_#{columns * '_and_'}_fk" + hashed_identifier = OpenSSL::Digest::SHA256.hexdigest(identifier).first(10) + + "fk_rails_#{hashed_identifier}" + end + end + + def foreign_key_for(from_table, **options) + return unless use_foreign_keys? + foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) } + end + + def foreign_key_for!(from_table, to_table: nil, **options) + foreign_key_for(from_table, to_table: to_table, **options) || + raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}") + end + + def extract_foreign_key_action(specifier) + case specifier + when "CASCADE"; :cascade + when "SET NULL"; :nullify + when "RESTRICT"; :restrict + end + end + + def foreign_keys_enabled? + @config.fetch(:foreign_keys, true) + end + + def check_constraint_name(table_name, **options) + options.fetch(:name) do + expression = options.fetch(:expression) + identifier = "#{table_name}_#{expression}_chk" + hashed_identifier = OpenSSL::Digest::SHA256.hexdigest(identifier).first(10) + + "chk_rails_#{hashed_identifier}" + end + end + + def check_constraint_for(table_name, **options) + return unless supports_check_constraints? + chk_name = check_constraint_name(table_name, **options) + check_constraints(table_name).detect { |chk| chk.defined_for?(name: chk_name, **options) } + end + + def check_constraint_for!(table_name, expression: nil, **options) + check_constraint_for(table_name, expression: expression, **options) || + raise(ArgumentError, "Table '#{table_name}' has no check constraint for #{expression || options}") + end + + def validate_index_length!(table_name, new_name, internal = false) + if new_name.length > index_name_length + raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters" + end + end + + def validate_table_length!(table_name) + if table_name.length > table_name_length + raise ArgumentError, "Table name '#{table_name}' is too long; the limit is #{table_name_length} characters" + end + end + + def extract_new_default_value(default_or_changes) + if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to) + default_or_changes[:to] + else + default_or_changes + end + end + alias :extract_new_comment_value :extract_new_default_value + + def can_remove_index_by_name?(column_name, options) + column_name.nil? && options.key?(:name) && options.except(:name, :algorithm).empty? + end + + def reference_name_for_table(table_name) + table_name.to_s.singularize + end + + def add_column_for_alter(table_name, column_name, type, **options) + td = create_table_definition(table_name) + cd = td.new_column_definition(column_name, type, **options) + schema_creation.accept(AddColumnDefinition.new(cd)) + end + + def change_column_default_for_alter(table_name, column_name, default_or_changes) + cd = build_change_column_default_definition(table_name, column_name, default_or_changes) + schema_creation.accept(cd) + end + + def rename_column_sql(table_name, column_name, new_column_name) + "RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}" + end + + def remove_column_for_alter(table_name, column_name, type = nil, **options) + "DROP COLUMN #{quote_column_name(column_name)}" + end + + def remove_columns_for_alter(table_name, *column_names, **options) + column_names.map { |column_name| remove_column_for_alter(table_name, column_name) } + end + + def add_timestamps_for_alter(table_name, **options) + options[:null] = false if options[:null].nil? + + if !options.key?(:precision) && supports_datetime_with_precision? + options[:precision] = 6 + end + + [ + add_column_for_alter(table_name, :created_at, :datetime, **options), + add_column_for_alter(table_name, :updated_at, :datetime, **options) + ] + end + + def remove_timestamps_for_alter(table_name, **options) + remove_columns_for_alter(table_name, :updated_at, :created_at) + end + + def insert_versions_sql(versions) + sm_table = quote_table_name(pool.schema_migration.table_name) + + if versions.is_a?(Array) + sql = +"INSERT INTO #{sm_table} (version) VALUES\n" + sql << versions.reverse.map { |v| "(#{quote(v)})" }.join(",\n") + sql << ";" + sql + else + "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});" + end + end + + def data_source_sql(name = nil, type: nil) + raise NotImplementedError + end + + def quoted_scope(name = nil, type: nil) + raise NotImplementedError + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/transaction.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/transaction.rb new file mode 100644 index 00000000..f5d21f99 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract/transaction.rb @@ -0,0 +1,676 @@ +# frozen_string_literal: true + +require "active_support/core_ext/digest" + +module ActiveRecord + module ConnectionAdapters + # = Active Record Connection Adapters Transaction State + class TransactionState + def initialize(state = nil) + @state = state + @children = nil + end + + def add_child(state) + @children ||= [] + @children << state + end + + def finalized? + @state + end + + def committed? + @state == :committed || @state == :fully_committed + end + + def fully_committed? + @state == :fully_committed + end + + def rolledback? + @state == :rolledback || @state == :fully_rolledback + end + + def fully_rolledback? + @state == :fully_rolledback + end + + def invalidated? + @state == :invalidated + end + + def fully_completed? + completed? + end + + def completed? + committed? || rolledback? + end + + def rollback! + @children&.each { |c| c.rollback! } + @state = :rolledback + end + + def full_rollback! + @children&.each { |c| c.rollback! } + @state = :fully_rolledback + end + + def invalidate! + @children&.each { |c| c.invalidate! } + @state = :invalidated + end + + def commit! + @state = :committed + end + + def full_commit! + @state = :fully_committed + end + + def nullify! + @state = nil + end + end + + class TransactionInstrumenter + def initialize(payload = {}) + @handle = nil + @started = false + @payload = nil + @base_payload = payload + end + + class InstrumentationNotStartedError < ActiveRecordError; end + class InstrumentationAlreadyStartedError < ActiveRecordError; end + + def start + raise InstrumentationAlreadyStartedError.new("Called start on an already started transaction") if @started + @started = true + + ActiveSupport::Notifications.instrument("start_transaction.active_record", @base_payload) + + @payload = @base_payload.dup # We dup because the payload for a given event is mutated later to add the outcome. + @handle = ActiveSupport::Notifications.instrumenter.build_handle("transaction.active_record", @payload) + @handle.start + end + + def finish(outcome) + raise InstrumentationNotStartedError.new("Called finish on a transaction that hasn't started") unless @started + @started = false + + @payload[:outcome] = outcome + @handle.finish + end + end + + class NullTransaction # :nodoc: + def state; end + def closed?; true; end + def open?; false; end + def joinable?; false; end + def add_record(record, _ = true); end + def restartable?; false; end + def dirty?; false; end + def dirty!; end + def invalidated?; false; end + def invalidate!; end + def materialized?; false; end + def before_commit; yield; end + def after_commit; yield; end + def after_rollback; end + def user_transaction; ActiveRecord::Transaction::NULL_TRANSACTION; end + end + + class Transaction # :nodoc: + class Callback # :nodoc: + def initialize(event, callback) + @event = event + @callback = callback + end + + def before_commit + @callback.call if @event == :before_commit + end + + def after_commit + @callback.call if @event == :after_commit + end + + def after_rollback + @callback.call if @event == :after_rollback + end + end + + attr_reader :connection, :state, :savepoint_name, :isolation_level, :user_transaction + attr_accessor :written + + delegate :invalidate!, :invalidated?, to: :@state + + def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false) + super() + @connection = connection + @state = TransactionState.new + @callbacks = nil + @records = nil + @isolation_level = isolation + @materialized = false + @joinable = joinable + @run_commit_callbacks = run_commit_callbacks + @lazy_enrollment_records = nil + @dirty = false + @user_transaction = joinable ? ActiveRecord::Transaction.new(self) : ActiveRecord::Transaction::NULL_TRANSACTION + @instrumenter = TransactionInstrumenter.new(connection: connection, transaction: @user_transaction) + end + + def dirty! + @dirty = true + end + + def dirty? + @dirty + end + + def open? + true + end + + def closed? + false + end + + def add_record(record, ensure_finalize = true) + @records ||= [] + if ensure_finalize + @records << record + else + @lazy_enrollment_records ||= ObjectSpace::WeakMap.new + @lazy_enrollment_records[record] = record + end + end + + def before_commit(&block) + if @state.finalized? + raise ActiveRecordError, "Cannot register callbacks on a finalized transaction" + end + + (@callbacks ||= []) << Callback.new(:before_commit, block) + end + + def after_commit(&block) + if @state.finalized? + raise ActiveRecordError, "Cannot register callbacks on a finalized transaction" + end + + (@callbacks ||= []) << Callback.new(:after_commit, block) + end + + def after_rollback(&block) + if @state.finalized? + raise ActiveRecordError, "Cannot register callbacks on a finalized transaction" + end + + (@callbacks ||= []) << Callback.new(:after_rollback, block) + end + + def records + if @lazy_enrollment_records + @records.concat @lazy_enrollment_records.values + @lazy_enrollment_records = nil + end + @records + end + + # Can this transaction's current state be recreated by + # rollback+begin ? + def restartable? + joinable? && !dirty? + end + + def incomplete! + @instrumenter.finish(:incomplete) if materialized? + end + + def materialize! + @materialized = true + @instrumenter.start + end + + def materialized? + @materialized + end + + def restore! + if materialized? + incomplete! + @materialized = false + materialize! + end + end + + def rollback_records + if records + begin + ite = unique_records + + instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite) + + run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks| + record.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: should_run_callbacks) + end + ensure + ite&.each do |i| + i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false) + end + end + end + + @callbacks&.each(&:after_rollback) + end + + def before_commit_records + if @run_commit_callbacks + if records + if ActiveRecord.before_committed_on_all_records + ite = unique_records + + instances_to_run_callbacks_on = records.each_with_object({}) do |record, candidates| + candidates[record] = record + end + + run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks| + record.before_committed! if should_run_callbacks + end + else + records.uniq.each(&:before_committed!) + end + end + + @callbacks&.each(&:before_commit) + end + # Note: When @run_commit_callbacks is false #commit_records takes care of appending + # remaining callbacks to the parent transaction + end + + def commit_records + if records + begin + ite = unique_records + + if @run_commit_callbacks + instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite) + + run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks| + record.committed!(should_run_callbacks: should_run_callbacks) + end + else + while record = ite.shift + # if not running callbacks, only adds the record to the parent transaction + connection.add_transaction_record(record) + end + end + ensure + ite&.each { |i| i.committed!(should_run_callbacks: false) } + end + end + + if @run_commit_callbacks + @callbacks&.each(&:after_commit) + elsif @callbacks + connection.current_transaction.append_callbacks(@callbacks) + end + end + + def full_rollback?; true; end + def joinable?; @joinable; end + + protected + def append_callbacks(callbacks) # :nodoc: + (@callbacks ||= []).concat(callbacks) + end + + private + def unique_records + records.uniq(&:__id__) + end + + def run_action_on_records(records, instances_to_run_callbacks_on) + while record = records.shift + should_run_callbacks = record.__id__ == instances_to_run_callbacks_on[record].__id__ + + yield record, should_run_callbacks + end + end + + def prepare_instances_to_run_callbacks_on(records) + records.each_with_object({}) do |record, candidates| + next unless record.trigger_transactional_callbacks? + + earlier_saved_candidate = candidates[record] + + next if earlier_saved_candidate && record.class.run_commit_callbacks_on_first_saved_instances_in_transaction + + # If the candidate instance destroyed itself in the database, then + # instances which were added to the transaction afterwards, and which + # think they updated themselves, are wrong. They should not replace + # our candidate as an instance to run callbacks on + next if earlier_saved_candidate&.destroyed? && !record.destroyed? + + # If the candidate instance was created inside of this transaction, + # then instances which were subsequently loaded from the database + # and updated need that state transferred to them so that + # the after_create_commit callbacks are run + record._new_record_before_last_commit = true if earlier_saved_candidate&._new_record_before_last_commit + + # The last instance to save itself is likeliest to have internal + # state that matches what's committed to the database + candidates[record] = record + end + end + end + + # = Active Record Restart Parent \Transaction + class RestartParentTransaction < Transaction + def initialize(connection, parent_transaction, **options) + super(connection, **options) + + @parent = parent_transaction + + if isolation_level + raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction" + end + + @parent.state.add_child(@state) + end + + delegate :materialize!, :materialized?, :restart, to: :@parent + + def rollback + @state.rollback! + @parent.restart + end + + def commit + @state.commit! + end + + def full_rollback?; false; end + end + + # = Active Record Savepoint \Transaction + class SavepointTransaction < Transaction + def initialize(connection, savepoint_name, parent_transaction, **options) + super(connection, **options) + + parent_transaction.state.add_child(@state) + + if isolation_level + raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction" + end + + @savepoint_name = savepoint_name + end + + def materialize! + connection.create_savepoint(savepoint_name) + super + end + + def restart + return unless materialized? + + @instrumenter.finish(:restart) + @instrumenter.start + + connection.rollback_to_savepoint(savepoint_name) + end + + def rollback + unless @state.invalidated? + connection.rollback_to_savepoint(savepoint_name) if materialized? && connection.active? + end + @state.rollback! + @instrumenter.finish(:rollback) if materialized? + end + + def commit + connection.release_savepoint(savepoint_name) if materialized? + @state.commit! + @instrumenter.finish(:commit) if materialized? + end + + def full_rollback?; false; end + end + + # = Active Record Real \Transaction + class RealTransaction < Transaction + def materialize! + if joinable? + if isolation_level + connection.begin_isolated_db_transaction(isolation_level) + else + connection.begin_db_transaction + end + else + connection.begin_deferred_transaction(isolation_level) + end + + super + end + + def restart + return unless materialized? + + @instrumenter.finish(:restart) + + if connection.supports_restart_db_transaction? + @instrumenter.start + connection.restart_db_transaction + else + connection.rollback_db_transaction + materialize! + end + end + + def rollback + if materialized? + connection.rollback_db_transaction + connection.reset_isolation_level if isolation_level + end + @state.full_rollback! + @instrumenter.finish(:rollback) if materialized? + end + + def commit + if materialized? + connection.commit_db_transaction + connection.reset_isolation_level if isolation_level + end + @state.full_commit! + @instrumenter.finish(:commit) if materialized? + end + end + + class TransactionManager # :nodoc: + def initialize(connection) + @stack = [] + @connection = connection + @has_unmaterialized_transactions = false + @materializing_transactions = false + @lazy_transactions_enabled = true + end + + def begin_transaction(isolation: nil, joinable: true, _lazy: true) + @connection.lock.synchronize do + run_commit_callbacks = !current_transaction.joinable? + transaction = + if @stack.empty? + RealTransaction.new( + @connection, + isolation: isolation, + joinable: joinable, + run_commit_callbacks: run_commit_callbacks + ) + elsif current_transaction.restartable? + RestartParentTransaction.new( + @connection, + current_transaction, + isolation: isolation, + joinable: joinable, + run_commit_callbacks: run_commit_callbacks + ) + else + SavepointTransaction.new( + @connection, + "active_record_#{@stack.size}", + current_transaction, + isolation: isolation, + joinable: joinable, + run_commit_callbacks: run_commit_callbacks + ) + end + + unless transaction.materialized? + if @connection.supports_lazy_transactions? && lazy_transactions_enabled? && _lazy + @has_unmaterialized_transactions = true + else + transaction.materialize! + end + end + @stack.push(transaction) + transaction + end + end + + def disable_lazy_transactions! + materialize_transactions + @lazy_transactions_enabled = false + end + + def enable_lazy_transactions! + @lazy_transactions_enabled = true + end + + def lazy_transactions_enabled? + @lazy_transactions_enabled + end + + def dirty_current_transaction + current_transaction.dirty! + end + + def restore_transactions + return false unless restorable? + + @stack.each(&:restore!) + + true + end + + def restorable? + @stack.none?(&:dirty?) + end + + def materialize_transactions + return if @materializing_transactions + + if @has_unmaterialized_transactions + @connection.lock.synchronize do + begin + @materializing_transactions = true + @stack.each { |t| t.materialize! unless t.materialized? } + ensure + @materializing_transactions = false + end + @has_unmaterialized_transactions = false + end + end + end + + def commit_transaction + @connection.lock.synchronize do + transaction = @stack.last + + begin + transaction.before_commit_records + ensure + @stack.pop + end + + dirty_current_transaction if transaction.dirty? + + transaction.commit + transaction.commit_records + end + end + + def rollback_transaction(transaction = nil) + @connection.lock.synchronize do + transaction ||= @stack.last + begin + transaction.rollback + ensure + @stack.pop if @stack.last == transaction + end + transaction.rollback_records + end + end + + def within_new_transaction(isolation: nil, joinable: true) + @connection.lock.synchronize do + transaction = begin_transaction(isolation: isolation, joinable: joinable) + begin + yield transaction.user_transaction + rescue Exception => error + rollback_transaction + after_failure_actions(transaction, error) + + raise + ensure + unless error + if Thread.current.status == "aborting" + rollback_transaction + else + begin + commit_transaction + rescue ActiveRecord::ConnectionFailed + transaction.invalidate! unless transaction.state.completed? + raise + rescue Exception + rollback_transaction(transaction) unless transaction.state.completed? + raise + end + end + end + end + ensure + unless transaction&.state&.completed? + @connection.throw_away! + transaction&.incomplete! + end + end + end + + def open_transactions + @stack.size + end + + def current_transaction + @stack.last || NULL_TRANSACTION + end + + private + NULL_TRANSACTION = NullTransaction.new.freeze + + # Deallocate invalidated prepared statements outside of the transaction + def after_failure_actions(transaction, error) + return unless transaction.is_a?(RealTransaction) + return unless error.is_a?(ActiveRecord::PreparedStatementCacheExpired) + @connection.clear_cache! + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract_adapter.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract_adapter.rb new file mode 100644 index 00000000..3313a7da --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract_adapter.rb @@ -0,0 +1,1234 @@ +# frozen_string_literal: true + +require "active_record/connection_adapters/sql_type_metadata" +require "active_record/connection_adapters/abstract/schema_dumper" +require "active_record/connection_adapters/abstract/schema_creation" +require "active_support/concurrency/null_lock" +require "active_support/concurrency/load_interlock_aware_monitor" +require "arel/collectors/bind" +require "arel/collectors/composite" +require "arel/collectors/sql_string" +require "arel/collectors/substitute_binds" + +module ActiveRecord + module ConnectionAdapters # :nodoc: + # = Active Record Abstract Adapter + # + # Active Record supports multiple database systems. AbstractAdapter and + # related classes form the abstraction layer which makes this possible. + # An AbstractAdapter represents a connection to a database, and provides an + # abstract interface for database-specific functionality such as establishing + # a connection, escaping values, building the right SQL fragments for +:offset+ + # and +:limit+ options, etc. + # + # All the concrete database adapters follow the interface laid down in this class. + # {ActiveRecord::Base.lease_connection}[rdoc-ref:ConnectionHandling#lease_connection] returns an AbstractAdapter object, which + # you can use. + # + # Most of the methods in the adapter are useful during migrations. Most + # notably, the instance methods provided by SchemaStatements are very useful. + class AbstractAdapter + ADAPTER_NAME = "Abstract" + include ActiveSupport::Callbacks + define_callbacks :checkout, :checkin + + include Quoting, DatabaseStatements, SchemaStatements + include DatabaseLimits + include QueryCache + include Savepoints + + SIMPLE_INT = /\A\d+\z/ + COMMENT_REGEX = %r{(?:--.*\n)|/\*(?:[^*]|\*[^/])*\*/} + + attr_reader :pool + attr_reader :visitor, :owner, :logger, :lock + alias :in_use? :owner + + def pool=(value) + return if value.eql?(@pool) + @schema_cache = nil + @pool = value + end + + set_callback :checkin, :after, :enable_lazy_transactions! + + def self.type_cast_config_to_integer(config) + if config.is_a?(Integer) + config + elsif SIMPLE_INT.match?(config) + config.to_i + else + config + end + end + + def self.type_cast_config_to_boolean(config) + if config == "false" + false + else + config + end + end + + def self.validate_default_timezone(config) + case config + when nil + when "utc", "local" + config.to_sym + else + raise ArgumentError, "default_timezone must be either 'utc' or 'local'" + end + end + + DEFAULT_READ_QUERY = [:begin, :commit, :explain, :release, :rollback, :savepoint, :select, :with] # :nodoc: + private_constant :DEFAULT_READ_QUERY + + def self.build_read_query_regexp(*parts) # :nodoc: + parts += DEFAULT_READ_QUERY + parts = parts.map { |part| /#{part}/i } + /\A(?:[(\s]|#{COMMENT_REGEX})*#{Regexp.union(*parts)}/ + end + + def self.find_cmd_and_exec(commands, *args) # :doc: + commands = Array(commands) + + dirs_on_path = ENV["PATH"].to_s.split(File::PATH_SEPARATOR) + unless (ext = RbConfig::CONFIG["EXEEXT"]).empty? + commands = commands.map { |cmd| "#{cmd}#{ext}" } + end + + full_path_command = nil + found = commands.detect do |cmd| + dirs_on_path.detect do |path| + full_path_command = File.join(path, cmd) + begin + stat = File.stat(full_path_command) + rescue SystemCallError + else + stat.file? && stat.executable? + end + end + end + + if found + exec full_path_command, *args + else + abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.") + end + end + + # Opens a database console session. + def self.dbconsole(config, options = {}) + raise NotImplementedError + end + + def initialize(config_or_deprecated_connection, deprecated_logger = nil, deprecated_connection_options = nil, deprecated_config = nil) # :nodoc: + super() + + @raw_connection = nil + @unconfigured_connection = nil + + if config_or_deprecated_connection.is_a?(Hash) + @config = config_or_deprecated_connection.symbolize_keys + @logger = ActiveRecord::Base.logger + + if deprecated_logger || deprecated_connection_options || deprecated_config + raise ArgumentError, "when initializing an Active Record adapter with a config hash, that should be the only argument" + end + else + # Soft-deprecated for now; we'll probably warn in future. + + @unconfigured_connection = config_or_deprecated_connection + @logger = deprecated_logger || ActiveRecord::Base.logger + if deprecated_config + @config = (deprecated_config || {}).symbolize_keys + @connection_parameters = deprecated_connection_options + else + @config = (deprecated_connection_options || {}).symbolize_keys + @connection_parameters = nil + end + end + + @owner = nil + @pool = ActiveRecord::ConnectionAdapters::NullPool.new + @idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @visitor = arel_visitor + @statements = build_statement_pool + self.lock_thread = nil + + @prepared_statements = !ActiveRecord.disable_prepared_statements && self.class.type_cast_config_to_boolean( + @config.fetch(:prepared_statements) { default_prepared_statements } + ) + + @advisory_locks_enabled = self.class.type_cast_config_to_boolean( + @config.fetch(:advisory_locks, true) + ) + + @default_timezone = self.class.validate_default_timezone(@config[:default_timezone]) + + @raw_connection_dirty = false + @last_activity = nil + @verified = false + end + + def inspect # :nodoc: + name_field = " name=#{pool.db_config.name.inspect}" unless pool.db_config.name == "primary" + shard_field = " shard=#{shard.inspect}" unless shard == :default + + "#<#{self.class.name}:#{'%#016x' % (object_id << 1)} env_name=#{pool.db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>" + end + + def lock_thread=(lock_thread) # :nodoc: + @lock = + case lock_thread + when Thread + ActiveSupport::Concurrency::ThreadLoadInterlockAwareMonitor.new + when Fiber + ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new + else + ActiveSupport::Concurrency::NullLock + end + end + + def check_if_write_query(sql) # :nodoc: + if preventing_writes? && write_query?(sql) + raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}" + end + end + + def replica? + @config[:replica] || false + end + + def connection_retries + (@config[:connection_retries] || 1).to_i + end + + def verify_timeout + (@config[:verify_timeout] || 2).to_i + end + + def retry_deadline + if @config[:retry_deadline] + @config[:retry_deadline].to_f + else + nil + end + end + + def default_timezone + @default_timezone || ActiveRecord.default_timezone + end + + # Determines whether writes are currently being prevented. + # + # Returns true if the connection is a replica or returns + # the value of +current_preventing_writes+. + def preventing_writes? + return true if replica? + return false if connection_descriptor.nil? + + connection_descriptor.current_preventing_writes + end + + def prepared_statements? + @prepared_statements && !prepared_statements_disabled_cache.include?(object_id) + end + alias :prepared_statements :prepared_statements? + + def prepared_statements_disabled_cache # :nodoc: + ActiveSupport::IsolatedExecutionState[:active_record_prepared_statements_disabled_cache] ||= Set.new + end + + class Version + include Comparable + + attr_reader :full_version_string + + def initialize(version_string, full_version_string = nil) + @version = version_string.split(".").map(&:to_i) + @full_version_string = full_version_string + end + + def <=>(version_string) + @version <=> version_string.split(".").map(&:to_i) + end + + def to_s + @version.join(".") + end + end + + def valid_type?(type) # :nodoc: + !native_database_types[type].nil? + end + + # this method must only be called while holding connection pool's mutex + def lease + if in_use? + msg = +"Cannot lease connection, " + if @owner == ActiveSupport::IsolatedExecutionState.context + msg << "it is already leased by the current thread." + else + msg << "it is already in use by a different thread: #{@owner}. " \ + "Current thread: #{ActiveSupport::IsolatedExecutionState.context}." + end + raise ActiveRecordError, msg + end + + @owner = ActiveSupport::IsolatedExecutionState.context + end + + def connection_descriptor # :nodoc: + @pool.connection_descriptor + end + + # The role (e.g. +:writing+) for the current connection. In a + # non-multi role application, +:writing+ is returned. + def role + @pool.role + end + + # The shard (e.g. +:default+) for the current connection. In + # a non-sharded application, +:default+ is returned. + def shard + @pool.shard + end + + def schema_cache + @pool.schema_cache || (@schema_cache ||= BoundSchemaReflection.for_lone_connection(@pool.schema_reflection, self)) + end + + # this method must only be called while holding connection pool's mutex + def expire + if in_use? + if @owner != ActiveSupport::IsolatedExecutionState.context + raise ActiveRecordError, "Cannot expire connection, " \ + "it is owned by a different thread: #{@owner}. " \ + "Current thread: #{ActiveSupport::IsolatedExecutionState.context}." + end + + @idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @owner = nil + else + raise ActiveRecordError, "Cannot expire connection, it is not currently leased." + end + end + + # this method must only be called while holding connection pool's mutex (and a desire for segfaults) + def steal! # :nodoc: + if in_use? + if @owner != ActiveSupport::IsolatedExecutionState.context + pool.send :remove_connection_from_thread_cache, self, @owner + + @owner = ActiveSupport::IsolatedExecutionState.context + end + else + raise ActiveRecordError, "Cannot steal connection, it is not currently leased." + end + end + + # Seconds since this connection was returned to the pool + def seconds_idle # :nodoc: + return 0 if in_use? + Process.clock_gettime(Process::CLOCK_MONOTONIC) - @idle_since + end + + # Seconds since this connection last communicated with the server + def seconds_since_last_activity # :nodoc: + if @raw_connection && @last_activity + Process.clock_gettime(Process::CLOCK_MONOTONIC) - @last_activity + end + end + + def unprepared_statement + cache = prepared_statements_disabled_cache.add?(object_id) if @prepared_statements + yield + ensure + cache&.delete(object_id) + end + + # Returns the human-readable name of the adapter. Use mixed case - one + # can always use downcase if needed. + def adapter_name + self.class::ADAPTER_NAME + end + + # Does the database for this adapter exist? + def self.database_exists?(config) + new(config).database_exists? + end + + def database_exists? + connect! + true + rescue ActiveRecord::NoDatabaseError + false + end + + # Does this adapter support DDL rollbacks in transactions? That is, would + # CREATE TABLE or ALTER TABLE get rolled back by a transaction? + def supports_ddl_transactions? + false + end + + def supports_bulk_alter? + false + end + + # Does this adapter support savepoints? + def supports_savepoints? + false + end + + # Do TransactionRollbackErrors on savepoints affect the parent + # transaction? + def savepoint_errors_invalidate_transactions? + false + end + + def supports_restart_db_transaction? + false + end + + # Does this adapter support application-enforced advisory locking? + def supports_advisory_locks? + false + end + + # Should primary key values be selected from their corresponding + # sequence before the insert statement? If true, next_sequence_value + # is called before each insert to set the record's primary key. + def prefetch_primary_key?(table_name = nil) + false + end + + def supports_partitioned_indexes? + false + end + + # Does this adapter support index sort order? + def supports_index_sort_order? + false + end + + # Does this adapter support partial indices? + def supports_partial_index? + false + end + + # Does this adapter support including non-key columns? + def supports_index_include? + false + end + + # Does this adapter support expression indices? + def supports_expression_index? + false + end + + # Does this adapter support explain? + def supports_explain? + false + end + + # Does this adapter support setting the isolation level for a transaction? + def supports_transaction_isolation? + false + end + + # Does this adapter support database extensions? + def supports_extensions? + false + end + + # Does this adapter support creating indexes in the same statement as + # creating the table? + def supports_indexes_in_create? + false + end + + # Does this adapter support creating foreign key constraints? + def supports_foreign_keys? + false + end + + # Does this adapter support creating invalid constraints? + def supports_validate_constraints? + false + end + + # Does this adapter support creating deferrable constraints? + def supports_deferrable_constraints? + false + end + + # Does this adapter support creating check constraints? + def supports_check_constraints? + false + end + + # Does this adapter support creating exclusion constraints? + def supports_exclusion_constraints? + false + end + + # Does this adapter support creating unique constraints? + def supports_unique_constraints? + false + end + + # Does this adapter support views? + def supports_views? + false + end + + # Does this adapter support materialized views? + def supports_materialized_views? + false + end + + # Does this adapter support datetime with precision? + def supports_datetime_with_precision? + false + end + + # Does this adapter support JSON data type? + def supports_json? + false + end + + # Does this adapter support metadata comments on database objects (tables, columns, indexes)? + def supports_comments? + false + end + + # Can comments for tables, columns, and indexes be specified in create/alter table statements? + def supports_comments_in_create? + false + end + + # Does this adapter support virtual columns? + def supports_virtual_columns? + false + end + + # Does this adapter support foreign/external tables? + def supports_foreign_tables? + false + end + + # Does this adapter support optimizer hints? + def supports_optimizer_hints? + false + end + + def supports_common_table_expressions? + false + end + + def supports_lazy_transactions? + false + end + + def supports_insert_returning? + false + end + + def supports_insert_on_duplicate_skip? + false + end + + def supports_insert_on_duplicate_update? + false + end + + def supports_insert_conflict_target? + false + end + + def supports_concurrent_connections? + true + end + + def supports_nulls_not_distinct? + false + end + + def return_value_after_insert?(column) # :nodoc: + column.auto_populated? + end + + def async_enabled? # :nodoc: + supports_concurrent_connections? && + !ActiveRecord.async_query_executor.nil? && !pool.async_executor.nil? + end + + # This is meant to be implemented by the adapters that support extensions + def disable_extension(name, **) + end + + # This is meant to be implemented by the adapters that support extensions + def enable_extension(name, **) + end + + # This is meant to be implemented by the adapters that support custom enum types + def create_enum(...) # :nodoc: + end + + # This is meant to be implemented by the adapters that support custom enum types + def drop_enum(...) # :nodoc: + end + + # This is meant to be implemented by the adapters that support custom enum types + def rename_enum(...) # :nodoc: + end + + # This is meant to be implemented by the adapters that support custom enum types + def add_enum_value(...) # :nodoc: + end + + # This is meant to be implemented by the adapters that support custom enum types + def rename_enum_value(...) # :nodoc: + end + + # This is meant to be implemented by the adapters that support virtual tables + def create_virtual_table(*) # :nodoc: + end + + # This is meant to be implemented by the adapters that support virtual tables + def drop_virtual_table(*) # :nodoc: + end + + def advisory_locks_enabled? # :nodoc: + supports_advisory_locks? && @advisory_locks_enabled + end + + # This is meant to be implemented by the adapters that support advisory + # locks + # + # Return true if we got the lock, otherwise false + def get_advisory_lock(lock_id) # :nodoc: + end + + # This is meant to be implemented by the adapters that support advisory + # locks. + # + # Return true if we released the lock, otherwise false + def release_advisory_lock(lock_id) # :nodoc: + end + + # A list of extensions, to be filled in by adapters that support them. + def extensions + [] + end + + # A list of index algorithms, to be filled by adapters that support them. + def index_algorithms + {} + end + + # REFERENTIAL INTEGRITY ==================================== + + # Override to turn off referential integrity while executing &block. + def disable_referential_integrity + yield + end + + # Override to check all foreign key constraints in a database. + # The adapter should raise a +ActiveRecord::StatementInvalid+ if foreign key + # constraints are not met. + def check_all_foreign_keys_valid! + end + + # CONNECTION MANAGEMENT ==================================== + + # Checks whether the connection to the database was established. This doesn't + # include checking whether the database is actually capable of responding, i.e. + # whether the connection is stale. + def connected? + !@raw_connection.nil? + end + + # Checks whether the connection to the database is still active. This includes + # checking whether the database is actually capable of responding, i.e. whether + # the connection isn't stale. + def active? + end + + # Disconnects from the database if already connected, and establishes a new + # connection with the database. Implementors should define private #reconnect + # instead. + def reconnect!(restore_transactions: false) + retries_available = connection_retries + deadline = retry_deadline && Process.clock_gettime(Process::CLOCK_MONOTONIC) + retry_deadline + + @lock.synchronize do + reconnect + + enable_lazy_transactions! + @raw_connection_dirty = false + @last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @verified = true + + reset_transaction(restore: restore_transactions) do + clear_cache!(new_connection: true) + attempt_configure_connection + end + rescue => original_exception + translated_exception = translate_exception_class(original_exception, nil, nil) + retry_deadline_exceeded = deadline && deadline < Process.clock_gettime(Process::CLOCK_MONOTONIC) + + if !retry_deadline_exceeded && retries_available > 0 + retries_available -= 1 + + if retryable_connection_error?(translated_exception) + backoff(connection_retries - retries_available) + retry + end + end + + @last_activity = nil + @verified = false + + raise translated_exception + end + end + + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. + def disconnect! + @lock.synchronize do + clear_cache!(new_connection: true) + reset_transaction + @raw_connection_dirty = false + end + end + + # Immediately forget this connection ever existed. Unlike disconnect!, + # this will not communicate with the server. + # + # After calling this method, the behavior of all other methods becomes + # undefined. This is called internally just before a forked process gets + # rid of a connection that belonged to its parent. + def discard! + # This should be overridden by concrete adapters. + end + + # Reset the state of this connection, directing the DBMS to clear + # transactions and other connection-related server-side state. Usually a + # database-dependent operation. + # + # If a database driver or protocol does not support such a feature, + # implementors may alias this to #reconnect!. Otherwise, implementors + # should call super immediately after resetting the connection (and while + # still holding @lock). + def reset! + clear_cache!(new_connection: true) + reset_transaction + attempt_configure_connection + end + + # Removes the connection from the pool and disconnect it. + def throw_away! + pool.remove self + disconnect! + end + + # Clear any caching the database adapter may be doing. + def clear_cache!(new_connection: false) + if @statements + @lock.synchronize do + if new_connection + @statements.reset + else + @statements.clear + end + end + end + end + + # Returns true if its required to reload the connection between requests for development mode. + def requires_reloading? + false + end + + # Checks whether the connection to the database is still active (i.e. not stale). + # This is done under the hood by calling #active?. If the connection + # is no longer active, then this method will reconnect to the database. + def verify! + unless active? + @lock.synchronize do + if @unconfigured_connection + @raw_connection = @unconfigured_connection + @unconfigured_connection = nil + attempt_configure_connection + @last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @verified = true + return + end + + reconnect!(restore_transactions: true) + end + end + + @verified = true + end + + def connect! + verify! + self + end + + def clean! # :nodoc: + @raw_connection_dirty = false + @verified = nil + end + + # Provides access to the underlying database driver for this adapter. For + # example, this method returns a Mysql2::Client object in case of Mysql2Adapter, + # and a PG::Connection object in case of PostgreSQLAdapter. + # + # This is useful for when you need to call a proprietary method such as + # PostgreSQL's lo_* methods. + # + # Active Record cannot track if the database is getting modified using + # this client. If that is the case, generally you'll want to invalidate + # the query cache using +ActiveRecord::Base.clear_query_cache+. + def raw_connection + with_raw_connection do |conn| + disable_lazy_transactions! + @raw_connection_dirty = true + conn + end + end + + def default_uniqueness_comparison(attribute, value) # :nodoc: + attribute.eq(value) + end + + def case_sensitive_comparison(attribute, value) # :nodoc: + attribute.eq(value) + end + + def case_insensitive_comparison(attribute, value) # :nodoc: + column = column_for_attribute(attribute) + + if can_perform_case_insensitive_comparison_for?(column) + attribute.lower.eq(attribute.relation.lower(value)) + else + attribute.eq(value) + end + end + + def can_perform_case_insensitive_comparison_for?(column) + true + end + private :can_perform_case_insensitive_comparison_for? + + # Check the connection back in to the connection pool + def close + pool.checkin self + end + + def default_index_type?(index) # :nodoc: + index.using.nil? + end + + # Called by ActiveRecord::InsertAll, + # Passed an instance of ActiveRecord::InsertAll::Builder, + # This method implements standard bulk inserts for all databases, but + # should be overridden by adapters to implement common features with + # non-standard syntax like handling duplicates or returning values. + def build_insert_sql(insert) # :nodoc: + if insert.skip_duplicates? || insert.update_duplicates? + raise NotImplementedError, "#{self.class} should define `build_insert_sql` to implement adapter-specific logic for handling duplicates during INSERT" + end + + "INSERT #{insert.into} #{insert.values_list}" + end + + def get_database_version # :nodoc: + end + + def database_version # :nodoc: + pool.server_version(self) + end + + def check_version # :nodoc: + end + + # Returns the version identifier of the schema currently available in + # the database. This is generally equal to the number of the highest- + # numbered migration that has been executed, or 0 if no schema + # information is present / the database is empty. + def schema_version + pool.migration_context.current_version + end + + class << self + def register_class_with_precision(mapping, key, klass, **kwargs) # :nodoc: + mapping.register_type(key) do |*args| + precision = extract_precision(args.last) + klass.new(precision: precision, **kwargs) + end + end + + def extended_type_map(default_timezone:) # :nodoc: + Type::TypeMap.new(self::TYPE_MAP).tap do |m| + register_class_with_precision m, %r(\A[^\(]*time)i, Type::Time, timezone: default_timezone + register_class_with_precision m, %r(\A[^\(]*datetime)i, Type::DateTime, timezone: default_timezone + m.alias_type %r(\A[^\(]*timestamp)i, "datetime" + end + end + + private + def initialize_type_map(m) + register_class_with_limit m, %r(boolean)i, Type::Boolean + register_class_with_limit m, %r(char)i, Type::String + register_class_with_limit m, %r(binary)i, Type::Binary + register_class_with_limit m, %r(text)i, Type::Text + register_class_with_precision m, %r(date)i, Type::Date + register_class_with_precision m, %r(time)i, Type::Time + register_class_with_precision m, %r(datetime)i, Type::DateTime + register_class_with_limit m, %r(float)i, Type::Float + register_class_with_limit m, %r(int)i, Type::Integer + + m.alias_type %r(blob)i, "binary" + m.alias_type %r(clob)i, "text" + m.alias_type %r(timestamp)i, "datetime" + m.alias_type %r(numeric)i, "decimal" + m.alias_type %r(number)i, "decimal" + m.alias_type %r(double)i, "float" + + m.register_type %r(^json)i, Type::Json.new + + m.register_type(%r(decimal)i) do |sql_type| + scale = extract_scale(sql_type) + precision = extract_precision(sql_type) + + if scale == 0 + # FIXME: Remove this class as well + Type::DecimalWithoutScale.new(precision: precision) + else + Type::Decimal.new(precision: precision, scale: scale) + end + end + end + + def register_class_with_limit(mapping, key, klass) + mapping.register_type(key) do |*args| + limit = extract_limit(args.last) + klass.new(limit: limit) + end + end + + def extract_scale(sql_type) + case sql_type + when /\((\d+)\)/ then 0 + when /\((\d+)(,(\d+))\)/ then $3.to_i + end + end + + def extract_precision(sql_type) + $1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/ + end + + def extract_limit(sql_type) + $1.to_i if sql_type =~ /\((.*)\)/ + end + end + + TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) } + EXTENDED_TYPE_MAPS = Concurrent::Map.new + + private + def reconnect_can_restore_state? + transaction_manager.restorable? && !@raw_connection_dirty + end + + # Lock the monitor, ensure we're properly connected and + # transactions are materialized, and then yield the underlying + # raw connection object. + # + # If +allow_retry+ is true, a connection-related exception will + # cause an automatic reconnect and re-run of the block, up to + # the connection's configured +connection_retries+ setting + # and the configured +retry_deadline+ limit. (Note that when + # +allow_retry+ is true, it's possible to return without having marked + # the connection as verified. If the block is guaranteed to exercise the + # connection, consider calling `verified!` to avoid needless + # verification queries in subsequent calls.) + # + # If +materialize_transactions+ is false, the block will be run without + # ensuring virtual transactions have been materialized in the DB + # server's state. The active transaction will also remain clean + # (if it is not already dirty), meaning it's able to be restored + # by reconnecting and opening an equivalent-depth set of new + # transactions. This should only be used by transaction control + # methods, and internal transaction-agnostic queries. + # + ### + # + # It's not the primary use case, so not something to optimize + # for, but note that this method does need to be re-entrant: + # +materialize_transactions+ will re-enter if it has work to do, + # and the yield block can also do so under some circumstances. + # + # In the latter case, we really ought to guarantee the inner + # call will not reconnect (which would interfere with the + # still-yielded connection in the outer block), but we currently + # provide no special enforcement there. + # + def with_raw_connection(allow_retry: false, materialize_transactions: true) + @lock.synchronize do + connect! if @raw_connection.nil? && reconnect_can_restore_state? + + self.materialize_transactions if materialize_transactions + + retries_available = allow_retry ? connection_retries : 0 + deadline = retry_deadline && Process.clock_gettime(Process::CLOCK_MONOTONIC) + retry_deadline + reconnectable = reconnect_can_restore_state? + + if @verified + # Cool, we're confident the connection's ready to use. (Note this might have + # become true during the above #materialize_transactions.) + elsif (last_activity = seconds_since_last_activity) && last_activity < verify_timeout + # We haven't actually verified the connection since we acquired it, but it + # has been used very recently. We're going to assume it's still okay. + elsif reconnectable + if allow_retry + # Not sure about the connection yet, but if anything goes wrong we can + # just reconnect and re-run our query + else + # We can reconnect if needed, but we don't trust the upcoming query to be + # safely re-runnable: let's verify the connection to be sure + verify! + end + else + # We don't know whether the connection is okay, but it also doesn't matter: + # we wouldn't be able to reconnect anyway. We're just going to run our query + # and hope for the best. + end + + begin + yield @raw_connection + rescue => original_exception + translated_exception = translate_exception_class(original_exception, nil, nil) + invalidate_transaction(translated_exception) + retry_deadline_exceeded = deadline && deadline < Process.clock_gettime(Process::CLOCK_MONOTONIC) + + if !retry_deadline_exceeded && retries_available > 0 + retries_available -= 1 + + if retryable_query_error?(translated_exception) + backoff(connection_retries - retries_available) + retry + elsif reconnectable && retryable_connection_error?(translated_exception) + reconnect!(restore_transactions: true) + # Only allowed to reconnect once, because reconnect! has its own retry + # loop + reconnectable = false + retry + end + end + + unless retryable_query_error?(translated_exception) + # Barring a known-retryable error inside the query (regardless of + # whether we were in a _position_ to retry it), we should infer that + # there's likely a real problem with the connection. + @last_activity = nil + @verified = false + end + + raise translated_exception + ensure + dirty_current_transaction if materialize_transactions + end + end + end + + # Mark the connection as verified. Call this inside a + # `with_raw_connection` block only when the block is guaranteed to + # exercise the raw connection. + def verified! + @last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @verified = true + end + + def retryable_connection_error?(exception) + (exception.is_a?(ConnectionNotEstablished) && !exception.is_a?(ConnectionNotDefined)) || + exception.is_a?(ConnectionFailed) + end + + def invalidate_transaction(exception) + return unless exception.is_a?(TransactionRollbackError) + return unless savepoint_errors_invalidate_transactions? + + current_transaction.invalidate! + end + + def retryable_query_error?(exception) + # We definitely can't retry if we were inside an invalidated transaction. + return false if current_transaction.invalidated? + + exception.is_a?(Deadlocked) || exception.is_a?(LockWaitTimeout) + end + + def backoff(counter) + sleep 0.1 * counter + end + + def reconnect + raise NotImplementedError + end + + # Returns a raw connection for internal use with methods that are known + # to both be thread-safe and not rely upon actual server communication. + # This is useful for e.g. string escaping methods. + def any_raw_connection + @raw_connection || valid_raw_connection + end + + # Similar to any_raw_connection, but ensures it is validated and + # connected. Any method called on this result still needs to be + # independently thread-safe, so it probably shouldn't talk to the + # server... but some drivers fail if they know the connection has gone + # away. + def valid_raw_connection + (@verified && @raw_connection) || + # `allow_retry: false`, to force verification: the block won't + # raise, so a retry wouldn't help us get the valid connection we + # need. + with_raw_connection(allow_retry: false, materialize_transactions: false) { |conn| conn } + end + + def extended_type_map_key + if @default_timezone + { default_timezone: @default_timezone } + end + end + + def type_map + if key = extended_type_map_key + self.class::EXTENDED_TYPE_MAPS.compute_if_absent(key) do + self.class.extended_type_map(**key) + end + else + self.class::TYPE_MAP + end + end + + def translate_exception_class(native_error, sql, binds) + return native_error if native_error.is_a?(ActiveRecordError) + + message = "#{native_error.class.name}: #{native_error.message}" + + active_record_error = translate_exception( + native_error, message: message, sql: sql, binds: binds + ) + active_record_error.set_backtrace(native_error.backtrace) + active_record_error + end + + def log(sql, name = "SQL", binds = [], type_casted_binds = [], async: false, &block) # :doc: + instrumenter.instrument( + "sql.active_record", + sql: sql, + name: name, + binds: binds, + type_casted_binds: type_casted_binds, + async: async, + connection: self, + transaction: current_transaction.user_transaction.presence, + row_count: 0, + &block + ) + rescue ActiveRecord::StatementInvalid => ex + raise ex.set_query(sql, binds) + end + + def instrumenter # :nodoc: + ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] ||= ActiveSupport::Notifications.instrumenter + end + + def translate_exception(exception, message:, sql:, binds:) + # override in derived class + case exception + when RuntimeError, ActiveRecord::ActiveRecordError + exception + else + ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool) + end + end + + def column_for(table_name, column_name) + column_name = column_name.to_s + columns(table_name).detect { |c| c.name == column_name } || + raise(ActiveRecordError, "No such column: #{table_name}.#{column_name}") + end + + def column_for_attribute(attribute) + table_name = attribute.relation.name + schema_cache.columns_hash(table_name)[attribute.name.to_s] + end + + def collector + if prepared_statements + Arel::Collectors::Composite.new( + Arel::Collectors::SQLString.new, + Arel::Collectors::Bind.new, + ) + else + Arel::Collectors::SubstituteBinds.new( + self, + Arel::Collectors::SQLString.new, + ) + end + end + + def arel_visitor + Arel::Visitors::ToSql.new(self) + end + + def build_statement_pool + end + + # Builds the result object. + # + # This is an internal hook to make possible connection adapters to build + # custom result objects with connection-specific data. + def build_result(columns:, rows:, column_types: nil) + ActiveRecord::Result.new(columns, rows, column_types) + end + + # Perform any necessary initialization upon the newly-established + # @raw_connection -- this is the place to modify the adapter's + # connection settings, run queries to configure any application-global + # "session" variables, etc. + # + # Implementations may assume this method will only be called while + # holding @lock (or from #initialize). + def configure_connection + check_version + end + + def attempt_configure_connection + configure_connection + rescue + disconnect! + raise + end + + def default_prepared_statements + true + end + + def warning_ignored?(warning) + ActiveRecord.db_warnings_ignore.any? do |warning_matcher| + warning.message.match?(warning_matcher) || warning.code.to_s.match?(warning_matcher) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract_mysql_adapter.rb new file mode 100644 index 00000000..f520e785 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -0,0 +1,1027 @@ +# frozen_string_literal: true + +require "active_record/connection_adapters/abstract_adapter" +require "active_record/connection_adapters/statement_pool" +require "active_record/connection_adapters/mysql/column" +require "active_record/connection_adapters/mysql/database_statements" +require "active_record/connection_adapters/mysql/explain_pretty_printer" +require "active_record/connection_adapters/mysql/quoting" +require "active_record/connection_adapters/mysql/schema_creation" +require "active_record/connection_adapters/mysql/schema_definitions" +require "active_record/connection_adapters/mysql/schema_dumper" +require "active_record/connection_adapters/mysql/schema_statements" +require "active_record/connection_adapters/mysql/type_metadata" + +module ActiveRecord + module ConnectionAdapters + class AbstractMysqlAdapter < AbstractAdapter + include MySQL::DatabaseStatements + include MySQL::Quoting + include MySQL::SchemaStatements + + ## + # :singleton-method: + # By default, the Mysql2Adapter will consider all columns of type tinyint(1) + # as boolean. If you wish to disable this emulation you can add the following line + # to your application.rb file: + # + # ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false + class_attribute :emulate_booleans, default: true + + NATIVE_DATABASE_TYPES = { + primary_key: "bigint auto_increment PRIMARY KEY", + string: { name: "varchar", limit: 255 }, + text: { name: "text" }, + integer: { name: "int", limit: 4 }, + bigint: { name: "bigint" }, + float: { name: "float", limit: 24 }, + decimal: { name: "decimal" }, + datetime: { name: "datetime" }, + timestamp: { name: "timestamp" }, + time: { name: "time" }, + date: { name: "date" }, + binary: { name: "blob" }, + blob: { name: "blob" }, + boolean: { name: "tinyint", limit: 1 }, + json: { name: "json" }, + } + + class StatementPool < ConnectionAdapters::StatementPool # :nodoc: + private + def dealloc(stmt) + stmt.close + end + end + + class << self + def dbconsole(config, options = {}) + mysql_config = config.configuration_hash + + args = { + host: "--host", + port: "--port", + socket: "--socket", + username: "--user", + encoding: "--default-character-set", + sslca: "--ssl-ca", + sslcert: "--ssl-cert", + sslcapath: "--ssl-capath", + sslcipher: "--ssl-cipher", + sslkey: "--ssl-key", + ssl_mode: "--ssl-mode" + }.filter_map { |opt, arg| "#{arg}=#{mysql_config[opt]}" if mysql_config[opt] } + + if mysql_config[:password] && options[:include_password] + args << "--password=#{mysql_config[:password]}" + elsif mysql_config[:password] && !mysql_config[:password].to_s.empty? + args << "-p" + end + + args << config.database + + find_cmd_and_exec(ActiveRecord.database_cli[:mysql], *args) + end + end + + def get_database_version # :nodoc: + full_version_string = get_full_version + version_string = version_string(full_version_string) + Version.new(version_string, full_version_string) + end + + def mariadb? # :nodoc: + /mariadb/i.match?(full_version) + end + + def supports_bulk_alter? + true + end + + def supports_index_sort_order? + mariadb? ? database_version >= "10.8.1" : database_version >= "8.0.1" + end + + def supports_expression_index? + !mariadb? && database_version >= "8.0.13" + end + + def supports_transaction_isolation? + true + end + + def supports_restart_db_transaction? + true + end + + def supports_explain? + true + end + + def supports_indexes_in_create? + true + end + + def supports_foreign_keys? + true + end + + def supports_check_constraints? + if mariadb? + database_version >= "10.3.10" || (database_version < "10.3" && database_version >= "10.2.22") + else + database_version >= "8.0.16" + end + end + + def supports_views? + true + end + + def supports_datetime_with_precision? + true + end + + def supports_virtual_columns? + mariadb? || database_version >= "5.7.5" + end + + # See https://dev.mysql.com/doc/refman/en/optimizer-hints.html for more details. + def supports_optimizer_hints? + !mariadb? && database_version >= "5.7.7" + end + + def supports_common_table_expressions? + if mariadb? + database_version >= "10.2.1" + else + database_version >= "8.0.1" + end + end + + def supports_advisory_locks? + true + end + + def supports_insert_on_duplicate_skip? + true + end + + def supports_insert_on_duplicate_update? + true + end + + def supports_insert_returning? + mariadb? && database_version >= "10.5.0" + end + + def return_value_after_insert?(column) # :nodoc: + supports_insert_returning? ? column.auto_populated? : column.auto_increment? + end + + def get_advisory_lock(lock_name, timeout = 0) # :nodoc: + query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1 + end + + def release_advisory_lock(lock_name) # :nodoc: + query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1 + end + + def native_database_types + NATIVE_DATABASE_TYPES + end + + def index_algorithms + { + default: "ALGORITHM = DEFAULT", + copy: "ALGORITHM = COPY", + inplace: "ALGORITHM = INPLACE", + instant: "ALGORITHM = INSTANT", + } + end + + # HELPER METHODS =========================================== + + # Must return the MySQL error number from the exception, if the exception has an + # error number. + def error_number(exception) # :nodoc: + raise NotImplementedError + end + + # REFERENTIAL INTEGRITY ==================================== + + def disable_referential_integrity # :nodoc: + old = query_value("SELECT @@FOREIGN_KEY_CHECKS") + + begin + update("SET FOREIGN_KEY_CHECKS = 0") + yield + ensure + update("SET FOREIGN_KEY_CHECKS = #{old}") if active? + end + end + + #-- + # DATABASE STATEMENTS ====================================== + #++ + + def begin_db_transaction # :nodoc: + internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false) + end + + def begin_isolated_db_transaction(isolation) # :nodoc: + # From MySQL manual: The [SET TRANSACTION] statement applies only to the next single transaction performed within the session. + # So we don't need to implement #reset_isolation_level + execute_batch( + ["SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "BEGIN"], + "TRANSACTION", + allow_retry: true, + materialize_transactions: false, + ) + end + + def commit_db_transaction # :nodoc: + internal_execute("COMMIT", "TRANSACTION", allow_retry: false, materialize_transactions: true) + end + + def exec_rollback_db_transaction # :nodoc: + internal_execute("ROLLBACK", "TRANSACTION", allow_retry: false, materialize_transactions: true) + end + + def exec_restart_db_transaction # :nodoc: + internal_execute("ROLLBACK AND CHAIN", "TRANSACTION", allow_retry: false, materialize_transactions: true) + end + + def empty_insert_statement_value(primary_key = nil) # :nodoc: + "VALUES ()" + end + + # SCHEMA STATEMENTS ======================================== + + # Drops the database specified on the +name+ attribute + # and creates it again using the provided +options+. + def recreate_database(name, options = {}) + drop_database(name) + sql = create_database(name, options) + reconnect! + sql + end + + # Create a new MySQL database with optional :charset and :collation. + # Charset defaults to utf8mb4. + # + # Example: + # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin' + # create_database 'matt_development' + # create_database 'matt_development', charset: :big5 + def create_database(name, options = {}) + if options[:collation] + execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}" + elsif options[:charset] + execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset])}" + elsif row_format_dynamic_by_default? + execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET `utf8mb4`" + else + raise "Configure a supported :charset and ensure innodb_large_prefix is enabled to support indexes on varchar(255) string columns." + end + end + + # Drops a MySQL database. + # + # Example: + # drop_database('sebastian_development') + def drop_database(name) # :nodoc: + execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}" + end + + def current_database + query_value("SELECT database()", "SCHEMA") + end + + # Returns the database character set. + def charset + show_variable "character_set_database" + end + + # Returns the database collation strategy. + def collation + show_variable "collation_database" + end + + def table_comment(table_name) # :nodoc: + scope = quoted_scope(table_name) + + query_value(<<~SQL, "SCHEMA").presence + SELECT table_comment + FROM information_schema.tables + WHERE table_schema = #{scope[:schema]} + AND table_name = #{scope[:name]} + SQL + end + + def change_table_comment(table_name, comment_or_changes) # :nodoc: + comment = extract_new_comment_value(comment_or_changes) + comment = "" if comment.nil? + execute("ALTER TABLE #{quote_table_name(table_name)} COMMENT #{quote(comment)}") + end + + # Renames a table. + # + # Example: + # rename_table('octopuses', 'octopi') + def rename_table(table_name, new_name, **options) + validate_table_length!(new_name) unless options[:_uses_legacy_table_name] + schema_cache.clear_data_source_cache!(table_name.to_s) + schema_cache.clear_data_source_cache!(new_name.to_s) + execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}" + rename_table_indexes(table_name, new_name, **options) + end + + # Drops a table or tables from the database. + # + # [:force] + # Set to +:cascade+ to drop dependent objects as well. + # Defaults to false. + # [:if_exists] + # Set to +true+ to only drop the table if it exists. + # Defaults to false. + # [:temporary] + # Set to +true+ to drop temporary table. + # Defaults to false. + # + # Although this command ignores most +options+ and the block if one is given, + # it can be helpful to provide these in a migration's +change+ method so it can be reverted. + # In that case, +options+ and the block will be used by #create_table except if you provide more than one table which is not supported. + def drop_table(*table_names, **options) + table_names.each { |table_name| schema_cache.clear_data_source_cache!(table_name.to_s) } + execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{table_names.map { |table_name| quote_table_name(table_name) }.join(', ')}#{' CASCADE' if options[:force] == :cascade}" + end + + def rename_index(table_name, old_name, new_name) + if supports_rename_index? + validate_index_length!(table_name, new_name) + + execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}" + else + super + end + end + + def change_column_default(table_name, column_name, default_or_changes) # :nodoc: + execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}" + end + + def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc: + column = column_for(table_name, column_name) + return unless column + + default = extract_new_default_value(default_or_changes) + ChangeColumnDefaultDefinition.new(column, default) + end + + def change_column_null(table_name, column_name, null, default = nil) # :nodoc: + validate_change_column_null_argument!(null) + + unless null || default.nil? + execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") + end + + change_column table_name, column_name, nil, null: null + end + + def change_column_comment(table_name, column_name, comment_or_changes) # :nodoc: + comment = extract_new_comment_value(comment_or_changes) + change_column table_name, column_name, nil, comment: comment + end + + def change_column(table_name, column_name, type, **options) # :nodoc: + execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, **options)}") + end + + # Builds a ChangeColumnDefinition object. + # + # This definition object contains information about the column change that would occur + # if the same arguments were passed to #change_column. See #change_column for information about + # passing a +table_name+, +column_name+, +type+ and other options that can be passed. + def build_change_column_definition(table_name, column_name, type, **options) # :nodoc: + column = column_for(table_name, column_name) + type ||= column.sql_type + + unless options.key?(:default) + options[:default] = if column.default_function + -> { column.default_function } + else + column.default + end + end + + unless options.key?(:null) + options[:null] = column.null + end + + unless options.key?(:comment) + options[:comment] = column.comment + end + + if options[:collation] == :no_collation + options.delete(:collation) + else + options[:collation] ||= column.collation if text_type?(type) + end + + unless options.key?(:auto_increment) + options[:auto_increment] = column.auto_increment? + end + + td = create_table_definition(table_name) + cd = td.new_column_definition(column.name, type, **options) + ChangeColumnDefinition.new(cd, column.name) + end + + def rename_column(table_name, column_name, new_column_name) # :nodoc: + execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}") + rename_column_indexes(table_name, column_name, new_column_name) + end + + def add_index(table_name, column_name, **options) # :nodoc: + create_index = build_create_index_definition(table_name, column_name, **options) + return unless create_index + + execute schema_creation.accept(create_index) + end + + def build_create_index_definition(table_name, column_name, **options) # :nodoc: + index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options) + + return if if_not_exists && index_exists?(table_name, column_name, name: index.name) + + CreateIndexDefinition.new(index, algorithm) + end + + def add_sql_comment!(sql, comment) # :nodoc: + sql << " COMMENT #{quote(comment)}" if comment.present? + sql + end + + def foreign_keys(table_name) + raise ArgumentError unless table_name.present? + + scope = quoted_scope(table_name) + + # MySQL returns 1 row for each column of composite foreign keys. + fk_info = internal_exec_query(<<~SQL, "SCHEMA") + SELECT fk.referenced_table_name AS 'to_table', + fk.referenced_column_name AS 'primary_key', + fk.column_name AS 'column', + fk.constraint_name AS 'name', + fk.ordinal_position AS 'position', + rc.update_rule AS 'on_update', + rc.delete_rule AS 'on_delete' + FROM information_schema.referential_constraints rc + JOIN information_schema.key_column_usage fk + USING (constraint_schema, constraint_name) + WHERE fk.referenced_column_name IS NOT NULL + AND fk.table_schema = #{scope[:schema]} + AND fk.table_name = #{scope[:name]} + AND rc.constraint_schema = #{scope[:schema]} + AND rc.table_name = #{scope[:name]} + SQL + + grouped_fk = fk_info.group_by { |row| row["name"] }.values.each { |group| group.sort_by! { |row| row["position"] } } + grouped_fk.map do |group| + row = group.first + options = { + name: row["name"], + on_update: extract_foreign_key_action(row["on_update"]), + on_delete: extract_foreign_key_action(row["on_delete"]) + } + + if group.one? + options[:column] = unquote_identifier(row["column"]) + options[:primary_key] = row["primary_key"] + else + options[:column] = group.map { |row| unquote_identifier(row["column"]) } + options[:primary_key] = group.map { |row| row["primary_key"] } + end + + ForeignKeyDefinition.new(table_name, unquote_identifier(row["to_table"]), options) + end + end + + def check_constraints(table_name) + if supports_check_constraints? + scope = quoted_scope(table_name) + + sql = <<~SQL + SELECT cc.constraint_name AS 'name', + cc.check_clause AS 'expression' + FROM information_schema.check_constraints cc + JOIN information_schema.table_constraints tc + USING (constraint_schema, constraint_name) + WHERE tc.table_schema = #{scope[:schema]} + AND tc.table_name = #{scope[:name]} + AND cc.constraint_schema = #{scope[:schema]} + SQL + sql += " AND cc.table_name = #{scope[:name]}" if mariadb? + + chk_info = internal_exec_query(sql, "SCHEMA") + + chk_info.map do |row| + options = { + name: row["name"] + } + expression = row["expression"] + expression = expression[1..-2] if expression.start_with?("(") && expression.end_with?(")") + expression = strip_whitespace_characters(expression) + + unless mariadb? + # MySQL returns check constraints expression in an already escaped form. + # This leads to duplicate escaping later (e.g. when the expression is used in the SchemaDumper). + expression = expression.gsub("\\'", "'") + end + + CheckConstraintDefinition.new(table_name, expression, options) + end + else + raise NotImplementedError + end + end + + def table_options(table_name) # :nodoc: + create_table_info = create_table_info(table_name) + + # strip create_definitions and partition_options + # Be aware that `create_table_info` might not include any table options due to `NO_TABLE_OPTIONS` sql mode. + raw_table_options = create_table_info.sub(/\A.*\n\) ?/m, "").sub(/\n\/\*!.*\*\/\n\z/m, "").strip + + return if raw_table_options.empty? + + table_options = {} + + if / DEFAULT CHARSET=(?\w+)(?: COLLATE=(?\w+))?/ =~ raw_table_options + raw_table_options = $` + $' # before part + after part + table_options[:charset] = charset + table_options[:collation] = collation if collation + end + + # strip AUTO_INCREMENT + raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1') + + # strip COMMENT + if raw_table_options.sub!(/ COMMENT='.+'/, "") + table_options[:comment] = table_comment(table_name) + end + + table_options[:options] = raw_table_options unless raw_table_options == "ENGINE=InnoDB" + table_options + end + + # SHOW VARIABLES LIKE 'name' + def show_variable(name) + query_value("SELECT @@#{name}", "SCHEMA", materialize_transactions: false, allow_retry: true) + rescue ActiveRecord::StatementInvalid + nil + end + + def primary_keys(table_name) # :nodoc: + raise ArgumentError unless table_name.present? + + scope = quoted_scope(table_name) + + query_values(<<~SQL, "SCHEMA") + SELECT column_name + FROM information_schema.statistics + WHERE index_name = 'PRIMARY' + AND table_schema = #{scope[:schema]} + AND table_name = #{scope[:name]} + ORDER BY seq_in_index + SQL + end + + def case_sensitive_comparison(attribute, value) # :nodoc: + column = column_for_attribute(attribute) + + if column.collation && !column.case_sensitive? + attribute.eq(Arel::Nodes::Bin.new(value)) + else + super + end + end + + def can_perform_case_insensitive_comparison_for?(column) + column.case_sensitive? + end + private :can_perform_case_insensitive_comparison_for? + + # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use + # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for + # distinct queries, and requires that the ORDER BY include the distinct column. + # See https://dev.mysql.com/doc/refman/en/group-by-handling.html + def columns_for_distinct(columns, orders) # :nodoc: + order_columns = orders.compact_blank.map { |s| + # Convert Arel node to string + s = visitor.compile(s) unless s.is_a?(String) + # Remove any ASC/DESC modifiers + s.gsub(/\s+(?:ASC|DESC)\b/i, "") + }.compact_blank.map.with_index { |column, i| "#{column} AS alias_#{i}" } + + (order_columns << super).join(", ") + end + + def strict_mode? + self.class.type_cast_config_to_boolean(@config.fetch(:strict, true)) + end + + def default_index_type?(index) # :nodoc: + index.using == :btree || super + end + + def build_insert_sql(insert) # :nodoc: + # Can use any column as it will be assigned to itself. + no_op_column = quote_column_name(insert.keys.first) if insert.keys.first + + # MySQL 8.0.19 replaces `VALUES()` clauses with row and column alias names, see https://dev.mysql.com/worklog/task/?id=6312 . + # then MySQL 8.0.20 deprecates the `VALUES()` see https://dev.mysql.com/worklog/task/?id=13325 . + if supports_insert_raw_alias_syntax? + quoted_table_name = insert.model.quoted_table_name + values_alias = quote_table_name("#{insert.model.table_name.parameterize}_values") + sql = +"INSERT #{insert.into} #{insert.values_list} AS #{values_alias}" + + if insert.skip_duplicates? + if no_op_column + sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{quoted_table_name}.#{no_op_column}" + end + elsif insert.update_duplicates? + if insert.raw_update_sql? + sql = +"INSERT #{insert.into} #{insert.values_list} ON DUPLICATE KEY UPDATE #{insert.raw_update_sql}" + else + sql << " ON DUPLICATE KEY UPDATE " + sql << insert.touch_model_timestamps_unless { |column| "#{quoted_table_name}.#{column}<=>#{values_alias}.#{column}" } + sql << insert.updatable_columns.map { |column| "#{column}=#{values_alias}.#{column}" }.join(",") + end + end + else + sql = +"INSERT #{insert.into} #{insert.values_list}" + + if insert.skip_duplicates? + if no_op_column + sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}" + end + elsif insert.update_duplicates? + sql << " ON DUPLICATE KEY UPDATE " + if insert.raw_update_sql? + sql << insert.raw_update_sql + else + sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" } + sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",") + end + end + end + + sql << " RETURNING #{insert.returning}" if insert.returning + sql + end + + def check_version # :nodoc: + if database_version < "5.6.4" + raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.6.4." + end + end + + #-- + # QUOTING ================================================== + #++ + + # Quotes strings for use in SQL input. + def quote_string(string) + with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection| + connection.escape(string) + end + end + + class << self + def extended_type_map(default_timezone: nil, emulate_booleans:) # :nodoc: + super(default_timezone: default_timezone).tap do |m| + if emulate_booleans + m.register_type %r(^tinyint\(1\))i, Type::Boolean.new + end + end + end + + private + def initialize_type_map(m) + super + + m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1) + m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1) + m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1) + m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1) + m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1) + m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1) + m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1) + m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1) + m.register_type %r(^float)i, Type::Float.new(limit: 24) + m.register_type %r(^double)i, Type::Float.new(limit: 53) + + register_integer_type m, %r(^bigint)i, limit: 8 + register_integer_type m, %r(^int)i, limit: 4 + register_integer_type m, %r(^mediumint)i, limit: 3 + register_integer_type m, %r(^smallint)i, limit: 2 + register_integer_type m, %r(^tinyint)i, limit: 1 + + m.alias_type %r(year)i, "integer" + m.alias_type %r(bit)i, "binary" + end + + def register_integer_type(mapping, key, **options) + mapping.register_type(key) do |sql_type| + if /\bunsigned\b/.match?(sql_type) + Type::UnsignedInteger.new(**options) + else + Type::Integer.new(**options) + end + end + end + + def extract_precision(sql_type) + if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type) + super || 0 + else + super + end + end + end + + EXTENDED_TYPE_MAPS = Concurrent::Map.new + EMULATE_BOOLEANS_TRUE = { emulate_booleans: true }.freeze + + private + def strip_whitespace_characters(expression) + expression.gsub('\\\n', "").gsub("x0A", "").squish + end + + def extended_type_map_key + if @default_timezone + { default_timezone: @default_timezone, emulate_booleans: emulate_booleans } + elsif emulate_booleans + EMULATE_BOOLEANS_TRUE + end + end + + def handle_warnings(sql) + return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0 + + warning_count = @raw_connection.warning_count + result = @raw_connection.query("SHOW WARNINGS") + result = [ + ["Warning", nil, "Query had warning_count=#{warning_count} but ‘SHOW WARNINGS’ did not return the warnings. Check MySQL logs or database configuration."], + ] if result.count == 0 + result.each do |level, code, message| + warning = SQLWarning.new(message, code, level, sql, @pool) + next if warning_ignored?(warning) + + ActiveRecord.db_warnings_action.call(warning) + end + end + + def warning_ignored?(warning) + warning.level == "Note" || super + end + + # See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html + ER_DB_CREATE_EXISTS = 1007 + ER_FILSORT_ABORT = 1028 + ER_DUP_ENTRY = 1062 + ER_SERVER_SHUTDOWN = 1053 + ER_NOT_NULL_VIOLATION = 1048 + ER_NO_REFERENCED_ROW = 1216 + ER_ROW_IS_REFERENCED = 1217 + ER_DO_NOT_HAVE_DEFAULT = 1364 + ER_ROW_IS_REFERENCED_2 = 1451 + ER_NO_REFERENCED_ROW_2 = 1452 + ER_DATA_TOO_LONG = 1406 + ER_OUT_OF_RANGE = 1264 + ER_LOCK_DEADLOCK = 1213 + ER_CANNOT_ADD_FOREIGN = 1215 + ER_CANNOT_CREATE_TABLE = 1005 + ER_LOCK_WAIT_TIMEOUT = 1205 + ER_QUERY_INTERRUPTED = 1317 + ER_CONNECTION_KILLED = 1927 + CR_SERVER_GONE_ERROR = 2006 + CR_SERVER_LOST = 2013 + ER_QUERY_TIMEOUT = 3024 + ER_FK_INCOMPATIBLE_COLUMNS = 3780 + ER_CLIENT_INTERACTION_TIMEOUT = 4031 + + def translate_exception(exception, message:, sql:, binds:) + case error_number(exception) + when nil + if exception.message.match?(/MySQL client is not connected/i) + ConnectionNotEstablished.new(exception, connection_pool: @pool) + else + super + end + when ER_CONNECTION_KILLED, ER_SERVER_SHUTDOWN, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT + ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool) + when ER_DB_CREATE_EXISTS + DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool) + when ER_DUP_ENTRY + RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool) + when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2 + InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool) + when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS + mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool) + when ER_CANNOT_CREATE_TABLE + if message.include?("errno: 150") + mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool) + else + super + end + when ER_DATA_TOO_LONG + ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool) + when ER_OUT_OF_RANGE + RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool) + when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT + NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool) + when ER_LOCK_DEADLOCK + Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool) + when ER_LOCK_WAIT_TIMEOUT + LockWaitTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool) + when ER_QUERY_TIMEOUT, ER_FILSORT_ABORT + StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool) + when ER_QUERY_INTERRUPTED + QueryCanceled.new(message, sql: sql, binds: binds, connection_pool: @pool) + else + super + end + end + + def change_column_for_alter(table_name, column_name, type, **options) + cd = build_change_column_definition(table_name, column_name, type, **options) + schema_creation.accept(cd) + end + + def rename_column_for_alter(table_name, column_name, new_column_name) + return rename_column_sql(table_name, column_name, new_column_name) if supports_rename_column? + + column = column_for(table_name, column_name) + options = { + default: column.default, + null: column.null, + auto_increment: column.auto_increment?, + comment: column.comment + } + + current_type = internal_exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"] + td = create_table_definition(table_name) + cd = td.new_column_definition(new_column_name, current_type, **options) + schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) + end + + def add_index_for_alter(table_name, column_name, **options) + index, algorithm, _ = add_index_options(table_name, column_name, **options) + algorithm = ", #{algorithm}" if algorithm + + "ADD #{schema_creation.accept(index)}#{algorithm}" + end + + def remove_index_for_alter(table_name, column_name = nil, **options) + index_name = index_name_for_remove(table_name, column_name, options) + "DROP INDEX #{quote_column_name(index_name)}" + end + + def supports_insert_raw_alias_syntax? + !mariadb? && database_version >= "8.0.19" + end + + def supports_rename_index? + if mariadb? + database_version >= "10.5.2" + else + database_version >= "5.7.6" + end + end + + def supports_rename_column? + if mariadb? + database_version >= "10.5.2" + else + database_version >= "8.0.3" + end + end + + def configure_connection + super + variables = @config.fetch(:variables, {}).stringify_keys + + # Increase timeout so the server doesn't disconnect us. + wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout]) + wait_timeout = 2147483 unless wait_timeout.is_a?(Integer) + variables["wait_timeout"] = wait_timeout + + defaults = [":default", :default].to_set + + # Make MySQL reject illegal values rather than truncating or blanking them, see + # https://dev.mysql.com/doc/refman/en/sql-mode.html#sqlmode_strict_all_tables + # If the user has provided another value for sql_mode, don't replace it. + if sql_mode = variables.delete("sql_mode") + sql_mode = quote(sql_mode) + elsif !defaults.include?(strict_mode?) + if strict_mode? + sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')" + else + sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')" + sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')" + sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')" + end + sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')" + end + sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode + + # NAMES does not have an equals sign, see + # https://dev.mysql.com/doc/refman/en/set-names.html + # (trailing comma because variable_assignments will always have content) + if @config[:encoding] + encoding = +"NAMES #{@config[:encoding]}" + encoding << " COLLATE #{@config[:collation]}" if @config[:collation] + encoding << ", " + end + + # Gather up all of the SET variables... + variable_assignments = variables.filter_map do |k, v| + if defaults.include?(v) + "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default + elsif !v.nil? + "@@SESSION.#{k} = #{quote(v)}" + end + end.join(", ") + + # ...and send them all in one query + raw_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA") + end + + def column_definitions(table_name) # :nodoc: + internal_exec_query("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") + end + + def create_table_info(table_name) # :nodoc: + internal_exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"] + end + + def arel_visitor + Arel::Visitors::MySQL.new(self) + end + + def build_statement_pool + StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit])) + end + + def mismatched_foreign_key_details(message:, sql:) + foreign_key_pat = + /Referencing column '(\w+)' and referenced/i =~ message ? $1 : '\w+' + + match = %r/ + (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?\w+)`?.+? + FOREIGN\s+KEY\s*\(`?(?#{foreign_key_pat})`?\)\s* + REFERENCES\s*(`?(?\w+)`?)\s*\(`?(?\w+)`?\) + /xmi.match(sql) + + options = {} + + if match + options[:table] = match[:table] + options[:foreign_key] = match[:foreign_key] + options[:target_table] = match[:target_table] + options[:primary_key] = match[:primary_key] + options[:primary_key_column] = column_for(match[:target_table], match[:primary_key]) + end + + options + end + + def mismatched_foreign_key(message, sql:, binds:, connection_pool:) + options = { + message: message, + sql: sql, + binds: binds, + connection_pool: connection_pool + } + + if sql + options.update mismatched_foreign_key_details(message: message, sql: sql) + else + options[:query_parser] = ->(sql) { mismatched_foreign_key_details(message: message, sql: sql) } + end + + MismatchedForeignKey.new(**options) + end + + def version_string(full_version_string) + if full_version_string && matches = full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/) + matches[1] + else + raise DatabaseVersionError, "Unable to parse MySQL version from #{full_version_string.inspect}" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/column.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/column.rb new file mode 100644 index 00000000..4c05be41 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/column.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +module ActiveRecord + # :stopdoc: + module ConnectionAdapters + # An abstract definition of a column in a table. + class Column + include Deduplicable + + attr_reader :name, :default, :sql_type_metadata, :null, :default_function, :collation, :comment + + delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true + + # Instantiates a new column in the table. + # + # +name+ is the column's name, such as supplier_id in supplier_id bigint. + # +default+ is the type-casted default value, such as +new+ in sales_stage varchar(20) default 'new'. + # +sql_type_metadata+ is various information about the type of the column + # +null+ determines if this column allows +NULL+ values. + def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, **) + @name = name.freeze + @sql_type_metadata = sql_type_metadata + @null = null + @default = default + @default_function = default_function + @collation = collation + @comment = comment + end + + def has_default? + !default.nil? || default_function + end + + def bigint? + /\Abigint\b/.match?(sql_type) + end + + # Returns the human name of the column name. + # + # ===== Examples + # Column.new('sales_stage', ...).human_name # => 'Sales stage' + def human_name + Base.human_attribute_name(@name) + end + + def init_with(coder) + @name = coder["name"] + @sql_type_metadata = coder["sql_type_metadata"] + @null = coder["null"] + @default = coder["default"] + @default_function = coder["default_function"] + @collation = coder["collation"] + @comment = coder["comment"] + end + + def encode_with(coder) + coder["name"] = @name + coder["sql_type_metadata"] = @sql_type_metadata + coder["null"] = @null + coder["default"] = @default + coder["default_function"] = @default_function + coder["collation"] = @collation + coder["comment"] = @comment + end + + # whether the column is auto-populated by the database using a sequence + def auto_incremented_by_db? + false + end + + def auto_populated? + auto_incremented_by_db? || default_function + end + + def ==(other) + other.is_a?(Column) && + name == other.name && + default == other.default && + sql_type_metadata == other.sql_type_metadata && + null == other.null && + default_function == other.default_function && + collation == other.collation && + comment == other.comment + end + alias :eql? :== + + def hash + Column.hash ^ + name.hash ^ + name.encoding.hash ^ + default.hash ^ + sql_type_metadata.hash ^ + null.hash ^ + default_function.hash ^ + collation.hash ^ + comment.hash + end + + def virtual? + false + end + + private + def deduplicated + @name = -name + @sql_type_metadata = sql_type_metadata.deduplicate if sql_type_metadata + @default = -default if default + @default_function = -default_function if default_function + @collation = -collation if collation + @comment = -comment if comment + super + end + end + + class NullColumn < Column + def initialize(name, **) + super(name, nil) + end + end + end + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/deduplicable.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/deduplicable.rb new file mode 100644 index 00000000..030fd171 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/deduplicable.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters # :nodoc: + module Deduplicable + extend ActiveSupport::Concern + + module ClassMethods + def registry + @registry ||= {} + end + + def new(*, **) + super.deduplicate + end + end + + def deduplicate + self.class.registry[self] ||= deduplicated + end + alias :-@ :deduplicate + + private + def deduplicated + freeze + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/column.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/column.rb new file mode 100644 index 00000000..0d4b0225 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/column.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module MySQL + class Column < ConnectionAdapters::Column # :nodoc: + delegate :extra, to: :sql_type_metadata, allow_nil: true + + def unsigned? + /\bunsigned(?: zerofill)?\z/.match?(sql_type) + end + + def case_sensitive? + collation && !collation.end_with?("_ci") + end + + def auto_increment? + extra == "auto_increment" + end + alias_method :auto_incremented_by_db?, :auto_increment? + + def virtual? + /\b(?:VIRTUAL|STORED|PERSISTENT)\b/.match?(extra) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/database_statements.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/database_statements.rb new file mode 100644 index 00000000..a358cd2b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/database_statements.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module MySQL + module DatabaseStatements + READ_QUERY = AbstractAdapter.build_read_query_regexp( + :desc, :describe, :set, :show, :use, :kill + ) # :nodoc: + private_constant :READ_QUERY + + # https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_current-timestamp + # https://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-syntax.html + HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)", retryable: true).freeze # :nodoc: + private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP + + def write_query?(sql) # :nodoc: + !READ_QUERY.match?(sql) + rescue ArgumentError # Invalid encoding + !READ_QUERY.match?(sql.b) + end + + def high_precision_current_timestamp + HIGH_PRECISION_CURRENT_TIMESTAMP + end + + def explain(arel, binds = [], options = []) + sql = build_explain_clause(options) + " " + to_sql(arel, binds) + start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + result = internal_exec_query(sql, "EXPLAIN", binds) + elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start + + MySQL::ExplainPrettyPrinter.new.pp(result, elapsed) + end + + def build_explain_clause(options = []) + return "EXPLAIN" if options.empty? + + explain_clause = "EXPLAIN #{options.join(" ").upcase}" + + if analyze_without_explain? && explain_clause.include?("ANALYZE") + explain_clause.sub("EXPLAIN ", "") + else + explain_clause + end + end + + private + # https://mariadb.com/kb/en/analyze-statement/ + def analyze_without_explain? + mariadb? && database_version >= "10.1.0" + end + + def default_insert_value(column) + super unless column.auto_increment? + end + + def returning_column_values(result) + if supports_insert_returning? + result.rows.first + else + super + end + end + + def combine_multi_statements(total_sql) + total_sql.each_with_object([]) do |sql, total_sql_chunks| + previous_packet = total_sql_chunks.last + if max_allowed_packet_reached?(sql, previous_packet) + total_sql_chunks << +sql + else + previous_packet << ";\n" + previous_packet << sql + end + end + end + + def max_allowed_packet_reached?(current_packet, previous_packet) + if current_packet.bytesize > max_allowed_packet + raise ActiveRecordError, + "Fixtures set is too large #{current_packet.bytesize}. Consider increasing the max_allowed_packet variable." + elsif previous_packet.nil? + true + else + (current_packet.bytesize + previous_packet.bytesize + 2) > max_allowed_packet + end + end + + def max_allowed_packet + @max_allowed_packet ||= show_variable("max_allowed_packet") + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb new file mode 100644 index 00000000..ed3e6813 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module MySQL + class ExplainPrettyPrinter # :nodoc: + # Pretty prints the result of an EXPLAIN in a way that resembles the output of the + # MySQL shell: + # + # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ + # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | + # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ + # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | | + # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where | + # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ + # 2 rows in set (0.00 sec) + # + # This is an exercise in Ruby hyperrealism :). + def pp(result, elapsed) + widths = compute_column_widths(result) + separator = build_separator(widths) + + pp = [] + + pp << separator + pp << build_cells(result.columns, widths) + pp << separator + + result.rows.each do |row| + pp << build_cells(row, widths) + end + + pp << separator + pp << build_footer(result.rows.length, elapsed) + + pp.join("\n") + "\n" + end + + private + def compute_column_widths(result) + [].tap do |widths| + result.columns.each_with_index do |column, i| + cells_in_column = [column] + result.rows.map { |r| r[i].nil? ? "NULL" : r[i].to_s } + widths << cells_in_column.map(&:length).max + end + end + end + + def build_separator(widths) + padding = 1 + "+" + widths.map { |w| "-" * (w + (padding * 2)) }.join("+") + "+" + end + + def build_cells(items, widths) + cells = [] + items.each_with_index do |item, i| + item = "NULL" if item.nil? + justifier = item.is_a?(Numeric) ? "rjust" : "ljust" + cells << item.to_s.public_send(justifier, widths[i]) + end + "| " + cells.join(" | ") + " |" + end + + def build_footer(nrows, elapsed) + rows_label = nrows == 1 ? "row" : "rows" + "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/quoting.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/quoting.rb new file mode 100644 index 00000000..70d99d28 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/quoting.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +require "active_support/time_with_zone" + +module ActiveRecord + module ConnectionAdapters + module MySQL + module Quoting # :nodoc: + extend ActiveSupport::Concern + + QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc: + QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc: + + module ClassMethods # :nodoc: + def column_name_matcher + / + \A + ( + (?: + # `table_name`.`column_name` | function(one or no argument) + ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\)) + ) + (?:(?:\s+AS)?\s+(?:\w+|`\w+`))? + ) + (?:\s*,\s*\g<1>)* + \z + /ix + end + + def column_name_with_order_matcher + / + \A + ( + (?: + # `table_name`.`column_name` | function(one or no argument) + ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\)) + ) + (?:\s+COLLATE\s+(?:\w+|"\w+"))? + (?:\s+ASC|\s+DESC)? + ) + (?:\s*,\s*\g<1>)* + \z + /ix + end + + def quote_column_name(name) + QUOTED_COLUMN_NAMES[name] ||= "`#{name.to_s.gsub('`', '``')}`".freeze + end + + def quote_table_name(name) + QUOTED_TABLE_NAMES[name] ||= "`#{name.to_s.gsub('`', '``').gsub(".", "`.`")}`".freeze + end + end + + def cast_bound_value(value) + case value + when Rational + value.to_f.to_s + when Numeric + value.to_s + when BigDecimal + value.to_s("F") + when true + "1" + when false + "0" + else + value + end + end + + def unquoted_true + 1 + end + + def unquoted_false + 0 + end + + def quoted_binary(value) + "x'#{value.hex}'" + end + + def unquote_identifier(identifier) + if identifier && identifier.start_with?("`") + identifier[1..-2] + else + identifier + end + end + + # Override +type_cast+ we pass to mysql2 Date and Time objects instead + # of Strings since MySQL adapters are able to handle those classes more efficiently. + def type_cast(value) # :nodoc: + case value + when ActiveSupport::TimeWithZone + # We need to check explicitly for ActiveSupport::TimeWithZone because + # we need to transform it to Time objects but we don't want to + # transform Time objects to themselves. + if default_timezone == :utc + value.getutc + else + value.getlocal + end + when Time + if default_timezone == :utc + value.utc? ? value : value.getutc + else + value.utc? ? value.getlocal : value + end + when Date + value + else + super + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/schema_creation.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/schema_creation.rb new file mode 100644 index 00000000..cc438921 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/schema_creation.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module MySQL + class SchemaCreation < SchemaCreation # :nodoc: + delegate :add_sql_comment!, :mariadb?, to: :@conn, private: true + + private + def visit_DropForeignKey(name) + "DROP FOREIGN KEY #{name}" + end + + def visit_DropCheckConstraint(name) + "DROP #{mariadb? ? 'CONSTRAINT' : 'CHECK'} #{name}" + end + + def visit_AddColumnDefinition(o) + add_column_position!(super, column_options(o.column)) + end + + def visit_ChangeColumnDefinition(o) + change_column_sql = +"CHANGE #{quote_column_name(o.name)} #{accept(o.column)}" + add_column_position!(change_column_sql, column_options(o.column)) + end + + def visit_ChangeColumnDefaultDefinition(o) + sql = +"ALTER COLUMN #{quote_column_name(o.column.name)} " + if o.default.nil? && !o.column.null + sql << "DROP DEFAULT" + else + sql << "SET DEFAULT #{quote_default_expression(o.default, o.column)}" + end + end + + def visit_CreateIndexDefinition(o) + sql = visit_IndexDefinition(o.index, true) + sql << " #{o.algorithm}" if o.algorithm + sql + end + + def visit_IndexDefinition(o, create = false) + index_type = o.type&.to_s&.upcase || o.unique && "UNIQUE" + + sql = create ? ["CREATE"] : [] + sql << index_type if index_type + sql << "INDEX" + sql << quote_column_name(o.name) + sql << "USING #{o.using}" if o.using + sql << "ON #{quote_table_name(o.table)}" if create + sql << "(#{quoted_columns(o)})" + + add_sql_comment!(sql.join(" "), o.comment) + end + + def add_table_options!(create_sql, o) + create_sql << " DEFAULT CHARSET=#{o.charset}" if o.charset + create_sql << " COLLATE=#{o.collation}" if o.collation + add_sql_comment!(super, o.comment) + end + + def add_column_options!(sql, options) + # By default, TIMESTAMP columns are NOT NULL, cannot contain NULL values, + # and assigning NULL assigns the current timestamp. To permit a TIMESTAMP + # column to contain NULL, explicitly declare it with the NULL attribute. + # See https://dev.mysql.com/doc/refman/en/timestamp-initialization.html + if /\Atimestamp\b/.match?(options[:column].sql_type) && !options[:primary_key] + sql << " NULL" unless options[:null] == false || options_include_default?(options) + end + + if charset = options[:charset] + sql << " CHARACTER SET #{charset}" + end + + if collation = options[:collation] + sql << " COLLATE #{collation}" + end + + if as = options[:as] + sql << " AS (#{as})" + if options[:stored] + sql << (mariadb? ? " PERSISTENT" : " STORED") + end + end + + add_sql_comment!(super, options[:comment]) + end + + def add_column_position!(sql, options) + if options[:first] + sql << " FIRST" + elsif options[:after] + sql << " AFTER #{quote_column_name(options[:after])}" + end + + sql + end + + def index_in_create(table_name, column_name, options) + index, _ = @conn.add_index_options(table_name, column_name, **options) + accept(index) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/schema_definitions.rb new file mode 100644 index 00000000..8c50b4af --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/schema_definitions.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module MySQL + module ColumnMethods + extend ActiveSupport::Concern + + ## + # :method: blob + # :call-seq: blob(*names, **options) + + ## + # :method: tinyblob + # :call-seq: tinyblob(*names, **options) + + ## + # :method: mediumblob + # :call-seq: mediumblob(*names, **options) + + ## + # :method: longblob + # :call-seq: longblob(*names, **options) + + ## + # :method: tinytext + # :call-seq: tinytext(*names, **options) + + ## + # :method: mediumtext + # :call-seq: mediumtext(*names, **options) + + ## + # :method: longtext + # :call-seq: longtext(*names, **options) + + ## + # :method: unsigned_integer + # :call-seq: unsigned_integer(*names, **options) + + ## + # :method: unsigned_bigint + # :call-seq: unsigned_bigint(*names, **options) + + included do + define_column_methods :blob, :tinyblob, :mediumblob, :longblob, + :tinytext, :mediumtext, :longtext, :unsigned_integer, :unsigned_bigint, + :unsigned_float, :unsigned_decimal + + deprecate :unsigned_float, :unsigned_decimal, deprecator: ActiveRecord.deprecator + end + end + + # = Active Record MySQL Adapter \Table Definition + class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + include ColumnMethods + + attr_reader :charset, :collation + + def initialize(conn, name, charset: nil, collation: nil, **) + super + @charset = charset + @collation = collation + end + + def new_column_definition(name, type, **options) # :nodoc: + case type + when :virtual + type = options[:type] + when :primary_key + type = :integer + options[:limit] ||= 8 + options[:primary_key] = true + when /\Aunsigned_(?.+)\z/ + type = $~[:type].to_sym + options[:unsigned] = true + end + + super + end + + private + def valid_column_definition_options + super + [:auto_increment, :charset, :as, :size, :unsigned, :first, :after, :type, :stored] + end + + def aliased_types(name, fallback) + fallback + end + + def integer_like_primary_key_type(type, options) + unless options[:auto_increment] == false + options[:auto_increment] = true + end + + type + end + end + + # = Active Record MySQL Adapter \Table + class Table < ActiveRecord::ConnectionAdapters::Table + include ColumnMethods + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/schema_dumper.rb new file mode 100644 index 00000000..da595356 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/schema_dumper.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module MySQL + class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc: + private + def prepare_column_options(column) + spec = super + spec[:unsigned] = "true" if column.unsigned? + spec[:auto_increment] = "true" if column.auto_increment? + + if /\A(?tiny|medium|long)(?:text|blob)/ =~ column.sql_type + spec = { size: size.to_sym.inspect }.merge!(spec) + end + + if @connection.supports_virtual_columns? && column.virtual? + spec[:as] = extract_expression_for_virtual_column(column) + spec[:stored] = "true" if /\b(?:STORED|PERSISTENT)\b/.match?(column.extra) + spec = { type: schema_type(column).inspect }.merge!(spec) + end + + spec + end + + def column_spec_for_primary_key(column) + spec = super + spec.delete(:auto_increment) if column.type == :integer && column.auto_increment? + spec + end + + def default_primary_key?(column) + super && column.auto_increment? && !column.unsigned? + end + + def explicit_primary_key_default?(column) + column.type == :integer && !column.auto_increment? + end + + def schema_type(column) + case column.sql_type + when /\Atimestamp\b/ + :timestamp + when /\A(?:enum|set)\b/ + column.sql_type + else + super + end + end + + def schema_limit(column) + super unless /\A(?:tiny|medium|long)?(?:text|blob)\b/.match?(column.sql_type) + end + + def schema_precision(column) + if /\Atime(?:stamp)?\b/.match?(column.sql_type) && column.precision == 0 + nil + elsif column.type == :datetime + column.precision == 0 ? "nil" : super + else + super + end + end + + def schema_collation(column) + if column.collation + @table_collation_cache ||= {} + @table_collation_cache[table_name] ||= + @connection.internal_exec_query("SHOW TABLE STATUS LIKE #{@connection.quote(table_name)}", "SCHEMA").first["Collation"] + column.collation.inspect if column.collation != @table_collation_cache[table_name] + end + end + + def extract_expression_for_virtual_column(column) + if @connection.mariadb? && @connection.database_version < "10.2.5" + create_table_info = @connection.send(:create_table_info, table_name) + column_name = @connection.quote_column_name(column.name) + if %r/#{column_name} #{Regexp.quote(column.sql_type)}(?: COLLATE \w+)? AS \((?.+?)\) #{column.extra}/ =~ create_table_info + $~[:expression].inspect + end + else + scope = @connection.send(:quoted_scope, table_name) + column_name = @connection.quote(column.name) + sql = "SELECT generation_expression FROM information_schema.columns" \ + " WHERE table_schema = #{scope[:schema]}" \ + " AND table_name = #{scope[:name]}" \ + " AND column_name = #{column_name}" + # Calling .inspect leads into issues with the query result + # which already returns escaped quotes. + # We remove the escape sequence from the result in order to deal with double escaping issues. + @connection.query_value(sql, "SCHEMA").gsub("\\'", "'").inspect + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/schema_statements.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/schema_statements.rb new file mode 100644 index 00000000..71ae4807 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/schema_statements.rb @@ -0,0 +1,307 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module MySQL + module SchemaStatements # :nodoc: + # Returns an array of indexes for the given table. + def indexes(table_name) + indexes = [] + current_index = nil + internal_exec_query("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA").each do |row| + if current_index != row["Key_name"] + next if row["Key_name"] == "PRIMARY" # skip the primary key + current_index = row["Key_name"] + + mysql_index_type = row["Index_type"].downcase.to_sym + case mysql_index_type + when :fulltext, :spatial + index_type = mysql_index_type + when :btree, :hash + index_using = mysql_index_type + end + + indexes << [ + row["Table"], + row["Key_name"], + row["Non_unique"].to_i == 0, + [], + lengths: {}, + orders: {}, + type: index_type, + using: index_using, + comment: row["Index_comment"].presence + ] + end + + if expression = row["Expression"] + expression = expression.gsub("\\'", "'") + expression = +"(#{expression})" unless expression.start_with?("(") + indexes.last[-2] << expression + indexes.last[-1][:expressions] ||= {} + indexes.last[-1][:expressions][expression] = expression + indexes.last[-1][:orders][expression] = :desc if row["Collation"] == "D" + else + indexes.last[-2] << row["Column_name"] + indexes.last[-1][:lengths][row["Column_name"]] = row["Sub_part"].to_i if row["Sub_part"] + indexes.last[-1][:orders][row["Column_name"]] = :desc if row["Collation"] == "D" + end + end + + indexes.map do |index| + options = index.pop + + if expressions = options.delete(:expressions) + orders = options.delete(:orders) + lengths = options.delete(:lengths) + + columns = index[-1].to_h { |name| + [ name.to_sym, expressions[name] || +quote_column_name(name) ] + } + + index[-1] = add_options_for_index_columns( + columns, order: orders, length: lengths + ).values.join(", ") + end + + IndexDefinition.new(*index, **options) + end + rescue StatementInvalid => e + if e.message.match?(/Table '.+' doesn't exist/) + [] + else + raise + end + end + + def remove_column(table_name, column_name, type = nil, **options) + if foreign_key_exists?(table_name, column: column_name) + remove_foreign_key(table_name, column: column_name) + end + super + end + + def create_table(table_name, options: default_row_format, **) + super + end + + def remove_foreign_key(from_table, to_table = nil, **options) + # RESTRICT is by default in MySQL. + options.delete(:on_update) if options[:on_update] == :restrict + options.delete(:on_delete) if options[:on_delete] == :restrict + super + end + + def internal_string_options_for_primary_key + super.tap do |options| + if !row_format_dynamic_by_default? && CHARSETS_OF_4BYTES_MAXLEN.include?(charset) + options[:collation] = collation.sub(/\A[^_]+/, "utf8") + end + end + end + + def update_table_definition(table_name, base) + MySQL::Table.new(table_name, base) + end + + def create_schema_dumper(options) + MySQL::SchemaDumper.create(self, options) + end + + # Maps logical Rails types to MySQL-specific data types. + def type_to_sql(type, limit: nil, precision: nil, scale: nil, size: limit_to_size(limit, type), unsigned: nil, **) + sql = + case type.to_s + when "integer" + integer_to_sql(limit) + when "text" + type_with_size_to_sql("text", size) + when "blob" + type_with_size_to_sql("blob", size) + when "binary" + if (0..0xfff) === limit + "varbinary(#{limit})" + else + type_with_size_to_sql("blob", size) + end + else + super + end + + sql = "#{sql} unsigned" if unsigned && type != :primary_key + sql + end + + def table_alias_length + 256 # https://dev.mysql.com/doc/refman/en/identifiers.html + end + + def schema_creation # :nodoc: + MySQL::SchemaCreation.new(self) + end + + private + CHARSETS_OF_4BYTES_MAXLEN = ["utf8mb4", "utf16", "utf16le", "utf32"] + + def row_format_dynamic_by_default? + if mariadb? + database_version >= "10.2.2" + else + database_version >= "5.7.9" + end + end + + def default_row_format + return if row_format_dynamic_by_default? + + unless defined?(@default_row_format) + if query_value("SELECT @@innodb_file_per_table = 1 AND @@innodb_file_format = 'Barracuda'") == 1 + @default_row_format = "ROW_FORMAT=DYNAMIC" + else + @default_row_format = nil + end + end + + @default_row_format + end + + def valid_primary_key_options + super + [:unsigned, :auto_increment] + end + + def create_table_definition(name, **options) + MySQL::TableDefinition.new(self, name, **options) + end + + def default_type(table_name, field_name) + match = create_table_info(table_name)&.match(/`#{field_name}` (.+) DEFAULT ('|\d+|[A-z]+)/) + default_pre = match[2] if match + + if default_pre == "'" + :string + elsif default_pre&.match?(/^\d+$/) + :integer + elsif default_pre&.match?(/^[A-z]+$/) + :function + end + end + + def new_column_from_field(table_name, field, _definitions) + field_name = field.fetch("Field") + type_metadata = fetch_type_metadata(field["Type"], field["Extra"]) + default, default_function = field["Default"], nil + + if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(default) + default = "#{default} ON UPDATE #{default}" if /on update CURRENT_TIMESTAMP/i.match?(field["Extra"]) + default, default_function = nil, default + elsif type_metadata.extra == "DEFAULT_GENERATED" + default = +"(#{default})" unless default.start_with?("(") + default = default.gsub("\\'", "'") + default, default_function = nil, default + elsif type_metadata.type == :text && default&.start_with?("'") + # strip and unescape quotes + default = default[1...-1].gsub("\\'", "'") + elsif default&.match?(/\A\d/) + # Its a number so we can skip the query to check if it is a function + elsif default && default_type(table_name, field_name) == :function + default, default_function = nil, default + end + + MySQL::Column.new( + field["Field"], + default, + type_metadata, + field["Null"] == "YES", + default_function, + collation: field["Collation"], + comment: field["Comment"].presence + ) + end + + def fetch_type_metadata(sql_type, extra = "") + MySQL::TypeMetadata.new(super(sql_type), extra: extra) + end + + def extract_foreign_key_action(specifier) + super unless specifier == "RESTRICT" + end + + def add_index_length(quoted_columns, **options) + lengths = options_for_index_columns(options[:length]) + quoted_columns.each do |name, column| + column << "(#{lengths[name]})" if lengths[name].present? + end + end + + def add_options_for_index_columns(quoted_columns, **options) + quoted_columns = add_index_length(quoted_columns, **options) + super + end + + def data_source_sql(name = nil, type: nil) + scope = quoted_scope(name, type: type) + + sql = +"SELECT table_name FROM information_schema.tables" + sql << " WHERE table_schema = #{scope[:schema]}" + + if scope[:name] + sql << " AND table_name = #{scope[:name]}" + sql << " AND table_name IN (SELECT table_name FROM information_schema.tables WHERE table_schema = #{scope[:schema]})" + end + + sql << " AND table_type = #{scope[:type]}" if scope[:type] + sql + end + + def quoted_scope(name = nil, type: nil) + schema, name = extract_schema_qualified_name(name) + scope = {} + scope[:schema] = schema ? quote(schema) : "database()" + scope[:name] = quote(name) if name + scope[:type] = quote(type) if type + scope + end + + def extract_schema_qualified_name(string) + schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/) + schema, name = nil, schema unless name + [schema, name] + end + + def type_with_size_to_sql(type, size) + case size&.to_s + when nil, "tiny", "medium", "long" + "#{size}#{type}" + else + raise ArgumentError, + "#{size.inspect} is invalid :size value. Only :tiny, :medium, and :long are allowed." + end + end + + def limit_to_size(limit, type) + case type.to_s + when "text", "blob", "binary" + case limit + when 0..0xff; "tiny" + when nil, 0x100..0xffff; nil + when 0x10000..0xffffff; "medium" + when 0x1000000..0xffffffff; "long" + else raise ArgumentError, "No #{type} type has byte size #{limit}" + end + end + end + + def integer_to_sql(limit) + case limit + when 1; "tinyint" + when 2; "smallint" + when 3; "mediumint" + when nil, 4; "int" + when 5..8; "bigint" + else raise ArgumentError, "No integer type has byte size #{limit}. Use a decimal with scale 0 instead." + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/type_metadata.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/type_metadata.rb new file mode 100644 index 00000000..a7232fa2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql/type_metadata.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module MySQL + class TypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc: + undef to_yaml if method_defined?(:to_yaml) + + include Deduplicable + + attr_reader :extra + + def initialize(type_metadata, extra: nil) + super(type_metadata) + @extra = extra + end + + def ==(other) + other.is_a?(TypeMetadata) && + __getobj__ == other.__getobj__ && + extra == other.extra + end + alias eql? == + + def hash + TypeMetadata.hash ^ + __getobj__.hash ^ + extra.hash + end + + private + def deduplicated + __setobj__(__getobj__.deduplicate) + @extra = -extra if extra + super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql2/database_statements.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql2/database_statements.rb new file mode 100644 index 00000000..26420f76 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql2/database_statements.rb @@ -0,0 +1,142 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module Mysql2 + module DatabaseStatements + # Returns an ActiveRecord::Result instance. + def select_all(*, **) # :nodoc: + if ExplainRegistry.collect? && prepared_statements + unprepared_statement { super } + else + super + end + end + + private + def execute_batch(statements, name = nil, **kwargs) + combine_multi_statements(statements).each do |statement| + raw_execute(statement, name, batch: true, **kwargs) + end + end + + def last_inserted_id(result) + if supports_insert_returning? + super + else + @raw_connection&.last_id + end + end + + def multi_statements_enabled? + flags = @config[:flags] + + if flags.is_a?(Array) + flags.include?("MULTI_STATEMENTS") + else + flags.anybits?(::Mysql2::Client::MULTI_STATEMENTS) + end + end + + def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false) + reset_multi_statement = if batch && !multi_statements_enabled? + raw_connection.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_ON) + true + end + + # Make sure we carry over any changes to ActiveRecord.default_timezone that have been + # made since we established the connection + raw_connection.query_options[:database_timezone] = default_timezone + + result = nil + if binds.nil? || binds.empty? + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + result = raw_connection.query(sql) + # Ref: https://github.com/brianmario/mysql2/pull/1383 + # As of mysql2 0.5.6 `#affected_rows` might raise Mysql2::Error if a prepared statement + # from that same connection was GCed while `#query` released the GVL. + # By avoiding to call `#affected_rows` when we have a result, we reduce the likeliness + # of hitting the bug. + @affected_rows_before_warnings = result&.size || raw_connection.affected_rows + end + elsif prepare + stmt = @statements[sql] ||= raw_connection.prepare(sql) + begin + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + result = stmt.execute(*type_casted_binds) + @affected_rows_before_warnings = stmt.affected_rows + end + rescue ::Mysql2::Error + @statements.delete(sql) + raise + end + else + stmt = raw_connection.prepare(sql) + + begin + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + result = stmt.execute(*type_casted_binds) + @affected_rows_before_warnings = stmt.affected_rows + end + + # Ref: https://github.com/brianmario/mysql2/pull/1383 + # by eagerly closing uncached prepared statements, we also reduce the chances of + # that bug happening. It can still happen if `#execute` is used as we have no callback + # to eagerly close the statement. + if result + result.instance_variable_set(:@_ar_stmt_to_close, stmt) + else + stmt.close + end + rescue ::Mysql2::Error + stmt.close + raise + end + end + + notification_payload[:affected_rows] = @affected_rows_before_warnings + notification_payload[:row_count] = result&.size || 0 + + raw_connection.abandon_results! + + verified! + handle_warnings(sql) + result + ensure + if reset_multi_statement && active? + raw_connection.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF) + end + end + + def cast_result(raw_result) + return ActiveRecord::Result.empty if raw_result.nil? + + fields = raw_result.fields + + result = if fields.empty? + ActiveRecord::Result.empty + else + ActiveRecord::Result.new(fields, raw_result.to_a) + end + + free_raw_result(raw_result) + + result + end + + def affected_rows(raw_result) + free_raw_result(raw_result) if raw_result + + @affected_rows_before_warnings + end + + def free_raw_result(raw_result) + raw_result.free + if stmt = raw_result.instance_variable_get(:@_ar_stmt_to_close) + stmt.close + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql2_adapter.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql2_adapter.rb new file mode 100644 index 00000000..02499832 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -0,0 +1,203 @@ +# frozen_string_literal: true + +require "active_record/connection_adapters/abstract_mysql_adapter" +require "active_record/connection_adapters/mysql2/database_statements" + +gem "mysql2", "~> 0.5" +require "mysql2" + +module ActiveRecord + module ConnectionAdapters + # = Active Record MySQL2 Adapter + class Mysql2Adapter < AbstractMysqlAdapter + ER_BAD_DB_ERROR = 1049 + ER_DBACCESS_DENIED_ERROR = 1044 + ER_ACCESS_DENIED_ERROR = 1045 + ER_CONN_HOST_ERROR = 2003 + ER_UNKNOWN_HOST_ERROR = 2005 + + ADAPTER_NAME = "Mysql2" + + include Mysql2::DatabaseStatements + + class << self + def new_client(config) + ::Mysql2::Client.new(config) + rescue ::Mysql2::Error => error + case error.error_number + when ER_BAD_DB_ERROR + raise ActiveRecord::NoDatabaseError.db_error(config[:database]) + when ER_DBACCESS_DENIED_ERROR, ER_ACCESS_DENIED_ERROR + raise ActiveRecord::DatabaseConnectionError.username_error(config[:username]) + when ER_CONN_HOST_ERROR, ER_UNKNOWN_HOST_ERROR + raise ActiveRecord::DatabaseConnectionError.hostname_error(config[:host]) + else + raise ActiveRecord::ConnectionNotEstablished, error.message + end + end + + private + def initialize_type_map(m) + super + + m.register_type(%r(char)i) do |sql_type| + limit = extract_limit(sql_type) + Type.lookup(:string, adapter: :mysql2, limit: limit) + end + + m.register_type %r(^enum)i, Type.lookup(:string, adapter: :mysql2) + m.register_type %r(^set)i, Type.lookup(:string, adapter: :mysql2) + end + end + + TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) } + + def initialize(...) + super + + @affected_rows_before_warnings = nil + @config[:flags] ||= 0 + + if @config[:flags].kind_of? Array + @config[:flags].push "FOUND_ROWS" + else + @config[:flags] |= ::Mysql2::Client::FOUND_ROWS + end + + @connection_parameters ||= @config + end + + def supports_json? + !mariadb? && database_version >= "5.7.8" + end + + def supports_comments? + true + end + + def supports_comments_in_create? + true + end + + def supports_savepoints? + true + end + + def savepoint_errors_invalidate_transactions? + true + end + + def supports_lazy_transactions? + true + end + + # HELPER METHODS =========================================== + + def error_number(exception) + exception.error_number if exception.respond_to?(:error_number) + end + + #-- + # CONNECTION MANAGEMENT ==================================== + #++ + + def connected? + !(@raw_connection.nil? || @raw_connection.closed?) + end + + def active? + if connected? + @lock.synchronize do + if @raw_connection&.ping + verified! + true + end + end + end || false + end + + alias :reset! :reconnect! + + # Disconnects from the database if already connected. + # Otherwise, this method does nothing. + def disconnect! + @lock.synchronize do + super + @raw_connection&.close + @raw_connection = nil + end + end + + def discard! # :nodoc: + @lock.synchronize do + super + @raw_connection&.automatic_close = false + @raw_connection = nil + end + end + + private + def text_type?(type) + TYPE_MAP.lookup(type).is_a?(Type::String) || TYPE_MAP.lookup(type).is_a?(Type::Text) + end + + def connect + @raw_connection = self.class.new_client(@connection_parameters) + rescue ConnectionNotEstablished => ex + raise ex.set_pool(@pool) + end + + def reconnect + @lock.synchronize do + @raw_connection&.close + @raw_connection = nil + connect + end + end + + def configure_connection + @raw_connection.query_options[:as] = :array + @raw_connection.query_options[:database_timezone] = default_timezone + super + end + + def full_version + database_version.full_version_string + end + + def get_full_version + any_raw_connection.server_info[:version] + end + + def translate_exception(exception, message:, sql:, binds:) + if exception.is_a?(::Mysql2::Error::TimeoutError) && !exception.error_number + ActiveRecord::AdapterTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool) + elsif exception.is_a?(::Mysql2::Error::ConnectionError) + if exception.message.match?(/MySQL client is not connected/i) + ActiveRecord::ConnectionNotEstablished.new(exception, connection_pool: @pool) + else + ActiveRecord::ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool) + end + else + super + end + end + + def default_prepared_statements + false + end + + ActiveRecord::Type.register(:immutable_string, adapter: :mysql2) do |_, **args| + Type::ImmutableString.new(true: "1", false: "0", **args) + end + + ActiveRecord::Type.register(:string, adapter: :mysql2) do |_, **args| + Type::String.new(true: "1", false: "0", **args) + end + + ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2) + end + + ActiveSupport.run_load_hooks(:active_record_mysql2adapter, Mysql2Adapter) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/pool_config.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/pool_config.rb new file mode 100644 index 00000000..ee5f34f6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/pool_config.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + class PoolConfig # :nodoc: + include MonitorMixin + + attr_reader :db_config, :role, :shard, :connection_descriptor + attr_writer :schema_reflection, :server_version + + def schema_reflection + @schema_reflection ||= SchemaReflection.new(db_config.lazy_schema_cache_path) + end + + INSTANCES = ObjectSpace::WeakMap.new + private_constant :INSTANCES + + class << self + def discard_pools! + INSTANCES.each_key(&:discard_pool!) + end + + def disconnect_all! + INSTANCES.each_key { |c| c.disconnect!(automatic_reconnect: true) } + end + end + + def initialize(connection_class, db_config, role, shard) + super() + @server_version = nil + self.connection_descriptor = connection_class + @db_config = db_config + @role = role + @shard = shard + @pool = nil + INSTANCES[self] = self + end + + def server_version(connection) + @server_version || synchronize { @server_version ||= connection.get_database_version } + end + + def connection_descriptor=(connection_descriptor) + case connection_descriptor + when ConnectionHandler::ConnectionDescriptor + @connection_descriptor = connection_descriptor + else + @connection_descriptor = ConnectionHandler::ConnectionDescriptor.new(connection_descriptor.name, connection_descriptor.primary_class?) + end + end + + def disconnect!(automatic_reconnect: false) + return unless @pool + + synchronize do + return unless @pool + + @pool.automatic_reconnect = automatic_reconnect + @pool.disconnect! + end + + nil + end + + def pool + @pool || synchronize { @pool ||= ConnectionAdapters::ConnectionPool.new(self) } + end + + def discard_pool! + return unless @pool + + synchronize do + return unless @pool + + @pool.discard! + @pool = nil + end + end + end + end +end + +ActiveSupport::ForkTracker.after_fork { ActiveRecord::ConnectionAdapters::PoolConfig.discard_pools! } diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/pool_manager.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/pool_manager.rb new file mode 100644 index 00000000..cb096943 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/pool_manager.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + class PoolManager # :nodoc: + def initialize + @role_to_shard_mapping = Hash.new { |h, k| h[k] = {} } + end + + def shard_names + @role_to_shard_mapping.values.flat_map { |shard_map| shard_map.keys }.uniq + end + + def role_names + @role_to_shard_mapping.keys + end + + def pool_configs(role = nil) + if role + @role_to_shard_mapping[role].values + else + @role_to_shard_mapping.flat_map { |_, shard_map| shard_map.values } + end + end + + def each_pool_config(role = nil, &block) + if role + @role_to_shard_mapping[role].each_value(&block) + else + @role_to_shard_mapping.each_value do |shard_map| + shard_map.each_value(&block) + end + end + end + + def remove_role(role) + @role_to_shard_mapping.delete(role) + end + + def remove_pool_config(role, shard) + @role_to_shard_mapping[role].delete(shard) + end + + def get_pool_config(role, shard) + @role_to_shard_mapping[role][shard] + end + + def set_pool_config(role, shard, pool_config) + if pool_config + @role_to_shard_mapping[role][shard] = pool_config + else + raise ArgumentError, "The `pool_config` for the :#{role} role and :#{shard} shard was `nil`. Please check your configuration. If you want your writing role to be something other than `:writing` set `config.active_record.writing_role` in your application configuration. The same setting should be applied for the `reading_role` if applicable." + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/column.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/column.rb new file mode 100644 index 00000000..926ab09d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/column.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + class Column < ConnectionAdapters::Column # :nodoc: + delegate :oid, :fmod, to: :sql_type_metadata + + def initialize(*, serial: nil, identity: nil, generated: nil, **) + super + @serial = serial + @identity = identity + @generated = generated + end + + def identity? + @identity + end + + def serial? + @serial + end + + def auto_incremented_by_db? + serial? || identity? + end + + def virtual? + # We assume every generated column is virtual, no matter the concrete type + @generated.present? + end + + def has_default? + super && !virtual? + end + + def array + sql_type_metadata.sql_type.end_with?("[]") + end + alias :array? :array + + def enum? + type == :enum + end + + def sql_type + super.delete_suffix("[]") + end + + def init_with(coder) + @serial = coder["serial"] + @identity = coder["identity"] + @generated = coder["generated"] + super + end + + def encode_with(coder) + coder["serial"] = @serial + coder["identity"] = @identity + coder["generated"] = @generated + super + end + + def ==(other) + other.is_a?(Column) && + super && + identity? == other.identity? && + serial? == other.serial? + end + alias :eql? :== + + def hash + Column.hash ^ + super.hash ^ + identity?.hash ^ + serial?.hash + end + end + end + PostgreSQLColumn = PostgreSQL::Column # :nodoc: + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/database_statements.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/database_statements.rb new file mode 100644 index 00000000..3a523ea6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -0,0 +1,231 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module DatabaseStatements + def explain(arel, binds = [], options = []) + sql = build_explain_clause(options) + " " + to_sql(arel, binds) + result = internal_exec_query(sql, "EXPLAIN", binds) + PostgreSQL::ExplainPrettyPrinter.new.pp(result) + end + + # Queries the database and returns the results in an Array-like object + def query(sql, name = nil) # :nodoc: + result = internal_execute(sql, name) + result.map_types!(@type_map_for_results).values + end + + READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp( + :close, :declare, :fetch, :move, :set, :show + ) # :nodoc: + private_constant :READ_QUERY + + def write_query?(sql) # :nodoc: + !READ_QUERY.match?(sql) + rescue ArgumentError # Invalid encoding + !READ_QUERY.match?(sql.b) + end + + # Executes an SQL statement, returning a PG::Result object on success + # or raising a PG::Error exception otherwise. + # + # Setting +allow_retry+ to true causes the db to reconnect and retry + # executing the SQL statement in case of a connection-related exception. + # This option should only be enabled for known idempotent queries. + # + # Note: the PG::Result object is manually memory managed; if you don't + # need it specifically, you may want consider the exec_query wrapper. + def execute(...) # :nodoc: + super + ensure + @notice_receiver_sql_warnings = [] + end + + def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc: + if use_insert_returning? || pk == false + super + else + result = internal_exec_query(sql, name, binds) + unless sequence_name + table_ref = extract_table_ref_from_insert_sql(sql) + if table_ref + pk = primary_key(table_ref) if pk.nil? + pk = suppress_composite_primary_key(pk) + sequence_name = default_sequence_name(table_ref, pk) + end + return result unless sequence_name + end + last_insert_id_result(sequence_name) + end + end + + # Begins a transaction. + def begin_db_transaction # :nodoc: + internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false) + end + + def begin_isolated_db_transaction(isolation) # :nodoc: + internal_execute("BEGIN ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "TRANSACTION", allow_retry: true, materialize_transactions: false) + end + + # Commits a transaction. + def commit_db_transaction # :nodoc: + internal_execute("COMMIT", "TRANSACTION", allow_retry: false, materialize_transactions: true) + end + + # Aborts a transaction. + def exec_rollback_db_transaction # :nodoc: + cancel_any_running_query + internal_execute("ROLLBACK", "TRANSACTION", allow_retry: false, materialize_transactions: true) + end + + def exec_restart_db_transaction # :nodoc: + cancel_any_running_query + internal_execute("ROLLBACK AND CHAIN", "TRANSACTION", allow_retry: false, materialize_transactions: true) + end + + # From https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT + HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP", retryable: true).freeze # :nodoc: + private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP + + def high_precision_current_timestamp + HIGH_PRECISION_CURRENT_TIMESTAMP + end + + def build_explain_clause(options = []) + return "EXPLAIN" if options.empty? + + "EXPLAIN (#{options.join(", ").upcase})" + end + + # Set when constraints will be checked for the current transaction. + # + # Not passing any specific constraint names will set the value for all deferrable constraints. + # + # [deferred] + # Valid values are +:deferred+ or +:immediate+. + # + # See https://www.postgresql.org/docs/current/sql-set-constraints.html + def set_constraints(deferred, *constraints) + unless %i[deferred immediate].include?(deferred) + raise ArgumentError, "deferred must be :deferred or :immediate" + end + + constraints = if constraints.empty? + "ALL" + else + constraints.map { |c| quote_table_name(c) }.join(", ") + end + execute("SET CONSTRAINTS #{constraints} #{deferred.to_s.upcase}") + end + + private + IDLE_TRANSACTION_STATUSES = [PG::PQTRANS_IDLE, PG::PQTRANS_INTRANS, PG::PQTRANS_INERROR] + private_constant :IDLE_TRANSACTION_STATUSES + + def cancel_any_running_query + return if @raw_connection.nil? || IDLE_TRANSACTION_STATUSES.include?(@raw_connection.transaction_status) + + @raw_connection.cancel + @raw_connection.block + rescue PG::Error + end + + def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false) + update_typemap_for_default_timezone + result = if prepare + begin + stmt_key = prepare_statement(sql, binds, raw_connection) + notification_payload[:statement_name] = stmt_key + raw_connection.exec_prepared(stmt_key, type_casted_binds) + rescue PG::FeatureNotSupported => error + if is_cached_plan_failure?(error) + # Nothing we can do if we are in a transaction because all commands + # will raise InFailedSQLTransaction + if in_transaction? + raise PreparedStatementCacheExpired.new(error.message, connection_pool: @pool) + else + @lock.synchronize do + # outside of transactions we can simply flush this query and retry + @statements.delete sql_key(sql) + end + retry + end + end + + raise + end + elsif binds.nil? || binds.empty? + raw_connection.async_exec(sql) + else + raw_connection.exec_params(sql, type_casted_binds) + end + + verified! + handle_warnings(result) + notification_payload[:row_count] = result.count + result + end + + def cast_result(result) + if result.fields.empty? + result.clear + return ActiveRecord::Result.empty + end + + types = {} + fields = result.fields + fields.each_with_index do |fname, i| + ftype = result.ftype i + fmod = result.fmod i + types[fname] = types[i] = get_oid_type(ftype, fmod, fname) + end + ar_result = ActiveRecord::Result.new(fields, result.values, types.freeze) + result.clear + ar_result + end + + def affected_rows(result) + affected_rows = result.cmd_tuples + result.clear + affected_rows + end + + def execute_batch(statements, name = nil, **kwargs) + raw_execute(combine_multi_statements(statements), name, batch: true, **kwargs) + end + + def build_truncate_statements(table_names) + ["TRUNCATE TABLE #{table_names.map(&method(:quote_table_name)).join(", ")}"] + end + + # Returns the current ID of a table's sequence. + def last_insert_id_result(sequence_name) + internal_exec_query("SELECT currval(#{quote(sequence_name)})", "SQL") + end + + def returning_column_values(result) + result.rows.first + end + + def suppress_composite_primary_key(pk) + pk unless pk.is_a?(Array) + end + + def handle_warnings(sql) + @notice_receiver_sql_warnings.each do |warning| + next if warning_ignored?(warning) + + warning.sql = sql + ActiveRecord.db_warnings_action.call(warning) + end + end + + def warning_ignored?(warning) + ["WARNING", "ERROR", "FATAL", "PANIC"].exclude?(warning.level) || super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb new file mode 100644 index 00000000..086a5dcc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + class ExplainPrettyPrinter # :nodoc: + # Pretty prints the result of an EXPLAIN in a way that resembles the output of the + # PostgreSQL shell: + # + # QUERY PLAN + # ------------------------------------------------------------------------------ + # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0) + # Join Filter: (posts.user_id = users.id) + # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4) + # Index Cond: (id = 1) + # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4) + # Filter: (posts.user_id = 1) + # (6 rows) + # + def pp(result) + header = result.columns.first + lines = result.rows.map(&:first) + + # We add 2 because there's one char of padding at both sides, note + # the extra hyphens in the example above. + width = [header, *lines].map(&:length).max + 2 + + pp = [] + + pp << header.center(width).rstrip + pp << "-" * width + + pp += lines.map { |line| " #{line}" } + + nrows = result.rows.length + rows_label = nrows == 1 ? "row" : "rows" + pp << "(#{nrows} #{rows_label})" + + pp.join("\n") + "\n" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid.rb new file mode 100644 index 00000000..30f0d49a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "active_record/connection_adapters/postgresql/oid/array" +require "active_record/connection_adapters/postgresql/oid/bit" +require "active_record/connection_adapters/postgresql/oid/bit_varying" +require "active_record/connection_adapters/postgresql/oid/bytea" +require "active_record/connection_adapters/postgresql/oid/cidr" +require "active_record/connection_adapters/postgresql/oid/date" +require "active_record/connection_adapters/postgresql/oid/date_time" +require "active_record/connection_adapters/postgresql/oid/decimal" +require "active_record/connection_adapters/postgresql/oid/enum" +require "active_record/connection_adapters/postgresql/oid/hstore" +require "active_record/connection_adapters/postgresql/oid/inet" +require "active_record/connection_adapters/postgresql/oid/interval" +require "active_record/connection_adapters/postgresql/oid/jsonb" +require "active_record/connection_adapters/postgresql/oid/macaddr" +require "active_record/connection_adapters/postgresql/oid/money" +require "active_record/connection_adapters/postgresql/oid/oid" +require "active_record/connection_adapters/postgresql/oid/point" +require "active_record/connection_adapters/postgresql/oid/legacy_point" +require "active_record/connection_adapters/postgresql/oid/range" +require "active_record/connection_adapters/postgresql/oid/specialized_string" +require "active_record/connection_adapters/postgresql/oid/timestamp" +require "active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone" +require "active_record/connection_adapters/postgresql/oid/uuid" +require "active_record/connection_adapters/postgresql/oid/vector" +require "active_record/connection_adapters/postgresql/oid/xml" + +require "active_record/connection_adapters/postgresql/oid/type_map_initializer" + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/array.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/array.rb new file mode 100644 index 00000000..f53843a4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/array.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Array < Type::Value # :nodoc: + include ActiveModel::Type::Helpers::Mutable + + Data = Struct.new(:encoder, :values) # :nodoc: + + attr_reader :subtype, :delimiter + delegate :type, :user_input_in_time_zone, :limit, :precision, :scale, to: :subtype + + def initialize(subtype, delimiter = ",") + @subtype = subtype + @delimiter = delimiter + + @pg_encoder = PG::TextEncoder::Array.new name: "#{type}[]", delimiter: delimiter + @pg_decoder = PG::TextDecoder::Array.new name: "#{type}[]", delimiter: delimiter + end + + def deserialize(value) + case value + when ::String + type_cast_array(@pg_decoder.decode(value), :deserialize) + when Data + type_cast_array(value.values, :deserialize) + else + super + end + end + + def cast(value) + if value.is_a?(::String) + value = begin + @pg_decoder.decode(value) + rescue TypeError + # malformed array string is treated as [], will raise in PG 2.0 gem + # this keeps a consistent implementation + [] + end + end + type_cast_array(value, :cast) + end + + def serialize(value) + if value.is_a?(::Array) + casted_values = type_cast_array(value, :serialize) + Data.new(@pg_encoder, casted_values) + else + super + end + end + + def ==(other) + other.is_a?(Array) && + subtype == other.subtype && + delimiter == other.delimiter + end + + def type_cast_for_schema(value) + return super unless value.is_a?(::Array) + "[" + value.map { |v| subtype.type_cast_for_schema(v) }.join(", ") + "]" + end + + def map(value, &block) + value.is_a?(::Array) ? value.map(&block) : subtype.map(value, &block) + end + + def changed_in_place?(raw_old_value, new_value) + deserialize(raw_old_value) != new_value + end + + def force_equality?(value) + value.is_a?(::Array) + end + + private + def type_cast_array(value, method) + if value.is_a?(::Array) + value.map { |item| type_cast_array(item, method) } + else + @subtype.public_send(method, value) + end + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/bit.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/bit.rb new file mode 100644 index 00000000..e9a79526 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/bit.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Bit < Type::Value # :nodoc: + def type + :bit + end + + def cast_value(value) + if ::String === value + case value + when /^0x/i + value[2..-1].hex.to_s(2) # Hexadecimal notation + else + value # Bit-string notation + end + else + value.to_s + end + end + + def serialize(value) + Data.new(super) if value + end + + class Data + def initialize(value) + @value = value + end + + def to_s + value + end + + def binary? + /\A[01]*\Z/.match?(value) + end + + def hex? + /\A[0-9A-F]*\Z/i.match?(value) + end + + private + attr_reader :value + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb new file mode 100644 index 00000000..dc7079dd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class BitVarying < OID::Bit # :nodoc: + def type + :bit_varying + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/bytea.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/bytea.rb new file mode 100644 index 00000000..a3c60ece --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/bytea.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Bytea < Type::Binary # :nodoc: + def deserialize(value) + return if value.nil? + return value.to_s if value.is_a?(Type::Binary::Data) + PG::Connection.unescape_bytea(super) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/cidr.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/cidr.rb new file mode 100644 index 00000000..1b5a688f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/cidr.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "ipaddr" + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Cidr < Type::Value # :nodoc: + def type + :cidr + end + + def type_cast_for_schema(value) + # If the subnet mask is equal to /32, don't output it + if value.prefix == 32 + "\"#{value}\"" + else + "\"#{value}/#{value.prefix}\"" + end + end + + def serialize(value) + if IPAddr === value + "#{value}/#{value.prefix}" + else + value + end + end + + # TODO: Remove when IPAddr#== compares IPAddr#prefix. See + # https://github.com/ruby/ipaddr/issues/21 + def changed?(old_value, new_value, _new_value_before_type_cast) + !old_value.eql?(new_value) || !old_value.nil? && old_value.prefix != new_value.prefix + end + + def cast_value(value) + if value.nil? + nil + elsif String === value + begin + IPAddr.new(value) + rescue ArgumentError + nil + end + else + value + end + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/date.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/date.rb new file mode 100644 index 00000000..633d7ddd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/date.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Date < Type::Date # :nodoc: + def cast_value(value) + case value + when "infinity" then ::Float::INFINITY + when "-infinity" then -::Float::INFINITY + when / BC$/ + value = value.sub(/^\d+/) { |year| format("%04d", -year.to_i + 1) } + super(value.delete_suffix!(" BC")) + else + super + end + end + + def type_cast_for_schema(value) + case value + when ::Float::INFINITY then "::Float::INFINITY" + when -::Float::INFINITY then "-::Float::INFINITY" + else super + end + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/date_time.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/date_time.rb new file mode 100644 index 00000000..fe29ebb8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/date_time.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class DateTime < Type::DateTime # :nodoc: + def cast_value(value) + case value + when "infinity" then ::Float::INFINITY + when "-infinity" then -::Float::INFINITY + when / BC$/ + value = value.sub(/^\d+/) { |year| format("%04d", -year.to_i + 1) } + super(value.delete_suffix!(" BC")) + else + super + end + end + + def type_cast_for_schema(value) + case value + when ::Float::INFINITY then "::Float::INFINITY" + when -::Float::INFINITY then "-::Float::INFINITY" + else super + end + end + + protected + def real_type_unless_aliased(real_type) + ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type == real_type ? :datetime : real_type + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/decimal.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/decimal.rb new file mode 100644 index 00000000..e7d33855 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/decimal.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Decimal < Type::Decimal # :nodoc: + def infinity(options = {}) + BigDecimal("Infinity") * (options[:negative] ? -1 : 1) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/enum.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/enum.rb new file mode 100644 index 00000000..bae34472 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/enum.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Enum < Type::Value # :nodoc: + def type + :enum + end + + private + def cast_value(value) + value.to_s + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/hstore.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/hstore.rb new file mode 100644 index 00000000..d6f60f56 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/hstore.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +require "strscan" + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Hstore < Type::Value # :nodoc: + ERROR = "Invalid Hstore document: %s" + + include ActiveModel::Type::Helpers::Mutable + + def type + :hstore + end + + def deserialize(value) + return value unless value.is_a?(::String) + + scanner = StringScanner.new(value) + hash = {} + + until scanner.eos? + unless scanner.skip(/"/) + raise(ArgumentError, ERROR % scanner.string.inspect) + end + + unless key = scanner.scan(/^(\\[\\"]|[^\\"])*?(?=")/) + raise(ArgumentError, ERROR % scanner.string.inspect) + end + + unless scanner.skip(/"=>?/) + raise(ArgumentError, ERROR % scanner.string.inspect) + end + + if scanner.scan(/NULL/) + value = nil + else + unless scanner.skip(/"/) + raise(ArgumentError, ERROR % scanner.string.inspect) + end + + unless value = scanner.scan(/^(\\[\\"]|[^\\"])*?(?=")/) + raise(ArgumentError, ERROR % scanner.string.inspect) + end + + unless scanner.skip(/"/) + raise(ArgumentError, ERROR % scanner.string.inspect) + end + end + + key.gsub!('\"', '"') + key.gsub!("\\\\", "\\") + + if value + value.gsub!('\"', '"') + value.gsub!("\\\\", "\\") + end + + hash[key] = value + + unless scanner.skip(/, /) || scanner.eos? + raise(ArgumentError, ERROR % scanner.string.inspect) + end + end + + hash + end + + def serialize(value) + if value.is_a?(::Hash) + value.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(", ") + elsif value.respond_to?(:to_unsafe_h) + serialize(value.to_unsafe_h) + else + value + end + end + + def accessor + ActiveRecord::Store::StringKeyedHashAccessor + end + + # Will compare the Hash equivalents of +raw_old_value+ and +new_value+. + # By comparing hashes, this avoids an edge case where the order of + # the keys change between the two hashes, and they would not be marked + # as equal. + def changed_in_place?(raw_old_value, new_value) + deserialize(raw_old_value) != new_value + end + + private + def escape_hstore(value) + if value.nil? + "NULL" + else + if value == "" + '""' + else + '"%s"' % value.to_s.gsub(/(["\\])/, '\\\\\1') + end + end + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/inet.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/inet.rb new file mode 100644 index 00000000..55be71fd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/inet.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Inet < Cidr # :nodoc: + def type + :inet + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/interval.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/interval.rb new file mode 100644 index 00000000..e52e851b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/interval.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "active_support/duration" + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Interval < Type::Value # :nodoc: + def type + :interval + end + + def cast_value(value) + case value + when ::ActiveSupport::Duration + value + when ::String + begin + ::ActiveSupport::Duration.parse(value) + rescue ::ActiveSupport::Duration::ISO8601Parser::ParsingError + nil + end + else + super + end + end + + def serialize(value) + case value + when ::ActiveSupport::Duration + value.iso8601(precision: self.precision) + when ::Numeric + # Sometimes operations on Times returns just float number of seconds so we need to handle that. + # Example: Time.current - (Time.current + 1.hour) # => -3600.000001776 (Float) + ActiveSupport::Duration.build(value).iso8601(precision: self.precision) + else + super + end + end + + def type_cast_for_schema(value) + serialize(value).inspect + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb new file mode 100644 index 00000000..e0216f10 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Jsonb < Type::Json # :nodoc: + def type + :jsonb + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb new file mode 100644 index 00000000..687f7595 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class LegacyPoint < Type::Value # :nodoc: + include ActiveModel::Type::Helpers::Mutable + + def type + :point + end + + def cast(value) + case value + when ::String + if value.start_with?("(") && value.end_with?(")") + value = value[1...-1] + end + cast(value.split(",")) + when ::Array + value.map { |v| Float(v) } + else + value + end + end + + def serialize(value) + if value.is_a?(::Array) + "(#{number_for_point(value[0])},#{number_for_point(value[1])})" + else + super + end + end + + private + def number_for_point(number) + number.to_s.delete_suffix(".0") + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb new file mode 100644 index 00000000..13e77c6a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Macaddr < Type::String # :nodoc: + def type + :macaddr + end + + def changed?(old_value, new_value, _new_value_before_type_cast) + old_value.class != new_value.class || + new_value && old_value.casecmp(new_value) != 0 + end + + def changed_in_place?(raw_old_value, new_value) + raw_old_value.class != new_value.class || + new_value && raw_old_value.casecmp(new_value) != 0 + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/money.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/money.rb new file mode 100644 index 00000000..86310407 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/money.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Money < Type::Decimal # :nodoc: + def type + :money + end + + def scale + 2 + end + + def cast_value(value) + return value unless ::String === value + + # Because money output is formatted according to the locale, there are two + # cases to consider (note the decimal separators): + # (1) $12,345,678.12 + # (2) $12.345.678,12 + # Negative values are represented as follows: + # (3) -$2.55 + # (4) ($2.55) + + value = value.sub(/^\((.+)\)$/, '-\1') # (4) + case value + when /^-?\D*+[\d,]+\.\d{2}$/ # (1) + value.delete!("^-0-9.") + when /^-?\D*+[\d.]+,\d{2}$/ # (2) + value.delete!("^-0-9,") + value.tr!(",", ".") + end + + super(value) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/oid.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/oid.rb new file mode 100644 index 00000000..86d84b00 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/oid.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Oid < Type::UnsignedInteger # :nodoc: + def type + :oid + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/point.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/point.rb new file mode 100644 index 00000000..dc8e67fe --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/point.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +module ActiveRecord + Point = Struct.new(:x, :y) + + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Point < Type::Value # :nodoc: + include ActiveModel::Type::Helpers::Mutable + + def type + :point + end + + def cast(value) + case value + when ::String + return if value.blank? + + if value.start_with?("(") && value.end_with?(")") + value = value[1...-1] + end + x, y = value.split(",") + build_point(x, y) + when ::Array + build_point(*value) + when ::Hash + return if value.blank? + + build_point(*values_array_from_hash(value)) + else + value + end + end + + def serialize(value) + case value + when ActiveRecord::Point + "(#{number_for_point(value.x)},#{number_for_point(value.y)})" + when ::Array + serialize(build_point(*value)) + when ::Hash + serialize(build_point(*values_array_from_hash(value))) + else + super + end + end + + def type_cast_for_schema(value) + if ActiveRecord::Point === value + [value.x, value.y] + else + super + end + end + + private + def number_for_point(number) + number.to_s.delete_suffix(".0") + end + + def build_point(x, y) + ActiveRecord::Point.new(Float(x), Float(y)) + end + + def values_array_from_hash(value) + [value.values_at(:x, "x").compact.first, value.values_at(:y, "y").compact.first] + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/range.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/range.rb new file mode 100644 index 00000000..ed674a50 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/range.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Range < Type::Value # :nodoc: + attr_reader :subtype, :type + delegate :user_input_in_time_zone, to: :subtype + + def initialize(subtype, type = :range) + @subtype = subtype + @type = type + end + + def type_cast_for_schema(value) + value.inspect.gsub("Infinity", "::Float::INFINITY") + end + + def cast_value(value) + return if ["empty", ""].include? value + return value unless value.is_a?(::String) + + extracted = extract_bounds(value) + from = type_cast_single extracted[:from] + to = type_cast_single extracted[:to] + + if !infinity?(from) && extracted[:exclude_start] + raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')" + end + ::Range.new(*sanitize_bounds(from, to), extracted[:exclude_end]) + end + + def serialize(value) + if value.is_a?(::Range) + from = type_cast_single_for_database(value.begin) + to = type_cast_single_for_database(value.end) + ::Range.new(from, to, value.exclude_end?) + else + super + end + end + + def ==(other) + other.is_a?(Range) && + other.subtype == subtype && + other.type == type + end + + def map(value) # :nodoc: + new_begin = yield(value.begin) + new_end = yield(value.end) + ::Range.new(new_begin, new_end, value.exclude_end?) + end + + def force_equality?(value) + value.is_a?(::Range) + end + + private + def type_cast_single(value) + infinity?(value) ? value : @subtype.deserialize(value) + end + + def type_cast_single_for_database(value) + infinity?(value) ? value : @subtype.serialize(@subtype.cast(value)) + end + + def extract_bounds(value) + from, to = value[1..-2].split(",", 2) + { + from: (from == "" || from == "-infinity") ? infinity(negative: true) : unquote(from), + to: (to == "" || to == "infinity") ? infinity : unquote(to), + exclude_start: value.start_with?("("), + exclude_end: value.end_with?(")") + } + end + + INFINITE_FLOAT_RANGE = (-::Float::INFINITY)..(::Float::INFINITY) # :nodoc: + + def sanitize_bounds(from, to) + [ + (from == -::Float::INFINITY && !INFINITE_FLOAT_RANGE.cover?(to)) ? nil : from, + (to == ::Float::INFINITY && !INFINITE_FLOAT_RANGE.cover?(from)) ? nil : to + ] + end + + # When formatting the bound values of range types, PostgreSQL quotes + # the bound value using double-quotes in certain conditions. Within + # a double-quoted string, literal " and \ characters are themselves + # escaped. In input, PostgreSQL accepts multiple escape styles for " + # (either \" or "") but in output always uses "". + # See: + # * https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-IO + # * https://www.postgresql.org/docs/current/rowtypes.html#ROWTYPES-IO-SYNTAX + def unquote(value) + if value.start_with?('"') && value.end_with?('"') + unquoted_value = value[1..-2] + unquoted_value.gsub!('""', '"') + unquoted_value.gsub!("\\\\", "\\") + unquoted_value + else + value + end + end + + def infinity(negative: false) + if subtype.respond_to?(:infinity) + subtype.infinity(negative: negative) + elsif negative + -::Float::INFINITY + else + ::Float::INFINITY + end + end + + def infinity?(value) + value.respond_to?(:infinite?) && value.infinite? + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb new file mode 100644 index 00000000..80316b35 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class SpecializedString < Type::String # :nodoc: + attr_reader :type + + def initialize(type, **options) + @type = type + super(**options) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb new file mode 100644 index 00000000..e6326ab0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Timestamp < DateTime # :nodoc: + def type + real_type_unless_aliased(:timestamp) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb new file mode 100644 index 00000000..9b99eae8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class TimestampWithTimeZone < DateTime # :nodoc: + def type + real_type_unless_aliased(:timestamptz) + end + + def cast_value(value) + return if value.blank? + + time = super + return time if time.is_a?(ActiveSupport::TimeWithZone) || !time.acts_like?(:time) + + # While in UTC mode, the PG gem may not return times back in "UTC" even if they were provided to PostgreSQL in UTC. + # We prefer times always in UTC, so here we convert back. + if is_utc? + time.getutc + else + time.getlocal + end + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb new file mode 100644 index 00000000..ec020cb8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/extract" + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + # This class uses the data from PostgreSQL pg_type table to build + # the OID -> Type mapping. + # - OID is an integer representing the type. + # - Type is an OID::Type object. + # This class has side effects on the +store+ passed during initialization. + class TypeMapInitializer # :nodoc: + def initialize(store) + @store = store + end + + def run(records) + nodes = records.reject { |row| @store.key? row["oid"].to_i } + mapped = nodes.extract! { |row| @store.key? row["typname"] } + ranges = nodes.extract! { |row| row["typtype"] == "r" } + enums = nodes.extract! { |row| row["typtype"] == "e" } + domains = nodes.extract! { |row| row["typtype"] == "d" } + arrays = nodes.extract! { |row| row["typinput"] == "array_in" } + composites = nodes.extract! { |row| row["typelem"].to_i != 0 } + + mapped.each { |row| register_mapped_type(row) } + enums.each { |row| register_enum_type(row) } + domains.each { |row| register_domain_type(row) } + arrays.each { |row| register_array_type(row) } + ranges.each { |row| register_range_type(row) } + composites.each { |row| register_composite_type(row) } + end + + def query_conditions_for_known_type_names + known_type_names = @store.keys.map { |n| "'#{n}'" } + <<~SQL % known_type_names.join(", ") + WHERE + t.typname IN (%s) + SQL + end + + def query_conditions_for_known_type_types + known_type_types = %w('r' 'e' 'd') + <<~SQL % known_type_types.join(", ") + WHERE + t.typtype IN (%s) + SQL + end + + def query_conditions_for_array_types + known_type_oids = @store.keys.reject { |k| k.is_a?(String) } + <<~SQL % [known_type_oids.join(", ")] + WHERE + t.typelem IN (%s) + SQL + end + + private + def register_mapped_type(row) + alias_type row["oid"], row["typname"] + end + + def register_enum_type(row) + register row["oid"], OID::Enum.new + end + + def register_array_type(row) + register_with_subtype(row["oid"], row["typelem"].to_i) do |subtype| + OID::Array.new(subtype, row["typdelim"]) + end + end + + def register_range_type(row) + register_with_subtype(row["oid"], row["rngsubtype"].to_i) do |subtype| + OID::Range.new(subtype, row["typname"].to_sym) + end + end + + def register_domain_type(row) + if base_type = @store.lookup(row["typbasetype"].to_i) + register row["oid"], base_type + else + warn "unknown base type (OID: #{row["typbasetype"]}) for domain #{row["typname"]}." + end + end + + def register_composite_type(row) + if subtype = @store.lookup(row["typelem"].to_i) + register row["oid"], OID::Vector.new(row["typdelim"], subtype) + end + end + + def register(oid, oid_type = nil, &block) + oid = assert_valid_registration(oid, oid_type || block) + if block_given? + @store.register_type(oid, &block) + else + @store.register_type(oid, oid_type) + end + end + + def alias_type(oid, target) + oid = assert_valid_registration(oid, target) + @store.alias_type(oid, target) + end + + def register_with_subtype(oid, target_oid) + if @store.key?(target_oid) + register(oid) do |_, *args| + yield @store.lookup(target_oid, *args) + end + end + end + + def assert_valid_registration(oid, oid_type) + raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil? + oid.to_i + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/uuid.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/uuid.rb new file mode 100644 index 00000000..6f81917e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/uuid.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Uuid < Type::Value # :nodoc: + ACCEPTABLE_UUID = %r{\A(\{)?([a-fA-F0-9]{4}-?){8}(?(1)\}|)\z} + CANONICAL_UUID = %r{\A[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}\z} + + alias :serialize :deserialize + + def type + :uuid + end + + def changed?(old_value, new_value, _new_value_before_type_cast) + old_value.class != new_value.class || + new_value != old_value + end + + def changed_in_place?(raw_old_value, new_value) + raw_old_value.class != new_value.class || + new_value != raw_old_value + end + + private + def cast_value(value) + value = value.to_s + format_uuid(value) if value.match?(ACCEPTABLE_UUID) + end + + def format_uuid(uuid) + if uuid.match?(CANONICAL_UUID) + uuid + else + uuid = uuid.delete("{}-").downcase + "#{uuid[..7]}-#{uuid[8..11]}-#{uuid[12..15]}-#{uuid[16..19]}-#{uuid[20..]}" + end + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/vector.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/vector.rb new file mode 100644 index 00000000..88ef626a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/vector.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Vector < Type::Value # :nodoc: + attr_reader :delim, :subtype + + # +delim+ corresponds to the `typdelim` column in the pg_types + # table. +subtype+ is derived from the `typelem` column in the + # pg_types table. + def initialize(delim, subtype) + @delim = delim + @subtype = subtype + end + + # FIXME: this should probably split on +delim+ and use +subtype+ + # to cast the values. Unfortunately, the current Rails behavior + # is to just return the string. + def cast(value) + value + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/xml.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/xml.rb new file mode 100644 index 00000000..042f32fd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/oid/xml.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Xml < Type::String # :nodoc: + def type + :xml + end + + def serialize(value) + return unless value + Data.new(super) + end + + class Data # :nodoc: + def initialize(value) + @value = value + end + + def to_s + @value + end + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/quoting.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/quoting.rb new file mode 100644 index 00000000..e2d7d416 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -0,0 +1,238 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module Quoting + extend ActiveSupport::Concern + + QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc: + QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc: + + module ClassMethods # :nodoc: + def column_name_matcher + / + \A + ( + (?: + # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name + ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?) + ) + (?:(?:\s+AS)?\s+(?:\w+|"\w+"))? + ) + (?:\s*,\s*\g<1>)* + \z + /ix + end + + def column_name_with_order_matcher + / + \A + ( + (?: + # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name + ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?) + ) + (?:\s+COLLATE\s+"\w+")? + (?:\s+ASC|\s+DESC)? + (?:\s+NULLS\s+(?:FIRST|LAST))? + ) + (?:\s*,\s*\g<1>)* + \z + /ix + end + + # Quotes column names for use in SQL queries. + def quote_column_name(name) # :nodoc: + QUOTED_COLUMN_NAMES[name] ||= PG::Connection.quote_ident(name.to_s).freeze + end + + # Checks the following cases: + # + # - table_name + # - "table.name" + # - schema_name.table_name + # - schema_name."table.name" + # - "schema.name".table_name + # - "schema.name"."table.name" + def quote_table_name(name) # :nodoc: + QUOTED_TABLE_NAMES[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze + end + end + + class IntegerOutOf64BitRange < StandardError + def initialize(msg) + super(msg) + end + end + + # Escapes binary strings for bytea input to the database. + def escape_bytea(value) + valid_raw_connection.escape_bytea(value) if value + end + + # Unescapes bytea output from a database to the binary string it represents. + # NOTE: This is NOT an inverse of escape_bytea! This is only to be used + # on escaped binary output from database drive. + def unescape_bytea(value) + valid_raw_connection.unescape_bytea(value) if value + end + + def check_int_in_range(value) + if value.to_int > 9223372036854775807 || value.to_int < -9223372036854775808 + exception = <<~ERROR + Provided value outside of the range of a signed 64bit integer. + + PostgreSQL will treat the column type in question as a numeric. + This may result in a slow sequential scan due to a comparison + being performed between an integer or bigint value and a numeric value. + + To allow for this potentially unwanted behavior, set + ActiveRecord.raise_int_wider_than_64bit to false. + ERROR + raise IntegerOutOf64BitRange.new exception + end + end + + def quote(value) # :nodoc: + if ActiveRecord.raise_int_wider_than_64bit && value.is_a?(Integer) + check_int_in_range(value) + end + + case value + when OID::Xml::Data + "xml '#{quote_string(value.to_s)}'" + when OID::Bit::Data + if value.binary? + "B'#{value}'" + elsif value.hex? + "X'#{value}'" + end + when Numeric + if value.finite? + super + else + "'#{value}'" + end + when OID::Array::Data + quote(encode_array(value)) + when Range + quote(encode_range(value)) + else + super + end + end + + # Quotes strings for use in SQL input. + def quote_string(s) # :nodoc: + with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection| + connection.escape(s) + end + end + + def quote_table_name_for_assignment(table, attr) + quote_column_name(attr) + end + + # Quotes schema names for use in SQL queries. + def quote_schema_name(schema_name) + quote_column_name(schema_name) + end + + # Quote date/time values for use in SQL input. + def quoted_date(value) # :nodoc: + if value.year <= 0 + bce_year = format("%04d", -value.year + 1) + super.sub(/^-?\d+/, bce_year) + " BC" + else + super + end + end + + def quoted_binary(value) # :nodoc: + "'#{escape_bytea(value.to_s)}'" + end + + def quote_default_expression(value, column) # :nodoc: + if value.is_a?(Proc) + value.call + elsif column.type == :uuid && value.is_a?(String) && value.include?("()") + value # Does not quote function default values for UUID columns + elsif column.respond_to?(:array?) + type = lookup_cast_type_from_column(column) + quote(type.serialize(value)) + else + super + end + end + + def type_cast(value) # :nodoc: + case value + when Type::Binary::Data + # Return a bind param hash with format as binary. + # See https://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared-doc + # for more information + { value: value.to_s, format: 1 } + when OID::Xml::Data, OID::Bit::Data + value.to_s + when OID::Array::Data + encode_array(value) + when Range + encode_range(value) + when Rational + value.to_f + else + super + end + end + + def lookup_cast_type_from_column(column) # :nodoc: + verify! if type_map.nil? + type_map.lookup(column.oid, column.fmod, column.sql_type) + end + + private + def lookup_cast_type(sql_type) + super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i) + end + + def encode_array(array_data) + encoder = array_data.encoder + values = type_cast_array(array_data.values) + + result = encoder.encode(values) + if encoding = determine_encoding_of_strings_in_array(values) + result.force_encoding(encoding) + end + result + end + + def encode_range(range) + "[#{type_cast_range_value(range.begin)},#{type_cast_range_value(range.end)}#{range.exclude_end? ? ')' : ']'}" + end + + def determine_encoding_of_strings_in_array(value) + case value + when ::Array then determine_encoding_of_strings_in_array(value.first) + when ::String then value.encoding + end + end + + def type_cast_array(values) + case values + when ::Array then values.map { |item| type_cast_array(item) } + else type_cast(values) + end + end + + def type_cast_range_value(value) + infinity?(value) ? "" : type_cast(value) + end + + def infinity?(value) + value.respond_to?(:infinite?) && value.infinite? + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/referential_integrity.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/referential_integrity.rb new file mode 100644 index 00000000..e64aadf0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/referential_integrity.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module ReferentialIntegrity # :nodoc: + def disable_referential_integrity # :nodoc: + original_exception = nil + + begin + transaction(requires_new: true) do + execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) + end + rescue ActiveRecord::ActiveRecordError => e + original_exception = e + end + + begin + yield + rescue ActiveRecord::InvalidForeignKey => e + warn <<-WARNING +WARNING: Rails was not able to disable referential integrity. + +This is most likely caused due to missing permissions. +Rails needs superuser privileges to disable referential integrity. + + cause: #{original_exception&.message} + + WARNING + raise e + end + + begin + transaction(requires_new: true) do + execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";")) + end + rescue ActiveRecord::ActiveRecordError + end + end + + def check_all_foreign_keys_valid! # :nodoc: + sql = <<~SQL + do $$ + declare r record; + BEGIN + FOR r IN ( + SELECT FORMAT( + 'UPDATE pg_catalog.pg_constraint SET convalidated=false WHERE conname = ''%1$I'' AND connamespace::regnamespace = ''%2$I''::regnamespace; ALTER TABLE %2$I.%3$I VALIDATE CONSTRAINT %1$I;', + constraint_name, + table_schema, + table_name + ) AS constraint_check + FROM information_schema.table_constraints WHERE constraint_type = 'FOREIGN KEY' + ) + LOOP + EXECUTE (r.constraint_check); + END LOOP; + END; + $$; + SQL + + transaction(requires_new: true) do + execute(sql) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/schema_creation.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/schema_creation.rb new file mode 100644 index 00000000..69e6a7de --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/schema_creation.rb @@ -0,0 +1,160 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + class SchemaCreation < SchemaCreation # :nodoc: + private + delegate :quoted_include_columns_for_index, to: :@conn + + def visit_AlterTable(o) + sql = super + sql << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ") + sql << o.exclusion_constraint_adds.map { |con| visit_AddExclusionConstraint con }.join(" ") + sql << o.unique_constraint_adds.map { |con| visit_AddUniqueConstraint con }.join(" ") + end + + def visit_AddForeignKey(o) + super.dup.tap do |sql| + sql << " NOT VALID" unless o.validate? + end + end + + def visit_ForeignKeyDefinition(o) + super.dup.tap do |sql| + sql << " DEFERRABLE INITIALLY #{o.deferrable.to_s.upcase}" if o.deferrable + end + end + + def visit_CheckConstraintDefinition(o) + super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? } + end + + def visit_ValidateConstraint(name) + "VALIDATE CONSTRAINT #{quote_column_name(name)}" + end + + def visit_ExclusionConstraintDefinition(o) + sql = ["CONSTRAINT"] + sql << quote_column_name(o.name) + sql << "EXCLUDE" + sql << "USING #{o.using}" if o.using + sql << "(#{o.expression})" + sql << "WHERE (#{o.where})" if o.where + sql << "DEFERRABLE INITIALLY #{o.deferrable.to_s.upcase}" if o.deferrable + + sql.join(" ") + end + + def visit_UniqueConstraintDefinition(o) + column_name = Array(o.column).map { |column| quote_column_name(column) }.join(", ") + + sql = ["CONSTRAINT"] + sql << quote_column_name(o.name) + sql << "UNIQUE" + sql << "NULLS NOT DISTINCT" if supports_nulls_not_distinct? && o.nulls_not_distinct + + if o.using_index + sql << "USING INDEX #{quote_column_name(o.using_index)}" + else + sql << "(#{column_name})" + end + + if o.deferrable + sql << "DEFERRABLE INITIALLY #{o.deferrable.to_s.upcase}" + end + + sql.join(" ") + end + + def visit_AddExclusionConstraint(o) + "ADD #{accept(o)}" + end + + def visit_AddUniqueConstraint(o) + "ADD #{accept(o)}" + end + + def visit_ChangeColumnDefinition(o) + column = o.column + column.sql_type = type_to_sql(column.type, **column.options) + quoted_column_name = quote_column_name(o.name) + + change_column_sql = +"ALTER COLUMN #{quoted_column_name} TYPE #{column.sql_type}" + + options = column_options(column) + + if options[:collation] + change_column_sql << " COLLATE \"#{options[:collation]}\"" + end + + if options[:using] + change_column_sql << " USING #{options[:using]}" + elsif options[:cast_as] + cast_as_type = type_to_sql(options[:cast_as], **options) + change_column_sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})" + end + + if options.key?(:default) + if options[:default].nil? + change_column_sql << ", ALTER COLUMN #{quoted_column_name} DROP DEFAULT" + else + quoted_default = quote_default_expression(options[:default], column) + change_column_sql << ", ALTER COLUMN #{quoted_column_name} SET DEFAULT #{quoted_default}" + end + end + + if options.key?(:null) + change_column_sql << ", ALTER COLUMN #{quoted_column_name} #{options[:null] ? 'DROP' : 'SET'} NOT NULL" + end + + change_column_sql + end + + def visit_ChangeColumnDefaultDefinition(o) + sql = +"ALTER COLUMN #{quote_column_name(o.column.name)} " + if o.default.nil? + sql << "DROP DEFAULT" + else + sql << "SET DEFAULT #{quote_default_expression(o.default, o.column)}" + end + end + + def add_column_options!(sql, options) + if options[:collation] + sql << " COLLATE \"#{options[:collation]}\"" + end + + if as = options[:as] + sql << " GENERATED ALWAYS AS (#{as})" + + if options[:stored] + sql << " STORED" + else + raise ArgumentError, <<~MSG + PostgreSQL currently does not support VIRTUAL (not persisted) generated columns. + Specify 'stored: true' option for '#{options[:column].name}' + MSG + end + end + super + end + + def quoted_include_columns(o) + String === o ? o : quoted_include_columns_for_index(o) + end + + # Returns any SQL string to go between CREATE and TABLE. May be nil. + def table_modifier_in_create(o) + # A table cannot be both TEMPORARY and UNLOGGED, since all TEMPORARY + # tables are already UNLOGGED. + if o.temporary + " TEMPORARY" + elsif o.unlogged + " UNLOGGED" + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/schema_definitions.rb new file mode 100644 index 00000000..96085d0e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -0,0 +1,388 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module ColumnMethods + extend ActiveSupport::Concern + + # Defines the primary key field. + # Use of the native PostgreSQL UUID type is supported, and can be used + # by defining your tables as such: + # + # create_table :stuffs, id: :uuid do |t| + # t.string :content + # t.timestamps + # end + # + # By default, this will use the gen_random_uuid() function from the + # +pgcrypto+ extension. As that extension is only available in + # PostgreSQL 9.4+, for earlier versions an explicit default can be set + # to use uuid_generate_v4() from the +uuid-ossp+ extension instead: + # + # create_table :stuffs, id: false do |t| + # t.primary_key :id, :uuid, default: "uuid_generate_v4()" + # t.uuid :foo_id + # t.timestamps + # end + # + # To enable the appropriate extension, which is a requirement, use + # the +enable_extension+ method in your migrations. + # + # To use a UUID primary key without any of the extensions, set the + # +:default+ option to +nil+: + # + # create_table :stuffs, id: false do |t| + # t.primary_key :id, :uuid, default: nil + # t.uuid :foo_id + # t.timestamps + # end + # + # You may also pass a custom stored procedure that returns a UUID or use a + # different UUID generation function from another library. + # + # Note that setting the UUID primary key default value to +nil+ will + # require you to assure that you always provide a UUID value before saving + # a record (as primary keys cannot be +nil+). This might be done via the + # +SecureRandom.uuid+ method and a +before_save+ callback, for instance. + def primary_key(name, type = :primary_key, **options) + if type == :uuid + options[:default] = options.fetch(:default, "gen_random_uuid()") + end + + super + end + + ## + # :method: bigserial + # :call-seq: bigserial(*names, **options) + + ## + # :method: bit + # :call-seq: bit(*names, **options) + + ## + # :method: bit_varying + # :call-seq: bit_varying(*names, **options) + + ## + # :method: cidr + # :call-seq: cidr(*names, **options) + + ## + # :method: citext + # :call-seq: citext(*names, **options) + + ## + # :method: daterange + # :call-seq: daterange(*names, **options) + + ## + # :method: hstore + # :call-seq: hstore(*names, **options) + + ## + # :method: inet + # :call-seq: inet(*names, **options) + + ## + # :method: interval + # :call-seq: interval(*names, **options) + + ## + # :method: int4range + # :call-seq: int4range(*names, **options) + + ## + # :method: int8range + # :call-seq: int8range(*names, **options) + + ## + # :method: jsonb + # :call-seq: jsonb(*names, **options) + + ## + # :method: ltree + # :call-seq: ltree(*names, **options) + + ## + # :method: macaddr + # :call-seq: macaddr(*names, **options) + + ## + # :method: money + # :call-seq: money(*names, **options) + + ## + # :method: numrange + # :call-seq: numrange(*names, **options) + + ## + # :method: oid + # :call-seq: oid(*names, **options) + + ## + # :method: point + # :call-seq: point(*names, **options) + + ## + # :method: line + # :call-seq: line(*names, **options) + + ## + # :method: lseg + # :call-seq: lseg(*names, **options) + + ## + # :method: box + # :call-seq: box(*names, **options) + + ## + # :method: path + # :call-seq: path(*names, **options) + + ## + # :method: polygon + # :call-seq: polygon(*names, **options) + + ## + # :method: circle + # :call-seq: circle(*names, **options) + + ## + # :method: serial + # :call-seq: serial(*names, **options) + + ## + # :method: tsrange + # :call-seq: tsrange(*names, **options) + + ## + # :method: tstzrange + # :call-seq: tstzrange(*names, **options) + + ## + # :method: tsvector + # :call-seq: tsvector(*names, **options) + + ## + # :method: uuid + # :call-seq: uuid(*names, **options) + + ## + # :method: xml + # :call-seq: xml(*names, **options) + + ## + # :method: timestamptz + # :call-seq: timestamptz(*names, **options) + + ## + # :method: enum + # :call-seq: enum(*names, **options) + + included do + define_column_methods :bigserial, :bit, :bit_varying, :cidr, :citext, :daterange, + :hstore, :inet, :interval, :int4range, :int8range, :jsonb, :ltree, :macaddr, + :money, :numrange, :oid, :point, :line, :lseg, :box, :path, :polygon, :circle, + :serial, :tsrange, :tstzrange, :tsvector, :uuid, :xml, :timestamptz, :enum + end + end + + ExclusionConstraintDefinition = Struct.new(:table_name, :expression, :options) do + def name + options[:name] + end + + def using + options[:using] + end + + def where + options[:where] + end + + def deferrable + options[:deferrable] + end + + def export_name_on_schema_dump? + !ActiveRecord::SchemaDumper.excl_ignore_pattern.match?(name) if name + end + end + + UniqueConstraintDefinition = Struct.new(:table_name, :column, :options) do + def name + options[:name] + end + + def deferrable + options[:deferrable] + end + + def using_index + options[:using_index] + end + + def nulls_not_distinct + options[:nulls_not_distinct] + end + + def export_name_on_schema_dump? + !ActiveRecord::SchemaDumper.unique_ignore_pattern.match?(name) if name + end + + def defined_for?(name: nil, column: nil, **options) + options = options.slice(*self.options.keys) + + (name.nil? || self.name == name.to_s) && + (column.nil? || Array(self.column) == Array(column).map(&:to_s)) && + options.all? { |k, v| self.options[k].to_s == v.to_s } + end + end + + # = Active Record PostgreSQL Adapter \Table Definition + class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + include ColumnMethods + + attr_reader :exclusion_constraints, :unique_constraints, :unlogged + + def initialize(*, **) + super + @exclusion_constraints = [] + @unique_constraints = [] + @unlogged = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables + end + + def exclusion_constraint(expression, **options) + exclusion_constraints << new_exclusion_constraint_definition(expression, options) + end + + def unique_constraint(column_name, **options) + unique_constraints << new_unique_constraint_definition(column_name, options) + end + + def new_exclusion_constraint_definition(expression, options) # :nodoc: + options = @conn.exclusion_constraint_options(name, expression, options) + ExclusionConstraintDefinition.new(name, expression, options) + end + + def new_unique_constraint_definition(column_name, options) # :nodoc: + options = @conn.unique_constraint_options(name, column_name, options) + UniqueConstraintDefinition.new(name, column_name, options) + end + + def new_column_definition(name, type, **options) # :nodoc: + case type + when :virtual + type = options[:type] + end + + super + end + + private + def valid_column_definition_options + super + [:array, :using, :cast_as, :as, :type, :enum_type, :stored] + end + + def aliased_types(name, fallback) + fallback + end + + def integer_like_primary_key_type(type, options) + if type == :bigint || options[:limit] == 8 + :bigserial + else + :serial + end + end + end + + # = Active Record PostgreSQL Adapter \Table + class Table < ActiveRecord::ConnectionAdapters::Table + include ColumnMethods + + # Adds an exclusion constraint. + # + # t.exclusion_constraint("price WITH =, availability_range WITH &&", using: :gist, name: "price_check") + # + # See {connection.add_exclusion_constraint}[rdoc-ref:SchemaStatements#add_exclusion_constraint] + def exclusion_constraint(...) + @base.add_exclusion_constraint(name, ...) + end + + # Removes the given exclusion constraint from the table. + # + # t.remove_exclusion_constraint(name: "price_check") + # + # See {connection.remove_exclusion_constraint}[rdoc-ref:SchemaStatements#remove_exclusion_constraint] + def remove_exclusion_constraint(...) + @base.remove_exclusion_constraint(name, ...) + end + + # Adds a unique constraint. + # + # t.unique_constraint(:position, name: 'unique_position', deferrable: :deferred, nulls_not_distinct: true) + # + # See {connection.add_unique_constraint}[rdoc-ref:SchemaStatements#add_unique_constraint] + def unique_constraint(...) + @base.add_unique_constraint(name, ...) + end + + # Removes the given unique constraint from the table. + # + # t.remove_unique_constraint(name: "unique_position") + # + # See {connection.remove_unique_constraint}[rdoc-ref:SchemaStatements#remove_unique_constraint] + def remove_unique_constraint(...) + @base.remove_unique_constraint(name, ...) + end + + # Validates the given constraint on the table. + # + # t.check_constraint("price > 0", name: "price_check", validate: false) + # t.validate_constraint "price_check" + # + # See {connection.validate_constraint}[rdoc-ref:SchemaStatements#validate_constraint] + def validate_constraint(...) + @base.validate_constraint(name, ...) + end + + # Validates the given check constraint on the table + # + # t.check_constraint("price > 0", name: "price_check", validate: false) + # t.validate_check_constraint name: "price_check" + # + # See {connection.validate_check_constraint}[rdoc-ref:SchemaStatements#validate_check_constraint] + def validate_check_constraint(...) + @base.validate_check_constraint(name, ...) + end + end + + # = Active Record PostgreSQL Adapter Alter \Table + class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable + attr_reader :constraint_validations, :exclusion_constraint_adds, :unique_constraint_adds + + def initialize(td) + super + @constraint_validations = [] + @exclusion_constraint_adds = [] + @unique_constraint_adds = [] + end + + def validate_constraint(name) + @constraint_validations << name + end + + def add_exclusion_constraint(expression, options) + @exclusion_constraint_adds << @td.new_exclusion_constraint_definition(expression, options) + end + + def add_unique_constraint(column_name, options) + @unique_constraint_adds << @td.new_unique_constraint_definition(column_name, options) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/schema_dumper.rb new file mode 100644 index 00000000..2e2290fc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/schema_dumper.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc: + private + def extensions(stream) + extensions = @connection.extensions + if extensions.any? + stream.puts " # These are extensions that must be enabled in order to support this database" + extensions.sort.each do |extension| + stream.puts " enable_extension #{extension.inspect}" + end + stream.puts + end + end + + def types(stream) + types = @connection.enum_types + if types.any? + stream.puts " # Custom types defined in this database." + stream.puts " # Note that some types may not work with other database engines. Be careful if changing database." + types.sort.each do |name, values| + stream.puts " create_enum #{name.inspect}, #{values.inspect}" + end + stream.puts + end + end + + def schemas(stream) + schema_names = @connection.schema_names - ["public"] + + if schema_names.any? + schema_names.sort.each do |name| + stream.puts " create_schema #{name.inspect}" + end + stream.puts + end + end + + def exclusion_constraints_in_create(table, stream) + if (exclusion_constraints = @connection.exclusion_constraints(table)).any? + add_exclusion_constraint_statements = exclusion_constraints.map do |exclusion_constraint| + parts = [ + "t.exclusion_constraint #{exclusion_constraint.expression.inspect}" + ] + + parts << "where: #{exclusion_constraint.where.inspect}" if exclusion_constraint.where + parts << "using: #{exclusion_constraint.using.inspect}" if exclusion_constraint.using + parts << "deferrable: #{exclusion_constraint.deferrable.inspect}" if exclusion_constraint.deferrable + + if exclusion_constraint.export_name_on_schema_dump? + parts << "name: #{exclusion_constraint.name.inspect}" + end + + " #{parts.join(', ')}" + end + + stream.puts add_exclusion_constraint_statements.sort.join("\n") + end + end + + def unique_constraints_in_create(table, stream) + if (unique_constraints = @connection.unique_constraints(table)).any? + add_unique_constraint_statements = unique_constraints.map do |unique_constraint| + parts = [ + "t.unique_constraint #{unique_constraint.column.inspect}" + ] + + parts << "nulls_not_distinct: #{unique_constraint.nulls_not_distinct.inspect}" if unique_constraint.nulls_not_distinct + parts << "deferrable: #{unique_constraint.deferrable.inspect}" if unique_constraint.deferrable + + if unique_constraint.export_name_on_schema_dump? + parts << "name: #{unique_constraint.name.inspect}" + end + + " #{parts.join(', ')}" + end + + stream.puts add_unique_constraint_statements.sort.join("\n") + end + end + + def prepare_column_options(column) + spec = super + spec[:array] = "true" if column.array? + + if @connection.supports_virtual_columns? && column.virtual? + spec[:as] = extract_expression_for_virtual_column(column) + spec[:stored] = true + spec = { type: schema_type(column).inspect }.merge!(spec) + end + + spec[:enum_type] = column.sql_type.inspect if column.enum? + + spec + end + + def default_primary_key?(column) + schema_type(column) == :bigserial + end + + def explicit_primary_key_default?(column) + column.type == :uuid || (column.type == :integer && !column.serial?) + end + + def schema_type(column) + return super unless column.serial? + + if column.bigint? + :bigserial + else + :serial + end + end + + def schema_expression(column) + super unless column.serial? + end + + def extract_expression_for_virtual_column(column) + column.default_function.inspect + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/schema_statements.rb new file mode 100644 index 00000000..15c33753 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -0,0 +1,1163 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module SchemaStatements + # Drops the database specified on the +name+ attribute + # and creates it again using the provided +options+. + def recreate_database(name, options = {}) # :nodoc: + drop_database(name) + create_database(name, options) + end + + # Create a new PostgreSQL database. Options include :owner, :template, + # :encoding (defaults to utf8), :collation, :ctype, + # :tablespace, and :connection_limit (note that MySQL uses + # :charset while PostgreSQL uses :encoding). + # + # Example: + # create_database config[:database], config + # create_database 'foo_development', encoding: 'unicode' + def create_database(name, options = {}) + options = { encoding: "utf8" }.merge!(options.symbolize_keys) + + option_string = options.each_with_object(+"") do |(key, value), memo| + memo << case key + when :owner + " OWNER = \"#{value}\"" + when :template + " TEMPLATE = \"#{value}\"" + when :encoding + " ENCODING = '#{value}'" + when :collation + " LC_COLLATE = '#{value}'" + when :ctype + " LC_CTYPE = '#{value}'" + when :tablespace + " TABLESPACE = \"#{value}\"" + when :connection_limit + " CONNECTION LIMIT = #{value}" + else + "" + end + end + + execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}" + end + + # Drops a PostgreSQL database. + # + # Example: + # drop_database 'matt_development' + def drop_database(name) # :nodoc: + execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}" + end + + def drop_table(*table_names, **options) # :nodoc: + table_names.each { |table_name| schema_cache.clear_data_source_cache!(table_name.to_s) } + execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{table_names.map { |table_name| quote_table_name(table_name) }.join(', ')}#{' CASCADE' if options[:force] == :cascade}" + end + + # Returns true if schema exists. + def schema_exists?(name) + query_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = #{quote(name)}", "SCHEMA").to_i > 0 + end + + # Verifies existence of an index with a given name. + def index_name_exists?(table_name, index_name) + table = quoted_scope(table_name) + index = quoted_scope(index_name) + + query_value(<<~SQL, "SCHEMA").to_i > 0 + SELECT COUNT(*) + FROM pg_class t + INNER JOIN pg_index d ON t.oid = d.indrelid + INNER JOIN pg_class i ON d.indexrelid = i.oid + LEFT JOIN pg_namespace n ON n.oid = t.relnamespace + WHERE i.relkind IN ('i', 'I') + AND i.relname = #{index[:name]} + AND t.relname = #{table[:name]} + AND n.nspname = #{table[:schema]} + SQL + end + + # Returns an array of indexes for the given table. + def indexes(table_name) # :nodoc: + scope = quoted_scope(table_name) + + result = query(<<~SQL, "SCHEMA") + SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid, + pg_catalog.obj_description(i.oid, 'pg_class') AS comment, d.indisvalid + FROM pg_class t + INNER JOIN pg_index d ON t.oid = d.indrelid + INNER JOIN pg_class i ON d.indexrelid = i.oid + LEFT JOIN pg_namespace n ON n.oid = t.relnamespace + WHERE i.relkind IN ('i', 'I') + AND d.indisprimary = 'f' + AND t.relname = #{scope[:name]} + AND n.nspname = #{scope[:schema]} + ORDER BY i.relname + SQL + + result.map do |row| + index_name = row[0] + unique = row[1] + indkey = row[2].split(" ").map(&:to_i) + inddef = row[3] + oid = row[4] + comment = row[5] + valid = row[6] + using, expressions, include, nulls_not_distinct, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: INCLUDE \((.+?)\))?( NULLS NOT DISTINCT)?(?: WHERE (.+))?\z/m).flatten + + orders = {} + opclasses = {} + include_columns = include ? include.split(",").map { |c| Utils.unquote_identifier(c.strip.gsub('""', '"')) } : [] + + if indkey.include?(0) + columns = expressions + else + columns = column_names_from_column_numbers(oid, indkey) + + # prevent INCLUDE columns from being matched + columns.reject! { |c| include_columns.include?(c) } + + # add info on sort order (only desc order is explicitly specified, asc is the default) + # and non-default opclasses + expressions.scan(/(?\w+)"?\s?(?\w+_ops(_\w+)?)?\s?(?DESC)?\s?(?NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls| + opclasses[column] = opclass.to_sym if opclass + if nulls + orders[column] = [desc, nulls].compact.join(" ") + else + orders[column] = :desc if desc + end + end + end + + IndexDefinition.new( + table_name, + index_name, + unique, + columns, + orders: orders, + opclasses: opclasses, + where: where, + using: using.to_sym, + include: include_columns.presence, + nulls_not_distinct: nulls_not_distinct.present?, + comment: comment.presence, + valid: valid + ) + end + end + + def table_options(table_name) # :nodoc: + options = {} + + comment = table_comment(table_name) + + options[:comment] = comment if comment + + inherited_table_names = inherited_table_names(table_name).presence + + options[:options] = "INHERITS (#{inherited_table_names.join(", ")})" if inherited_table_names + + if !options[:options] && supports_native_partitioning? + partition_definition = table_partition_definition(table_name) + + options[:options] = "PARTITION BY #{partition_definition}" if partition_definition + end + + options + end + + # Returns a comment stored in database for given table + def table_comment(table_name) # :nodoc: + scope = quoted_scope(table_name, type: "BASE TABLE") + if scope[:name] + query_value(<<~SQL, "SCHEMA") + SELECT pg_catalog.obj_description(c.oid, 'pg_class') + FROM pg_catalog.pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relname = #{scope[:name]} + AND c.relkind IN (#{scope[:type]}) + AND n.nspname = #{scope[:schema]} + SQL + end + end + + # Returns the partition definition of a given table + def table_partition_definition(table_name) # :nodoc: + scope = quoted_scope(table_name, type: "BASE TABLE") + + query_value(<<~SQL, "SCHEMA") + SELECT pg_catalog.pg_get_partkeydef(c.oid) + FROM pg_catalog.pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relname = #{scope[:name]} + AND c.relkind IN (#{scope[:type]}) + AND n.nspname = #{scope[:schema]} + SQL + end + + # Returns the inherited table name of a given table + def inherited_table_names(table_name) # :nodoc: + scope = quoted_scope(table_name, type: "BASE TABLE") + + query_values(<<~SQL, "SCHEMA") + SELECT parent.relname + FROM pg_catalog.pg_inherits i + JOIN pg_catalog.pg_class child ON i.inhrelid = child.oid + JOIN pg_catalog.pg_class parent ON i.inhparent = parent.oid + LEFT JOIN pg_namespace n ON n.oid = child.relnamespace + WHERE child.relname = #{scope[:name]} + AND child.relkind IN (#{scope[:type]}) + AND n.nspname = #{scope[:schema]} + SQL + end + + # Returns the current database name. + def current_database + query_value("SELECT current_database()", "SCHEMA") + end + + # Returns the current schema name. + def current_schema + query_value("SELECT current_schema", "SCHEMA") + end + + # Returns the current database encoding format. + def encoding + query_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA") + end + + # Returns the current database collation. + def collation + query_value("SELECT datcollate FROM pg_database WHERE datname = current_database()", "SCHEMA") + end + + # Returns the current database ctype. + def ctype + query_value("SELECT datctype FROM pg_database WHERE datname = current_database()", "SCHEMA") + end + + # Returns an array of schema names. + def schema_names + query_values(<<~SQL, "SCHEMA") + SELECT nspname + FROM pg_namespace + WHERE nspname !~ '^pg_.*' + AND nspname NOT IN ('information_schema') + ORDER by nspname; + SQL + end + + # Creates a schema for the given schema name. + def create_schema(schema_name, force: nil, if_not_exists: nil) + if force && if_not_exists + raise ArgumentError, "Options `:force` and `:if_not_exists` cannot be used simultaneously." + end + + if force + drop_schema(schema_name, if_exists: true) + end + + execute("CREATE SCHEMA#{' IF NOT EXISTS' if if_not_exists} #{quote_schema_name(schema_name)}") + end + + # Drops the schema for the given schema name. + def drop_schema(schema_name, **options) + execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE" + end + + # Sets the schema search path to a string of comma-separated schema names. + # Names beginning with $ have to be quoted (e.g. $user => '$user'). + # See: https://www.postgresql.org/docs/current/static/ddl-schemas.html + # + # This should be not be called manually but set in database.yml. + def schema_search_path=(schema_csv) + if schema_csv + internal_execute("SET search_path TO #{schema_csv}") + @schema_search_path = schema_csv + end + end + + # Returns the active schema search path. + def schema_search_path + @schema_search_path ||= query_value("SHOW search_path", "SCHEMA") + end + + # Returns the current client message level. + def client_min_messages + query_value("SHOW client_min_messages", "SCHEMA") + end + + # Set the client message level. + def client_min_messages=(level) + internal_execute("SET client_min_messages TO '#{level}'", "SCHEMA") + end + + # Returns the sequence name for a table's primary key or some other specified key. + def default_sequence_name(table_name, pk = "id") # :nodoc: + return nil if pk.is_a?(Array) + + result = serial_sequence(table_name, pk) + return nil unless result + Utils.extract_schema_qualified_name(result).to_s + rescue ActiveRecord::StatementInvalid + PostgreSQL::Name.new(nil, "#{table_name}_#{pk}_seq").to_s + end + + def serial_sequence(table, column) + query_value("SELECT pg_get_serial_sequence(#{quote(table)}, #{quote(column)})", "SCHEMA") + end + + # Sets the sequence of a table's primary key to the specified value. + def set_pk_sequence!(table, value) # :nodoc: + pk, sequence = pk_and_sequence_for(table) + + if pk + if sequence + quoted_sequence = quote_table_name(sequence) + + query_value("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA") + else + @logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger + end + end + end + + # Resets the sequence of a table's primary key to the maximum value. + def reset_pk_sequence!(table, pk = nil, sequence = nil) # :nodoc: + unless pk && sequence + default_pk, default_sequence = pk_and_sequence_for(table) + + pk ||= default_pk + sequence ||= default_sequence + end + + if @logger && pk && !sequence + @logger.warn "#{table} has primary key #{pk} with no default sequence." + end + + if pk && sequence + quoted_sequence = quote_table_name(sequence) + max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA") + if max_pk.nil? + if database_version >= 10_00_00 + minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA") + else + minvalue = query_value("SELECT min_value FROM #{quoted_sequence}", "SCHEMA") + end + end + + query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk || minvalue}, #{max_pk ? true : false})", "SCHEMA") + end + end + + # Returns a table's primary key and belonging sequence. + def pk_and_sequence_for(table) # :nodoc: + # First try looking for a sequence with a dependency on the + # given table's primary key. + result = query(<<~SQL, "SCHEMA")[0] + SELECT attr.attname, nsp.nspname, seq.relname + FROM pg_class seq, + pg_attribute attr, + pg_depend dep, + pg_constraint cons, + pg_namespace nsp + WHERE seq.oid = dep.objid + AND seq.relkind = 'S' + AND attr.attrelid = dep.refobjid + AND attr.attnum = dep.refobjsubid + AND attr.attrelid = cons.conrelid + AND attr.attnum = cons.conkey[1] + AND seq.relnamespace = nsp.oid + AND cons.contype = 'p' + AND dep.classid = 'pg_class'::regclass + AND dep.refobjid = #{quote(quote_table_name(table))}::regclass + SQL + + if result.nil? || result.empty? + result = query(<<~SQL, "SCHEMA")[0] + SELECT attr.attname, nsp.nspname, + CASE + WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL + WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN + substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), + strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1) + ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) + END + FROM pg_class t + JOIN pg_attribute attr ON (t.oid = attrelid) + JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum) + JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1]) + JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid) + WHERE t.oid = #{quote(quote_table_name(table))}::regclass + AND cons.contype = 'p' + AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate|gen_random_uuid' + SQL + end + + pk = result.shift + if result.last + [pk, PostgreSQL::Name.new(*result)] + else + [pk, nil] + end + rescue + nil + end + + def primary_keys(table_name) # :nodoc: + query_values(<<~SQL, "SCHEMA") + SELECT a.attname + FROM ( + SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx + FROM pg_index + WHERE indrelid = #{quote(quote_table_name(table_name))}::regclass + AND indisprimary + ) i + JOIN pg_attribute a + ON a.attrelid = i.indrelid + AND a.attnum = i.indkey[i.idx] + ORDER BY i.idx + SQL + end + + # Renames a table. + # Also renames a table's primary key sequence if the sequence name exists and + # matches the Active Record default. + # + # Example: + # rename_table('octopuses', 'octopi') + def rename_table(table_name, new_name, **options) + validate_table_length!(new_name) unless options[:_uses_legacy_table_name] + clear_cache! + schema_cache.clear_data_source_cache!(table_name.to_s) + schema_cache.clear_data_source_cache!(new_name.to_s) + execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}" + pk, seq = pk_and_sequence_for(new_name) + if pk + # PostgreSQL automatically creates an index for PRIMARY KEY with name consisting of + # truncated table name and "_pkey" suffix fitting into max_identifier_length number of characters. + max_pkey_prefix = max_identifier_length - "_pkey".size + idx = "#{table_name[0, max_pkey_prefix]}_pkey" + new_idx = "#{new_name[0, max_pkey_prefix]}_pkey" + execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}" + + # PostgreSQL automatically creates a sequence for PRIMARY KEY with name consisting of + # truncated table name and "#{primary_key}_seq" suffix fitting into max_identifier_length number of characters. + max_seq_prefix = max_identifier_length - "_#{pk}_seq".size + if seq && seq.identifier == "#{table_name[0, max_seq_prefix]}_#{pk}_seq" + new_seq = "#{new_name[0, max_seq_prefix]}_#{pk}_seq" + execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}" + end + end + rename_table_indexes(table_name, new_name, **options) + end + + def add_column(table_name, column_name, type, **options) # :nodoc: + clear_cache! + super + change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment) + end + + def change_column(table_name, column_name, type, **options) # :nodoc: + clear_cache! + sqls, procs = Array(change_column_for_alter(table_name, column_name, type, **options)).partition { |v| v.is_a?(String) } + execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}" + procs.each(&:call) + end + + # Builds a ChangeColumnDefinition object. + # + # This definition object contains information about the column change that would occur + # if the same arguments were passed to #change_column. See #change_column for information about + # passing a +table_name+, +column_name+, +type+ and other options that can be passed. + def build_change_column_definition(table_name, column_name, type, **options) # :nodoc: + td = create_table_definition(table_name) + cd = td.new_column_definition(column_name, type, **options) + ChangeColumnDefinition.new(cd, column_name) + end + + # Changes the default value of a table column. + def change_column_default(table_name, column_name, default_or_changes) # :nodoc: + execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}" + end + + def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc: + column = column_for(table_name, column_name) + return unless column + + default = extract_new_default_value(default_or_changes) + ChangeColumnDefaultDefinition.new(column, default) + end + + def change_column_null(table_name, column_name, null, default = nil) # :nodoc: + validate_change_column_null_argument!(null) + + clear_cache! + unless null || default.nil? + column = column_for(table_name, column_name) + execute "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL" if column + end + execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL" + end + + # Adds comment for given table column or drops it if +comment+ is a +nil+ + def change_column_comment(table_name, column_name, comment_or_changes) # :nodoc: + clear_cache! + comment = extract_new_comment_value(comment_or_changes) + execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS #{quote(comment)}" + end + + # Adds comment for given table or drops it if +comment+ is a +nil+ + def change_table_comment(table_name, comment_or_changes) # :nodoc: + clear_cache! + comment = extract_new_comment_value(comment_or_changes) + execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS #{quote(comment)}" + end + + # Renames a column in a table. + def rename_column(table_name, column_name, new_column_name) # :nodoc: + clear_cache! + execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}") + rename_column_indexes(table_name, column_name, new_column_name) + end + + def add_index(table_name, column_name, **options) # :nodoc: + create_index = build_create_index_definition(table_name, column_name, **options) + result = execute schema_creation.accept(create_index) + + index = create_index.index + execute "COMMENT ON INDEX #{quote_column_name(index.name)} IS #{quote(index.comment)}" if index.comment + result + end + + def build_create_index_definition(table_name, column_name, **options) # :nodoc: + index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options) + CreateIndexDefinition.new(index, algorithm, if_not_exists) + end + + def remove_index(table_name, column_name = nil, **options) # :nodoc: + table = Utils.extract_schema_qualified_name(table_name.to_s) + + if options.key?(:name) + provided_index = Utils.extract_schema_qualified_name(options[:name].to_s) + + options[:name] = provided_index.identifier + table = PostgreSQL::Name.new(provided_index.schema, table.identifier) unless table.schema.present? + + if provided_index.schema.present? && table.schema != provided_index.schema + raise ArgumentError.new("Index schema '#{provided_index.schema}' does not match table schema '#{table.schema}'") + end + end + + return if options[:if_exists] && !index_exists?(table_name, column_name, **options) + + index_to_remove = PostgreSQL::Name.new(table.schema, index_name_for_remove(table.to_s, column_name, options)) + + execute "DROP INDEX #{index_algorithm(options[:algorithm])} #{quote_table_name(index_to_remove)}" + end + + # Renames an index of a table. Raises error if length of new + # index name is greater than allowed limit. + def rename_index(table_name, old_name, new_name) + validate_index_length!(table_name, new_name) + + schema, = extract_schema_qualified_name(table_name) + execute "ALTER INDEX #{quote_table_name(schema) + '.' if schema}#{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}" + end + + def index_name(table_name, options) # :nodoc: + _schema, table_name = extract_schema_qualified_name(table_name.to_s) + super + end + + def add_foreign_key(from_table, to_table, **options) + assert_valid_deferrable(options[:deferrable]) + + super + end + + def foreign_keys(table_name) + scope = quoted_scope(table_name) + fk_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false) + SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid, c.condeferrable AS deferrable, c.condeferred AS deferred, c.conkey, c.confkey, c.conrelid, c.confrelid + FROM pg_constraint c + JOIN pg_class t1 ON c.conrelid = t1.oid + JOIN pg_class t2 ON c.confrelid = t2.oid + JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid + JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid + JOIN pg_namespace t3 ON c.connamespace = t3.oid + WHERE c.contype = 'f' + AND t1.relname = #{scope[:name]} + AND t3.nspname = #{scope[:schema]} + ORDER BY c.conname + SQL + + fk_info.map do |row| + to_table = Utils.unquote_identifier(row["to_table"]) + conkey = row["conkey"].scan(/\d+/).map(&:to_i) + confkey = row["confkey"].scan(/\d+/).map(&:to_i) + + if conkey.size > 1 + column = column_names_from_column_numbers(row["conrelid"], conkey) + primary_key = column_names_from_column_numbers(row["confrelid"], confkey) + else + column = Utils.unquote_identifier(row["column"]) + primary_key = row["primary_key"] + end + + options = { + column: column, + name: row["name"], + primary_key: primary_key + } + + options[:on_delete] = extract_foreign_key_action(row["on_delete"]) + options[:on_update] = extract_foreign_key_action(row["on_update"]) + options[:deferrable] = extract_constraint_deferrable(row["deferrable"], row["deferred"]) + + options[:validate] = row["valid"] + + ForeignKeyDefinition.new(table_name, to_table, options) + end + end + + def foreign_tables + query_values(data_source_sql(type: "FOREIGN TABLE"), "SCHEMA") + end + + def foreign_table_exists?(table_name) + query_values(data_source_sql(table_name, type: "FOREIGN TABLE"), "SCHEMA").any? if table_name.present? + end + + def check_constraints(table_name) # :nodoc: + scope = quoted_scope(table_name) + + check_info = internal_exec_query(<<-SQL, "SCHEMA", allow_retry: true, materialize_transactions: false) + SELECT conname, pg_get_constraintdef(c.oid, true) AS constraintdef, c.convalidated AS valid + FROM pg_constraint c + JOIN pg_class t ON c.conrelid = t.oid + JOIN pg_namespace n ON n.oid = c.connamespace + WHERE c.contype = 'c' + AND t.relname = #{scope[:name]} + AND n.nspname = #{scope[:schema]} + SQL + + check_info.map do |row| + options = { + name: row["conname"], + validate: row["valid"] + } + expression = row["constraintdef"][/CHECK \((.+)\)/m, 1] + + CheckConstraintDefinition.new(table_name, expression, options) + end + end + + # Returns an array of exclusion constraints for the given table. + # The exclusion constraints are represented as ExclusionConstraintDefinition objects. + def exclusion_constraints(table_name) + scope = quoted_scope(table_name) + + exclusion_info = internal_exec_query(<<-SQL, "SCHEMA") + SELECT conname, pg_get_constraintdef(c.oid) AS constraintdef, c.condeferrable, c.condeferred + FROM pg_constraint c + JOIN pg_class t ON c.conrelid = t.oid + JOIN pg_namespace n ON n.oid = c.connamespace + WHERE c.contype = 'x' + AND t.relname = #{scope[:name]} + AND n.nspname = #{scope[:schema]} + SQL + + exclusion_info.map do |row| + method_and_elements, predicate = row["constraintdef"].split(" WHERE ") + method_and_elements_parts = method_and_elements.match(/EXCLUDE(?: USING (?\S+))? \((?.+)\)/) + predicate.remove!(/ DEFERRABLE(?: INITIALLY (?:IMMEDIATE|DEFERRED))?/) if predicate + predicate = predicate.from(2).to(-3) if predicate # strip 2 opening and closing parentheses + + deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"]) + + options = { + name: row["conname"], + using: method_and_elements_parts["using"].to_sym, + where: predicate, + deferrable: deferrable + } + + ExclusionConstraintDefinition.new(table_name, method_and_elements_parts["expression"], options) + end + end + + # Returns an array of unique constraints for the given table. + # The unique constraints are represented as UniqueConstraintDefinition objects. + def unique_constraints(table_name) + scope = quoted_scope(table_name) + + unique_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false) + SELECT c.conname, c.conrelid, c.conkey, c.condeferrable, c.condeferred, pg_get_constraintdef(c.oid) AS constraintdef + FROM pg_constraint c + JOIN pg_class t ON c.conrelid = t.oid + JOIN pg_namespace n ON n.oid = c.connamespace + WHERE c.contype = 'u' + AND t.relname = #{scope[:name]} + AND n.nspname = #{scope[:schema]} + SQL + + unique_info.map do |row| + conkey = row["conkey"].delete("{}").split(",").map(&:to_i) + columns = column_names_from_column_numbers(row["conrelid"], conkey) + + nulls_not_distinct = row["constraintdef"].start_with?("UNIQUE NULLS NOT DISTINCT") + deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"]) + + options = { + name: row["conname"], + nulls_not_distinct: nulls_not_distinct, + deferrable: deferrable + } + + UniqueConstraintDefinition.new(table_name, columns, options) + end + end + + # Adds a new exclusion constraint to the table. +expression+ is a String + # representation of a list of exclusion elements and operators. + # + # add_exclusion_constraint :products, "price WITH =, availability_range WITH &&", using: :gist, name: "price_check" + # + # generates: + # + # ALTER TABLE "products" ADD CONSTRAINT price_check EXCLUDE USING gist (price WITH =, availability_range WITH &&) + # + # The +options+ hash can include the following keys: + # [:name] + # The constraint name. Defaults to excl_rails_. + # [:deferrable] + # Specify whether or not the exclusion constraint should be deferrable. Valid values are +false+ or +:immediate+ or +:deferred+ to specify the default behavior. Defaults to +false+. + # [:using] + # Specify which index method to use when creating this exclusion constraint (e.g. +:btree+, +:gist+ etc). + # [:where] + # Specify an exclusion constraint on a subset of the table (internally PostgreSQL creates a partial index for this). + def add_exclusion_constraint(table_name, expression, **options) + options = exclusion_constraint_options(table_name, expression, options) + at = create_alter_table(table_name) + at.add_exclusion_constraint(expression, options) + + execute schema_creation.accept(at) + end + + def exclusion_constraint_options(table_name, expression, options) # :nodoc: + assert_valid_deferrable(options[:deferrable]) + + options = options.dup + options[:name] ||= exclusion_constraint_name(table_name, expression: expression, **options) + options + end + + # Removes the given exclusion constraint from the table. + # + # remove_exclusion_constraint :products, name: "price_check" + # + # The +expression+ parameter will be ignored if present. It can be helpful + # to provide this in a migration's +change+ method so it can be reverted. + # In that case, +expression+ will be used by #add_exclusion_constraint. + def remove_exclusion_constraint(table_name, expression = nil, **options) + excl_name_to_delete = exclusion_constraint_for!(table_name, expression: expression, **options).name + + remove_constraint(table_name, excl_name_to_delete) + end + + # Adds a new unique constraint to the table. + # + # add_unique_constraint :sections, [:position], deferrable: :deferred, name: "unique_position", nulls_not_distinct: true + # + # generates: + # + # ALTER TABLE "sections" ADD CONSTRAINT unique_position UNIQUE (position) DEFERRABLE INITIALLY DEFERRED + # + # If you want to change an existing unique index to deferrable, you can use :using_index to create deferrable unique constraints. + # + # add_unique_constraint :sections, deferrable: :deferred, name: "unique_position", using_index: "index_sections_on_position" + # + # The +options+ hash can include the following keys: + # [:name] + # The constraint name. Defaults to uniq_rails_. + # [:deferrable] + # Specify whether or not the unique constraint should be deferrable. Valid values are +false+ or +:immediate+ or +:deferred+ to specify the default behavior. Defaults to +false+. + # [:using_index] + # To specify an existing unique index name. Defaults to +nil+. + # [:nulls_not_distinct] + # Create a unique constraint where NULLs are treated equally. + # Note: only supported by PostgreSQL version 15.0.0 and greater. + def add_unique_constraint(table_name, column_name = nil, **options) + options = unique_constraint_options(table_name, column_name, options) + at = create_alter_table(table_name) + at.add_unique_constraint(column_name, options) + + execute schema_creation.accept(at) + end + + def unique_constraint_options(table_name, column_name, options) # :nodoc: + assert_valid_deferrable(options[:deferrable]) + + if column_name && options[:using_index] + raise ArgumentError, "Cannot specify both column_name and :using_index options." + end + + options = options.dup + options[:name] ||= unique_constraint_name(table_name, column: column_name, **options) + options + end + + # Removes the given unique constraint from the table. + # + # remove_unique_constraint :sections, name: "unique_position" + # + # The +column_name+ parameter will be ignored if present. It can be helpful + # to provide this in a migration's +change+ method so it can be reverted. + # In that case, +column_name+ will be used by #add_unique_constraint. + def remove_unique_constraint(table_name, column_name = nil, **options) + unique_name_to_delete = unique_constraint_for!(table_name, column: column_name, **options).name + + remove_constraint(table_name, unique_name_to_delete) + end + + # Maps logical Rails types to PostgreSQL-specific data types. + def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, enum_type: nil, **) # :nodoc: + sql = \ + case type.to_s + when "binary" + # PostgreSQL doesn't support limits on binary (bytea) columns. + # The hard limit is 1GB, because of a 32-bit size field, and TOAST. + case limit + when nil, 0..0x3fffffff; super(type) + else raise ArgumentError, "No binary type has byte size #{limit}. The limit on binary can be at most 1GB - 1byte." + end + when "text" + # PostgreSQL doesn't support limits on text columns. + # The hard limit is 1GB, according to section 8.3 in the manual. + case limit + when nil, 0..0x3fffffff; super(type) + else raise ArgumentError, "No text type has byte size #{limit}. The limit on text can be at most 1GB - 1byte." + end + when "integer" + case limit + when 1, 2; "smallint" + when nil, 3, 4; "integer" + when 5..8; "bigint" + else raise ArgumentError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead." + end + when "enum" + raise ArgumentError, "enum_type is required for enums" if enum_type.nil? + + enum_type + else + super + end + + sql = "#{sql}[]" if array && type != :primary_key + sql + end + + # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and + # requires that the ORDER BY include the distinct column. + def columns_for_distinct(columns, orders) # :nodoc: + order_columns = orders.compact_blank.map { |s| + # Convert Arel node to string + s = visitor.compile(s) unless s.is_a?(String) + # Remove any ASC/DESC modifiers + s.gsub(/\s+(?:ASC|DESC)\b/i, "") + .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "") + }.compact_blank.map.with_index { |column, i| "#{column} AS alias_#{i}" } + + (order_columns << super).join(", ") + end + + def update_table_definition(table_name, base) # :nodoc: + PostgreSQL::Table.new(table_name, base) + end + + def create_schema_dumper(options) # :nodoc: + PostgreSQL::SchemaDumper.create(self, options) + end + + # Validates the given constraint. + # + # Validates the constraint named +constraint_name+ on +accounts+. + # + # validate_constraint :accounts, :constraint_name + def validate_constraint(table_name, constraint_name) + at = create_alter_table table_name + at.validate_constraint constraint_name + + execute schema_creation.accept(at) + end + + # Validates the given foreign key. + # + # Validates the foreign key on +accounts.branch_id+. + # + # validate_foreign_key :accounts, :branches + # + # Validates the foreign key on +accounts.owner_id+. + # + # validate_foreign_key :accounts, column: :owner_id + # + # Validates the foreign key named +special_fk_name+ on the +accounts+ table. + # + # validate_foreign_key :accounts, name: :special_fk_name + # + # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key. + def validate_foreign_key(from_table, to_table = nil, **options) + fk_name_to_validate = foreign_key_for!(from_table, to_table: to_table, **options).name + + validate_constraint from_table, fk_name_to_validate + end + + # Validates the given check constraint. + # + # validate_check_constraint :products, name: "price_check" + # + # The +options+ hash accepts the same keys as {add_check_constraint}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_check_constraint]. + def validate_check_constraint(table_name, **options) + chk_name_to_validate = check_constraint_for!(table_name, **options).name + + validate_constraint table_name, chk_name_to_validate + end + + def foreign_key_column_for(table_name, column_name) # :nodoc: + _schema, table_name = extract_schema_qualified_name(table_name) + super + end + + def add_index_options(table_name, column_name, **options) # :nodoc: + if (where = options[:where]) && table_exists?(table_name) && column_exists?(table_name, where) + options[:where] = quote_column_name(where) + end + super + end + + def quoted_include_columns_for_index(column_names) # :nodoc: + return quote_column_name(column_names) if column_names.is_a?(Symbol) + + quoted_columns = column_names.each_with_object({}) do |name, result| + result[name.to_sym] = quote_column_name(name).dup + end + add_options_for_index_columns(quoted_columns).values.join(", ") + end + + def schema_creation # :nodoc: + PostgreSQL::SchemaCreation.new(self) + end + + private + def create_table_definition(name, **options) + PostgreSQL::TableDefinition.new(self, name, **options) + end + + def create_alter_table(name) + PostgreSQL::AlterTable.new create_table_definition(name) + end + + def new_column_from_field(table_name, field, _definitions) + column_name, type, default, notnull, oid, fmod, collation, comment, identity, attgenerated = field + type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i) + default_value = extract_value_from_default(default) + + if attgenerated.present? + default_function = default + else + default_function = extract_default_function(default_value, default) + end + + if match = default_function&.match(/\Anextval\('"?(?.+_(?seq\d*))"?'::regclass\)\z/) + serial = sequence_name_from_parts(table_name, column_name, match[:suffix]) == match[:sequence_name] + end + + PostgreSQL::Column.new( + column_name, + default_value, + type_metadata, + !notnull, + default_function, + collation: collation, + comment: comment.presence, + serial: serial, + identity: identity.presence, + generated: attgenerated + ) + end + + def fetch_type_metadata(column_name, sql_type, oid, fmod) + cast_type = get_oid_type(oid, fmod, column_name, sql_type) + simple_type = SqlTypeMetadata.new( + sql_type: sql_type, + type: cast_type.type, + limit: cast_type.limit, + precision: cast_type.precision, + scale: cast_type.scale, + ) + PostgreSQL::TypeMetadata.new(simple_type, oid: oid, fmod: fmod) + end + + def sequence_name_from_parts(table_name, column_name, suffix) + over_length = [table_name, column_name, suffix].sum(&:length) + 2 - max_identifier_length + + if over_length > 0 + column_name_length = [(max_identifier_length - suffix.length - 2) / 2, column_name.length].min + over_length -= column_name.length - column_name_length + column_name = column_name[0, column_name_length - [over_length, 0].min] + end + + if over_length > 0 + table_name = table_name[0, table_name.length - over_length] + end + + "#{table_name}_#{column_name}_#{suffix}" + end + + def extract_foreign_key_action(specifier) + case specifier + when "c"; :cascade + when "n"; :nullify + when "r"; :restrict + end + end + + def assert_valid_deferrable(deferrable) + return if !deferrable || %i(immediate deferred).include?(deferrable) + + raise ArgumentError, "deferrable must be `:immediate` or `:deferred`, got: `#{deferrable.inspect}`" + end + + def extract_constraint_deferrable(deferrable, deferred) + deferrable && (deferred ? :deferred : :immediate) + end + + def reference_name_for_table(table_name) + _schema, table_name = extract_schema_qualified_name(table_name.to_s) + table_name.singularize + end + + def add_column_for_alter(table_name, column_name, type, **options) + return super unless options.key?(:comment) + [super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }] + end + + def change_column_for_alter(table_name, column_name, type, **options) + change_col_def = build_change_column_definition(table_name, column_name, type, **options) + sqls = [schema_creation.accept(change_col_def)] + sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment) + sqls + end + + def change_column_null_for_alter(table_name, column_name, null, default = nil) + if default.nil? + "ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL" + else + Proc.new { change_column_null(table_name, column_name, null, default) } + end + end + + def add_index_opclass(quoted_columns, **options) + opclasses = options_for_index_columns(options[:opclass]) + quoted_columns.each do |name, column| + column << " #{opclasses[name]}" if opclasses[name].present? + end + end + + def add_options_for_index_columns(quoted_columns, **options) + quoted_columns = add_index_opclass(quoted_columns, **options) + super + end + + def exclusion_constraint_name(table_name, **options) + options.fetch(:name) do + expression = options.fetch(:expression) + identifier = "#{table_name}_#{expression}_excl" + hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10) + + "excl_rails_#{hashed_identifier}" + end + end + + def exclusion_constraint_for(table_name, **options) + excl_name = exclusion_constraint_name(table_name, **options) + exclusion_constraints(table_name).detect { |excl| excl.name == excl_name } + end + + def exclusion_constraint_for!(table_name, expression: nil, **options) + exclusion_constraint_for(table_name, expression: expression, **options) || + raise(ArgumentError, "Table '#{table_name}' has no exclusion constraint for #{expression || options}") + end + + def unique_constraint_name(table_name, **options) + options.fetch(:name) do + column_or_index = Array(options[:column] || options[:using_index]).map(&:to_s) + identifier = "#{table_name}_#{column_or_index * '_and_'}_unique" + hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10) + + "uniq_rails_#{hashed_identifier}" + end + end + + def unique_constraint_for(table_name, **options) + name = unique_constraint_name(table_name, **options) unless options.key?(:column) + unique_constraints(table_name).detect { |unique_constraint| unique_constraint.defined_for?(name: name, **options) } + end + + def unique_constraint_for!(table_name, column: nil, **options) + unique_constraint_for(table_name, column: column, **options) || + raise(ArgumentError, "Table '#{table_name}' has no unique constraint for #{column || options}") + end + + def data_source_sql(name = nil, type: nil) + scope = quoted_scope(name, type: type) + scope[:type] ||= "'r','v','m','p','f'" # (r)elation/table, (v)iew, (m)aterialized view, (p)artitioned table, (f)oreign table + + sql = +"SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace" + sql << " WHERE n.nspname = #{scope[:schema]}" + sql << " AND c.relname = #{scope[:name]}" if scope[:name] + sql << " AND c.relkind IN (#{scope[:type]})" + sql + end + + def quoted_scope(name = nil, type: nil) + schema, name = extract_schema_qualified_name(name) + type = \ + case type + when "BASE TABLE" + "'r','p'" + when "VIEW" + "'v','m'" + when "FOREIGN TABLE" + "'f'" + end + scope = {} + scope[:schema] = schema ? quote(schema) : "ANY (current_schemas(false))" + scope[:name] = quote(name) if name + scope[:type] = type if type + scope + end + + def extract_schema_qualified_name(string) + name = Utils.extract_schema_qualified_name(string.to_s) + [name.schema, name.identifier] + end + + def column_names_from_column_numbers(table_oid, column_numbers) + Hash[query(<<~SQL, "SCHEMA")].values_at(*column_numbers).compact + SELECT a.attnum, a.attname + FROM pg_attribute a + WHERE a.attrelid = #{table_oid} + AND a.attnum IN (#{column_numbers.join(", ")}) + SQL + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/type_metadata.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/type_metadata.rb new file mode 100644 index 00000000..b7f64793 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/type_metadata.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module ActiveRecord + # :stopdoc: + module ConnectionAdapters + module PostgreSQL + class TypeMetadata < DelegateClass(SqlTypeMetadata) + undef to_yaml if method_defined?(:to_yaml) + + include Deduplicable + + attr_reader :oid, :fmod + + def initialize(type_metadata, oid: nil, fmod: nil) + super(type_metadata) + @oid = oid + @fmod = fmod + end + + def ==(other) + other.is_a?(TypeMetadata) && + __getobj__ == other.__getobj__ && + oid == other.oid && + fmod == other.fmod + end + alias eql? == + + def hash + TypeMetadata.hash ^ + __getobj__.hash ^ + oid.hash ^ + fmod.hash + end + + private + def deduplicated + __setobj__(__getobj__.deduplicate) + super + end + end + end + PostgreSQLTypeMetadata = PostgreSQL::TypeMetadata + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/utils.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/utils.rb new file mode 100644 index 00000000..110b8017 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql/utils.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + # Value Object to hold a schema qualified name. + # This is usually the name of a PostgreSQL relation but it can also represent + # schema qualified type names. +schema+ and +identifier+ are unquoted to prevent + # double quoting. + class Name # :nodoc: + SEPARATOR = "." + attr_reader :schema, :identifier + + def initialize(schema, identifier) + @schema, @identifier = Utils.unquote_identifier(schema), Utils.unquote_identifier(identifier) + end + + def to_s + parts.join SEPARATOR + end + + def quoted + if schema + PG::Connection.quote_ident(schema) << SEPARATOR << PG::Connection.quote_ident(identifier) + else + PG::Connection.quote_ident(identifier) + end + end + + def ==(o) + o.class == self.class && o.parts == parts + end + alias_method :eql?, :== + + def hash + parts.hash + end + + protected + def parts + @parts ||= [@schema, @identifier].compact + end + end + + module Utils # :nodoc: + extend self + + # Returns an instance of ActiveRecord::ConnectionAdapters::PostgreSQL::Name + # extracted from +string+. + # +schema+ is +nil+ if not specified in +string+. + # +schema+ and +identifier+ exclude surrounding quotes (regardless of whether provided in +string+) + # +string+ supports the range of schema/table references understood by PostgreSQL, for example: + # + # * table_name + # * "table.name" + # * schema_name.table_name + # * schema_name."table.name" + # * "schema_name".table_name + # * "schema.name"."table name" + def extract_schema_qualified_name(string) + schema, table = string.scan(/[^".]+|"[^"]*"/) + if table.nil? + table = schema + schema = nil + end + PostgreSQL::Name.new(schema, table) + end + + def unquote_identifier(identifier) + if identifier && identifier.start_with?('"') + identifier[1..-2] + else + identifier + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql_adapter.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql_adapter.rb new file mode 100644 index 00000000..2cc185ae --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -0,0 +1,1189 @@ +# frozen_string_literal: true + +gem "pg", "~> 1.1" +require "pg" + +require "active_support/core_ext/object/try" +require "active_record/connection_adapters/abstract_adapter" +require "active_record/connection_adapters/statement_pool" +require "active_record/connection_adapters/postgresql/column" +require "active_record/connection_adapters/postgresql/database_statements" +require "active_record/connection_adapters/postgresql/explain_pretty_printer" +require "active_record/connection_adapters/postgresql/oid" +require "active_record/connection_adapters/postgresql/quoting" +require "active_record/connection_adapters/postgresql/referential_integrity" +require "active_record/connection_adapters/postgresql/schema_creation" +require "active_record/connection_adapters/postgresql/schema_definitions" +require "active_record/connection_adapters/postgresql/schema_dumper" +require "active_record/connection_adapters/postgresql/schema_statements" +require "active_record/connection_adapters/postgresql/type_metadata" +require "active_record/connection_adapters/postgresql/utils" + +module ActiveRecord + module ConnectionAdapters + # = Active Record PostgreSQL Adapter + # + # The PostgreSQL adapter works with the native C (https://github.com/ged/ruby-pg) driver. + # + # Options: + # + # * :host - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets, + # the default is to connect to localhost. + # * :port - Defaults to 5432. + # * :username - Defaults to be the same as the operating system name of the user running the application. + # * :password - Password to be used if the server demands password authentication. + # * :database - Defaults to be the same as the username. + # * :schema_search_path - An optional schema search path for the connection given + # as a string of comma-separated schema names. This is backward-compatible with the :schema_order option. + # * :encoding - An optional client encoding that is used in a SET client_encoding TO + # call on the connection. + # * :min_messages - An optional client min messages that is used in a + # SET client_min_messages TO call on the connection. + # * :variables - An optional hash of additional parameters that + # will be used in SET SESSION key = val calls on the connection. + # * :insert_returning - An optional boolean to control the use of RETURNING for INSERT statements + # defaults to true. + # + # Any further options are used as connection parameters to libpq. See + # https://www.postgresql.org/docs/current/static/libpq-connect.html for the + # list of parameters. + # + # In addition, default connection parameters of libpq can be set per environment variables. + # See https://www.postgresql.org/docs/current/static/libpq-envars.html . + class PostgreSQLAdapter < AbstractAdapter + ADAPTER_NAME = "PostgreSQL" + + class << self + def new_client(conn_params) + PG.connect(**conn_params) + rescue ::PG::Error => error + if conn_params && conn_params[:dbname] == "postgres" + raise ActiveRecord::ConnectionNotEstablished, error.message + elsif conn_params && conn_params[:dbname] && error.message.include?(conn_params[:dbname]) + raise ActiveRecord::NoDatabaseError.db_error(conn_params[:dbname]) + elsif conn_params && conn_params[:user] && error.message.include?(conn_params[:user]) + raise ActiveRecord::DatabaseConnectionError.username_error(conn_params[:user]) + elsif conn_params && conn_params[:host] && error.message.include?(conn_params[:host]) + raise ActiveRecord::DatabaseConnectionError.hostname_error(conn_params[:host]) + else + raise ActiveRecord::ConnectionNotEstablished, error.message + end + end + + def dbconsole(config, options = {}) + pg_config = config.configuration_hash + + ENV["PGUSER"] = pg_config[:username] if pg_config[:username] + ENV["PGHOST"] = pg_config[:host] if pg_config[:host] + ENV["PGPORT"] = pg_config[:port].to_s if pg_config[:port] + ENV["PGPASSWORD"] = pg_config[:password].to_s if pg_config[:password] && options[:include_password] + ENV["PGSSLMODE"] = pg_config[:sslmode].to_s if pg_config[:sslmode] + ENV["PGSSLCERT"] = pg_config[:sslcert].to_s if pg_config[:sslcert] + ENV["PGSSLKEY"] = pg_config[:sslkey].to_s if pg_config[:sslkey] + ENV["PGSSLROOTCERT"] = pg_config[:sslrootcert].to_s if pg_config[:sslrootcert] + if pg_config[:variables] + ENV["PGOPTIONS"] = pg_config[:variables].filter_map do |name, value| + "-c #{name}=#{value.to_s.gsub(/[ \\]/, '\\\\\0')}" unless value == ":default" || value == :default + end.join(" ") + end + find_cmd_and_exec(ActiveRecord.database_cli[:postgresql], config.database) + end + end + + ## + # :singleton-method: + # PostgreSQL allows the creation of "unlogged" tables, which do not record + # data in the PostgreSQL Write-Ahead Log. This can make the tables faster, + # but significantly increases the risk of data loss if the database + # crashes. As a result, this should not be used in production + # environments. If you would like all created tables to be unlogged in + # the test environment you can add the following to your test.rb file: + # + # ActiveSupport.on_load(:active_record_postgresqladapter) do + # self.create_unlogged_tables = true + # end + class_attribute :create_unlogged_tables, default: false + + ## + # :singleton-method: + # PostgreSQL supports multiple types for DateTimes. By default, if you use +datetime+ + # in migrations, \Rails will translate this to a PostgreSQL "timestamp without time zone". + # Change this in an initializer to use another NATIVE_DATABASE_TYPES. For example, to + # store DateTimes as "timestamp with time zone": + # + # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamptz + # + # Or if you are adding a custom type: + # + # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:my_custom_type] = { name: "my_custom_type_name" } + # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :my_custom_type + # + # If you're using +:ruby+ as your +config.active_record.schema_format+ and you change this + # setting, you should immediately run bin/rails db:migrate to update the types in your schema.rb. + class_attribute :datetime_type, default: :timestamp + + ## + # :singleton-method: + # Toggles automatic decoding of date columns. + # + # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.select_value("select '2024-01-01'::date").class #=> String + # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.decode_dates = true + # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.select_value("select '2024-01-01'::date").class #=> Date + class_attribute :decode_dates, default: false + + NATIVE_DATABASE_TYPES = { + primary_key: "bigserial primary key", + string: { name: "character varying" }, + text: { name: "text" }, + integer: { name: "integer", limit: 4 }, + bigint: { name: "bigint" }, + float: { name: "float" }, + decimal: { name: "decimal" }, + datetime: {}, # set dynamically based on datetime_type + timestamp: { name: "timestamp" }, + timestamptz: { name: "timestamptz" }, + time: { name: "time" }, + date: { name: "date" }, + daterange: { name: "daterange" }, + numrange: { name: "numrange" }, + tsrange: { name: "tsrange" }, + tstzrange: { name: "tstzrange" }, + int4range: { name: "int4range" }, + int8range: { name: "int8range" }, + binary: { name: "bytea" }, + boolean: { name: "boolean" }, + xml: { name: "xml" }, + tsvector: { name: "tsvector" }, + hstore: { name: "hstore" }, + inet: { name: "inet" }, + cidr: { name: "cidr" }, + macaddr: { name: "macaddr" }, + uuid: { name: "uuid" }, + json: { name: "json" }, + jsonb: { name: "jsonb" }, + ltree: { name: "ltree" }, + citext: { name: "citext" }, + point: { name: "point" }, + line: { name: "line" }, + lseg: { name: "lseg" }, + box: { name: "box" }, + path: { name: "path" }, + polygon: { name: "polygon" }, + circle: { name: "circle" }, + bit: { name: "bit" }, + bit_varying: { name: "bit varying" }, + money: { name: "money" }, + interval: { name: "interval" }, + oid: { name: "oid" }, + enum: {} # special type https://www.postgresql.org/docs/current/datatype-enum.html + } + + OID = PostgreSQL::OID # :nodoc: + + include PostgreSQL::Quoting + include PostgreSQL::ReferentialIntegrity + include PostgreSQL::SchemaStatements + include PostgreSQL::DatabaseStatements + + def supports_bulk_alter? + true + end + + def supports_index_sort_order? + true + end + + def supports_partitioned_indexes? + database_version >= 11_00_00 # >= 11.0 + end + + def supports_partial_index? + true + end + + def supports_index_include? + database_version >= 11_00_00 # >= 11.0 + end + + def supports_expression_index? + true + end + + def supports_transaction_isolation? + true + end + + def supports_foreign_keys? + true + end + + def supports_check_constraints? + true + end + + def supports_exclusion_constraints? + true + end + + def supports_unique_constraints? + true + end + + def supports_validate_constraints? + true + end + + def supports_deferrable_constraints? + true + end + + def supports_views? + true + end + + def supports_datetime_with_precision? + true + end + + def supports_json? + true + end + + def supports_comments? + true + end + + def supports_savepoints? + true + end + + def supports_restart_db_transaction? + database_version >= 12_00_00 # >= 12.0 + end + + def supports_insert_returning? + true + end + + def supports_insert_on_conflict? + database_version >= 9_05_00 # >= 9.5 + end + alias supports_insert_on_duplicate_skip? supports_insert_on_conflict? + alias supports_insert_on_duplicate_update? supports_insert_on_conflict? + alias supports_insert_conflict_target? supports_insert_on_conflict? + + def supports_virtual_columns? + database_version >= 12_00_00 # >= 12.0 + end + + def supports_identity_columns? # :nodoc: + database_version >= 10_00_00 # >= 10.0 + end + + def supports_nulls_not_distinct? + database_version >= 15_00_00 # >= 15.0 + end + + def supports_native_partitioning? # :nodoc: + database_version >= 10_00_00 # >= 10.0 + end + + def index_algorithms + { concurrently: "CONCURRENTLY" } + end + + class StatementPool < ConnectionAdapters::StatementPool # :nodoc: + def initialize(connection, max) + super(max) + @connection = connection + @counter = 0 + end + + def next_key + "a#{@counter += 1}" + end + + private + def dealloc(key) + # This is ugly, but safe: the statement pool is only + # accessed while holding the connection's lock. (And we + # don't need the complication of with_raw_connection because + # a reconnect would invalidate the entire statement pool.) + if conn = @connection.instance_variable_get(:@raw_connection) + conn.query "DEALLOCATE #{key}" if conn.status == PG::CONNECTION_OK + end + rescue PG::Error + end + end + + # Initializes and connects a PostgreSQL adapter. + def initialize(...) + super + + conn_params = @config.compact + + # Map ActiveRecords param names to PGs. + conn_params[:user] = conn_params.delete(:username) if conn_params[:username] + conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database] + + # Forward only valid config params to PG::Connection.connect. + valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl] + conn_params.slice!(*valid_conn_param_keys) + + @connection_parameters = conn_params + + @max_identifier_length = nil + @type_map = nil + @raw_connection = nil + @notice_receiver_sql_warnings = [] + + @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true + end + + def connected? + !(@raw_connection.nil? || @raw_connection.finished?) + end + + # Is this connection alive and ready for queries? + def active? + @lock.synchronize do + return false unless @raw_connection + @raw_connection.query ";" + verified! + end + true + rescue PG::Error + false + end + + def reload_type_map # :nodoc: + @lock.synchronize do + if @type_map + type_map.clear + else + @type_map = Type::HashLookupTypeMap.new + end + + initialize_type_map + end + end + + def reset! + @lock.synchronize do + return connect! unless @raw_connection + + unless @raw_connection.transaction_status == ::PG::PQTRANS_IDLE + @raw_connection.query "ROLLBACK" + end + @raw_connection.query "DISCARD ALL" + + super + end + end + + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. + def disconnect! + @lock.synchronize do + super + @raw_connection&.close rescue nil + @raw_connection = nil + end + end + + def discard! # :nodoc: + super + @raw_connection&.socket_io&.reopen(IO::NULL) rescue nil + @raw_connection = nil + end + + def native_database_types # :nodoc: + self.class.native_database_types + end + + def self.native_database_types # :nodoc: + @native_database_types ||= begin + types = NATIVE_DATABASE_TYPES.dup + types[:datetime] = types[datetime_type] + types + end + end + + def set_standard_conforming_strings + internal_execute("SET standard_conforming_strings = on", "SCHEMA") + end + + def supports_ddl_transactions? + true + end + + def supports_advisory_locks? + true + end + + def supports_explain? + true + end + + def supports_extensions? + true + end + + def supports_materialized_views? + true + end + + def supports_foreign_tables? + true + end + + def supports_pgcrypto_uuid? + database_version >= 9_04_00 # >= 9.4 + end + + def supports_optimizer_hints? + unless defined?(@has_pg_hint_plan) + @has_pg_hint_plan = extension_available?("pg_hint_plan") + end + @has_pg_hint_plan + end + + def supports_common_table_expressions? + true + end + + def supports_lazy_transactions? + true + end + + def get_advisory_lock(lock_id) # :nodoc: + unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63 + raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer") + end + query_value("SELECT pg_try_advisory_lock(#{lock_id})") + end + + def release_advisory_lock(lock_id) # :nodoc: + unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63 + raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer") + end + query_value("SELECT pg_advisory_unlock(#{lock_id})") + end + + def enable_extension(name, **) + schema, name = name.to_s.split(".").values_at(-2, -1) + sql = +"CREATE EXTENSION IF NOT EXISTS \"#{name}\"" + sql << " SCHEMA #{schema}" if schema + + internal_exec_query(sql).tap { reload_type_map } + end + + # Removes an extension from the database. + # + # [:force] + # Set to +:cascade+ to drop dependent objects as well. + # Defaults to false. + def disable_extension(name, force: false) + _schema, name = name.to_s.split(".").values_at(-2, -1) + internal_exec_query("DROP EXTENSION IF EXISTS \"#{name}\"#{' CASCADE' if force == :cascade}").tap { + reload_type_map + } + end + + def extension_available?(name) + query_value("SELECT true FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA") + end + + def extension_enabled?(name) + query_value("SELECT installed_version IS NOT NULL FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA") + end + + def extensions + query = <<~SQL + SELECT + pg_extension.extname, + n.nspname AS schema + FROM pg_extension + JOIN pg_namespace n ON pg_extension.extnamespace = n.oid + SQL + + internal_exec_query(query, "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values.map do |row| + name, schema = row[0], row[1] + schema = nil if schema == current_schema + [schema, name].compact.join(".") + end + end + + # Returns a list of defined enum types, and their values. + def enum_types + query = <<~SQL + SELECT + type.typname AS name, + type.OID AS oid, + n.nspname AS schema, + array_agg(enum.enumlabel ORDER BY enum.enumsortorder) AS value + FROM pg_enum AS enum + JOIN pg_type AS type ON (type.oid = enum.enumtypid) + JOIN pg_namespace n ON type.typnamespace = n.oid + WHERE n.nspname = ANY (current_schemas(false)) + GROUP BY type.OID, n.nspname, type.typname; + SQL + + internal_exec_query(query, "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values.each_with_object({}) do |row, memo| + name, schema = row[0], row[2] + schema = nil if schema == current_schema + full_name = [schema, name].compact.join(".") + memo[full_name] = row.last + end.to_a + end + + # Given a name and an array of values, creates an enum type. + def create_enum(name, values, **options) + sql_values = values.map { |s| quote(s) }.join(", ") + scope = quoted_scope(name) + query = <<~SQL + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_type t + JOIN pg_namespace n ON t.typnamespace = n.oid + WHERE t.typname = #{scope[:name]} + AND n.nspname = #{scope[:schema]} + ) THEN + CREATE TYPE #{quote_table_name(name)} AS ENUM (#{sql_values}); + END IF; + END + $$; + SQL + internal_exec_query(query).tap { reload_type_map } + end + + # Drops an enum type. + # + # If the if_exists: true option is provided, the enum is dropped + # only if it exists. Otherwise, if the enum doesn't exist, an error is + # raised. + # + # The +values+ parameter will be ignored if present. It can be helpful + # to provide this in a migration's +change+ method so it can be reverted. + # In that case, +values+ will be used by #create_enum. + def drop_enum(name, values = nil, **options) + query = <<~SQL + DROP TYPE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(name)}; + SQL + internal_exec_query(query).tap { reload_type_map } + end + + # Rename an existing enum type to something else. + def rename_enum(name, new_name = nil, **options) + new_name ||= options.fetch(:to) do + raise ArgumentError, "rename_enum requires two from/to name positional arguments." + end + + exec_query("ALTER TYPE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}").tap { reload_type_map } + end + + # Add enum value to an existing enum type. + def add_enum_value(type_name, value, **options) + before, after = options.values_at(:before, :after) + sql = +"ALTER TYPE #{quote_table_name(type_name)} ADD VALUE" + sql << " IF NOT EXISTS" if options[:if_not_exists] + sql << " #{quote(value)}" + + if before && after + raise ArgumentError, "Cannot have both :before and :after at the same time" + elsif before + sql << " BEFORE #{quote(before)}" + elsif after + sql << " AFTER #{quote(after)}" + end + + execute(sql).tap { reload_type_map } + end + + # Rename enum value on an existing enum type. + def rename_enum_value(type_name, **options) + unless database_version >= 10_00_00 # >= 10.0 + raise ArgumentError, "Renaming enum values is only supported in PostgreSQL 10 or later" + end + + from = options.fetch(:from) { raise ArgumentError, ":from is required" } + to = options.fetch(:to) { raise ArgumentError, ":to is required" } + + execute("ALTER TYPE #{quote_table_name(type_name)} RENAME VALUE #{quote(from)} TO #{quote(to)}").tap { + reload_type_map + } + end + + # Returns the configured supported identifier length supported by PostgreSQL + def max_identifier_length + @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i + end + + # Set the authorized user for this session + def session_auth=(user) + clear_cache! + internal_execute("SET SESSION AUTHORIZATION #{user}", nil, materialize_transactions: true) + end + + def use_insert_returning? + @use_insert_returning + end + + # Returns the version of the connected PostgreSQL server. + def get_database_version # :nodoc: + with_raw_connection do |conn| + version = conn.server_version + if version == 0 + raise ActiveRecord::ConnectionFailed, "Could not determine PostgreSQL version" + end + version + end + end + alias :postgresql_version :database_version + + def default_index_type?(index) # :nodoc: + index.using == :btree || super + end + + def build_insert_sql(insert) # :nodoc: + sql = +"INSERT #{insert.into} #{insert.values_list}" + + if insert.skip_duplicates? + sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING" + elsif insert.update_duplicates? + sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET " + if insert.raw_update_sql? + sql << insert.raw_update_sql + else + sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column} IS NOT DISTINCT FROM excluded.#{column}" } + sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",") + end + end + + sql << " RETURNING #{insert.returning}" if insert.returning + sql + end + + def check_version # :nodoc: + if database_version < 9_03_00 # < 9.3 + raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3." + end + end + + class << self + def initialize_type_map(m) # :nodoc: + m.register_type "int2", Type::Integer.new(limit: 2) + m.register_type "int4", Type::Integer.new(limit: 4) + m.register_type "int8", Type::Integer.new(limit: 8) + m.register_type "oid", OID::Oid.new + m.register_type "float4", Type::Float.new(limit: 24) + m.register_type "float8", Type::Float.new + m.register_type "text", Type::Text.new + register_class_with_limit m, "varchar", Type::String + m.alias_type "char", "varchar" + m.alias_type "name", "varchar" + m.alias_type "bpchar", "varchar" + m.register_type "bool", Type::Boolean.new + register_class_with_limit m, "bit", OID::Bit + register_class_with_limit m, "varbit", OID::BitVarying + m.register_type "date", OID::Date.new + + m.register_type "money", OID::Money.new + m.register_type "bytea", OID::Bytea.new + m.register_type "point", OID::Point.new + m.register_type "hstore", OID::Hstore.new + m.register_type "json", Type::Json.new + m.register_type "jsonb", OID::Jsonb.new + m.register_type "cidr", OID::Cidr.new + m.register_type "inet", OID::Inet.new + m.register_type "uuid", OID::Uuid.new + m.register_type "xml", OID::Xml.new + m.register_type "tsvector", OID::SpecializedString.new(:tsvector) + m.register_type "macaddr", OID::Macaddr.new + m.register_type "citext", OID::SpecializedString.new(:citext) + m.register_type "ltree", OID::SpecializedString.new(:ltree) + m.register_type "line", OID::SpecializedString.new(:line) + m.register_type "lseg", OID::SpecializedString.new(:lseg) + m.register_type "box", OID::SpecializedString.new(:box) + m.register_type "path", OID::SpecializedString.new(:path) + m.register_type "polygon", OID::SpecializedString.new(:polygon) + m.register_type "circle", OID::SpecializedString.new(:circle) + + m.register_type "numeric" do |_, fmod, sql_type| + precision = extract_precision(sql_type) + scale = extract_scale(sql_type) + + # The type for the numeric depends on the width of the field, + # so we'll do something special here. + # + # When dealing with decimal columns: + # + # places after decimal = fmod - 4 & 0xffff + # places before decimal = (fmod - 4) >> 16 & 0xffff + if fmod && (fmod - 4 & 0xffff).zero? + # FIXME: Remove this class, and the second argument to + # lookups on PG + Type::DecimalWithoutScale.new(precision: precision) + else + OID::Decimal.new(precision: precision, scale: scale) + end + end + + m.register_type "interval" do |*args, sql_type| + precision = extract_precision(sql_type) + OID::Interval.new(precision: precision) + end + end + end + + private + attr_reader :type_map + + def initialize_type_map(m = type_map) + self.class.initialize_type_map(m) + + self.class.register_class_with_precision m, "time", Type::Time, timezone: @default_timezone + self.class.register_class_with_precision m, "timestamp", OID::Timestamp, timezone: @default_timezone + self.class.register_class_with_precision m, "timestamptz", OID::TimestampWithTimeZone + + load_additional_types + end + + # Extracts the value from a PostgreSQL column default definition. + def extract_value_from_default(default) + case default + # Quoted types + when /\A[(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m + # The default 'now'::date is CURRENT_DATE + if $1 == "now" && $2 == "date" + nil + else + $1.gsub("''", "'") + end + # Boolean types + when "true", "false" + default + # Numeric types + when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/ + $1 + # Object identifier types + when /\A-?\d+\z/ + $1 + else + # Anything else is blank, some user type, or some function + # and we can't know the value of that, so return nil. + nil + end + end + + def extract_default_function(default_value, default) + default if has_default_function?(default_value, default) + end + + def has_default_function?(default_value, default) + !default_value && %r{\w+\(.*\)|\(.*\)::\w+|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default) + end + + # See https://www.postgresql.org/docs/current/static/errcodes-appendix.html + VALUE_LIMIT_VIOLATION = "22001" + NUMERIC_VALUE_OUT_OF_RANGE = "22003" + NOT_NULL_VIOLATION = "23502" + FOREIGN_KEY_VIOLATION = "23503" + UNIQUE_VIOLATION = "23505" + SERIALIZATION_FAILURE = "40001" + DEADLOCK_DETECTED = "40P01" + DUPLICATE_DATABASE = "42P04" + LOCK_NOT_AVAILABLE = "55P03" + QUERY_CANCELED = "57014" + + def translate_exception(exception, message:, sql:, binds:) + return exception unless exception.respond_to?(:result) + + case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE) + when nil + if exception.message.match?(/connection is closed/i) || exception.message.match?(/no connection to the server/i) + ConnectionNotEstablished.new(exception, connection_pool: @pool) + elsif exception.is_a?(PG::ConnectionBad) + # libpq message style always ends with a newline; the pg gem's internal + # errors do not. We separate these cases because a pg-internal + # ConnectionBad means it failed before it managed to send the query, + # whereas a libpq failure could have occurred at any time (meaning the + # server may have already executed part or all of the query). + if exception.message.end_with?("\n") + ConnectionFailed.new(exception, connection_pool: @pool) + else + ConnectionNotEstablished.new(exception, connection_pool: @pool) + end + else + super + end + when UNIQUE_VIOLATION + RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool) + when FOREIGN_KEY_VIOLATION + InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool) + when VALUE_LIMIT_VIOLATION + ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool) + when NUMERIC_VALUE_OUT_OF_RANGE + RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool) + when NOT_NULL_VIOLATION + NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool) + when SERIALIZATION_FAILURE + SerializationFailure.new(message, sql: sql, binds: binds, connection_pool: @pool) + when DEADLOCK_DETECTED + Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool) + when DUPLICATE_DATABASE + DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool) + when LOCK_NOT_AVAILABLE + LockWaitTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool) + when QUERY_CANCELED + QueryCanceled.new(message, sql: sql, binds: binds, connection_pool: @pool) + else + super + end + end + + def retryable_query_error?(exception) + # We cannot retry anything if we're inside a broken transaction; we need to at + # least raise until the innermost savepoint is rolled back + @raw_connection&.transaction_status != ::PG::PQTRANS_INERROR && + super + end + + def get_oid_type(oid, fmod, column_name, sql_type = "") + if !type_map.key?(oid) + load_additional_types([oid]) + end + + type_map.fetch(oid, fmod, sql_type) { + warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String." + Type.default_value.tap do |cast_type| + type_map.register_type(oid, cast_type) + end + } + end + + def load_additional_types(oids = nil) + initializer = OID::TypeMapInitializer.new(type_map) + load_types_queries(initializer, oids) do |query| + records = internal_execute(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) + initializer.run(records) + end + end + + def load_types_queries(initializer, oids) + query = <<~SQL + SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype + FROM pg_type as t + LEFT JOIN pg_range as r ON oid = rngtypid + SQL + if oids + yield query + "WHERE t.oid IN (%s)" % oids.join(", ") + else + yield query + initializer.query_conditions_for_known_type_names + yield query + initializer.query_conditions_for_known_type_types + yield query + initializer.query_conditions_for_array_types + end + end + + FEATURE_NOT_SUPPORTED = "0A000" # :nodoc: + + # Annoyingly, the code for prepared statements whose return value may + # have changed is FEATURE_NOT_SUPPORTED. + # + # This covers various different error types so we need to do additional + # work to classify the exception definitively as a + # ActiveRecord::PreparedStatementCacheExpired + # + # Check here for more details: + # https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573 + def is_cached_plan_failure?(pgerror) + pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED && + pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "RevalidateCachedQuery" + rescue + false + end + + def in_transaction? + open_transactions > 0 + end + + # Returns the statement identifier for the client side cache + # of statements + def sql_key(sql) + "#{schema_search_path}-#{sql}" + end + + # Prepare the statement if it hasn't been prepared, return + # the statement key. + def prepare_statement(sql, binds, conn) + sql_key = sql_key(sql) + unless @statements.key? sql_key + nextkey = @statements.next_key + begin + conn.prepare nextkey, sql + rescue => e + raise translate_exception_class(e, sql, binds) + end + # Clear the queue + conn.get_last_result + @statements[sql_key] = nextkey + end + @statements[sql_key] + end + + # Connects to a PostgreSQL server and sets up the adapter depending on the + # connected server's characteristics. + def connect + @raw_connection = self.class.new_client(@connection_parameters) + rescue ConnectionNotEstablished => ex + raise ex.set_pool(@pool) + end + + def reconnect + begin + @raw_connection&.reset + rescue PG::ConnectionBad + @raw_connection = nil + end + + connect unless @raw_connection + end + + # Configures the encoding, verbosity, schema search path, and time zone of the connection. + # This is called by #connect and should not be called manually. + def configure_connection + super + + if @config[:encoding] + @raw_connection.set_client_encoding(@config[:encoding]) + end + self.client_min_messages = @config[:min_messages] || "warning" + self.schema_search_path = @config[:schema_search_path] || @config[:schema_order] + + unless ActiveRecord.db_warnings_action.nil? + @raw_connection.set_notice_receiver do |result| + message = result.error_field(PG::Result::PG_DIAG_MESSAGE_PRIMARY) + code = result.error_field(PG::Result::PG_DIAG_SQLSTATE) + level = result.error_field(PG::Result::PG_DIAG_SEVERITY) + @notice_receiver_sql_warnings << SQLWarning.new(message, code, level, nil, @pool) + end + end + + # Use standard-conforming strings so we don't have to do the E'...' dance. + set_standard_conforming_strings + + variables = @config.fetch(:variables, {}).stringify_keys + + # Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse + internal_execute("SET intervalstyle = iso_8601", "SCHEMA") + + # SET statements from :variables config hash + # https://www.postgresql.org/docs/current/static/sql-set.html + variables.map do |k, v| + if v == ":default" || v == :default + # Sets the value to the global or compile default + internal_execute("SET SESSION #{k} TO DEFAULT", "SCHEMA") + elsif !v.nil? + internal_execute("SET SESSION #{k} TO #{quote(v)}", "SCHEMA") + end + end + + add_pg_encoders + add_pg_decoders + + reload_type_map + end + + def reconfigure_connection_timezone + variables = @config.fetch(:variables, {}).stringify_keys + + # If it's been directly configured as a connection variable, we don't + # need to do anything here; it will be set up by configure_connection + # and then never changed. + return if variables["timezone"] + + # If using Active Record's time zone support configure the connection + # to return TIMESTAMP WITH ZONE types in UTC. + if default_timezone == :utc + raw_execute("SET SESSION timezone TO 'UTC'", "SCHEMA") + else + raw_execute("SET SESSION timezone TO DEFAULT", "SCHEMA") + end + end + + # Returns the list of a table's column names, data types, and default values. + # + # The underlying query is roughly: + # SELECT column.name, column.type, default.value, column.comment + # FROM column LEFT JOIN default + # ON column.table_id = default.table_id + # AND column.num = default.column_num + # WHERE column.table_id = get_table_id('table_name') + # AND column.num > 0 + # AND NOT column.is_dropped + # ORDER BY column.num + # + # If the table name is not prefixed with a schema, the database will + # take the first match from the schema search path. + # + # Query implementation notes: + # - format_type includes the column size constraint, e.g. varchar(50) + # - ::regclass is a function that gives the id for a table name + def column_definitions(table_name) + query(<<~SQL, "SCHEMA") + SELECT a.attname, format_type(a.atttypid, a.atttypmod), + pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod, + c.collname, col_description(a.attrelid, a.attnum) AS comment, + #{supports_identity_columns? ? 'attidentity' : quote('')} AS identity, + #{supports_virtual_columns? ? 'attgenerated' : quote('')} as attgenerated + FROM pg_attribute a + LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum + LEFT JOIN pg_type t ON a.atttypid = t.oid + LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation + WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass + AND a.attnum > 0 AND NOT a.attisdropped + ORDER BY a.attnum + SQL + end + + def arel_visitor + Arel::Visitors::PostgreSQL.new(self) + end + + def build_statement_pool + StatementPool.new(self, self.class.type_cast_config_to_integer(@config[:statement_limit])) + end + + def can_perform_case_insensitive_comparison_for?(column) + # NOTE: citext is an exception. It is possible to perform a + # case-insensitive comparison using `LOWER()`, but it is + # unnecessary, as `citext` is case-insensitive by definition. + @case_insensitive_cache ||= { "citext" => false } + @case_insensitive_cache.fetch(column.sql_type) do + @case_insensitive_cache[column.sql_type] = begin + sql = <<~SQL + SELECT exists( + SELECT * FROM pg_proc + WHERE proname = 'lower' + AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector + ) OR exists( + SELECT * FROM pg_proc + INNER JOIN pg_cast + ON ARRAY[casttarget]::oidvector = proargtypes + WHERE proname = 'lower' + AND castsource = #{quote column.sql_type}::regtype + ) + SQL + result = internal_execute(sql, "SCHEMA", [], allow_retry: true, materialize_transactions: false) + result.getvalue(0, 0) + end + end + end + + def add_pg_encoders + map = PG::TypeMapByClass.new + map[Integer] = PG::TextEncoder::Integer.new + map[TrueClass] = PG::TextEncoder::Boolean.new + map[FalseClass] = PG::TextEncoder::Boolean.new + @raw_connection.type_map_for_queries = map + end + + def update_typemap_for_default_timezone + if @raw_connection && @mapped_default_timezone != default_timezone && @timestamp_decoder + decoder_class = default_timezone == :utc ? + PG::TextDecoder::TimestampUtc : + PG::TextDecoder::TimestampWithoutTimeZone + + @timestamp_decoder = decoder_class.new(**@timestamp_decoder.to_h) + @raw_connection.type_map_for_results.add_coder(@timestamp_decoder) + + @mapped_default_timezone = default_timezone + + # if default timezone has changed, we need to reconfigure the connection + # (specifically, the session time zone) + reconfigure_connection_timezone + + true + end + end + + def add_pg_decoders + @mapped_default_timezone = nil + @timestamp_decoder = nil + + coders_by_name = { + "int2" => PG::TextDecoder::Integer, + "int4" => PG::TextDecoder::Integer, + "int8" => PG::TextDecoder::Integer, + "oid" => PG::TextDecoder::Integer, + "float4" => PG::TextDecoder::Float, + "float8" => PG::TextDecoder::Float, + "numeric" => PG::TextDecoder::Numeric, + "bool" => PG::TextDecoder::Boolean, + "timestamp" => PG::TextDecoder::TimestampUtc, + "timestamptz" => PG::TextDecoder::TimestampWithTimeZone, + } + coders_by_name["date"] = PG::TextDecoder::Date if decode_dates + + known_coder_types = coders_by_name.keys.map { |n| quote(n) } + query = <<~SQL % known_coder_types.join(", ") + SELECT t.oid, t.typname + FROM pg_type as t + WHERE t.typname IN (%s) + SQL + result = internal_execute(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) + coders = result.filter_map { |row| construct_coder(row, coders_by_name[row["typname"]]) } + + map = PG::TypeMapByOid.new + coders.each { |coder| map.add_coder(coder) } + @raw_connection.type_map_for_results = map + + @type_map_for_results = PG::TypeMapByOid.new + @type_map_for_results.default_type_map = map + @type_map_for_results.add_coder(PG::TextDecoder::Bytea.new(oid: 17, name: "bytea")) + @type_map_for_results.add_coder(MoneyDecoder.new(oid: 790, name: "money")) + + # extract timestamp decoder for use in update_typemap_for_default_timezone + @timestamp_decoder = coders.find { |coder| coder.name == "timestamp" } + update_typemap_for_default_timezone + end + + def construct_coder(row, coder_class) + return unless coder_class + coder_class.new(oid: row["oid"].to_i, name: row["typname"]) + end + + class MoneyDecoder < PG::SimpleDecoder # :nodoc: + TYPE = OID::Money.new + + def decode(value, tuple = nil, field = nil) + TYPE.deserialize(value) + end + end + + ActiveRecord::Type.add_modifier({ array: true }, OID::Array, adapter: :postgresql) + ActiveRecord::Type.add_modifier({ range: true }, OID::Range, adapter: :postgresql) + ActiveRecord::Type.register(:bit, OID::Bit, adapter: :postgresql) + ActiveRecord::Type.register(:bit_varying, OID::BitVarying, adapter: :postgresql) + ActiveRecord::Type.register(:binary, OID::Bytea, adapter: :postgresql) + ActiveRecord::Type.register(:cidr, OID::Cidr, adapter: :postgresql) + ActiveRecord::Type.register(:date, OID::Date, adapter: :postgresql) + ActiveRecord::Type.register(:datetime, OID::DateTime, adapter: :postgresql) + ActiveRecord::Type.register(:decimal, OID::Decimal, adapter: :postgresql) + ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql) + ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql) + ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql) + ActiveRecord::Type.register(:interval, OID::Interval, adapter: :postgresql) + ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql) + ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql) + ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql) + ActiveRecord::Type.register(:legacy_point, OID::LegacyPoint, adapter: :postgresql) + ActiveRecord::Type.register(:uuid, OID::Uuid, adapter: :postgresql) + ActiveRecord::Type.register(:vector, OID::Vector, adapter: :postgresql) + ActiveRecord::Type.register(:xml, OID::Xml, adapter: :postgresql) + end + ActiveSupport.run_load_hooks(:active_record_postgresqladapter, PostgreSQLAdapter) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/schema_cache.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/schema_cache.rb new file mode 100644 index 00000000..77ea0f46 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/schema_cache.rb @@ -0,0 +1,478 @@ +# frozen_string_literal: true + +require "active_support/core_ext/file/atomic" + +module ActiveRecord + module ConnectionAdapters + class SchemaReflection + class << self + attr_accessor :use_schema_cache_dump + attr_accessor :check_schema_cache_dump_version + end + + self.use_schema_cache_dump = true + self.check_schema_cache_dump_version = true + + def initialize(cache_path, cache = nil) + @cache = cache + @cache_path = cache_path + end + + def clear! + @cache = empty_cache + + nil + end + + def load!(pool) + cache(pool) + + self + end + + def primary_keys(pool, table_name) + cache(pool).primary_keys(pool, table_name) + end + + def data_source_exists?(pool, name) + cache(pool).data_source_exists?(pool, name) + end + + def add(pool, name) + cache(pool).add(pool, name) + end + + def data_sources(pool, name) + cache(pool).data_source_exists?(pool, name) + end + + def columns(pool, table_name) + cache(pool).columns(pool, table_name) + end + + def columns_hash(pool, table_name) + cache(pool).columns_hash(pool, table_name) + end + + def columns_hash?(pool, table_name) + cache(pool).columns_hash?(pool, table_name) + end + + def indexes(pool, table_name) + cache(pool).indexes(pool, table_name) + end + + def version(pool) + cache(pool).version(pool) + end + + def size(pool) + cache(pool).size + end + + def clear_data_source_cache!(pool, name) + return if @cache.nil? && !possible_cache_available? + + cache(pool).clear_data_source_cache!(pool, name) + end + + def cached?(table_name) + if @cache.nil? + # If `check_schema_cache_dump_version` is enabled we can't load + # the schema cache dump without connecting to the database. + unless self.class.check_schema_cache_dump_version + @cache = load_cache(nil) + end + end + + @cache&.cached?(table_name) + end + + def dump_to(pool, filename) + fresh_cache = empty_cache + fresh_cache.add_all(pool) + fresh_cache.dump_to(filename) + + @cache = fresh_cache + end + + private + def empty_cache + new_cache = SchemaCache.allocate + new_cache.send(:initialize) + new_cache + end + + def cache(pool) + @cache ||= load_cache(pool) || empty_cache + end + + def possible_cache_available? + self.class.use_schema_cache_dump && + @cache_path && + File.file?(@cache_path) + end + + def load_cache(pool) + # Can't load if schema dumps are disabled + return unless possible_cache_available? + + # Check we can find one + return unless new_cache = SchemaCache._load_from(@cache_path) + + if self.class.check_schema_cache_dump_version + begin + pool.with_connection do |connection| + current_version = connection.schema_version + + if new_cache.version(connection) != current_version + warn "Ignoring #{@cache_path} because it has expired. The current schema version is #{current_version}, but the one in the schema cache file is #{new_cache.schema_version}." + return + end + end + rescue ActiveRecordError => error + warn "Failed to validate the schema cache because of #{error.class}: #{error.message}" + return + end + end + + new_cache + end + end + + class BoundSchemaReflection + class FakePool # :nodoc + def initialize(connection) + @connection = connection + end + + def with_connection + yield @connection + end + end + + class << self + def for_lone_connection(abstract_schema_reflection, connection) # :nodoc: + new(abstract_schema_reflection, FakePool.new(connection)) + end + end + + def initialize(abstract_schema_reflection, pool) + @schema_reflection = abstract_schema_reflection + @pool = pool + end + + def clear! + @schema_reflection.clear! + end + + def load! + @schema_reflection.load!(@pool) + end + + def cached?(table_name) + @schema_reflection.cached?(table_name) + end + + def primary_keys(table_name) + @schema_reflection.primary_keys(@pool, table_name) + end + + def data_source_exists?(name) + @schema_reflection.data_source_exists?(@pool, name) + end + + def add(name) + @schema_reflection.add(@pool, name) + end + + def data_sources(name) + @schema_reflection.data_sources(@pool, name) + end + + def columns(table_name) + @schema_reflection.columns(@pool, table_name) + end + + def columns_hash(table_name) + @schema_reflection.columns_hash(@pool, table_name) + end + + def columns_hash?(table_name) + @schema_reflection.columns_hash?(@pool, table_name) + end + + def indexes(table_name) + @schema_reflection.indexes(@pool, table_name) + end + + def version + @schema_reflection.version(@pool) + end + + def size + @schema_reflection.size(@pool) + end + + def clear_data_source_cache!(name) + @schema_reflection.clear_data_source_cache!(@pool, name) + end + + def dump_to(filename) + @schema_reflection.dump_to(@pool, filename) + end + end + + # = Active Record Connection Adapters Schema Cache + class SchemaCache + def self._load_from(filename) # :nodoc: + return unless File.file?(filename) + + read(filename) do |file| + if filename.include?(".dump") + Marshal.load(file) + else + if YAML.respond_to?(:unsafe_load) + YAML.unsafe_load(file) + else + YAML.load(file) + end + end + end + end + + def self.read(filename, &block) + if File.extname(filename) == ".gz" + Zlib::GzipReader.open(filename) { |gz| + yield gz.read + } + else + yield File.read(filename) + end + end + private_class_method :read + + def initialize # :nodoc: + @columns = {} + @columns_hash = {} + @primary_keys = {} + @data_sources = {} + @indexes = {} + @version = nil + end + + def initialize_dup(other) # :nodoc: + super + @columns = @columns.dup + @columns_hash = @columns_hash.dup + @primary_keys = @primary_keys.dup + @data_sources = @data_sources.dup + @indexes = @indexes.dup + end + + def encode_with(coder) # :nodoc: + coder["columns"] = @columns.sort.to_h + coder["primary_keys"] = @primary_keys.sort.to_h + coder["data_sources"] = @data_sources.sort.to_h + coder["indexes"] = @indexes.sort.to_h + coder["version"] = @version + end + + def init_with(coder) # :nodoc: + @columns = coder["columns"] + @columns_hash = coder["columns_hash"] + @primary_keys = coder["primary_keys"] + @data_sources = coder["data_sources"] + @indexes = coder["indexes"] || {} + @version = coder["version"] + + unless coder["deduplicated"] + derive_columns_hash_and_deduplicate_values + end + end + + def cached?(table_name) + @columns.key?(table_name) + end + + def primary_keys(pool, table_name) + @primary_keys.fetch(table_name) do + pool.with_connection do |connection| + if data_source_exists?(pool, table_name) + @primary_keys[deep_deduplicate(table_name)] = deep_deduplicate(connection.primary_key(table_name)) + end + end + end + end + + # A cached lookup for table existence. + def data_source_exists?(pool, name) + return if ignored_table?(name) + + if @data_sources.empty? + tables_to_cache(pool).each do |source| + @data_sources[source] = true + end + end + + return @data_sources[name] if @data_sources.key? name + + @data_sources[deep_deduplicate(name)] = pool.with_connection do |connection| + connection.data_source_exists?(name) + end + end + + # Add internal cache for table with +table_name+. + def add(pool, table_name) + pool.with_connection do + if data_source_exists?(pool, table_name) + primary_keys(pool, table_name) + columns(pool, table_name) + columns_hash(pool, table_name) + indexes(pool, table_name) + end + end + end + + # Get the columns for a table + def columns(pool, table_name) + if ignored_table?(table_name) + raise ActiveRecord::StatementInvalid.new("Table '#{table_name}' doesn't exist", connection_pool: pool) + end + + @columns.fetch(table_name) do + pool.with_connection do |connection| + @columns[deep_deduplicate(table_name)] = deep_deduplicate(connection.columns(table_name)) + end + end + end + + # Get the columns for a table as a hash, key is the column name + # value is the column object. + def columns_hash(pool, table_name) + @columns_hash.fetch(table_name) do + @columns_hash[deep_deduplicate(table_name)] = columns(pool, table_name).index_by(&:name).freeze + end + end + + # Checks whether the columns hash is already cached for a table. + def columns_hash?(_pool, table_name) + @columns_hash.key?(table_name) + end + + def indexes(pool, table_name) + @indexes.fetch(table_name) do + pool.with_connection do |connection| + if data_source_exists?(pool, table_name) + @indexes[deep_deduplicate(table_name)] = deep_deduplicate(connection.indexes(table_name)) + else + [] + end + end + end + end + + def version(pool) + @version ||= pool.with_connection(&:schema_version) + end + + def schema_version + @version + end + + def size + [@columns, @columns_hash, @primary_keys, @data_sources].sum(&:size) + end + + # Clear out internal caches for the data source +name+. + def clear_data_source_cache!(_connection, name) + @columns.delete name + @columns_hash.delete name + @primary_keys.delete name + @data_sources.delete name + @indexes.delete name + end + + def add_all(pool) # :nodoc: + pool.with_connection do + tables_to_cache(pool).each do |table| + add(pool, table) + end + + version(pool) + end + end + + def dump_to(filename) + open(filename) { |f| + if filename.include?(".dump") + f.write(Marshal.dump(self)) + else + f.write(YAML.dump(self)) + end + } + end + + def marshal_dump # :nodoc: + [@version, @columns, {}, @primary_keys, @data_sources, @indexes] + end + + def marshal_load(array) # :nodoc: + @version, @columns, _columns_hash, @primary_keys, @data_sources, @indexes, _database_version = array + @indexes ||= {} + + derive_columns_hash_and_deduplicate_values + end + + private + def tables_to_cache(pool) + pool.with_connection do |connection| + connection.data_sources.reject do |table| + ignored_table?(table) + end + end + end + + def ignored_table?(table_name) + ActiveRecord.schema_cache_ignored_table?(table_name) + end + + def derive_columns_hash_and_deduplicate_values + @columns = deep_deduplicate(@columns) + @columns_hash = @columns.transform_values { |columns| columns.index_by(&:name) } + @primary_keys = deep_deduplicate(@primary_keys) + @data_sources = deep_deduplicate(@data_sources) + @indexes = deep_deduplicate(@indexes) + end + + def deep_deduplicate(value) + case value + when Hash + value.transform_keys { |k| deep_deduplicate(k) }.transform_values { |v| deep_deduplicate(v) } + when Array + value.map { |i| deep_deduplicate(i) } + when String, Deduplicable + -value + else + value + end + end + + def open(filename) + FileUtils.mkdir_p(File.dirname(filename)) + + File.atomic_write(filename) do |file| + if File.extname(filename) == ".gz" + zipper = Zlib::GzipWriter.new file + zipper.mtime = 0 + yield zipper + zipper.flush + zipper.close + else + yield file + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sql_type_metadata.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sql_type_metadata.rb new file mode 100644 index 00000000..63f07b91 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sql_type_metadata.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module ActiveRecord + # :stopdoc: + module ConnectionAdapters + class SqlTypeMetadata + include Deduplicable + + attr_reader :sql_type, :type, :limit, :precision, :scale + + def initialize(sql_type: nil, type: nil, limit: nil, precision: nil, scale: nil) + @sql_type = sql_type + @type = type + @limit = limit + @precision = precision + @scale = scale + end + + def ==(other) + other.is_a?(SqlTypeMetadata) && + sql_type == other.sql_type && + type == other.type && + limit == other.limit && + precision == other.precision && + scale == other.scale + end + alias eql? == + + def hash + SqlTypeMetadata.hash ^ + sql_type.hash ^ + type.hash ^ + limit.hash ^ + precision.hash >> 1 ^ + scale.hash >> 2 + end + + private + def deduplicated + @sql_type = -sql_type + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/column.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/column.rb new file mode 100644 index 00000000..2f1153df --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/column.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + class Column < ConnectionAdapters::Column # :nodoc: + attr_reader :rowid + + def initialize(*, auto_increment: nil, rowid: false, generated_type: nil, **) + super + @auto_increment = auto_increment + @rowid = rowid + @generated_type = generated_type + end + + def auto_increment? + @auto_increment + end + + def auto_incremented_by_db? + auto_increment? || rowid + end + + def virtual? + !@generated_type.nil? + end + + def virtual_stored? + virtual? && @generated_type == :stored + end + + def has_default? + super && !virtual? + end + + def init_with(coder) + @auto_increment = coder["auto_increment"] + super + end + + def encode_with(coder) + coder["auto_increment"] = @auto_increment + super + end + + def ==(other) + other.is_a?(Column) && + super && + auto_increment? == other.auto_increment? + end + alias :eql? :== + + def hash + Column.hash ^ + super.hash ^ + auto_increment?.hash ^ + rowid.hash + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/database_statements.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/database_statements.rb new file mode 100644 index 00000000..06782199 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/database_statements.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + module DatabaseStatements + READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp( + :pragma + ) # :nodoc: + private_constant :READ_QUERY + + def write_query?(sql) # :nodoc: + !READ_QUERY.match?(sql) + rescue ArgumentError # Invalid encoding + !READ_QUERY.match?(sql.b) + end + + def explain(arel, binds = [], _options = []) + sql = "EXPLAIN QUERY PLAN " + to_sql(arel, binds) + result = internal_exec_query(sql, "EXPLAIN", []) + SQLite3::ExplainPrettyPrinter.new.pp(result) + end + + def begin_deferred_transaction(isolation = nil) # :nodoc: + internal_begin_transaction(:deferred, isolation) + end + + def begin_isolated_db_transaction(isolation) # :nodoc: + internal_begin_transaction(:deferred, isolation) + end + + def begin_db_transaction # :nodoc: + internal_begin_transaction(:immediate, nil) + end + + def commit_db_transaction # :nodoc: + internal_execute("COMMIT TRANSACTION", "TRANSACTION", allow_retry: true, materialize_transactions: false) + end + + def exec_rollback_db_transaction # :nodoc: + internal_execute("ROLLBACK TRANSACTION", "TRANSACTION", allow_retry: true, materialize_transactions: false) + end + + # https://stackoverflow.com/questions/17574784 + # https://www.sqlite.org/lang_datefunc.html + HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')", retryable: true).freeze # :nodoc: + private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP + + def high_precision_current_timestamp + HIGH_PRECISION_CURRENT_TIMESTAMP + end + + def execute(...) # :nodoc: + # SQLite3Adapter was refactored to use ActiveRecord::Result internally + # but for backward compatibility we have to keep returning arrays of hashes here + super&.to_a + end + + def reset_isolation_level # :nodoc: + internal_execute("PRAGMA read_uncommitted=#{@previous_read_uncommitted}", "TRANSACTION", allow_retry: true, materialize_transactions: false) + @previous_read_uncommitted = nil + end + + private + def internal_begin_transaction(mode, isolation) + if isolation + raise TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted + raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache? + end + + internal_execute("BEGIN #{mode} TRANSACTION", "TRANSACTION", allow_retry: true, materialize_transactions: false) + if isolation + @previous_read_uncommitted = query_value("PRAGMA read_uncommitted") + internal_execute("PRAGMA read_uncommitted=ON", "TRANSACTION", allow_retry: true, materialize_transactions: false) + end + end + + def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false) + if batch + raw_connection.execute_batch2(sql) + elsif prepare + stmt = @statements[sql] ||= raw_connection.prepare(sql) + stmt.reset! + stmt.bind_params(type_casted_binds) + + result = if stmt.column_count.zero? # No return + stmt.step + ActiveRecord::Result.empty + else + ActiveRecord::Result.new(stmt.columns, stmt.to_a) + end + else + # Don't cache statements if they are not prepared. + stmt = raw_connection.prepare(sql) + begin + unless binds.nil? || binds.empty? + stmt.bind_params(type_casted_binds) + end + result = if stmt.column_count.zero? # No return + stmt.step + ActiveRecord::Result.empty + else + ActiveRecord::Result.new(stmt.columns, stmt.to_a) + end + ensure + stmt.close + end + end + @last_affected_rows = raw_connection.changes + verified! + + notification_payload[:row_count] = result&.length || 0 + result + end + + def cast_result(result) + # Given that SQLite3 doesn't really a Result type, raw_execute already return an ActiveRecord::Result + # and we have nothing to cast here. + result + end + + def affected_rows(result) + @last_affected_rows + end + + def execute_batch(statements, name = nil, **kwargs) + sql = combine_multi_statements(statements) + raw_execute(sql, name, batch: true, **kwargs) + end + + def build_truncate_statement(table_name) + "DELETE FROM #{quote_table_name(table_name)}" + end + + def returning_column_values(result) + result.rows.first + end + + def default_insert_value(column) + if column.default_function + Arel.sql(column.default_function) + else + column.default + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb new file mode 100644 index 00000000..832fdfe5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + class ExplainPrettyPrinter # :nodoc: + # Pretty prints the result of an EXPLAIN QUERY PLAN in a way that resembles + # the output of the SQLite shell: + # + # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows) + # 0|1|1|SCAN TABLE posts (~100000 rows) + # + def pp(result) + result.rows.map do |row| + row.join("|") + end.join("\n") + "\n" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/quoting.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/quoting.rb new file mode 100644 index 00000000..0aaef16b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/quoting.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + module Quoting # :nodoc: + extend ActiveSupport::Concern + + QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc: + QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc: + + module ClassMethods # :nodoc: + def column_name_matcher + / + \A + ( + (?: + # "table_name"."column_name" | function(one or no argument) + ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+") | \w+\((?:|\g<2>)\)) + ) + (?:(?:\s+AS)?\s+(?:\w+|"\w+"))? + ) + (?:\s*,\s*\g<1>)* + \z + /ix + end + + def column_name_with_order_matcher + / + \A + ( + (?: + # "table_name"."column_name" | function(one or no argument) + ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+") | \w+\((?:|\g<2>)\)) + ) + (?:\s+COLLATE\s+(?:\w+|"\w+"))? + (?:\s+ASC|\s+DESC)? + ) + (?:\s*,\s*\g<1>)* + \z + /ix + end + + def quote_column_name(name) + QUOTED_COLUMN_NAMES[name] ||= %Q("#{name.to_s.gsub('"', '""')}").freeze + end + + def quote_table_name(name) + QUOTED_TABLE_NAMES[name] ||= %Q("#{name.to_s.gsub('"', '""').gsub(".", "\".\"")}").freeze + end + end + + def quote(value) # :nodoc: + case value + when Numeric + if value.finite? + super + else + "'#{value}'" + end + else + super + end + end + + def quote_string(s) + ::SQLite3::Database.quote(s) + end + + def quote_table_name_for_assignment(table, attr) + quote_column_name(attr) + end + + def quoted_time(value) + value = value.change(year: 2000, month: 1, day: 1) + quoted_date(value).sub(/\A\d\d\d\d-\d\d-\d\d /, "2000-01-01 ") + end + + def quoted_binary(value) + "x'#{value.hex}'" + end + + def quoted_true + "1" + end + + def unquoted_true + 1 + end + + def quoted_false + "0" + end + + def unquoted_false + 0 + end + + def quote_default_expression(value, column) # :nodoc: + if value.is_a?(Proc) + value = value.call + if value.match?(/\A\w+\(.*\)\z/) + "(#{value})" + else + value + end + else + super + end + end + + def type_cast(value) # :nodoc: + case value + when BigDecimal, Rational + value.to_f + when String + if value.encoding == Encoding::ASCII_8BIT + super(value.encode(Encoding::UTF_8)) + else + super + end + else + super + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/schema_creation.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/schema_creation.rb new file mode 100644 index 00000000..0e7bf37b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/schema_creation.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + class SchemaCreation < SchemaCreation # :nodoc: + private + def visit_ForeignKeyDefinition(o) + super.dup.tap do |sql| + sql << " DEFERRABLE INITIALLY #{o.deferrable.to_s.upcase}" if o.deferrable + end + end + + def supports_index_using? + false + end + + def add_column_options!(sql, options) + if options[:collation] + sql << " COLLATE \"#{options[:collation]}\"" + end + + if as = options[:as] + sql << " GENERATED ALWAYS AS (#{as})" + + if options[:stored] + sql << " STORED" + else + sql << " VIRTUAL" + end + end + super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb new file mode 100644 index 00000000..59852cfc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + # = Active Record SQLite3 Adapter \Table Definition + class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + def change_column(column_name, type, **options) + name = column_name.to_s + @columns_hash[name] = nil + column(name, type, **options) + end + + def references(*args, **options) + super(*args, type: :integer, **options) + end + alias :belongs_to :references + + def new_column_definition(name, type, **options) # :nodoc: + case type + when :virtual + type = options[:type] + end + + super + end + + private + def integer_like_primary_key_type(type, options) + :primary_key + end + + def valid_column_definition_options + super + [:as, :type, :stored] + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb new file mode 100644 index 00000000..f7dd1198 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc: + private + def virtual_tables(stream) + virtual_tables = @connection.virtual_tables + if virtual_tables.any? + stream.puts + stream.puts " # Virtual tables defined in this database." + stream.puts " # Note that virtual tables may not work with other database engines. Be careful if changing database." + virtual_tables.sort.each do |table_name, options| + module_name, arguments = options + stream.puts " create_virtual_table #{table_name.inspect}, #{module_name.inspect}, #{arguments.split(", ").inspect}" + end + end + end + + def default_primary_key?(column) + schema_type(column) == :integer + end + + def explicit_primary_key_default?(column) + column.bigint? + end + + def prepare_column_options(column) + spec = super + + if @connection.supports_virtual_columns? && column.virtual? + spec[:as] = extract_expression_for_virtual_column(column) + spec[:stored] = column.virtual_stored? + spec = { type: schema_type(column).inspect }.merge!(spec) + end + + spec + end + + def extract_expression_for_virtual_column(column) + column.default_function.inspect + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/schema_statements.rb new file mode 100644 index 00000000..36b7b7e3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3/schema_statements.rb @@ -0,0 +1,223 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + module SchemaStatements # :nodoc: + # Returns an array of indexes for the given table. + def indexes(table_name) + internal_exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").filter_map do |row| + # Indexes SQLite creates implicitly for internal use start with "sqlite_". + # See https://www.sqlite.org/fileformat2.html#intschema + next if row["name"].start_with?("sqlite_") + + index_sql = query_value(<<~SQL, "SCHEMA") + SELECT sql + FROM sqlite_master + WHERE name = #{quote(row['name'])} AND type = 'index' + UNION ALL + SELECT sql + FROM sqlite_temp_master + WHERE name = #{quote(row['name'])} AND type = 'index' + SQL + + /\bON\b\s*"?(\w+?)"?\s*\((?.+?)\)(?:\s*WHERE\b\s*(?.+))?(?:\s*\/\*.*\*\/)?\z/i =~ index_sql + + columns = internal_exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col| + col["name"] + end + + where = where.sub(/\s*\/\*.*\*\/\z/, "") if where + orders = {} + + if columns.any?(&:nil?) # index created with an expression + columns = expressions + else + # Add info on sort order for columns (only desc order is explicitly specified, + # asc is the default) + if index_sql # index_sql can be null in case of primary key indexes + index_sql.scan(/"(\w+)" DESC/).flatten.each { |order_column| + orders[order_column] = :desc + } + end + end + + IndexDefinition.new( + table_name, + row["name"], + row["unique"] != 0, + columns, + where: where, + orders: orders + ) + end + end + + def add_foreign_key(from_table, to_table, **options) + assert_valid_deferrable(options[:deferrable]) + + alter_table(from_table) do |definition| + to_table = strip_table_name_prefix_and_suffix(to_table) + definition.foreign_key(to_table, **options) + end + end + + def remove_foreign_key(from_table, to_table = nil, **options) + return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table) + + to_table ||= options[:to_table] + options = options.except(:name, :to_table, :validate) + foreign_keys = foreign_keys(from_table) + + fkey = foreign_keys.detect do |fk| + table = to_table || begin + table = options[:column].to_s.delete_suffix("_id") + Base.pluralize_table_names ? table.pluralize : table + end + table = strip_table_name_prefix_and_suffix(table) + options = options.slice(*fk.options.keys) + fk_to_table = strip_table_name_prefix_and_suffix(fk.to_table) + fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s } + end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}") + + foreign_keys.delete(fkey) + alter_table(from_table, foreign_keys) + end + + def virtual_table_exists?(table_name) + query_values(data_source_sql(table_name, type: "VIRTUAL TABLE"), "SCHEMA").any? + end + + def check_constraints(table_name) + table_sql = query_value(<<-SQL, "SCHEMA") + SELECT sql + FROM sqlite_master + WHERE name = #{quote(table_name)} AND type = 'table' + UNION ALL + SELECT sql + FROM sqlite_temp_master + WHERE name = #{quote(table_name)} AND type = 'table' + SQL + + table_sql.to_s.scan(/CONSTRAINT\s+(?\w+)\s+CHECK\s+\((?(:?[^()]|\(\g\))+)\)/i).map do |name, expression| + CheckConstraintDefinition.new(table_name, expression, name: name) + end + end + + def add_check_constraint(table_name, expression, **options) + alter_table(table_name) do |definition| + definition.check_constraint(expression, **options) + end + end + + def remove_check_constraint(table_name, expression = nil, if_exists: false, **options) + return if if_exists && !check_constraint_exists?(table_name, **options) + + check_constraints = check_constraints(table_name) + chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name + check_constraints.delete_if { |chk| chk.name == chk_name_to_delete } + alter_table(table_name, foreign_keys(table_name), check_constraints) + end + + def create_schema_dumper(options) + SQLite3::SchemaDumper.create(self, options) + end + + def schema_creation # :nodoc + SQLite3::SchemaCreation.new(self) + end + + private + def valid_table_definition_options + super + [:rename] + end + + def create_table_definition(name, **options) + SQLite3::TableDefinition.new(self, name, **options) + end + + def validate_index_length!(table_name, new_name, internal = false) + super unless internal + end + + def new_column_from_field(table_name, field, definitions) + default = field["dflt_value"] + + type_metadata = fetch_type_metadata(field["type"]) + default_value = extract_value_from_default(default) + generated_type = extract_generated_type(field) + + if generated_type.present? + default_function = default + else + default_function = extract_default_function(default_value, default) + end + + rowid = is_column_the_rowid?(field, definitions) + + Column.new( + field["name"], + default_value, + type_metadata, + field["notnull"].to_i == 0, + default_function, + collation: field["collation"], + auto_increment: field["auto_increment"], + rowid: rowid, + generated_type: generated_type + ) + end + + INTEGER_REGEX = /integer/i + # if a rowid table has a primary key that consists of a single column + # and the declared type of that column is "INTEGER" in any mixture of upper and lower case, + # then the column becomes an alias for the rowid. + def is_column_the_rowid?(field, column_definitions) + return false unless INTEGER_REGEX.match?(field["type"]) && field["pk"] == 1 + # is the primary key a single column? + column_definitions.one? { |c| c["pk"] > 0 } + end + + def data_source_sql(name = nil, type: nil) + scope = quoted_scope(name, type: type) + scope[:type] ||= "'table','view'" + + sql = +"SELECT name FROM pragma_table_list WHERE schema <> 'temp'" + sql << " AND name NOT IN ('sqlite_sequence', 'sqlite_schema')" + sql << " AND name = #{scope[:name]}" if scope[:name] + sql << " AND type IN (#{scope[:type]})" + sql + end + + def quoted_scope(name = nil, type: nil) + type = \ + case type + when "BASE TABLE" + "'table'" + when "VIEW" + "'view'" + when "VIRTUAL TABLE" + "'virtual'" + end + scope = {} + scope[:name] = quote(name) if name + scope[:type] = type if type + scope + end + + def assert_valid_deferrable(deferrable) + return if !deferrable || %i(immediate deferred).include?(deferrable) + + raise ArgumentError, "deferrable must be `:immediate` or `:deferred`, got: `#{deferrable.inspect}`" + end + + def extract_generated_type(field) + case field["hidden"] + when 2 then :virtual + when 3 then :stored + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3_adapter.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3_adapter.rb new file mode 100644 index 00000000..0d7e8eb8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -0,0 +1,849 @@ +# frozen_string_literal: true + +require "active_record/connection_adapters/abstract_adapter" +require "active_record/connection_adapters/statement_pool" +require "active_record/connection_adapters/sqlite3/column" +require "active_record/connection_adapters/sqlite3/explain_pretty_printer" +require "active_record/connection_adapters/sqlite3/quoting" +require "active_record/connection_adapters/sqlite3/database_statements" +require "active_record/connection_adapters/sqlite3/schema_creation" +require "active_record/connection_adapters/sqlite3/schema_definitions" +require "active_record/connection_adapters/sqlite3/schema_dumper" +require "active_record/connection_adapters/sqlite3/schema_statements" + +gem "sqlite3", ">= 2.1" +require "sqlite3" + +# Suppress the warning that SQLite3 issues when open writable connections are carried across fork() +SQLite3::ForkSafety.suppress_warnings! + +module ActiveRecord + module ConnectionAdapters # :nodoc: + # = Active Record SQLite3 Adapter + # + # The SQLite3 adapter works with the sqlite3-ruby drivers + # (available as gem from https://rubygems.org/gems/sqlite3). + # + # Options: + # + # * :database - Path to the database file. + class SQLite3Adapter < AbstractAdapter + ADAPTER_NAME = "SQLite" + + class << self + def new_client(config) + ::SQLite3::Database.new(config[:database].to_s, config) + rescue Errno::ENOENT => error + if error.message.include?("No such file or directory") + raise ActiveRecord::NoDatabaseError + else + raise + end + end + + def dbconsole(config, options = {}) + args = [] + + args << "-#{options[:mode]}" if options[:mode] + args << "-header" if options[:header] + args << File.expand_path(config.database, Rails.respond_to?(:root) ? Rails.root : nil) + + find_cmd_and_exec(ActiveRecord.database_cli[:sqlite], *args) + end + end + + include SQLite3::Quoting + include SQLite3::SchemaStatements + include SQLite3::DatabaseStatements + + ## + # :singleton-method: + # Configure the SQLite3Adapter to be used in a strict strings mode. + # This will disable double-quoted string literals, because otherwise typos can silently go unnoticed. + # For example, it is possible to create an index for a non existing column. + # If you wish to enable this mode you can add the following line to your application.rb file: + # + # config.active_record.sqlite3_adapter_strict_strings_by_default = true + class_attribute :strict_strings_by_default, default: false + + NATIVE_DATABASE_TYPES = { + primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL", + string: { name: "varchar" }, + text: { name: "text" }, + integer: { name: "integer" }, + float: { name: "float" }, + decimal: { name: "decimal" }, + datetime: { name: "datetime" }, + time: { name: "time" }, + date: { name: "date" }, + binary: { name: "blob" }, + boolean: { name: "boolean" }, + json: { name: "json" }, + } + + DEFAULT_PRAGMAS = { + "foreign_keys" => true, + "journal_mode" => :wal, + "synchronous" => :normal, + "mmap_size" => 134217728, # 128 megabytes + "journal_size_limit" => 67108864, # 64 megabytes + "cache_size" => 2000 + } + + class StatementPool < ConnectionAdapters::StatementPool # :nodoc: + alias reset clear + + private + def dealloc(stmt) + stmt.close unless stmt.closed? + end + end + + def initialize(...) + super + + @memory_database = false + case @config[:database].to_s + when "" + raise ArgumentError, "No database file specified. Missing argument: database" + when ":memory:" + @memory_database = true + when /\Afile:/ + else + # Otherwise we have a path relative to Rails.root + @config[:database] = File.expand_path(@config[:database], Rails.root) if defined?(Rails.root) + dirname = File.dirname(@config[:database]) + unless File.directory?(dirname) + begin + FileUtils.mkdir_p(dirname) + rescue SystemCallError + raise ActiveRecord::NoDatabaseError.new(connection_pool: @pool) + end + end + end + + @last_affected_rows = nil + @previous_read_uncommitted = nil + @config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict) + @connection_parameters = @config.merge( + database: @config[:database].to_s, + results_as_hash: true, + default_transaction_mode: :immediate, + ) + end + + def database_exists? + @config[:database] == ":memory:" || File.exist?(@config[:database].to_s) + end + + def supports_ddl_transactions? + true + end + + def supports_savepoints? + true + end + + def supports_transaction_isolation? + true + end + + def supports_partial_index? + true + end + + def supports_expression_index? + database_version >= "3.9.0" + end + + def requires_reloading? + true + end + + def supports_foreign_keys? + true + end + + def supports_check_constraints? + true + end + + def supports_views? + true + end + + def supports_datetime_with_precision? + true + end + + def supports_json? + true + end + + def supports_common_table_expressions? + database_version >= "3.8.3" + end + + def supports_insert_returning? + database_version >= "3.35.0" + end + + def supports_insert_on_conflict? + database_version >= "3.24.0" + end + alias supports_insert_on_duplicate_skip? supports_insert_on_conflict? + alias supports_insert_on_duplicate_update? supports_insert_on_conflict? + alias supports_insert_conflict_target? supports_insert_on_conflict? + + def supports_concurrent_connections? + !@memory_database + end + + def supports_virtual_columns? + database_version >= "3.31.0" + end + + def connected? + !(@raw_connection.nil? || @raw_connection.closed?) + end + + def active? + if connected? + verified! + true + end + end + + alias :reset! :reconnect! + + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. + def disconnect! + super + + @raw_connection&.close rescue nil + @raw_connection = nil + end + + def supports_index_sort_order? + true + end + + def native_database_types # :nodoc: + NATIVE_DATABASE_TYPES + end + + # Returns the current database encoding format as a string, e.g. 'UTF-8' + def encoding + any_raw_connection.encoding.to_s + end + + def supports_explain? + true + end + + def supports_lazy_transactions? + true + end + + def supports_deferrable_constraints? + true + end + + # REFERENTIAL INTEGRITY ==================================== + + def disable_referential_integrity # :nodoc: + old_foreign_keys = query_value("PRAGMA foreign_keys") + old_defer_foreign_keys = query_value("PRAGMA defer_foreign_keys") + + begin + execute("PRAGMA defer_foreign_keys = ON") + execute("PRAGMA foreign_keys = OFF") + yield + ensure + execute("PRAGMA defer_foreign_keys = #{old_defer_foreign_keys}") + execute("PRAGMA foreign_keys = #{old_foreign_keys}") + end + end + + def check_all_foreign_keys_valid! # :nodoc: + sql = "PRAGMA foreign_key_check" + result = execute(sql) + + unless result.blank? + tables = result.map { |row| row["table"] } + raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql, connection_pool: @pool) + end + end + + # SCHEMA STATEMENTS ======================================== + + def primary_keys(table_name) # :nodoc: + pks = table_structure(table_name).select { |f| f["pk"] > 0 } + pks.sort_by { |f| f["pk"] }.map { |f| f["name"] } + end + + def remove_index(table_name, column_name = nil, **options) # :nodoc: + return if options[:if_exists] && !index_exists?(table_name, column_name, **options) + + index_name = index_name_for_remove(table_name, column_name, options) + + exec_query "DROP INDEX #{quote_column_name(index_name)}" + end + + VIRTUAL_TABLE_REGEX = /USING\s+(\w+)\s*\((.+)\)/i + + # Returns a list of defined virtual tables + def virtual_tables + query = <<~SQL + SELECT name, sql FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL %'; + SQL + + exec_query(query, "SCHEMA").cast_values.each_with_object({}) do |row, memo| + table_name, sql = row[0], row[1] + _, module_name, arguments = sql.match(VIRTUAL_TABLE_REGEX).to_a + memo[table_name] = [module_name, arguments] + end.to_a + end + + # Creates a virtual table + # + # Example: + # create_virtual_table :emails, :fts5, ['sender', 'title',' body'] + def create_virtual_table(table_name, module_name, values) + exec_query "CREATE VIRTUAL TABLE IF NOT EXISTS #{table_name} USING #{module_name} (#{values.join(", ")})" + end + + # Drops a virtual table + # + # Although this command ignores +module_name+ and +values+, + # it can be helpful to provide these in a migration's +change+ method so it can be reverted. + # In that case, +module_name+, +values+ and +options+ will be used by #create_virtual_table. + def drop_virtual_table(table_name, module_name, values, **options) + drop_table(table_name) + end + + # Renames a table. + # + # Example: + # rename_table('octopuses', 'octopi') + def rename_table(table_name, new_name, **options) + validate_table_length!(new_name) unless options[:_uses_legacy_table_name] + schema_cache.clear_data_source_cache!(table_name.to_s) + schema_cache.clear_data_source_cache!(new_name.to_s) + exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}" + rename_table_indexes(table_name, new_name, **options) + end + + def add_column(table_name, column_name, type, **options) # :nodoc: + type = type.to_sym + if invalid_alter_table_type?(type, options) + alter_table(table_name) do |definition| + definition.column(column_name, type, **options) + end + else + super + end + end + + def remove_column(table_name, column_name, type = nil, **options) # :nodoc: + alter_table(table_name) do |definition| + definition.remove_column column_name + definition.foreign_keys.delete_if { |fk| fk.column == column_name.to_s } + end + end + + def remove_columns(table_name, *column_names, type: nil, **options) # :nodoc: + alter_table(table_name) do |definition| + column_names.each do |column_name| + definition.remove_column column_name + end + column_names = column_names.map(&:to_s) + definition.foreign_keys.delete_if { |fk| column_names.include?(fk.column) } + end + end + + def change_column_default(table_name, column_name, default_or_changes) # :nodoc: + default = extract_new_default_value(default_or_changes) + + alter_table(table_name) do |definition| + definition[column_name].default = default + end + end + + def change_column_null(table_name, column_name, null, default = nil) # :nodoc: + validate_change_column_null_argument!(null) + + unless null || default.nil? + internal_exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") + end + alter_table(table_name) do |definition| + definition[column_name].null = null + end + end + + def change_column(table_name, column_name, type, **options) # :nodoc: + alter_table(table_name) do |definition| + definition.change_column(column_name, type, **options) + end + end + + def rename_column(table_name, column_name, new_column_name) # :nodoc: + column = column_for(table_name, column_name) + alter_table(table_name, rename: { column.name => new_column_name.to_s }) + rename_column_indexes(table_name, column.name, new_column_name) + end + + def add_timestamps(table_name, **options) + options[:null] = false if options[:null].nil? + + if !options.key?(:precision) + options[:precision] = 6 + end + + alter_table(table_name) do |definition| + definition.column :created_at, :datetime, **options + definition.column :updated_at, :datetime, **options + end + end + + def add_reference(table_name, ref_name, **options) # :nodoc: + super(table_name, ref_name, type: :integer, **options) + end + alias :add_belongs_to :add_reference + + FK_REGEX = /.*FOREIGN KEY\s+\("([^"]+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/ + DEFERRABLE_REGEX = /DEFERRABLE INITIALLY (\w+)/ + def foreign_keys(table_name) + # SQLite returns 1 row for each column of composite foreign keys. + fk_info = internal_exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA") + # Deferred or immediate foreign keys can only be seen in the CREATE TABLE sql + fk_defs = table_structure_sql(table_name) + .select do |column_string| + column_string.start_with?("CONSTRAINT") && + column_string.include?("FOREIGN KEY") + end + .to_h do |fk_string| + _, from, table, to = fk_string.match(FK_REGEX).to_a + _, mode = fk_string.match(DEFERRABLE_REGEX).to_a + deferred = mode&.downcase&.to_sym || false + [[table, from, to], deferred] + end + + grouped_fk = fk_info.group_by { |row| row["id"] }.values.each { |group| group.sort_by! { |row| row["seq"] } } + grouped_fk.map do |group| + row = group.first + options = { + on_delete: extract_foreign_key_action(row["on_delete"]), + on_update: extract_foreign_key_action(row["on_update"]), + deferrable: fk_defs[[row["table"], row["from"], row["to"]]] + } + + if group.one? + options[:column] = row["from"] + options[:primary_key] = row["to"] + else + options[:column] = group.map { |row| row["from"] } + options[:primary_key] = group.map { |row| row["to"] } + end + ForeignKeyDefinition.new(table_name, row["table"], options) + end + end + + def build_insert_sql(insert) # :nodoc: + sql = +"INSERT #{insert.into} #{insert.values_list}" + + if insert.skip_duplicates? + sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING" + elsif insert.update_duplicates? + sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET " + if insert.raw_update_sql? + sql << insert.raw_update_sql + else + sql << insert.touch_model_timestamps_unless { |column| "#{column} IS excluded.#{column}" } + sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",") + end + end + + sql << " RETURNING #{insert.returning}" if insert.returning + sql + end + + def shared_cache? # :nodoc: + @config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE) + end + + def get_database_version # :nodoc: + SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA")) + end + + def check_version # :nodoc: + if database_version < "3.8.0" + raise "Your version of SQLite (#{database_version}) is too old. Active Record supports SQLite >= 3.8." + end + end + + class SQLite3Integer < Type::Integer # :nodoc: + private + def _limit + # INTEGER storage class can be stored 8 bytes value. + # See https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes + limit || 8 + end + end + + ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3) + + class << self + private + def initialize_type_map(m) + super + register_class_with_limit m, %r(int)i, SQLite3Integer + end + end + + TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) } + EXTENDED_TYPE_MAPS = Concurrent::Map.new + + private + # See https://www.sqlite.org/limits.html, + # the default value is 999 when not configured. + def bind_params_length + 999 + end + + def table_structure(table_name) + structure = table_info(table_name) + raise ActiveRecord::StatementInvalid.new("Could not find table '#{table_name}'", connection_pool: @pool) if structure.empty? + table_structure_with_collation(table_name, structure) + end + alias column_definitions table_structure + + def extract_value_from_default(default) + case default + when /^null$/i + nil + # Quoted types + when /^'([^|]*)'$/m + $1.gsub("''", "'") + # Quoted types + when /^"([^|]*)"$/m + $1.gsub('""', '"') + # Numeric types + when /\A-?\d+(\.\d*)?\z/ + $& + # Binary columns + when /x'(.*)'/ + [ $1 ].pack("H*") + else + # Anything else is blank or some function + # and we can't know the value of that, so return nil. + nil + end + end + + def extract_default_function(default_value, default) + default if has_default_function?(default_value, default) + end + + def has_default_function?(default_value, default) + !default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP|\|\|}.match?(default) + end + + # See: https://www.sqlite.org/lang_altertable.html + # SQLite has an additional restriction on the ALTER TABLE statement + def invalid_alter_table_type?(type, options) + type == :primary_key || options[:primary_key] || + options[:null] == false && options[:default].nil? || + (type == :virtual && options[:stored]) + end + + def alter_table( + table_name, + foreign_keys = foreign_keys(table_name), + check_constraints = check_constraints(table_name), + **options + ) + altered_table_name = "a#{table_name}" + + caller = lambda do |definition| + rename = options[:rename] || {} + foreign_keys.each do |fk| + if column = rename[fk.options[:column]] + fk.options[:column] = column + end + to_table = strip_table_name_prefix_and_suffix(fk.to_table) + definition.foreign_key(to_table, **fk.options) + end + + check_constraints.each do |chk| + definition.check_constraint(chk.expression, **chk.options) + end + + yield definition if block_given? + end + + transaction do + disable_referential_integrity do + move_table(table_name, altered_table_name, options.merge(temporary: true)) + move_table(altered_table_name, table_name, &caller) + end + end + end + + def move_table(from, to, options = {}, &block) + copy_table(from, to, options, &block) + drop_table(from) + end + + def copy_table(from, to, options = {}) + from_primary_key = primary_key(from) + options[:id] = false + create_table(to, **options) do |definition| + @definition = definition + if from_primary_key.is_a?(Array) + @definition.primary_keys from_primary_key + end + + columns(from).each do |column| + column_name = options[:rename] ? + (options[:rename][column.name] || + options[:rename][column.name.to_sym] || + column.name) : column.name + + column_options = { + limit: column.limit, + precision: column.precision, + scale: column.scale, + null: column.null, + collation: column.collation, + primary_key: column_name == from_primary_key + } + + if column.virtual? + column_options[:as] = column.default_function + column_options[:stored] = column.virtual_stored? + column_options[:type] = column.type + elsif column.has_default? + type = lookup_cast_type_from_column(column) + default = type.deserialize(column.default) + default = -> { column.default_function } if default.nil? + + unless column.auto_increment? + column_options[:default] = default + end + end + + column_type = column.virtual? ? :virtual : (column.bigint? ? :bigint : column.type) + @definition.column(column_name, column_type, **column_options) + end + + yield @definition if block_given? + end + copy_table_indexes(from, to, options[:rename] || {}) + + columns_to_copy = @definition.columns.reject { |col| col.options.key?(:as) }.map(&:name) + copy_table_contents(from, to, + columns_to_copy, + options[:rename] || {}) + end + + def copy_table_indexes(from, to, rename = {}) + indexes(from).each do |index| + name = index.name + if to == "a#{from}" + name = "t#{name}" + elsif from == "a#{to}" + name = name[1..-1] + end + + columns = index.columns + if columns.is_a?(Array) + to_column_names = columns(to).map(&:name) + columns = columns.map { |c| rename[c] || c }.select do |column| + to_column_names.include?(column) + end + end + + unless columns.empty? + # index name can't be the same + options = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true } + options[:unique] = true if index.unique + options[:where] = index.where if index.where + options[:order] = index.orders if index.orders + add_index(to, columns, **options) + end + end + end + + def copy_table_contents(from, to, columns, rename = {}) + column_mappings = Hash[columns.map { |name| [name, name] }] + rename.each { |a| column_mappings[a.last] = a.first } + from_columns = columns(from).collect(&:name) + columns = columns.find_all { |col| from_columns.include?(column_mappings[col]) } + from_columns_to_copy = columns.map { |col| column_mappings[col] } + quoted_columns = columns.map { |col| quote_column_name(col) } * "," + quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * "," + + internal_exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns}) + SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}") + end + + def translate_exception(exception, message:, sql:, binds:) + # SQLite 3.8.2 returns a newly formatted error message: + # UNIQUE constraint failed: *table_name*.*column_name* + # Older versions of SQLite return: + # column *column_name* is not unique + if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i) + RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool) + elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i) + NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool) + elsif exception.message.match?(/FOREIGN KEY constraint failed/i) + InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool) + elsif exception.message.match?(/called on a closed database/i) + ConnectionNotEstablished.new(exception, connection_pool: @pool) + elsif exception.is_a?(::SQLite3::BusyException) + StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool) + else + super + end + end + + COLLATE_REGEX = /.*"(\w+)".*collate\s+"(\w+)".*/i + PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*"(\w+)".+PRIMARY KEY AUTOINCREMENT/i + GENERATED_ALWAYS_AS_REGEX = /.*"(\w+)".+GENERATED ALWAYS AS \((.+)\) (?:STORED|VIRTUAL)/i + + def table_structure_with_collation(table_name, basic_structure) + collation_hash = {} + auto_increments = {} + generated_columns = {} + + column_strings = table_structure_sql(table_name, basic_structure.map { |column| column["name"] }) + + if column_strings.any? + column_strings.each do |column_string| + # This regex will match the column name and collation type and will save + # the value in $1 and $2 respectively. + collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string + auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string + generated_columns[$1] = $2 if GENERATED_ALWAYS_AS_REGEX =~ column_string + end + + basic_structure.map do |column| + column_name = column["name"] + + if collation_hash.has_key? column_name + column["collation"] = collation_hash[column_name] + end + + if auto_increments.has_key?(column_name) + column["auto_increment"] = true + end + + if generated_columns.has_key?(column_name) + column["dflt_value"] = generated_columns[column_name] + end + + column + end + else + basic_structure.to_a + end + end + + UNQUOTED_OPEN_PARENS_REGEX = /\((?![^'"]*['"][^'"]*$)/ + FINAL_CLOSE_PARENS_REGEX = /\);*\z/ + + def table_structure_sql(table_name, column_names = nil) + unless column_names + column_info = table_info(table_name) + column_names = column_info.map { |column| column["name"] } + end + + sql = <<~SQL + SELECT sql FROM + (SELECT * FROM sqlite_master UNION ALL + SELECT * FROM sqlite_temp_master) + WHERE type = 'table' AND name = #{quote(table_name)} + SQL + + # Result will have following sample string + # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + # "password_digest" varchar COLLATE "NOCASE", + # "o_id" integer, + # CONSTRAINT "fk_rails_78146ddd2e" FOREIGN KEY ("o_id") REFERENCES "os" ("id")); + result = query_value(sql, "SCHEMA") + + return [] unless result + + # Splitting with left parentheses and discarding the first part will return all + # columns separated with comma(,). + result.partition(UNQUOTED_OPEN_PARENS_REGEX) + .last + .sub(FINAL_CLOSE_PARENS_REGEX, "") + # column definitions can have a comma in them, so split on commas followed + # by a space and a column name in quotes or followed by the keyword CONSTRAINT + .split(/,(?=\s(?:CONSTRAINT|"(?:#{Regexp.union(column_names).source})"))/i) + .map(&:strip) + end + + def table_info(table_name) + if supports_virtual_columns? + internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA") + else + internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA") + end + end + + def arel_visitor + Arel::Visitors::SQLite.new(self) + end + + def build_statement_pool + StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit])) + end + + def connect + @raw_connection = self.class.new_client(@connection_parameters) + rescue ConnectionNotEstablished => ex + raise ex.set_pool(@pool) + end + + def reconnect + if active? + @raw_connection.rollback rescue nil + else + connect + end + end + + def configure_connection + if @config[:timeout] && @config[:retries] + raise ArgumentError, "Cannot specify both timeout and retries arguments" + elsif @config[:timeout] + timeout = self.class.type_cast_config_to_integer(@config[:timeout]) + raise TypeError, "timeout must be integer, not #{timeout}" unless timeout.is_a?(Integer) + @raw_connection.busy_handler_timeout = timeout + elsif @config[:retries] + ActiveRecord.deprecator.warn(<<~MSG) + The retries option is deprecated and will be removed in Rails 8.1. Use timeout instead. + MSG + retries = self.class.type_cast_config_to_integer(@config[:retries]) + raw_connection.busy_handler { |count| count <= retries } + end + + super + + pragmas = @config.fetch(:pragmas, {}).stringify_keys + DEFAULT_PRAGMAS.merge(pragmas).each do |pragma, value| + if ::SQLite3::Pragmas.method_defined?("#{pragma}=") + @raw_connection.public_send("#{pragma}=", value) + else + warn "Unknown SQLite pragma: #{pragma}" + end + end + end + end + ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/statement_pool.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/statement_pool.rb new file mode 100644 index 00000000..e84db133 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/statement_pool.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + class StatementPool # :nodoc: + include Enumerable + + DEFAULT_STATEMENT_LIMIT = 1000 + + def initialize(statement_limit = nil) + @cache = Hash.new { |h, pid| h[pid] = {} } + @statement_limit = statement_limit || DEFAULT_STATEMENT_LIMIT + end + + def each(&block) + cache.each(&block) + end + + def key?(key) + cache.key?(key) + end + + def [](key) + cache[key] + end + + def length + cache.length + end + + def []=(sql, stmt) + while @statement_limit <= cache.size + dealloc(cache.shift.last) + end + cache[sql] = stmt + end + + def clear + cache.each_value do |stmt| + dealloc stmt + end + cache.clear + end + + # Clear the pool without deallocating; this is only safe when we + # know the server has independently deallocated all statements + # (e.g. due to a reconnect, or a DISCARD ALL) + def reset + cache.clear + end + + def delete(key) + if stmt = cache.delete(key) + dealloc(stmt) + end + stmt + end + + private + def cache + @cache[Process.pid] + end + + def dealloc(stmt) + raise NotImplementedError + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/trilogy/database_statements.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/trilogy/database_statements.rb new file mode 100644 index 00000000..ff93ae2e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/trilogy/database_statements.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module Trilogy + module DatabaseStatements + def exec_insert(sql, name, binds, pk = nil, sequence_name = nil, returning: nil) # :nodoc: + sql, _binds = sql_for_insert(sql, pk, binds, returning) + internal_execute(sql, name) + end + + private + def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false) + reset_multi_statement = if batch && !@config[:multi_statement] + raw_connection.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_ON) + true + end + + # Make sure we carry over any changes to ActiveRecord.default_timezone that have been + # made since we established the connection + if default_timezone == :local + raw_connection.query_flags |= ::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE + else + raw_connection.query_flags &= ~::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE + end + + result = raw_connection.query(sql) + while raw_connection.more_results_exist? + raw_connection.next_result + end + verified! + handle_warnings(sql) + notification_payload[:row_count] = result.count + result + ensure + if reset_multi_statement && active? + raw_connection.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF) + end + end + + def cast_result(result) + if result.fields.empty? + ActiveRecord::Result.empty + else + ActiveRecord::Result.new(result.fields, result.rows) + end + end + + def affected_rows(result) + result.affected_rows + end + + def last_inserted_id(result) + if supports_insert_returning? + super + else + result.last_insert_id + end + end + + def execute_batch(statements, name = nil, **kwargs) + combine_multi_statements(statements).each do |statement| + raw_execute(statement, name, batch: true, **kwargs) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/trilogy_adapter.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/trilogy_adapter.rb new file mode 100644 index 00000000..d3e8d606 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_adapters/trilogy_adapter.rb @@ -0,0 +1,212 @@ +# frozen_string_literal: true + +require "active_record/connection_adapters/abstract_mysql_adapter" + +gem "trilogy", "~> 2.7" +require "trilogy" + +require "active_record/connection_adapters/trilogy/database_statements" + +module ActiveRecord + module ConnectionAdapters + class TrilogyAdapter < AbstractMysqlAdapter + ER_BAD_DB_ERROR = 1049 + ER_DBACCESS_DENIED_ERROR = 1044 + ER_ACCESS_DENIED_ERROR = 1045 + + ADAPTER_NAME = "Trilogy" + + include Trilogy::DatabaseStatements + + SSL_MODES = { + SSL_MODE_DISABLED: ::Trilogy::SSL_DISABLED, + SSL_MODE_PREFERRED: ::Trilogy::SSL_PREFERRED_NOVERIFY, + SSL_MODE_REQUIRED: ::Trilogy::SSL_REQUIRED_NOVERIFY, + SSL_MODE_VERIFY_CA: ::Trilogy::SSL_VERIFY_CA, + SSL_MODE_VERIFY_IDENTITY: ::Trilogy::SSL_VERIFY_IDENTITY + }.freeze + + class << self + def new_client(config) + config[:ssl_mode] = parse_ssl_mode(config[:ssl_mode]) if config[:ssl_mode] + ::Trilogy.new(config) + rescue ::Trilogy::Error => error + raise translate_connect_error(config, error) + end + + def parse_ssl_mode(mode) + return mode if mode.is_a? Integer + + m = mode.to_s.upcase + m = "SSL_MODE_#{m}" unless m.start_with? "SSL_MODE_" + + SSL_MODES.fetch(m.to_sym, mode) + end + + def translate_connect_error(config, error) + case error.error_code + when ER_DBACCESS_DENIED_ERROR, ER_BAD_DB_ERROR + ActiveRecord::NoDatabaseError.db_error(config[:database]) + when ER_ACCESS_DENIED_ERROR + ActiveRecord::DatabaseConnectionError.username_error(config[:username]) + else + if error.message.include?("TRILOGY_DNS_ERROR") + ActiveRecord::DatabaseConnectionError.hostname_error(config[:host]) + else + ActiveRecord::ConnectionNotEstablished.new(error.message) + end + end + end + + private + def initialize_type_map(m) + super + + m.register_type(%r(char)i) do |sql_type| + limit = extract_limit(sql_type) + Type.lookup(:string, adapter: :trilogy, limit: limit) + end + + m.register_type %r(^enum)i, Type.lookup(:string, adapter: :trilogy) + m.register_type %r(^set)i, Type.lookup(:string, adapter: :trilogy) + end + end + + def initialize(config, *) + config = config.dup + + # Trilogy ignores `socket` if `host is set. We want the opposite to allow + # configuring UNIX domain sockets via `DATABASE_URL`. + config.delete(:host) if config[:socket] + + # Set FOUND_ROWS capability on the connection so UPDATE queries returns number of rows + # matched rather than number of rows updated. + config[:found_rows] = true + + if config[:prepared_statements] + raise ArgumentError, "Trilogy currently doesn't support prepared statements. Remove `prepared_statements: true` from your database configuration." + end + + super + end + + TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) } + + def supports_json? + !mariadb? && database_version >= "5.7.8" + end + + def supports_comments? + true + end + + def supports_comments_in_create? + true + end + + def supports_savepoints? + true + end + + def savepoint_errors_invalidate_transactions? + true + end + + def supports_lazy_transactions? + true + end + + def connected? + !(@raw_connection.nil? || @raw_connection.closed?) + end + + def active? + connected? && @lock.synchronize { @raw_connection&.ping; verified! } || false + rescue ::Trilogy::Error + false + end + + alias reset! reconnect! + + def disconnect! + @lock.synchronize do + super + @raw_connection&.close + @raw_connection = nil + end + end + + def discard! + @lock.synchronize do + super + @raw_connection&.discard! + @raw_connection = nil + end + end + + private + def text_type?(type) + TYPE_MAP.lookup(type).is_a?(Type::String) || TYPE_MAP.lookup(type).is_a?(Type::Text) + end + + def error_number(exception) + exception.error_code if exception.respond_to?(:error_code) + end + + def connect + @raw_connection = self.class.new_client(@config) + rescue ConnectionNotEstablished => ex + raise ex.set_pool(@pool) + end + + def reconnect + @raw_connection&.close + @raw_connection = nil + connect + end + + def full_version + database_version.full_version_string + end + + def get_full_version + with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn| + conn.server_info[:version] + end + end + + def translate_exception(exception, message:, sql:, binds:) + if exception.is_a?(::Trilogy::TimeoutError) && !exception.error_code + return ActiveRecord::AdapterTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool) + end + + case exception + when ::Trilogy::ConnectionClosed, ::Trilogy::EOFError + return ConnectionFailed.new(message, connection_pool: @pool) + when ::Trilogy::Error + if exception.is_a?(SystemCallError) || exception.message.include?("TRILOGY_INVALID_SEQUENCE_ID") + return ConnectionFailed.new(message, connection_pool: @pool) + end + end + + super + end + + def default_prepared_statements + false + end + + ActiveRecord::Type.register(:immutable_string, adapter: :trilogy) do |_, **args| + Type::ImmutableString.new(true: "1", false: "0", **args) + end + + ActiveRecord::Type.register(:string, adapter: :trilogy) do |_, **args| + Type::String.new(true: "1", false: "0", **args) + end + + ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :trilogy) + end + + ActiveSupport.run_load_hooks(:active_record_trilogyadapter, TrilogyAdapter) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_handling.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_handling.rb new file mode 100644 index 00000000..91ddf828 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/connection_handling.rb @@ -0,0 +1,413 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record Connection Handling + module ConnectionHandling + RAILS_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"].presence || ENV["RACK_ENV"].presence } + DEFAULT_ENV = -> { RAILS_ENV.call || "default_env" } + + # Establishes the connection to the database. Accepts a hash as input where + # the :adapter key must be specified with the name of a database adapter (in lower-case) + # example for regular databases (MySQL, PostgreSQL, etc): + # + # ActiveRecord::Base.establish_connection( + # adapter: "mysql2", + # host: "localhost", + # username: "myuser", + # password: "mypass", + # database: "somedatabase" + # ) + # + # Example for SQLite database: + # + # ActiveRecord::Base.establish_connection( + # adapter: "sqlite3", + # database: "path/to/dbfile" + # ) + # + # Also accepts keys as strings (for parsing from YAML for example): + # + # ActiveRecord::Base.establish_connection( + # "adapter" => "sqlite3", + # "database" => "path/to/dbfile" + # ) + # + # Or a URL: + # + # ActiveRecord::Base.establish_connection( + # "postgres://myuser:mypass@localhost/somedatabase" + # ) + # + # In case {ActiveRecord::Base.configurations}[rdoc-ref:Core.configurations] + # is set (\Rails automatically loads the contents of config/database.yml into it), + # a symbol can also be given as argument, representing a key in the + # configuration hash: + # + # ActiveRecord::Base.establish_connection(:production) + # + # The exceptions AdapterNotSpecified, AdapterNotFound, and +ArgumentError+ + # may be returned on an error. + def establish_connection(config_or_env = nil) + config_or_env ||= DEFAULT_ENV.call.to_sym + db_config = resolve_config_for_connection(config_or_env) + connection_handler.establish_connection(db_config, owner_name: self, role: current_role, shard: current_shard) + end + + # Connects a model to the databases specified. The +database+ keyword + # takes a hash consisting of a +role+ and a +database_key+. + # + # This will look up the database config using the +database_key+ and + # establish a connection to that config. + # + # class AnimalsModel < ApplicationRecord + # self.abstract_class = true + # + # connects_to database: { writing: :primary, reading: :primary_replica } + # end + # + # +connects_to+ also supports horizontal sharding. The horizontal sharding API + # supports read replicas as well. You can connect a model to a list of shards like this: + # + # class AnimalsModel < ApplicationRecord + # self.abstract_class = true + # + # connects_to shards: { + # default: { writing: :primary, reading: :primary_replica }, + # shard_two: { writing: :primary_shard_two, reading: :primary_shard_replica_two } + # } + # end + # + # Returns an array of database connections. + def connects_to(database: {}, shards: {}) + raise NotImplementedError, "`connects_to` can only be called on ActiveRecord::Base or abstract classes" unless self == Base || abstract_class? + + if database.present? && shards.present? + raise ArgumentError, "`connects_to` can only accept a `database` or `shards` argument, but not both arguments." + end + + connections = [] + + @shard_keys = shards.keys + + if shards.empty? + shards[:default] = database + end + + self.default_shard = shards.keys.first + + shards.each do |shard, database_keys| + database_keys.each do |role, database_key| + db_config = resolve_config_for_connection(database_key) + + self.connection_class = true + connections << connection_handler.establish_connection(db_config, owner_name: self, role: role, shard: shard.to_sym) + end + end + + connections + end + + # Connects to a role (e.g. writing, reading, or a custom role) and/or + # shard for the duration of the block. At the end of the block the + # connection will be returned to the original role / shard. + # + # If only a role is passed, Active Record will look up the connection + # based on the requested role. If a non-established role is requested + # an +ActiveRecord::ConnectionNotEstablished+ error will be raised: + # + # ActiveRecord::Base.connected_to(role: :writing) do + # Dog.create! # creates dog using dog writing connection + # end + # + # ActiveRecord::Base.connected_to(role: :reading) do + # Dog.create! # throws exception because we're on a replica + # end + # + # When swapping to a shard, the role must be passed as well. If a non-existent + # shard is passed, an +ActiveRecord::ConnectionNotEstablished+ error will be + # raised. + # + # When a shard and role is passed, Active Record will first lookup the role, + # and then look up the connection by shard key. + # + # ActiveRecord::Base.connected_to(role: :reading, shard: :shard_one_replica) do + # Dog.first # finds first Dog record stored on the shard one replica + # end + def connected_to(role: nil, shard: nil, prevent_writes: false, &blk) + if self != Base && !abstract_class + raise NotImplementedError, "calling `connected_to` is only allowed on ActiveRecord::Base or abstract classes." + end + + if !connection_class? && !primary_class? + raise NotImplementedError, "calling `connected_to` is only allowed on the abstract class that established the connection." + end + + unless role || shard + raise ArgumentError, "must provide a `shard` and/or `role`." + end + + with_role_and_shard(role, shard, prevent_writes, &blk) + end + + # Connects a role and/or shard to the provided connection names. Optionally +prevent_writes+ + # can be passed to block writes on a connection. +reading+ will automatically set + # +prevent_writes+ to true. + # + # +connected_to_many+ is an alternative to deeply nested +connected_to+ blocks. + # + # Usage: + # + # ActiveRecord::Base.connected_to_many(AnimalsRecord, MealsRecord, role: :reading) do + # Dog.first # Read from animals replica + # Dinner.first # Read from meals replica + # Person.first # Read from primary writer + # end + def connected_to_many(*classes, role:, shard: nil, prevent_writes: false) + classes = classes.flatten + + if self != Base || classes.include?(Base) + raise NotImplementedError, "connected_to_many can only be called on ActiveRecord::Base." + end + + prevent_writes = true if role == ActiveRecord.reading_role + + append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: classes) + yield + ensure + connected_to_stack.pop + end + + # Passes the block to +connected_to+ for every +shard+ the + # model is configured to connect to (if any), and returns the + # results in an array. + # + # Optionally, +role+ and/or +prevent_writes+ can be passed which + # will be forwarded to each +connected_to+ call. + def connected_to_all_shards(role: nil, prevent_writes: false, &blk) + shard_keys.map do |shard| + connected_to(shard: shard, role: role, prevent_writes: prevent_writes, &blk) + end + end + + # Use a specified connection. + # + # This method is useful for ensuring that a specific connection is + # being used. For example, when booting a console in readonly mode. + # + # It is not recommended to use this method in a request since it + # does not yield to a block like +connected_to+. + def connecting_to(role: default_role, shard: default_shard, prevent_writes: false) + prevent_writes = true if role == ActiveRecord.reading_role + + append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self]) + end + + # Prohibit swapping shards while inside of the passed block. + # + # In some cases you may want to be able to swap shards but not allow a + # nested call to connected_to or connected_to_many to swap again. This + # is useful in cases you're using sharding to provide per-request + # database isolation. + def prohibit_shard_swapping(enabled = true) + prev_value = ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping] + ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping] = enabled + yield + ensure + ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping] = prev_value + end + + # Determine whether or not shard swapping is currently prohibited + def shard_swapping_prohibited? + ActiveSupport::IsolatedExecutionState[:active_record_prohibit_shard_swapping] + end + + # Prevent writing to the database regardless of role. + # + # In some cases you may want to prevent writes to the database + # even if you are on a database that can write. +while_preventing_writes+ + # will prevent writes to the database for the duration of the block. + # + # This method does not provide the same protection as a readonly + # user and is meant to be a safeguard against accidental writes. + # + # See +READ_QUERY+ for the queries that are blocked by this + # method. + def while_preventing_writes(enabled = true, &block) + connected_to(role: current_role, prevent_writes: enabled, &block) + end + + # Returns true if role is the current connected role and/or + # current connected shard. If no shard is passed, the default will be + # used. + # + # ActiveRecord::Base.connected_to(role: :writing) do + # ActiveRecord::Base.connected_to?(role: :writing) #=> true + # ActiveRecord::Base.connected_to?(role: :reading) #=> false + # end + # + # ActiveRecord::Base.connected_to(role: :reading, shard: :shard_one) do + # ActiveRecord::Base.connected_to?(role: :reading, shard: :shard_one) #=> true + # ActiveRecord::Base.connected_to?(role: :reading, shard: :default) #=> false + # ActiveRecord::Base.connected_to?(role: :writing, shard: :shard_one) #=> true + # end + def connected_to?(role:, shard: ActiveRecord::Base.default_shard) + current_role == role.to_sym && current_shard == shard.to_sym + end + + # Clears the query cache for all connections associated with the current thread. + def clear_query_caches_for_current_thread + connection_handler.each_connection_pool do |pool| + pool.clear_query_cache + end + end + + # Returns the connection currently associated with the class. This can + # also be used to "borrow" the connection to do database work unrelated + # to any of the specific Active Records. + # The connection will remain leased for the entire duration of the request + # or job, or until +#release_connection+ is called. + def lease_connection + connection_pool.lease_connection + end + + # Soft deprecated. Use +#with_connection+ or +#lease_connection+ instead. + def connection + pool = connection_pool + if pool.permanent_lease? + case ActiveRecord.permanent_connection_checkout + when :deprecated + ActiveRecord.deprecator.warn <<~MESSAGE + Called deprecated `ActiveRecord::Base.connection` method. + + Either use `with_connection` or `lease_connection`. + MESSAGE + when :disallowed + raise ActiveRecordError, <<~MESSAGE + Called deprecated `ActiveRecord::Base.connection` method. + + Either use `with_connection` or `lease_connection`. + MESSAGE + end + pool.lease_connection + else + pool.active_connection + end + end + + # Return the currently leased connection into the pool + def release_connection + connection_pool.release_connection + end + + # Checkouts a connection from the pool, yield it and then check it back in. + # If a connection was already leased via #lease_connection or a parent call to + # #with_connection, that same connection is yieled. + # If #lease_connection is called inside the block, the connection won't be checked + # back in. + # If #connection is called inside the block, the connection won't be checked back in + # unless the +prevent_permanent_checkout+ argument is set to +true+. + def with_connection(prevent_permanent_checkout: false, &block) + connection_pool.with_connection(prevent_permanent_checkout: prevent_permanent_checkout, &block) + end + + attr_writer :connection_specification_name + + # Returns the connection specification name from the current class or its parent. + def connection_specification_name + if @connection_specification_name.nil? + return self == Base ? Base.name : superclass.connection_specification_name + end + @connection_specification_name + end + + def primary_class? # :nodoc: + self == Base || application_record_class? + end + + # Returns the db_config object from the associated connection: + # + # ActiveRecord::Base.connection_db_config + # # + # + # Use only for reading. + def connection_db_config + connection_pool.db_config + end + + def adapter_class # :nodoc: + connection_pool.db_config.adapter_class + end + + def connection_pool + connection_handler.retrieve_connection_pool(connection_specification_name, role: current_role, shard: current_shard, strict: true) + end + + def retrieve_connection + connection_handler.retrieve_connection(connection_specification_name, role: current_role, shard: current_shard) + end + + # Returns +true+ if Active Record is connected. + def connected? + connection_handler.connected?(connection_specification_name, role: current_role, shard: current_shard) + end + + def remove_connection + name = @connection_specification_name if defined?(@connection_specification_name) + + # if removing a connection that has a pool, we reset the + # connection_specification_name so it will use the parent + # pool. + if connection_handler.retrieve_connection_pool(name, role: current_role, shard: current_shard) + self.connection_specification_name = nil + end + + connection_handler.remove_connection_pool(name, role: current_role, shard: current_shard) + end + + def schema_cache # :nodoc: + connection_pool.schema_cache + end + + def clear_cache! # :nodoc: + connection_pool.schema_cache.clear! + end + + def shard_keys + connection_class_for_self.instance_variable_get(:@shard_keys) || [] + end + + def sharded? + shard_keys.any? + end + + private + def resolve_config_for_connection(config_or_env) + raise "Anonymous class is not allowed." unless name + + connection_name = primary_class? ? Base.name : name + self.connection_specification_name = connection_name + + Base.configurations.resolve(config_or_env) + end + + def with_role_and_shard(role, shard, prevent_writes) + prevent_writes = true if role == ActiveRecord.reading_role + + append_to_connected_to_stack(role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self]) + return_value = yield + return_value.load if return_value.is_a? ActiveRecord::Relation + return_value + ensure + self.connected_to_stack.pop + end + + def append_to_connected_to_stack(entry) + if shard_swapping_prohibited? && entry[:shard].present? + raise ArgumentError, "cannot swap `shard` while shard swapping is prohibited." + end + + connected_to_stack << entry + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/core.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/core.rb new file mode 100644 index 00000000..9b3a6856 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/core.rb @@ -0,0 +1,894 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" +require "active_support/core_ext/module/delegation" +require "active_support/parameter_filter" +require "concurrent/map" + +module ActiveRecord + # = Active Record \Core + module Core + extend ActiveSupport::Concern + include ActiveModel::Access + + included do + ## + # :singleton-method: + # + # Accepts a logger conforming to the interface of Log4r or the default + # Ruby +Logger+ class, which is then passed on to any new database + # connections made. You can retrieve this logger by calling +logger+ on + # either an Active Record model class or an Active Record model instance. + class_attribute :logger, instance_writer: false + + class_attribute :_destroy_association_async_job, instance_accessor: false, default: "ActiveRecord::DestroyAssociationAsyncJob" + + # The job class used to destroy associations in the background. + def self.destroy_association_async_job + if _destroy_association_async_job.is_a?(String) + self._destroy_association_async_job = _destroy_association_async_job.constantize + end + _destroy_association_async_job + rescue NameError => error + raise NameError, "Unable to load destroy_association_async_job: #{error.message}" + end + + singleton_class.alias_method :destroy_association_async_job=, :_destroy_association_async_job= + delegate :destroy_association_async_job, to: :class + + ## + # :singleton-method: + # + # Specifies the maximum number of records that will be destroyed in a + # single background job by the dependent: :destroy_async + # association option. When +nil+ (default), all dependent records will be + # destroyed in a single background job. If specified, the records to be + # destroyed will be split into multiple background jobs. + class_attribute :destroy_association_async_batch_size, instance_writer: false, instance_predicate: false, default: nil + + ## + # Contains the database configuration - as is typically stored in config/database.yml - + # as an ActiveRecord::DatabaseConfigurations object. + # + # For example, the following database.yml... + # + # development: + # adapter: sqlite3 + # database: storage/development.sqlite3 + # + # production: + # adapter: sqlite3 + # database: storage/production.sqlite3 + # + # ...would result in ActiveRecord::Base.configurations to look like this: + # + # #, + # # + # ]> + def self.configurations=(config) + @@configurations = ActiveRecord::DatabaseConfigurations.new(config) + end + self.configurations = {} + + # Returns a fully resolved ActiveRecord::DatabaseConfigurations object. + def self.configurations + @@configurations + end + + ## + # :singleton-method: + # Force enumeration of all columns in SELECT statements. + # e.g. SELECT first_name, last_name FROM ... instead of SELECT * FROM ... + # This avoids +PreparedStatementCacheExpired+ errors when a column is added + # to the database while the app is running. + class_attribute :enumerate_columns_in_select_statements, instance_accessor: false, default: false + + class_attribute :belongs_to_required_by_default, instance_accessor: false + + class_attribute :strict_loading_by_default, instance_accessor: false, default: false + class_attribute :strict_loading_mode, instance_accessor: false, default: :all + + class_attribute :has_many_inversing, instance_accessor: false, default: false + + class_attribute :run_commit_callbacks_on_first_saved_instances_in_transaction, instance_accessor: false, default: true + + class_attribute :default_connection_handler, instance_writer: false + + class_attribute :default_role, instance_writer: false + + class_attribute :default_shard, instance_writer: false + + class_attribute :shard_selector, instance_accessor: false, default: nil + + ## + # :singleton-method: + # + # Specifies the attributes that will be included in the output of the + # #inspect method: + # + # Post.attributes_for_inspect = [:id, :title] + # Post.first.inspect #=> "#" + # + # When set to `:all` inspect will list all the record's attributes: + # + # Post.attributes_for_inspect = :all + # Post.first.inspect #=> "#" + class_attribute :attributes_for_inspect, instance_accessor: false, default: :all + + def self.application_record_class? # :nodoc: + if ActiveRecord.application_record_class + self == ActiveRecord.application_record_class + else + if defined?(ApplicationRecord) && self == ApplicationRecord + true + end + end + end + + self.filter_attributes = [] + + def self.connection_handler + ActiveSupport::IsolatedExecutionState[:active_record_connection_handler] || default_connection_handler + end + + def self.connection_handler=(handler) + ActiveSupport::IsolatedExecutionState[:active_record_connection_handler] = handler + end + + def self.asynchronous_queries_session # :nodoc: + asynchronous_queries_tracker.current_session + end + + def self.asynchronous_queries_tracker # :nodoc: + ActiveSupport::IsolatedExecutionState[:active_record_asynchronous_queries_tracker] ||= \ + AsynchronousQueriesTracker.new + end + + # Returns the symbol representing the current connected role. + # + # ActiveRecord::Base.connected_to(role: :writing) do + # ActiveRecord::Base.current_role #=> :writing + # end + # + # ActiveRecord::Base.connected_to(role: :reading) do + # ActiveRecord::Base.current_role #=> :reading + # end + def self.current_role + connected_to_stack.reverse_each do |hash| + return hash[:role] if hash[:role] && hash[:klasses].include?(Base) + return hash[:role] if hash[:role] && hash[:klasses].include?(connection_class_for_self) + end + + default_role + end + + # Returns the symbol representing the current connected shard. + # + # ActiveRecord::Base.connected_to(role: :reading) do + # ActiveRecord::Base.current_shard #=> :default + # end + # + # ActiveRecord::Base.connected_to(role: :writing, shard: :one) do + # ActiveRecord::Base.current_shard #=> :one + # end + def self.current_shard + connected_to_stack.reverse_each do |hash| + return hash[:shard] if hash[:shard] && hash[:klasses].include?(Base) + return hash[:shard] if hash[:shard] && hash[:klasses].include?(connection_class_for_self) + end + + default_shard + end + + # Returns the symbol representing the current setting for + # preventing writes. + # + # ActiveRecord::Base.connected_to(role: :reading) do + # ActiveRecord::Base.current_preventing_writes #=> true + # end + # + # ActiveRecord::Base.connected_to(role: :writing) do + # ActiveRecord::Base.current_preventing_writes #=> false + # end + def self.current_preventing_writes + connected_to_stack.reverse_each do |hash| + return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base) + return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(connection_class_for_self) + end + + false + end + + # Intended to behave like `.current_preventing_writes` given the class name as input. + # See PoolConfig and ConnectionHandler::ConnectionDescriptor. + def self.preventing_writes?(class_name) # :nodoc: + connected_to_stack.reverse_each do |hash| + return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base) + return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].any? { |klass| klass.name == class_name } + end + + false + end + + def self.connected_to_stack # :nodoc: + if connected_to_stack = ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack] + connected_to_stack + else + connected_to_stack = Concurrent::Array.new + ActiveSupport::IsolatedExecutionState[:active_record_connected_to_stack] = connected_to_stack + connected_to_stack + end + end + + def self.connection_class=(b) # :nodoc: + @connection_class = b + end + + def self.connection_class # :nodoc: + @connection_class ||= false + end + + def self.connection_class? # :nodoc: + self.connection_class + end + + def self.connection_class_for_self # :nodoc: + klass = self + + until klass == Base + break if klass.connection_class? + klass = klass.superclass + end + + klass + end + + self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new + self.default_role = ActiveRecord.writing_role + self.default_shard = :default + + def self.strict_loading_violation!(owner:, reflection:) # :nodoc: + case ActiveRecord.action_on_strict_loading_violation + when :raise + message = reflection.strict_loading_violation_message(owner) + raise ActiveRecord::StrictLoadingViolationError.new(message) + when :log + name = "strict_loading_violation.active_record" + ActiveSupport::Notifications.instrument(name, owner: owner, reflection: reflection) + end + end + end + + module ClassMethods + def initialize_find_by_cache # :nodoc: + @find_by_statement_cache = { true => Concurrent::Map.new, false => Concurrent::Map.new } + end + + def find(*ids) # :nodoc: + # We don't have cache keys for this stuff yet + return super unless ids.length == 1 + return super if block_given? || primary_key.nil? || scope_attributes? + + id = ids.first + + return super if StatementCache.unsupported_value?(id) + + cached_find_by([primary_key], [id]) || + raise(RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}", name, primary_key, id)) + end + + def find_by(*args) # :nodoc: + return super if scope_attributes? + + hash = args.first + return super unless Hash === hash + + hash = hash.each_with_object({}) do |(key, value), h| + key = key.to_s + key = attribute_aliases[key] || key + + return super if reflect_on_aggregation(key) + + reflection = _reflect_on_association(key) + + if !reflection + value = value.id if value.respond_to?(:id) + elsif reflection.belongs_to? && !reflection.polymorphic? + key = reflection.join_foreign_key + pkey = reflection.join_primary_key + + if pkey.is_a?(Array) + if pkey.all? { |attribute| value.respond_to?(attribute) } + value = pkey.map do |attribute| + if attribute == "id" + value.id_value + else + value.public_send(attribute) + end + end + composite_primary_key = true + end + else + value = value.public_send(pkey) if value.respond_to?(pkey) + end + end + + if !composite_primary_key && + (!columns_hash.key?(key) || StatementCache.unsupported_value?(value)) + return super + end + + h[key] = value + end + + cached_find_by(hash.keys, hash.values) + end + + def find_by!(*args) # :nodoc: + find_by(*args) || where(*args).raise_record_not_found_exception! + end + + def initialize_generated_modules # :nodoc: + generated_association_methods + end + + def generated_association_methods # :nodoc: + @generated_association_methods ||= begin + mod = const_set(:GeneratedAssociationMethods, Module.new) + private_constant :GeneratedAssociationMethods + include mod + + mod + end + end + + # Returns columns which shouldn't be exposed while calling +#inspect+. + def filter_attributes + if @filter_attributes.nil? + superclass.filter_attributes + else + @filter_attributes + end + end + + # Specifies columns which shouldn't be exposed while calling +#inspect+. + def filter_attributes=(filter_attributes) + @inspection_filter = nil + @filter_attributes = filter_attributes + end + + def inspection_filter # :nodoc: + if @filter_attributes.nil? + superclass.inspection_filter + else + @inspection_filter ||= begin + mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED) + ActiveSupport::ParameterFilter.new(@filter_attributes, mask: mask) + end + end + end + + # Returns a string like 'Post(id:integer, title:string, body:text)' + def inspect # :nodoc: + if self == Base || singleton_class? + super + elsif abstract_class? + "#{super}(abstract)" + elsif !schema_loaded? && !connected? + "#{super} (call '#{super}.load_schema' to load schema informations)" + elsif table_exists? + attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ", " + "#{super}(#{attr_list})" + else + "#{super}(Table doesn't exist)" + end + end + + # Returns an instance of +Arel::Table+ loaded with the current table name. + def arel_table # :nodoc: + @arel_table ||= Arel::Table.new(table_name, klass: self) + end + + def predicate_builder # :nodoc: + @predicate_builder ||= PredicateBuilder.new(TableMetadata.new(self, arel_table)) + end + + def type_caster # :nodoc: + TypeCaster::Map.new(self) + end + + def cached_find_by_statement(connection, key, &block) # :nodoc: + cache = @find_by_statement_cache[connection.prepared_statements] + cache.compute_if_absent(key) { StatementCache.create(connection, &block) } + end + + private + def inherited(subclass) + super + + # initialize cache at class definition for thread safety + subclass.initialize_find_by_cache + unless subclass.base_class? + klass = self + until klass.base_class? + klass.initialize_find_by_cache + klass = klass.superclass + end + end + + subclass.class_eval do + @arel_table = nil + @predicate_builder = nil + @inspection_filter = nil + @filter_attributes ||= nil + @generated_association_methods ||= nil + end + end + + def relation + relation = Relation.create(self) + + if finder_needs_type_condition? && !ignore_default_scope? + relation.where!(type_condition) + else + relation + end + end + + def cached_find_by(keys, values) + with_connection do |connection| + statement = cached_find_by_statement(connection, keys) { |params| + wheres = keys.index_with do |key| + if key.is_a?(Array) + [key.map { params.bind }] + else + params.bind + end + end + where(wheres).limit(1) + } + + statement.execute(values.flatten, connection, allow_retry: true).then do |r| + r.first + rescue TypeError + raise ActiveRecord::StatementInvalid + end + end + end + end + + # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with + # attributes but not yet saved (pass a hash with key names matching the associated table column names). + # In both instances, valid attribute keys are determined by the column names of the associated table -- + # hence you can't have attributes that aren't part of the table columns. + # + # ==== Example + # # Instantiates a single new object + # User.new(first_name: 'Jamie') + def initialize(attributes = nil) + @new_record = true + @attributes = self.class._default_attributes.deep_dup + + init_internals + initialize_internals_callback + + super + + yield self if block_given? + _run_initialize_callbacks + end + + # Initialize an empty model object from +coder+. +coder+ should be + # the result of previously encoding an Active Record model, using + # #encode_with. + # + # class Post < ActiveRecord::Base + # end + # + # old_post = Post.new(title: "hello world") + # coder = {} + # old_post.encode_with(coder) + # + # post = Post.allocate + # post.init_with(coder) + # post.title # => 'hello world' + def init_with(coder, &block) + coder = LegacyYamlAdapter.convert(coder) + attributes = self.class.yaml_encoder.decode(coder) + init_with_attributes(attributes, coder["new_record"], &block) + end + + ## + # Initialize an empty model object from +attributes+. + # +attributes+ should be an attributes object, and unlike the + # `initialize` method, no assignment calls are made per attribute. + def init_with_attributes(attributes, new_record = false) # :nodoc: + @new_record = new_record + @attributes = attributes + + init_internals + + yield self if block_given? + + _run_find_callbacks + _run_initialize_callbacks + + self + end + + ## + # :method: clone + # Identical to Ruby's clone method. This is a "shallow" copy. Be warned that your attributes are not copied. + # That means that modifying attributes of the clone will modify the original, since they will both point to the + # same attributes hash. If you need a copy of your attributes hash, please use the #dup method. + # + # user = User.first + # new_user = user.clone + # user.name # => "Bob" + # new_user.name = "Joe" + # user.name # => "Joe" + # + # user.object_id == new_user.object_id # => false + # user.name.object_id == new_user.name.object_id # => true + # + # user.name.object_id == user.dup.name.object_id # => false + + ## + # :method: dup + # Duped objects have no id assigned and are treated as new records. Note + # that this is a "shallow" copy as it copies the object's attributes + # only, not its associations. The extent of a "deep" copy is application + # specific and is therefore left to the application to implement according + # to its need. + # The dup method does not preserve the timestamps (created|updated)_(at|on) + # and locking column. + + ## + def initialize_dup(other) # :nodoc: + @attributes = init_attributes(other) + + _run_initialize_callbacks + + @new_record = true + @previously_new_record = false + @destroyed = false + @_start_transaction_state = nil + + super + end + + def init_attributes(_) # :nodoc: + attrs = @attributes.deep_dup + + if self.class.composite_primary_key? + @primary_key.each { |key| attrs.reset(key) } + else + attrs.reset(@primary_key) + end + + attrs + end + + # Populate +coder+ with attributes about this record that should be + # serialized. The structure of +coder+ defined in this method is + # guaranteed to match the structure of +coder+ passed to the #init_with + # method. + # + # Example: + # + # class Post < ActiveRecord::Base + # end + # coder = {} + # Post.new.encode_with(coder) + # coder # => {"attributes" => {"id" => nil, ... }} + def encode_with(coder) + self.class.yaml_encoder.encode(@attributes, coder) + coder["new_record"] = new_record? + coder["active_record_yaml_version"] = 2 + end + + ## + # :method: slice + # + # :call-seq: slice(*methods) + # + # Returns a hash of the given methods with their names as keys and returned + # values as values. + # + # topic = Topic.new(title: "Budget", author_name: "Jason") + # topic.slice(:title, :author_name) + # => { "title" => "Budget", "author_name" => "Jason" } + # + #-- + # Implemented by ActiveModel::Access#slice. + + ## + # :method: values_at + # + # :call-seq: values_at(*methods) + # + # Returns an array of the values returned by the given methods. + # + # topic = Topic.new(title: "Budget", author_name: "Jason") + # topic.values_at(:title, :author_name) + # => ["Budget", "Jason"] + # + #-- + # Implemented by ActiveModel::Access#values_at. + + # Returns true if +comparison_object+ is the same exact object, or +comparison_object+ + # is of the same type and +self+ has an ID and it is equal to +comparison_object.id+. + # + # Note that new records are different from any other record by definition, unless the + # other record is the receiver itself. Besides, if you fetch existing records with + # +select+ and leave the ID out, you're on your own, this predicate will return false. + # + # Note also that destroying a record preserves its ID in the model instance, so deleted + # models are still comparable. + def ==(comparison_object) + super || + comparison_object.instance_of?(self.class) && + primary_key_values_present? && + comparison_object.id == id + end + alias :eql? :== + + # Delegates to id in order to allow two records of the same type and id to work with something like: + # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ] + def hash + id = self.id + + if primary_key_values_present? + self.class.hash ^ id.hash + else + super + end + end + + # Clone and freeze the attributes hash such that associations are still + # accessible, even on destroyed records, but cloned models will not be + # frozen. + def freeze + @attributes = @attributes.clone.freeze + self + end + + # Returns +true+ if the attributes hash has been frozen. + def frozen? + @attributes.frozen? + end + + # Allows sort on objects + def <=>(other_object) + if other_object.is_a?(self.class) + to_key <=> other_object.to_key + else + super + end + end + + def present? # :nodoc: + true + end + + def blank? # :nodoc: + false + end + + # Returns +true+ if the record is read only. + def readonly? + @readonly + end + + # Returns +true+ if the record is in strict_loading mode. + def strict_loading? + @strict_loading + end + + # Sets the record to strict_loading mode. This will raise an error + # if the record tries to lazily load an association. + # + # user = User.first + # user.strict_loading! # => true + # user.address.city + # => ActiveRecord::StrictLoadingViolationError + # user.comments.to_a + # => ActiveRecord::StrictLoadingViolationError + # + # ==== Parameters + # + # * +value+ - Boolean specifying whether to enable or disable strict loading. + # * :mode - Symbol specifying strict loading mode. Defaults to :all. Using + # :n_plus_one_only mode will only raise an error if an association that + # will lead to an n plus one query is lazily loaded. + # + # ==== Examples + # + # user = User.first + # user.strict_loading!(false) # => false + # user.address.city # => "Tatooine" + # user.comments.to_a # => [# "Tatooine" + # user.comments.to_a # => [# ActiveRecord::StrictLoadingViolationError + def strict_loading!(value = true, mode: :all) + unless [:all, :n_plus_one_only].include?(mode) + raise ArgumentError, "The :mode option must be one of [:all, :n_plus_one_only] but #{mode.inspect} was provided." + end + + @strict_loading_mode = mode + @strict_loading = value + end + + attr_reader :strict_loading_mode + + # Returns +true+ if the record uses strict_loading with +:n_plus_one_only+ mode enabled. + def strict_loading_n_plus_one_only? + @strict_loading_mode == :n_plus_one_only + end + + # Returns +true+ if the record uses strict_loading with +:all+ mode enabled. + def strict_loading_all? + @strict_loading_mode == :all + end + + # Prevents records from being written to the database: + # + # customer = Customer.new + # customer.readonly! + # customer.save # raises ActiveRecord::ReadOnlyRecord + # + # customer = Customer.first + # customer.readonly! + # customer.update(name: 'New Name') # raises ActiveRecord::ReadOnlyRecord + # + # Read-only records cannot be deleted from the database either: + # + # customer = Customer.first + # customer.readonly! + # customer.destroy # raises ActiveRecord::ReadOnlyRecord + # + # Please, note that the objects themselves are still mutable in memory: + # + # customer = Customer.new + # customer.readonly! + # customer.name = 'New Name' # OK + # + # but you won't be able to persist the changes. + def readonly! + @readonly = true + end + + def connection_handler + self.class.connection_handler + end + + # Returns the attributes of the record as a nicely formatted string. + # + # Post.first.inspect + # #=> "#" + # + # The attributes can be limited by setting .attributes_for_inspect. + # + # Post.attributes_for_inspect = [:id, :title] + # Post.first.inspect + # #=> "#" + def inspect + inspect_with_attributes(attributes_for_inspect) + end + + # Returns all attributes of the record as a nicely formatted string, + # ignoring .attributes_for_inspect. + # + # Post.first.full_inspect + # #=> "#" + # + def full_inspect + inspect_with_attributes(all_attributes_for_inspect) + end + + # Takes a PP and prettily prints this record to it, allowing you to get a nice result from pp record + # when pp is required. + def pretty_print(pp) + return super if custom_inspect_method_defined? + pp.object_address_group(self) do + if @attributes + attr_names = attributes_for_inspect.select { |name| _has_attribute?(name.to_s) } + pp.seplist(attr_names, proc { pp.text "," }) do |attr_name| + attr_name = attr_name.to_s + pp.breakable " " + pp.group(1) do + pp.text attr_name + pp.text ":" + pp.breakable + value = attribute_for_inspect(attr_name) + pp.text value + end + end + else + pp.breakable " " + pp.text "not initialized" + end + end + end + + private + # +Array#flatten+ will call +#to_ary+ (recursively) on each of the elements of + # the array, and then rescues from the possible +NoMethodError+. If those elements are + # +ActiveRecord::Base+'s, then this triggers the various +method_missing+'s that we have, + # which significantly impacts upon performance. + # + # So we can avoid the +method_missing+ hit by explicitly defining +#to_ary+ as +nil+ here. + # + # See also https://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html + def to_ary + nil + end + + def init_internals + @readonly = false + @previously_new_record = false + @destroyed = false + @marked_for_destruction = false + @destroyed_by_association = nil + @_start_transaction_state = nil + + klass = self.class + + @primary_key = klass.primary_key + @strict_loading = klass.strict_loading_by_default + @strict_loading_mode = klass.strict_loading_mode + + klass.define_attribute_methods + end + + def initialize_internals_callback + end + + def custom_inspect_method_defined? + self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner + end + + class InspectionMask < DelegateClass(::String) + def pretty_print(pp) + pp.text __getobj__ + end + end + private_constant :InspectionMask + + def inspection_filter + self.class.inspection_filter + end + + def inspect_with_attributes(attributes_to_list) + inspection = if @attributes + attributes_to_list.filter_map do |name| + name = name.to_s + if _has_attribute?(name) + "#{name}: #{attribute_for_inspect(name)}" + end + end.join(", ") + else + "not initialized" + end + + "#<#{self.class} #{inspection}>" + end + + def attributes_for_inspect + self.class.attributes_for_inspect == :all ? all_attributes_for_inspect : self.class.attributes_for_inspect + end + + def all_attributes_for_inspect + return [] unless @attributes + + attribute_names + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/counter_cache.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/counter_cache.rb new file mode 100644 index 00000000..544a5652 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/counter_cache.rb @@ -0,0 +1,230 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record Counter Cache + module CounterCache + extend ActiveSupport::Concern + + included do + class_attribute :_counter_cache_columns, instance_accessor: false, default: [] + class_attribute :counter_cached_association_names, instance_writer: false, default: [] + end + + module ClassMethods + # Resets one or more counter caches to their correct value using an SQL + # count query. This is useful when adding new counter caches, or if the + # counter has been corrupted or modified directly by SQL. + # + # ==== Parameters + # + # * +id+ - The id of the object you wish to reset a counter on. + # * +counters+ - One or more association counters to reset. Association name or counter name can be given. + # * :touch - Touch timestamp columns when updating. + # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to + # touch that column or an array of symbols to touch just those ones. + # + # ==== Examples + # + # # For the Post with id #1, reset the comments_count + # Post.reset_counters(1, :comments) + # + # # Like above, but also touch the updated_at and/or updated_on + # # attributes. + # Post.reset_counters(1, :comments, touch: true) + def reset_counters(id, *counters, touch: nil) + object = find(id) + + updates = {} + counters.each do |counter_association| + has_many_association = _reflect_on_association(counter_association) + unless has_many_association + has_many = reflect_on_all_associations(:has_many) + has_many_association = has_many.find { |association| association.counter_cache_column && association.counter_cache_column.to_sym == counter_association.to_sym } + counter_association = has_many_association.plural_name if has_many_association + end + raise ArgumentError, "'#{name}' has no association called '#{counter_association}'" unless has_many_association + + if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection + has_many_association = has_many_association.through_reflection + end + + foreign_key = has_many_association.foreign_key.to_s + child_class = has_many_association.klass + reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? } + counter_name = reflection.counter_cache_column + + count_was = object.send(counter_name) + count = object.send(counter_association).count(:all) + updates[counter_name] = count if count != count_was + end + + if touch + names = touch if touch != true + names = Array.wrap(names) + options = names.extract_options! + touch_updates = touch_attributes_with_time(*names, **options) + updates.merge!(touch_updates) + end + + unscoped.where(primary_key => [object.id]).update_all(updates) if updates.any? + + true + end + + # A generic "counter updater" implementation, intended primarily to be + # used by #increment_counter and #decrement_counter, but which may also + # be useful on its own. It simply does a direct SQL update for the record + # with the given ID, altering the given hash of counters by the amount + # given by the corresponding value: + # + # ==== Parameters + # + # * +id+ - The id of the object you wish to update a counter on or an array of ids. + # * +counters+ - A Hash containing the names of the fields + # to update as keys and the amount to update the field by as values. + # * :touch option - Touch timestamp columns when updating. + # If attribute names are passed, they are updated along with updated_at/on + # attributes. + # + # ==== Examples + # + # # For the Post with id of 5, decrement the comments_count by 1, and + # # increment the actions_count by 1 + # Post.update_counters 5, comments_count: -1, actions_count: 1 + # # Executes the following SQL: + # # UPDATE posts + # # SET comments_count = COALESCE(comments_count, 0) - 1, + # # actions_count = COALESCE(actions_count, 0) + 1 + # # WHERE id = 5 + # + # # For the Posts with id of 10 and 15, increment the comments_count by 1 + # Post.update_counters [10, 15], comments_count: 1 + # # Executes the following SQL: + # # UPDATE posts + # # SET comments_count = COALESCE(comments_count, 0) + 1 + # # WHERE id IN (10, 15) + # + # # For the Posts with id of 10 and 15, increment the comments_count by 1 + # # and update the updated_at value for each counter. + # Post.update_counters [10, 15], comments_count: 1, touch: true + # # Executes the following SQL: + # # UPDATE posts + # # SET comments_count = COALESCE(comments_count, 0) + 1, + # # `updated_at` = '2016-10-13T09:59:23-05:00' + # # WHERE id IN (10, 15) + def update_counters(id, counters) + id = [id] if composite_primary_key? && id.is_a?(Array) && !id[0].is_a?(Array) + unscoped.where!(primary_key => id).update_counters(counters) + end + + # Increment a numeric field by one, via a direct SQL update. + # + # This method is used primarily for maintaining counter_cache columns that are + # used to store aggregate values. For example, a +DiscussionBoard+ may cache + # posts_count and comments_count to avoid running an SQL query to calculate the + # number of posts and comments there are, each time it is displayed. + # + # ==== Parameters + # + # * +counter_name+ - The name of the field that should be incremented. + # * +id+ - The id of the object that should be incremented or an array of ids. + # * :by - The amount by which to increment the value. Defaults to +1+. + # * :touch - Touch timestamp columns when updating. + # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to + # touch that column or an array of symbols to touch just those ones. + # + # ==== Examples + # + # # Increment the posts_count column for the record with an id of 5 + # DiscussionBoard.increment_counter(:posts_count, 5) + # + # # Increment the posts_count column for the record with an id of 5 + # # by a specific amount. + # DiscussionBoard.increment_counter(:posts_count, 5, by: 3) + # + # # Increment the posts_count column for the record with an id of 5 + # # and update the updated_at value. + # DiscussionBoard.increment_counter(:posts_count, 5, touch: true) + def increment_counter(counter_name, id, by: 1, touch: nil) + update_counters(id, counter_name => by, touch: touch) + end + + # Decrement a numeric field by one, via a direct SQL update. + # + # This works the same as #increment_counter but reduces the column value by + # 1 instead of increasing it. + # + # ==== Parameters + # + # * +counter_name+ - The name of the field that should be decremented. + # * +id+ - The id of the object that should be decremented or an array of ids. + # * :by - The amount by which to decrement the value. Defaults to +1+. + # * :touch - Touch timestamp columns when updating. + # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to + # touch that column or an array of symbols to touch just those ones. + # + # ==== Examples + # + # # Decrement the posts_count column for the record with an id of 5 + # DiscussionBoard.decrement_counter(:posts_count, 5) + # + # # Decrement the posts_count column for the record with an id of 5 + # by a specific amount. + # DiscussionBoard.decrement_counter(:posts_count, 5, by: 3) + # + # # Decrement the posts_count column for the record with an id of 5 + # # and update the updated_at value. + # DiscussionBoard.decrement_counter(:posts_count, 5, touch: true) + def decrement_counter(counter_name, id, by: 1, touch: nil) + update_counters(id, counter_name => -by, touch: touch) + end + + def counter_cache_column?(name) # :nodoc: + _counter_cache_columns.include?(name) + end + + def load_schema! # :nodoc: + super + + association_names = _reflections.filter_map do |name, reflection| + next unless reflection.belongs_to? && reflection.counter_cache_column + + name.to_sym + end + + self.counter_cached_association_names |= association_names + end + end + + private + def _create_record(attribute_names = self.attribute_names) + id = super + + counter_cached_association_names.each do |association_name| + association(association_name).increment_counters + end + + id + end + + def destroy_row + affected_rows = super + + if affected_rows > 0 + counter_cached_association_names.each do |association_name| + association = association(association_name) + + unless destroyed_by_association && _foreign_keys_equal?(destroyed_by_association.foreign_key, association.reflection.foreign_key) + association.decrement_counters + end + end + end + + affected_rows + end + + def _foreign_keys_equal?(fkey1, fkey2) + fkey1 == fkey2 || Array(fkey1).map(&:to_sym) == Array(fkey2).map(&:to_sym) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/database_configurations.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/database_configurations.rb new file mode 100644 index 00000000..906f7873 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/database_configurations.rb @@ -0,0 +1,309 @@ +# frozen_string_literal: true + +require "uri" +require "active_record/database_configurations/database_config" +require "active_record/database_configurations/hash_config" +require "active_record/database_configurations/url_config" +require "active_record/database_configurations/connection_url_resolver" + +module ActiveRecord + # = Active Record Database Configurations + # + # +ActiveRecord::DatabaseConfigurations+ returns an array of +DatabaseConfig+ + # objects that are constructed from the application's database + # configuration hash or URL string. + # + # The array of +DatabaseConfig+ objects in an application default to either a + # HashConfig or UrlConfig. You can retrieve your application's config by using + # ActiveRecord::Base.configurations. + # + # If you register a custom handler, objects will be created according to the + # conditions of the handler. See ::register_db_config_handler for more on + # registering custom handlers. + class DatabaseConfigurations + class InvalidConfigurationError < StandardError; end + + attr_reader :configurations + delegate :any?, to: :configurations + + singleton_class.attr_accessor :db_config_handlers # :nodoc: + self.db_config_handlers = [] # :nodoc: + + # Allows an application to register a custom handler for database configuration + # objects. This is useful for creating a custom handler that responds to + # methods your application needs but Active Record doesn't implement. For + # example if you are using Vitess, you may want your Vitess configurations + # to respond to `sharded?`. To implement this define the following in an + # initializer: + # + # ActiveRecord::DatabaseConfigurations.register_db_config_handler do |env_name, name, url, config| + # next unless config.key?(:vitess) + # VitessConfig.new(env_name, name, config) + # end + # + # Note: applications must handle the condition in which custom config should be + # created in your handler registration otherwise all objects will use the custom + # handler. + # + # Then define your +VitessConfig+ to respond to the methods your application + # needs. It is recommended that you inherit from one of the existing + # database config classes to avoid having to reimplement all methods. Custom + # config handlers should only implement methods Active Record does not. + # + # class VitessConfig < ActiveRecord::DatabaseConfigurations::UrlConfig + # def sharded? + # configuration_hash.fetch("sharded", false) + # end + # end + # + # For configs that have a +:vitess+ key, a +VitessConfig+ object will be + # created instead of a +UrlConfig+. + def self.register_db_config_handler(&block) + db_config_handlers << block + end + + register_db_config_handler do |env_name, name, url, config| + if url + UrlConfig.new(env_name, name, url, config) + else + HashConfig.new(env_name, name, config) + end + end + + def initialize(configurations = {}) + @configurations = build_configs(configurations) + end + + # Collects the configs for the environment and optionally the specification + # name passed in. To include replica configurations pass include_hidden: true. + # + # If a name is provided a single +DatabaseConfig+ object will be + # returned, otherwise an array of +DatabaseConfig+ objects will be + # returned that corresponds with the environment and type requested. + # + # ==== Options + # + # * env_name: The environment name. Defaults to +nil+ which will collect + # configs for all environments. + # * name: The db config name (i.e. primary, animals, etc.). Defaults + # to +nil+. If no +env_name+ is specified the config for the default env and the + # passed +name+ will be returned. + # * config_key: Selects configs that contain a particular key in the configuration + # hash. Useful for selecting configs that use a custom db config handler or finding + # configs with hashes that contain a particular key. + # * include_hidden: Determines whether to include replicas and configurations + # hidden by database_tasks: false in the returned list. Most of the time we're only + # iterating over the primary connections (i.e. migrations don't need to run for the + # write and read connection). Defaults to +false+. + def configs_for(env_name: nil, name: nil, config_key: nil, include_hidden: false) + env_name ||= default_env if name + configs = env_with_configs(env_name) + + unless include_hidden + configs = configs.select do |db_config| + db_config.database_tasks? + end + end + + if config_key + configs = configs.select do |db_config| + db_config.configuration_hash.key?(config_key) + end + end + + if name + configs.find do |db_config| + db_config.name == name.to_s + end + else + configs + end + end + + # Returns a single +DatabaseConfig+ object based on the requested environment. + # + # If the application has multiple databases +find_db_config+ will return + # the first +DatabaseConfig+ for the environment. + def find_db_config(env) + env = env.to_s + configurations.find do |db_config| + db_config.for_current_env? && (db_config.env_name == env || db_config.name == env) + end || configurations.find do |db_config| + db_config.env_name == env + end + end + + # A primary configuration is one that is named primary or if there is + # no primary, the first configuration for an environment will be treated + # as primary. This is used as the "default" configuration and is used + # when the application needs to treat one configuration differently. For + # example, when Rails dumps the schema, the primary configuration's schema + # file will be named `schema.rb` instead of `primary_schema.rb`. + def primary?(name) # :nodoc: + return true if name == "primary" + + first_config = find_db_config(default_env) + first_config && name == first_config.name + end + + # Checks if the application's configurations are empty. + def empty? + configurations.empty? + end + alias :blank? :empty? + + # Returns fully resolved connection, accepts hash, string or symbol. + # Always returns a DatabaseConfiguration::DatabaseConfig + # + # == Examples + # + # Symbol representing current environment. + # + # DatabaseConfigurations.new("production" => {}).resolve(:production) + # # => DatabaseConfigurations::HashConfig.new(env_name: "production", config: {}) + # + # One layer deep hash of connection values. + # + # DatabaseConfigurations.new({}).resolve("adapter" => "sqlite3") + # # => DatabaseConfigurations::HashConfig.new(config: {"adapter" => "sqlite3"}) + # + # Connection URL. + # + # DatabaseConfigurations.new({}).resolve("postgresql://localhost/foo") + # # => DatabaseConfigurations::UrlConfig.new(config: {"adapter" => "postgresql", "host" => "localhost", "database" => "foo"}) + def resolve(config) # :nodoc: + return config if DatabaseConfigurations::DatabaseConfig === config + + case config + when Symbol + resolve_symbol_connection(config) + when Hash, String + build_db_config_from_raw_config(default_env, "primary", config) + else + raise TypeError, "Invalid type for configuration. Expected Symbol, String, or Hash. Got #{config.inspect}" + end + end + + private + def default_env + ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s + end + + def env_with_configs(env = nil) + if env + configurations.select { |db_config| db_config.env_name == env } + else + configurations + end + end + + def build_configs(configs) + return configs.configurations if configs.is_a?(DatabaseConfigurations) + return configs if configs.is_a?(Array) + + db_configs = configs.flat_map do |env_name, config| + if config.is_a?(Hash) && config.values.all?(Hash) + walk_configs(env_name.to_s, config) + else + build_db_config_from_raw_config(env_name.to_s, "primary", config) + end + end + + unless db_configs.find(&:for_current_env?) + db_configs << environment_url_config(default_env, "primary", {}) + end + + merge_db_environment_variables(default_env, db_configs.compact) + end + + def walk_configs(env_name, config) + config.map do |name, sub_config| + build_db_config_from_raw_config(env_name, name.to_s, sub_config) + end + end + + def resolve_symbol_connection(name) + if db_config = find_db_config(name) + db_config + else + raise AdapterNotSpecified, <<~MSG + The `#{name}` database is not configured for the `#{default_env}` environment. + + Available database configurations are: + + #{build_configuration_sentence} + MSG + end + end + + def build_configuration_sentence + configs = configs_for(include_hidden: true) + + configs.group_by(&:env_name).map do |env, config| + names = config.map(&:name) + if names.size > 1 + "#{env}: #{names.join(", ")}" + else + env + end + end.join("\n") + end + + def build_db_config_from_raw_config(env_name, name, config) + case config + when String + build_db_config_from_string(env_name, name, config) + when Hash + build_db_config_from_hash(env_name, name, config.symbolize_keys) + else + raise InvalidConfigurationError, "'{ #{env_name} => #{config} }' is not a valid configuration. Expected '#{config}' to be a URL string or a Hash." + end + end + + def build_db_config_from_string(env_name, name, config) + url = config + uri = URI.parse(url) + if uri.scheme + UrlConfig.new(env_name, name, url) + else + raise InvalidConfigurationError, "'{ #{env_name} => #{config} }' is not a valid configuration. Expected '#{config}' to be a URL string or a Hash." + end + end + + def build_db_config_from_hash(env_name, name, config) + url = config[:url] + config_without_url = config.dup + config_without_url.delete :url + + DatabaseConfigurations.db_config_handlers.reverse_each do |handler| + config = handler.call(env_name, name, url, config_without_url) + return config if config + end + + nil + end + + def merge_db_environment_variables(current_env, configs) + configs.map do |config| + next config if config.is_a?(UrlConfig) || config.env_name != current_env + + url_config = environment_url_config(current_env, config.name, config.configuration_hash) + url_config || config + end + end + + def environment_url_config(env, name, config) + url = environment_value_for(name) + return unless url + + UrlConfig.new(env, name, url, config) + end + + def environment_value_for(name) + name_env_key = "#{name.upcase}_DATABASE_URL" + url = ENV[name_env_key] + url ||= ENV["DATABASE_URL"] if name == "primary" + url + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/database_configurations/connection_url_resolver.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/database_configurations/connection_url_resolver.rb new file mode 100644 index 00000000..ffc07eeb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/database_configurations/connection_url_resolver.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +require "uri" +require "active_support/core_ext/enumerable" +require "active_support/core_ext/hash/reverse_merge" + +module ActiveRecord + class DatabaseConfigurations + # Expands a connection string into a hash. + class ConnectionUrlResolver # :nodoc: + # == Example + # + # url = "postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000" + # ConnectionUrlResolver.new(url).to_hash + # # => { + # adapter: "postgresql", + # host: "localhost", + # port: 9000, + # database: "foo_test", + # username: "foo", + # password: "bar", + # pool: "5", + # timeout: "3000" + # } + def initialize(url) + raise "Database URL cannot be empty" if url.blank? + @uri = uri_parser.parse(url) + @adapter = resolved_adapter + + if @uri.opaque + @uri.opaque, @query = @uri.opaque.split("?", 2) + else + @query = @uri.query + end + end + + # Converts the given URL to a full connection hash. + def to_hash + config = raw_config.compact_blank + config.map { |key, value| config[key] = uri_parser.unescape(value) if value.is_a? String } + config + end + + private + attr_reader :uri + + def uri_parser + @uri_parser ||= URI::RFC2396_Parser.new + end + + # Converts the query parameters of the URI into a hash. + # + # "localhost?pool=5&reaping_frequency=2" + # # => { pool: "5", reaping_frequency: "2" } + # + # returns empty hash if no query present. + # + # "localhost" + # # => {} + def query_hash + Hash[(@query || "").split("&").map { |pair| pair.split("=", 2) }].symbolize_keys + end + + def raw_config + if uri.opaque + query_hash.merge( + adapter: @adapter, + database: uri.opaque + ) + else + query_hash.reverse_merge( + adapter: @adapter, + username: uri.user, + password: uri.password, + port: uri.port, + database: database_from_path, + host: uri.hostname + ) + end + end + + def resolved_adapter + adapter = uri.scheme && @uri.scheme.tr("-", "_") + if adapter && ActiveRecord.protocol_adapters[adapter] + adapter = ActiveRecord.protocol_adapters[adapter] + end + adapter + end + + # Returns name of the database. + def database_from_path + if @adapter == "sqlite3" + # 'sqlite3:/foo' is absolute, because that makes sense. The + # corresponding relative version, 'sqlite3:foo', is handled + # elsewhere, as an "opaque". + + uri.path + else + # Only SQLite uses a filename as the "database" name; for + # anything else, a leading slash would be silly. + + uri.path.delete_prefix("/") + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/database_configurations/database_config.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/database_configurations/database_config.rb new file mode 100644 index 00000000..d6f09581 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/database_configurations/database_config.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +module ActiveRecord + class DatabaseConfigurations + # ActiveRecord::Base.configurations will return either a HashConfig or + # UrlConfig respectively. It will never return a +DatabaseConfig+ object, + # as this is the parent class for the types of database configuration objects. + class DatabaseConfig # :nodoc: + attr_reader :env_name, :name + + def initialize(env_name, name) + @env_name = env_name + @name = name + @adapter_class = nil + end + + def adapter_class + @adapter_class ||= ActiveRecord::ConnectionAdapters.resolve(adapter) + end + + def inspect # :nodoc: + "#<#{self.class.name} env_name=#{@env_name} name=#{@name} adapter_class=#{adapter_class}>" + end + + def new_connection + adapter_class.new(configuration_hash) + end + + def validate! + adapter_class if adapter + + true + end + + def host + raise NotImplementedError + end + + def database + raise NotImplementedError + end + + def _database=(database) + raise NotImplementedError + end + + def adapter + raise NotImplementedError + end + + def pool + raise NotImplementedError + end + + def min_threads + raise NotImplementedError + end + + def max_threads + raise NotImplementedError + end + + def max_queue + raise NotImplementedError + end + + def query_cache + raise NotImplementedError + end + + def checkout_timeout + raise NotImplementedError + end + + def reaping_frequency + raise NotImplementedError + end + + def idle_timeout + raise NotImplementedError + end + + def replica? + raise NotImplementedError + end + + def migrations_paths + raise NotImplementedError + end + + def for_current_env? + env_name == ActiveRecord::ConnectionHandling::DEFAULT_ENV.call + end + + def schema_cache_path + raise NotImplementedError + end + + def use_metadata_table? + raise NotImplementedError + end + + def seeds? + raise NotImplementedError + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/database_configurations/hash_config.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/database_configurations/hash_config.rb new file mode 100644 index 00000000..7a9b05bd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/database_configurations/hash_config.rb @@ -0,0 +1,180 @@ +# frozen_string_literal: true + +# :markup: markdown + +module ActiveRecord + class DatabaseConfigurations + # # Active Record Database Hash Config + # + # A `HashConfig` object is created for each database configuration entry that is + # created from a hash. + # + # A hash config: + # + # { "development" => { "database" => "db_name" } } + # + # Becomes: + # + # # + # + # See ActiveRecord::DatabaseConfigurations for more info. + class HashConfig < DatabaseConfig + attr_reader :configuration_hash + + # Initialize a new `HashConfig` object + # + # #### Parameters + # + # * `env_name` - The Rails environment, i.e. "development". + # * `name` - The db config name. In a standard two-tier database configuration + # this will default to "primary". In a multiple database three-tier database + # configuration this corresponds to the name used in the second tier, for + # example "primary_readonly". + # * `configuration_hash` - The config hash. This is the hash that contains the + # database adapter, name, and other important information for database + # connections. + # + def initialize(env_name, name, configuration_hash) + super(env_name, name) + @configuration_hash = configuration_hash.symbolize_keys.freeze + end + + # Determines whether a database configuration is for a replica / readonly + # connection. If the `replica` key is present in the config, `replica?` will + # return `true`. + def replica? + configuration_hash[:replica] + end + + # The migrations paths for a database configuration. If the `migrations_paths` + # key is present in the config, `migrations_paths` will return its value. + def migrations_paths + configuration_hash[:migrations_paths] + end + + def host + configuration_hash[:host] + end + + def socket # :nodoc: + configuration_hash[:socket] + end + + def database + configuration_hash[:database] + end + + def _database=(database) # :nodoc: + @configuration_hash = configuration_hash.merge(database: database).freeze + end + + def pool + (configuration_hash[:pool] || 5).to_i + end + + def min_threads + (configuration_hash[:min_threads] || 0).to_i + end + + def max_threads + (configuration_hash[:max_threads] || pool).to_i + end + + def query_cache + configuration_hash[:query_cache] + end + + def max_queue + max_threads * 4 + end + + def checkout_timeout + (configuration_hash[:checkout_timeout] || 5).to_f + end + + # `reaping_frequency` is configurable mostly for historical reasons, but it + # could also be useful if someone wants a very low `idle_timeout`. + def reaping_frequency + configuration_hash.fetch(:reaping_frequency, 60)&.to_f + end + + def idle_timeout + timeout = configuration_hash.fetch(:idle_timeout, 300).to_f + timeout if timeout > 0 + end + + def adapter + configuration_hash[:adapter]&.to_s + end + + # The path to the schema cache dump file for a database. If omitted, the + # filename will be read from ENV or a default will be derived. + def schema_cache_path + configuration_hash[:schema_cache_path] + end + + def default_schema_cache_path(db_dir = "db") + if primary? + File.join(db_dir, "schema_cache.yml") + else + File.join(db_dir, "#{name}_schema_cache.yml") + end + end + + def lazy_schema_cache_path + schema_cache_path || default_schema_cache_path + end + + def primary? # :nodoc: + Base.configurations.primary?(name) + end + + # Determines whether the db:prepare task should seed the database from db/seeds.rb. + # + # If the `seeds` key is present in the config, `seeds?` will return its value. Otherwise, it + # will return `true` for the primary database and `false` for all other configs. + def seeds? + configuration_hash.fetch(:seeds, primary?) + end + + # Determines whether to dump the schema/structure files and the filename that + # should be used. + # + # If `configuration_hash[:schema_dump]` is set to `false` or `nil` the schema + # will not be dumped. + # + # If the config option is set that will be used. Otherwise Rails will generate + # the filename from the database config name. + def schema_dump(format = ActiveRecord.schema_format) + if configuration_hash.key?(:schema_dump) + if config = configuration_hash[:schema_dump] + config + end + elsif primary? + schema_file_type(format) + else + "#{name}_#{schema_file_type(format)}" + end + end + + def database_tasks? # :nodoc: + !replica? && !!configuration_hash.fetch(:database_tasks, true) + end + + def use_metadata_table? # :nodoc: + configuration_hash.fetch(:use_metadata_table, true) + end + + private + def schema_file_type(format) + case format + when :ruby + "schema.rb" + when :sql + "structure.sql" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/database_configurations/url_config.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/database_configurations/url_config.rb new file mode 100644 index 00000000..820b5a03 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/database_configurations/url_config.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module ActiveRecord + class DatabaseConfigurations + # = Active Record Database Url Config + # + # A +UrlConfig+ object is created for each database configuration + # entry that is created from a URL. This can either be a URL string + # or a hash with a URL in place of the config hash. + # + # A URL config: + # + # postgres://localhost/foo + # + # Becomes: + # + # # + # + # See ActiveRecord::DatabaseConfigurations for more info. + # + class UrlConfig < HashConfig + attr_reader :url + + # Initialize a new +UrlConfig+ object + # + # ==== Options + # + # * :env_name - The \Rails environment, i.e. "development". + # * :name - The db config name. In a standard two-tier + # database configuration this will default to "primary". In a multiple + # database three-tier database configuration this corresponds to the name + # used in the second tier, for example "primary_readonly". + # * :url - The database URL. + # * :config - The config hash. This is the hash that contains the + # database adapter, name, and other important information for database + # connections. + def initialize(env_name, name, url, configuration_hash = {}) + super(env_name, name, configuration_hash) + + @url = url + @configuration_hash = @configuration_hash.merge(build_url_hash) + + if @configuration_hash[:schema_dump] == "false" + @configuration_hash[:schema_dump] = false + end + + if @configuration_hash[:query_cache] == "false" + @configuration_hash[:query_cache] = false + end + + to_boolean!(@configuration_hash, :replica) + to_boolean!(@configuration_hash, :database_tasks) + + @configuration_hash.freeze + end + + private + def to_boolean!(configuration_hash, key) + if configuration_hash[key].is_a?(String) + configuration_hash[key] = configuration_hash[key] != "false" + end + end + + # Return a Hash that can be merged into the main config that represents + # the passed in url + def build_url_hash + if url.nil? || url.start_with?("jdbc:", "http:", "https:") + { url: url } + else + ConnectionUrlResolver.new(url).to_hash + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/delegated_type.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/delegated_type.rb new file mode 100644 index 00000000..f2024e1c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/delegated_type.rb @@ -0,0 +1,279 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/inquiry" + +module ActiveRecord + # = Delegated types + # + # Class hierarchies can map to relational database tables in many ways. Active Record, for example, offers + # purely abstract classes, where the superclass doesn't persist any attributes, and single-table inheritance, + # where all attributes from all levels of the hierarchy are represented in a single table. Both have their + # places, but neither are without their drawbacks. + # + # The problem with purely abstract classes is that all concrete subclasses must persist all the shared + # attributes themselves in their own tables (also known as class-table inheritance). This makes it hard to + # do queries across the hierarchy. For example, imagine you have the following hierarchy: + # + # Entry < ApplicationRecord + # Message < Entry + # Comment < Entry + # + # How do you show a feed that has both +Message+ and +Comment+ records, which can be easily paginated? + # Well, you can't! Messages are backed by a messages table and comments by a comments table. You can't + # pull from both tables at once and use a consistent OFFSET/LIMIT scheme. + # + # You can get around the pagination problem by using single-table inheritance, but now you're forced into + # a single mega table with all the attributes from all subclasses. No matter how divergent. If a Message + # has a subject, but the comment does not, well, now the comment does anyway! So STI works best when there's + # little divergence between the subclasses and their attributes. + # + # But there's a third way: Delegated types. With this approach, the "superclass" is a concrete class + # that is represented by its own table, where all the superclass attributes that are shared amongst all the + # "subclasses" are stored. And then each of the subclasses have their own individual tables for additional + # attributes that are particular to their implementation. This is similar to what's called multi-table + # inheritance in Django, but instead of actual inheritance, this approach uses delegation to form the + # hierarchy and share responsibilities. + # + # Let's look at that entry/message/comment example using delegated types: + # + # # Schema: entries[ id, account_id, creator_id, entryable_type, entryable_id, created_at, updated_at ] + # class Entry < ApplicationRecord + # belongs_to :account + # belongs_to :creator + # delegated_type :entryable, types: %w[ Message Comment ] + # end + # + # module Entryable + # extend ActiveSupport::Concern + # + # included do + # has_one :entry, as: :entryable, touch: true + # end + # end + # + # # Schema: messages[ id, subject, body, created_at, updated_at ] + # class Message < ApplicationRecord + # include Entryable + # end + # + # # Schema: comments[ id, content, created_at, updated_at ] + # class Comment < ApplicationRecord + # include Entryable + # end + # + # As you can see, neither +Message+ nor +Comment+ are meant to stand alone. Crucial metadata for both classes + # resides in the +Entry+ "superclass". But the +Entry+ absolutely can stand alone in terms of querying capacity + # in particular. You can now easily do things like: + # + # Account.find(1).entries.order(created_at: :desc).limit(50) + # + # Which is exactly what you want when displaying both comments and messages together. The entry itself can + # be rendered as its delegated type easily, like so: + # + # # entries/_entry.html.erb + # <%= render "entries/entryables/#{entry.entryable_name}", entry: entry %> + # + # # entries/entryables/_message.html.erb + #
    + #
    <%= entry.message.subject %>
    + #

    <%= entry.message.body %>

    + # Posted on <%= entry.created_at %> by <%= entry.creator.name %> + #
    + # + # # entries/entryables/_comment.html.erb + #
    + # <%= entry.creator.name %> said: <%= entry.comment.content %> + #
    + # + # == Sharing behavior with concerns and controllers + # + # The entry "superclass" also serves as a perfect place to put all that shared logic that applies to both + # messages and comments, and which acts primarily on the shared attributes. Imagine: + # + # class Entry < ApplicationRecord + # include Eventable, Forwardable, Redeliverable + # end + # + # Which allows you to have controllers for things like +ForwardsController+ and +RedeliverableController+ + # that both act on entries, and thus provide the shared functionality to both messages and comments. + # + # == Creating new records + # + # You create a new record that uses delegated typing by creating the delegator and delegatee at the same time, + # like so: + # + # Entry.create! entryable: Comment.new(content: "Hello!"), creator: Current.user, account: Current.account + # + # If you need more complicated composition, or you need to perform dependent validation, you should build a factory + # method or class to take care of the complicated needs. This could be as simple as: + # + # class Entry < ApplicationRecord + # def self.create_with_comment(content, creator: Current.user, account: Current.account) + # create! entryable: Comment.new(content: content), creator: creator, account: account + # end + # end + # + # == Querying across records + # + # A consequence of delegated types is that querying attributes spread across multiple classes becomes slightly more + # tricky, but not impossible. + # + # The simplest method is to join the "superclass" to the "subclass" and apply the query parameters (i.e. #where) + # in appropriate places: + # + # Comment.joins(:entry).where(comments: { content: 'Hello!' }, entry: { creator: Current.user } ) + # + # For convenience, add a scope on the concern. Now all classes that implement the concern will automatically include + # the method: + # + # # app/models/concerns/entryable.rb + # scope :with_entry, ->(attrs) { joins(:entry).where(entry: attrs) } + # + # Now the query can be shortened significantly: + # + # Comment.where(content: 'Hello!').with_entry(creator: Current.user) + # + # == Adding further delegation + # + # The delegated type shouldn't just answer the question of what the underlying class is called. In fact, that's + # an anti-pattern most of the time. The reason you're building this hierarchy is to take advantage of polymorphism. + # So here's a simple example of that: + # + # class Entry < ApplicationRecord + # delegated_type :entryable, types: %w[ Message Comment ] + # delegate :title, to: :entryable + # end + # + # class Message < ApplicationRecord + # def title + # subject + # end + # end + # + # class Comment < ApplicationRecord + # def title + # content.truncate(20) + # end + # end + # + # Now you can list a bunch of entries, call Entry#title, and polymorphism will provide you with the answer. + # + # == Nested \Attributes + # + # Enabling nested attributes on a delegated_type association allows you to + # create the entry and message in one go: + # + # class Entry < ApplicationRecord + # delegated_type :entryable, types: %w[ Message Comment ] + # accepts_nested_attributes_for :entryable + # end + # + # params = { entry: { entryable_type: 'Message', entryable_attributes: { subject: 'Smiling' } } } + # entry = Entry.create(params[:entry]) + # entry.entryable.id # => 2 + # entry.entryable.subject # => 'Smiling' + module DelegatedType + # Defines this as a class that'll delegate its type for the passed +role+ to the class references in +types+. + # That'll create a polymorphic +belongs_to+ relationship to that +role+, and it'll add all the delegated + # type convenience methods: + # + # class Entry < ApplicationRecord + # delegated_type :entryable, types: %w[ Message Comment ], dependent: :destroy + # end + # + # @entry.entryable_class # => Message or Comment + # @entry.entryable_name # => "message" or "comment" + # Entry.messages # => Entry.where(entryable_type: "Message") + # @entry.message? # => true when entryable_type == "Message" + # @entry.message # => returns the message record, when entryable_type == "Message", otherwise nil + # @entry.message_id # => returns entryable_id, when entryable_type == "Message", otherwise nil + # Entry.comments # => Entry.where(entryable_type: "Comment") + # @entry.comment? # => true when entryable_type == "Comment" + # @entry.comment # => returns the comment record, when entryable_type == "Comment", otherwise nil + # @entry.comment_id # => returns entryable_id, when entryable_type == "Comment", otherwise nil + # + # You can also declare namespaced types: + # + # class Entry < ApplicationRecord + # delegated_type :entryable, types: %w[ Message Comment Access::NoticeMessage ], dependent: :destroy + # end + # + # Entry.access_notice_messages + # @entry.access_notice_message + # @entry.access_notice_message? + # + # === Options + # + # The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc. + # The following options can be included to specialize the behavior of the delegated type convenience methods. + # + # [+:foreign_key+] + # Specify the foreign key used for the convenience methods. By default this is guessed to be the passed + # +role+ with an "_id" suffix. So a class that defines a + # delegated_type :entryable, types: %w[ Message Comment ] association will use "entryable_id" as + # the default :foreign_key. + # [+:foreign_type+] + # Specify the column used to store the associated object's type. By default this is inferred to be the passed + # +role+ with a "_type" suffix. A class that defines a + # delegated_type :entryable, types: %w[ Message Comment ] association will use "entryable_type" as + # the default :foreign_type. + # [+:primary_key+] + # Specify the method that returns the primary key of associated object used for the convenience methods. + # By default this is +id+. + # + # Option examples: + # class Entry < ApplicationRecord + # delegated_type :entryable, types: %w[ Message Comment ], primary_key: :uuid, foreign_key: :entryable_uuid + # end + # + # @entry.message_uuid # => returns entryable_uuid, when entryable_type == "Message", otherwise nil + # @entry.comment_uuid # => returns entryable_uuid, when entryable_type == "Comment", otherwise nil + def delegated_type(role, types:, **options) + belongs_to role, options.delete(:scope), **options.merge(polymorphic: true) + define_delegated_type_methods role, types: types, options: options + end + + private + def define_delegated_type_methods(role, types:, options:) + primary_key = options[:primary_key] || "id" + role_type = options[:foreign_type] || "#{role}_type" + role_id = options[:foreign_key] || "#{role}_id" + + define_singleton_method "#{role}_types" do + types.map(&:to_s) + end + + define_method "#{role}_class" do + public_send(role_type).constantize + end + + define_method "#{role}_name" do + public_send("#{role}_class").model_name.singular.inquiry + end + + define_method "build_#{role}" do |*params| + public_send("#{role}=", public_send("#{role}_class").new(*params)) + end + + types.each do |type| + scope_name = type.tableize.tr("/", "_") + singular = scope_name.singularize + query = "#{singular}?" + + scope scope_name, -> { where(role_type => type) } + + define_method query do + public_send(role_type) == type + end + + define_method singular do + public_send(role) if public_send(query) + end + + define_method "#{singular}_#{primary_key}" do + public_send(role_id) if public_send(query) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/deprecator.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/deprecator.rb new file mode 100644 index 00000000..aa262965 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/deprecator.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module ActiveRecord + def self.deprecator # :nodoc: + @deprecator ||= ActiveSupport::Deprecation.new + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/destroy_association_async_job.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/destroy_association_async_job.rb new file mode 100644 index 00000000..931442f9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/destroy_association_async_job.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module ActiveRecord + class DestroyAssociationAsyncError < StandardError + end + + # = Active Record Destroy Association Async Job + # + # Job to destroy the records associated with a destroyed record in background. + class DestroyAssociationAsyncJob < ActiveJob::Base + queue_as { ActiveRecord.queues[:destroy] } + + discard_on ActiveJob::DeserializationError + + def perform( + owner_model_name: nil, owner_id: nil, + association_class: nil, association_ids: nil, association_primary_key_column: nil, + ensuring_owner_was_method: nil + ) + association_model = association_class.constantize + owner_class = owner_model_name.constantize + owner = owner_class.find_by(owner_class.primary_key => [owner_id]) + + if !owner_destroyed?(owner, ensuring_owner_was_method) + raise DestroyAssociationAsyncError, "owner record not destroyed" + end + + association_model.where(association_primary_key_column => association_ids).find_each do |r| + r.destroy + end + end + + private + def owner_destroyed?(owner, ensuring_owner_was_method) + !owner || (ensuring_owner_was_method && owner.public_send(ensuring_owner_was_method)) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/disable_joins_association_relation.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/disable_joins_association_relation.rb new file mode 100644 index 00000000..c5067034 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/disable_joins_association_relation.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module ActiveRecord + class DisableJoinsAssociationRelation < Relation # :nodoc: + attr_reader :ids, :key + + def initialize(klass, key, ids) + @ids = ids.uniq + @key = key + super(klass) + end + + def limit(value) + records.take(value) + end + + def first(limit = nil) + if limit + records.limit(limit).first + else + records.first + end + end + + def load + super + records = @records + + records_by_id = records.group_by do |record| + record[key] + end + + records = ids.flat_map { |id| records_by_id[id] } + records.compact! + + @records = records + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/dynamic_matchers.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/dynamic_matchers.rb new file mode 100644 index 00000000..f3836b17 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/dynamic_matchers.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +module ActiveRecord + module DynamicMatchers # :nodoc: + private + def respond_to_missing?(name, _) + if self == Base + super + else + match = Method.match(self, name) + match && match.valid? || super + end + end + + def method_missing(name, ...) + match = Method.match(self, name) + + if match && match.valid? + match.define + send(name, ...) + else + super + end + end + + class Method + @matchers = [] + + class << self + attr_reader :matchers + + def match(model, name) + klass = matchers.find { |k| k.pattern.match?(name) } + klass.new(model, name) if klass + end + + def pattern + @pattern ||= /\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/ + end + + def prefix + raise NotImplementedError + end + + def suffix + "" + end + end + + attr_reader :model, :name, :attribute_names + + def initialize(model, method_name) + @model = model + @name = method_name.to_s + @attribute_names = @name.match(self.class.pattern)[1].split("_and_") + @attribute_names.map! { |name| @model.attribute_aliases[name] || name } + end + + def valid? + attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) } + end + + def define + model.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def self.#{name}(#{signature}) + #{body} + end + CODE + end + + private + def body + "#{finder}(#{attributes_hash})" + end + + # The parameters in the signature may have reserved Ruby words, in order + # to prevent errors, we start each param name with `_`. + def signature + attribute_names.map { |name| "_#{name}" }.join(", ") + end + + # Given that the parameters starts with `_`, the finder needs to use the + # same parameter name. + def attributes_hash + "{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(",") + "}" + end + + def finder + raise NotImplementedError + end + end + + class FindBy < Method + Method.matchers << self + + def self.prefix + "find_by" + end + + def finder + "find_by" + end + end + + class FindByBang < Method + Method.matchers << self + + def self.prefix + "find_by" + end + + def self.suffix + "!" + end + + def finder + "find_by!" + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption.rb new file mode 100644 index 00000000..606047e4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module" +require "active_support/core_ext/array" + +module ActiveRecord + module Encryption + extend ActiveSupport::Autoload + + eager_autoload do + autoload :AutoFilteredParameters + autoload :Cipher + autoload :Config + autoload :Configurable + autoload :Context + autoload :Contexts + autoload :DerivedSecretKeyProvider + autoload :EncryptableRecord + autoload :EncryptedAttributeType + autoload :EncryptedFixtures + autoload :EncryptingOnlyEncryptor + autoload :DeterministicKeyProvider + autoload :Encryptor + autoload :EnvelopeEncryptionKeyProvider + autoload :Errors + autoload :ExtendedDeterministicQueries + autoload :ExtendedDeterministicUniquenessValidator + autoload :Key + autoload :KeyGenerator + autoload :KeyProvider + autoload :Message + autoload :MessageSerializer + autoload :NullEncryptor + autoload :Properties + autoload :ReadOnlyNullEncryptor + autoload :Scheme + end + + class Cipher + extend ActiveSupport::Autoload + + eager_autoload do + autoload :Aes256Gcm + end + end + + include Configurable + include Contexts + + def self.eager_load! + super + + Cipher.eager_load! + end + end + + ActiveSupport.run_load_hooks :active_record_encryption, Encryption +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/auto_filtered_parameters.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/auto_filtered_parameters.rb new file mode 100644 index 00000000..313c8f3e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/auto_filtered_parameters.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + class AutoFilteredParameters + def initialize(app) + @app = app + @attributes_by_class = Concurrent::Map.new + @collecting = true + + install_collecting_hook + end + + def enable + apply_collected_attributes + @collecting = false + end + + private + attr_reader :app + + def install_collecting_hook + ActiveRecord::Encryption.on_encrypted_attribute_declared do |klass, attribute| + attribute_was_declared(klass, attribute) + end + end + + def attribute_was_declared(klass, attribute) + if collecting? + collect_for_later(klass, attribute) + else + apply_filter(klass, attribute) + end + end + + def apply_collected_attributes + @attributes_by_class.each do |klass, attributes| + attributes.each do |attribute| + apply_filter(klass, attribute) + end + end + end + + def collecting? + @collecting + end + + def collect_for_later(klass, attribute) + @attributes_by_class[klass] ||= Concurrent::Array.new + @attributes_by_class[klass] << attribute + end + + def apply_filter(klass, attribute) + filter = [("#{klass.model_name.element}" if klass.name), attribute.to_s].compact.join(".") + unless excluded_from_filter_parameters?(filter) + app.config.filter_parameters << filter unless app.config.filter_parameters.include?(filter) + klass.filter_attributes += [ attribute ] + end + end + + def excluded_from_filter_parameters?(filter_parameter) + ActiveRecord::Encryption.config.excluded_from_filter_parameters.find { |excluded_filter| excluded_filter.to_s == filter_parameter } + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/cipher.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/cipher.rb new file mode 100644 index 00000000..c4ab197b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/cipher.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + # The algorithm used for encrypting and decrypting +Message+ objects. + # + # It uses AES-256-GCM. It will generate a random IV for non deterministic encryption (default) + # or derive an initialization vector from the encrypted content for deterministic encryption. + # + # See +Cipher::Aes256Gcm+. + class Cipher + DEFAULT_ENCODING = Encoding::UTF_8 + + # Encrypts the provided text and return an encrypted +Message+. + def encrypt(clean_text, key:, deterministic: false) + cipher_for(key, deterministic: deterministic).encrypt(clean_text).tap do |message| + message.headers.encoding = clean_text.encoding.name unless clean_text.encoding == DEFAULT_ENCODING + end + end + + # Decrypt the provided +Message+. + # + # When +key+ is an Array, it will try all the keys raising a + # +ActiveRecord::Encryption::Errors::Decryption+ if none works. + def decrypt(encrypted_message, key:) + try_to_decrypt_with_each(encrypted_message, keys: Array(key)).tap do |decrypted_text| + decrypted_text.force_encoding(encrypted_message.headers.encoding || DEFAULT_ENCODING) + end + end + + def key_length + Aes256Gcm.key_length + end + + def iv_length + Aes256Gcm.iv_length + end + + private + def try_to_decrypt_with_each(encrypted_text, keys:) + keys.each.with_index do |key, index| + return cipher_for(key).decrypt(encrypted_text) + rescue ActiveRecord::Encryption::Errors::Decryption + raise if index == keys.length - 1 + end + end + + def cipher_for(secret, deterministic: false) + Aes256Gcm.new(secret, deterministic: deterministic) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/cipher/aes256_gcm.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/cipher/aes256_gcm.rb new file mode 100644 index 00000000..611fe510 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/cipher/aes256_gcm.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require "openssl" + +module ActiveRecord + module Encryption + class Cipher + # A 256-GCM cipher. + # + # By default it will use random initialization vectors. For deterministic encryption, it will use a SHA-256 hash of + # the text to encrypt and the secret. + # + # See +Encryptor+ + class Aes256Gcm + CIPHER_TYPE = "aes-256-gcm" + + class << self + def key_length + OpenSSL::Cipher.new(CIPHER_TYPE).key_len + end + + def iv_length + OpenSSL::Cipher.new(CIPHER_TYPE).iv_len + end + end + + # When iv not provided, it will generate a random iv on each encryption operation (default and + # recommended operation) + def initialize(secret, deterministic: false) + @secret = secret + @deterministic = deterministic + end + + def encrypt(clear_text) + # This code is extracted from +ActiveSupport::MessageEncryptor+. Not using it directly because we want to control + # the message format and only serialize things once at the +ActiveRecord::Encryption::Message+ level. Also, this + # cipher is prepared to deal with deterministic/non deterministic encryption modes. + + cipher = OpenSSL::Cipher.new(CIPHER_TYPE) + cipher.encrypt + cipher.key = @secret + + iv = generate_iv(cipher, clear_text) + cipher.iv = iv + + encrypted_data = clear_text.empty? ? clear_text.dup : cipher.update(clear_text) + encrypted_data << cipher.final + + ActiveRecord::Encryption::Message.new(payload: encrypted_data).tap do |message| + message.headers.iv = iv + message.headers.auth_tag = cipher.auth_tag + end + end + + def decrypt(encrypted_message) + encrypted_data = encrypted_message.payload + iv = encrypted_message.headers.iv + auth_tag = encrypted_message.headers.auth_tag + + # Currently the OpenSSL bindings do not raise an error if auth_tag is + # truncated, which would allow an attacker to easily forge it. See + # https://github.com/ruby/openssl/issues/63 + raise ActiveRecord::Encryption::Errors::EncryptedContentIntegrity if auth_tag.nil? || auth_tag.bytes.length != 16 + + cipher = OpenSSL::Cipher.new(CIPHER_TYPE) + + cipher.decrypt + cipher.key = @secret + cipher.iv = iv + + cipher.auth_tag = auth_tag + cipher.auth_data = "" + + decrypted_data = encrypted_data.empty? ? encrypted_data : cipher.update(encrypted_data) + decrypted_data << cipher.final + + decrypted_data + rescue OpenSSL::Cipher::CipherError, TypeError, ArgumentError + raise ActiveRecord::Encryption::Errors::Decryption + end + + def inspect # :nodoc: + "#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>" + end + + private + def generate_iv(cipher, clear_text) + if @deterministic + generate_deterministic_iv(clear_text) + else + cipher.random_iv + end + end + + def generate_deterministic_iv(clear_text) + OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, @secret, clear_text)[0, ActiveRecord::Encryption.cipher.iv_length] + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/config.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/config.rb new file mode 100644 index 00000000..7bbf023f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/config.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require "openssl" + +module ActiveRecord + module Encryption + # Container of configuration options + class Config + attr_accessor :primary_key, :deterministic_key, :store_key_references, :key_derivation_salt, :hash_digest_class, + :support_unencrypted_data, :encrypt_fixtures, :validate_column_size, :add_to_filter_parameters, + :excluded_from_filter_parameters, :extend_queries, :previous_schemes, :forced_encoding_for_deterministic_encryption, + :compressor + + def initialize + set_defaults + end + + # Configure previous encryption schemes. + # + # config.active_record.encryption.previous = [ { key_provider: MyOldKeyProvider.new } ] + def previous=(previous_schemes_properties) + previous_schemes_properties.each do |properties| + add_previous_scheme(**properties) + end + end + + def support_sha1_for_non_deterministic_encryption=(value) + if value && has_primary_key? + sha1_key_generator = ActiveRecord::Encryption::KeyGenerator.new(hash_digest_class: OpenSSL::Digest::SHA1) + sha1_key_provider = ActiveRecord::Encryption::DerivedSecretKeyProvider.new(primary_key, key_generator: sha1_key_generator) + add_previous_scheme key_provider: sha1_key_provider + end + end + + %w(key_derivation_salt primary_key deterministic_key).each do |key| + silence_redefinition_of_method "has_#{key}?" + define_method("has_#{key}?") do + instance_variable_get(:"@#{key}").presence + end + + silence_redefinition_of_method key + define_method(key) do + public_send("has_#{key}?") or + raise Errors::Configuration, "Missing Active Record encryption credential: active_record_encryption.#{key}" + end + end + + private + def set_defaults + self.store_key_references = false + self.support_unencrypted_data = false + self.encrypt_fixtures = false + self.validate_column_size = true + self.add_to_filter_parameters = true + self.excluded_from_filter_parameters = [] + self.previous_schemes = [] + self.forced_encoding_for_deterministic_encryption = Encoding::UTF_8 + self.hash_digest_class = OpenSSL::Digest::SHA1 + self.compressor = Zlib + + # TODO: Setting to false for now as the implementation is a bit experimental + self.extend_queries = false + end + + def add_previous_scheme(**properties) + previous_schemes << ActiveRecord::Encryption::Scheme.new(**properties) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/configurable.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/configurable.rb new file mode 100644 index 00000000..472b1249 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/configurable.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + # Configuration API for ActiveRecord::Encryption + module Configurable + extend ActiveSupport::Concern + + included do + mattr_reader :config, default: Config.new + mattr_accessor :encrypted_attribute_declaration_listeners + end + + class_methods do + # Expose getters for context properties + Context::PROPERTIES.each do |name| + delegate name, to: :context + end + + def configure(primary_key: nil, deterministic_key: nil, key_derivation_salt: nil, **properties) # :nodoc: + config.primary_key = primary_key + config.deterministic_key = deterministic_key + config.key_derivation_salt = key_derivation_salt + + # Set the default for this property here instead of in +Config#set_defaults+ as this needs + # to happen *after* the keys have been set. + properties[:support_sha1_for_non_deterministic_encryption] = true if properties[:support_sha1_for_non_deterministic_encryption].nil? + + properties.each do |name, value| + ActiveRecord::Encryption.config.send "#{name}=", value if ActiveRecord::Encryption.config.respond_to?("#{name}=") + end + + ActiveRecord::Encryption.reset_default_context + + properties.each do |name, value| + ActiveRecord::Encryption.context.send "#{name}=", value if ActiveRecord::Encryption.context.respond_to?("#{name}=") + end + end + + # Register callback to be invoked when an encrypted attribute is declared. + # + # === Example + # + # ActiveRecord::Encryption.on_encrypted_attribute_declared do |klass, attribute_name| + # ... + # end + def on_encrypted_attribute_declared(&block) + self.encrypted_attribute_declaration_listeners ||= Concurrent::Array.new + self.encrypted_attribute_declaration_listeners << block + end + + def encrypted_attribute_was_declared(klass, name) # :nodoc: + self.encrypted_attribute_declaration_listeners&.each do |block| + block.call(klass, name) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/context.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/context.rb new file mode 100644 index 00000000..4c57f4e1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/context.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + # An encryption context configures the different entities used to perform encryption: + # + # * A key provider + # * A key generator + # * An encryptor, the facade to encrypt data + # * A cipher, the encryption algorithm + # * A message serializer + class Context + PROPERTIES = %i[ key_provider key_generator cipher message_serializer encryptor frozen_encryption ] + + attr_accessor(*PROPERTIES) + + def initialize + set_defaults + end + + alias frozen_encryption? frozen_encryption + + silence_redefinition_of_method :key_provider + def key_provider + @key_provider ||= build_default_key_provider + end + + private + def set_defaults + self.frozen_encryption = false + self.key_generator = ActiveRecord::Encryption::KeyGenerator.new + self.cipher = ActiveRecord::Encryption::Cipher.new + self.encryptor = ActiveRecord::Encryption::Encryptor.new + self.message_serializer = ActiveRecord::Encryption::MessageSerializer.new + end + + def build_default_key_provider + ActiveRecord::Encryption::DerivedSecretKeyProvider.new(ActiveRecord::Encryption.config.primary_key) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/contexts.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/contexts.rb new file mode 100644 index 00000000..a55a1d40 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/contexts.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + # ActiveRecord::Encryption uses encryption contexts to configure the different entities used to + # encrypt/decrypt at a given moment in time. + # + # By default, the library uses a default encryption context. This is the Context that gets configured + # initially via +config.active_record.encryption+ options. Library users can define nested encryption contexts + # when running blocks of code. + # + # See Context. + module Contexts + extend ActiveSupport::Concern + + included do + mattr_accessor :default_context, default: Context.new + thread_mattr_accessor :custom_contexts + end + + class_methods do + # Configures a custom encryption context to use when running the provided block of code. + # + # It supports overriding all the properties defined in +Context+. + # + # Example: + # + # ActiveRecord::Encryption.with_encryption_context(encryptor: ActiveRecord::Encryption::NullEncryptor.new) do + # ... + # end + # + # Encryption contexts can be nested. + def with_encryption_context(properties) + self.custom_contexts ||= [] + self.custom_contexts << default_context.dup + properties.each do |key, value| + self.current_custom_context.send("#{key}=", value) + end + + yield + ensure + self.custom_contexts.pop + end + + # Runs the provided block in an encryption context where encryption is disabled: + # + # * Reading encrypted content will return its ciphertexts. + # * Writing encrypted content will write its clear text. + def without_encryption(&block) + with_encryption_context encryptor: ActiveRecord::Encryption::NullEncryptor.new, &block + end + + # Runs the provided block in an encryption context where: + # + # * Reading encrypted content will return its ciphertext. + # * Writing encrypted content will fail. + def protecting_encrypted_data(&block) + with_encryption_context encryptor: ActiveRecord::Encryption::EncryptingOnlyEncryptor.new, frozen_encryption: true, &block + end + + # Returns the current context. By default it will return the current context. + def context + self.current_custom_context || self.default_context + end + + def current_custom_context + self.custom_contexts&.last + end + + def reset_default_context + self.default_context = Context.new + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/derived_secret_key_provider.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/derived_secret_key_provider.rb new file mode 100644 index 00000000..1582d409 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/derived_secret_key_provider.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + # A KeyProvider that derives keys from passwords. + class DerivedSecretKeyProvider < KeyProvider + def initialize(passwords, key_generator: ActiveRecord::Encryption.key_generator) + super(Array(passwords).collect { |password| derive_key_from(password, using: key_generator) }) + end + + private + def derive_key_from(password, using: key_generator) + secret = using.derive_key_from(password) + ActiveRecord::Encryption::Key.new(secret) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/deterministic_key_provider.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/deterministic_key_provider.rb new file mode 100644 index 00000000..70eb1fea --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/deterministic_key_provider.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + # A KeyProvider that derives keys from passwords. + class DeterministicKeyProvider < DerivedSecretKeyProvider + def initialize(password) + passwords = Array(password) + raise ActiveRecord::Encryption::Errors::Configuration, "Deterministic encryption keys can't be rotated" if passwords.length > 1 + super(passwords) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/encryptable_record.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/encryptable_record.rb new file mode 100644 index 00000000..9d80b09b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/encryptable_record.rb @@ -0,0 +1,230 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + # This is the concern mixed in Active Record models to make them encryptable. It adds the +encrypts+ + # attribute declaration, as well as the API to encrypt and decrypt records. + module EncryptableRecord + extend ActiveSupport::Concern + + included do + class_attribute :encrypted_attributes + + validate :cant_modify_encrypted_attributes_when_frozen, if: -> { has_encrypted_attributes? && ActiveRecord::Encryption.context.frozen_encryption? } + end + + class_methods do + # Encrypts the +name+ attribute. + # + # === Options + # + # * :key_provider - A key provider to provide encryption and decryption keys. Defaults to + # +ActiveRecord::Encryption.key_provider+. + # * :key - A password to derive the key from. It's a shorthand for a +:key_provider+ that + # serves derivated keys. Both options can't be used at the same time. + # * :deterministic - By default, encryption is not deterministic. It will use a random + # initialization vector for each encryption operation. This means that encrypting the same content + # with the same key twice will generate different ciphertexts. When set to +true+, it will generate the + # initialization vector based on the encrypted content. This means that the same content will generate + # the same ciphertexts. This enables querying encrypted text with Active Record. Deterministic encryption + # will use the oldest encryption scheme to encrypt new data by default. You can change this by setting + # deterministic: { fixed: false }. That will make it use the newest encryption scheme for encrypting new + # data. + # * :support_unencrypted_data - If `config.active_record.encryption.support_unencrypted_data` is +true+, + # you can set this to +false+ to opt out of unencrypted data support for this attribute. This is useful for + # scenarios where you encrypt one column, and want to disable support for unencrypted data without having to tweak + # the global setting. + # * :downcase - When true, it converts the encrypted content to downcase automatically. This allows to + # effectively ignore case when querying data. Notice that the case is lost. Use +:ignore_case+ if you are interested + # in preserving it. + # * :ignore_case - When true, it behaves like +:downcase+ but, it also preserves the original case in a specially + # designated column +original_+. When reading the encrypted content, the version with the original case is + # served. But you can still execute queries that will ignore the case. This option can only be used when +:deterministic+ + # is true. + # * :context_properties - Additional properties that will override +Context+ settings when this attribute is + # encrypted and decrypted. E.g: +encryptor:+, +cipher:+, +message_serializer:+, etc. + # * :previous - List of previous encryption schemes. When provided, they will be used in order when trying to read + # the attribute. Each entry of the list can contain the properties supported by #encrypts. Also, when deterministic + # encryption is used, they will be used to generate additional ciphertexts to check in the queries. + def encrypts(*names, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], compress: true, compressor: nil, **context_properties) + self.encrypted_attributes ||= Set.new # not using :default because the instance would be shared across classes + + names.each do |name| + encrypt_attribute name, key_provider: key_provider, key: key, deterministic: deterministic, support_unencrypted_data: support_unencrypted_data, downcase: downcase, ignore_case: ignore_case, previous: previous, compress: compress, compressor: compressor, **context_properties + end + end + + # Returns the list of deterministic encryptable attributes in the model class. + def deterministic_encrypted_attributes + @deterministic_encrypted_attributes ||= encrypted_attributes&.find_all do |attribute_name| + type_for_attribute(attribute_name).deterministic? + end + end + + # Given a attribute name, it returns the name of the source attribute when it's a preserved one. + def source_attribute_from_preserved_attribute(attribute_name) + attribute_name.to_s.sub(ORIGINAL_ATTRIBUTE_PREFIX, "") if attribute_name.start_with?(ORIGINAL_ATTRIBUTE_PREFIX) + end + + private + def scheme_for(key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties) + ActiveRecord::Encryption::Scheme.new(key_provider: key_provider, key: key, deterministic: deterministic, + support_unencrypted_data: support_unencrypted_data, downcase: downcase, ignore_case: ignore_case, **context_properties).tap do |scheme| + scheme.previous_schemes = global_previous_schemes_for(scheme) + + Array.wrap(previous).collect { |scheme_config| ActiveRecord::Encryption::Scheme.new(**scheme_config) } + end + end + + def global_previous_schemes_for(scheme) + ActiveRecord::Encryption.config.previous_schemes.filter_map do |previous_scheme| + scheme.merge(previous_scheme) if scheme.compatible_with?(previous_scheme) + end + end + + def encrypt_attribute(name, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], compress: true, compressor: nil, **context_properties) + encrypted_attributes << name.to_sym + + decorate_attributes([name]) do |name, cast_type| + scheme = scheme_for key_provider: key_provider, key: key, deterministic: deterministic, support_unencrypted_data: support_unencrypted_data, \ + downcase: downcase, ignore_case: ignore_case, previous: previous, compress: compress, compressor: compressor, **context_properties + + ActiveRecord::Encryption::EncryptedAttributeType.new(scheme: scheme, cast_type: cast_type, default: columns_hash[name.to_s]&.default) + end + + preserve_original_encrypted(name) if ignore_case + ActiveRecord::Encryption.encrypted_attribute_was_declared(self, name) + end + + def preserve_original_encrypted(name) + original_attribute_name = "#{ORIGINAL_ATTRIBUTE_PREFIX}#{name}".to_sym + + if !ActiveRecord::Encryption.config.support_unencrypted_data && !column_names.include?(original_attribute_name.to_s) + raise Errors::Configuration, "To use :ignore_case for '#{name}' you must create an additional column named '#{original_attribute_name}'" + end + + encrypts original_attribute_name + override_accessors_to_preserve_original name, original_attribute_name + end + + def override_accessors_to_preserve_original(name, original_attribute_name) + include(Module.new do + define_method name do + if ((value = super()) && encrypted_attribute?(name)) || !ActiveRecord::Encryption.config.support_unencrypted_data + send(original_attribute_name) + else + value + end + end + + define_method "#{name}=" do |value| + self.send "#{original_attribute_name}=", value + super(value) + end + end) + end + + def load_schema! # :nodoc: + super + + add_length_validation_for_encrypted_columns if ActiveRecord::Encryption.config.validate_column_size + end + + def add_length_validation_for_encrypted_columns + encrypted_attributes&.each do |attribute_name| + validate_column_size attribute_name + end + end + + def validate_column_size(attribute_name) + if limit = columns_hash[attribute_name.to_s]&.limit + validates_length_of attribute_name, maximum: limit + end + end + end + + # Returns whether a given attribute is encrypted or not. + def encrypted_attribute?(attribute_name) + name = attribute_name.to_s + name = self.class.attribute_aliases[name] || name + + return false unless self.class.encrypted_attributes&.include? name.to_sym + + type = type_for_attribute(name) + type.encrypted? read_attribute_before_type_cast(name) + end + + # Returns the ciphertext for +attribute_name+. + def ciphertext_for(attribute_name) + if encrypted_attribute?(attribute_name) + read_attribute_before_type_cast(attribute_name) + else + read_attribute_for_database(attribute_name) + end + end + + # Encrypts all the encryptable attributes and saves the changes. + def encrypt + encrypt_attributes if has_encrypted_attributes? + end + + # Decrypts all the encryptable attributes and saves the changes. + def decrypt + decrypt_attributes if has_encrypted_attributes? + end + + private + ORIGINAL_ATTRIBUTE_PREFIX = "original_" + + def _create_record(attribute_names = self.attribute_names) + if has_encrypted_attributes? + # Always persist encrypted attributes, because an attribute might be + # encrypting a column default value. + attribute_names |= self.class.encrypted_attributes.map(&:to_s) + end + super + end + + def encrypt_attributes + validate_encryption_allowed + + update_columns build_encrypt_attribute_assignments + end + + def decrypt_attributes + validate_encryption_allowed + + decrypt_attribute_assignments = build_decrypt_attribute_assignments + ActiveRecord::Encryption.without_encryption { update_columns decrypt_attribute_assignments } + end + + def validate_encryption_allowed + raise ActiveRecord::Encryption::Errors::Configuration, "can't be modified because it is encrypted" if ActiveRecord::Encryption.context.frozen_encryption? + end + + def has_encrypted_attributes? + self.class.encrypted_attributes.present? + end + + def build_encrypt_attribute_assignments + Array(self.class.encrypted_attributes).index_with do |attribute_name| + self[attribute_name] + end + end + + def build_decrypt_attribute_assignments + Array(self.class.encrypted_attributes).to_h do |attribute_name| + type = type_for_attribute(attribute_name) + encrypted_value = ciphertext_for(attribute_name) + new_value = type.deserialize(encrypted_value) + [attribute_name, new_value] + end + end + + def cant_modify_encrypted_attributes_when_frozen + self.class.encrypted_attributes.each do |attribute| + errors.add(attribute.to_sym, "can't be modified because it is encrypted") if changed_attributes.include?(attribute) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/encrypted_attribute_type.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/encrypted_attribute_type.rb new file mode 100644 index 00000000..14c32410 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/encrypted_attribute_type.rb @@ -0,0 +1,184 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + # An ActiveModel::Type::Value that encrypts/decrypts strings of text. + # + # This is the central piece that connects the encryption system with +encrypts+ declarations in the + # model classes. Whenever you declare an attribute as encrypted, it configures an +EncryptedAttributeType+ + # for that attribute. + class EncryptedAttributeType < ::ActiveModel::Type::Value + include ActiveModel::Type::Helpers::Mutable + + attr_reader :scheme, :cast_type + + delegate :key_provider, :downcase?, :deterministic?, :previous_schemes, :with_context, :fixed?, to: :scheme + delegate :accessor, :type, to: :cast_type + + # === Options + # + # * :scheme - A +Scheme+ with the encryption properties for this attribute. + # * :cast_type - A type that will be used to serialize (before encrypting) and deserialize + # (after decrypting). ActiveModel::Type::String by default. + def initialize(scheme:, cast_type: ActiveModel::Type::String.new, previous_type: false, default: nil) + super() + @scheme = scheme + @cast_type = cast_type + @previous_type = previous_type + @default = default + end + + def cast(value) + cast_type.cast(value) + end + + def deserialize(value) + cast_type.deserialize decrypt(value) + end + + def serialize(value) + if serialize_with_oldest? + serialize_with_oldest(value) + else + serialize_with_current(value) + end + end + + def encrypted?(value) + with_context { encryptor.encrypted? value } + end + + def changed_in_place?(raw_old_value, new_value) + old_value = raw_old_value.nil? ? nil : deserialize(raw_old_value) + old_value != new_value + end + + def previous_types # :nodoc: + @previous_types ||= {} # Memoizing on support_unencrypted_data so that we can tweak it during tests + @previous_types[support_unencrypted_data?] ||= build_previous_types_for(previous_schemes_including_clean_text) + end + + def support_unencrypted_data? + ActiveRecord::Encryption.config.support_unencrypted_data && scheme.support_unencrypted_data? && !previous_type? + end + + private + def previous_schemes_including_clean_text + previous_schemes.including((clean_text_scheme if support_unencrypted_data?)).compact + end + + def previous_types_without_clean_text + @previous_types_without_clean_text ||= build_previous_types_for(previous_schemes) + end + + def build_previous_types_for(schemes) + schemes.collect do |scheme| + EncryptedAttributeType.new(scheme: scheme, previous_type: true) + end + end + + def previous_type? + @previous_type + end + + def decrypt_as_text(value) + with_context do + unless value.nil? + if @default && @default == value + value + else + encryptor.decrypt(value, **decryption_options) + end + end + end + rescue ActiveRecord::Encryption::Errors::Base => error + if previous_types_without_clean_text.blank? + handle_deserialize_error(error, value) + else + try_to_deserialize_with_previous_encrypted_types(value) + end + end + + def decrypt(value) + text_to_database_type decrypt_as_text(database_type_to_text(value)) + end + + def try_to_deserialize_with_previous_encrypted_types(value) + previous_types.each.with_index do |type, index| + break type.deserialize(value) + rescue ActiveRecord::Encryption::Errors::Base => error + handle_deserialize_error(error, value) if index == previous_types.length - 1 + end + end + + def handle_deserialize_error(error, value) + if error.is_a?(Errors::Decryption) && support_unencrypted_data? + value + else + raise error + end + end + + def serialize_with_oldest? + @serialize_with_oldest ||= fixed? && previous_types_without_clean_text.present? + end + + def serialize_with_oldest(value) + previous_types.first.serialize(value) + end + + def serialize_with_current(value) + casted_value = cast_type.serialize(value) + casted_value = casted_value&.downcase if downcase? + encrypt(casted_value.to_s) unless casted_value.nil? + end + + def encrypt_as_text(value) + with_context do + if encryptor.binary? && !cast_type.binary? + raise Errors::Encoding, "Binary encoded data can only be stored in binary columns" + end + + encryptor.encrypt(value, **encryption_options) + end + end + + def encrypt(value) + text_to_database_type encrypt_as_text(value) + end + + def encryptor + ActiveRecord::Encryption.encryptor + end + + def encryption_options + { key_provider: key_provider, cipher_options: { deterministic: deterministic? } }.compact + end + + def decryption_options + { key_provider: key_provider }.compact + end + + def clean_text_scheme + @clean_text_scheme ||= ActiveRecord::Encryption::Scheme.new(downcase: downcase?, encryptor: ActiveRecord::Encryption::NullEncryptor.new) + end + + def text_to_database_type(value) + if value && cast_type.binary? + ActiveModel::Type::Binary::Data.new(value) + else + value + end + end + + def database_type_to_text(value) + if value && cast_type.binary? + binary_cast_type = cast_type.serialized? ? cast_type.subtype : cast_type + binary_cast_type.deserialize(value) + else + value + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/encrypted_fixtures.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/encrypted_fixtures.rb new file mode 100644 index 00000000..a64dd427 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/encrypted_fixtures.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + module EncryptedFixtures + def initialize(fixture, model_class) + @clean_values = {} + encrypt_fixture_data(fixture, model_class) + process_preserved_original_columns(fixture, model_class) + super + end + + private + def encrypt_fixture_data(fixture, model_class) + model_class&.encrypted_attributes&.each do |attribute_name| + if clean_value = fixture[attribute_name.to_s] + @clean_values[attribute_name.to_s] = clean_value + + type = model_class.type_for_attribute(attribute_name) + encrypted_value = type.serialize(clean_value) + fixture[attribute_name.to_s] = encrypted_value + end + end + end + + def process_preserved_original_columns(fixture, model_class) + model_class&.encrypted_attributes&.each do |attribute_name| + if source_attribute_name = model_class.source_attribute_from_preserved_attribute(attribute_name) + clean_value = @clean_values[source_attribute_name.to_s] + type = model_class.type_for_attribute(attribute_name) + encrypted_value = type.serialize(clean_value) + fixture[attribute_name.to_s] = encrypted_value + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/encrypting_only_encryptor.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/encrypting_only_encryptor.rb new file mode 100644 index 00000000..dc0c53a4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/encrypting_only_encryptor.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + # An encryptor that can encrypt data but can't decrypt it. + class EncryptingOnlyEncryptor < Encryptor + def decrypt(encrypted_text, key_provider: nil, cipher_options: {}) + encrypted_text + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/encryptor.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/encryptor.rb new file mode 100644 index 00000000..0426f941 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/encryptor.rb @@ -0,0 +1,177 @@ +# frozen_string_literal: true + +require "openssl" +require "active_support/core_ext/numeric" + +module ActiveRecord + module Encryption + # An encryptor exposes the encryption API that ActiveRecord::Encryption::EncryptedAttributeType + # uses for encrypting and decrypting attribute values. + # + # It interacts with a KeyProvider for getting the keys, and delegate to + # ActiveRecord::Encryption::Cipher the actual encryption algorithm. + class Encryptor + # The compressor to use for compressing the payload + attr_reader :compressor + + # === Options + # + # * :compress - Boolean indicating whether records should be compressed before encryption. + # Defaults to +true+. + # * :compressor - The compressor to use. + # 1. If compressor is provided, it will be used. + # 2. If not, it will use ActiveRecord::Encryption.config.compressor which default value is +Zlib+. + # If you want to use a custom compressor, it must respond to +deflate+ and +inflate+. + def initialize(compress: true, compressor: nil) + @compress = compress + @compressor = compressor || ActiveRecord::Encryption.config.compressor + end + + # Encrypts +clean_text+ and returns the encrypted result + # + # Internally, it will: + # + # 1. Create a new ActiveRecord::Encryption::Message + # 2. Compress and encrypt +clean_text+ as the message payload + # 3. Serialize it with +ActiveRecord::Encryption.message_serializer+ (+ActiveRecord::Encryption::SafeMarshal+ + # by default) + # 4. Encode the result with Base 64 + # + # === Options + # + # [:key_provider] + # Key provider to use for the encryption operation. It will default to + # +ActiveRecord::Encryption.key_provider+ when not provided. + # + # [:cipher_options] + # Cipher-specific options that will be passed to the Cipher configured in + # +ActiveRecord::Encryption.cipher+ + def encrypt(clear_text, key_provider: default_key_provider, cipher_options: {}) + clear_text = force_encoding_if_needed(clear_text) if cipher_options[:deterministic] + + validate_payload_type(clear_text) + serialize_message build_encrypted_message(clear_text, key_provider: key_provider, cipher_options: cipher_options) + end + + # Decrypts an +encrypted_text+ and returns the result as clean text + # + # === Options + # + # [:key_provider] + # Key provider to use for the encryption operation. It will default to + # +ActiveRecord::Encryption.key_provider+ when not provided + # + # [:cipher_options] + # Cipher-specific options that will be passed to the Cipher configured in + # +ActiveRecord::Encryption.cipher+ + def decrypt(encrypted_text, key_provider: default_key_provider, cipher_options: {}) + message = deserialize_message(encrypted_text) + keys = key_provider.decryption_keys(message) + raise Errors::Decryption unless keys.present? + uncompress_if_needed(cipher.decrypt(message, key: keys.collect(&:secret), **cipher_options), message.headers.compressed) + rescue *(ENCODING_ERRORS + DECRYPT_ERRORS) + raise Errors::Decryption + end + + # Returns whether the text is encrypted or not + def encrypted?(text) + deserialize_message(text) + true + rescue Errors::Encoding, *DECRYPT_ERRORS + false + end + + def binary? + serializer.binary? + end + + def compress? # :nodoc: + @compress + end + + private + DECRYPT_ERRORS = [OpenSSL::Cipher::CipherError, Errors::EncryptedContentIntegrity, Errors::Decryption] + ENCODING_ERRORS = [EncodingError, Errors::Encoding] + THRESHOLD_TO_JUSTIFY_COMPRESSION = 140.bytes + + def default_key_provider + ActiveRecord::Encryption.key_provider + end + + def validate_payload_type(clear_text) + unless clear_text.is_a?(String) + raise ActiveRecord::Encryption::Errors::ForbiddenClass, "The encryptor can only encrypt string values (#{clear_text.class})" + end + end + + def cipher + ActiveRecord::Encryption.cipher + end + + def build_encrypted_message(clear_text, key_provider:, cipher_options:) + key = key_provider.encryption_key + + clear_text, was_compressed = compress_if_worth_it(clear_text) + cipher.encrypt(clear_text, key: key.secret, **cipher_options).tap do |message| + message.headers.add(key.public_tags) + message.headers.compressed = true if was_compressed + end + end + + def serialize_message(message) + serializer.dump(message) + end + + def deserialize_message(message) + serializer.load message + rescue ArgumentError, TypeError, Errors::ForbiddenClass + raise Errors::Encoding + end + + def serializer + ActiveRecord::Encryption.message_serializer + end + + # Under certain threshold, ZIP compression is actually worse that not compressing + def compress_if_worth_it(string) + if compress? && string.bytesize > THRESHOLD_TO_JUSTIFY_COMPRESSION + [compress(string), true] + else + [string, false] + end + end + + def compress(data) + @compressor.deflate(data).tap do |compressed_data| + compressed_data.force_encoding(data.encoding) + end + end + + def uncompress_if_needed(data, compressed) + if compressed + uncompress(data) + else + data + end + end + + def uncompress(data) + @compressor.inflate(data).tap do |uncompressed_data| + uncompressed_data.force_encoding(data.encoding) + end + end + + def force_encoding_if_needed(value) + if forced_encoding_for_deterministic_encryption && value && value.encoding != forced_encoding_for_deterministic_encryption + value.encode(forced_encoding_for_deterministic_encryption, invalid: :replace, undef: :replace) + else + value + end + end + + def forced_encoding_for_deterministic_encryption + ActiveRecord::Encryption.config.forced_encoding_for_deterministic_encryption + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/envelope_encryption_key_provider.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/envelope_encryption_key_provider.rb new file mode 100644 index 00000000..0f67aa10 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/envelope_encryption_key_provider.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + # Implements a simple envelope encryption approach where: + # + # * It generates a random data-encryption key for each encryption operation. + # * It stores the generated key along with the encrypted payload. It encrypts this key + # with the master key provided in the +active_record_encryption.primary_key+ credential. + # + # This provider can work with multiple master keys. It will use the last one for encrypting. + # + # When +config.active_record.encryption.store_key_references+ is true, it will also store a reference to + # the specific master key that was used to encrypt the data-encryption key. When not set, + # it will try all the configured master keys looking for the right one, in order to + # return the right decryption key. + class EnvelopeEncryptionKeyProvider + def encryption_key + random_secret = generate_random_secret + ActiveRecord::Encryption::Key.new(random_secret).tap do |key| + key.public_tags.encrypted_data_key = encrypt_data_key(random_secret) + key.public_tags.encrypted_data_key_id = active_primary_key.id if ActiveRecord::Encryption.config.store_key_references + end + end + + def decryption_keys(encrypted_message) + secret = decrypt_data_key(encrypted_message) + secret ? [ActiveRecord::Encryption::Key.new(secret)] : [] + end + + def active_primary_key + @active_primary_key ||= primary_key_provider.encryption_key + end + + private + def encrypt_data_key(random_secret) + ActiveRecord::Encryption.cipher.encrypt(random_secret, key: active_primary_key.secret) + end + + def decrypt_data_key(encrypted_message) + encrypted_data_key = encrypted_message.headers.encrypted_data_key + key = primary_key_provider.decryption_keys(encrypted_message)&.collect(&:secret) + ActiveRecord::Encryption.cipher.decrypt encrypted_data_key, key: key if key + end + + def primary_key_provider + @primary_key_provider ||= DerivedSecretKeyProvider.new(ActiveRecord::Encryption.config.primary_key) + end + + def generate_random_secret + ActiveRecord::Encryption.key_generator.generate_random_key + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/errors.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/errors.rb new file mode 100644 index 00000000..8bd44292 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/errors.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + module Errors + class Base < StandardError; end + class Encoding < Base; end + class Decryption < Base; end + class Encryption < Base; end + class Configuration < Base; end + class ForbiddenClass < Base; end + class EncryptedContentIntegrity < Base; end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/extended_deterministic_queries.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/extended_deterministic_queries.rb new file mode 100644 index 00000000..02a55edf --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/extended_deterministic_queries.rb @@ -0,0 +1,159 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + # Automatically expand encrypted arguments to support querying both encrypted and unencrypted data + # + # Active Record \Encryption supports querying the db using deterministic attributes. For example: + # + # Contact.find_by(email_address: "jorge@hey.com") + # + # The value "jorge@hey.com" will get encrypted automatically to perform the query. But there is + # a problem while the data is being encrypted. This won't work. During that time, you need these + # queries to be: + # + # Contact.find_by(email_address: [ "jorge@hey.com", "" ]) + # + # This patches ActiveRecord to support this automatically. It addresses both: + # + # * ActiveRecord::Base - Used in Contact.find_by_email_address(...) + # * ActiveRecord::Relation - Used in Contact.internal.find_by_email_address(...) + # + # This module is included if `config.active_record.encryption.extend_queries` is `true`. + module ExtendedDeterministicQueries + def self.install_support + # ActiveRecord::Base relies on ActiveRecord::Relation (ActiveRecord::QueryMethods) but it does + # some prepared statements caching. That's why we need to intercept +ActiveRecord::Base+ as soon + # as it's invoked (so that the proper prepared statement is cached). + ActiveRecord::Relation.prepend(RelationQueries) + ActiveRecord::Base.include(CoreQueries) + ActiveRecord::Encryption::EncryptedAttributeType.prepend(ExtendedEncryptableType) + end + + # When modifying this file run performance tests in + # +activerecord/test/cases/encryption/performance/extended_deterministic_queries_performance_test.rb+ + # to make sure performance overhead is acceptable. + # + # @TODO We will extend this to support previous "encryption context" versions in future iterations + # @TODO Experimental. Support for every kind of query is pending + # @TODO It should not patch anything if not needed (no previous schemes or no support for previous encryption schemes) + + module EncryptedQuery # :nodoc: + class << self + def process_arguments(owner, args, check_for_additional_values) + owner = owner.model if owner.is_a?(Relation) + + return args if owner.deterministic_encrypted_attributes&.empty? + + if args.is_a?(Array) && (options = args.first).is_a?(Hash) + options = options.transform_keys do |key| + if key.is_a?(Array) + key.map(&:to_s) + else + key.to_s + end + end + args[0] = options + + owner.deterministic_encrypted_attributes&.each do |attribute_name| + attribute_name = attribute_name.to_s + type = owner.type_for_attribute(attribute_name) + if !type.previous_types.empty? && value = options[attribute_name] + options[attribute_name] = process_encrypted_query_argument(value, check_for_additional_values, type) + end + end + end + + args + end + + private + def process_encrypted_query_argument(value, check_for_additional_values, type) + return value if check_for_additional_values && value.is_a?(Array) && value.last.is_a?(AdditionalValue) + + case value + when String, Array + list = Array(value) + list + list.flat_map do |each_value| + if check_for_additional_values && each_value.is_a?(AdditionalValue) + each_value + else + additional_values_for(each_value, type) + end + end + else + value + end + end + + def additional_values_for(value, type) + type.previous_types.collect do |additional_type| + AdditionalValue.new(value, additional_type) + end + end + end + end + + module RelationQueries + def where(*args) + super(*EncryptedQuery.process_arguments(self, args, true)) + end + + def exists?(*args) + super(*EncryptedQuery.process_arguments(self, args, true)) + end + + def scope_for_create + return super unless model.deterministic_encrypted_attributes&.any? + + scope_attributes = super + wheres = where_values_hash + + model.deterministic_encrypted_attributes.each do |attribute_name| + attribute_name = attribute_name.to_s + values = wheres[attribute_name] + if values.is_a?(Array) && values[1..].all?(AdditionalValue) + scope_attributes[attribute_name] = values.first + end + end + + scope_attributes + end + end + + module CoreQueries + extend ActiveSupport::Concern + + class_methods do + def find_by(*args) + super(*EncryptedQuery.process_arguments(self, args, false)) + end + end + end + + class AdditionalValue + attr_reader :value, :type + + def initialize(value, type) + @type = type + @value = process(value) + end + + private + def process(value) + type.serialize(value) + end + end + + module ExtendedEncryptableType + def serialize(data) + if data.is_a?(AdditionalValue) + data.value + else + super + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb new file mode 100644 index 00000000..68265bd4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + module ExtendedDeterministicUniquenessValidator + def self.install_support + ActiveRecord::Validations::UniquenessValidator.prepend(EncryptedUniquenessValidator) + end + + module EncryptedUniquenessValidator + def validate_each(record, attribute, value) + super(record, attribute, value) + + klass = record.class + if klass.deterministic_encrypted_attributes&.include?(attribute) + encrypted_type = klass.type_for_attribute(attribute) + encrypted_type.previous_types.each do |type| + encrypted_value = type.serialize(value) + ActiveRecord::Encryption.without_encryption do + super(record, attribute, encrypted_value) + end + end + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/key.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/key.rb new file mode 100644 index 00000000..d87b350d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/key.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + # A key is a container for a given +secret+ + # + # Optionally, it can include +public_tags+. These tags are meant to be stored + # in clean (public) and can be used, for example, to include information that + # references the key for a future retrieval operation. + class Key + attr_reader :secret, :public_tags + + def initialize(secret) + @secret = secret + @public_tags = Properties.new + end + + def self.derive_from(password) + secret = ActiveRecord::Encryption.key_generator.derive_key_from(password) + ActiveRecord::Encryption::Key.new(secret) + end + + def id + Digest::SHA1.hexdigest(secret).first(4) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/key_generator.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/key_generator.rb new file mode 100644 index 00000000..6411f48e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/key_generator.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require "securerandom" + +module ActiveRecord + module Encryption + # Utility for generating and deriving random keys. + class KeyGenerator + attr_reader :hash_digest_class + + def initialize(hash_digest_class: ActiveRecord::Encryption.config.hash_digest_class) + @hash_digest_class = hash_digest_class + end + + # Returns a random key. The key will have a size in bytes of +:length+ (configured +Cipher+'s length by default) + def generate_random_key(length: key_length) + SecureRandom.random_bytes(length) + end + + # Returns a random key in hexadecimal format. The key will have a size in bytes of +:length+ (configured +Cipher+'s + # length by default) + # + # Hexadecimal format is handy for representing keys as printable text. To maximize the space of characters used, it is + # good practice including not printable characters. Hexadecimal format ensures that generated keys are representable with + # plain text + # + # To convert back to the original string with the desired length: + # + # [ value ].pack("H*") + def generate_random_hex_key(length: key_length) + generate_random_key(length: length).unpack("H*")[0] + end + + # Derives a key from the given password. The key will have a size in bytes of +:length+ (configured +Cipher+'s length + # by default) + # + # The generated key will be salted with the value of +ActiveRecord::Encryption.key_derivation_salt+ + def derive_key_from(password, length: key_length) + ActiveSupport::KeyGenerator.new(password, hash_digest_class: hash_digest_class) + .generate_key(key_derivation_salt, length) + end + + private + def key_derivation_salt + @key_derivation_salt ||= ActiveRecord::Encryption.config.key_derivation_salt + end + + def key_length + @key_length ||= ActiveRecord::Encryption.cipher.key_length + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/key_provider.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/key_provider.rb new file mode 100644 index 00000000..548c9a41 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/key_provider.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + # A +KeyProvider+ serves keys: + # + # * An encryption key + # * A list of potential decryption keys. Serving multiple decryption keys supports rotation-schemes + # where new keys are added but old keys need to continue working + class KeyProvider + def initialize(keys) + @keys = Array(keys) + end + + # Returns the last key in the list as the active key to perform encryptions + # + # When +ActiveRecord::Encryption.config.store_key_references+ is true, the key will include + # a public tag referencing the key itself. That key will be stored in the public + # headers of the encrypted message + def encryption_key + @encryption_key ||= @keys.last.tap do |key| + key.public_tags.encrypted_data_key_id = key.id if ActiveRecord::Encryption.config.store_key_references + end + + @encryption_key + end + + # Returns the list of decryption keys + # + # When the message holds a reference to its encryption key, it will return an array + # with that key. If not, it will return the list of keys. + def decryption_keys(encrypted_message) + if encrypted_message.headers.encrypted_data_key_id + keys_grouped_by_id[encrypted_message.headers.encrypted_data_key_id] + else + @keys + end + end + + private + def keys_grouped_by_id + @keys_grouped_by_id ||= @keys.group_by(&:id) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/message.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/message.rb new file mode 100644 index 00000000..e7cfe165 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/message.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + # A message defines the structure of the data we store in encrypted attributes. It contains: + # + # * An encrypted payload + # * A list of unencrypted headers + # + # See Encryptor#encrypt + class Message + attr_accessor :payload, :headers + + def initialize(payload: nil, headers: {}) + validate_payload_type(payload) + + @payload = payload + @headers = Properties.new(headers) + end + + def ==(other_message) + payload == other_message.payload && headers == other_message.headers + end + + private + def validate_payload_type(payload) + unless payload.is_a?(String) || payload.nil? + raise ActiveRecord::Encryption::Errors::ForbiddenClass, "Only string payloads allowed" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/message_pack_message_serializer.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/message_pack_message_serializer.rb new file mode 100644 index 00000000..6eab6753 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/message_pack_message_serializer.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require "active_support/message_pack" + +module ActiveRecord + module Encryption + # A message serializer that serializes +Messages+ with MessagePack. + # + # The message is converted to a hash with this structure: + # + # { + # p: , + # h: { + # header1: value1, + # header2: value2, + # ... + # } + # } + # + # Then it is converted to the MessagePack format. + class MessagePackMessageSerializer + def dump(message) + raise Errors::ForbiddenClass unless message.is_a?(Message) + ActiveSupport::MessagePack.dump(message_to_hash(message)) + end + + def load(serialized_content) + data = ActiveSupport::MessagePack.load(serialized_content) + hash_to_message(data, 1) + rescue RuntimeError + raise Errors::Decryption + end + + def binary? + true + end + + private + def message_to_hash(message) + { + "p" => message.payload, + "h" => headers_to_hash(message.headers) + } + end + + def headers_to_hash(headers) + headers.transform_values do |value| + value.is_a?(Message) ? message_to_hash(value) : value + end + end + + def hash_to_message(data, level) + validate_message_data_format(data, level) + Message.new(payload: data["p"], headers: parse_properties(data["h"], level)) + end + + def validate_message_data_format(data, level) + if level > 2 + raise Errors::Decryption, "More than one level of hash nesting in headers is not supported" + end + + unless data.is_a?(Hash) && data.has_key?("p") + raise Errors::Decryption, "Invalid data format: hash without payload" + end + end + + def parse_properties(headers, level) + Properties.new.tap do |properties| + headers&.each do |key, value| + properties[key] = value.is_a?(Hash) ? hash_to_message(value, level + 1) : value + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/message_serializer.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/message_serializer.rb new file mode 100644 index 00000000..7e08242c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/message_serializer.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require "base64" + +module ActiveRecord + module Encryption + # A message serializer that serializes +Messages+ with JSON. + # + # The generated structure is pretty simple: + # + # { + # p: , + # h: { + # header1: value1, + # header2: value2, + # ... + # } + # } + # + # Both the payload and the header values are encoded with Base64 + # to prevent JSON parsing errors and encoding issues when + # storing the resulting serialized data. + class MessageSerializer + def load(serialized_content) + data = JSON.parse(serialized_content) + parse_message(data, 1) + rescue JSON::ParserError + raise ActiveRecord::Encryption::Errors::Encoding + end + + def dump(message) + raise ActiveRecord::Encryption::Errors::ForbiddenClass unless message.is_a?(ActiveRecord::Encryption::Message) + JSON.dump message_to_json(message) + end + + def binary? + false + end + + private + def parse_message(data, level) + validate_message_data_format(data, level) + ActiveRecord::Encryption::Message.new(payload: decode_if_needed(data["p"]), headers: parse_properties(data["h"], level)) + end + + def validate_message_data_format(data, level) + if level > 2 + raise ActiveRecord::Encryption::Errors::Decryption, "More than one level of hash nesting in headers is not supported" + end + + unless data.is_a?(Hash) && data.has_key?("p") + raise ActiveRecord::Encryption::Errors::Decryption, "Invalid data format: hash without payload" + end + end + + def parse_properties(headers, level) + ActiveRecord::Encryption::Properties.new.tap do |properties| + headers&.each do |key, value| + properties[key] = value.is_a?(Hash) ? parse_message(value, level + 1) : decode_if_needed(value) + end + end + end + + def message_to_json(message) + { + p: encode_if_needed(message.payload), + h: headers_to_json(message.headers) + } + end + + def headers_to_json(headers) + headers.transform_values do |value| + value.is_a?(ActiveRecord::Encryption::Message) ? message_to_json(value) : encode_if_needed(value) + end + end + + def encode_if_needed(value) + if value.is_a?(String) + ::Base64.strict_encode64 value + else + value + end + end + + def decode_if_needed(value) + if value.is_a?(String) + ::Base64.strict_decode64(value) + else + value + end + rescue ArgumentError, TypeError + raise Errors::Encoding + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/null_encryptor.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/null_encryptor.rb new file mode 100644 index 00000000..4041e95a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/null_encryptor.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + # An encryptor that won't decrypt or encrypt. It will just return the passed + # values + class NullEncryptor + def encrypt(clean_text, key_provider: nil, cipher_options: {}) + clean_text + end + + def decrypt(encrypted_text, key_provider: nil, cipher_options: {}) + encrypted_text + end + + def encrypted?(text) + false + end + + def binary? + false + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/properties.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/properties.rb new file mode 100644 index 00000000..1ab80fbb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/properties.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + # This is a wrapper for a hash of encryption properties. It is used by + # +Key+ (public tags) and +Message+ (headers). + # + # Since properties are serialized in messages, it is important for storage + # efficiency to keep their keys as short as possible. It defines accessors + # for common properties that will keep these keys very short while exposing + # a readable name. + # + # message.headers.encrypted_data_key # instead of message.headers[:k] + # + # See +Properties::DEFAULT_PROPERTIES+, Key, Message + class Properties + ALLOWED_VALUE_CLASSES = [String, ActiveRecord::Encryption::Message, Numeric, Integer, Float, BigDecimal, TrueClass, FalseClass, Symbol, NilClass] + + delegate_missing_to :data + delegate :==, :[], :each, :key?, to: :data + + # For each entry it generates an accessor exposing the full name + DEFAULT_PROPERTIES = { + encrypted_data_key: "k", + encrypted_data_key_id: "i", + compressed: "c", + iv: "iv", + auth_tag: "at", + encoding: "e" + } + + DEFAULT_PROPERTIES.each do |name, key| + define_method name do + self[key.to_sym] + end + + define_method "#{name}=" do |value| + self[key.to_sym] = value + end + end + + def initialize(initial_properties = {}) + @data = {} + add(initial_properties) + end + + # Set a value for a given key + # + # It will raise an +EncryptedContentIntegrity+ if the value exists + def []=(key, value) + raise Errors::EncryptedContentIntegrity, "Properties can't be overridden: #{key}" if key?(key) + validate_value_type(value) + data[key] = value + end + + def validate_value_type(value) + unless ALLOWED_VALUE_CLASSES.include?(value.class) || ALLOWED_VALUE_CLASSES.any? { |klass| value.is_a?(klass) } + raise ActiveRecord::Encryption::Errors::ForbiddenClass, "Can't store a #{value.class}, only properties of type #{ALLOWED_VALUE_CLASSES.inspect} are allowed" + end + end + + def add(other_properties) + other_properties.each do |key, value| + self[key.to_sym] = value + end + end + + def to_h + data + end + + private + attr_reader :data + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/read_only_null_encryptor.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/read_only_null_encryptor.rb new file mode 100644 index 00000000..173be25f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/read_only_null_encryptor.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + # A +NullEncryptor+ that will raise an error when trying to encrypt data + # + # This is useful when you want to reveal ciphertexts for debugging purposes + # and you want to make sure you won't overwrite any encryptable attribute with + # the wrong content. + class ReadOnlyNullEncryptor + def encrypt(clean_text, key_provider: nil, cipher_options: {}) + raise Errors::Encryption, "This encryptor is read-only" + end + + def decrypt(encrypted_text, key_provider: nil, cipher_options: {}) + encrypted_text + end + + def encrypted?(text) + false + end + + def binary? + false + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/scheme.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/scheme.rb new file mode 100644 index 00000000..122ae115 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/encryption/scheme.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +module ActiveRecord + module Encryption + # A container of attribute encryption options. + # + # It validates and serves attribute encryption options. + # + # See EncryptedAttributeType, Context + class Scheme + attr_accessor :previous_schemes + + def initialize(key_provider: nil, key: nil, deterministic: nil, support_unencrypted_data: nil, downcase: nil, ignore_case: nil, + previous_schemes: nil, compress: true, compressor: nil, **context_properties) + # Initializing all attributes to +nil+ as we want to allow a "not set" semantics so that we + # can merge schemes without overriding values with defaults. See +#merge+ + + @key_provider_param = key_provider + @key = key + @deterministic = deterministic + @support_unencrypted_data = support_unencrypted_data + @downcase = downcase || ignore_case + @ignore_case = ignore_case + @previous_schemes_param = previous_schemes + @previous_schemes = Array.wrap(previous_schemes) + @context_properties = context_properties + @compress = compress + @compressor = compressor + + validate_config! + + @context_properties[:encryptor] = Encryptor.new(compress: @compress) unless @compress + @context_properties[:encryptor] = Encryptor.new(compressor: compressor) if compressor + end + + def ignore_case? + @ignore_case + end + + def downcase? + @downcase + end + + def deterministic? + !!@deterministic + end + + def support_unencrypted_data? + @support_unencrypted_data.nil? ? ActiveRecord::Encryption.config.support_unencrypted_data : @support_unencrypted_data + end + + def fixed? + # by default deterministic encryption is fixed + @fixed ||= @deterministic && (!@deterministic.is_a?(Hash) || @deterministic[:fixed]) + end + + def key_provider + @key_provider_param || key_provider_from_key || deterministic_key_provider || default_key_provider + end + + def merge(other_scheme) + self.class.new(**to_h.merge(other_scheme.to_h)) + end + + def to_h + { key_provider: @key_provider_param, deterministic: @deterministic, downcase: @downcase, ignore_case: @ignore_case, + previous_schemes: @previous_schemes_param, **@context_properties }.compact + end + + def with_context(&block) + if @context_properties.present? + ActiveRecord::Encryption.with_encryption_context(**@context_properties, &block) + else + block.call + end + end + + def compatible_with?(other_scheme) + deterministic? == other_scheme.deterministic? + end + + private + def validate_config! + raise Errors::Configuration, "ignore_case: can only be used with deterministic encryption" if @ignore_case && !@deterministic + raise Errors::Configuration, "key_provider: and key: can't be used simultaneously" if @key_provider_param && @key + raise Errors::Configuration, "compressor: can't be used with compress: false" if !@compress && @compressor + raise Errors::Configuration, "compressor: can't be used with encryptor" if @compressor && @context_properties[:encryptor] + end + + def key_provider_from_key + @key_provider_from_key ||= if @key.present? + DerivedSecretKeyProvider.new(@key) + end + end + + def deterministic_key_provider + @deterministic_key_provider ||= if @deterministic + DeterministicKeyProvider.new(ActiveRecord::Encryption.config.deterministic_key) + end + end + + def default_key_provider + ActiveRecord::Encryption.key_provider + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/enum.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/enum.rb new file mode 100644 index 00000000..02ebbc19 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/enum.rb @@ -0,0 +1,411 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/object/deep_dup" + +module ActiveRecord + # Declare an enum attribute where the values map to integers in the database, + # but can be queried by name. Example: + # + # class Conversation < ActiveRecord::Base + # enum :status, [ :active, :archived ] + # end + # + # # conversation.update! status: 0 + # conversation.active! + # conversation.active? # => true + # conversation.status # => "active" + # + # # conversation.update! status: 1 + # conversation.archived! + # conversation.archived? # => true + # conversation.status # => "archived" + # + # # conversation.status = 1 + # conversation.status = "archived" + # + # conversation.status = nil + # conversation.status.nil? # => true + # conversation.status # => nil + # + # Scopes based on the allowed values of the enum field will be provided + # as well. With the above example: + # + # Conversation.active + # Conversation.not_active + # Conversation.archived + # Conversation.not_archived + # + # Of course, you can also query them directly if the scopes don't fit your + # needs: + # + # Conversation.where(status: [:active, :archived]) + # Conversation.where.not(status: :active) + # + # Defining scopes can be disabled by setting +:scopes+ to +false+. + # + # class Conversation < ActiveRecord::Base + # enum :status, [ :active, :archived ], scopes: false + # end + # + # You can set the default enum value by setting +:default+, like: + # + # class Conversation < ActiveRecord::Base + # enum :status, [ :active, :archived ], default: :active + # end + # + # conversation = Conversation.new + # conversation.status # => "active" + # + # It's possible to explicitly map the relation between attribute and + # database integer with a hash: + # + # class Conversation < ActiveRecord::Base + # enum :status, active: 0, archived: 1 + # end + # + # Finally it's also possible to use a string column to persist the enumerated value. + # Note that this will likely lead to slower database queries: + # + # class Conversation < ActiveRecord::Base + # enum :status, active: "active", archived: "archived" + # end + # + # Note that when an array is used, the implicit mapping from the values to database + # integers is derived from the order the values appear in the array. In the example, + # :active is mapped to +0+ as it's the first element, and :archived + # is mapped to +1+. In general, the +i+-th element is mapped to i-1 in the + # database. + # + # Therefore, once a value is added to the enum array, its position in the array must + # be maintained, and new values should only be added to the end of the array. To + # remove unused values, the explicit hash syntax should be used. + # + # In rare circumstances you might need to access the mapping directly. + # The mappings are exposed through a class method with the pluralized attribute + # name, which return the mapping in a ActiveSupport::HashWithIndifferentAccess : + # + # Conversation.statuses[:active] # => 0 + # Conversation.statuses["archived"] # => 1 + # + # Use that class method when you need to know the ordinal value of an enum. + # For example, you can use that when manually building SQL strings: + # + # Conversation.where("status <> ?", Conversation.statuses[:archived]) + # + # You can use the +:prefix+ or +:suffix+ options when you need to define + # multiple enums with same values. If the passed value is +true+, the methods + # are prefixed/suffixed with the name of the enum. It is also possible to + # supply a custom value: + # + # class Conversation < ActiveRecord::Base + # enum :status, [ :active, :archived ], suffix: true + # enum :comments_status, [ :active, :inactive ], prefix: :comments + # end + # + # With the above example, the bang and predicate methods along with the + # associated scopes are now prefixed and/or suffixed accordingly: + # + # conversation.active_status! + # conversation.archived_status? # => false + # + # conversation.comments_inactive! + # conversation.comments_active? # => false + # + # If you want to disable the auto-generated methods on the model, you can do + # so by setting the +:instance_methods+ option to false: + # + # class Conversation < ActiveRecord::Base + # enum :status, [ :active, :archived ], instance_methods: false + # end + # + # If you want the enum value to be validated before saving, use the option +:validate+: + # + # class Conversation < ActiveRecord::Base + # enum :status, [ :active, :archived ], validate: true + # end + # + # conversation = Conversation.new + # + # conversation.status = :unknown + # conversation.valid? # => false + # + # conversation.status = nil + # conversation.valid? # => false + # + # conversation.status = :active + # conversation.valid? # => true + # + # It is also possible to pass additional validation options: + # + # class Conversation < ActiveRecord::Base + # enum :status, [ :active, :archived ], validate: { allow_nil: true } + # end + # + # conversation = Conversation.new + # + # conversation.status = :unknown + # conversation.valid? # => false + # + # conversation.status = nil + # conversation.valid? # => true + # + # conversation.status = :active + # conversation.valid? # => true + # + # Otherwise +ArgumentError+ will raise: + # + # class Conversation < ActiveRecord::Base + # enum :status, [ :active, :archived ] + # end + # + # conversation = Conversation.new + # + # conversation.status = :unknown # 'unknown' is not a valid status (ArgumentError) + module Enum + def self.extended(base) # :nodoc: + base.class_attribute(:defined_enums, instance_writer: false, default: {}) + end + + class EnumType < Type::Value # :nodoc: + delegate :type, to: :subtype + + def initialize(name, mapping, subtype, raise_on_invalid_values: true) + @name = name + @mapping = mapping + @subtype = subtype + @_raise_on_invalid_values = raise_on_invalid_values + end + + def cast(value) + if mapping.has_key?(value) + value.to_s + elsif mapping.has_value?(value) + mapping.key(value) + else + value.presence + end + end + + def deserialize(value) + mapping.key(subtype.deserialize(value)) + end + + def serialize(value) + subtype.serialize(mapping.fetch(value, value)) + end + + def serializable?(value, &block) + subtype.serializable?(mapping.fetch(value, value), &block) + end + + def assert_valid_value(value) + return unless @_raise_on_invalid_values + + unless value.blank? || mapping.has_key?(value) || mapping.has_value?(value) + raise ArgumentError, "'#{value}' is not a valid #{name}" + end + end + + attr_reader :subtype + + private + attr_reader :name, :mapping + end + + def enum(name, values = nil, **options) + values, options = options, {} unless values + _enum(name, values, **options) + end + + private + def _enum(name, values, prefix: nil, suffix: nil, scopes: true, instance_methods: true, validate: false, **options) + assert_valid_enum_definition_values(values) + assert_valid_enum_options(options) + + # statuses = { } + enum_values = ActiveSupport::HashWithIndifferentAccess.new + name = name.to_s + + # def self.statuses() statuses end + detect_enum_conflict!(name, name.pluralize, true) + singleton_class.define_method(name.pluralize) { enum_values } + defined_enums[name] = enum_values + + detect_enum_conflict!(name, name) + detect_enum_conflict!(name, "#{name}=") + + attribute(name, **options) + + decorate_attributes([name]) do |_name, subtype| + if subtype == ActiveModel::Type.default_value + raise "Undeclared attribute type for enum '#{name}' in #{self.name}. Enums must be" \ + " backed by a database column or declared with an explicit type" \ + " via `attribute`." + end + + subtype = subtype.subtype if EnumType === subtype + EnumType.new(name, enum_values, subtype, raise_on_invalid_values: !validate) + end + + value_method_names = [] + _enum_methods_module.module_eval do + prefix = if prefix + prefix == true ? "#{name}_" : "#{prefix}_" + end + + suffix = if suffix + suffix == true ? "_#{name}" : "_#{suffix}" + end + + pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index + pairs.each do |label, value| + enum_values[label] = value + label = label.to_s + + value_method_name = "#{prefix}#{label}#{suffix}" + value_method_names << value_method_name + define_enum_methods(name, value_method_name, value, scopes, instance_methods) + + method_friendly_label = label.gsub(/[\W&&[:ascii:]]+/, "_") + value_method_alias = "#{prefix}#{method_friendly_label}#{suffix}" + + if value_method_alias != value_method_name && !value_method_names.include?(value_method_alias) + value_method_names << value_method_alias + define_enum_methods(name, value_method_alias, value, scopes, instance_methods) + end + end + end + detect_negative_enum_conditions!(value_method_names) if scopes + + if validate + validate = {} unless Hash === validate + validates_inclusion_of name, in: enum_values.keys, **validate + end + + enum_values.freeze + end + + def inherited(base) + base.defined_enums = defined_enums.deep_dup + super + end + + class EnumMethods < Module # :nodoc: + def initialize(klass) + @klass = klass + end + + private + attr_reader :klass + + def define_enum_methods(name, value_method_name, value, scopes, instance_methods) + if instance_methods + # def active?() status_for_database == 0 end + klass.send(:detect_enum_conflict!, name, "#{value_method_name}?") + define_method("#{value_method_name}?") { public_send(:"#{name}_for_database") == value } + + # def active!() update!(status: 0) end + klass.send(:detect_enum_conflict!, name, "#{value_method_name}!") + define_method("#{value_method_name}!") { update!(name => value) } + end + + if scopes + # scope :active, -> { where(status: 0) } + klass.send(:detect_enum_conflict!, name, value_method_name, true) + klass.scope value_method_name, -> { where(name => value) } + + # scope :not_active, -> { where.not(status: 0) } + klass.send(:detect_enum_conflict!, name, "not_#{value_method_name}", true) + klass.scope "not_#{value_method_name}", -> { where.not(name => value) } + end + end + end + private_constant :EnumMethods + + def _enum_methods_module + @_enum_methods_module ||= begin + mod = EnumMethods.new(self) + include mod + mod + end + end + + def assert_valid_enum_definition_values(values) + case values + when Hash + if values.empty? + raise ArgumentError, "Enum values #{values} must not be empty." + end + + if values.keys.any?(&:blank?) + raise ArgumentError, "Enum values #{values} must not contain a blank name." + end + when Array + if values.empty? + raise ArgumentError, "Enum values #{values} must not be empty." + end + + unless values.all?(Symbol) || values.all?(String) + raise ArgumentError, "Enum values #{values} must only contain symbols or strings." + end + + if values.any?(&:blank?) + raise ArgumentError, "Enum values #{values} must not contain a blank name." + end + else + raise ArgumentError, "Enum values #{values} must be either a non-empty hash or an array." + end + end + + def assert_valid_enum_options(options) + invalid_keys = options.keys & %i[_prefix _suffix _scopes _default _instance_methods] + unless invalid_keys.empty? + raise ArgumentError, "invalid option(s): #{invalid_keys.map(&:inspect).join(", ")}. Valid options are: :prefix, :suffix, :scopes, :default, :instance_methods, and :validate." + end + end + + ENUM_CONFLICT_MESSAGE = \ + "You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \ + "this will generate a %{type} method \"%{method}\", which is already defined " \ + "by %{source}." + private_constant :ENUM_CONFLICT_MESSAGE + + def detect_enum_conflict!(enum_name, method_name, klass_method = false) + if klass_method && dangerous_class_method?(method_name) + raise_conflict_error(enum_name, method_name, type: "class") + elsif klass_method && method_defined_within?(method_name, Relation) + raise_conflict_error(enum_name, method_name, type: "class", source: Relation.name) + elsif klass_method && method_name.to_sym == :id + raise_conflict_error(enum_name, method_name) + elsif !klass_method && dangerous_attribute_method?(method_name) + raise_conflict_error(enum_name, method_name) + elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module) + raise_conflict_error(enum_name, method_name, source: "another enum") + end + end + + def raise_conflict_error(enum_name, method_name, type: "instance", source: "Active Record") + raise ArgumentError, ENUM_CONFLICT_MESSAGE % { + enum: enum_name, + klass: name, + type: type, + method: method_name, + source: source + } + end + + def detect_negative_enum_conditions!(method_names) + return unless logger + + method_names.select { |m| m.start_with?("not_") }.each do |potential_not| + inverted_form = potential_not.sub("not_", "") + if method_names.include?(inverted_form) + logger.warn "Enum element '#{potential_not}' in #{self.name} uses the prefix 'not_'." \ + " This has caused a conflict with auto generated negative scopes." \ + " Avoid using enum elements starting with 'not' where the positive form is also an element." + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/errors.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/errors.rb new file mode 100644 index 00000000..35000cb5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/errors.rb @@ -0,0 +1,614 @@ +# frozen_string_literal: true + +require "active_support/deprecation" + +module ActiveRecord + include ActiveSupport::Deprecation::DeprecatedConstantAccessor + + # = Active Record Errors + # + # Generic Active Record exception class. + class ActiveRecordError < StandardError + end + + # Raised when the single-table inheritance mechanism fails to locate the subclass + # (for example due to improper usage of column that + # {ActiveRecord::Base.inheritance_column}[rdoc-ref:ModelSchema::ClassMethods#inheritance_column] + # points to). + class SubclassNotFound < ActiveRecordError + end + + # Raised when an object assigned to an association has an incorrect type. + # + # class Ticket < ActiveRecord::Base + # has_many :patches + # end + # + # class Patch < ActiveRecord::Base + # belongs_to :ticket + # end + # + # # Comments are not patches, this assignment raises AssociationTypeMismatch. + # @ticket.patches << Comment.new(content: "Please attach tests to your patch.") + class AssociationTypeMismatch < ActiveRecordError + end + + # Raised when unserialized object's type mismatches one specified for serializable field. + class SerializationTypeMismatch < ActiveRecordError + end + + # Raised when adapter not specified on connection (or configuration file + # +config/database.yml+ misses adapter field). + class AdapterNotSpecified < ActiveRecordError + end + + # Raised when a model makes a query but it has not specified an associated table. + class TableNotSpecified < ActiveRecordError + end + + # Raised when Active Record cannot find database adapter specified in + # +config/database.yml+ or programmatically. + class AdapterNotFound < ActiveRecordError + end + + # Superclass for all errors raised from an Active Record adapter. + class AdapterError < ActiveRecordError + def initialize(message = nil, connection_pool: nil) + @connection_pool = connection_pool + super(message) + end + + attr_reader :connection_pool + end + + # Raised when connection to the database could not been established (for example when + # {ActiveRecord::Base.lease_connection=}[rdoc-ref:ConnectionHandling#lease_connection] + # is given a +nil+ object). + class ConnectionNotEstablished < AdapterError + def initialize(message = nil, connection_pool: nil) + super(message, connection_pool: connection_pool) + end + + def set_pool(connection_pool) + unless @connection_pool + @connection_pool = connection_pool + end + + self + end + end + + # Raised when a connection could not be obtained within the connection + # acquisition timeout period: because max connections in pool + # are in use. + class ConnectionTimeoutError < ConnectionNotEstablished + end + + # Raised when a database connection pool is requested but + # has not been defined. + class ConnectionNotDefined < ConnectionNotEstablished + def initialize(message = nil, connection_name: nil, role: nil, shard: nil) + super(message) + @connection_name = connection_name + @role = role + @shard = shard + end + + attr_reader :connection_name, :role, :shard + end + + # Raised when connection to the database could not been established because it was not + # able to connect to the host or when the authorization failed. + class DatabaseConnectionError < ConnectionNotEstablished + def initialize(message = nil) + super(message || "Database connection error") + end + + class << self + def hostname_error(hostname) + DatabaseConnectionError.new(<<~MSG) + There is an issue connecting with your hostname: #{hostname}.\n + Please check your database configuration and ensure there is a valid connection to your database. + MSG + end + + def username_error(username) + DatabaseConnectionError.new(<<~MSG) + There is an issue connecting to your database with your username/password, username: #{username}.\n + Please check your database configuration to ensure the username/password are valid. + MSG + end + end + end + + # Raised when a pool was unable to get ahold of all its connections + # to perform a "group" action such as + # {ActiveRecord::Base.connection_pool.disconnect!}[rdoc-ref:ConnectionAdapters::ConnectionPool#disconnect!] + # or {ActiveRecord::Base.connection_handler.clear_reloadable_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_reloadable_connections!]. + class ExclusiveConnectionTimeoutError < ConnectionTimeoutError + end + + # Raised when a write to the database is attempted on a read only connection. + class ReadOnlyError < ActiveRecordError + end + + # Raised when Active Record cannot find a record by given id or set of ids. + class RecordNotFound < ActiveRecordError + attr_reader :model, :primary_key, :id + + def initialize(message = nil, model = nil, primary_key = nil, id = nil) + @primary_key = primary_key + @model = model + @id = id + + super(message) + end + end + + # Raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and + # {ActiveRecord::Base.update_attribute!}[rdoc-ref:Persistence#update_attribute!] + # methods when a record failed to validate or cannot be saved due to any of the + # before_* callbacks throwing +:abort+. See + # ActiveRecord::Callbacks for further details. + # + # class Product < ActiveRecord::Base + # before_save do + # throw :abort if price < 0 + # end + # end + # + # Product.create! # => raises an ActiveRecord::RecordNotSaved + class RecordNotSaved < ActiveRecordError + attr_reader :record + + def initialize(message = nil, record = nil) + @record = record + super(message) + end + end + + # Raised by {ActiveRecord::Base#destroy!}[rdoc-ref:Persistence#destroy!] + # when a record cannot be destroyed due to any of the + # before_destroy callbacks throwing +:abort+. See + # ActiveRecord::Callbacks for further details. + # + # class User < ActiveRecord::Base + # before_destroy do + # throw :abort if still_active? + # end + # end + # + # User.first.destroy! # => raises an ActiveRecord::RecordNotDestroyed + class RecordNotDestroyed < ActiveRecordError + attr_reader :record + + def initialize(message = nil, record = nil) + @record = record + super(message) + end + end + + # Raised when Active Record finds multiple records but only expected one. + class SoleRecordExceeded < ActiveRecordError + attr_reader :record + + def initialize(record = nil) + @record = record + super "Wanted only one #{record&.name || "record"}" + end + end + + # Superclass for all database execution errors. + # + # Wraps the underlying database error as +cause+. + class StatementInvalid < AdapterError + def initialize(message = nil, sql: nil, binds: nil, connection_pool: nil) + super(message || $!&.message, connection_pool: connection_pool) + @sql = sql + @binds = binds + end + + attr_reader :sql, :binds + + def set_query(sql, binds) + unless @sql + @sql = sql + @binds = binds + end + + self + end + end + + # Defunct wrapper class kept for compatibility. + # StatementInvalid wraps the original exception now. + class WrappedDatabaseException < StatementInvalid + end + + # Raised when a record cannot be inserted or updated because it would violate a uniqueness constraint. + class RecordNotUnique < WrappedDatabaseException + end + + # Raised when a record cannot be inserted or updated because it references a non-existent record, + # or when a record cannot be deleted because a parent record references it. + class InvalidForeignKey < WrappedDatabaseException + end + + # Raised when a foreign key constraint cannot be added because the column type does not match the referenced column type. + class MismatchedForeignKey < StatementInvalid + def initialize( + message: nil, + sql: nil, + binds: nil, + table: nil, + foreign_key: nil, + target_table: nil, + primary_key: nil, + primary_key_column: nil, + query_parser: nil, + connection_pool: nil + ) + @original_message = message + @query_parser = query_parser + + if table + type = primary_key_column.bigint? ? :bigint : primary_key_column.type + msg = <<~EOM.squish + Column `#{foreign_key}` on table `#{table}` does not match column `#{primary_key}` on `#{target_table}`, + which has type `#{primary_key_column.sql_type}`. + To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :#{type}. + (For example `t.#{type} :#{foreign_key}`). + EOM + else + msg = <<~EOM.squish + There is a mismatch between the foreign key and primary key column types. + Verify that the foreign key column type and the primary key of the associated table match types. + EOM + end + if message + msg << "\nOriginal message: #{message}" + end + + super(msg, sql: sql, binds: binds, connection_pool: connection_pool) + end + + def set_query(sql, binds) + if @query_parser && !@sql + self.class.new( + message: @original_message, + sql: sql, + binds: binds, + connection_pool: @connection_pool, + **@query_parser.call(sql) + ).tap do |exception| + exception.set_backtrace backtrace + end + else + super + end + end + end + + # Raised when a record cannot be inserted or updated because it would violate a not null constraint. + class NotNullViolation < StatementInvalid + end + + # Raised when a record cannot be inserted or updated because a value too long for a column type. + class ValueTooLong < StatementInvalid + end + + # Raised when values that executed are out of range. + class RangeError < StatementInvalid + end + + # Raised when a statement produces an SQL warning. + class SQLWarning < AdapterError + attr_reader :code, :level + attr_accessor :sql + + def initialize(message = nil, code = nil, level = nil, sql = nil, connection_pool = nil) + super(message, connection_pool: connection_pool) + @code = code + @level = level + @sql = sql + end + end + + # Raised when the number of placeholders in an SQL fragment passed to + # {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where] + # does not match the number of values supplied. + # + # For example, when there are two placeholders with only one value supplied: + # + # Location.where("lat = ? AND lng = ?", 53.7362) + class PreparedStatementInvalid < ActiveRecordError + end + + # Raised when a given database does not exist. + class NoDatabaseError < StatementInvalid + include ActiveSupport::ActionableError + + action "Create database" do + ActiveRecord::Tasks::DatabaseTasks.create_current + end + + def initialize(message = nil, connection_pool: nil) + super(message || "Database not found", connection_pool: connection_pool) + end + + class << self + def db_error(db_name) + NoDatabaseError.new(<<~MSG) + We could not find your database: #{db_name}. Available database configurations can be found in config/database.yml. + + To resolve this error: + + - Did you not create the database, or did you delete it? To create the database, run: + + bin/rails db:create + + - Has the database name changed? Verify that config/database.yml contains the correct database name. + MSG + end + end + end + + # Raised when creating a database if it exists. + class DatabaseAlreadyExists < StatementInvalid + end + + # Raised when PostgreSQL returns 'cached plan must not change result type' and + # we cannot retry gracefully (e.g. inside a transaction) + class PreparedStatementCacheExpired < StatementInvalid + end + + # Raised on attempt to save stale record. Record is stale when it's being saved in another query after + # instantiation, for example, when two users edit the same wiki page and one starts editing and saves + # the page before the other. + # + # Read more about optimistic locking in ActiveRecord::Locking module + # documentation. + class StaleObjectError < ActiveRecordError + attr_reader :record, :attempted_action + + def initialize(record = nil, attempted_action = nil) + if record && attempted_action + @record = record + @attempted_action = attempted_action + super("Attempted to #{attempted_action} a stale object: #{record.class.name}.") + else + super("Stale object error.") + end + end + end + + # Raised when association is being configured improperly or user tries to use + # offset and limit together with + # {ActiveRecord::Base.has_many}[rdoc-ref:Associations::ClassMethods#has_many] or + # {ActiveRecord::Base.has_and_belongs_to_many}[rdoc-ref:Associations::ClassMethods#has_and_belongs_to_many] + # associations. + class ConfigurationError < ActiveRecordError + end + + # Raised on attempt to update record that is instantiated as read only. + class ReadOnlyRecord < ActiveRecordError + end + + # Raised on attempt to lazily load records that are marked as strict loading. + # + # You can resolve this error by eager loading marked records before accessing + # them. The + # {Eager Loading Associations}[https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations] + # guide covers solutions, such as using + # {ActiveRecord::Base.includes}[rdoc-ref:QueryMethods#includes]. + class StrictLoadingViolationError < ActiveRecordError + end + + # {ActiveRecord::Base.transaction}[rdoc-ref:Transactions::ClassMethods#transaction] + # uses this exception to distinguish a deliberate rollback from other exceptional situations. + # Normally, raising an exception will cause the + # {.transaction}[rdoc-ref:Transactions::ClassMethods#transaction] method to rollback + # the database transaction *and* pass on the exception. But if you raise an + # ActiveRecord::Rollback exception, then the database transaction will be rolled back, + # without passing on the exception. + # + # For example, you could do this in your controller to rollback a transaction: + # + # class BooksController < ActionController::Base + # def create + # Book.transaction do + # book = Book.new(params[:book]) + # book.save! + # if today_is_friday? + # # The system must fail on Friday so that our support department + # # won't be out of job. We silently rollback this transaction + # # without telling the user. + # raise ActiveRecord::Rollback + # end + # end + # # ActiveRecord::Rollback is the only exception that won't be passed on + # # by ActiveRecord::Base.transaction, so this line will still be reached + # # even on Friday. + # redirect_to root_url + # end + # end + class Rollback < ActiveRecordError + end + + # Raised when attribute has a name reserved by Active Record (when attribute + # has name of one of Active Record instance methods). + class DangerousAttributeError < ActiveRecordError + end + + # Raised when unknown attributes are supplied via mass assignment. + UnknownAttributeError = ActiveModel::UnknownAttributeError + + # Raised when an error occurred while doing a mass assignment to an attribute through the + # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method. + # The exception has an +attribute+ property that is the name of the offending attribute. + class AttributeAssignmentError < ActiveRecordError + attr_reader :exception, :attribute + + def initialize(message = nil, exception = nil, attribute = nil) + super(message) + @exception = exception + @attribute = attribute + end + end + + # Raised when there are multiple errors while doing a mass assignment through the + # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] + # method. The exception has an +errors+ property that contains an array of AttributeAssignmentError + # objects, each corresponding to the error while assigning to an attribute. + class MultiparameterAssignmentErrors < ActiveRecordError + attr_reader :errors + + def initialize(errors = nil) + @errors = errors + end + end + + # Raised when a primary key is needed, but not specified in the schema or model. + class UnknownPrimaryKey < ActiveRecordError + attr_reader :model + + def initialize(model = nil, description = nil) + if model + message = "Unknown primary key for table #{model.table_name} in model #{model}." + message += "\n#{description}" if description + @model = model + super(message) + else + super("Unknown primary key.") + end + end + end + + # Raised when a relation cannot be mutated because it's already loaded. + # + # class Task < ActiveRecord::Base + # end + # + # relation = Task.all + # relation.loaded? # => true + # + # # Methods which try to mutate a loaded relation fail. + # relation.where!(title: 'TODO') # => ActiveRecord::UnmodifiableRelation + # relation.limit!(5) # => ActiveRecord::UnmodifiableRelation + class UnmodifiableRelation < ActiveRecordError + end + + # TransactionIsolationError will be raised under the following conditions: + # + # * The adapter does not support setting the isolation level + # * You are joining an existing open transaction + # * You are creating a nested (savepoint) transaction + # + # The mysql2, trilogy, and postgresql adapters support setting the transaction isolation level. + class TransactionIsolationError < ActiveRecordError + end + + # TransactionRollbackError will be raised when a transaction is rolled + # back by the database due to a serialization failure or a deadlock. + # + # These exceptions should not be generally rescued in nested transaction + # blocks, because they have side-effects in the actual enclosing transaction + # and internal Active Record state. They can be rescued if you are above the + # root transaction block, though. + # + # In that case, beware of transactional tests, however, because they run test + # cases in their own umbrella transaction. If you absolutely need to handle + # these exceptions in tests please consider disabling transactional tests in + # the affected test class (self.use_transactional_tests = false). + # + # Due to the aforementioned side-effects, this exception should not be raised + # manually by users. + # + # See the following: + # + # * https://www.postgresql.org/docs/current/static/transaction-iso.html + # * https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html#error_er_lock_deadlock + class TransactionRollbackError < StatementInvalid + end + + # AsynchronousQueryInsideTransactionError will be raised when attempting + # to perform an asynchronous query from inside a transaction + class AsynchronousQueryInsideTransactionError < ActiveRecordError + end + + # SerializationFailure will be raised when a transaction is rolled + # back by the database due to a serialization failure. + # + # This is a subclass of TransactionRollbackError, please make sure to check + # its documentation to be aware of its caveats. + class SerializationFailure < TransactionRollbackError + end + + # Deadlocked will be raised when a transaction is rolled + # back by the database when a deadlock is encountered. + # + # This is a subclass of TransactionRollbackError, please make sure to check + # its documentation to be aware of its caveats. + class Deadlocked < TransactionRollbackError + end + + # IrreversibleOrderError is raised when a relation's order is too complex for + # +reverse_order+ to automatically reverse. + class IrreversibleOrderError < ActiveRecordError + end + + # Superclass for errors that have been aborted (either by client or server). + class QueryAborted < StatementInvalid + end + + # LockWaitTimeout will be raised when lock wait timeout exceeded. + class LockWaitTimeout < StatementInvalid + end + + # StatementTimeout will be raised when statement timeout exceeded. + class StatementTimeout < QueryAborted + end + + # QueryCanceled will be raised when canceling statement due to user request. + class QueryCanceled < QueryAborted + end + + # AdapterTimeout will be raised when database clients times out while waiting from the server. + class AdapterTimeout < QueryAborted + end + + # ConnectionFailed will be raised when the network connection to the + # database fails while sending a query or waiting for its result. + class ConnectionFailed < QueryAborted + end + + # UnknownAttributeReference is raised when an unknown and potentially unsafe + # value is passed to a query method. For example, passing a non column name + # value to a relation's #order method might cause this exception. + # + # When working around this exception, caution should be taken to avoid SQL + # injection vulnerabilities when passing user-provided values to query + # methods. Known-safe values can be passed to query methods by wrapping them + # in Arel.sql. + # + # For example, the following code would raise this exception: + # + # Post.order("REPLACE(title, 'misc', 'zzzz') asc").pluck(:id) + # + # The desired result can be accomplished by wrapping the known-safe string + # in Arel.sql: + # + # Post.order(Arel.sql("REPLACE(title, 'misc', 'zzzz') asc")).pluck(:id) + # + # Again, such a workaround should *not* be used when passing user-provided + # values, such as request parameters or model attributes to query methods. + class UnknownAttributeReference < ActiveRecordError + end + + # DatabaseVersionError will be raised when the database version is not supported, or when + # the database version cannot be determined. + class DatabaseVersionError < ActiveRecordError + end +end + +require "active_record/associations/errors" diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/explain.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/explain.rb new file mode 100644 index 00000000..20e93965 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/explain.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require "active_record/explain_registry" + +module ActiveRecord + module Explain + # Executes the block with the collect flag enabled. Queries are collected + # asynchronously by the subscriber and returned. + def collecting_queries_for_explain # :nodoc: + ExplainRegistry.collect = true + yield + ExplainRegistry.queries + ensure + ExplainRegistry.reset + end + + # Makes the adapter execute EXPLAIN for the tuples of queries and bindings. + # Returns a formatted string ready to be logged. + def exec_explain(queries, options = []) # :nodoc: + str = with_connection do |c| + queries.map do |sql, binds| + msg = +"#{build_explain_clause(c, options)} #{sql}" + unless binds.empty? + msg << " " + msg << binds.map { |attr| render_bind(c, attr) }.inspect + end + msg << "\n" + msg << c.explain(sql, binds, options) + end.join("\n") + end + # Overriding inspect to be more human readable, especially in the console. + def str.inspect + self + end + + str + end + + private + def render_bind(connection, attr) + if ActiveModel::Attribute === attr + value = if attr.type.binary? && attr.value + "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>" + else + connection.type_cast(attr.value_for_database) + end + else + value = connection.type_cast(attr) + attr = nil + end + + [attr&.name, value] + end + + def build_explain_clause(connection, options = []) + if connection.respond_to?(:build_explain_clause, true) + connection.build_explain_clause(options) + else + "EXPLAIN for:" + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/explain_registry.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/explain_registry.rb new file mode 100644 index 00000000..41950009 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/explain_registry.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/delegation" + +module ActiveRecord + # This is a thread locals registry for EXPLAIN. For example + # + # ActiveRecord::ExplainRegistry.queries + # + # returns the collected queries local to the current thread. + class ExplainRegistry # :nodoc: + class << self + delegate :reset, :collect, :collect=, :collect?, :queries, to: :instance + + private + def instance + ActiveSupport::IsolatedExecutionState[:active_record_explain_registry] ||= new + end + end + + attr_accessor :collect + attr_reader :queries + + def initialize + reset + end + + def collect? + @collect + end + + def reset + @collect = false + @queries = [] + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/explain_subscriber.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/explain_subscriber.rb new file mode 100644 index 00000000..d95dd6c4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/explain_subscriber.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "active_support/notifications" +require "active_record/explain_registry" + +module ActiveRecord + class ExplainSubscriber # :nodoc: + def start(name, id, payload) + # unused + end + + def finish(name, id, payload) + if ExplainRegistry.collect? && !ignore_payload?(payload) + ExplainRegistry.queries << payload.values_at(:sql, :binds) + end + end + + # SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on + # our own EXPLAINs no matter how loopingly beautiful that would be. + # + # On the other hand, we want to monitor the performance of our real database + # queries, not the performance of the access to the query cache. + IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN) + EXPLAINED_SQLS = /\A\s*(\/\*.*\*\/)?\s*(with|select|update|delete|insert)\b/i + def ignore_payload?(payload) + payload[:exception] || + payload[:cached] || + IGNORED_PAYLOADS.include?(payload[:name]) || + !payload[:sql].match?(EXPLAINED_SQLS) + end + + ActiveSupport::Notifications.subscribe("sql.active_record", new) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/fixture_set/file.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/fixture_set/file.rb new file mode 100644 index 00000000..1112ac0f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/fixture_set/file.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require "active_support/configuration_file" + +module ActiveRecord + class FixtureSet + class File # :nodoc: + include Enumerable + + ## + # Open a fixture file named +file+. When called with a block, the block + # is called with the filehandle and the filehandle is automatically closed + # when the block finishes. + def self.open(file) + x = new file + block_given? ? yield(x) : x + end + + def initialize(file) + @file = file + end + + def each(&block) + rows.each(&block) + end + + def model_class + config_row["model_class"] + end + + def ignored_fixtures + config_row["ignore"] + end + + private + def rows + @rows ||= raw_rows.reject { |fixture_name, _| fixture_name == "_fixture" } + end + + def config_row + @config_row ||= begin + row = raw_rows.find { |fixture_name, _| fixture_name == "_fixture" } + if row + validate_config_row(row.last) + else + { 'model_class': nil, 'ignore': nil } + end + end + end + + def raw_rows + @raw_rows ||= begin + data = ActiveSupport::ConfigurationFile.parse(@file, context: + ActiveRecord::FixtureSet::RenderContext.create_subclass.new.get_binding) + data ? validate(data).to_a : [] + rescue RuntimeError => error + raise Fixture::FormatError, error.message + end + end + + def validate_config_row(data) + unless Hash === data + raise Fixture::FormatError, "Invalid `_fixture` section: `_fixture` must be a hash: #{@file}" + end + + begin + data.assert_valid_keys("model_class", "ignore") + rescue ArgumentError => error + raise Fixture::FormatError, "Invalid `_fixture` section: #{error.message}: #{@file}" + end + + data + end + + # Validate our unmarshalled data. + def validate(data) + unless Hash === data || YAML::Omap === data + raise Fixture::FormatError, "fixture is not a hash: #{@file}" + end + + invalid = data.reject { |_, row| Hash === row } + if invalid.any? + raise Fixture::FormatError, "fixture key is not a hash: #{@file}, keys: #{invalid.keys.inspect}" + end + data + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/fixture_set/model_metadata.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/fixture_set/model_metadata.rb new file mode 100644 index 00000000..a00674d1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/fixture_set/model_metadata.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module ActiveRecord + class FixtureSet + class ModelMetadata # :nodoc: + def initialize(model_class) + @model_class = model_class + end + + def primary_key_name + @primary_key_name ||= @model_class && @model_class.primary_key + end + + def primary_key_type + @primary_key_type ||= @model_class && column_type(@model_class.primary_key) + end + + def column_type(column_name) + @column_type ||= {} + return @column_type[column_name] if @column_type.key?(column_name) + + @column_type[column_name] = @model_class && @model_class.type_for_attribute(column_name).type + end + + def has_column?(column_name) + column_names.include?(column_name) + end + + def column_names + @column_names ||= @model_class ? @model_class.columns.map(&:name).to_set : Set.new + end + + def timestamp_column_names + @model_class.all_timestamp_attributes_in_model + end + + def inheritance_column_name + @inheritance_column_name ||= @model_class && @model_class.inheritance_column + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/fixture_set/render_context.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/fixture_set/render_context.rb new file mode 100644 index 00000000..3233a6b6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/fixture_set/render_context.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "base64" + +# NOTE: This class has to be defined in compact style in +# order for rendering context subclassing to work correctly. +class ActiveRecord::FixtureSet::RenderContext # :nodoc: + def self.create_subclass + Class.new(ActiveRecord::FixtureSet.context_class) do + def get_binding + binding() + end + + def binary(path) + %(!!binary "#{Base64.strict_encode64(File.binread(path))}") + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/fixture_set/table_row.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/fixture_set/table_row.rb new file mode 100644 index 00000000..78af6aab --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/fixture_set/table_row.rb @@ -0,0 +1,208 @@ +# frozen_string_literal: true + +module ActiveRecord + class FixtureSet + class TableRow # :nodoc: + class ReflectionProxy # :nodoc: + def initialize(association) + @association = association + end + + def join_table + @association.join_table + end + + def name + @association.name + end + + def primary_key_type + @association.klass.type_for_attribute(@association.klass.primary_key).type + end + end + + class HasManyThroughProxy < ReflectionProxy # :nodoc: + def rhs_key + @association.foreign_key + end + + def lhs_key + @association.through_reflection.foreign_key + end + + def join_table + @association.through_reflection.table_name + end + + def timestamp_column_names + @association.through_reflection.klass.all_timestamp_attributes_in_model + end + end + + class PrimaryKeyError < StandardError # :nodoc: + def initialize(label, association, value) + super(<<~MSG) + Unable to set #{association.name} to #{value} because the association has a + custom primary key (#{association.join_primary_key}) that does not match the + associated table's primary key (#{association.klass.primary_key}). + + To fix this, change your fixture from + + #{label}: + #{association.name}: #{value} + + to + + #{label}: + #{association.foreign_key}: **value** + + where **value** is the #{association.join_primary_key} value for the + associated #{association.klass.name} record. + MSG + end + end + + def initialize(fixture, table_rows:, label:, now:) + @table_rows = table_rows + @label = label + @now = now + @row = fixture.to_hash + fill_row_model_attributes + end + + def to_hash + @row + end + + private + def model_metadata + @table_rows.model_metadata + end + + def model_class + @table_rows.model_class + end + + def fill_row_model_attributes + return unless model_class + fill_timestamps + interpolate_label + model_class.composite_primary_key? ? generate_composite_primary_key : generate_primary_key + resolve_enums + resolve_sti_reflections + end + + def reflection_class + @reflection_class ||= if @row.include?(model_metadata.inheritance_column_name) + @row[model_metadata.inheritance_column_name].constantize rescue model_class + else + model_class + end + end + + def fill_timestamps + # fill in timestamp columns if they aren't specified and the model is set to record_timestamps + if model_class.record_timestamps + model_metadata.timestamp_column_names.each do |c_name| + @row[c_name] = @now unless @row.key?(c_name) + end + end + end + + def interpolate_label + # interpolate the fixture label + @row.each do |key, value| + @row[key] = value.gsub("$LABEL", @label.to_s) if value.is_a?(String) + end + end + + def generate_primary_key + pk = model_metadata.primary_key_name + + unless column_defined?(pk) + @row[pk] = ActiveRecord::FixtureSet.identify(@label, model_metadata.column_type(pk)) + end + end + + def generate_composite_primary_key + composite_key = ActiveRecord::FixtureSet.composite_identify(@label, model_metadata.primary_key_name) + composite_key.each do |column, value| + next if column_defined?(column) + + @row[column] = value + end + end + + def column_defined?(col) + !model_metadata.has_column?(col) || @row.include?(col) + end + + def resolve_enums + reflection_class.defined_enums.each do |name, values| + if @row.include?(name) + @row[name] = values.fetch(@row[name], @row[name]) + end + end + end + + def resolve_sti_reflections + # If STI is used, find the correct subclass for association reflection + reflection_class._reflections.each_value do |association| + case association.macro + when :belongs_to + # Do not replace association name with association foreign key if they are named the same + fk_name = association.join_foreign_key + + if association.name.to_s != fk_name && value = @row.delete(association.name.to_s) + if association.polymorphic? + if value.sub!(/\s*\(([^)]*)\)\s*$/, "") + # support polymorphic belongs_to as "label (Type)" + @row[association.join_foreign_type] = $1 + end + elsif association.join_primary_key != association.klass.primary_key + raise PrimaryKeyError.new(@label, association, value) + end + + if fk_name.is_a?(Array) + composite_key = ActiveRecord::FixtureSet.composite_identify(value, fk_name) + composite_key.each do |column, value| + next if column_defined?(column) + + @row[column] = value + end + else + fk_type = reflection_class.type_for_attribute(fk_name).type + @row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type) + end + end + when :has_many + if association.options[:through] + add_join_records(HasManyThroughProxy.new(association)) + end + end + end + end + + def add_join_records(association) + # This is the case when the join table has no fixtures file + if (targets = @row.delete(association.name.to_s)) + table_name = association.join_table + column_type = association.primary_key_type + lhs_key = association.lhs_key + rhs_key = association.rhs_key + + targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/) + joins = targets.map do |target| + join = { lhs_key => @row[model_metadata.primary_key_name], + rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) } + association.timestamp_column_names.each do |col| + join[col] = @now + end + join + end + @table_rows.tables[table_name].concat(joins) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/fixture_set/table_rows.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/fixture_set/table_rows.rb new file mode 100644 index 00000000..b7173729 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/fixture_set/table_rows.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require "active_record/fixture_set/table_row" +require "active_record/fixture_set/model_metadata" + +module ActiveRecord + class FixtureSet + class TableRows # :nodoc: + def initialize(table_name, model_class:, fixtures:) + @model_class = model_class + + # track any join tables we need to insert later + @tables = Hash.new { |h, table| h[table] = [] } + + # ensure this table is loaded before any HABTM associations + @tables[table_name] = nil + + build_table_rows_from(table_name, fixtures) + end + + attr_reader :tables, :model_class + + def to_hash + @tables.transform_values { |rows| rows.map(&:to_hash) } + end + + def model_metadata + @model_metadata ||= ModelMetadata.new(model_class) + end + + private + def build_table_rows_from(table_name, fixtures) + now = ActiveRecord.default_timezone == :utc ? Time.now.utc : Time.now + + @tables[table_name] = fixtures.map do |label, fixture| + TableRow.new( + fixture, + table_rows: self, + label: label, + now: now, + ) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/fixtures.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/fixtures.rb new file mode 100644 index 00000000..43633848 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/fixtures.rb @@ -0,0 +1,849 @@ +# frozen_string_literal: true + +require "erb" +require "yaml" +require "active_support/dependencies" +require "active_support/core_ext/digest/uuid" +require "active_record/test_fixtures" + +module ActiveRecord + class FixtureClassNotFound < ActiveRecord::ActiveRecordError # :nodoc: + end + + # = Active Record \Fixtures + # + # \Fixtures are a way of organizing data that you want to test against; in short, sample data. + # + # They are stored in YAML files, one file per model, which are by default placed in either + # /test/fixtures/ or in the test/fixtures + # folder under any of your application's engines. + # + # The location can also be changed with ActiveSupport::TestCase.fixture_paths=, + # once you have require "rails/test_help" in your +test_helper.rb+. + # + # The fixture file ends with the +.yml+ file extension, for example: + # /test/fixtures/web_sites.yml). + # + # The format of a fixture file looks like this: + # + # rubyonrails: + # id: 1 + # name: Ruby on Rails + # url: http://www.rubyonrails.org + # + # google: + # id: 2 + # name: Google + # url: http://www.google.com + # + # This fixture file includes two fixtures. Each YAML fixture (i.e. record) is given a name and + # is followed by an indented list of key/value pairs in the "key: value" format. Records are + # separated by a blank line for your viewing pleasure. + # + # == Ordering + # + # Fixtures by default are unordered. This is because the maps in YAML are unordered. + # + # If you want ordered fixtures, use the omap YAML type. + # See https://yaml.org/type/omap.html for the specification. + # + # You will need ordered fixtures when you have foreign key constraints + # on keys in the same table. This is commonly needed for tree structures. + # + # For example: + # + # --- !omap + # - parent: + # id: 1 + # parent_id: NULL + # title: Parent + # - child: + # id: 2 + # parent_id: 1 + # title: Child + # + # == Using Fixtures in Test Cases + # + # Since fixtures are a testing construct, we use them in our unit and functional tests. There + # are two ways to use the fixtures, but first let's take a look at a sample unit test: + # + # require "test_helper" + # + # class WebSiteTest < ActiveSupport::TestCase + # test "web_site_count" do + # assert_equal 2, WebSite.count + # end + # end + # + # By default, +test_helper.rb+ will load all of your fixtures into your test + # database, so this test will succeed. + # + # The testing environment will automatically load all the fixtures into the database before each + # test. To ensure consistent data, the environment deletes the fixtures before running the load. + # + # In addition to being available in the database, the fixture's data may also be accessed by + # using a special dynamic method, which has the same name as the model. + # + # Passing in a fixture name to this dynamic method returns the fixture matching this name: + # + # test "find one" do + # assert_equal "Ruby on Rails", web_sites(:rubyonrails).name + # end + # + # Passing in multiple fixture names returns all fixtures matching these names: + # + # test "find all by name" do + # assert_equal 2, web_sites(:rubyonrails, :google).length + # end + # + # Passing in no arguments returns all fixtures: + # + # test "find all" do + # assert_equal 2, web_sites.length + # end + # + # Passing in any fixture name that does not exist will raise StandardError: + # + # test "find by name that does not exist" do + # assert_raise(StandardError) { web_sites(:reddit) } + # end + # + # If the model names conflicts with a +TestCase+ methods, you can use the generic +fixture+ accessor + # + # test "generic find" do + # assert_equal "Ruby on Rails", fixture(:web_sites, :rubyonrails).name + # end + # + # Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the + # following tests: + # + # test "find_alt_method_1" do + # assert_equal "Ruby on Rails", @web_sites['rubyonrails']['name'] + # end + # + # test "find_alt_method_2" do + # assert_equal "Ruby on Rails", @rubyonrails.name + # end + # + # In order to use these methods to access fixtured data within your test cases, you must specify one of the + # following in your ActiveSupport::TestCase-derived class: + # + # - to fully enable instantiated fixtures (enable alternate methods #1 and #2 above) + # self.use_instantiated_fixtures = true + # + # - create only the hash for the fixtures, do not 'find' each instance (enable alternate method #1 only) + # self.use_instantiated_fixtures = :no_instances + # + # Using either of these alternate methods incurs a performance hit, as the fixtured data must be fully + # traversed in the database to create the fixture hash and/or instance variables. This is expensive for + # large sets of fixtured data. + # + # == Dynamic fixtures with \ERB + # + # Sometimes you don't care about the content of the fixtures as much as you care about the volume. + # In these cases, you can mix ERB in with your YAML fixtures to create a bunch of fixtures for load + # testing, like: + # + # <% 1.upto(1000) do |i| %> + # fix_<%= i %>: + # id: <%= i %> + # name: guy_<%= i %> + # <% end %> + # + # This will create 1000 very simple fixtures. + # + # Using ERB, you can also inject dynamic values into your fixtures with inserts like + # <%= Date.today.strftime("%Y-%m-%d") %>. + # This is however a feature to be used with some caution. The point of fixtures are that they're + # stable units of predictable sample data. If you feel that you need to inject dynamic values, then + # perhaps you should reexamine whether your application is properly testable. Hence, dynamic values + # in fixtures are to be considered a code smell. + # + # Helper methods defined in a fixture will not be available in other fixtures, to prevent against + # unwanted inter-test dependencies. Methods used by multiple fixtures should be defined in a module + # that is included in ActiveRecord::FixtureSet.context_class. + # + # - define a helper method in test_helper.rb + # module FixtureFileHelpers + # def file_sha(path) + # OpenSSL::Digest::SHA256.hexdigest(File.read(Rails.root.join('test/fixtures', path))) + # end + # end + # ActiveRecord::FixtureSet.context_class.include FixtureFileHelpers + # + # - use the helper method in a fixture + # photo: + # name: kitten.png + # sha: <%= file_sha 'files/kitten.png' %> + # + # == Transactional Tests + # + # Test cases can use begin+rollback to isolate their changes to the database instead of having to + # delete+insert for every test case. + # + # class FooTest < ActiveSupport::TestCase + # self.use_transactional_tests = true + # + # test "godzilla" do + # assert_not_empty Foo.all + # Foo.destroy_all + # assert_empty Foo.all + # end + # + # test "godzilla aftermath" do + # assert_not_empty Foo.all + # end + # end + # + # If you preload your test database with all fixture data (probably by running bin/rails db:fixtures:load) + # and use transactional tests, then you may omit all fixtures declarations in your test cases since + # all the data's already there and every case rolls back its changes. + # + # In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to + # true. This will provide access to fixture data for every table that has been loaded through + # fixtures (depending on the value of +use_instantiated_fixtures+). + # + # When *not* to use transactional tests: + # + # 1. You're testing whether a transaction works correctly. Nested transactions don't commit until + # all parent transactions commit, particularly, the fixtures transaction which is begun in setup + # and rolled back in teardown. Thus, you won't be able to verify + # the results of your transaction until Active Record supports nested transactions or savepoints (in progress). + # 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM. + # Use InnoDB, MaxDB, or NDB instead. + # + # == Advanced Fixtures + # + # Fixtures that don't specify an ID get some extra features: + # + # * Stable, autogenerated IDs + # * Label references for associations (belongs_to, has_one, has_many) + # * HABTM associations as inline lists + # + # There are some more advanced features available even if the id is specified: + # + # * Autofilled timestamp columns + # * Fixture label interpolation + # * Support for YAML defaults + # + # === Stable, Autogenerated IDs + # + # Here, have a monkey fixture: + # + # george: + # id: 1 + # name: George the Monkey + # + # reginald: + # id: 2 + # name: Reginald the Pirate + # + # Each of these fixtures has two unique identifiers: one for the database + # and one for the humans. Why don't we generate the primary key instead? + # Hashing each fixture's label yields a consistent ID: + # + # george: # generated id: 503576764 + # name: George the Monkey + # + # reginald: # generated id: 324201669 + # name: Reginald the Pirate + # + # Active Record looks at the fixture's model class, discovers the correct + # primary key, and generates it right before inserting the fixture + # into the database. + # + # The generated ID for a given label is constant, so we can discover + # any fixture's ID without loading anything, as long as we know the label. + # + # === Label references for associations (+belongs_to+, +has_one+, +has_many+) + # + # Specifying foreign keys in fixtures can be very fragile, not to + # mention difficult to read. Since Active Record can figure out the ID of + # any fixture from its label, you can specify FK's by label instead of ID. + # + # ==== +belongs_to+ + # + # Let's break out some more monkeys and pirates. + # + # ### in pirates.yml + # + # reginald: + # id: 1 + # name: Reginald the Pirate + # monkey_id: 1 + # + # + # + # ### in monkeys.yml + # + # george: + # id: 1 + # name: George the Monkey + # pirate_id: 1 + # + # Add a few more monkeys and pirates and break this into multiple files, + # and it gets pretty hard to keep track of what's going on. Let's + # use labels instead of IDs: + # + # ### in pirates.yml + # + # reginald: + # name: Reginald the Pirate + # monkey: george + # + # + # + # ### in monkeys.yml + # + # george: + # name: George the Monkey + # pirate: reginald + # + # Pow! All is made clear. Active Record reflects on the fixture's model class, + # finds all the +belongs_to+ associations, and allows you to specify + # a target *label* for the *association* (monkey: george) rather than + # a target *id* for the *FK* (monkey_id: 1). + # + # ==== Polymorphic +belongs_to+ + # + # Supporting polymorphic relationships is a little bit more complicated, since + # Active Record needs to know what type your association is pointing at. Something + # like this should look familiar: + # + # ### in fruit.rb + # + # belongs_to :eater, polymorphic: true + # + # + # + # ### in fruits.yml + # + # apple: + # id: 1 + # name: apple + # eater_id: 1 + # eater_type: Monkey + # + # Can we do better? You bet! + # + # apple: + # eater: george (Monkey) + # + # Just provide the polymorphic target type and Active Record will take care of the rest. + # + # ==== +has_and_belongs_to_many+ or has_many :through + # + # \Time to give our monkey some fruit. + # + # ### in monkeys.yml + # + # george: + # id: 1 + # name: George the Monkey + # + # + # + # ### in fruits.yml + # + # apple: + # id: 1 + # name: apple + # + # orange: + # id: 2 + # name: orange + # + # grape: + # id: 3 + # name: grape + # + # + # + # ### in fruits_monkeys.yml + # + # apple_george: + # fruit_id: 1 + # monkey_id: 1 + # + # orange_george: + # fruit_id: 2 + # monkey_id: 1 + # + # grape_george: + # fruit_id: 3 + # monkey_id: 1 + # + # Let's make the HABTM fixture go away. + # + # ### in monkeys.yml + # + # george: + # id: 1 + # name: George the Monkey + # fruits: apple, orange, grape + # + # + # + # ### in fruits.yml + # + # apple: + # name: apple + # + # orange: + # name: orange + # + # grape: + # name: grape + # + # Zap! No more fruits_monkeys.yml file. We've specified the list of fruits + # on George's fixture, but we could've just as easily specified a list + # of monkeys on each fruit. As with +belongs_to+, Active Record reflects on + # the fixture's model class and discovers the +has_and_belongs_to_many+ + # associations. + # + # === Autofilled \Timestamp Columns + # + # If your table/model specifies any of Active Record's + # standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+), + # they will automatically be set to Time.now. + # + # If you've set specific values, they'll be left alone. + # + # === Fixture label interpolation + # + # The label of the current fixture is always available as a column value: + # + # geeksomnia: + # name: Geeksomnia's Account + # subdomain: $LABEL + # email: $LABEL@email.com + # + # Also, sometimes (like when porting older join table fixtures) you'll need + # to be able to get a hold of the identifier for a given label. ERB + # to the rescue: + # + # george_reginald: + # monkey_id: <%= ActiveRecord::FixtureSet.identify(:reginald) %> + # pirate_id: <%= ActiveRecord::FixtureSet.identify(:george) %> + # + # If the model uses UUID values for identifiers, add the +:uuid+ argument: + # + # ActiveRecord::FixtureSet.identify(:boaty_mcboatface, :uuid) + # + # === Support for YAML defaults + # + # You can set and reuse defaults in your fixtures YAML file. + # This is the same technique used in the +database.yml+ file to specify + # defaults: + # + # DEFAULTS: &DEFAULTS + # created_on: <%= 3.weeks.ago.to_fs(:db) %> + # + # first: + # name: Smurf + # <<: *DEFAULTS + # + # second: + # name: Fraggle + # <<: *DEFAULTS + # + # Any fixture labeled "DEFAULTS" is safely ignored. + # + # Besides using "DEFAULTS", you can also specify what fixtures will + # be ignored by setting "ignore" in "_fixture" section. + # + # # users.yml + # _fixture: + # ignore: + # - base + # # or use "ignore: base" when there is only one fixture that needs to be ignored. + # + # base: &base + # admin: false + # introduction: "This is a default description" + # + # admin: + # <<: *base + # admin: true + # + # visitor: + # <<: *base + # + # In the above example, 'base' will be ignored when creating fixtures. + # This can be used for common attributes inheriting. + # + # == Composite Primary Key Fixtures + # + # Fixtures for composite primary key tables are fairly similar to normal tables. + # When using an id column, the column may be omitted as usual: + # + # # app/models/book.rb + # class Book < ApplicationRecord + # self.primary_key = [:author_id, :id] + # belongs_to :author + # end + # + # + # + # # books.yml + # alices_adventure_in_wonderland: + # author_id: <%= ActiveRecord::FixtureSet.identify(:lewis_carroll) %> + # title: "Alice's Adventures in Wonderland" + # + # However, in order to support composite primary key relationships, + # you must use the `composite_identify` method: + # + # # app/models/book_orders.rb + # class BookOrder < ApplicationRecord + # self.primary_key = [:shop_id, :id] + # belongs_to :order, foreign_key: [:shop_id, :order_id] + # belongs_to :book, foreign_key: [:author_id, :book_id] + # end + # + # + # + # # book_orders.yml + # alices_adventure_in_wonderland_in_books: + # author: lewis_carroll + # book_id: <%= ActiveRecord::FixtureSet.composite_identify( + # :alices_adventure_in_wonderland, Book.primary_key)[:id] %> + # shop: book_store + # order_id: <%= ActiveRecord::FixtureSet.composite_identify( + # :books, Order.primary_key)[:id] %> + # + # == Configure the fixture model class + # + # It's possible to set the fixture's model class directly in the YAML file. + # This is helpful when fixtures are loaded outside tests and + # +set_fixture_class+ is not available (e.g. + # when running bin/rails db:fixtures:load). + # + # _fixture: + # model_class: User + # david: + # name: David + # + # Any fixtures labeled "_fixture" are safely ignored. + class FixtureSet + require "active_record/fixture_set/file" + require "active_record/fixture_set/render_context" + require "active_record/fixture_set/table_rows" + + #-- + # An instance of FixtureSet is normally stored in a single YAML file and + # possibly in a folder with the same name. + #++ + + MAX_ID = 2**30 - 1 + + @@all_cached_fixtures = Hash.new { |h, k| h[k] = {} } + + cattr_accessor :all_loaded_fixtures, default: {} + + class << self + def default_fixture_model_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc: + config.pluralize_table_names ? + fixture_set_name.singularize.camelize : + fixture_set_name.camelize + end + + def default_fixture_table_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc: + "#{ config.table_name_prefix }"\ + "#{ fixture_set_name.tr('/', '_') }"\ + "#{ config.table_name_suffix }".to_sym + end + + def reset_cache + @@all_cached_fixtures.clear + end + + def cache_for_connection_pool(connection_pool) + @@all_cached_fixtures[connection_pool] + end + + def fixture_is_cached?(connection_pool, table_name) + cache_for_connection_pool(connection_pool)[table_name] + end + + def cached_fixtures(connection_pool, keys_to_fetch = nil) + if keys_to_fetch + cache_for_connection_pool(connection_pool).values_at(*keys_to_fetch) + else + cache_for_connection_pool(connection_pool).values + end + end + + def cache_fixtures(connection_pool, fixtures_map) + cache_for_connection_pool(connection_pool).update(fixtures_map) + end + + def instantiate_fixtures(object, fixture_set, load_instances = true) + return unless load_instances + fixture_set.each do |fixture_name, fixture| + object.instance_variable_set "@#{fixture_name}", fixture.find + rescue FixtureClassNotFound + nil + end + end + + def instantiate_all_loaded_fixtures(object, load_instances = true) + all_loaded_fixtures.each_value do |fixture_set| + instantiate_fixtures(object, fixture_set, load_instances) + end + end + + def create_fixtures(fixtures_directories, fixture_set_names, class_names = {}, config = ActiveRecord::Base) + fixture_set_names = Array(fixture_set_names).map(&:to_s) + class_names.stringify_keys! + + connection_pool = config.connection_pool + fixture_files_to_read = fixture_set_names.reject do |fs_name| + fixture_is_cached?(connection_pool, fs_name) + end + + if fixture_files_to_read.any? + fixtures_map = read_and_insert( + Array(fixtures_directories), + fixture_files_to_read, + class_names, + connection_pool, + ) + cache_fixtures(connection_pool, fixtures_map) + end + cached_fixtures(connection_pool, fixture_set_names) + end + + # Returns a consistent, platform-independent identifier for +label+. + # + # \Integer identifiers are values less than 2^30. UUIDs are RFC 4122 version 5 SHA-1 hashes. + def identify(label, column_type = :integer) + if column_type == :uuid + Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, label.to_s) + else + Zlib.crc32(label.to_s) % MAX_ID + end + end + + # Returns a consistent, platform-independent hash representing a mapping + # between the label and the subcomponents of the provided composite key. + # + # Example: + # + # composite_identify("label", [:a, :b, :c]) # => { a: hash_1, b: hash_2, c: hash_3 } + def composite_identify(label, key) + key + .index_with + .with_index { |sub_key, index| (identify(label) << index) % MAX_ID } + .with_indifferent_access + end + + # Superclass for the evaluation contexts used by \ERB fixtures. + def context_class + @context_class ||= Class.new + end + + private + def read_and_insert(fixtures_directories, fixture_files, class_names, connection_pool) # :nodoc: + fixtures_map = {} + directory_glob = "{#{fixtures_directories.join(",")}}" + fixture_sets = fixture_files.map do |fixture_set_name| + klass = class_names[fixture_set_name] + fixtures_map[fixture_set_name] = new( # ActiveRecord::FixtureSet.new + nil, + fixture_set_name, + klass, + ::File.join(directory_glob, fixture_set_name) + ) + end + update_all_loaded_fixtures(fixtures_map) + + insert(fixture_sets, connection_pool) + + fixtures_map + end + + def insert(fixture_sets, connection_pool) # :nodoc: + fixture_sets_by_pool = fixture_sets.group_by do |fixture_set| + if fixture_set.model_class + fixture_set.model_class.connection_pool + else + connection_pool + end + end + + fixture_sets_by_pool.each do |pool, set| + table_rows_for_connection = Hash.new { |h, k| h[k] = [] } + + set.each do |fixture_set| + fixture_set.table_rows.each do |table, rows| + table_rows_for_connection[table].unshift(*rows) + end + end + + pool.with_connection do |conn| + conn.insert_fixtures_set(table_rows_for_connection, table_rows_for_connection.keys) + + check_all_foreign_keys_valid!(conn) + + # Cap primary key sequences to max(pk). + if conn.respond_to?(:reset_pk_sequence!) + set.each { |fs| conn.reset_pk_sequence!(fs.table_name) } + end + end + end + end + + def check_all_foreign_keys_valid!(conn) + return unless ActiveRecord.verify_foreign_keys_for_fixtures + + begin + conn.check_all_foreign_keys_valid! + rescue ActiveRecord::StatementInvalid => e + raise "Foreign key violations found in your fixture data. Ensure you aren't referring to labels that don't exist on associations. Error from database:\n\n#{e.message}" + end + end + + def update_all_loaded_fixtures(fixtures_map) # :nodoc: + all_loaded_fixtures.update(fixtures_map) + end + end + + attr_reader :table_name, :name, :fixtures, :model_class, :ignored_fixtures, :config + + def initialize(_, name, class_name, path, config = ActiveRecord::Base) + @name = name + @path = path + @config = config + + self.model_class = class_name + @fixtures = read_fixture_files(path) + + @table_name = model_class&.table_name || self.class.default_fixture_table_name(name, config) + end + + def [](x) + fixtures[x] + end + + def []=(k, v) + fixtures[k] = v + end + + def each(&block) + fixtures.each(&block) + end + + def size + fixtures.size + end + + # Returns a hash of rows to be inserted. The key is the table, the value is + # a list of rows to insert to that table. + def table_rows + # allow specifying fixtures to be ignored by setting `ignore` in `_fixture` section + fixtures.except!(*ignored_fixtures) + + TableRows.new( + table_name, + model_class: model_class, + fixtures: fixtures, + ).to_hash + end + + private + def model_class=(class_name) + if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any? + @model_class = class_name + else + @model_class = class_name.safe_constantize if class_name + end + end + + def ignored_fixtures=(base) + @ignored_fixtures = + case base + when Array + base + when String + [base] + else + [] + end + + @ignored_fixtures << "DEFAULTS" unless @ignored_fixtures.include?("DEFAULTS") + @ignored_fixtures.compact + end + + # Loads the fixtures from the YAML file at +path+. + # If the file sets the +model_class+ and current instance value is not set, + # it uses the file value. + + def read_fixture_files(path) + yaml_files = Dir["#{path}{.yml,/{**,*}/*.yml}"].select { |f| + ::File.file?(f) + } + + raise ArgumentError, "No fixture files found for #{@name}" if yaml_files.empty? + + yaml_files.each_with_object({}) do |file, fixtures| + FixtureSet::File.open(file) do |fh| + self.model_class ||= fh.model_class if fh.model_class + self.model_class ||= default_fixture_model_class + self.ignored_fixtures ||= fh.ignored_fixtures + fh.each do |fixture_name, row| + fixtures[fixture_name] = ActiveRecord::Fixture.new(row, model_class) + end + end + end + end + + def default_fixture_model_class + klass = ActiveRecord::FixtureSet.default_fixture_model_name(@name, @config).safe_constantize + klass if klass && klass < ActiveRecord::Base + end + end + + class Fixture # :nodoc: + include Enumerable + + class FixtureError < StandardError # :nodoc: + end + + class FormatError < FixtureError # :nodoc: + end + + attr_reader :model_class, :fixture + + def initialize(fixture, model_class) + @fixture = fixture + @model_class = model_class + end + + def class_name + model_class.name if model_class + end + + def each(&block) + fixture.each(&block) + end + + def [](key) + fixture[key] + end + + alias :to_hash :fixture + + def find + raise FixtureClassNotFound, "No class attached to find." unless model_class + object = model_class.unscoped do + pk_clauses = fixture.slice(*Array(model_class.primary_key)) + model_class.find_by!(pk_clauses) + end + # Fixtures can't be eagerly loaded + object.instance_variable_set(:@strict_loading, false) + object + end + end +end + +ActiveSupport.run_load_hooks :active_record_fixture_set, ActiveRecord::FixtureSet diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/future_result.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/future_result.rb new file mode 100644 index 00000000..54539088 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/future_result.rb @@ -0,0 +1,182 @@ +# frozen_string_literal: true + +module ActiveRecord + class FutureResult # :nodoc: + class Complete + attr_reader :result + delegate :empty?, :to_a, to: :result + + def initialize(result) + @result = result + end + + def pending? + false + end + + def canceled? + false + end + + def then(&block) + Promise::Complete.new(@result.then(&block)) + end + end + + class EventBuffer + def initialize(future_result, instrumenter) + @future_result = future_result + @instrumenter = instrumenter + @events = [] + end + + def instrument(name, payload = {}, &block) + event = @instrumenter.new_event(name, payload) + begin + event.record(&block) + ensure + @events << event + end + end + + def flush + events, @events = @events, [] + events.each do |event| + event.payload[:lock_wait] = @future_result.lock_wait + ActiveSupport::Notifications.publish_event(event) + end + end + end + + Canceled = Class.new(ActiveRecordError) + + def self.wrap(result) + case result + when self, Complete + result + else + Complete.new(result) + end + end + + delegate :empty?, :to_a, to: :result + + attr_reader :lock_wait + + def initialize(pool, *args, **kwargs) + @mutex = Mutex.new + + @session = nil + @pool = pool + @args = args + @kwargs = kwargs + + @pending = true + @error = nil + @result = nil + @instrumenter = ActiveSupport::Notifications.instrumenter + @event_buffer = nil + end + + def then(&block) + Promise.new(self, block) + end + + def schedule!(session) + @session = session + @pool.schedule_query(self) + end + + def execute!(connection) + execute_query(connection) + end + + def cancel + @pending = false + @error = Canceled + self + end + + def execute_or_skip + return unless pending? + + @session.synchronize do + return unless pending? + + @pool.with_connection do |connection| + return unless @mutex.try_lock + begin + if pending? + @event_buffer = EventBuffer.new(self, @instrumenter) + ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = @event_buffer + + execute_query(connection, async: true) + end + ensure + @mutex.unlock + end + end + end + end + + def result + execute_or_wait + @event_buffer&.flush + + if canceled? + raise Canceled + elsif @error + raise @error + else + @result + end + end + + def pending? + @pending && (!@session || @session.active?) + end + + def canceled? + @session && !@session.active? + end + + private + def execute_or_wait + if pending? + start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) + @mutex.synchronize do + if pending? + @pool.with_connection do |connection| + execute_query(connection) + end + else + @lock_wait = (Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start) + end + end + else + @lock_wait = 0.0 + end + end + + def execute_query(connection, async: false) + @result = exec_query(connection, *@args, **@kwargs, async: async) + rescue => error + @error = error + ensure + @pending = false + end + + def exec_query(connection, *args, **kwargs) + connection.raw_exec_query(*args, **kwargs) + end + + class SelectAll < FutureResult # :nodoc: + private + def exec_query(*, **) + super + rescue ::RangeError + ActiveRecord::Result.empty + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/gem_version.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/gem_version.rb new file mode 100644 index 00000000..b01ed9fa --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/gem_version.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveRecord + # Returns the currently loaded version of Active Record as a +Gem::Version+. + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 8 + MINOR = 0 + TINY = 2 + PRE = nil + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/inheritance.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/inheritance.rb new file mode 100644 index 00000000..acf4f448 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/inheritance.rb @@ -0,0 +1,366 @@ +# frozen_string_literal: true + +require "active_support/inflector" +require "active_support/core_ext/hash/indifferent_access" + +module ActiveRecord + # = Single table inheritance + # + # Active Record allows inheritance by storing the name of the class in a column that by + # default is named "type" (can be changed by overwriting Base.inheritance_column). + # This means that an inheritance looking like this: + # + # class Company < ActiveRecord::Base; end + # class Firm < Company; end + # class Client < Company; end + # class PriorityClient < Client; end + # + # When you do Firm.create(name: "37signals"), this record will be saved in + # the companies table with type = "Firm". You can then fetch this row again using + # Company.where(name: '37signals').first and it will return a Firm object. + # + # Be aware that because the type column is an attribute on the record every new + # subclass will instantly be marked as dirty and the type column will be included + # in the list of changed attributes on the record. This is different from non + # Single Table Inheritance(STI) classes: + # + # Company.new.changed? # => false + # Firm.new.changed? # => true + # Firm.new.changes # => {"type"=>["","Firm"]} + # + # If you don't have a type column defined in your table, single-table inheritance won't + # be triggered. In that case, it'll work just like normal subclasses with no special magic + # for differentiating between them or reloading the right type with find. + # + # Note, all the attributes for all the cases are kept in the same table. + # Read more: + # * https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html + # + module Inheritance + extend ActiveSupport::Concern + + included do + class_attribute :store_full_class_name, instance_writer: false, default: true + + # Determines whether to store the full constant name including namespace when using STI. + # This is true, by default. + class_attribute :store_full_sti_class, instance_writer: false, default: true + + set_base_class + end + + module ClassMethods + # Determines if one of the attributes passed in is the inheritance column, + # and if the inheritance column is attr accessible, it initializes an + # instance of the given subclass instead of the base class. + def new(attributes = nil, &block) + if abstract_class? || self == Base + raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated." + end + + if _has_attribute?(inheritance_column) + subclass = subclass_from_attributes(attributes) + + if subclass.nil? && scope_attributes = current_scope&.scope_for_create + subclass = subclass_from_attributes(scope_attributes) + end + + if subclass.nil? && base_class? + subclass = subclass_from_attributes(column_defaults) + end + end + + if subclass && subclass != self + subclass.new(attributes, &block) + else + super + end + end + + # Returns +true+ if this does not need STI type condition. Returns + # +false+ if STI type condition needs to be applied. + def descends_from_active_record? + if self == Base + false + elsif superclass.abstract_class? + superclass.descends_from_active_record? + else + superclass == Base || !columns_hash.include?(inheritance_column) + end + end + + def finder_needs_type_condition? # :nodoc: + # This is like this because benchmarking justifies the strange :false stuff + :true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true) + end + + # Returns the first class in the inheritance hierarchy that descends from either an + # abstract class or from ActiveRecord::Base. + # + # Consider the following behaviour: + # + # class ApplicationRecord < ActiveRecord::Base + # self.abstract_class = true + # end + # class Shape < ApplicationRecord + # self.abstract_class = true + # end + # Polygon = Class.new(Shape) + # Square = Class.new(Polygon) + # + # ApplicationRecord.base_class # => ApplicationRecord + # Shape.base_class # => Shape + # Polygon.base_class # => Polygon + # Square.base_class # => Polygon + attr_reader :base_class + + # Returns whether the class is a base class. + # See #base_class for more information. + def base_class? + base_class == self + end + + # Set this to +true+ if this is an abstract class (see + # abstract_class?). + # If you are using inheritance with Active Record and don't want a class + # to be considered as part of the STI hierarchy, you must set this to + # true. + # +ApplicationRecord+, for example, is generated as an abstract class. + # + # Consider the following default behavior: + # + # Shape = Class.new(ActiveRecord::Base) + # Polygon = Class.new(Shape) + # Square = Class.new(Polygon) + # + # Shape.table_name # => "shapes" + # Polygon.table_name # => "shapes" + # Square.table_name # => "shapes" + # Shape.create! # => # + # Polygon.create! # => # + # Square.create! # => # + # + # However, when using abstract_class, +Shape+ is omitted from + # the hierarchy: + # + # class Shape < ActiveRecord::Base + # self.abstract_class = true + # end + # Polygon = Class.new(Shape) + # Square = Class.new(Polygon) + # + # Shape.table_name # => nil + # Polygon.table_name # => "polygons" + # Square.table_name # => "polygons" + # Shape.create! # => NotImplementedError: Shape is an abstract class and cannot be instantiated. + # Polygon.create! # => # + # Square.create! # => # + # + # Note that in the above example, to disallow the creation of a plain + # +Polygon+, you should use validates :type, presence: true, + # instead of setting it as an abstract class. This way, +Polygon+ will + # stay in the hierarchy, and Active Record will continue to correctly + # derive the table name. + attr_accessor :abstract_class + + # Returns whether this class is an abstract class or not. + def abstract_class? + @abstract_class == true + end + + # Sets the application record class for Active Record + # + # This is useful if your application uses a different class than + # ApplicationRecord for your primary abstract class. This class + # will share a database connection with Active Record. It is the class + # that connects to your primary database. + def primary_abstract_class + if ActiveRecord.application_record_class && ActiveRecord.application_record_class.name != name + raise ArgumentError, "The `primary_abstract_class` is already set to #{ActiveRecord.application_record_class.inspect}. There can only be one `primary_abstract_class` in an application." + end + + self.abstract_class = true + ActiveRecord.application_record_class = self + end + + # Returns the value to be stored in the inheritance column for STI. + def sti_name + store_full_sti_class && store_full_class_name ? name : name.demodulize + end + + # Returns the class for the provided +type_name+. + # + # It is used to find the class correspondent to the value stored in the inheritance column. + def sti_class_for(type_name) + if store_full_sti_class && store_full_class_name + type_name.constantize + else + compute_type(type_name) + end + rescue NameError + raise SubclassNotFound, + "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \ + "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \ + "Please rename this column if you didn't intend it to be used for storing the inheritance class " \ + "or overwrite #{name}.inheritance_column to use another column for that information. " \ + "If you wish to disable single-table inheritance for #{name} set " \ + "#{name}.inheritance_column to nil" + end + + # Returns the value to be stored in the polymorphic type column for Polymorphic Associations. + def polymorphic_name + store_full_class_name ? base_class.name : base_class.name.demodulize + end + + # Returns the class for the provided +name+. + # + # It is used to find the class correspondent to the value stored in the polymorphic type column. + def polymorphic_class_for(name) + if store_full_class_name + name.constantize + else + compute_type(name) + end + end + + def dup # :nodoc: + # `initialize_dup` / `initialize_copy` don't work when defined + # in the `singleton_class`. + other = super + other.set_base_class + other + end + + def initialize_clone(other) # :nodoc: + super + set_base_class + end + + protected + # Returns the class type of the record using the current module as a prefix. So descendants of + # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass. + def compute_type(type_name) + if type_name.start_with?("::") + # If the type is prefixed with a scope operator then we assume that + # the type_name is an absolute reference. + type_name.constantize + else + type_candidate = @_type_candidates_cache[type_name] + if type_candidate && type_constant = type_candidate.safe_constantize + return type_constant + end + + # Build a list of candidates to search for + candidates = [] + name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" } + candidates << type_name + + candidates.each do |candidate| + constant = candidate.safe_constantize + if candidate == constant.to_s + @_type_candidates_cache[type_name] = candidate + return constant + end + end + + raise NameError.new("uninitialized constant #{candidates.first}", candidates.first) + end + end + + def set_base_class # :nodoc: + @base_class = if self == Base + self + else + unless self < Base + raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord" + end + + if superclass == Base || superclass.abstract_class? + self + else + superclass.base_class + end + end + end + + private + def inherited(subclass) + super + subclass.set_base_class + subclass.instance_variable_set(:@_type_candidates_cache, Concurrent::Map.new) + subclass.class_eval do + @finder_needs_type_condition = nil + end + end + + # Called by +instantiate+ to decide which class to use for a new + # record instance. For single-table inheritance, we check the record + # for a +type+ column and return the corresponding class. + def discriminate_class_for_record(record) + if using_single_table_inheritance?(record) + find_sti_class(record[inheritance_column]) + else + super + end + end + + def using_single_table_inheritance?(record) + record[inheritance_column].present? && _has_attribute?(inheritance_column) + end + + def find_sti_class(type_name) + type_name = base_class.type_for_attribute(inheritance_column).cast(type_name) + subclass = sti_class_for(type_name) + + unless subclass == self || descendants.include?(subclass) + raise SubclassNotFound, "Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}" + end + + subclass + end + + def type_condition(table = arel_table) + sti_column = table[inheritance_column] + sti_names = ([self] + descendants).map(&:sti_name) + + predicate_builder.build(sti_column, sti_names) + end + + # Detect the subclass from the inheritance column of attrs. If the inheritance column value + # is not self or a valid subclass, raises ActiveRecord::SubclassNotFound + def subclass_from_attributes(attrs) + attrs = attrs.to_h if attrs.respond_to?(:permitted?) + if attrs.is_a?(Hash) + subclass_name = attrs[inheritance_column] || attrs[inheritance_column.to_sym] + + if subclass_name.present? + find_sti_class(subclass_name) + end + end + end + end + + def initialize_dup(other) + super + ensure_proper_type + end + + private + def initialize_internals_callback + super + ensure_proper_type + end + + # Sets the attribute used for single table inheritance to this class name if this is not the + # ActiveRecord::Base descendant. + # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to + # do Reply.new without having to set Reply[Reply.inheritance_column] = "Reply" yourself. + # No such attribute would be set for objects of the Message class in that example. + def ensure_proper_type + klass = self.class + if klass.finder_needs_type_condition? + _write_attribute(klass.inheritance_column, klass.sti_name) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/insert_all.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/insert_all.rb new file mode 100644 index 00000000..c3f97a83 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/insert_all.rb @@ -0,0 +1,328 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" + +module ActiveRecord + class InsertAll # :nodoc: + attr_reader :model, :connection, :inserts, :keys + attr_reader :on_duplicate, :update_only, :returning, :unique_by, :update_sql + + class << self + def execute(relation, ...) + relation.model.with_connection do |c| + new(relation, c, ...).execute + end + end + end + + def initialize(relation, connection, inserts, on_duplicate:, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil) + @relation = relation + @model, @connection, @inserts = relation.model, connection, inserts.map(&:stringify_keys) + @on_duplicate, @update_only, @returning, @unique_by = on_duplicate, update_only, returning, unique_by + @record_timestamps = record_timestamps.nil? ? model.record_timestamps : record_timestamps + + disallow_raw_sql!(on_duplicate) + disallow_raw_sql!(returning) + + if @inserts.empty? + @keys = [] + else + resolve_sti + resolve_attribute_aliases + @keys = @inserts.first.keys + end + + @scope_attributes = relation.scope_for_create.except(@model.inheritance_column) + @keys |= @scope_attributes.keys + @keys = @keys.to_set + + @returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil? + @returning = false if @returning == [] + + @unique_by = find_unique_index_for(@unique_by) + + configure_on_duplicate_update_logic + ensure_valid_options_for_connection! + end + + def execute + return ActiveRecord::Result.empty if inserts.empty? + + message = +"#{model} " + message << "Bulk " if inserts.many? + message << (on_duplicate == :update ? "Upsert" : "Insert") + connection.exec_insert_all to_sql, message + end + + def updatable_columns + @updatable_columns ||= keys - readonly_columns - unique_by_columns + end + + def primary_keys + Array(@model.schema_cache.primary_keys(model.table_name)) + end + + def skip_duplicates? + on_duplicate == :skip + end + + def update_duplicates? + on_duplicate == :update + end + + def map_key_with_value + inserts.map do |attributes| + attributes = attributes.stringify_keys + attributes.merge!(@scope_attributes) + attributes.reverse_merge!(timestamps_for_create) if record_timestamps? + + verify_attributes(attributes) + + keys_including_timestamps.map do |key| + yield key, attributes[key] + end + end + end + + def record_timestamps? + @record_timestamps + end + + # TODO: Consider renaming this method, as it only conditionally extends keys, not always + def keys_including_timestamps + @keys_including_timestamps ||= if record_timestamps? + keys + model.all_timestamp_attributes_in_model + else + keys + end + end + + private + def has_attribute_aliases?(attributes) + attributes.keys.any? { |attribute| model.attribute_alias?(attribute) } + end + + def resolve_sti + return if model.descends_from_active_record? + + sti_type = model.sti_name + @inserts = @inserts.map do |insert| + insert.reverse_merge(model.inheritance_column.to_s => sti_type) + end + end + + def resolve_attribute_aliases + return unless has_attribute_aliases?(@inserts.first) + + @inserts = @inserts.map do |insert| + insert.transform_keys { |attribute| resolve_attribute_alias(attribute) } + end + + @update_only = Array(@update_only).map { |attribute| resolve_attribute_alias(attribute) } if @update_only + @unique_by = Array(@unique_by).map { |attribute| resolve_attribute_alias(attribute) } if @unique_by + end + + def resolve_attribute_alias(attribute) + model.attribute_alias(attribute) || attribute + end + + def configure_on_duplicate_update_logic + if custom_update_sql_provided? && update_only.present? + raise ArgumentError, "You can't set :update_only and provide custom update SQL via :on_duplicate at the same time" + end + + if update_only.present? + @updatable_columns = Array(update_only) + @on_duplicate = :update + elsif custom_update_sql_provided? + @update_sql = on_duplicate + @on_duplicate = :update + elsif @on_duplicate == :update && updatable_columns.empty? + @on_duplicate = :skip + end + end + + def custom_update_sql_provided? + @custom_update_sql_provided ||= Arel.arel_node?(on_duplicate) + end + + def find_unique_index_for(unique_by) + if !connection.supports_insert_conflict_target? + return if unique_by.nil? + + raise ArgumentError, "#{connection.class} does not support :unique_by" + end + + name_or_columns = unique_by || model.primary_key + match = Array(name_or_columns).map(&:to_s) + sorted_match = match.sort + + if index = unique_indexes.find { |i| match.include?(i.name) || Array(i.columns).sort == sorted_match } + index + elsif match == primary_keys + unique_by.nil? ? nil : ActiveRecord::ConnectionAdapters::IndexDefinition.new(model.table_name, "#{model.table_name}_primary_key", true, match) + else + raise ArgumentError, "No unique index found for #{name_or_columns}" + end + end + + def unique_indexes + @model.schema_cache.indexes(model.table_name).select(&:unique) + end + + def ensure_valid_options_for_connection! + if returning && !connection.supports_insert_returning? + raise ArgumentError, "#{connection.class} does not support :returning" + end + + if skip_duplicates? && !connection.supports_insert_on_duplicate_skip? + raise ArgumentError, "#{connection.class} does not support skipping duplicates" + end + + if update_duplicates? && !connection.supports_insert_on_duplicate_update? + raise ArgumentError, "#{connection.class} does not support upsert" + end + + if unique_by && !connection.supports_insert_conflict_target? + raise ArgumentError, "#{connection.class} does not support :unique_by" + end + end + + + def to_sql + connection.build_insert_sql(ActiveRecord::InsertAll::Builder.new(self)) + end + + + def readonly_columns + primary_keys + model.readonly_attributes + end + + def unique_by_columns + Array(unique_by&.columns) + end + + + def verify_attributes(attributes) + if keys_including_timestamps != attributes.keys.to_set + raise ArgumentError, "All objects being inserted must have the same keys" + end + end + + def disallow_raw_sql!(value) + return if !value.is_a?(String) || Arel.arel_node?(value) + + raise ArgumentError, "Dangerous query method (method whose arguments are used as raw " \ + "SQL) called: #{value}. " \ + "Known-safe values can be passed " \ + "by wrapping them in Arel.sql()." + end + + def timestamps_for_create + model.all_timestamp_attributes_in_model.index_with(connection.high_precision_current_timestamp) + end + + class Builder # :nodoc: + attr_reader :model + + delegate :skip_duplicates?, :update_duplicates?, :keys, :keys_including_timestamps, :record_timestamps?, to: :insert_all + + def initialize(insert_all) + @insert_all, @model, @connection = insert_all, insert_all.model, insert_all.connection + end + + def into + "INTO #{model.quoted_table_name} (#{columns_list})" + end + + def values_list + types = extract_types_from_columns_on(model.table_name, keys: keys_including_timestamps) + + values_list = insert_all.map_key_with_value do |key, value| + next value if Arel::Nodes::SqlLiteral === value + ActiveModel::Type::SerializeCastValue.serialize(type = types[key], type.cast(value)) + end + + connection.visitor.compile(Arel::Nodes::ValuesList.new(values_list)) + end + + def returning + return unless insert_all.returning + + if insert_all.returning.is_a?(String) + insert_all.returning + else + Array(insert_all.returning).map do |attribute| + if model.attribute_alias?(attribute) + "#{quote_column(model.attribute_alias(attribute))} AS #{quote_column(attribute)}" + else + quote_column(attribute) + end + end.join(",") + end + end + + def conflict_target + if index = insert_all.unique_by + sql = +"(#{format_columns(index.columns)})" + sql << " WHERE #{index.where}" if index.where + sql + elsif update_duplicates? + "(#{format_columns(insert_all.primary_keys)})" + end + end + + def updatable_columns + quote_columns(insert_all.updatable_columns) + end + + def touch_model_timestamps_unless(&block) + return "" unless update_duplicates? && record_timestamps? + + model.timestamp_attributes_for_update_in_model.filter_map do |column_name| + if touch_timestamp_attribute?(column_name) + "#{column_name}=(CASE WHEN (#{updatable_columns.map(&block).join(" AND ")}) THEN #{model.quoted_table_name}.#{column_name} ELSE #{connection.high_precision_current_timestamp} END)," + end + end.join + end + + def raw_update_sql + insert_all.update_sql + end + + alias raw_update_sql? raw_update_sql + + private + attr_reader :connection, :insert_all + + def touch_timestamp_attribute?(column_name) + insert_all.updatable_columns.exclude?(column_name) + end + + def columns_list + format_columns(insert_all.keys_including_timestamps) + end + + def extract_types_from_columns_on(table_name, keys:) + columns = @model.schema_cache.columns_hash(table_name) + + unknown_column = (keys - columns.keys).first + raise UnknownAttributeError.new(model.new, unknown_column) if unknown_column + + keys.index_with { |key| model.type_for_attribute(key) } + end + + def format_columns(columns) + columns.respond_to?(:map) ? quote_columns(columns).join(",") : columns + end + + def quote_columns(columns) + columns.map { |column| quote_column(column) } + end + + def quote_column(column) + connection.quote_column_name(column) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/integration.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/integration.rb new file mode 100644 index 00000000..2a5d0b12 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/integration.rb @@ -0,0 +1,209 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/filters" + +module ActiveRecord + module Integration + extend ActiveSupport::Concern + + included do + ## + # :singleton-method: + # Indicates the format used to generate the timestamp in the cache key, if + # versioning is off. Accepts any of the symbols in +Time::DATE_FORMATS+. + # + # This is +:usec+, by default. + class_attribute :cache_timestamp_format, instance_writer: false, default: :usec + + ## + # :singleton-method: + # Indicates whether to use a stable #cache_key method that is accompanied + # by a changing version in the #cache_version method. + # + # This is +true+, by default on \Rails 5.2 and above. + class_attribute :cache_versioning, instance_writer: false, default: false + + ## + # :singleton-method: + # Indicates whether to use a stable #cache_key method that is accompanied + # by a changing version in the #cache_version method on collections. + # + # This is +false+, by default until \Rails 6.1. + class_attribute :collection_cache_versioning, instance_writer: false, default: false + end + + # Returns a +String+, which Action Pack uses for constructing a URL to this + # object. The default implementation returns this record's id as a +String+, + # or +nil+ if this record's unsaved. + # + # For example, suppose that you have a User model, and that you have a + # resources :users route. Normally, +user_path+ will + # construct a path with the user object's 'id' in it: + # + # user = User.find_by(name: 'Phusion') + # user_path(user) # => "/users/1" + # + # You can override +to_param+ in your model to make +user_path+ construct + # a path using the user's name instead of the user's id: + # + # class User < ActiveRecord::Base + # def to_param # overridden + # name + # end + # end + # + # user = User.find_by(name: 'Phusion') + # user_path(user) # => "/users/Phusion" + def to_param + return unless id + Array(id).join(self.class.param_delimiter) + end + + # Returns a stable cache key that can be used to identify this record. + # + # Product.new.cache_key # => "products/new" + # Product.find(5).cache_key # => "products/5" + # + # If ActiveRecord::Base.cache_versioning is turned off, as it was in \Rails 5.1 and earlier, + # the cache key will also include a version. + # + # Product.cache_versioning = false + # Product.find(5).cache_key # => "products/5-20071224150000" (updated_at available) + def cache_key + if new_record? + "#{model_name.cache_key}/new" + else + if cache_version + "#{model_name.cache_key}/#{id}" + else + timestamp = max_updated_column_timestamp + + if timestamp + timestamp = timestamp.utc.to_fs(cache_timestamp_format) + "#{model_name.cache_key}/#{id}-#{timestamp}" + else + "#{model_name.cache_key}/#{id}" + end + end + end + end + + # Returns a cache version that can be used together with the cache key to form + # a recyclable caching scheme. By default, the #updated_at column is used for the + # cache_version, but this method can be overwritten to return something else. + # + # Note, this method will return nil if ActiveRecord::Base.cache_versioning is set to + # +false+. + def cache_version + return unless cache_versioning + + if has_attribute?("updated_at") + timestamp = updated_at_before_type_cast + if can_use_fast_cache_version?(timestamp) + raw_timestamp_to_cache_version(timestamp) + + elsif timestamp = updated_at + timestamp.utc.to_fs(cache_timestamp_format) + end + elsif self.class.has_attribute?("updated_at") + raise ActiveModel::MissingAttributeError, "missing attribute 'updated_at' for #{self.class}" + end + end + + # Returns a cache key along with the version. + def cache_key_with_version + if version = cache_version + "#{cache_key}-#{version}" + else + cache_key + end + end + + module ClassMethods + # Defines your model's +to_param+ method to generate "pretty" URLs + # using +method_name+, which can be any attribute or method that + # responds to +to_s+. + # + # class User < ActiveRecord::Base + # to_param :name + # end + # + # user = User.find_by(name: 'Fancy Pants') + # user.id # => 123 + # user_path(user) # => "/users/123-fancy-pants" + # + # Values longer than 20 characters will be truncated. The value + # is truncated word by word. + # + # user = User.find_by(name: 'David Heinemeier Hansson') + # user.id # => 125 + # user_path(user) # => "/users/125-david-heinemeier" + # + # Because the generated param begins with the record's +id+, it is + # suitable for passing to +find+. In a controller, for example: + # + # params[:id] # => "123-fancy-pants" + # User.find(params[:id]).id # => 123 + def to_param(method_name = nil) + if method_name.nil? + super() + else + define_method :to_param do + if (default = super()) && + (result = send(method_name).to_s).present? && + (param = result.squish.parameterize.truncate(20, separator: /-/, omission: "")).present? + "#{default}-#{param}" + else + default + end + end + end + end + + def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc: + collection.send(:compute_cache_key, timestamp_column) + end + end + + private + # Detects if the value before type cast + # can be used to generate a cache_version. + # + # The fast cache version only works with a + # string value directly from the database. + # + # We also must check if the timestamp format has been changed + # or if the timezone is not set to UTC then + # we cannot apply our transformations correctly. + def can_use_fast_cache_version?(timestamp) + timestamp.is_a?(String) && + cache_timestamp_format == :usec && + # FIXME: checking out a connection for this is wasteful + # we should store/cache this information in the schema cache + # or similar. + self.class.with_connection(&:default_timezone) == :utc && + !updated_at_came_from_user? + end + + # Converts a raw database string to `:usec` + # format. + # + # Example: + # + # timestamp = "2018-10-15 20:02:15.266505" + # raw_timestamp_to_cache_version(timestamp) + # # => "20181015200215266505" + # + # PostgreSQL truncates trailing zeros, + # https://github.com/postgres/postgres/commit/3e1beda2cde3495f41290e1ece5d544525810214 + # to account for this we pad the output with zeros + def raw_timestamp_to_cache_version(timestamp) + key = timestamp.delete("- :.") + if key.length < 20 + key.ljust(20, "0") + else + key + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/internal_metadata.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/internal_metadata.rb new file mode 100644 index 00000000..ac2f3333 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/internal_metadata.rb @@ -0,0 +1,164 @@ +# frozen_string_literal: true + +require "active_record/scoping/default" +require "active_record/scoping/named" + +module ActiveRecord + # This class is used to create a table that keeps track of values and keys such + # as which environment migrations were run in. + # + # This is enabled by default. To disable this functionality set + # `use_metadata_table` to false in your database configuration. + class InternalMetadata # :nodoc: + class NullInternalMetadata # :nodoc: + end + + attr_reader :arel_table + + def initialize(pool) + @pool = pool + @arel_table = Arel::Table.new(table_name) + end + + def primary_key + "key" + end + + def value_key + "value" + end + + def table_name + "#{ActiveRecord::Base.table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{ActiveRecord::Base.table_name_suffix}" + end + + def enabled? + @pool.db_config.use_metadata_table? + end + + def []=(key, value) + return unless enabled? + + @pool.with_connection do |connection| + update_or_create_entry(connection, key, value) + end + end + + def [](key) + return unless enabled? + + @pool.with_connection do |connection| + if entry = select_entry(connection, key) + entry[value_key] + end + end + end + + def delete_all_entries + dm = Arel::DeleteManager.new(arel_table) + + @pool.with_connection do |connection| + connection.delete(dm, "#{self.class} Destroy") + end + end + + def count + sm = Arel::SelectManager.new(arel_table) + sm.project(*Arel::Nodes::Count.new([Arel.star])) + + @pool.with_connection do |connection| + connection.select_values(sm, "#{self.class} Count").first + end + end + + def create_table_and_set_flags(environment, schema_sha1 = nil) + return unless enabled? + + @pool.with_connection do |connection| + create_table + update_or_create_entry(connection, :environment, environment) + update_or_create_entry(connection, :schema_sha1, schema_sha1) if schema_sha1 + end + end + + # Creates an internal metadata table with columns +key+ and +value+ + def create_table + return unless enabled? + + @pool.with_connection do |connection| + unless connection.table_exists?(table_name) + connection.create_table(table_name, id: false) do |t| + t.string :key, **connection.internal_string_options_for_primary_key + t.string :value + t.timestamps + end + end + end + end + + def drop_table + return unless enabled? + + @pool.with_connection do |connection| + connection.drop_table table_name, if_exists: true + end + end + + def table_exists? + @pool.schema_cache.data_source_exists?(table_name) + end + + private + def update_or_create_entry(connection, key, value) + entry = select_entry(connection, key) + + if entry + if entry[value_key] != value + update_entry(connection, key, value) + else + entry[value_key] + end + else + create_entry(connection, key, value) + end + end + + def current_time(connection) + connection.default_timezone == :utc ? Time.now.utc : Time.now + end + + def create_entry(connection, key, value) + im = Arel::InsertManager.new(arel_table) + im.insert [ + [arel_table[primary_key], key], + [arel_table[value_key], value], + [arel_table[:created_at], current_time(connection)], + [arel_table[:updated_at], current_time(connection)] + ] + + connection.insert(im, "#{self.class} Create", primary_key, key) + end + + def update_entry(connection, key, new_value) + um = Arel::UpdateManager.new(arel_table) + um.set [ + [arel_table[value_key], new_value], + [arel_table[:updated_at], current_time(connection)] + ] + + um.where(arel_table[primary_key].eq(key)) + + connection.update(um, "#{self.class} Update") + end + + def select_entry(connection, key) + sm = Arel::SelectManager.new(arel_table) + sm.project(Arel::Nodes::SqlLiteral.new("*", retryable: true)) + sm.where(arel_table[primary_key].eq(Arel::Nodes::BindParam.new(key))) + sm.order(arel_table[primary_key].asc) + sm.limit = 1 + + connection.select_all(sm, "#{self.class} Load").first + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/legacy_yaml_adapter.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/legacy_yaml_adapter.rb new file mode 100644 index 00000000..3e511cf5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/legacy_yaml_adapter.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveRecord + module LegacyYamlAdapter # :nodoc: + def self.convert(coder) + return coder unless coder.is_a?(Psych::Coder) + + case coder["active_record_yaml_version"] + when 1, 2 then coder + else + raise("Active Record doesn't know how to load YAML with this format.") + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/locale/en.yml b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/locale/en.yml new file mode 100644 index 00000000..0b35027b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/locale/en.yml @@ -0,0 +1,48 @@ +en: + # Attributes names common to most models + #attributes: + #created_at: "Created at" + #updated_at: "Updated at" + + # Default error messages + errors: + messages: + required: "must exist" + taken: "has already been taken" + + # Active Record models configuration + activerecord: + errors: + messages: + record_invalid: "Validation failed: %{errors}" + restrict_dependent_destroy: + has_one: "Cannot delete record because a dependent %{record} exists" + has_many: "Cannot delete record because dependent %{record} exist" + # Append your own errors here or at the model/attributes scope. + + # You can define own errors for models or model attributes. + # The values :model, :attribute and :value are always available for interpolation. + # + # For example, + # models: + # user: + # blank: "This is a custom blank message for %{model}: %{attribute}" + # attributes: + # login: + # blank: "This is a custom blank message for User login" + # Will define custom blank validation message for User model and + # custom blank validation message for login attribute of User model. + #models: + + # Translate model names. Used in Model.human_name(). + #models: + # For example, + # user: "Dude" + # will translate User model name to "Dude" + + # Translate model attribute names. Used in Model.human_attribute_name(attribute). + #attributes: + # For example, + # user: + # login: "Handle" + # will translate User attribute "login" as "Handle" diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/locking/optimistic.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/locking/optimistic.rb new file mode 100644 index 00000000..8f68f1a9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/locking/optimistic.rb @@ -0,0 +1,228 @@ +# frozen_string_literal: true + +module ActiveRecord + module Locking + # == What is \Optimistic \Locking + # + # Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of + # conflicts with the data. It does this by checking whether another process has made changes to a record since + # it was opened, an ActiveRecord::StaleObjectError exception is thrown if that has occurred + # and the update is ignored. + # + # Check out ActiveRecord::Locking::Pessimistic for an alternative. + # + # == Usage + # + # Active Record supports optimistic locking if the +lock_version+ field is present. Each update to the + # record increments the integer column +lock_version+ and the locking facilities ensure that records instantiated twice + # will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example: + # + # p1 = Person.find(1) + # p2 = Person.find(1) + # + # p1.first_name = "Michael" + # p1.save + # + # p2.first_name = "should fail" + # p2.save # Raises an ActiveRecord::StaleObjectError + # + # Optimistic locking will also check for stale data when objects are destroyed. Example: + # + # p1 = Person.find(1) + # p2 = Person.find(1) + # + # p1.first_name = "Michael" + # p1.save + # + # p2.destroy # Raises an ActiveRecord::StaleObjectError + # + # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging, + # or otherwise apply the business logic needed to resolve the conflict. + # + # This locking mechanism will function inside a single Ruby process. To make it work across all + # web requests, the recommended approach is to add +lock_version+ as a hidden field to your form. + # + # This behavior can be turned off by setting ActiveRecord::Base.lock_optimistically = false. + # To override the name of the +lock_version+ column, set the locking_column class attribute: + # + # class Person < ActiveRecord::Base + # self.locking_column = :lock_person + # end + # + module Optimistic + extend ActiveSupport::Concern + + included do + class_attribute :lock_optimistically, instance_writer: false, default: true + end + + def locking_enabled? # :nodoc: + self.class.locking_enabled? + end + + def increment!(*, **) # :nodoc: + super.tap do + if locking_enabled? + self[self.class.locking_column] += 1 + clear_attribute_change(self.class.locking_column) + end + end + end + + def initialize_dup(other) # :nodoc: + super + _clear_locking_column if locking_enabled? + end + + private + def _create_record(attribute_names = self.attribute_names) + if locking_enabled? + # We always want to persist the locking version, even if we don't detect + # a change from the default, since the database might have no default + attribute_names |= [self.class.locking_column] + end + super + end + + def _touch_row(attribute_names, time) + @_touch_attr_names << self.class.locking_column if locking_enabled? + super + end + + def _update_row(attribute_names, attempted_action = "update") + return super unless locking_enabled? + + begin + locking_column = self.class.locking_column + lock_attribute_was = @attributes[locking_column] + + update_constraints = _query_constraints_hash + + attribute_names = attribute_names.dup if attribute_names.frozen? + attribute_names << locking_column + + self[locking_column] += 1 + + affected_rows = self.class._update_record( + attributes_with_values(attribute_names), + update_constraints + ) + + if affected_rows != 1 + raise ActiveRecord::StaleObjectError.new(self, attempted_action) + end + + affected_rows + + # If something went wrong, revert the locking_column value. + rescue Exception + @attributes[locking_column] = lock_attribute_was + raise + end + end + + def destroy_row + affected_rows = super + + if locking_enabled? && affected_rows != 1 + raise ActiveRecord::StaleObjectError.new(self, "destroy") + end + + affected_rows + end + + def _lock_value_for_database(locking_column) + if will_save_change_to_attribute?(locking_column) + @attributes[locking_column].value_for_database + else + @attributes[locking_column].original_value_for_database + end + end + + def _clear_locking_column + self[self.class.locking_column] = nil + clear_attribute_change(self.class.locking_column) + end + + def _query_constraints_hash + return super unless locking_enabled? + + locking_column = self.class.locking_column + super.merge(locking_column => _lock_value_for_database(locking_column)) + end + + module ClassMethods + DEFAULT_LOCKING_COLUMN = "lock_version" + + # Returns true if the +lock_optimistically+ flag is set to true + # (which it is, by default) and the table includes the + # +locking_column+ column (defaults to +lock_version+). + def locking_enabled? + lock_optimistically && columns_hash[locking_column] + end + + # Set the column to use for optimistic locking. Defaults to +lock_version+. + def locking_column=(value) + reload_schema_from_cache + @locking_column = value.to_s + end + + # The version column used for optimistic locking. Defaults to +lock_version+. + attr_reader :locking_column + + # Reset the column used for optimistic locking back to the +lock_version+ default. + def reset_locking_column + self.locking_column = DEFAULT_LOCKING_COLUMN + end + + # Make sure the lock version column gets updated when counters are + # updated. + def update_counters(id, counters) + counters = counters.merge(locking_column => 1) if locking_enabled? + super + end + + private + def hook_attribute_type(name, cast_type) + if lock_optimistically && name == locking_column + cast_type = LockingType.new(cast_type) + end + + super + end + + def inherited(base) + super + base.class_eval do + @locking_column = DEFAULT_LOCKING_COLUMN + end + end + end + end + + # In de/serialize we change `nil` to 0, so that we can allow passing + # `nil` values to `lock_version`, and not result in `ActiveRecord::StaleObjectError` + # during update record. + class LockingType < DelegateClass(Type::Value) # :nodoc: + def self.new(subtype) + self === subtype ? subtype : super + end + + def deserialize(value) + super.to_i + end + + def serialize(value) + super.to_i + end + + def init_with(coder) + __setobj__(coder["subtype"]) + end + + def encode_with(coder) + coder["subtype"] = __getobj__ + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/locking/pessimistic.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/locking/pessimistic.rb new file mode 100644 index 00000000..8ad312ac --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/locking/pessimistic.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +module ActiveRecord + module Locking + # = \Pessimistic \Locking + # + # Locking::Pessimistic provides support for row-level locking using + # SELECT ... FOR UPDATE and other lock types. + # + # Chain ActiveRecord::Base#find to ActiveRecord::QueryMethods#lock to obtain an exclusive + # lock on the selected rows: + # # select * from accounts where id=1 for update + # Account.lock.find(1) + # + # Call lock('some locking clause') to use a database-specific locking clause + # of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example: + # + # Account.transaction do + # # select * from accounts where name = 'shugo' limit 1 for update nowait + # shugo = Account.lock("FOR UPDATE NOWAIT").find_by(name: "shugo") + # yuko = Account.lock("FOR UPDATE NOWAIT").find_by(name: "yuko") + # shugo.balance -= 100 + # shugo.save! + # yuko.balance += 100 + # yuko.save! + # end + # + # You can also use ActiveRecord::Base#lock! method to lock one record by id. + # This may be better if you don't need to lock every row. Example: + # + # Account.transaction do + # # select * from accounts where ... + # accounts = Account.where(...) + # account1 = accounts.detect { |account| ... } + # account2 = accounts.detect { |account| ... } + # # select * from accounts where id=? for update + # account1.lock! + # account2.lock! + # account1.balance -= 100 + # account1.save! + # account2.balance += 100 + # account2.save! + # end + # + # You can start a transaction and acquire the lock in one go by calling + # with_lock with a block. The block is called from within + # a transaction, the object is already locked. Example: + # + # account = Account.first + # account.with_lock do + # # This block is called within a transaction, + # # account is already locked. + # account.balance -= 100 + # account.save! + # end + # + # Database-specific information on row locking: + # + # [MySQL] + # https://dev.mysql.com/doc/refman/en/innodb-locking-reads.html + # + # [PostgreSQL] + # https://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE + module Pessimistic + # Obtain a row lock on this record. Reloads the record to obtain the requested + # lock. Pass an SQL locking clause to append the end of the SELECT statement + # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns + # the locked record. + def lock!(lock = true) + if persisted? + if has_changes_to_save? + raise(<<-MSG.squish) + Locking a record with unpersisted changes is not supported. Use + `save` to persist the changes, or `reload` to discard them + explicitly. + Changed attributes: #{changed.map(&:inspect).join(', ')}. + MSG + end + + reload(lock: lock) + end + self + end + + # Wraps the passed block in a transaction, reloading the object with a + # lock before yielding. You can pass the SQL locking clause + # as an optional argument (see #lock!). + # + # You can also pass options like requires_new:, isolation:, + # and joinable: to the wrapping transaction (see + # ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction). + def with_lock(*args) + transaction_opts = args.extract_options! + lock = args.present? ? args.first : true + transaction(**transaction_opts) do + lock!(lock) + yield + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/log_subscriber.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/log_subscriber.rb new file mode 100644 index 00000000..fbb75d73 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/log_subscriber.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +module ActiveRecord + class LogSubscriber < ActiveSupport::LogSubscriber + IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"] + + class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new + + def strict_loading_violation(event) + debug do + owner = event.payload[:owner] + reflection = event.payload[:reflection] + color(reflection.strict_loading_violation_message(owner), RED) + end + end + subscribe_log_level :strict_loading_violation, :debug + + def sql(event) + payload = event.payload + + return if IGNORE_PAYLOAD_NAMES.include?(payload[:name]) + + name = if payload[:async] + "ASYNC #{payload[:name]} (#{payload[:lock_wait].round(1)}ms) (db time #{event.duration.round(1)}ms)" + else + "#{payload[:name]} (#{event.duration.round(1)}ms)" + end + name = "CACHE #{name}" if payload[:cached] + sql = payload[:sql] + binds = nil + + if payload[:binds]&.any? + casted_params = type_casted_binds(payload[:type_casted_binds]) + + binds = [] + payload[:binds].each_with_index do |attr, i| + attribute_name = if attr.respond_to?(:name) + attr.name + elsif attr.respond_to?(:[]) && attr[i].respond_to?(:name) + attr[i].name + else + nil + end + + filtered_params = filter(attribute_name, casted_params[i]) + + binds << render_bind(attr, filtered_params) + end + binds = binds.inspect + binds.prepend(" ") + end + + name = colorize_payload_name(name, payload[:name]) + sql = color(sql, sql_color(sql), bold: true) if colorize_logging + + debug " #{name} #{sql}#{binds}" + end + subscribe_log_level :sql, :debug + + private + def type_casted_binds(casted_binds) + casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds + end + + def render_bind(attr, value) + case attr + when ActiveModel::Attribute + if attr.type.binary? && attr.value + value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>" + end + when Array + attr = attr.first + else + attr = nil + end + + [attr&.name, value] + end + + def colorize_payload_name(name, payload_name) + if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists + color(name, MAGENTA, bold: true) + else + color(name, CYAN, bold: true) + end + end + + def sql_color(sql) + case sql + when /\A\s*rollback/mi + RED + when /select .*for update/mi, /\A\s*lock/mi + WHITE + when /\A\s*select/i + BLUE + when /\A\s*insert/i + GREEN + when /\A\s*update/i + YELLOW + when /\A\s*delete/i + RED + when /transaction\s*\Z/i + CYAN + else + MAGENTA + end + end + + def logger + ActiveRecord::Base.logger + end + + def debug(progname = nil, &block) + return unless super + + if ActiveRecord.verbose_query_logs + log_query_source + end + end + + def log_query_source + source = query_source_location + + if source + logger.debug(" ↳ #{source}") + end + end + + def query_source_location + Thread.each_caller_location do |location| + frame = backtrace_cleaner.clean_frame(location) + return frame if frame + end + nil + end + + def filter(name, value) + ActiveRecord::Base.inspection_filter.filter_param(name, value) + end + end +end + +ActiveRecord::LogSubscriber.attach_to :active_record diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/marshalling.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/marshalling.rb new file mode 100644 index 00000000..463bb28f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/marshalling.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module ActiveRecord + module Marshalling + @format_version = 6.1 + + class << self + attr_reader :format_version + + def format_version=(version) + case version + when 6.1 + Methods.remove_method(:marshal_dump) if Methods.method_defined?(:marshal_dump) + when 7.1 + Methods.alias_method(:marshal_dump, :_marshal_dump_7_1) + else + raise ArgumentError, "Unknown marshalling format: #{version.inspect}" + end + @format_version = version + end + end + + module Methods + def _marshal_dump_7_1 + payload = [attributes_for_database, new_record?] + + cached_associations = self.class.reflect_on_all_associations.select do |reflection| + if association_cached?(reflection.name) + association = association(reflection.name) + association.loaded? || association.target.present? + end + end + + unless cached_associations.empty? + payload << cached_associations.map do |reflection| + [reflection.name, association(reflection.name).target] + end + end + + payload + end + + def marshal_load(state) + attributes_from_database, new_record, associations = state + + attributes = self.class.attributes_builder.build_from_database(attributes_from_database) + init_with_attributes(attributes, new_record) + + if associations + associations.each do |name, target| + association(name).target = target + rescue ActiveRecord::AssociationNotFoundError + # the association no longer exist, we can just skip it. + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/message_pack.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/message_pack.rb new file mode 100644 index 00000000..208cf10f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/message_pack.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +module ActiveRecord + module MessagePack # :nodoc: + FORMAT_VERSION = 1 + + class << self + def dump(input) + encoder = Encoder.new + [FORMAT_VERSION, encoder.encode(input), encoder.entries] + end + + def load(dumped) + format_version, top_level, entries = dumped + unless format_version == FORMAT_VERSION + raise "Invalid format version: #{format_version.inspect}" + end + Decoder.new(entries).decode(top_level) + end + end + + module Extensions + extend self + + def install(registry) + registry.register_type 119, ActiveModel::Type::Binary::Data, + packer: :to_s, + unpacker: :new + + registry.register_type 120, ActiveRecord::Base, + packer: method(:write_record), + unpacker: method(:read_record), + recursive: true + end + + def write_record(record, packer) + packer.write(ActiveRecord::MessagePack.dump(record)) + end + + def read_record(unpacker) + ActiveRecord::MessagePack.load(unpacker.read) + end + end + + class Encoder + attr_reader :entries + + def initialize + @entries = [] + @refs = {}.compare_by_identity + end + + def encode(input) + if input.is_a?(Array) + input.map { |record| encode_record(record) } + elsif input + encode_record(input) + end + end + + def encode_record(record) + ref = @refs[record] + + if !ref + ref = @refs[record] = @entries.size + @entries << build_entry(record) + add_cached_associations(record, @entries.last) + end + + ref + end + + def build_entry(record) + [ + ActiveSupport::MessagePack::Extensions.dump_class(record.class), + record.attributes_for_database, + record.new_record? + ] + end + + def add_cached_associations(record, entry) + record.class.normalized_reflections.each_value do |reflection| + if record.association_cached?(reflection.name) && record.association(reflection.name).loaded? + entry << reflection.name << encode(record.association(reflection.name).target) + end + end + end + end + + class Decoder + def initialize(entries) + @records = entries.map { |entry| build_record(entry) } + @records.zip(entries) { |record, entry| resolve_cached_associations(record, entry) } + end + + def decode(ref) + if ref.is_a?(Array) + ref.map { |r| @records[r] } + elsif ref + @records[ref] + end + end + + def build_record(entry) + class_name, attributes_hash, is_new_record, * = entry + klass = ActiveSupport::MessagePack::Extensions.load_class(class_name) + attributes = klass.attributes_builder.build_from_database(attributes_hash) + klass.allocate.init_with_attributes(attributes, is_new_record) + end + + def resolve_cached_associations(record, entry) + i = 3 # entry == [class_name, attributes_hash, is_new_record, *associations] + while i < entry.length + begin + record.association(entry[i]).target = decode(entry[i + 1]) + rescue ActiveRecord::AssociationNotFoundError + # The association no longer exists, so just skip it. + end + i += 2 + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/middleware/database_selector.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/middleware/database_selector.rb new file mode 100644 index 00000000..f5f094ca --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/middleware/database_selector.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require "active_record/middleware/database_selector/resolver" + +module ActiveRecord + module Middleware + # = Database Selector \Middleware + # + # The DatabaseSelector Middleware provides a framework for automatically + # swapping from the primary to the replica database connection. \Rails + # provides a basic framework to determine when to swap and allows for + # applications to write custom strategy classes to override the default + # behavior. + # + # The resolver class defines when the application should switch (i.e. read + # from the primary if a write occurred less than 2 seconds ago) and a + # resolver context class that sets a value that helps the resolver class + # decide when to switch. + # + # \Rails default middleware uses the request's session to set a timestamp + # that informs the application when to read from a primary or read from a + # replica. + # + # To use the DatabaseSelector in your application with default settings, + # run the provided generator. + # + # $ bin/rails g active_record:multi_db + # + # This will create a file named +config/initializers/multi_db.rb+ with the + # following contents: + # + # Rails.application.configure do + # config.active_record.database_selector = { delay: 2.seconds } + # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver + # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session + # end + # + # Alternatively you can set the options in your environment config or + # any other config file loaded on boot. + # + # The default behavior can be changed by setting the config options to a + # custom class: + # + # config.active_record.database_selector = { delay: 2.seconds } + # config.active_record.database_resolver = MyResolver + # config.active_record.database_resolver_context = MyResolver::MySession + # + # Note: If you are using rails new my_app --minimal you will need + # to call require "active_support/core_ext/integer/time" to load + # the core extension in order to use +2.seconds+ + class DatabaseSelector + def initialize(app, resolver_klass = nil, context_klass = nil, options = {}) + @app = app + @resolver_klass = resolver_klass || Resolver + @context_klass = context_klass || Resolver::Session + @options = options + end + + attr_reader :resolver_klass, :context_klass, :options + + # Middleware that determines which database connection to use in a multiple + # database application. + def call(env) + request = ActionDispatch::Request.new(env) + + select_database(request) do + @app.call(env) + end + end + + private + def select_database(request, &blk) + context = context_klass.call(request) + resolver = resolver_klass.call(context, options) + + response = if resolver.reading_request?(request) + resolver.read(&blk) + else + resolver.write(&blk) + end + + resolver.update_context(response) + response + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/middleware/database_selector/resolver.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/middleware/database_selector/resolver.rb new file mode 100644 index 00000000..2b5c39f9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/middleware/database_selector/resolver.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require "active_record/middleware/database_selector/resolver/session" +require "active_support/core_ext/numeric/time" + +module ActiveRecord + module Middleware + class DatabaseSelector + # The Resolver class is used by the DatabaseSelector middleware to + # determine which database the request should use. + # + # To change the behavior of the Resolver class in your application, + # create a custom resolver class that inherits from + # DatabaseSelector::Resolver and implements the methods that need to + # be changed. + # + # By default the Resolver class will send read traffic to the replica + # if it's been 2 seconds since the last write. + class Resolver # :nodoc: + SEND_TO_REPLICA_DELAY = 2.seconds + + def self.call(context, options = {}) + new(context, options) + end + + def initialize(context, options = {}) + @context = context + @options = options + @delay = @options && @options[:delay] ? @options[:delay] : SEND_TO_REPLICA_DELAY + @instrumenter = ActiveSupport::Notifications.instrumenter + end + + attr_reader :context, :delay, :instrumenter + + def read(&blk) + if read_from_primary? + read_from_primary(&blk) + else + read_from_replica(&blk) + end + end + + def write(&blk) + write_to_primary(&blk) + end + + def update_context(response) + context.save(response) + end + + def reading_request?(request) + request.get? || request.head? + end + + private + def read_from_primary(&blk) + ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role, prevent_writes: true) do + instrumenter.instrument("database_selector.active_record.read_from_primary", &blk) + end + end + + def read_from_replica(&blk) + ActiveRecord::Base.connected_to(role: ActiveRecord.reading_role, prevent_writes: true) do + instrumenter.instrument("database_selector.active_record.read_from_replica", &blk) + end + end + + def write_to_primary + ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role, prevent_writes: false) do + instrumenter.instrument("database_selector.active_record.wrote_to_primary") do + yield + ensure + context.update_last_write_timestamp + end + end + end + + def read_from_primary? + !time_since_last_write_ok? + end + + def send_to_replica_delay + delay + end + + def time_since_last_write_ok? + Time.now - context.last_write_timestamp >= send_to_replica_delay + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/middleware/database_selector/resolver/session.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/middleware/database_selector/resolver/session.rb new file mode 100644 index 00000000..530701fb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/middleware/database_selector/resolver/session.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module ActiveRecord + module Middleware + class DatabaseSelector + class Resolver + # The session class is used by the DatabaseSelector::Resolver to save + # timestamps of the last write in the session. + # + # The last_write is used to determine whether it's safe to read + # from the replica or the request needs to be sent to the primary. + class Session # :nodoc: + def self.call(request) + new(request.session) + end + + # Converts time to a timestamp that represents milliseconds since + # epoch. + def self.convert_time_to_timestamp(time) + time.to_i * 1000 + time.usec / 1000 + end + + # Converts milliseconds since epoch timestamp into a time object. + def self.convert_timestamp_to_time(timestamp) + timestamp ? Time.at(timestamp / 1000, (timestamp % 1000) * 1000) : Time.at(0) + end + + def initialize(session) + @session = session + end + + attr_reader :session + + def last_write_timestamp + self.class.convert_timestamp_to_time(session[:last_write]) + end + + def update_last_write_timestamp + session[:last_write] = self.class.convert_time_to_timestamp(Time.now) + end + + def save(response) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/middleware/shard_selector.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/middleware/shard_selector.rb new file mode 100644 index 00000000..1db88b90 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/middleware/shard_selector.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module ActiveRecord + module Middleware + # = Shard Selector \Middleware + # + # The ShardSelector Middleware provides a framework for automatically + # swapping shards. \Rails provides a basic framework to determine which + # shard to switch to and allows for applications to write custom strategies + # for swapping if needed. + # + # The ShardSelector takes a set of options (currently only +lock+ is supported) + # that can be used by the middleware to alter behavior. +lock+ is + # true by default and will prohibit the request from switching shards once + # inside the block. If +lock+ is false, then shard swapping will be allowed. + # For tenant based sharding, +lock+ should always be true to prevent application + # code from mistakenly switching between tenants. + # + # Options can be set in the config: + # + # config.active_record.shard_selector = { lock: true } + # + # Applications must also provide the code for the resolver as it depends on application + # specific models. An example resolver would look like this: + # + # config.active_record.shard_resolver = ->(request) { + # subdomain = request.subdomain + # tenant = Tenant.find_by_subdomain!(subdomain) + # tenant.shard + # } + class ShardSelector + def initialize(app, resolver, options = {}) + @app = app + @resolver = resolver + @options = options + end + + attr_reader :resolver, :options + + def call(env) + request = ActionDispatch::Request.new(env) + + shard = selected_shard(request) + + set_shard(shard) do + @app.call(env) + end + end + + private + def selected_shard(request) + resolver.call(request) + end + + def set_shard(shard, &block) + ActiveRecord::Base.connected_to(shard: shard.to_sym) do + ActiveRecord::Base.prohibit_shard_swapping(options.fetch(:lock, true), &block) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration.rb new file mode 100644 index 00000000..3bc6199e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration.rb @@ -0,0 +1,1621 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/access" +require "active_support/core_ext/enumerable" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/actionable_error" +require "active_record/migration/pending_migration_connection" + +module ActiveRecord + class MigrationError < ActiveRecordError # :nodoc: + def initialize(message = nil) + message = "\n\n#{message}\n\n" if message + super + end + end + + # Exception that can be raised to stop migrations from being rolled back. + # For example the following migration is not reversible. + # Rolling back this migration will raise an ActiveRecord::IrreversibleMigration error. + # + # class IrreversibleMigrationExample < ActiveRecord::Migration[8.0] + # def change + # create_table :distributors do |t| + # t.string :zipcode + # end + # + # execute <<~SQL + # ALTER TABLE distributors + # ADD CONSTRAINT zipchk + # CHECK (char_length(zipcode) = 5) NO INHERIT; + # SQL + # end + # end + # + # There are two ways to mitigate this problem. + # + # 1. Define #up and #down methods instead of #change: + # + # class ReversibleMigrationExample < ActiveRecord::Migration[8.0] + # def up + # create_table :distributors do |t| + # t.string :zipcode + # end + # + # execute <<~SQL + # ALTER TABLE distributors + # ADD CONSTRAINT zipchk + # CHECK (char_length(zipcode) = 5) NO INHERIT; + # SQL + # end + # + # def down + # execute <<~SQL + # ALTER TABLE distributors + # DROP CONSTRAINT zipchk + # SQL + # + # drop_table :distributors + # end + # end + # + # 2. Use the #reversible method in #change method: + # + # class ReversibleMigrationExample < ActiveRecord::Migration[8.0] + # def change + # create_table :distributors do |t| + # t.string :zipcode + # end + # + # reversible do |dir| + # dir.up do + # execute <<~SQL + # ALTER TABLE distributors + # ADD CONSTRAINT zipchk + # CHECK (char_length(zipcode) = 5) NO INHERIT; + # SQL + # end + # + # dir.down do + # execute <<~SQL + # ALTER TABLE distributors + # DROP CONSTRAINT zipchk + # SQL + # end + # end + # end + # end + class IrreversibleMigration < MigrationError + end + + class DuplicateMigrationVersionError < MigrationError # :nodoc: + def initialize(version = nil) + if version + super("Multiple migrations have the version number #{version}.") + else + super("Duplicate migration version error.") + end + end + end + + class DuplicateMigrationNameError < MigrationError # :nodoc: + def initialize(name = nil) + if name + super("Multiple migrations have the name #{name}.") + else + super("Duplicate migration name.") + end + end + end + + class UnknownMigrationVersionError < MigrationError # :nodoc: + def initialize(version = nil) + if version + super("No migration with version number #{version}.") + else + super("Unknown migration version.") + end + end + end + + class IllegalMigrationNameError < MigrationError # :nodoc: + def initialize(name = nil) + if name + super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed).") + else + super("Illegal name for migration.") + end + end + end + + class InvalidMigrationTimestampError < MigrationError # :nodoc: + def initialize(version = nil, name = nil) + if version && name + super(<<~MSG) + Invalid timestamp #{version} for migration file: #{name}. + Timestamp must be in form YYYYMMDDHHMMSS, and less than #{(Time.now.utc + 1.day).strftime("%Y%m%d%H%M%S")}. + MSG + else + super(<<~MSG) + Invalid timestamp for migration. + Timestamp must be in form YYYYMMDDHHMMSS, and less than #{(Time.now.utc + 1.day).strftime("%Y%m%d%H%M%S")}. + MSG + end + end + end + + class PendingMigrationError < MigrationError # :nodoc: + include ActiveSupport::ActionableError + + action "Run pending migrations" do + ActiveRecord::Tasks::DatabaseTasks.migrate + + if ActiveRecord.dump_schema_after_migration + connection = ActiveRecord::Tasks::DatabaseTasks.migration_connection + ActiveRecord::Tasks::DatabaseTasks.dump_schema(connection.pool.db_config) + end + end + + def initialize(message = nil, pending_migrations: nil) + if pending_migrations.nil? + pending_migrations = connection_pool.migration_context.open.pending_migrations + end + + super(message || detailed_migration_message(pending_migrations)) + end + + private + def detailed_migration_message(pending_migrations) + message = "Migrations are pending. To resolve this issue, run:\n\n bin/rails db:migrate" + message += " RAILS_ENV=#{::Rails.env}" if defined?(Rails.env) && !Rails.env.local? + message += "\n\n" + + message += "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}\n\n" + + pending_migrations.each do |pending_migration| + message += "#{pending_migration.filename}\n" + end + + message + end + + def connection_pool + ActiveRecord::Tasks::DatabaseTasks.migration_connection_pool + end + end + + class ConcurrentMigrationError < MigrationError # :nodoc: + DEFAULT_MESSAGE = "Cannot run migrations because another migration process is currently running." + RELEASE_LOCK_FAILED_MESSAGE = "Failed to release advisory lock" + + def initialize(message = DEFAULT_MESSAGE) + super + end + end + + class NoEnvironmentInSchemaError < MigrationError # :nodoc: + def initialize + msg = "Environment data not found in the schema. To resolve this issue, run: \n\n bin/rails db:environment:set" + if defined?(Rails.env) + super("#{msg} RAILS_ENV=#{::Rails.env}") + else + super(msg) + end + end + end + + class ProtectedEnvironmentError < ActiveRecordError # :nodoc: + def initialize(env = "production") + msg = +"You are attempting to run a destructive action against your '#{env}' database.\n" + msg << "If you are sure you want to continue, run the same command with the environment variable:\n" + msg << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1" + super(msg) + end + end + + class EnvironmentMismatchError < ActiveRecordError + def initialize(current: nil, stored: nil) + msg = +"You are attempting to modify a database that was last run in `#{ stored }` environment.\n" + msg << "You are running in `#{ current }` environment. " + msg << "If you are sure you want to continue, first set the environment using:\n\n" + msg << " bin/rails db:environment:set" + if defined?(Rails.env) + super("#{msg} RAILS_ENV=#{::Rails.env}\n\n") + else + super("#{msg}\n\n") + end + end + end + + class EnvironmentStorageError < ActiveRecordError # :nodoc: + def initialize + msg = +"You are attempting to store the environment in a database where metadata is disabled.\n" + msg << "Check your database configuration to see if this is intended." + super(msg) + end + end + + # = Active Record Migrations + # + # Migrations can manage the evolution of a schema used by several physical + # databases. It's a solution to the common problem of adding a field to make + # a new feature work in your local database, but being unsure of how to + # push that change to other developers and to the production server. With + # migrations, you can describe the transformations in self-contained classes + # that can be checked into version control systems and executed against + # another database that might be one, two, or five versions behind. + # + # Example of a simple migration: + # + # class AddSsl < ActiveRecord::Migration[8.0] + # def up + # add_column :accounts, :ssl_enabled, :boolean, default: true + # end + # + # def down + # remove_column :accounts, :ssl_enabled + # end + # end + # + # This migration will add a boolean flag to the accounts table and remove it + # if you're backing out of the migration. It shows how all migrations have + # two methods +up+ and +down+ that describes the transformations + # required to implement or remove the migration. These methods can consist + # of both the migration specific methods like +add_column+ and +remove_column+, + # but may also contain regular Ruby code for generating data needed for the + # transformations. + # + # Example of a more complex migration that also needs to initialize data: + # + # class AddSystemSettings < ActiveRecord::Migration[8.0] + # def up + # create_table :system_settings do |t| + # t.string :name + # t.string :label + # t.text :value + # t.string :type + # t.integer :position + # end + # + # SystemSetting.create name: 'notice', + # label: 'Use notice?', + # value: 1 + # end + # + # def down + # drop_table :system_settings + # end + # end + # + # This migration first adds the +system_settings+ table, then creates the very + # first row in it using the Active Record model that relies on the table. It + # also uses the more advanced +create_table+ syntax where you can specify a + # complete table schema in one block call. + # + # == Available transformations + # + # === Creation + # + # * create_join_table(table_1, table_2, options): Creates a join + # table having its name as the lexical order of the first two + # arguments. See + # ActiveRecord::ConnectionAdapters::SchemaStatements#create_join_table for + # details. + # * create_table(name, options): Creates a table called +name+ and + # makes the table object available to a block that can then add columns to it, + # following the same format as +add_column+. See example above. The options hash + # is for fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create + # table definition. + # * add_column(table_name, column_name, type, options): Adds a new column + # to the table called +table_name+ + # named +column_name+ specified to be one of the following types: + # :string, :text, :integer, :float, + # :decimal, :datetime, :timestamp, :time, + # :date, :binary, :boolean. A default value can be + # specified by passing an +options+ hash like { default: 11 }. + # Other options include :limit and :null (e.g. + # { limit: 50, null: false }) -- see + # ActiveRecord::ConnectionAdapters::TableDefinition#column for details. + # * add_foreign_key(from_table, to_table, options): Adds a new + # foreign key. +from_table+ is the table with the key column, +to_table+ contains + # the referenced primary key. + # * add_index(table_name, column_names, options): Adds a new index + # with the name of the column. Other options include + # :name, :unique (e.g. + # { name: 'users_name_index', unique: true }) and :order + # (e.g. { order: { name: :desc } }). + # * add_reference(:table_name, :reference_name): Adds a new column + # +reference_name_id+ by default an integer. See + # ActiveRecord::ConnectionAdapters::SchemaStatements#add_reference for details. + # * add_timestamps(table_name, options): Adds timestamps (+created_at+ + # and +updated_at+) columns to +table_name+. + # + # === Modification + # + # * change_column(table_name, column_name, type, options): Changes + # the column to a different type using the same parameters as add_column. + # * change_column_default(table_name, column_name, default_or_changes): + # Sets a default value for +column_name+ defined by +default_or_changes+ on + # +table_name+. Passing a hash containing :from and :to + # as +default_or_changes+ will make this change reversible in the migration. + # * change_column_null(table_name, column_name, null, default = nil): + # Sets or removes a NOT NULL constraint on +column_name+. The +null+ flag + # indicates whether the value can be +NULL+. See + # ActiveRecord::ConnectionAdapters::SchemaStatements#change_column_null for + # details. + # * change_table(name, options): Allows to make column alterations to + # the table called +name+. It makes the table object available to a block that + # can then add/remove columns, indexes, or foreign keys to it. + # * rename_column(table_name, column_name, new_column_name): Renames + # a column but keeps the type and content. + # * rename_index(table_name, old_name, new_name): Renames an index. + # * rename_table(old_name, new_name): Renames the table called +old_name+ + # to +new_name+. + # + # === Deletion + # + # * drop_table(*names): Drops the given tables. + # * drop_join_table(table_1, table_2, options): Drops the join table + # specified by the given arguments. + # * remove_column(table_name, column_name, type, options): Removes the column + # named +column_name+ from the table called +table_name+. + # * remove_columns(table_name, *column_names): Removes the given + # columns from the table definition. + # * remove_foreign_key(from_table, to_table = nil, **options): Removes the + # given foreign key from the table called +table_name+. + # * remove_index(table_name, column: column_names): Removes the index + # specified by +column_names+. + # * remove_index(table_name, name: index_name): Removes the index + # specified by +index_name+. + # * remove_reference(table_name, ref_name, options): Removes the + # reference(s) on +table_name+ specified by +ref_name+. + # * remove_timestamps(table_name, options): Removes the timestamp + # columns (+created_at+ and +updated_at+) from the table definition. + # + # == Irreversible transformations + # + # Some transformations are destructive in a manner that cannot be reversed. + # Migrations of that kind should raise an ActiveRecord::IrreversibleMigration + # exception in their +down+ method. + # + # == Running migrations from within \Rails + # + # The \Rails package has several tools to help create and apply migrations. + # + # To generate a new migration, you can use + # + # $ bin/rails generate migration MyNewMigration + # + # where MyNewMigration is the name of your migration. The generator will + # create an empty migration file timestamp_my_new_migration.rb + # in the db/migrate/ directory where timestamp is the + # UTC formatted date and time that the migration was generated. + # + # There is a special syntactic shortcut to generate migrations that add fields to a table. + # + # $ bin/rails generate migration add_fieldname_to_tablename fieldname:string + # + # This will generate the file timestamp_add_fieldname_to_tablename.rb, which will look like this: + # class AddFieldnameToTablename < ActiveRecord::Migration[8.0] + # def change + # add_column :tablenames, :fieldname, :string + # end + # end + # + # To run migrations against the currently configured database, use + # bin/rails db:migrate. This will update the database by running all of the + # pending migrations, creating the schema_migrations table + # (see "About the schema_migrations table" section below) if missing. It will also + # invoke the db:schema:dump command, which will update your db/schema.rb file + # to match the structure of your database. + # + # To roll the database back to a previous migration version, use + # bin/rails db:rollback VERSION=X where X is the version to which + # you wish to downgrade. Alternatively, you can also use the STEP option if you + # wish to rollback last few migrations. bin/rails db:rollback STEP=2 will rollback + # the latest two migrations. + # + # If any of the migrations throw an ActiveRecord::IrreversibleMigration exception, + # that step will fail and you'll have some manual work to do. + # + # == More examples + # + # Not all migrations change the schema. Some just fix the data: + # + # class RemoveEmptyTags < ActiveRecord::Migration[8.0] + # def up + # Tag.all.each { |tag| tag.destroy if tag.pages.empty? } + # end + # + # def down + # # not much we can do to restore deleted data + # raise ActiveRecord::IrreversibleMigration, "Can't recover the deleted tags" + # end + # end + # + # Others remove columns when they migrate up instead of down: + # + # class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration[8.0] + # def up + # remove_column :items, :incomplete_items_count + # remove_column :items, :completed_items_count + # end + # + # def down + # add_column :items, :incomplete_items_count + # add_column :items, :completed_items_count + # end + # end + # + # And sometimes you need to do something in SQL not abstracted directly by migrations: + # + # class MakeJoinUnique < ActiveRecord::Migration[8.0] + # def up + # execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)" + # end + # + # def down + # execute "ALTER TABLE `pages_linked_pages` DROP INDEX `page_id_linked_page_id`" + # end + # end + # + # == Using a model after changing its table + # + # Sometimes you'll want to add a column in a migration and populate it + # immediately after. In that case, you'll need to make a call to + # Base#reset_column_information in order to ensure that the model has the + # latest column data from after the new column was added. Example: + # + # class AddPeopleSalary < ActiveRecord::Migration[8.0] + # def up + # add_column :people, :salary, :integer + # Person.reset_column_information + # Person.all.each do |p| + # p.update_attribute :salary, SalaryCalculator.compute(p) + # end + # end + # end + # + # == Controlling verbosity + # + # By default, migrations will describe the actions they are taking, writing + # them to the console as they happen, along with benchmarks describing how + # long each step took. + # + # You can quiet them down by setting ActiveRecord::Migration.verbose = false. + # + # You can also insert your own messages and benchmarks by using the +say_with_time+ + # method: + # + # def up + # ... + # say_with_time "Updating salaries..." do + # Person.all.each do |p| + # p.update_attribute :salary, SalaryCalculator.compute(p) + # end + # end + # ... + # end + # + # The phrase "Updating salaries..." would then be printed, along with the + # benchmark for the block when the block completes. + # + # == Timestamped Migrations + # + # By default, \Rails generates migrations that look like: + # + # 20080717013526_your_migration_name.rb + # + # The prefix is a generation timestamp (in UTC). Timestamps should not be + # modified manually. To validate that migration timestamps adhere to the + # format Active Record expects, you can use the following configuration option: + # + # config.active_record.validate_migration_timestamps = true + # + # If you'd prefer to use numeric prefixes, you can turn timestamped migrations + # off by setting: + # + # config.active_record.timestamped_migrations = false + # + # In application.rb. + # + # == Reversible Migrations + # + # Reversible migrations are migrations that know how to go +down+ for you. + # You simply supply the +up+ logic, and the Migration system figures out + # how to execute the down commands for you. + # + # To define a reversible migration, define the +change+ method in your + # migration like this: + # + # class TenderloveMigration < ActiveRecord::Migration[8.0] + # def change + # create_table(:horses) do |t| + # t.column :content, :text + # t.column :remind_at, :datetime + # end + # end + # end + # + # This migration will create the horses table for you on the way up, and + # automatically figure out how to drop the table on the way down. + # + # Some commands cannot be reversed. If you care to define how to move up + # and down in these cases, you should define the +up+ and +down+ methods + # as before. + # + # If a command cannot be reversed, an + # ActiveRecord::IrreversibleMigration exception will be raised when + # the migration is moving down. + # + # For a list of commands that are reversible, please see + # +ActiveRecord::Migration::CommandRecorder+. + # + # == Transactional Migrations + # + # If the database adapter supports DDL transactions, all migrations will + # automatically be wrapped in a transaction. There are queries that you + # can't execute inside a transaction though, and for these situations + # you can turn the automatic transactions off. + # + # class ChangeEnum < ActiveRecord::Migration[8.0] + # disable_ddl_transaction! + # + # def up + # execute "ALTER TYPE model_size ADD VALUE 'new_value'" + # end + # end + # + # Remember that you can still open your own transactions, even if you + # are in a Migration with self.disable_ddl_transaction!. + class Migration + autoload :CommandRecorder, "active_record/migration/command_recorder" + autoload :Compatibility, "active_record/migration/compatibility" + autoload :JoinTable, "active_record/migration/join_table" + autoload :ExecutionStrategy, "active_record/migration/execution_strategy" + autoload :DefaultStrategy, "active_record/migration/default_strategy" + + # This must be defined before the inherited hook, below + class Current < Migration # :nodoc: + def create_table(table_name, **options) + if block_given? + super { |t| yield compatible_table_definition(t) } + else + super + end + end + + def change_table(table_name, **options) + if block_given? + super { |t| yield compatible_table_definition(t) } + else + super + end + end + + def create_join_table(table_1, table_2, **options) + if block_given? + super { |t| yield compatible_table_definition(t) } + else + super + end + end + + def drop_table(*table_names, **options) + if block_given? + super { |t| yield compatible_table_definition(t) } + else + super + end + end + + def compatible_table_definition(t) + t + end + end + + def self.inherited(subclass) # :nodoc: + super + if subclass.superclass == Migration + major = ActiveRecord::VERSION::MAJOR + minor = ActiveRecord::VERSION::MINOR + raise StandardError, "Directly inheriting from ActiveRecord::Migration is not supported. " \ + "Please specify the Active Record release the migration was written for:\n" \ + "\n" \ + " class #{subclass} < ActiveRecord::Migration[#{major}.#{minor}]" + end + end + + def self.[](version) + Compatibility.find(version) + end + + def self.current_version + ActiveRecord::VERSION::STRING.to_f + end + + MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/ # :nodoc: + + def self.valid_version_format?(version_string) # :nodoc: + [ + MigrationFilenameRegexp, + /\A\d(_?\d)*\z/ # integer with optional underscores + ].any? { |pattern| pattern.match?(version_string) } + end + + # This class is used to verify that all migrations have been run before + # loading a web page if config.active_record.migration_error is set to +:page_load+. + class CheckPending + def initialize(app, file_watcher: ActiveSupport::FileUpdateChecker) + @app = app + @needs_check = true + @mutex = Mutex.new + @file_watcher = file_watcher + end + + def call(env) + @mutex.synchronize do + @watcher ||= build_watcher do + @needs_check = true + ActiveRecord::Migration.check_pending_migrations + @needs_check = false + end + + if @needs_check + @watcher.execute + else + @watcher.execute_if_updated + end + end + + @app.call(env) + end + + private + def build_watcher(&block) + current_environment = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call + all_configs = ActiveRecord::Base.configurations.configs_for(env_name: current_environment) + paths = all_configs.flat_map { |config| config.migrations_paths || Migrator.migrations_paths }.uniq + @file_watcher.new([], paths.index_with(["rb"]), &block) + end + end + + class << self + attr_accessor :delegate # :nodoc: + attr_accessor :disable_ddl_transaction # :nodoc: + + def nearest_delegate # :nodoc: + delegate || superclass.nearest_delegate + end + + # Raises ActiveRecord::PendingMigrationError error if any migrations are pending + # for all database configurations in an environment. + def check_all_pending! + pending_migrations = [] + + ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: env) do |pool| + if pending = pool.migration_context.open.pending_migrations + pending_migrations << pending + end + end + + migrations = pending_migrations.flatten + + if migrations.any? + raise ActiveRecord::PendingMigrationError.new(pending_migrations: migrations) + end + end + + def load_schema_if_pending! + if any_schema_needs_update? + load_schema! + end + + check_pending_migrations + end + + def maintain_test_schema! # :nodoc: + if ActiveRecord.maintain_test_schema + suppress_messages { load_schema_if_pending! } + end + end + + def method_missing(name, ...) # :nodoc: + nearest_delegate.send(name, ...) + end + + def migrate(direction) + new.migrate direction + end + + # Disable the transaction wrapping this migration. + # You can still create your own transactions even after calling #disable_ddl_transaction! + # + # For more details read the {"Transactional Migrations" section above}[rdoc-ref:Migration]. + def disable_ddl_transaction! + @disable_ddl_transaction = true + end + + def check_pending_migrations # :nodoc: + migrations = pending_migrations + + if migrations.any? + raise ActiveRecord::PendingMigrationError.new(pending_migrations: migrations) + end + end + + private + def any_schema_needs_update? + !db_configs_in_current_env.all? do |db_config| + Tasks::DatabaseTasks.schema_up_to_date?(db_config, ActiveRecord.schema_format) + end + end + + def db_configs_in_current_env + ActiveRecord::Base.configurations.configs_for(env_name: env) + end + + def pending_migrations + pending_migrations = [] + + ActiveRecord::Base.configurations.configs_for(env_name: env).each do |db_config| + ActiveRecord::PendingMigrationConnection.with_temporary_pool(db_config) do |pool| + if pending = pool.migration_context.open.pending_migrations + pending_migrations << pending + end + end + end + + pending_migrations.flatten + end + + def env + ActiveRecord::ConnectionHandling::DEFAULT_ENV.call + end + + def load_schema! + # Roundtrip to Rake to allow plugins to hook into database initialization. + root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root + + FileUtils.cd(root) do + Base.connection_handler.clear_all_connections!(:all) + system("bin/rails db:test:prepare") + end + end + end + + def disable_ddl_transaction # :nodoc: + self.class.disable_ddl_transaction + end + + ## + # :singleton-method: verbose + # + # Specifies if migrations will write the actions they are taking to the console as they + # happen, along with benchmarks describing how long each step took. Defaults to + # true. + cattr_accessor :verbose + attr_accessor :name, :version + + def initialize(name = self.class.name, version = nil) + @name = name + @version = version + @connection = nil + @pool = nil + end + + def execution_strategy + @execution_strategy ||= ActiveRecord.migration_strategy.new(self) + end + + self.verbose = true + # instantiate the delegate object after initialize is defined + self.delegate = new + + # Reverses the migration commands for the given block and + # the given migrations. + # + # The following migration will remove the table 'horses' + # and create the table 'apples' on the way up, and the reverse + # on the way down. + # + # class FixTLMigration < ActiveRecord::Migration[8.0] + # def change + # revert do + # create_table(:horses) do |t| + # t.text :content + # t.datetime :remind_at + # end + # end + # create_table(:apples) do |t| + # t.string :variety + # end + # end + # end + # + # Or equivalently, if +TenderloveMigration+ is defined as in the + # documentation for Migration: + # + # require_relative "20121212123456_tenderlove_migration" + # + # class FixupTLMigration < ActiveRecord::Migration[8.0] + # def change + # revert TenderloveMigration + # + # create_table(:apples) do |t| + # t.string :variety + # end + # end + # end + # + # This command can be nested. + def revert(*migration_classes, &block) + run(*migration_classes.reverse, revert: true) unless migration_classes.empty? + if block_given? + if connection.respond_to? :revert + connection.revert(&block) + else + recorder = command_recorder + @connection = recorder + suppress_messages do + connection.revert(&block) + end + @connection = recorder.delegate + recorder.replay(self) + end + end + end + + def reverting? + connection.respond_to?(:reverting) && connection.reverting + end + + ReversibleBlockHelper = Struct.new(:reverting) do # :nodoc: + def up + yield unless reverting + end + + def down + yield if reverting + end + end + + # Used to specify an operation that can be run in one direction or another. + # Call the methods +up+ and +down+ of the yielded object to run a block + # only in one given direction. + # The whole block will be called in the right order within the migration. + # + # In the following example, the looping on users will always be done + # when the three columns 'first_name', 'last_name' and 'full_name' exist, + # even when migrating down: + # + # class SplitNameMigration < ActiveRecord::Migration[8.0] + # def change + # add_column :users, :first_name, :string + # add_column :users, :last_name, :string + # + # reversible do |dir| + # User.reset_column_information + # User.all.each do |u| + # dir.up { u.first_name, u.last_name = u.full_name.split(' ') } + # dir.down { u.full_name = "#{u.first_name} #{u.last_name}" } + # u.save + # end + # end + # + # revert { add_column :users, :full_name, :string } + # end + # end + def reversible + helper = ReversibleBlockHelper.new(reverting?) + execute_block { yield helper } + end + + # Used to specify an operation that is only run when migrating up + # (for example, populating a new column with its initial values). + # + # In the following example, the new column +published+ will be given + # the value +true+ for all existing records. + # + # class AddPublishedToPosts < ActiveRecord::Migration[8.0] + # def change + # add_column :posts, :published, :boolean, default: false + # up_only do + # execute "update posts set published = 'true'" + # end + # end + # end + def up_only(&block) + execute_block(&block) unless reverting? + end + + # Runs the given migration classes. + # Last argument can specify options: + # + # - +:direction+ - Default is +:up+. + # - +:revert+ - Default is +false+. + def run(*migration_classes) + opts = migration_classes.extract_options! + dir = opts[:direction] || :up + dir = (dir == :down ? :up : :down) if opts[:revert] + if reverting? + # If in revert and going :up, say, we want to execute :down without reverting, so + revert { run(*migration_classes, direction: dir, revert: true) } + else + migration_classes.each do |migration_class| + migration_class.new.exec_migration(connection, dir) + end + end + end + + def up + self.class.delegate = self + return unless self.class.respond_to?(:up) + self.class.up + end + + def down + self.class.delegate = self + return unless self.class.respond_to?(:down) + self.class.down + end + + # Execute this migration in the named direction + def migrate(direction) + return unless respond_to?(direction) + + case direction + when :up then announce "migrating" + when :down then announce "reverting" + end + + time_elapsed = nil + ActiveRecord::Tasks::DatabaseTasks.migration_connection.pool.with_connection do |conn| + time_elapsed = ActiveSupport::Benchmark.realtime do + exec_migration(conn, direction) + end + end + + case direction + when :up then announce "migrated (%.4fs)" % time_elapsed; write + when :down then announce "reverted (%.4fs)" % time_elapsed; write + end + end + + def exec_migration(conn, direction) + @connection = conn + if respond_to?(:change) + if direction == :down + revert { change } + else + change + end + else + public_send(direction) + end + ensure + @connection = nil + @execution_strategy = nil + end + + def write(text = "") + puts(text) if verbose + end + + def announce(message) + text = "#{version} #{name}: #{message}" + length = [0, 75 - text.length].max + write "== %s %s" % [text, "=" * length] + end + + # Takes a message argument and outputs it as is. + # A second boolean argument can be passed to specify whether to indent or not. + def say(message, subitem = false) + write "#{subitem ? " ->" : "--"} #{message}" + end + + # Outputs text along with how long it took to run its block. + # If the block returns an integer it assumes it is the number of rows affected. + def say_with_time(message) + say(message) + result = nil + time_elapsed = ActiveSupport::Benchmark.realtime { result = yield } + say "%.4fs" % time_elapsed, :subitem + say("#{result} rows", :subitem) if result.is_a?(Integer) + result + end + + # Takes a block as an argument and suppresses any output generated by the block. + def suppress_messages + save, self.verbose = verbose, false + yield + ensure + self.verbose = save + end + + def connection + @connection || ActiveRecord::Tasks::DatabaseTasks.migration_connection + end + + def connection_pool + @pool || ActiveRecord::Tasks::DatabaseTasks.migration_connection_pool + end + + def method_missing(method, *arguments, &block) + say_with_time "#{method}(#{format_arguments(arguments)})" do + unless connection.respond_to? :revert + unless arguments.empty? || [:execute, :enable_extension, :disable_extension].include?(method) + arguments[0] = proper_table_name(arguments.first, table_name_options) + if method == :rename_table || + (method == :remove_foreign_key && !arguments.second.is_a?(Hash)) + arguments[1] = proper_table_name(arguments.second, table_name_options) + end + end + end + return super unless execution_strategy.respond_to?(method) + execution_strategy.send(method, *arguments, &block) + end + end + ruby2_keywords(:method_missing) + + def copy(destination, sources, options = {}) + copied = [] + + FileUtils.mkdir_p(destination) unless File.exist?(destination) + schema_migration = SchemaMigration::NullSchemaMigration.new + internal_metadata = InternalMetadata::NullInternalMetadata.new + + destination_migrations = ActiveRecord::MigrationContext.new(destination, schema_migration, internal_metadata).migrations + last = destination_migrations.last + sources.each do |scope, path| + source_migrations = ActiveRecord::MigrationContext.new(path, schema_migration, internal_metadata).migrations + + source_migrations.each do |migration| + source = File.binread(migration.filename) + inserted_comment = "# This migration comes from #{scope} (originally #{migration.version})\n" + magic_comments = +"" + loop do + # If we have a magic comment in the original migration, + # insert our comment after the first newline(end of the magic comment line) + # so the magic keep working. + # Note that magic comments must be at the first line(except sh-bang). + source.sub!(/\A(?:#.*\b(?:en)?coding:\s*\S+|#\s*frozen_string_literal:\s*(?:true|false)).*\n/) do |magic_comment| + magic_comments << magic_comment; "" + end || break + end + + if !magic_comments.empty? && source.start_with?("\n") + magic_comments << "\n" + source = source[1..-1] + end + + source = "#{magic_comments}#{inserted_comment}#{source}" + + if duplicate = destination_migrations.detect { |m| m.name == migration.name } + if options[:on_skip] && duplicate.scope != scope.to_s + options[:on_skip].call(scope, migration) + end + next + end + + migration.version = next_migration_number(last ? last.version + 1 : 0).to_i + new_path = File.join(destination, "#{migration.version}_#{migration.name.underscore}.#{scope}.rb") + old_path, migration.filename = migration.filename, new_path + last = migration + + File.binwrite(migration.filename, source) + copied << migration + options[:on_copy].call(scope, migration, old_path) if options[:on_copy] + destination_migrations << migration + end + end + + copied + end + + # Finds the correct table name given an Active Record object. + # Uses the Active Record object's own table_name, or pre/suffix from the + # options passed in. + def proper_table_name(name, options = {}) + if name.respond_to? :table_name + name.table_name + else + "#{options[:table_name_prefix]}#{name}#{options[:table_name_suffix]}" + end + end + + # Determines the version number of the next migration. + def next_migration_number(number) + if ActiveRecord.timestamped_migrations + [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max + else + "%.3d" % number.to_i + end + end + + # Builds a hash for use in ActiveRecord::Migration#proper_table_name using + # the Active Record object's table_name prefix and suffix + def table_name_options(config = ActiveRecord::Base) # :nodoc: + { + table_name_prefix: config.table_name_prefix, + table_name_suffix: config.table_name_suffix + } + end + + private + def execute_block + if connection.respond_to? :execute_block + super # use normal delegation to record the block + else + yield + end + end + + def format_arguments(arguments) + arg_list = arguments[0...-1].map(&:inspect) + last_arg = arguments.last + if last_arg.is_a?(Hash) + last_arg = last_arg.reject { |k, _v| internal_option?(k) } + arg_list << last_arg.inspect unless last_arg.empty? + else + arg_list << last_arg.inspect + end + arg_list.join(", ") + end + + def internal_option?(option_name) + option_name.start_with?("_") + end + + def command_recorder + CommandRecorder.new(connection) + end + end + + # MigrationProxy is used to defer loading of the actual migration classes + # until they are needed + MigrationProxy = Struct.new(:name, :version, :filename, :scope) do + def initialize(name, version, filename, scope) + super + @migration = nil + end + + def basename + File.basename(filename) + end + + delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration + + private + def migration + @migration ||= load_migration + end + + def load_migration + Object.send(:remove_const, name) rescue nil + + load(File.expand_path(filename)) + name.constantize.new(name, version) + end + end + + # = \Migration \Context + # + # MigrationContext sets the context in which a migration is run. + # + # A migration context requires the path to the migrations is set + # in the +migrations_paths+ parameter. Optionally a +schema_migration+ + # class can be provided. Multiple database applications will instantiate + # a +SchemaMigration+ object per database. From the Rake tasks, \Rails will + # handle this for you. + class MigrationContext + attr_reader :migrations_paths, :schema_migration, :internal_metadata + + def initialize(migrations_paths, schema_migration = nil, internal_metadata = nil) + @migrations_paths = migrations_paths + @schema_migration = schema_migration || SchemaMigration.new(connection_pool) + @internal_metadata = internal_metadata || InternalMetadata.new(connection_pool) + end + + # Runs the migrations in the +migrations_path+. + # + # If +target_version+ is +nil+, +migrate+ will run +up+. + # + # If the +current_version+ and +target_version+ are both + # 0 then an empty array will be returned and no migrations + # will be run. + # + # If the +current_version+ in the schema is greater than + # the +target_version+, then +down+ will be run. + # + # If none of the conditions are met, +up+ will be run with + # the +target_version+. + def migrate(target_version = nil, &block) + case + when target_version.nil? + up(target_version, &block) + when current_version == 0 && target_version == 0 + [] + when current_version > target_version + down(target_version, &block) + else + up(target_version, &block) + end + end + + def rollback(steps = 1) # :nodoc: + move(:down, steps) + end + + def forward(steps = 1) # :nodoc: + move(:up, steps) + end + + def up(target_version = nil, &block) # :nodoc: + selected_migrations = if block_given? + migrations.select(&block) + else + migrations + end + + Migrator.new(:up, selected_migrations, schema_migration, internal_metadata, target_version).migrate + end + + def down(target_version = nil, &block) # :nodoc: + selected_migrations = if block_given? + migrations.select(&block) + else + migrations + end + + Migrator.new(:down, selected_migrations, schema_migration, internal_metadata, target_version).migrate + end + + def run(direction, target_version) # :nodoc: + Migrator.new(direction, migrations, schema_migration, internal_metadata, target_version).run + end + + def open # :nodoc: + Migrator.new(:up, migrations, schema_migration, internal_metadata) + end + + def get_all_versions # :nodoc: + if schema_migration.table_exists? + schema_migration.integer_versions + else + [] + end + end + + def current_version # :nodoc: + get_all_versions.max || 0 + rescue ActiveRecord::NoDatabaseError + end + + def needs_migration? # :nodoc: + pending_migration_versions.size > 0 + end + + def pending_migration_versions # :nodoc: + migrations.collect(&:version) - get_all_versions + end + + def migrations # :nodoc: + migrations = migration_files.map do |file| + version, name, scope = parse_migration_filename(file) + raise IllegalMigrationNameError.new(file) unless version + if validate_timestamp? && !valid_migration_timestamp?(version) + raise InvalidMigrationTimestampError.new(version, name) + end + version = version.to_i + name = name.camelize + + MigrationProxy.new(name, version, file, scope) + end + + migrations.sort_by(&:version) + end + + def migrations_status # :nodoc: + db_list = schema_migration.normalized_versions + + file_list = migration_files.filter_map do |file| + version, name, scope = parse_migration_filename(file) + raise IllegalMigrationNameError.new(file) unless version + if validate_timestamp? && !valid_migration_timestamp?(version) + raise InvalidMigrationTimestampError.new(version, name) + end + version = schema_migration.normalize_migration_number(version) + status = db_list.delete(version) ? "up" : "down" + [status, version, (name + scope).humanize] + end + + db_list.map! do |version| + ["up", version, "********** NO FILE **********"] + end + + (db_list + file_list).sort_by { |_, version, _| version.to_i } + end + + def current_environment # :nodoc: + ActiveRecord::ConnectionHandling::DEFAULT_ENV.call + end + + def protected_environment? # :nodoc: + ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment + end + + def last_stored_environment # :nodoc: + internal_metadata = connection_pool.internal_metadata + return nil unless internal_metadata.enabled? + return nil if current_version == 0 + raise NoEnvironmentInSchemaError unless internal_metadata.table_exists? + + environment = internal_metadata[:environment] + raise NoEnvironmentInSchemaError unless environment + environment + end + + private + def connection + ActiveRecord::Tasks::DatabaseTasks.migration_connection + end + + def connection_pool + ActiveRecord::Tasks::DatabaseTasks.migration_connection_pool + end + + def migration_files + paths = Array(migrations_paths) + Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }] + end + + def parse_migration_filename(filename) + File.basename(filename).scan(Migration::MigrationFilenameRegexp).first + end + + def validate_timestamp? + ActiveRecord.timestamped_migrations && ActiveRecord.validate_migration_timestamps + end + + def valid_migration_timestamp?(version) + version.to_i < (Time.now.utc + 1.day).strftime("%Y%m%d%H%M%S").to_i + end + + def move(direction, steps) + migrator = Migrator.new(direction, migrations, schema_migration, internal_metadata) + + if current_version != 0 && !migrator.current_migration + raise UnknownMigrationVersionError.new(current_version) + end + + start_index = + if current_version == 0 + 0 + else + migrator.migrations.index(migrator.current_migration) + end + + finish = migrator.migrations[start_index + steps] + version = finish ? finish.version : 0 + public_send(direction, version) + end + end + + class Migrator # :nodoc: + class << self + attr_accessor :migrations_paths + + # For cases where a table doesn't exist like loading from schema cache + def current_version + connection_pool = ActiveRecord::Tasks::DatabaseTasks.migration_connection_pool + schema_migration = SchemaMigration.new(connection_pool) + internal_metadata = InternalMetadata.new(connection_pool) + + MigrationContext.new(migrations_paths, schema_migration, internal_metadata).current_version + end + end + + self.migrations_paths = ["db/migrate"] + + def initialize(direction, migrations, schema_migration, internal_metadata, target_version = nil) + @direction = direction + @target_version = target_version + @migrated_versions = nil + @migrations = migrations + @schema_migration = schema_migration + @internal_metadata = internal_metadata + + validate(@migrations) + + @schema_migration.create_table + @internal_metadata.create_table + end + + def current_version + migrated.max || 0 + end + + def current_migration + migrations.detect { |m| m.version == current_version } + end + alias :current :current_migration + + def run + if use_advisory_lock? + with_advisory_lock { run_without_lock } + else + run_without_lock + end + end + + def migrate + if use_advisory_lock? + with_advisory_lock { migrate_without_lock } + else + migrate_without_lock + end + end + + def runnable + runnable = migrations[start..finish] + if up? + runnable.reject { |m| ran?(m) } + else + # skip the last migration if we're headed down, but not ALL the way down + runnable.pop if target + runnable.find_all { |m| ran?(m) } + end + end + + def migrations + down? ? @migrations.reverse : @migrations.sort_by(&:version) + end + + def pending_migrations + already_migrated = migrated + migrations.reject { |m| already_migrated.include?(m.version) } + end + + def migrated + @migrated_versions || load_migrated + end + + def load_migrated + @migrated_versions = Set.new(@schema_migration.integer_versions) + end + + private + def connection + ActiveRecord::Tasks::DatabaseTasks.migration_connection + end + + # Used for running a specific migration. + def run_without_lock + migration = migrations.detect { |m| m.version == @target_version } + raise UnknownMigrationVersionError.new(@target_version) if migration.nil? + + record_environment + execute_migration_in_transaction(migration) + end + + # Used for running multiple migrations up to or down to a certain value. + def migrate_without_lock + if invalid_target? + raise UnknownMigrationVersionError.new(@target_version) + end + + record_environment + runnable.each(&method(:execute_migration_in_transaction)) + end + + # Stores the current environment in the database. + def record_environment + return if down? + + @internal_metadata[:environment] = connection.pool.db_config.env_name + end + + def ran?(migration) + migrated.include?(migration.version.to_i) + end + + # Return true if a valid version is not provided. + def invalid_target? + @target_version && @target_version != 0 && !target + end + + def execute_migration_in_transaction(migration) + return if down? && !migrated.include?(migration.version.to_i) + return if up? && migrated.include?(migration.version.to_i) + + Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger + + ddl_transaction(migration) do + migration.migrate(@direction) + record_version_state_after_migrating(migration.version) + end + rescue => e + msg = +"An error has occurred, " + msg << "this and " if use_transaction?(migration) + msg << "all later migrations canceled:\n\n#{e}" + raise StandardError, msg, e.backtrace + end + + def target + migrations.detect { |m| m.version == @target_version } + end + + def finish + migrations.index(target) || migrations.size - 1 + end + + def start + up? ? 0 : (migrations.index(current) || 0) + end + + def validate(migrations) + name, = migrations.group_by(&:name).find { |_, v| v.length > 1 } + raise DuplicateMigrationNameError.new(name) if name + + version, = migrations.group_by(&:version).find { |_, v| v.length > 1 } + raise DuplicateMigrationVersionError.new(version) if version + end + + def record_version_state_after_migrating(version) + if down? + migrated.delete(version) + @schema_migration.delete_version(version.to_s) + else + migrated << version + @schema_migration.create_version(version.to_s) + end + end + + def up? + @direction == :up + end + + def down? + @direction == :down + end + + # Wrap the migration in a transaction only if supported by the adapter. + def ddl_transaction(migration, &block) + if use_transaction?(migration) + connection.transaction(&block) + else + yield + end + end + + def use_transaction?(migration) + !migration.disable_ddl_transaction && connection.supports_ddl_transactions? + end + + def use_advisory_lock? + connection.advisory_locks_enabled? + end + + def with_advisory_lock + lock_id = generate_migrator_advisory_lock_id + + got_lock = connection.get_advisory_lock(lock_id) + raise ConcurrentMigrationError unless got_lock + load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock + yield + ensure + if got_lock && !connection.release_advisory_lock(lock_id) + raise ConcurrentMigrationError.new( + ConcurrentMigrationError::RELEASE_LOCK_FAILED_MESSAGE + ) + end + end + + MIGRATOR_SALT = 2053462845 + def generate_migrator_advisory_lock_id + db_name_hash = Zlib.crc32(connection.current_database) + MIGRATOR_SALT * db_name_hash + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration/command_recorder.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration/command_recorder.rb new file mode 100644 index 00000000..360b4a34 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration/command_recorder.rb @@ -0,0 +1,409 @@ +# frozen_string_literal: true + +module ActiveRecord + class Migration + # = \Migration Command Recorder + # + # +ActiveRecord::Migration::CommandRecorder+ records commands done during + # a migration and knows how to reverse those commands. The CommandRecorder + # knows how to invert the following commands: + # + # * add_column + # * add_foreign_key + # * add_check_constraint + # * add_exclusion_constraint + # * add_unique_constraint + # * add_index + # * add_reference + # * add_timestamps + # * change_column_default (must supply a +:from+ and +:to+ option) + # * change_column_null + # * change_column_comment (must supply a +:from+ and +:to+ option) + # * change_table_comment (must supply a +:from+ and +:to+ option) + # * create_enum + # * create_join_table + # * create_virtual_table + # * create_table + # * disable_extension + # * drop_enum (must supply a list of values) + # * drop_join_table + # * drop_virtual_table (must supply options) + # * drop_table (must supply a block) + # * enable_extension + # * remove_column (must supply a type) + # * remove_columns (must supply a +:type+ option) + # * remove_foreign_key (must supply a second table) + # * remove_check_constraint + # * remove_exclusion_constraint + # * remove_unique_constraint + # * remove_index + # * remove_reference + # * remove_timestamps + # * rename_column + # * rename_enum + # * rename_enum_value (must supply a +:from+ and +:to+ option) + # * rename_index + # * rename_table + class CommandRecorder + ReversibleAndIrreversibleMethods = [ + :create_table, :create_join_table, :rename_table, :add_column, :remove_column, + :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, + :change_column_default, :add_reference, :remove_reference, :transaction, + :drop_join_table, :drop_table, :execute_block, :enable_extension, :disable_extension, + :change_column, :execute, :remove_columns, :change_column_null, + :add_foreign_key, :remove_foreign_key, + :change_column_comment, :change_table_comment, + :add_check_constraint, :remove_check_constraint, + :add_exclusion_constraint, :remove_exclusion_constraint, + :add_unique_constraint, :remove_unique_constraint, + :create_enum, :drop_enum, :rename_enum, :add_enum_value, :rename_enum_value, + :create_schema, :drop_schema, + :create_virtual_table, :drop_virtual_table + ] + include JoinTable + + attr_accessor :commands, :delegate, :reverting + + def initialize(delegate = nil) + @commands = [] + @delegate = delegate + @reverting = false + end + + # While executing the given block, the recorded will be in reverting mode. + # All commands recorded will end up being recorded reverted + # and in reverse order. + # For example: + # + # recorder.revert{ recorder.record(:rename_table, [:old, :new]) } + # # same effect as recorder.record(:rename_table, [:new, :old]) + def revert + @reverting = !@reverting + previous = @commands + @commands = [] + yield + ensure + @commands = previous.concat(@commands.reverse) + @reverting = !@reverting + end + + # Record +command+. +command+ should be a method name and arguments. + # For example: + # + # recorder.record(:method_name, [:arg1, :arg2]) + def record(*command, &block) + if @reverting + @commands << inverse_of(*command, &block) + else + @commands << (command << block) + end + end + + # Returns the inverse of the given command. For example: + # + # recorder.inverse_of(:rename_table, [:old, :new]) + # # => [:rename_table, [:new, :old]] + # + # If the inverse of a command requires several commands, returns array of commands. + # + # recorder.inverse_of(:remove_columns, [:some_table, :foo, :bar, type: :string]) + # # => [[:add_column, :some_table, :foo, :string], [:add_column, :some_table, :bar, :string]] + # + # This method will raise an +IrreversibleMigration+ exception if it cannot + # invert the +command+. + def inverse_of(command, args, &block) + method = :"invert_#{command}" + raise IrreversibleMigration, <<~MSG unless respond_to?(method, true) + This migration uses #{command}, which is not automatically reversible. + To make the migration reversible you can either: + 1. Define #up and #down methods in place of the #change method. + 2. Use the #reversible method to define reversible behavior. + MSG + send(method, args, &block) + end + + ReversibleAndIrreversibleMethods.each do |method| + class_eval <<-EOV, __FILE__, __LINE__ + 1 + def #{method}(*args, &block) # def create_table(*args, &block) + record(:"#{method}", args, &block) # record(:create_table, args, &block) + end # end + EOV + ruby2_keywords(method) + end + alias :add_belongs_to :add_reference + alias :remove_belongs_to :remove_reference + + def change_table(table_name, **options) # :nodoc: + if delegate.supports_bulk_alter? && options[:bulk] + recorder = self.class.new(self.delegate) + recorder.reverting = @reverting + yield recorder.delegate.update_table_definition(table_name, recorder) + commands = recorder.commands + @commands << [:change_table, [table_name], -> t { bulk_change_table(table_name, commands) }] + else + yield delegate.update_table_definition(table_name, self) + end + end + + def replay(migration) + commands.each do |cmd, args, block| + migration.send(cmd, *args, &block) + end + end + + private + module StraightReversions # :nodoc: + private + { + execute_block: :execute_block, + create_table: :drop_table, + create_join_table: :drop_join_table, + add_column: :remove_column, + add_index: :remove_index, + add_timestamps: :remove_timestamps, + add_reference: :remove_reference, + add_foreign_key: :remove_foreign_key, + add_check_constraint: :remove_check_constraint, + add_exclusion_constraint: :remove_exclusion_constraint, + add_unique_constraint: :remove_unique_constraint, + enable_extension: :disable_extension, + create_enum: :drop_enum, + create_schema: :drop_schema, + create_virtual_table: :drop_virtual_table + }.each do |cmd, inv| + [[inv, cmd], [cmd, inv]].uniq.each do |method, inverse| + class_eval <<-EOV, __FILE__, __LINE__ + 1 + def invert_#{method}(args, &block) # def invert_create_table(args, &block) + [:#{inverse}, args, block] # [:drop_table, args, block] + end # end + EOV + end + end + end + + include StraightReversions + + def invert_transaction(args, &block) + sub_recorder = CommandRecorder.new(delegate) + sub_recorder.revert(&block) + + invertions_proc = proc { + sub_recorder.replay(self) + } + + [:transaction, args, invertions_proc] + end + + def invert_create_table(args, &block) + if args.last.is_a?(Hash) + args.last.delete(:if_not_exists) + end + super + end + + def invert_drop_table(args, &block) + options = args.extract_options! + options.delete(:if_exists) + + if args.size > 1 + raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given a single table name." + end + + if args.size == 1 && options == {} && block == nil + raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)." + end + + args << options unless options.empty? + + super(args, &block) + end + + def invert_rename_table(args) + old_name, new_name, options = args + args = [new_name, old_name] + args << options if options + [:rename_table, args] + end + + def invert_remove_column(args) + raise ActiveRecord::IrreversibleMigration, "remove_column is only reversible if given a type." if args.size <= 2 + super + end + + def invert_remove_columns(args) + unless args[-1].is_a?(Hash) && args[-1].has_key?(:type) + raise ActiveRecord::IrreversibleMigration, "remove_columns is only reversible if given a type." + end + + [:add_columns, args] + end + + def invert_rename_index(args) + table_name, old_name, new_name = args + [:rename_index, [table_name, new_name, old_name]] + end + + def invert_rename_column(args) + table_name, old_name, new_name = args + [:rename_column, [table_name, new_name, old_name]] + end + + def invert_remove_index(args) + options = args.extract_options! + table, columns = args + + columns ||= options.delete(:column) + + unless columns + raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option." + end + + options.delete(:if_exists) + + args = [table, columns] + args << options unless options.empty? + + [:add_index, args] + end + + alias :invert_add_belongs_to :invert_add_reference + alias :invert_remove_belongs_to :invert_remove_reference + + def invert_change_column_default(args) + table, column, options = args + + unless options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to) + raise ActiveRecord::IrreversibleMigration, "change_column_default is only reversible if given a :from and :to option." + end + + [:change_column_default, [table, column, from: options[:to], to: options[:from]]] + end + + def invert_change_column_null(args) + args[2] = !args[2] + [:change_column_null, args] + end + + def invert_add_foreign_key(args) + args.last.delete(:validate) if args.last.is_a?(Hash) + super + end + + def invert_remove_foreign_key(args) + options = args.extract_options! + from_table, to_table = args + + to_table ||= options.delete(:to_table) + + raise ActiveRecord::IrreversibleMigration, "remove_foreign_key is only reversible if given a second table" if to_table.nil? + + reversed_args = [from_table, to_table] + reversed_args << options unless options.empty? + + [:add_foreign_key, reversed_args] + end + + def invert_change_column_comment(args) + table, column, options = args + + unless options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to) + raise ActiveRecord::IrreversibleMigration, "change_column_comment is only reversible if given a :from and :to option." + end + + [:change_column_comment, [table, column, from: options[:to], to: options[:from]]] + end + + def invert_change_table_comment(args) + table, options = args + + unless options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to) + raise ActiveRecord::IrreversibleMigration, "change_table_comment is only reversible if given a :from and :to option." + end + + [:change_table_comment, [table, from: options[:to], to: options[:from]]] + end + + def invert_add_check_constraint(args) + if (options = args.last).is_a?(Hash) + options.delete(:validate) + options[:if_exists] = options.delete(:if_not_exists) if options.key?(:if_not_exists) + end + super + end + + def invert_remove_check_constraint(args) + raise ActiveRecord::IrreversibleMigration, "remove_check_constraint is only reversible if given an expression." if args.size < 2 + + if (options = args.last).is_a?(Hash) + options[:if_not_exists] = options.delete(:if_exists) if options.key?(:if_exists) + end + super + end + + def invert_remove_exclusion_constraint(args) + raise ActiveRecord::IrreversibleMigration, "remove_exclusion_constraint is only reversible if given an expression." if args.size < 2 + super + end + + def invert_add_unique_constraint(args) + options = args.dup.extract_options! + + raise ActiveRecord::IrreversibleMigration, "add_unique_constraint is not reversible if given an using_index." if options[:using_index] + super + end + + def invert_remove_unique_constraint(args) + _table, columns = args.dup.tap(&:extract_options!) + + raise ActiveRecord::IrreversibleMigration, "remove_unique_constraint is only reversible if given an column_name." if columns.blank? + super + end + + def invert_drop_enum(args) + _enum, values = args.dup.tap(&:extract_options!) + raise ActiveRecord::IrreversibleMigration, "drop_enum is only reversible if given a list of enum values." unless values + super + end + + def invert_rename_enum(args) + name, new_name, = args + + if new_name.is_a?(Hash) && new_name.key?(:to) + new_name = new_name[:to] + end + + [:rename_enum, [new_name, name]] + end + + def invert_rename_enum_value(args) + type_name, options = args + + unless options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to) + raise ActiveRecord::IrreversibleMigration, "rename_enum_value is only reversible if given a :from and :to option." + end + + options[:to], options[:from] = options[:from], options[:to] + [:rename_enum_value, [type_name, options]] + end + + def invert_drop_virtual_table(args) + _enum, values = args.dup.tap(&:extract_options!) + raise ActiveRecord::IrreversibleMigration, "drop_virtual_table is only reversible if given options." unless values + super + end + + def respond_to_missing?(method, _) + super || delegate.respond_to?(method) + end + + # Forwards any missing method call to the \target. + def method_missing(method, ...) + if delegate.respond_to?(method) + delegate.public_send(method, ...) + else + super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration/compatibility.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration/compatibility.rb new file mode 100644 index 00000000..994aeb81 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration/compatibility.rb @@ -0,0 +1,490 @@ +# frozen_string_literal: true + +module ActiveRecord + class Migration + module Compatibility # :nodoc: all + def self.find(version) + version = version.to_s + name = "V#{version.tr('.', '_')}" + unless const_defined?(name) + versions = constants.grep(/\AV[0-9_]+\z/).map { |s| s.to_s.delete("V").tr("_", ".").inspect } + raise ArgumentError, "Unknown migration version #{version.inspect}; expected one of #{versions.sort.join(', ')}" + end + const_get(name) + end + + # This file exists to ensure that old migrations run the same way they did before a Rails upgrade. + # e.g. if you write a migration on Rails 6.1, then upgrade to Rails 7, the migration should do the same thing to your + # database as it did when you were running Rails 6.1 + # + # "Current" is an alias for `ActiveRecord::Migration`, it represents the current Rails version. + # New migration functionality that will never be backward compatible should be added directly to `ActiveRecord::Migration`. + # + # There are classes for each prior Rails version. Each class descends from the *next* Rails version, so: + # 5.2 < 6.0 < 6.1 < 7.0 < 7.1 < 7.2 < 8.0 + # + # If you are introducing new migration functionality that should only apply from Rails 7 onward, then you should + # find the class that immediately precedes it (6.1), and override the relevant migration methods to undo your changes. + # + # For example, Rails 6 added a default value for the `precision` option on datetime columns. So in this file, the `V5_2` + # class sets the value of `precision` to `nil` if it's not explicitly provided. This way, the default value will not apply + # for migrations written for 5.2, but will for migrations written for 6.0. + V8_0 = Current + + class V7_2 < V8_0 + end + + class V7_1 < V7_2 + end + + class V7_0 < V7_1 + module LegacyIndexName + private + def legacy_index_name(table_name, options) + if Hash === options + if options[:column] + "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}" + elsif options[:name] + options[:name] + else + raise ArgumentError, "You must specify the index name" + end + else + legacy_index_name(table_name, index_name_options(options)) + end + end + + def index_name_options(column_names) + if expression_column_name?(column_names) + column_names = column_names.scan(/\w+/).join("_") + end + + { column: column_names } + end + + def expression_column_name?(column_name) + column_name.is_a?(String) && /\W/.match?(column_name) + end + end + + module TableDefinition + include LegacyIndexName + + def column(name, type, **options) + options[:_skip_validate_options] = true + super + end + + def change(name, type, **options) + options[:_skip_validate_options] = true + super + end + + def index(column_name, **options) + options[:name] = legacy_index_name(name, column_name) if options[:name].nil? + super + end + + def references(*args, **options) + options[:_skip_validate_options] = true + super + end + + private + def raise_on_if_exist_options(options) + end + end + + include LegacyIndexName + + def add_column(table_name, column_name, type, **options) + options[:_skip_validate_options] = true + super + end + + def add_index(table_name, column_name, **options) + options[:name] = legacy_index_name(table_name, column_name) if options[:name].nil? + super + end + + def add_reference(table_name, ref_name, **options) + options[:_skip_validate_options] = true + super + end + alias :add_belongs_to :add_reference + + def create_table(table_name, **options) + options[:_uses_legacy_table_name] = true + options[:_skip_validate_options] = true + + super + end + + def rename_table(table_name, new_name, **options) + options[:_uses_legacy_table_name] = true + options[:_uses_legacy_index_name] = true + super + end + + def change_column(table_name, column_name, type, **options) + options[:_skip_validate_options] = true + if connection.adapter_name == "Mysql2" || connection.adapter_name == "Trilogy" + options[:collation] ||= :no_collation + end + super + end + + def change_column_null(table_name, column_name, null, default = nil) + super(table_name, column_name, !!null, default) + end + + def disable_extension(name, **options) + if connection.adapter_name == "PostgreSQL" + options[:force] = :cascade + end + super + end + + def add_foreign_key(from_table, to_table, **options) + if connection.adapter_name == "PostgreSQL" && options[:deferrable] == true + options[:deferrable] = :immediate + end + super + end + + private + def compatible_table_definition(t) + class << t + prepend TableDefinition + end + super + end + end + + class V6_1 < V7_0 + class PostgreSQLCompat + def self.compatible_timestamp_type(type, connection) + if connection.adapter_name == "PostgreSQL" + # For Rails <= 6.1, :datetime was aliased to :timestamp + # See: https://github.com/rails/rails/blob/v6.1.3.2/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L108 + # From Rails 7 onwards, you can define what :datetime resolves to (the default is still :timestamp) + # See `ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type` + type.to_sym == :datetime ? :timestamp : type + else + type + end + end + end + + def add_column(table_name, column_name, type, **options) + if type == :datetime + options[:precision] ||= nil + end + + type = PostgreSQLCompat.compatible_timestamp_type(type, connection) + super + end + + def change_column(table_name, column_name, type, **options) + if type == :datetime + options[:precision] ||= nil + end + + type = PostgreSQLCompat.compatible_timestamp_type(type, connection) + super + end + + module TableDefinition + def new_column_definition(name, type, **options) + type = PostgreSQLCompat.compatible_timestamp_type(type, @conn) + super + end + + def change(name, type, index: nil, **options) + options[:precision] ||= nil + super + end + + def column(name, type, index: nil, **options) + options[:precision] ||= nil + super + end + + private + def raise_on_if_exist_options(options) + end + end + + private + def compatible_table_definition(t) + class << t + prepend TableDefinition + end + super + end + end + + class V6_0 < V6_1 + class ReferenceDefinition < ConnectionAdapters::ReferenceDefinition + def index_options(table_name) + as_options(index) + end + end + + module TableDefinition + def references(*args, **options) + options[:_uses_legacy_reference_index_name] = true + super + end + alias :belongs_to :references + + def column(name, type, index: nil, **options) + options[:precision] ||= nil + super + end + + private + def raise_on_if_exist_options(options) + end + end + + def add_reference(table_name, ref_name, **options) + if connection.adapter_name == "SQLite" + options[:type] = :integer + end + + options[:_uses_legacy_reference_index_name] = true + super + end + alias :add_belongs_to :add_reference + + private + def compatible_table_definition(t) + class << t + prepend TableDefinition + end + super + end + end + + class V5_2 < V6_0 + module TableDefinition + def timestamps(**options) + options[:precision] ||= nil + super + end + + def column(name, type, index: nil, **options) + options[:precision] ||= nil + super + end + + private + def raise_on_if_exist_options(options) + end + + def raise_on_duplicate_column(name) + end + end + + module CommandRecorder + def invert_transaction(args, &block) + [:transaction, args, block] + end + + def invert_change_column_comment(args) + [:change_column_comment, args] + end + + def invert_change_table_comment(args) + [:change_table_comment, args] + end + end + + def add_timestamps(table_name, **options) + options[:precision] ||= nil + super + end + + private + def compatible_table_definition(t) + class << t + prepend TableDefinition + end + super + end + + def command_recorder + recorder = super + class << recorder + prepend CommandRecorder + end + recorder + end + end + + class V5_1 < V5_2 + def change_column(table_name, column_name, type, **options) + if connection.adapter_name == "PostgreSQL" + super(table_name, column_name, type, **options.except(:default, :null, :comment)) + connection.change_column_default(table_name, column_name, options[:default]) if options.key?(:default) + connection.change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null) + connection.change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment) + else + super + end + end + + def create_table(table_name, **options) + if connection.adapter_name == "Mysql2" || connection.adapter_name == "Trilogy" + super(table_name, options: "ENGINE=InnoDB", **options) + else + super + end + end + end + + class V5_0 < V5_1 + module TableDefinition + def primary_key(name, type = :primary_key, **options) + type = :integer if type == :primary_key + super + end + + def references(*args, **options) + super(*args, type: :integer, **options) + end + alias :belongs_to :references + + private + def raise_on_if_exist_options(options) + end + end + + def create_table(table_name, **options) + if connection.adapter_name == "PostgreSQL" + if options[:id] == :uuid && !options.key?(:default) + options[:default] = "uuid_generate_v4()" + end + end + + unless ["Mysql2", "Trilogy"].include?(connection.adapter_name) && options[:id] == :bigint + if [:integer, :bigint].include?(options[:id]) && !options.key?(:default) + options[:default] = nil + end + end + + # Since 5.1 PostgreSQL adapter uses bigserial type for primary + # keys by default and MySQL uses bigint. This compat layer makes old migrations utilize + # serial/int type instead -- the way it used to work before 5.1. + unless options.key?(:id) + options[:id] = :integer + end + + super + end + + def create_join_table(table_1, table_2, column_options: {}, **options) + column_options.reverse_merge!(type: :integer) + super + end + + def add_column(table_name, column_name, type, **options) + if type == :primary_key + type = :integer + options[:primary_key] = true + elsif type == :datetime + options[:precision] ||= nil + end + super + end + + def add_reference(table_name, ref_name, **options) + super(table_name, ref_name, type: :integer, **options) + end + alias :add_belongs_to :add_reference + + private + def compatible_table_definition(t) + class << t + prepend TableDefinition + end + super + end + end + + class V4_2 < V5_0 + module TableDefinition + def references(*, **options) + options[:index] ||= false + super + end + alias :belongs_to :references + + def timestamps(**options) + options[:null] = true if options[:null].nil? + super + end + + private + def raise_on_if_exist_options(options) + end + end + + def add_reference(table_name, ref_name, **options) + options[:index] ||= false + super + end + alias :add_belongs_to :add_reference + + def add_timestamps(table_name, **options) + options[:null] = true if options[:null].nil? + super + end + + def index_exists?(table_name, column_name, **options) + column_names = Array(column_name).map(&:to_s) + options[:name] = + if options[:name].present? + options[:name].to_s + else + connection.index_name(table_name, column: column_names) + end + super + end + + def remove_index(table_name, column_name = nil, **options) + options[:name] = index_name_for_remove(table_name, column_name, options) + super + end + + private + def compatible_table_definition(t) + class << t + prepend TableDefinition + end + super + end + + def index_name_for_remove(table_name, column_name, options) + index_name = connection.index_name(table_name, column_name || options) + + unless connection.index_name_exists?(table_name, index_name) + if options.key?(:name) + options_without_column = options.except(:column) + index_name_without_column = connection.index_name(table_name, options_without_column) + + if connection.index_name_exists?(table_name, index_name_without_column) + return index_name_without_column + end + end + + raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist" + end + + index_name + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration/default_strategy.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration/default_strategy.rb new file mode 100644 index 00000000..c598564b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration/default_strategy.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module ActiveRecord + class Migration + # The default strategy for executing migrations. Delegates method calls + # to the connection adapter. + class DefaultStrategy < ExecutionStrategy # :nodoc: + private + def method_missing(method, ...) + connection.send(method, ...) + end + + def respond_to_missing?(method, include_private = false) + connection.respond_to?(method, include_private) || super + end + + def connection + migration.connection + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration/execution_strategy.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration/execution_strategy.rb new file mode 100644 index 00000000..299e8902 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration/execution_strategy.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module ActiveRecord + class Migration + # ExecutionStrategy is used by the migration to respond to any method calls + # that the migration class does not implement directly. This is the base strategy. + # All strategies should inherit from this class. + # + # The ExecutionStrategy receives the current +migration+ when initialized. + class ExecutionStrategy # :nodoc: + def initialize(migration) + @migration = migration + end + + private + attr_reader :migration + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration/join_table.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration/join_table.rb new file mode 100644 index 00000000..7d3c59bb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration/join_table.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module ActiveRecord + class Migration + module JoinTable # :nodoc: + private + def find_join_table_name(table_1, table_2, options = {}) + options.delete(:table_name) || join_table_name(table_1, table_2) + end + + def join_table_name(table_1, table_2) + ModelSchema.derive_join_table_name(table_1, table_2).to_sym + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration/pending_migration_connection.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration/pending_migration_connection.rb new file mode 100644 index 00000000..992da1c5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/migration/pending_migration_connection.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module ActiveRecord + class PendingMigrationConnection # :nodoc: + def self.with_temporary_pool(db_config, &block) + pool = ActiveRecord::Base.connection_handler.establish_connection(db_config, owner_name: self) + + yield pool + ensure + ActiveRecord::Base.connection_handler.remove_connection_pool(self.name) + end + + def self.primary_class? + false + end + + def self.current_preventing_writes + false + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/model_schema.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/model_schema.rb new file mode 100644 index 00000000..026d7d05 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/model_schema.rb @@ -0,0 +1,633 @@ +# frozen_string_literal: true + +require "monitor" + +module ActiveRecord + module ModelSchema + extend ActiveSupport::Concern + + ## + # :method: id_value + # :call-seq: id_value + # + # Returns the underlying column value for a column named "id". Useful when defining + # a composite primary key including an "id" column so that the value is readable. + + ## + # :singleton-method: primary_key_prefix_type + # :call-seq: primary_key_prefix_type + # + # The prefix type that will be prepended to every primary key column name. + # The options are +:table_name+ and +:table_name_with_underscore+. If the first is specified, + # the Product class will look for "productid" instead of "id" as the primary column. If the + # latter is specified, the Product class will look for "product_id" instead of "id". Remember + # that this is a global setting for all Active Records. + + ## + # :singleton-method: primary_key_prefix_type= + # :call-seq: primary_key_prefix_type=(prefix_type) + # + # Sets the prefix type that will be prepended to every primary key column name. + # The options are +:table_name+ and +:table_name_with_underscore+. If the first is specified, + # the Product class will look for "productid" instead of "id" as the primary column. If the + # latter is specified, the Product class will look for "product_id" instead of "id". Remember + # that this is a global setting for all Active Records. + + ## + # :singleton-method: table_name_prefix + # :call-seq: table_name_prefix + # + # The prefix string to prepend to every table name. + + ## + # :singleton-method: table_name_prefix= + # :call-seq: table_name_prefix=(prefix) + # + # Sets the prefix string to prepend to every table name. So if set to "basecamp_", all table + # names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient + # way of creating a namespace for tables in a shared database. By default, the prefix is the + # empty string. + # + # If you are organising your models within modules you can add a prefix to the models within + # a namespace by defining a singleton method in the parent module called table_name_prefix which + # returns your chosen prefix. + + ## + # :singleton-method: table_name_suffix + # :call-seq: table_name_suffix + # + # The suffix string to append to every table name. + + ## + # :singleton-method: table_name_suffix= + # :call-seq: table_name_suffix=(suffix) + # + # Works like +table_name_prefix=+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp", + # "people_basecamp"). By default, the suffix is the empty string. + # + # If you are organising your models within modules, you can add a suffix to the models within + # a namespace by defining a singleton method in the parent module called table_name_suffix which + # returns your chosen suffix. + + ## + # :singleton-method: schema_migrations_table_name + # :call-seq: schema_migrations_table_name + # + # The name of the schema migrations table. By default, the value is "schema_migrations". + + ## + # :singleton-method: schema_migrations_table_name= + # :call-seq: schema_migrations_table_name=(table_name) + # + # Sets the name of the schema migrations table. + + ## + # :singleton-method: internal_metadata_table_name + # :call-seq: internal_metadata_table_name + # + # The name of the internal metadata table. By default, the value is "ar_internal_metadata". + + ## + # :singleton-method: internal_metadata_table_name= + # :call-seq: internal_metadata_table_name=(table_name) + # + # Sets the name of the internal metadata table. + + ## + # :singleton-method: pluralize_table_names + # :call-seq: pluralize_table_names + # + # Indicates whether table names should be the pluralized versions of the corresponding class names. + # If true, the default table name for a Product class will be "products". If false, it would just be "product". + # See table_name for the full rules on table/class naming. This is true, by default. + + ## + # :singleton-method: pluralize_table_names= + # :call-seq: pluralize_table_names=(value) + # + # Set whether table names should be the pluralized versions of the corresponding class names. + # If true, the default table name for a Product class will be "products". If false, it would just be "product". + # See table_name for the full rules on table/class naming. This is true, by default. + + ## + # :singleton-method: implicit_order_column + # :call-seq: implicit_order_column + # + # The name of the column records are ordered by if no explicit order clause + # is used during an ordered finder call. If not set the primary key is used. + + ## + # :singleton-method: implicit_order_column= + # :call-seq: implicit_order_column=(column_name) + # + # Sets the column to sort records by when no explicit order clause is used + # during an ordered finder call. Useful when the primary key is not an + # auto-incrementing integer, for example when it's a UUID. Records are subsorted + # by the primary key if it exists to ensure deterministic results. + + ## + # :singleton-method: immutable_strings_by_default= + # :call-seq: immutable_strings_by_default=(bool) + # + # Determines whether columns should infer their type as +:string+ or + # +:immutable_string+. This setting does not affect the behavior of + # attribute :foo, :string. Defaults to false. + + ## + # :singleton-method: inheritance_column + # :call-seq: inheritance_column + # + # The name of the table column which stores the class name on single-table + # inheritance situations. + # + # The default inheritance column name is +type+, which means it's a + # reserved word inside Active Record. To be able to use single-table + # inheritance with another column name, or to use the column +type+ in + # your own model for something else, you can set +inheritance_column+: + # + # self.inheritance_column = 'zoink' + # + # If you wish to disable single-table inheritance altogether you can set + # +inheritance_column+ to +nil+ + # + # self.inheritance_column = nil + + ## + # :singleton-method: inheritance_column= + # :call-seq: inheritance_column=(column) + # + # Defines the name of the table column which will store the class name on single-table + # inheritance situations. + + included do + class_attribute :primary_key_prefix_type, instance_writer: false + class_attribute :table_name_prefix, instance_writer: false, default: "" + class_attribute :table_name_suffix, instance_writer: false, default: "" + class_attribute :schema_migrations_table_name, instance_accessor: false, default: "schema_migrations" + class_attribute :internal_metadata_table_name, instance_accessor: false, default: "ar_internal_metadata" + class_attribute :pluralize_table_names, instance_writer: false, default: true + class_attribute :implicit_order_column, instance_accessor: false + class_attribute :immutable_strings_by_default, instance_accessor: false + + class_attribute :inheritance_column, instance_accessor: false, default: "type" + singleton_class.class_eval do + alias_method :_inheritance_column=, :inheritance_column= + private :_inheritance_column= + alias_method :inheritance_column=, :real_inheritance_column= + end + + self.protected_environments = ["production"] + + self.ignored_columns = [].freeze + + delegate :type_for_attribute, :column_for_attribute, to: :class + + initialize_load_schema_monitor + end + + # Derives the join table name for +first_table+ and +second_table+. The + # table names appear in alphabetical order. A common prefix is removed + # (useful for namespaced models like Music::Artist and Music::Record): + # + # artists, records => artists_records + # records, artists => artists_records + # music_artists, music_records => music_artists_records + # music.artists, music.records => music.artists_records + def self.derive_join_table_name(first_table, second_table) # :nodoc: + [first_table.to_s, second_table.to_s].sort.join("\0").gsub(/^(.*[_.])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_") + end + + module ClassMethods + # Guesses the table name (in forced lower-case) based on the name of the class in the + # inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy + # looks like: Reply < Message < ActiveRecord::Base, then Message is used + # to guess the table name even when called on Reply. The rules used to do the guess + # are handled by the Inflector class in Active Support, which knows almost all common + # English inflections. You can add new inflections in config/initializers/inflections.rb. + # + # Nested classes are given table names prefixed by the singular form of + # the parent's table name. Enclosing modules are not considered. + # + # ==== Examples + # + # class Invoice < ActiveRecord::Base + # end + # + # file class table_name + # invoice.rb Invoice invoices + # + # class Invoice < ActiveRecord::Base + # class Lineitem < ActiveRecord::Base + # end + # end + # + # file class table_name + # invoice.rb Invoice::Lineitem invoice_lineitems + # + # module Invoice + # class Lineitem < ActiveRecord::Base + # end + # end + # + # file class table_name + # invoice/lineitem.rb Invoice::Lineitem lineitems + # + # Additionally, the class-level +table_name_prefix+ is prepended and the + # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix, + # the table name guess for an Invoice class becomes "myapp_invoices". + # Invoice::Lineitem becomes "myapp_invoice_lineitems". + # + # Active Model Naming's +model_name+ is the base name used to guess the + # table name. In case a custom Active Model Name is defined, it will be + # used for the table name as well: + # + # class PostRecord < ActiveRecord::Base + # class << self + # def model_name + # ActiveModel::Name.new(self, nil, "Post") + # end + # end + # end + # + # PostRecord.table_name + # # => "posts" + # + # You can also set your own table name explicitly: + # + # class Mouse < ActiveRecord::Base + # self.table_name = "mice" + # end + def table_name + reset_table_name unless defined?(@table_name) + @table_name + end + + # Sets the table name explicitly. Example: + # + # class Project < ActiveRecord::Base + # self.table_name = "project" + # end + def table_name=(value) + value = value && value.to_s + + if defined?(@table_name) + return if value == @table_name + reset_column_information if connected? + end + + @table_name = value + @arel_table = nil + @sequence_name = nil unless @explicit_sequence_name + @predicate_builder = nil + end + + # Returns a quoted version of the table name. + def quoted_table_name + adapter_class.quote_table_name(table_name) + end + + # Computes the table name, (re)sets it internally, and returns it. + def reset_table_name # :nodoc: + self.table_name = if self == Base + nil + elsif abstract_class? + superclass.table_name + elsif superclass.abstract_class? + superclass.table_name || compute_table_name + else + compute_table_name + end + end + + def full_table_name_prefix # :nodoc: + (module_parents.detect { |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix + end + + def full_table_name_suffix # :nodoc: + (module_parents.detect { |p| p.respond_to?(:table_name_suffix) } || self).table_name_suffix + end + + # The array of names of environments where destructive actions should be prohibited. By default, + # the value is ["production"]. + def protected_environments + if defined?(@protected_environments) + @protected_environments + else + superclass.protected_environments + end + end + + # Sets an array of names of environments where destructive actions should be prohibited. + def protected_environments=(environments) + @protected_environments = environments.map(&:to_s) + end + + def real_inheritance_column=(value) # :nodoc: + self._inheritance_column = value.to_s + end + + # The list of columns names the model should ignore. Ignored columns won't have attribute + # accessors defined, and won't be referenced in SQL queries. + def ignored_columns + @ignored_columns || superclass.ignored_columns + end + + # Sets the columns names the model should ignore. Ignored columns won't have attribute + # accessors defined, and won't be referenced in SQL queries. + # + # A common usage pattern for this method is to ensure all references to an attribute + # have been removed and deployed, before a migration to drop the column from the database + # has been deployed and run. Using this two step approach to dropping columns ensures there + # is no code that raises errors due to having a cached schema in memory at the time the + # schema migration is run. + # + # For example, given a model where you want to drop the "category" attribute, first mark it + # as ignored: + # + # class Project < ActiveRecord::Base + # # schema: + # # id :bigint + # # name :string, limit: 255 + # # category :string, limit: 255 + # + # self.ignored_columns += [:category] + # end + # + # The schema still contains "category", but now the model omits it, so any meta-driven code or + # schema caching will not attempt to use the column: + # + # Project.columns_hash["category"] => nil + # + # You will get an error if accessing that attribute directly, so ensure all usages of the + # column are removed (automated tests can help you find any usages). + # + # user = Project.create!(name: "First Project") + # user.category # => raises NoMethodError + def ignored_columns=(columns) + reload_schema_from_cache + @ignored_columns = columns.map(&:to_s).freeze + end + + def sequence_name + if base_class? + @sequence_name ||= reset_sequence_name + else + (@sequence_name ||= nil) || base_class.sequence_name + end + end + + def reset_sequence_name # :nodoc: + @explicit_sequence_name = false + @sequence_name = with_connection { |c| c.default_sequence_name(table_name, primary_key) } + end + + # Sets the name of the sequence to use when generating ids to the given + # value, or (if the value is +nil+ or +false+) to the value returned by the + # given block. This is required for Oracle and is useful for any + # database which relies on sequences for primary key generation. + # + # If a sequence name is not explicitly set when using Oracle, + # it will default to the commonly used pattern of: #{table_name}_seq + # + # If a sequence name is not explicitly set when using PostgreSQL, it + # will discover the sequence corresponding to your primary key for you. + # + # class Project < ActiveRecord::Base + # self.sequence_name = "projectseq" # default would have been "project_seq" + # end + def sequence_name=(value) + @sequence_name = value.to_s + @explicit_sequence_name = true + end + + # Determines if the primary key values should be selected from their + # corresponding sequence before the insert statement. + def prefetch_primary_key? + with_connection { |c| c.prefetch_primary_key?(table_name) } + end + + # Returns the next value that will be used as the primary key on + # an insert statement. + def next_sequence_value + with_connection { |c| c.next_sequence_value(sequence_name) } + end + + # Indicates whether the table associated with this class exists + def table_exists? + schema_cache.data_source_exists?(table_name) + end + + def attributes_builder # :nodoc: + @attributes_builder ||= begin + defaults = _default_attributes.except(*(column_names - [primary_key])) + ActiveModel::AttributeSet::Builder.new(attribute_types, defaults) + end + end + + def columns_hash # :nodoc: + load_schema unless @columns_hash + @columns_hash + end + + def columns + @columns ||= columns_hash.values.freeze + end + + def _returning_columns_for_insert(connection) # :nodoc: + @_returning_columns_for_insert ||= begin + auto_populated_columns = columns.filter_map do |c| + c.name if connection.return_value_after_insert?(c) + end + + auto_populated_columns.empty? ? Array(primary_key) : auto_populated_columns + end + end + + def yaml_encoder # :nodoc: + @yaml_encoder ||= ActiveModel::AttributeSet::YAMLEncoder.new(attribute_types) + end + + # Returns the column object for the named attribute. + # Returns an ActiveRecord::ConnectionAdapters::NullColumn if the + # named attribute does not exist. + # + # class Person < ActiveRecord::Base + # end + # + # person = Person.new + # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter + # # => # + # + # person.column_for_attribute(:nothing) + # # => #, ...> + def column_for_attribute(name) + name = name.to_s + columns_hash.fetch(name) do + ConnectionAdapters::NullColumn.new(name) + end + end + + # Returns a hash where the keys are column names and the values are + # default values when instantiating the Active Record object for this table. + def column_defaults + load_schema + @column_defaults ||= _default_attributes.deep_dup.to_hash.freeze + end + + # Returns an array of column names as strings. + def column_names + @column_names ||= columns.map(&:name).freeze + end + + def symbol_column_to_string(name_symbol) # :nodoc: + @symbol_column_to_string_name_hash ||= column_names.index_by(&:to_sym) + @symbol_column_to_string_name_hash[name_symbol] + end + + # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count", + # and columns used for single table inheritance have been removed. + def content_columns + @content_columns ||= columns.reject do |c| + c.name == primary_key || + c.name == inheritance_column || + c.name.end_with?("_id", "_count") + end.freeze + end + + # Resets all the cached information about columns, which will cause them + # to be reloaded on the next request. + # + # The most common usage pattern for this method is probably in a migration, + # when just after creating a table you want to populate it with some default + # values, e.g.: + # + # class CreateJobLevels < ActiveRecord::Migration[8.0] + # def up + # create_table :job_levels do |t| + # t.integer :id + # t.string :name + # + # t.timestamps + # end + # + # JobLevel.reset_column_information + # %w{assistant executive manager director}.each do |type| + # JobLevel.create(name: type) + # end + # end + # + # def down + # drop_table :job_levels + # end + # end + def reset_column_information + connection_pool.active_connection&.clear_cache! + ([self] + descendants).each(&:undefine_attribute_methods) + schema_cache.clear_data_source_cache!(table_name) + + reload_schema_from_cache + initialize_find_by_cache + end + + # Load the model's schema information either from the schema cache + # or directly from the database. + def load_schema + return if schema_loaded? + @load_schema_monitor.synchronize do + return if schema_loaded? + + load_schema! + + @schema_loaded = true + rescue + reload_schema_from_cache # If the schema loading failed half way through, we must reset the state. + raise + end + end + + protected + def initialize_load_schema_monitor + @load_schema_monitor = Monitor.new + end + + def reload_schema_from_cache(recursive = true) + @_returning_columns_for_insert = nil + @arel_table = nil + @column_names = nil + @symbol_column_to_string_name_hash = nil + @content_columns = nil + @column_defaults = nil + @attributes_builder = nil + @columns = nil + @columns_hash = nil + @schema_loaded = false + @attribute_names = nil + @yaml_encoder = nil + if recursive + subclasses.each do |descendant| + descendant.send(:reload_schema_from_cache) + end + end + end + + private + def inherited(child_class) + super + child_class.initialize_load_schema_monitor + child_class.reload_schema_from_cache(false) + child_class.class_eval do + @ignored_columns = nil + end + end + + def schema_loaded? + @schema_loaded + end + + def load_schema! + unless table_name + raise ActiveRecord::TableNotSpecified, "#{self} has no table configured. Set one with #{self}.table_name=" + end + + columns_hash = schema_cache.columns_hash(table_name) + columns_hash = columns_hash.except(*ignored_columns) unless ignored_columns.empty? + @columns_hash = columns_hash.freeze + + _default_attributes # Precompute to cache DB-dependent attribute types + end + + # Guesses the table name, but does not decorate it with prefix and suffix information. + def undecorated_table_name(model_name) + table_name = model_name.to_s.demodulize.underscore + pluralize_table_names ? table_name.pluralize : table_name + end + + # Computes and returns a table name according to default conventions. + def compute_table_name + if base_class? + # Nested classes are prefixed with singular parent table name. + if module_parent < Base && !module_parent.abstract_class? + contained = module_parent.table_name + contained = contained.singularize if module_parent.pluralize_table_names + contained += "_" + end + + "#{full_table_name_prefix}#{contained}#{undecorated_table_name(model_name)}#{full_table_name_suffix}" + else + # STI subclasses always use their superclass's table. + base_class.table_name + end + end + + def type_for_column(connection, column) + type = connection.lookup_cast_type_from_column(column) + + if immutable_strings_by_default && type.respond_to?(:to_immutable_string) + type = type.to_immutable_string + end + + type + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/nested_attributes.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/nested_attributes.rb new file mode 100644 index 00000000..0e2dc1e7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/nested_attributes.rb @@ -0,0 +1,633 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/except" +require "active_support/core_ext/module/redefine_method" +require "active_support/core_ext/hash/indifferent_access" + +module ActiveRecord + module NestedAttributes # :nodoc: + class TooManyRecords < ActiveRecordError + end + + extend ActiveSupport::Concern + + included do + class_attribute :nested_attributes_options, instance_writer: false, default: {} + end + + # = Active Record Nested \Attributes + # + # Nested attributes allow you to save attributes on associated records + # through the parent. By default nested attribute updating is turned off + # and you can enable it using the accepts_nested_attributes_for class + # method. When you enable nested attributes an attribute writer is + # defined on the model. + # + # The attribute writer is named after the association, which means that + # in the following example, two new methods are added to your model: + # + # author_attributes=(attributes) and + # pages_attributes=(attributes). + # + # class Book < ActiveRecord::Base + # has_one :author + # has_many :pages + # + # accepts_nested_attributes_for :author, :pages + # end + # + # Note that the :autosave option is automatically enabled on every + # association that accepts_nested_attributes_for is used for. + # + # === One-to-one + # + # Consider a Member model that has one Avatar: + # + # class Member < ActiveRecord::Base + # has_one :avatar + # accepts_nested_attributes_for :avatar + # end + # + # Enabling nested attributes on a one-to-one association allows you to + # create the member and avatar in one go: + # + # params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } } + # member = Member.create(params[:member]) + # member.avatar.id # => 2 + # member.avatar.icon # => 'smiling' + # + # It also allows you to update the avatar through the member: + # + # params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } } + # member.update params[:member] + # member.avatar.icon # => 'sad' + # + # If you want to update the current avatar without providing the id, you must add :update_only option. + # + # class Member < ActiveRecord::Base + # has_one :avatar + # accepts_nested_attributes_for :avatar, update_only: true + # end + # + # params = { member: { avatar_attributes: { icon: 'sad' } } } + # member.update params[:member] + # member.avatar.id # => 2 + # member.avatar.icon # => 'sad' + # + # By default you will only be able to set and update attributes on the + # associated model. If you want to destroy the associated model through the + # attributes hash, you have to enable it first using the + # :allow_destroy option. + # + # class Member < ActiveRecord::Base + # has_one :avatar + # accepts_nested_attributes_for :avatar, allow_destroy: true + # end + # + # Now, when you add the _destroy key to the attributes hash, with a + # value that evaluates to +true+, you will destroy the associated model: + # + # member.avatar_attributes = { id: '2', _destroy: '1' } + # member.avatar.marked_for_destruction? # => true + # member.save + # member.reload.avatar # => nil + # + # Note that the model will _not_ be destroyed until the parent is saved. + # + # Also note that the model will not be destroyed unless you also specify + # its id in the updated hash. + # + # === One-to-many + # + # Consider a member that has a number of posts: + # + # class Member < ActiveRecord::Base + # has_many :posts + # accepts_nested_attributes_for :posts + # end + # + # You can now set or update attributes on the associated posts through + # an attribute hash for a member: include the key +:posts_attributes+ + # with an array of hashes of post attributes as a value. + # + # For each hash that does _not_ have an id key a new record will + # be instantiated, unless the hash also contains a _destroy key + # that evaluates to +true+. + # + # params = { member: { + # name: 'joe', posts_attributes: [ + # { title: 'Kari, the awesome Ruby documentation browser!' }, + # { title: 'The egalitarian assumption of the modern citizen' }, + # { title: '', _destroy: '1' } # this will be ignored + # ] + # }} + # + # member = Member.create(params[:member]) + # member.posts.length # => 2 + # member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!' + # member.posts.second.title # => 'The egalitarian assumption of the modern citizen' + # + # You may also set a +:reject_if+ proc to silently ignore any new record + # hashes if they fail to pass your criteria. For example, the previous + # example could be rewritten as: + # + # class Member < ActiveRecord::Base + # has_many :posts + # accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? } + # end + # + # params = { member: { + # name: 'joe', posts_attributes: [ + # { title: 'Kari, the awesome Ruby documentation browser!' }, + # { title: 'The egalitarian assumption of the modern citizen' }, + # { title: '' } # this will be ignored because of the :reject_if proc + # ] + # }} + # + # member = Member.create(params[:member]) + # member.posts.length # => 2 + # member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!' + # member.posts.second.title # => 'The egalitarian assumption of the modern citizen' + # + # Alternatively, +:reject_if+ also accepts a symbol for using methods: + # + # class Member < ActiveRecord::Base + # has_many :posts + # accepts_nested_attributes_for :posts, reject_if: :new_record? + # end + # + # class Member < ActiveRecord::Base + # has_many :posts + # accepts_nested_attributes_for :posts, reject_if: :reject_posts + # + # def reject_posts(attributes) + # attributes['title'].blank? + # end + # end + # + # If the hash contains an id key that matches an already + # associated record, the matching record will be modified: + # + # member.attributes = { + # name: 'Joe', + # posts_attributes: [ + # { id: 1, title: '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' }, + # { id: 2, title: '[UPDATED] other post' } + # ] + # } + # + # member.posts.first.title # => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' + # member.posts.second.title # => '[UPDATED] other post' + # + # However, the above applies if the parent model is being updated as well. + # For example, if you wanted to create a +member+ named _joe_ and wanted to + # update the +posts+ at the same time, that would give an + # ActiveRecord::RecordNotFound error. + # + # By default the associated records are protected from being destroyed. If + # you want to destroy any of the associated records through the attributes + # hash, you have to enable it first using the :allow_destroy + # option. This will allow you to also use the _destroy key to + # destroy existing records: + # + # class Member < ActiveRecord::Base + # has_many :posts + # accepts_nested_attributes_for :posts, allow_destroy: true + # end + # + # params = { member: { + # posts_attributes: [{ id: '2', _destroy: '1' }] + # }} + # + # member.attributes = params[:member] + # member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true + # member.posts.length # => 2 + # member.save + # member.reload.posts.length # => 1 + # + # Nested attributes for an associated collection can also be passed in + # the form of a hash of hashes instead of an array of hashes: + # + # Member.create( + # name: 'joe', + # posts_attributes: { + # first: { title: 'Foo' }, + # second: { title: 'Bar' } + # } + # ) + # + # has the same effect as + # + # Member.create( + # name: 'joe', + # posts_attributes: [ + # { title: 'Foo' }, + # { title: 'Bar' } + # ] + # ) + # + # The keys of the hash which is the value for +:posts_attributes+ are + # ignored in this case. + # However, it is not allowed to use 'id' or :id for one of + # such keys, otherwise the hash will be wrapped in an array and + # interpreted as an attribute hash for a single post. + # + # Passing attributes for an associated collection in the form of a hash + # of hashes can be used with hashes generated from HTTP/HTML parameters, + # where there may be no natural way to submit an array of hashes. + # + # === Saving + # + # All changes to models, including the destruction of those marked for + # destruction, are saved and destroyed automatically and atomically when + # the parent model is saved. This happens inside the transaction initiated + # by the parent's save method. See ActiveRecord::AutosaveAssociation. + # + # === Validating the presence of a parent model + # + # The +belongs_to+ association validates the presence of the parent model + # by default. You can disable this behavior by specifying optional: true. + # This can be used, for example, when conditionally validating the presence + # of the parent model: + # + # class Veterinarian < ActiveRecord::Base + # has_many :patients, inverse_of: :veterinarian + # accepts_nested_attributes_for :patients + # end + # + # class Patient < ActiveRecord::Base + # belongs_to :veterinarian, inverse_of: :patients, optional: true + # validates :veterinarian, presence: true, unless: -> { awaiting_intake } + # end + # + # Note that if you do not specify the +:inverse_of+ option, then + # Active Record will try to automatically guess the inverse association + # based on heuristics. + # + # For one-to-one nested associations, if you build the new (in-memory) + # child object yourself before assignment, then this module will not + # overwrite it, e.g.: + # + # class Member < ActiveRecord::Base + # has_one :avatar + # accepts_nested_attributes_for :avatar + # + # def avatar + # super || build_avatar(width: 200) + # end + # end + # + # member = Member.new + # member.avatar_attributes = {icon: 'sad'} + # member.avatar.width # => 200 + # + # === Creating forms with nested attributes + # + # Use ActionView::Helpers::FormHelper#fields_for to create form elements for + # nested attributes. + # + # Integration test params should reflect the structure of the form. For + # example: + # + # post members_path, params: { + # member: { + # name: 'joe', + # posts_attributes: { + # '0' => { title: 'Foo' }, + # '1' => { title: 'Bar' } + # } + # } + # } + module ClassMethods + REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == "_destroy" || value.blank? } } + + # Defines an attributes writer for the specified association(s). + # + # Supported options: + # [:allow_destroy] + # If true, destroys any members from the attributes hash with a + # _destroy key and a value that evaluates to +true+ + # (e.g. 1, '1', true, or 'true'). This option is false by default. + # [:reject_if] + # Allows you to specify a Proc or a Symbol pointing to a method + # that checks whether a record should be built for a certain attribute + # hash. The hash is passed to the supplied Proc or the method + # and it should return either +true+ or +false+. When no +:reject_if+ + # is specified, a record will be built for all attribute hashes that + # do not have a _destroy value that evaluates to true. + # Passing :all_blank instead of a Proc will create a proc + # that will reject a record where all the attributes are blank excluding + # any value for +_destroy+. + # [:limit] + # Allows you to specify the maximum number of associated records that + # can be processed with the nested attributes. Limit also can be specified + # as a Proc or a Symbol pointing to a method that should return a number. + # If the size of the nested attributes array exceeds the specified limit, + # NestedAttributes::TooManyRecords exception is raised. If omitted, any + # number of associations can be processed. + # Note that the +:limit+ option is only applicable to one-to-many + # associations. + # [:update_only] + # For a one-to-one association, this option allows you to specify how + # nested attributes are going to be used when an associated record already + # exists. In general, an existing record may either be updated with the + # new set of attribute values or be replaced by a wholly new record + # containing those values. By default the +:update_only+ option is false + # and the nested attributes are used to update the existing record only + # if they include the record's :id value. Otherwise a new + # record will be instantiated and used to replace the existing one. + # However if the +:update_only+ option is true, the nested attributes + # are used to update the record's attributes always, regardless of + # whether the :id is present. The option is ignored for collection + # associations. + # + # Examples: + # # creates avatar_attributes= + # accepts_nested_attributes_for :avatar, reject_if: proc { |attributes| attributes['name'].blank? } + # # creates avatar_attributes= + # accepts_nested_attributes_for :avatar, reject_if: :all_blank + # # creates avatar_attributes= and posts_attributes= + # accepts_nested_attributes_for :avatar, :posts, allow_destroy: true + def accepts_nested_attributes_for(*attr_names) + options = { allow_destroy: false, update_only: false } + options.update(attr_names.extract_options!) + options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only) + options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank + + attr_names.each do |association_name| + if reflection = _reflect_on_association(association_name) + reflection.autosave = true + define_autosave_validation_callbacks(reflection) + + nested_attributes_options = self.nested_attributes_options.dup + nested_attributes_options[association_name.to_sym] = options + self.nested_attributes_options = nested_attributes_options + + type = (reflection.collection? ? :collection : :one_to_one) + generate_association_writer(association_name, type) + else + raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?" + end + end + end + + private + # Generates a writer method for this association. Serves as a point for + # accessing the objects in the association. For example, this method + # could generate the following: + # + # def pirate_attributes=(attributes) + # assign_nested_attributes_for_one_to_one_association(:pirate, attributes) + # end + # + # This redirects the attempts to write objects in an association through + # the helper methods defined below. Makes it seem like the nested + # associations are just regular associations. + def generate_association_writer(association_name, type) + generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1 + silence_redefinition_of_method :#{association_name}_attributes= + def #{association_name}_attributes=(attributes) + assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes) + end + eoruby + end + end + + # Returns ActiveRecord::AutosaveAssociation#marked_for_destruction? It's + # used in conjunction with fields_for to build a form element for the + # destruction of this association. + # + # See ActionView::Helpers::FormHelper#fields_for for more info. + def _destroy + marked_for_destruction? + end + + private + # Attribute hash keys that should not be assigned as normal attributes. + # These hash keys are nested attributes implementation details. + UNASSIGNABLE_KEYS = %w( id _destroy ) + + # Assigns the given attributes to the association. + # + # If an associated record does not yet exist, one will be instantiated. If + # an associated record already exists, the method's behavior depends on + # the value of the update_only option. If update_only is +false+ and the + # given attributes include an :id that matches the existing record's + # id, then the existing record will be modified. If no :id is provided + # it will be replaced with a new record. If update_only is +true+ the existing + # record will be modified regardless of whether an :id is provided. + # + # If the given attributes include a matching :id attribute, or + # update_only is true, and a :_destroy key set to a truthy value, + # then the existing record will be marked for destruction. + def assign_nested_attributes_for_one_to_one_association(association_name, attributes) + if attributes.respond_to?(:permitted?) + attributes = attributes.to_h + end + + unless attributes.is_a?(Hash) + raise ArgumentError, "Hash expected for `#{association_name}` attributes, got #{attributes.class.name}" + end + + options = nested_attributes_options[association_name] + attributes = attributes.with_indifferent_access + existing_record = send(association_name) + + if (options[:update_only] || !attributes["id"].blank?) && existing_record && + (options[:update_only] || existing_record.id.to_s == attributes["id"].to_s) + assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes) + + elsif attributes["id"].present? + raise_nested_attributes_record_not_found!(association_name, attributes["id"]) + + elsif !reject_new_record?(association_name, attributes) + assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS) + + if existing_record && existing_record.new_record? + existing_record.assign_attributes(assignable_attributes) + association(association_name).initialize_attributes(existing_record) + else + method = :"build_#{association_name}" + if respond_to?(method) + send(method, assignable_attributes) + else + raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?" + end + end + end + end + + # Assigns the given attributes to the collection association. + # + # Hashes with an :id value matching an existing associated record + # will update that record. Hashes without an :id value will build + # a new record for the association. Hashes with a matching :id + # value and a :_destroy key set to a truthy value will mark the + # matched record for destruction. + # + # For example: + # + # assign_nested_attributes_for_collection_association(:people, { + # '1' => { id: '1', name: 'Peter' }, + # '2' => { name: 'John' }, + # '3' => { id: '2', _destroy: true } + # }) + # + # Will update the name of the Person with ID 1, build a new associated + # person with the name 'John', and mark the associated Person with ID 2 + # for destruction. + # + # Also accepts an Array of attribute hashes: + # + # assign_nested_attributes_for_collection_association(:people, [ + # { id: '1', name: 'Peter' }, + # { name: 'John' }, + # { id: '2', _destroy: true } + # ]) + def assign_nested_attributes_for_collection_association(association_name, attributes_collection) + options = nested_attributes_options[association_name] + if attributes_collection.respond_to?(:permitted?) + attributes_collection = attributes_collection.to_h + end + + unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array) + raise ArgumentError, "Hash or Array expected for `#{association_name}` attributes, got #{attributes_collection.class.name}" + end + + check_record_limit!(options[:limit], attributes_collection) + + if attributes_collection.is_a? Hash + keys = attributes_collection.keys + attributes_collection = if keys.include?("id") || keys.include?(:id) + [attributes_collection] + else + attributes_collection.values + end + end + + association = association(association_name) + + existing_records = if association.loaded? + association.target + else + attribute_ids = attributes_collection.filter_map { |a| a["id"] || a[:id] } + attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids) + end + + records = attributes_collection.map do |attributes| + if attributes.respond_to?(:permitted?) + attributes = attributes.to_h + end + attributes = attributes.with_indifferent_access + + if attributes["id"].blank? + unless reject_new_record?(association_name, attributes) + association.reader.build(attributes.except(*UNASSIGNABLE_KEYS)) + end + elsif existing_record = find_record_by_id(association.klass, existing_records, attributes["id"]) + unless call_reject_if(association_name, attributes) + # Make sure we are operating on the actual object which is in the association's + # proxy_target array (either by finding it, or adding it if not found) + # Take into account that the proxy_target may have changed due to callbacks + target_record = find_record_by_id(association.klass, association.target, attributes["id"]) + if target_record + existing_record = target_record + else + association.add_to_target(existing_record, skip_callbacks: true) + end + + assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) + existing_record + end + else + raise_nested_attributes_record_not_found!(association_name, attributes["id"]) + end + end + + association.nested_attributes_target = records + end + + # Takes in a limit and checks if the attributes_collection has too many + # records. It accepts limit in the form of symbol, proc, or + # number-like object (anything that can be compared with an integer). + # + # Raises TooManyRecords error if the attributes_collection is + # larger than the limit. + def check_record_limit!(limit, attributes_collection) + if limit + limit = \ + case limit + when Symbol + send(limit) + when Proc + limit.call + else + limit + end + + if limit && attributes_collection.size > limit + raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead." + end + end + end + + # Updates a record with the +attributes+ or marks it for destruction if + # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+. + def assign_to_or_mark_for_destruction(record, attributes, allow_destroy) + record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS)) + record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy + end + + # Determines if a hash contains a truthy _destroy key. + def has_destroy_flag?(hash) + Type::Boolean.new.cast(hash["_destroy"]) + end + + # Determines if a new record should be rejected by checking + # has_destroy_flag? or if a :reject_if proc exists for this + # association and evaluates to +true+. + def reject_new_record?(association_name, attributes) + will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes) + end + + # Determines if a record with the particular +attributes+ should be + # rejected by calling the reject_if Symbol or Proc (if defined). + # The reject_if option is defined by +accepts_nested_attributes_for+. + # + # Returns false if there is a +destroy_flag+ on the attributes. + def call_reject_if(association_name, attributes) + return false if will_be_destroyed?(association_name, attributes) + + case callback = nested_attributes_options[association_name][:reject_if] + when Symbol + method(callback).arity == 0 ? send(callback) : send(callback, attributes) + when Proc + callback.call(attributes) + end + end + + # Only take into account the destroy flag if :allow_destroy is true + def will_be_destroyed?(association_name, attributes) + allow_destroy?(association_name) && has_destroy_flag?(attributes) + end + + def allow_destroy?(association_name) + nested_attributes_options[association_name][:allow_destroy] + end + + def raise_nested_attributes_record_not_found!(association_name, record_id) + model = self.class._reflect_on_association(association_name).klass.name + raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}", + model, "id", record_id) + end + + def find_record_by_id(klass, records, id) + if klass.composite_primary_key? + id = Array(id).map(&:to_s) + records.find { |record| Array(record.id).map(&:to_s) == id } + else + records.find { |record| record.id.to_s == id.to_s } + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/no_touching.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/no_touching.rb new file mode 100644 index 00000000..d0834691 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/no_touching.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record No Touching + module NoTouching + extend ActiveSupport::Concern + + module ClassMethods + # Lets you selectively disable calls to +touch+ for the + # duration of a block. + # + # ==== Examples + # ActiveRecord::Base.no_touching do + # Project.first.touch # does nothing + # Message.first.touch # does nothing + # end + # + # Project.no_touching do + # Project.first.touch # does nothing + # Message.first.touch # works, but does not touch the associated project + # end + # + def no_touching(&block) + NoTouching.apply_to(self, &block) + end + end + + class << self + def apply_to(klass) # :nodoc: + klasses.push(klass) + yield + ensure + klasses.pop + end + + def applied_to?(klass) # :nodoc: + klasses.any? { |k| k >= klass } + end + + private + def klasses + ActiveSupport::IsolatedExecutionState[:active_record_no_touching_classes] ||= [] + end + end + + # Returns +true+ if the class has +no_touching+ set, +false+ otherwise. + # + # Project.no_touching do + # Project.first.no_touching? # true + # Message.first.no_touching? # false + # end + # + def no_touching? + NoTouching.applied_to?(self.class) + end + + def touch_later(*) # :nodoc: + super unless no_touching? + end + + def touch(*, **) # :nodoc: + super unless no_touching? + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/normalization.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/normalization.rb new file mode 100644 index 00000000..b6616547 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/normalization.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true + +module ActiveRecord # :nodoc: + module Normalization + extend ActiveSupport::Concern + + included do + class_attribute :normalized_attributes, default: Set.new + + before_validation :normalize_changed_in_place_attributes + end + + # Normalizes a specified attribute using its declared normalizations. + # + # ==== Examples + # + # class User < ActiveRecord::Base + # normalizes :email, with: -> email { email.strip.downcase } + # end + # + # legacy_user = User.find(1) + # legacy_user.email # => " CRUISE-CONTROL@EXAMPLE.COM\n" + # legacy_user.normalize_attribute(:email) + # legacy_user.email # => "cruise-control@example.com" + # legacy_user.save + def normalize_attribute(name) + # Treat the value as a new, unnormalized value. + self[name] = self[name] + end + + module ClassMethods + # Declares a normalization for one or more attributes. The normalization + # is applied when the attribute is assigned or updated, and the normalized + # value will be persisted to the database. The normalization is also + # applied to the corresponding keyword argument of query methods. This + # allows a record to be created and later queried using unnormalized + # values. + # + # However, to prevent confusion, the normalization will not be applied + # when the attribute is fetched from the database. This means that if a + # record was persisted before the normalization was declared, the record's + # attribute will not be normalized until either it is assigned a new + # value, or it is explicitly migrated via Normalization#normalize_attribute. + # + # Because the normalization may be applied multiple times, it should be + # _idempotent_. In other words, applying the normalization more than once + # should have the same result as applying it only once. + # + # By default, the normalization will not be applied to +nil+ values. This + # behavior can be changed with the +:apply_to_nil+ option. + # + # Be aware that if your app was created before Rails 7.1, and your app + # marshals instances of the targeted model (for example, when caching), + # then you should set ActiveRecord.marshalling_format_version to +7.1+ or + # higher via either config.load_defaults 7.1 or + # config.active_record.marshalling_format_version = 7.1. + # Otherwise, +Marshal+ may attempt to serialize the normalization +Proc+ + # and raise +TypeError+. + # + # ==== Options + # + # * +:with+ - Any callable object that accepts the attribute's value as + # its sole argument, and returns it normalized. + # * +:apply_to_nil+ - Whether to apply the normalization to +nil+ values. + # Defaults to +false+. + # + # ==== Examples + # + # class User < ActiveRecord::Base + # normalizes :email, with: -> email { email.strip.downcase } + # normalizes :phone, with: -> phone { phone.delete("^0-9").delete_prefix("1") } + # end + # + # user = User.create(email: " CRUISE-CONTROL@EXAMPLE.COM\n") + # user.email # => "cruise-control@example.com" + # + # user = User.find_by(email: "\tCRUISE-CONTROL@EXAMPLE.COM ") + # user.email # => "cruise-control@example.com" + # user.email_before_type_cast # => "cruise-control@example.com" + # + # User.where(email: "\tCRUISE-CONTROL@EXAMPLE.COM ").count # => 1 + # User.where(["email = ?", "\tCRUISE-CONTROL@EXAMPLE.COM "]).count # => 0 + # + # User.exists?(email: "\tCRUISE-CONTROL@EXAMPLE.COM ") # => true + # User.exists?(["email = ?", "\tCRUISE-CONTROL@EXAMPLE.COM "]) # => false + # + # User.normalize_value_for(:phone, "+1 (555) 867-5309") # => "5558675309" + def normalizes(*names, with:, apply_to_nil: false) + decorate_attributes(names) do |name, cast_type| + NormalizedValueType.new(cast_type: cast_type, normalizer: with, normalize_nil: apply_to_nil) + end + + self.normalized_attributes += names.map(&:to_sym) + end + + # Normalizes a given +value+ using normalizations declared for +name+. + # + # ==== Examples + # + # class User < ActiveRecord::Base + # normalizes :email, with: -> email { email.strip.downcase } + # end + # + # User.normalize_value_for(:email, " CRUISE-CONTROL@EXAMPLE.COM\n") + # # => "cruise-control@example.com" + def normalize_value_for(name, value) + type_for_attribute(name).cast(value) + end + end + + private + def normalize_changed_in_place_attributes + self.class.normalized_attributes.each do |name| + normalize_attribute(name) if attribute_changed_in_place?(name) + end + end + + class NormalizedValueType < DelegateClass(ActiveModel::Type::Value) # :nodoc: + include ActiveModel::Type::SerializeCastValue + + attr_reader :cast_type, :normalizer, :normalize_nil + alias :normalize_nil? :normalize_nil + + def initialize(cast_type:, normalizer:, normalize_nil:) + @cast_type = cast_type + @normalizer = normalizer + @normalize_nil = normalize_nil + super(cast_type) + end + + def cast(value) + normalize(super(value)) + end + + def serialize(value) + serialize_cast_value(cast(value)) + end + + def serialize_cast_value(value) + ActiveModel::Type::SerializeCastValue.serialize(cast_type, value) + end + + def ==(other) + self.class == other.class && + normalize_nil? == other.normalize_nil? && + normalizer == other.normalizer && + cast_type == other.cast_type + end + alias eql? == + + def hash + [self.class, cast_type, normalizer, normalize_nil?].hash + end + + define_method(:inspect, Kernel.instance_method(:inspect)) + + private + def normalize(value) + normalizer.call(value) unless value.nil? && !normalize_nil? + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/persistence.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/persistence.rb new file mode 100644 index 00000000..1d03c0e9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/persistence.rb @@ -0,0 +1,968 @@ +# frozen_string_literal: true + +require "active_record/insert_all" + +module ActiveRecord + # = Active Record \Persistence + module Persistence + extend ActiveSupport::Concern + + module ClassMethods + # Creates an object (or multiple objects) and saves it to the database, if validations pass. + # The resulting object is returned whether the object was saved successfully to the database or not. + # + # The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the + # attributes on the objects that are to be created. + # + # ==== Examples + # # Create a single new object + # User.create(first_name: 'Jamie') + # + # # Create an Array of new objects + # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) + # + # # Create a single object and pass it into a block to set other attributes. + # User.create(first_name: 'Jamie') do |u| + # u.is_admin = false + # end + # + # # Creating an Array of new objects using a block, where the block is executed for each object: + # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u| + # u.is_admin = false + # end + def create(attributes = nil, &block) + if attributes.is_a?(Array) + attributes.collect { |attr| create(attr, &block) } + else + object = new(attributes, &block) + object.save + object + end + end + + # Creates an object (or multiple objects) and saves it to the database, + # if validations pass. Raises a RecordInvalid error if validations fail, + # unlike Base#create. + # + # The +attributes+ parameter can be either a Hash or an Array of Hashes. + # These describe which attributes to be created on the object, or + # multiple objects when given an Array of Hashes. + def create!(attributes = nil, &block) + if attributes.is_a?(Array) + attributes.collect { |attr| create!(attr, &block) } + else + object = new(attributes, &block) + object.save! + object + end + end + + # Builds an object (or multiple objects) and returns either the built object or a list of built + # objects. + # + # The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the + # attributes on the objects that are to be built. + # + # ==== Examples + # # Build a single new object + # User.build(first_name: 'Jamie') + # + # # Build an Array of new objects + # User.build([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) + # + # # Build a single object and pass it into a block to set other attributes. + # User.build(first_name: 'Jamie') do |u| + # u.is_admin = false + # end + # + # # Building an Array of new objects using a block, where the block is executed for each object: + # User.build([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u| + # u.is_admin = false + # end + def build(attributes = nil, &block) + if attributes.is_a?(Array) + attributes.collect { |attr| build(attr, &block) } + else + new(attributes, &block) + end + end + + # Given an attributes hash, +instantiate+ returns a new instance of + # the appropriate class. Accepts only keys as strings. + # + # For example, +Post.all+ may return Comments, Messages, and Emails + # by storing the record's subclass in a +type+ attribute. By calling + # +instantiate+ instead of +new+, finder methods ensure they get new + # instances of the appropriate class for each record. + # + # See ActiveRecord::Inheritance#discriminate_class_for_record to see + # how this "single-table" inheritance mapping is implemented. + def instantiate(attributes, column_types = {}, &block) + klass = discriminate_class_for_record(attributes) + instantiate_instance_of(klass, attributes, column_types, &block) + end + + # Updates an object (or multiple objects) and saves it to the database, if validations pass. + # The resulting object is returned whether the object was saved successfully to the database or not. + # + # ==== Parameters + # + # * +id+ - This should be the id or an array of ids to be updated. + # Optional argument, defaults to all records in the relation. + # * +attributes+ - This should be a hash of attributes or an array of hashes. + # + # ==== Examples + # + # # Updates one record + # Person.update(15, user_name: "Samuel", group: "expert") + # + # # Updates multiple records + # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } } + # Person.update(people.keys, people.values) + # + # # Updates multiple records from the result of a relation + # people = Person.where(group: "expert") + # people.update(group: "masters") + # + # Note: Updating a large number of records will run an UPDATE + # query for each record, which may cause a performance issue. + # When running callbacks is not needed for each record update, + # it is preferred to use {update_all}[rdoc-ref:Relation#update_all] + # for updating all records in a single query. + def update(id = :all, attributes) + if id.is_a?(Array) + if id.any?(ActiveRecord::Base) + raise ArgumentError, + "You are passing an array of ActiveRecord::Base instances to `update`. " \ + "Please pass the ids of the objects by calling `pluck(:id)` or `map(&:id)`." + end + id.map { |one_id| find(one_id) }.each_with_index { |object, idx| + object.update(attributes[idx]) + } + elsif id == :all + all.each { |record| record.update(attributes) } + else + if ActiveRecord::Base === id + raise ArgumentError, + "You are passing an instance of ActiveRecord::Base to `update`. " \ + "Please pass the id of the object by calling `.id`." + end + object = find(id) + object.update(attributes) + object + end + end + + # Updates the object (or multiple objects) just like #update but calls #update! instead + # of +update+, so an exception is raised if the record is invalid and saving will fail. + def update!(id = :all, attributes) + if id.is_a?(Array) + if id.any?(ActiveRecord::Base) + raise ArgumentError, + "You are passing an array of ActiveRecord::Base instances to `update!`. " \ + "Please pass the ids of the objects by calling `pluck(:id)` or `map(&:id)`." + end + id.map { |one_id| find(one_id) }.each_with_index { |object, idx| + object.update!(attributes[idx]) + } + elsif id == :all + all.each { |record| record.update!(attributes) } + else + if ActiveRecord::Base === id + raise ArgumentError, + "You are passing an instance of ActiveRecord::Base to `update!`. " \ + "Please pass the id of the object by calling `.id`." + end + object = find(id) + object.update!(attributes) + object + end + end + + # Accepts a list of attribute names to be used in the WHERE clause + # of SELECT / UPDATE / DELETE queries and in the ORDER BY clause for +#first+ and +#last+ finder methods. + # + # class Developer < ActiveRecord::Base + # query_constraints :company_id, :id + # end + # + # developer = Developer.first + # # SELECT "developers".* FROM "developers" ORDER BY "developers"."company_id" ASC, "developers"."id" ASC LIMIT 1 + # developer.inspect # => # + # + # developer.update!(name: "Nikita") + # # UPDATE "developers" SET "name" = 'Nikita' WHERE "developers"."company_id" = 1 AND "developers"."id" = 1 + # + # # It is possible to update an attribute used in the query_constraints clause: + # developer.update!(company_id: 2) + # # UPDATE "developers" SET "company_id" = 2 WHERE "developers"."company_id" = 1 AND "developers"."id" = 1 + # + # developer.name = "Bob" + # developer.save! + # # UPDATE "developers" SET "name" = 'Bob' WHERE "developers"."company_id" = 1 AND "developers"."id" = 1 + # + # developer.destroy! + # # DELETE FROM "developers" WHERE "developers"."company_id" = 1 AND "developers"."id" = 1 + # + # developer.delete + # # DELETE FROM "developers" WHERE "developers"."company_id" = 1 AND "developers"."id" = 1 + # + # developer.reload + # # SELECT "developers".* FROM "developers" WHERE "developers"."company_id" = 1 AND "developers"."id" = 1 LIMIT 1 + def query_constraints(*columns_list) + raise ArgumentError, "You must specify at least one column to be used in querying" if columns_list.empty? + + @query_constraints_list = columns_list.map(&:to_s) + @has_query_constraints = @query_constraints_list + end + + def has_query_constraints? # :nodoc: + @has_query_constraints + end + + def query_constraints_list # :nodoc: + @query_constraints_list ||= if base_class? || primary_key != base_class.primary_key + primary_key if primary_key.is_a?(Array) + else + base_class.query_constraints_list + end + end + + # Returns an array of column names to be used in queries. The source of column + # names is derived from +query_constraints_list+ or +primary_key+. This method + # is for internal use when the primary key is to be treated as an array. + def composite_query_constraints_list # :nodoc: + @composite_query_constraints_list ||= query_constraints_list || Array(primary_key) + end + + def _insert_record(connection, values, returning) # :nodoc: + primary_key = self.primary_key + primary_key_value = nil + + if prefetch_primary_key? && primary_key + values[primary_key] ||= begin + primary_key_value = next_sequence_value + _default_attributes[primary_key].with_cast_value(primary_key_value) + end + end + + im = Arel::InsertManager.new(arel_table) + + if values.empty? + im.insert(connection.empty_insert_statement_value(primary_key)) + else + im.insert(values.transform_keys { |name| arel_table[name] }) + end + + connection.insert( + im, "#{self} Create", primary_key || false, primary_key_value, + returning: returning + ) + end + + def _update_record(values, constraints) # :nodoc: + constraints = constraints.map { |name, value| predicate_builder[name, value] } + + default_constraint = build_default_constraint + constraints << default_constraint if default_constraint + + if current_scope = self.global_current_scope + constraints << current_scope.where_clause.ast + end + + um = Arel::UpdateManager.new(arel_table) + um.set(values.transform_keys { |name| arel_table[name] }) + um.wheres = constraints + + with_connection do |c| + c.update(um, "#{self} Update") + end + end + + def _delete_record(constraints) # :nodoc: + constraints = constraints.map { |name, value| predicate_builder[name, value] } + + default_constraint = build_default_constraint + constraints << default_constraint if default_constraint + + if current_scope = self.global_current_scope + constraints << current_scope.where_clause.ast + end + + dm = Arel::DeleteManager.new(arel_table) + dm.wheres = constraints + + with_connection do |c| + c.delete(dm, "#{self} Destroy") + end + end + + private + def inherited(subclass) + super + subclass.class_eval do + @_query_constraints_list = nil + @has_query_constraints = false + end + end + + # Given a class, an attributes hash, +instantiate_instance_of+ returns a + # new instance of the class. Accepts only keys as strings. + def instantiate_instance_of(klass, attributes, column_types = {}, &block) + attributes = klass.attributes_builder.build_from_database(attributes, column_types) + klass.allocate.init_with_attributes(attributes, &block) + end + + # Called by +instantiate+ to decide which class to use for a new + # record instance. + # + # See +ActiveRecord::Inheritance#discriminate_class_for_record+ for + # the single-table inheritance discriminator. + def discriminate_class_for_record(record) + self + end + + # Called by +_update_record+ and +_delete_record+ + # to build `where` clause from default scopes. + # Skips empty scopes. + def build_default_constraint + return unless default_scopes?(all_queries: true) + + default_where_clause = default_scoped(all_queries: true).where_clause + default_where_clause.ast unless default_where_clause.empty? + end + end + + # Returns true if this object hasn't been saved yet -- that is, a record + # for the object doesn't exist in the database yet; otherwise, returns false. + def new_record? + @new_record + end + + # Returns true if this object was just created -- that is, prior to the last + # update or delete, the object didn't exist in the database and new_record? would have + # returned true. + def previously_new_record? + @previously_new_record + end + + # Returns true if this object was previously persisted but now it has been deleted. + def previously_persisted? + !new_record? && destroyed? + end + + # Returns true if this object has been destroyed, otherwise returns false. + def destroyed? + @destroyed + end + + # Returns true if the record is persisted, i.e. it's not a new record and it was + # not destroyed, otherwise returns false. + def persisted? + !(@new_record || @destroyed) + end + + ## + # :call-seq: + # save(**options) + # + # Saves the model. + # + # If the model is new, a record gets created in the database, otherwise + # the existing record gets updated. + # + # By default, save always runs validations. If any of them fail the action + # is cancelled and #save returns +false+, and the record won't be saved. However, if you supply + # validate: false, validations are bypassed altogether. See + # ActiveRecord::Validations for more information. + # + # By default, #save also sets the +updated_at+/+updated_on+ attributes to + # the current time. However, if you supply touch: false, these + # timestamps will not be updated. + # + # There's a series of callbacks associated with #save. If any of the + # before_* callbacks throws +:abort+ the action is cancelled and + # #save returns +false+. See ActiveRecord::Callbacks for further + # details. + # + # Attributes marked as readonly are silently ignored if the record is + # being updated. + def save(**options, &block) + create_or_update(**options, &block) + rescue ActiveRecord::RecordInvalid + false + end + + ## + # :call-seq: + # save!(**options) + # + # Saves the model. + # + # If the model is new, a record gets created in the database, otherwise + # the existing record gets updated. + # + # By default, #save! always runs validations. If any of them fail + # ActiveRecord::RecordInvalid gets raised, and the record won't be saved. However, if you supply + # validate: false, validations are bypassed altogether. See + # ActiveRecord::Validations for more information. + # + # By default, #save! also sets the +updated_at+/+updated_on+ attributes to + # the current time. However, if you supply touch: false, these + # timestamps will not be updated. + # + # There's a series of callbacks associated with #save!. If any of + # the before_* callbacks throws +:abort+ the action is cancelled + # and #save! raises ActiveRecord::RecordNotSaved. See + # ActiveRecord::Callbacks for further details. + # + # Attributes marked as readonly are silently ignored if the record is + # being updated. + # + # Unless an error is raised, returns true. + def save!(**options, &block) + create_or_update(**options, &block) || raise(RecordNotSaved.new("Failed to save the record", self)) + end + + # Deletes the record in the database and freezes this instance to + # reflect that no changes should be made (since they can't be + # persisted). Returns the frozen instance. + # + # The row is simply removed with an SQL +DELETE+ statement on the + # record's primary key, and no callbacks are executed. + # + # Note that this will also delete records marked as {#readonly?}[rdoc-ref:Core#readonly?]. + # + # To enforce the object's +before_destroy+ and +after_destroy+ + # callbacks or any :dependent association + # options, use #destroy. + def delete + _delete_row if persisted? + @destroyed = true + @previously_new_record = false + freeze + end + + # Deletes the record in the database and freezes this instance to reflect + # that no changes should be made (since they can't be persisted). + # + # There's a series of callbacks associated with #destroy. If the + # before_destroy callback throws +:abort+ the action is cancelled + # and #destroy returns +false+. + # See ActiveRecord::Callbacks for further details. + def destroy + _raise_readonly_record_error if readonly? + destroy_associations + @_trigger_destroy_callback ||= persisted? && destroy_row > 0 + @destroyed = true + @previously_new_record = false + freeze + end + + # Deletes the record in the database and freezes this instance to reflect + # that no changes should be made (since they can't be persisted). + # + # There's a series of callbacks associated with #destroy!. If the + # before_destroy callback throws +:abort+ the action is cancelled + # and #destroy! raises ActiveRecord::RecordNotDestroyed. + # See ActiveRecord::Callbacks for further details. + def destroy! + destroy || _raise_record_not_destroyed + end + + # Returns an instance of the specified +klass+ with the attributes of the + # current record. This is mostly useful in relation to single table + # inheritance (STI) structures where you want a subclass to appear as the + # superclass. This can be used along with record identification in + # Action Pack to allow, say, Client < Company to do something + # like render partial: @client.becomes(Company) to render that + # instance using the companies/company partial instead of clients/client. + # + # Note: The new instance will share a link to the same attributes as the original class. + # Therefore the STI column value will still be the same. + # Any change to the attributes on either instance will affect both instances. + # This includes any attribute initialization done by the new instance. + # + # If you want to change the STI column as well, use #becomes! instead. + def becomes(klass) + became = klass.allocate + + became.send(:initialize) do |becoming| + @attributes.reverse_merge!(becoming.instance_variable_get(:@attributes)) + becoming.instance_variable_set(:@attributes, @attributes) + becoming.instance_variable_set(:@mutations_from_database, @mutations_from_database ||= nil) + becoming.instance_variable_set(:@new_record, new_record?) + becoming.instance_variable_set(:@destroyed, destroyed?) + becoming.errors.copy!(errors) + end + + became + end + + # Wrapper around #becomes that also changes the instance's STI column value. + # This is especially useful if you want to persist the changed class in your + # database. + # + # Note: The old instance's STI column value will be changed too, as both objects + # share the same set of attributes. + def becomes!(klass) + became = becomes(klass) + sti_type = nil + if !klass.descends_from_active_record? + sti_type = klass.sti_name + end + became.public_send("#{klass.inheritance_column}=", sti_type) + became + end + + # Updates a single attribute and saves the record. + # This is especially useful for boolean flags on existing records. Also note that + # + # * Validation is skipped. + # * \Callbacks are invoked. + # * updated_at/updated_on column is updated if that column is available. + # * Updates all the attributes that are dirty in this object. + # + # This method raises an ActiveRecord::ActiveRecordError if the + # attribute is marked as readonly. + # + # Also see #update_column. + def update_attribute(name, value) + name = name.to_s + verify_readonly_attribute(name) + public_send("#{name}=", value) + + save(validate: false) + end + + # Updates a single attribute and saves the record. + # This is especially useful for boolean flags on existing records. Also note that + # + # * Validation is skipped. + # * \Callbacks are invoked. + # * updated_at/updated_on column is updated if that column is available. + # * Updates all the attributes that are dirty in this object. + # + # This method raises an ActiveRecord::ActiveRecordError if the + # attribute is marked as readonly. + # + # If any of the before_* callbacks throws +:abort+ the action is cancelled + # and #update_attribute! raises ActiveRecord::RecordNotSaved. See + # ActiveRecord::Callbacks for further details. + def update_attribute!(name, value) + name = name.to_s + verify_readonly_attribute(name) + public_send("#{name}=", value) + + save!(validate: false) + end + + # Updates the attributes of the model from the passed-in hash and saves the + # record, all wrapped in a transaction. If the object is invalid, the saving + # will fail and false will be returned. + def update(attributes) + # The following transaction covers any possible database side-effects of the + # attributes assignment. For example, setting the IDs of a child collection. + with_transaction_returning_status do + assign_attributes(attributes) + save + end + end + + # Updates its receiver just like #update but calls #save! instead + # of +save+, so an exception is raised if the record is invalid and saving will fail. + def update!(attributes) + # The following transaction covers any possible database side-effects of the + # attributes assignment. For example, setting the IDs of a child collection. + with_transaction_returning_status do + assign_attributes(attributes) + save! + end + end + + # Equivalent to update_columns(name => value). + def update_column(name, value) + update_columns(name => value) + end + + # Updates the attributes directly in the database issuing an UPDATE SQL + # statement and sets them in the receiver: + # + # user.update_columns(last_request_at: Time.current) + # + # This is the fastest way to update attributes because it goes straight to + # the database, but take into account that in consequence the regular update + # procedures are totally bypassed. In particular: + # + # * \Validations are skipped. + # * \Callbacks are skipped. + # * +updated_at+/+updated_on+ are not updated. + # * However, attributes are serialized with the same rules as ActiveRecord::Relation#update_all + # + # This method raises an ActiveRecord::ActiveRecordError when called on new + # objects, or when at least one of the attributes is marked as readonly. + def update_columns(attributes) + raise ActiveRecordError, "cannot update a new record" if new_record? + raise ActiveRecordError, "cannot update a destroyed record" if destroyed? + _raise_readonly_record_error if readonly? + + attributes = attributes.transform_keys do |key| + name = key.to_s + name = self.class.attribute_aliases[name] || name + verify_readonly_attribute(name) || name + end + + update_constraints = _query_constraints_hash + attributes = attributes.each_with_object({}) do |(k, v), h| + h[k] = @attributes.write_cast_value(k, v) + clear_attribute_change(k) + end + + affected_rows = self.class._update_record( + attributes, + update_constraints + ) + + affected_rows == 1 + end + + # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1). + # The increment is performed directly on the underlying attribute, no setter is invoked. + # Only makes sense for number-based attributes. Returns +self+. + def increment(attribute, by = 1) + self[attribute] ||= 0 + self[attribute] += by + self + end + + # Wrapper around #increment that writes the update to the database. + # Only +attribute+ is updated; the record itself is not saved. + # This means that any other modified attributes will still be dirty. + # Validations and callbacks are skipped. Supports the +touch+ option from + # +update_counters+, see that for more. + # Returns +self+. + def increment!(attribute, by = 1, touch: nil) + increment(attribute, by) + change = public_send(attribute) - (public_send(:"#{attribute}_in_database") || 0) + self.class.update_counters(id, attribute => change, touch: touch) + public_send(:"clear_#{attribute}_change") + self + end + + # Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1). + # The decrement is performed directly on the underlying attribute, no setter is invoked. + # Only makes sense for number-based attributes. Returns +self+. + def decrement(attribute, by = 1) + increment(attribute, -by) + end + + # Wrapper around #decrement that writes the update to the database. + # Only +attribute+ is updated; the record itself is not saved. + # This means that any other modified attributes will still be dirty. + # Validations and callbacks are skipped. Supports the +touch+ option from + # +update_counters+, see that for more. + # Returns +self+. + def decrement!(attribute, by = 1, touch: nil) + increment!(attribute, -by, touch: touch) + end + + # Assigns to +attribute+ the boolean opposite of attribute?. So + # if the predicate returns +true+ the attribute will become +false+. This + # method toggles directly the underlying value without calling any setter. + # Returns +self+. + # + # Example: + # + # user = User.first + # user.banned? # => false + # user.toggle(:banned) + # user.banned? # => true + # + def toggle(attribute) + self[attribute] = !public_send("#{attribute}?") + self + end + + # Wrapper around #toggle that saves the record. This method differs from + # its non-bang version in the sense that it passes through the attribute setter. + # Saving is not subjected to validation checks. Returns +true+ if the + # record could be saved. + def toggle!(attribute) + toggle(attribute).update_attribute(attribute, self[attribute]) + end + + # Reloads the record from the database. + # + # This method finds the record by its primary key (which could be assigned + # manually) and modifies the receiver in-place: + # + # account = Account.new + # # => # + # account.id = 1 + # account.reload + # # Account Load (1.2ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT 1 [["id", 1]] + # # => # + # + # Attributes are reloaded from the database, and caches busted, in + # particular the associations cache and the QueryCache. + # + # If the record no longer exists in the database ActiveRecord::RecordNotFound + # is raised. Otherwise, in addition to the in-place modification the method + # returns +self+ for convenience. + # + # The optional :lock flag option allows you to lock the reloaded record: + # + # reload(lock: true) # reload with pessimistic locking + # + # Reloading is commonly used in test suites to test something is actually + # written to the database, or when some action modifies the corresponding + # row in the database but not the object in memory: + # + # assert account.deposit!(25) + # assert_equal 25, account.credit # check it is updated in memory + # assert_equal 25, account.reload.credit # check it is also persisted + # + # Another common use case is optimistic locking handling: + # + # def with_optimistic_retry + # begin + # yield + # rescue ActiveRecord::StaleObjectError + # begin + # # Reload lock_version in particular. + # reload + # rescue ActiveRecord::RecordNotFound + # # If the record is gone there is nothing to do. + # else + # retry + # end + # end + # end + # + def reload(options = nil) + self.class.connection_pool.clear_query_cache + + fresh_object = if apply_scoping?(options) + _find_record((options || {}).merge(all_queries: true)) + else + self.class.unscoped { _find_record(options) } + end + + @association_cache = fresh_object.instance_variable_get(:@association_cache) + @association_cache.each_value { |association| association.owner = self } + @attributes = fresh_object.instance_variable_get(:@attributes) + @new_record = false + @previously_new_record = false + self + end + + # Saves the record with the updated_at/on attributes set to the current time + # or the time specified. + # Please note that no validation is performed and only the +after_touch+, + # +after_commit+ and +after_rollback+ callbacks are executed. + # + # This method can be passed attribute names and an optional time argument. + # If attribute names are passed, they are updated along with updated_at/on + # attributes. If no time argument is passed, the current time is used as default. + # + # product.touch # updates updated_at/on with current time + # product.touch(time: Time.new(2015, 2, 16, 0, 0, 0)) # updates updated_at/on with specified time + # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on + # product.touch(:started_at, :ended_at) # updates started_at, ended_at and updated_at/on attributes + # + # If used along with {belongs_to}[rdoc-ref:Associations::ClassMethods#belongs_to] + # then +touch+ will invoke +touch+ method on associated object. + # + # class Brake < ActiveRecord::Base + # belongs_to :car, touch: true + # end + # + # class Car < ActiveRecord::Base + # belongs_to :corporation, touch: true + # end + # + # # triggers @brake.car.touch and @brake.car.corporation.touch + # @brake.touch + # + # Note that +touch+ must be used on a persisted object, or else an + # ActiveRecordError will be thrown. For example: + # + # ball = Ball.new + # ball.touch(:updated_at) # => raises ActiveRecordError + # + def touch(*names, time: nil) + _raise_record_not_touched_error unless persisted? + _raise_readonly_record_error if readonly? + + attribute_names = timestamp_attributes_for_update_in_model + attribute_names = (attribute_names | names).map! do |name| + name = name.to_s + name = self.class.attribute_aliases[name] || name + verify_readonly_attribute(name) + name + end + + unless attribute_names.empty? + affected_rows = _touch_row(attribute_names, time) + @_trigger_update_callback = affected_rows == 1 + else + true + end + end + + private + def init_internals + super + @_trigger_destroy_callback = @_trigger_update_callback = nil + @previously_new_record = false + end + + def strict_loaded_associations + @association_cache.find_all do |_, assoc| + assoc.owner.strict_loading? && !assoc.owner.strict_loading_n_plus_one_only? + end.map(&:first) + end + + def _find_record(options) + all_queries = options ? options[:all_queries] : nil + base = self.class.all(all_queries: all_queries).preload(strict_loaded_associations) + + if options && options[:lock] + base.lock(options[:lock]).find_by!(_in_memory_query_constraints_hash) + else + base.find_by!(_in_memory_query_constraints_hash) + end + end + + def _in_memory_query_constraints_hash + if self.class.query_constraints_list.nil? + { @primary_key => id } + else + self.class.query_constraints_list.index_with do |column_name| + attribute(column_name) + end + end + end + + def apply_scoping?(options) + !(options && options[:unscoped]) && + (self.class.default_scopes?(all_queries: true) || self.class.global_current_scope) + end + + def _query_constraints_hash + if self.class.query_constraints_list.nil? + { @primary_key => id_in_database } + else + self.class.query_constraints_list.index_with do |column_name| + attribute_in_database(column_name) + end + end + end + + # A hook to be overridden by association modules. + def destroy_associations + end + + def destroy_row + _delete_row + end + + def _delete_row + self.class._delete_record(_query_constraints_hash) + end + + def _touch_row(attribute_names, time) + time ||= current_time_from_proper_timezone + + attribute_names.each do |attr_name| + _write_attribute(attr_name, time) + end + + _update_row(attribute_names, "touch") + end + + def _update_row(attribute_names, attempted_action = "update") + self.class._update_record( + attributes_with_values(attribute_names), + _query_constraints_hash + ) + end + + def create_or_update(**, &block) + _raise_readonly_record_error if readonly? + return false if destroyed? + result = new_record? ? _create_record(&block) : _update_record(&block) + result != false + end + + # Updates the associated record with values matching those of the instance attributes. + # Returns the number of affected rows. + def _update_record(attribute_names = self.attribute_names) + attribute_names = attributes_for_update(attribute_names) + + if attribute_names.empty? + affected_rows = 0 + @_trigger_update_callback = true + else + affected_rows = _update_row(attribute_names) + @_trigger_update_callback = affected_rows == 1 + end + + @previously_new_record = false + + yield(self) if block_given? + + affected_rows + end + + # Creates a record with values matching those of the instance attributes + # and returns its id. + def _create_record(attribute_names = self.attribute_names) + attribute_names = attributes_for_create(attribute_names) + + self.class.with_connection do |connection| + returning_columns = self.class._returning_columns_for_insert(connection) + + returning_values = self.class._insert_record( + connection, + attributes_with_values(attribute_names), + returning_columns + ) + + returning_columns.zip(returning_values).each do |column, value| + _write_attribute(column, type_for_attribute(column).deserialize(value)) if !_read_attribute(column) + end if returning_values + end + + @new_record = false + @previously_new_record = true + + yield(self) if block_given? + + id + end + + def verify_readonly_attribute(name) + raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attribute?(name) + end + + def _raise_record_not_destroyed + @_association_destroy_exception ||= nil + key = self.class.primary_key + raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy #{self.class} with #{key}=#{id}", self) + ensure + @_association_destroy_exception = nil + end + + def _raise_readonly_record_error + raise ReadOnlyRecord, "#{self.class} is marked as readonly" + end + + def _raise_record_not_touched_error + raise ActiveRecordError, <<~MSG.squish + Cannot touch on a new or destroyed record object. Consider using + persisted?, new_record?, or destroyed? before touching. + MSG + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/promise.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/promise.rb new file mode 100644 index 00000000..4e9b2ff0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/promise.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +module ActiveRecord + class Promise < BasicObject + undef_method :==, :!, :!= + + def initialize(future_result, block) # :nodoc: + @future_result = future_result + @block = block + end + + # Returns whether the associated query is still being executed or not. + def pending? + @future_result.pending? + end + + # Returns the query result. + # If the query wasn't completed yet, accessing +#value+ will block until the query completes. + # If the query failed, +#value+ will raise the corresponding error. + def value + return @value if defined? @value + + result = @future_result.result + @value = if @block + @block.call(result) + else + result + end + end + + # Returns a new +ActiveRecord::Promise+ that will apply the passed block + # when the value is accessed: + # + # Post.async_pick(:title).then { |title| title.upcase }.value + # # => "POST TITLE" + def then(&block) + Promise.new(@future_result, @block ? @block >> block : block) + end + + [:class, :respond_to?, :is_a?].each do |method| + define_method(method, ::Object.instance_method(method)) + end + + def inspect # :nodoc: + "#" + end + + def pretty_print(q) # :nodoc: + q.text(inspect) + end + + private + def status + if @future_result.pending? + :pending + elsif @future_result.canceled? + :canceled + else + :complete + end + end + + class Complete < self # :nodoc: + attr_reader :value + + def initialize(value) + @value = value + end + + def then + Complete.new(yield @value) + end + + def pending? + false + end + + private + def status + :complete + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/query_cache.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/query_cache.rb new file mode 100644 index 00000000..3a4e49e8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/query_cache.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record Query Cache + class QueryCache + module ClassMethods + # Enable the query cache within the block if Active Record is configured. + # If it's not, it will execute the given block. + def cache(&block) + if connected? || !configurations.empty? + pool = connection_pool + was_enabled = pool.query_cache_enabled + begin + pool.enable_query_cache(&block) + ensure + pool.clear_query_cache unless was_enabled + end + else + yield + end + end + + # Disable the query cache within the block if Active Record is configured. + # If it's not, it will execute the given block. + # + # Set dirties: false to prevent query caches on all connections from being cleared by write operations. + # (By default, write operations dirty all connections' query caches in case they are replicas whose cache would now be outdated.) + def uncached(dirties: true, &block) + if connected? || !configurations.empty? + connection_pool.disable_query_cache(dirties: dirties, &block) + else + yield + end + end + end + + def self.run + ActiveRecord::Base.connection_handler.each_connection_pool.reject(&:query_cache_enabled).each do |pool| + next if pool.db_config&.query_cache == false + pool.enable_query_cache! + end + end + + def self.complete(pools) + pools.each do |pool| + pool.disable_query_cache! + pool.clear_query_cache + end + end + + def self.install_executor_hooks(executor = ActiveSupport::Executor) + executor.register_hook(self) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/query_logs.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/query_logs.rb new file mode 100644 index 00000000..0d4361e8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/query_logs.rb @@ -0,0 +1,241 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors_per_thread" +require "active_record/query_logs_formatter" + +module ActiveRecord + # = Active Record Query Logs + # + # Automatically append comments to SQL queries with runtime information tags. This can be used to trace troublesome + # SQL statements back to the application code that generated these statements. + # + # Query logs can be enabled via \Rails configuration in config/application.rb or an initializer: + # + # config.active_record.query_log_tags_enabled = true + # + # By default the name of the application, the name and action of the controller, or the name of the job are logged. + # The default format is {SQLCommenter}[https://open-telemetry.github.io/opentelemetry-sqlcommenter/]. + # The tags shown in a query comment can be configured via \Rails configuration: + # + # config.active_record.query_log_tags = [ :application, :controller, :action, :job ] + # + # Active Record defines default tags available for use: + # + # * +application+ + # * +pid+ + # * +socket+ + # * +db_host+ + # * +database+ + # * +source_location+ + # + # Action Controller adds default tags when loaded: + # + # * +controller+ + # * +action+ + # * +namespaced_controller+ + # + # Active Job adds default tags when loaded: + # + # * +job+ + # + # New comment tags can be defined by adding them in a +Hash+ to the tags +Array+. Tags can have dynamic content by + # setting a +Proc+ or lambda value in the +Hash+, and can reference any value stored by \Rails in the +context+ object. + # ActiveSupport::CurrentAttributes can be used to store application values. Tags with +nil+ values are + # omitted from the query comment. + # + # Escaping is performed on the string returned, however untrusted user input should not be used. + # + # Example: + # + # config.active_record.query_log_tags = [ + # :namespaced_controller, + # :action, + # :job, + # { + # request_id: ->(context) { context[:controller]&.request&.request_id }, + # job_id: ->(context) { context[:job]&.job_id }, + # tenant_id: -> { Current.tenant&.id }, + # static: "value", + # }, + # ] + # + # By default the name of the application, the name and action of the controller, or the name of the job are logged + # using the {SQLCommenter}[https://open-telemetry.github.io/opentelemetry-sqlcommenter/] format. This can be changed + # via {config.active_record.query_log_tags_format}[https://guides.rubyonrails.org/configuring.html#config-active-record-query-log-tags-format] + # + # Tag comments can be prepended to the query: + # + # ActiveRecord::QueryLogs.prepend_comment = true + # + # For applications where the content will not change during the lifetime of + # the request or job execution, the tags can be cached for reuse in every query: + # + # config.active_record.cache_query_log_tags = true + module QueryLogs + class GetKeyHandler # :nodoc: + def initialize(name) + @name = name + end + + def call(context) + context[@name] + end + end + + class IdentityHandler # :nodoc: + def initialize(value) + @value = value + end + + def call(_context) + @value + end + end + + class ZeroArityHandler # :nodoc: + def initialize(proc) + @proc = proc + end + + def call(_context) + @proc.call + end + end + + @taggings = {}.freeze + @tags = [ :application ].freeze + @prepend_comment = false + @cache_query_log_tags = false + @tags_formatter = false + + thread_mattr_accessor :cached_comment, instance_accessor: false + + class << self + attr_reader :tags, :taggings, :tags_formatter # :nodoc: + attr_accessor :prepend_comment, :cache_query_log_tags # :nodoc: + + def taggings=(taggings) # :nodoc: + @taggings = taggings.freeze + @handlers = rebuild_handlers + end + + def tags=(tags) # :nodoc: + @tags = tags.freeze + @handlers = rebuild_handlers + end + + def tags_formatter=(format) # :nodoc: + @formatter = case format + when :legacy + LegacyFormatter + when :sqlcommenter + SQLCommenter + else + raise ArgumentError, "Formatter is unsupported: #{format}" + end + @tags_formatter = format + end + + def call(sql, connection) # :nodoc: + comment = self.comment(connection) + + if comment.blank? + sql + elsif prepend_comment + "#{comment} #{sql}" + else + "#{sql} #{comment}" + end + end + + def clear_cache # :nodoc: + self.cached_comment = nil + end + + def query_source_location # :nodoc: + Thread.each_caller_location do |location| + frame = LogSubscriber.backtrace_cleaner.clean_frame(location) + return frame if frame + end + nil + end + + ActiveSupport::ExecutionContext.after_change { ActiveRecord::QueryLogs.clear_cache } + + private + def rebuild_handlers + handlers = [] + @tags.each do |i| + if i.is_a?(Hash) + i.each do |k, v| + handlers << [k, build_handler(k, v)] + end + else + handlers << [i, build_handler(i)] + end + end + handlers.sort_by! { |(key, _)| key.to_s } + end + + def build_handler(name, handler = nil) + handler ||= @taggings[name] + if handler.nil? + GetKeyHandler.new(name) + elsif handler.respond_to?(:call) + if handler.arity == 0 + ZeroArityHandler.new(handler) + else + handler + end + else + IdentityHandler.new(handler) + end + end + + # Returns an SQL comment +String+ containing the query log tags. + # Sets and returns a cached comment if cache_query_log_tags is +true+. + def comment(connection) + if cache_query_log_tags + self.cached_comment ||= uncached_comment(connection) + else + uncached_comment(connection) + end + end + + def uncached_comment(connection) + content = tag_content(connection) + + if content.present? + "/*#{escape_sql_comment(content)}*/" + end + end + + def escape_sql_comment(content) + # Sanitize a string to appear within a SQL comment + # For compatibility, this also surrounding "/*+", "/*", and "*/" + # characters, possibly with single surrounding space. + # Then follows that by replacing any internal "*/" or "/ *" with + # "* /" or "/ *" + comment = content.to_s.dup + comment.gsub!(%r{\A\s*/\*\+?\s?|\s?\*/\s*\Z}, "") + comment.gsub!("*/", "* /") + comment.gsub!("/*", "/ *") + comment + end + + def tag_content(connection) + context = ActiveSupport::ExecutionContext.to_h + context[:connection] ||= connection + + pairs = @handlers.filter_map do |(key, handler)| + val = handler.call(context) + @formatter.format(key, val) unless val.nil? + end + @formatter.join(pairs) + end + end + + @handlers = rebuild_handlers + self.tags_formatter = :legacy + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/query_logs_formatter.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/query_logs_formatter.rb new file mode 100644 index 00000000..3969efe9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/query_logs_formatter.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module ActiveRecord + module QueryLogs + module LegacyFormatter # :nodoc: + class << self + # Formats the key value pairs into a string. + def format(key, value) + "#{key}:#{value}" + end + + def join(pairs) + pairs.join(",") + end + end + end + + class SQLCommenter # :nodoc: + class << self + def format(key, value) + "#{key}='#{ERB::Util.url_encode(value)}'" + end + + def join(pairs) + pairs.join(",") + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/querying.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/querying.rb new file mode 100644 index 00000000..aabf5faa --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/querying.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +module ActiveRecord + module Querying + QUERYING_METHODS = [ + :find, :find_by, :find_by!, :take, :take!, :sole, :find_sole_by, :first, :first!, :last, :last!, + :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, + :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!, + :exists?, :any?, :many?, :none?, :one?, + :first_or_create, :first_or_create!, :first_or_initialize, + :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, + :create_or_find_by, :create_or_find_by!, + :destroy, :destroy_all, :delete, :delete_all, :update_all, :touch_all, :destroy_by, :delete_by, + :find_each, :find_in_batches, :in_batches, + :select, :reselect, :order, :regroup, :in_order_of, :reorder, :group, :limit, :offset, :joins, :left_joins, :left_outer_joins, + :where, :rewhere, :invert_where, :preload, :extract_associated, :eager_load, :includes, :from, :lock, :readonly, + :and, :or, :annotate, :optimizer_hints, :extending, + :having, :create_with, :distinct, :references, :none, :unscope, :merge, :except, :only, + :count, :average, :minimum, :maximum, :sum, :calculate, + :pluck, :pick, :ids, :async_ids, :strict_loading, :excluding, :without, :with, :with_recursive, + :async_count, :async_average, :async_minimum, :async_maximum, :async_sum, :async_pluck, :async_pick, + :insert, :insert_all, :insert!, :insert_all!, :upsert, :upsert_all + ].freeze # :nodoc: + delegate(*QUERYING_METHODS, to: :all) + + # Executes a custom SQL query against your database and returns all the results. The results will + # be returned as an array, with the requested columns encapsulated as attributes of the model you call + # this method from. For example, if you call Product.find_by_sql, then the results will be returned in + # a +Product+ object with the attributes you specified in the SQL query. + # + # If you call a complicated SQL query which spans multiple tables, the columns specified by the + # SELECT will be attributes of the model, whether or not they are columns of the corresponding + # table. + # + # The +sql+ parameter is a full SQL query as a string. It will be called as is; there will be + # no database agnostic conversions performed. This should be a last resort because using + # database-specific terms will lock you into using that particular database engine, or require you to + # change your call if you switch engines. + # + # # A simple SQL query spanning multiple tables + # Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id" + # # => [#"Ruby Meetup", "author"=>"Quentin"}>, ...] + # + # You can use the same string replacement techniques as you can with ActiveRecord::QueryMethods#where : + # + # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date] + # Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }] + # + # Note that building your own SQL query string from user input may expose your application to + # injection attacks (https://guides.rubyonrails.org/security.html#sql-injection). + def find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block) + result = with_connection do |c| + _query_by_sql(c, sql, binds, preparable: preparable, allow_retry: allow_retry) + end + _load_from_sql(result, &block) + end + + # Same as #find_by_sql but perform the query asynchronously and returns an ActiveRecord::Promise. + def async_find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block) + with_connection do |c| + _query_by_sql(c, sql, binds, preparable: preparable, allow_retry: allow_retry, async: true) + end.then do |result| + _load_from_sql(result, &block) + end + end + + def _query_by_sql(connection, sql, binds = [], preparable: nil, async: false, allow_retry: false) # :nodoc: + connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable, async: async, allow_retry: allow_retry) + end + + def _load_from_sql(result_set, &block) # :nodoc: + return [] if result_set.empty? + + column_types = result_set.column_types + + unless column_types.empty? + column_types = column_types.reject { |k, _| attribute_types.key?(k) } + end + + message_bus = ActiveSupport::Notifications.instrumenter + + payload = { + record_count: result_set.length, + class_name: name + } + + message_bus.instrument("instantiation.active_record", payload) do + if result_set.includes_column?(inheritance_column) + result_set.indexed_rows.map { |record| instantiate(record, column_types, &block) } + else + # Instantiate a homogeneous set + result_set.indexed_rows.map { |record| instantiate_instance_of(self, record, column_types, &block) } + end + end + end + + # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part. + # The use of this method should be restricted to complicated SQL queries that can't be executed + # using the ActiveRecord::Calculations class methods. Look into those before using this method, + # as it could lock you into a specific database engine or require a code change to switch + # database engines. + # + # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id" + # # => 12 + # + # ==== Parameters + # + # * +sql+ - An SQL statement which should return a count query from the database, see the example above. + def count_by_sql(sql) + with_connection do |c| + c.select_value(sanitize_sql(sql), "#{name} Count").to_i + end + end + + # Same as #count_by_sql but perform the query asynchronously and returns an ActiveRecord::Promise. + def async_count_by_sql(sql) + with_connection do |c| + c.select_value(sanitize_sql(sql), "#{name} Count", async: true).then(&:to_i) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/railtie.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/railtie.rb new file mode 100644 index 00000000..3b99a066 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/railtie.rb @@ -0,0 +1,417 @@ +# frozen_string_literal: true + +require "active_record" +require "rails" +require "active_support/core_ext/object/try" +require "active_model/railtie" + +# For now, action_controller must always be present with +# Rails, so let's make sure that it gets required before +# here. This is needed for correctly setting up the middleware. +# In the future, this might become an optional require. +require "action_controller/railtie" + +module ActiveRecord + # = Active Record Railtie + class Railtie < Rails::Railtie # :nodoc: + config.active_record = ActiveSupport::OrderedOptions.new + config.active_record.encryption = ActiveSupport::OrderedOptions.new + + config.app_generators.orm :active_record, migration: true, + timestamps: true + + config.action_dispatch.rescue_responses.merge!( + "ActiveRecord::RecordNotFound" => :not_found, + "ActiveRecord::StaleObjectError" => :conflict, + "ActiveRecord::RecordInvalid" => :unprocessable_entity, + "ActiveRecord::RecordNotSaved" => :unprocessable_entity + ) + + config.active_record.use_schema_cache_dump = true + config.active_record.check_schema_cache_dump_version = true + config.active_record.maintain_test_schema = true + config.active_record.has_many_inversing = false + config.active_record.query_log_tags_enabled = false + config.active_record.query_log_tags = [ :application ] + config.active_record.query_log_tags_format = :legacy + config.active_record.cache_query_log_tags = false + config.active_record.raise_on_assign_to_attr_readonly = false + config.active_record.belongs_to_required_validates_foreign_key = true + config.active_record.generate_secure_token_on = :create + + config.active_record.queues = ActiveSupport::InheritableOptions.new + + config.eager_load_namespaces << ActiveRecord + + rake_tasks do + namespace :db do + task :load_config do + if defined?(ENGINE_ROOT) && engine = Rails::Engine.find(ENGINE_ROOT) + if engine.paths["db/migrate"].existent + ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths["db/migrate"].to_a + end + end + end + end + + load "active_record/railties/databases.rake" + end + + # When loading console, force ActiveRecord::Base to be loaded + # to avoid cross references when loading a constant for the + # first time. Also, make it output to STDERR. + console do |app| + require "active_record/railties/console_sandbox" if app.sandbox? + require "active_record/base" + unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDERR, STDOUT) + console = ActiveSupport::Logger.new(STDERR) + console.level = Rails.logger.level + Rails.logger.broadcast_to(console) + end + ActiveRecord.verbose_query_logs = false + ActiveRecord::Base.attributes_for_inspect = :all + end + + runner do + require "active_record/base" + end + + initializer "active_record.deprecator", before: :load_environment_config do |app| + app.deprecators[:active_record] = ActiveRecord.deprecator + end + + initializer "active_record.initialize_timezone" do + ActiveSupport.on_load(:active_record) do + self.time_zone_aware_attributes = true + end + end + + initializer "active_record.postgresql_time_zone_aware_types" do + ActiveSupport.on_load(:active_record_postgresqladapter) do + ActiveSupport.on_load(:active_record) do + ActiveRecord::Base.time_zone_aware_types << :timestamptz + end + end + end + + initializer "active_record.logger" do + ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger } + end + + initializer "active_record.backtrace_cleaner" do + ActiveSupport.on_load(:active_record) { LogSubscriber.backtrace_cleaner = ::Rails.backtrace_cleaner } + end + + initializer "active_record.migration_error" do |app| + if config.active_record.migration_error == :page_load + config.app_middleware.insert_after ::ActionDispatch::Callbacks, + ActiveRecord::Migration::CheckPending, + file_watcher: app.config.file_watcher + end + end + + initializer "active_record.cache_versioning_support" do + config.after_initialize do |app| + ActiveSupport.on_load(:active_record) do + if app.config.active_record.cache_versioning && Rails.cache + unless Rails.cache.class.try(:supports_cache_versioning?) + raise <<-end_error + +You're using a cache store that doesn't support native cache versioning. +Your best option is to upgrade to a newer version of #{Rails.cache.class} +that supports cache versioning (#{Rails.cache.class}.supports_cache_versioning? #=> true). + +Next best, switch to a different cache store that does support cache versioning: +https://guides.rubyonrails.org/caching_with_rails.html#cache-stores. + +To keep using the current cache store, you can turn off cache versioning entirely: + + config.active_record.cache_versioning = false + + end_error + end + end + end + end + end + + initializer "active_record.copy_schema_cache_config" do + active_record_config = config.active_record + + ActiveRecord::ConnectionAdapters::SchemaReflection.use_schema_cache_dump = active_record_config.use_schema_cache_dump + ActiveRecord::ConnectionAdapters::SchemaReflection.check_schema_cache_dump_version = active_record_config.check_schema_cache_dump_version + end + + initializer "active_record.define_attribute_methods" do |app| + # For resiliency, it is critical that a Rails application should be + # able to boot without depending on the database (or any other service) + # being responsive. + # + # Otherwise a bad deploy adding a lot of load on the database may require to + # entirely shutdown the application so the database can recover before a fixed + # version can be deployed again. + # + # This is why this initializer tries hard not to query the database, and if it + # does, it makes sure to rescue any possible database error. + check_schema_cache_dump_version = config.active_record.check_schema_cache_dump_version + config.after_initialize do + ActiveSupport.on_load(:active_record) do + # In development and test we shouldn't eagerly define attribute methods because + # db:test:prepare will trigger later and might change the schema. + # + # Additionally if `check_schema_cache_dump_version` is enabled (which is the default), + # loading the schema cache dump trigger a database connection to compare the schema + # versions. + # This means the attribute methods will be lazily defined when the model is accessed, + # likely as part of the first few requests or jobs. This isn't good for performance + # but we unfortunately have to arbitrate between resiliency and performance, and chose + # resiliency. + if !check_schema_cache_dump_version && app.config.eager_load && !Rails.env.local? + begin + descendants.each do |model| + if model.connection_pool.schema_reflection.cached?(model.table_name) + model.define_attribute_methods + end + end + rescue ActiveRecordError => error + # Regardless of whether there was already a connection or not, we rescue any database + # error because it is critical that the application can boot even if the database + # is unhealthy. + warn "Failed to define attribute methods because of #{error.class}: #{error.message}" + end + end + end + end + end + + initializer "active_record.sqlite3_adapter_strict_strings_by_default" do + config.after_initialize do + if config.active_record.sqlite3_adapter_strict_strings_by_default + ActiveSupport.on_load(:active_record_sqlite3adapter) do + self.strict_strings_by_default = true + end + end + end + end + + initializer "active_record.postgresql_adapter_decode_dates" do + config.after_initialize do + if config.active_record.postgresql_adapter_decode_dates + ActiveSupport.on_load(:active_record_postgresqladapter) do + self.decode_dates = true + end + end + end + end + + initializer "active_record.set_configs" do |app| + configs = app.config.active_record + + config.after_initialize do + configs.each do |k, v| + next if k == :encryption + setter = "#{k}=" + if ActiveRecord.respond_to?(setter) + ActiveRecord.send(setter, v) + end + end + end + + ActiveSupport.on_load(:active_record) do + configs_used_in_other_initializers = configs.except( + :migration_error, + :database_selector, + :database_resolver, + :database_resolver_context, + :shard_selector, + :shard_resolver, + :query_log_tags_enabled, + :query_log_tags, + :query_log_tags_format, + :cache_query_log_tags, + :sqlite3_adapter_strict_strings_by_default, + :check_schema_cache_dump_version, + :use_schema_cache_dump, + :postgresql_adapter_decode_dates, + ) + + configs_used_in_other_initializers.each do |k, v| + next if k == :encryption + setter = "#{k}=" + # Some existing initializers might rely on Active Record configuration + # being copied from the config object to their actual destination when + # `ActiveRecord::Base` is loaded. + # So to preserve backward compatibility we copy the config a second time. + if ActiveRecord.respond_to?(setter) + ActiveRecord.send(setter, v) + else + send(setter, v) + end + end + end + end + + # This sets the database configuration from Configuration#database_configuration + # and then establishes the connection. + initializer "active_record.initialize_database" do + ActiveSupport.on_load(:active_record) do + self.configurations = Rails.application.config.database_configuration + + establish_connection + end + end + + # Expose database runtime for logging. + initializer "active_record.log_runtime" do + require "active_record/railties/controller_runtime" + ActiveSupport.on_load(:action_controller) do + include ActiveRecord::Railties::ControllerRuntime + end + + require "active_record/railties/job_runtime" + ActiveSupport.on_load(:active_job) do + include ActiveRecord::Railties::JobRuntime + end + end + + initializer "active_record.set_reloader_hooks" do + ActiveSupport.on_load(:active_record) do + ActiveSupport::Reloader.before_class_unload do + if ActiveRecord::Base.connected? + ActiveRecord::Base.clear_cache! + ActiveRecord::Base.connection_handler.clear_reloadable_connections!(:all) + end + end + end + end + + initializer "active_record.set_executor_hooks" do + ActiveRecord::QueryCache.install_executor_hooks + ActiveRecord::AsynchronousQueriesTracker.install_executor_hooks + ActiveRecord::ConnectionAdapters::ConnectionPool.install_executor_hooks + end + + initializer "active_record.add_watchable_files" do |app| + path = app.paths["db"].first + config.watchable_files.concat ["#{path}/schema.rb", "#{path}/structure.sql"] + end + + initializer "active_record.clear_active_connections" do + config.after_initialize do + ActiveSupport.on_load(:active_record) do + # Ideally the application doesn't connect to the database during boot, + # but sometimes it does. In case it did, we want to empty out the + # connection pools so that a non-database-using process (e.g. a master + # process in a forking server model) doesn't retain a needless + # connection. If it was needed, the incremental cost of reestablishing + # this connection is trivial: the rest of the pool would need to be + # populated anyway. + + connection_handler.clear_active_connections!(:all) + connection_handler.flush_idle_connections!(:all) + end + end + end + + initializer "active_record.set_filter_attributes" do + ActiveSupport.on_load(:active_record) do + self.filter_attributes += Rails.application.config.filter_parameters + end + end + + initializer "active_record.set_signed_id_verifier_secret" do + ActiveSupport.on_load(:active_record) do + self.signed_id_verifier_secret ||= -> { Rails.application.key_generator.generate_key("active_record/signed_id") } + end + end + + initializer "active_record.generated_token_verifier" do + config.after_initialize do |app| + ActiveSupport.on_load(:active_record) do + self.generated_token_verifier ||= app.message_verifier("active_record/token_for") + end + end + end + + initializer "active_record_encryption.configuration" do |app| + ActiveSupport.on_load(:active_record_encryption) do + ActiveRecord::Encryption.configure( + primary_key: app.credentials.dig(:active_record_encryption, :primary_key), + deterministic_key: app.credentials.dig(:active_record_encryption, :deterministic_key), + key_derivation_salt: app.credentials.dig(:active_record_encryption, :key_derivation_salt), + **app.config.active_record.encryption + ) + + auto_filtered_parameters = ActiveRecord::Encryption::AutoFilteredParameters.new(app) + auto_filtered_parameters.enable if ActiveRecord::Encryption.config.add_to_filter_parameters + end + + ActiveSupport.on_load(:active_record) do + # Support extended queries for deterministic attributes and validations + if ActiveRecord::Encryption.config.extend_queries + ActiveRecord::Encryption::ExtendedDeterministicQueries.install_support + ActiveRecord::Encryption::ExtendedDeterministicUniquenessValidator.install_support + end + end + + ActiveSupport.on_load(:active_record_fixture_set) do + # Encrypt Active Record fixtures + if ActiveRecord::Encryption.config.encrypt_fixtures + ActiveRecord::Fixture.prepend ActiveRecord::Encryption::EncryptedFixtures + end + end + end + + initializer "active_record.query_log_tags_config" do |app| + config.after_initialize do + if app.config.active_record.query_log_tags_enabled + ActiveRecord.query_transformers << ActiveRecord::QueryLogs + ActiveRecord::QueryLogs.taggings = ActiveRecord::QueryLogs.taggings.merge( + application: Rails.application.class.name.split("::").first, + pid: -> { Process.pid.to_s }, + socket: ->(context) { context[:connection].pool.db_config.socket }, + db_host: ->(context) { context[:connection].pool.db_config.host }, + database: ->(context) { context[:connection].pool.db_config.database }, + source_location: -> { QueryLogs.query_source_location } + ) + ActiveRecord.disable_prepared_statements = true + + if app.config.active_record.query_log_tags.present? + ActiveRecord::QueryLogs.tags = app.config.active_record.query_log_tags + end + + if app.config.active_record.query_log_tags_format + ActiveRecord::QueryLogs.tags_formatter = app.config.active_record.query_log_tags_format + end + + if app.config.active_record.cache_query_log_tags + ActiveRecord::QueryLogs.cache_query_log_tags = true + end + end + end + end + + initializer "active_record.unregister_current_scopes_on_unload" do |app| + config.after_initialize do + if app.config.reloading_enabled? + Rails.autoloaders.main.on_unload do |_cpath, value, _abspath| + # Conditions are written this way to be robust against custom + # implementations of value#is_a? or value#<. + if Class === value && ActiveRecord::Base > value + value.current_scope = nil + end + end + end + end + end + + initializer "active_record.message_pack" do + ActiveSupport.on_load(:message_pack) do + ActiveSupport.on_load(:active_record) do + require "active_record/message_pack" + ActiveRecord::MessagePack::Extensions.install(ActiveSupport::MessagePack::CacheSerializer) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/railties/console_sandbox.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/railties/console_sandbox.rb new file mode 100644 index 00000000..70af4392 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/railties/console_sandbox.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +ActiveRecord::ConnectionAdapters::AbstractAdapter.set_callback(:checkout, :after) do + begin_transaction(joinable: false) +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/railties/controller_runtime.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/railties/controller_runtime.rb new file mode 100644 index 00000000..a9ec0e70 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/railties/controller_runtime.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attr_internal" +require "active_record/runtime_registry" + +module ActiveRecord + module Railties # :nodoc: + module ControllerRuntime # :nodoc: + extend ActiveSupport::Concern + + module ClassMethods # :nodoc: + def log_process_action(payload) + messages, db_runtime = super, payload[:db_runtime] + + if db_runtime + queries_count = payload[:queries_count] || 0 + cached_queries_count = payload[:cached_queries_count] || 0 + messages << ("ActiveRecord: %.1fms (%d %s, %d cached)" % [db_runtime.to_f, queries_count, + "query".pluralize(queries_count), cached_queries_count]) + end + + messages + end + end + + def initialize(...) # :nodoc: + super + self.db_runtime = nil + end + + private + attr_internal :db_runtime + + def process_action(action, *args) + # We also need to reset the runtime before each action + # because of queries in middleware or in cases we are streaming + # and it won't be cleaned up by the method below. + ActiveRecord::RuntimeRegistry.reset + super + end + + def cleanup_view_runtime + if logger && logger.info? + db_rt_before_render = ActiveRecord::RuntimeRegistry.reset_runtimes + self.db_runtime = (db_runtime || 0) + db_rt_before_render + runtime = super + queries_rt = ActiveRecord::RuntimeRegistry.sql_runtime - ActiveRecord::RuntimeRegistry.async_sql_runtime + db_rt_after_render = ActiveRecord::RuntimeRegistry.reset_runtimes + self.db_runtime += db_rt_after_render + runtime - queries_rt + else + super + end + end + + def append_info_to_payload(payload) + super + + payload[:db_runtime] = (db_runtime || 0) + ActiveRecord::RuntimeRegistry.reset_runtimes + payload[:queries_count] = ActiveRecord::RuntimeRegistry.reset_queries_count + payload[:cached_queries_count] = ActiveRecord::RuntimeRegistry.reset_cached_queries_count + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/railties/databases.rake b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/railties/databases.rake new file mode 100644 index 00000000..337f9748 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/railties/databases.rake @@ -0,0 +1,626 @@ +# frozen_string_literal: true + +require "active_record" +require "active_support/configuration_file" +require "active_support/deprecation" + +databases = ActiveRecord::Tasks::DatabaseTasks.setup_initial_database_yaml + +db_namespace = namespace :db do + desc "Set the environment value for the database" + task "environment:set" => :load_config do + pool = ActiveRecord::Tasks::DatabaseTasks.migration_connection_pool + raise ActiveRecord::EnvironmentStorageError unless pool.internal_metadata.enabled? + + pool.internal_metadata.create_table_and_set_flags(pool.migration_context.current_environment) + end + + task check_protected_environments: :load_config do + ActiveRecord::Tasks::DatabaseTasks.check_protected_environments! + end + + task load_config: :environment do + if ActiveRecord::Base.configurations.empty? + ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration + end + + ActiveRecord::Migrator.migrations_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths + end + + namespace :create do + task all: :load_config do + ActiveRecord::Tasks::DatabaseTasks.create_all + end + + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Create #{name} database for current environment" + task name => :load_config do + db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name) + ActiveRecord::Tasks::DatabaseTasks.create(db_config) + end + end + end + + desc "Create the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all to create all databases in the config). Without RAILS_ENV or when RAILS_ENV is development, it defaults to creating the development and test databases, except when DATABASE_URL is present." + task create: [:load_config] do + ActiveRecord::Tasks::DatabaseTasks.create_current + end + + namespace :drop do + task all: [:load_config, :check_protected_environments] do + ActiveRecord::Tasks::DatabaseTasks.drop_all + end + + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Drop #{name} database for current environment" + task name => [:load_config, :check_protected_environments] do + db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name) + ActiveRecord::Tasks::DatabaseTasks.drop(db_config) + end + end + end + + desc "Drop the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV or when RAILS_ENV is development, it defaults to dropping the development and test databases, except when DATABASE_URL is present." + task drop: [:load_config, :check_protected_environments] do + db_namespace["drop:_unsafe"].invoke + end + + task "drop:_unsafe" => [:load_config] do + ActiveRecord::Tasks::DatabaseTasks.drop_current + end + + namespace :purge do + task all: [:load_config, :check_protected_environments] do + ActiveRecord::Tasks::DatabaseTasks.purge_all + end + end + + # desc "Truncates tables of each database for current environment" + task truncate_all: [:load_config, :check_protected_environments] do + ActiveRecord::Tasks::DatabaseTasks.truncate_all + end + + # desc "Empty the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:purge:all to purge all databases in the config). Without RAILS_ENV it defaults to purging the development and test databases, except when DATABASE_URL is present." + task purge: [:load_config, :check_protected_environments] do + ActiveRecord::Tasks::DatabaseTasks.purge_current + end + + desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)." + task migrate: :load_config do + ActiveRecord::Tasks::DatabaseTasks.migrate_all + db_namespace["_dump"].invoke + end + + # IMPORTANT: This task won't dump the schema if ActiveRecord.dump_schema_after_migration is set to false + task :_dump do + if ActiveRecord.dump_schema_after_migration + db_namespace["schema:dump"].invoke + end + # Allow this task to be called as many times as required. An example is the + # migrate:redo task, which calls other two internally that depend on this one. + db_namespace["_dump"].reenable + end + + namespace :_dump do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + # IMPORTANT: This task won't dump the schema if ActiveRecord.dump_schema_after_migration is set to false + task name do + if ActiveRecord.dump_schema_after_migration + db_namespace["schema:dump:#{name}"].invoke + end + + # Allow this task to be called as many times as required. An example is the + # migrate:redo task, which calls other two internally that depend on this one. + db_namespace["_dump:#{name}"].reenable + end + end + end + + namespace :migrate do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Migrate #{name} database for current environment" + task name => :load_config do + ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: Rails.env, name: name) do + ActiveRecord::Tasks::DatabaseTasks.migrate + end + + db_namespace["_dump:#{name}"].invoke + end + end + + desc "Roll back the database one migration and re-migrate up (options: STEP=x, VERSION=x)." + task redo: :load_config do + ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:migrate:redo") + + raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty? + + if ENV["VERSION"] + db_namespace["migrate:down"].invoke + db_namespace["migrate:up"].invoke + else + db_namespace["rollback"].invoke + db_namespace["migrate"].invoke + end + end + + namespace :redo do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Roll back #{name} database one migration and re-migrate up (options: STEP=x, VERSION=x)." + task name => :load_config do + raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty? + + if ENV["VERSION"] + db_namespace["migrate:down:#{name}"].invoke + db_namespace["migrate:up:#{name}"].invoke + else + db_namespace["rollback:#{name}"].invoke + db_namespace["migrate:#{name}"].invoke + end + end + end + end + + desc "Resets your database using your migrations for the current environment" + task reset: ["db:drop", "db:create", "db:schema:dump", "db:migrate"] + + desc 'Run the "up" for a given migration VERSION.' + task up: :load_config do + ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:migrate:up") + + raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty? + + ActiveRecord::Tasks::DatabaseTasks.check_target_version + + ActiveRecord::Tasks::DatabaseTasks.migration_connection_pool.migration_context.run( + :up, + ActiveRecord::Tasks::DatabaseTasks.target_version + ) + db_namespace["_dump"].invoke + end + + namespace :up do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Run the \"up\" on #{name} database for a given migration VERSION." + task name => :load_config do + raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty? + + ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: Rails.env, name: name) do |pool| + ActiveRecord::Tasks::DatabaseTasks.check_target_version + pool.migration_context.run(:up, ActiveRecord::Tasks::DatabaseTasks.target_version) + end + + db_namespace["_dump:#{name}"].invoke + end + end + end + + desc 'Run the "down" for a given migration VERSION.' + task down: :load_config do + ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:migrate:down") + + raise "VERSION is required - To go down one migration, use db:rollback" if !ENV["VERSION"] || ENV["VERSION"].empty? + + ActiveRecord::Tasks::DatabaseTasks.check_target_version + + ActiveRecord::Tasks::DatabaseTasks.migration_connection_pool.migration_context.run( + :down, + ActiveRecord::Tasks::DatabaseTasks.target_version + ) + db_namespace["_dump"].invoke + end + + namespace :down do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Run the \"down\" on #{name} database for a given migration VERSION." + task name => :load_config do + raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty? + + ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: Rails.env, name: name) do |pool| + ActiveRecord::Tasks::DatabaseTasks.check_target_version + pool.migration_context.run(:down, ActiveRecord::Tasks::DatabaseTasks.target_version) + end + + db_namespace["_dump:#{name}"].invoke + end + end + end + + desc "Display status of migrations" + task status: :load_config do + ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each do + ActiveRecord::Tasks::DatabaseTasks.migrate_status + end + end + + namespace :status do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Display status of migrations for #{name} database" + task name => :load_config do + ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: Rails.env, name: name) do + ActiveRecord::Tasks::DatabaseTasks.migrate_status + end + end + end + end + end + + namespace :rollback do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Rollback #{name} database for current environment (specify steps w/ STEP=n)." + task name => :load_config do + step = ENV["STEP"] ? ENV["STEP"].to_i : 1 + + ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: Rails.env, name: name) do |pool| + pool.migration_context.rollback(step) + end + + db_namespace["_dump:#{name}"].invoke + end + end + end + + desc "Roll the schema back to the previous version (specify steps w/ STEP=n)." + task rollback: :load_config do + ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:rollback") + raise "VERSION is not supported - To rollback a specific version, use db:migrate:down" if ENV["VERSION"] + + step = ENV["STEP"] ? ENV["STEP"].to_i : 1 + + ActiveRecord::Tasks::DatabaseTasks.migration_connection_pool.migration_context.rollback(step) + + db_namespace["_dump"].invoke + end + + # desc 'Pushes the schema to the next version (specify steps w/ STEP=n).' + task forward: :load_config do + step = ENV["STEP"] ? ENV["STEP"].to_i : 1 + + ActiveRecord::Tasks::DatabaseTasks.migration_connection_pool.migration_context.forward(step) + + db_namespace["_dump"].invoke + end + + namespace :reset do + task all: ["db:drop", "db:setup"] + + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Drop and recreate the #{name} database from its schema for the current environment and load the seeds." + task name => ["db:drop:#{name}", "db:setup:#{name}"] + end + end + + desc "Drop and recreate all databases from their schema for the current environment and load the seeds." + task reset: [ "db:drop", "db:setup" ] + + # desc "Retrieve the charset for the current environment's database" + task charset: :load_config do + puts ActiveRecord::Tasks::DatabaseTasks.charset_current + end + + # desc "Retrieve the collation for the current environment's database" + task collation: :load_config do + puts ActiveRecord::Tasks::DatabaseTasks.collation_current + rescue NoMethodError + $stderr.puts "Sorry, your database adapter is not supported yet. Feel free to submit a patch." + end + + desc "Retrieve the current schema version number" + task version: :load_config do + ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: Rails.env) do |pool| + puts "\ndatabase: #{pool.db_config.database}\n" + puts "Current version: #{pool.migration_context.current_version}" + puts + end + end + + namespace :version do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Retrieve the current schema version number for #{name} database" + task name => :load_config do + db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name) + ActiveRecord::Tasks::DatabaseTasks.with_temporary_connection(db_config) do |connection| + puts "Current version: #{connection.schema_version}" + end + end + end + end + + # desc "Raises an error if there are pending migrations" + task abort_if_pending_migrations: :load_config do + pending_migrations = [] + + ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each do |pool| + pending_migrations << pool.migration_context.open.pending_migrations + end + + pending_migrations = pending_migrations.flatten! + + if pending_migrations.any? + puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}" + + pending_migrations.each do |pending_migration| + puts " %4d %s" % [pending_migration.version, pending_migration.name] + end + + abort %{Run `bin/rails db:migrate` to update your database then try again.} + end + end + + namespace :abort_if_pending_migrations do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + # desc "Raise an error if there are pending migrations for #{name} database" + task name => :load_config do + ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: Rails.env, name: name) do |pool| + pending_migrations = pool.migration_context.open.pending_migrations + + if pending_migrations.any? + puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}" + + pending_migrations.each do |pending_migration| + puts " %4d %s" % [pending_migration.version, pending_migration.name] + end + + abort %{Run `bin/rails db:migrate:#{name}` to update your database then try again.} + end + end + end + end + end + + namespace :setup do + task all: ["db:create", :environment, "db:schema:load", :seed] + + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Create the #{name} database, load the schema, and initialize with the seed data (use db:reset:#{name} to also drop the database first)" + task name => ["db:create:#{name}", :environment, "db:schema:load:#{name}", "db:seed"] + end + end + + desc "Create all databases, load all schemas, and initialize with the seed data (use db:reset to also drop all databases first)" + task setup: ["db:create", :environment, "db:schema:load", :seed] + + desc "Run setup if database does not exist, or run migrations if it does" + task prepare: :load_config do + ActiveRecord::Tasks::DatabaseTasks.prepare_all + end + + desc "Load the seed data from db/seeds.rb" + task seed: :load_config do + db_namespace["abort_if_pending_migrations"].invoke + ActiveRecord::Tasks::DatabaseTasks.load_seed + end + + namespace :seed do + desc "Truncate tables of each database for current environment and load the seeds" + task replant: [:load_config, :truncate_all, :seed] + end + + namespace :fixtures do + desc "Load fixtures into the current environment's database. To load specific fixtures, use FIXTURES=x,y. To load from subdirectory in test/fixtures, use FIXTURES_DIR=z. To specify an alternative path (e.g. spec/fixtures), use FIXTURES_PATH=spec/fixtures." + task load: :load_config do + require "active_record/fixtures" + + base_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path + + fixtures_dir = if ENV["FIXTURES_DIR"] + File.join base_dir, ENV["FIXTURES_DIR"] + else + base_dir + end + + fixture_files = if ENV["FIXTURES"] + ENV["FIXTURES"].split(",") + else + files = Dir[File.join(fixtures_dir, "**/*.{yml}")] + files.reject! { |f| f.start_with?(File.join(fixtures_dir, "files")) } + files.map! { |f| f[fixtures_dir.to_s.size..-5].delete_prefix("/") } + end + + ActiveRecord::FixtureSet.create_fixtures(fixtures_dir, fixture_files) + end + + # desc "Search for a fixture given a LABEL or ID. Specify an alternative path (e.g. spec/fixtures) using FIXTURES_PATH=spec/fixtures." + task identify: :load_config do + require "active_record/fixtures" + + label, id = ENV["LABEL"], ENV["ID"] + raise "LABEL or ID required" if label.blank? && id.blank? + + puts %Q(The fixture ID for "#{label}" is #{ActiveRecord::FixtureSet.identify(label)}.) if label + + base_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path + + Dir["#{base_dir}/**/*.yml"].each do |file| + if data = ActiveSupport::ConfigurationFile.parse(file) + data.each_key do |key| + key_id = ActiveRecord::FixtureSet.identify(key) + + if key == label || key_id == id.to_i + puts "#{file}: #{key} (#{key_id})" + end + end + end + end + end + end + + namespace :schema do + desc "Create a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`)" + task dump: :load_config do + ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each do |pool| + db_config = pool.db_config + schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym + ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, schema_format) + end + + db_namespace["schema:dump"].reenable + end + + desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`) into the database" + task load: [:load_config, :check_protected_environments] do + ActiveRecord::Tasks::DatabaseTasks.load_schema_current(ActiveRecord.schema_format, ENV["SCHEMA"]) + end + + namespace :dump do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Create a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`) for #{name} database" + task name => :load_config do + ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(name: name) do |pool| + db_config = pool.db_config + schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym + ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, schema_format) + end + + db_namespace["schema:dump:#{name}"].reenable + end + end + end + + namespace :load do + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`) into the #{name} database" + task name => "db:test:purge:#{name}" do + ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(name: name) do |pool| + db_config = pool.db_config + schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym + ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, schema_format) + end + end + end + end + + namespace :cache do + desc "Create a db/schema_cache.yml file." + task dump: :load_config do + ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each do |pool| + db_config = pool.db_config + filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(db_config) + + ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(pool, filename) + end + end + + desc "Clear a db/schema_cache.yml file." + task clear: :load_config do + ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config| + filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(db_config) + ActiveRecord::Tasks::DatabaseTasks.clear_schema_cache( + filename, + ) + end + end + end + end + + namespace :encryption do + desc "Generate a set of keys for configuring Active Record encryption in a given environment" + task :init do + puts <<~MSG + Add this entry to the credentials of the target environment:#{' '} + + active_record_encryption: + primary_key: #{SecureRandom.alphanumeric(32)} + deterministic_key: #{SecureRandom.alphanumeric(32)} + key_derivation_salt: #{SecureRandom.alphanumeric(32)} + MSG + end + end + + namespace :test do + # desc "Recreate the test database from an existent schema file (schema.rb or structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`)" + task load_schema: %w(db:test:purge) do + ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: "test") do |pool| + db_config = pool.db_config + ActiveRecord::Schema.verbose = false + schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym + ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, schema_format) + end + end + + # desc "Empty the test database" + task purge: %w(load_config check_protected_environments) do + ActiveRecord::Base.configurations.configs_for(env_name: "test").each do |db_config| + ActiveRecord::Tasks::DatabaseTasks.purge(db_config) + end + end + + # desc 'Load the test schema' + task prepare: :load_config do + unless ActiveRecord::Base.configurations.blank? + db_namespace["test:load_schema"].invoke + end + end + + ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| + # desc "Recreate the #{name} test database from an existent schema.rb file" + namespace :load_schema do + task name => "db:test:purge:#{name}" do + ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: "test", name: name) do |pool| + db_config = pool.db_config + ActiveRecord::Schema.verbose = false + schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym + ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, schema_format) + end + end + end + + # desc "Empty the #{name} test database" + namespace :purge do + task name => %w(load_config check_protected_environments) do + ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: "test", name: name) do |pool| + db_config = pool.db_config + ActiveRecord::Tasks::DatabaseTasks.purge(db_config) + end + end + end + + # desc 'Load the #{name} database test schema' + namespace :prepare do + task name => :load_config do + db_namespace["test:load_schema:#{name}"].invoke + end + end + end + end +end + +namespace :railties do + namespace :install do + # desc "Copy missing migrations from Railties (e.g. engines). You can specify Railties to use with FROM=railtie1,railtie2 and database to copy to with DATABASE=database." + task migrations: :'db:load_config' do + to_load = ENV["FROM"].blank? ? :all : ENV["FROM"].split(",").map(&:strip) + railties = {} + Rails.application.migration_railties.each do |railtie| + next unless to_load == :all || to_load.include?(railtie.railtie_name) + + if railtie.respond_to?(:paths) && (path = railtie.paths["db/migrate"].first) + railties[railtie.railtie_name] = path + end + + unless ENV["MIGRATIONS_PATH"].blank? + railties[railtie.railtie_name] = railtie.root + ENV["MIGRATIONS_PATH"] + end + end + + on_skip = Proc.new do |name, migration| + puts "NOTE: Migration #{migration.basename} from #{name} has been skipped. Migration with the same name already exists." + end + + on_copy = Proc.new do |name, migration| + puts "Copied migration #{migration.basename} from #{name}" + end + + if ENV["DATABASE"].present? && ENV["DATABASE"] != "primary" + config = ActiveRecord::Base.configurations.configs_for(name: ENV["DATABASE"]) + raise "Invalid DATABASE provided" if config.blank? + destination = config.migrations_paths + raise "#{ENV["DATABASE"]} does not have a custom migration path" if destination.blank? + else + destination = ActiveRecord::Tasks::DatabaseTasks.migrations_paths.first + end + + ActiveRecord::Migration.copy(destination, railties, + on_skip: on_skip, on_copy: on_copy) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/railties/job_runtime.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/railties/job_runtime.rb new file mode 100644 index 00000000..c34bc5c9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/railties/job_runtime.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "active_record/runtime_registry" + +module ActiveRecord + module Railties # :nodoc: + module JobRuntime # :nodoc: + private + def instrument(operation, payload = {}, &block) + if operation == :perform && block + super(operation, payload) do + db_runtime_before_perform = ActiveRecord::RuntimeRegistry.sql_runtime + result = block.call + payload[:db_runtime] = ActiveRecord::RuntimeRegistry.sql_runtime - db_runtime_before_perform + result + end + else + super + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/readonly_attributes.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/readonly_attributes.rb new file mode 100644 index 00000000..310d105a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/readonly_attributes.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module ActiveRecord + class ReadonlyAttributeError < ActiveRecordError + end + + module ReadonlyAttributes + extend ActiveSupport::Concern + + included do + class_attribute :_attr_readonly, instance_accessor: false, default: [] + end + + module ClassMethods + # Attributes listed as readonly will be used to create a new record. + # Assigning a new value to a readonly attribute on a persisted record raises an error. + # + # By setting +config.active_record.raise_on_assign_to_attr_readonly+ to +false+, it will + # not raise. The value will change in memory, but will not be persisted on +save+. + # + # ==== Examples + # + # class Post < ActiveRecord::Base + # attr_readonly :title + # end + # + # post = Post.create!(title: "Introducing Ruby on Rails!") + # post.title = "a different title" # raises ActiveRecord::ReadonlyAttributeError + # post.update(title: "a different title") # raises ActiveRecord::ReadonlyAttributeError + def attr_readonly(*attributes) + self._attr_readonly |= attributes.map(&:to_s) + + if ActiveRecord.raise_on_assign_to_attr_readonly + include(HasReadonlyAttributes) + end + end + + # Returns an array of all the attributes that have been specified as readonly. + def readonly_attributes + _attr_readonly + end + + def readonly_attribute?(name) # :nodoc: + _attr_readonly.include?(name) + end + end + + module HasReadonlyAttributes # :nodoc: + def write_attribute(attr_name, value) + if !new_record? && self.class.readonly_attribute?(attr_name.to_s) + raise ReadonlyAttributeError.new(attr_name) + end + + super + end + + def _write_attribute(attr_name, value) + if !new_record? && self.class.readonly_attribute?(attr_name.to_s) + raise ReadonlyAttributeError.new(attr_name) + end + + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/reflection.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/reflection.rb new file mode 100644 index 00000000..51edd9fb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/reflection.rb @@ -0,0 +1,1282 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/filters" + +module ActiveRecord + # = Active Record Reflection + module Reflection # :nodoc: + extend ActiveSupport::Concern + + included do + class_attribute :_reflections, instance_writer: false, default: {} + class_attribute :aggregate_reflections, instance_writer: false, default: {} + class_attribute :automatic_scope_inversing, instance_writer: false, default: false + class_attribute :automatically_invert_plural_associations, instance_writer: false, default: false + end + + class << self + def create(macro, name, scope, options, ar) + reflection = reflection_class_for(macro).new(name, scope, options, ar) + options[:through] ? ThroughReflection.new(reflection) : reflection + end + + def add_reflection(ar, name, reflection) + ar.clear_reflections_cache + name = name.to_sym + ar._reflections = ar._reflections.except(name).merge!(name => reflection) + end + + def add_aggregate_reflection(ar, name, reflection) + ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_sym => reflection) + end + + private + def reflection_class_for(macro) + case macro + when :composed_of + AggregateReflection + when :has_many + HasManyReflection + when :has_one + HasOneReflection + when :belongs_to + BelongsToReflection + else + raise "Unsupported Macro: #{macro}" + end + end + end + + # = Active Record Reflection + # + # \Reflection enables the ability to examine the associations and aggregations of + # Active Record classes and objects. This information, for example, + # can be used in a form builder that takes an Active Record object + # and creates input fields for all of the attributes depending on their type + # and displays the associations to other objects. + # + # MacroReflection class has info for AggregateReflection and AssociationReflection + # classes. + module ClassMethods + # Returns an array of AggregateReflection objects for all the aggregations in the class. + def reflect_on_all_aggregations + aggregate_reflections.values + end + + # Returns the AggregateReflection object for the named +aggregation+ (use the symbol). + # + # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection + # + def reflect_on_aggregation(aggregation) + aggregate_reflections[aggregation.to_sym] + end + + # Returns a Hash of name of the reflection as the key and an AssociationReflection as the value. + # + # Account.reflections # => {"balance" => AggregateReflection} + # + def reflections + normalized_reflections.stringify_keys + end + + def normalized_reflections # :nodoc: + @__reflections ||= begin + ref = {} + + _reflections.each do |name, reflection| + parent_reflection = reflection.parent_reflection + + if parent_reflection + parent_name = parent_reflection.name + ref[parent_name] = parent_reflection + else + ref[name] = reflection + end + end + + ref.freeze + end + end + + # Returns an array of AssociationReflection objects for all the + # associations in the class. If you only want to reflect on a certain + # association type, pass in the symbol (:has_many, :has_one, + # :belongs_to) as the first parameter. + # + # Example: + # + # Account.reflect_on_all_associations # returns an array of all associations + # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations + # + def reflect_on_all_associations(macro = nil) + association_reflections = normalized_reflections.values + association_reflections.select! { |reflection| reflection.macro == macro } if macro + association_reflections + end + + # Returns the AssociationReflection object for the +association+ (use the symbol). + # + # Account.reflect_on_association(:owner) # returns the owner AssociationReflection + # Invoice.reflect_on_association(:line_items).macro # returns :has_many + # + def reflect_on_association(association) + normalized_reflections[association.to_sym] + end + + def _reflect_on_association(association) # :nodoc: + _reflections[association.to_sym] + end + + # Returns an array of AssociationReflection objects for all associations which have :autosave enabled. + def reflect_on_all_autosave_associations + reflections = normalized_reflections.values + reflections.select! { |reflection| reflection.options[:autosave] } + reflections + end + + def clear_reflections_cache # :nodoc: + @__reflections = nil + end + + private + def inherited(subclass) + super + subclass.class_eval do + @__reflections = nil + end + end + end + + # Holds all the methods that are shared between MacroReflection and ThroughReflection. + # + # AbstractReflection + # MacroReflection + # AggregateReflection + # AssociationReflection + # HasManyReflection + # HasOneReflection + # BelongsToReflection + # HasAndBelongsToManyReflection + # ThroughReflection + # PolymorphicReflection + # RuntimeReflection + class AbstractReflection # :nodoc: + def initialize + @class_name = nil + @counter_cache_column = nil + @inverse_of = nil + @inverse_which_updates_counter_cache_defined = false + @inverse_which_updates_counter_cache = nil + end + + def through_reflection? + false + end + + def table_name + klass.table_name + end + + # Returns a new, unsaved instance of the associated class. +attributes+ will + # be passed to the class's constructor. + def build_association(attributes, &block) + klass.new(attributes, &block) + end + + # Returns the class name for the macro. + # + # composed_of :balance, class_name: 'Money' returns 'Money' + # has_many :clients returns 'Client' + def class_name + @class_name ||= -(options[:class_name] || derive_class_name).to_s + end + + # Returns a list of scopes that should be applied for this Reflection + # object when querying the database. + def scopes + scope ? [scope] : [] + end + + def join_scope(table, foreign_table, foreign_klass) + predicate_builder = klass.predicate_builder.with(TableMetadata.new(klass, table)) + scope_chain_items = join_scopes(table, predicate_builder) + klass_scope = klass_join_scope(table, predicate_builder) + + if type + klass_scope.where!(type => foreign_klass.polymorphic_name) + end + + scope_chain_items.inject(klass_scope, &:merge!) + + primary_key_column_names = Array(join_primary_key) + foreign_key_column_names = Array(join_foreign_key) + + primary_foreign_key_pairs = primary_key_column_names.zip(foreign_key_column_names) + + primary_foreign_key_pairs.each do |primary_key_column_name, foreign_key_column_name| + klass_scope.where!(table[primary_key_column_name].eq(foreign_table[foreign_key_column_name])) + end + + if klass.finder_needs_type_condition? + klass_scope.where!(klass.send(:type_condition, table)) + end + + klass_scope + end + + def join_scopes(table, predicate_builder = nil, klass = self.klass, record = nil) # :nodoc: + if scope + [scope_for(build_scope(table, predicate_builder, klass), record)] + else + [] + end + end + + def klass_join_scope(table, predicate_builder = nil) # :nodoc: + relation = build_scope(table, predicate_builder) + klass.scope_for_association(relation) + end + + def constraints + chain.flat_map(&:scopes) + end + + def counter_cache_column + @counter_cache_column ||= begin + counter_cache = options[:counter_cache] + + if belongs_to? + if counter_cache + counter_cache[:column] || -"#{active_record.name.demodulize.underscore.pluralize}_count" + end + else + -((counter_cache && -counter_cache[:column]) || "#{name}_count") + end + end + end + + def inverse_of + return unless inverse_name + + @inverse_of ||= klass._reflect_on_association inverse_name + end + + def check_validity_of_inverse! + if !polymorphic? && has_inverse? + if inverse_of.nil? + raise InverseOfAssociationNotFoundError.new(self) + end + if inverse_of == self + raise InverseOfAssociationRecursiveError.new(self) + end + end + end + + # We need to avoid the following situation: + # + # * An associated record is deleted via record.destroy + # * Hence the callbacks run, and they find a belongs_to on the record with a + # :counter_cache options which points back at our owner. So they update the + # counter cache. + # * In which case, we must make sure to *not* update the counter cache, or else + # it will be decremented twice. + # + # Hence this method. + def inverse_which_updates_counter_cache + unless @inverse_which_updates_counter_cache_defined + if counter_cache_column + inverse_candidates = inverse_of ? [inverse_of] : klass.reflect_on_all_associations(:belongs_to) + @inverse_which_updates_counter_cache = inverse_candidates.find do |inverse| + inverse.counter_cache_column == counter_cache_column && (inverse.polymorphic? || inverse.klass == active_record) + end + end + @inverse_which_updates_counter_cache_defined = true + end + @inverse_which_updates_counter_cache + end + alias inverse_updates_counter_cache? inverse_which_updates_counter_cache + + def inverse_updates_counter_in_memory? + inverse_of && inverse_which_updates_counter_cache == inverse_of + end + + # Returns whether this association has a counter cache. + # + # The counter_cache option must be given on either the owner or inverse + # association, and the column must be present on the owner. + def has_cached_counter? + options[:counter_cache] || + inverse_which_updates_counter_cache && inverse_which_updates_counter_cache.options[:counter_cache] && + active_record.has_attribute?(counter_cache_column) + end + + # Returns whether this association has a counter cache and its column values were backfilled + # (and so it is used internally by methods like +size+/+any?+/etc). + def has_active_cached_counter? + return false unless has_cached_counter? + + counter_cache = options[:counter_cache] || + (inverse_which_updates_counter_cache && inverse_which_updates_counter_cache.options[:counter_cache]) + + counter_cache[:active] != false + end + + def counter_must_be_updated_by_has_many? + !inverse_updates_counter_in_memory? && has_cached_counter? + end + + def alias_candidate(name) + "#{plural_name}_#{name}" + end + + def chain + collect_join_chain + end + + def build_scope(table, predicate_builder = nil, klass = self.klass) + Relation.create(klass, table:, predicate_builder:) + end + + def strict_loading? + options[:strict_loading] + end + + def strict_loading_violation_message(owner) + message = +"`#{owner}` is marked for strict_loading." + message << " The #{polymorphic? ? "polymorphic association" : "#{klass} association"}" + message << " named `:#{name}` cannot be lazily loaded." + end + + protected + def actual_source_reflection # FIXME: this is a horrible name + self + end + + private + def primary_key(klass) + klass.primary_key || raise(UnknownPrimaryKey.new(klass)) + end + + def ensure_option_not_given_as_class!(option_name) + if options[option_name] && options[option_name].class == Class + raise ArgumentError, "A class was passed to `:#{option_name}` but we are expecting a string." + end + end + end + + # Base class for AggregateReflection and AssociationReflection. Objects of + # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods. + class MacroReflection < AbstractReflection + # Returns the name of the macro. + # + # composed_of :balance, class_name: 'Money' returns :balance + # has_many :clients returns :clients + attr_reader :name + + attr_reader :scope + + # Returns the hash of options used for the macro. + # + # composed_of :balance, class_name: 'Money' returns { class_name: "Money" } + # has_many :clients returns {} + attr_reader :options + + attr_reader :active_record + + attr_reader :plural_name # :nodoc: + + def initialize(name, scope, options, active_record) + super() + @name = name + @scope = scope + @options = normalize_options(options) + @active_record = active_record + @klass = options[:anonymous_class] + @plural_name = active_record.pluralize_table_names ? + name.to_s.pluralize : name.to_s + end + + def autosave=(autosave) + @options[:autosave] = autosave + parent_reflection = self.parent_reflection + if parent_reflection + parent_reflection.autosave = autosave + end + end + + # Returns the class for the macro. + # + # composed_of :balance, class_name: 'Money' returns the Money class + # has_many :clients returns the Client class + # + # class Company < ActiveRecord::Base + # has_many :clients + # end + # + # Company.reflect_on_association(:clients).klass + # # => Client + # + # Note: Do not call +klass.new+ or +klass.create+ to instantiate + # a new association object. Use +build_association+ or +create_association+ + # instead. This allows plugins to hook into association object creation. + def klass + @klass ||= _klass(class_name) + end + + def _klass(class_name) # :nodoc: + if active_record.name.demodulize == class_name + return compute_class("::#{class_name}") rescue NameError + end + + compute_class(class_name) + end + + def compute_class(name) + name.constantize + end + + # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute, + # and +other_aggregation+ has an options hash assigned to it. + def ==(other_aggregation) + super || + other_aggregation.kind_of?(self.class) && + name == other_aggregation.name && + !other_aggregation.options.nil? && + active_record == other_aggregation.active_record + end + + def scope_for(relation, owner = nil) + relation.instance_exec(owner, &scope) || relation + end + + private + def derive_class_name + name.to_s.camelize + end + + def normalize_options(options) + counter_cache = options.delete(:counter_cache) + + if counter_cache + active = true + + case counter_cache + when String, Symbol + column = -counter_cache.to_s + when Hash + active = counter_cache.fetch(:active, true) + column = counter_cache[:column]&.to_s + end + + options[:counter_cache] = { active: active, column: column } + end + + options + end + end + + # Holds all the metadata about an aggregation as it was specified in the + # Active Record class. + class AggregateReflection < MacroReflection # :nodoc: + def mapping + mapping = options[:mapping] || [name, name] + mapping.first.is_a?(Array) ? mapping : [mapping] + end + end + + # Holds all the metadata about an association as it was specified in the + # Active Record class. + class AssociationReflection < MacroReflection # :nodoc: + def compute_class(name) + if polymorphic? + raise ArgumentError, "Polymorphic associations do not support computing the class." + end + + begin + klass = active_record.send(:compute_type, name) + rescue NameError => error + if error.name.match?(/(?:\A|::)#{name}\z/) + message = "Missing model class #{name} for the #{active_record}##{self.name} association." + message += " You can specify a different model class with the :class_name option." unless options[:class_name] + raise NameError.new(message, name) + else + raise + end + end + + unless klass < ActiveRecord::Base + raise ArgumentError, "The #{name} model class for the #{active_record}##{self.name} association is not an ActiveRecord::Base subclass." + end + + klass + end + + attr_reader :type, :foreign_type + attr_accessor :parent_reflection # Reflection + + def initialize(name, scope, options, active_record) + super + @type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as] + @foreign_type = -(options[:foreign_type]&.to_s || "#{name}_type") if options[:polymorphic] + @join_table = nil + @foreign_key = nil + @association_foreign_key = nil + @association_primary_key = nil + if options[:query_constraints] + raise ConfigurationError, <<~MSG.squish + Setting `query_constraints:` option on `#{active_record}.#{macro} :#{name}` is not allowed. + To get the same behavior, use the `foreign_key` option instead. + MSG + end + + # If the foreign key is an array, set query constraints options and don't use the foreign key + if options[:foreign_key].is_a?(Array) + options[:query_constraints] = options.delete(:foreign_key) + end + + ensure_option_not_given_as_class!(:class_name) + end + + def association_scope_cache(klass, owner, &block) + key = self + if polymorphic? + key = [key, owner._read_attribute(@foreign_type)] + end + klass.with_connection do |connection| + klass.cached_find_by_statement(connection, key, &block) + end + end + + def join_table + @join_table ||= -(options[:join_table]&.to_s || derive_join_table) + end + + def foreign_key(infer_from_inverse_of: true) + @foreign_key ||= if options[:foreign_key] + if options[:foreign_key].is_a?(Array) + options[:foreign_key].map { |fk| -fk.to_s.freeze }.freeze + else + options[:foreign_key].to_s.freeze + end + elsif options[:query_constraints] + options[:query_constraints].map { |fk| -fk.to_s.freeze }.freeze + else + derived_fk = derive_foreign_key(infer_from_inverse_of: infer_from_inverse_of) + + if active_record.has_query_constraints? + derived_fk = derive_fk_query_constraints(derived_fk) + end + + if derived_fk.is_a?(Array) + derived_fk.map! { |fk| -fk.freeze } + derived_fk.freeze + else + -derived_fk.freeze + end + end + end + + def association_foreign_key + @association_foreign_key ||= -(options[:association_foreign_key]&.to_s || class_name.foreign_key) + end + + def association_primary_key(klass = nil) + primary_key(klass || self.klass) + end + + def active_record_primary_key + custom_primary_key = options[:primary_key] + @active_record_primary_key ||= if custom_primary_key + if custom_primary_key.is_a?(Array) + custom_primary_key.map { |pk| pk.to_s.freeze }.freeze + else + custom_primary_key.to_s.freeze + end + elsif active_record.has_query_constraints? || options[:query_constraints] + active_record.query_constraints_list + elsif active_record.composite_primary_key? + # If active_record has composite primary key of shape [:, :id], infer primary_key as :id + primary_key = primary_key(active_record) + primary_key.include?("id") ? "id" : primary_key.freeze + else + primary_key(active_record).freeze + end + end + + def join_primary_key(klass = nil) + foreign_key + end + + def join_primary_type + type + end + + def join_foreign_key + active_record_primary_key + end + + def check_validity! + check_validity_of_inverse! + + if !polymorphic? && (klass.composite_primary_key? || active_record.composite_primary_key?) + if (has_one? || collection?) && Array(active_record_primary_key).length != Array(foreign_key).length + raise CompositePrimaryKeyMismatchError.new(self) + elsif belongs_to? && Array(association_primary_key).length != Array(foreign_key).length + raise CompositePrimaryKeyMismatchError.new(self) + end + end + end + + def check_eager_loadable! + return unless scope + + unless scope.arity == 0 + raise ArgumentError, <<-MSG.squish + The association scope '#{name}' is instance dependent (the scope + block takes an argument). Eager loading instance dependent scopes + is not supported. + MSG + end + end + + def join_id_for(owner) # :nodoc: + Array(join_foreign_key).map { |key| owner._read_attribute(key) } + end + + def through_reflection + nil + end + + def source_reflection + self + end + + # A chain of reflections from this one back to the owner. For more see the explanation in + # ThroughReflection. + def collect_join_chain + [self] + end + + # This is for clearing cache on the reflection. Useful for tests that need to compare + # SQL queries on associations. + def clear_association_scope_cache # :nodoc: + klass.initialize_find_by_cache + end + + def nested? + false + end + + def has_scope? + scope + end + + def has_inverse? + inverse_name + end + + def polymorphic_inverse_of(associated_class) + if has_inverse? + if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of]) + inverse_relationship + else + raise InverseOfAssociationNotFoundError.new(self, associated_class) + end + end + end + + # Returns the macro type. + # + # has_many :clients returns :has_many + def macro; raise NotImplementedError; end + + # Returns whether or not this association reflection is for a collection + # association. Returns +true+ if the +macro+ is either +has_many+ or + # +has_and_belongs_to_many+, +false+ otherwise. + def collection? + false + end + + # Returns whether or not the association should be validated as part of + # the parent's validation. + # + # Unless you explicitly disable validation with + # validate: false, validation will take place when: + # + # * you explicitly enable validation; validate: true + # * you use autosave; autosave: true + # * the association is a +has_many+ association + def validate? + !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || collection?) + end + + # Returns +true+ if +self+ is a +belongs_to+ reflection. + def belongs_to?; false; end + + # Returns +true+ if +self+ is a +has_one+ reflection. + def has_one?; false; end + + def association_class; raise NotImplementedError; end + + def polymorphic? + options[:polymorphic] + end + + def polymorphic_name + active_record.polymorphic_name + end + + def add_as_source(seed) + seed + end + + def add_as_polymorphic_through(reflection, seed) + seed + [PolymorphicReflection.new(self, reflection)] + end + + def add_as_through(seed) + seed + [self] + end + + def extensions + Array(options[:extend]) + end + + private + # Attempts to find the inverse association name automatically. + # If it cannot find a suitable inverse association name, it returns + # +nil+. + def inverse_name + unless defined?(@inverse_name) + @inverse_name = options.fetch(:inverse_of) { automatic_inverse_of } + end + + @inverse_name + end + + # returns either +nil+ or the inverse association name that it finds. + def automatic_inverse_of + if can_find_inverse_of_automatically?(self) + inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym + + begin + reflection = klass._reflect_on_association(inverse_name) + if !reflection && active_record.automatically_invert_plural_associations + plural_inverse_name = ActiveSupport::Inflector.pluralize(inverse_name) + reflection = klass._reflect_on_association(plural_inverse_name) + end + rescue NameError => error + raise unless error.name.to_s == class_name + + # Give up: we couldn't compute the klass type so we won't be able + # to find any associations either. + reflection = false + end + + if valid_inverse_reflection?(reflection) + reflection.name + end + end + end + + # Checks if the inverse reflection that is returned from the + # +automatic_inverse_of+ method is a valid reflection. We must + # make sure that the reflection's active_record name matches up + # with the current reflection's klass name. + def valid_inverse_reflection?(reflection) + reflection && + reflection != self && + foreign_key == reflection.foreign_key && + klass <= reflection.active_record && + can_find_inverse_of_automatically?(reflection, true) + end + + # Checks to see if the reflection doesn't have any options that prevent + # us from being able to guess the inverse automatically. First, the + # inverse_of option cannot be set to false. Second, we must + # have has_many, has_one, belongs_to associations. + # Third, we must not have options such as :foreign_key + # which prevent us from correctly guessing the inverse association. + def can_find_inverse_of_automatically?(reflection, inverse_reflection = false) + reflection.options[:inverse_of] != false && + !reflection.options[:through] && + !reflection.options[:foreign_key] && + scope_allows_automatic_inverse_of?(reflection, inverse_reflection) + end + + # Scopes on the potential inverse reflection prevent automatic + # inverse_of, since the scope could exclude the owner record + # we would inverse from. Scopes on the reflection itself allow for + # automatic inverse_of as long as + # config.active_record.automatic_scope_inversing is set to + # +true+ (the default for new applications). + def scope_allows_automatic_inverse_of?(reflection, inverse_reflection) + if inverse_reflection + !reflection.scope + else + !reflection.scope || reflection.klass.automatic_scope_inversing + end + end + + def derive_class_name + class_name = name.to_s + class_name = class_name.singularize if collection? + class_name.camelize + end + + def derive_foreign_key(infer_from_inverse_of: true) + if belongs_to? + "#{name}_id" + elsif options[:as] + "#{options[:as]}_id" + elsif options[:inverse_of] && infer_from_inverse_of + inverse_of.foreign_key(infer_from_inverse_of: false) + else + active_record.model_name.to_s.foreign_key + end + end + + def derive_fk_query_constraints(foreign_key) + primary_query_constraints = active_record.query_constraints_list + owner_pk = active_record.primary_key + + if primary_query_constraints.size > 2 + raise ArgumentError, <<~MSG.squish + The query constraints list on the `#{active_record}` model has more than 2 + attributes. Active Record is unable to derive the query constraints + for the association. You need to explicitly define the query constraints + for this association. + MSG + end + + if !primary_query_constraints.include?(owner_pk) + raise ArgumentError, <<~MSG.squish + The query constraints on the `#{active_record}` model does not include the primary + key so Active Record is unable to derive the foreign key constraints for + the association. You need to explicitly define the query constraints for this + association. + MSG + end + + return foreign_key if primary_query_constraints.include?(foreign_key) + + first_key, last_key = primary_query_constraints + + if first_key == owner_pk + [foreign_key, last_key.to_s] + elsif last_key == owner_pk + [first_key.to_s, foreign_key] + else + raise ArgumentError, <<~MSG.squish + Active Record couldn't correctly interpret the query constraints + for the `#{active_record}` model. The query constraints on `#{active_record}` are + `#{primary_query_constraints}` and the foreign key is `#{foreign_key}`. + You need to explicitly set the query constraints for this association. + MSG + end + end + + def derive_join_table + ModelSchema.derive_join_table_name active_record.table_name, klass.table_name + end + end + + class HasManyReflection < AssociationReflection # :nodoc: + def macro; :has_many; end + + def collection?; true; end + + def association_class + if options[:through] + Associations::HasManyThroughAssociation + else + Associations::HasManyAssociation + end + end + end + + class HasOneReflection < AssociationReflection # :nodoc: + def macro; :has_one; end + + def has_one?; true; end + + def association_class + if options[:through] + Associations::HasOneThroughAssociation + else + Associations::HasOneAssociation + end + end + end + + class BelongsToReflection < AssociationReflection # :nodoc: + def macro; :belongs_to; end + + def belongs_to?; true; end + + def association_class + if polymorphic? + Associations::BelongsToPolymorphicAssociation + else + Associations::BelongsToAssociation + end + end + + # klass option is necessary to support loading polymorphic associations + def association_primary_key(klass = nil) + if primary_key = options[:primary_key] + @association_primary_key ||= if primary_key.is_a?(Array) + primary_key.map { |pk| pk.to_s.freeze }.freeze + else + -primary_key.to_s + end + elsif (klass || self.klass).has_query_constraints? || options[:query_constraints] + (klass || self.klass).composite_query_constraints_list + elsif (klass || self.klass).composite_primary_key? + # If klass has composite primary key of shape [:, :id], infer primary_key as :id + primary_key = (klass || self.klass).primary_key + primary_key.include?("id") ? "id" : primary_key + else + primary_key(klass || self.klass) + end + end + + def join_primary_key(klass = nil) + polymorphic? ? association_primary_key(klass) : association_primary_key + end + + def join_foreign_key + foreign_key + end + + def join_foreign_type + foreign_type + end + + private + def can_find_inverse_of_automatically?(*) + !polymorphic? && super + end + end + + class HasAndBelongsToManyReflection < AssociationReflection # :nodoc: + def macro; :has_and_belongs_to_many; end + + def collection? + true + end + end + + # Holds all the metadata about a :through association as it was specified + # in the Active Record class. + class ThroughReflection < AbstractReflection # :nodoc: + delegate :foreign_key, :foreign_type, :association_foreign_key, :join_id_for, :type, + :active_record_primary_key, :join_foreign_key, to: :source_reflection + + def initialize(delegate_reflection) + super() + @delegate_reflection = delegate_reflection + @klass = delegate_reflection.options[:anonymous_class] + @source_reflection_name = delegate_reflection.options[:source] + + ensure_option_not_given_as_class!(:source_type) + end + + def through_reflection? + true + end + + def klass + @klass ||= delegate_reflection._klass(class_name) + end + + # Returns the source of the through reflection. It checks both a singularized + # and pluralized form for :belongs_to or :has_many. + # + # class Post < ActiveRecord::Base + # has_many :taggings + # has_many :tags, through: :taggings + # end + # + # class Tagging < ActiveRecord::Base + # belongs_to :post + # belongs_to :tag + # end + # + # tags_reflection = Post.reflect_on_association(:tags) + # tags_reflection.source_reflection + # # => + # + def source_reflection + return unless source_reflection_name + + through_reflection.klass._reflect_on_association(source_reflection_name) + end + + # Returns the AssociationReflection object specified in the :through option + # of a HasManyThrough or HasOneThrough association. + # + # class Post < ActiveRecord::Base + # has_many :taggings + # has_many :tags, through: :taggings + # end + # + # tags_reflection = Post.reflect_on_association(:tags) + # tags_reflection.through_reflection + # # => + # + def through_reflection + active_record._reflect_on_association(options[:through]) + end + + # Returns an array of reflections which are involved in this association. Each item in the + # array corresponds to a table which will be part of the query for this association. + # + # The chain is built by recursively calling #chain on the source reflection and the through + # reflection. The base case for the recursion is a normal association, which just returns + # [self] as its #chain. + # + # class Post < ActiveRecord::Base + # has_many :taggings + # has_many :tags, through: :taggings + # end + # + # tags_reflection = Post.reflect_on_association(:tags) + # tags_reflection.chain + # # => [, + # ] + # + def collect_join_chain + collect_join_reflections [self] + end + + # This is for clearing cache on the reflection. Useful for tests that need to compare + # SQL queries on associations. + def clear_association_scope_cache # :nodoc: + delegate_reflection.clear_association_scope_cache + source_reflection.clear_association_scope_cache + through_reflection.clear_association_scope_cache + end + + def scopes + source_reflection.scopes + super + end + + def join_scopes(table, predicate_builder = nil, klass = self.klass, record = nil) # :nodoc: + source_reflection.join_scopes(table, predicate_builder, klass, record) + super + end + + def has_scope? + scope || options[:source_type] || + source_reflection.has_scope? || + through_reflection.has_scope? + end + + # A through association is nested if there would be more than one join table + def nested? + source_reflection.through_reflection? || through_reflection.through_reflection? + end + + # We want to use the klass from this reflection, rather than just delegate straight to + # the source_reflection, because the source_reflection may be polymorphic. We still + # need to respect the source_reflection's :primary_key option, though. + def association_primary_key(klass = nil) + # Get the "actual" source reflection if the immediate source reflection has a + # source reflection itself + if primary_key = actual_source_reflection.options[:primary_key] + @association_primary_key ||= -primary_key.to_s + else + primary_key(klass || self.klass) + end + end + + def join_primary_key(klass = self.klass) + source_reflection.join_primary_key(klass) + end + + # Gets an array of possible :through source reflection names in both singular and plural form. + # + # class Post < ActiveRecord::Base + # has_many :taggings + # has_many :tags, through: :taggings + # end + # + # tags_reflection = Post.reflect_on_association(:tags) + # tags_reflection.source_reflection_names + # # => [:tag, :tags] + # + def source_reflection_names + options[:source] ? [options[:source]] : [name.to_s.singularize, name].uniq + end + + def source_reflection_name # :nodoc: + @source_reflection_name ||= begin + names = [name.to_s.singularize, name].collect(&:to_sym).uniq + names = names.find_all { |n| + through_reflection.klass._reflect_on_association(n) + } + + if names.length > 1 + raise AmbiguousSourceReflectionForThroughAssociation.new( + active_record.name, + macro, + name, + options, + source_reflection_names + ) + end + names.first + end + end + + def source_options + source_reflection.options + end + + def through_options + through_reflection.options + end + + def check_validity! + if through_reflection.nil? + raise HasManyThroughAssociationNotFoundError.new(active_record, self) + end + + if through_reflection.polymorphic? + if has_one? + raise HasOneAssociationPolymorphicThroughError.new(active_record.name, self) + else + raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self) + end + end + + if source_reflection.nil? + raise HasManyThroughSourceAssociationNotFoundError.new(self) + end + + if options[:source_type] && !source_reflection.polymorphic? + raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection) + end + + if source_reflection.polymorphic? && options[:source_type].nil? + raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection) + end + + if has_one? && through_reflection.collection? + raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection) + end + + if parent_reflection.nil? + reflections = active_record.normalized_reflections.keys + + if reflections.index(through_reflection.name) > reflections.index(name) + raise HasManyThroughOrderError.new(active_record.name, self, through_reflection) + end + end + + check_validity_of_inverse! + end + + def constraints + scope_chain = source_reflection.constraints + scope_chain << scope if scope + scope_chain + end + + def add_as_source(seed) + collect_join_reflections seed + end + + def add_as_polymorphic_through(reflection, seed) + collect_join_reflections(seed + [PolymorphicReflection.new(self, reflection)]) + end + + def add_as_through(seed) + collect_join_reflections(seed + [self]) + end + + protected + def actual_source_reflection # FIXME: this is a horrible name + source_reflection.actual_source_reflection + end + + private + attr_reader :delegate_reflection + + def collect_join_reflections(seed) + a = source_reflection.add_as_source seed + if options[:source_type] + through_reflection.add_as_polymorphic_through self, a + else + through_reflection.add_as_through a + end + end + + def inverse_name; delegate_reflection.send(:inverse_name); end + + def derive_class_name + # get the class_name of the belongs_to association of the through reflection + options[:source_type] || source_reflection.class_name + end + + delegate_methods = AssociationReflection.public_instance_methods - + public_instance_methods + + delegate(*delegate_methods, to: :delegate_reflection) + end + + class PolymorphicReflection < AbstractReflection # :nodoc: + delegate :klass, :scope, :plural_name, :type, :join_primary_key, :join_foreign_key, + :name, :scope_for, to: :@reflection + + def initialize(reflection, previous_reflection) + super() + @reflection = reflection + @previous_reflection = previous_reflection + end + + def join_scopes(table, predicate_builder = nil, klass = self.klass, record = nil) # :nodoc: + scopes = super + unless @previous_reflection.through_reflection? + scopes += @previous_reflection.join_scopes(table, predicate_builder, klass, record) + end + scopes << build_scope(table, predicate_builder, klass).instance_exec(record, &source_type_scope) + end + + def constraints + @reflection.constraints + [source_type_scope] + end + + private + def source_type_scope + type = @previous_reflection.foreign_type + source_type = @previous_reflection.options[:source_type] + lambda { |object| where(type => source_type) } + end + end + + class RuntimeReflection < AbstractReflection # :nodoc: + delegate :scope, :type, :constraints, :join_foreign_key, to: :@reflection + + def initialize(reflection, association) + super() + @reflection = reflection + @association = association + end + + def klass + @association.klass + end + + def aliased_table + klass.arel_table + end + + def join_primary_key(klass = self.klass) + @reflection.join_primary_key(klass) + end + + def all_includes; yield; end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation.rb new file mode 100644 index 00000000..229f4b41 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation.rb @@ -0,0 +1,1502 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record \Relation + class Relation + class ExplainProxy # :nodoc: + def initialize(relation, options) + @relation = relation + @options = options + end + + def inspect + exec_explain { @relation.send(:exec_queries) } + end + + def average(column_name) + exec_explain { @relation.average(column_name) } + end + + def count(column_name = nil) + exec_explain { @relation.count(column_name) } + end + + def first(limit = nil) + exec_explain { @relation.first(limit) } + end + + def last(limit = nil) + exec_explain { @relation.last(limit) } + end + + def maximum(column_name) + exec_explain { @relation.maximum(column_name) } + end + + def minimum(column_name) + exec_explain { @relation.minimum(column_name) } + end + + def pluck(*column_names) + exec_explain { @relation.pluck(*column_names) } + end + + def sum(identity_or_column = nil) + exec_explain { @relation.sum(identity_or_column) } + end + + private + def exec_explain(&block) + @relation.exec_explain(@relation.collecting_queries_for_explain { block.call }, @options) + end + end + + MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group, + :order, :joins, :left_outer_joins, :references, + :extending, :unscope, :optimizer_hints, :annotate, + :with] + + SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering, :strict_loading, + :reverse_order, :distinct, :create_with, :skip_query_cache] + + CLAUSE_METHODS = [:where, :having, :from] + INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :with, :with_recursive] + + VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS + + include Enumerable + include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation + include SignedId::RelationMethods, TokenFor::RelationMethods + + attr_reader :table, :model, :loaded, :predicate_builder + attr_accessor :skip_preloading_value + alias :klass :model + alias :loaded? :loaded + alias :locked? :lock_value + + def initialize(model, table: nil, predicate_builder: nil, values: {}) + if table + predicate_builder ||= model.predicate_builder.with(TableMetadata.new(model, table)) + else + table = model.arel_table + predicate_builder ||= model.predicate_builder + end + + @model = model + @table = table + @values = values + @loaded = false + @predicate_builder = predicate_builder + @delegate_to_model = false + @future_result = nil + @records = nil + @async = false + @none = false + end + + def initialize_copy(other) + @values = @values.dup + reset + end + + def bind_attribute(name, value) # :nodoc: + if reflection = model._reflect_on_association(name) + name = reflection.foreign_key + value = value.read_attribute(reflection.association_primary_key) unless value.nil? + end + + attr = table[name] + bind = predicate_builder.build_bind_attribute(attr.name, value) + yield attr, bind + end + + # Initializes new record from relation while maintaining the current + # scope. + # + # Expects arguments in the same format as {ActiveRecord::Base.new}[rdoc-ref:Core.new]. + # + # users = User.where(name: 'DHH') + # user = users.new # => # + # + # You can also pass a block to new with the new record as argument: + # + # user = users.new { |user| user.name = 'Oscar' } + # user.name # => Oscar + def new(attributes = nil, &block) + if attributes.is_a?(Array) + attributes.collect { |attr| new(attr, &block) } + else + block = current_scope_restoring_block(&block) + scoping { _new(attributes, &block) } + end + end + alias build new + + # Tries to create a new record with the same scoped attributes + # defined in the relation. Returns the initialized object if validation fails. + # + # Expects arguments in the same format as + # {ActiveRecord::Base.create}[rdoc-ref:Persistence::ClassMethods#create]. + # + # ==== Examples + # + # users = User.where(name: 'Oscar') + # users.create # => # + # + # users.create(name: 'fxn') + # users.create # => # + # + # users.create { |user| user.name = 'tenderlove' } + # # => # + # + # users.create(name: nil) # validation on name + # # => # + def create(attributes = nil, &block) + if attributes.is_a?(Array) + attributes.collect { |attr| create(attr, &block) } + else + block = current_scope_restoring_block(&block) + scoping { _create(attributes, &block) } + end + end + + # Similar to #create, but calls + # {create!}[rdoc-ref:Persistence::ClassMethods#create!] + # on the base class. Raises an exception if a validation error occurs. + # + # Expects arguments in the same format as + # {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]. + def create!(attributes = nil, &block) + if attributes.is_a?(Array) + attributes.collect { |attr| create!(attr, &block) } + else + block = current_scope_restoring_block(&block) + scoping { _create!(attributes, &block) } + end + end + + def first_or_create(attributes = nil, &block) # :nodoc: + first || create(attributes, &block) + end + + def first_or_create!(attributes = nil, &block) # :nodoc: + first || create!(attributes, &block) + end + + def first_or_initialize(attributes = nil, &block) # :nodoc: + first || new(attributes, &block) + end + + # Finds the first record with the given attributes, or creates a record + # with the attributes if one is not found: + # + # # Find the first user named "Penélope" or create a new one. + # User.find_or_create_by(first_name: 'Penélope') + # # => # + # + # # Find the first user named "Penélope" or create a new one. + # # We already have one so the existing record will be returned. + # User.find_or_create_by(first_name: 'Penélope') + # # => # + # + # # Find the first user named "Scarlett" or create a new one with + # # a particular last name. + # User.create_with(last_name: 'Johansson').find_or_create_by(first_name: 'Scarlett') + # # => # + # + # This method accepts a block, which is passed down to #create. The last example + # above can be alternatively written this way: + # + # # Find the first user named "Scarlett" or create a new one with a + # # particular last name. + # User.find_or_create_by(first_name: 'Scarlett') do |user| + # user.last_name = 'Johansson' + # end + # # => # + # + # This method always returns a record, but if creation was attempted and + # failed due to validation errors it won't be persisted, you get what + # #create returns in such situation. + # + # If creation failed because of a unique constraint, this method will + # assume it encountered a race condition and will try finding the record + # once more. If somehow the second find still does not find a record + # because a concurrent DELETE happened, it will then raise an + # ActiveRecord::RecordNotFound exception. + # + # Please note this method is not atomic, it runs first a SELECT, + # and if there are no results an INSERT is attempted. So if the table + # doesn't have a relevant unique constraint it could be the case that + # you end up with two or more similar records. + def find_or_create_by(attributes, &block) + find_by(attributes) || create_or_find_by(attributes, &block) + end + + # Like #find_or_create_by, but calls + # {create!}[rdoc-ref:Persistence::ClassMethods#create!] so an exception + # is raised if the created record is invalid. + def find_or_create_by!(attributes, &block) + find_by(attributes) || create_or_find_by!(attributes, &block) + end + + # Attempts to create a record with the given attributes in a table that has a unique database constraint + # on one or several of its columns. If a row already exists with one or several of these + # unique constraints, the exception such an insertion would normally raise is caught, + # and the existing record with those attributes is found using #find_by!. + # + # This is similar to #find_or_create_by, but tries to create the record first. As such it is + # better suited for cases where the record is most likely not to exist yet. + # + # There are several drawbacks to #create_or_find_by, though: + # + # * The underlying table must have the relevant columns defined with unique database constraints. + # * A unique constraint violation may be triggered by only one, or at least less than all, + # of the given attributes. This means that the subsequent #find_by! may fail to find a + # matching record, which will then raise an ActiveRecord::RecordNotFound exception, + # rather than a record with the given attributes. + # * While we avoid the race condition between SELECT -> INSERT from #find_or_create_by, + # we actually have another race condition between INSERT -> SELECT, which can be triggered + # if a DELETE between those two statements is run by another client. But for most applications, + # that's a significantly less likely condition to hit. + # * It relies on exception handling to handle control flow, which may be marginally slower. + # * The primary key may auto-increment on each create, even if it fails. This can accelerate + # the problem of running out of integers, if the underlying table is still stuck on a primary + # key of type int (note: All \Rails apps since 5.1+ have defaulted to bigint, which is not liable + # to this problem). + # * Columns with unique database constraints should not have uniqueness validations defined, + # otherwise #create will fail due to validation errors and #find_by will never be called. + # + # This method will return a record if all given attributes are covered by unique constraints + # (unless the INSERT -> DELETE -> SELECT race condition is triggered), but if creation was attempted + # and failed due to validation errors it won't be persisted, you get what #create returns in + # such situation. + def create_or_find_by(attributes, &block) + with_connection do |connection| + transaction(requires_new: true) { create(attributes, &block) } + rescue ActiveRecord::RecordNotUnique + if connection.transaction_open? + where(attributes).lock.find_by!(attributes) + else + find_by!(attributes) + end + end + end + + # Like #create_or_find_by, but calls + # {create!}[rdoc-ref:Persistence::ClassMethods#create!] so an exception + # is raised if the created record is invalid. + def create_or_find_by!(attributes, &block) + with_connection do |connection| + transaction(requires_new: true) { create!(attributes, &block) } + rescue ActiveRecord::RecordNotUnique + if connection.transaction_open? + where(attributes).lock.find_by!(attributes) + else + find_by!(attributes) + end + end + end + + # Like #find_or_create_by, but calls {new}[rdoc-ref:Core#new] + # instead of {create}[rdoc-ref:Persistence::ClassMethods#create]. + def find_or_initialize_by(attributes, &block) + find_by(attributes) || new(attributes, &block) + end + + # Runs EXPLAIN on the query or queries triggered by this relation and + # returns the result as a string. The string is formatted imitating the + # ones printed by the database shell. + # + # User.all.explain + # # EXPLAIN SELECT `users`.* FROM `users` + # # ... + # + # Note that this method actually runs the queries, since the results of some + # are needed by the next ones when eager loading is going on. + # + # To run EXPLAIN on queries created by +first+, +pluck+ and +count+, call + # these methods on +explain+: + # + # User.all.explain.count + # # EXPLAIN SELECT COUNT(*) FROM `users` + # # ... + # + # The column name can be passed if required: + # + # User.all.explain.maximum(:id) + # # EXPLAIN SELECT MAX(`users`.`id`) FROM `users` + # # ... + # + # Please see further details in the + # {Active Record Query Interface guide}[https://guides.rubyonrails.org/active_record_querying.html#running-explain]. + def explain(*options) + ExplainProxy.new(self, options) + end + + # Converts relation objects to Array. + def to_ary + records.dup + end + alias to_a to_ary + + def records # :nodoc: + load + @records + end + + # Serializes the relation objects Array. + def encode_with(coder) + coder.represent_seq(nil, records) + end + + # Returns size of the records. + def size + if loaded? + records.length + else + count(:all) + end + end + + # Returns true if there are no records. + def empty? + return true if @none + + if loaded? + records.empty? + else + !exists? + end + end + + # Returns true if there are no records. + # + # When a pattern argument is given, this method checks whether elements in + # the Enumerable match the pattern via the case-equality operator (===). + # + # posts.none?(Comment) # => true or false + def none?(*args) + return true if @none + + return super if args.present? || block_given? + empty? + end + + # Returns true if there are any records. + # + # When a pattern argument is given, this method checks whether elements in + # the Enumerable match the pattern via the case-equality operator (===). + # + # posts.any?(Post) # => true or false + def any?(*args) + return false if @none + + return super if args.present? || block_given? + !empty? + end + + # Returns true if there is exactly one record. + # + # When a pattern argument is given, this method checks whether elements in + # the Enumerable match the pattern via the case-equality operator (===). + # + # posts.one?(Post) # => true or false + def one?(*args) + return false if @none + + return super if args.present? || block_given? + return records.one? if loaded? + limited_count == 1 + end + + # Returns true if there is more than one record. + def many? + return false if @none + + return super if block_given? + return records.many? if loaded? + limited_count > 1 + end + + # Returns a stable cache key that can be used to identify this query. + # The cache key is built with a fingerprint of the SQL query. + # + # Product.where("name like ?", "%Cosmic Encounter%").cache_key + # # => "products/query-1850ab3d302391b85b8693e941286659" + # + # If ActiveRecord::Base.collection_cache_versioning is turned off, as it was + # in \Rails 6.0 and earlier, the cache key will also include a version. + # + # ActiveRecord::Base.collection_cache_versioning = false + # Product.where("name like ?", "%Cosmic Encounter%").cache_key + # # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000" + # + # You can also pass a custom timestamp column to fetch the timestamp of the + # last updated record. + # + # Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at) + def cache_key(timestamp_column = "updated_at") + @cache_keys ||= {} + @cache_keys[timestamp_column] ||= model.collection_cache_key(self, timestamp_column) + end + + def compute_cache_key(timestamp_column = :updated_at) # :nodoc: + query_signature = ActiveSupport::Digest.hexdigest(to_sql) + key = "#{model.model_name.cache_key}/query-#{query_signature}" + + if model.collection_cache_versioning + key + else + "#{key}-#{compute_cache_version(timestamp_column)}" + end + end + private :compute_cache_key + + # Returns a cache version that can be used together with the cache key to form + # a recyclable caching scheme. The cache version is built with the number of records + # matching the query, and the timestamp of the last updated record. When a new record + # comes to match the query, or any of the existing records is updated or deleted, + # the cache version changes. + # + # If the collection is loaded, the method will iterate through the records + # to generate the timestamp, otherwise it will trigger one SQL query like: + # + # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%') + def cache_version(timestamp_column = :updated_at) + if model.collection_cache_versioning + @cache_versions ||= {} + @cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column) + end + end + + def compute_cache_version(timestamp_column) # :nodoc: + timestamp_column = timestamp_column.to_s + + if loaded? + size = records.size + if size > 0 + timestamp = records.map { |record| record.read_attribute(timestamp_column) }.max + end + else + collection = eager_loading? ? apply_join_dependency : self + + with_connection do |c| + column = c.visitor.compile(table[timestamp_column]) + select_values = "COUNT(*) AS #{model.adapter_class.quote_column_name("size")}, MAX(%s) AS timestamp" + + if collection.has_limit_or_offset? + query = collection.select("#{column} AS collection_cache_key_timestamp") + query._select!(table[Arel.star]) if distinct_value && collection.select_values.empty? + subquery_alias = "subquery_for_cache_key" + subquery_column = "#{subquery_alias}.collection_cache_key_timestamp" + arel = query.build_subquery(subquery_alias, select_values % subquery_column) + else + query = collection.unscope(:order) + query.select_values = [select_values % column] + arel = query.arel + end + + size, timestamp = c.select_rows(arel, nil).first + + if size + column_type = model.type_for_attribute(timestamp_column) + timestamp = column_type.deserialize(timestamp) + else + size = 0 + end + end + end + + if timestamp + "#{size}-#{timestamp.utc.to_fs(model.cache_timestamp_format)}" + else + "#{size}" + end + end + private :compute_cache_version + + # Returns a cache key along with the version. + def cache_key_with_version + if version = cache_version + "#{cache_key}-#{version}" + else + cache_key + end + end + + # Scope all queries to the current scope. + # + # Comment.where(post_id: 1).scoping do + # Comment.first + # end + # # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1 + # + # If all_queries: true is passed, scoping will apply to all queries + # for the relation including +update+ and +delete+ on instances. + # Once +all_queries+ is set to true it cannot be set to false in a + # nested block. + # + # Please check unscoped if you want to remove all previous scopes (including + # the default_scope) during the execution of a block. + def scoping(all_queries: nil, &block) + registry = model.scope_registry + if global_scope?(registry) && all_queries == false + raise ArgumentError, "Scoping is set to apply to all queries and cannot be unset in a nested block." + elsif already_in_scope?(registry) + yield + else + _scoping(self, registry, all_queries, &block) + end + end + + def _exec_scope(...) # :nodoc: + @delegate_to_model = true + registry = model.scope_registry + _scoping(nil, registry) { instance_exec(...) || self } + ensure + @delegate_to_model = false + end + + # Updates all records in the current relation with details given. This method constructs a single SQL UPDATE + # statement and sends it straight to the database. It does not instantiate the involved models and it does not + # trigger Active Record callbacks or validations. However, values passed to #update_all will still go through + # Active Record's normal type casting and serialization. Returns the number of rows affected. + # + # Note: As Active Record callbacks are not triggered, this method will not automatically update +updated_at+/+updated_on+ columns. + # + # ==== Parameters + # + # * +updates+ - A string, array, or hash representing the SET part of an SQL statement. Any strings provided will + # be type cast, unless you use +Arel.sql+. (Don't pass user-provided values to +Arel.sql+.) + # + # ==== Examples + # + # # Update all customers with the given attributes + # Customer.update_all wants_email: true + # + # # Update all books with 'Rails' in their title + # Book.where('title LIKE ?', '%Rails%').update_all(author: 'David') + # + # # Update all books that match conditions, but limit it to 5 ordered by date + # Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David') + # + # # Update all invoices and set the number column to its id value. + # Invoice.update_all('number = id') + # + # # Update all books with 'Rails' in their title + # Book.where('title LIKE ?', '%Rails%').update_all(title: Arel.sql("title + ' - volume 1'")) + def update_all(updates) + raise ArgumentError, "Empty list of attributes to change" if updates.blank? + + return 0 if @none + + if updates.is_a?(Hash) + if model.locking_enabled? && + !updates.key?(model.locking_column) && + !updates.key?(model.locking_column.to_sym) + attr = table[model.locking_column] + updates[attr.name] = _increment_attribute(attr) + end + values = _substitute_values(updates) + else + values = Arel.sql(model.sanitize_sql_for_assignment(updates, table.name)) + end + + model.with_connection do |c| + arel = eager_loading? ? apply_join_dependency.arel : build_arel(c) + arel.source.left = table + + group_values_arel_columns = arel_columns(group_values.uniq) + having_clause_ast = having_clause.ast unless having_clause.empty? + key = if model.composite_primary_key? + primary_key.map { |pk| table[pk] } + else + table[primary_key] + end + stmt = arel.compile_update(values, key, having_clause_ast, group_values_arel_columns) + c.update(stmt, "#{model} Update All").tap { reset } + end + end + + def update(id = :all, attributes) # :nodoc: + if id == :all + each { |record| record.update(attributes) } + else + model.update(id, attributes) + end + end + + def update!(id = :all, attributes) # :nodoc: + if id == :all + each { |record| record.update!(attributes) } + else + model.update!(id, attributes) + end + end + + + # Inserts a single record into the database in a single SQL INSERT + # statement. It does not instantiate any models nor does it trigger + # Active Record callbacks or validations. Though passed values + # go through Active Record's type casting and serialization. + # + # See #insert_all for documentation. + def insert(attributes, returning: nil, unique_by: nil, record_timestamps: nil) + insert_all([ attributes ], returning: returning, unique_by: unique_by, record_timestamps: record_timestamps) + end + + # Inserts multiple records into the database in a single SQL INSERT + # statement. It does not instantiate any models nor does it trigger + # Active Record callbacks or validations. Though passed values + # go through Active Record's type casting and serialization. + # + # The +attributes+ parameter is an Array of Hashes. Every Hash determines + # the attributes for a single row and must have the same keys. + # + # Rows are considered to be unique by every unique index on the table. Any + # duplicate rows are skipped. + # Override with :unique_by (see below). + # + # Returns an ActiveRecord::Result with its contents based on + # :returning (see below). + # + # ==== Options + # + # [:returning] + # (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully + # inserted records, which by default is the primary key. + # Pass returning: %w[ id name ] for both id and name + # or returning: false to omit the underlying RETURNING SQL + # clause entirely. + # + # You can also pass an SQL string if you need more control on the return values + # (for example, returning: Arel.sql("id, name as new_name")). + # + # [:unique_by] + # (PostgreSQL and SQLite only) By default rows are considered to be unique + # by every unique index on the table. Any duplicate rows are skipped. + # + # To skip rows according to just one unique index pass :unique_by. + # + # Consider a Book model where no duplicate ISBNs make sense, but if any + # row has an existing id, or is not unique by another unique index, + # ActiveRecord::RecordNotUnique is raised. + # + # Unique indexes can be identified by columns or name: + # + # unique_by: :isbn + # unique_by: %i[ author_id name ] + # unique_by: :index_books_on_isbn + # + # [:record_timestamps] + # By default, automatic setting of timestamp columns is controlled by + # the model's record_timestamps config, matching typical + # behavior. + # + # To override this and force automatic setting of timestamp columns one + # way or the other, pass :record_timestamps: + # + # record_timestamps: true # Always set timestamps automatically + # record_timestamps: false # Never set timestamps automatically + # + # Because it relies on the index information from the database + # :unique_by is recommended to be paired with + # Active Record's schema_cache. + # + # ==== Example + # + # # Insert records and skip inserting any duplicates. + # # Here "Eloquent Ruby" is skipped because its id is not unique. + # + # Book.insert_all([ + # { id: 1, title: "Rework", author: "David" }, + # { id: 1, title: "Eloquent Ruby", author: "Russ" } + # ]) + # + # # insert_all works on chained scopes, and you can use create_with + # # to set default attributes for all inserted records. + # + # author.books.create_with(created_at: Time.now).insert_all([ + # { id: 1, title: "Rework" }, + # { id: 2, title: "Eloquent Ruby" } + # ]) + def insert_all(attributes, returning: nil, unique_by: nil, record_timestamps: nil) + InsertAll.execute(self, attributes, on_duplicate: :skip, returning: returning, unique_by: unique_by, record_timestamps: record_timestamps) + end + + # Inserts a single record into the database in a single SQL INSERT + # statement. It does not instantiate any models nor does it trigger + # Active Record callbacks or validations. Though passed values + # go through Active Record's type casting and serialization. + # + # See #insert_all! for more. + def insert!(attributes, returning: nil, record_timestamps: nil) + insert_all!([ attributes ], returning: returning, record_timestamps: record_timestamps) + end + + # Inserts multiple records into the database in a single SQL INSERT + # statement. It does not instantiate any models nor does it trigger + # Active Record callbacks or validations. Though passed values + # go through Active Record's type casting and serialization. + # + # The +attributes+ parameter is an Array of Hashes. Every Hash determines + # the attributes for a single row and must have the same keys. + # + # Raises ActiveRecord::RecordNotUnique if any rows violate a + # unique index on the table. In that case, no rows are inserted. + # + # To skip duplicate rows, see #insert_all. To replace them, see #upsert_all. + # + # Returns an ActiveRecord::Result with its contents based on + # :returning (see below). + # + # ==== Options + # + # [:returning] + # (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully + # inserted records, which by default is the primary key. + # Pass returning: %w[ id name ] for both id and name + # or returning: false to omit the underlying RETURNING SQL + # clause entirely. + # + # You can also pass an SQL string if you need more control on the return values + # (for example, returning: Arel.sql("id, name as new_name")). + # + # [:record_timestamps] + # By default, automatic setting of timestamp columns is controlled by + # the model's record_timestamps config, matching typical + # behavior. + # + # To override this and force automatic setting of timestamp columns one + # way or the other, pass :record_timestamps: + # + # record_timestamps: true # Always set timestamps automatically + # record_timestamps: false # Never set timestamps automatically + # + # ==== Examples + # + # # Insert multiple records + # Book.insert_all!([ + # { title: "Rework", author: "David" }, + # { title: "Eloquent Ruby", author: "Russ" } + # ]) + # + # # Raises ActiveRecord::RecordNotUnique because "Eloquent Ruby" + # # does not have a unique id. + # Book.insert_all!([ + # { id: 1, title: "Rework", author: "David" }, + # { id: 1, title: "Eloquent Ruby", author: "Russ" } + # ]) + def insert_all!(attributes, returning: nil, record_timestamps: nil) + InsertAll.execute(self, attributes, on_duplicate: :raise, returning: returning, record_timestamps: record_timestamps) + end + + # Updates or inserts (upserts) a single record into the database in a + # single SQL INSERT statement. It does not instantiate any models nor does + # it trigger Active Record callbacks or validations. Though passed values + # go through Active Record's type casting and serialization. + # + # See #upsert_all for documentation. + def upsert(attributes, **kwargs) + upsert_all([ attributes ], **kwargs) + end + + # Updates or inserts (upserts) multiple records into the database in a + # single SQL INSERT statement. It does not instantiate any models nor does + # it trigger Active Record callbacks or validations. Though passed values + # go through Active Record's type casting and serialization. + # + # The +attributes+ parameter is an Array of Hashes. Every Hash determines + # the attributes for a single row and must have the same keys. + # + # Returns an ActiveRecord::Result with its contents based on + # :returning (see below). + # + # By default, +upsert_all+ will update all the columns that can be updated when + # there is a conflict. These are all the columns except primary keys, read-only + # columns, and columns covered by the optional +unique_by+. + # + # ==== Options + # + # [:returning] + # (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully + # upserted records, which by default is the primary key. + # Pass returning: %w[ id name ] for both id and name + # or returning: false to omit the underlying RETURNING SQL + # clause entirely. + # + # You can also pass an SQL string if you need more control on the return values + # (for example, returning: Arel.sql("id, name as new_name")). + # + # [:unique_by] + # (PostgreSQL and SQLite only) By default rows are considered to be unique + # by every unique index on the table. Any duplicate rows are skipped. + # + # To skip rows according to just one unique index pass :unique_by. + # + # Consider a Book model where no duplicate ISBNs make sense, but if any + # row has an existing id, or is not unique by another unique index, + # ActiveRecord::RecordNotUnique is raised. + # + # Unique indexes can be identified by columns or name: + # + # unique_by: :isbn + # unique_by: %i[ author_id name ] + # unique_by: :index_books_on_isbn + # + # Because it relies on the index information from the database + # :unique_by is recommended to be paired with + # Active Record's schema_cache. + # + # [:on_duplicate] + # Configure the SQL update sentence that will be used in case of conflict. + # + # NOTE: If you use this option you must provide all the columns you want to update + # by yourself. + # + # Example: + # + # Commodity.upsert_all( + # [ + # { id: 2, name: "Copper", price: 4.84 }, + # { id: 4, name: "Gold", price: 1380.87 }, + # { id: 6, name: "Aluminium", price: 0.35 } + # ], + # on_duplicate: Arel.sql("price = GREATEST(commodities.price, EXCLUDED.price)") + # ) + # + # See the related +:update_only+ option. Both options can't be used at the same time. + # + # [:update_only] + # Provide a list of column names that will be updated in case of conflict. If not provided, + # +upsert_all+ will update all the columns that can be updated. These are all the columns + # except primary keys, read-only columns, and columns covered by the optional +unique_by+ + # + # Example: + # + # Commodity.upsert_all( + # [ + # { id: 2, name: "Copper", price: 4.84 }, + # { id: 4, name: "Gold", price: 1380.87 }, + # { id: 6, name: "Aluminium", price: 0.35 } + # ], + # update_only: [:price] # Only prices will be updated + # ) + # + # See the related +:on_duplicate+ option. Both options can't be used at the same time. + # + # [:record_timestamps] + # By default, automatic setting of timestamp columns is controlled by + # the model's record_timestamps config, matching typical + # behavior. + # + # To override this and force automatic setting of timestamp columns one + # way or the other, pass :record_timestamps: + # + # record_timestamps: true # Always set timestamps automatically + # record_timestamps: false # Never set timestamps automatically + # + # ==== Examples + # + # # Inserts multiple records, performing an upsert when records have duplicate ISBNs. + # # Here "Eloquent Ruby" overwrites "Rework" because its ISBN is duplicate. + # + # Book.upsert_all([ + # { title: "Rework", author: "David", isbn: "1" }, + # { title: "Eloquent Ruby", author: "Russ", isbn: "1" } + # ], unique_by: :isbn) + # + # Book.find_by(isbn: "1").title # => "Eloquent Ruby" + def upsert_all(attributes, on_duplicate: :update, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil) + InsertAll.execute(self, attributes, on_duplicate: on_duplicate, update_only: update_only, returning: returning, unique_by: unique_by, record_timestamps: record_timestamps) + end + + # Updates the counters of the records in the current relation. + # + # ==== Parameters + # + # * +counter+ - A Hash containing the names of the fields to update as keys and the amount to update as values. + # * :touch option - Touch the timestamp columns when updating. + # * If attributes names are passed, they are updated along with update_at/on attributes. + # + # ==== Examples + # + # # For Posts by a given author increment the comment_count by 1. + # Post.where(author_id: author.id).update_counters(comment_count: 1) + def update_counters(counters) + touch = counters.delete(:touch) + + updates = {} + counters.each do |counter_name, value| + attr = table[counter_name] + updates[attr.name] = _increment_attribute(attr, value) + end + + if touch + names = touch if touch != true + names = Array.wrap(names) + options = names.extract_options! + touch_updates = model.touch_attributes_with_time(*names, **options) + updates.merge!(touch_updates) unless touch_updates.empty? + end + + update_all updates + end + + # Touches all records in the current relation, setting the +updated_at+/+updated_on+ attributes to the current time or the time specified. + # It does not instantiate the involved models, and it does not trigger Active Record callbacks or validations. + # This method can be passed attribute names and an optional time argument. + # If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes. + # If no time argument is passed, the current time is used as default. + # + # === Examples + # + # # Touch all records + # Person.all.touch_all + # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670'" + # + # # Touch multiple records with a custom attribute + # Person.all.touch_all(:created_at) + # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670', \"created_at\" = '2018-01-04 22:55:23.132670'" + # + # # Touch multiple records with a specified time + # Person.all.touch_all(time: Time.new(2020, 5, 16, 0, 0, 0)) + # # => "UPDATE \"people\" SET \"updated_at\" = '2020-05-16 00:00:00'" + # + # # Touch records with scope + # Person.where(name: 'David').touch_all + # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'" + def touch_all(*names, time: nil) + update_all model.touch_attributes_with_time(*names, time: time) + end + + # Destroys the records by instantiating each + # record and calling its {#destroy}[rdoc-ref:Persistence#destroy] method. + # Each object's callbacks are executed (including :dependent association options). + # Returns the collection of objects that were destroyed; each will be frozen, to + # reflect that no changes should be made (since they can't be persisted). + # + # Note: Instantiation, callback execution, and deletion of each + # record can be time consuming when you're removing many records at + # once. It generates at least one SQL +DELETE+ query per record (or + # possibly more, to enforce your callbacks). If you want to delete many + # rows quickly, without concern for their associations or callbacks, use + # #delete_all instead. + # + # ==== Examples + # + # Person.where(age: 0..18).destroy_all + def destroy_all + records.each(&:destroy).tap { reset } + end + + # Deletes the records without instantiating the records + # first, and hence not calling the {#destroy}[rdoc-ref:Persistence#destroy] + # method nor invoking callbacks. + # This is a single SQL DELETE statement that goes straight to the database, much more + # efficient than #destroy_all. Be careful with relations though, in particular + # :dependent rules defined on associations are not honored. Returns the + # number of rows affected. + # + # Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all + # + # Both calls delete the affected posts all at once with a single DELETE statement. + # If you need to destroy dependent associations or call your before_* or + # +after_destroy+ callbacks, use the #destroy_all method instead. + # + # If an invalid method is supplied, #delete_all raises an ActiveRecordError: + # + # Post.distinct.delete_all + # # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct + def delete_all + return 0 if @none + + invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method| + value = @values[method] + method == :distinct ? value : value&.any? + end + if invalid_methods.any? + raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}") + end + + model.with_connection do |c| + arel = eager_loading? ? apply_join_dependency.arel : build_arel(c) + arel.source.left = table + + group_values_arel_columns = arel_columns(group_values.uniq) + having_clause_ast = having_clause.ast unless having_clause.empty? + key = if model.composite_primary_key? + primary_key.map { |pk| table[pk] } + else + table[primary_key] + end + stmt = arel.compile_delete(key, having_clause_ast, group_values_arel_columns) + + c.delete(stmt, "#{model} Delete All").tap { reset } + end + end + + # Deletes the row with a primary key matching the +id+ argument, using an + # SQL +DELETE+ statement, and returns the number of rows deleted. Active + # Record objects are not instantiated, so the object's callbacks are not + # executed, including any :dependent association options. + # + # You can delete multiple rows at once by passing an Array of ids. + # + # Note: Although it is often much faster than the alternative, #destroy, + # skipping callbacks might bypass business logic in your application + # that ensures referential integrity or performs other essential jobs. + # + # ==== Examples + # + # # Delete a single row + # Todo.delete(1) + # + # # Delete multiple rows + # Todo.delete([2,3,4]) + def delete(id_or_array) + return 0 if id_or_array.nil? || (id_or_array.is_a?(Array) && id_or_array.empty?) + + where(model.primary_key => id_or_array).delete_all + end + + + # Destroy an object (or multiple objects) that has the given id. The object is instantiated first, + # therefore all callbacks and filters are fired off before the object is deleted. This method is + # less efficient than #delete but allows cleanup methods and other actions to be run. + # + # This essentially finds the object (or multiple objects) with the given id, creates a new object + # from the attributes, and then calls destroy on it. + # + # ==== Parameters + # + # * +id+ - This should be the id or an array of ids to be destroyed. + # + # ==== Examples + # + # # Destroy a single object + # Todo.destroy(1) + # + # # Destroy multiple objects + # todos = [1,2,3] + # Todo.destroy(todos) + def destroy(id) + multiple_ids = if model.composite_primary_key? + id.first.is_a?(Array) + else + id.is_a?(Array) + end + + if multiple_ids + find(id).each(&:destroy) + else + find(id).destroy + end + end + + # Finds and destroys all records matching the specified conditions. + # This is short-hand for relation.where(condition).destroy_all. + # Returns the collection of objects that were destroyed. + # + # If no record is found, returns empty array. + # + # Person.destroy_by(id: 13) + # Person.destroy_by(name: 'Spartacus', rating: 4) + # Person.destroy_by("published_at < ?", 2.weeks.ago) + def destroy_by(*args) + where(*args).destroy_all + end + + # Finds and deletes all records matching the specified conditions. + # This is short-hand for relation.where(condition).delete_all. + # Returns the number of rows affected. + # + # If no record is found, returns 0 as zero rows were affected. + # + # Person.delete_by(id: 13) + # Person.delete_by(name: 'Spartacus', rating: 4) + # Person.delete_by("published_at < ?", 2.weeks.ago) + def delete_by(*args) + where(*args).delete_all + end + + # Schedule the query to be performed from a background thread pool. + # + # Post.where(published: true).load_async # => # + # + # When the +Relation+ is iterated, if the background query wasn't executed yet, + # it will be performed by the foreground thread. + # + # Note that {config.active_record.async_query_executor}[https://guides.rubyonrails.org/configuring.html#config-active-record-async-query-executor] must be configured + # for queries to actually be executed concurrently. Otherwise it defaults to + # executing them in the foreground. + # + # If the query was actually executed in the background, the Active Record logs will show + # it by prefixing the log line with ASYNC: + # + # ASYNC Post Load (0.0ms) (db time 2ms) SELECT "posts".* FROM "posts" LIMIT 100 + def load_async + with_connection do |c| + return load if !c.async_enabled? + + unless loaded? + result = exec_main_query(async: !c.current_transaction.joinable?) + + if result.is_a?(Array) + @records = result + else + @future_result = result + end + @loaded = true + end + end + + self + end + + def then(&block) # :nodoc: + if @future_result + @future_result.then do + yield self + end + else + super + end + end + + # Returns true if the relation was scheduled on the background + # thread pool. + def scheduled? + !!@future_result + end + + # Causes the records to be loaded from the database if they have not + # been loaded already. You can use this if for some reason you need + # to explicitly load some records before actually using them. The + # return value is the relation itself, not the records. + # + # Post.where(published: true).load # => # + def load(&block) + if !loaded? || scheduled? + @records = exec_queries(&block) + @loaded = true + end + + self + end + + # Forces reloading of relation. + def reload + reset + load + end + + def reset + @future_result&.cancel + @future_result = nil + @delegate_to_model = false + @to_sql = @arel = @loaded = @should_eager_load = nil + @offsets = @take = nil + @cache_keys = nil + @cache_versions = nil + @records = nil + self + end + + # Returns sql statement for the relation. + # + # User.where(name: 'Oscar').to_sql + # # SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar' + def to_sql + @to_sql ||= if eager_loading? + apply_join_dependency do |relation, join_dependency| + relation = join_dependency.apply_column_aliases(relation) + relation.to_sql + end + else + model.with_connection do |conn| + conn.unprepared_statement { conn.to_sql(arel) } + end + end + end + + # Returns a hash of where conditions. + # + # User.where(name: 'Oscar').where_values_hash + # # => {name: "Oscar"} + def where_values_hash(relation_table_name = model.table_name) # :nodoc: + where_clause.to_h(relation_table_name) + end + + def scope_for_create + hash = where_clause.to_h(model.table_name, equality_only: true) + create_with_value.each { |k, v| hash[k.to_s] = v } unless create_with_value.empty? + hash + end + + # Returns true if relation needs eager loading. + def eager_loading? + @should_eager_load ||= + eager_load_values.any? || + includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?) + end + + # Joins that are also marked for preloading. In which case we should just eager load them. + # Note that this is a naive implementation because we could have strings and symbols which + # represent the same association, but that aren't matched by this. Also, we could have + # nested hashes which partially match, e.g. { a: :b } & { a: [:b, :c] } + def joined_includes_values + includes_values & joins_values + end + + # Compares two relations for equality. + def ==(other) + case other + when Associations::CollectionProxy, AssociationRelation + self == other.records + when Relation + other.to_sql == to_sql + when Array + records == other + end + end + + def pretty_print(pp) + subject = loaded? ? records : annotate("loading for pp") + entries = subject.take([limit_value, 11].compact.min) + + entries[10] = "..." if entries.size == 11 + + pp.pp(entries) + end + + # Returns true if relation is blank. + def blank? + records.blank? + end + + def readonly? + readonly_value + end + + def values + @values.dup + end + + def values_for_queries # :nodoc: + @values.except(:extending, :skip_query_cache, :strict_loading) + end + + def inspect + subject = loaded? ? records : annotate("loading for inspect") + entries = subject.take([limit_value, 11].compact.min).map!(&:inspect) + + entries[10] = "..." if entries.size == 11 + + "#<#{self.class.name} [#{entries.join(', ')}]>" + end + + def empty_scope? # :nodoc: + @values == model.unscoped.values + end + + def has_limit_or_offset? # :nodoc: + limit_value || offset_value + end + + def alias_tracker(joins = [], aliases = nil) # :nodoc: + ActiveRecord::Associations::AliasTracker.create(model.connection_pool, table.name, joins, aliases) + end + + class StrictLoadingScope # :nodoc: + def self.empty_scope? + true + end + + def self.strict_loading_value + true + end + end + + def preload_associations(records) # :nodoc: + preload = preload_values + preload += includes_values unless eager_loading? + scope = strict_loading_value ? StrictLoadingScope : nil + preload.each do |associations| + ActiveRecord::Associations::Preloader.new(records: records, associations: associations, scope: scope).call + end + end + + protected + def load_records(records) + @records = records.freeze + @loaded = true + end + + private + def already_in_scope?(registry) + @delegate_to_model && registry.current_scope(model, true) + end + + def global_scope?(registry) + registry.global_current_scope(model, true) + end + + def current_scope_restoring_block(&block) + current_scope = model.current_scope(true) + -> record do + model.current_scope = current_scope + yield record if block_given? + end + end + + def _new(attributes, &block) + model.new(attributes, &block) + end + + def _create(attributes, &block) + model.create(attributes, &block) + end + + def _create!(attributes, &block) + model.create!(attributes, &block) + end + + def _scoping(scope, registry, all_queries = false) + previous = registry.current_scope(model, true) + registry.set_current_scope(model, scope) + + if all_queries + previous_global = registry.global_current_scope(model, true) + registry.set_global_current_scope(model, scope) + end + yield + ensure + registry.set_current_scope(model, previous) + if all_queries + registry.set_global_current_scope(model, previous_global) + end + end + + def _substitute_values(values) + values.map do |name, value| + attr = table[name] + if Arel.arel_node?(value) + if value.is_a?(Arel::Nodes::SqlLiteral) + value = Arel::Nodes::Grouping.new(value) + end + else + type = model.type_for_attribute(attr.name) + value = predicate_builder.build_bind_attribute(attr.name, type.cast(value)) + end + [attr, value] + end + end + + def _increment_attribute(attribute, value = 1) + bind = predicate_builder.build_bind_attribute(attribute.name, value.abs) + expr = table.coalesce(Arel::Nodes::UnqualifiedColumn.new(attribute), 0) + expr = value < 0 ? expr - bind : expr + bind + expr.expr + end + + def exec_queries(&block) + skip_query_cache_if_necessary do + rows = if scheduled? + future = @future_result + @future_result = nil + future.result + else + exec_main_query + end + + records = instantiate_records(rows, &block) + preload_associations(records) unless skip_preloading_value + + records.each(&:readonly!) if readonly_value + records.each { |record| record.strict_loading!(strict_loading_value) } unless strict_loading_value.nil? + + records + end + end + + def exec_main_query(async: false) + if @none + if async + return FutureResult.wrap([]) + else + return [] + end + end + + skip_query_cache_if_necessary do + if where_clause.contradiction? + [].freeze + elsif eager_loading? + model.with_connection do |c| + apply_join_dependency do |relation, join_dependency| + if relation.null_relation? + [].freeze + else + relation = join_dependency.apply_column_aliases(relation) + @_join_dependency = join_dependency + c.select_all(relation.arel, "SQL", async: async) + end + end + end + else + model.with_connection do |c| + model._query_by_sql(c, arel, async: async) + end + end + end + end + + def instantiate_records(rows, &block) + return [].freeze if rows.empty? + if eager_loading? + records = @_join_dependency.instantiate(rows, strict_loading_value, &block).freeze + @_join_dependency = nil + records + else + model._load_from_sql(rows, &block).freeze + end + end + + def skip_query_cache_if_necessary(&block) + if skip_query_cache_value + model.uncached(&block) + else + yield + end + end + + def references_eager_loaded_tables? + joined_tables = build_joins([]).flat_map do |join| + if join.is_a?(Arel::Nodes::StringJoin) + tables_in_string(join.left) + else + join.left.name + end + end + + joined_tables << table.name + + # always convert table names to downcase as in Oracle quoted table names are in uppercase + joined_tables.map!(&:downcase) + + !(references_values.map(&:to_s) - joined_tables).empty? + end + + def tables_in_string(string) + return [] if string.blank? + # always convert table names to downcase as in Oracle quoted table names are in uppercase + # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries + string.scan(/[a-zA-Z_][.\w]+(?=.?\.)/).map!(&:downcase) - ["raw_sql_"] + end + + def limited_count + limit_value ? count : limit(2).count + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/batches.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/batches.rb new file mode 100644 index 00000000..eea785b6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/batches.rb @@ -0,0 +1,491 @@ +# frozen_string_literal: true + +require "active_record/relation/batches/batch_enumerator" + +module ActiveRecord + # = Active Record \Batches + module Batches + ORDER_IGNORE_MESSAGE = "Scoped order is ignored, use :cursor with :order to configure custom order." + DEFAULT_ORDER = :asc + + # Looping through a collection of records from the database + # (using the Scoping::Named::ClassMethods.all method, for example) + # is very inefficient since it will try to instantiate all the objects at once. + # + # In that case, batch processing methods allow you to work + # with the records in batches, thereby greatly reducing memory consumption. + # + # The #find_each method uses #find_in_batches with a batch size of 1000 (or as + # specified by the +:batch_size+ option). + # + # Person.find_each do |person| + # person.do_awesome_stuff + # end + # + # Person.where("age > 21").find_each do |person| + # person.party_all_night! + # end + # + # If you do not provide a block to #find_each, it will return an Enumerator + # for chaining with other methods: + # + # Person.find_each.with_index do |person, index| + # person.award_trophy(index + 1) + # end + # + # ==== Options + # * :batch_size - Specifies the size of the batch. Defaults to 1000. + # * :start - Specifies the cursor column value to start from, inclusive of the value. + # * :finish - Specifies the cursor column value to end at, inclusive of the value. + # * :error_on_ignore - Overrides the application config to specify if an error should be raised when + # an order is present in the relation. + # * :cursor - Specifies the column to use for batching (can be a column name or an array + # of column names). Defaults to primary key. + # * :order - Specifies the cursor column order (can be +:asc+ or +:desc+ or an array consisting + # of :asc or :desc). Defaults to +:asc+. + # + # class Order < ActiveRecord::Base + # self.primary_key = [:id_1, :id_2] + # end + # + # Order.find_each(order: [:asc, :desc]) + # + # In the above code, +id_1+ is sorted in ascending order and +id_2+ in descending order. + # + # Limits are honored, and if present there is no requirement for the batch + # size: it can be less than, equal to, or greater than the limit. + # + # The options +start+ and +finish+ are especially useful if you want + # multiple workers dealing with the same processing queue. You can make + # worker 1 handle all the records between id 1 and 9999 and worker 2 + # handle from 10000 and beyond by setting the +:start+ and +:finish+ + # option on each worker. + # + # # In worker 1, let's process until 9999 records. + # Person.find_each(finish: 9_999) do |person| + # person.party_all_night! + # end + # + # # In worker 2, let's process from record 10_000 and onwards. + # Person.find_each(start: 10_000) do |person| + # person.party_all_night! + # end + # + # NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to + # ascending on the primary key ("id ASC"). + # This also means that this method only works when the cursor column is + # orderable (e.g. an integer or string). + # + # NOTE: When using custom columns for batching, they should include at least one unique column + # (e.g. primary key) as a tiebreaker. Also, to reduce the likelihood of race conditions, + # all columns should be static (unchangeable after it was set). + # + # NOTE: By its nature, batch processing is subject to race conditions if + # other processes are modifying the database. + def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER, &block) + if block_given? + find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, cursor: cursor, order: order) do |records| + records.each(&block) + end + else + enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, cursor: cursor, order: order) do + relation = self + cursor = Array(cursor) + apply_limits(relation, cursor, start, finish, build_batch_orders(cursor, order)).size + end + end + end + + # Yields each batch of records that was found by the find options as + # an array. + # + # Person.where("age > 21").find_in_batches do |group| + # sleep(50) # Make sure it doesn't get too crowded in there! + # group.each { |person| person.party_all_night! } + # end + # + # If you do not provide a block to #find_in_batches, it will return an Enumerator + # for chaining with other methods: + # + # Person.find_in_batches.with_index do |group, batch| + # puts "Processing group ##{batch}" + # group.each(&:recover_from_last_night!) + # end + # + # To be yielded each record one by one, use #find_each instead. + # + # ==== Options + # * :batch_size - Specifies the size of the batch. Defaults to 1000. + # * :start - Specifies the cursor column value to start from, inclusive of the value. + # * :finish - Specifies the cursor column value to end at, inclusive of the value. + # * :error_on_ignore - Overrides the application config to specify if an error should be raised when + # an order is present in the relation. + # * :cursor - Specifies the column to use for batching (can be a column name or an array + # of column names). Defaults to primary key. + # * :order - Specifies the cursor column order (can be +:asc+ or +:desc+ or an array consisting + # of :asc or :desc). Defaults to +:asc+. + # + # class Order < ActiveRecord::Base + # self.primary_key = [:id_1, :id_2] + # end + # + # Order.find_in_batches(order: [:asc, :desc]) + # + # In the above code, +id_1+ is sorted in ascending order and +id_2+ in descending order. + # + # Limits are honored, and if present there is no requirement for the batch + # size: it can be less than, equal to, or greater than the limit. + # + # The options +start+ and +finish+ are especially useful if you want + # multiple workers dealing with the same processing queue. You can make + # worker 1 handle all the records between id 1 and 9999 and worker 2 + # handle from 10000 and beyond by setting the +:start+ and +:finish+ + # option on each worker. + # + # # Let's process from record 10_000 on. + # Person.find_in_batches(start: 10_000) do |group| + # group.each { |person| person.party_all_night! } + # end + # + # NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to + # ascending on the primary key ("id ASC"). + # This also means that this method only works when the cursor column is + # orderable (e.g. an integer or string). + # + # NOTE: When using custom columns for batching, they should include at least one unique column + # (e.g. primary key) as a tiebreaker. Also, to reduce the likelihood of race conditions, + # all columns should be static (unchangeable after it was set). + # + # NOTE: By its nature, batch processing is subject to race conditions if + # other processes are modifying the database. + def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER) + relation = self + unless block_given? + return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, cursor: cursor, order: order) do + cursor = Array(cursor) + total = apply_limits(relation, cursor, start, finish, build_batch_orders(cursor, order)).size + (total - 1).div(batch_size) + 1 + end + end + + in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore, cursor: cursor, order: order) do |batch| + yield batch.to_a + end + end + + # Yields ActiveRecord::Relation objects to work with a batch of records. + # + # Person.where("age > 21").in_batches do |relation| + # relation.delete_all + # sleep(10) # Throttle the delete queries + # end + # + # If you do not provide a block to #in_batches, it will return a + # BatchEnumerator which is enumerable. + # + # Person.in_batches.each_with_index do |relation, batch_index| + # puts "Processing relation ##{batch_index}" + # relation.delete_all + # end + # + # Examples of calling methods on the returned BatchEnumerator object: + # + # Person.in_batches.delete_all + # Person.in_batches.update_all(awesome: true) + # Person.in_batches.each_record(&:party_all_night!) + # + # ==== Options + # * :of - Specifies the size of the batch. Defaults to 1000. + # * :load - Specifies if the relation should be loaded. Defaults to false. + # * :start - Specifies the cursor column value to start from, inclusive of the value. + # * :finish - Specifies the cursor column value to end at, inclusive of the value. + # * :error_on_ignore - Overrides the application config to specify if an error should be raised when + # an order is present in the relation. + # * :cursor - Specifies the column to use for batching (can be a column name or an array + # of column names). Defaults to primary key. + # * :order - Specifies the cursor column order (can be +:asc+ or +:desc+ or an array consisting + # of :asc or :desc). Defaults to +:asc+. + # + # class Order < ActiveRecord::Base + # self.primary_key = [:id_1, :id_2] + # end + # + # Order.in_batches(order: [:asc, :desc]) + # + # In the above code, +id_1+ is sorted in ascending order and +id_2+ in descending order. + # + # * :use_ranges - Specifies whether to use range iteration (id >= x AND id <= y). + # It can make iterating over the whole or almost whole tables several times faster. + # Only whole table iterations use this style of iteration by default. You can disable this behavior by passing +false+. + # If you iterate over the table and the only condition is, e.g., archived_at: nil (and only a tiny fraction + # of the records are archived), it makes sense to opt in to this approach. + # + # Limits are honored, and if present there is no requirement for the batch + # size, it can be less than, equal, or greater than the limit. + # + # The options +start+ and +finish+ are especially useful if you want + # multiple workers dealing with the same processing queue. You can make + # worker 1 handle all the records between id 1 and 9999 and worker 2 + # handle from 10000 and beyond by setting the +:start+ and +:finish+ + # option on each worker. + # + # # Let's process from record 10_000 on. + # Person.in_batches(start: 10_000).update_all(awesome: true) + # + # An example of calling where query method on the relation: + # + # Person.in_batches.each do |relation| + # relation.update_all('age = age + 1') + # relation.where('age > 21').update_all(should_party: true) + # relation.where('age <= 21').delete_all + # end + # + # NOTE: If you are going to iterate through each record, you should call + # #each_record on the yielded BatchEnumerator: + # + # Person.in_batches.each_record(&:party_all_night!) + # + # NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to + # ascending on the primary key ("id ASC"). + # This also means that this method only works when the cursor column is + # orderable (e.g. an integer or string). + # + # NOTE: When using custom columns for batching, they should include at least one unique column + # (e.g. primary key) as a tiebreaker. Also, to reduce the likelihood of race conditions, + # all columns should be static (unchangeable after it was set). + # + # NOTE: By its nature, batch processing is subject to race conditions if + # other processes are modifying the database. + def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER, use_ranges: nil, &block) + cursor = Array(cursor).map(&:to_s) + ensure_valid_options_for_batching!(cursor, start, finish, order) + + if arel.orders.present? + act_on_ignored_order(error_on_ignore) + end + + unless block + return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self, cursor: cursor, order: order, use_ranges: use_ranges) + end + + batch_limit = of + + if limit_value + remaining = limit_value + batch_limit = remaining if remaining < batch_limit + end + + if self.loaded? + batch_on_loaded_relation( + relation: self, + start: start, + finish: finish, + cursor: cursor, + order: order, + batch_limit: batch_limit, + &block + ) + else + batch_on_unloaded_relation( + relation: self, + start: start, + finish: finish, + load: load, + cursor: cursor, + order: order, + use_ranges: use_ranges, + remaining: remaining, + batch_limit: batch_limit, + &block + ) + end + end + + private + def ensure_valid_options_for_batching!(cursor, start, finish, order) + if start && Array(start).size != cursor.size + raise ArgumentError, ":start must contain one value per cursor column" + end + + if finish && Array(finish).size != cursor.size + raise ArgumentError, ":finish must contain one value per cursor column" + end + + if (Array(primary_key) - cursor).any? + indexes = model.schema_cache.indexes(table_name) + unique_index = indexes.find { |index| index.unique && index.where.nil? && (Array(index.columns) - cursor).empty? } + + unless unique_index + raise ArgumentError, ":cursor must include a primary key or other unique column(s)" + end + end + + if (Array(order) - [:asc, :desc]).any? + raise ArgumentError, ":order must be :asc or :desc or an array consisting of :asc or :desc, got #{order.inspect}" + end + end + + def apply_limits(relation, cursor, start, finish, batch_orders) + relation = apply_start_limit(relation, cursor, start, batch_orders) if start + relation = apply_finish_limit(relation, cursor, finish, batch_orders) if finish + relation + end + + def apply_start_limit(relation, cursor, start, batch_orders) + operators = batch_orders.map do |_column, order| + order == :desc ? :lteq : :gteq + end + batch_condition(relation, cursor, start, operators) + end + + def apply_finish_limit(relation, cursor, finish, batch_orders) + operators = batch_orders.map do |_column, order| + order == :desc ? :gteq : :lteq + end + batch_condition(relation, cursor, finish, operators) + end + + def batch_condition(relation, cursor, values, operators) + cursor_positions = cursor.zip(Array(values), operators) + + first_clause_column, first_clause_value, operator = cursor_positions.pop + where_clause = predicate_builder[first_clause_column, first_clause_value, operator] + + cursor_positions.reverse_each do |column_name, value, operator| + where_clause = predicate_builder[column_name, value, operator == :lteq ? :lt : :gt].or( + predicate_builder[column_name, value, :eq].and(where_clause) + ) + end + + relation.where(where_clause) + end + + def build_batch_orders(cursor, order) + cursor.zip(Array(order)).map do |column, order_| + [column, order_ || DEFAULT_ORDER] + end + end + + def act_on_ignored_order(error_on_ignore) + raise_error = (error_on_ignore.nil? ? ActiveRecord.error_on_ignored_order : error_on_ignore) + + if raise_error + raise ArgumentError.new(ORDER_IGNORE_MESSAGE) + elsif model.logger + model.logger.warn(ORDER_IGNORE_MESSAGE) + end + end + + def batch_on_loaded_relation(relation:, start:, finish:, cursor:, order:, batch_limit:) + records = relation.to_a + order = build_batch_orders(cursor, order).map(&:second) + + if start || finish + records = records.filter do |record| + values = record_cursor_values(record, cursor) + + (start.nil? || compare_values_for_order(values, Array(start), order) >= 0) && + (finish.nil? || compare_values_for_order(values, Array(finish), order) <= 0) + end + end + + records.sort! do |record1, record2| + values1 = record_cursor_values(record1, cursor) + values2 = record_cursor_values(record2, cursor) + compare_values_for_order(values1, values2, order) + end + + records.each_slice(batch_limit) do |subrecords| + subrelation = relation.spawn + subrelation.load_records(subrecords) + + yield subrelation + end + + nil + end + + def record_cursor_values(record, cursor) + record.attributes.slice(*cursor).values + end + + # This is a custom implementation of `<=>` operator, + # which also takes into account how the collection will be ordered. + def compare_values_for_order(values1, values2, order) + values1.each_with_index do |element1, index| + element2 = values2[index] + direction = order[index] + comparison = element1 <=> element2 + comparison = -comparison if direction == :desc + return comparison if comparison != 0 + end + + 0 + end + + def batch_on_unloaded_relation(relation:, start:, finish:, load:, cursor:, order:, use_ranges:, remaining:, batch_limit:) + batch_orders = build_batch_orders(cursor, order) + relation = relation.reorder(batch_orders.to_h).limit(batch_limit) + relation = apply_limits(relation, cursor, start, finish, batch_orders) + relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching + batch_relation = relation + empty_scope = to_sql == model.unscoped.all.to_sql + + loop do + if load + records = batch_relation.records + values = records.pluck(*cursor) + yielded_relation = where(cursor => values) + yielded_relation.load_records(records) + elsif (empty_scope && use_ranges != false) || use_ranges + values = batch_relation.pluck(*cursor) + + finish = values.last + if finish + yielded_relation = apply_finish_limit(batch_relation, cursor, finish, batch_orders) + yielded_relation = yielded_relation.except(:limit, :order) + yielded_relation.skip_query_cache!(false) + end + else + values = batch_relation.pluck(*cursor) + yielded_relation = where(cursor => values) + end + + break if values.empty? + + if values.flatten.any?(nil) + raise ArgumentError, "Not all of the batch cursor columns were included in the custom select clause "\ + "or some columns contain nil." + end + + yield yielded_relation + + break if values.length < batch_limit + + if limit_value + remaining -= values.length + + if remaining == 0 + # Saves a useless iteration when the limit is a multiple of the + # batch size. + break + elsif remaining < batch_limit + relation = relation.limit(remaining) + end + end + + batch_orders_copy = batch_orders.dup + _last_column, last_order = batch_orders_copy.pop + operators = batch_orders_copy.map do |_column, order| + order == :desc ? :lteq : :gteq + end + operators << (last_order == :desc ? :lt : :gt) + + cursor_value = values.last + batch_relation = batch_condition(relation, cursor, cursor_value, operators) + end + + nil + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/batches/batch_enumerator.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/batches/batch_enumerator.rb new file mode 100644 index 00000000..78cd753c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/batches/batch_enumerator.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +module ActiveRecord + module Batches + class BatchEnumerator + include Enumerable + + def initialize(of: 1000, start: nil, finish: nil, relation:, cursor:, order: :asc, use_ranges: nil) # :nodoc: + @of = of + @relation = relation + @start = start + @finish = finish + @cursor = cursor + @order = order + @use_ranges = use_ranges + end + + # The primary key value from which the BatchEnumerator starts, inclusive of the value. + attr_reader :start + + # The primary key value at which the BatchEnumerator ends, inclusive of the value. + attr_reader :finish + + # The relation from which the BatchEnumerator yields batches. + attr_reader :relation + + # The size of the batches yielded by the BatchEnumerator. + def batch_size + @of + end + + # Looping through a collection of records from the database (using the + # +all+ method, for example) is very inefficient since it will try to + # instantiate all the objects at once. + # + # In that case, batch processing methods allow you to work with the + # records in batches, thereby greatly reducing memory consumption. + # + # Person.in_batches.each_record do |person| + # person.do_awesome_stuff + # end + # + # Person.where("age > 21").in_batches(of: 10).each_record do |person| + # person.party_all_night! + # end + # + # If you do not provide a block to #each_record, it will return an Enumerator + # for chaining with other methods: + # + # Person.in_batches.each_record.with_index do |person, index| + # person.award_trophy(index + 1) + # end + def each_record(&block) + return to_enum(:each_record) unless block_given? + + @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true, cursor: @cursor, order: @order).each do |relation| + relation.records.each(&block) + end + end + + # Deletes records in batches. Returns the total number of rows affected. + # + # Person.in_batches.delete_all + # + # See Relation#delete_all for details of how each batch is deleted. + def delete_all + sum(&:delete_all) + end + + # Updates records in batches. Returns the total number of rows affected. + # + # Person.in_batches.update_all("age = age + 1") + # + # See Relation#update_all for details of how each batch is updated. + def update_all(updates) + sum do |relation| + relation.update_all(updates) + end + end + + # Touches records in batches. Returns the total number of rows affected. + # + # Person.in_batches.touch_all + # + # See Relation#touch_all for details of how each batch is touched. + def touch_all(...) + sum do |relation| + relation.touch_all(...) + end + end + + # Destroys records in batches. Returns the total number of rows affected. + # + # Person.where("age < 10").in_batches.destroy_all + # + # See Relation#destroy_all for details of how each batch is destroyed. + def destroy_all + sum do |relation| + relation.destroy_all.count(&:destroyed?) + end + end + + # Yields an ActiveRecord::Relation object for each batch of records. + # + # Person.in_batches.each do |relation| + # relation.update_all(awesome: true) + # end + def each(&block) + enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false, cursor: @cursor, order: @order, use_ranges: @use_ranges) + return enum.each(&block) if block_given? + enum + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/calculations.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/calculations.rb new file mode 100644 index 00000000..25bbcbd5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/calculations.rb @@ -0,0 +1,681 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" + +module ActiveRecord + # = Active Record \Calculations + module Calculations + class ColumnAliasTracker # :nodoc: + def initialize(connection) + @connection = connection + @aliases = Hash.new(0) + end + + def alias_for(field) + aliased_name = column_alias_for(field) + + if @aliases[aliased_name] == 0 + @aliases[aliased_name] = 1 + aliased_name + else + # Update the count + count = @aliases[aliased_name] += 1 + "#{truncate(aliased_name)}_#{count}" + end + end + + private + # Converts the given field to the value that the database adapter returns as + # a usable column name: + # + # column_alias_for("users.id") # => "users_id" + # column_alias_for("sum(id)") # => "sum_id" + # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id" + # column_alias_for("count(*)") # => "count_all" + def column_alias_for(field) + column_alias = +field + column_alias.gsub!(/\*/, "all") + column_alias.gsub!(/\W+/, " ") + column_alias.strip! + column_alias.gsub!(/ +/, "_") + @connection.table_alias_for(column_alias) + end + + def truncate(name) + name.slice(0, @connection.table_alias_length - 2) + end + end + + # Count the records. + # + # Person.count + # # => the total count of all people + # + # Person.count(:age) + # # => returns the total count of all people whose age is present in database + # + # Person.count(:all) + # # => performs a COUNT(*) (:all is an alias for '*') + # + # Person.distinct.count(:age) + # # => counts the number of different age values + # + # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group], + # it returns a Hash whose keys represent the aggregated column, + # and the values are the respective amounts: + # + # Person.group(:city).count + # # => { 'Rome' => 5, 'Paris' => 3 } + # + # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose + # keys are an array containing the individual values of each column and the value + # of each key would be the #count. + # + # Article.group(:status, :category).count + # # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2} + # + # If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns: + # + # Person.select(:age).count + # # => counts the number of different age values + # + # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ + # between databases. In invalid cases, an error from the database is thrown. + # + # When given a block, loads all records in the relation, if the relation + # hasn't been loaded yet. Calls the block with each record in the relation. + # Returns the number of records for which the block returns a truthy value. + # + # Person.count { |person| person.age > 21 } + # # => counts the number of people older that 21 + # + # Note: If there are a lot of records in the relation, loading all records + # could result in performance issues. + def count(column_name = nil) + if block_given? + unless column_name.nil? + raise ArgumentError, "Column name argument is not supported when a block is passed." + end + + super() + else + calculate(:count, column_name) + end + end + + # Same as #count, but performs the query asynchronously and returns an + # ActiveRecord::Promise. + def async_count(column_name = nil) + async.count(column_name) + end + + # Calculates the average value on a given column. Returns +nil+ if there's + # no row. See #calculate for examples with options. + # + # Person.average(:age) # => 35.8 + def average(column_name) + calculate(:average, column_name) + end + + # Same as #average, but performs the query asynchronously and returns an + # ActiveRecord::Promise. + def async_average(column_name) + async.average(column_name) + end + + # Calculates the minimum value on a given column. The value is returned + # with the same data type of the column, or +nil+ if there's no row. See + # #calculate for examples with options. + # + # Person.minimum(:age) # => 7 + def minimum(column_name) + calculate(:minimum, column_name) + end + + # Same as #minimum, but performs the query asynchronously and returns an + # ActiveRecord::Promise. + def async_minimum(column_name) + async.minimum(column_name) + end + + # Calculates the maximum value on a given column. The value is returned + # with the same data type of the column, or +nil+ if there's no row. See + # #calculate for examples with options. + # + # Person.maximum(:age) # => 93 + def maximum(column_name) + calculate(:maximum, column_name) + end + + # Same as #maximum, but performs the query asynchronously and returns an + # ActiveRecord::Promise. + def async_maximum(column_name) + async.maximum(column_name) + end + + # Calculates the sum of values on a given column. The value is returned + # with the same data type of the column, +0+ if there's no row. See + # #calculate for examples with options. + # + # Person.sum(:age) # => 4562 + # + # When given a block, loads all records in the relation, if the relation + # hasn't been loaded yet. Calls the block with each record in the relation. + # Returns the sum of +initial_value_or_column+ and the block return + # values: + # + # Person.sum { |person| person.age } # => 4562 + # Person.sum(1000) { |person| person.age } # => 5562 + # + # Note: If there are a lot of records in the relation, loading all records + # could result in performance issues. + def sum(initial_value_or_column = 0, &block) + if block_given? + map(&block).sum(initial_value_or_column) + else + calculate(:sum, initial_value_or_column) + end + end + + # Same as #sum, but performs the query asynchronously and returns an + # ActiveRecord::Promise. + def async_sum(identity_or_column = nil) + async.sum(identity_or_column) + end + + # This calculates aggregate values in the given column. Methods for #count, #sum, #average, + # #minimum, and #maximum have been added as shortcuts. + # + # Person.calculate(:count, :all) # The same as Person.count + # Person.average(:age) # SELECT AVG(age) FROM people... + # + # # Selects the minimum age for any family without any minors + # Person.group(:last_name).having("min(age) > 17").minimum(:age) + # + # Person.sum("2 * age") + # + # There are two basic forms of output: + # + # * Single aggregate value: The single value is type cast to Integer for COUNT, Float + # for AVG, and the given column's type for everything else. + # + # * Grouped values: This returns an ordered hash of the values and groups them. It + # takes either a column name, or the name of a belongs_to association. + # + # values = Person.group('last_name').maximum(:age) + # puts values["Drake"] + # # => 43 + # + # drake = Family.find_by(last_name: 'Drake') + # values = Person.group(:family).maximum(:age) # Person belongs_to :family + # puts values[drake] + # # => 43 + # + # values.each do |family, max_age| + # ... + # end + def calculate(operation, column_name) + operation = operation.to_s.downcase + + if @none + case operation + when "count", "sum" + result = group_values.any? ? Hash.new : 0 + return @async ? Promise::Complete.new(result) : result + when "average", "minimum", "maximum" + result = group_values.any? ? Hash.new : nil + return @async ? Promise::Complete.new(result) : result + end + end + + if has_include?(column_name) + relation = apply_join_dependency + + if operation == "count" + unless distinct_value || distinct_select?(column_name || select_for_count) + relation.distinct! + relation.select_values = Array(model.primary_key || table[Arel.star]) + end + # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT + relation.order_values = [] if group_values.empty? + end + + relation.calculate(operation, column_name) + else + perform_calculation(operation, column_name) + end + end + + # Use #pluck as a shortcut to select one or more attributes without + # loading an entire record object per row. + # + # Person.pluck(:name) + # + # instead of + # + # Person.all.map(&:name) + # + # Pluck returns an Array of attribute values type-casted to match + # the plucked column names, if they can be deduced. Plucking an SQL fragment + # returns String values by default. + # + # Person.pluck(:name) + # # SELECT people.name FROM people + # # => ['David', 'Jeremy', 'Jose'] + # + # Person.pluck(:id, :name) + # # SELECT people.id, people.name FROM people + # # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']] + # + # Person.distinct.pluck(:role) + # # SELECT DISTINCT role FROM people + # # => ['admin', 'member', 'guest'] + # + # Person.where(age: 21).limit(5).pluck(:id) + # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5 + # # => [2, 3] + # + # Comment.joins(:person).pluck(:id, person: :id) + # # SELECT comments.id, person.id FROM comments INNER JOIN people person ON person.id = comments.person_id + # # => [[1, 2], [2, 2]] + # + # Comment.joins(:person).pluck(:id, person: [:id, :name]) + # # SELECT comments.id, person.id, person.name FROM comments INNER JOIN people person ON person.id = comments.person_id + # # => [[1, 2, 'David'], [2, 2, 'David']] + # + # Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)')) + # # SELECT DATEDIFF(updated_at, created_at) FROM people + # # => ['0', '27761', '173'] + # + # See also #ids. + def pluck(*column_names) + if @none + if @async + return Promise::Complete.new([]) + else + return [] + end + end + + if loaded? && all_attributes?(column_names) + result = records.pluck(*column_names) + if @async + return Promise::Complete.new(result) + else + return result + end + end + + if has_include?(column_names.first) + relation = apply_join_dependency + relation.pluck(*column_names) + else + model.disallow_raw_sql!(flattened_args(column_names)) + relation = spawn + columns = relation.arel_columns(column_names) + relation.select_values = columns + result = skip_query_cache_if_necessary do + if where_clause.contradiction? + ActiveRecord::Result.empty(async: @async) + else + model.with_connection do |c| + c.select_all(relation.arel, "#{model.name} Pluck", async: @async) + end + end + end + result.then do |result| + type_cast_pluck_values(result, columns) + end + end + end + + # Same as #pluck, but performs the query asynchronously and returns an + # ActiveRecord::Promise. + def async_pluck(*column_names) + async.pluck(*column_names) + end + + # Pick the value(s) from the named column(s) in the current relation. + # This is short-hand for relation.limit(1).pluck(*column_names).first, and is primarily useful + # when you have a relation that's already narrowed down to a single row. + # + # Just like #pluck, #pick will only load the actual value, not the entire record object, so it's also + # more efficient. The value is, again like with pluck, typecast by the column type. + # + # Person.where(id: 1).pick(:name) + # # SELECT people.name FROM people WHERE id = 1 LIMIT 1 + # # => 'David' + # + # Person.where(id: 1).pick(:name, :email_address) + # # SELECT people.name, people.email_address FROM people WHERE id = 1 LIMIT 1 + # # => [ 'David', 'david@loudthinking.com' ] + def pick(*column_names) + if loaded? && all_attributes?(column_names) + result = records.pick(*column_names) + return @async ? Promise::Complete.new(result) : result + end + + limit(1).pluck(*column_names).then(&:first) + end + + # Same as #pick, but performs the query asynchronously and returns an + # ActiveRecord::Promise. + def async_pick(*column_names) + async.pick(*column_names) + end + + # Returns the base model's ID's for the relation using the table's primary key + # + # Person.ids # SELECT people.id FROM people + # Person.joins(:company).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id + def ids + primary_key_array = Array(primary_key) + + if loaded? + result = records.map do |record| + if primary_key_array.one? + record._read_attribute(primary_key_array.first) + else + primary_key_array.map { |column| record._read_attribute(column) } + end + end + return @async ? Promise::Complete.new(result) : result + end + + if has_include?(primary_key) + relation = apply_join_dependency.group(*primary_key_array) + return relation.ids + end + + columns = arel_columns(primary_key_array) + relation = spawn + relation.select_values = columns + + result = if relation.where_clause.contradiction? + ActiveRecord::Result.empty + else + skip_query_cache_if_necessary do + model.with_connection do |c| + c.select_all(relation, "#{model.name} Ids", async: @async) + end + end + end + + result.then { |result| type_cast_pluck_values(result, columns) } + end + + # Same as #ids, but performs the query asynchronously and returns an + # ActiveRecord::Promise. + def async_ids + async.ids + end + + protected + def aggregate_column(column_name) + case column_name + when Arel::Expressions + column_name + when :all + Arel.star + else + arel_column(column_name) + end + end + + private + def all_attributes?(column_names) + (column_names.map(&:to_s) - model.attribute_names - model.attribute_aliases.keys).empty? + end + + def has_include?(column_name) + eager_loading? || (includes_values.present? && column_name && column_name != :all) + end + + def perform_calculation(operation, column_name) + operation = operation.to_s.downcase + + # If #count is used with #distinct (i.e. `relation.distinct.count`) it is + # considered distinct. + distinct = distinct_value + + if operation == "count" + column_name ||= select_for_count + if column_name == :all + if !distinct + distinct = distinct_select?(select_for_count) if group_values.empty? + elsif group_values.any? || select_values.empty? && order_values.empty? + column_name = primary_key + end + elsif distinct_select?(column_name) + distinct = nil + end + end + + if group_values.any? + execute_grouped_calculation(operation, column_name, distinct) + else + execute_simple_calculation(operation, column_name, distinct) + end + end + + def distinct_select?(column_name) + column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name) + end + + def operation_over_aggregate_column(column, operation, distinct) + operation == "count" ? column.count(distinct) : column.public_send(operation) + end + + def execute_simple_calculation(operation, column_name, distinct) # :nodoc: + if build_count_subquery?(operation, column_name, distinct) + # Shortcut when limit is zero. + return 0 if limit_value == 0 + + relation = self + query_builder = build_count_subquery(spawn, column_name, distinct) + else + # PostgreSQL doesn't like ORDER BY when there are no GROUP BY + relation = unscope(:order).distinct!(false) + + column = relation.aggregate_column(column_name) + select_value = operation_over_aggregate_column(column, operation, distinct) + select_value.distinct = true if operation == "sum" && distinct + + relation.select_values = [select_value] + + query_builder = relation.arel + end + + query_result = if relation.where_clause.contradiction? + if @async + FutureResult.wrap(ActiveRecord::Result.empty) + else + ActiveRecord::Result.empty + end + else + skip_query_cache_if_necessary do + model.with_connection do |c| + c.select_all(query_builder, "#{model.name} #{operation.capitalize}", async: @async) + end + end + end + + query_result.then do |result| + if operation != "count" + type = column.try(:type_caster) || + lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value + type = type.subtype if Enum::EnumType === type + end + + type_cast_calculated_value(result.cast_values.first, operation, type) + end + end + + def execute_grouped_calculation(operation, column_name, distinct) # :nodoc: + group_fields = group_values + group_fields = group_fields.uniq if group_fields.size > 1 + + if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym) + association = model._reflect_on_association(group_fields.first) + associated = association && association.belongs_to? # only count belongs_to associations + group_fields = Array(association.foreign_key) if associated + end + + relation = except(:group).distinct!(false) + group_fields = relation.arel_columns(group_fields) + + model.with_connection do |connection| + column_alias_tracker = ColumnAliasTracker.new(connection) + + group_aliases = group_fields.map { |field| + field = connection.visitor.compile(field) if Arel.arel_node?(field) + column_alias_tracker.alias_for(field.to_s.downcase) + } + group_columns = group_aliases.zip(group_fields) + + column = relation.aggregate_column(column_name) + column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}") + select_value = operation_over_aggregate_column(column, operation, distinct) + select_value.as(model.adapter_class.quote_column_name(column_alias)) + + select_values = [select_value] + select_values += self.select_values unless having_clause.empty? + + select_values.concat group_columns.map { |aliaz, field| + aliaz = model.adapter_class.quote_column_name(aliaz) + if field.respond_to?(:as) + field.as(aliaz) + else + "#{field} AS #{aliaz}" + end + } + + relation.group_values = group_fields + relation.select_values = select_values + + result = skip_query_cache_if_necessary do + connection.select_all(relation.arel, "#{model.name} #{operation.capitalize}", async: @async) + end + + result.then do |calculated_data| + if association + key_ids = calculated_data.collect { |row| row[group_aliases.first] } + key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids) + key_records = key_records.index_by(&:id) + end + + key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types| + types[aliaz] = col_name.try(:type_caster) || + type_for(col_name) do + calculated_data.column_types.fetch(aliaz, Type.default_value) + end + end + + hash_rows = calculated_data.cast_values(key_types).map! do |row| + calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i| + hash[col_name] = row[i] + end + end + + if operation != "count" + type = column.try(:type_caster) || + lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value + type = type.subtype if Enum::EnumType === type + end + + hash_rows.each_with_object({}) do |row, result| + key = group_aliases.map { |aliaz| row[aliaz] } + key = key.first if key.size == 1 + key = key_records[key] if associated + + result[key] = type_cast_calculated_value(row[column_alias], operation, type) + end + end + end + end + + def type_for(field, &block) + field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last + model.type_for_attribute(field_name, &block) + end + + def lookup_cast_type_from_join_dependencies(name, join_dependencies = build_join_dependencies) + each_join_dependencies(join_dependencies) do |join| + type = join.base_klass.attribute_types.fetch(name, nil) + return type if type + end + nil + end + + def type_cast_pluck_values(result, columns) + cast_types = if result.columns.size != columns.size + model.attribute_types + else + join_dependencies = nil + columns.map.with_index do |column, i| + column.try(:type_caster) || + model.attribute_types.fetch(name = result.columns[i]) do + join_dependencies ||= build_join_dependencies + lookup_cast_type_from_join_dependencies(name, join_dependencies) || + result.column_types[i] || Type.default_value + end + end + end + result.cast_values(cast_types) + end + + def type_cast_calculated_value(value, operation, type) + case operation + when "count" + value.to_i + when "sum" + type.deserialize(value || 0) + when "average" + case type.type + when :integer, :decimal + value&.to_d + else + type.deserialize(value) + end + else # "minimum", "maximum" + type.deserialize(value) + end + end + + def select_for_count + if select_values.empty? + :all + else + with_connection do |conn| + arel_columns(select_values).map { |column| conn.visitor.compile(column) }.join(", ") + end + end + end + + def build_count_subquery?(operation, column_name, distinct) + # SQLite and older MySQL does not support `COUNT DISTINCT` with `*` or + # multiple columns, so we need to use subquery for this. + operation == "count" && + (((column_name == :all || select_values.many?) && distinct) || has_limit_or_offset?) + end + + def build_count_subquery(relation, column_name, distinct) + if column_name == :all + column_alias = Arel.star + relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct + else + column_alias = Arel.sql("count_column") + relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ] + end + + subquery_alias = Arel.sql("subquery_for_count", retryable: true) + select_value = operation_over_aggregate_column(column_alias, "count", false) + + if column_name == :all + relation.unscope(:order).build_subquery(subquery_alias, select_value) + else + relation.build_subquery(subquery_alias, select_value) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/delegation.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/delegation.rb new file mode 100644 index 00000000..22937d3f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/delegation.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/delegation" + +module ActiveRecord + module Delegation # :nodoc: + class << self + def delegated_classes + [ + ActiveRecord::Relation, + ActiveRecord::Associations::CollectionProxy, + ActiveRecord::AssociationRelation, + ActiveRecord::DisableJoinsAssociationRelation, + ] + end + + def uncacheable_methods + @uncacheable_methods ||= ( + delegated_classes.flat_map(&:public_instance_methods) - ActiveRecord::Relation.public_instance_methods + ).to_set.freeze + end + end + + module DelegateCache # :nodoc: + @delegate_base_methods = true + singleton_class.attr_accessor :delegate_base_methods + + def relation_delegate_class(klass) + @relation_delegate_cache[klass] + end + + def initialize_relation_delegate_cache + @relation_delegate_cache = cache = {} + Delegation.delegated_classes.each do |klass| + delegate = Class.new(klass) { + include ClassSpecificRelation + } + include_relation_methods(delegate) + mangled_name = klass.name.gsub("::", "_") + const_set mangled_name, delegate + private_constant mangled_name + + cache[klass] = delegate + end + end + + def inherited(child_class) + child_class.initialize_relation_delegate_cache + super + end + + def generate_relation_method(method) + generated_relation_methods.generate_method(method) + end + + protected + def include_relation_methods(delegate) + superclass.include_relation_methods(delegate) unless base_class? + delegate.include generated_relation_methods + end + + private + def generated_relation_methods + @generated_relation_methods ||= GeneratedRelationMethods.new.tap do |mod| + const_set(:GeneratedRelationMethods, mod) + private_constant :GeneratedRelationMethods + end + end + end + + class GeneratedRelationMethods < Module # :nodoc: + MUTEX = Mutex.new + + def generate_method(method) + MUTEX.synchronize do + return if method_defined?(method) + + if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !::ActiveSupport::Delegation::RESERVED_METHOD_NAMES.include?(method.to_s) + module_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{method}(...) + scoping { model.#{method}(...) } + end + RUBY + else + define_method(method) do |*args, **kwargs, &block| + scoping { model.public_send(method, *args, **kwargs, &block) } + end + end + end + end + end + private_constant :GeneratedRelationMethods + + extend ActiveSupport::Concern + + # This module creates compiled delegation methods dynamically at runtime, which makes + # subsequent calls to that method faster by avoiding method_missing. The delegations + # may vary depending on the model of a relation, so we create a subclass of Relation + # for each different model, and the delegations are compiled into that subclass only. + + delegate :to_xml, :encode_with, :length, :each, :join, :intersect?, + :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of, + :to_sentence, :to_fs, :to_formatted_s, :as_json, + :shuffle, :split, :slice, :index, :rindex, to: :records + + delegate :primary_key, :with_connection, :connection, :table_name, :transaction, :sanitize_sql_like, :unscoped, :name, to: :model + + module ClassSpecificRelation # :nodoc: + extend ActiveSupport::Concern + + module ClassMethods # :nodoc: + def name + superclass.name + end + end + + private + def method_missing(method, ...) + if model.respond_to?(method) + if !DelegateCache.delegate_base_methods && Base.respond_to?(method) + # A common mistake in Active Record's own code is to call `ActiveRecord::Base` + # class methods on Association. It works because it's automatically delegated, but + # can introduce subtle bugs because it sets the global scope. + # We can't deprecate this behavior because gems might depend on it, however we + # can ban it from Active Record's own test suite to avoid regressions. + raise NotImplementedError, "Active Record code shouldn't rely on association delegation into ActiveRecord::Base methods" + elsif !Delegation.uncacheable_methods.include?(method) + model.generate_relation_method(method) + end + + scoping { model.public_send(method, ...) } + else + super + end + end + end + + module ClassMethods # :nodoc: + def create(model, ...) + relation_class_for(model).new(model, ...) + end + + private + def relation_class_for(model) + model.relation_delegate_class(self) + end + end + + private + def respond_to_missing?(method, _) + super || model.respond_to?(method) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/finder_methods.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/finder_methods.rb new file mode 100644 index 00000000..2c2a48de --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/finder_methods.rb @@ -0,0 +1,661 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/filters" + +module ActiveRecord + module FinderMethods + ONE_AS_ONE = "1 AS one" + + # Find by id - This can either be a specific id (ID), a list of ids (ID, ID, ID), or an array of ids ([ID, ID, ID]). + # `ID` refers to an "identifier". For models with a single-column primary key, `ID` will be a single value, + # and for models with a composite primary key, it will be an array of values. + # If one or more records cannot be found for the requested ids, then ActiveRecord::RecordNotFound will be raised. + # If the primary key is an integer, find by id coerces its arguments by using +to_i+. + # + # Person.find(1) # returns the object for ID = 1 + # Person.find("1") # returns the object for ID = 1 + # Person.find("31-sarah") # returns the object for ID = 31 + # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6) + # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17), or with composite primary key [7, 17] + # Person.find([1]) # returns an array for the object with ID = 1 + # Person.where("administrator = 1").order("created_on DESC").find(1) + # + # ==== Find a record for a composite primary key model + # TravelRoute.primary_key = [:origin, :destination] + # + # TravelRoute.find(["Ottawa", "London"]) + # => # + # + # TravelRoute.find([["Paris", "Montreal"]]) + # => [#] + # + # TravelRoute.find(["New York", "Las Vegas"], ["New York", "Portland"]) + # => [ + # #, + # # + # ] + # + # TravelRoute.find([["Berlin", "London"], ["Barcelona", "Lisbon"]]) + # => [ + # #, + # # + # ] + # + # NOTE: The returned records are in the same order as the ids you provide. + # If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where + # method and provide an explicit ActiveRecord::QueryMethods#order option. + # But ActiveRecord::QueryMethods#where method doesn't raise ActiveRecord::RecordNotFound. + # + # ==== Find with lock + # + # Example for find with a lock: Imagine two concurrent transactions: + # each will read person.visits == 2, add 1 to it, and save, resulting + # in two saves of person.visits = 3. By locking the row, the second + # transaction has to wait until the first is finished; we get the + # expected person.visits == 4. + # + # Person.transaction do + # person = Person.lock(true).find(1) + # person.visits += 1 + # person.save! + # end + # + # ==== Variations of #find + # + # Person.where(name: 'Spartacus', rating: 4) + # # returns a chainable list (which can be empty). + # + # Person.find_by(name: 'Spartacus', rating: 4) + # # returns the first item or nil. + # + # Person.find_or_initialize_by(name: 'Spartacus', rating: 4) + # # returns the first item or returns a new instance (requires you call .save to persist against the database). + # + # Person.find_or_create_by(name: 'Spartacus', rating: 4) + # # returns the first item or creates it and returns it. + # + # ==== Alternatives for #find + # + # Person.where(name: 'Spartacus', rating: 4).exists?(conditions = :none) + # # returns a boolean indicating if any record with the given conditions exist. + # + # Person.where(name: 'Spartacus', rating: 4).select("field1, field2, field3") + # # returns a chainable list of instances with only the mentioned fields. + # + # Person.where(name: 'Spartacus', rating: 4).ids + # # returns an Array of ids. + # + # Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2) + # # returns an Array of the required fields. + # + # ==== Edge Cases + # + # Person.find(37) # raises ActiveRecord::RecordNotFound exception if the record with the given ID does not exist. + # Person.find([37]) # raises ActiveRecord::RecordNotFound exception if the record with the given ID in the input array does not exist. + # Person.find(nil) # raises ActiveRecord::RecordNotFound exception if the argument is nil. + # Person.find([]) # returns an empty array if the argument is an empty array. + # Person.find # raises ActiveRecord::RecordNotFound exception if the argument is not provided. + def find(*args) + return super if block_given? + find_with_ids(*args) + end + + # Finds the first record matching the specified conditions. There + # is no implied ordering so if order matters, you should specify it + # yourself. + # + # If no record is found, returns nil. + # + # Post.find_by name: 'Spartacus', rating: 4 + # Post.find_by "published_at < ?", 2.weeks.ago + def find_by(arg, *args) + where(arg, *args).take + end + + # Like #find_by, except that if no record is found, raises + # an ActiveRecord::RecordNotFound error. + def find_by!(arg, *args) + where(arg, *args).take! + end + + # Gives a record (or N records if a parameter is supplied) without any implied + # order. The order will depend on the database implementation. + # If an order is supplied it will be respected. + # + # Person.take # returns an object fetched by SELECT * FROM people LIMIT 1 + # Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5 + # Person.where(["name LIKE '%?'", name]).take + def take(limit = nil) + limit ? find_take_with_limit(limit) : find_take + end + + # Same as #take but raises ActiveRecord::RecordNotFound if no record + # is found. Note that #take! accepts no arguments. + def take! + take || raise_record_not_found_exception! + end + + # Finds the sole matching record. Raises ActiveRecord::RecordNotFound if no + # record is found. Raises ActiveRecord::SoleRecordExceeded if more than one + # record is found. + # + # Product.where(["price = %?", price]).sole + def sole + found, undesired = first(2) + + if found.nil? + raise_record_not_found_exception! + elsif undesired.nil? + found + else + raise ActiveRecord::SoleRecordExceeded.new(model) + end + end + + # Finds the sole matching record. Raises ActiveRecord::RecordNotFound if no + # record is found. Raises ActiveRecord::SoleRecordExceeded if more than one + # record is found. + # + # Product.find_sole_by(["price = %?", price]) + def find_sole_by(arg, *args) + where(arg, *args).sole + end + + # Find the first record (or first N records if a parameter is supplied). + # If no order is defined it will order by primary key. + # + # Person.first # returns the first object fetched by SELECT * FROM people ORDER BY people.id LIMIT 1 + # Person.where(["user_name = ?", user_name]).first + # Person.where(["user_name = :u", { u: user_name }]).first + # Person.order("created_on DESC").offset(5).first + # Person.first(3) # returns the first three objects fetched by SELECT * FROM people ORDER BY people.id LIMIT 3 + # + def first(limit = nil) + if limit + find_nth_with_limit(0, limit) + else + find_nth 0 + end + end + + # Same as #first but raises ActiveRecord::RecordNotFound if no record + # is found. Note that #first! accepts no arguments. + def first! + first || raise_record_not_found_exception! + end + + # Find the last record (or last N records if a parameter is supplied). + # If no order is defined it will order by primary key. + # + # Person.last # returns the last object fetched by SELECT * FROM people + # Person.where(["user_name = ?", user_name]).last + # Person.order("created_on DESC").offset(5).last + # Person.last(3) # returns the last three objects fetched by SELECT * FROM people. + # + # Take note that in that last case, the results are sorted in ascending order: + # + # [#, #, #] + # + # and not: + # + # [#, #, #] + def last(limit = nil) + return find_last(limit) if loaded? || has_limit_or_offset? + + result = ordered_relation.limit(limit) + result = result.reverse_order! + + limit ? result.reverse : result.first + end + + # Same as #last but raises ActiveRecord::RecordNotFound if no record + # is found. Note that #last! accepts no arguments. + def last! + last || raise_record_not_found_exception! + end + + # Find the second record. + # If no order is defined it will order by primary key. + # + # Person.second # returns the second object fetched by SELECT * FROM people + # Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4) + # Person.where(["user_name = :u", { u: user_name }]).second + def second + find_nth 1 + end + + # Same as #second but raises ActiveRecord::RecordNotFound if no record + # is found. + def second! + second || raise_record_not_found_exception! + end + + # Find the third record. + # If no order is defined it will order by primary key. + # + # Person.third # returns the third object fetched by SELECT * FROM people + # Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5) + # Person.where(["user_name = :u", { u: user_name }]).third + def third + find_nth 2 + end + + # Same as #third but raises ActiveRecord::RecordNotFound if no record + # is found. + def third! + third || raise_record_not_found_exception! + end + + # Find the fourth record. + # If no order is defined it will order by primary key. + # + # Person.fourth # returns the fourth object fetched by SELECT * FROM people + # Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6) + # Person.where(["user_name = :u", { u: user_name }]).fourth + def fourth + find_nth 3 + end + + # Same as #fourth but raises ActiveRecord::RecordNotFound if no record + # is found. + def fourth! + fourth || raise_record_not_found_exception! + end + + # Find the fifth record. + # If no order is defined it will order by primary key. + # + # Person.fifth # returns the fifth object fetched by SELECT * FROM people + # Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7) + # Person.where(["user_name = :u", { u: user_name }]).fifth + def fifth + find_nth 4 + end + + # Same as #fifth but raises ActiveRecord::RecordNotFound if no record + # is found. + def fifth! + fifth || raise_record_not_found_exception! + end + + # Find the forty-second record. Also known as accessing "the reddit". + # If no order is defined it will order by primary key. + # + # Person.forty_two # returns the forty-second object fetched by SELECT * FROM people + # Person.offset(3).forty_two # returns the forty-second object from OFFSET 3 (which is OFFSET 44) + # Person.where(["user_name = :u", { u: user_name }]).forty_two + def forty_two + find_nth 41 + end + + # Same as #forty_two but raises ActiveRecord::RecordNotFound if no record + # is found. + def forty_two! + forty_two || raise_record_not_found_exception! + end + + # Find the third-to-last record. + # If no order is defined it will order by primary key. + # + # Person.third_to_last # returns the third-to-last object fetched by SELECT * FROM people + # Person.offset(3).third_to_last # returns the third-to-last object from OFFSET 3 + # Person.where(["user_name = :u", { u: user_name }]).third_to_last + def third_to_last + find_nth_from_last 3 + end + + # Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record + # is found. + def third_to_last! + third_to_last || raise_record_not_found_exception! + end + + # Find the second-to-last record. + # If no order is defined it will order by primary key. + # + # Person.second_to_last # returns the second-to-last object fetched by SELECT * FROM people + # Person.offset(3).second_to_last # returns the second-to-last object from OFFSET 3 + # Person.where(["user_name = :u", { u: user_name }]).second_to_last + def second_to_last + find_nth_from_last 2 + end + + # Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record + # is found. + def second_to_last! + second_to_last || raise_record_not_found_exception! + end + + # Returns true if a record exists in the table that matches the +id+ or + # conditions given, or false otherwise. The argument can take six forms: + # + # * Integer - Finds the record with this primary key. + # * String - Finds the record with a primary key corresponding to this + # string (such as '5'). + # * Array - Finds the record that matches these +where+-style conditions + # (such as ['name LIKE ?', "%#{query}%"]). + # * Hash - Finds the record that matches these +where+-style conditions + # (such as {name: 'David'}). + # * +false+ - Returns always +false+. + # * No args - Returns +false+ if the relation is empty, +true+ otherwise. + # + # For more information about specifying conditions as a hash or array, + # see the Conditions section in the introduction to ActiveRecord::Base. + # + # Note: You can't pass in a condition as a string (like name = + # 'Jamie'), since it would be sanitized and then queried against + # the primary key column, like id = 'name = \'Jamie\''. + # + # Person.exists?(5) + # Person.exists?('5') + # Person.exists?(['name LIKE ?', "%#{query}%"]) + # Person.exists?(id: [1, 4, 8]) + # Person.exists?(name: 'David') + # Person.exists?(false) + # Person.exists? + # Person.where(name: 'Spartacus', rating: 4).exists? + def exists?(conditions = :none) + return false if @none + + if Base === conditions + raise ArgumentError, <<-MSG.squish + You are passing an instance of ActiveRecord::Base to `exists?`. + Please pass the id of the object by calling `.id`. + MSG + end + + return false if !conditions || limit_value == 0 + + if eager_loading? + relation = apply_join_dependency(eager_loading: false) + return relation.exists?(conditions) + end + + relation = construct_relation_for_exists(conditions) + return false if relation.where_clause.contradiction? + + skip_query_cache_if_necessary do + with_connection do |c| + c.select_rows(relation.arel, "#{model.name} Exists?").size == 1 + end + end + end + + # Returns true if the relation contains the given record or false otherwise. + # + # No query is performed if the relation is loaded; the given record is + # compared to the records in memory. If the relation is unloaded, an + # efficient existence query is performed, as in #exists?. + def include?(record) + # The existing implementation relies on receiving an Active Record instance as the input parameter named record. + # Any non-Active Record object passed to this implementation is guaranteed to return `false`. + return false unless record.is_a?(model) + + if loaded? || offset_value || limit_value || having_clause.any? + records.include?(record) + else + id = if record.class.composite_primary_key? + record.class.primary_key.zip(record.id).to_h + else + record.id + end + + exists?(id) + end + end + + alias :member? :include? + + # This method is called whenever no records are found with either a single + # id or multiple ids and raises an ActiveRecord::RecordNotFound exception. + # + # The error message is different depending on whether a single id or + # multiple ids are provided. If multiple ids are provided, then the number + # of results obtained should be provided in the +result_size+ argument and + # the expected number of results should be provided in the +expected_size+ + # argument. + def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key, not_found_ids = nil) # :nodoc: + conditions = " [#{arel.where_sql(model)}]" unless where_clause.empty? + + name = model.name + + if ids.nil? + error = +"Couldn't find #{name}" + error << " with#{conditions}" if conditions + raise RecordNotFound.new(error, name, key) + elsif Array.wrap(ids).size == 1 + error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}" + raise RecordNotFound.new(error, name, key, ids) + else + error = +"Couldn't find all #{name.pluralize} with '#{key}': " + error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})." + error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids + raise RecordNotFound.new(error, name, key, ids) + end + end + + private + def construct_relation_for_exists(conditions) + conditions = sanitize_forbidden_attributes(conditions) + + if distinct_value && offset_value + relation = except(:order).limit!(1) + else + relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1) + end + + case conditions + when Array, Hash + relation.where!(conditions) unless conditions.empty? + else + relation.where!(primary_key => conditions) unless conditions == :none + end + + relation + end + + def apply_join_dependency(eager_loading: group_values.empty?) + join_dependency = construct_join_dependency( + eager_load_values | includes_values, Arel::Nodes::OuterJoin + ) + relation = except(:includes, :eager_load, :preload).joins!(join_dependency) + + if eager_loading && has_limit_or_offset? && !( + using_limitable_reflections?(join_dependency.reflections) && + using_limitable_reflections?( + construct_join_dependency( + select_association_list(joins_values).concat( + select_association_list(left_outer_joins_values) + ), nil + ).reflections + ) + ) + relation = skip_query_cache_if_necessary do + model.with_connection do |c| + c.distinct_relation_for_primary_key(relation) + end + end + end + + if block_given? + yield relation, join_dependency + else + relation + end + end + + def using_limitable_reflections?(reflections) + reflections.none?(&:collection?) + end + + def find_with_ids(*ids) + raise UnknownPrimaryKey.new(model) if primary_key.nil? + + expects_array = if model.composite_primary_key? + ids.first.first.is_a?(Array) + else + ids.first.is_a?(Array) + end + + return [] if expects_array && ids.first.empty? + + ids = ids.first if expects_array + + ids = ids.compact.uniq + + model_name = model.name + + case ids.size + when 0 + error_message = "Couldn't find #{model_name} without an ID" + raise RecordNotFound.new(error_message, model_name, primary_key) + when 1 + result = find_one(ids.first) + expects_array ? [ result ] : result + else + find_some(ids) + end + end + + def find_one(id) + if ActiveRecord::Base === id + raise ArgumentError, <<-MSG.squish + You are passing an instance of ActiveRecord::Base to `find`. + Please pass the id of the object by calling `.id`. + MSG + end + + relation = if model.composite_primary_key? + where(primary_key.zip(id).to_h) + else + where(primary_key => id) + end + + record = relation.take + + raise_record_not_found_exception!(id, 0, 1) unless record + + record + end + + def find_some(ids) + return find_some_ordered(ids) unless order_values.present? + + relation = where(primary_key => ids) + relation = relation.select(table[primary_key]) unless select_values.empty? + result = relation.to_a + + expected_size = + if limit_value && ids.size > limit_value + limit_value + else + ids.size + end + + # 11 ids with limit 3, offset 9 should give 2 results. + if offset_value && (ids.size - offset_value < expected_size) + expected_size = ids.size - offset_value + end + + if result.size == expected_size + result + else + raise_record_not_found_exception!(ids, result.size, expected_size) + end + end + + def find_some_ordered(ids) + ids = ids.slice(offset_value || 0, limit_value || ids.size) || [] + + relation = except(:limit, :offset) + relation = relation.where(primary_key => ids) + relation = relation.select(table[primary_key]) unless select_values.empty? + result = relation.records + + if result.size == ids.size + result.in_order_of(:id, ids.map { |id| model.type_for_attribute(primary_key).cast(id) }) + else + raise_record_not_found_exception!(ids, result.size, ids.size) + end + end + + def find_take + if loaded? + records.first + else + @take ||= limit(1).records.first + end + end + + def find_take_with_limit(limit) + if loaded? + records.take(limit) + else + limit(limit).to_a + end + end + + def find_nth(index) + @offsets ||= {} + @offsets[index] ||= find_nth_with_limit(index, 1).first + end + + def find_nth_with_limit(index, limit) + if loaded? + records[index, limit] || [] + else + relation = ordered_relation + + if limit_value + limit = [limit_value - index, limit].min + end + + if limit > 0 + relation = relation.offset((offset_value || 0) + index) unless index.zero? + relation.limit(limit).to_a + else + [] + end + end + end + + def find_nth_from_last(index) + if loaded? + records[-index] + else + relation = ordered_relation + + if relation.order_values.empty? || relation.has_limit_or_offset? + relation.records[-index] + else + relation.reverse_order.offset(index - 1).first + end + end + end + + def find_last(limit) + limit ? records.last(limit) : records.last + end + + def ordered_relation + if order_values.empty? && (model.implicit_order_column || !model.query_constraints_list.nil? || primary_key) + order(_order_columns.map { |column| table[column].asc }) + else + self + end + end + + def _order_columns + oc = [] + + oc << model.implicit_order_column if model.implicit_order_column + oc << model.query_constraints_list if model.query_constraints_list + + if model.primary_key && model.query_constraints_list.nil? + oc << model.primary_key + end + + oc.flatten.uniq.compact + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/from_clause.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/from_clause.rb new file mode 100644 index 00000000..d738154a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/from_clause.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module ActiveRecord + class Relation + class FromClause # :nodoc: + attr_reader :value, :name + + def initialize(value, name) + @value = value + @name = name + end + + def merge(other) + self + end + + def empty? + value.nil? + end + + def ==(other) + self.class == other.class && value == other.value && name == other.name + end + + def self.empty + @empty ||= new(nil, nil).freeze + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/merger.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/merger.rb new file mode 100644 index 00000000..80c10734 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/merger.rb @@ -0,0 +1,192 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/keys" + +module ActiveRecord + class Relation + class HashMerger # :nodoc: + attr_reader :relation, :hash + + def initialize(relation, hash) + hash.assert_valid_keys(*Relation::VALUE_METHODS) + + @relation = relation + @hash = hash + end + + def merge + Merger.new(relation, other).merge + end + + # Applying values to a relation has some side effects. E.g. + # interpolation might take place for where values. So we should + # build a relation to merge in rather than directly merging + # the values. + def other + other = Relation.create( + relation.model, + table: relation.table, + predicate_builder: relation.predicate_builder + ) + hash.each do |k, v| + k = :_select if k == :select + if Array === v + other.public_send("#{k}!", *v) + else + other.public_send("#{k}!", v) + end + end + other + end + end + + class Merger # :nodoc: + attr_reader :relation, :values, :other + + def initialize(relation, other) + @relation = relation + @values = other.values + @other = other + end + + NORMAL_VALUES = Relation::VALUE_METHODS - Relation::CLAUSE_METHODS - + [ + :select, :includes, :preload, :joins, :left_outer_joins, + :order, :reverse_order, :lock, :create_with, :reordering + ] + + def merge + NORMAL_VALUES.each do |name| + value = values[name] + # The unless clause is here mostly for performance reasons (since the `send` call might be moderately + # expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that + # `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values + # don't fall through the cracks. + unless value.nil? || (value.blank? && false != value) + relation.public_send(:"#{name}!", *value) + end + end + + relation.none! if other.null_relation? + + merge_select_values + merge_multi_values + merge_single_values + merge_clauses + merge_preloads + merge_joins + merge_outer_joins + + relation + end + + private + def merge_select_values + return if other.select_values.empty? + + if other.model == relation.model + relation.select_values |= other.select_values + else + relation.select_values |= other.instance_eval do + arel_columns(select_values) + end + end + end + + def merge_preloads + return if other.preload_values.empty? && other.includes_values.empty? + + if other.model == relation.model + relation.preload_values |= other.preload_values unless other.preload_values.empty? + relation.includes_values |= other.includes_values unless other.includes_values.empty? + else + reflection = relation.model.reflect_on_all_associations.find do |r| + r.class_name == other.model.name + end || return + + unless other.preload_values.empty? + relation.preload! reflection.name => other.preload_values + end + + unless other.includes_values.empty? + relation.includes! reflection.name => other.includes_values + end + end + end + + def merge_joins + return if other.joins_values.empty? + + if other.model == relation.model + relation.joins_values |= other.joins_values + else + associations, others = other.joins_values.partition do |join| + case join + when Hash, Symbol, Array; true + end + end + + join_dependency = other.construct_join_dependency( + associations, Arel::Nodes::InnerJoin + ) + relation.joins!(join_dependency, *others) + end + end + + def merge_outer_joins + return if other.left_outer_joins_values.empty? + + if other.model == relation.model + relation.left_outer_joins_values |= other.left_outer_joins_values + else + associations, others = other.left_outer_joins_values.partition do |join| + case join + when Hash, Symbol, Array; true + end + end + + join_dependency = other.construct_join_dependency( + associations, Arel::Nodes::OuterJoin + ) + relation.left_outer_joins!(join_dependency, *others) + end + end + + def merge_multi_values + if other.reordering_value + # override any order specified in the original relation + relation.reorder!(*other.order_values) + elsif other.order_values.any? + # merge in order_values from relation + relation.order!(*other.order_values) + end + + extensions = other.extensions - relation.extensions + relation.extending!(*extensions) if extensions.any? + end + + def merge_single_values + relation.lock_value ||= other.lock_value if other.lock_value + + unless other.create_with_value.blank? + relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value) + end + end + + def merge_clauses + relation.from_clause = other.from_clause if replace_from_clause? + + where_clause = relation.where_clause.merge(other.where_clause) + relation.where_clause = where_clause unless where_clause.empty? + + having_clause = relation.having_clause.merge(other.having_clause) + relation.having_clause = having_clause unless having_clause.empty? + end + + def replace_from_clause? + relation.from_clause.empty? && !other.from_clause.empty? && + relation.model.base_class == other.model.base_class + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder.rb new file mode 100644 index 00000000..b24ce400 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder.rb @@ -0,0 +1,189 @@ +# frozen_string_literal: true + +module ActiveRecord + class PredicateBuilder # :nodoc: + require "active_record/relation/predicate_builder/array_handler" + require "active_record/relation/predicate_builder/basic_object_handler" + require "active_record/relation/predicate_builder/range_handler" + require "active_record/relation/predicate_builder/relation_handler" + require "active_record/relation/predicate_builder/association_query_value" + require "active_record/relation/predicate_builder/polymorphic_array_value" + + def initialize(table) + @table = table + @handlers = [] + + register_handler(BasicObject, BasicObjectHandler.new(self)) + register_handler(Range, RangeHandler.new(self)) + register_handler(Relation, RelationHandler.new) + register_handler(Array, ArrayHandler.new(self)) + register_handler(Set, ArrayHandler.new(self)) + end + + def build_from_hash(attributes, &block) + attributes = convert_dot_notation_to_hash(attributes) + expand_from_hash(attributes, &block) + end + + def self.references(attributes) + attributes.each_with_object([]) do |(key, value), result| + if value.is_a?(Hash) + result << Arel.sql(key, retryable: true) + elsif (idx = key.rindex(".")) + result << Arel.sql(key[0, idx], retryable: true) + end + end + end + + # Define how a class is converted to Arel nodes when passed to +where+. + # The handler can be any object that responds to +call+, and will be used + # for any value that +===+ the class given. For example: + # + # MyCustomDateRange = Struct.new(:start, :end) + # handler = proc do |column, range| + # Arel::Nodes::Between.new(column, + # Arel::Nodes::And.new([range.start, range.end]) + # ) + # end + # ActiveRecord::PredicateBuilder.new("users").register_handler(MyCustomDateRange, handler) + def register_handler(klass, handler) + @handlers.unshift([klass, handler]) + end + + def [](attr_name, value, operator = nil) + build(table.arel_table[attr_name], value, operator) + end + + def build(attribute, value, operator = nil) + value = value.id if value.respond_to?(:id) + if operator ||= table.type(attribute.name).force_equality?(value) && :eq + bind = build_bind_attribute(attribute.name, value) + attribute.public_send(operator, bind) + else + handler_for(value).call(attribute, value) + end + end + + def build_bind_attribute(column_name, value) + Relation::QueryAttribute.new(column_name, value, table.type(column_name)) + end + + def resolve_arel_attribute(table_name, column_name, &block) + table.associated_table(table_name, &block).arel_table[column_name] + end + + def with(table) + other = dup + other.table = table + other + end + + protected + attr_writer :table + + def expand_from_hash(attributes, &block) + return ["1=0"] if attributes.empty? + + attributes.flat_map do |key, value| + if key.is_a?(Array) && key.size == 1 + key = key.first + value = value.flatten + end + + if key.is_a?(Array) + queries = Array(value).map do |ids_set| + raise ArgumentError, "Expected corresponding value for #{key} to be an Array" unless ids_set.is_a?(Array) + expand_from_hash(key.zip(ids_set).to_h) + end + grouping_queries(queries) + elsif value.is_a?(Hash) && !table.has_column?(key) + table.associated_table(key, &block) + .predicate_builder.expand_from_hash(value.stringify_keys) + elsif table.associated_with?(key) + # Find the foreign key when using queries such as: + # Post.where(author: author) + # + # For polymorphic relationships, find the foreign key and type: + # PriceEstimate.where(estimate_of: treasure) + associated_table = table.associated_table(key) + if associated_table.polymorphic_association? + value = [value] unless value.is_a?(Array) + klass = PolymorphicArrayValue + elsif associated_table.through_association? + next associated_table.predicate_builder.expand_from_hash( + associated_table.primary_key => value + ) + end + + klass ||= AssociationQueryValue + queries = klass.new(associated_table, value).queries.map! do |query| + # If the query produced is identical to attributes don't go any deeper. + # Prevents stack level too deep errors when association and foreign_key are identical. + query == attributes ? self[key, value] : expand_from_hash(query) + end + + grouping_queries(queries) + elsif table.aggregated_with?(key) + mapping = table.reflect_on_aggregation(key).mapping + values = value.nil? ? [nil] : Array.wrap(value) + if mapping.length == 1 || values.empty? + column_name, aggr_attr = mapping.first + values = values.map do |object| + object.respond_to?(aggr_attr) ? object.public_send(aggr_attr) : object + end + self[column_name, values] + else + queries = values.map do |object| + mapping.map do |field_attr, aggregate_attr| + self[field_attr, object.try!(aggregate_attr)] + end + end + + grouping_queries(queries) + end + else + self[key, value] + end + end + end + + private + attr_reader :table + + def grouping_queries(queries) + if queries.one? + queries.first + else + queries.map! { |query| query.reduce(&:and) } + queries = Arel::Nodes::Or.new(queries) + Arel::Nodes::Grouping.new(queries) + end + end + + def convert_dot_notation_to_hash(attributes) + attributes.each_with_object({}) do |(key, value), converted| + if value.is_a?(Hash) + if (existing = converted[key]) + existing.merge!(value) + else + converted[key] = value.dup + end + elsif (idx = key.rindex(".")) + table_name, column_name = key[0, idx], key[idx + 1, key.length] + + if (existing = converted[table_name]) + existing[column_name] = value + else + converted[table_name] = { column_name => value } + end + else + converted[key] = value + end + end + end + + def handler_for(object) + @handlers.detect { |klass, _| klass === object }.last + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder/array_handler.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder/array_handler.rb new file mode 100644 index 00000000..c8455799 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder/array_handler.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/extract" + +module ActiveRecord + class PredicateBuilder + class ArrayHandler # :nodoc: + def initialize(predicate_builder) + @predicate_builder = predicate_builder + end + + def call(attribute, value) + return attribute.in([]) if value.empty? + + values = value.map { |x| x.is_a?(Base) ? x.id : x } + nils = values.compact! + ranges = values.extract! { |v| v.is_a?(Range) } + + values_predicate = + case values.length + when 0 then NullPredicate + when 1 then predicate_builder.build(attribute, values.first) + else Arel::Nodes::HomogeneousIn.new(values, attribute, :in) + end + + if nils + values_predicate = values_predicate.or(attribute.eq(nil)) + end + + if ranges.empty? + values_predicate + else + array_predicates = ranges.map! { |range| predicate_builder.build(attribute, range) } + array_predicates.inject(values_predicate, &:or) + end + end + + private + attr_reader :predicate_builder + + module NullPredicate # :nodoc: + def self.or(other) + other + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder/association_query_value.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder/association_query_value.rb new file mode 100644 index 00000000..af61dc34 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder/association_query_value.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module ActiveRecord + class PredicateBuilder + class AssociationQueryValue # :nodoc: + def initialize(associated_table, value) + @associated_table = associated_table + @value = value + end + + def queries + if associated_table.join_foreign_key.is_a?(Array) + id_list = ids + id_list = id_list.pluck(primary_key) if id_list.is_a?(Relation) + + id_list.map { |ids_set| associated_table.join_foreign_key.zip(ids_set).to_h } + else + [ associated_table.join_foreign_key => ids ] + end + end + + private + attr_reader :associated_table, :value + + def ids + case value + when Relation + relation = value + relation = relation.select(primary_key) if select_clause? + relation = relation.where(primary_type => polymorphic_name) if polymorphic_clause? + relation + when Array + value.map { |v| convert_to_id(v) } + else + [convert_to_id(value)] + end + end + + def primary_key + associated_table.join_primary_key + end + + def primary_type + associated_table.join_primary_type + end + + def polymorphic_name + associated_table.polymorphic_name_association + end + + def select_clause? + value.select_values.empty? + end + + def polymorphic_clause? + primary_type && !value.where_values_hash.has_key?(primary_type) + end + + def convert_to_id(value) + if primary_key.is_a?(Array) + primary_key.map do |attribute| + next nil if value.nil? + + if attribute == "id" + value.id_value + else + value.public_send(attribute) + end + end + elsif value.respond_to?(primary_key) + value.public_send(primary_key) + else + value + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder/basic_object_handler.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder/basic_object_handler.rb new file mode 100644 index 00000000..e8c9f608 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder/basic_object_handler.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module ActiveRecord + class PredicateBuilder + class BasicObjectHandler # :nodoc: + def initialize(predicate_builder) + @predicate_builder = predicate_builder + end + + def call(attribute, value) + bind = predicate_builder.build_bind_attribute(attribute.name, value) + attribute.eq(bind) + end + + private + attr_reader :predicate_builder + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb new file mode 100644 index 00000000..7179d6cc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module ActiveRecord + class PredicateBuilder + class PolymorphicArrayValue # :nodoc: + def initialize(associated_table, values) + @associated_table = associated_table + @values = values + end + + def queries + return [ associated_table.join_foreign_key => values ] if values.empty? + + type_to_ids_mapping.map do |type, ids| + query = {} + query[associated_table.join_foreign_type] = type if type + query[associated_table.join_foreign_key] = ids + query + end + end + + private + attr_reader :associated_table, :values + + def type_to_ids_mapping + default_hash = Hash.new { |hsh, key| hsh[key] = [] } + values.each_with_object(default_hash) do |value, hash| + hash[klass(value)&.polymorphic_name] << convert_to_id(value) + end + end + + def primary_key(value) + associated_table.join_primary_key(klass(value)) + end + + def klass(value) + if value.is_a?(Base) + value.class + elsif value.is_a?(Relation) + value.model + end + end + + def convert_to_id(value) + if value.is_a?(Base) + primary_key = primary_key(value) + if primary_key.is_a?(Array) + primary_key.map { |column| value._read_attribute(column) } + else + value._read_attribute(primary_key) + end + elsif value.is_a?(Relation) + value.select(primary_key(value)) + else + value + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder/range_handler.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder/range_handler.rb new file mode 100644 index 00000000..2ea27c84 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder/range_handler.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module ActiveRecord + class PredicateBuilder + class RangeHandler # :nodoc: + RangeWithBinds = Struct.new(:begin, :end, :exclude_end?) + + def initialize(predicate_builder) + @predicate_builder = predicate_builder + end + + def call(attribute, value) + begin_bind = predicate_builder.build_bind_attribute(attribute.name, value.begin) + end_bind = predicate_builder.build_bind_attribute(attribute.name, value.end) + attribute.between(RangeWithBinds.new(begin_bind, end_bind, value.exclude_end?)) + end + + private + attr_reader :predicate_builder + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder/relation_handler.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder/relation_handler.rb new file mode 100644 index 00000000..b43a3429 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/predicate_builder/relation_handler.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module ActiveRecord + class PredicateBuilder + class RelationHandler # :nodoc: + def call(attribute, value) + if value.eager_loading? + value = value.send(:apply_join_dependency) + end + + if value.select_values.empty? + model = value.model + if model.composite_primary_key? + raise ArgumentError, "Cannot map composite primary key #{model.primary_key} to #{attribute.name}" + else + value = value.select(value.table[model.primary_key]) + end + end + + attribute.in(value.arel) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/query_attribute.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/query_attribute.rb new file mode 100644 index 00000000..03551af8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/query_attribute.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require "active_model/attribute" + +module ActiveRecord + class Relation + class QueryAttribute < ActiveModel::Attribute # :nodoc: + def initialize(...) + super + + # The query attribute value may be mutated before we actually "compile" the query. + # To avoid that if the type uses a serializer we eagerly compute the value for database + if value_before_type_cast.is_a?(StatementCache::Substitute) + # we don't need to serialize StatementCache::Substitute + elsif @type.serialized? + value_for_database + elsif @type.mutable? # If the type is simply mutable, we deep_dup it. + @value_before_type_cast = @value_before_type_cast.deep_dup + end + end + + def type_cast(value) + value + end + + def value_for_database + @value_for_database = _value_for_database unless defined?(@value_for_database) + @value_for_database + end + + def with_cast_value(value) + QueryAttribute.new(name, value, type) + end + + def nil? + unless value_before_type_cast.is_a?(StatementCache::Substitute) + value_before_type_cast.nil? || + (type.respond_to?(:subtype) || type.respond_to?(:normalizer)) && serializable? && value_for_database.nil? + end + end + + def infinite? + infinity?(value_before_type_cast) || serializable? && infinity?(value_for_database) + end + + def unboundable? + unless defined?(@_unboundable) + serializable? { |value| @_unboundable = value <=> 0 } && @_unboundable = nil + end + @_unboundable + end + + def ==(other) + super && value_for_database == other.value_for_database + end + alias eql? == + + def hash + [self.class, name, value_for_database, type].hash + end + + private + def infinity?(value) + value.respond_to?(:infinite?) && value.infinite? + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/query_methods.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/query_methods.rb new file mode 100644 index 00000000..bc9b520e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/query_methods.rb @@ -0,0 +1,2279 @@ +# frozen_string_literal: true + +require "active_record/relation/from_clause" +require "active_record/relation/query_attribute" +require "active_record/relation/where_clause" +require "active_support/core_ext/array/wrap" + +module ActiveRecord + module QueryMethods + include ActiveModel::ForbiddenAttributesProtection + + # +WhereChain+ objects act as placeholder for queries in which +where+ does not have any parameter. + # In this case, +where+ can be chained to return a new relation. + class WhereChain + def initialize(scope) # :nodoc: + @scope = scope + end + + # Returns a new relation expressing WHERE + NOT condition according to + # the conditions in the arguments. + # + # #not accepts conditions as a string, array, or hash. See QueryMethods#where for + # more details on each format. + # + # User.where.not("name = 'Jon'") + # # SELECT * FROM users WHERE NOT (name = 'Jon') + # + # User.where.not(["name = ?", "Jon"]) + # # SELECT * FROM users WHERE NOT (name = 'Jon') + # + # User.where.not(name: "Jon") + # # SELECT * FROM users WHERE name != 'Jon' + # + # User.where.not(name: nil) + # # SELECT * FROM users WHERE name IS NOT NULL + # + # User.where.not(name: %w(Ko1 Nobu)) + # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu') + # + # User.where.not(name: "Jon", role: "admin") + # # SELECT * FROM users WHERE NOT (name = 'Jon' AND role = 'admin') + # + # If there is a non-nil condition on a nullable column in the hash condition, the records that have + # nil values on the nullable column won't be returned. + # User.create!(nullable_country: nil) + # User.where.not(nullable_country: "UK") + # # SELECT * FROM users WHERE NOT (nullable_country = 'UK') + # # => [] + def not(opts, *rest) + where_clause = @scope.send(:build_where_clause, opts, rest) + + @scope.where_clause += where_clause.invert + + @scope + end + + # Returns a new relation with joins and where clause to identify + # associated relations. + # + # For example, posts that are associated to a related author: + # + # Post.where.associated(:author) + # # SELECT "posts".* FROM "posts" + # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id" + # # WHERE "authors"."id" IS NOT NULL + # + # Additionally, multiple relations can be combined. This will return posts + # associated to both an author and any comments: + # + # Post.where.associated(:author, :comments) + # # SELECT "posts".* FROM "posts" + # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id" + # # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id" + # # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL + # + # You can define join type in the scope and +associated+ will not use `JOIN` by default. + # + # Post.left_joins(:author).where.associated(:author) + # # SELECT "posts".* FROM "posts" + # # LEFT OUTER JOIN "authors" "authors"."id" = "posts"."author_id" + # # WHERE "authors"."id" IS NOT NULL + # + # Post.left_joins(:comments).where.associated(:author) + # # SELECT "posts".* FROM "posts" + # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id" + # # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" + # # WHERE "author"."id" IS NOT NULL + def associated(*associations) + associations.each do |association| + reflection = scope_association_reflection(association) + unless @scope.joins_values.include?(reflection.name) || @scope.left_outer_joins_values.include?(reflection.name) + @scope.joins!(association) + end + + association_conditions = Array(reflection.association_primary_key).index_with(nil) + if reflection.options[:class_name] + self.not(association => association_conditions) + else + self.not(reflection.table_name => association_conditions) + end + end + + @scope + end + + # Returns a new relation with left outer joins and where clause to identify + # missing relations. + # + # For example, posts that are missing a related author: + # + # Post.where.missing(:author) + # # SELECT "posts".* FROM "posts" + # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id" + # # WHERE "authors"."id" IS NULL + # + # Additionally, multiple relations can be combined. This will return posts + # that are missing both an author and any comments: + # + # Post.where.missing(:author, :comments) + # # SELECT "posts".* FROM "posts" + # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id" + # # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" + # # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL + def missing(*associations) + associations.each do |association| + reflection = scope_association_reflection(association) + @scope.left_outer_joins!(association) + association_conditions = Array(reflection.association_primary_key).index_with(nil) + if reflection.options[:class_name] + @scope.where!(association => association_conditions) + else + @scope.where!(reflection.table_name => association_conditions) + end + end + + @scope + end + + private + def scope_association_reflection(association) + model = @scope.model + reflection = model._reflect_on_association(association) + unless reflection + raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{model.name}`.") + end + reflection + end + end + + # A wrapper to distinguish CTE joins from other nodes. + class CTEJoin # :nodoc: + attr_reader :name + + def initialize(name) + @name = name + end + end + + FROZEN_EMPTY_ARRAY = [].freeze + FROZEN_EMPTY_HASH = {}.freeze + + Relation::VALUE_METHODS.each do |name| + method_name, default = + case name + when *Relation::MULTI_VALUE_METHODS + ["#{name}_values", "FROZEN_EMPTY_ARRAY"] + when *Relation::SINGLE_VALUE_METHODS + ["#{name}_value", name == :create_with ? "FROZEN_EMPTY_HASH" : "nil"] + when *Relation::CLAUSE_METHODS + ["#{name}_clause", name == :from ? "Relation::FromClause.empty" : "Relation::WhereClause.empty"] + end + + class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{method_name} # def includes_values + @values.fetch(:#{name}, #{default}) # @values.fetch(:includes, FROZEN_EMPTY_ARRAY) + end # end + + def #{method_name}=(value) # def includes_values=(value) + assert_modifiable! # assert_modifiable! + @values[:#{name}] = value # @values[:includes] = value + end # end + CODE + end + + alias extensions extending_values + + # Specify associations +args+ to be eager loaded to prevent N + 1 queries. + # A separate query is performed for each association, unless a join is + # required by conditions. + # + # For example: + # + # users = User.includes(:address).limit(5) + # users.each do |user| + # user.address.city + # end + # + # # SELECT "users".* FROM "users" LIMIT 5 + # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5) + # + # Instead of loading the 5 addresses with 5 separate queries, all addresses + # are loaded with a single query. + # + # Loading the associations in a separate query will often result in a + # performance improvement over a simple join, as a join can result in many + # rows that contain redundant data and it performs poorly at scale. + # + # You can also specify multiple associations. Each association will result + # in an additional query: + # + # User.includes(:address, :friends).to_a + # # SELECT "users".* FROM "users" + # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5) + # # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5) + # + # Loading nested associations is possible using a Hash: + # + # User.includes(:address, friends: [:address, :followers]) + # + # === Conditions + # + # If you want to add string conditions to your included models, you'll have + # to explicitly reference them. For example: + # + # User.includes(:posts).where('posts.name = ?', 'example').to_a + # + # Will throw an error, but this will work: + # + # User.includes(:posts).where('posts.name = ?', 'example').references(:posts).to_a + # # SELECT "users"."id" AS t0_r0, ... FROM "users" + # # LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id" + # # WHERE "posts"."name" = ? [["name", "example"]] + # + # As the LEFT OUTER JOIN already contains the posts, the second query for + # the posts is no longer performed. + # + # Note that #includes works with association names while #references needs + # the actual table name. + # + # If you pass the conditions via a Hash, you don't need to call #references + # explicitly, as #where references the tables for you. For example, this + # will work correctly: + # + # User.includes(:posts).where(posts: { name: 'example' }) + # + # NOTE: Conditions affect both sides of an association. For example, the + # above code will return only users that have a post named "example", + # and will only include posts named "example", even when a + # matching user has other additional posts. + def includes(*args) + check_if_method_has_arguments!(__callee__, args) + spawn.includes!(*args) + end + + def includes!(*args) # :nodoc: + self.includes_values |= args + self + end + + def all # :nodoc: + spawn + end + + # Specify associations +args+ to be eager loaded using a LEFT OUTER JOIN. + # Performs a single query joining all specified associations. For example: + # + # users = User.eager_load(:address).limit(5) + # users.each do |user| + # user.address.city + # end + # + # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users" + # # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id" + # # LIMIT 5 + # + # Instead of loading the 5 addresses with 5 separate queries, all addresses + # are loaded with a single joined query. + # + # Loading multiple and nested associations is possible using Hashes and Arrays, + # similar to #includes: + # + # User.eager_load(:address, friends: [:address, :followers]) + # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users" + # # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id" + # # LEFT OUTER JOIN "friends" ON "friends"."user_id" = "users"."id" + # # ... + # + # NOTE: Loading the associations in a join can result in many rows that + # contain redundant data and it performs poorly at scale. + def eager_load(*args) + check_if_method_has_arguments!(__callee__, args) + spawn.eager_load!(*args) + end + + def eager_load!(*args) # :nodoc: + self.eager_load_values |= args + self + end + + # Specify associations +args+ to be eager loaded using separate queries. + # A separate query is performed for each association. + # + # users = User.preload(:address).limit(5) + # users.each do |user| + # user.address.city + # end + # + # # SELECT "users".* FROM "users" LIMIT 5 + # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5) + # + # Instead of loading the 5 addresses with 5 separate queries, all addresses + # are loaded with a separate query. + # + # Loading multiple and nested associations is possible using Hashes and Arrays, + # similar to #includes: + # + # User.preload(:address, friends: [:address, :followers]) + # # SELECT "users".* FROM "users" + # # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5) + # # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5) + # # SELECT ... + def preload(*args) + check_if_method_has_arguments!(__callee__, args) + spawn.preload!(*args) + end + + def preload!(*args) # :nodoc: + self.preload_values |= args + self + end + + # Extracts a named +association+ from the relation. The named association is first preloaded, + # then the individual association records are collected from the relation. Like so: + # + # account.memberships.extract_associated(:user) + # # => Returns collection of User records + # + # This is short-hand for: + # + # account.memberships.preload(:user).collect(&:user) + def extract_associated(association) + preload(association).collect(&association) + end + + # Use to indicate that the given +table_names+ are referenced by an SQL string, + # and should therefore be +JOIN+ed in any query rather than loaded separately. + # This method only works in conjunction with #includes. + # See #includes for more details. + # + # User.includes(:posts).where("posts.name = 'foo'") + # # Doesn't JOIN the posts table, resulting in an error. + # + # User.includes(:posts).where("posts.name = 'foo'").references(:posts) + # # Query now knows the string references posts, so adds a JOIN + def references(*table_names) + check_if_method_has_arguments!(__callee__, table_names) + spawn.references!(*table_names) + end + + def references!(*table_names) # :nodoc: + self.references_values |= table_names + self + end + + # Works in two unique ways. + # + # First: takes a block so it can be used just like Array#select. + # + # Model.all.select { |m| m.field == value } + # + # This will build an array of objects from the database for the scope, + # converting them into an array and iterating through them using + # Array#select. + # + # Second: Modifies the SELECT statement for the query so that only certain + # fields are retrieved: + # + # Model.select(:field) + # # => [#] + # + # Although in the above example it looks as though this method returns an + # array, it actually returns a relation object and can have other query + # methods appended to it, such as the other methods in ActiveRecord::QueryMethods. + # + # The argument to the method can also be an array of fields. + # + # Model.select(:field, :other_field, :and_one_more) + # # => [#] + # + # The argument also can be a hash of fields and aliases. + # + # Model.select(models: { field: :alias, other_field: :other_alias }) + # # => [#] + # + # Model.select(models: [:field, :other_field]) + # # => [#] + # + # You can also use one or more strings, which will be used unchanged as SELECT fields. + # + # Model.select('field AS field_one', 'other_field AS field_two') + # # => [#] + # + # If an alias was specified, it will be accessible from the resulting objects: + # + # Model.select('field AS field_one').first.field_one + # # => "value" + # + # Accessing attributes of an object that do not have fields retrieved by a select + # except +id+ will throw ActiveModel::MissingAttributeError: + # + # Model.select(:field).first.other_field + # # => ActiveModel::MissingAttributeError: missing attribute 'other_field' for Model + def select(*fields) + if block_given? + if fields.any? + raise ArgumentError, "`select' with block doesn't take arguments." + end + + return super() + end + + check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.") + + fields = process_select_args(fields) + spawn._select!(*fields) + end + + def _select!(*fields) # :nodoc: + self.select_values |= fields + self + end + + # Add a Common Table Expression (CTE) that you can then reference within another SELECT statement. + # + # Note: CTE's are only supported in MySQL for versions 8.0 and above. You will not be able to + # use CTE's with MySQL 5.7. + # + # Post.with(posts_with_tags: Post.where("tags_count > ?", 0)) + # # => ActiveRecord::Relation + # # WITH posts_with_tags AS ( + # # SELECT * FROM posts WHERE (tags_count > 0) + # # ) + # # SELECT * FROM posts + # + # You can also pass an array of sub-queries to be joined in a +UNION ALL+. + # + # Post.with(posts_with_tags_or_comments: [Post.where("tags_count > ?", 0), Post.where("comments_count > ?", 0)]) + # # => ActiveRecord::Relation + # # WITH posts_with_tags_or_comments AS ( + # # (SELECT * FROM posts WHERE (tags_count > 0)) + # # UNION ALL + # # (SELECT * FROM posts WHERE (comments_count > 0)) + # # ) + # # SELECT * FROM posts + # + # Once you define Common Table Expression you can use custom +FROM+ value or +JOIN+ to reference it. + # + # Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).from("posts_with_tags AS posts") + # # => ActiveRecord::Relation + # # WITH posts_with_tags AS ( + # # SELECT * FROM posts WHERE (tags_count > 0) + # # ) + # # SELECT * FROM posts_with_tags AS posts + # + # Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).joins("JOIN posts_with_tags ON posts_with_tags.id = posts.id") + # # => ActiveRecord::Relation + # # WITH posts_with_tags AS ( + # # SELECT * FROM posts WHERE (tags_count > 0) + # # ) + # # SELECT * FROM posts JOIN posts_with_tags ON posts_with_tags.id = posts.id + # + # It is recommended to pass a query as ActiveRecord::Relation. If that is not possible + # and you have verified it is safe for the database, you can pass it as SQL literal + # using +Arel+. + # + # Post.with(popular_posts: Arel.sql("... complex sql to calculate posts popularity ...")) + # + # Great caution should be taken to avoid SQL injection vulnerabilities. This method should not + # be used with unsafe values that include unsanitized input. + # + # To add multiple CTEs just pass multiple key-value pairs + # + # Post.with( + # posts_with_comments: Post.where("comments_count > ?", 0), + # posts_with_tags: Post.where("tags_count > ?", 0) + # ) + # + # or chain multiple +.with+ calls + # + # Post + # .with(posts_with_comments: Post.where("comments_count > ?", 0)) + # .with(posts_with_tags: Post.where("tags_count > ?", 0)) + def with(*args) + raise ArgumentError, "ActiveRecord::Relation#with does not accept a block" if block_given? + check_if_method_has_arguments!(__callee__, args) + spawn.with!(*args) + end + + # Like #with, but modifies relation in place. + def with!(*args) # :nodoc: + args = process_with_args(args) + self.with_values |= args + self + end + + # Add a recursive Common Table Expression (CTE) that you can then reference within another SELECT statement. + # + # Post.with_recursive(post_and_replies: [Post.where(id: 42), Post.joins('JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id')]) + # # => ActiveRecord::Relation + # # WITH RECURSIVE post_and_replies AS ( + # # (SELECT * FROM posts WHERE id = 42) + # # UNION ALL + # # (SELECT * FROM posts JOIN posts_and_replies ON posts.in_reply_to_id = posts_and_replies.id) + # # ) + # # SELECT * FROM posts + # + # See `#with` for more information. + def with_recursive(*args) + check_if_method_has_arguments!(__callee__, args) + spawn.with_recursive!(*args) + end + + # Like #with_recursive but modifies the relation in place. + def with_recursive!(*args) # :nodoc: + args = process_with_args(args) + self.with_values |= args + @with_is_recursive = true + self + end + + # Allows you to change a previously set select statement. + # + # Post.select(:title, :body) + # # SELECT `posts`.`title`, `posts`.`body` FROM `posts` + # + # Post.select(:title, :body).reselect(:created_at) + # # SELECT `posts`.`created_at` FROM `posts` + # + # This is short-hand for unscope(:select).select(fields). + # Note that we're unscoping the entire select statement. + def reselect(*args) + check_if_method_has_arguments!(__callee__, args) + args = process_select_args(args) + spawn.reselect!(*args) + end + + # Same as #reselect but operates on relation in-place instead of copying. + def reselect!(*args) # :nodoc: + self.select_values = args + self + end + + # Allows to specify a group attribute: + # + # User.group(:name) + # # SELECT "users".* FROM "users" GROUP BY name + # + # Returns an array with distinct records based on the +group+ attribute: + # + # User.select([:id, :name]) + # # => [#, #, #] + # + # User.group(:name) + # # => [#, #] + # + # User.group('name AS grouped_name, age') + # # => [#, #, #] + # + # Passing in an array of attributes to group by is also supported. + # + # User.select([:id, :first_name]).group(:id, :first_name).first(3) + # # => [#, #, #] + def group(*args) + check_if_method_has_arguments!(__callee__, args) + spawn.group!(*args) + end + + def group!(*args) # :nodoc: + self.group_values += args + self + end + + # Allows you to change a previously set group statement. + # + # Post.group(:title, :body) + # # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`, `posts`.`body` + # + # Post.group(:title, :body).regroup(:title) + # # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title` + # + # This is short-hand for unscope(:group).group(fields). + # Note that we're unscoping the entire group statement. + def regroup(*args) + check_if_method_has_arguments!(__callee__, args) + spawn.regroup!(*args) + end + + # Same as #regroup but operates on relation in-place instead of copying. + def regroup!(*args) # :nodoc: + self.group_values = args + self + end + + # Applies an ORDER BY clause to a query. + # + # #order accepts arguments in one of several formats. + # + # === symbols + # + # The symbol represents the name of the column you want to order the results by. + # + # User.order(:name) + # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC + # + # By default, the order is ascending. If you want descending order, you can + # map the column name symbol to +:desc+. + # + # User.order(email: :desc) + # # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC + # + # Multiple columns can be passed this way, and they will be applied in the order specified. + # + # User.order(:name, email: :desc) + # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC + # + # === strings + # + # Strings are passed directly to the database, allowing you to specify + # simple SQL expressions. + # + # This could be a source of SQL injection, so only strings composed of plain + # column names and simple function(column_name) expressions + # with optional +ASC+/+DESC+ modifiers are allowed. + # + # User.order('name') + # # SELECT "users".* FROM "users" ORDER BY name + # + # User.order('name DESC') + # # SELECT "users".* FROM "users" ORDER BY name DESC + # + # User.order('name DESC, email') + # # SELECT "users".* FROM "users" ORDER BY name DESC, email + # + # === Arel + # + # If you need to pass in complicated expressions that you have verified + # are safe for the database, you can use Arel. + # + # User.order(Arel.sql('end_date - start_date')) + # # SELECT "users".* FROM "users" ORDER BY end_date - start_date + # + # Custom query syntax, like JSON columns for PostgreSQL, is supported in this way. + # + # User.order(Arel.sql("payload->>'kind'")) + # # SELECT "users".* FROM "users" ORDER BY payload->>'kind' + def order(*args) + check_if_method_has_arguments!(__callee__, args) do + sanitize_order_arguments(args) + end + spawn.order!(*args) + end + + # Same as #order but operates on relation in-place instead of copying. + def order!(*args) # :nodoc: + preprocess_order_args(args) unless args.empty? + self.order_values |= args + self + end + + # Applies an ORDER BY clause based on a given +column+, + # ordered and filtered by a specific set of +values+. + # + # User.in_order_of(:id, [1, 5, 3]) + # # SELECT "users".* FROM "users" + # # WHERE "users"."id" IN (1, 5, 3) + # # ORDER BY CASE + # # WHEN "users"."id" = 1 THEN 1 + # # WHEN "users"."id" = 5 THEN 2 + # # WHEN "users"."id" = 3 THEN 3 + # # END ASC + # + # +column+ can point to an enum column; the actual query generated may be different depending + # on the database adapter and the column definition. + # + # class Conversation < ActiveRecord::Base + # enum :status, [ :active, :archived ] + # end + # + # Conversation.in_order_of(:status, [:archived, :active]) + # # SELECT "conversations".* FROM "conversations" + # # WHERE "conversations"."status" IN (1, 0) + # # ORDER BY CASE + # # WHEN "conversations"."status" = 1 THEN 1 + # # WHEN "conversations"."status" = 0 THEN 2 + # # END ASC + # + # +values+ can also include +nil+. + # + # Conversation.in_order_of(:status, [nil, :archived, :active]) + # # SELECT "conversations".* FROM "conversations" + # # WHERE ("conversations"."status" IN (1, 0) OR "conversations"."status" IS NULL) + # # ORDER BY CASE + # # WHEN "conversations"."status" IS NULL THEN 1 + # # WHEN "conversations"."status" = 1 THEN 2 + # # WHEN "conversations"."status" = 0 THEN 3 + # # END ASC + # + # +filter+ can be set to +false+ to include all results instead of only the ones specified in +values+. + # + # Conversation.in_order_of(:status, [:archived, :active], filter: false) + # # SELECT "conversations".* FROM "conversations" + # # ORDER BY CASE + # # WHEN "conversations"."status" = 1 THEN 1 + # # WHEN "conversations"."status" = 0 THEN 2 + # # ELSE 3 + # # END ASC + def in_order_of(column, values, filter: true) + model.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher) + return spawn.none! if values.empty? + + references = column_references([column]) + self.references_values |= references unless references.empty? + + values = values.map { |value| model.type_caster.type_cast_for_database(column, value) } + arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s) + + scope = spawn.order!(build_case_for_value_position(arel_column, values, filter: filter)) + + if filter + where_clause = + if values.include?(nil) + arel_column.in(values.compact).or(arel_column.eq(nil)) + else + arel_column.in(values) + end + + scope = scope.where!(where_clause) + end + + scope + end + + # Replaces any existing order defined on the relation with the specified order. + # + # User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC' + # + # Subsequent calls to order on the same relation will be appended. For example: + # + # User.order('email DESC').reorder('id ASC').order('name ASC') + # + # generates a query with ORDER BY id ASC, name ASC. + def reorder(*args) + check_if_method_has_arguments!(__callee__, args) do + sanitize_order_arguments(args) + end + spawn.reorder!(*args) + end + + # Same as #reorder but operates on relation in-place instead of copying. + def reorder!(*args) # :nodoc: + preprocess_order_args(args) + args.uniq! + self.reordering_value = true + self.order_values = args + self + end + + VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock, + :limit, :offset, :joins, :left_outer_joins, :annotate, + :includes, :eager_load, :preload, :from, :readonly, + :having, :optimizer_hints, :with]) + + # Removes an unwanted relation that is already defined on a chain of relations. + # This is useful when passing around chains of relations and would like to + # modify the relations without reconstructing the entire chain. + # + # User.order('email DESC').unscope(:order) == User.all + # + # The method arguments are symbols which correspond to the names of the methods + # which should be unscoped. The valid arguments are given in VALID_UNSCOPING_VALUES. + # The method can also be called with multiple arguments. For example: + # + # User.order('email DESC').select('id').where(name: "John") + # .unscope(:order, :select, :where) == User.all + # + # One can additionally pass a hash as an argument to unscope specific +:where+ values. + # This is done by passing a hash with a single key-value pair. The key should be + # +:where+ and the value should be the where value to unscope. For example: + # + # User.where(name: "John", active: true).unscope(where: :name) + # == User.where(active: true) + # + # This method is similar to #except, but unlike + # #except, it persists across merges: + # + # User.order('email').merge(User.except(:order)) + # == User.order('email') + # + # User.order('email').merge(User.unscope(:order)) + # == User.all + # + # This means it can be used in association definitions: + # + # has_many :comments, -> { unscope(where: :trashed) } + # + def unscope(*args) + check_if_method_has_arguments!(__callee__, args) + spawn.unscope!(*args) + end + + def unscope!(*args) # :nodoc: + self.unscope_values += args + + args.each do |scope| + case scope + when Symbol + scope = :left_outer_joins if scope == :left_joins + if !VALID_UNSCOPING_VALUES.include?(scope) + raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}." + end + assert_modifiable! + @values.delete(scope) + when Hash + scope.each do |key, target_value| + if key != :where + raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key." + end + + target_values = resolve_arel_attributes(Array.wrap(target_value)) + self.where_clause = where_clause.except(*target_values) + end + else + raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example." + end + end + + self + end + + # Performs JOINs on +args+. The given symbol(s) should match the name of + # the association(s). + # + # User.joins(:posts) + # # SELECT "users".* + # # FROM "users" + # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id" + # + # Multiple joins: + # + # User.joins(:posts, :account) + # # SELECT "users".* + # # FROM "users" + # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id" + # # INNER JOIN "accounts" ON "accounts"."id" = "users"."account_id" + # + # Nested joins: + # + # User.joins(posts: [:comments]) + # # SELECT "users".* + # # FROM "users" + # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id" + # # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id" + # + # You can use strings in order to customize your joins: + # + # User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id") + # # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id + def joins(*args) + check_if_method_has_arguments!(__callee__, args) + spawn.joins!(*args) + end + + def joins!(*args) # :nodoc: + self.joins_values |= args + self + end + + # Performs LEFT OUTER JOINs on +args+: + # + # User.left_outer_joins(:posts) + # # SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id" + # + def left_outer_joins(*args) + check_if_method_has_arguments!(__callee__, args) + spawn.left_outer_joins!(*args) + end + alias :left_joins :left_outer_joins + + def left_outer_joins!(*args) # :nodoc: + self.left_outer_joins_values |= args + self + end + + # Returns a new relation, which is the result of filtering the current relation + # according to the conditions in the arguments. + # + # #where accepts conditions in one of several formats. In the examples below, the resulting + # SQL is given as an illustration; the actual query generated may be different depending + # on the database adapter. + # + # === \String + # + # A single string, without additional arguments, is passed to the query + # constructor as an SQL fragment, and used in the where clause of the query. + # + # Client.where("orders_count = '2'") + # # SELECT * from clients where orders_count = '2'; + # + # Note that building your own string from user input may expose your application + # to injection attacks if not done properly. As an alternative, it is recommended + # to use one of the following methods. + # + # === \Array + # + # If an array is passed, then the first element of the array is treated as a template, and + # the remaining elements are inserted into the template to generate the condition. + # Active Record takes care of building the query to avoid injection attacks, and will + # convert from the ruby type to the database type where needed. Elements are inserted + # into the string in the order in which they appear. + # + # User.where(["name = ? and email = ?", "Joe", "joe@example.com"]) + # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'; + # + # Alternatively, you can use named placeholders in the template, and pass a hash as the + # second element of the array. The names in the template are replaced with the corresponding + # values from the hash. + # + # User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }]) + # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'; + # + # This can make for more readable code in complex queries. + # + # Lastly, you can use sprintf-style % escapes in the template. This works slightly differently + # than the previous methods; you are responsible for ensuring that the values in the template + # are properly quoted. The values are passed to the connector for quoting, but the caller + # is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting, + # the values are inserted using the same escapes as the Ruby core method +Kernel::sprintf+. + # + # User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"]) + # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'; + # + # If #where is called with multiple arguments, these are treated as if they were passed as + # the elements of a single array. + # + # User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" }) + # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'; + # + # When using strings to specify conditions, you can use any operator available from + # the database. While this provides the most flexibility, you can also unintentionally introduce + # dependencies on the underlying database. If your code is intended for general consumption, + # test with multiple database backends. + # + # === \Hash + # + # #where will also accept a hash condition, in which the keys are fields and the values + # are values to be searched for. + # + # Fields can be symbols or strings. Values can be single values, arrays, or ranges. + # + # User.where(name: "Joe", email: "joe@example.com") + # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com' + # + # User.where(name: ["Alice", "Bob"]) + # # SELECT * FROM users WHERE name IN ('Alice', 'Bob') + # + # User.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight) + # # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000') + # + # In the case of a belongs_to relationship, an association key can be used + # to specify the model if an ActiveRecord object is used as the value. + # + # author = Author.find(1) + # + # # The following queries will be equivalent: + # Post.where(author: author) + # Post.where(author_id: author) + # + # This also works with polymorphic belongs_to relationships: + # + # treasure = Treasure.create(name: 'gold coins') + # treasure.price_estimates << PriceEstimate.create(price: 125) + # + # # The following queries will be equivalent: + # PriceEstimate.where(estimate_of: treasure) + # PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure) + # + # Hash conditions may also be specified in a tuple-like syntax. Hash keys may be + # an array of columns with an array of tuples as values. + # + # Article.where([:author_id, :id] => [[15, 1], [15, 2]]) + # # SELECT * FROM articles WHERE author_id = 15 AND id = 1 OR author_id = 15 AND id = 2 + # + # === Joins + # + # If the relation is the result of a join, you may create a condition which uses any of the + # tables in the join. For string and array conditions, use the table name in the condition. + # + # User.joins(:posts).where("posts.created_at < ?", Time.now) + # + # For hash conditions, you can either use the table name in the key, or use a sub-hash. + # + # User.joins(:posts).where("posts.published" => true) + # User.joins(:posts).where(posts: { published: true }) + # + # === No Argument + # + # If no argument is passed, #where returns a new instance of WhereChain, that + # can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated. + # + # Chaining with WhereChain#not: + # + # User.where.not(name: "Jon") + # # SELECT * FROM users WHERE name != 'Jon' + # + # Chaining with WhereChain#associated: + # + # Post.where.associated(:author) + # # SELECT "posts".* FROM "posts" + # # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id" + # # WHERE "authors"."id" IS NOT NULL + # + # Chaining with WhereChain#missing: + # + # Post.where.missing(:author) + # # SELECT "posts".* FROM "posts" + # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id" + # # WHERE "authors"."id" IS NULL + # + # === Blank Condition + # + # If the condition is any blank-ish object, then #where is a no-op and returns + # the current relation. + def where(*args) + if args.empty? + WhereChain.new(spawn) + elsif args.length == 1 && args.first.blank? + self + else + spawn.where!(*args) + end + end + + def where!(opts, *rest) # :nodoc: + self.where_clause += build_where_clause(opts, rest) + self + end + + # Allows you to change a previously set where condition for a given attribute, instead of appending to that condition. + # + # Post.where(trashed: true).where(trashed: false) + # # WHERE `trashed` = 1 AND `trashed` = 0 + # + # Post.where(trashed: true).rewhere(trashed: false) + # # WHERE `trashed` = 0 + # + # Post.where(active: true).where(trashed: true).rewhere(trashed: false) + # # WHERE `active` = 1 AND `trashed` = 0 + # + # This is short-hand for unscope(where: conditions.keys).where(conditions). + # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement. + def rewhere(conditions) + return unscope(:where) if conditions.nil? + + scope = spawn + where_clause = scope.build_where_clause(conditions) + + scope.unscope!(where: where_clause.extract_attributes) + scope.where_clause += where_clause + scope + end + + # Allows you to invert an entire where clause instead of manually applying conditions. + # + # class User + # scope :active, -> { where(accepted: true, locked: false) } + # end + # + # User.where(accepted: true) + # # WHERE `accepted` = 1 + # + # User.where(accepted: true).invert_where + # # WHERE `accepted` != 1 + # + # User.active + # # WHERE `accepted` = 1 AND `locked` = 0 + # + # User.active.invert_where + # # WHERE NOT (`accepted` = 1 AND `locked` = 0) + # + # Be careful because this inverts all conditions before +invert_where+ call. + # + # class User + # scope :active, -> { where(accepted: true, locked: false) } + # scope :inactive, -> { active.invert_where } # Do not attempt it + # end + # + # # It also inverts `where(role: 'admin')` unexpectedly. + # User.where(role: 'admin').inactive + # # WHERE NOT (`role` = 'admin' AND `accepted` = 1 AND `locked` = 0) + # + def invert_where + spawn.invert_where! + end + + def invert_where! # :nodoc: + self.where_clause = where_clause.invert + self + end + + # Checks whether the given relation is structurally compatible with this relation, to determine + # if it's possible to use the #and and #or methods without raising an error. Structurally + # compatible is defined as: they must be scoping the same model, and they must differ only by + # #where (if no #group has been defined) or #having (if a #group is present). + # + # Post.where("id = 1").structurally_compatible?(Post.where("author_id = 3")) + # # => true + # + # Post.joins(:comments).structurally_compatible?(Post.where("id = 1")) + # # => false + # + def structurally_compatible?(other) + structurally_incompatible_values_for(other).empty? + end + + # Returns a new relation, which is the logical intersection of this relation and the one passed + # as an argument. + # + # The two relations must be structurally compatible: they must be scoping the same model, and + # they must differ only by #where (if no #group has been defined) or #having (if a #group is + # present). + # + # Post.where(id: [1, 2]).and(Post.where(id: [2, 3])) + # # SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (1, 2) AND `posts`.`id` IN (2, 3) + # + def and(other) + if other.is_a?(Relation) + spawn.and!(other) + else + raise ArgumentError, "You have passed #{other.class.name} object to #and. Pass an ActiveRecord::Relation object instead." + end + end + + def and!(other) # :nodoc: + incompatible_values = structurally_incompatible_values_for(other) + + unless incompatible_values.empty? + raise ArgumentError, "Relation passed to #and must be structurally compatible. Incompatible values: #{incompatible_values}" + end + + self.where_clause |= other.where_clause + self.having_clause |= other.having_clause + self.references_values |= other.references_values + + self + end + + # Returns a new relation, which is the logical union of this relation and the one passed as an + # argument. + # + # The two relations must be structurally compatible: they must be scoping the same model, and + # they must differ only by #where (if no #group has been defined) or #having (if a #group is + # present). + # + # Post.where("id = 1").or(Post.where("author_id = 3")) + # # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3)) + # + def or(other) + if other.is_a?(Relation) + if @none + other.spawn + else + spawn.or!(other) + end + else + raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead." + end + end + + def or!(other) # :nodoc: + incompatible_values = structurally_incompatible_values_for(other) + + unless incompatible_values.empty? + raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}" + end + + self.where_clause = where_clause.or(other.where_clause) + self.having_clause = having_clause.or(other.having_clause) + self.references_values |= other.references_values + + self + end + + # Allows to specify a HAVING clause. Note that you can't use HAVING + # without also specifying a GROUP clause. + # + # Order.having('SUM(price) > 30').group('user_id') + def having(opts, *rest) + opts.blank? ? self : spawn.having!(opts, *rest) + end + + def having!(opts, *rest) # :nodoc: + self.having_clause += build_having_clause(opts, rest) + self + end + + # Specifies a limit for the number of records to retrieve. + # + # User.limit(10) # generated SQL has 'LIMIT 10' + # + # User.limit(10).limit(20) # generated SQL has 'LIMIT 20' + def limit(value) + spawn.limit!(value) + end + + def limit!(value) # :nodoc: + self.limit_value = value + self + end + + # Specifies the number of rows to skip before returning rows. + # + # User.offset(10) # generated SQL has "OFFSET 10" + # + # Should be used with order. + # + # User.offset(10).order("name ASC") + def offset(value) + spawn.offset!(value) + end + + def offset!(value) # :nodoc: + self.offset_value = value + self + end + + # Specifies locking settings (default to +true+). For more information + # on locking, please see ActiveRecord::Locking. + def lock(locks = true) + spawn.lock!(locks) + end + + def lock!(locks = true) # :nodoc: + case locks + when String, TrueClass, NilClass + self.lock_value = locks || true + else + self.lock_value = false + end + + self + end + + # Returns a chainable relation with zero records. + # + # The returned relation implements the Null Object pattern. It is an + # object with defined null behavior and always returns an empty array of + # records without querying the database. + # + # Any subsequent condition chained to the returned relation will continue + # generating an empty relation and will not fire any query to the database. + # + # Used in cases where a method or scope could return zero records but the + # result needs to be chainable. + # + # For example: + # + # @posts = current_user.visible_posts.where(name: params[:name]) + # # the visible_posts method is expected to return a chainable Relation + # + # def visible_posts + # case role + # when 'Country Manager' + # Post.where(country: country) + # when 'Reviewer' + # Post.published + # when 'Bad User' + # Post.none # It can't be chained if [] is returned. + # end + # end + # + def none + spawn.none! + end + + def none! # :nodoc: + unless @none + where!("1=0") + @none = true + end + self + end + + def null_relation? # :nodoc: + @none + end + + # Mark a relation as readonly. Attempting to update a record will result in + # an error. + # + # users = User.readonly + # users.first.save + # => ActiveRecord::ReadOnlyRecord: User is marked as readonly + # + # To make a readonly relation writable, pass +false+. + # + # users.readonly(false) + # users.first.save + # => true + def readonly(value = true) + spawn.readonly!(value) + end + + def readonly!(value = true) # :nodoc: + self.readonly_value = value + self + end + + # Sets the returned relation to strict_loading mode. This will raise an error + # if the record tries to lazily load an association. + # + # user = User.strict_loading.first + # user.comments.to_a + # => ActiveRecord::StrictLoadingViolationError + def strict_loading(value = true) + spawn.strict_loading!(value) + end + + def strict_loading!(value = true) # :nodoc: + self.strict_loading_value = value + self + end + + # Sets attributes to be used when creating new records from a + # relation object. + # + # users = User.where(name: 'Oscar') + # users.new.name # => 'Oscar' + # + # users = users.create_with(name: 'DHH') + # users.new.name # => 'DHH' + # + # You can pass +nil+ to #create_with to reset attributes: + # + # users = users.create_with(nil) + # users.new.name # => 'Oscar' + def create_with(value) + spawn.create_with!(value) + end + + def create_with!(value) # :nodoc: + if value + value = sanitize_forbidden_attributes(value) + self.create_with_value = create_with_value.merge(value) + else + self.create_with_value = FROZEN_EMPTY_HASH + end + + self + end + + # Specifies the table from which the records will be fetched. For example: + # + # Topic.select('title').from('posts') + # # SELECT title FROM posts + # + # Can accept other relation objects. For example: + # + # Topic.select('title').from(Topic.approved) + # # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery + # + # Passing a second argument (string or symbol), creates the alias for the SQL from clause. Otherwise the alias "subquery" is used: + # + # Topic.select('a.title').from(Topic.approved, :a) + # # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a + # + # It does not add multiple arguments to the SQL from clause. The last +from+ chained is the one used: + # + # Topic.select('title').from(Topic.approved).from(Topic.inactive) + # # SELECT title FROM (SELECT topics.* FROM topics WHERE topics.active = 'f') subquery + # + # For multiple arguments for the SQL from clause, you can pass a string with the exact elements in the SQL from list: + # + # color = "red" + # Color + # .from("colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)") + # .where("colorvalue->>'color' = ?", color) + # .select("c.*").to_a + # # SELECT c.* + # # FROM colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue) + # # WHERE (colorvalue->>'color' = 'red') + def from(value, subquery_name = nil) + spawn.from!(value, subquery_name) + end + + def from!(value, subquery_name = nil) # :nodoc: + self.from_clause = Relation::FromClause.new(value, subquery_name) + self + end + + # Specifies whether the records should be unique or not. For example: + # + # User.select(:name) + # # Might return two records with the same name + # + # User.select(:name).distinct + # # Returns 1 record per distinct name + # + # User.select(:name).distinct.distinct(false) + # # You can also remove the uniqueness + def distinct(value = true) + spawn.distinct!(value) + end + + # Like #distinct, but modifies relation in place. + def distinct!(value = true) # :nodoc: + self.distinct_value = value + self + end + + # Used to extend a scope with additional methods, either through + # a module or through a block provided. + # + # The object returned is a relation, which can be further extended. + # + # === Using a \Module + # + # module Pagination + # def page(number) + # # pagination code goes here + # end + # end + # + # scope = Model.all.extending(Pagination) + # scope.page(params[:page]) + # + # You can also pass a list of modules: + # + # scope = Model.all.extending(Pagination, SomethingElse) + # + # === Using a Block + # + # scope = Model.all.extending do + # def page(number) + # # pagination code goes here + # end + # end + # scope.page(params[:page]) + # + # You can also use a block and a module list: + # + # scope = Model.all.extending(Pagination) do + # def per_page(number) + # # pagination code goes here + # end + # end + def extending(*modules, &block) + if modules.any? || block + spawn.extending!(*modules, &block) + else + self + end + end + + def extending!(*modules, &block) # :nodoc: + modules << Module.new(&block) if block + modules.flatten! + + self.extending_values += modules + extend(*extending_values) if extending_values.any? + + self + end + + # Specify optimizer hints to be used in the SELECT statement. + # + # Example (for MySQL): + # + # Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)") + # # SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics` + # + # Example (for PostgreSQL with pg_hint_plan): + # + # Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)") + # # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics" + def optimizer_hints(*args) + check_if_method_has_arguments!(__callee__, args) + spawn.optimizer_hints!(*args) + end + + def optimizer_hints!(*args) # :nodoc: + self.optimizer_hints_values |= args + self + end + + # Reverse the existing order clause on the relation. + # + # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC' + def reverse_order + spawn.reverse_order! + end + + def reverse_order! # :nodoc: + orders = order_values.compact_blank + self.order_values = reverse_sql_order(orders) + self + end + + def skip_query_cache!(value = true) # :nodoc: + self.skip_query_cache_value = value + self + end + + def skip_preloading! # :nodoc: + self.skip_preloading_value = true + self + end + + # Adds an SQL comment to queries generated from this relation. For example: + # + # User.annotate("selecting user names").select(:name) + # # SELECT "users"."name" FROM "users" /* selecting user names */ + # + # User.annotate("selecting", "user", "names").select(:name) + # # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */ + # + # The SQL block comment delimiters, "/*" and "*/", will be added automatically. + # + # Some escaping is performed, however untrusted user input should not be used. + def annotate(*args) + check_if_method_has_arguments!(__callee__, args) + spawn.annotate!(*args) + end + + # Like #annotate, but modifies relation in place. + def annotate!(*args) # :nodoc: + self.annotate_values += args + self + end + + # Deduplicate multiple values. + def uniq!(name) + if values = @values[name] + values.uniq! if values.is_a?(Array) && !values.empty? + end + self + end + + # Excludes the specified record (or collection of records) from the resulting + # relation. For example: + # + # Post.excluding(post) + # # SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1 + # + # Post.excluding(post_one, post_two) + # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2) + # + # Post.excluding(Post.drafts) + # # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (3, 4, 5) + # + # This can also be called on associations. As with the above example, either + # a single record of collection thereof may be specified: + # + # post = Post.find(1) + # comment = Comment.find(2) + # post.comments.excluding(comment) + # # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 2 + # + # This is short-hand for .where.not(id: post.id) and .where.not(id: [post_one.id, post_two.id]). + # + # An ArgumentError will be raised if either no records are + # specified, or if any of the records in the collection (if a collection + # is passed in) are not instances of the same model that the relation is + # scoping. + def excluding(*records) + relations = records.extract! { |element| element.is_a?(Relation) } + records.flatten!(1) + records.compact! + + unless records.all?(model) && relations.all? { |relation| relation.model == model } + raise ArgumentError, "You must only pass a single or collection of #{model.name} objects to ##{__callee__}." + end + + spawn.excluding!(records + relations.flat_map(&:ids)) + end + alias :without :excluding + + def excluding!(records) # :nodoc: + predicates = [ predicate_builder[primary_key, records].invert ] + self.where_clause += Relation::WhereClause.new(predicates) + self + end + + # Returns the Arel object associated with the relation. + def arel(aliases = nil) # :nodoc: + @arel ||= with_connection { |c| build_arel(c, aliases) } + end + + def construct_join_dependency(associations, join_type) # :nodoc: + ActiveRecord::Associations::JoinDependency.new( + model, table, associations, join_type + ) + end + + protected + def build_subquery(subquery_alias, select_value) # :nodoc: + subquery = except(:optimizer_hints).arel.as(subquery_alias) + + Arel::SelectManager.new(subquery).project(select_value).tap do |arel| + arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty? + end + end + + def build_where_clause(opts, rest = []) # :nodoc: + opts = sanitize_forbidden_attributes(opts) + + if opts.is_a?(Array) + opts, *rest = opts + end + + case opts + when String + if rest.empty? + parts = [Arel.sql(opts)] + elsif rest.first.is_a?(Hash) && /:\w+/.match?(opts) + parts = [build_named_bound_sql_literal(opts, rest.first)] + elsif opts.include?("?") + parts = [build_bound_sql_literal(opts, rest)] + else + parts = [model.sanitize_sql(rest.empty? ? opts : [opts, *rest])] + end + when Hash + opts = opts.transform_keys do |key| + if key.is_a?(Array) + key.map { |k| model.attribute_aliases[k.to_s] || k.to_s } + else + key = key.to_s + model.attribute_aliases[key] || key + end + end + references = PredicateBuilder.references(opts) + self.references_values |= references unless references.empty? + + parts = predicate_builder.build_from_hash(opts) do |table_name| + lookup_table_klass_from_join_dependencies(table_name) + end + when Arel::Nodes::Node + parts = [opts] + else + raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})" + end + + Relation::WhereClause.new(parts) + end + alias :build_having_clause :build_where_clause + + def async! + @async = true + self + end + + protected + def arel_columns(columns) + columns.flat_map do |field| + case field + when Symbol, String + arel_column(field) + when Proc + field.call + when Hash + arel_columns_from_hash(field) + else + field + end + end + end + + private + def async + spawn.async! + end + + def build_named_bound_sql_literal(statement, values) + bound_values = values.transform_values do |value| + if ActiveRecord::Relation === value + Arel.sql(value.to_sql) + elsif value.respond_to?(:map) && !value.acts_like?(:string) + values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v } + values.empty? ? nil : values + else + value = value.id_for_database if value.respond_to?(:id_for_database) + value + end + end + + begin + Arel::Nodes::BoundSqlLiteral.new("(#{statement})", nil, bound_values) + rescue Arel::BindError => error + raise ActiveRecord::PreparedStatementInvalid, error.message + end + end + + def build_bound_sql_literal(statement, values) + bound_values = values.map do |value| + if ActiveRecord::Relation === value + Arel.sql(value.to_sql) + elsif value.respond_to?(:map) && !value.acts_like?(:string) + values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v } + values.empty? ? nil : values + else + value = value.id_for_database if value.respond_to?(:id_for_database) + value + end + end + + begin + Arel::Nodes::BoundSqlLiteral.new("(#{statement})", bound_values, nil) + rescue Arel::BindError => error + raise ActiveRecord::PreparedStatementInvalid, error.message + end + end + + def lookup_table_klass_from_join_dependencies(table_name) + each_join_dependencies do |join| + return join.base_klass if table_name == join.table_name + end + nil + end + + def each_join_dependencies(join_dependencies = build_join_dependencies, &block) + join_dependencies.each do |join_dependency| + join_dependency.each(&block) + end + end + + def build_join_dependencies + joins = joins_values | left_outer_joins_values + joins |= eager_load_values unless eager_load_values.empty? + joins |= includes_values unless includes_values.empty? + + join_dependencies = [] + join_dependencies.unshift construct_join_dependency( + select_named_joins(joins, join_dependencies), nil + ) + end + + def assert_modifiable! + raise UnmodifiableRelation if @loaded || @arel + end + + def build_arel(connection, aliases = nil) + arel = Arel::SelectManager.new(table) + + build_joins(arel.join_sources, aliases) + + arel.where(where_clause.ast) unless where_clause.empty? + arel.having(having_clause.ast) unless having_clause.empty? + arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value + arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value + arel.group(*arel_columns(group_values.uniq)) unless group_values.empty? + + build_order(arel) + build_with(arel) + build_select(arel) + + arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty? + arel.distinct(distinct_value) + arel.from(build_from) unless from_clause.empty? + arel.lock(lock_value) if lock_value + + unless annotate_values.empty? + annotates = annotate_values + annotates = annotates.uniq if annotates.size > 1 + arel.comment(*annotates) + end + + arel + end + + def build_cast_value(name, value) + ActiveModel::Attribute.with_cast_value(name, value, Type.default_value) + end + + def build_from + opts = from_clause.value + name = from_clause.name + case opts + when Relation + if opts.eager_loading? + opts = opts.send(:apply_join_dependency) + end + name ||= "subquery" + opts.arel.as(name.to_s) + else + opts + end + end + + def select_named_joins(join_names, stashed_joins = nil, &block) + cte_joins, associations = join_names.partition do |join_name| + Symbol === join_name && with_values.any? { _1.key?(join_name) } + end + + cte_joins.each do |cte_name| + block&.call(CTEJoin.new(cte_name)) + end + + select_association_list(associations, stashed_joins, &block) + end + + def select_association_list(associations, stashed_joins = nil) + result = [] + associations.each do |association| + case association + when Hash, Symbol, Array + result << association + when ActiveRecord::Associations::JoinDependency + stashed_joins&.<< association + else + yield association if block_given? + end + end + result + end + + def build_join_buckets + buckets = Hash.new { |h, k| h[k] = [] } + + unless left_outer_joins_values.empty? + stashed_left_joins = [] + left_joins = select_named_joins(left_outer_joins_values, stashed_left_joins) do |left_join| + if left_join.is_a?(CTEJoin) + buckets[:join_node] << build_with_join_node(left_join.name, Arel::Nodes::OuterJoin) + else + raise ArgumentError, "only Hash, Symbol and Array are allowed" + end + end + + if joins_values.empty? + buckets[:named_join] = left_joins + buckets[:stashed_join] = stashed_left_joins + return buckets, Arel::Nodes::OuterJoin + else + stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin) + end + end + + joins = joins_values.dup + if joins.last.is_a?(ActiveRecord::Associations::JoinDependency) + stashed_eager_load = joins.pop if joins.last.base_klass == model + end + + joins.each_with_index do |join, i| + joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String) + end + + while joins.first.is_a?(Arel::Nodes::Join) + join_node = joins.shift + if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins) + buckets[:join_node] << join_node + else + buckets[:leading_join] << join_node + end + end + + buckets[:named_join] = select_named_joins(joins, buckets[:stashed_join]) do |join| + if join.is_a?(Arel::Nodes::Join) + buckets[:join_node] << join + elsif join.is_a?(CTEJoin) + buckets[:join_node] << build_with_join_node(join.name) + else + raise "unknown class: %s" % join.class.name + end + end + + buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins + buckets[:stashed_join] << stashed_eager_load if stashed_eager_load + + return buckets, Arel::Nodes::InnerJoin + end + + def build_joins(join_sources, aliases = nil) + return join_sources if joins_values.empty? && left_outer_joins_values.empty? + + buckets, join_type = build_join_buckets + + named_joins = buckets[:named_join] + stashed_joins = buckets[:stashed_join] + leading_joins = buckets[:leading_join] + join_nodes = buckets[:join_node] + + join_sources.concat(leading_joins) unless leading_joins.empty? + + unless named_joins.empty? && stashed_joins.empty? + alias_tracker = alias_tracker(leading_joins + join_nodes, aliases) + join_dependency = construct_join_dependency(named_joins, join_type) + join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values)) + end + + join_sources.concat(join_nodes) unless join_nodes.empty? + join_sources + end + + def build_select(arel) + if select_values.any? + arel.project(*arel_columns(select_values)) + elsif model.ignored_columns.any? || model.enumerate_columns_in_select_statements + arel.project(*model.column_names.map { |field| table[field] }) + else + arel.project(table[Arel.star]) + end + end + + def build_with(arel) + return if with_values.empty? + + with_statements = with_values.map do |with_value| + build_with_value_from_hash(with_value) + end + + @with_is_recursive ? arel.with(:recursive, with_statements) : arel.with(with_statements) + end + + def build_with_value_from_hash(hash) + hash.map do |name, value| + Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name) + end + end + + def build_with_expression_from_value(value, nested = false) + case value + when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value) + when ActiveRecord::Relation + if nested + value.arel.ast + else + value.arel + end + when Arel::SelectManager then value + when Array + return build_with_expression_from_value(value.first, false) if value.size == 1 + + parts = value.map do |query| + build_with_expression_from_value(query, true) + end + + parts.reduce do |result, value| + Arel::Nodes::UnionAll.new(result, value) + end + else + raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}" + end + end + + def build_with_join_node(name, kind = Arel::Nodes::InnerJoin) + with_table = Arel::Table.new(name) + + table.join(with_table, kind).on( + with_table[model.model_name.to_s.foreign_key].eq(table[model.primary_key]) + ).join_sources.first + end + + def arel_columns_from_hash(fields) + fields.flat_map do |table_name, columns| + table_name = table_name.name if table_name.is_a?(Symbol) + case columns + when Symbol, String + arel_column_with_table(table_name, columns) + when Array + columns.map do |column| + arel_column_with_table(table_name, column) + end + else + raise TypeError, "Expected Symbol, String or Array, got: #{columns.class}" + end + end + end + + def arel_column_with_table(table_name, column_name) + self.references_values |= [Arel.sql(table_name, retryable: true)] + + if column_name.is_a?(Symbol) || !column_name.match?(/\W/) + predicate_builder.resolve_arel_attribute(table_name, column_name) do + lookup_table_klass_from_join_dependencies(table_name) + end + else + Arel.sql("#{model.adapter_class.quote_table_name(table_name)}.#{column_name}") + end + end + + def arel_column(field) + field = field.name if is_symbol = field.is_a?(Symbol) + + field = model.attribute_aliases[field] || field.to_s + from = from_clause.name || from_clause.value + + if model.columns_hash.key?(field) && (!from || table_name_matches?(from)) + table[field] + elsif /\A(?
    (?:\w+\.)?\w+)\.(?\w+)\z/ =~ field + arel_column_with_table(table, column) + elsif block_given? + yield field + elsif Arel.arel_node?(field) + field + else + Arel.sql(is_symbol ? model.adapter_class.quote_table_name(field) : field) + end + end + + def table_name_matches?(from) + table_name = Regexp.escape(table.name) + quoted_table_name = Regexp.escape(model.adapter_class.quote_table_name(table.name)) + /(?:\A|(? v } } + end + end + + STRUCTURAL_VALUE_METHODS = ( + Relation::VALUE_METHODS - + [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints] + ).freeze # :nodoc: + + def structurally_incompatible_values_for(other) + values = other.values + STRUCTURAL_VALUE_METHODS.reject do |method| + v1, v2 = @values[method], values[method] + if v1.is_a?(Array) + next true unless v2.is_a?(Array) + v1 = v1.uniq + v2 = v2.uniq + end + v1 == v2 + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/spawn_methods.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/spawn_methods.rb new file mode 100644 index 00000000..66ab9531 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/spawn_methods.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/slice" +require "active_record/relation/merger" + +module ActiveRecord + module SpawnMethods + def spawn # :nodoc: + already_in_scope?(model.scope_registry) ? model.all : clone + end + + # Merges in the conditions from other, if other is an ActiveRecord::Relation. + # Returns an array representing the intersection of the resulting records with other, if other is an array. + # + # Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) ) + # # Performs a single join query with both where conditions. + # + # recent_posts = Post.order('created_at DESC').first(5) + # Post.where(published: true).merge(recent_posts) + # # Returns the intersection of all published posts with the 5 most recently created posts. + # # (This is just an example. You'd probably want to do this with a single query!) + # + # Procs will be evaluated by merge: + # + # Post.where(published: true).merge(-> { joins(:comments) }) + # # => Post.where(published: true).joins(:comments) + # + # This is mainly intended for sharing common conditions between multiple associations. + # + # For conditions that exist in both relations, those from other will take precedence. + # To find the intersection of two relations, use QueryMethods#and. + def merge(other, *rest) + if other.is_a?(Array) + records & other + elsif other + spawn.merge!(other, *rest) + else + raise ArgumentError, "invalid argument: #{other.inspect}." + end + end + + def merge!(other, *rest) # :nodoc: + if other.is_a?(Hash) + Relation::HashMerger.new(self, other).merge + elsif other.is_a?(Relation) + Relation::Merger.new(self, other).merge + elsif other.respond_to?(:to_proc) + instance_exec(&other) + else + raise ArgumentError, "#{other.inspect} is not an ActiveRecord::Relation" + end + end + + # Removes from the query the condition(s) specified in +skips+. + # + # Post.order('id asc').except(:order) # discards the order condition + # Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order + def except(*skips) + relation_with values.except(*skips) + end + + # Removes any condition from the query other than the one(s) specified in +onlies+. + # + # Post.order('id asc').only(:where) # discards the order condition + # Post.order('id asc').only(:where, :order) # uses the specified order + def only(*onlies) + relation_with values.slice(*onlies) + end + + private + def relation_with(values) + result = spawn + result.instance_variable_set(:@values, values) + result + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/where_clause.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/where_clause.rb new file mode 100644 index 00000000..3da41988 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/relation/where_clause.rb @@ -0,0 +1,218 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/extract" + +module ActiveRecord + class Relation + class WhereClause # :nodoc: + delegate :any?, :empty?, to: :predicates + + def initialize(predicates) + @predicates = predicates + end + + def +(other) + WhereClause.new(predicates + other.predicates) + end + + def -(other) + WhereClause.new(predicates - other.predicates) + end + + def |(other) + WhereClause.new(predicates | other.predicates) + end + + def merge(other) + predicates = except_predicates(other.extract_attributes) + + WhereClause.new(predicates | other.predicates) + end + + def except(*columns) + WhereClause.new(except_predicates(columns)) + end + + def or(other) + left = self - other + common = self - left + right = other - common + + if left.empty? || right.empty? + common + else + left = left.ast + left = left.expr if left.is_a?(Arel::Nodes::Grouping) + + right = right.ast + right = right.expr if right.is_a?(Arel::Nodes::Grouping) + + or_clause = if left.is_a?(Arel::Nodes::Or) + Arel::Nodes::Or.new(left.children + [right]) + else + Arel::Nodes::Or.new([left, right]) + end + + common.predicates << Arel::Nodes::Grouping.new(or_clause) + common + end + end + + def to_h(table_name = nil, equality_only: false) + equalities(predicates, equality_only).each_with_object({}) do |node, hash| + next if table_name&.!= node.left.relation.name + name = node.left.name.to_s + value = extract_node_value(node.right) + hash[name] = value + end + end + + def ast + predicates = predicates_with_wrapped_sql_literals + predicates.one? ? predicates.first : Arel::Nodes::And.new(predicates) + end + + def ==(other) + other.is_a?(WhereClause) && + predicates == other.predicates + end + alias :eql? :== + + def hash + [self.class, predicates].hash + end + + def invert + if predicates.size == 1 + inverted_predicates = [ invert_predicate(predicates.first) ] + else + inverted_predicates = [ Arel::Nodes::Not.new(ast) ] + end + + WhereClause.new(inverted_predicates) + end + + def self.empty + @empty ||= new([]).freeze + end + + def contradiction? + predicates.any? do |x| + case x + when Arel::Nodes::In + Array === x.right && x.right.empty? + when Arel::Nodes::Equality + x.right.respond_to?(:unboundable?) && x.right.unboundable? + end + end + end + + def extract_attributes + attrs = [] + each_attributes { |attr, _| attrs << attr } + attrs + end + + protected + attr_reader :predicates + + def referenced_columns + hash = {} + each_attributes { |attr, node| hash[attr] = node } + hash + end + + private + def each_attributes + predicates.each do |node| + attr = extract_attribute(node) || begin + node.left if equality_node?(node) && node.left.is_a?(Arel::Predications) + end + + yield attr, node if attr + end + end + + def extract_attribute(node) + attr_node = nil + Arel.fetch_attribute(node) do |attr| + return if attr_node&.!= attr # all attr nodes should be the same + attr_node = attr + end + attr_node + end + + def equalities(predicates, equality_only) + equalities = [] + + predicates.each do |node| + if equality_only ? Arel::Nodes::Equality === node : equality_node?(node) + equalities << node + elsif node.is_a?(Arel::Nodes::And) + equalities.concat equalities(node.children, equality_only) + end + end + + equalities + end + + def equality_node?(node) + !node.is_a?(String) && node.equality? + end + + def invert_predicate(node) + case node + when NilClass + raise ArgumentError, "Invalid argument for .where.not(), got nil." + when String + Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node)) + else + node.invert + end + end + + def except_predicates(columns) + attrs = columns.extract! { |node| node.is_a?(Arel::Attribute) } + non_attrs = columns.extract! { |node| node.is_a?(Arel::Predications) } + + predicates.reject do |node| + if !non_attrs.empty? && node.equality? && node.left.is_a?(Arel::Predications) + non_attrs.include?(node.left) + end || Arel.fetch_attribute(node) do |attr| + attrs.include?(attr) || columns.include?(attr.name.to_s) + end + end + end + + def predicates_with_wrapped_sql_literals + non_empty_predicates.map do |node| + case node + when Arel::Nodes::SqlLiteral, ::String + wrap_sql_literal(node) + else node + end + end + end + + ARRAY_WITH_EMPTY_STRING = [""] + def non_empty_predicates + predicates - ARRAY_WITH_EMPTY_STRING + end + + def wrap_sql_literal(node) + if ::String === node + node = Arel.sql(node) + end + Arel::Nodes::Grouping.new(node) + end + + def extract_node_value(node) + if node.respond_to?(:value_before_type_cast) + node.value_before_type_cast + elsif Array === node + node.map { |v| extract_node_value(v) } + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/result.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/result.rb new file mode 100644 index 00000000..2b6b307c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/result.rb @@ -0,0 +1,250 @@ +# frozen_string_literal: true + +module ActiveRecord + ### + # = Active Record \Result + # + # This class encapsulates a result returned from calling + # {#exec_query}[rdoc-ref:ConnectionAdapters::DatabaseStatements#exec_query] + # on any database connection adapter. For example: + # + # result = ActiveRecord::Base.lease_connection.exec_query('SELECT id, title, body FROM posts') + # result # => # + # + # # Get the column names of the result: + # result.columns + # # => ["id", "title", "body"] + # + # # Get the record values of the result: + # result.rows + # # => [[1, "title_1", "body_1"], + # [2, "title_2", "body_2"], + # ... + # ] + # + # # Get an array of hashes representing the result (column => value): + # result.to_a + # # => [{"id" => 1, "title" => "title_1", "body" => "body_1"}, + # {"id" => 2, "title" => "title_2", "body" => "body_2"}, + # ... + # ] + # + # # ActiveRecord::Result also includes Enumerable. + # result.each do |row| + # puts row['title'] + " " + row['body'] + # end + class Result + include Enumerable + + class IndexedRow + def initialize(column_indexes, row) + @column_indexes = column_indexes + @row = row + end + + def size + @column_indexes.size + end + alias_method :length, :size + + def each_key(&block) + @column_indexes.each_key(&block) + end + + def keys + @column_indexes.keys + end + + def ==(other) + if other.is_a?(Hash) + to_hash == other + else + super + end + end + + def key?(column) + @column_indexes.key?(column) + end + + def fetch(column) + if index = @column_indexes[column] + @row[index] + elsif block_given? + yield + else + raise KeyError, "key not found: #{column.inspect}" + end + end + + def [](column) + if index = @column_indexes[column] + @row[index] + end + end + + def to_h + @column_indexes.transform_values { |index| @row[index] } + end + alias_method :to_hash, :to_h + end + + attr_reader :columns, :rows, :column_types + + def self.empty(async: false) # :nodoc: + if async + EMPTY_ASYNC + else + EMPTY + end + end + + def initialize(columns, rows, column_types = nil) + # We freeze the strings to prevent them getting duped when + # used as keys in ActiveRecord::Base's @attributes hash + @columns = columns.each(&:-@).freeze + @rows = rows + @hash_rows = nil + @column_types = column_types || EMPTY_HASH + @column_indexes = nil + end + + # Returns true if this result set includes the column named +name+ + def includes_column?(name) + @columns.include? name + end + + # Returns the number of elements in the rows array. + def length + @rows.length + end + + # Calls the given block once for each element in row collection, passing + # row as parameter. Each row is a Hash-like, read only object. + # + # To get real hashes, use +.to_a.each+. + # + # Returns an +Enumerator+ if no block is given. + def each(&block) + if block_given? + hash_rows.each(&block) + else + hash_rows.to_enum { @rows.size } + end + end + + # Returns true if there are no records, otherwise false. + def empty? + rows.empty? + end + + # Returns an array of hashes representing each row record. + def to_ary + hash_rows + end + + alias :to_a :to_ary + + def [](idx) + hash_rows[idx] + end + + # Returns the last record from the rows collection. + def last(n = nil) + n ? hash_rows.last(n) : hash_rows.last + end + + def result # :nodoc: + self + end + + def cancel # :nodoc: + self + end + + def cast_values(type_overrides = {}) # :nodoc: + if columns.one? + # Separated to avoid allocating an array per row + + type = if type_overrides.is_a?(Array) + type_overrides.first + else + column_type(columns.first, 0, type_overrides) + end + + rows.map do |(value)| + type.deserialize(value) + end + else + types = if type_overrides.is_a?(Array) + type_overrides + else + columns.map.with_index { |name, i| column_type(name, i, type_overrides) } + end + + rows.map do |values| + Array.new(values.size) { |i| types[i].deserialize(values[i]) } + end + end + end + + def initialize_copy(other) + @rows = rows.dup + @column_types = column_types.dup + @hash_rows = nil + end + + def freeze # :nodoc: + hash_rows.freeze + indexed_rows.freeze + super + end + + def column_indexes # :nodoc: + @column_indexes ||= begin + index = 0 + hash = {} + length = columns.length + while index < length + hash[columns[index]] = index + index += 1 + end + hash.freeze + end + end + + def indexed_rows # :nodoc: + @indexed_rows ||= begin + columns = column_indexes + @rows.map { |row| IndexedRow.new(columns, row) }.freeze + end + end + + private + def column_type(name, index, type_overrides) + type_overrides.fetch(name) do + column_types.fetch(index) do + column_types.fetch(name, Type.default_value) + end + end + end + + def hash_rows + # We use transform_values to rows. + # This is faster because we avoid any reallocs and avoid hashing entirely. + @hash_rows ||= @rows.map do |row| + column_indexes.transform_values { |index| row[index] } + end + end + + empty_array = [].freeze + EMPTY_HASH = {}.freeze + private_constant :EMPTY_HASH + + EMPTY = new(empty_array, empty_array, EMPTY_HASH).freeze + private_constant :EMPTY + + EMPTY_ASYNC = FutureResult.wrap(EMPTY).freeze + private_constant :EMPTY_ASYNC + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/runtime_registry.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/runtime_registry.rb new file mode 100644 index 00000000..3b35779d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/runtime_registry.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +module ActiveRecord + # This is a thread locals registry for Active Record. For example: + # + # ActiveRecord::RuntimeRegistry.sql_runtime + # + # returns the connection handler local to the current unit of execution (either thread of fiber). + module RuntimeRegistry # :nodoc: + extend self + + def sql_runtime + ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] ||= 0.0 + end + + def sql_runtime=(runtime) + ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] = runtime + end + + def async_sql_runtime + ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] ||= 0.0 + end + + def async_sql_runtime=(runtime) + ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] = runtime + end + + def queries_count + ActiveSupport::IsolatedExecutionState[:active_record_queries_count] ||= 0 + end + + def queries_count=(count) + ActiveSupport::IsolatedExecutionState[:active_record_queries_count] = count + end + + def cached_queries_count + ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] ||= 0 + end + + def cached_queries_count=(count) + ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] = count + end + + def reset + reset_runtimes + reset_queries_count + reset_cached_queries_count + end + + def reset_runtimes + rt, self.sql_runtime = sql_runtime, 0.0 + self.async_sql_runtime = 0.0 + rt + end + + def reset_queries_count + qc = queries_count + self.queries_count = 0 + qc + end + + def reset_cached_queries_count + qc = cached_queries_count + self.cached_queries_count = 0 + qc + end + end +end + +ActiveSupport::Notifications.monotonic_subscribe("sql.active_record") do |name, start, finish, id, payload| + unless ["SCHEMA", "TRANSACTION"].include?(payload[:name]) + ActiveRecord::RuntimeRegistry.queries_count += 1 + ActiveRecord::RuntimeRegistry.cached_queries_count += 1 if payload[:cached] + end + + runtime = (finish - start) * 1_000.0 + + if payload[:async] + ActiveRecord::RuntimeRegistry.async_sql_runtime += (runtime - payload[:lock_wait]) + end + ActiveRecord::RuntimeRegistry.sql_runtime += runtime +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/sanitization.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/sanitization.rb new file mode 100644 index 00000000..e80f45d1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/sanitization.rb @@ -0,0 +1,254 @@ +# frozen_string_literal: true + +module ActiveRecord + module Sanitization + extend ActiveSupport::Concern + + module ClassMethods + # Accepts an array of SQL conditions and sanitizes them into a valid + # SQL fragment for a WHERE clause. + # + # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4]) + # # => "name='foo''bar' and group_id=4" + # + # sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4]) + # # => "name='foo''bar' and group_id='4'" + # + # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4]) + # # => "name='foo''bar' and group_id='4'" + # + # This method will NOT sanitize an SQL string since it won't contain + # any conditions in it and will return the string as is. + # + # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'") + # # => "name='foo''bar' and group_id='4'" + # + # Note that this sanitization method is not schema-aware, hence won't do any type casting + # and will directly use the database adapter's +quote+ method. + # For MySQL specifically this means that numeric parameters will be quoted as strings + # to prevent query manipulation attacks. + # + # sanitize_sql_for_conditions(["role = ?", 0]) + # # => "role = '0'" + def sanitize_sql_for_conditions(condition) + return nil if condition.blank? + + case condition + when Array; sanitize_sql_array(condition) + else condition + end + end + alias :sanitize_sql :sanitize_sql_for_conditions + + # Accepts an array or hash of SQL conditions and sanitizes them into + # a valid SQL fragment for a SET clause. + # + # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4]) + # # => "name=NULL and group_id=4" + # + # sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4]) + # # => "name=NULL and group_id=4" + # + # Post.sanitize_sql_for_assignment({ name: nil, group_id: 4 }) + # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4" + # + # This method will NOT sanitize an SQL string since it won't contain + # any conditions in it and will return the string as is. + # + # sanitize_sql_for_assignment("name=NULL and group_id='4'") + # # => "name=NULL and group_id='4'" + # + # Note that this sanitization method is not schema-aware, hence won't do any type casting + # and will directly use the database adapter's +quote+ method. + # For MySQL specifically this means that numeric parameters will be quoted as strings + # to prevent query manipulation attacks. + # + # sanitize_sql_for_assignment(["role = ?", 0]) + # # => "role = '0'" + def sanitize_sql_for_assignment(assignments, default_table_name = table_name) + case assignments + when Array; sanitize_sql_array(assignments) + when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name) + else assignments + end + end + + # Accepts an array, or string of SQL conditions and sanitizes + # them into a valid SQL fragment for an ORDER clause. + # + # sanitize_sql_for_order([Arel.sql("field(id, ?)"), [1,3,2]]) + # # => "field(id, 1,3,2)" + # + # sanitize_sql_for_order("id ASC") + # # => "id ASC" + def sanitize_sql_for_order(condition) + if condition.is_a?(Array) && condition.first.to_s.include?("?") + disallow_raw_sql!( + [condition.first], + permit: adapter_class.column_name_with_order_matcher + ) + + # Ensure we aren't dealing with a subclass of String that might + # override methods we use (e.g. Arel::Nodes::SqlLiteral). + if condition.first.kind_of?(String) && !condition.first.instance_of?(String) + condition = [String.new(condition.first), *condition[1..-1]] + end + + Arel.sql(sanitize_sql_array(condition)) + else + condition + end + end + + # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause. + # + # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts") + # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1" + def sanitize_sql_hash_for_assignment(attrs, table) + with_connection do |c| + attrs.map do |attr, value| + type = type_for_attribute(attr) + value = type.serialize(type.cast(value)) + "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}" + end.join(", ") + end + end + + # Sanitizes a +string+ so that it is safe to use within an SQL + # LIKE statement. This method uses +escape_character+ to escape all + # occurrences of itself, "_" and "%". + # + # sanitize_sql_like("100% true!") + # # => "100\\% true!" + # + # sanitize_sql_like("snake_cased_string") + # # => "snake\\_cased\\_string" + # + # sanitize_sql_like("100% true!", "!") + # # => "100!% true!!" + # + # sanitize_sql_like("snake_cased_string", "!") + # # => "snake!_cased!_string" + def sanitize_sql_like(string, escape_character = "\\") + if string.include?(escape_character) && escape_character != "%" && escape_character != "_" + string = string.gsub(escape_character, '\0\0') + end + + string.gsub(/(?=[%_])/, escape_character) + end + + # Accepts an array of conditions. The array has each value + # sanitized and interpolated into the SQL statement. If using named bind + # variables in SQL statements where a colon is required verbatim use a + # backslash to escape. + # + # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4]) + # # => "name='foo''bar' and group_id=4" + # + # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4]) + # # => "name='foo''bar' and group_id=4" + # + # sanitize_sql_array(["TO_TIMESTAMP(:date, 'YYYY/MM/DD HH12\\:MI\\:SS')", date: "foo"]) + # # => "TO_TIMESTAMP('foo', 'YYYY/MM/DD HH12:MI:SS')" + # + # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4]) + # # => "name='foo''bar' and group_id='4'" + # + # Note that this sanitization method is not schema-aware, hence won't do any type casting + # and will directly use the database adapter's +quote+ method. + # For MySQL specifically this means that numeric parameters will be quoted as strings + # to prevent query manipulation attacks. + # + # sanitize_sql_array(["role = ?", 0]) + # # => "role = '0'" + def sanitize_sql_array(ary) + statement, *values = ary + if values.first.is_a?(Hash) && /:\w+/.match?(statement) + with_connection do |c| + replace_named_bind_variables(c, statement, values.first) + end + elsif statement.include?("?") + with_connection do |c| + replace_bind_variables(c, statement, values) + end + elsif statement.blank? + statement + else + with_connection do |c| + statement % values.collect { |value| c.quote_string(value.to_s) } + end + end + end + + def disallow_raw_sql!(args, permit: adapter_class.column_name_matcher) # :nodoc: + unexpected = nil + args.each do |arg| + next if arg.is_a?(Symbol) || Arel.arel_node?(arg) || permit.match?(arg.to_s.strip) + (unexpected ||= []) << arg + end + + if unexpected + raise(ActiveRecord::UnknownAttributeReference, + "Dangerous query method (method whose arguments are used as raw " \ + "SQL) called with non-attribute argument(s): " \ + "#{unexpected.map(&:inspect).join(", ")}." \ + "This method should not be called with user-provided values, such as request " \ + "parameters or model attributes. Known-safe values can be passed " \ + "by wrapping them in Arel.sql()." + ) + end + end + + private + def replace_bind_variables(connection, statement, values) + raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size) + bound = values.dup + statement.gsub(/\?/) do + replace_bind_variable(connection, bound.shift) + end + end + + def replace_bind_variable(connection, value) + if ActiveRecord::Relation === value + value.to_sql + else + quote_bound_value(connection, value) + end + end + + def replace_named_bind_variables(connection, statement, bind_vars) + statement.gsub(/([:\\]?):([a-zA-Z]\w*)/) do |match| + if $1 == ":" # skip PostgreSQL casts + match # return the whole match + elsif $1 == "\\" # escaped literal colon + match[1..-1] # return match with escaping backlash char removed + elsif bind_vars.include?(match = $2.to_sym) + replace_bind_variable(connection, bind_vars[match]) + else + raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}" + end + end + end + + def quote_bound_value(connection, value) + if value.respond_to?(:map) && !value.acts_like?(:string) + values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v } + if values.empty? + connection.quote(connection.cast_bound_value(nil)) + else + values.map! { |v| connection.quote(connection.cast_bound_value(v)) }.join(",") + end + else + value = value.id_for_database if value.respond_to?(:id_for_database) + connection.quote(connection.cast_bound_value(value)) + end + end + + def raise_if_bind_arity_mismatch(statement, expected, provided) + unless expected == provided + raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/schema.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/schema.rb new file mode 100644 index 00000000..152752a0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/schema.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record \Schema + # + # Allows programmers to programmatically define a schema in a portable + # DSL. This means you can define tables, indexes, etc. without using SQL + # directly, so your applications can more easily support multiple + # databases. + # + # Usage: + # + # ActiveRecord::Schema[7.0].define do + # create_table :authors do |t| + # t.string :name, null: false + # end + # + # add_index :authors, :name, :unique + # + # create_table :posts do |t| + # t.integer :author_id, null: false + # t.string :subject + # t.text :body + # t.boolean :private, default: false + # end + # + # add_index :posts, :author_id + # end + # + # ActiveRecord::Schema is only supported by database adapters that also + # support migrations, the two features being very similar. + class Schema < Migration::Current + module Definition + extend ActiveSupport::Concern + + module ClassMethods + # Eval the given block. All methods available to the current connection + # adapter are available within the block, so you can easily use the + # database definition DSL to build up your schema ( + # {create_table}[rdoc-ref:ConnectionAdapters::SchemaStatements#create_table], + # {add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index], etc.). + # + # The +info+ hash is optional, and if given is used to define metadata + # about the current schema (currently, only the schema's version): + # + # ActiveRecord::Schema[7.0].define(version: 2038_01_19_000001) do + # ... + # end + def define(info = {}, &block) + new.define(info, &block) + end + end + + def define(info, &block) # :nodoc: + connection_pool.with_connection do |connection| + instance_eval(&block) + + connection_pool.schema_migration.create_table + if info[:version].present? + connection.assume_migrated_upto_version(info[:version]) + end + + connection_pool.internal_metadata.create_table_and_set_flags(connection_pool.migration_context.current_environment) + end + end + end + + include Definition + + def self.[](version) + @class_for_version ||= {} + @class_for_version[version] ||= Class.new(Migration::Compatibility.find(version)) do + include Definition + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/schema_dumper.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/schema_dumper.rb new file mode 100644 index 00000000..cb5ce1ee --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/schema_dumper.rb @@ -0,0 +1,382 @@ +# frozen_string_literal: true + +require "stringio" + +module ActiveRecord + # = Active Record Schema Dumper + # + # This class is used to dump the database schema for some connection to some + # output format (i.e., ActiveRecord::Schema). + class SchemaDumper # :nodoc: + private_class_method :new + + ## + # :singleton-method: + # A list of tables which should not be dumped to the schema. + # Acceptable values are strings and regexps. + cattr_accessor :ignore_tables, default: [] + + ## + # :singleton-method: + # Specify a custom regular expression matching foreign keys which name + # should not be dumped to db/schema.rb. + cattr_accessor :fk_ignore_pattern, default: /^fk_rails_[0-9a-f]{10}$/ + + ## + # :singleton-method: + # Specify a custom regular expression matching check constraints which name + # should not be dumped to db/schema.rb. + cattr_accessor :chk_ignore_pattern, default: /^chk_rails_[0-9a-f]{10}$/ + + ## + # :singleton-method: + # Specify a custom regular expression matching exclusion constraints which name + # should not be dumped to db/schema.rb. + cattr_accessor :excl_ignore_pattern, default: /^excl_rails_[0-9a-f]{10}$/ + + ## + # :singleton-method: + # Specify a custom regular expression matching unique constraints which name + # should not be dumped to db/schema.rb. + cattr_accessor :unique_ignore_pattern, default: /^uniq_rails_[0-9a-f]{10}$/ + + class << self + def dump(pool = ActiveRecord::Base.connection_pool, stream = $stdout, config = ActiveRecord::Base) + pool.with_connection do |connection| + connection.create_schema_dumper(generate_options(config)).dump(stream) + end + stream + end + + private + def generate_options(config) + { + table_name_prefix: config.table_name_prefix, + table_name_suffix: config.table_name_suffix + } + end + end + + def dump(stream) + header(stream) + schemas(stream) + extensions(stream) + types(stream) + tables(stream) + virtual_tables(stream) + trailer(stream) + stream + end + + private + attr_accessor :table_name + + def initialize(connection, options = {}) + @connection = connection + @version = connection.pool.migration_context.current_version rescue nil + @options = options + @ignore_tables = [ + ActiveRecord::Base.schema_migrations_table_name, + ActiveRecord::Base.internal_metadata_table_name, + self.class.ignore_tables + ].flatten + end + + # turns 20170404131909 into "2017_04_04_131909" + def formatted_version + stringified = @version.to_s + return stringified unless stringified.length == 14 + stringified.insert(4, "_").insert(7, "_").insert(10, "_") + end + + def define_params + @version ? "version: #{formatted_version}" : "" + end + + def header(stream) + stream.puts <<~HEADER + # This file is auto-generated from the current state of the database. Instead + # of editing this file, please use the migrations feature of Active Record to + # incrementally modify your database, and then regenerate this schema definition. + # + # This file is the source Rails uses to define your schema when running `bin/rails + # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to + # be faster and is potentially less error prone than running all of your + # migrations from scratch. Old migrations may fail to apply correctly if those + # migrations use external dependencies or application code. + # + # It's strongly recommended that you check this file into your version control system. + + ActiveRecord::Schema[#{ActiveRecord::Migration.current_version}].define(#{define_params}) do + HEADER + end + + def trailer(stream) + stream.puts "end" + end + + # extensions are only supported by PostgreSQL + def extensions(stream) + end + + # (enum) types are only supported by PostgreSQL + def types(stream) + end + + # schemas are only supported by PostgreSQL + def schemas(stream) + end + + # virtual tables are only supported by SQLite + def virtual_tables(stream) + end + + def tables(stream) + sorted_tables = @connection.tables.sort + + not_ignored_tables = sorted_tables.reject { |table_name| ignored?(table_name) } + + not_ignored_tables.each_with_index do |table_name, index| + table(table_name, stream) + stream.puts if index < not_ignored_tables.count - 1 + end + + # dump foreign keys at the end to make sure all dependent tables exist. + if @connection.supports_foreign_keys? + foreign_keys_stream = StringIO.new + not_ignored_tables.each do |tbl| + foreign_keys(tbl, foreign_keys_stream) + end + + foreign_keys_string = foreign_keys_stream.string + stream.puts if foreign_keys_string.length > 0 + + stream.print foreign_keys_string + end + end + + def table(table, stream) + columns = @connection.columns(table) + begin + self.table_name = table + + tbl = StringIO.new + + # first dump primary key column + pk = @connection.primary_key(table) + + tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}" + + case pk + when String + tbl.print ", primary_key: #{pk.inspect}" unless pk == "id" + pkcol = columns.detect { |c| c.name == pk } + pkcolspec = column_spec_for_primary_key(pkcol) + unless pkcolspec.empty? + if pkcolspec != pkcolspec.slice(:id, :default) + pkcolspec = { id: { type: pkcolspec.delete(:id), **pkcolspec }.compact } + end + tbl.print ", #{format_colspec(pkcolspec)}" + end + when Array + tbl.print ", primary_key: #{pk.inspect}" + else + tbl.print ", id: false" + end + + table_options = @connection.table_options(table) + if table_options.present? + tbl.print ", #{format_options(table_options)}" + end + + tbl.puts ", force: :cascade do |t|" + + # then dump all non-primary key columns + columns.each do |column| + raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type) + next if column.name == pk + + type, colspec = column_spec(column) + if type.is_a?(Symbol) + tbl.print " t.#{type} #{column.name.inspect}" + else + tbl.print " t.column #{column.name.inspect}, #{type.inspect}" + end + tbl.print ", #{format_colspec(colspec)}" if colspec.present? + tbl.puts + end + + indexes_in_create(table, tbl) + remaining = check_constraints_in_create(table, tbl) if @connection.supports_check_constraints? + exclusion_constraints_in_create(table, tbl) if @connection.supports_exclusion_constraints? + unique_constraints_in_create(table, tbl) if @connection.supports_unique_constraints? + + tbl.puts " end" + + if remaining + tbl.puts + tbl.print remaining.string + end + + stream.print tbl.string + rescue => e + stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}" + stream.puts "# #{e.message}" + stream.puts + ensure + self.table_name = nil + end + end + + # Keep it for indexing materialized views + def indexes(table, stream) + if (indexes = @connection.indexes(table)).any? + add_index_statements = indexes.map do |index| + table_name = remove_prefix_and_suffix(index.table).inspect + " add_index #{([table_name] + index_parts(index)).join(', ')}" + end + + stream.puts add_index_statements.sort.join("\n") + stream.puts + end + end + + def indexes_in_create(table, stream) + if (indexes = @connection.indexes(table)).any? + if @connection.supports_exclusion_constraints? && (exclusion_constraints = @connection.exclusion_constraints(table)).any? + exclusion_constraint_names = exclusion_constraints.collect(&:name) + + indexes = indexes.reject { |index| exclusion_constraint_names.include?(index.name) } + end + + if @connection.supports_unique_constraints? && (unique_constraints = @connection.unique_constraints(table)).any? + unique_constraint_names = unique_constraints.collect(&:name) + + indexes = indexes.reject { |index| unique_constraint_names.include?(index.name) } + end + + index_statements = indexes.map do |index| + " t.index #{index_parts(index).join(', ')}" + end + stream.puts index_statements.sort.join("\n") + end + end + + def index_parts(index) + index_parts = [ + index.columns.inspect, + "name: #{index.name.inspect}", + ] + index_parts << "unique: true" if index.unique + index_parts << "length: #{format_index_parts(index.lengths)}" if index.lengths.present? + index_parts << "order: #{format_index_parts(index.orders)}" if index.orders.present? + index_parts << "opclass: #{format_index_parts(index.opclasses)}" if index.opclasses.present? + index_parts << "where: #{index.where.inspect}" if index.where + index_parts << "using: #{index.using.inspect}" if !@connection.default_index_type?(index) + index_parts << "include: #{index.include.inspect}" if index.include + index_parts << "nulls_not_distinct: #{index.nulls_not_distinct.inspect}" if index.nulls_not_distinct + index_parts << "type: #{index.type.inspect}" if index.type + index_parts << "comment: #{index.comment.inspect}" if index.comment + index_parts + end + + def check_constraints_in_create(table, stream) + if (check_constraints = @connection.check_constraints(table)).any? + check_valid, check_invalid = check_constraints.partition { |chk| chk.validate? } + + unless check_valid.empty? + check_constraint_statements = check_valid.map do |check| + " t.check_constraint #{check_parts(check).join(', ')}" + end + + stream.puts check_constraint_statements.sort.join("\n") + end + + unless check_invalid.empty? + remaining = StringIO.new + table_name = remove_prefix_and_suffix(table).inspect + + add_check_constraint_statements = check_invalid.map do |check| + " add_check_constraint #{([table_name] + check_parts(check)).join(', ')}" + end + + remaining.puts add_check_constraint_statements.sort.join("\n") + remaining + end + end + end + + def check_parts(check) + check_parts = [ check.expression.inspect ] + check_parts << "name: #{check.name.inspect}" if check.export_name_on_schema_dump? + check_parts << "validate: #{check.validate?.inspect}" unless check.validate? + check_parts + end + + def foreign_keys(table, stream) + if (foreign_keys = @connection.foreign_keys(table)).any? + add_foreign_key_statements = foreign_keys.map do |foreign_key| + parts = [ + "add_foreign_key #{remove_prefix_and_suffix(foreign_key.from_table).inspect}", + remove_prefix_and_suffix(foreign_key.to_table).inspect, + ] + + if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table, "id") + parts << "column: #{foreign_key.column.inspect}" + end + + if foreign_key.custom_primary_key? + parts << "primary_key: #{foreign_key.primary_key.inspect}" + end + + if foreign_key.export_name_on_schema_dump? + parts << "name: #{foreign_key.name.inspect}" + end + + parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update + parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete + parts << "deferrable: #{foreign_key.deferrable.inspect}" if foreign_key.deferrable + parts << "validate: #{foreign_key.validate?.inspect}" unless foreign_key.validate? + + " #{parts.join(', ')}" + end + + stream.puts add_foreign_key_statements.sort.join("\n") + end + end + + def format_colspec(colspec) + colspec.map do |key, value| + "#{key}: #{ value.is_a?(Hash) ? "{ #{format_colspec(value)} }" : value }" + end.join(", ") + end + + def format_options(options) + options.map { |key, value| "#{key}: #{value.inspect}" }.join(", ") + end + + def format_index_parts(options) + if options.is_a?(Hash) + "{ #{format_options(options)} }" + else + options.inspect + end + end + + def remove_prefix_and_suffix(table) + # This method appears at the top when profiling active_record test cases run. + # Avoid costly calculation when there are no prefix and suffix. + return table if @options[:table_name_prefix].blank? && @options[:table_name_suffix].blank? + + prefix = Regexp.escape(@options[:table_name_prefix].to_s) + suffix = Regexp.escape(@options[:table_name_suffix].to_s) + table.sub(/\A#{prefix}(.+)#{suffix}\z/, "\\1") + end + + def ignored?(table_name) + @ignore_tables.any? do |ignored| + ignored === remove_prefix_and_suffix(table_name) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/schema_migration.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/schema_migration.rb new file mode 100644 index 00000000..aa576ec1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/schema_migration.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +module ActiveRecord + # This class is used to create a table that keeps track of which migrations + # have been applied to a given database. When a migration is run, its schema + # number is inserted in to the schema migrations table so it doesn't need + # to be executed the next time. + class SchemaMigration # :nodoc: + class NullSchemaMigration # :nodoc: + end + + attr_reader :arel_table + + def initialize(pool) + @pool = pool + @arel_table = Arel::Table.new(table_name) + end + + def create_version(version) + im = Arel::InsertManager.new(arel_table) + im.insert(arel_table[primary_key] => version) + @pool.with_connection do |connection| + connection.insert(im, "#{self.class} Create", primary_key, version) + end + end + + def delete_version(version) + dm = Arel::DeleteManager.new(arel_table) + dm.wheres = [arel_table[primary_key].eq(version)] + + @pool.with_connection do |connection| + connection.delete(dm, "#{self.class} Destroy") + end + end + + def delete_all_versions + # Eagerly check in connection to avoid checking in/out many times in the called method. + @pool.with_connection do + versions.each do |version| + delete_version(version) + end + end + end + + def primary_key + "version" + end + + def table_name + "#{ActiveRecord::Base.table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{ActiveRecord::Base.table_name_suffix}" + end + + def create_table + @pool.with_connection do |connection| + unless connection.table_exists?(table_name) + connection.create_table(table_name, id: false) do |t| + t.string :version, **connection.internal_string_options_for_primary_key + end + end + end + end + + def drop_table + @pool.with_connection do |connection| + connection.drop_table table_name, if_exists: true + end + end + + def normalize_migration_number(number) + "%.3d" % number.to_i + end + + def normalized_versions + versions.map { |v| normalize_migration_number v } + end + + def versions + sm = Arel::SelectManager.new(arel_table) + sm.project(arel_table[primary_key]) + sm.order(arel_table[primary_key].asc) + + @pool.with_connection do |connection| + connection.select_values(sm, "#{self.class} Load") + end + end + + def integer_versions + versions.map(&:to_i) + end + + def count + sm = Arel::SelectManager.new(arel_table) + sm.project(*Arel::Nodes::Count.new([Arel.star])) + + @pool.with_connection do |connection| + connection.select_values(sm, "#{self.class} Count").first + end + end + + def table_exists? + @pool.with_connection do |connection| + connection.data_source_exists?(table_name) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/scoping.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/scoping.rb new file mode 100644 index 00000000..7ba81253 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/scoping.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/delegation" + +module ActiveRecord + module Scoping + extend ActiveSupport::Concern + + included do + include Default + include Named + end + + module ClassMethods # :nodoc: + # Collects attributes from scopes that should be applied when creating + # an AR instance for the particular class this is called on. + def scope_attributes + all.scope_for_create + end + + # Are there attributes associated with this scope? + def scope_attributes? + current_scope + end + + def current_scope(skip_inherited_scope = false) + ScopeRegistry.current_scope(self, skip_inherited_scope) + end + + def current_scope=(scope) + ScopeRegistry.set_current_scope(self, scope) + end + + def global_current_scope(skip_inherited_scope = false) + ScopeRegistry.global_current_scope(self, skip_inherited_scope) + end + + def global_current_scope=(scope) + ScopeRegistry.set_global_current_scope(self, scope) + end + + def scope_registry + ScopeRegistry.instance + end + end + + def populate_with_current_scope_attributes # :nodoc: + return unless self.class.scope_attributes? + + attributes = self.class.scope_attributes + _assign_attributes(attributes) if attributes.any? + end + + def initialize_internals_callback # :nodoc: + super + populate_with_current_scope_attributes + end + + # This class stores the +:current_scope+ and +:ignore_default_scope+ values + # for different classes. The registry is stored as either a thread or fiber + # local depending on the application configuration. + # + # This class allows you to store and get the scope values on different + # classes and different types of scopes. For example, if you are attempting + # to get the current_scope for the +Board+ model, then you would use the + # following code: + # + # registry = ActiveRecord::Scoping::ScopeRegistry + # registry.set_current_scope(Board, some_new_scope) + # + # Now when you run: + # + # registry.current_scope(Board) + # + # You will obtain whatever was defined in +some_new_scope+. + class ScopeRegistry # :nodoc: + class << self + delegate :current_scope, :set_current_scope, :ignore_default_scope, :set_ignore_default_scope, + :global_current_scope, :set_global_current_scope, to: :instance + + def instance + ActiveSupport::IsolatedExecutionState[:active_record_scope_registry] ||= new + end + end + + def initialize + @current_scope = {} + @ignore_default_scope = {} + @global_current_scope = {} + end + + def current_scope(model, skip_inherited_scope = false) + value_for(@current_scope, model, skip_inherited_scope) + end + + def set_current_scope(model, value) + set_value_for(@current_scope, model, value) + end + + def ignore_default_scope(model, skip_inherited_scope = false) + value_for(@ignore_default_scope, model, skip_inherited_scope) + end + + def set_ignore_default_scope(model, value) + set_value_for(@ignore_default_scope, model, value) + end + + def global_current_scope(model, skip_inherited_scope = false) + value_for(@global_current_scope, model, skip_inherited_scope) + end + + def set_global_current_scope(model, value) + set_value_for(@global_current_scope, model, value) + end + + private + # Obtains the value for a given +scope_type+ and +model+. + def value_for(scope_type, model, skip_inherited_scope = false) + return scope_type[model.name] if skip_inherited_scope + klass = model + base = model.base_class + while klass != base + value = scope_type[klass.name] + return value if value + klass = klass.superclass + end + scope_type[klass.name] + end + + # Sets the +value+ for a given +scope_type+ and +model+. + def set_value_for(scope_type, model, value) + scope_type[model.name] = value + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/scoping/default.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/scoping/default.rb new file mode 100644 index 00000000..bde820a6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/scoping/default.rb @@ -0,0 +1,205 @@ +# frozen_string_literal: true + +module ActiveRecord + module Scoping + class DefaultScope # :nodoc: + attr_reader :scope, :all_queries + + def initialize(scope, all_queries = nil) + @scope = scope + @all_queries = all_queries + end + end + + module Default + extend ActiveSupport::Concern + + included do + # Stores the default scope for the class. + class_attribute :default_scopes, instance_writer: false, instance_predicate: false, default: [] + class_attribute :default_scope_override, instance_writer: false, instance_predicate: false, default: nil + end + + module ClassMethods + # Returns a scope for the model without the previously set scopes. + # + # class Post < ActiveRecord::Base + # belongs_to :user + # + # def self.default_scope + # where(published: true) + # end + # end + # + # class User < ActiveRecord::Base + # has_many :posts + # end + # + # Post.all # Fires "SELECT * FROM posts WHERE published = true" + # Post.unscoped.all # Fires "SELECT * FROM posts" + # Post.where(published: false).unscoped.all # Fires "SELECT * FROM posts" + # User.find(1).posts # Fires "SELECT * FROM posts WHERE published = true AND posts.user_id = 1" + # User.find(1).posts.unscoped # Fires "SELECT * FROM posts" + # + # This method also accepts a block. All queries inside the block will + # not use the previously set scopes. + # + # Post.unscoped { + # Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10" + # } + def unscoped(&block) + block_given? ? relation.scoping(&block) : relation + end + + # Are there attributes associated with this scope? + def scope_attributes? # :nodoc: + super || default_scopes.any? || respond_to?(:default_scope) + end + + # Checks if the model has any default scopes. If all_queries + # is set to true, the method will check if there are any + # default_scopes for the model where +all_queries+ is true. + def default_scopes?(all_queries: false) + if all_queries + self.default_scopes.any?(&:all_queries) + else + self.default_scopes.any? + end + end + + private + # Use this macro in your model to set a default scope for all operations on + # the model. + # + # class Article < ActiveRecord::Base + # default_scope { where(published: true) } + # end + # + # Article.all + # # SELECT * FROM articles WHERE published = true + # + # The #default_scope is also applied while creating/building a record. + # It is not applied while updating or deleting a record. + # + # Article.new.published # => true + # Article.create.published # => true + # + # To apply a #default_scope when updating or deleting a record, add + # all_queries: true: + # + # class Article < ActiveRecord::Base + # default_scope -> { where(blog_id: 1) }, all_queries: true + # end + # + # Applying a default scope to all queries will ensure that records + # are always queried by the additional conditions. Note that only + # where clauses apply, as it does not make sense to add order to + # queries that return a single object by primary key. + # + # Article.find(1).destroy + # # DELETE ... FROM `articles` where ID = 1 AND blog_id = 1; + # + # (You can also pass any object which responds to +call+ to the + # +default_scope+ macro, and it will be called when building the + # default scope.) + # + # If you use multiple #default_scope declarations in your model then + # they will be merged together: + # + # class Article < ActiveRecord::Base + # default_scope { where(published: true) } + # default_scope { where(rating: 'G') } + # end + # + # Article.all + # # SELECT * FROM articles WHERE published = true AND rating = 'G' + # + # This is also the case with inheritance and module includes where the + # parent or module defines a #default_scope and the child or including + # class defines a second one. + # + # If you need to do more complex things with a default scope, you can + # alternatively define it as a class method: + # + # class Article < ActiveRecord::Base + # def self.default_scope + # # Should return a scope, you can call 'super' here etc. + # end + # end + def default_scope(scope = nil, all_queries: nil, &block) # :doc: + scope = block if block_given? + + if scope.is_a?(Relation) || !scope.respond_to?(:call) + raise ArgumentError, + "Support for calling #default_scope without a block is removed. For example instead " \ + "of `default_scope where(color: 'red')`, please use " \ + "`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \ + "self.default_scope.)" + end + + default_scope = DefaultScope.new(scope, all_queries) + + self.default_scopes += [default_scope] + end + + def build_default_scope(relation = relation(), all_queries: nil) + return if abstract_class? + + if default_scope_override.nil? + self.default_scope_override = !Base.is_a?(method(:default_scope).owner) + end + + if default_scope_override + # The user has defined their own default scope method, so call that + evaluate_default_scope do + relation.scoping { default_scope } + end + elsif default_scopes.any? + evaluate_default_scope do + default_scopes.inject(relation) do |combined_scope, scope_obj| + if execute_scope?(all_queries, scope_obj) + scope = scope_obj.scope.respond_to?(:to_proc) ? scope_obj.scope : scope_obj.scope.method(:call) + + combined_scope.instance_exec(&scope) || combined_scope + else + combined_scope + end + end + end + end + end + + # If all_queries is nil, only execute on select and insert queries. + # + # If all_queries is true, check if the default_scope object has + # all_queries set, then execute on all queries; select, insert, update, + # delete, and reload. + def execute_scope?(all_queries, default_scope_obj) + all_queries.nil? || all_queries && default_scope_obj.all_queries + end + + def ignore_default_scope? + ScopeRegistry.ignore_default_scope(base_class) + end + + def ignore_default_scope=(ignore) + ScopeRegistry.set_ignore_default_scope(base_class, ignore) + end + + # The ignore_default_scope flag is used to prevent an infinite recursion + # situation where a default scope references a scope which has a default + # scope which references a scope... + def evaluate_default_scope + return if ignore_default_scope? + + begin + self.ignore_default_scope = true + yield + ensure + self.ignore_default_scope = false + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/scoping/named.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/scoping/named.rb new file mode 100644 index 00000000..cb1cf876 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/scoping/named.rb @@ -0,0 +1,202 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record \Named \Scopes + module Scoping + module Named + extend ActiveSupport::Concern + + module ClassMethods + # Returns an ActiveRecord::Relation scope object. + # + # posts = Post.all + # posts.size # Fires "select count(*) from posts" and returns the count + # posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects + # + # fruits = Fruit.all + # fruits = fruits.where(color: 'red') if options[:red_only] + # fruits = fruits.limit(10) if limited? + # + # You can define a scope that applies to all finders using + # {default_scope}[rdoc-ref:Scoping::Default::ClassMethods#default_scope]. + def all(all_queries: nil) + scope = current_scope + + if scope + if self == scope.model + scope.clone + else + relation.merge!(scope) + end + else + default_scoped(all_queries: all_queries) + end + end + + def scope_for_association(scope = relation) # :nodoc: + if current_scope&.empty_scope? + scope + else + default_scoped(scope) + end + end + + # Returns a scope for the model with default scopes. + def default_scoped(scope = relation, all_queries: nil) + build_default_scope(scope, all_queries: all_queries) || scope + end + + def default_extensions # :nodoc: + if scope = scope_for_association || build_default_scope + scope.extensions + else + [] + end + end + + # Adds a class method for retrieving and querying objects. + # The method is intended to return an ActiveRecord::Relation + # object, which is composable with other scopes. + # If it returns +nil+ or +false+, an + # {all}[rdoc-ref:Scoping::Named::ClassMethods#all] scope is returned instead. + # + # A \scope represents a narrowing of a database query, such as + # where(color: :red).select('shirts.*').includes(:washing_instructions). + # + # class Shirt < ActiveRecord::Base + # scope :red, -> { where(color: 'red') } + # scope :dry_clean_only, -> { joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true) } + # end + # + # The above calls to #scope define class methods Shirt.red and + # Shirt.dry_clean_only. Shirt.red, in effect, + # represents the query Shirt.where(color: 'red'). + # + # Note that this is simply 'syntactic sugar' for defining an actual + # class method: + # + # class Shirt < ActiveRecord::Base + # def self.red + # where(color: 'red') + # end + # end + # + # Unlike Shirt.find(...), however, the object returned by + # Shirt.red is not an Array but an ActiveRecord::Relation, + # which is composable with other scopes; it resembles the association object + # constructed by a {has_many}[rdoc-ref:Associations::ClassMethods#has_many] + # declaration. For instance, you can invoke Shirt.red.first, Shirt.red.count, + # Shirt.red.where(size: 'small'). Also, just as with the + # association objects, named \scopes act like an Array, implementing + # Enumerable; Shirt.red.each(&block), Shirt.red.first, + # and Shirt.red.inject(memo, &block) all behave as if + # Shirt.red really was an array. + # + # These named \scopes are composable. For instance, + # Shirt.red.dry_clean_only will produce all shirts that are + # both red and dry clean only. Nested finds and calculations also work + # with these compositions: Shirt.red.dry_clean_only.count + # returns the number of garments for which these criteria obtain. + # Similarly with Shirt.red.dry_clean_only.average(:thread_count). + # + # All scopes are available as class methods on the ActiveRecord::Base + # descendant upon which the \scopes were defined. But they are also + # available to {has_many}[rdoc-ref:Associations::ClassMethods#has_many] + # associations. If, + # + # class Person < ActiveRecord::Base + # has_many :shirts + # end + # + # then elton.shirts.red.dry_clean_only will return all of + # Elton's red, dry clean only shirts. + # + # \Named scopes can also have extensions, just as with + # {has_many}[rdoc-ref:Associations::ClassMethods#has_many] declarations: + # + # class Shirt < ActiveRecord::Base + # scope :red, -> { where(color: 'red') } do + # def dom_id + # 'red_shirts' + # end + # end + # end + # + # Scopes can also be used while creating/building a record. + # + # class Article < ActiveRecord::Base + # scope :published, -> { where(published: true) } + # end + # + # Article.published.new.published # => true + # Article.published.create.published # => true + # + # \Class methods on your model are automatically available + # on scopes. Assuming the following setup: + # + # class Article < ActiveRecord::Base + # scope :published, -> { where(published: true) } + # scope :featured, -> { where(featured: true) } + # + # def self.latest_article + # order('published_at desc').first + # end + # + # def self.titles + # pluck(:title) + # end + # end + # + # We are able to call the methods like this: + # + # Article.published.featured.latest_article + # Article.featured.titles + def scope(name, body, &block) + unless body.respond_to?(:call) + raise ArgumentError, "The scope body needs to be callable." + end + + if dangerous_class_method?(name) + raise ArgumentError, "You tried to define a scope named \"#{name}\" " \ + "on the model \"#{self.name}\", but Active Record already defined " \ + "a class method with the same name." + end + + if method_defined_within?(name, Relation) + raise ArgumentError, "You tried to define a scope named \"#{name}\" " \ + "on the model \"#{self.name}\", but ActiveRecord::Relation already defined " \ + "an instance method with the same name." + end + + extension = Module.new(&block) if block + + if body.respond_to?(:to_proc) + singleton_class.define_method(name) do |*args| + scope = all._exec_scope(*args, &body) + scope = scope.extending(extension) if extension + scope + end + else + singleton_class.define_method(name) do |*args| + scope = body.call(*args) || all + scope = scope.extending(extension) if extension + scope + end + end + singleton_class.send(:ruby2_keywords, name) + + generate_relation_method(name) + end + + private + def singleton_method_added(name) + super + # Most Kernel extends are both singleton and instance methods so + # respond_to is a fast check, but we don't want to define methods + # only on the module (ex. Module#name) + generate_relation_method(name) if Kernel.respond_to?(name) && (Kernel.method_defined?(name) || Kernel.private_method_defined?(name)) && !ActiveRecord::Relation.method_defined?(name) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/secure_password.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/secure_password.rb new file mode 100644 index 00000000..159d7394 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/secure_password.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module ActiveRecord + module SecurePassword + extend ActiveSupport::Concern + + include ActiveModel::SecurePassword + + module ClassMethods + # Given a set of attributes, finds a record using the non-password + # attributes, and then authenticates that record using the password + # attributes. Returns the record if authentication succeeds; otherwise, + # returns +nil+. + # + # Regardless of whether a record is found, +authenticate_by+ will + # cryptographically digest the given password attributes. This behavior + # helps mitigate timing-based enumeration attacks, wherein an attacker can + # determine if a passworded record exists even without knowing the + # password. + # + # Raises an ArgumentError if the set of attributes doesn't contain at + # least one password and one non-password attribute. + # + # ==== Examples + # + # class User < ActiveRecord::Base + # has_secure_password + # end + # + # User.create(name: "John Doe", email: "jdoe@example.com", password: "abc123") + # + # User.authenticate_by(email: "jdoe@example.com", password: "abc123").name # => "John Doe" (in 373.4ms) + # User.authenticate_by(email: "jdoe@example.com", password: "wrong") # => nil (in 373.9ms) + # User.authenticate_by(email: "wrong@example.com", password: "abc123") # => nil (in 373.6ms) + # + # User.authenticate_by(email: "jdoe@example.com", password: nil) # => nil (no queries executed) + # User.authenticate_by(email: "jdoe@example.com", password: "") # => nil (no queries executed) + # + # User.authenticate_by(email: "jdoe@example.com") # => ArgumentError + # User.authenticate_by(password: "abc123") # => ArgumentError + def authenticate_by(attributes) + passwords, identifiers = attributes.to_h.partition do |name, value| + !has_attribute?(name) && has_attribute?("#{name}_digest") + end.map(&:to_h) + + raise ArgumentError, "One or more password arguments are required" if passwords.empty? + raise ArgumentError, "One or more finder arguments are required" if identifiers.empty? + + return if passwords.any? { |name, value| value.nil? || value.empty? } + + if record = find_by(identifiers) + record if passwords.count { |name, value| record.public_send(:"authenticate_#{name}", value) } == passwords.size + else + new(passwords) + nil + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/secure_token.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/secure_token.rb new file mode 100644 index 00000000..f68d6cb7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/secure_token.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module ActiveRecord + module SecureToken + class MinimumLengthError < StandardError; end + + MINIMUM_TOKEN_LENGTH = 24 + + extend ActiveSupport::Concern + + module ClassMethods + # Example using #has_secure_token + # + # # Schema: User(token:string, auth_token:string) + # class User < ActiveRecord::Base + # has_secure_token + # has_secure_token :auth_token, length: 36 + # end + # + # user = User.new + # user.save + # user.token # => "pX27zsMN2ViQKta1bGfLmVJE" + # user.auth_token # => "tU9bLuZseefXQ4yQxQo8wjtBvsAfPc78os6R" + # user.regenerate_token # => true + # user.regenerate_auth_token # => true + # + # +SecureRandom::base58+ is used to generate at minimum a 24-character unique token, so collisions are highly unlikely. + # + # Note that it's still possible to generate a race condition in the database in the same way that + # {validates_uniqueness_of}[rdoc-ref:Validations::ClassMethods#validates_uniqueness_of] can. + # You're encouraged to add a unique index in the database to deal with this even more unlikely scenario. + # + # === Options + # + # [:length] + # Length of the Secure Random, with a minimum of 24 characters. It will + # default to 24. + # + # [:on] + # The callback when the value is generated. When called with on: + # :initialize, the value is generated in an + # after_initialize callback, otherwise the value will be used + # in a before_ callback. When not specified, +:on+ will use the value of + # config.active_record.generate_secure_token_on, which defaults to +:initialize+ + # starting in \Rails 7.1. + def has_secure_token(attribute = :token, length: MINIMUM_TOKEN_LENGTH, on: ActiveRecord.generate_secure_token_on) + if length < MINIMUM_TOKEN_LENGTH + raise MinimumLengthError, "Token requires a minimum length of #{MINIMUM_TOKEN_LENGTH} characters." + end + + # Load securerandom only when has_secure_token is used. + require "active_support/core_ext/securerandom" + define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(length: length) } + set_callback on, on == :initialize ? :after : :before do + if new_record? && !query_attribute(attribute) + send("#{attribute}=", self.class.generate_unique_secure_token(length: length)) + end + end + end + + def generate_unique_secure_token(length: MINIMUM_TOKEN_LENGTH) + SecureRandom.base58(length) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/serialization.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/serialization.rb new file mode 100644 index 00000000..e9e5159e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/serialization.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module ActiveRecord # :nodoc: + # = Active Record \Serialization + module Serialization + extend ActiveSupport::Concern + include ActiveModel::Serializers::JSON + + included do + self.include_root_in_json = false + end + + def serializable_hash(options = nil) + if self.class._has_attribute?(self.class.inheritance_column) + options = options ? options.dup : {} + + options[:except] = Array(options[:except]).map(&:to_s) + options[:except] |= Array(self.class.inheritance_column) + end + + super(options) + end + + private + def attribute_names_for_serialization + attribute_names + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/signed_id.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/signed_id.rb new file mode 100644 index 00000000..d3e83ff2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/signed_id.rb @@ -0,0 +1,138 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record Signed Id + module SignedId + extend ActiveSupport::Concern + + included do + ## + # :singleton-method: + # Set the secret used for the signed id verifier instance when using Active Record outside of \Rails. + # Within \Rails, this is automatically set using the \Rails application key generator. + class_attribute :signed_id_verifier_secret, instance_writer: false + end + + module RelationMethods # :nodoc: + def find_signed(...) + scoping { model.find_signed(...) } + end + + def find_signed!(...) + scoping { model.find_signed!(...) } + end + end + + module ClassMethods + # Lets you find a record based on a signed id that's safe to put into the world without risk of tampering. + # This is particularly useful for things like password reset or email verification, where you want + # the bearer of the signed id to be able to interact with the underlying record, but usually only within + # a certain time period. + # + # You set the time period that the signed id is valid for during generation, using the instance method + # signed_id(expires_in: 15.minutes). If the time has elapsed before a signed find is attempted, + # the signed id will no longer be valid, and nil is returned. + # + # It's possible to further restrict the use of a signed id with a purpose. This helps when you have a + # general base model, like a User, which might have signed ids for several things, like password reset + # or email verification. The purpose that was set during generation must match the purpose set when + # finding. If there's a mismatch, nil is again returned. + # + # ==== Examples + # + # signed_id = User.first.signed_id expires_in: 15.minutes, purpose: :password_reset + # + # User.find_signed signed_id # => nil, since the purpose does not match + # + # travel 16.minutes + # User.find_signed signed_id, purpose: :password_reset # => nil, since the signed id has expired + # + # travel_back + # User.find_signed signed_id, purpose: :password_reset # => User.first + def find_signed(signed_id, purpose: nil) + raise UnknownPrimaryKey.new(self) if primary_key.nil? + + if id = signed_id_verifier.verified(signed_id, purpose: combine_signed_id_purposes(purpose)) + find_by primary_key => id + end + end + + # Works like find_signed, but will raise an +ActiveSupport::MessageVerifier::InvalidSignature+ + # exception if the +signed_id+ has either expired, has a purpose mismatch, is for another record, + # or has been tampered with. It will also raise an +ActiveRecord::RecordNotFound+ exception if + # the valid signed id can't find a record. + # + # === Examples + # + # User.find_signed! "bad data" # => ActiveSupport::MessageVerifier::InvalidSignature + # + # signed_id = User.first.signed_id + # User.first.destroy + # User.find_signed! signed_id # => ActiveRecord::RecordNotFound + def find_signed!(signed_id, purpose: nil) + if id = signed_id_verifier.verify(signed_id, purpose: combine_signed_id_purposes(purpose)) + find(id) + end + end + + # The verifier instance that all signed ids are generated and verified from. By default, it'll be initialized + # with the class-level +signed_id_verifier_secret+, which within Rails comes from + # {Rails.application.key_generator}[rdoc-ref:Rails::Application#key_generator]. + # By default, it's SHA256 for the digest and JSON for the serialization. + def signed_id_verifier + @signed_id_verifier ||= begin + secret = signed_id_verifier_secret + secret = secret.call if secret.respond_to?(:call) + + if secret.nil? + raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed ids" + else + ActiveSupport::MessageVerifier.new secret, digest: "SHA256", serializer: JSON, url_safe: true + end + end + end + + # Allows you to pass in a custom verifier used for the signed ids. This also allows you to use different + # verifiers for different classes. This is also helpful if you need to rotate keys, as you can prepare + # your custom verifier for that in advance. See ActiveSupport::MessageVerifier for details. + def signed_id_verifier=(verifier) + @signed_id_verifier = verifier + end + + # :nodoc: + def combine_signed_id_purposes(purpose) + [ base_class.name.underscore, purpose.to_s ].compact_blank.join("/") + end + end + + + # Returns a signed id that's generated using a preconfigured +ActiveSupport::MessageVerifier+ instance. + # + # This signed id is tamper proof, so it's safe to send in an email or otherwise share with the outside world. + # However, as with any message signed with a +ActiveSupport::MessageVerifier+, + # {the signed id is not encrypted}[link:classes/ActiveSupport/MessageVerifier.html#class-ActiveSupport::MessageVerifier-label-Signing+is+not+encryption]. + # It's just encoded and protected against tampering. + # + # This means that the ID can be decoded by anyone; however, if tampered with (so to point to a different ID), + # the cryptographic signature will no longer match, and the signed id will be considered invalid and return nil + # when passed to +find_signed+ (or raise with +find_signed!+). + # + # It can furthermore be set to expire (the default is not to expire), and scoped down with a specific purpose. + # If the expiration date has been exceeded before +find_signed+ is called, the id won't find the designated + # record. If a purpose is set, this too must match. + # + # If you accidentally let a signed id out in the wild that you wish to retract sooner than its expiration date + # (or maybe you forgot to set an expiration date while meaning to!), you can use the purpose to essentially + # version the signed_id, like so: + # + # user.signed_id purpose: :v2 + # + # And you then change your +find_signed+ calls to require this new purpose. Any old signed ids that were not + # created with the purpose will no longer find the record. + def signed_id(expires_in: nil, expires_at: nil, purpose: nil) + raise ArgumentError, "Cannot get a signed_id for a new record" if new_record? + + self.class.signed_id_verifier.generate id, expires_in: expires_in, expires_at: expires_at, purpose: self.class.combine_signed_id_purposes(purpose) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/statement_cache.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/statement_cache.rb new file mode 100644 index 00000000..abed3773 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/statement_cache.rb @@ -0,0 +1,164 @@ +# frozen_string_literal: true + +module ActiveRecord + # Statement cache is used to cache a single statement in order to avoid creating the AST again. + # Initializing the cache is done by passing the statement in the create block: + # + # cache = StatementCache.create(ClothingItem.lease_connection) do |params| + # Book.where(name: "my book").where("author_id > 3") + # end + # + # The cached statement is executed by using the + # {connection.execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute] method: + # + # cache.execute([], ClothingItem.lease_connection) + # + # The relation returned by the block is cached, and for each + # {execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute] + # call the cached relation gets duped. Database is queried when +to_a+ is called on the relation. + # + # If you want to cache the statement without the values you can use the +bind+ method of the + # block parameter. + # + # cache = StatementCache.create(ClothingItem.lease_connection) do |params| + # Book.where(name: params.bind) + # end + # + # And pass the bind values as the first argument of +execute+ call. + # + # cache.execute(["my book"], ClothingItem.lease_connection) + class StatementCache # :nodoc: + class Substitute; end # :nodoc: + + class Query # :nodoc: + def initialize(sql) + @sql = sql + end + + def sql_for(binds, connection) + @sql + end + end + + class PartialQuery < Query # :nodoc: + def initialize(values) + @values = values + @indexes = values.each_with_index.find_all { |thing, i| + Substitute === thing + }.map(&:last) + end + + def sql_for(binds, connection) + val = @values.dup + @indexes.each do |i| + value = binds.shift + if ActiveModel::Attribute === value + value = value.value_for_database + end + val[i] = connection.quote(value) + end + val.join + end + end + + class PartialQueryCollector + attr_accessor :preparable, :retryable + + def initialize + @parts = [] + @binds = [] + end + + def <<(str) + @parts << str + self + end + + def add_bind(obj, &) + @binds << obj + @parts << Substitute.new + self + end + + def add_binds(binds, proc_for_binds = nil, &) + @binds.concat proc_for_binds ? binds.map(&proc_for_binds) : binds + binds.size.times do |i| + @parts << ", " unless i == 0 + @parts << Substitute.new + end + self + end + + def value + [@parts, @binds] + end + end + + def self.query(sql) + Query.new(sql) + end + + def self.partial_query(values) + PartialQuery.new(values) + end + + def self.partial_query_collector + PartialQueryCollector.new + end + + class Params # :nodoc: + def bind; Substitute.new; end + end + + class BindMap # :nodoc: + def initialize(bound_attributes) + @indexes = [] + @bound_attributes = bound_attributes + + bound_attributes.each_with_index do |attr, i| + if ActiveModel::Attribute === attr && Substitute === attr.value + @indexes << i + end + end + end + + def bind(values) + bas = @bound_attributes.dup + @indexes.each_with_index { |offset, i| bas[offset] = bas[offset].with_cast_value(values[i]) } + bas + end + end + + def self.create(connection, callable = nil, &block) + relation = (callable || block).call Params.new + query_builder, binds = connection.cacheable_query(self, relation.arel) + bind_map = BindMap.new(binds) + new(query_builder, bind_map, relation.model) + end + + def initialize(query_builder, bind_map, model) + @query_builder = query_builder + @bind_map = bind_map + @model = model + end + + def execute(params, connection, allow_retry: false, async: false, &block) + bind_values = @bind_map.bind params + sql = @query_builder.sql_for bind_values, connection + + if async + @model.async_find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block) + else + @model.find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block) + end + rescue ::RangeError + async ? Promise.wrap([]) : [] + end + + def self.unsupported_value?(value) + case value + when NilClass, Array, Range, Hash, Relation, Base then true + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/store.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/store.rb new file mode 100644 index 00000000..2f9868ec --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/store.rb @@ -0,0 +1,299 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/indifferent_access" + +module ActiveRecord + # = Active Record \Store + # + # Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column. + # It's like a simple key/value store baked into your record when you don't care about being able to + # query that store outside the context of a single record. + # + # You can then declare accessors to this store that are then accessible just like any other attribute + # of the model. This is very helpful for easily exposing store keys to a form or elsewhere that's + # already built around just accessing attributes on the model. + # + # Every accessor comes with dirty tracking methods (+key_changed?+, +key_was+ and +key_change+) and + # methods to access the changes made during the last save (+saved_change_to_key?+, +saved_change_to_key+ and + # +key_before_last_save+). + # + # NOTE: There is no +key_will_change!+ method for accessors, use +store_will_change!+ instead. + # + # Make sure that you declare the database column used for the serialized store as a text, so there's + # plenty of room. + # + # You can set custom coder to encode/decode your serialized attributes to/from different formats. + # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+. + # + # NOTE: If you are using structured database data types (e.g. PostgreSQL +hstore+/+json+, MySQL 5.7+ + # +json+, or SQLite 3.38+ +json+) there is no need for the serialization provided by {.store}[rdoc-ref:rdoc-ref:ClassMethods#store]. + # Simply use {.store_accessor}[rdoc-ref:ClassMethods#store_accessor] instead to generate + # the accessor methods. Be aware that these columns use a string keyed hash and do not allow access + # using a symbol. + # + # NOTE: The default validations with the exception of +uniqueness+ will work. + # For example, if you want to check for +uniqueness+ with +hstore+ you will + # need to use a custom validation to handle it. + # + # Examples: + # + # class User < ActiveRecord::Base + # store :settings, accessors: [ :color, :homepage ], coder: JSON + # store :parent, accessors: [ :name ], coder: JSON, prefix: true + # store :spouse, accessors: [ :name ], coder: JSON, prefix: :partner + # store :settings, accessors: [ :two_factor_auth ], suffix: true + # store :settings, accessors: [ :login_retry ], suffix: :config + # end + # + # u = User.new(color: 'black', homepage: '37signals.com', parent_name: 'Mary', partner_name: 'Lily') + # u.color # Accessor stored attribute + # u.parent_name # Accessor stored attribute with prefix + # u.partner_name # Accessor stored attribute with custom prefix + # u.two_factor_auth_settings # Accessor stored attribute with suffix + # u.login_retry_config # Accessor stored attribute with custom suffix + # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor + # + # # There is no difference between strings and symbols for accessing custom attributes + # u.settings[:country] # => 'Denmark' + # u.settings['country'] # => 'Denmark' + # + # # Dirty tracking + # u.color = 'green' + # u.color_changed? # => true + # u.color_was # => 'black' + # u.color_change # => ['black', 'green'] + # + # # Add additional accessors to an existing store through store_accessor + # class SuperUser < User + # store_accessor :settings, :privileges, :servants + # store_accessor :parent, :birthday, prefix: true + # store_accessor :settings, :secret_question, suffix: :config + # end + # + # The stored attribute names can be retrieved using {.stored_attributes}[rdoc-ref:rdoc-ref:ClassMethods#stored_attributes]. + # + # User.stored_attributes[:settings] # => [:color, :homepage, :two_factor_auth, :login_retry] + # + # == Overwriting default accessors + # + # All stored values are automatically available through accessors on the Active Record + # object, but sometimes you want to specialize this behavior. This can be done by overwriting + # the default accessors (using the same name as the attribute) and calling super + # to actually change things. + # + # class Song < ActiveRecord::Base + # # Uses a stored integer to hold the volume adjustment of the song + # store :settings, accessors: [:volume_adjustment] + # + # def volume_adjustment=(decibels) + # super(decibels.to_i) + # end + # + # def volume_adjustment + # super.to_i + # end + # end + module Store + extend ActiveSupport::Concern + + included do + class << self + attr_accessor :local_stored_attributes + end + end + + module ClassMethods + def store(store_attribute, options = {}) + coder = build_column_serializer(store_attribute, options[:coder], Object, options[:yaml]) + serialize store_attribute, coder: IndifferentCoder.new(store_attribute, coder) + store_accessor(store_attribute, options[:accessors], **options.slice(:prefix, :suffix)) if options.has_key? :accessors + end + + def store_accessor(store_attribute, *keys, prefix: nil, suffix: nil) + keys = keys.flatten + + accessor_prefix = + case prefix + when String, Symbol + "#{prefix}_" + when TrueClass + "#{store_attribute}_" + else + "" + end + accessor_suffix = + case suffix + when String, Symbol + "_#{suffix}" + when TrueClass + "_#{store_attribute}" + else + "" + end + + _store_accessors_module.module_eval do + keys.each do |key| + accessor_key = "#{accessor_prefix}#{key}#{accessor_suffix}" + + define_method("#{accessor_key}=") do |value| + write_store_attribute(store_attribute, key, value) + end + + define_method(accessor_key) do + read_store_attribute(store_attribute, key) + end + + define_method("#{accessor_key}_changed?") do + return false unless attribute_changed?(store_attribute) + prev_store, new_store = changes[store_attribute] + prev_store&.dig(key) != new_store&.dig(key) + end + + define_method("#{accessor_key}_change") do + return unless attribute_changed?(store_attribute) + prev_store, new_store = changes[store_attribute] + [prev_store&.dig(key), new_store&.dig(key)] + end + + define_method("#{accessor_key}_was") do + return unless attribute_changed?(store_attribute) + prev_store, _new_store = changes[store_attribute] + prev_store&.dig(key) + end + + define_method("saved_change_to_#{accessor_key}?") do + return false unless saved_change_to_attribute?(store_attribute) + prev_store, new_store = saved_changes[store_attribute] + prev_store&.dig(key) != new_store&.dig(key) + end + + define_method("saved_change_to_#{accessor_key}") do + return unless saved_change_to_attribute?(store_attribute) + prev_store, new_store = saved_changes[store_attribute] + [prev_store&.dig(key), new_store&.dig(key)] + end + + define_method("#{accessor_key}_before_last_save") do + return unless saved_change_to_attribute?(store_attribute) + prev_store, _new_store = saved_changes[store_attribute] + prev_store&.dig(key) + end + end + end + + # assign new store attribute and create new hash to ensure that each class in the hierarchy + # has its own hash of stored attributes. + self.local_stored_attributes ||= {} + self.local_stored_attributes[store_attribute] ||= [] + self.local_stored_attributes[store_attribute] |= keys + end + + def _store_accessors_module # :nodoc: + @_store_accessors_module ||= begin + mod = Module.new + include mod + mod + end + end + + def stored_attributes + parent = superclass.respond_to?(:stored_attributes) ? superclass.stored_attributes : {} + if local_stored_attributes + parent.merge!(local_stored_attributes) { |k, a, b| a | b } + end + parent + end + end + + private + def read_store_attribute(store_attribute, key) # :doc: + accessor = store_accessor_for(store_attribute) + accessor.read(self, store_attribute, key) + end + + def write_store_attribute(store_attribute, key, value) # :doc: + accessor = store_accessor_for(store_attribute) + accessor.write(self, store_attribute, key, value) + end + + def store_accessor_for(store_attribute) + type_for_attribute(store_attribute).tap do |type| + unless type.respond_to?(:accessor) + raise ConfigurationError, "the column '#{store_attribute}' has not been configured as a store. Please make sure the column is declared serializable via 'ActiveRecord.store' or, if your database supports it, use a structured column type like hstore or json." + end + end.accessor + end + + class HashAccessor # :nodoc: + def self.read(object, attribute, key) + prepare(object, attribute) + object.public_send(attribute)[key] + end + + def self.write(object, attribute, key, value) + prepare(object, attribute) + object.public_send(attribute)[key] = value if value != read(object, attribute, key) + end + + def self.prepare(object, attribute) + object.public_send :"#{attribute}=", {} unless object.send(attribute) + end + end + + class StringKeyedHashAccessor < HashAccessor # :nodoc: + def self.read(object, attribute, key) + super object, attribute, key.to_s + end + + def self.write(object, attribute, key, value) + super object, attribute, key.to_s, value + end + end + + class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor # :nodoc: + def self.prepare(object, store_attribute) + attribute = object.send(store_attribute) + unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess) + attribute = IndifferentCoder.as_indifferent_hash(attribute) + object.public_send :"#{store_attribute}=", attribute + end + attribute + end + end + + class IndifferentCoder # :nodoc: + def initialize(attr_name, coder_or_class_name) + @coder = + if coder_or_class_name.respond_to?(:load) && coder_or_class_name.respond_to?(:dump) + coder_or_class_name + else + ActiveRecord::Coders::YAMLColumn.new(attr_name, coder_or_class_name || Object) + end + end + + def dump(obj) + @coder.dump as_regular_hash(obj) + end + + def load(yaml) + self.class.as_indifferent_hash(@coder.load(yaml || "")) + end + + def self.as_indifferent_hash(obj) + case obj + when ActiveSupport::HashWithIndifferentAccess + obj + when Hash + obj.with_indifferent_access + else + ActiveSupport::HashWithIndifferentAccess.new + end + end + + private + def as_regular_hash(obj) + obj.respond_to?(:to_hash) ? obj.to_hash : {} + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/suppressor.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/suppressor.rb new file mode 100644 index 00000000..94353dc2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/suppressor.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record \Suppressor + # + # ActiveRecord::Suppressor prevents the receiver from being saved during + # a given block. + # + # For example, here's a pattern of creating notifications when new comments + # are posted. (The notification may in turn trigger an email, a push + # notification, or just appear in the UI somewhere): + # + # class Comment < ActiveRecord::Base + # belongs_to :commentable, polymorphic: true + # after_create -> { Notification.create! comment: self, + # recipients: commentable.recipients } + # end + # + # That's what you want the bulk of the time. New comment creates a new + # Notification. But there may well be off cases, like copying a commentable + # and its comments, where you don't want that. So you'd have a concern + # something like this: + # + # module Copyable + # def copy_to(destination) + # Notification.suppress do + # # Copy logic that creates new comments that we do not want + # # triggering notifications. + # end + # end + # end + module Suppressor + extend ActiveSupport::Concern + + class << self + def registry # :nodoc: + ActiveSupport::IsolatedExecutionState[:active_record_suppressor_registry] ||= {} + end + end + + module ClassMethods + def suppress(&block) + previous_state = Suppressor.registry[name] + Suppressor.registry[name] = true + yield + ensure + Suppressor.registry[name] = previous_state + end + end + + def save(**) # :nodoc: + Suppressor.registry[self.class.name] ? true : super + end + + def save!(**) # :nodoc: + Suppressor.registry[self.class.name] ? true : super + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/table_metadata.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/table_metadata.rb new file mode 100644 index 00000000..e8ec0bbc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/table_metadata.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +module ActiveRecord + class TableMetadata # :nodoc: + delegate :join_primary_key, :join_primary_type, :join_foreign_key, :join_foreign_type, to: :reflection + + def initialize(klass, arel_table, reflection = nil) + @klass = klass + @arel_table = arel_table + @reflection = reflection + end + + def primary_key + klass&.primary_key + end + + def type(column_name) + arel_table.type_for_attribute(column_name) + end + + def has_column?(column_name) + klass&.columns_hash&.key?(column_name) + end + + def associated_with?(table_name) + klass&._reflect_on_association(table_name) + end + + def associated_table(table_name) + reflection = klass._reflect_on_association(table_name) || klass._reflect_on_association(table_name.singularize) + + if !reflection && table_name == arel_table.name + return self + end + + if reflection + association_klass = reflection.klass unless reflection.polymorphic? + elsif block_given? + association_klass = yield table_name + end + + if association_klass + arel_table = association_klass.arel_table + arel_table = arel_table.alias(table_name) if arel_table.name != table_name + TableMetadata.new(association_klass, arel_table, reflection) + else + type_caster = TypeCaster::Connection.new(klass, table_name) + arel_table = Arel::Table.new(table_name, type_caster: type_caster) + TableMetadata.new(nil, arel_table, reflection) + end + end + + def polymorphic_association? + reflection&.polymorphic? + end + + def polymorphic_name_association + reflection&.polymorphic_name + end + + def through_association? + reflection&.through_reflection? + end + + def reflect_on_aggregation(aggregation_name) + klass&.reflect_on_aggregation(aggregation_name) + end + alias :aggregated_with? :reflect_on_aggregation + + def predicate_builder + if klass + klass.predicate_builder.with(self) + else + PredicateBuilder.new(self) + end + end + + attr_reader :arel_table + + private + attr_reader :klass, :reflection + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/tasks/database_tasks.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/tasks/database_tasks.rb new file mode 100644 index 00000000..029256c8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/tasks/database_tasks.rb @@ -0,0 +1,673 @@ +# frozen_string_literal: true + +require "active_record/database_configurations" + +module ActiveRecord + module Tasks # :nodoc: + class DatabaseNotSupported < StandardError; end # :nodoc: + + # = Active Record \DatabaseTasks + # + # ActiveRecord::Tasks::DatabaseTasks is a utility class, which encapsulates + # logic behind common tasks used to manage database and migrations. + # + # The tasks defined here are used with \Rails commands provided by Active Record. + # + # In order to use DatabaseTasks, a few config values need to be set. All the needed + # config values are set by \Rails already, so it's necessary to do it only if you + # want to change the defaults or when you want to use Active Record outside of \Rails + # (in such case after configuring the database tasks, you can also use the rake tasks + # defined in Active Record). + # + # The possible config values are: + # + # * +env+: current environment (like Rails.env). + # * +database_configuration+: configuration of your databases (as in +config/database.yml+). + # * +db_dir+: your +db+ directory. + # * +fixtures_path+: a path to fixtures directory. + # * +migrations_paths+: a list of paths to directories with migrations. + # * +seed_loader+: an object which will load seeds, it needs to respond to the +load_seed+ method. + # * +root+: a path to the root of the application. + # + # Example usage of DatabaseTasks outside \Rails could look as such: + # + # include ActiveRecord::Tasks + # DatabaseTasks.database_configuration = YAML.load_file('my_database_config.yml') + # DatabaseTasks.db_dir = 'db' + # # other settings... + # + # DatabaseTasks.create_current('production') + module DatabaseTasks + ## + # :singleton-method: + # Extra flags passed to database CLI tool (mysqldump/pg_dump) when calling db:schema:dump + # It can be used as a string/array (the typical case) or a hash (when you use multiple adapters) + # Example: + # ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags = { + # mysql2: ['--no-defaults', '--skip-add-drop-table'], + # postgres: '--no-tablespaces' + # } + mattr_accessor :structure_dump_flags, instance_accessor: false + + ## + # :singleton-method: + # Extra flags passed to database CLI tool when calling db:schema:load + # It can be used as a string/array (the typical case) or a hash (when you use multiple adapters) + mattr_accessor :structure_load_flags, instance_accessor: false + + extend self + + attr_writer :db_dir, :migrations_paths, :fixtures_path, :root, :env, :seed_loader + attr_accessor :database_configuration + + LOCAL_HOSTS = ["127.0.0.1", "localhost"] + + def check_protected_environments!(environment = env) + return if ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"] + + configs_for(env_name: environment).each do |db_config| + check_current_protected_environment!(db_config) + end + end + + def register_task(pattern, task) + @tasks ||= {} + @tasks[pattern] = task + end + + register_task(/mysql/, "ActiveRecord::Tasks::MySQLDatabaseTasks") + register_task(/trilogy/, "ActiveRecord::Tasks::MySQLDatabaseTasks") + register_task(/postgresql/, "ActiveRecord::Tasks::PostgreSQLDatabaseTasks") + register_task(/sqlite/, "ActiveRecord::Tasks::SQLiteDatabaseTasks") + + def db_dir + @db_dir ||= Rails.application.config.paths["db"].first + end + + def migrations_paths + @migrations_paths ||= Rails.application.paths["db/migrate"].to_a + end + + def fixtures_path + @fixtures_path ||= if ENV["FIXTURES_PATH"] + File.join(root, ENV["FIXTURES_PATH"]) + else + File.join(root, "test", "fixtures") + end + end + + def root + @root ||= Rails.root + end + + def env + @env ||= Rails.env + end + + def name + @name ||= "primary" + end + + def seed_loader + @seed_loader ||= Rails.application + end + + def create(configuration, *arguments) + db_config = resolve_configuration(configuration) + database_adapter_for(db_config, *arguments).create + $stdout.puts "Created database '#{db_config.database}'" if verbose? + rescue DatabaseAlreadyExists + $stderr.puts "Database '#{db_config.database}' already exists" if verbose? + rescue Exception => error + $stderr.puts error + $stderr.puts "Couldn't create '#{db_config.database}' database. Please check your configuration." + raise + end + + def create_all + db_config = migration_connection.pool.db_config + + each_local_configuration { |db_config| create(db_config) } + + migration_class.establish_connection(db_config) + end + + def setup_initial_database_yaml # :nodoc: + return {} unless defined?(Rails) + + Rails.application.config.load_database_yaml + end + + def for_each(databases) # :nodoc: + return {} unless defined?(Rails) + + database_configs = ActiveRecord::DatabaseConfigurations.new(databases).configs_for(env_name: Rails.env) + + # if this is a single database application we don't want tasks for each primary database + return if database_configs.count == 1 + + database_configs.each do |db_config| + next unless db_config.database_tasks? + + yield db_config.name + end + end + + def raise_for_multi_db(environment = env, command:) # :nodoc: + db_configs = configs_for(env_name: environment) + + if db_configs.count > 1 + dbs_list = [] + + db_configs.each do |db| + dbs_list << "#{command}:#{db.name}" + end + + raise "You're using a multiple database application. To use `#{command}` you must run the namespaced task with a VERSION. Available tasks are #{dbs_list.to_sentence}." + end + end + + def create_current(environment = env, name = nil) + each_current_configuration(environment, name) { |db_config| create(db_config) } + + migration_class.establish_connection(environment.to_sym) + end + + def prepare_all + seed = false + dump_db_configs = [] + + each_current_configuration(env) do |db_config| + database_initialized = initialize_database(db_config) + + seed = true if database_initialized && db_config.seeds? + end + + each_current_environment(env) do |environment| + db_configs_with_versions(environment).sort.each do |version, db_configs| + dump_db_configs |= db_configs + + db_configs.each do |db_config| + with_temporary_pool(db_config) do + migrate(version) + end + end + end + end + + # Dump schema for databases that were migrated. + if ActiveRecord.dump_schema_after_migration + dump_db_configs.each do |db_config| + with_temporary_pool(db_config) do + dump_schema(db_config) + end + end + end + + load_seed if seed + end + + def drop(configuration, *arguments) + db_config = resolve_configuration(configuration) + database_adapter_for(db_config, *arguments).drop + $stdout.puts "Dropped database '#{db_config.database}'" if verbose? + rescue ActiveRecord::NoDatabaseError + $stderr.puts "Database '#{db_config.database}' does not exist" + rescue Exception => error + $stderr.puts error + $stderr.puts "Couldn't drop database '#{db_config.database}'" + raise + end + + def drop_all + each_local_configuration { |db_config| drop(db_config) } + end + + def drop_current(environment = env) + each_current_configuration(environment) { |db_config| drop(db_config) } + end + + def truncate_tables(db_config) + with_temporary_connection(db_config) do |conn| + conn.truncate_tables(*conn.tables) + end + end + private :truncate_tables + + def truncate_all(environment = env) + configs_for(env_name: environment).each do |db_config| + truncate_tables(db_config) + end + end + + def migrate_all + db_configs = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env) + db_configs.each { |db_config| initialize_database(db_config) } + + if db_configs.size == 1 && db_configs.first.primary? + ActiveRecord::Tasks::DatabaseTasks.migrate(skip_initialize: true) + else + mapped_versions = ActiveRecord::Tasks::DatabaseTasks.db_configs_with_versions + + mapped_versions.sort.each do |version, db_configs| + db_configs.each do |db_config| + ActiveRecord::Tasks::DatabaseTasks.with_temporary_connection(db_config) do + ActiveRecord::Tasks::DatabaseTasks.migrate(version, skip_initialize: true) + end + end + end + end + end + + def migrate(version = nil, skip_initialize: false) + scope = ENV["SCOPE"] + verbose_was, Migration.verbose = Migration.verbose, verbose? + + check_target_version + + initialize_database(migration_connection_pool.db_config) unless skip_initialize + + migration_connection_pool.migration_context.migrate(target_version) do |migration| + if version.blank? + scope.blank? || scope == migration.scope + else + migration.version == version + end + end.tap do |migrations_ran| + Migration.write("No migrations ran. (using #{scope} scope)") if scope.present? && migrations_ran.empty? + end + + migration_connection_pool.schema_cache.clear! + ensure + Migration.verbose = verbose_was + end + + def db_configs_with_versions(environment = env) # :nodoc: + db_configs_with_versions = Hash.new { |h, k| h[k] = [] } + + with_temporary_pool_for_each(env: environment) do |pool| + db_config = pool.db_config + versions_to_run = pool.migration_context.pending_migration_versions + target_version = ActiveRecord::Tasks::DatabaseTasks.target_version + + versions_to_run.each do |version| + next if target_version && target_version != version + db_configs_with_versions[version] << db_config + end + end + + db_configs_with_versions + end + + def migrate_status + unless migration_connection_pool.schema_migration.table_exists? + Kernel.abort "Schema migrations table does not exist yet." + end + + # output + puts "\ndatabase: #{migration_connection_pool.db_config.database}\n\n" + puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name" + puts "-" * 50 + migration_connection_pool.migration_context.migrations_status.each do |status, version, name| + puts "#{status.center(8)} #{version.ljust(14)} #{name}" + end + puts + end + + def check_target_version + if target_version && !Migration.valid_version_format?(ENV["VERSION"]) + raise "Invalid format of target version: `VERSION=#{ENV['VERSION']}`" + end + end + + def target_version + ENV["VERSION"].to_i if ENV["VERSION"] && !ENV["VERSION"].empty? + end + + def charset_current(env_name = env, db_name = name) + db_config = configs_for(env_name: env_name, name: db_name) + charset(db_config) + end + + def charset(configuration, *arguments) + db_config = resolve_configuration(configuration) + database_adapter_for(db_config, *arguments).charset + end + + def collation_current(env_name = env, db_name = name) + db_config = configs_for(env_name: env_name, name: db_name) + collation(db_config) + end + + def collation(configuration, *arguments) + db_config = resolve_configuration(configuration) + database_adapter_for(db_config, *arguments).collation + end + + def purge(configuration) + db_config = resolve_configuration(configuration) + database_adapter_for(db_config).purge + end + + def purge_all + each_local_configuration { |db_config| purge(db_config) } + end + + def purge_current(environment = env) + each_current_configuration(environment) { |db_config| purge(db_config) } + + migration_class.establish_connection(environment.to_sym) + end + + def structure_dump(configuration, *arguments) + db_config = resolve_configuration(configuration) + filename = arguments.delete_at(0) + flags = structure_dump_flags_for(db_config.adapter) + database_adapter_for(db_config, *arguments).structure_dump(filename, flags) + end + + def structure_load(configuration, *arguments) + db_config = resolve_configuration(configuration) + filename = arguments.delete_at(0) + flags = structure_load_flags_for(db_config.adapter) + database_adapter_for(db_config, *arguments).structure_load(filename, flags) + end + + def load_schema(db_config, format = ActiveRecord.schema_format, file = nil) # :nodoc: + file ||= schema_dump_path(db_config, format) + return unless file + + verbose_was, Migration.verbose = Migration.verbose, verbose? && ENV["VERBOSE"] + check_schema_file(file) + + case format + when :ruby + load(file) + when :sql + structure_load(db_config, file) + else + raise ArgumentError, "unknown format #{format.inspect}" + end + + migration_connection_pool.internal_metadata.create_table_and_set_flags(db_config.env_name, schema_sha1(file)) + ensure + Migration.verbose = verbose_was + end + + def schema_up_to_date?(configuration, format = ActiveRecord.schema_format, file = nil) + db_config = resolve_configuration(configuration) + + file ||= schema_dump_path(db_config) + + return true unless file && File.exist?(file) + + with_temporary_pool(db_config) do |pool| + internal_metadata = pool.internal_metadata + return false unless internal_metadata.enabled? + return false unless internal_metadata.table_exists? + + internal_metadata[:schema_sha1] == schema_sha1(file) + end + end + + def reconstruct_from_schema(db_config, format = ActiveRecord.schema_format, file = nil) # :nodoc: + file ||= schema_dump_path(db_config, format) + + check_schema_file(file) if file + + with_temporary_pool(db_config, clobber: true) do + if schema_up_to_date?(db_config, format, file) + truncate_tables(db_config) unless ENV["SKIP_TEST_DATABASE_TRUNCATE"] + else + purge(db_config) + load_schema(db_config, format, file) + end + rescue ActiveRecord::NoDatabaseError + create(db_config) + load_schema(db_config, format, file) + end + end + + def dump_schema(db_config, format = ActiveRecord.schema_format) # :nodoc: + return unless db_config.schema_dump + + require "active_record/schema_dumper" + filename = schema_dump_path(db_config, format) + return unless filename + + FileUtils.mkdir_p(db_dir) + case format + when :ruby + File.open(filename, "w:utf-8") do |file| + ActiveRecord::SchemaDumper.dump(migration_connection_pool, file) + end + when :sql + structure_dump(db_config, filename) + if migration_connection_pool.schema_migration.table_exists? + File.open(filename, "a") do |f| + f.puts migration_connection.dump_schema_information + f.print "\n" + end + end + end + end + + def schema_dump_path(db_config, format = ActiveRecord.schema_format) + return ENV["SCHEMA"] if ENV["SCHEMA"] + + filename = db_config.schema_dump(format) + return unless filename + + if File.dirname(filename) == ActiveRecord::Tasks::DatabaseTasks.db_dir + filename + else + File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename) + end + end + + def cache_dump_filename(db_config, schema_cache_path: nil) + schema_cache_path || + db_config.schema_cache_path || + db_config.default_schema_cache_path(ActiveRecord::Tasks::DatabaseTasks.db_dir) + end + + def load_schema_current(format = ActiveRecord.schema_format, file = nil, environment = env) + each_current_configuration(environment) do |db_config| + with_temporary_connection(db_config) do + load_schema(db_config, format, file) + end + end + end + + def check_schema_file(filename) + unless File.exist?(filename) + message = +%{#{filename} doesn't exist yet. Run `bin/rails db:migrate` to create it, then try again.} + message << %{ If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded.} if defined?(::Rails.root) + Kernel.abort message + end + end + + def load_seed + if seed_loader + seed_loader.load_seed + else + raise "You tried to load seed data, but no seed loader is specified. Please specify seed " \ + "loader with ActiveRecord::Tasks::DatabaseTasks.seed_loader = your_seed_loader\n" \ + "Seed loader should respond to load_seed method" + end + end + + # Dumps the schema cache in YAML format for the connection into the file + # + # ==== Examples + # ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.lease_connection, "tmp/schema_dump.yaml") + def dump_schema_cache(conn_or_pool, filename) + conn_or_pool.schema_cache.dump_to(filename) + end + + def clear_schema_cache(filename) + FileUtils.rm_f filename, verbose: false + end + + def with_temporary_pool_for_each(env: ActiveRecord::Tasks::DatabaseTasks.env, name: nil, clobber: false, &block) # :nodoc: + if name + db_config = ActiveRecord::Base.configurations.configs_for(env_name: env, name: name) + with_temporary_pool(db_config, clobber: clobber, &block) + else + ActiveRecord::Base.configurations.configs_for(env_name: env, name: name).each do |db_config| + with_temporary_pool(db_config, clobber: clobber, &block) + end + end + end + + def with_temporary_connection(db_config, clobber: false, &block) # :nodoc: + with_temporary_pool(db_config, clobber: clobber) do |pool| + pool.with_connection(&block) + end + end + + def migration_class # :nodoc: + ActiveRecord::Base + end + + def migration_connection # :nodoc: + migration_class.lease_connection + end + + def migration_connection_pool # :nodoc: + migration_class.connection_pool + end + + private + def with_temporary_pool(db_config, clobber: false) + original_db_config = migration_class.connection_db_config + pool = migration_class.connection_handler.establish_connection(db_config, clobber: clobber) + + yield pool + ensure + migration_class.connection_handler.establish_connection(original_db_config, clobber: clobber) + end + + def configs_for(**options) + Base.configurations.configs_for(**options) + end + + def resolve_configuration(configuration) + Base.configurations.resolve(configuration) + end + + def verbose? + ENV["VERBOSE"] ? ENV["VERBOSE"] != "false" : true + end + + # Create a new instance for the specified db configuration object + # For classes that have been converted to use db_config objects, pass a + # `DatabaseConfig`, otherwise pass a `Hash` + def database_adapter_for(db_config, *arguments) + klass = class_for_adapter(db_config.adapter) + converted = klass.respond_to?(:using_database_configurations?) && klass.using_database_configurations? + + config = converted ? db_config : db_config.configuration_hash + klass.new(config, *arguments) + end + + def class_for_adapter(adapter) + _key, task = @tasks.reverse_each.detect { |pattern, _task| adapter[pattern] } + unless task + raise DatabaseNotSupported, "Rake tasks not supported by '#{adapter}' adapter" + end + task.is_a?(String) ? task.constantize : task + end + + def each_current_configuration(environment, name = nil) + each_current_environment(environment) do |env| + configs_for(env_name: env).each do |db_config| + next if name && name != db_config.name + + yield db_config + end + end + end + + def each_current_environment(environment, &block) + environments = [environment] + environments << "test" if environment == "development" && !ENV["SKIP_TEST_DATABASE"] && !ENV["DATABASE_URL"] + environments.each(&block) + end + + def each_local_configuration + configs_for.each do |db_config| + next unless db_config.database + + if local_database?(db_config) + yield db_config + else + $stderr.puts "This task only modifies local databases. #{db_config.database} is on a remote host." + end + end + end + + def local_database?(db_config) + host = db_config.host + host.blank? || LOCAL_HOSTS.include?(host) + end + + def schema_sha1(file) + OpenSSL::Digest::SHA1.hexdigest(File.read(file)) + end + + def structure_dump_flags_for(adapter) + if structure_dump_flags.is_a?(Hash) + structure_dump_flags[adapter.to_sym] + else + structure_dump_flags + end + end + + def structure_load_flags_for(adapter) + if structure_load_flags.is_a?(Hash) + structure_load_flags[adapter.to_sym] + else + structure_load_flags + end + end + + def check_current_protected_environment!(db_config) + with_temporary_pool(db_config) do |pool| + migration_context = pool.migration_context + current = migration_context.current_environment + stored = migration_context.last_stored_environment + + if migration_context.protected_environment? + raise ActiveRecord::ProtectedEnvironmentError.new(stored) + end + + if stored && stored != current + raise ActiveRecord::EnvironmentMismatchError.new(current: current, stored: stored) + end + rescue ActiveRecord::NoDatabaseError + end + end + + def initialize_database(db_config) + with_temporary_pool(db_config) do + begin + database_already_initialized = migration_connection_pool.schema_migration.table_exists? + rescue ActiveRecord::NoDatabaseError + create(db_config) + retry + end + + unless database_already_initialized + schema_dump_path = schema_dump_path(db_config) + if schema_dump_path && File.exist?(schema_dump_path) + load_schema(db_config, ActiveRecord.schema_format, nil) + end + end + + !database_already_initialized + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/tasks/mysql_database_tasks.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/tasks/mysql_database_tasks.rb new file mode 100644 index 00000000..daa82d55 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/tasks/mysql_database_tasks.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +module ActiveRecord + module Tasks # :nodoc: + class MySQLDatabaseTasks # :nodoc: + def self.using_database_configurations? + true + end + + def initialize(db_config) + @db_config = db_config + @configuration_hash = db_config.configuration_hash + end + + def create + establish_connection(configuration_hash_without_database) + connection.create_database(db_config.database, creation_options) + establish_connection + end + + def drop + establish_connection + connection.drop_database(db_config.database) + end + + def purge + establish_connection(configuration_hash_without_database) + connection.recreate_database(db_config.database, creation_options) + establish_connection + end + + def charset + connection.charset + end + + def collation + connection.collation + end + + def structure_dump(filename, extra_flags) + args = prepare_command_options + args.concat(["--result-file", "#{filename}"]) + args.concat(["--no-data"]) + args.concat(["--routines"]) + args.concat(["--skip-comments"]) + + ignore_tables = ActiveRecord::SchemaDumper.ignore_tables + if ignore_tables.any? + ignore_tables = connection.data_sources.select { |table| ignore_tables.any? { |pattern| pattern === table } } + args += ignore_tables.map { |table| "--ignore-table=#{db_config.database}.#{table}" } + end + + args.concat([db_config.database.to_s]) + args.unshift(*extra_flags) if extra_flags + + run_cmd("mysqldump", args, "dumping") + end + + def structure_load(filename, extra_flags) + args = prepare_command_options + args.concat(["--execute", %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}]) + args.concat(["--database", db_config.database.to_s]) + args.unshift(*extra_flags) if extra_flags + + run_cmd("mysql", args, "loading") + end + + private + attr_reader :db_config, :configuration_hash + + def connection + ActiveRecord::Base.lease_connection + end + + def establish_connection(config = db_config) + ActiveRecord::Base.establish_connection(config) + end + + def configuration_hash_without_database + configuration_hash.merge(database: nil) + end + + def creation_options + Hash.new.tap do |options| + options[:charset] = configuration_hash[:encoding] if configuration_hash.include?(:encoding) + options[:collation] = configuration_hash[:collation] if configuration_hash.include?(:collation) + end + end + + def prepare_command_options + args = { + host: "--host", + port: "--port", + socket: "--socket", + username: "--user", + password: "--password", + encoding: "--default-character-set", + sslca: "--ssl-ca", + sslcert: "--ssl-cert", + sslcapath: "--ssl-capath", + sslcipher: "--ssl-cipher", + sslkey: "--ssl-key", + ssl_mode: "--ssl-mode" + }.filter_map { |opt, arg| "#{arg}=#{configuration_hash[opt]}" if configuration_hash[opt] } + + args + end + + def run_cmd(cmd, args, action) + fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args) + end + + def run_cmd_error(cmd, args, action) + msg = +"failed to execute: `#{cmd}`\n" + msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n" + msg + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/tasks/postgresql_database_tasks.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/tasks/postgresql_database_tasks.rb new file mode 100644 index 00000000..40cd370f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/tasks/postgresql_database_tasks.rb @@ -0,0 +1,147 @@ +# frozen_string_literal: true + +require "tempfile" + +module ActiveRecord + module Tasks # :nodoc: + class PostgreSQLDatabaseTasks # :nodoc: + DEFAULT_ENCODING = ENV["CHARSET"] || "utf8" + ON_ERROR_STOP_1 = "ON_ERROR_STOP=1" + SQL_COMMENT_BEGIN = "--" + + def self.using_database_configurations? + true + end + + def initialize(db_config) + @db_config = db_config + @configuration_hash = db_config.configuration_hash + end + + def create(connection_already_established = false) + establish_connection(public_schema_config) unless connection_already_established + connection.create_database(db_config.database, configuration_hash.merge(encoding: encoding)) + establish_connection + end + + def drop + establish_connection(public_schema_config) + connection.drop_database(db_config.database) + end + + def charset + connection.encoding + end + + def collation + connection.collation + end + + def purge + ActiveRecord::Base.connection_handler.clear_active_connections!(:all) + drop + create true + end + + def structure_dump(filename, extra_flags) + search_path = \ + case ActiveRecord.dump_schemas + when :schema_search_path + configuration_hash[:schema_search_path] + when :all + nil + when String + ActiveRecord.dump_schemas + end + + args = ["--schema-only", "--no-privileges", "--no-owner"] + args.concat(["--file", filename]) + + args.concat(Array(extra_flags)) if extra_flags + + unless search_path.blank? + args += search_path.split(",").map do |part| + "--schema=#{part.strip}" + end + end + + ignore_tables = ActiveRecord::SchemaDumper.ignore_tables + if ignore_tables.any? + ignore_tables = connection.data_sources.select { |table| ignore_tables.any? { |pattern| pattern === table } } + args += ignore_tables.flat_map { |table| ["-T", table] } + end + + args << db_config.database + run_cmd("pg_dump", args, "dumping") + remove_sql_header_comments(filename) + File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" } + end + + def structure_load(filename, extra_flags) + args = ["--set", ON_ERROR_STOP_1, "--quiet", "--no-psqlrc", "--output", File::NULL, "--file", filename] + args.concat(Array(extra_flags)) if extra_flags + args << db_config.database + run_cmd("psql", args, "loading") + end + + private + attr_reader :db_config, :configuration_hash + + def connection + ActiveRecord::Base.lease_connection + end + + def establish_connection(config = db_config) + ActiveRecord::Base.establish_connection(config) + end + + def encoding + configuration_hash[:encoding] || DEFAULT_ENCODING + end + + def public_schema_config + configuration_hash.merge(database: "postgres", schema_search_path: "public") + end + + def psql_env + {}.tap do |env| + env["PGHOST"] = db_config.host if db_config.host + env["PGPORT"] = configuration_hash[:port].to_s if configuration_hash[:port] + env["PGPASSWORD"] = configuration_hash[:password].to_s if configuration_hash[:password] + env["PGUSER"] = configuration_hash[:username].to_s if configuration_hash[:username] + env["PGSSLMODE"] = configuration_hash[:sslmode].to_s if configuration_hash[:sslmode] + env["PGSSLCERT"] = configuration_hash[:sslcert].to_s if configuration_hash[:sslcert] + env["PGSSLKEY"] = configuration_hash[:sslkey].to_s if configuration_hash[:sslkey] + env["PGSSLROOTCERT"] = configuration_hash[:sslrootcert].to_s if configuration_hash[:sslrootcert] + end + end + + def run_cmd(cmd, args, action) + fail run_cmd_error(cmd, args, action) unless Kernel.system(psql_env, cmd, *args) + end + + def run_cmd_error(cmd, args, action) + msg = +"failed to execute:\n" + msg << "#{cmd} #{args.join(' ')}\n\n" + msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n" + msg + end + + def remove_sql_header_comments(filename) + removing_comments = true + tempfile = Tempfile.open("uncommented_structure.sql") + begin + File.foreach(filename) do |line| + unless removing_comments && (line.start_with?(SQL_COMMENT_BEGIN) || line.blank?) + tempfile << line + removing_comments = false + end + end + ensure + tempfile.close + end + FileUtils.cp(tempfile.path, filename) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/tasks/sqlite_database_tasks.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/tasks/sqlite_database_tasks.rb new file mode 100644 index 00000000..ce073ae1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/tasks/sqlite_database_tasks.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +module ActiveRecord + module Tasks # :nodoc: + class SQLiteDatabaseTasks # :nodoc: + def self.using_database_configurations? + true + end + + def initialize(db_config, root = ActiveRecord::Tasks::DatabaseTasks.root) + @db_config = db_config + @root = root + end + + def create + raise DatabaseAlreadyExists if File.exist?(db_config.database) + + establish_connection + connection + end + + def drop + db_path = db_config.database + file = File.absolute_path?(db_path) ? db_path : File.join(root, db_path) + FileUtils.rm(file) + FileUtils.rm_f(["#{file}-shm", "#{file}-wal"]) + rescue Errno::ENOENT => error + raise NoDatabaseError.new(error.message) + end + + def purge + connection.disconnect! + drop + rescue NoDatabaseError + ensure + create + connection.reconnect! + end + + def charset + connection.encoding + end + + def structure_dump(filename, extra_flags) + args = [] + args.concat(Array(extra_flags)) if extra_flags + args << db_config.database + + ignore_tables = ActiveRecord::SchemaDumper.ignore_tables + if ignore_tables.any? + ignore_tables = connection.data_sources.select { |table| ignore_tables.any? { |pattern| pattern === table } } + condition = ignore_tables.map { |table| connection.quote(table) }.join(", ") + args << "SELECT sql || ';' FROM sqlite_master WHERE tbl_name NOT IN (#{condition}) ORDER BY tbl_name, type DESC, name" + else + args << ".schema --nosys" + end + run_cmd("sqlite3", args, filename) + end + + def structure_load(filename, extra_flags) + flags = extra_flags.join(" ") if extra_flags + `sqlite3 #{flags} #{db_config.database} < "#{filename}"` + end + + private + attr_reader :db_config, :root + + def connection + ActiveRecord::Base.lease_connection + end + + def establish_connection(config = db_config) + ActiveRecord::Base.establish_connection(config) + connection.connect! + end + + def run_cmd(cmd, args, out) + fail run_cmd_error(cmd, args) unless Kernel.system(cmd, *args, out: out) + end + + def run_cmd_error(cmd, args) + msg = +"failed to execute:\n" + msg << "#{cmd} #{args.join(' ')}\n\n" + msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n" + msg + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/test_databases.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/test_databases.rb new file mode 100644 index 00000000..0c9bb691 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/test_databases.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "active_support/testing/parallelization" + +module ActiveRecord + module TestDatabases # :nodoc: + ActiveSupport::Testing::Parallelization.after_fork_hook do |i| + create_and_load_schema(i, env_name: ActiveRecord::ConnectionHandling::DEFAULT_ENV.call) + end + + def self.create_and_load_schema(i, env_name:) + old, ENV["VERBOSE"] = ENV["VERBOSE"], "false" + + ActiveRecord::Base.configurations.configs_for(env_name: env_name).each do |db_config| + db_config._database = "#{db_config.database}-#{i}" + + ActiveRecord::Tasks::DatabaseTasks.reconstruct_from_schema(db_config, ActiveRecord.schema_format, nil) + end + ensure + ActiveRecord::Base.establish_connection + ENV["VERBOSE"] = old + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/test_fixtures.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/test_fixtures.rb new file mode 100644 index 00000000..3d617e78 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/test_fixtures.rb @@ -0,0 +1,321 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" + +module ActiveRecord + module TestFixtures + extend ActiveSupport::Concern + + def before_setup # :nodoc: + setup_fixtures + super + end + + def after_teardown # :nodoc: + super + ensure + teardown_fixtures + end + + included do + ## + # :singleton-method: fixture_paths + # + # Returns the ActiveRecord::FixtureSet collection + + ## + # :singleton-method: fixture_paths= + # + # :call-seq: + # fixture_paths=(fixture_paths) + class_attribute :fixture_paths, instance_writer: false, default: [] + class_attribute :fixture_table_names, default: [] + class_attribute :fixture_class_names, default: {} + class_attribute :use_transactional_tests, default: true + class_attribute :use_instantiated_fixtures, default: false # true, false, or :no_instances + class_attribute :pre_loaded_fixtures, default: false + class_attribute :lock_threads, default: true + class_attribute :fixture_sets, default: {} + + ActiveSupport.run_load_hooks(:active_record_fixtures, self) + end + + module ClassMethods + # Sets the model class for a fixture when the class name cannot be inferred from the fixture name. + # + # Examples: + # + # set_fixture_class some_fixture: SomeModel, + # 'namespaced/fixture' => Another::Model + # + # The keys must be the fixture names, that coincide with the short paths to the fixture files. + def set_fixture_class(class_names = {}) + self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys) + end + + def fixtures(*fixture_set_names) + if fixture_set_names.first == :all + raise StandardError, "No fixture path found. Please set `#{self}.fixture_paths`." if fixture_paths.blank? + fixture_set_names = fixture_paths.flat_map do |path| + names = Dir[::File.join(path, "{**,*}/*.{yml}")].uniq + names.reject! { |f| f.start_with?(file_fixture_path.to_s) } if defined?(file_fixture_path) && file_fixture_path + names.map! { |f| f[path.to_s.size..-5].delete_prefix("/") } + end.uniq + else + fixture_set_names = fixture_set_names.flatten.map(&:to_s) + end + + self.fixture_table_names = (fixture_table_names | fixture_set_names).sort + setup_fixture_accessors(fixture_set_names) + end + + def setup_fixture_accessors(fixture_set_names = nil) + fixture_set_names = Array(fixture_set_names || fixture_table_names) + unless fixture_set_names.empty? + self.fixture_sets = fixture_sets.dup + fixture_set_names.each do |fs_name| + key = fs_name.to_s.include?("/") ? -fs_name.to_s.tr("/", "_") : fs_name + key = -key.to_s if key.is_a?(Symbol) + fs_name = -fs_name.to_s if fs_name.is_a?(Symbol) + fixture_sets[key] = fs_name + end + end + end + + # Prevents automatically wrapping each specified test in a transaction, + # to allow application logic transactions to be tested in a top-level + # (non-nested) context. + def uses_transaction(*methods) + @uses_transaction = [] unless defined?(@uses_transaction) + @uses_transaction.concat methods.map(&:to_s) + end + + def uses_transaction?(method) + @uses_transaction = [] unless defined?(@uses_transaction) + @uses_transaction.include?(method.to_s) + end + end + + # Generic fixture accessor for fixture names that may conflict with other methods. + # + # assert_equal "Ruby on Rails", web_sites(:rubyonrails).name + # assert_equal "Ruby on Rails", fixture(:web_sites, :rubyonrails).name + def fixture(fixture_set_name, *fixture_names) + active_record_fixture(fixture_set_name, *fixture_names) + end + + private + def run_in_transaction? + use_transactional_tests && + !self.class.uses_transaction?(name) + end + + def setup_fixtures(config = ActiveRecord::Base) + if pre_loaded_fixtures && !use_transactional_tests + raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests" + end + + @fixture_cache = {} + @fixture_cache_key = [self.class.fixture_table_names.dup, self.class.fixture_paths.dup, self.class.fixture_class_names.dup] + @fixture_connection_pools = [] + @@already_loaded_fixtures ||= {} + @connection_subscriber = nil + @saved_pool_configs = Hash.new { |hash, key| hash[key] = {} } + + if run_in_transaction? + # Load fixtures once and begin transaction. + @loaded_fixtures = @@already_loaded_fixtures[@fixture_cache_key] + unless @loaded_fixtures + @@already_loaded_fixtures.clear + @loaded_fixtures = @@already_loaded_fixtures[@fixture_cache_key] = load_fixtures(config) + end + + setup_transactional_fixtures + else + # Load fixtures for every test. + ActiveRecord::FixtureSet.reset_cache + invalidate_already_loaded_fixtures + @loaded_fixtures = load_fixtures(config) + end + setup_asynchronous_queries_session + + # Instantiate fixtures for every test if requested. + instantiate_fixtures if use_instantiated_fixtures + end + + def teardown_fixtures + teardown_asynchronous_queries_session + + # Rollback changes if a transaction is active. + if run_in_transaction? + teardown_transactional_fixtures + else + ActiveRecord::FixtureSet.reset_cache + invalidate_already_loaded_fixtures + end + + ActiveRecord::Base.connection_handler.clear_active_connections!(:all) + end + + def setup_asynchronous_queries_session + @_async_queries_session = ActiveRecord::Base.asynchronous_queries_tracker.start_session + end + + def teardown_asynchronous_queries_session + ActiveRecord::Base.asynchronous_queries_tracker.finalize_session(true) if @_async_queries_session + end + + def invalidate_already_loaded_fixtures + @@already_loaded_fixtures.clear + end + + def setup_transactional_fixtures + setup_shared_connection_pool + + # Begin transactions for connections already established + @fixture_connection_pools = ActiveRecord::Base.connection_handler.connection_pool_list(:writing) + @fixture_connection_pools.each do |pool| + pool.pin_connection!(lock_threads) + pool.lease_connection + end + + # When connections are established in the future, begin a transaction too + @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload| + connection_name = payload[:connection_name] if payload.key?(:connection_name) + shard = payload[:shard] if payload.key?(:shard) + + if connection_name + pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(connection_name, shard: shard) + if pool + setup_shared_connection_pool + + unless @fixture_connection_pools.include?(pool) + pool.pin_connection!(lock_threads) + pool.lease_connection + @fixture_connection_pools << pool + end + end + end + end + end + + def teardown_transactional_fixtures + ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber + + unless @fixture_connection_pools.map(&:unpin_connection!).all? + # Something caused the transaction to be committed or rolled back + # We can no longer trust the database is in a clean state. + @@already_loaded_fixtures.clear + end + @fixture_connection_pools.clear + teardown_shared_connection_pool + end + + # Shares the writing connection pool with connections on + # other handlers. + # + # In an application with a primary and replica the test fixtures + # need to share a connection pool so that the reading connection + # can see data in the open transaction on the writing connection. + def setup_shared_connection_pool + handler = ActiveRecord::Base.connection_handler + + handler.connection_pool_names.each do |name| + pool_manager = handler.send(:connection_name_to_pool_manager)[name] + pool_manager.shard_names.each do |shard_name| + writing_pool_config = pool_manager.get_pool_config(ActiveRecord.writing_role, shard_name) + @saved_pool_configs[name][shard_name] ||= {} + pool_manager.role_names.each do |role| + next unless pool_config = pool_manager.get_pool_config(role, shard_name) + next if pool_config == writing_pool_config + + @saved_pool_configs[name][shard_name][role] = pool_config + pool_manager.set_pool_config(role, shard_name, writing_pool_config) + end + end + end + end + + def teardown_shared_connection_pool + handler = ActiveRecord::Base.connection_handler + + @saved_pool_configs.each_pair do |name, shards| + pool_manager = handler.send(:connection_name_to_pool_manager)[name] + shards.each_pair do |shard_name, roles| + roles.each_pair do |role, pool_config| + next unless pool_manager.get_pool_config(role, shard_name) + + pool_manager.set_pool_config(role, shard_name, pool_config) + end + end + end + + @saved_pool_configs.clear + end + + def load_fixtures(config) + ActiveRecord::FixtureSet.create_fixtures(fixture_paths, fixture_table_names, fixture_class_names, config).index_by(&:name) + end + + def instantiate_fixtures + if pre_loaded_fixtures + raise RuntimeError, "Load fixtures before instantiating them." if ActiveRecord::FixtureSet.all_loaded_fixtures.empty? + ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?) + else + raise RuntimeError, "Load fixtures before instantiating them." if @loaded_fixtures.nil? + @loaded_fixtures.each_value do |fixture_set| + ActiveRecord::FixtureSet.instantiate_fixtures(self, fixture_set, load_instances?) + end + end + end + + def load_instances? + use_instantiated_fixtures != :no_instances + end + + def method_missing(method, ...) + if fixture_sets.key?(method.name) + active_record_fixture(method, ...) + else + super + end + end + + def respond_to_missing?(method, include_private = false) + if include_private && fixture_sets.key?(method.name) + true + else + super + end + end + + def active_record_fixture(fixture_set_name, *fixture_names) + if fs_name = fixture_sets[fixture_set_name.name] + access_fixture(fs_name, *fixture_names) + else + raise StandardError, "No fixture set named '#{fixture_set_name.inspect}'" + end + end + + def access_fixture(fs_name, *fixture_names) + force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload + return_single_record = fixture_names.size == 1 + + fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty? + @fixture_cache[fs_name] ||= {} + + instances = fixture_names.map do |f_name| + f_name = f_name.to_s if f_name.is_a?(Symbol) + @fixture_cache[fs_name].delete(f_name) if force_reload + + if @loaded_fixtures[fs_name][f_name] + @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find + else + raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'" + end + end + + return_single_record ? instances.first : instances + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/testing/query_assertions.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/testing/query_assertions.rb new file mode 100644 index 00000000..565875f6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/testing/query_assertions.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +module ActiveRecord + module Assertions + module QueryAssertions + # Asserts that the number of SQL queries executed in the given block matches the expected count. + # + # # Check for exact number of queries + # assert_queries_count(1) { Post.first } + # + # # Check for any number of queries + # assert_queries_count { Post.first } + # + # If the +:include_schema+ option is provided, any queries (including schema related) are counted. + # + # assert_queries_count(1, include_schema: true) { Post.columns } + # + def assert_queries_count(count = nil, include_schema: false, &block) + ActiveRecord::Base.lease_connection.materialize_transactions + + counter = SQLCounter.new + ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do + result = _assert_nothing_raised_or_warn("assert_queries_count", &block) + queries = include_schema ? counter.log_all : counter.log + if count + assert_equal count, queries.size, "#{queries.size} instead of #{count} queries were executed. Queries: #{queries.join("\n\n")}" + else + assert_operator queries.size, :>=, 1, "1 or more queries expected, but none were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}" + end + result + end + end + + # Asserts that no SQL queries are executed in the given block. + # + # assert_no_queries { post.comments } + # + # If the +:include_schema+ option is provided, any queries (including schema related) are counted. + # + # assert_no_queries(include_schema: true) { Post.columns } + # + def assert_no_queries(include_schema: false, &block) + assert_queries_count(0, include_schema: include_schema, &block) + end + + # Asserts that the SQL queries executed in the given block match expected pattern. + # + # # Check for exact number of queries + # assert_queries_match(/LIMIT \?/, count: 1) { Post.first } + # + # # Check for any number of queries + # assert_queries_match(/LIMIT \?/) { Post.first } + # + # If the +:include_schema+ option is provided, any queries (including schema related) + # that match the matcher are considered. + # + # assert_queries_match(/FROM pg_attribute/i, include_schema: true) { Post.columns } + # + def assert_queries_match(match, count: nil, include_schema: false, &block) + ActiveRecord::Base.lease_connection.materialize_transactions + + counter = SQLCounter.new + ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do + result = _assert_nothing_raised_or_warn("assert_queries_match", &block) + queries = include_schema ? counter.log_all : counter.log + matched_queries = queries.select { |query| match === query } + + if count + assert_equal count, matched_queries.size, "#{matched_queries.size} instead of #{count} queries were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}" + else + assert_operator matched_queries.size, :>=, 1, "1 or more queries expected, but none were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}" + end + + result + end + end + + # Asserts that no SQL queries matching the pattern are executed in the given block. + # + # assert_no_queries_match(/SELECT/i) { post.comments } + # + # If the +:include_schema+ option is provided, any queries (including schema related) + # that match the matcher are counted. + # + # assert_no_queries_match(/FROM pg_attribute/i, include_schema: true) { Post.columns } + # + def assert_no_queries_match(match, include_schema: false, &block) + assert_queries_match(match, count: 0, include_schema: include_schema, &block) + end + + class SQLCounter # :nodoc: + attr_reader :log_full, :log_all + + def initialize + @log_full = [] + @log_all = [] + end + + def log + @log_full.map(&:first) + end + + def call(*, payload) + return if payload[:cached] + + sql = payload[:sql] + @log_all << sql + + unless payload[:name] == "SCHEMA" + bound_values = (payload[:binds] || []).map do |value| + value = value.value_for_database if value.respond_to?(:value_for_database) + value + end + + @log_full << [sql, bound_values] + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/timestamp.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/timestamp.rb new file mode 100644 index 00000000..6c1f9325 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/timestamp.rb @@ -0,0 +1,177 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record \Timestamp + # + # Active Record automatically timestamps create and update operations if the + # table has fields named created_at/created_on or + # updated_at/updated_on. + # + # Timestamping can be turned off by setting: + # + # config.active_record.record_timestamps = false + # + # Timestamps are in UTC by default but you can use the local timezone by setting: + # + # config.active_record.default_timezone = :local + # + # == Time Zone aware attributes + # + # Active Record keeps all the datetime and time columns + # timezone aware. By default, these values are stored in the database as UTC + # and converted back to the current Time.zone when pulled from the database. + # + # This feature can be turned off completely by setting: + # + # config.active_record.time_zone_aware_attributes = false + # + # You can also specify that only datetime columns should be time-zone + # aware (while time should not) by setting: + # + # ActiveRecord::Base.time_zone_aware_types = [:datetime] + # + # You can also add database-specific timezone aware types. For example, for PostgreSQL: + # + # ActiveRecord::Base.time_zone_aware_types += [:tsrange, :tstzrange] + # + # Finally, you can indicate specific attributes of a model for which time zone + # conversion should not applied, for instance by setting: + # + # class Topic < ActiveRecord::Base + # self.skip_time_zone_conversion_for_attributes = [:written_on] + # end + module Timestamp + extend ActiveSupport::Concern + + included do + class_attribute :record_timestamps, default: true + end + + def initialize_dup(other) # :nodoc: + super + clear_timestamp_attributes + end + + module ClassMethods # :nodoc: + def touch_attributes_with_time(*names, time: nil) + names = names.map(&:to_s) + names = names.map { |name| attribute_aliases[name] || name } + attribute_names = timestamp_attributes_for_update_in_model + attribute_names |= names + attribute_names.index_with(time || current_time_from_proper_timezone) + end + + def timestamp_attributes_for_create_in_model + @timestamp_attributes_for_create_in_model ||= + (timestamp_attributes_for_create & column_names).freeze + end + + def timestamp_attributes_for_update_in_model + @timestamp_attributes_for_update_in_model ||= + (timestamp_attributes_for_update & column_names).freeze + end + + def all_timestamp_attributes_in_model + @all_timestamp_attributes_in_model ||= + (timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model).freeze + end + + def current_time_from_proper_timezone + with_connection { |c| c.default_timezone == :utc ? Time.now.utc : Time.now } + end + + protected + def reload_schema_from_cache(recursive = true) + @timestamp_attributes_for_create_in_model = nil + @timestamp_attributes_for_update_in_model = nil + @all_timestamp_attributes_in_model = nil + super + end + + private + def timestamp_attributes_for_create + ["created_at", "created_on"].map! { |name| attribute_aliases[name] || name } + end + + def timestamp_attributes_for_update + ["updated_at", "updated_on"].map! { |name| attribute_aliases[name] || name } + end + end + + private + def init_internals + super + @_touch_record = nil + end + + def _create_record + if record_timestamps + current_time = current_time_from_proper_timezone + + all_timestamp_attributes_in_model.each do |column| + _write_attribute(column, current_time) unless _read_attribute(column) + end + end + + super + end + + def _update_record + record_update_timestamps + + super + end + + def create_or_update(touch: true, **) + @_touch_record = touch + super + end + + def record_update_timestamps + if @_touch_record && should_record_timestamps? + current_time = current_time_from_proper_timezone + + timestamp_attributes_for_update_in_model.each do |column| + next if will_save_change_to_attribute?(column) + _write_attribute(column, current_time) + end + end + + yield if block_given? + end + + def should_record_timestamps? + record_timestamps && (!partial_updates? || has_changes_to_save?) + end + + def timestamp_attributes_for_create_in_model + self.class.timestamp_attributes_for_create_in_model + end + + def timestamp_attributes_for_update_in_model + self.class.timestamp_attributes_for_update_in_model + end + + def all_timestamp_attributes_in_model + self.class.all_timestamp_attributes_in_model + end + + def current_time_from_proper_timezone + self.class.current_time_from_proper_timezone + end + + def max_updated_column_timestamp + timestamp_attributes_for_update_in_model + .filter_map { |attr| (v = self[attr]) && (v.is_a?(::Time) ? v : v.to_time) } + .max + end + + # Clear attributes and changed_attributes + def clear_timestamp_attributes + all_timestamp_attributes_in_model.each do |attribute_name| + self[attribute_name] = nil + clear_attribute_change(attribute_name) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/token_for.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/token_for.rb new file mode 100644 index 00000000..fa7a6f39 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/token_for.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/json" + +module ActiveRecord + module TokenFor + extend ActiveSupport::Concern + + included do + class_attribute :token_definitions, instance_accessor: false, instance_predicate: false, default: {} + class_attribute :generated_token_verifier, instance_accessor: false, instance_predicate: false + end + + TokenDefinition = Struct.new(:defining_class, :purpose, :expires_in, :block) do # :nodoc: + def full_purpose + @full_purpose ||= [defining_class.name, purpose, expires_in].join("\n") + end + + def message_verifier + defining_class.generated_token_verifier + end + + def payload_for(model) + block ? [model.id, model.instance_eval(&block).as_json] : [model.id] + end + + def generate_token(model) + message_verifier.generate(payload_for(model), expires_in: expires_in, purpose: full_purpose) + end + + def resolve_token(token) + payload = message_verifier.verified(token, purpose: full_purpose) + model = yield(payload[0]) if payload + model if model && payload_for(model) == payload + end + end + + module RelationMethods + # Finds a record using a given +token+ for a predefined +purpose+. Returns + # +nil+ if the token is invalid or the record was not found. + def find_by_token_for(purpose, token) + raise UnknownPrimaryKey.new(self) unless model.primary_key + model.token_definitions.fetch(purpose).resolve_token(token) { |id| find_by(model.primary_key => [id]) } + end + + # Finds a record using a given +token+ for a predefined +purpose+. Raises + # ActiveSupport::MessageVerifier::InvalidSignature if the token is invalid + # (e.g. expired, bad format, etc). Raises ActiveRecord::RecordNotFound if + # the token is valid but the record was not found. + def find_by_token_for!(purpose, token) + model.token_definitions.fetch(purpose).resolve_token(token) { |id| find(id) } || + (raise ActiveSupport::MessageVerifier::InvalidSignature) + end + end + + module ClassMethods + # Defines the behavior of tokens generated for a specific +purpose+. + # A token can be generated by calling TokenFor#generate_token_for on a + # record. Later, that record can be fetched by calling #find_by_token_for + # (or #find_by_token_for!) with the same purpose and token. + # + # Tokens are signed so that they are tamper-proof. Thus they can be + # exposed to outside world as, for example, password reset tokens. + # + # By default, tokens do not expire. They can be configured to expire by + # specifying a duration via the +expires_in+ option. The duration becomes + # part of the token's signature, so changing the value of +expires_in+ + # will automatically invalidate previously generated tokens. + # + # A block may also be specified. When generating a token with + # TokenFor#generate_token_for, the block will be evaluated in the context + # of the record, and its return value will be embedded in the token as + # JSON. Later, when fetching the record with #find_by_token_for, the block + # will be evaluated again in the context of the fetched record. If the two + # JSON values do not match, the token will be treated as invalid. Note + # that the value returned by the block should not contain sensitive + # information because it will be embedded in the token as + # human-readable plaintext JSON. + # + # ==== Examples + # + # class User < ActiveRecord::Base + # has_secure_password + # + # generates_token_for :password_reset, expires_in: 15.minutes do + # # Last 10 characters of password salt, which changes when password is updated: + # password_salt&.last(10) + # end + # end + # + # user = User.first + # + # token = user.generate_token_for(:password_reset) + # User.find_by_token_for(:password_reset, token) # => user + # # 16 minutes later... + # User.find_by_token_for(:password_reset, token) # => nil + # + # token = user.generate_token_for(:password_reset) + # User.find_by_token_for(:password_reset, token) # => user + # user.update!(password: "new password") + # User.find_by_token_for(:password_reset, token) # => nil + def generates_token_for(purpose, expires_in: nil, &block) + self.token_definitions = token_definitions.merge(purpose => TokenDefinition.new(self, purpose, expires_in, block)) + end + + def find_by_token_for(purpose, token) # :nodoc: + all.find_by_token_for(purpose, token) + end + + def find_by_token_for!(purpose, token) # :nodoc: + all.find_by_token_for!(purpose, token) + end + end + + # Generates a token for a predefined +purpose+. + # + # Use ClassMethods#generates_token_for to define a token purpose and + # behavior. + def generate_token_for(purpose) + self.class.token_definitions.fetch(purpose).generate_token(self) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/touch_later.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/touch_later.rb new file mode 100644 index 00000000..f84c543f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/touch_later.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record Touch Later + module TouchLater # :nodoc: + def before_committed! + touch_deferred_attributes if has_defer_touch_attrs? && persisted? + super + end + + def touch_later(*names) # :nodoc: + _raise_record_not_touched_error unless persisted? + + @_defer_touch_attrs ||= timestamp_attributes_for_update_in_model + @_defer_touch_attrs |= names.map! do |name| + name = name.to_s + self.class.attribute_aliases[name] || name + end unless names.empty? + + @_touch_time = current_time_from_proper_timezone + + surreptitiously_touch @_defer_touch_attrs + add_to_transaction + @_new_record_before_last_commit ||= false + + # touch the parents as we are not calling the after_save callbacks + self.class.reflect_on_all_associations.each do |r| + if touch = r.options[:touch] + if r.macro == :belongs_to + ActiveRecord::Associations::Builder::BelongsTo.touch_record(self, changes_to_save, r.foreign_key, r.name, touch) + elsif r.macro == :has_one + ActiveRecord::Associations::Builder::HasOne.touch_record(self, r.name, touch) + end + end + end + end + + def touch(*names, time: nil) # :nodoc: + if has_defer_touch_attrs? + names |= @_defer_touch_attrs + super(*names, time: time) + @_defer_touch_attrs, @_touch_time = nil, nil + else + super + end + end + + private + def init_internals + super + @_defer_touch_attrs = nil + end + + def surreptitiously_touch(attr_names) + attr_names.each do |attr_name| + _write_attribute(attr_name, @_touch_time) + clear_attribute_change(attr_name) + end + end + + def touch_deferred_attributes + @_skip_dirty_tracking = true + touch(time: @_touch_time) + end + + def has_defer_touch_attrs? + @_defer_touch_attrs.present? + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/transaction.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/transaction.rb new file mode 100644 index 00000000..d3ba770a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/transaction.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +require "active_support/core_ext/digest" + +module ActiveRecord + # Class specifies the interface to interact with the current transaction state. + # + # It can either map to an actual transaction/savepoint, or represent the + # absence of a transaction. + # + # == State + # + # We say that a transaction is _finalized_ when it wraps a real transaction + # that has been either committed or rolled back. + # + # A transaction is _open_ if it wraps a real transaction that is not finalized. + # + # On the other hand, a transaction is _closed_ when it is not open. That is, + # when it represents absence of transaction, or it wraps a real but finalized + # one. + # + # You can check whether a transaction is open or closed with the +open?+ and + # +closed?+ predicates: + # + # if Article.current_transaction.open? + # # We are inside a real and not finalized transaction. + # end + # + # Closed transactions are `blank?` too. + # + # == Callbacks + # + # After updating the database state, you may sometimes need to perform some extra work, or reflect these + # changes in a remote system like clearing or updating a cache: + # + # def publish_article(article) + # article.update!(published: true) + # NotificationService.article_published(article) + # end + # + # The above code works but has one important flaw, which is that it no longer works properly if called inside + # a transaction, as it will interact with the remote system before the changes are persisted: + # + # Article.transaction do + # article = create_article(article) + # publish_article(article) + # end + # + # The callbacks offered by ActiveRecord::Transaction allow to rewriting this method in a way that is compatible + # with transactions: + # + # def publish_article(article) + # article.update!(published: true) + # Article.current_transaction.after_commit do + # NotificationService.article_published(article) + # end + # end + # + # In the above example, if +publish_article+ is called inside a transaction, the callback will be invoked + # after the transaction is successfully committed, and if called outside a transaction, the callback will be invoked + # immediately. + # + # == Caveats + # + # When using after_commit callbacks, it is important to note that if the callback raises an error, the transaction + # won't be rolled back as it was already committed. Relying solely on these to synchronize state between multiple + # systems may lead to consistency issues. + class Transaction + def initialize(internal_transaction) # :nodoc: + @internal_transaction = internal_transaction + @uuid = nil + end + + # Registers a block to be called after the transaction is fully committed. + # + # If there is no currently open transactions, the block is called + # immediately, unless the transaction is finalized, in which case attempting + # to register the callback raises ActiveRecord::ActiveRecordError. + # + # If the transaction has a parent transaction, the callback is transferred to + # the parent when the current transaction commits, or dropped when the current transaction + # is rolled back. This operation is repeated until the outermost transaction is reached. + # + # If the callback raises an error, the transaction remains committed. + def after_commit(&block) + if @internal_transaction.nil? + yield + else + @internal_transaction.after_commit(&block) + end + end + + # Registers a block to be called after the transaction is rolled back. + # + # If there is no currently open transactions, the block is not called. But + # if the transaction is finalized, attempting to register the callback + # raises ActiveRecord::ActiveRecordError. + # + # If the transaction is successfully committed but has a parent + # transaction, the callback is automatically added to the parent transaction. + # + # If the entire chain of nested transactions are all successfully committed, + # the block is never called. + # + # If the transaction is already finalized, attempting to register a callback + # will raise ActiveRecord::ActiveRecordError. + def after_rollback(&block) + @internal_transaction&.after_rollback(&block) + end + + # Returns true if the transaction exists and isn't finalized yet. + def open? + !closed? + end + + # Returns true if the transaction doesn't exist or is finalized. + def closed? + @internal_transaction.nil? || @internal_transaction.state.finalized? + end + + alias_method :blank?, :closed? + + # Returns a UUID for this transaction or +nil+ if no transaction is open. + def uuid + if @internal_transaction + @uuid ||= Digest::UUID.uuid_v4 + end + end + + NULL_TRANSACTION = new(nil).freeze + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/transactions.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/transactions.rb new file mode 100644 index 00000000..a05912d4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/transactions.rb @@ -0,0 +1,522 @@ +# frozen_string_literal: true + +module ActiveRecord + # See ActiveRecord::Transactions::ClassMethods for documentation. + module Transactions + extend ActiveSupport::Concern + # :nodoc: + ACTIONS = [:create, :destroy, :update] + + included do + define_callbacks :commit, :rollback, + :before_commit, + scope: [:kind, :name] + end + + attr_accessor :_new_record_before_last_commit # :nodoc: + + # = Active Record \Transactions + # + # \Transactions are protective blocks where SQL statements are only permanent + # if they can all succeed as one atomic action. The classic example is a + # transfer between two accounts where you can only have a deposit if the + # withdrawal succeeded and vice versa. \Transactions enforce the integrity of + # the database and guard the data against program errors or database + # break-downs. So basically you should use transaction blocks whenever you + # have a number of statements that must be executed together or not at all. + # + # For example: + # + # ActiveRecord::Base.transaction do + # david.withdrawal(100) + # mary.deposit(100) + # end + # + # This example will only take money from David and give it to Mary if neither + # +withdrawal+ nor +deposit+ raise an exception. Exceptions will force a + # ROLLBACK that returns the database to the state before the transaction + # began. Be aware, though, that the objects will _not_ have their instance + # data returned to their pre-transactional state. + # + # == Different Active Record classes in a single transaction + # + # Though the #transaction class method is called on some Active Record class, + # the objects within the transaction block need not all be instances of + # that class. This is because transactions are per-database connection, not + # per-model. + # + # In this example a +balance+ record is transactionally saved even + # though #transaction is called on the +Account+ class: + # + # Account.transaction do + # balance.save! + # account.save! + # end + # + # The #transaction method is also available as a model instance method. + # For example, you can also do this: + # + # balance.transaction do + # balance.save! + # account.save! + # end + # + # == Transactions are not distributed across database connections + # + # A transaction acts on a single database connection. If you have + # multiple class-specific databases, the transaction will not protect + # interaction among them. One workaround is to begin a transaction + # on each class whose models you alter: + # + # Student.transaction do + # Course.transaction do + # course.enroll(student) + # student.units += course.units + # end + # end + # + # This is a poor solution, but fully distributed transactions are beyond + # the scope of Active Record. + # + # == +save+ and +destroy+ are automatically wrapped in a transaction + # + # Both {#save}[rdoc-ref:Persistence#save] and + # {#destroy}[rdoc-ref:Persistence#destroy] come wrapped in a transaction that ensures + # that whatever you do in validations or callbacks will happen under its + # protected cover. So you can use validations to check for values that + # the transaction depends on or you can raise exceptions in the callbacks + # to rollback, including after_* callbacks. + # + # As a consequence changes to the database are not seen outside your connection + # until the operation is complete. For example, if you try to update the index + # of a search engine in +after_save+ the indexer won't see the updated record. + # The #after_commit callback is the only one that is triggered once the update + # is committed. See below. + # + # == Exception handling and rolling back + # + # Also have in mind that exceptions thrown within a transaction block will + # be propagated (after triggering the ROLLBACK), so you should be ready to + # catch those in your application code. + # + # One exception is the ActiveRecord::Rollback exception, which will trigger + # a ROLLBACK when raised, but not be re-raised by the transaction block. Any + # other exception will be re-raised. + # + # *Warning*: one should not catch ActiveRecord::StatementInvalid exceptions + # inside a transaction block. ActiveRecord::StatementInvalid exceptions indicate that an + # error occurred at the database level, for example when a unique constraint + # is violated. On some database systems, such as PostgreSQL, database errors + # inside a transaction cause the entire transaction to become unusable + # until it's restarted from the beginning. Here is an example which + # demonstrates the problem: + # + # # Suppose that we have a Number model with a unique column called 'i'. + # Number.transaction do + # Number.create(i: 0) + # begin + # # This will raise a unique constraint error... + # Number.create(i: 0) + # rescue ActiveRecord::StatementInvalid + # # ...which we ignore. + # end + # + # # On PostgreSQL, the transaction is now unusable. The following + # # statement will cause a PostgreSQL error, even though the unique + # # constraint is no longer violated: + # Number.create(i: 1) + # # => "PG::Error: ERROR: current transaction is aborted, commands + # # ignored until end of transaction block" + # end + # + # One should restart the entire transaction if an + # ActiveRecord::StatementInvalid occurred. + # + # == Nested transactions + # + # #transaction calls can be nested. By default, this makes all database + # statements in the nested transaction block become part of the parent + # transaction. For example, the following behavior may be surprising: + # + # User.transaction do + # User.create(username: 'Kotori') + # User.transaction do + # User.create(username: 'Nemu') + # raise ActiveRecord::Rollback + # end + # end + # + # creates both "Kotori" and "Nemu". Reason is the ActiveRecord::Rollback + # exception in the nested block does not issue a ROLLBACK. Since these exceptions + # are captured in transaction blocks, the parent block does not see it and the + # real transaction is committed. + # + # In order to get a ROLLBACK for the nested transaction you may ask for a real + # sub-transaction by passing requires_new: true. If anything goes wrong, + # the database rolls back to the beginning of the sub-transaction without rolling + # back the parent transaction. If we add it to the previous example: + # + # User.transaction do + # User.create(username: 'Kotori') + # User.transaction(requires_new: true) do + # User.create(username: 'Nemu') + # raise ActiveRecord::Rollback + # end + # end + # + # only "Kotori" is created. + # + # Most databases don't support true nested transactions. At the time of + # writing, the only database that we're aware of that supports true nested + # transactions, is MS-SQL. Because of this, Active Record emulates nested + # transactions by using savepoints. See + # https://dev.mysql.com/doc/refman/en/savepoint.html + # for more information about savepoints. + # + # === \Callbacks + # + # There are two types of callbacks associated with committing and rolling back transactions: + # #after_commit and #after_rollback. + # + # #after_commit callbacks are called on every record saved or destroyed within a + # transaction immediately after the transaction is committed. #after_rollback callbacks + # are called on every record saved or destroyed within a transaction immediately after the + # transaction or savepoint is rolled back. + # + # These callbacks are useful for interacting with other systems since you will be guaranteed + # that the callback is only executed when the database is in a permanent state. For example, + # #after_commit is a good spot to put in a hook to clearing a cache since clearing it from + # within a transaction could trigger the cache to be regenerated before the database is updated. + # + # ==== NOTE: Callbacks are deduplicated per callback by filter. + # + # Trying to define multiple callbacks with the same filter will result in a single callback being run. + # + # For example: + # + # after_commit :do_something + # after_commit :do_something # only the last one will be called + # + # This applies to all variations of after_*_commit callbacks as well. + # + # after_commit :do_something + # after_create_commit :do_something + # after_save_commit :do_something + # + # It is recommended to use the +on:+ option to specify when the callback should be run. + # + # after_commit :do_something, on: [:create, :update] + # + # This is equivalent to using +after_create_commit+ and +after_update_commit+, but will not be deduplicated. + # + # === Caveats + # + # If you're on MySQL, then do not use Data Definition Language (DDL) operations in nested + # transactions blocks that are emulated with savepoints. That is, do not execute statements + # like 'CREATE TABLE' inside such blocks. This is because MySQL automatically + # releases all savepoints upon executing a DDL operation. When +transaction+ + # is finished and tries to release the savepoint it created earlier, a + # database error will occur because the savepoint has already been + # automatically released. The following example demonstrates the problem: + # + # Model.transaction do # BEGIN + # Model.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1 + # Model.lease_connection.create_table(...) # active_record_1 now automatically released + # end # RELEASE SAVEPOINT active_record_1 + # end # ^^^^ BOOM! database error! + # + # Note that "TRUNCATE" is also a MySQL DDL statement! + module ClassMethods + # See the ConnectionAdapters::DatabaseStatements#transaction API docs. + def transaction(**options, &block) + with_connection do |connection| + connection.transaction(**options, &block) + end + end + + # Returns a representation of the current transaction state, + # which can be a top level transaction, a savepoint, or the absence of a transaction. + # + # An object is always returned, whether or not a transaction is currently active. + # To check if a transaction was opened, use current_transaction.open?. + # + # See the ActiveRecord::Transaction documentation for detailed behavior. + def current_transaction + connection_pool.active_connection&.current_transaction&.user_transaction || Transaction::NULL_TRANSACTION + end + + def before_commit(*args, &block) # :nodoc: + set_options_for_callbacks!(args) + set_callback(:before_commit, :before, *args, &block) + end + + # This callback is called after a record has been created, updated, or destroyed. + # + # You can specify that the callback should only be fired by a certain action with + # the +:on+ option: + # + # after_commit :do_foo, on: :create + # after_commit :do_bar, on: :update + # after_commit :do_baz, on: :destroy + # + # after_commit :do_foo_bar, on: [:create, :update] + # after_commit :do_bar_baz, on: [:update, :destroy] + # + def after_commit(*args, &block) + set_options_for_callbacks!(args, prepend_option) + set_callback(:commit, :after, *args, &block) + end + + # Shortcut for after_commit :hook, on: [ :create, :update ]. + def after_save_commit(*args, &block) + set_options_for_callbacks!(args, on: [ :create, :update ], **prepend_option) + set_callback(:commit, :after, *args, &block) + end + + # Shortcut for after_commit :hook, on: :create. + def after_create_commit(*args, &block) + set_options_for_callbacks!(args, on: :create, **prepend_option) + set_callback(:commit, :after, *args, &block) + end + + # Shortcut for after_commit :hook, on: :update. + def after_update_commit(*args, &block) + set_options_for_callbacks!(args, on: :update, **prepend_option) + set_callback(:commit, :after, *args, &block) + end + + # Shortcut for after_commit :hook, on: :destroy. + def after_destroy_commit(*args, &block) + set_options_for_callbacks!(args, on: :destroy, **prepend_option) + set_callback(:commit, :after, *args, &block) + end + + # This callback is called after a create, update, or destroy are rolled back. + # + # Please check the documentation of #after_commit for options. + def after_rollback(*args, &block) + set_options_for_callbacks!(args, prepend_option) + set_callback(:rollback, :after, *args, &block) + end + + # Similar to ActiveSupport::Callbacks::ClassMethods#set_callback, but with + # support for options available on #after_commit and #after_rollback callbacks. + def set_callback(name, *filter_list, &block) + options = filter_list.extract_options! + filter_list << options + + if name.in?([:commit, :rollback]) && options[:on] + fire_on = Array(options[:on]) + assert_valid_transaction_action(fire_on) + options[:if] = [ + -> { transaction_include_any_action?(fire_on) }, + *options[:if] + ] + end + + + super(name, *filter_list, &block) + end + + private + def prepend_option + if ActiveRecord.run_after_transaction_callbacks_in_order_defined + { prepend: true } + else + {} + end + end + + def set_options_for_callbacks!(args, enforced_options = {}) + options = args.extract_options!.merge!(enforced_options) + args << options + + if options[:on] + fire_on = Array(options[:on]) + assert_valid_transaction_action(fire_on) + options[:if] = [ + -> { transaction_include_any_action?(fire_on) }, + *options[:if] + ] + end + end + + def assert_valid_transaction_action(actions) + if (actions - ACTIONS).any? + raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS}" + end + end + end + + # See ActiveRecord::Transactions::ClassMethods for detailed documentation. + def transaction(**options, &block) + self.class.transaction(**options, &block) + end + + def destroy # :nodoc: + with_transaction_returning_status { super } + end + + def save(**) # :nodoc: + with_transaction_returning_status { super } + end + + def save!(**) # :nodoc: + with_transaction_returning_status { super } + end + + def touch(*, **) # :nodoc: + with_transaction_returning_status { super } + end + + def before_committed! # :nodoc: + _run_before_commit_callbacks + end + + # Call the #after_commit callbacks. + # + # Ensure that it is not called if the object was never persisted (failed create), + # but call it after the commit of a destroyed object. + def committed!(should_run_callbacks: true) # :nodoc: + @_start_transaction_state = nil + if should_run_callbacks + @_committed_already_called = true + _run_commit_callbacks + end + ensure + @_committed_already_called = @_trigger_update_callback = @_trigger_destroy_callback = false + end + + # Call the #after_rollback callbacks. The +force_restore_state+ argument indicates if the record + # state should be rolled back to the beginning or just to the last savepoint. + def rolledback!(force_restore_state: false, should_run_callbacks: true) # :nodoc: + if should_run_callbacks + _run_rollback_callbacks + end + ensure + restore_transaction_record_state(force_restore_state) + clear_transaction_record_state + @_trigger_update_callback = @_trigger_destroy_callback = false if force_restore_state + end + + # Executes a block within a transaction and captures its return value as a + # status flag. If the status is true, the transaction is committed, + # otherwise a ROLLBACK is issued. In any case, the status flag is returned. + # + # This method is available within the context of an ActiveRecord::Base + # instance. + def with_transaction_returning_status + self.class.with_connection do |connection| + status = nil + ensure_finalize = !connection.transaction_open? + + connection.transaction do + add_to_transaction(ensure_finalize || has_transactional_callbacks?) + remember_transaction_record_state + + status = yield + raise ActiveRecord::Rollback unless status + end + status + end + end + + def trigger_transactional_callbacks? # :nodoc: + (@_new_record_before_last_commit || _trigger_update_callback) && persisted? || + _trigger_destroy_callback && destroyed? + end + + private + attr_reader :_committed_already_called, :_trigger_update_callback, :_trigger_destroy_callback + + def init_internals + super + @_start_transaction_state = nil + @_committed_already_called = nil + @_new_record_before_last_commit = nil + end + + # Save the new record state and id of a record so it can be restored later if a transaction fails. + def remember_transaction_record_state + @_start_transaction_state ||= { + id: id, + new_record: @new_record, + previously_new_record: @previously_new_record, + destroyed: @destroyed, + attributes: @attributes, + frozen?: frozen?, + level: 0 + } + @_start_transaction_state[:level] += 1 + + if _committed_already_called + @_new_record_before_last_commit = false + else + @_new_record_before_last_commit = @_start_transaction_state[:new_record] + end + end + + # Clear the new record state and id of a record. + def clear_transaction_record_state + return unless @_start_transaction_state + @_start_transaction_state[:level] -= 1 + @_start_transaction_state = nil if @_start_transaction_state[:level] < 1 + end + + # Restore the new record state and id of a record that was previously saved by a call to save_record_state. + def restore_transaction_record_state(force_restore_state = false) + if restore_state = @_start_transaction_state + if force_restore_state || restore_state[:level] <= 1 + @new_record = restore_state[:new_record] + @previously_new_record = restore_state[:previously_new_record] + @destroyed = restore_state[:destroyed] + @attributes = restore_state[:attributes].map do |attr| + value = @attributes.fetch_value(attr.name) + attr = attr.with_value_from_user(value) if attr.value != value + attr + end + @mutations_from_database = nil + @mutations_before_last_save = nil + if self.class.composite_primary_key? + if restore_state[:id] != @primary_key.map { |col| @attributes.fetch_value(col) } + @primary_key.zip(restore_state[:id]).each do |col, val| + @attributes.write_from_user(col, val) + end + end + else + if @attributes.fetch_value(@primary_key) != restore_state[:id] + @attributes.write_from_user(@primary_key, restore_state[:id]) + end + end + freeze if restore_state[:frozen?] + end + end + end + + # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks. + def transaction_include_any_action?(actions) + actions.any? do |action| + case action + when :create + persisted? && @_new_record_before_last_commit + when :update + !(@_new_record_before_last_commit || destroyed?) && _trigger_update_callback + when :destroy + _trigger_destroy_callback + end + end + end + + # Add the record to the current transaction so that the #after_rollback and #after_commit + # callbacks can be called. + def add_to_transaction(ensure_finalize = true) + self.class.with_connection do |connection| + connection.add_transaction_record(self, ensure_finalize) + end + end + + def has_transactional_callbacks? + !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_before_commit_callbacks.empty? + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/translation.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/translation.rb new file mode 100644 index 00000000..41241fe4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/translation.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module ActiveRecord + module Translation + # Set the lookup ancestors for ActiveModel. + def lookup_ancestors # :nodoc: + klass = self + classes = [klass] + return classes if klass == ActiveRecord::Base + + while !klass.base_class? + classes << klass = klass.superclass + end + classes + end + + # Set the i18n scope to override ActiveModel. + def i18n_scope # :nodoc: + :activerecord + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type.rb new file mode 100644 index 00000000..dadc0209 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require "active_model/type" + +require "active_record/type/internal/timezone" + +require "active_record/type/date" +require "active_record/type/date_time" +require "active_record/type/decimal_without_scale" +require "active_record/type/json" +require "active_record/type/time" +require "active_record/type/text" +require "active_record/type/unsigned_integer" + +require "active_record/type/serialized" +require "active_record/type/adapter_specific_registry" + +require "active_record/type/type_map" +require "active_record/type/hash_lookup_type_map" + +module ActiveRecord + module Type + @registry = AdapterSpecificRegistry.new + + class << self + attr_accessor :registry # :nodoc: + delegate :add_modifier, to: :registry + + # Add a new type to the registry, allowing it to be referenced as a + # symbol by {ActiveRecord::Base.attribute}[rdoc-ref:Attributes::ClassMethods#attribute]. + # If your type is only meant to be used with a specific database adapter, you can + # do so by passing adapter: :postgresql. If your type has the same + # name as a native type for the current adapter, an exception will be + # raised unless you specify an +:override+ option. override: true will + # cause your type to be used instead of the native type. override: + # false will cause the native type to be used over yours if one exists. + def register(type_name, klass = nil, **options, &block) + registry.register(type_name, klass, **options, &block) + end + + def lookup(*args, adapter: current_adapter_name, **kwargs) # :nodoc: + registry.lookup(*args, adapter: adapter, **kwargs) + end + + def default_value # :nodoc: + @default_value ||= Value.new + end + + def adapter_name_from(model) # :nodoc: + model.connection_db_config.adapter.to_sym + end + + private + def current_adapter_name + adapter_name_from(ActiveRecord::Base) + end + end + + BigInteger = ActiveModel::Type::BigInteger + Binary = ActiveModel::Type::Binary + Boolean = ActiveModel::Type::Boolean + Decimal = ActiveModel::Type::Decimal + Float = ActiveModel::Type::Float + Integer = ActiveModel::Type::Integer + ImmutableString = ActiveModel::Type::ImmutableString + String = ActiveModel::Type::String + Value = ActiveModel::Type::Value + + register(:big_integer, Type::BigInteger, override: false) + register(:binary, Type::Binary, override: false) + register(:boolean, Type::Boolean, override: false) + register(:date, Type::Date, override: false) + register(:datetime, Type::DateTime, override: false) + register(:decimal, Type::Decimal, override: false) + register(:float, Type::Float, override: false) + register(:integer, Type::Integer, override: false) + register(:immutable_string, Type::ImmutableString, override: false) + register(:json, Type::Json, override: false) + register(:string, Type::String, override: false) + register(:text, Type::Text, override: false) + register(:time, Type::Time, override: false) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/adapter_specific_registry.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/adapter_specific_registry.rb new file mode 100644 index 00000000..5dfca27d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/adapter_specific_registry.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +module ActiveRecord + # :stopdoc: + module Type + class AdapterSpecificRegistry # :nodoc: + def initialize + @registrations = [] + end + + def initialize_copy(other) + @registrations = @registrations.dup + end + + def add_modifier(options, klass, **args) + registrations << DecorationRegistration.new(options, klass, **args) + end + + def register(type_name, klass = nil, **options, &block) + unless block_given? + block = proc { |_, *args| klass.new(*args) } + block.ruby2_keywords if block.respond_to?(:ruby2_keywords) + end + registrations << Registration.new(type_name, block, **options) + end + + def lookup(symbol, *args, **kwargs) + registration = find_registration(symbol, *args, **kwargs) + + if registration + registration.call(self, symbol, *args, **kwargs) + else + raise ArgumentError, "Unknown type #{symbol.inspect}" + end + end + + private + attr_reader :registrations + + def find_registration(symbol, *args, **kwargs) + registrations + .select { |registration| registration.matches?(symbol, *args, **kwargs) } + .max + end + end + + class Registration # :nodoc: + def initialize(name, block, adapter: nil, override: nil) + @name = name + @block = block + @adapter = adapter + @override = override + end + + def call(_registry, *args, adapter: nil, **kwargs) + block.call(*args, **kwargs) + end + + def matches?(type_name, *args, **kwargs) + type_name == name && matches_adapter?(**kwargs) + end + + def <=>(other) + if conflicts_with?(other) + raise TypeConflictError.new("Type #{name} was registered for all + adapters, but shadows a native type with + the same name for #{other.adapter}".squish) + end + priority <=> other.priority + end + + protected + attr_reader :name, :block, :adapter, :override + + def priority + result = 0 + if adapter + result |= 1 + end + if override + result |= 2 + end + result + end + + def priority_except_adapter + priority & 0b111111100 + end + + private + def matches_adapter?(adapter: nil, **) + (self.adapter.nil? || adapter == self.adapter) + end + + def conflicts_with?(other) + same_priority_except_adapter?(other) && + has_adapter_conflict?(other) + end + + def same_priority_except_adapter?(other) + priority_except_adapter == other.priority_except_adapter + end + + def has_adapter_conflict?(other) + (override.nil? && other.adapter) || + (adapter && other.override.nil?) + end + end + + class DecorationRegistration < Registration # :nodoc: + def initialize(options, klass, adapter: nil) + @options = options + @klass = klass + @adapter = adapter + end + + def call(registry, *args, **kwargs) + subtype = registry.lookup(*args, **kwargs.except(*options.keys)) + klass.new(subtype) + end + + def matches?(*args, **kwargs) + matches_adapter?(**kwargs) && matches_options?(**kwargs) + end + + def priority + super | 4 + end + + private + attr_reader :options, :klass + + def matches_options?(**kwargs) + options.all? do |key, value| + kwargs[key] == value + end + end + end + end + + class TypeConflictError < StandardError # :nodoc: + end + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/date.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/date.rb new file mode 100644 index 00000000..8177074a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/date.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + class Date < ActiveModel::Type::Date + include Internal::Timezone + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/date_time.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/date_time.rb new file mode 100644 index 00000000..4acde6b9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/date_time.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + class DateTime < ActiveModel::Type::DateTime + include Internal::Timezone + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/decimal_without_scale.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/decimal_without_scale.rb new file mode 100644 index 00000000..a207940d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/decimal_without_scale.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + class DecimalWithoutScale < ActiveModel::Type::BigInteger # :nodoc: + def type + :decimal + end + + def type_cast_for_schema(value) + value.to_s.inspect + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/hash_lookup_type_map.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/hash_lookup_type_map.rb new file mode 100644 index 00000000..9cf1c6c6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/hash_lookup_type_map.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + class HashLookupTypeMap # :nodoc: + def initialize(parent = nil) + @mapping = {} + @cache = Concurrent::Map.new do |h, key| + h.fetch_or_store(key, Concurrent::Map.new) + end + end + + def lookup(lookup_key, *args) + fetch(lookup_key, *args) { Type.default_value } + end + + def fetch(lookup_key, *args, &block) + @cache[lookup_key].fetch_or_store(args) do + perform_fetch(lookup_key, *args, &block) + end + end + + def register_type(key, value = nil, &block) + raise ::ArgumentError unless value || block + + if block + @mapping[key] = block + else + @mapping[key] = proc { value } + end + @cache.clear + end + + def clear + @mapping.clear + @cache.clear + end + + def alias_type(type, alias_type) + register_type(type) { |_, *args| lookup(alias_type, *args) } + end + + def key?(key) + @mapping.key?(key) + end + + def keys + @mapping.keys + end + + private + def perform_fetch(type, *args, &block) + @mapping.fetch(type, block).call(type, *args) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/internal/timezone.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/internal/timezone.rb new file mode 100644 index 00000000..98a4c5b6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/internal/timezone.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + module Internal + module Timezone + def initialize(timezone: nil, **kwargs) + super(**kwargs) + @timezone = timezone + end + + def is_utc? + default_timezone == :utc + end + + def default_timezone + @timezone || ActiveRecord.default_timezone + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/json.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/json.rb new file mode 100644 index 00000000..3f9ff227 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/json.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + class Json < ActiveModel::Type::Value + include ActiveModel::Type::Helpers::Mutable + + def type + :json + end + + def deserialize(value) + return value unless value.is_a?(::String) + ActiveSupport::JSON.decode(value) rescue nil + end + + def serialize(value) + ActiveSupport::JSON.encode(value) unless value.nil? + end + + def changed_in_place?(raw_old_value, new_value) + deserialize(raw_old_value) != new_value + end + + def accessor + ActiveRecord::Store::StringKeyedHashAccessor + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/serialized.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/serialized.rb new file mode 100644 index 00000000..16f77c02 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/serialized.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + class Serialized < DelegateClass(ActiveModel::Type::Value) # :nodoc: + undef to_yaml if method_defined?(:to_yaml) + + include ActiveModel::Type::Helpers::Mutable + + attr_reader :subtype, :coder + + def initialize(subtype, coder) + @subtype = subtype + @coder = coder + super(subtype) + end + + def deserialize(value) + if default_value?(value) + value + else + coder.load(super) + end + end + + def serialize(value) + return if value.nil? + unless default_value?(value) + super coder.dump(value) + end + end + + define_method(:inspect, Kernel.instance_method(:inspect)) + + def changed_in_place?(raw_old_value, value) + return false if value.nil? + raw_new_value = encoded(value) + raw_old_value.nil? != raw_new_value.nil? || + subtype.changed_in_place?(raw_old_value, raw_new_value) + end + + def accessor + ActiveRecord::Store::IndifferentHashAccessor + end + + def assert_valid_value(value) + if coder.respond_to?(:assert_valid_value) + coder.assert_valid_value(value, action: "serialize") + end + end + + def force_equality?(value) + coder.respond_to?(:object_class) && value.is_a?(coder.object_class) + end + + def serialized? # :nodoc: + true + end + + private + def default_value?(value) + value == coder.load(nil) + end + + def encoded(value) + return if default_value?(value) + payload = coder.dump(value) + if payload && @subtype.binary? + ActiveModel::Type::Binary::Data.new(payload) + else + payload + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/text.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/text.rb new file mode 100644 index 00000000..6d196966 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/text.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + class Text < ActiveModel::Type::String # :nodoc: + def type + :text + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/time.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/time.rb new file mode 100644 index 00000000..6946dc5e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/time.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + class Time < ActiveModel::Type::Time + include Internal::Timezone + + class Value < DelegateClass(::Time) # :nodoc: + end + + def serialize(value) + case value = super + when ::Time + Value.new(value) + else + value + end + end + + def serialize_cast_value(value) # :nodoc: + Value.new(super) if value + end + + private + def cast_value(value) + case value = super + when Value + value.__getobj__ + else + value + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/type_map.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/type_map.rb new file mode 100644 index 00000000..87106d15 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/type_map.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require "concurrent/map" + +module ActiveRecord + module Type + class TypeMap # :nodoc: + def initialize(parent = nil) + @mapping = {} + @parent = parent + @cache = Concurrent::Map.new + end + + def lookup(lookup_key) + fetch(lookup_key) { Type.default_value } + end + + def fetch(lookup_key, &block) + @cache.fetch_or_store(lookup_key) do + perform_fetch(lookup_key, &block) + end + end + + def register_type(key, value = nil, &block) + raise ::ArgumentError unless value || block + + if block + @mapping[key] = block + else + @mapping[key] = proc { value } + end + @cache.clear + end + + def alias_type(key, target_key) + register_type(key) do |sql_type| + metadata = sql_type[/\(.*\)/, 0] + lookup("#{target_key}#{metadata}") + end + end + + protected + def perform_fetch(lookup_key, &block) + matching_pair = @mapping.reverse_each.detect do |key, _| + key === lookup_key + end + + if matching_pair + matching_pair.last.call(lookup_key) + elsif @parent + @parent.perform_fetch(lookup_key, &block) + else + yield lookup_key + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/unsigned_integer.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/unsigned_integer.rb new file mode 100644 index 00000000..535369e6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type/unsigned_integer.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + class UnsignedInteger < ActiveModel::Type::Integer # :nodoc: + private + def max_value + super * 2 + end + + def min_value + 0 + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type_caster.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type_caster.rb new file mode 100644 index 00000000..2e5f45fa --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type_caster.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require "active_record/type_caster/map" +require "active_record/type_caster/connection" + +module ActiveRecord + module TypeCaster # :nodoc: + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type_caster/connection.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type_caster/connection.rb new file mode 100644 index 00000000..fa289185 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type_caster/connection.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module ActiveRecord + module TypeCaster + class Connection # :nodoc: + def initialize(klass, table_name) + @klass = klass + @table_name = table_name + end + + def type_cast_for_database(attr_name, value) + type = type_for_attribute(attr_name) + type.serialize(value) + end + + def type_for_attribute(attr_name) + schema_cache = @klass.schema_cache + + if schema_cache.data_source_exists?(table_name) + column = schema_cache.columns_hash(table_name)[attr_name.to_s] + if column + type = @klass.with_connection { |connection| connection.lookup_cast_type_from_column(column) } + end + end + + type || Type.default_value + end + + private + attr_reader :table_name + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type_caster/map.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type_caster/map.rb new file mode 100644 index 00000000..b3dd7a01 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/type_caster/map.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module ActiveRecord + module TypeCaster + class Map # :nodoc: + def initialize(klass) + @klass = klass + end + + def type_cast_for_database(attr_name, value) + type = type_for_attribute(attr_name) + type.serialize(value) + end + + def type_for_attribute(name) + klass.type_for_attribute(name) + end + + private + attr_reader :klass + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations.rb new file mode 100644 index 00000000..f0072590 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record \RecordInvalid + # + # Raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and + # {ActiveRecord::Base#create!}[rdoc-ref:Persistence::ClassMethods#create!] when the record is invalid. + # Use the #record method to retrieve the record which did not validate. + # + # begin + # complex_operation_that_internally_calls_save! + # rescue ActiveRecord::RecordInvalid => invalid + # puts invalid.record.errors + # end + class RecordInvalid < ActiveRecordError + attr_reader :record + + def initialize(record = nil) + if record + @record = record + errors = @record.errors.full_messages.join(", ") + message = I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", errors: errors, default: :"errors.messages.record_invalid") + else + message = "Record invalid" + end + + super(message) + end + end + + # = Active Record \Validations + # + # Active Record includes the majority of its validations from ActiveModel::Validations. + # + # In Active Record, all validations are performed on save by default. + # Validations accept the :on argument to define the context where + # the validations are active. Active Record will pass either the context of + # :create or :update depending on whether the model is a + # {new_record?}[rdoc-ref:Persistence#new_record?]. + module Validations + extend ActiveSupport::Concern + + # The validation process on save can be skipped by passing validate: false. + # The validation context can be changed by passing context: context. + # The regular {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] method is replaced + # with this when the validations module is mixed in, which it is by default. + def save(**options) + perform_validations(options) ? super : false + end + + # Attempts to save the record just like {ActiveRecord::Base#save}[rdoc-ref:Base#save] but + # will raise an ActiveRecord::RecordInvalid exception instead of returning +false+ if the record is not valid. + def save!(**options) + perform_validations(options) ? super : raise_validation_error + end + + # Runs all the validations within the specified context. Returns +true+ if + # no errors are found, +false+ otherwise. + # + # Aliased as #validate. + # + # If the argument is +false+ (default is +nil+), the context is set to :create if + # {new_record?}[rdoc-ref:Persistence#new_record?] is +true+, and to :update if it is not. + # If the argument is an array of contexts, post.valid?([:create, :update]), the validations are + # run within multiple contexts. + # + # \Validations with no :on option will run no matter the context. \Validations with + # some :on option will only run in the specified context. + def valid?(context = nil) + context ||= default_validation_context + output = super(context) + errors.empty? && output + end + + alias_method :validate, :valid? + + def custom_validation_context? # :nodoc: + validation_context && [:create, :update].exclude?(validation_context) + end + + private + def default_validation_context + new_record? ? :create : :update + end + + def raise_validation_error + raise(RecordInvalid.new(self)) + end + + def perform_validations(options = {}) + options[:validate] == false || valid?(options[:context]) + end + end +end + +require "active_record/validations/associated" +require "active_record/validations/uniqueness" +require "active_record/validations/presence" +require "active_record/validations/absence" +require "active_record/validations/length" +require "active_record/validations/numericality" diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations/absence.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations/absence.rb new file mode 100644 index 00000000..b37457a8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations/absence.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module ActiveRecord + module Validations + class AbsenceValidator < ActiveModel::Validations::AbsenceValidator # :nodoc: + def validate_each(record, attribute, association_or_value) + if record.class._reflect_on_association(attribute) + association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?) + end + super + end + end + + module ClassMethods + # Validates that the specified attributes are not present (as defined by + # Object#present?). If the attribute is an association, the associated object + # is also considered not present if it is marked for destruction. + # + # See ActiveModel::Validations::HelperMethods.validates_absence_of for more information. + def validates_absence_of(*attr_names) + validates_with AbsenceValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations/associated.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations/associated.rb new file mode 100644 index 00000000..f0d1f01c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations/associated.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module ActiveRecord + module Validations + class AssociatedValidator < ActiveModel::EachValidator # :nodoc: + def validate_each(record, attribute, value) + context = record_validation_context_for_association(record) + + if Array(value).reject { |association| valid_object?(association, context) }.any? + record.errors.add(attribute, :invalid, **options.merge(value: value)) + end + end + + private + def valid_object?(record, context) + (record.respond_to?(:marked_for_destruction?) && record.marked_for_destruction?) || record.valid?(context) + end + + def record_validation_context_for_association(record) + record.custom_validation_context? ? record.validation_context : nil + end + end + + module ClassMethods + # Validates whether the associated object or objects are all valid. + # Works with any kind of association. + # + # class Book < ActiveRecord::Base + # has_many :pages + # belongs_to :library + # + # validates_associated :pages, :library + # end + # + # WARNING: This validation must not be used on both ends of an association. + # Doing so will lead to a circular dependency and cause infinite recursion. + # + # NOTE: This validation will not fail if the association hasn't been + # assigned. If you want to ensure that the association is both present and + # guaranteed to be valid, you also need to use + # {validates_presence_of}[rdoc-ref:Validations::ClassMethods#validates_presence_of]. + # + # Configuration options: + # + # * :message - A custom error message (default is: "is invalid"). + # * :on - Specifies the contexts where this validation is active. + # Runs in all validation contexts by default +nil+. You can pass a symbol + # or an array of symbols. (e.g. on: :create or + # on: :custom_validation_context or + # on: [:create, :custom_validation_context]) + # * :if - Specifies a method, proc, or string to call to determine + # if the validation should occur (e.g. if: :allow_validation, + # or if: Proc.new { |user| user.signup_step > 2 }). The method, + # proc or string should return or evaluate to a +true+ or +false+ value. + # * :unless - Specifies a method, proc, or string to call to + # determine if the validation should not occur (e.g. unless: :skip_validation, + # or unless: Proc.new { |user| user.signup_step <= 2 }). The + # method, proc, or string should return or evaluate to a +true+ or +false+ + # value. + def validates_associated(*attr_names) + validates_with AssociatedValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations/length.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations/length.rb new file mode 100644 index 00000000..f47b14ae --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations/length.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module ActiveRecord + module Validations + class LengthValidator < ActiveModel::Validations::LengthValidator # :nodoc: + def validate_each(record, attribute, association_or_value) + if association_or_value.respond_to?(:loaded?) && association_or_value.loaded? + association_or_value = association_or_value.target.reject(&:marked_for_destruction?) + end + super + end + end + + module ClassMethods + # Validates that the specified attributes match the length restrictions supplied. + # If the attribute is an association, records that are marked for destruction are not counted. + # + # See ActiveModel::Validations::HelperMethods.validates_length_of for more information. + def validates_length_of(*attr_names) + validates_with LengthValidator, _merge_attributes(attr_names) + end + + alias_method :validates_size_of, :validates_length_of + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations/numericality.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations/numericality.rb new file mode 100644 index 00000000..1fb98577 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations/numericality.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module ActiveRecord + module Validations + class NumericalityValidator < ActiveModel::Validations::NumericalityValidator # :nodoc: + def validate_each(record, attribute, value, precision: nil, scale: nil) + precision = [column_precision_for(record, attribute) || Float::DIG, Float::DIG].min + scale = column_scale_for(record, attribute) + super(record, attribute, value, precision: precision, scale: scale) + end + + private + def column_precision_for(record, attribute) + record.class.type_for_attribute(attribute.to_s)&.precision + end + + def column_scale_for(record, attribute) + record.class.type_for_attribute(attribute.to_s)&.scale + end + end + + module ClassMethods + # Validates whether the value of the specified attribute is numeric by + # trying to convert it to a float with +Kernel.Float+ (if + # only_integer is +false+) or applying it to the regular + # expression /\A[\+\-]?\d+\z/ (if only_integer is set to + # +true+). +Kernel.Float+ precision defaults to the column's precision + # value or 15. + # + # See ActiveModel::Validations::HelperMethods.validates_numericality_of for more information. + def validates_numericality_of(*attr_names) + validates_with NumericalityValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations/presence.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations/presence.rb new file mode 100644 index 00000000..335792b4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations/presence.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module ActiveRecord + module Validations + class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc: + def validate_each(record, attribute, association_or_value) + if record.class._reflect_on_association(attribute) + association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?) + end + super + end + end + + module ClassMethods + # Validates that the specified attributes are not blank (as defined by + # Object#blank?). If the attribute is an association, the associated object + # is also considered blank if it is marked for destruction. + # + # class Person < ActiveRecord::Base + # has_one :face + # validates_presence_of :face + # end + # + # The face attribute must be in the object and it cannot be blank or marked + # for destruction. + # + # This validator defers to the Active Model validation for presence, adding the + # check to see that an associated object is not marked for destruction. This + # prevents the parent object from validating successfully and saving, which then + # deletes the associated object, thus putting the parent object into an invalid + # state. + # + # See ActiveModel::Validations::HelperMethods.validates_presence_of for + # more information. + # + # NOTE: This validation will not fail while using it with an association + # if the latter was assigned but not valid. If you want to ensure that + # it is both present and valid, you also need to use + # {validates_associated}[rdoc-ref:Validations::ClassMethods#validates_associated]. + def validates_presence_of(*attr_names) + validates_with PresenceValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations/uniqueness.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations/uniqueness.rb new file mode 100644 index 00000000..d661cc40 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/validations/uniqueness.rb @@ -0,0 +1,296 @@ +# frozen_string_literal: true + +module ActiveRecord + module Validations + class UniquenessValidator < ActiveModel::EachValidator # :nodoc: + def initialize(options) + if options[:conditions] && !options[:conditions].respond_to?(:call) + raise ArgumentError, "#{options[:conditions]} was passed as :conditions but is not callable. " \ + "Pass a callable instead: `conditions: -> { where(approved: true) }`" + end + unless Array(options[:scope]).all? { |scope| scope.respond_to?(:to_sym) } + raise ArgumentError, "#{options[:scope]} is not supported format for :scope option. " \ + "Pass a symbol or an array of symbols instead: `scope: :user_id`" + end + super + @klass = options[:class] + @klass = @klass.superclass if @klass.singleton_class? + end + + def validate_each(record, attribute, value) + finder_class = find_finder_class_for(record) + value = map_enum_attribute(finder_class, attribute, value) + + return if record.persisted? && !validation_needed?(finder_class, record, attribute) + + relation = build_relation(finder_class, attribute, value) + if record.persisted? + if finder_class.primary_key + relation = relation.where.not(finder_class.primary_key => [record.id_in_database]) + else + raise UnknownPrimaryKey.new(finder_class, "Cannot validate uniqueness for persisted record without primary key.") + end + end + relation = scope_relation(record, relation) + + if options[:conditions] + conditions = options[:conditions] + + relation = if conditions.arity.zero? + relation.instance_exec(&conditions) + else + relation.instance_exec(record, &conditions) + end + end + + if relation.exists? + error_options = options.except(:case_sensitive, :scope, :conditions) + error_options[:value] = value + + record.errors.add(attribute, :taken, **error_options) + end + end + + private + # The check for an existing value should be run from a class that + # isn't abstract. This means working down from the current class + # (self), to the first non-abstract class. + def find_finder_class_for(record) + current_class = record.class + found_class = nil + loop do + found_class = current_class unless current_class.abstract_class? + break if current_class == @klass + current_class = current_class.superclass + end + + found_class + end + + def validation_needed?(klass, record, attribute) + return true if options[:conditions] || options.key?(:case_sensitive) + + scope = Array(options[:scope]) + attributes = scope + [attribute] + attributes = resolve_attributes(record, attributes) + + return true if attributes.any? { |attr| record.attribute_changed?(attr) || + record.read_attribute(attr).nil? } + + !covered_by_unique_index?(klass, record, attribute, scope) + end + + def covered_by_unique_index?(klass, record, attribute, scope) + @covered ||= self.attributes.map(&:to_s).select do |attr| + attributes = scope + [attr] + attributes = resolve_attributes(record, attributes) + + klass.schema_cache.indexes(klass.table_name).any? do |index| + index.unique && + index.where.nil? && + (Array(index.columns) - attributes).empty? + end + end + + @covered.include?(attribute.to_s) + end + + def resolve_attributes(record, attributes) + attributes.flat_map do |attribute| + reflection = record.class._reflect_on_association(attribute) + + if reflection.nil? + attribute.to_s + elsif reflection.polymorphic? + [reflection.foreign_key, reflection.foreign_type] + else + reflection.foreign_key + end + end + end + + def build_relation(klass, attribute, value) + relation = klass.unscoped + # TODO: Add case-sensitive / case-insensitive operators to Arel + # to no longer need to checkout a connection here. + comparison = klass.with_connection do |connection| + relation.bind_attribute(attribute, value) do |attr, bind| + return relation.none! if bind.unboundable? + + if !options.key?(:case_sensitive) || bind.nil? + connection.default_uniqueness_comparison(attr, bind) + elsif options[:case_sensitive] + connection.case_sensitive_comparison(attr, bind) + else + # will use SQL LOWER function before comparison, unless it detects a case insensitive collation + connection.case_insensitive_comparison(attr, bind) + end + end + end + + relation.where!(comparison) + end + + def scope_relation(record, relation) + Array(options[:scope]).each do |scope_item| + scope_value = if record.class._reflect_on_association(scope_item) + record.association(scope_item).reader + else + record.read_attribute(scope_item) + end + relation = relation.where(scope_item => scope_value) + end + + relation + end + + def map_enum_attribute(klass, attribute, value) + mapping = klass.defined_enums[attribute.to_s] + value = mapping[value] if value && mapping + value + end + end + + module ClassMethods + # Validates whether the value of the specified attributes are unique + # across the system. Useful for making sure that only one user + # can be named "davidhh". + # + # class Person < ActiveRecord::Base + # validates_uniqueness_of :user_name + # end + # + # It can also validate whether the value of the specified attributes are + # unique based on a :scope parameter: + # + # class Person < ActiveRecord::Base + # validates_uniqueness_of :user_name, scope: :account_id + # end + # + # Or even multiple scope parameters. For example, making sure that a + # teacher can only be on the schedule once per semester for a particular + # class. + # + # class TeacherSchedule < ActiveRecord::Base + # validates_uniqueness_of :teacher_id, scope: [:semester_id, :class_id] + # end + # + # It is also possible to limit the uniqueness constraint to a set of + # records matching certain conditions. In this example archived articles + # are not being taken into consideration when validating uniqueness + # of the title attribute: + # + # class Article < ActiveRecord::Base + # validates_uniqueness_of :title, conditions: -> { where.not(status: 'archived') } + # end + # + # To build conditions based on the record's state, define the conditions + # callable with a parameter, which will be the record itself. This + # example validates the title is unique for the year of publication: + # + # class Article < ActiveRecord::Base + # validates_uniqueness_of :title, conditions: ->(article) { + # published_at = article.published_at + # where(published_at: published_at.beginning_of_year..published_at.end_of_year) + # } + # end + # + # When the record is created, a check is performed to make sure that no + # record exists in the database with the given value for the specified + # attribute (that maps to a column). When the record is updated, + # the same check is made but disregarding the record itself. + # + # Configuration options: + # + # * :message - Specifies a custom error message (default is: + # "has already been taken"). + # * :scope - One or more columns by which to limit the scope of + # the uniqueness constraint. + # * :conditions - Specify the conditions to be included as a + # WHERE SQL fragment to limit the uniqueness constraint lookup + # (e.g. conditions: -> { where(status: 'active') }). + # * :case_sensitive - Looks for an exact match. Ignored by + # non-text columns. The default behavior respects the default database collation. + # * :allow_nil - If set to +true+, skips this validation if the + # attribute is +nil+ (default is +false+). + # * :allow_blank - If set to +true+, skips this validation if the + # attribute is blank (default is +false+). + # * :if - Specifies a method, proc, or string to call to determine + # if the validation should occur (e.g. if: :allow_validation, + # or if: Proc.new { |user| user.signup_step > 2 }). The method, + # proc or string should return or evaluate to a +true+ or +false+ value. + # * :unless - Specifies a method, proc, or string to call to + # determine if the validation should not occur (e.g. unless: :skip_validation, + # or unless: Proc.new { |user| user.signup_step <= 2 }). The + # method, proc, or string should return or evaluate to a +true+ or +false+ + # value. + # + # === Concurrency and integrity + # + # Using this validation method in conjunction with + # {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] + # does not guarantee the absence of duplicate record insertions, because + # uniqueness checks on the application level are inherently prone to race + # conditions. For example, suppose that two users try to post a Comment at + # the same time, and a Comment's title must be unique. At the database-level, + # the actions performed by these users could be interleaved in the following manner: + # + # User 1 | User 2 + # ------------------------------------+-------------------------------------- + # # User 1 checks whether there's | + # # already a comment with the title | + # # 'My Post'. This is not the case. | + # SELECT * FROM comments | + # WHERE title = 'My Post' | + # | + # | # User 2 does the same thing and also + # | # infers that their title is unique. + # | SELECT * FROM comments + # | WHERE title = 'My Post' + # | + # # User 1 inserts their comment. | + # INSERT INTO comments | + # (title, content) VALUES | + # ('My Post', 'hi!') | + # | + # | # User 2 does the same thing. + # | INSERT INTO comments + # | (title, content) VALUES + # | ('My Post', 'hello!') + # | + # | # ^^^^^^ + # | # Boom! We now have a duplicate + # | # title! + # + # The best way to work around this problem is to add a unique index to the database table using + # {connection.add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index]. + # In the rare case that a race condition occurs, the database will guarantee + # the field's uniqueness. + # + # When the database catches such a duplicate insertion, + # {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] will raise an ActiveRecord::StatementInvalid + # exception. You can either choose to let this error propagate (which + # will result in the default \Rails exception page being shown), or you + # can catch it and restart the transaction (e.g. by telling the user + # that the title already exists, and asking them to re-enter the title). + # This technique is also known as + # {optimistic concurrency control}[https://en.wikipedia.org/wiki/Optimistic_concurrency_control]. + # + # The bundled ActiveRecord::ConnectionAdapters distinguish unique index + # constraint errors from other types of database errors by throwing an + # ActiveRecord::RecordNotUnique exception. For other adapters you will + # have to parse the (database-specific) exception message to detect such + # a case. + # + # The following bundled adapters throw the ActiveRecord::RecordNotUnique exception: + # + # * ActiveRecord::ConnectionAdapters::Mysql2Adapter. + # * ActiveRecord::ConnectionAdapters::TrilogyAdapter. + # * ActiveRecord::ConnectionAdapters::SQLite3Adapter. + # * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter. + def validates_uniqueness_of(*attr_names) + validates_with UniquenessValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/version.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/version.rb new file mode 100644 index 00000000..96e981c0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/active_record/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative "gem_version" + +module ActiveRecord + # Returns the currently loaded version of Active Record as a +Gem::Version+. + def self.version + gem_version + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel.rb new file mode 100644 index 00000000..738e80df --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require "arel/errors" + +require "arel/crud" +require "arel/factory_methods" + +require "arel/expressions" +require "arel/predications" +require "arel/filter_predications" +require "arel/window_predications" +require "arel/math" +require "arel/alias_predication" +require "arel/order_predications" +require "arel/table" +require "arel/attributes/attribute" + +require "arel/visitors" +require "arel/collectors/sql_string" + +require "arel/tree_manager" +require "arel/insert_manager" +require "arel/select_manager" +require "arel/update_manager" +require "arel/delete_manager" +require "arel/nodes" + +module Arel + VERSION = "10.0.0" + + # Wrap a known-safe SQL string for passing to query methods, e.g. + # + # Post.order(Arel.sql("REPLACE(title, 'misc', 'zzzz') asc")).pluck(:id) + # + # Great caution should be taken to avoid SQL injection vulnerabilities. + # This method should not be used with unsafe values such as request + # parameters or model attributes. + # + # Take a look at the {security guide}[https://guides.rubyonrails.org/security.html#sql-injection] + # for more information. + # + # To construct a more complex query fragment, including the possible + # use of user-provided values, the +sql_string+ may contain ? and + # +:key+ placeholders, corresponding to the additional arguments. Note + # that this behavior only applies when bind value parameters are + # supplied in the call; without them, the placeholder tokens have no + # special meaning, and will be passed through to the query as-is. + # + # The +:retryable+ option can be used to mark the SQL as safe to retry. + # Use this option only if the SQL is idempotent, as it could be executed + # more than once. + def self.sql(sql_string, *positional_binds, retryable: false, **named_binds) + if positional_binds.empty? && named_binds.empty? + Arel::Nodes::SqlLiteral.new(sql_string, retryable: retryable) + else + Arel::Nodes::BoundSqlLiteral.new sql_string, positional_binds, named_binds + end + end + + def self.star # :nodoc: + sql("*", retryable: true) + end + + def self.arel_node?(value) # :nodoc: + value.is_a?(Arel::Nodes::Node) || value.is_a?(Arel::Attribute) || value.is_a?(Arel::Nodes::SqlLiteral) + end + + def self.fetch_attribute(value, &block) # :nodoc: + unless String === value + value.fetch_attribute(&block) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/alias_predication.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/alias_predication.rb new file mode 100644 index 00000000..1f7af26c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/alias_predication.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module AliasPredication + def as(other) + Nodes::As.new self, Nodes::SqlLiteral.new(other, retryable: true) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/attributes/attribute.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/attributes/attribute.rb new file mode 100644 index 00000000..dd910e19 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/attributes/attribute.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Attributes + class Attribute < Struct.new :relation, :name + include Arel::Expressions + include Arel::Predications + include Arel::AliasPredication + include Arel::OrderPredications + include Arel::Math + + def type_caster + relation.type_for_attribute(name) + end + + ### + # Create a node for lowering this attribute + def lower + relation.lower self + end + + def type_cast_for_database(value) + relation.type_cast_for_database(name, value) + end + + def able_to_type_cast? + relation.able_to_type_cast? + end + end + end + + Attribute = Attributes::Attribute +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/collectors/bind.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/collectors/bind.rb new file mode 100644 index 00000000..e900bfd8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/collectors/bind.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Collectors + class Bind + attr_accessor :retryable + + def initialize + @binds = [] + end + + def <<(str) + self + end + + def add_bind(bind, &) + @binds << bind + self + end + + def add_binds(binds, proc_for_binds = nil, &) + @binds.concat proc_for_binds ? binds.map(&proc_for_binds) : binds + self + end + + def value + @binds + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/collectors/composite.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/collectors/composite.rb new file mode 100644 index 00000000..f31634c7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/collectors/composite.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Collectors + class Composite + attr_accessor :preparable + attr_reader :retryable + + def initialize(left, right) + @left = left + @right = right + end + + def retryable=(retryable) + left.retryable = retryable + right.retryable = retryable + @retryable = retryable + end + + def <<(str) + left << str + right << str + self + end + + def add_bind(bind, &block) + left.add_bind bind, &block + right.add_bind bind, &block + self + end + + def add_binds(binds, proc_for_binds = nil, &block) + left.add_binds(binds, proc_for_binds, &block) + right.add_binds(binds, proc_for_binds, &block) + self + end + + def value + [left.value, right.value] + end + + private + attr_reader :left, :right + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/collectors/plain_string.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/collectors/plain_string.rb new file mode 100644 index 00000000..c0e9fff3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/collectors/plain_string.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Collectors + class PlainString + def initialize + @str = +"" + end + + def value + @str + end + + def <<(str) + @str << str + self + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/collectors/sql_string.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/collectors/sql_string.rb new file mode 100644 index 00000000..a2af02b1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/collectors/sql_string.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "arel/collectors/plain_string" + +module Arel # :nodoc: all + module Collectors + class SQLString < PlainString + attr_accessor :preparable, :retryable + + def initialize(*) + super + @bind_index = 1 + end + + def add_bind(bind, &) + self << yield(@bind_index) + @bind_index += 1 + self + end + + def add_binds(binds, proc_for_binds = nil, &block) + self << (@bind_index...@bind_index += binds.size).map(&block).join(", ") + self + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/collectors/substitute_binds.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/collectors/substitute_binds.rb new file mode 100644 index 00000000..d3a27215 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/collectors/substitute_binds.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Collectors + class SubstituteBinds + attr_accessor :preparable, :retryable + + def initialize(quoter, delegate_collector) + @quoter = quoter + @delegate = delegate_collector + end + + def <<(str) + delegate << str + self + end + + def add_bind(bind, &) + bind = bind.value_for_database if bind.respond_to?(:value_for_database) + self << quoter.quote(bind) + end + + def add_binds(binds, proc_for_binds = nil, &) + self << binds.map { |bind| quoter.quote(bind) }.join(", ") + end + + def value + delegate.value + end + + private + attr_reader :quoter, :delegate + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/crud.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/crud.rb new file mode 100644 index 00000000..18240c8b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/crud.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + ### + # FIXME hopefully we can remove this + module Crud + def compile_insert(values) + im = create_insert + im.insert values + im + end + + def create_insert + InsertManager.new + end + + def compile_update( + values, + key = nil, + having_clause = nil, + group_values_columns = [] + ) + um = UpdateManager.new(source) + um.set(values) + um.take(limit) + um.offset(offset) + um.order(*orders) + um.wheres = constraints + um.key = key + + um.group(group_values_columns) unless group_values_columns.empty? + um.having(having_clause) unless having_clause.nil? + um + end + + def compile_delete(key = nil, having_clause = nil, group_values_columns = []) + dm = DeleteManager.new(source) + dm.take(limit) + dm.offset(offset) + dm.order(*orders) + dm.wheres = constraints + dm.key = key + dm.group(group_values_columns) unless group_values_columns.empty? + dm.having(having_clause) unless having_clause.nil? + dm + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/delete_manager.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/delete_manager.rb new file mode 100644 index 00000000..bf7f26f5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/delete_manager.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + class DeleteManager < Arel::TreeManager + include TreeManager::StatementMethods + + def initialize(table = nil) + @ast = Nodes::DeleteStatement.new(table) + end + + def from(relation) + @ast.relation = relation + self + end + + def group(columns) + columns.each do |column| + column = Nodes::SqlLiteral.new(column) if String === column + column = Nodes::SqlLiteral.new(column.to_s) if Symbol === column + + @ast.groups.push Nodes::Group.new column + end + + self + end + + def having(expr) + @ast.havings << expr + self + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/errors.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/errors.rb new file mode 100644 index 00000000..2ad23993 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/errors.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + class ArelError < StandardError + end + + class EmptyJoinError < ArelError + end + + class BindError < ArelError + def initialize(message, sql = nil) + if sql + super("#{message} in: #{sql.inspect}") + else + super(message) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/expressions.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/expressions.rb new file mode 100644 index 00000000..da8afb33 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/expressions.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Expressions + def count(distinct = false) + Nodes::Count.new [self], distinct + end + + def sum + Nodes::Sum.new [self] + end + + def maximum + Nodes::Max.new [self] + end + + def minimum + Nodes::Min.new [self] + end + + def average + Nodes::Avg.new [self] + end + + def extract(field) + Nodes::Extract.new [self], field + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/factory_methods.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/factory_methods.rb new file mode 100644 index 00000000..83122723 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/factory_methods.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + ### + # Methods for creating various nodes + module FactoryMethods + def create_true + Arel::Nodes::True.new + end + + def create_false + Arel::Nodes::False.new + end + + def create_table_alias(relation, name) + Nodes::TableAlias.new(relation, name) + end + + def create_join(to, constraint = nil, klass = Nodes::InnerJoin) + klass.new(to, constraint) + end + + def create_string_join(to) + create_join to, nil, Nodes::StringJoin + end + + def create_and(clauses) + Nodes::And.new clauses + end + + def create_on(expr) + Nodes::On.new expr + end + + def grouping(expr) + Nodes::Grouping.new expr + end + + ### + # Create a LOWER() function + def lower(column) + Nodes::NamedFunction.new "LOWER", [Nodes.build_quoted(column)] + end + + def coalesce(*exprs) + Nodes::NamedFunction.new "COALESCE", exprs + end + + def cast(name, type) + Nodes::NamedFunction.new "CAST", [name.as(type)] + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/filter_predications.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/filter_predications.rb new file mode 100644 index 00000000..959f4c6e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/filter_predications.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module FilterPredications + def filter(expr) + Nodes::Filter.new(self, expr) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/insert_manager.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/insert_manager.rb new file mode 100644 index 00000000..2bf57b7d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/insert_manager.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + class InsertManager < Arel::TreeManager + def initialize(table = nil) + @ast = Nodes::InsertStatement.new(table) + end + + def into(table) + @ast.relation = table + self + end + + def columns; @ast.columns end + def values=(val); @ast.values = val; end + + def select(select) + @ast.select = select + end + + def insert(fields) + return if fields.empty? + + if String === fields + @ast.values = Nodes::SqlLiteral.new(fields) + else + @ast.relation ||= fields.first.first.relation + + values = [] + + fields.each do |column, value| + @ast.columns << column + values << value + end + @ast.values = create_values(values) + end + self + end + + def create_values(values) + Nodes::ValuesList.new([values]) + end + + def create_values_list(rows) + Nodes::ValuesList.new(rows) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/math.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/math.rb new file mode 100644 index 00000000..2359f131 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/math.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Math + def *(other) + Arel::Nodes::Multiplication.new(self, other) + end + + def +(other) + Arel::Nodes::Grouping.new(Arel::Nodes::Addition.new(self, other)) + end + + def -(other) + Arel::Nodes::Grouping.new(Arel::Nodes::Subtraction.new(self, other)) + end + + def /(other) + Arel::Nodes::Division.new(self, other) + end + + def &(other) + Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseAnd.new(self, other)) + end + + def |(other) + Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseOr.new(self, other)) + end + + def ^(other) + Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseXor.new(self, other)) + end + + def <<(other) + Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseShiftLeft.new(self, other)) + end + + def >>(other) + Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseShiftRight.new(self, other)) + end + + def ~@ + Arel::Nodes::BitwiseNot.new(self) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes.rb new file mode 100644 index 00000000..417ad548 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +# node +require "arel/nodes/node" +require "arel/nodes/node_expression" +require "arel/nodes/select_statement" +require "arel/nodes/select_core" +require "arel/nodes/insert_statement" +require "arel/nodes/update_statement" +require "arel/nodes/bind_param" +require "arel/nodes/fragments" + +# terminal + +require "arel/nodes/terminal" +require "arel/nodes/true" +require "arel/nodes/false" + +# unary +require "arel/nodes/unary" +require "arel/nodes/grouping" +require "arel/nodes/homogeneous_in" +require "arel/nodes/ordering" +require "arel/nodes/ascending" +require "arel/nodes/descending" +require "arel/nodes/unqualified_column" +require "arel/nodes/with" + +# binary +require "arel/nodes/binary" +require "arel/nodes/equality" +require "arel/nodes/filter" +require "arel/nodes/in" +require "arel/nodes/join_source" +require "arel/nodes/delete_statement" +require "arel/nodes/table_alias" +require "arel/nodes/infix_operation" +require "arel/nodes/unary_operation" +require "arel/nodes/over" +require "arel/nodes/matches" +require "arel/nodes/regexp" +require "arel/nodes/cte" + +# nary (And and Or) +require "arel/nodes/nary" + +# function +# FIXME: Function + Alias can be rewritten as a Function and Alias node. +# We should make Function a Unary node and deprecate the use of "aliaz" +require "arel/nodes/function" +require "arel/nodes/count" +require "arel/nodes/extract" +require "arel/nodes/values_list" +require "arel/nodes/named_function" + +# windows +require "arel/nodes/window" + +# conditional expressions +require "arel/nodes/case" + +# joins +require "arel/nodes/full_outer_join" +require "arel/nodes/inner_join" +require "arel/nodes/outer_join" +require "arel/nodes/right_outer_join" +require "arel/nodes/string_join" +require "arel/nodes/leading_join" + +require "arel/nodes/comment" + +require "arel/nodes/sql_literal" +require "arel/nodes/bound_sql_literal" + +require "arel/nodes/casted" diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/ascending.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/ascending.rb new file mode 100644 index 00000000..8b617f4d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/ascending.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Ascending < Ordering + def reverse + Descending.new(expr) + end + + def direction + :asc + end + + def ascending? + true + end + + def descending? + false + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/binary.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/binary.rb new file mode 100644 index 00000000..64668485 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/binary.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Binary < Arel::Nodes::NodeExpression + attr_accessor :left, :right + + def initialize(left, right) + super() + @left = left + @right = right + end + + def initialize_copy(other) + super + @left = @left.clone if @left + @right = @right.clone if @right + end + + def hash + [self.class, @left, @right].hash + end + + def eql?(other) + self.class == other.class && + self.left == other.left && + self.right == other.right + end + alias :== :eql? + end + + module FetchAttribute + def fetch_attribute(&) + if left.is_a?(Arel::Attributes::Attribute) + yield left + elsif right.is_a?(Arel::Attributes::Attribute) + yield right + end + end + end + + class As < Binary + def to_cte + Arel::Nodes::Cte.new(left.name, right) + end + end + + class Between < Binary; include FetchAttribute; end + + class GreaterThan < Binary + include FetchAttribute + + def invert + Arel::Nodes::LessThanOrEqual.new(left, right) + end + end + + class GreaterThanOrEqual < Binary + include FetchAttribute + + def invert + Arel::Nodes::LessThan.new(left, right) + end + end + + class LessThan < Binary + include FetchAttribute + + def invert + Arel::Nodes::GreaterThanOrEqual.new(left, right) + end + end + + class LessThanOrEqual < Binary + include FetchAttribute + + def invert + Arel::Nodes::GreaterThan.new(left, right) + end + end + + class IsDistinctFrom < Binary + include FetchAttribute + + def invert + Arel::Nodes::IsNotDistinctFrom.new(left, right) + end + end + + class IsNotDistinctFrom < Binary + include FetchAttribute + + def invert + Arel::Nodes::IsDistinctFrom.new(left, right) + end + end + + class NotEqual < Binary + include FetchAttribute + + def invert + Arel::Nodes::Equality.new(left, right) + end + end + + class NotIn < Binary + include FetchAttribute + + def invert + Arel::Nodes::In.new(left, right) + end + end + + %w{ + Assignment + Join + Union + UnionAll + Intersect + Except + }.each do |name| + const_set name, Class.new(Binary) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/bind_param.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/bind_param.rb new file mode 100644 index 00000000..1d900eeb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/bind_param.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class BindParam < Node + attr_reader :value + + def initialize(value) + @value = value + super() + end + + def hash + [self.class, self.value].hash + end + + def eql?(other) + other.is_a?(BindParam) && + value == other.value + end + alias :== :eql? + + def nil? + value.nil? + end + + def value_before_type_cast + if value.respond_to?(:value_before_type_cast) + value.value_before_type_cast + else + value + end + end + + def infinite? + value.respond_to?(:infinite?) && value.infinite? + end + + def unboundable? + value.respond_to?(:unboundable?) && value.unboundable? + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/bound_sql_literal.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/bound_sql_literal.rb new file mode 100644 index 00000000..989802d3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/bound_sql_literal.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class BoundSqlLiteral < NodeExpression + attr_reader :sql_with_placeholders, :positional_binds, :named_binds + + def initialize(sql_with_placeholders, positional_binds, named_binds) + has_positional = !(positional_binds.nil? || positional_binds.empty?) + has_named = !(named_binds.nil? || named_binds.empty?) + + if has_positional + if has_named + raise BindError.new("cannot mix positional and named binds", sql_with_placeholders) + end + if positional_binds.size != (expected = sql_with_placeholders.count("?")) + raise BindError.new("wrong number of bind variables (#{positional_binds.size} for #{expected})", sql_with_placeholders) + end + elsif has_named + tokens_in_string = sql_with_placeholders.scan(/:(?" + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/case.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/case.rb new file mode 100644 index 00000000..1c4b727b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/case.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Case < Arel::Nodes::NodeExpression + attr_accessor :case, :conditions, :default + + def initialize(expression = nil, default = nil) + @case = expression + @conditions = [] + @default = default + end + + def when(condition, expression = nil) + @conditions << When.new(Nodes.build_quoted(condition), expression) + self + end + + def then(expression) + @conditions.last.right = Nodes.build_quoted(expression) + self + end + + def else(expression) + @default = Else.new Nodes.build_quoted(expression) + self + end + + def initialize_copy(other) + super + @case = @case.clone if @case + @conditions = @conditions.map { |x| x.clone } + @default = @default.clone if @default + end + + def hash + [@case, @conditions, @default].hash + end + + def eql?(other) + self.class == other.class && + self.case == other.case && + self.conditions == other.conditions && + self.default == other.default + end + alias :== :eql? + end + + class When < Binary # :nodoc: + end + + class Else < Unary # :nodoc: + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/casted.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/casted.rb new file mode 100644 index 00000000..bd389f39 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/casted.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Casted < Arel::Nodes::NodeExpression # :nodoc: + attr_reader :value, :attribute + alias :value_before_type_cast :value + + def initialize(value, attribute) + @value = value + @attribute = attribute + super() + end + + def nil?; value.nil?; end + + def value_for_database + if attribute.able_to_type_cast? + attribute.type_cast_for_database(value) + else + value + end + end + + def hash + [self.class, value, attribute].hash + end + + def eql?(other) + self.class == other.class && + self.value == other.value && + self.attribute == other.attribute + end + alias :== :eql? + end + + class Quoted < Arel::Nodes::Unary # :nodoc: + alias :value_for_database :value + alias :value_before_type_cast :value + + def nil?; value.nil?; end + + def infinite? + value.respond_to?(:infinite?) && value.infinite? + end + end + + def self.build_quoted(other, attribute = nil) + case other + when Arel::Nodes::Node, Arel::Attributes::Attribute, Arel::Table, Arel::SelectManager, Arel::Nodes::SqlLiteral, ActiveModel::Attribute + other + else + case attribute + when Arel::Attributes::Attribute + Casted.new other, attribute + else + Quoted.new other + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/comment.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/comment.rb new file mode 100644 index 00000000..237ff27e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/comment.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Comment < Arel::Nodes::Node + attr_reader :values + + def initialize(values) + super() + @values = values + end + + def initialize_copy(other) + super + @values = @values.clone + end + + def hash + [@values].hash + end + + def eql?(other) + self.class == other.class && + self.values == other.values + end + alias :== :eql? + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/count.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/count.rb new file mode 100644 index 00000000..88046463 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/count.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Count < Arel::Nodes::Function + def initialize(expr, distinct = false, aliaz = nil) + super(expr, aliaz) + @distinct = distinct + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/cte.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/cte.rb new file mode 100644 index 00000000..f94247a7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/cte.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Cte < Arel::Nodes::Binary + alias :name :left + alias :relation :right + attr_reader :materialized + + def initialize(name, relation, materialized: nil) + super(name, relation) + @materialized = materialized + end + + def hash + [name, relation, materialized].hash + end + + def eql?(other) + self.class == other.class && + self.name == other.name && + self.relation == other.relation && + self.materialized == other.materialized + end + alias :== :eql? + + def to_cte + self + end + + def to_table + Arel::Table.new(name) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/delete_statement.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/delete_statement.rb new file mode 100644 index 00000000..a9a1babb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/delete_statement.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class DeleteStatement < Arel::Nodes::Node + attr_accessor :relation, :wheres, :groups, :havings, :orders, :limit, :offset, :key + + def initialize(relation = nil, wheres = []) + super() + @relation = relation + @wheres = wheres + @groups = [] + @havings = [] + @orders = [] + @limit = nil + @offset = nil + @key = nil + end + + def initialize_copy(other) + super + @relation = @relation.clone if @relation + @wheres = @wheres.clone if @wheres + end + + def hash + [self.class, @relation, @wheres, @orders, @limit, @offset, @key].hash + end + + def eql?(other) + self.class == other.class && + self.relation == other.relation && + self.wheres == other.wheres && + self.orders == other.orders && + self.groups == other.groups && + self.havings == other.havings && + self.limit == other.limit && + self.offset == other.offset && + self.key == other.key + end + alias :== :eql? + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/descending.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/descending.rb new file mode 100644 index 00000000..f3f6992c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/descending.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Descending < Ordering + def reverse + Ascending.new(expr) + end + + def direction + :desc + end + + def ascending? + false + end + + def descending? + true + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/equality.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/equality.rb new file mode 100644 index 00000000..61041197 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/equality.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Equality < Arel::Nodes::Binary + include FetchAttribute + + def equality?; true; end + + def invert + Arel::Nodes::NotEqual.new(left, right) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/extract.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/extract.rb new file mode 100644 index 00000000..5799ee9b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/extract.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Extract < Arel::Nodes::Unary + attr_accessor :field + + def initialize(expr, field) + super(expr) + @field = field + end + + def hash + super ^ @field.hash + end + + def eql?(other) + super && + self.field == other.field + end + alias :== :eql? + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/false.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/false.rb new file mode 100644 index 00000000..1e5bf04b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/false.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class False < Arel::Nodes::NodeExpression + def hash + self.class.hash + end + + def eql?(other) + self.class == other.class + end + alias :== :eql? + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/filter.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/filter.rb new file mode 100644 index 00000000..e3f5ec68 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/filter.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Filter < Binary + include Arel::WindowPredications + include Arel::AliasPredication + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/fragments.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/fragments.rb new file mode 100644 index 00000000..8d82aea4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/fragments.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Fragments < Arel::Nodes::Node + attr_reader :values + + def initialize(values = []) + super() + @values = values + end + + def initialize_copy(other) + super + @values = @values.clone + end + + def hash + [@values].hash + end + + def +(other) + raise ArgumentError, "Expected Arel node" unless Arel.arel_node?(other) + + self.class.new([*@values, other]) + end + + def eql?(other) + self.class == other.class && + self.values == other.values + end + alias :== :eql? + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/full_outer_join.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/full_outer_join.rb new file mode 100644 index 00000000..91bb81f2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/full_outer_join.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class FullOuterJoin < Arel::Nodes::Join + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/function.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/function.rb new file mode 100644 index 00000000..49dbb0ee --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/function.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Function < Arel::Nodes::NodeExpression + include Arel::WindowPredications + include Arel::FilterPredications + attr_accessor :expressions, :alias, :distinct + + def initialize(expr, aliaz = nil) + super() + @expressions = expr + @alias = aliaz && SqlLiteral.new(aliaz) + @distinct = false + end + + def as(aliaz) + self.alias = SqlLiteral.new(aliaz) + self + end + + def hash + [@expressions, @alias, @distinct].hash + end + + def eql?(other) + self.class == other.class && + self.expressions == other.expressions && + self.alias == other.alias && + self.distinct == other.distinct + end + alias :== :eql? + end + + %w{ + Sum + Exists + Max + Min + Avg + }.each do |name| + const_set(name, Class.new(Function)) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/grouping.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/grouping.rb new file mode 100644 index 00000000..e01d97ff --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/grouping.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Grouping < Unary + def fetch_attribute(&block) + expr.fetch_attribute(&block) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/homogeneous_in.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/homogeneous_in.rb new file mode 100644 index 00000000..c67d9acf --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/homogeneous_in.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class HomogeneousIn < Node + attr_reader :attribute, :values, :type + + def initialize(values, attribute, type) + @values = values + @attribute = attribute + @type = type + end + + def hash + ivars.hash + end + + def eql?(other) + super || (self.class == other.class && self.ivars == other.ivars) + end + alias :== :eql? + + def equality? + type == :in + end + + def invert + Arel::Nodes::HomogeneousIn.new(values, attribute, type == :in ? :notin : :in) + end + + def left + attribute + end + + def right + attribute.quoted_array(values) + end + + def casted_values + type = attribute.type_caster + + casted_values = values.map do |raw_value| + type.serialize(raw_value) if type.serializable?(raw_value) + end + + casted_values.compact! + casted_values + end + + def proc_for_binds + -> value { ActiveModel::Attribute.with_cast_value(attribute.name, value, ActiveModel::Type.default_value) } + end + + def fetch_attribute(&block) + if attribute + yield attribute + else + expr.fetch_attribute(&block) + end + end + + protected + def ivars + [@attribute, @values, @type] + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/in.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/in.rb new file mode 100644 index 00000000..2154bc6e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/in.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class In < Arel::Nodes::Binary + include FetchAttribute + + def equality?; true; end + + def invert + Arel::Nodes::NotIn.new(left, right) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/infix_operation.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/infix_operation.rb new file mode 100644 index 00000000..462870d1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/infix_operation.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class InfixOperation < Binary + include Arel::Expressions + include Arel::Predications + include Arel::OrderPredications + include Arel::AliasPredication + include Arel::Math + + attr_reader :operator + + def initialize(operator, left, right) + super(left, right) + @operator = operator + end + end + + class Multiplication < InfixOperation + def initialize(left, right) + super(:*, left, right) + end + end + + class Division < InfixOperation + def initialize(left, right) + super(:/, left, right) + end + end + + class Addition < InfixOperation + def initialize(left, right) + super(:+, left, right) + end + end + + class Subtraction < InfixOperation + def initialize(left, right) + super(:-, left, right) + end + end + + class Concat < InfixOperation + def initialize(left, right) + super(:"||", left, right) + end + end + + class Contains < InfixOperation + def initialize(left, right) + super(:"@>", left, right) + end + end + + class Overlaps < InfixOperation + def initialize(left, right) + super(:"&&", left, right) + end + end + + class BitwiseAnd < InfixOperation + def initialize(left, right) + super(:&, left, right) + end + end + + class BitwiseOr < InfixOperation + def initialize(left, right) + super(:|, left, right) + end + end + + class BitwiseXor < InfixOperation + def initialize(left, right) + super(:^, left, right) + end + end + + class BitwiseShiftLeft < InfixOperation + def initialize(left, right) + super(:<<, left, right) + end + end + + class BitwiseShiftRight < InfixOperation + def initialize(left, right) + super(:>>, left, right) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/inner_join.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/inner_join.rb new file mode 100644 index 00000000..519fafad --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/inner_join.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class InnerJoin < Arel::Nodes::Join + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/insert_statement.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/insert_statement.rb new file mode 100644 index 00000000..fdfc179e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/insert_statement.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class InsertStatement < Arel::Nodes::Node + attr_accessor :relation, :columns, :values, :select + + def initialize(relation = nil) + super() + @relation = relation + @columns = [] + @values = nil + @select = nil + end + + def initialize_copy(other) + super + @columns = @columns.clone + @values = @values.clone if @values + @select = @select.clone if @select + end + + def hash + [@relation, @columns, @values, @select].hash + end + + def eql?(other) + self.class == other.class && + self.relation == other.relation && + self.columns == other.columns && + self.select == other.select && + self.values == other.values + end + alias :== :eql? + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/join_source.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/join_source.rb new file mode 100644 index 00000000..d65a9e66 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/join_source.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + ### + # Class that represents a join source + # + # https://www.sqlite.org/syntaxdiagrams.html#join-source + + class JoinSource < Arel::Nodes::Binary + def initialize(single_source, joinop = []) + super + end + + def empty? + !left && right.empty? + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/leading_join.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/leading_join.rb new file mode 100644 index 00000000..339cf12f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/leading_join.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class LeadingJoin < Arel::Nodes::InnerJoin + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/matches.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/matches.rb new file mode 100644 index 00000000..fd5734f4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/matches.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Matches < Binary + attr_reader :escape + attr_accessor :case_sensitive + + def initialize(left, right, escape = nil, case_sensitive = false) + super(left, right) + @escape = escape && Nodes.build_quoted(escape) + @case_sensitive = case_sensitive + end + end + + class DoesNotMatch < Matches; end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/named_function.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/named_function.rb new file mode 100644 index 00000000..126462d6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/named_function.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class NamedFunction < Arel::Nodes::Function + attr_accessor :name + + def initialize(name, expr, aliaz = nil) + super(expr, aliaz) + @name = name + end + + def hash + super ^ @name.hash + end + + def eql?(other) + super && self.name == other.name + end + alias :== :eql? + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/nary.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/nary.rb new file mode 100644 index 00000000..bfd058be --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/nary.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Nary < Arel::Nodes::NodeExpression + attr_reader :children + + def initialize(children) + super() + @children = children + end + + def left + children.first + end + + def right + children[1] + end + + def fetch_attribute(&block) + children.any? && children.all? { |child| child.fetch_attribute(&block) } + end + + def hash + [self.class, children].hash + end + + def eql?(other) + self.class == other.class && + self.children == other.children + end + alias :== :eql? + end + + And = Class.new(Nary) + Or = Class.new(Nary) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/node.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/node.rb new file mode 100644 index 00000000..153e9592 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/node.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + # = Using +Arel::Nodes::Node+ + # + # Active Record uses Arel to compose SQL statements. Instead of building SQL strings directly, it's building an + # abstract syntax tree (AST) of the statement using various types of Arel::Nodes::Node. Each node represents a + # fragment of a SQL statement. + # + # The intermediate representation allows Arel to compile the statement into the database's specific SQL dialect + # only before sending it without having to care about the nuances of each database when building the statement. + # It also allows easier composition of statements without having to resort to (brittle and unsafe) string manipulation. + # + # == Building constraints + # + # One of the most common use cases of Arel is generating constraints for +SELECT+ statements. To help with that, + # most nodes include a couple of useful factory methods to create subtree structures for common constraints. For + # a full list of those, please refer to Arel::Predications. + # + # The following example creates an equality constraint where the value of the name column on the users table + # matches the value DHH. + # + # users = Arel::Table.new(:users) + # constraint = users[:name].eq("DHH") + # + # # => Arel::Nodes::Equality.new( + # # Arel::Attributes::Attribute.new(users, "name"), + # # Arel::Nodes::Casted.new( + # # "DHH", + # # Arel::Attributes::Attribute.new(users, "name") + # # ) + # # ) + # + # The resulting SQL fragment will look like this: + # + # "users"."name" = 'DHH' + # + # The constraint fragments can be used with regular ActiveRecord::Relation objects instead of a Hash. The + # following two examples show two ways of creating the same query. + # + # User.where(name: 'DHH') + # + # # SELECT "users".* FROM "users" WHERE "users"."name" = 'DHH' + # + # users = User.arel_table + # + # User.where(users[:name].eq('DHH')) + # + # # SELECT "users".* FROM "users" WHERE "users"."name" = 'DHH' + # + # == Functions + # + # Arel comes with built-in support for SQL functions like +COUNT+, +SUM+, +MIN+, +MAX+, and +AVG+. The + # Arel::Expressions module includes factory methods for the default functions. + # + # employees = Employee.arel_table + # + # Employee.select(employees[:department_id], employees[:salary].average).group(employees[:department_id]) + # + # # SELECT "employees"."department_id", AVG("employees"."salary") + # # FROM "employees" GROUP BY "employees"."department_id" + # + # It’s also possible to use custom functions by using the Arel::Nodes::NamedFunction node type. It accepts a + # function name and an array of parameters. + # + # Arel::Nodes::NamedFunction.new('date_trunc', [Arel::Nodes.build_quoted('day'), User.arel_table[:created_at]]) + # + # # date_trunc('day', "users"."created_at") + # + # == Quoting & bind params + # + # Values that you pass to Arel nodes need to be quoted or wrapped in bind params. This ensures they are properly + # converted into the correct format without introducing a possible SQL injection vulnerability. Most factory + # methods (like +eq+, +gt+, +lteq+, …) quote passed values automatically. When not using a factory method, it’s + # possible to convert a value and wrap it in an Arel::Nodes::Quoted node (if necessary) by calling +Arel::Nodes. + # build_quoted+. + # + # Arel::Nodes.build_quoted("foo") # 'foo' + # Arel::Nodes.build_quoted(12.3) # 12.3 + # + # Instead of quoting values and embedding them directly in the SQL statement, it’s also possible to create bind + # params. This keeps the actual values outside of the statement and allows using the prepared statement feature + # of some databases. + # + # attribute = ActiveRecord::Relation::QueryAttribute.new(:name, "DHH", ActiveRecord::Type::String.new) + # Arel::Nodes::BindParam.new(attribute) + # + # When ActiveRecord runs the query, bind params are replaced by placeholders (like +$1+) and the values are passed + # separately. + # + # == SQL Literals + # + # For cases where there is no way to represent a particular SQL fragment using Arel nodes, you can use an SQL + # literal. SQL literals are strings that Arel will treat “as is”. + # + # Arel.sql('LOWER("users"."name")').eq('dhh') + # + # # LOWER("users"."name") = 'dhh' + # + # Please keep in mind that passing data as raw SQL literals might introduce a possible SQL injection. However, + # `Arel.sql` supports binding parameters which will ensure proper quoting. This can be useful when you need to + # control the exact SQL you run, but you still have potentially user-supplied values. + # + # Arel.sql('LOWER("users"."name") = ?', 'dhh') + # + # # LOWER("users"."name") = 'dhh' + # + # You can also combine SQL literals. + # + # sql = Arel.sql('SELECT * FROM "users" WHERE ') + # sql += Arel.sql('LOWER("users"."name") = :name', name: 'dhh') + # sql += Arel.sql('AND "users"."age" > :age', age: 35) + # + # # SELECT * FROM "users" WHERE LOWER("users"."name") = 'dhh' AND "users"."age" > '35' + class Node + include Arel::FactoryMethods + + ### + # Factory method to create a Nodes::Not node that has the recipient of + # the caller as a child. + def not + Nodes::Not.new self + end + + ### + # Factory method to create a Nodes::Grouping node that has an Nodes::Or + # node as a child. + def or(right) + Nodes::Grouping.new Nodes::Or.new([self, right]) + end + + ### + # Factory method to create an Nodes::And node. + def and(right) + Nodes::And.new [self, right] + end + + def invert + Arel::Nodes::Not.new(self) + end + + # FIXME: this method should go away. I don't like people calling + # to_sql on non-head nodes. This forces us to walk the AST until we + # can find a node that has a "relation" member. + # + # Maybe we should just use `Table.engine`? :'( + def to_sql(engine = Table.engine) + collector = Arel::Collectors::SQLString.new + engine.with_connection do |connection| + connection.visitor.accept(self, collector).value + end + end + + def fetch_attribute(&) + end + + def equality?; false; end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/node_expression.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/node_expression.rb new file mode 100644 index 00000000..cbcfaba3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/node_expression.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class NodeExpression < Arel::Nodes::Node + include Arel::Expressions + include Arel::Predications + include Arel::AliasPredication + include Arel::OrderPredications + include Arel::Math + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/ordering.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/ordering.rb new file mode 100644 index 00000000..b09ce2b9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/ordering.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Ordering < Unary + def nulls_first + NullsFirst.new(self) + end + + def nulls_last + NullsLast.new(self) + end + end + + class NullsFirst < Ordering + def reverse + NullsLast.new(expr.reverse) + end + end + + class NullsLast < Ordering + def reverse + NullsFirst.new(expr.reverse) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/outer_join.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/outer_join.rb new file mode 100644 index 00000000..0a3042be --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/outer_join.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class OuterJoin < Arel::Nodes::Join + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/over.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/over.rb new file mode 100644 index 00000000..91176764 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/over.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Over < Binary + include Arel::AliasPredication + + def initialize(left, right = nil) + super(left, right) + end + + def operator; "OVER" end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/regexp.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/regexp.rb new file mode 100644 index 00000000..7c250955 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/regexp.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Regexp < Binary + attr_accessor :case_sensitive + + def initialize(left, right, case_sensitive = true) + super(left, right) + @case_sensitive = case_sensitive + end + end + + class NotRegexp < Regexp; end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/right_outer_join.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/right_outer_join.rb new file mode 100644 index 00000000..04ed4aaa --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/right_outer_join.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class RightOuterJoin < Arel::Nodes::Join + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/select_core.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/select_core.rb new file mode 100644 index 00000000..a9bc7986 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/select_core.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class SelectCore < Arel::Nodes::Node + attr_accessor :projections, :wheres, :groups, :windows, :comment + attr_accessor :havings, :source, :set_quantifier, :optimizer_hints + + def initialize(relation = nil) + super() + @source = JoinSource.new(relation) + + # https://ronsavage.github.io/SQL/sql-92.bnf.html#set%20quantifier + @set_quantifier = nil + @optimizer_hints = nil + @projections = [] + @wheres = [] + @groups = [] + @havings = [] + @windows = [] + @comment = nil + end + + def from + @source.left + end + + def from=(value) + @source.left = value + end + + alias :froms= :from= + alias :froms :from + + def initialize_copy(other) + super + @source = @source.clone if @source + @projections = @projections.clone + @wheres = @wheres.clone + @groups = @groups.clone + @havings = @havings.clone + @windows = @windows.clone + end + + def hash + [ + @source, @set_quantifier, @projections, @optimizer_hints, + @wheres, @groups, @havings, @windows, @comment + ].hash + end + + def eql?(other) + self.class == other.class && + self.source == other.source && + self.set_quantifier == other.set_quantifier && + self.optimizer_hints == other.optimizer_hints && + self.projections == other.projections && + self.wheres == other.wheres && + self.groups == other.groups && + self.havings == other.havings && + self.windows == other.windows && + self.comment == other.comment + end + alias :== :eql? + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/select_statement.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/select_statement.rb new file mode 100644 index 00000000..ad2162b5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/select_statement.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class SelectStatement < Arel::Nodes::NodeExpression + attr_reader :cores + attr_accessor :limit, :orders, :lock, :offset, :with + + def initialize(relation = nil) + super() + @cores = [SelectCore.new(relation)] + @orders = [] + @limit = nil + @lock = nil + @offset = nil + @with = nil + end + + def initialize_copy(other) + super + @cores = @cores.map { |x| x.clone } + @orders = @orders.map { |x| x.clone } + end + + def hash + [@cores, @orders, @limit, @lock, @offset, @with].hash + end + + def eql?(other) + self.class == other.class && + self.cores == other.cores && + self.orders == other.orders && + self.limit == other.limit && + self.lock == other.lock && + self.offset == other.offset && + self.with == other.with + end + alias :== :eql? + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/sql_literal.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/sql_literal.rb new file mode 100644 index 00000000..2e2e9384 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/sql_literal.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class SqlLiteral < String + include Arel::Expressions + include Arel::Predications + include Arel::AliasPredication + include Arel::OrderPredications + + attr_reader :retryable + + def initialize(string, retryable: false) + @retryable = retryable + super(string) + end + + def encode_with(coder) + coder.scalar = self.to_s + end + + def fetch_attribute(&) + end + + def +(other) + raise ArgumentError, "Expected Arel node" unless Arel.arel_node?(other) + + Fragments.new([self, other]) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/string_join.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/string_join.rb new file mode 100644 index 00000000..86027fca --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/string_join.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class StringJoin < Arel::Nodes::Join + def initialize(left, right = nil) + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/table_alias.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/table_alias.rb new file mode 100644 index 00000000..969b2705 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/table_alias.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class TableAlias < Arel::Nodes::Binary + alias :name :right + alias :relation :left + alias :table_alias :name + + def [](name) + relation.is_a?(Table) ? relation[name, self] : Attribute.new(self, name) + end + + def table_name + relation.respond_to?(:name) ? relation.name : name + end + + def type_cast_for_database(attr_name, value) + relation.type_cast_for_database(attr_name, value) + end + + def type_for_attribute(name) + relation.type_for_attribute(name) + end + + def able_to_type_cast? + relation.respond_to?(:able_to_type_cast?) && relation.able_to_type_cast? + end + + def to_cte + Arel::Nodes::Cte.new(name, relation) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/terminal.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/terminal.rb new file mode 100644 index 00000000..d84c453f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/terminal.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Distinct < Arel::Nodes::NodeExpression + def hash + self.class.hash + end + + def eql?(other) + self.class == other.class + end + alias :== :eql? + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/true.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/true.rb new file mode 100644 index 00000000..c8910129 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/true.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class True < Arel::Nodes::NodeExpression + def hash + self.class.hash + end + + def eql?(other) + self.class == other.class + end + alias :== :eql? + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/unary.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/unary.rb new file mode 100644 index 00000000..cf1eca28 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/unary.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Unary < Arel::Nodes::NodeExpression + attr_accessor :expr + alias :value :expr + + def initialize(expr) + super() + @expr = expr + end + + def hash + @expr.hash + end + + def eql?(other) + self.class == other.class && + self.expr == other.expr + end + alias :== :eql? + end + + %w{ + Bin + Cube + DistinctOn + Group + GroupingElement + GroupingSet + Lateral + Limit + Lock + Not + Offset + On + OptimizerHints + RollUp + }.each do |name| + const_set(name, Class.new(Unary)) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/unary_operation.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/unary_operation.rb new file mode 100644 index 00000000..524282ac --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/unary_operation.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class UnaryOperation < Unary + attr_reader :operator + + def initialize(operator, operand) + super(operand) + @operator = operator + end + end + + class BitwiseNot < UnaryOperation + def initialize(operand) + super(:~, operand) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/unqualified_column.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/unqualified_column.rb new file mode 100644 index 00000000..7c3e0720 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/unqualified_column.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class UnqualifiedColumn < Arel::Nodes::Unary + alias :attribute :expr + alias :attribute= :expr= + + def relation + @expr.relation + end + + def column + @expr.column + end + + def name + @expr.name + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/update_statement.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/update_statement.rb new file mode 100644 index 00000000..6cd25cb5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/update_statement.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class UpdateStatement < Arel::Nodes::Node + attr_accessor :relation, :wheres, :values, :groups, :havings, :orders, :limit, :offset, :key + + def initialize(relation = nil) + super() + @relation = relation + @wheres = [] + @values = [] + @groups = [] + @havings = [] + @orders = [] + @limit = nil + @offset = nil + @key = nil + end + + def initialize_copy(other) + super + @wheres = @wheres.clone + @values = @values.clone + end + + def hash + [@relation, @wheres, @values, @orders, @limit, @offset, @key].hash + end + + def eql?(other) + self.class == other.class && + self.relation == other.relation && + self.wheres == other.wheres && + self.values == other.values && + self.groups == other.groups && + self.havings == other.havings && + self.orders == other.orders && + self.limit == other.limit && + self.offset == other.offset && + self.key == other.key + end + alias :== :eql? + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/values_list.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/values_list.rb new file mode 100644 index 00000000..1a9d9ebf --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/values_list.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class ValuesList < Unary + alias :rows :expr + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/window.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/window.rb new file mode 100644 index 00000000..4916fc7f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/window.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class Window < Arel::Nodes::Node + attr_accessor :orders, :framing, :partitions + + def initialize + @orders = [] + @partitions = [] + @framing = nil + end + + def order(*expr) + # FIXME: We SHOULD NOT be converting these to SqlLiteral automatically + @orders.concat expr.map { |x| + String === x || Symbol === x ? Nodes::SqlLiteral.new(x.to_s) : x + } + self + end + + def partition(*expr) + # FIXME: We SHOULD NOT be converting these to SqlLiteral automatically + @partitions.concat expr.map { |x| + String === x || Symbol === x ? Nodes::SqlLiteral.new(x.to_s) : x + } + self + end + + def frame(expr) + @framing = expr + end + + def rows(expr = nil) + if @framing + Rows.new(expr) + else + frame(Rows.new(expr)) + end + end + + def range(expr = nil) + if @framing + Range.new(expr) + else + frame(Range.new(expr)) + end + end + + def initialize_copy(other) + super + @orders = @orders.map { |x| x.clone } + end + + def hash + [@orders, @framing].hash + end + + def eql?(other) + self.class == other.class && + self.orders == other.orders && + self.framing == other.framing && + self.partitions == other.partitions + end + alias :== :eql? + end + + class NamedWindow < Window + attr_accessor :name + + def initialize(name) + super() + @name = name + end + + def initialize_copy(other) + super + @name = other.name.clone + end + + def hash + super ^ @name.hash + end + + def eql?(other) + super && self.name == other.name + end + alias :== :eql? + end + + class Rows < Unary + def initialize(expr = nil) + super(expr) + end + end + + class Range < Unary + def initialize(expr = nil) + super(expr) + end + end + + class CurrentRow < Node + def hash + self.class.hash + end + + def eql?(other) + self.class == other.class + end + alias :== :eql? + end + + class Preceding < Unary + def initialize(expr = nil) + super(expr) + end + end + + class Following < Unary + def initialize(expr = nil) + super(expr) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/with.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/with.rb new file mode 100644 index 00000000..157bdcaa --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/nodes/with.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Nodes + class With < Arel::Nodes::Unary + alias children expr + end + + class WithRecursive < With; end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/order_predications.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/order_predications.rb new file mode 100644 index 00000000..d785bbba --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/order_predications.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module OrderPredications + def asc + Nodes::Ascending.new self + end + + def desc + Nodes::Descending.new self + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/predications.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/predications.rb new file mode 100644 index 00000000..09cdcb54 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/predications.rb @@ -0,0 +1,260 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Predications + def not_eq(other) + Nodes::NotEqual.new self, quoted_node(other) + end + + def not_eq_any(others) + grouping_any :not_eq, others + end + + def not_eq_all(others) + grouping_all :not_eq, others + end + + def eq(other) + Nodes::Equality.new self, quoted_node(other) + end + + def is_not_distinct_from(other) + Nodes::IsNotDistinctFrom.new self, quoted_node(other) + end + + def is_distinct_from(other) + Nodes::IsDistinctFrom.new self, quoted_node(other) + end + + def eq_any(others) + grouping_any :eq, others + end + + def eq_all(others) + grouping_all :eq, quoted_array(others) + end + + def between(other) + if unboundable?(other.begin) == 1 || unboundable?(other.end) == -1 + self.in([]) + elsif open_ended?(other.begin) + if open_ended?(other.end) + if infinity?(other.begin) == 1 || infinity?(other.end) == -1 + self.in([]) + else + not_in([]) + end + elsif other.exclude_end? + lt(other.end) + else + lteq(other.end) + end + elsif open_ended?(other.end) + gteq(other.begin) + elsif other.exclude_end? + gteq(other.begin).and(lt(other.end)) + elsif other.begin == other.end + eq(other.begin) + else + left = quoted_node(other.begin) + right = quoted_node(other.end) + Nodes::Between.new(self, Nodes::And.new([left, right])) + end + end + + def in(other) + case other + when Arel::SelectManager + Arel::Nodes::In.new(self, other.ast) + when Enumerable + Nodes::In.new self, quoted_array(other) + else + Nodes::In.new self, quoted_node(other) + end + end + + def in_any(others) + grouping_any :in, others + end + + def in_all(others) + grouping_all :in, others + end + + def not_between(other) + if unboundable?(other.begin) == 1 || unboundable?(other.end) == -1 + not_in([]) + elsif open_ended?(other.begin) + if open_ended?(other.end) + if infinity?(other.begin) == 1 || infinity?(other.end) == -1 + not_in([]) + else + self.in([]) + end + elsif other.exclude_end? + gteq(other.end) + else + gt(other.end) + end + elsif open_ended?(other.end) + lt(other.begin) + else + left = lt(other.begin) + right = if other.exclude_end? + gteq(other.end) + else + gt(other.end) + end + left.or(right) + end + end + + def not_in(other) + case other + when Arel::SelectManager + Arel::Nodes::NotIn.new(self, other.ast) + when Enumerable + Nodes::NotIn.new self, quoted_array(other) + else + Nodes::NotIn.new self, quoted_node(other) + end + end + + def not_in_any(others) + grouping_any :not_in, others + end + + def not_in_all(others) + grouping_all :not_in, others + end + + def matches(other, escape = nil, case_sensitive = false) + Nodes::Matches.new self, quoted_node(other), escape, case_sensitive + end + + def matches_regexp(other, case_sensitive = true) + Nodes::Regexp.new self, quoted_node(other), case_sensitive + end + + def matches_any(others, escape = nil, case_sensitive = false) + grouping_any :matches, others, escape, case_sensitive + end + + def matches_all(others, escape = nil, case_sensitive = false) + grouping_all :matches, others, escape, case_sensitive + end + + def does_not_match(other, escape = nil, case_sensitive = false) + Nodes::DoesNotMatch.new self, quoted_node(other), escape, case_sensitive + end + + def does_not_match_regexp(other, case_sensitive = true) + Nodes::NotRegexp.new self, quoted_node(other), case_sensitive + end + + def does_not_match_any(others, escape = nil) + grouping_any :does_not_match, others, escape + end + + def does_not_match_all(others, escape = nil) + grouping_all :does_not_match, others, escape + end + + def gteq(right) + Nodes::GreaterThanOrEqual.new self, quoted_node(right) + end + + def gteq_any(others) + grouping_any :gteq, others + end + + def gteq_all(others) + grouping_all :gteq, others + end + + def gt(right) + Nodes::GreaterThan.new self, quoted_node(right) + end + + def gt_any(others) + grouping_any :gt, others + end + + def gt_all(others) + grouping_all :gt, others + end + + def lt(right) + Nodes::LessThan.new self, quoted_node(right) + end + + def lt_any(others) + grouping_any :lt, others + end + + def lt_all(others) + grouping_all :lt, others + end + + def lteq(right) + Nodes::LessThanOrEqual.new self, quoted_node(right) + end + + def lteq_any(others) + grouping_any :lteq, others + end + + def lteq_all(others) + grouping_all :lteq, others + end + + def when(right) + Nodes::Case.new(self).when quoted_node(right) + end + + def concat(other) + Nodes::Concat.new self, other + end + + def contains(other) + Arel::Nodes::Contains.new self, quoted_node(other) + end + + def overlaps(other) + Arel::Nodes::Overlaps.new self, quoted_node(other) + end + + def quoted_array(others) + others.map { |v| quoted_node(v) } + end + + private + def grouping_any(method_id, others, *extras) + nodes = others.map { |expr| send(method_id, expr, *extras) } + Nodes::Grouping.new nodes.inject { |memo, node| + Nodes::Or.new([memo, node]) + } + end + + def grouping_all(method_id, others, *extras) + nodes = others.map { |expr| send(method_id, expr, *extras) } + Nodes::Grouping.new Nodes::And.new(nodes) + end + + def quoted_node(other) + Nodes.build_quoted(other, self) + end + + def infinity?(value) + value.respond_to?(:infinite?) && value.infinite? + end + + def unboundable?(value) + value.respond_to?(:unboundable?) && value.unboundable? + end + + def open_ended?(value) + value.nil? || infinity?(value) || unboundable?(value) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/select_manager.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/select_manager.rb new file mode 100644 index 00000000..2bdb6bc1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/select_manager.rb @@ -0,0 +1,276 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + class SelectManager < Arel::TreeManager + include Arel::Crud + + STRING_OR_SYMBOL_CLASS = [Symbol, String] + + def initialize(table = nil) + @ast = Nodes::SelectStatement.new(table) + @ctx = @ast.cores.last + end + + def initialize_copy(other) + super + @ctx = @ast.cores.last + end + + def limit + @ast.limit && @ast.limit.expr + end + alias :taken :limit + + def constraints + @ctx.wheres + end + + def offset + @ast.offset && @ast.offset.expr + end + + def skip(amount) + if amount + @ast.offset = Nodes::Offset.new(amount) + else + @ast.offset = nil + end + self + end + alias :offset= :skip + + ### + # Produces an Arel::Nodes::Exists node + def exists + Arel::Nodes::Exists.new @ast + end + + def as(other) + create_table_alias grouping(@ast), Nodes::SqlLiteral.new(other, retryable: true) + end + + def lock(locking = Arel.sql("FOR UPDATE")) + case locking + when true + locking = Arel.sql("FOR UPDATE") + when Arel::Nodes::SqlLiteral + when String + locking = Arel.sql locking + end + + @ast.lock = Nodes::Lock.new(locking) + self + end + + def locked + @ast.lock + end + + def on(*exprs) + @ctx.source.right.last.right = Nodes::On.new(collapse(exprs)) + self + end + + def group(*columns) + columns.each do |column| + # FIXME: backwards compat + column = Nodes::SqlLiteral.new(column) if String === column + column = Nodes::SqlLiteral.new(column.to_s) if Symbol === column + + @ctx.groups.push Nodes::Group.new column + end + self + end + + def from(table) + table = Nodes::SqlLiteral.new(table) if String === table + + case table + when Nodes::Join + @ctx.source.right << table + else + @ctx.source.left = table + end + + self + end + + def froms + @ast.cores.filter_map { |x| x.from } + end + + def join(relation, klass = Nodes::InnerJoin) + return self unless relation + + case relation + when String, Nodes::SqlLiteral + raise EmptyJoinError if relation.empty? + klass = Nodes::StringJoin + end + + @ctx.source.right << create_join(relation, nil, klass) + self + end + + def outer_join(relation) + join(relation, Nodes::OuterJoin) + end + + def having(expr) + @ctx.havings << expr + self + end + + def window(name) + window = Nodes::NamedWindow.new(name) + @ctx.windows.push window + window + end + + def project(*projections) + # FIXME: converting these to SQLLiterals is probably not good, but + # rails tests require it. + @ctx.projections.concat projections.map { |x| + STRING_OR_SYMBOL_CLASS.include?(x.class) ? Nodes::SqlLiteral.new(x.to_s) : x + } + self + end + + def projections + @ctx.projections + end + + def projections=(projections) + @ctx.projections = projections + end + + def optimizer_hints(*hints) + unless hints.empty? + @ctx.optimizer_hints = Arel::Nodes::OptimizerHints.new(hints) + end + self + end + + def distinct(value = true) + if value + @ctx.set_quantifier = Arel::Nodes::Distinct.new + else + @ctx.set_quantifier = nil + end + self + end + + def distinct_on(value) + if value + @ctx.set_quantifier = Arel::Nodes::DistinctOn.new(value) + else + @ctx.set_quantifier = nil + end + self + end + + def order(*expr) + # FIXME: We SHOULD NOT be converting these to SqlLiteral automatically + @ast.orders.concat expr.map { |x| + STRING_OR_SYMBOL_CLASS.include?(x.class) ? Nodes::SqlLiteral.new(x.to_s) : x + } + self + end + + def orders + @ast.orders + end + + def where(expr) + if Arel::TreeManager === expr + expr = expr.ast + end + @ctx.wheres << expr + self + end + + def where_sql(engine = Table.engine) + return if @ctx.wheres.empty? + + Nodes::SqlLiteral.new("WHERE #{Nodes::And.new(@ctx.wheres).to_sql(engine)}") + end + + def union(operation, other = nil) + if other + node_class = Nodes.const_get("Union#{operation.to_s.capitalize}") + else + other = operation + node_class = Nodes::Union + end + + node_class.new self.ast, other.ast + end + + def intersect(other) + Nodes::Intersect.new ast, other.ast + end + + def except(other) + Nodes::Except.new ast, other.ast + end + alias :minus :except + + def lateral(table_name = nil) + base = table_name.nil? ? ast : as(table_name) + Nodes::Lateral.new(base) + end + + def with(*subqueries) + if subqueries.first.is_a? Symbol + node_class = Nodes.const_get("With#{subqueries.shift.to_s.capitalize}") + else + node_class = Nodes::With + end + @ast.with = node_class.new(subqueries.flatten) + + self + end + + def take(limit) + if limit + @ast.limit = Nodes::Limit.new(limit) + else + @ast.limit = nil + end + self + end + alias limit= take + + def join_sources + @ctx.source.right + end + + def source + @ctx.source + end + + def comment(*values) + @ctx.comment = Nodes::Comment.new(values) + self + end + + private + def collapse(exprs) + exprs = exprs.compact + exprs.map! { |expr| + if String === expr + # FIXME: Don't do this automatically + Arel.sql(expr) + else + expr + end + } + + if exprs.length == 1 + exprs.first + else + create_and exprs + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/table.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/table.rb new file mode 100644 index 00000000..8569ee86 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/table.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + class Table + include Arel::FactoryMethods + include Arel::AliasPredication + + @engine = nil + class << self; attr_accessor :engine; end + + attr_accessor :name + attr_reader :table_alias + + def initialize(name, as: nil, klass: nil, type_caster: klass&.type_caster) + name = name.name if name.is_a?(Symbol) + + @name = name + @klass = klass + @type_caster = type_caster + + # Sometime AR sends an :as parameter to table, to let the table know + # that it is an Alias. We may want to override new, and return a + # TableAlias node? + if as.to_s == @name + as = nil + end + @table_alias = as + end + + def alias(name = "#{self.name}_2") + Nodes::TableAlias.new(self, name) + end + + def from + SelectManager.new(self) + end + + def join(relation, klass = Nodes::InnerJoin) + return from unless relation + + case relation + when String, Nodes::SqlLiteral + raise EmptyJoinError if relation.empty? + klass = Nodes::StringJoin + end + + from.join(relation, klass) + end + + def outer_join(relation) + join(relation, Nodes::OuterJoin) + end + + def group(*columns) + from.group(*columns) + end + + def order(*expr) + from.order(*expr) + end + + def where(condition) + from.where condition + end + + def project(*things) + from.project(*things) + end + + def take(amount) + from.take amount + end + + def skip(amount) + from.skip amount + end + + def having(expr) + from.having expr + end + + def [](name, table = self) + name = name.name if name.is_a?(Symbol) + name = @klass.attribute_aliases[name] || name if @klass + Attribute.new(table, name) + end + + def hash + # Perf note: aliases and table alias is excluded from the hash + # aliases can have a loop back to this table breaking hashes in parent + # relations, for the vast majority of cases @name is unique to a query + @name.hash + end + + def eql?(other) + self.class == other.class && + self.name == other.name && + self.table_alias == other.table_alias + end + alias :== :eql? + + def type_cast_for_database(attr_name, value) + type_caster.type_cast_for_database(attr_name, value) + end + + def type_for_attribute(name) + type_caster.type_for_attribute(name) + end + + def able_to_type_cast? + !type_caster.nil? + end + + private + attr_reader :type_caster + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/tree_manager.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/tree_manager.rb new file mode 100644 index 00000000..783cea0b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/tree_manager.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + class TreeManager + include Arel::FactoryMethods + + module StatementMethods + def take(limit) + @ast.limit = Nodes::Limit.new(Nodes.build_quoted(limit)) if limit + self + end + + def offset(offset) + @ast.offset = Nodes::Offset.new(Nodes.build_quoted(offset)) if offset + self + end + + def order(*expr) + @ast.orders = expr + self + end + + def key=(key) + @ast.key = if key.is_a?(Array) + key.map { |k| Nodes.build_quoted(k) } + else + Nodes.build_quoted(key) + end + end + + def key + @ast.key + end + + def wheres=(exprs) + @ast.wheres = exprs + end + + def where(expr) + @ast.wheres << expr + self + end + end + + attr_reader :ast + + def to_dot + collector = Arel::Collectors::PlainString.new + collector = Visitors::Dot.new.accept @ast, collector + collector.value + end + + def to_sql(engine = Table.engine) + collector = Arel::Collectors::SQLString.new + engine.with_connection do |connection| + connection.visitor.accept(@ast, collector).value + end + end + + def initialize_copy(other) + super + @ast = @ast.clone + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/update_manager.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/update_manager.rb new file mode 100644 index 00000000..01951c3a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/update_manager.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + class UpdateManager < Arel::TreeManager + include TreeManager::StatementMethods + + def initialize(table = nil) + @ast = Nodes::UpdateStatement.new(table) + end + + ### + # UPDATE +table+ + def table(table) + @ast.relation = table + self + end + + def set(values) + case values + when String, Nodes::BoundSqlLiteral + @ast.values = [values] + else + @ast.values = values.map { |column, value| + Nodes::Assignment.new( + Nodes::UnqualifiedColumn.new(column), + value + ) + } + end + self + end + + def group(columns) + columns.each do |column| + column = Nodes::SqlLiteral.new(column) if String === column + column = Nodes::SqlLiteral.new(column.to_s) if Symbol === column + + @ast.groups.push Nodes::Group.new column + end + + self + end + + def having(expr) + @ast.havings << expr + self + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/visitors.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/visitors.rb new file mode 100644 index 00000000..ea55a5e4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/visitors.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require "arel/visitors/visitor" +require "arel/visitors/to_sql" +require "arel/visitors/sqlite" +require "arel/visitors/postgresql" +require "arel/visitors/mysql" +require "arel/visitors/dot" + +module Arel # :nodoc: all + module Visitors + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/visitors/dot.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/visitors/dot.rb new file mode 100644 index 00000000..ddc30ef0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/visitors/dot.rb @@ -0,0 +1,299 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Visitors + class Dot < Arel::Visitors::Visitor + class Node # :nodoc: + attr_accessor :name, :id, :fields + + def initialize(name, id, fields = []) + @name = name + @id = id + @fields = fields + end + end + + class Edge < Struct.new :name, :from, :to # :nodoc: + end + + def initialize + super() + @nodes = [] + @edges = [] + @node_stack = [] + @edge_stack = [] + @seen = {} + end + + def accept(object, collector) + visit object + collector << to_dot + end + + private + def visit_Arel_Nodes_Function(o) + visit_edge o, "expressions" + visit_edge o, "distinct" + visit_edge o, "alias" + end + + def visit_Arel_Nodes_Unary(o) + visit_edge o, "expr" + end + + def visit_Arel_Nodes_Binary(o) + visit_edge o, "left" + visit_edge o, "right" + end + + def visit_Arel_Nodes_UnaryOperation(o) + visit_edge o, "operator" + visit_edge o, "expr" + end + + def visit_Arel_Nodes_InfixOperation(o) + visit_edge o, "operator" + visit_edge o, "left" + visit_edge o, "right" + end + + def visit__regexp(o) + visit_edge o, "left" + visit_edge o, "right" + visit_edge o, "case_sensitive" + end + alias :visit_Arel_Nodes_Regexp :visit__regexp + alias :visit_Arel_Nodes_NotRegexp :visit__regexp + + def visit_Arel_Nodes_Ordering(o) + visit_edge o, "expr" + end + + def visit_Arel_Nodes_TableAlias(o) + visit_edge o, "name" + visit_edge o, "relation" + end + + def visit_Arel_Nodes_Count(o) + visit_edge o, "expressions" + visit_edge o, "distinct" + end + + def visit_Arel_Nodes_ValuesList(o) + visit_edge o, "rows" + end + + def visit_Arel_Nodes_StringJoin(o) + visit_edge o, "left" + end + + def visit_Arel_Nodes_Window(o) + visit_edge o, "partitions" + visit_edge o, "orders" + visit_edge o, "framing" + end + + def visit_Arel_Nodes_NamedWindow(o) + visit_edge o, "partitions" + visit_edge o, "orders" + visit_edge o, "framing" + visit_edge o, "name" + end + + def visit__no_edges(o) + # intentionally left blank + end + alias :visit_Arel_Nodes_CurrentRow :visit__no_edges + alias :visit_Arel_Nodes_Distinct :visit__no_edges + + def visit_Arel_Nodes_Extract(o) + visit_edge o, "expressions" + visit_edge o, "alias" + end + + def visit_Arel_Nodes_NamedFunction(o) + visit_edge o, "name" + visit_edge o, "expressions" + visit_edge o, "distinct" + visit_edge o, "alias" + end + + def visit_Arel_Nodes_InsertStatement(o) + visit_edge o, "relation" + visit_edge o, "columns" + visit_edge o, "values" + visit_edge o, "select" + end + + def visit_Arel_Nodes_SelectCore(o) + visit_edge o, "source" + visit_edge o, "projections" + visit_edge o, "wheres" + visit_edge o, "windows" + visit_edge o, "groups" + visit_edge o, "comment" + visit_edge o, "havings" + visit_edge o, "set_quantifier" + visit_edge o, "optimizer_hints" + end + + def visit_Arel_Nodes_SelectStatement(o) + visit_edge o, "cores" + visit_edge o, "limit" + visit_edge o, "orders" + visit_edge o, "offset" + visit_edge o, "lock" + visit_edge o, "with" + end + + def visit_Arel_Nodes_UpdateStatement(o) + visit_edge o, "relation" + visit_edge o, "wheres" + visit_edge o, "values" + visit_edge o, "orders" + visit_edge o, "limit" + visit_edge o, "offset" + visit_edge o, "key" + end + + def visit_Arel_Nodes_DeleteStatement(o) + visit_edge o, "relation" + visit_edge o, "wheres" + visit_edge o, "orders" + visit_edge o, "limit" + visit_edge o, "offset" + visit_edge o, "key" + end + + def visit_Arel_Table(o) + visit_edge o, "name" + end + + def visit_Arel_Nodes_Casted(o) + visit_edge o, "value" + visit_edge o, "attribute" + end + + def visit_Arel_Nodes_HomogeneousIn(o) + visit_edge o, "values" + visit_edge o, "type" + visit_edge o, "attribute" + end + + def visit_Arel_Attributes_Attribute(o) + visit_edge o, "relation" + visit_edge o, "name" + end + + def visit__children(o) + o.children.each_with_index do |child, i| + edge(i) { visit child } + end + end + alias :visit_Arel_Nodes_And :visit__children + alias :visit_Arel_Nodes_Or :visit__children + alias :visit_Arel_Nodes_With :visit__children + + def visit_String(o) + @node_stack.last.fields << o + end + alias :visit_Time :visit_String + alias :visit_Date :visit_String + alias :visit_DateTime :visit_String + alias :visit_NilClass :visit_String + alias :visit_TrueClass :visit_String + alias :visit_FalseClass :visit_String + alias :visit_Integer :visit_String + alias :visit_BigDecimal :visit_String + alias :visit_Float :visit_String + alias :visit_Symbol :visit_String + alias :visit_Arel_Nodes_SqlLiteral :visit_String + + def visit_Arel_Nodes_BindParam(o) + visit_edge(o, "value") + end + + def visit_ActiveModel_Attribute(o) + visit_edge(o, "value_before_type_cast") + end + + def visit_Hash(o) + o.each_with_index do |pair, i| + edge("pair_#{i}") { visit pair } + end + end + + def visit_Array(o) + o.each_with_index do |member, i| + edge(i) { visit member } + end + end + alias :visit_Set :visit_Array + + def visit_Arel_Nodes_Comment(o) + visit_edge(o, "values") + end + + def visit_Arel_Nodes_Case(o) + visit_edge(o, "case") + visit_edge(o, "conditions") + visit_edge(o, "default") + end + + def visit_edge(o, method) + edge(method) { visit o.send(method) } + end + + def visit(o) + if node = @seen[o.object_id] + @edge_stack.last.to = node + return + end + + node = Node.new(o.class.name, o.object_id) + @seen[node.id] = node + @nodes << node + with_node node do + super + end + end + + def edge(name) + edge = Edge.new(name, @node_stack.last) + @edge_stack.push edge + @edges << edge + yield + @edge_stack.pop + end + + def with_node(node) + if edge = @edge_stack.last + edge.to = node + end + + @node_stack.push node + yield + @node_stack.pop + end + + def quote(string) + string.to_s.gsub('"', '\"') + end + + def to_dot + "digraph \"Arel\" {\nnode [width=0.375,height=0.25,shape=record];\n" + + @nodes.map { |node| + label = "#{node.name}" + + node.fields.each_with_index do |field, i| + label += "|#{quote field}" + end + + "#{node.id} [label=\"#{label}\"];" + }.join("\n") + "\n" + @edges.map { |edge| + "#{edge.from.id} -> #{edge.to.id} [label=\"#{edge.name}\"];" + }.join("\n") + "\n}" + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/visitors/mysql.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/visitors/mysql.rb new file mode 100644 index 00000000..937aa96d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/visitors/mysql.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Visitors + class MySQL < Arel::Visitors::ToSql + private + def visit_Arel_Nodes_Bin(o, collector) + collector << "CAST(" + visit o.expr, collector + collector << " AS BINARY)" + end + + def visit_Arel_Nodes_UnqualifiedColumn(o, collector) + visit o.expr, collector + end + + ### + # :'( + # To retrieve all rows from a certain offset up to the end of the result set, + # you can use some large number for the second parameter. + # https://dev.mysql.com/doc/refman/en/select.html + def visit_Arel_Nodes_SelectStatement(o, collector) + if o.offset && !o.limit + o.limit = Arel::Nodes::Limit.new(18446744073709551615) + end + super + end + + def visit_Arel_Nodes_SelectCore(o, collector) + o.froms ||= Arel.sql("DUAL", retryable: true) + super + end + + def visit_Arel_Nodes_Concat(o, collector) + collector << " CONCAT(" + visit o.left, collector + collector << ", " + visit o.right, collector + collector << ") " + collector + end + + def visit_Arel_Nodes_IsNotDistinctFrom(o, collector) + collector = visit o.left, collector + collector << " <=> " + visit o.right, collector + end + + def visit_Arel_Nodes_IsDistinctFrom(o, collector) + collector << "NOT " + visit_Arel_Nodes_IsNotDistinctFrom o, collector + end + + def visit_Arel_Nodes_Regexp(o, collector) + infix_value o, collector, " REGEXP " + end + + def visit_Arel_Nodes_NotRegexp(o, collector) + infix_value o, collector, " NOT REGEXP " + end + + def visit_Arel_Nodes_NullsFirst(o, collector) + visit(o.expr.expr, collector) << " IS NOT NULL, " + visit(o.expr, collector) + end + + def visit_Arel_Nodes_NullsLast(o, collector) + visit(o.expr.expr, collector) << " IS NULL, " + visit(o.expr, collector) + end + + def visit_Arel_Nodes_Cte(o, collector) + collector << quote_table_name(o.name) + collector << " AS " + visit o.relation, collector + end + + # In the simple case, MySQL allows us to place JOINs directly into the UPDATE + # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support + # these, we must use a subquery. + def prepare_update_statement(o) + if o.offset || has_group_by_and_having?(o) || + has_join_sources?(o) && has_limit_or_offset_or_orders?(o) + super + else + o + end + end + alias :prepare_delete_statement :prepare_update_statement + + # MySQL doesn't automatically create a temporary table for use subquery, so we have + # to give it some prompting in the form of a subsubquery. + def build_subselect(key, o) + subselect = super + + # Materialize subquery by adding distinct + # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on' + unless has_limit_or_offset_or_orders?(subselect) + core = subselect.cores.last + core.set_quantifier = Arel::Nodes::Distinct.new + end + + Nodes::SelectStatement.new.tap do |stmt| + core = stmt.cores.last + core.froms = Nodes::Grouping.new(subselect).as("__active_record_temp") + core.projections = [Arel.sql(quote_column_name(key.name), retryable: true)] + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/visitors/postgresql.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/visitors/postgresql.rb new file mode 100644 index 00000000..69aab80c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/visitors/postgresql.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Visitors + class PostgreSQL < Arel::Visitors::ToSql + private + def visit_Arel_Nodes_Matches(o, collector) + op = o.case_sensitive ? " LIKE " : " ILIKE " + collector = infix_value o, collector, op + if o.escape + collector << " ESCAPE " + visit o.escape, collector + else + collector + end + end + + def visit_Arel_Nodes_DoesNotMatch(o, collector) + op = o.case_sensitive ? " NOT LIKE " : " NOT ILIKE " + collector = infix_value o, collector, op + if o.escape + collector << " ESCAPE " + visit o.escape, collector + else + collector + end + end + + def visit_Arel_Nodes_Regexp(o, collector) + op = o.case_sensitive ? " ~ " : " ~* " + infix_value o, collector, op + end + + def visit_Arel_Nodes_NotRegexp(o, collector) + op = o.case_sensitive ? " !~ " : " !~* " + infix_value o, collector, op + end + + def visit_Arel_Nodes_DistinctOn(o, collector) + collector << "DISTINCT ON ( " + visit(o.expr, collector) << " )" + end + + def visit_Arel_Nodes_GroupingElement(o, collector) + collector << "( " + visit(o.expr, collector) << " )" + end + + def visit_Arel_Nodes_Cube(o, collector) + collector << "CUBE" + grouping_array_or_grouping_element o, collector + end + + def visit_Arel_Nodes_RollUp(o, collector) + collector << "ROLLUP" + grouping_array_or_grouping_element o, collector + end + + def visit_Arel_Nodes_GroupingSet(o, collector) + collector << "GROUPING SETS" + grouping_array_or_grouping_element o, collector + end + + def visit_Arel_Nodes_Lateral(o, collector) + collector << "LATERAL " + grouping_parentheses o.expr, collector + end + + def visit_Arel_Nodes_IsNotDistinctFrom(o, collector) + collector = visit o.left, collector + collector << " IS NOT DISTINCT FROM " + visit o.right, collector + end + + def visit_Arel_Nodes_IsDistinctFrom(o, collector) + collector = visit o.left, collector + collector << " IS DISTINCT FROM " + visit o.right, collector + end + + BIND_BLOCK = proc { |i| "$#{i}" } + private_constant :BIND_BLOCK + + def bind_block; BIND_BLOCK; end + + # Utilized by GroupingSet, Cube & RollUp visitors to + # handle grouping aggregation semantics + def grouping_array_or_grouping_element(o, collector) + if o.expr.is_a? Array + collector << "( " + visit o.expr, collector + collector << " )" + else + visit o.expr, collector + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/visitors/sqlite.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/visitors/sqlite.rb new file mode 100644 index 00000000..ec4426b1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/visitors/sqlite.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Visitors + class SQLite < Arel::Visitors::ToSql + private + # Locks are not supported in SQLite + def visit_Arel_Nodes_Lock(o, collector) + collector + end + + def visit_Arel_Nodes_SelectStatement(o, collector) + o.limit = Arel::Nodes::Limit.new(-1) if o.offset && !o.limit + super + end + + def visit_Arel_Nodes_True(o, collector) + collector << "1" + end + + def visit_Arel_Nodes_False(o, collector) + collector << "0" + end + + def visit_Arel_Nodes_IsNotDistinctFrom(o, collector) + collector = visit o.left, collector + collector << " IS " + visit o.right, collector + end + + def visit_Arel_Nodes_IsDistinctFrom(o, collector) + collector = visit o.left, collector + collector << " IS NOT " + visit o.right, collector + end + + # Queries used in UNION should not be wrapped by parentheses, + # because it is an invalid syntax in SQLite. + def infix_value_with_paren(o, collector, value, suppress_parens = false) + collector << "( " unless suppress_parens + + left = o.left.is_a?(Nodes::Grouping) ? o.left.expr : o.left + collector = if left.class == o.class + infix_value_with_paren(left, collector, value, true) + else + grouping_parentheses left, collector, false + end + + collector << value + + right = o.right.is_a?(Nodes::Grouping) ? o.right.expr : o.right + collector = if right.class == o.class + infix_value_with_paren(right, collector, value, true) + else + grouping_parentheses right, collector, false + end + + collector << " )" unless suppress_parens + collector + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/visitors/to_sql.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/visitors/to_sql.rb new file mode 100644 index 00000000..c9862b04 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/visitors/to_sql.rb @@ -0,0 +1,1033 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module Visitors + class UnsupportedVisitError < StandardError + def initialize(object) + super "Unsupported argument type: #{object.class.name}. Construct an Arel node instead." + end + end + + class ToSql < Arel::Visitors::Visitor + def initialize(connection) + super() + @connection = connection + end + + def compile(node, collector = Arel::Collectors::SQLString.new) + accept(node, collector).value + end + + private + def visit_Arel_Nodes_DeleteStatement(o, collector) + collector.retryable = false + o = prepare_delete_statement(o) + + if has_join_sources?(o) + collector << "DELETE " + visit o.relation.left, collector + collector << " FROM " + else + collector << "DELETE FROM " + end + collector = visit o.relation, collector + + collect_nodes_for o.wheres, collector, " WHERE ", " AND " + collect_nodes_for o.orders, collector, " ORDER BY " + maybe_visit o.limit, collector + end + + def visit_Arel_Nodes_UpdateStatement(o, collector) + collector.retryable = false + o = prepare_update_statement(o) + + collector << "UPDATE " + collector = visit o.relation, collector + collect_nodes_for o.values, collector, " SET " + + collect_nodes_for o.wheres, collector, " WHERE ", " AND " + collect_nodes_for o.orders, collector, " ORDER BY " + maybe_visit o.limit, collector + end + + def visit_Arel_Nodes_InsertStatement(o, collector) + collector.retryable = false + collector << "INSERT INTO " + collector = visit o.relation, collector + + unless o.columns.empty? + collector << " (" + o.columns.each_with_index do |x, i| + collector << ", " unless i == 0 + collector << quote_column_name(x.name) + end + collector << ")" + end + + if o.values + maybe_visit o.values, collector + elsif o.select + maybe_visit o.select, collector + else + collector + end + end + + def visit_Arel_Nodes_Exists(o, collector) + collector << "EXISTS (" + collector = visit(o.expressions, collector) << ")" + if o.alias + collector << " AS " + visit o.alias, collector + else + collector + end + end + + def visit_Arel_Nodes_Casted(o, collector) + collector << quote(o.value_for_database).to_s + end + alias :visit_Arel_Nodes_Quoted :visit_Arel_Nodes_Casted + + def visit_Arel_Nodes_True(o, collector) + collector << "TRUE" + end + + def visit_Arel_Nodes_False(o, collector) + collector << "FALSE" + end + + def visit_Arel_Nodes_ValuesList(o, collector) + collector << "VALUES " + + o.rows.each_with_index do |row, i| + collector << ", " unless i == 0 + collector << "(" + row.each_with_index do |value, k| + collector << ", " unless k == 0 + case value + when Nodes::SqlLiteral, Nodes::BindParam, ActiveModel::Attribute + collector = visit(value, collector) + else + collector << quote(value).to_s + end + end + collector << ")" + end + collector + end + + def visit_Arel_Nodes_SelectStatement(o, collector) + if o.with + collector = visit o.with, collector + collector << " " + end + + collector = o.cores.inject(collector) { |c, x| + visit_Arel_Nodes_SelectCore(x, c) + } + + unless o.orders.empty? + collector << " ORDER BY " + o.orders.each_with_index do |x, i| + collector << ", " unless i == 0 + collector = visit(x, collector) + end + end + + visit_Arel_Nodes_SelectOptions(o, collector) + end + + # The Oracle enhanced adapter uses this private method, + # see https://github.com/rsim/oracle-enhanced/issues/2186 + def visit_Arel_Nodes_SelectOptions(o, collector) + collector = maybe_visit o.limit, collector + collector = maybe_visit o.offset, collector + maybe_visit o.lock, collector + end + + def visit_Arel_Nodes_SelectCore(o, collector) + collector << "SELECT" + + collector = collect_optimizer_hints(o, collector) + collector = maybe_visit o.set_quantifier, collector + + collect_nodes_for o.projections, collector, " " + + if o.source && !o.source.empty? + collector << " FROM " + collector = visit o.source, collector + end + + collect_nodes_for o.wheres, collector, " WHERE ", " AND " + collect_nodes_for o.groups, collector, " GROUP BY " + collect_nodes_for o.havings, collector, " HAVING ", " AND " + collect_nodes_for o.windows, collector, " WINDOW " + + maybe_visit o.comment, collector + end + + def visit_Arel_Nodes_OptimizerHints(o, collector) + hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join(" ") + collector << "/*+ #{hints} */" + end + + def visit_Arel_Nodes_Comment(o, collector) + collector << o.values.map { |v| "/* #{sanitize_as_sql_comment(v)} */" }.join(" ") + end + + def collect_nodes_for(nodes, collector, spacer, connector = ", ") + unless nodes.empty? + collector << spacer + inject_join nodes, collector, connector + end + end + + def visit_Arel_Nodes_Bin(o, collector) + visit o.expr, collector + end + + def visit_Arel_Nodes_Distinct(o, collector) + collector << "DISTINCT" + end + + def visit_Arel_Nodes_DistinctOn(o, collector) + raise NotImplementedError, "DISTINCT ON not implemented for this db" + end + + def visit_Arel_Nodes_With(o, collector) + collector << "WITH " + collect_ctes(o.children, collector) + end + + def visit_Arel_Nodes_WithRecursive(o, collector) + collector << "WITH RECURSIVE " + collect_ctes(o.children, collector) + end + + def visit_Arel_Nodes_Union(o, collector) + infix_value_with_paren(o, collector, " UNION ") + end + + def visit_Arel_Nodes_UnionAll(o, collector) + infix_value_with_paren(o, collector, " UNION ALL ") + end + + def visit_Arel_Nodes_Intersect(o, collector) + collector << "( " + infix_value(o, collector, " INTERSECT ") << " )" + end + + def visit_Arel_Nodes_Except(o, collector) + collector << "( " + infix_value(o, collector, " EXCEPT ") << " )" + end + + def visit_Arel_Nodes_NamedWindow(o, collector) + collector << quote_column_name(o.name) + collector << " AS " + visit_Arel_Nodes_Window o, collector + end + + def visit_Arel_Nodes_Window(o, collector) + collector << "(" + + collect_nodes_for o.partitions, collector, "PARTITION BY " + + if o.orders.any? + collector << " " if o.partitions.any? + collector << "ORDER BY " + collector = inject_join o.orders, collector, ", " + end + + if o.framing + collector << " " if o.partitions.any? || o.orders.any? + collector = visit o.framing, collector + end + + collector << ")" + end + + def visit_Arel_Nodes_Filter(o, collector) + visit o.left, collector + collector << " FILTER (WHERE " + visit o.right, collector + collector << ")" + end + + def visit_Arel_Nodes_Rows(o, collector) + if o.expr + collector << "ROWS " + visit o.expr, collector + else + collector << "ROWS" + end + end + + def visit_Arel_Nodes_Range(o, collector) + if o.expr + collector << "RANGE " + visit o.expr, collector + else + collector << "RANGE" + end + end + + def visit_Arel_Nodes_Preceding(o, collector) + collector = if o.expr + visit o.expr, collector + else + collector << "UNBOUNDED" + end + + collector << " PRECEDING" + end + + def visit_Arel_Nodes_Following(o, collector) + collector = if o.expr + visit o.expr, collector + else + collector << "UNBOUNDED" + end + + collector << " FOLLOWING" + end + + def visit_Arel_Nodes_CurrentRow(o, collector) + collector << "CURRENT ROW" + end + + def visit_Arel_Nodes_Over(o, collector) + case o.right + when nil + visit(o.left, collector) << " OVER ()" + when Arel::Nodes::SqlLiteral + infix_value o, collector, " OVER " + when String, Symbol + visit(o.left, collector) << " OVER #{quote_column_name o.right.to_s}" + else + infix_value o, collector, " OVER " + end + end + + def visit_Arel_Nodes_Offset(o, collector) + collector << "OFFSET " + visit o.expr, collector + end + + def visit_Arel_Nodes_Limit(o, collector) + collector << "LIMIT " + visit o.expr, collector + end + + def visit_Arel_Nodes_Lock(o, collector) + visit o.expr, collector + end + + def visit_Arel_Nodes_Grouping(o, collector) + if o.expr.is_a? Nodes::Grouping + visit(o.expr, collector) + else + collector << "(" + visit(o.expr, collector) << ")" + end + end + + def visit_Arel_Nodes_HomogeneousIn(o, collector) + collector.preparable = false + + visit o.left, collector + + if o.type == :in + collector << " IN (" + else + collector << " NOT IN (" + end + + values = o.casted_values + + if values.empty? + collector << @connection.quote(nil) + else + collector.add_binds(values, o.proc_for_binds, &bind_block) + end + + collector << ")" + end + + def visit_Arel_SelectManager(o, collector) + collector << "(" + visit(o.ast, collector) << ")" + end + + def visit_Arel_Nodes_Ascending(o, collector) + visit(o.expr, collector) << " ASC" + end + + def visit_Arel_Nodes_Descending(o, collector) + visit(o.expr, collector) << " DESC" + end + + # NullsFirst is available on all but MySQL, where it is redefined. + def visit_Arel_Nodes_NullsFirst(o, collector) + visit o.expr, collector + collector << " NULLS FIRST" + end + + def visit_Arel_Nodes_NullsLast(o, collector) + visit o.expr, collector + collector << " NULLS LAST" + end + + def visit_Arel_Nodes_Group(o, collector) + visit o.expr, collector + end + + def visit_Arel_Nodes_NamedFunction(o, collector) + collector.retryable = false + collector << o.name + collector << "(" + collector << "DISTINCT " if o.distinct + collector = inject_join(o.expressions, collector, ", ") << ")" + if o.alias + collector << " AS " + visit o.alias, collector + else + collector + end + end + + def visit_Arel_Nodes_Extract(o, collector) + collector << "EXTRACT(#{o.field.to_s.upcase} FROM " + visit(o.expr, collector) << ")" + end + + def visit_Arel_Nodes_Count(o, collector) + aggregate "COUNT", o, collector + end + + def visit_Arel_Nodes_Sum(o, collector) + aggregate "SUM", o, collector + end + + def visit_Arel_Nodes_Max(o, collector) + aggregate "MAX", o, collector + end + + def visit_Arel_Nodes_Min(o, collector) + aggregate "MIN", o, collector + end + + def visit_Arel_Nodes_Avg(o, collector) + aggregate "AVG", o, collector + end + + def visit_Arel_Nodes_TableAlias(o, collector) + collector = visit o.relation, collector + collector << " " + collector << quote_table_name(o.name) + end + + def visit_Arel_Nodes_Between(o, collector) + collector = visit o.left, collector + collector << " BETWEEN " + visit o.right, collector + end + + def visit_Arel_Nodes_GreaterThanOrEqual(o, collector) + case unboundable?(o.right) + when 1 + return collector << "1=0" + when -1 + return collector << "1=1" + end + collector = visit o.left, collector + collector << " >= " + visit o.right, collector + end + + def visit_Arel_Nodes_GreaterThan(o, collector) + case unboundable?(o.right) + when 1 + return collector << "1=0" + when -1 + return collector << "1=1" + end + collector = visit o.left, collector + collector << " > " + visit o.right, collector + end + + def visit_Arel_Nodes_LessThanOrEqual(o, collector) + case unboundable?(o.right) + when 1 + return collector << "1=1" + when -1 + return collector << "1=0" + end + collector = visit o.left, collector + collector << " <= " + visit o.right, collector + end + + def visit_Arel_Nodes_LessThan(o, collector) + case unboundable?(o.right) + when 1 + return collector << "1=1" + when -1 + return collector << "1=0" + end + collector = visit o.left, collector + collector << " < " + visit o.right, collector + end + + def visit_Arel_Nodes_Matches(o, collector) + collector = visit o.left, collector + collector << " LIKE " + collector = visit o.right, collector + if o.escape + collector << " ESCAPE " + visit o.escape, collector + else + collector + end + end + + def visit_Arel_Nodes_DoesNotMatch(o, collector) + collector = visit o.left, collector + collector << " NOT LIKE " + collector = visit o.right, collector + if o.escape + collector << " ESCAPE " + visit o.escape, collector + else + collector + end + end + + def visit_Arel_Nodes_JoinSource(o, collector) + if o.left + collector = visit o.left, collector + end + if o.right.any? + collector << " " if o.left + collector = inject_join o.right, collector, " " + end + collector + end + + def visit_Arel_Nodes_Regexp(o, collector) + raise NotImplementedError, "~ not implemented for this db" + end + + def visit_Arel_Nodes_NotRegexp(o, collector) + raise NotImplementedError, "!~ not implemented for this db" + end + + def visit_Arel_Nodes_StringJoin(o, collector) + visit o.left, collector + end + + def visit_Arel_Nodes_FullOuterJoin(o, collector) + collector << "FULL OUTER JOIN " + collector = visit o.left, collector + collector << " " + visit o.right, collector + end + + def visit_Arel_Nodes_OuterJoin(o, collector) + collector << "LEFT OUTER JOIN " + collector = visit o.left, collector + collector << " " + visit o.right, collector + end + + def visit_Arel_Nodes_RightOuterJoin(o, collector) + collector << "RIGHT OUTER JOIN " + collector = visit o.left, collector + collector << " " + visit o.right, collector + end + + def visit_Arel_Nodes_InnerJoin(o, collector) + collector << "INNER JOIN " + collector = visit o.left, collector + if o.right + collector << " " + visit(o.right, collector) + else + collector + end + end + + def visit_Arel_Nodes_On(o, collector) + collector << "ON " + visit o.expr, collector + end + + def visit_Arel_Nodes_Not(o, collector) + collector << "NOT (" + visit(o.expr, collector) << ")" + end + + def visit_Arel_Table(o, collector) + if Arel::Nodes::Node === o.name + visit o.name, collector + else + collector << quote_table_name(o.name) + end + + if o.table_alias + collector << " " << quote_table_name(o.table_alias) + end + + collector + end + + def visit_Arel_Nodes_In(o, collector) + attr, values = o.left, o.right + + if Array === values + collector.preparable = false + + unless values.empty? + values.delete_if { |value| unboundable?(value) } + end + + return collector << "1=0" if values.empty? + end + + visit(attr, collector) << " IN (" + visit(values, collector) << ")" + end + + def visit_Arel_Nodes_NotIn(o, collector) + attr, values = o.left, o.right + + if Array === values + collector.preparable = false + + unless values.empty? + values.delete_if { |value| unboundable?(value) } + end + + return collector << "1=1" if values.empty? + end + + visit(attr, collector) << " NOT IN (" + visit(values, collector) << ")" + end + + def visit_Arel_Nodes_And(o, collector) + inject_join o.children, collector, " AND " + end + + def visit_Arel_Nodes_Or(o, collector) + inject_join o.children, collector, " OR " + end + + def visit_Arel_Nodes_Assignment(o, collector) + case o.right + when Arel::Nodes::Node, Arel::Attributes::Attribute, ActiveModel::Attribute + collector = visit o.left, collector + collector << " = " + visit o.right, collector + else + collector = visit o.left, collector + collector << " = " + collector << quote(o.right).to_s + end + end + + def visit_Arel_Nodes_Equality(o, collector) + right = o.right + + return collector << "1=0" if unboundable?(right) + + collector = visit o.left, collector + + if right.nil? + collector << " IS NULL" + else + collector << " = " + visit right, collector + end + end + + def visit_Arel_Nodes_IsNotDistinctFrom(o, collector) + if o.right.nil? + collector = visit o.left, collector + collector << " IS NULL" + else + collector = is_distinct_from(o, collector) + collector << " = 0" + end + end + + def visit_Arel_Nodes_IsDistinctFrom(o, collector) + if o.right.nil? + collector = visit o.left, collector + collector << " IS NOT NULL" + else + collector = is_distinct_from(o, collector) + collector << " = 1" + end + end + + def visit_Arel_Nodes_NotEqual(o, collector) + right = o.right + + return collector << "1=1" if unboundable?(right) + + collector = visit o.left, collector + + if right.nil? + collector << " IS NOT NULL" + else + collector << " != " + visit right, collector + end + end + + def visit_Arel_Nodes_As(o, collector) + collector = visit o.left, collector + collector << " AS " + visit o.right, collector + end + + def visit_Arel_Nodes_Case(o, collector) + collector << "CASE " + if o.case + visit o.case, collector + collector << " " + end + o.conditions.each do |condition| + visit condition, collector + collector << " " + end + if o.default + visit o.default, collector + collector << " " + end + collector << "END" + end + + def visit_Arel_Nodes_When(o, collector) + collector << "WHEN " + visit o.left, collector + collector << " THEN " + visit o.right, collector + end + + def visit_Arel_Nodes_Else(o, collector) + collector << "ELSE " + visit o.expr, collector + end + + def visit_Arel_Nodes_UnqualifiedColumn(o, collector) + collector << quote_column_name(o.name) + end + + def visit_Arel_Nodes_Cte(o, collector) + collector << quote_table_name(o.name) + collector << " AS " + + case o.materialized + when true + collector << "MATERIALIZED " + when false + collector << "NOT MATERIALIZED " + end + + visit o.relation, collector + end + + def visit_Arel_Attributes_Attribute(o, collector) + join_name = o.relation.table_alias || o.relation.name + collector << quote_table_name(join_name) << "." << quote_column_name(o.name) + end + + BIND_BLOCK = proc { "?" } + private_constant :BIND_BLOCK + + def bind_block; BIND_BLOCK; end + + def visit_ActiveModel_Attribute(o, collector) + collector.add_bind(o, &bind_block) + end + + def visit_Arel_Nodes_BindParam(o, collector) + collector.add_bind(o.value, &bind_block) + end + + def visit_Arel_Nodes_SqlLiteral(o, collector) + collector.preparable = false + collector.retryable &&= o.retryable + collector << o.to_s + end + + def visit_Arel_Nodes_BoundSqlLiteral(o, collector) + collector.retryable = false + bind_index = 0 + + new_bind = lambda do |value| + if Arel.arel_node?(value) + visit value, collector + elsif value.is_a?(Array) + if value.empty? + collector << @connection.quote(nil) + else + if value.none? { |v| Arel.arel_node?(v) } + collector.add_binds(value.map { |v| @connection.cast_bound_value(v) }, &bind_block) + else + value.each_with_index do |v, i| + collector << ", " unless i == 0 + if Arel.arel_node?(v) + visit v, collector + else + collector.add_bind(@connection.cast_bound_value(v), &bind_block) + end + end + end + end + else + collector.add_bind(@connection.cast_bound_value(value), &bind_block) + end + end + + if o.positional_binds + o.sql_with_placeholders.scan(/\?|([^?]+)/) do + if $1 + collector << $1 + else + value = o.positional_binds[bind_index] + bind_index += 1 + + new_bind.call(value) + end + end + else + o.sql_with_placeholders.scan(/:(? e + raise e if respond_to?(dispatch_method, true) + superklass = object.class.ancestors.find { |klass| + respond_to?(dispatch[klass], true) + } + raise(TypeError, "Cannot visit #{object.class}") unless superklass + dispatch[object.class] = dispatch[superklass] + retry + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/window_predications.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/window_predications.rb new file mode 100644 index 00000000..3a8ee41f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/arel/window_predications.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Arel # :nodoc: all + module WindowPredications + def over(expr = nil) + Nodes::Over.new(self, expr) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record.rb new file mode 100644 index 00000000..a7e5e373 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "rails/generators/named_base" +require "rails/generators/active_model" +require "rails/generators/active_record/migration" +require "active_record" + +module ActiveRecord + module Generators # :nodoc: + class Base < Rails::Generators::NamedBase # :nodoc: + include ActiveRecord::Generators::Migration + + # Set the current directory as base for the inherited generators. + def self.base_root + __dir__ + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/application_record/USAGE b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/application_record/USAGE new file mode 100644 index 00000000..730dcb6f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/application_record/USAGE @@ -0,0 +1,8 @@ +Description: + Generates an `ApplicationRecord` base class for other models to inherit from. + +Example: + `bin/rails generate application_record` + + This generates the base class. A test is not generated because no + behaviour is included in `ApplicationRecord` by default. diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/application_record/application_record_generator.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/application_record/application_record_generator.rb new file mode 100644 index 00000000..56b9628a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/application_record/application_record_generator.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require "rails/generators/active_record" + +module ActiveRecord + module Generators # :nodoc: + class ApplicationRecordGenerator < ::Rails::Generators::Base # :nodoc: + source_root File.expand_path("templates", __dir__) + + # FIXME: Change this file to a symlink once RubyGems 2.5.0 is required. + def create_application_record + template "application_record.rb", application_record_file_name + end + + private + def application_record_file_name + @application_record_file_name ||= + if namespaced? + "app/models/#{namespaced_path}/application_record.rb" + else + "app/models/application_record.rb" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt new file mode 100644 index 00000000..58ddf718 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt @@ -0,0 +1,5 @@ +<% module_namespacing do -%> +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end +<% end -%> diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/migration.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/migration.rb new file mode 100644 index 00000000..98b8c0c9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/migration.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "rails/generators/migration" + +module ActiveRecord + module Generators # :nodoc: + module Migration + extend ActiveSupport::Concern + include Rails::Generators::Migration + + module ClassMethods + # Implement the required interface for Rails::Generators::Migration. + def next_migration_number(dirname) + next_migration_number = current_migration_number(dirname) + 1 + ActiveRecord::Migration.next_migration_number(next_migration_number) + end + end + + private + def primary_key_type + key_type = options[:primary_key_type] + ", id: :#{key_type}" if key_type + end + + def foreign_key_type + key_type = options[:primary_key_type] + ", type: :#{key_type}" if key_type + end + + def db_migrate_path + if defined?(Rails.application) && Rails.application + configured_migrate_path || default_migrate_path + else + "db/migrate" + end + end + + def default_migrate_path + Rails.application.config.paths["db/migrate"].to_ary.first + end + + def configured_migrate_path + return unless database = options[:database] + + config = ActiveRecord::Base.configurations.configs_for( + env_name: Rails.env, + name: database + ) + + Array(config&.migrations_paths).first + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/migration/migration_generator.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/migration/migration_generator.rb new file mode 100644 index 00000000..0620a515 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/migration/migration_generator.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require "rails/generators/active_record" + +module ActiveRecord + module Generators # :nodoc: + class MigrationGenerator < Base # :nodoc: + argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]" + + class_option :timestamps, type: :boolean + class_option :primary_key_type, type: :string, desc: "The type for primary key" + class_option :database, type: :string, aliases: %i(--db), desc: "The database for your migration. By default, the current environment's primary database is used." + + def create_migration_file + set_local_assigns! + validate_file_name! + migration_template @migration_template, File.join(db_migrate_path, "#{file_name}.rb") + end + + private + attr_reader :migration_action, :join_tables + + # Sets the default migration template that is being used for the generation of the migration. + # Depending on command line arguments, the migration template and the table name instance + # variables are set up. + def set_local_assigns! + @migration_template = "migration.rb" + case file_name + when /^(add)_.*_to_(.*)/, /^(remove)_.*?_from_(.*)/ + @migration_action = $1 + @table_name = normalize_table_name($2) + when /join_table/ + if attributes.length == 2 + @migration_action = "join" + @join_tables = pluralize_table_names? ? attributes.map(&:plural_name) : attributes.map(&:singular_name) + + set_index_names + end + when /^create_(.+)/ + @table_name = normalize_table_name($1) + @migration_template = "create_table_migration.rb" + end + end + + def set_index_names + attributes.each_with_index do |attr, i| + attr.index_name = [attr, attributes[i - 1]].map { |a| index_name_for(a) } + end + end + + def index_name_for(attribute) + if attribute.foreign_key? + attribute.name + else + attribute.name.singularize.foreign_key + end.to_sym + end + + def attributes_with_index + attributes.select { |a| !a.reference? && a.has_index? } + end + + # A migration file name can only contain underscores (_), lowercase characters, + # and numbers 0-9. Any other file name will raise an IllegalMigrationNameError. + def validate_file_name! + unless /^[_a-z0-9]+$/.match?(file_name) + raise IllegalMigrationNameError.new(file_name) + end + end + + def normalize_table_name(_table_name) + pluralize_table_names? ? _table_name.pluralize : _table_name.singularize + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt new file mode 100644 index 00000000..a467f2a4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt @@ -0,0 +1,29 @@ +class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>] + def change + create_table :<%= table_name %><%= primary_key_type %> do |t| +<% attributes.each do |attribute| -%> +<% if attribute.password_digest? -%> + t.string :password_digest<%= attribute.inject_options %> +<% elsif attribute.token? -%> + t.string :<%= attribute.name %><%= attribute.inject_options %> +<% elsif attribute.reference? -%> + t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %><%= foreign_key_type %> +<% elsif !attribute.virtual? -%> + t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %> +<% end -%> +<% end -%> +<% unless attributes.empty? -%> + +<% end -%> +<% if options[:timestamps] -%> + t.timestamps +<% end -%> + end +<% attributes.select(&:token?).each do |attribute| -%> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>, unique: true +<% end -%> +<% attributes_with_index.each do |attribute| -%> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> +<% end -%> + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/migration/templates/migration.rb.tt b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/migration/templates/migration.rb.tt new file mode 100644 index 00000000..f2038233 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/migration/templates/migration.rb.tt @@ -0,0 +1,48 @@ +class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>] +<%- if migration_action == 'add' -%> + def change +<% attributes.each do |attribute| -%> + <%- if attribute.reference? -%> + add_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %><%= foreign_key_type %> + <%- elsif attribute.token? -%> + add_column :<%= table_name %>, :<%= attribute.name %>, :string<%= attribute.inject_options %> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>, unique: true + <%- elsif !attribute.virtual? -%> + add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> + <%- if attribute.has_index? -%> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> + <%- end -%> + <%- end -%> +<%- end -%> + end +<%- elsif migration_action == 'join' -%> + def change + create_join_table :<%= join_tables.first %>, :<%= join_tables.second %> do |t| + <%- attributes.each do |attribute| -%> + <%- if attribute.reference? -%> + t.references :<%= attribute.name %><%= attribute.inject_options %><%= foreign_key_type %> + <%- elsif !attribute.virtual? -%> + <%= '# ' unless attribute.has_index? -%>t.index <%= attribute.index_name %><%= attribute.inject_index_options %> + <%- end -%> + <%- end -%> + end + end +<%- else -%> + def change +<% attributes.each do |attribute| -%> +<%- if migration_action -%> + <%- if attribute.reference? -%> + remove_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %><%= foreign_key_type %> + <%- else -%> + <%- if attribute.has_index? -%> + remove_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> + <%- end -%> + <%- if !attribute.virtual? -%> + remove_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> + <%- end -%> + <%- end -%> +<%- end -%> +<%- end -%> + end +<%- end -%> +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/model/USAGE b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/model/USAGE new file mode 100644 index 00000000..2d1b841e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/model/USAGE @@ -0,0 +1,113 @@ +Description: + Generates a new model. Pass the model name, either CamelCased or + under_scored, and an optional list of attribute pairs as arguments. + + Attribute pairs are field:type arguments specifying the + model's attributes. Timestamps are added by default, so you don't have to + specify them by hand as 'created_at:datetime updated_at:datetime'. + + As a special case, specifying 'password:digest' will generate a + password_digest field of string type, and configure your generated model and + tests for use with Active Model has_secure_password (assuming the default ORM + and test framework are being used). + + You don't have to think up every attribute up front, but it helps to + sketch out a few so you can start working with the model immediately. + + This generator invokes your configured ORM and test framework, which + defaults to Active Record and TestUnit. + + Finally, if --parent option is given, it's used as superclass of the + created model. This allows you create Single Table Inheritance models. + + If you pass a namespaced model name (e.g. admin/account or Admin::Account) + then the generator will create a module with a table_name_prefix method + to prefix the model's table name with the module name (e.g. admin_accounts) + +Available field types: + + Just after the field name you can specify a type like text or boolean. + It will generate the column with the associated SQL type. For instance: + + `bin/rails generate model post title:string body:text` + + will generate a title column with a varchar type and a body column with a text + type. If no type is specified the string type will be used by default. + You can use the following types: + + integer + primary_key + decimal + float + boolean + binary + string + text + date + time + datetime + + You can also consider `references` as a kind of type. For instance, if you run: + + `bin/rails generate model photo title:string album:references` + + It will generate an `album_id` column. You should generate these kinds of fields when + you will use a `belongs_to` association, for instance. `references` also supports + polymorphism, you can enable polymorphism like this: + + `bin/rails generate model product supplier:references{polymorphic}` + + For integer, string, text and binary fields, an integer in curly braces will + be set as the limit: + + `bin/rails generate model user pseudo:string{30}` + + For decimal, two integers separated by a comma in curly braces will be used + for precision and scale: + + `bin/rails generate model product 'price:decimal{10,2}'` + + You can add a `:uniq` or `:index` suffix for unique or standard indexes + respectively: + + `bin/rails generate model user pseudo:string:uniq` + `bin/rails generate model user pseudo:string:index` + + You can combine any single curly brace option with the index options: + + `bin/rails generate model user username:string{30}:uniq` + `bin/rails generate model product supplier:references{polymorphic}:index` + + If you require a `password_digest` string column for use with + has_secure_password, you can specify `password:digest`: + + `bin/rails generate model user password:digest` + + If you require a `token` string column for use with + has_secure_token, you can specify `auth_token:token`: + + `bin/rails generate model user auth_token:token` + +Examples: + `bin/rails generate model account` + + For Active Record and TestUnit it creates: + + Model: app/models/account.rb + Test: test/models/account_test.rb + Fixtures: test/fixtures/accounts.yml + Migration: db/migrate/XXX_create_accounts.rb + + `bin/rails generate model post title:string body:text published:boolean` + + Creates a Post model with a string title, text body, and published flag. + + `bin/rails generate model admin/account` + + For Active Record and TestUnit it creates: + + Module: app/models/admin.rb + Model: app/models/admin/account.rb + Test: test/models/admin/account_test.rb + Fixtures: test/fixtures/admin/accounts.yml + Migration: db/migrate/XXX_create_admin_accounts.rb diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/model/model_generator.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/model/model_generator.rb new file mode 100644 index 00000000..ae6adac6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/model/model_generator.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require "rails/generators/active_record" + +module ActiveRecord + module Generators # :nodoc: + class ModelGenerator < Base # :nodoc: + argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]" + + check_class_collision + + class_option :migration, type: :boolean + class_option :timestamps, type: :boolean + class_option :parent, type: :string, default: "ApplicationRecord", desc: "The parent class for the generated model" + class_option :indexes, type: :boolean, default: true, desc: "Add indexes for references and belongs_to columns" + class_option :primary_key_type, type: :string, desc: "The type for primary key" + class_option :database, type: :string, aliases: %i(--db), desc: "The database for your model's migration. By default, the current environment's primary database is used." + + Rails::Generators.templates_path.each do |path| + source_paths << File.join(path, base_name, "migration") + end + source_paths << File.expand_path(File.join(base_name, "migration", "templates"), base_root) + + # creates the migration file for the model. + def create_migration_file + return if skip_migration_creation? + attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false + migration_template "create_table_migration.rb", File.join(db_migrate_path, "create_#{table_name}.rb") + end + + def create_model_file + generate_abstract_class if database && !custom_parent? + template "model.rb", File.join("app/models", class_path, "#{file_name}.rb") + end + + def create_module_file + return if regular_class_path.empty? + template "module.rb", File.join("app/models", "#{class_path.join('/')}.rb") if behavior == :invoke + end + + hook_for :test_framework + + private + # Skip creating migration file if: + # - options parent is present and database option is not present + # - migrations option is nil or false + def skip_migration_creation? + custom_parent? && !database || !migration + end + + def attributes_with_index + attributes.select { |a| !a.reference? && a.has_index? } + end + + # Used by the migration template to determine the parent name of the model + def parent_class_name + if custom_parent? + parent + elsif database + abstract_class_name + else + parent + end + end + + def generate_abstract_class + path = File.join("app/models", "#{database.underscore}_record.rb") + return if File.exist?(path) + + template "abstract_base_class.rb", path + end + + def abstract_class_name + "#{database.camelize}Record" + end + + def database + options[:database] + end + + def parent + options[:parent] + end + + def custom_parent? + parent != self.class.class_options[:parent].default + end + + def migration + options[:migration] + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt new file mode 100644 index 00000000..ee5cc651 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt @@ -0,0 +1,7 @@ +<% module_namespacing do -%> +class <%= abstract_class_name %> < ApplicationRecord + self.abstract_class = true + + connects_to database: { <%= ActiveRecord.writing_role %>: :<%= database -%> } +end +<% end -%> diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/model/templates/model.rb.tt b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/model/templates/model.rb.tt new file mode 100644 index 00000000..e2c7e4e1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/model/templates/model.rb.tt @@ -0,0 +1,22 @@ +<% module_namespacing do -%> +class <%= class_name %> < <%= parent_class_name.classify %> +<% attributes.select(&:reference?).each do |attribute| -%> + belongs_to :<%= attribute.name %><%= ", polymorphic: true" if attribute.polymorphic? %> +<% end -%> +<% attributes.select(&:rich_text?).each do |attribute| -%> + has_rich_text :<%= attribute.name %> +<% end -%> +<% attributes.select(&:attachment?).each do |attribute| -%> + has_one_attached :<%= attribute.name %> +<% end -%> +<% attributes.select(&:attachments?).each do |attribute| -%> + has_many_attached :<%= attribute.name %> +<% end -%> +<% attributes.select(&:token?).each do |attribute| -%> + has_secure_token<% if attribute.name != "token" %> :<%= attribute.name %><% end %> +<% end -%> +<% if attributes.any?(&:password_digest?) -%> + has_secure_password +<% end -%> +end +<% end -%> diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/model/templates/module.rb.tt b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/model/templates/module.rb.tt new file mode 100644 index 00000000..a38f8b93 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/model/templates/module.rb.tt @@ -0,0 +1,7 @@ +<% module_namespacing do -%> +module <%= class_path.map(&:camelize).join("::") %> + def self.table_name_prefix + "<%= namespaced? ? namespaced_class_path.join("_") : class_path.join("_") %>_" + end +end +<% end -%> diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/multi_db/multi_db_generator.rb b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/multi_db/multi_db_generator.rb new file mode 100644 index 00000000..32a11b4d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/multi_db/multi_db_generator.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "rails/generators/active_record" + +module ActiveRecord + module Generators # :nodoc: + class MultiDbGenerator < ::Rails::Generators::Base # :nodoc: + source_root File.expand_path("templates", __dir__) + + def create_multi_db + filename = "multi_db.rb" + template filename, "config/initializers/#{filename}" + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt new file mode 100644 index 00000000..6016d955 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activerecord-8.0.2/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt @@ -0,0 +1,44 @@ +# Multi-db Configuration +# +# This file is used for configuration settings related to multiple databases. +# +# Enable Database Selector +# +# Inserts middleware to perform automatic connection switching. +# The `database_selector` hash is used to pass options to the DatabaseSelector +# middleware. The `delay` is used to determine how long to wait after a write +# to send a subsequent read to the primary. +# +# The `database_resolver` class is used by the middleware to determine which +# database is appropriate to use based on the time delay. +# +# The `database_resolver_context` class is used by the middleware to set +# timestamps for the last write to the primary. The resolver uses the context +# class timestamps to determine how long to wait before reading from the +# replica. +# +# By default Rails will store a last write timestamp in the session. The +# DatabaseSelector middleware is designed as such you can define your own +# strategy for connection switching and pass that into the middleware through +# these configuration options. +# +# Rails.application.configure do +# config.active_record.database_selector = { delay: 2.seconds } +# config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver +# config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session +# end +# +# Enable Shard Selector +# +# Inserts middleware to perform automatic shard swapping. The `shard_selector` hash +# can be used to pass options to the `ShardSelector` middleware. The `lock` option is +# used to determine whether shard swapping should be prohibited for the request. +# +# The `shard_resolver` option is used by the middleware to determine which shard +# to switch to. The application must provide a mechanism for finding the shard name +# in a proc. See guides for an example. +# +# Rails.application.configure do +# config.active_record.shard_selector = { lock: true } +# config.active_record.shard_resolver = ->(request) { Tenant.find_by!(host: request.host).shard } +# end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/CHANGELOG.md b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/CHANGELOG.md new file mode 100644 index 00000000..3ba2954f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/CHANGELOG.md @@ -0,0 +1,255 @@ +## Rails 8.0.2 (March 12, 2025) ## + +* No changes. + + +## Rails 8.0.2 (March 12, 2025) ## + +* Fix setting `to_time_preserves_timezone` from `new_framework_defaults_8_0.rb`. + + *fatkodima* + +* Fix Active Support Cache `fetch_multi` when local store is active. + + `fetch_multi` now properly yield to the provided block for missing entries + that have been recorded as such in the local store. + + *Jean Boussier* + +* Fix execution wrapping to report all exceptions, including `Exception`. + + If a more serious error like `SystemStackError` or `NoMemoryError` happens, + the error reporter should be able to report these kinds of exceptions. + + *Gannon McGibbon* + +* Fix `RedisCacheStore` and `MemCacheStore` to also handle connection pool related errors. + + These errors are rescued and reported to `Rails.error`. + + *Jean Boussier* + +* Fix `ActiveSupport::Cache#read_multi` to respect version expiry when using local cache. + + *zzak* + +* Fix `ActiveSupport::MessageVerifier` and `ActiveSupport::MessageEncryptor` configuration of `on_rotation` callback. + + ```ruby + verifier.rotate(old_secret).on_rotation { ... } + ``` + + Now both work as documented. + + *Jean Boussier* + +* Fix `ActiveSupport::MessageVerifier` to always be able to verify both URL-safe and URL-unsafe payloads. + + This is to allow transitioning seemlessly from either configuration without immediately invalidating + all previously generated signed messages. + + *Jean Boussier*, *Florent Beaurain*, *Ali Sepehri* + +* Fix `cache.fetch` to honor the provided expiry when `:race_condition_ttl` is used. + + ```ruby + cache.fetch("key", expires_in: 1.hour, race_condition_ttl: 5.second) do + "something" + end + ``` + + In the above example, the final cache entry would have a 10 seconds TTL instead + of the requested 1 hour. + + *Dhia* + +* Better handle procs with splat arguments in `set_callback`. + + *Radamés Roriz* + +* Fix `String#mb_chars` to not mutate the receiver. + + Previously it would call `force_encoding` on the receiver, + now it dups the receiver first. + + *Jean Boussier* + +* Improve `ErrorSubscriber` to also mark error causes as reported. + + This avoid some cases of errors being reported twice, notably in views because of how + errors are wrapped in `ActionView::Template::Error`. + + *Jean Boussier* + +* Fix `Module#module_parent_name` to return the correct name after the module has been named. + + When called on an anonymous module, the return value wouldn't change after the module was given a name + later by being assigned to a constant. + + ```ruby + mod = Module.new + mod.module_parent_name # => "Object" + MyModule::Something = mod + mod.module_parent_name # => "MyModule" + ``` + + *Jean Boussier* + + +## Rails 8.0.1 (December 13, 2024) ## + +* Fix a bug in `ERB::Util.tokenize` that causes incorrect tokenization when ERB tags are preceeded by multibyte characters. + + *Martin Emde* + +* Restore the ability to decorate methods generated by `class_attribute`. + + It always has been complicated to use Module#prepend or an alias method chain + to decorate methods defined by `class_attribute`, but became even harder in 8.0. + + This capability is now supported for both reader and writer methods. + + *Jean Boussier* + + +## Rails 8.0.0.1 (December 10, 2024) ## + +* No changes. + + +## Rails 8.0.0 (November 07, 2024) ## + +* No changes. + + +## Rails 8.0.0.rc2 (October 30, 2024) ## + +* No changes. + + +## Rails 8.0.0.rc1 (October 19, 2024) ## + +* Remove deprecated support to passing an array of strings to `ActiveSupport::Deprecation#warn`. + + *Rafael Mendonça França* + +* Remove deprecated support to setting `attr_internal_naming_format` with a `@` prefix. + + *Rafael Mendonça França* + +* Remove deprecated `ActiveSupport::ProxyObject`. + + *Rafael Mendonça França* + +* Don't execute i18n watcher on boot. It shouldn't catch any file changes initially, + and unnecessarily slows down boot of applications with lots of translations. + + *Gannon McGibbon*, *David Stosik* + +* Fix `ActiveSupport::HashWithIndifferentAccess#stringify_keys` to stringify all keys not just symbols. + + Previously: + + ```ruby + { 1 => 2 }.with_indifferent_access.stringify_keys[1] # => 2 + ``` + + After this change: + + ```ruby + { 1 => 2 }.with_indifferent_access.stringify_keys["1"] # => 2 + ``` + + This change can be seen as a bug fix, but since it behaved like this for a very long time, we're deciding + to not backport the fix and to make the change in a major release. + + *Jean Boussier* + +## Rails 8.0.0.beta1 (September 26, 2024) ## + +* Include options when instrumenting `ActiveSupport::Cache::Store#delete` and `ActiveSupport::Cache::Store#delete_multi`. + + *Adam Renberg Tamm* + +* Print test names when running `rails test -v` for parallel tests. + + *John Hawthorn*, *Abeid Ahmed* + +* Deprecate `Benchmark.ms` core extension. + + The `benchmark` gem will become bundled in Ruby 3.5 + + *Earlopain* + +* `ActiveSupport::TimeWithZone#inspect` now uses ISO 8601 style time like `Time#inspect` + + *John Hawthorn* + +* `ActiveSupport::ErrorReporter#report` now assigns a backtrace to unraised exceptions. + + Previously reporting an un-raised exception would result in an error report without + a backtrace. Now it automatically generates one. + + *Jean Boussier* + +* Add `escape_html_entities` option to `ActiveSupport::JSON.encode`. + + This allows for overriding the global configuration found at + `ActiveSupport.escape_html_entities_in_json` for specific calls to `to_json`. + + This should be usable from controllers in the following manner: + ```ruby + class MyController < ApplicationController + def index + render json: { hello: "world" }, escape_html_entities: false + end + end + ``` + + *Nigel Baillie* + +* Raise when using key which can't respond to `#to_sym` in `EncryptedConfiguration`. + + As is the case when trying to use an Integer or Float as a key, which is unsupported. + + *zzak* + +* Deprecate addition and since between two `Time` and `ActiveSupport::TimeWithZone`. + + Previously adding time instances together such as `10.days.ago + 10.days.ago` or `10.days.ago.since(10.days.ago)` produced a nonsensical future date. This behavior is deprecated and will be removed in Rails 8.1. + + *Nick Schwaderer* + +* Support rfc2822 format for Time#to_fs & Date#to_fs. + + *Akshay Birajdar* + +* Optimize load time for `Railtie#initialize_i18n`. Filter `I18n.load_path`s passed to the file watcher to only those + under `Rails.root`. Previously the watcher would grab all available locales, including those in gems + which do not require a watcher because they won't change. + + *Nick Schwaderer* + +* Add a `filter` option to `in_order_of` to prioritize certain values in the sorting without filtering the results + by these values. + + *Igor Depolli* + +* Improve error message when using `assert_difference` or `assert_changes` with a + proc by printing the proc's source code (MRI only). + + *Richard Böhme*, *Jean Boussier* + +* Add a new configuration value `:zone` for `ActiveSupport.to_time_preserves_timezone` and rename the previous `true` value to `:offset`. The new default value is `:zone`. + + *Jason Kim*, *John Hawthorn* + +* Align instrumentation `payload[:key]` in ActiveSupport::Cache to follow the same pattern, with namespaced and normalized keys. + + *Frederik Erbs Spang Thomsen* + +* Fix `travel_to` to set usec 0 when `with_usec` is `false` and the given argument String or DateTime. + + *mopp* + +Please check [7-2-stable](https://github.com/rails/rails/blob/7-2-stable/activesupport/CHANGELOG.md) for previous changes. diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/MIT-LICENSE b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/MIT-LICENSE new file mode 100644 index 00000000..f12cfa76 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) David Heinemeier Hansson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/README.rdoc b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/README.rdoc new file mode 100644 index 00000000..04e1f778 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/README.rdoc @@ -0,0 +1,40 @@ += Active Support -- Utility classes and Ruby extensions from \Rails + +Active Support is a collection of utility classes and standard library +extensions that were found useful for the \Rails framework. These additions +reside in this package so they can be loaded as needed in Ruby projects +outside of \Rails. + +You can read more about the extensions in the {Active Support Core Extensions}[https://guides.rubyonrails.org/active_support_core_extensions.html] guide. + +== Download and installation + +The latest version of Active Support can be installed with RubyGems: + + $ gem install activesupport + +Source code can be downloaded as part of the \Rails project on GitHub: + +* https://github.com/rails/rails/tree/main/activesupport + + +== License + +Active Support is released under the MIT license: + +* https://opensource.org/licenses/MIT + + +== Support + +API documentation is at: + +* https://api.rubyonrails.org + +Bug reports for the Ruby on \Rails project can be filed here: + +* https://github.com/rails/rails/issues + +Feature requests should be discussed on the rails-core mailing list here: + +* https://discuss.rubyonrails.org/c/rubyonrails-core diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support.rb new file mode 100644 index 00000000..fd729135 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) David Heinemeier Hansson +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ + +require "securerandom" +require "active_support/dependencies/autoload" +require "active_support/version" +require "active_support/deprecator" +require "active_support/logger" +require "active_support/broadcast_logger" +require "active_support/lazy_load_hooks" +require "active_support/core_ext/date_and_time/compatibility" + +# :include: ../README.rdoc +module ActiveSupport + extend ActiveSupport::Autoload + + autoload :Concern + autoload :CodeGenerator + autoload :ActionableError + autoload :ConfigurationFile + autoload :CurrentAttributes + autoload :Dependencies + autoload :DescendantsTracker + autoload :ExecutionWrapper + autoload :Executor + autoload :ErrorReporter + autoload :FileUpdateChecker + autoload :EventedFileUpdateChecker + autoload :ForkTracker + autoload :LogSubscriber + autoload :IsolatedExecutionState + autoload :Notifications + autoload :Reloader + autoload :SecureCompareRotator + + eager_autoload do + autoload :BacktraceCleaner + autoload :Benchmark + autoload :Benchmarkable + autoload :Cache + autoload :Callbacks + autoload :Configurable + autoload :ClassAttribute + autoload :Deprecation + autoload :Delegation + autoload :Digest + autoload :ExecutionContext + autoload :Gzip + autoload :Inflector + autoload :JSON + autoload :KeyGenerator + autoload :MessageEncryptor + autoload :MessageEncryptors + autoload :MessageVerifier + autoload :MessageVerifiers + autoload :Multibyte + autoload :NumberHelper + autoload :OptionMerger + autoload :OrderedHash + autoload :OrderedOptions + autoload :StringInquirer + autoload :EnvironmentInquirer + autoload :TaggedLogging + autoload :XmlMini + autoload :ArrayInquirer + end + + autoload :Rescuable + autoload :SafeBuffer, "active_support/core_ext/string/output_safety" + autoload :TestCase + + def self.eager_load! + super + + NumberHelper.eager_load! + end + + cattr_accessor :test_order # :nodoc: + cattr_accessor :test_parallelization_threshold, default: 50 # :nodoc: + + @error_reporter = ActiveSupport::ErrorReporter.new + singleton_class.attr_accessor :error_reporter # :nodoc: + + def self.cache_format_version + Cache.format_version + end + + def self.cache_format_version=(value) + Cache.format_version = value + end + + def self.to_time_preserves_timezone + DateAndTime::Compatibility.preserve_timezone + end + + def self.to_time_preserves_timezone=(value) + if !value + ActiveSupport.deprecator.warn( + "`to_time` will always preserve the receiver timezone rather than system local time in Rails 8.1. " \ + "To opt in to the new behavior, set `config.active_support.to_time_preserves_timezone = :zone`." + ) + elsif value != :zone + ActiveSupport.deprecator.warn( + "`to_time` will always preserve the full timezone rather than offset of the receiver in Rails 8.1. " \ + "To opt in to the new behavior, set `config.active_support.to_time_preserves_timezone = :zone`." + ) + end + + DateAndTime::Compatibility.preserve_timezone = value + end + + def self.utc_to_local_returns_utc_offset_times + DateAndTime::Compatibility.utc_to_local_returns_utc_offset_times + end + + def self.utc_to_local_returns_utc_offset_times=(value) + DateAndTime::Compatibility.utc_to_local_returns_utc_offset_times = value + end +end + +autoload :I18n, "active_support/i18n" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/actionable_error.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/actionable_error.rb new file mode 100644 index 00000000..8c05e563 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/actionable_error.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module ActiveSupport + # = Actionable Errors + # + # Actionable errors lets you define actions to resolve an error. + # + # To make an error actionable, include the +ActiveSupport::ActionableError+ + # module and invoke the +action+ class macro to define the action. An action + # needs a name and a block to execute. + module ActionableError + extend Concern + + class NonActionable < StandardError; end + + included do + class_attribute :_actions, default: {} + end + + def self.actions(error) # :nodoc: + case error + when ActionableError, -> it { Class === it && it < ActionableError } + error._actions + else + {} + end + end + + def self.dispatch(error, name) # :nodoc: + actions(error).fetch(name).call + rescue KeyError + raise NonActionable, "Cannot find action \"#{name}\"" + end + + module ClassMethods + # Defines an action that can resolve the error. + # + # class PendingMigrationError < MigrationError + # include ActiveSupport::ActionableError + # + # action "Run pending migrations" do + # ActiveRecord::Tasks::DatabaseTasks.migrate + # end + # end + def action(name, &block) + _actions[name] = block + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/all.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/all.rb new file mode 100644 index 00000000..4adf446a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/all.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require "active_support" +require "active_support/time" +require "active_support/core_ext" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/array_inquirer.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/array_inquirer.rb new file mode 100644 index 00000000..6fb62085 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/array_inquirer.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module ActiveSupport + # = \Array Inquirer + # + # Wrapping an array in an +ArrayInquirer+ gives a friendlier way to check + # its string-like contents: + # + # variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet]) + # + # variants.phone? # => true + # variants.tablet? # => true + # variants.desktop? # => false + class ArrayInquirer < Array + # Passes each element of +candidates+ collection to ArrayInquirer collection. + # The method returns true if any element from the ArrayInquirer collection + # is equal to the stringified or symbolized form of any element in the +candidates+ collection. + # + # If +candidates+ collection is not given, method returns true. + # + # variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet]) + # + # variants.any? # => true + # variants.any?(:phone, :tablet) # => true + # variants.any?('phone', 'desktop') # => true + # variants.any?(:desktop, :watch) # => false + def any?(*candidates) + if candidates.none? + super + else + candidates.any? do |candidate| + include?(candidate.to_sym) || include?(candidate.to_s) + end + end + end + + private + def respond_to_missing?(name, include_private = false) + name.end_with?("?") || super + end + + def method_missing(name, ...) + if name.end_with?("?") + any?(name[0..-2]) + else + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/backtrace_cleaner.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/backtrace_cleaner.rb new file mode 100644 index 00000000..0b9ec346 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/backtrace_cleaner.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true + +module ActiveSupport + # = Backtrace Cleaner + # + # Backtraces often include many lines that are not relevant for the context + # under review. This makes it hard to find the signal amongst the backtrace + # noise, and adds debugging time. With a BacktraceCleaner, filters and + # silencers are used to remove the noisy lines, so that only the most relevant + # lines remain. + # + # Filters are used to modify lines of data, while silencers are used to remove + # lines entirely. The typical filter use case is to remove lengthy path + # information from the start of each line, and view file paths relevant to the + # app directory instead of the file system root. The typical silencer use case + # is to exclude the output of a noisy library from the backtrace, so that you + # can focus on the rest. + # + # bc = ActiveSupport::BacktraceCleaner.new + # root = "#{Rails.root}/" + # bc.add_filter { |line| line.delete_prefix(root) } # strip the Rails.root prefix + # bc.add_silencer { |line| /puma|rubygems/.match?(line) } # skip any lines from puma or rubygems + # bc.clean(exception.backtrace) # perform the cleanup + # + # To reconfigure an existing BacktraceCleaner (like the default one in \Rails) + # and show as much data as possible, you can always call + # BacktraceCleaner#remove_silencers!, which will restore the + # backtrace to a pristine state. If you need to reconfigure an existing + # BacktraceCleaner so that it does not filter or modify the paths of any lines + # of the backtrace, you can call BacktraceCleaner#remove_filters! + # These two methods will give you a completely untouched backtrace. + # + # Inspired by the Quiet Backtrace gem by thoughtbot. + class BacktraceCleaner + def initialize + @filters, @silencers = [], [] + add_core_silencer + add_gem_filter + add_gem_silencer + add_stdlib_silencer + end + + # Returns the backtrace after all filters and silencers have been run + # against it. Filters run first, then silencers. + def clean(backtrace, kind = :silent) + filtered = filter_backtrace(backtrace) + + case kind + when :silent + silence(filtered) + when :noise + noise(filtered) + else + filtered + end + end + alias :filter :clean + + # Returns the frame with all filters applied. + # returns +nil+ if the frame was silenced. + def clean_frame(frame, kind = :silent) + frame = frame.to_s + @filters.each do |f| + frame = f.call(frame.to_s) + end + + case kind + when :silent + frame unless @silencers.any? { |s| s.call(frame) } + when :noise + frame if @silencers.any? { |s| s.call(frame) } + else + frame + end + end + + # Adds a filter from the block provided. Each line in the backtrace will be + # mapped against this filter. + # + # # Will turn "/my/rails/root/app/models/person.rb" into "app/models/person.rb" + # root = "#{Rails.root}/" + # backtrace_cleaner.add_filter { |line| line.delete_prefix(root) } + def add_filter(&block) + @filters << block + end + + # Adds a silencer from the block provided. If the silencer returns +true+ + # for a given line, it will be excluded from the clean backtrace. + # + # # Will reject all lines that include the word "puma", like "/gems/puma/server.rb" or "/app/my_puma_server/rb" + # backtrace_cleaner.add_silencer { |line| /puma/.match?(line) } + def add_silencer(&block) + @silencers << block + end + + # Removes all silencers, but leaves in the filters. Useful if your + # context of debugging suddenly expands as you suspect a bug in one of + # the libraries you use. + def remove_silencers! + @silencers = [] + end + + # Removes all filters, but leaves in the silencers. Useful if you suddenly + # need to see entire filepaths in the backtrace that you had already + # filtered out. + def remove_filters! + @filters = [] + end + + private + FORMATTED_GEMS_PATTERN = /\A[^\/]+ \([\w.]+\) / + + def initialize_copy(_other) + @filters = @filters.dup + @silencers = @silencers.dup + end + + def add_gem_filter + gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) } + return if gems_paths.empty? + + gems_regexp = %r{\A(#{gems_paths.join('|')})/(bundler/)?gems/([^/]+)-([\w.]+)/(.*)} + gems_result = '\3 (\4) \5' + add_filter { |line| line.sub(gems_regexp, gems_result) } + end + + def add_core_silencer + add_silencer { |line| line.include?(" 0.10007 + # + # ActiveSupport::Benchmark.realtime(:float_millisecond) { sleep 0.1 } + # # => 100.07 + # + # `unit` can be any of the values accepted by Ruby's `Process.clock_gettime`. + def self.realtime(unit = :float_second, &block) + time_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, unit) + yield + Process.clock_gettime(Process::CLOCK_MONOTONIC, unit) - time_start + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/benchmarkable.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/benchmarkable.rb new file mode 100644 index 00000000..8697e7c4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/benchmarkable.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/keys" + +module ActiveSupport + # = \Benchmarkable + module Benchmarkable + # Allows you to measure the execution time of a block in a template and + # records the result to the log. Wrap this block around expensive operations + # or possible bottlenecks to get a time reading for the operation. For + # example, let's say you thought your file processing method was taking too + # long; you could wrap it in a benchmark block. + # + # <% benchmark 'Process data files' do %> + # <%= expensive_files_operation %> + # <% end %> + # + # That would add something like "Process data files (345.2ms)" to the log, + # which you can then use to compare timings when optimizing your code. + # + # You may give an optional logger level (:debug, :info, + # :warn, :error) as the :level option. The + # default logger level value is :info. + # + # <% benchmark 'Low-level files', level: :debug do %> + # <%= lowlevel_files_operation %> + # <% end %> + # + # Finally, you can pass true as the third argument to silence all log + # activity (other than the timing information) from inside the block. This + # is great for boiling down a noisy block to just a single statement that + # produces one log line: + # + # <% benchmark 'Process data files', level: :info, silence: true do %> + # <%= expensive_and_chatty_files_operation %> + # <% end %> + def benchmark(message = "Benchmarking", options = {}, &block) + if logger + options.assert_valid_keys(:level, :silence) + options[:level] ||= :info + + result = nil + ms = ActiveSupport::Benchmark.realtime(:float_millisecond) do + result = options[:silence] ? logger.silence(&block) : yield + end + logger.public_send(options[:level], "%s (%.1fms)" % [ message, ms ]) + result + else + yield + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/broadcast_logger.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/broadcast_logger.rb new file mode 100644 index 00000000..ae3db205 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/broadcast_logger.rb @@ -0,0 +1,251 @@ +# frozen_string_literal: true + +module ActiveSupport + # = Active Support Broadcast Logger + # + # The Broadcast logger is a logger used to write messages to multiple IO. It is commonly used + # in development to display messages on STDOUT and also write them to a file (development.log). + # With the Broadcast logger, you can broadcast your logs to a unlimited number of sinks. + # + # The BroadcastLogger acts as a standard logger and all methods you are used to are available. + # However, all the methods on this logger will propagate and be delegated to the other loggers + # that are part of the broadcast. + # + # Broadcasting your logs. + # + # stdout_logger = Logger.new(STDOUT) + # file_logger = Logger.new("development.log") + # broadcast = BroadcastLogger.new(stdout_logger, file_logger) + # + # broadcast.info("Hello world!") # Writes the log to STDOUT and the development.log file. + # + # Add a logger to the broadcast. + # + # stdout_logger = Logger.new(STDOUT) + # broadcast = BroadcastLogger.new(stdout_logger) + # file_logger = Logger.new("development.log") + # broadcast.broadcast_to(file_logger) + # + # broadcast.info("Hello world!") # Writes the log to STDOUT and the development.log file. + # + # Modifying the log level for all broadcasted loggers. + # + # stdout_logger = Logger.new(STDOUT) + # file_logger = Logger.new("development.log") + # broadcast = BroadcastLogger.new(stdout_logger, file_logger) + # + # broadcast.level = Logger::FATAL # Modify the log level for the whole broadcast. + # + # Stop broadcasting log to a sink. + # + # stdout_logger = Logger.new(STDOUT) + # file_logger = Logger.new("development.log") + # broadcast = BroadcastLogger.new(stdout_logger, file_logger) + # broadcast.info("Hello world!") # Writes the log to STDOUT and the development.log file. + # + # broadcast.stop_broadcasting_to(file_logger) + # broadcast.info("Hello world!") # Writes the log *only* to STDOUT. + # + # At least one sink has to be part of the broadcast. Otherwise, your logs will not + # be written anywhere. For instance: + # + # broadcast = BroadcastLogger.new + # broadcast.info("Hello world") # The log message will appear nowhere. + # + # If you are adding a custom logger with custom methods to the broadcast, + # the `BroadcastLogger` will proxy them and return the raw value, or an array + # of raw values, depending on how many loggers in the broadcasts responded to + # the method: + # + # class MyLogger < ::Logger + # def loggable? + # true + # end + # end + # + # logger = BroadcastLogger.new + # logger.loggable? # => A NoMethodError exception is raised because no loggers in the broadcasts could respond. + # + # logger.broadcast_to(MyLogger.new(STDOUT)) + # logger.loggable? # => true + # logger.broadcast_to(MyLogger.new(STDOUT)) + # puts logger.broadcasts # => [MyLogger, MyLogger] + # logger.loggable? # [true, true] + class BroadcastLogger + include ActiveSupport::LoggerSilence + + # Returns all the logger that are part of this broadcast. + attr_reader :broadcasts + attr_reader :formatter + attr_accessor :progname + + def initialize(*loggers) + @broadcasts = [] + @progname = "Broadcast" + + broadcast_to(*loggers) + end + + # Add logger(s) to the broadcast. + # + # broadcast_logger = ActiveSupport::BroadcastLogger.new + # broadcast_logger.broadcast_to(Logger.new(STDOUT), Logger.new(STDERR)) + def broadcast_to(*loggers) + @broadcasts.concat(loggers) + end + + # Remove a logger from the broadcast. When a logger is removed, messages sent to + # the broadcast will no longer be written to its sink. + # + # sink = Logger.new(STDOUT) + # broadcast_logger = ActiveSupport::BroadcastLogger.new + # + # broadcast_logger.stop_broadcasting_to(sink) + def stop_broadcasting_to(logger) + @broadcasts.delete(logger) + end + + def level + @broadcasts.map(&:level).min + end + + def <<(message) + dispatch { |logger| logger.<<(message) } + end + + def add(...) + dispatch { |logger| logger.add(...) } + end + alias_method :log, :add + + def debug(...) + dispatch { |logger| logger.debug(...) } + end + + def info(...) + dispatch { |logger| logger.info(...) } + end + + def warn(...) + dispatch { |logger| logger.warn(...) } + end + + def error(...) + dispatch { |logger| logger.error(...) } + end + + def fatal(...) + dispatch { |logger| logger.fatal(...) } + end + + def unknown(...) + dispatch { |logger| logger.unknown(...) } + end + + def formatter=(formatter) + dispatch { |logger| logger.formatter = formatter } + + @formatter = formatter + end + + def level=(level) + dispatch { |logger| logger.level = level } + end + alias_method :sev_threshold=, :level= + + def local_level=(level) + dispatch do |logger| + logger.local_level = level if logger.respond_to?(:local_level=) + end + end + + def close + dispatch { |logger| logger.close } + end + + # True if the log level allows entries with severity +Logger::DEBUG+ to be written + # to at least one broadcast. False otherwise. + def debug? + @broadcasts.any? { |logger| logger.debug? } + end + + # Sets the log level to +Logger::DEBUG+ for the whole broadcast. + def debug! + dispatch { |logger| logger.debug! } + end + + # True if the log level allows entries with severity +Logger::INFO+ to be written + # to at least one broadcast. False otherwise. + def info? + @broadcasts.any? { |logger| logger.info? } + end + + # Sets the log level to +Logger::INFO+ for the whole broadcast. + def info! + dispatch { |logger| logger.info! } + end + + # True if the log level allows entries with severity +Logger::WARN+ to be written + # to at least one broadcast. False otherwise. + def warn? + @broadcasts.any? { |logger| logger.warn? } + end + + # Sets the log level to +Logger::WARN+ for the whole broadcast. + def warn! + dispatch { |logger| logger.warn! } + end + + # True if the log level allows entries with severity +Logger::ERROR+ to be written + # to at least one broadcast. False otherwise. + def error? + @broadcasts.any? { |logger| logger.error? } + end + + # Sets the log level to +Logger::ERROR+ for the whole broadcast. + def error! + dispatch { |logger| logger.error! } + end + + # True if the log level allows entries with severity +Logger::FATAL+ to be written + # to at least one broadcast. False otherwise. + def fatal? + @broadcasts.any? { |logger| logger.fatal? } + end + + # Sets the log level to +Logger::FATAL+ for the whole broadcast. + def fatal! + dispatch { |logger| logger.fatal! } + end + + def initialize_copy(other) + @broadcasts = [] + @progname = other.progname.dup + @formatter = other.formatter.dup + + broadcast_to(*other.broadcasts.map(&:dup)) + end + + private + def dispatch(&block) + @broadcasts.each { |logger| block.call(logger) } + true + end + + def method_missing(name, ...) + loggers = @broadcasts.select { |logger| logger.respond_to?(name) } + + if loggers.none? + super + elsif loggers.one? + loggers.first.send(name, ...) + else + loggers.map { |logger| logger.send(name, ...) } + end + end + + def respond_to_missing?(method, include_all) + @broadcasts.any? { |logger| logger.respond_to?(method, include_all) } + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/builder.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/builder.rb new file mode 100644 index 00000000..cd49ec6b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/builder.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +begin + require "builder" +rescue LoadError => e + warn "You don't have builder installed in your application. Please add it to your Gemfile and run bundle install" + raise e +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache.rb new file mode 100644 index 00000000..46b1c448 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache.rb @@ -0,0 +1,1106 @@ +# frozen_string_literal: true + +require "zlib" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/enumerable" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/numeric/bytes" +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/try" +require "active_support/core_ext/string/inflections" +require_relative "cache/coder" +require_relative "cache/entry" +require_relative "cache/serializer_with_fallback" + +module ActiveSupport + # See ActiveSupport::Cache::Store for documentation. + module Cache + autoload :FileStore, "active_support/cache/file_store" + autoload :MemoryStore, "active_support/cache/memory_store" + autoload :MemCacheStore, "active_support/cache/mem_cache_store" + autoload :NullStore, "active_support/cache/null_store" + autoload :RedisCacheStore, "active_support/cache/redis_cache_store" + + # These options mean something to all cache implementations. Individual cache + # implementations may support additional options. + UNIVERSAL_OPTIONS = [ + :coder, + :compress, + :compress_threshold, + :compressor, + :expire_in, + :expired_in, + :expires_in, + :namespace, + :race_condition_ttl, + :serializer, + :skip_nil, + ] + + # Mapping of canonical option names to aliases that a store will recognize. + OPTION_ALIASES = { + expires_in: [:expire_in, :expired_in] + }.freeze + + DEFAULT_COMPRESS_LIMIT = 1.kilobyte + + # Raised by coders when the cache entry can't be deserialized. + # This error is treated as a cache miss. + DeserializationError = Class.new(StandardError) + + module Strategy + autoload :LocalCache, "active_support/cache/strategy/local_cache" + end + + @format_version = 7.0 + + class << self + attr_accessor :format_version + + # Creates a new Store object according to the given options. + # + # If no arguments are passed to this method, then a new + # ActiveSupport::Cache::MemoryStore object will be returned. + # + # If you pass a Symbol as the first argument, then a corresponding cache + # store class under the ActiveSupport::Cache namespace will be created. + # For example: + # + # ActiveSupport::Cache.lookup_store(:memory_store) + # # => returns a new ActiveSupport::Cache::MemoryStore object + # + # ActiveSupport::Cache.lookup_store(:mem_cache_store) + # # => returns a new ActiveSupport::Cache::MemCacheStore object + # + # Any additional arguments will be passed to the corresponding cache store + # class's constructor: + # + # ActiveSupport::Cache.lookup_store(:file_store, '/tmp/cache') + # # => same as: ActiveSupport::Cache::FileStore.new('/tmp/cache') + # + # If the first argument is not a Symbol, then it will simply be returned: + # + # ActiveSupport::Cache.lookup_store(MyOwnCacheStore.new) + # # => returns MyOwnCacheStore.new + def lookup_store(store = nil, *parameters) + case store + when Symbol + options = parameters.extract_options! + retrieve_store_class(store).new(*parameters, **options) + when Array + lookup_store(*store) + when nil + ActiveSupport::Cache::MemoryStore.new + else + store + end + end + + # Expands out the +key+ argument into a key that can be used for the + # cache store. Optionally accepts a namespace, and all keys will be + # scoped within that namespace. + # + # If the +key+ argument provided is an array, or responds to +to_a+, then + # each of elements in the array will be turned into parameters/keys and + # concatenated into a single key. For example: + # + # ActiveSupport::Cache.expand_cache_key([:foo, :bar]) # => "foo/bar" + # ActiveSupport::Cache.expand_cache_key([:foo, :bar], "namespace") # => "namespace/foo/bar" + # + # The +key+ argument can also respond to +cache_key+ or +to_param+. + def expand_cache_key(key, namespace = nil) + expanded_cache_key = namespace ? +"#{namespace}/" : +"" + + if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"] + expanded_cache_key << "#{prefix}/" + end + + expanded_cache_key << retrieve_cache_key(key) + expanded_cache_key + end + + private + def retrieve_cache_key(key) + case + when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version + when key.respond_to?(:cache_key) then key.cache_key + when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param + when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a) + else key.to_param + end.to_s + end + + # Obtains the specified cache store class, given the name of the +store+. + # Raises an error when the store class cannot be found. + def retrieve_store_class(store) + # require_relative cannot be used here because the class might be + # provided by another gem, like redis-activesupport for example. + require "active_support/cache/#{store}" + rescue LoadError => e + raise "Could not find cache store adapter for #{store} (#{e})" + else + ActiveSupport::Cache.const_get(store.to_s.camelize) + end + end + + # = Active Support \Cache \Store + # + # An abstract cache store class. There are multiple cache store + # implementations, each having its own additional features. See the classes + # under the ActiveSupport::Cache module, e.g. + # ActiveSupport::Cache::MemCacheStore. MemCacheStore is currently the most + # popular cache store for large production websites. + # + # Some implementations may not support all methods beyond the basic cache + # methods of #fetch, #write, #read, #exist?, and #delete. + # + # +ActiveSupport::Cache::Store+ can store any Ruby object that is supported + # by its +coder+'s +dump+ and +load+ methods. + # + # cache = ActiveSupport::Cache::MemoryStore.new + # + # cache.read('city') # => nil + # cache.write('city', "Duckburgh") # => true + # cache.read('city') # => "Duckburgh" + # + # cache.write('not serializable', Proc.new {}) # => TypeError + # + # Keys are always translated into Strings and are case sensitive. When an + # object is specified as a key and has a +cache_key+ method defined, this + # method will be called to define the key. Otherwise, the +to_param+ + # method will be called. Hashes and Arrays can also be used as keys. The + # elements will be delimited by slashes, and the elements within a Hash + # will be sorted by key so they are consistent. + # + # cache.read('city') == cache.read(:city) # => true + # + # Nil values can be cached. + # + # If your cache is on a shared infrastructure, you can define a namespace + # for your cache entries. If a namespace is defined, it will be prefixed on + # to every key. The namespace can be either a static value or a Proc. If it + # is a Proc, it will be invoked when each key is evaluated so that you can + # use application logic to invalidate keys. + # + # cache.namespace = -> { @last_mod_time } # Set the namespace to a variable + # @last_mod_time = Time.now # Invalidate the entire cache by changing namespace + # + class Store + cattr_accessor :logger, instance_writer: true + cattr_accessor :raise_on_invalid_cache_expiration_time, default: false + + attr_reader :silence, :options + alias :silence? :silence + + class << self + private + DEFAULT_POOL_OPTIONS = { size: 5, timeout: 5 }.freeze + private_constant :DEFAULT_POOL_OPTIONS + + def retrieve_pool_options(options) + if options.key?(:pool) + pool_options = options.delete(:pool) + else + pool_options = true + end + + case pool_options + when false, nil + return false + when true + pool_options = DEFAULT_POOL_OPTIONS + when Hash + pool_options[:size] = Integer(pool_options[:size]) if pool_options.key?(:size) + pool_options[:timeout] = Float(pool_options[:timeout]) if pool_options.key?(:timeout) + pool_options = DEFAULT_POOL_OPTIONS.merge(pool_options) + else + raise TypeError, "Invalid :pool argument, expected Hash, got: #{pool_options.inspect}" + end + + pool_options unless pool_options.empty? + end + end + + # Creates a new cache. + # + # ==== Options + # + # [+:namespace+] + # Sets the namespace for the cache. This option is especially useful if + # your application shares a cache with other applications. + # + # [+:serializer+] + # The serializer for cached values. Must respond to +dump+ and +load+. + # + # The default serializer depends on the cache format version (set via + # +config.active_support.cache_format_version+ when using Rails). The + # default serializer for each format version includes a fallback + # mechanism to deserialize values from any format version. This behavior + # makes it easy to migrate between format versions without invalidating + # the entire cache. + # + # You can also specify serializer: :message_pack to use a + # preconfigured serializer based on ActiveSupport::MessagePack. The + # +:message_pack+ serializer includes the same deserialization fallback + # mechanism, allowing easy migration from (or to) the default + # serializer. The +:message_pack+ serializer may improve performance, + # but it requires the +msgpack+ gem. + # + # [+:compressor+] + # The compressor for serialized cache values. Must respond to +deflate+ + # and +inflate+. + # + # The default compressor is +Zlib+. To define a new custom compressor + # that also decompresses old cache entries, you can check compressed + # values for Zlib's "\x78" signature: + # + # module MyCompressor + # def self.deflate(dumped) + # # compression logic... (make sure result does not start with "\x78"!) + # end + # + # def self.inflate(compressed) + # if compressed.start_with?("\x78") + # Zlib.inflate(compressed) + # else + # # decompression logic... + # end + # end + # end + # + # ActiveSupport::Cache.lookup_store(:redis_cache_store, compressor: MyCompressor) + # + # [+:coder+] + # The coder for serializing and (optionally) compressing cache entries. + # Must respond to +dump+ and +load+. + # + # The default coder composes the serializer and compressor, and includes + # some performance optimizations. If you only need to override the + # serializer or compressor, you should specify the +:serializer+ or + # +:compressor+ options instead. + # + # If the store can handle cache entries directly, you may also specify + # coder: nil to omit the serializer, compressor, and coder. For + # example, if you are using ActiveSupport::Cache::MemoryStore and can + # guarantee that cache values will not be mutated, you can specify + # coder: nil to avoid the overhead of safeguarding against + # mutation. + # + # The +:coder+ option is mutually exclusive with the +:serializer+ and + # +:compressor+ options. Specifying them together will raise an + # +ArgumentError+. + # + # Any other specified options are treated as default options for the + # relevant cache operations, such as #read, #write, and #fetch. + def initialize(options = nil) + @options = options ? validate_options(normalize_options(options)) : {} + + @options[:compress] = true unless @options.key?(:compress) + @options[:compress_threshold] ||= DEFAULT_COMPRESS_LIMIT + + @coder = @options.delete(:coder) do + legacy_serializer = Cache.format_version < 7.1 && !@options[:serializer] + serializer = @options.delete(:serializer) || default_serializer + serializer = Cache::SerializerWithFallback[serializer] if serializer.is_a?(Symbol) + compressor = @options.delete(:compressor) { Zlib } + + Cache::Coder.new(serializer, compressor, legacy_serializer: legacy_serializer) + end + + @coder ||= Cache::SerializerWithFallback[:passthrough] + + @coder_supports_compression = @coder.respond_to?(:dump_compressed) + end + + # Silences the logger. + def silence! + @silence = true + self + end + + # Silences the logger within a block. + def mute + previous_silence, @silence = @silence, true + yield + ensure + @silence = previous_silence + end + + # Fetches data from the cache, using the given key. If there is data in + # the cache with the given key, then that data is returned. + # + # If there is no such data in the cache (a cache miss), then +nil+ will be + # returned. However, if a block has been passed, that block will be passed + # the key and executed in the event of a cache miss. The return value of the + # block will be written to the cache under the given cache key, and that + # return value will be returned. + # + # cache.write('today', 'Monday') + # cache.fetch('today') # => "Monday" + # + # cache.fetch('city') # => nil + # cache.fetch('city') do + # 'Duckburgh' + # end + # cache.fetch('city') # => "Duckburgh" + # + # ==== Options + # + # Internally, +fetch+ calls +read_entry+, and calls +write_entry+ on a + # cache miss. Thus, +fetch+ supports the same options as #read and #write. + # Additionally, +fetch+ supports the following options: + # + # * force: true - Forces a cache "miss," meaning we treat the + # cache value as missing even if it's present. Passing a block is + # required when +force+ is true so this always results in a cache write. + # + # cache.write('today', 'Monday') + # cache.fetch('today', force: true) { 'Tuesday' } # => 'Tuesday' + # cache.fetch('today', force: true) # => ArgumentError + # + # The +:force+ option is useful when you're calling some other method to + # ask whether you should force a cache write. Otherwise, it's clearer to + # just call +write+. + # + # * skip_nil: true - Prevents caching a nil result: + # + # cache.fetch('foo') { nil } + # cache.fetch('bar', skip_nil: true) { nil } + # cache.exist?('foo') # => true + # cache.exist?('bar') # => false + # + # * +:race_condition_ttl+ - Specifies the number of seconds during which + # an expired value can be reused while a new value is being generated. + # This can be used to prevent race conditions when cache entries expire, + # by preventing multiple processes from simultaneously regenerating the + # same entry (also known as the dog pile effect). + # + # When a process encounters a cache entry that has expired less than + # +:race_condition_ttl+ seconds ago, it will bump the expiration time by + # +:race_condition_ttl+ seconds before generating a new value. During + # this extended time window, while the process generates a new value, + # other processes will continue to use the old value. After the first + # process writes the new value, other processes will then use it. + # + # If the first process errors out while generating a new value, another + # process can try to generate a new value after the extended time window + # has elapsed. + # + # # Set all values to expire after one second. + # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1) + # + # cache.write("foo", "original value") + # val_1 = nil + # val_2 = nil + # p cache.read("foo") # => "original value" + # + # sleep 1 # wait until the cache expires + # + # t1 = Thread.new do + # # fetch does the following: + # # 1. gets an recent expired entry + # # 2. extends the expiry by 2 seconds (race_condition_ttl) + # # 3. regenerates the new value + # val_1 = cache.fetch("foo", race_condition_ttl: 2) do + # sleep 1 + # "new value 1" + # end + # end + # + # # Wait until t1 extends the expiry of the entry + # # but before generating the new value + # sleep 0.1 + # + # val_2 = cache.fetch("foo", race_condition_ttl: 2) do + # # This block won't be executed because t1 extended the expiry + # "new value 2" + # end + # + # t1.join + # + # p val_1 # => "new value 1" + # p val_2 # => "original value" + # p cache.fetch("foo") # => "new value 1" + # + # # The entry requires 3 seconds to expire (expires_in + race_condition_ttl) + # # We have waited 2 seconds already (sleep(1) + t1.join) thus we need to wait 1 + # # more second to see the entry expire. + # sleep 1 + # + # p cache.fetch("foo") # => nil + # + # ==== Dynamic Options + # + # In some cases it may be necessary to dynamically compute options based + # on the cached value. To support this, an ActiveSupport::Cache::WriteOptions + # instance is passed as the second argument to the block. For example: + # + # cache.fetch("authentication-token:#{user.id}") do |key, options| + # token = authenticate_to_service + # options.expires_at = token.expires_at + # token + # end + # + def fetch(name, options = nil, &block) + if block_given? + options = merged_options(options) + key = normalize_key(name, options) + + entry = nil + unless options[:force] + instrument(:read, key, options) do |payload| + cached_entry = read_entry(key, **options, event: payload) + entry = handle_expired_entry(cached_entry, key, options) + if entry + if entry.mismatched?(normalize_version(name, options)) + entry = nil + else + begin + entry.value + rescue DeserializationError + entry = nil + end + end + end + payload[:super_operation] = :fetch if payload + payload[:hit] = !!entry if payload + end + end + + if entry + get_entry_value(entry, name, options) + else + save_block_result_to_cache(name, key, options, &block) + end + elsif options && options[:force] + raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block." + else + read(name, options) + end + end + + # Reads data from the cache, using the given key. If there is data in + # the cache with the given key, then that data is returned. Otherwise, + # +nil+ is returned. + # + # Note, if data was written with the :expires_in or + # :version options, both of these conditions are applied before + # the data is returned. + # + # ==== Options + # + # * +:namespace+ - Replace the store namespace for this call. + # * +:version+ - Specifies a version for the cache entry. If the cached + # version does not match the requested version, the read will be treated + # as a cache miss. This feature is used to support recyclable cache keys. + # + # Other options will be handled by the specific cache store implementation. + def read(name, options = nil) + options = merged_options(options) + key = normalize_key(name, options) + version = normalize_version(name, options) + + instrument(:read, key, options) do |payload| + entry = read_entry(key, **options, event: payload) + + if entry + if entry.expired? + delete_entry(key, **options) + payload[:hit] = false if payload + nil + elsif entry.mismatched?(version) + payload[:hit] = false if payload + nil + else + payload[:hit] = true if payload + begin + entry.value + rescue DeserializationError + payload[:hit] = false + nil + end + end + else + payload[:hit] = false if payload + nil + end + end + end + + # Reads multiple values at once from the cache. Options can be passed + # in the last argument. + # + # Some cache implementation may optimize this method. + # + # Returns a hash mapping the names provided to the values found. + def read_multi(*names) + return {} if names.empty? + + options = names.extract_options! + options = merged_options(options) + keys = names.map { |name| normalize_key(name, options) } + + instrument_multi :read_multi, keys, options do |payload| + read_multi_entries(names, **options, event: payload).tap do |results| + payload[:hits] = results.keys.map { |name| normalize_key(name, options) } + end + end + end + + # Cache Storage API to write multiple values at once. + def write_multi(hash, options = nil) + return hash if hash.empty? + + options = merged_options(options) + normalized_hash = hash.transform_keys { |key| normalize_key(key, options) } + + instrument_multi :write_multi, normalized_hash, options do |payload| + entries = hash.each_with_object({}) do |(name, value), memo| + memo[normalize_key(name, options)] = Entry.new(value, **options.merge(version: normalize_version(name, options))) + end + + write_multi_entries entries, **options + end + end + + # Fetches data from the cache, using the given keys. If there is data in + # the cache with the given keys, then that data is returned. Otherwise, + # the supplied block is called for each key for which there was no data, + # and the result will be written to the cache and returned. + # Therefore, you need to pass a block that returns the data to be written + # to the cache. If you do not want to write the cache when the cache is + # not found, use #read_multi. + # + # Returns a hash with the data for each of the names. For example: + # + # cache.write("bim", "bam") + # cache.fetch_multi("bim", "unknown_key") do |key| + # "Fallback value for key: #{key}" + # end + # # => { "bim" => "bam", + # # "unknown_key" => "Fallback value for key: unknown_key" } + # + # You may also specify additional options via the +options+ argument. See #fetch for details. + # Other options are passed to the underlying cache implementation. For example: + # + # cache.fetch_multi("fizz", expires_in: 5.seconds) do |key| + # "buzz" + # end + # # => {"fizz"=>"buzz"} + # cache.read("fizz") + # # => "buzz" + # sleep(6) + # cache.read("fizz") + # # => nil + def fetch_multi(*names) + raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given? + return {} if names.empty? + + options = names.extract_options! + options = merged_options(options) + keys = names.map { |name| normalize_key(name, options) } + writes = {} + ordered = instrument_multi :read_multi, keys, options do |payload| + if options[:force] + reads = {} + else + reads = read_multi_entries(names, **options) + end + + ordered = names.index_with do |name| + reads.fetch(name) { writes[name] = yield(name) } + end + writes.compact! if options[:skip_nil] + + payload[:hits] = reads.keys.map { |name| normalize_key(name, options) } + payload[:super_operation] = :fetch_multi + + ordered + end + + write_multi(writes, options) + + ordered + end + + # Writes the value to the cache with the key. The value must be supported + # by the +coder+'s +dump+ and +load+ methods. + # + # Returns +true+ if the write succeeded, +nil+ if there was an error talking + # to the cache backend, or +false+ if the write failed for another reason. + # + # By default, cache entries larger than 1kB are compressed. Compression + # allows more data to be stored in the same memory footprint, leading to + # fewer cache evictions and higher hit rates. + # + # ==== Options + # + # * compress: false - Disables compression of the cache entry. + # + # * +:compress_threshold+ - The compression threshold, specified in bytes. + # \Cache entries larger than this threshold will be compressed. Defaults + # to +1.kilobyte+. + # + # * +:expires_in+ - Sets a relative expiration time for the cache entry, + # specified in seconds. +:expire_in+ and +:expired_in+ are aliases for + # +:expires_in+. + # + # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes) + # cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry + # + # * +:expires_at+ - Sets an absolute expiration time for the cache entry. + # + # cache = ActiveSupport::Cache::MemoryStore.new + # cache.write(key, value, expires_at: Time.now.at_end_of_hour) + # + # * +:version+ - Specifies a version for the cache entry. When reading + # from the cache, if the cached version does not match the requested + # version, the read will be treated as a cache miss. This feature is + # used to support recyclable cache keys. + # + # Other options will be handled by the specific cache store implementation. + def write(name, value, options = nil) + options = merged_options(options) + key = normalize_key(name, options) + + instrument(:write, key, options) do + entry = Entry.new(value, **options.merge(version: normalize_version(name, options))) + write_entry(key, entry, **options) + end + end + + # Deletes an entry in the cache. Returns +true+ if an entry is deleted + # and +false+ otherwise. + # + # Options are passed to the underlying cache implementation. + def delete(name, options = nil) + options = merged_options(options) + key = normalize_key(name, options) + + instrument(:delete, key, options) do + delete_entry(key, **options) + end + end + + # Deletes multiple entries in the cache. Returns the number of deleted + # entries. + # + # Options are passed to the underlying cache implementation. + def delete_multi(names, options = nil) + return 0 if names.empty? + + options = merged_options(options) + names.map! { |key| normalize_key(key, options) } + + instrument_multi(:delete_multi, names, options) do + delete_multi_entries(names, **options) + end + end + + # Returns +true+ if the cache contains an entry for the given key. + # + # Options are passed to the underlying cache implementation. + def exist?(name, options = nil) + options = merged_options(options) + key = normalize_key(name, options) + + instrument(:exist?, key) do |payload| + entry = read_entry(key, **options, event: payload) + (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false + end + end + + def new_entry(value, options = nil) # :nodoc: + Entry.new(value, **merged_options(options)) + end + + # Deletes all entries with keys matching the pattern. + # + # Options are passed to the underlying cache implementation. + # + # Some implementations may not support this method. + def delete_matched(matcher, options = nil) + raise NotImplementedError.new("#{self.class.name} does not support delete_matched") + end + + # Increments an integer value in the cache. + # + # Options are passed to the underlying cache implementation. + # + # Some implementations may not support this method. + def increment(name, amount = 1, options = nil) + raise NotImplementedError.new("#{self.class.name} does not support increment") + end + + # Decrements an integer value in the cache. + # + # Options are passed to the underlying cache implementation. + # + # Some implementations may not support this method. + def decrement(name, amount = 1, options = nil) + raise NotImplementedError.new("#{self.class.name} does not support decrement") + end + + # Cleans up the cache by removing expired entries. + # + # Options are passed to the underlying cache implementation. + # + # Some implementations may not support this method. + def cleanup(options = nil) + raise NotImplementedError.new("#{self.class.name} does not support cleanup") + end + + # Clears the entire cache. Be careful with this method since it could + # affect other processes if shared cache is being used. + # + # The options hash is passed to the underlying cache implementation. + # + # Some implementations may not support this method. + def clear(options = nil) + raise NotImplementedError.new("#{self.class.name} does not support clear") + end + + private + def default_serializer + case Cache.format_version + when 7.0 + Cache::SerializerWithFallback[:marshal_7_0] + when 7.1 + Cache::SerializerWithFallback[:marshal_7_1] + else + raise ArgumentError, "Unrecognized ActiveSupport::Cache.format_version: #{Cache.format_version.inspect}" + end + end + + # Adds the namespace defined in the options to a pattern designed to + # match keys. Implementations that support delete_matched should call + # this method to translate a pattern that matches names into one that + # matches namespaced keys. + def key_matcher(pattern, options) # :doc: + prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace] + if prefix + source = pattern.source + if source.start_with?("^") + source = source[1, source.length] + else + source = ".*#{source[0, source.length]}" + end + Regexp.new("^#{Regexp.escape(prefix)}:#{source}", pattern.options) + else + pattern + end + end + + # Reads an entry from the cache implementation. Subclasses must implement + # this method. + def read_entry(key, **options) + raise NotImplementedError.new + end + + # Writes an entry to the cache implementation. Subclasses must implement + # this method. + def write_entry(key, entry, **options) + raise NotImplementedError.new + end + + def serialize_entry(entry, **options) + options = merged_options(options) + if @coder_supports_compression && options[:compress] + @coder.dump_compressed(entry, options[:compress_threshold]) + else + @coder.dump(entry) + end + end + + def deserialize_entry(payload, **) + payload.nil? ? nil : @coder.load(payload) + rescue DeserializationError + nil + end + + # Reads multiple entries from the cache implementation. Subclasses MAY + # implement this method. + def read_multi_entries(names, **options) + names.each_with_object({}) do |name, results| + key = normalize_key(name, options) + entry = read_entry(key, **options) + + next unless entry + + version = normalize_version(name, options) + + if entry.expired? + delete_entry(key, **options) + elsif !entry.mismatched?(version) + results[name] = entry.value + end + end + end + + # Writes multiple entries to the cache implementation. Subclasses MAY + # implement this method. + def write_multi_entries(hash, **options) + hash.each do |key, entry| + write_entry key, entry, **options + end + end + + # Deletes an entry from the cache implementation. Subclasses must + # implement this method. + def delete_entry(key, **options) + raise NotImplementedError.new + end + + # Deletes multiples entries in the cache implementation. Subclasses MAY + # implement this method. + def delete_multi_entries(entries, **options) + entries.count { |key| delete_entry(key, **options) } + end + + # Merges the default options with ones specific to a method call. + def merged_options(call_options) + if call_options + call_options = normalize_options(call_options) + if call_options.key?(:expires_in) && call_options.key?(:expires_at) + raise ArgumentError, "Either :expires_in or :expires_at can be supplied, but not both" + end + + expires_at = call_options.delete(:expires_at) + call_options[:expires_in] = (expires_at - Time.now) if expires_at + + if call_options[:expires_in].is_a?(Time) + expires_in = call_options[:expires_in] + raise ArgumentError.new("expires_in parameter should not be a Time. Did you mean to use expires_at? Got: #{expires_in}") + end + if call_options[:expires_in]&.negative? + expires_in = call_options.delete(:expires_in) + handle_invalid_expires_in("Cache expiration time is invalid, cannot be negative: #{expires_in}") + end + + if options.empty? + call_options + else + options.merge(call_options) + end + else + options + end + end + + def handle_invalid_expires_in(message) + error = ArgumentError.new(message) + if ActiveSupport::Cache::Store.raise_on_invalid_cache_expiration_time + raise error + else + ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning) + logger.error("#{error.class}: #{error.message}") if logger + end + end + + # Normalize aliased options to their canonical form + def normalize_options(options) + options = options.dup + OPTION_ALIASES.each do |canonical_name, aliases| + alias_key = aliases.detect { |key| options.key?(key) } + options[canonical_name] ||= options[alias_key] if alias_key + options.except!(*aliases) + end + + options + end + + def validate_options(options) + if options.key?(:coder) && options[:serializer] + raise ArgumentError, "Cannot specify :serializer and :coder options together" + end + + if options.key?(:coder) && options[:compressor] + raise ArgumentError, "Cannot specify :compressor and :coder options together" + end + + if Cache.format_version < 7.1 && !options[:serializer] && options[:compressor] + raise ArgumentError, "Cannot specify :compressor option when using" \ + " default serializer and cache format version is < 7.1" + end + + options + end + + # Expands and namespaces the cache key. + # Raises an exception when the key is +nil+ or an empty string. + # May be overridden by cache stores to do additional normalization. + def normalize_key(key, options = nil) + str_key = expanded_key(key) + raise(ArgumentError, "key cannot be blank") if !str_key || str_key.empty? + + namespace_key str_key, options + end + + # Prefix the key with a namespace string: + # + # namespace_key 'foo', namespace: 'cache' + # # => 'cache:foo' + # + # With a namespace block: + # + # namespace_key 'foo', namespace: -> { 'cache' } + # # => 'cache:foo' + def namespace_key(key, call_options = nil) + namespace = if call_options&.key?(:namespace) + call_options[:namespace] + else + options[:namespace] + end + + if namespace.respond_to?(:call) + namespace = namespace.call + end + + if key && key.encoding != Encoding::UTF_8 + key = key.dup.force_encoding(Encoding::UTF_8) + end + + if namespace + "#{namespace}:#{key}" + else + key + end + end + + # Expands key to be a consistent string value. Invokes +cache_key+ if + # object responds to +cache_key+. Otherwise, +to_param+ method will be + # called. If the key is a Hash, then keys will be sorted alphabetically. + def expanded_key(key) + return key.cache_key.to_s if key.respond_to?(:cache_key) + + case key + when Array + if key.size > 1 + key.collect { |element| expanded_key(element) } + else + expanded_key(key.first) + end + when Hash + key.collect { |k, v| "#{k}=#{v}" }.sort! + else + key + end.to_param + end + + def normalize_version(key, options = nil) + (options && options[:version].try(:to_param)) || expanded_version(key) + end + + def expanded_version(key) + case + when key.respond_to?(:cache_version) then key.cache_version.to_param + when key.is_a?(Array) then key.map { |element| expanded_version(element) }.tap(&:compact!).to_param + when key.respond_to?(:to_a) then expanded_version(key.to_a) + end + end + + def instrument(operation, key, options = nil, &block) + _instrument(operation, key: key, options: options, &block) + end + + def instrument_multi(operation, keys, options = nil, &block) + _instrument(operation, multi: true, key: keys, options: options, &block) + end + + def _instrument(operation, multi: false, options: nil, **payload, &block) + if logger && logger.debug? && !silence? + debug_key = + if multi + ": #{payload[:key].size} key(s) specified" + elsif payload[:key] + ": #{payload[:key]}" + end + + debug_options = " (#{options.inspect})" unless options.blank? + + logger.debug "Cache #{operation}#{debug_key}#{debug_options}" + end + + payload[:store] = self.class.name + payload.merge!(options) if options.is_a?(Hash) + ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) do + block&.call(payload) + end + end + + def handle_expired_entry(entry, key, options) + if entry && entry.expired? + race_ttl = options[:race_condition_ttl].to_i + if (race_ttl > 0) && (Time.now.to_f - entry.expires_at <= race_ttl) + # When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache + # for a brief period while the entry is being recalculated. + entry.expires_at = Time.now.to_f + race_ttl + write_entry(key, entry, **options, expires_in: race_ttl * 2) + else + delete_entry(key, **options) + end + entry = nil + end + entry + end + + def get_entry_value(entry, name, options) + instrument(:fetch_hit, name, options) + entry.value + end + + def save_block_result_to_cache(name, key, options) + options = options.dup + + result = instrument(:generate, key, options) do + yield(name, WriteOptions.new(options)) + end + + write(name, result, options) unless result.nil? && options[:skip_nil] + result + end + end + + # Enables the dynamic configuration of Cache entry options while ensuring + # that conflicting options are not both set. When a block is given to + # ActiveSupport::Cache::Store#fetch, the second argument will be an + # instance of +WriteOptions+. + class WriteOptions + def initialize(options) # :nodoc: + @options = options + end + + def version + @options[:version] + end + + def version=(version) + @options[:version] = version + end + + def expires_in + @options[:expires_in] + end + + # Sets the Cache entry's +expires_in+ value. If an +expires_at+ option was + # previously set, this will unset it since +expires_in+ and +expires_at+ + # cannot both be set. + def expires_in=(expires_in) + @options.delete(:expires_at) + @options[:expires_in] = expires_in + end + + def expires_at + @options[:expires_at] + end + + # Sets the Cache entry's +expires_at+ value. If an +expires_in+ option was + # previously set, this will unset it since +expires_at+ and +expires_in+ + # cannot both be set. + def expires_at=(expires_at) + @options.delete(:expires_in) + @options[:expires_at] = expires_at + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/coder.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/coder.rb new file mode 100644 index 00000000..b21d07dc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/coder.rb @@ -0,0 +1,153 @@ +# frozen_string_literal: true + +require_relative "entry" + +module ActiveSupport + module Cache + class Coder # :nodoc: + def initialize(serializer, compressor, legacy_serializer: false) + @serializer = serializer + @compressor = compressor + @legacy_serializer = legacy_serializer + end + + def dump(entry) + return @serializer.dump(entry) if @legacy_serializer + + dump_compressed(entry, Float::INFINITY) + end + + def dump_compressed(entry, threshold) + return @serializer.dump_compressed(entry, threshold) if @legacy_serializer + + # If value is a string with a supported encoding, use it as the payload + # instead of passing it through the serializer. + if type = type_for_string(entry.value) + payload = entry.value.b + else + type = OBJECT_DUMP_TYPE + payload = @serializer.dump(entry.value) + end + + if compressed = try_compress(payload, threshold) + payload = compressed + type = type | COMPRESSED_FLAG + end + + expires_at = entry.expires_at || -1.0 + + version = dump_version(entry.version) if entry.version + version_length = version&.bytesize || -1 + + packed = SIGNATURE.b + packed << [type, expires_at, version_length].pack(PACKED_TEMPLATE) + packed << version if version + packed << payload + end + + def load(dumped) + return @serializer.load(dumped) if !signature?(dumped) + + type = dumped.unpack1(PACKED_TYPE_TEMPLATE) + expires_at = dumped.unpack1(PACKED_EXPIRES_AT_TEMPLATE) + version_length = dumped.unpack1(PACKED_VERSION_LENGTH_TEMPLATE) + + expires_at = nil if expires_at < 0 + version = load_version(dumped.byteslice(PACKED_VERSION_INDEX, version_length)) if version_length >= 0 + payload = dumped.byteslice((PACKED_VERSION_INDEX + [version_length, 0].max)..) + + compressor = @compressor if type & COMPRESSED_FLAG > 0 + serializer = STRING_DESERIALIZERS[type & ~COMPRESSED_FLAG] || @serializer + + LazyEntry.new(serializer, compressor, payload, version: version, expires_at: expires_at) + end + + private + SIGNATURE = "\x00\x11".b.freeze + + OBJECT_DUMP_TYPE = 0x01 + + STRING_ENCODINGS = { + 0x02 => Encoding::UTF_8, + 0x03 => Encoding::BINARY, + 0x04 => Encoding::US_ASCII, + } + + COMPRESSED_FLAG = 0x80 + + PACKED_TEMPLATE = "CEl<" + PACKED_TYPE_TEMPLATE = "@#{SIGNATURE.bytesize}C" + PACKED_EXPIRES_AT_TEMPLATE = "@#{[0].pack(PACKED_TYPE_TEMPLATE).bytesize}E" + PACKED_VERSION_LENGTH_TEMPLATE = "@#{[0].pack(PACKED_EXPIRES_AT_TEMPLATE).bytesize}l<" + PACKED_VERSION_INDEX = [0].pack(PACKED_VERSION_LENGTH_TEMPLATE).bytesize + + MARSHAL_SIGNATURE = "\x04\x08".b.freeze + + class StringDeserializer + def initialize(encoding) + @encoding = encoding + end + + def load(payload) + payload.force_encoding(@encoding) + end + end + + STRING_DESERIALIZERS = STRING_ENCODINGS.transform_values { |encoding| StringDeserializer.new(encoding) } + + class LazyEntry < Cache::Entry + def initialize(serializer, compressor, payload, **options) + super(payload, **options) + @serializer = serializer + @compressor = compressor + @resolved = false + end + + def value + if !@resolved + @value = @serializer.load(@compressor ? @compressor.inflate(@value) : @value) + @resolved = true + end + @value + end + + def mismatched?(version) + super.tap { |mismatched| value if !mismatched } + rescue Cache::DeserializationError + true + end + end + + def signature?(dumped) + dumped.is_a?(String) && dumped.start_with?(SIGNATURE) + end + + def type_for_string(value) + STRING_ENCODINGS.key(value.encoding) if value.instance_of?(String) + end + + def try_compress(string, threshold) + if @compressor && string.bytesize >= threshold + compressed = @compressor.deflate(string) + compressed if compressed.bytesize < string.bytesize + end + end + + def dump_version(version) + if version.encoding != Encoding::UTF_8 || version.start_with?(MARSHAL_SIGNATURE) + Marshal.dump(version) + else + version.b + end + end + + def load_version(dumped_version) + if dumped_version.start_with?(MARSHAL_SIGNATURE) + Marshal.load(dumped_version) + else + dumped_version.force_encoding(Encoding::UTF_8) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/entry.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/entry.rb new file mode 100644 index 00000000..99318d82 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/entry.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true + +require "zlib" + +module ActiveSupport + module Cache + # This class is used to represent cache entries. Cache entries have a value, an optional + # expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option + # on the cache. The version is used to support the :version option on the cache for rejecting + # mismatches. + # + # Since cache entries in most instances will be serialized, the internals of this class are highly optimized + # using short instance variable names that are lazily defined. + class Entry # :nodoc: + class << self + def unpack(members) + new(members[0], expires_at: members[1], version: members[2]) + end + end + + attr_reader :version + + # Creates a new cache entry for the specified value. Options supported are + # +:compressed+, +:version+, +:expires_at+ and +:expires_in+. + def initialize(value, compressed: false, version: nil, expires_in: nil, expires_at: nil, **) + @value = value + @version = version + @created_at = 0.0 + @expires_in = expires_at&.to_f || expires_in && (expires_in.to_f + Time.now.to_f) + @compressed = true if compressed + end + + def value + compressed? ? uncompress(@value) : @value + end + + def mismatched?(version) + @version && version && @version != version + end + + # Checks if the entry is expired. The +expires_in+ parameter can override + # the value set when the entry was created. + def expired? + @expires_in && @created_at + @expires_in <= Time.now.to_f + end + + def expires_at + @expires_in ? @created_at + @expires_in : nil + end + + def expires_at=(value) + if value + @expires_in = value.to_f - @created_at + else + @expires_in = nil + end + end + + # Returns the size of the cached value. This could be less than + # value.bytesize if the data is compressed. + def bytesize + case value + when NilClass + 0 + when String + @value.bytesize + else + @s ||= Marshal.dump(@value).bytesize + end + end + + def compressed? # :nodoc: + defined?(@compressed) + end + + def compressed(compress_threshold) + return self if compressed? + + case @value + when nil, true, false, Numeric + uncompressed_size = 0 + when String + uncompressed_size = @value.bytesize + else + serialized = Marshal.dump(@value) + uncompressed_size = serialized.bytesize + end + + if uncompressed_size >= compress_threshold + serialized ||= Marshal.dump(@value) + compressed = Zlib::Deflate.deflate(serialized) + + if compressed.bytesize < uncompressed_size + return Entry.new(compressed, compressed: true, expires_at: expires_at, version: version) + end + end + self + end + + def local? + false + end + + # Duplicates the value in a class. This is used by cache implementations that don't natively + # serialize entries to protect against accidental cache modifications. + def dup_value! + if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false) + if @value.is_a?(String) + @value = @value.dup + else + @value = Marshal.load(Marshal.dump(@value)) + end + end + end + + def pack + members = [value, expires_at, version] + members.pop while !members.empty? && members.last.nil? + members + end + + private + def uncompress(value) + marshal_load(Zlib::Inflate.inflate(value)) + end + + def marshal_load(payload) + Marshal.load(payload) + rescue ArgumentError => error + raise Cache::DeserializationError, error.message + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/file_store.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/file_store.rb new file mode 100644 index 00000000..4c4e737e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/file_store.rb @@ -0,0 +1,244 @@ +# frozen_string_literal: true + +require "active_support/core_ext/file/atomic" +require "active_support/core_ext/string/conversions" +require "uri/common" + +module ActiveSupport + module Cache + # = \File \Cache \Store + # + # A cache store implementation which stores everything on the filesystem. + class FileStore < Store + attr_reader :cache_path + + DIR_FORMATTER = "%03X" + FILENAME_MAX_SIZE = 226 # max filename size on file system is 255, minus room for timestamp, pid, and random characters appended by Tempfile (used by atomic write) + FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room + GITKEEP_FILES = [".gitkeep", ".keep"].freeze + + def initialize(cache_path, **options) + super(options) + @cache_path = cache_path.to_s + end + + # Advertise cache versioning support. + def self.supports_cache_versioning? + true + end + + # Deletes all items from the cache. In this case it deletes all the entries in the specified + # file store directory except for .keep or .gitkeep. Be careful which directory is specified in your + # config file when using +FileStore+ because everything in that directory will be deleted. + def clear(options = nil) + root_dirs = (Dir.children(cache_path) - GITKEEP_FILES) + FileUtils.rm_r(root_dirs.collect { |f| File.join(cache_path, f) }) + rescue Errno::ENOENT, Errno::ENOTEMPTY + end + + # Preemptively iterates through all stored keys and removes the ones which have expired. + def cleanup(options = nil) + options = merged_options(options) + search_dir(cache_path) do |fname| + entry = read_entry(fname, **options) + delete_entry(fname, **options) if entry && entry.expired? + end + end + + # Increment a cached integer value. Returns the updated value. + # + # If the key is unset, it starts from +0+: + # + # cache.increment("foo") # => 1 + # cache.increment("bar", 100) # => 100 + # + # To set a specific value, call #write: + # + # cache.write("baz", 5) + # cache.increment("baz") # => 6 + # + def increment(name, amount = 1, **options) + options = merged_options(options) + key = normalize_key(name, options) + + instrument(:increment, key, amount: amount) do + modify_value(name, amount, options) + end + end + + # Decrement a cached integer value. Returns the updated value. + # + # If the key is unset, it will be set to +-amount+. + # + # cache.decrement("foo") # => -1 + # + # To set a specific value, call #write: + # + # cache.write("baz", 5) + # cache.decrement("baz") # => 4 + # + def decrement(name, amount = 1, **options) + options = merged_options(options) + key = normalize_key(name, options) + + instrument(:decrement, key, amount: amount) do + modify_value(name, -amount, options) + end + end + + def delete_matched(matcher, options = nil) + options = merged_options(options) + matcher = key_matcher(matcher, options) + + instrument(:delete_matched, matcher.inspect) do + search_dir(cache_path) do |path| + key = file_path_key(path) + delete_entry(path, **options) if key.match(matcher) + end + end + end + + def inspect # :nodoc: + "#<#{self.class.name} cache_path=#{@cache_path}, options=#{@options.inspect}>" + end + + private + def read_entry(key, **options) + if payload = read_serialized_entry(key, **options) + entry = deserialize_entry(payload) + entry if entry.is_a?(Cache::Entry) + end + end + + def read_serialized_entry(key, **) + File.binread(key) if File.exist?(key) + rescue => error + logger.error("FileStoreError (#{error}): #{error.message}") if logger + nil + end + + def write_entry(key, entry, **options) + write_serialized_entry(key, serialize_entry(entry, **options), **options) + end + + def write_serialized_entry(key, payload, **options) + return false if options[:unless_exist] && File.exist?(key) + ensure_cache_path(File.dirname(key)) + File.atomic_write(key, cache_path) { |f| f.write(payload) } + true + end + + def delete_entry(key, **options) + if File.exist?(key) + begin + File.delete(key) + delete_empty_directories(File.dirname(key)) + true + rescue + # Just in case the error was caused by another process deleting the file first. + raise if File.exist?(key) + false + end + else + false + end + end + + # Lock a file for a block so only one process can modify it at a time. + def lock_file(file_name, &block) + if File.exist?(file_name) + File.open(file_name, "r+") do |f| + f.flock File::LOCK_EX + yield + ensure + f.flock File::LOCK_UN + end + else + yield + end + end + + # Translate a key into a file path. + def normalize_key(key, options) + key = super + fname = URI.encode_www_form_component(key) + + if fname.size > FILEPATH_MAX_SIZE + fname = ActiveSupport::Digest.hexdigest(key) + end + + hash = Zlib.adler32(fname) + hash, dir_1 = hash.divmod(0x1000) + dir_2 = hash.modulo(0x1000) + + # Make sure file name doesn't exceed file system limits. + if fname.length < FILENAME_MAX_SIZE + fname_paths = fname + else + fname_paths = [] + begin + fname_paths << fname[0, FILENAME_MAX_SIZE] + fname = fname[FILENAME_MAX_SIZE..-1] + end until fname.blank? + end + + File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, fname_paths) + end + + # Translate a file path into a key. + def file_path_key(path) + fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last.delete(File::SEPARATOR) + URI.decode_www_form_component(fname, Encoding::UTF_8) + end + + # Delete empty directories in the cache. + def delete_empty_directories(dir) + return if File.realpath(dir) == File.realpath(cache_path) + if Dir.children(dir).empty? + Dir.delete(dir) rescue nil + delete_empty_directories(File.dirname(dir)) + end + end + + # Make sure a file path's directories exist. + def ensure_cache_path(path) + FileUtils.makedirs(path) unless File.exist?(path) + end + + def search_dir(dir, &callback) + return if !File.exist?(dir) + Dir.each_child(dir) do |d| + name = File.join(dir, d) + if File.directory?(name) + search_dir(name, &callback) + else + callback.call name + end + end + end + + # Modifies the amount of an integer value that is stored in the cache. + # If the key is not found it is created and set to +amount+. + def modify_value(name, amount, options) + options = merged_options(options) + key = normalize_key(name, options) + version = normalize_version(name, options) + amount = Integer(amount) + + lock_file(key) do + entry = read_entry(key, **options) + + if !entry || entry.expired? || entry.mismatched?(version) + write(name, amount, options) + amount + else + num = entry.value.to_i + amount + entry = Entry.new(num, expires_at: entry.expires_at, version: entry.version) + write_entry(key, entry) + num + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/mem_cache_store.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/mem_cache_store.rb new file mode 100644 index 00000000..723b65bd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/mem_cache_store.rb @@ -0,0 +1,290 @@ +# frozen_string_literal: true + +begin + require "dalli" +rescue LoadError => e + warn "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install" + raise e +end + +require "connection_pool" +require "delegate" +require "active_support/core_ext/enumerable" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/numeric/time" + +module ActiveSupport + module Cache + # = Memcached \Cache \Store + # + # A cache store implementation which stores data in Memcached: + # https://memcached.org + # + # This is currently the most popular cache store for production websites. + # + # Special features: + # - Clustering and load balancing. One can specify multiple memcached servers, + # and +MemCacheStore+ will load balance between all available servers. If a + # server goes down, then +MemCacheStore+ will ignore it until it comes back up. + # + # +MemCacheStore+ implements the Strategy::LocalCache strategy which + # implements an in-memory cache inside of a block. + class MemCacheStore < Store + # These options represent behavior overridden by this implementation and should + # not be allowed to get down to the Dalli client + OVERRIDDEN_OPTIONS = UNIVERSAL_OPTIONS + + # Advertise cache versioning support. + def self.supports_cache_versioning? + true + end + + prepend Strategy::LocalCache + + KEY_MAX_SIZE = 250 + ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n + + # Creates a new Dalli::Client instance with specified addresses and options. + # If no addresses are provided, we give nil to Dalli::Client, so it uses its fallbacks: + # - ENV["MEMCACHE_SERVERS"] (if defined) + # - "127.0.0.1:11211" (otherwise) + # + # ActiveSupport::Cache::MemCacheStore.build_mem_cache + # # => # + # ActiveSupport::Cache::MemCacheStore.build_mem_cache('localhost:10290') + # # => # + def self.build_mem_cache(*addresses) # :nodoc: + addresses = addresses.flatten + options = addresses.extract_options! + addresses = nil if addresses.compact.empty? + pool_options = retrieve_pool_options(options) + + if pool_options + ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) } + else + Dalli::Client.new(addresses, options) + end + end + + # Creates a new +MemCacheStore+ object, with the given memcached server + # addresses. Each address is either a host name, or a host-with-port string + # in the form of "host_name:port". For example: + # + # ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229") + # + # If no addresses are provided, but ENV['MEMCACHE_SERVERS'] is defined, it will be used instead. Otherwise, + # +MemCacheStore+ will connect to localhost:11211 (the default memcached port). + def initialize(*addresses) + addresses = addresses.flatten + options = addresses.extract_options! + if options.key?(:cache_nils) + options[:skip_nil] = !options.delete(:cache_nils) + end + super(options) + + unless [String, Dalli::Client, NilClass].include?(addresses.first.class) + raise ArgumentError, "First argument must be an empty array, address, or array of addresses." + end + + @mem_cache_options = options.dup + # The value "compress: false" prevents duplicate compression within Dalli. + @mem_cache_options[:compress] = false + (OVERRIDDEN_OPTIONS - %i(compress)).each { |name| @mem_cache_options.delete(name) } + @data = self.class.build_mem_cache(*(addresses + [@mem_cache_options])) + end + + def inspect + instance = @data || @mem_cache_options + "#<#{self.class} options=#{options.inspect} mem_cache=#{instance.inspect}>" + end + + ## + # :method: write + # :call-seq: write(name, value, options = nil) + # + # Behaves the same as ActiveSupport::Cache::Store#write, but supports + # additional options specific to memcached. + # + # ==== Additional Options + # + # * raw: true - Sends the value directly to the server as raw + # bytes. The value must be a string or number. You can use memcached + # direct operations like +increment+ and +decrement+ only on raw values. + # + # * unless_exist: true - Prevents overwriting an existing cache + # entry. + + # Increment a cached integer value using the memcached incr atomic operator. + # Returns the updated value. + # + # If the key is unset or has expired, it will be set to +amount+: + # + # cache.increment("foo") # => 1 + # cache.increment("bar", 100) # => 100 + # + # To set a specific value, call #write passing raw: true: + # + # cache.write("baz", 5, raw: true) + # cache.increment("baz") # => 6 + # + # Incrementing a non-numeric value, or a value written without + # raw: true, will fail and return +nil+. + def increment(name, amount = 1, options = nil) + options = merged_options(options) + key = normalize_key(name, options) + + instrument(:increment, key, amount: amount) do + rescue_error_with nil do + @data.with { |c| c.incr(key, amount, options[:expires_in], amount) } + end + end + end + + # Decrement a cached integer value using the memcached decr atomic operator. + # Returns the updated value. + # + # If the key is unset or has expired, it will be set to 0. Memcached + # does not support negative counters. + # + # cache.decrement("foo") # => 0 + # + # To set a specific value, call #write passing raw: true: + # + # cache.write("baz", 5, raw: true) + # cache.decrement("baz") # => 4 + # + # Decrementing a non-numeric value, or a value written without + # raw: true, will fail and return +nil+. + def decrement(name, amount = 1, options = nil) + options = merged_options(options) + key = normalize_key(name, options) + + instrument(:decrement, key, amount: amount) do + rescue_error_with nil do + @data.with { |c| c.decr(key, amount, options[:expires_in], 0) } + end + end + end + + # Clear the entire cache on all memcached servers. This method should + # be used with care when shared cache is being used. + def clear(options = nil) + rescue_error_with(nil) { @data.with { |c| c.flush_all } } + end + + # Get the statistics from the memcached servers. + def stats + @data.with { |c| c.stats } + end + + private + # Read an entry from the cache. + def read_entry(key, **options) + deserialize_entry(read_serialized_entry(key, **options), **options) + end + + def read_serialized_entry(key, **options) + rescue_error_with(nil) do + @data.with { |c| c.get(key, options) } + end + end + + # Write an entry to the cache. + def write_entry(key, entry, **options) + write_serialized_entry(key, serialize_entry(entry, **options), **options) + end + + def write_serialized_entry(key, payload, **options) + method = options[:unless_exist] ? :add : :set + expires_in = options[:expires_in].to_i + if options[:race_condition_ttl] && expires_in > 0 && !options[:raw] + # Set the memcache expire a few minutes in the future to support race condition ttls on read + expires_in += 5.minutes + end + rescue_error_with nil do + # Don't pass compress option to Dalli since we are already dealing with compression. + options.delete(:compress) + @data.with { |c| !!c.send(method, key, payload, expires_in, **options) } + end + end + + # Reads multiple entries from the cache implementation. + def read_multi_entries(names, **options) + keys_to_names = names.index_by { |name| normalize_key(name, options) } + + raw_values = begin + @data.with { |c| c.get_multi(keys_to_names.keys) } + rescue Dalli::UnmarshalError + {} + end + + values = {} + + raw_values.each do |key, value| + entry = deserialize_entry(value, raw: options[:raw]) + + unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options)) + begin + values[keys_to_names[key]] = entry.value + rescue DeserializationError + end + end + end + + values + end + + # Delete an entry from the cache. + def delete_entry(key, **options) + rescue_error_with(false) { @data.with { |c| c.delete(key) } } + end + + def serialize_entry(entry, raw: false, **options) + if raw + entry.value.to_s + else + super(entry, raw: raw, **options) + end + end + + # Memcache keys are binaries. So we need to force their encoding to binary + # before applying the regular expression to ensure we are escaping all + # characters properly. + def normalize_key(key, options) + key = super + if key + key = key.dup.force_encoding(Encoding::ASCII_8BIT) + key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" } + + if key.size > KEY_MAX_SIZE + key_separator = ":hash:" + key_hash = ActiveSupport::Digest.hexdigest(key) + key_trim_size = KEY_MAX_SIZE - key_separator.size - key_hash.size + key = "#{key[0, key_trim_size]}#{key_separator}#{key_hash}" + end + end + key + end + + def deserialize_entry(payload, raw: false, **) + if payload && raw + Entry.new(payload) + else + super(payload) + end + end + + def rescue_error_with(fallback) + yield + rescue Dalli::DalliError, ConnectionPool::Error, ConnectionPool::TimeoutError => error + logger.error("DalliError (#{error}): #{error.message}") if logger + ActiveSupport.error_reporter&.report( + error, + severity: :warning, + source: "mem_cache_store.active_support", + ) + fallback + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/memory_store.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/memory_store.rb new file mode 100644 index 00000000..3317dcd4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/memory_store.rb @@ -0,0 +1,262 @@ +# frozen_string_literal: true + +require "monitor" + +module ActiveSupport + module Cache + # = Memory \Cache \Store + # + # A cache store implementation which stores everything into memory in the + # same process. If you're running multiple Ruby on \Rails server processes + # (which is the case if you're using Phusion Passenger or puma clustered mode), + # then this means that \Rails server process instances won't be able + # to share cache data with each other and this may not be the most + # appropriate cache in that scenario. + # + # This cache has a bounded size specified by the +:size+ options to the + # initializer (default is 32Mb). When the cache exceeds the allotted size, + # a cleanup will occur which tries to prune the cache down to three quarters + # of the maximum size by removing the least recently used entries. + # + # Unlike other Cache store implementations, +MemoryStore+ does not compress + # values by default. +MemoryStore+ does not benefit from compression as much + # as other Store implementations, as it does not send data over a network. + # However, when compression is enabled, it still pays the full cost of + # compression in terms of cpu use. + # + # +MemoryStore+ is thread-safe. + class MemoryStore < Store + module DupCoder # :nodoc: + extend self + + def dump(entry) + if entry.value && entry.value != true && !entry.value.is_a?(Numeric) + Cache::Entry.new(dump_value(entry.value), expires_at: entry.expires_at, version: entry.version) + else + entry + end + end + + def dump_compressed(entry, threshold) + compressed_entry = entry.compressed(threshold) + compressed_entry.compressed? ? compressed_entry : dump(entry) + end + + def load(entry) + if !entry.compressed? && entry.value.is_a?(String) + Cache::Entry.new(load_value(entry.value), expires_at: entry.expires_at, version: entry.version) + else + entry + end + end + + private + MARSHAL_SIGNATURE = "\x04\x08".b.freeze + + def dump_value(value) + if value.is_a?(String) && !value.start_with?(MARSHAL_SIGNATURE) + value.dup + else + Marshal.dump(value) + end + end + + def load_value(string) + if string.start_with?(MARSHAL_SIGNATURE) + Marshal.load(string) + else + string.dup + end + end + end + + def initialize(options = nil) + options ||= {} + options[:coder] = DupCoder unless options.key?(:coder) || options.key?(:serializer) + # Disable compression by default. + options[:compress] ||= false + super(options) + @data = {} + @max_size = options[:size] || 32.megabytes + @max_prune_time = options[:max_prune_time] || 2 + @cache_size = 0 + @monitor = Monitor.new + @pruning = false + end + + # Advertise cache versioning support. + def self.supports_cache_versioning? + true + end + + # Delete all data stored in a given cache store. + def clear(options = nil) + synchronize do + @data.clear + @cache_size = 0 + end + end + + # Preemptively iterates through all stored keys and removes the ones which have expired. + def cleanup(options = nil) + options = merged_options(options) + _instrument(:cleanup, size: @data.size) do + keys = synchronize { @data.keys } + keys.each do |key| + entry = @data[key] + delete_entry(key, **options) if entry && entry.expired? + end + end + end + + # To ensure entries fit within the specified memory prune the cache by removing the least + # recently accessed entries. + def prune(target_size, max_time = nil) + return if pruning? + @pruning = true + begin + start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + cleanup + instrument(:prune, target_size, from: @cache_size) do + keys = synchronize { @data.keys } + keys.each do |key| + delete_entry(key, **options) + return if @cache_size <= target_size || (max_time && Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time > max_time) + end + end + ensure + @pruning = false + end + end + + # Returns true if the cache is currently being pruned. + def pruning? + @pruning + end + + # Increment a cached integer value. Returns the updated value. + # + # If the key is unset, it will be set to +amount+: + # + # cache.increment("foo") # => 1 + # cache.increment("bar", 100) # => 100 + # + # To set a specific value, call #write: + # + # cache.write("baz", 5) + # cache.increment("baz") # => 6 + # + def increment(name, amount = 1, **options) + instrument(:increment, name, amount: amount) do + modify_value(name, amount, **options) + end + end + + # Decrement a cached integer value. Returns the updated value. + # + # If the key is unset or has expired, it will be set to +-amount+. + # + # cache.decrement("foo") # => -1 + # + # To set a specific value, call #write: + # + # cache.write("baz", 5) + # cache.decrement("baz") # => 4 + # + def decrement(name, amount = 1, **options) + instrument(:decrement, name, amount: amount) do + modify_value(name, -amount, **options) + end + end + + # Deletes cache entries if the cache key matches a given pattern. + def delete_matched(matcher, options = nil) + options = merged_options(options) + matcher = key_matcher(matcher, options) + + instrument(:delete_matched, matcher.inspect) do + keys = synchronize { @data.keys } + keys.each do |key| + delete_entry(key, **options) if key.match(matcher) + end + end + end + + def inspect # :nodoc: + "#<#{self.class.name} entries=#{@data.size}, size=#{@cache_size}, options=#{@options.inspect}>" + end + + # Synchronize calls to the cache. This should be called wherever the underlying cache implementation + # is not thread safe. + def synchronize(&block) # :nodoc: + @monitor.synchronize(&block) + end + + private + PER_ENTRY_OVERHEAD = 240 + + def cached_size(key, payload) + key.to_s.bytesize + payload.bytesize + PER_ENTRY_OVERHEAD + end + + def read_entry(key, **options) + entry = nil + synchronize do + payload = @data.delete(key) + if payload + @data[key] = payload + entry = deserialize_entry(payload) + end + end + entry + end + + def write_entry(key, entry, **options) + payload = serialize_entry(entry, **options) + synchronize do + return false if options[:unless_exist] && exist?(key, namespace: nil) + + old_payload = @data[key] + if old_payload + @cache_size -= (old_payload.bytesize - payload.bytesize) + else + @cache_size += cached_size(key, payload) + end + @data[key] = payload + prune(@max_size * 0.75, @max_prune_time) if @cache_size > @max_size + true + end + end + + def delete_entry(key, **options) + synchronize do + payload = @data.delete(key) + @cache_size -= cached_size(key, payload) if payload + !!payload + end + end + + # Modifies the amount of an integer value that is stored in the cache. + # If the key is not found it is created and set to +amount+. + def modify_value(name, amount, **options) + options = merged_options(options) + key = normalize_key(name, options) + version = normalize_version(name, options) + + synchronize do + entry = read_entry(key, **options) + + if !entry || entry.expired? || entry.mismatched?(version) + write(name, Integer(amount), options) + amount + else + num = entry.value.to_i + amount + entry = Entry.new(num, expires_at: entry.expires_at, version: entry.version) + write_entry(key, entry) + num + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/null_store.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/null_store.rb new file mode 100644 index 00000000..7479a264 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/null_store.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module ActiveSupport + module Cache + # = Null \Cache \Store + # + # A cache store implementation which doesn't actually store anything. Useful in + # development and test environments where you don't want caching turned on but + # need to go through the caching interface. + # + # This cache does implement the local cache strategy, so values will actually + # be cached inside blocks that utilize this strategy. See + # ActiveSupport::Cache::Strategy::LocalCache for more details. + class NullStore < Store + prepend Strategy::LocalCache + + # Advertise cache versioning support. + def self.supports_cache_versioning? + true + end + + def clear(options = nil) + end + + def cleanup(options = nil) + end + + def increment(name, amount = 1, **options) + end + + def decrement(name, amount = 1, **options) + end + + def delete_matched(matcher, options = nil) + end + + def inspect # :nodoc: + "#<#{self.class.name} options=#{@options.inspect}>" + end + + private + def read_entry(key, **s) + deserialize_entry(read_serialized_entry(key)) + end + + def read_serialized_entry(_key, **) + end + + def write_entry(key, entry, **) + write_serialized_entry(key, serialize_entry(entry)) + end + + def write_serialized_entry(_key, _payload, **) + true + end + + def delete_entry(key, **options) + false + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/redis_cache_store.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/redis_cache_store.rb new file mode 100644 index 00000000..4192d4ab --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/redis_cache_store.rb @@ -0,0 +1,492 @@ +# frozen_string_literal: true + +begin + gem "redis", ">= 4.0.1" + require "redis" + require "redis/distributed" +rescue LoadError + warn "The Redis cache store requires the redis gem, version 4.0.1 or later. Please add it to your Gemfile: `gem \"redis\", \">= 4.0.1\"`" + raise +end + +require "connection_pool" +require "active_support/core_ext/array/wrap" +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/numeric/time" +require "active_support/digest" + +module ActiveSupport + module Cache + # = Redis \Cache \Store + # + # Deployment note: Take care to use a dedicated Redis cache rather + # than pointing this at a persistent Redis server (for example, one used as + # an Active Job queue). Redis won't cope well with mixed usage patterns and it + # won't expire cache entries by default. + # + # Redis cache server setup guide: https://redis.io/topics/lru-cache + # + # * Supports vanilla Redis, hiredis, and +Redis::Distributed+. + # * Supports Memcached-like sharding across Redises with +Redis::Distributed+. + # * Fault tolerant. If the Redis server is unavailable, no exceptions are + # raised. Cache fetches are all misses and writes are dropped. + # * Local cache. Hot in-memory primary cache within block/middleware scope. + # * +read_multi+ and +write_multi+ support for Redis mget/mset. Use + # +Redis::Distributed+ 4.0.1+ for distributed mget support. + # * +delete_matched+ support for Redis KEYS globs. + class RedisCacheStore < Store + # Keys are truncated with the Active Support digest if they exceed 1kB + MAX_KEY_BYTESIZE = 1024 + + DEFAULT_REDIS_OPTIONS = { + connect_timeout: 1, + read_timeout: 1, + write_timeout: 1, + } + + DEFAULT_ERROR_HANDLER = -> (method:, returning:, exception:) do + if logger + logger.error { "RedisCacheStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" } + end + ActiveSupport.error_reporter&.report( + exception, + severity: :warning, + source: "redis_cache_store.active_support", + ) + end + + # The maximum number of entries to receive per SCAN call. + SCAN_BATCH_SIZE = 1000 + private_constant :SCAN_BATCH_SIZE + + # Advertise cache versioning support. + def self.supports_cache_versioning? + true + end + + prepend Strategy::LocalCache + + class << self + # Factory method to create a new Redis instance. + # + # Handles four options: :redis block, :redis instance, single :url + # string, and multiple :url strings. + # + # Option Class Result + # :redis Proc -> options[:redis].call + # :redis Object -> options[:redis] + # :url String -> Redis.new(url: …) + # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …]) + # + def build_redis(redis: nil, url: nil, **redis_options) # :nodoc: + urls = Array(url) + + if redis.is_a?(Proc) + redis.call + elsif redis + redis + elsif urls.size > 1 + build_redis_distributed_client(urls: urls, **redis_options) + elsif urls.empty? + build_redis_client(**redis_options) + else + build_redis_client(url: urls.first, **redis_options) + end + end + + private + def build_redis_distributed_client(urls:, **redis_options) + ::Redis::Distributed.new([], DEFAULT_REDIS_OPTIONS.merge(redis_options)).tap do |dist| + urls.each { |u| dist.add_node url: u } + end + end + + def build_redis_client(**redis_options) + ::Redis.new(DEFAULT_REDIS_OPTIONS.merge(redis_options)) + end + end + + attr_reader :max_key_bytesize + attr_reader :redis + + # Creates a new Redis cache store. + # + # There are four ways to provide the Redis client used by the cache: the + # +:redis+ param can be a Redis instance or a block that returns a Redis + # instance, or the +:url+ param can be a string or an array of strings + # which will be used to create a Redis instance or a +Redis::Distributed+ + # instance. + # + # Option Class Result + # :redis Proc -> options[:redis].call + # :redis Object -> options[:redis] + # :url String -> Redis.new(url: …) + # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …]) + # + # No namespace is set by default. Provide one if the Redis cache + # server is shared with other apps: namespace: 'myapp-cache'. + # + # Compression is enabled by default with a 1kB threshold, so cached + # values larger than 1kB are automatically compressed. Disable by + # passing compress: false or change the threshold by passing + # compress_threshold: 4.kilobytes. + # + # No expiry is set on cache entries by default. Redis is expected to + # be configured with an eviction policy that automatically deletes + # least-recently or -frequently used keys when it reaches max memory. + # See https://redis.io/topics/lru-cache for cache server setup. + # + # Race condition TTL is not set by default. This can be used to avoid + # "thundering herd" cache writes when hot cache entries are expired. + # See ActiveSupport::Cache::Store#fetch for more. + # + # Setting skip_nil: true will not cache nil results: + # + # cache.fetch('foo') { nil } + # cache.fetch('bar', skip_nil: true) { nil } + # cache.exist?('foo') # => true + # cache.exist?('bar') # => false + def initialize(error_handler: DEFAULT_ERROR_HANDLER, **redis_options) + universal_options = redis_options.extract!(*UNIVERSAL_OPTIONS) + + if pool_options = self.class.send(:retrieve_pool_options, redis_options) + @redis = ::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) } + else + @redis = self.class.build_redis(**redis_options) + end + + @max_key_bytesize = MAX_KEY_BYTESIZE + @error_handler = error_handler + + super(universal_options) + end + + def inspect + "#<#{self.class} options=#{options.inspect} redis=#{redis.inspect}>" + end + + # Cache Store API implementation. + # + # Read multiple values at once. Returns a hash of requested keys -> + # fetched values. + def read_multi(*names) + return {} if names.empty? + + options = names.extract_options! + options = merged_options(options) + keys = names.map { |name| normalize_key(name, options) } + + instrument_multi(:read_multi, keys, options) do |payload| + read_multi_entries(names, **options).tap do |results| + payload[:hits] = results.keys.map { |name| normalize_key(name, options) } + end + end + end + + # Cache Store API implementation. + # + # Supports Redis KEYS glob patterns: + # + # h?llo matches hello, hallo and hxllo + # h*llo matches hllo and heeeello + # h[ae]llo matches hello and hallo, but not hillo + # h[^e]llo matches hallo, hbllo, ... but not hello + # h[a-b]llo matches hallo and hbllo + # + # Use \ to escape special characters if you want to match them verbatim. + # + # See https://redis.io/commands/KEYS for more. + # + # Failsafe: Raises errors. + def delete_matched(matcher, options = nil) + unless String === matcher + raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}" + end + pattern = namespace_key(matcher, options) + + instrument :delete_matched, pattern do + redis.then do |c| + cursor = "0" + # Fetch keys in batches using SCAN to avoid blocking the Redis server. + nodes = c.respond_to?(:nodes) ? c.nodes : [c] + + nodes.each do |node| + begin + cursor, keys = node.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE) + node.del(*keys) unless keys.empty? + end until cursor == "0" + end + end + end + end + + # Increment a cached integer value using the Redis incrby atomic operator. + # Returns the updated value. + # + # If the key is unset or has expired, it will be set to +amount+: + # + # cache.increment("foo") # => 1 + # cache.increment("bar", 100) # => 100 + # + # To set a specific value, call #write passing raw: true: + # + # cache.write("baz", 5, raw: true) + # cache.increment("baz") # => 6 + # + # Incrementing a non-numeric value, or a value written without + # raw: true, will fail and return +nil+. + # + # Failsafe: Raises errors. + def increment(name, amount = 1, options = nil) + options = merged_options(options) + key = normalize_key(name, options) + + instrument :increment, key, amount: amount do + failsafe :increment do + change_counter(key, amount, options) + end + end + end + + # Decrement a cached integer value using the Redis decrby atomic operator. + # Returns the updated value. + # + # If the key is unset or has expired, it will be set to +-amount+: + # + # cache.decrement("foo") # => -1 + # + # To set a specific value, call #write passing raw: true: + # + # cache.write("baz", 5, raw: true) + # cache.decrement("baz") # => 4 + # + # Decrementing a non-numeric value, or a value written without + # raw: true, will fail and return +nil+. + # + # Failsafe: Raises errors. + def decrement(name, amount = 1, options = nil) + options = merged_options(options) + key = normalize_key(name, options) + + instrument :decrement, key, amount: amount do + failsafe :decrement do + change_counter(key, -amount, options) + end + end + end + + # Cache Store API implementation. + # + # Removes expired entries. Handled natively by Redis least-recently-/ + # least-frequently-used expiry, so manual cleanup is not supported. + def cleanup(options = nil) + super + end + + # Clear the entire cache on all Redis servers. Safe to use on + # shared servers if the cache is namespaced. + # + # Failsafe: Raises errors. + def clear(options = nil) + failsafe :clear do + if namespace = merged_options(options)[:namespace] + delete_matched "*", namespace: namespace + else + redis.then { |c| c.flushdb } + end + end + end + + # Get info from redis servers. + def stats + redis.then { |c| c.info } + end + + private + def pipeline_entries(entries, &block) + redis.then { |c| + if c.is_a?(Redis::Distributed) + entries.group_by { |k, _v| c.node_for(k) }.each do |node, sub_entries| + node.pipelined { |pipe| yield(pipe, sub_entries) } + end + else + c.pipelined { |pipe| yield(pipe, entries) } + end + } + end + + # Store provider interface: + # Read an entry from the cache. + def read_entry(key, **options) + deserialize_entry(read_serialized_entry(key, **options), **options) + end + + def read_serialized_entry(key, raw: false, **options) + failsafe :read_entry do + redis.then { |c| c.get(key) } + end + end + + def read_multi_entries(names, **options) + options = merged_options(options) + return {} if names == [] + raw = options&.fetch(:raw, false) + + keys = names.map { |name| normalize_key(name, options) } + + values = failsafe(:read_multi_entries, returning: {}) do + redis.then { |c| c.mget(*keys) } + end + + names.zip(values).each_with_object({}) do |(name, value), results| + if value + entry = deserialize_entry(value, raw: raw) + unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options)) + begin + results[name] = entry.value + rescue DeserializationError + end + end + end + end + end + + # Write an entry to the cache. + # + # Requires Redis 2.6.12+ for extended SET options. + def write_entry(key, entry, raw: false, **options) + write_serialized_entry(key, serialize_entry(entry, raw: raw, **options), raw: raw, **options) + end + + def write_serialized_entry(key, payload, raw: false, unless_exist: false, expires_in: nil, race_condition_ttl: nil, pipeline: nil, **options) + # If race condition TTL is in use, ensure that cache entries + # stick around a bit longer after they would have expired + # so we can purposefully serve stale entries. + if race_condition_ttl && expires_in && expires_in > 0 && !raw + expires_in += 5.minutes + end + + modifiers = {} + if unless_exist || expires_in + modifiers[:nx] = unless_exist + modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in + end + + if pipeline + pipeline.set(key, payload, **modifiers) + else + failsafe :write_entry, returning: nil do + redis.then { |c| !!c.set(key, payload, **modifiers) } + end + end + end + + # Delete an entry from the cache. + def delete_entry(key, **options) + failsafe :delete_entry, returning: false do + redis.then { |c| c.del(key) == 1 } + end + end + + # Deletes multiple entries in the cache. Returns the number of entries deleted. + def delete_multi_entries(entries, **_options) + failsafe :delete_multi_entries, returning: 0 do + redis.then { |c| c.del(entries) } + end + end + + # Nonstandard store provider API to write multiple values at once. + def write_multi_entries(entries, **options) + return if entries.empty? + + failsafe :write_multi_entries do + pipeline_entries(entries) do |pipeline, sharded_entries| + options = options.dup + options[:pipeline] = pipeline + sharded_entries.each do |key, entry| + write_entry key, entry, **options + end + end + end + end + + # Truncate keys that exceed 1kB. + def normalize_key(key, options) + truncate_key super&.b + end + + def truncate_key(key) + if key && key.bytesize > max_key_bytesize + suffix = ":hash:#{ActiveSupport::Digest.hexdigest(key)}" + truncate_at = max_key_bytesize - suffix.bytesize + "#{key.byteslice(0, truncate_at)}#{suffix}" + else + key + end + end + + def deserialize_entry(payload, raw: false, **) + if raw && !payload.nil? + Entry.new(payload) + else + super(payload) + end + end + + def serialize_entry(entry, raw: false, **options) + if raw + entry.value.to_s + else + super(entry, raw: raw, **options) + end + end + + def serialize_entries(entries, **options) + entries.transform_values do |entry| + serialize_entry(entry, **options) + end + end + + def change_counter(key, amount, options) + redis.then do |c| + c = c.node_for(key) if c.is_a?(Redis::Distributed) + + expires_in = options[:expires_in] + + if expires_in + if supports_expire_nx? + count, _ = c.pipelined do |pipeline| + pipeline.incrby(key, amount) + pipeline.call(:expire, key, expires_in.to_i, "NX") + end + else + count, ttl = c.pipelined do |pipeline| + pipeline.incrby(key, amount) + pipeline.ttl(key) + end + c.expire(key, expires_in.to_i) if ttl < 0 + end + else + count = c.incrby(key, amount) + end + + count + end + end + + def supports_expire_nx? + return @supports_expire_nx if defined?(@supports_expire_nx) + + redis_versions = redis.then { |c| Array.wrap(c.info("server")).pluck("redis_version") } + @supports_expire_nx = redis_versions.all? { |v| Gem::Version.new(v) >= Gem::Version.new("7.0.0") } + end + + def failsafe(method, returning: nil) + yield + rescue ::Redis::BaseError, ConnectionPool::Error, ConnectionPool::TimeoutError => error + @error_handler&.call(method: method, exception: error, returning: returning) + returning + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/serializer_with_fallback.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/serializer_with_fallback.rb new file mode 100644 index 00000000..99358490 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/serializer_with_fallback.rb @@ -0,0 +1,152 @@ +# frozen_string_literal: true + +require "zlib" +require "active_support/core_ext/kernel/reporting" + +module ActiveSupport + module Cache + module SerializerWithFallback # :nodoc: + def self.[](format) + if format.to_s.include?("message_pack") && !defined?(ActiveSupport::MessagePack) + require "active_support/message_pack" + end + + SERIALIZERS.fetch(format) + end + + def load(dumped) + if dumped.is_a?(String) + case + when MessagePackWithFallback.dumped?(dumped) + MessagePackWithFallback._load(dumped) + when Marshal71WithFallback.dumped?(dumped) + Marshal71WithFallback._load(dumped) + when Marshal70WithFallback.dumped?(dumped) + Marshal70WithFallback._load(dumped) + else + Cache::Store.logger&.warn("Unrecognized payload prefix #{dumped.byteslice(0).inspect}; deserializing as nil") + nil + end + elsif PassthroughWithFallback.dumped?(dumped) + PassthroughWithFallback._load(dumped) + else + Cache::Store.logger&.warn("Unrecognized payload class #{dumped.class}; deserializing as nil") + nil + end + end + + private + def marshal_load(payload) + Marshal.load(payload) + rescue ArgumentError => error + raise Cache::DeserializationError, error.message + end + + module PassthroughWithFallback + include SerializerWithFallback + extend self + + def dump(entry) + entry + end + + def dump_compressed(entry, threshold) + entry.compressed(threshold) + end + + def _load(entry) + entry + end + + def dumped?(dumped) + dumped.is_a?(Cache::Entry) + end + end + + module Marshal70WithFallback + include SerializerWithFallback + extend self + + MARK_UNCOMPRESSED = "\x00".b.freeze + MARK_COMPRESSED = "\x01".b.freeze + + def dump(entry) + MARK_UNCOMPRESSED + Marshal.dump(entry.pack) + end + + def dump_compressed(entry, threshold) + dumped = Marshal.dump(entry.pack) + + if dumped.bytesize >= threshold + compressed = Zlib::Deflate.deflate(dumped) + return MARK_COMPRESSED + compressed if compressed.bytesize < dumped.bytesize + end + + MARK_UNCOMPRESSED + dumped + end + + def _load(marked) + dumped = marked.byteslice(1..-1) + dumped = Zlib::Inflate.inflate(dumped) if marked.start_with?(MARK_COMPRESSED) + Cache::Entry.unpack(marshal_load(dumped)) + end + + def dumped?(dumped) + dumped.start_with?(MARK_UNCOMPRESSED, MARK_COMPRESSED) + end + end + + module Marshal71WithFallback + include SerializerWithFallback + extend self + + MARSHAL_SIGNATURE = "\x04\x08".b.freeze + + def dump(value) + Marshal.dump(value) + end + + def _load(dumped) + marshal_load(dumped) + end + + def dumped?(dumped) + dumped.start_with?(MARSHAL_SIGNATURE) + end + end + + module MessagePackWithFallback + include SerializerWithFallback + extend self + + def dump(value) + ActiveSupport::MessagePack::CacheSerializer.dump(value) + end + + def _load(dumped) + ActiveSupport::MessagePack::CacheSerializer.load(dumped) + end + + def dumped?(dumped) + available? && ActiveSupport::MessagePack.signature?(dumped) + end + + private + def available? + return @available if defined?(@available) + silence_warnings { require "active_support/message_pack" } + @available = true + rescue LoadError + @available = false + end + end + + SERIALIZERS = { + passthrough: PassthroughWithFallback, + marshal_7_0: Marshal70WithFallback, + marshal_7_1: Marshal71WithFallback, + message_pack: MessagePackWithFallback, + } + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/strategy/local_cache.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/strategy/local_cache.rb new file mode 100644 index 00000000..d40d5efd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/strategy/local_cache.rb @@ -0,0 +1,237 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/inflections" + +module ActiveSupport + module Cache + module Strategy + # = Local \Cache \Strategy + # + # Caches that implement LocalCache will be backed by an in-memory cache for the + # duration of a block. Repeated calls to the cache for the same key will hit the + # in-memory cache for faster access. + module LocalCache + autoload :Middleware, "active_support/cache/strategy/local_cache_middleware" + + # Class for storing and registering the local caches. + module LocalCacheRegistry # :nodoc: + extend self + + def cache_for(local_cache_key) + registry = ActiveSupport::IsolatedExecutionState[:active_support_local_cache_registry] ||= {} + registry[local_cache_key] + end + + def set_cache_for(local_cache_key, value) + registry = ActiveSupport::IsolatedExecutionState[:active_support_local_cache_registry] ||= {} + registry[local_cache_key] = value + end + end + + # = Local \Cache \Store + # + # Simple memory backed cache. This cache is not thread safe and is intended only + # for serving as a temporary memory cache for a single thread. + class LocalStore + def initialize + @data = {} + end + + def clear(options = nil) + @data.clear + end + + def read_entry(key) + @data[key] + end + + def read_multi_entries(keys) + @data.slice(*keys) + end + + def write_entry(key, entry) + @data[key] = entry + true + end + + def delete_entry(key) + !!@data.delete(key) + end + + def fetch_entry(key) # :nodoc: + @data.fetch(key) { @data[key] = yield } + end + end + + # Use a local cache for the duration of block. + def with_local_cache(&block) + use_temporary_local_cache(LocalStore.new, &block) + end + + # Middleware class can be inserted as a Rack handler to be local cache for the + # duration of request. + def middleware + @middleware ||= Middleware.new( + "ActiveSupport::Cache::Strategy::LocalCache", + local_cache_key) + end + + def clear(options = nil) # :nodoc: + return super unless cache = local_cache + cache.clear(options) + super + end + + def cleanup(options = nil) # :nodoc: + return super unless cache = local_cache + cache.clear(options) + super + end + + def delete_matched(matcher, options = nil) # :nodoc: + return super unless cache = local_cache + cache.clear(options) + super + end + + def increment(name, amount = 1, **options) # :nodoc: + return super unless local_cache + value = bypass_local_cache { super } + write_cache_value(name, value, raw: true, **options) + value + end + + def decrement(name, amount = 1, **options) # :nodoc: + return super unless local_cache + value = bypass_local_cache { super } + write_cache_value(name, value, raw: true, **options) + value + end + + def fetch_multi(*names, &block) # :nodoc: + return super if local_cache.nil? || names.empty? + + options = names.extract_options! + options = merged_options(options) + + keys_to_names = names.index_by { |name| normalize_key(name, options) } + + local_entries = local_cache.read_multi_entries(keys_to_names.keys) + results = local_entries.each_with_object({}) do |(key, value), result| + # If we recorded a miss in the local cache, `#fetch_multi` will forward + # that key to the real store, and the entry will be replaced + # local_cache.delete_entry(key) + next if value.nil? + + entry = deserialize_entry(value, **options) + + normalized_key = keys_to_names[key] + if entry.nil? + result[normalized_key] = nil + elsif entry.expired? || entry.mismatched?(normalize_version(normalized_key, options)) + local_cache.delete_entry(key) + else + result[normalized_key] = entry.value + end + end + + if results.size < names.size + results.merge!(super(*(names - results.keys), options, &block)) + end + + results + end + + private + def read_serialized_entry(key, raw: false, **options) + if cache = local_cache + hit = true + entry = cache.fetch_entry(key) do + hit = false + super + end + options[:event][:store] = cache.class.name if hit && options[:event] + entry + else + super + end + end + + def read_multi_entries(names, **options) + return super unless local_cache + + keys_to_names = names.index_by { |name| normalize_key(name, options) } + + local_entries = local_cache.read_multi_entries(keys_to_names.keys) + + results = local_entries.each_with_object({}) do |(key, value), result| + next if value.nil? # recorded cache miss + + entry = deserialize_entry(value, **options) + + normalized_key = keys_to_names[key] + if entry.nil? + result[normalized_key] = nil + elsif entry.expired? || entry.mismatched?(normalize_version(normalized_key, options)) + local_cache.delete_entry(key) + else + result[normalized_key] = entry.value + end + end + + if results.size < names.size + results.merge!(super(names - results.keys, **options)) + end + + results + end + + def write_serialized_entry(key, payload, **) + if return_value = super + local_cache.write_entry(key, payload) if local_cache + else + local_cache.delete_entry(key) if local_cache + end + return_value + end + + def delete_entry(key, **) + local_cache.delete_entry(key) if local_cache + super + end + + def write_cache_value(name, value, **options) + name = normalize_key(name, options) + cache = local_cache + if value + cache.write_entry(name, serialize_entry(new_entry(value, **options), **options)) + else + cache.delete_entry(name) + end + end + + def local_cache_key + @local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, "_").to_sym + end + + def local_cache + LocalCacheRegistry.cache_for(local_cache_key) + end + + def bypass_local_cache(&block) + use_temporary_local_cache(nil, &block) + end + + def use_temporary_local_cache(temporary_cache) + save_cache = LocalCacheRegistry.cache_for(local_cache_key) + begin + LocalCacheRegistry.set_cache_for(local_cache_key, temporary_cache) + yield + ensure + LocalCacheRegistry.set_cache_for(local_cache_key, save_cache) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/strategy/local_cache_middleware.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/strategy/local_cache_middleware.rb new file mode 100644 index 00000000..62542bdb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/cache/strategy/local_cache_middleware.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "rack/body_proxy" +require "rack/utils" + +module ActiveSupport + module Cache + module Strategy + module LocalCache + #-- + # This class wraps up local storage for middlewares. Only the middleware method should + # construct them. + class Middleware # :nodoc: + attr_reader :name, :local_cache_key + + def initialize(name, local_cache_key) + @name = name + @local_cache_key = local_cache_key + @app = nil + end + + def new(app) + @app = app + self + end + + def call(env) + LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new) + response = @app.call(env) + response[2] = ::Rack::BodyProxy.new(response[2]) do + LocalCacheRegistry.set_cache_for(local_cache_key, nil) + end + cleanup_on_body_close = true + response + rescue Rack::Utils::InvalidParameterError + [400, {}, []] + ensure + LocalCacheRegistry.set_cache_for(local_cache_key, nil) unless + cleanup_on_body_close + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/callbacks.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/callbacks.rb new file mode 100644 index 00000000..ea3c3ccb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/callbacks.rb @@ -0,0 +1,948 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/descendants_tracker" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/class/attribute" +require "active_support/core_ext/string/filters" +require "active_support/core_ext/object/blank" + +module ActiveSupport + # = Active Support \Callbacks + # + # \Callbacks are code hooks that are run at key points in an object's life cycle. + # The typical use case is to have a base class define a set of callbacks + # relevant to the other functionality it supplies, so that subclasses can + # install callbacks that enhance or modify the base functionality without + # needing to override or redefine methods of the base class. + # + # Mixing in this module allows you to define the events in the object's + # life cycle that will support callbacks (via ClassMethods#define_callbacks), + # set the instance methods, procs, or callback objects to be called (via + # ClassMethods#set_callback), and run the installed callbacks at the + # appropriate times (via +run_callbacks+). + # + # By default callbacks are halted by throwing +:abort+. + # See ClassMethods#define_callbacks for details. + # + # Three kinds of callbacks are supported: before callbacks, run before a + # certain event; after callbacks, run after the event; and around callbacks, + # blocks that surround the event, triggering it when they yield. Callback code + # can be contained in instance methods, procs or lambdas, or callback objects + # that respond to certain predetermined methods. See ClassMethods#set_callback + # for details. + # + # class Record + # include ActiveSupport::Callbacks + # define_callbacks :save + # + # def save + # run_callbacks :save do + # puts "- save" + # end + # end + # end + # + # class PersonRecord < Record + # set_callback :save, :before, :saving_message + # def saving_message + # puts "saving..." + # end + # + # set_callback :save, :after do |object| + # puts "saved" + # end + # end + # + # person = PersonRecord.new + # person.save + # + # Output: + # saving... + # - save + # saved + module Callbacks + extend Concern + + included do + extend ActiveSupport::DescendantsTracker + class_attribute :__callbacks, instance_writer: false, instance_predicate: false, default: {} + end + + CALLBACK_FILTER_TYPES = [:before, :after, :around].freeze + + # Runs the callbacks for the given event. + # + # Calls the before and around callbacks in the order they were set, yields + # the block (if given one), and then runs the after callbacks in reverse + # order. + # + # If the callback chain was halted, returns +false+. Otherwise returns the + # result of the block, +nil+ if no callbacks have been set, or +true+ + # if callbacks have been set but no block is given. + # + # run_callbacks :save do + # save + # end + # + #-- + # + # As this method is used in many places, and often wraps large portions of + # user code, it has an additional design goal of minimizing its impact on + # the visible call stack. An exception from inside a :before or :after + # callback can be as noisy as it likes -- but when control has passed + # smoothly through and into the supplied block, we want as little evidence + # as possible that we were here. + def run_callbacks(kind, type = nil) + callbacks = __callbacks[kind.to_sym] + + if callbacks.empty? + yield if block_given? + else + env = Filters::Environment.new(self, false, nil) + + next_sequence = callbacks.compile(type) + + # Common case: no 'around' callbacks defined + if next_sequence.final? + next_sequence.invoke_before(env) + env.value = !env.halted && (!block_given? || yield) + next_sequence.invoke_after(env) + env.value + else + invoke_sequence = Proc.new do + skipped = nil + + while true + current = next_sequence + current.invoke_before(env) + if current.final? + env.value = !env.halted && (!block_given? || yield) + elsif current.skip?(env) + (skipped ||= []) << current + next_sequence = next_sequence.nested + next + else + next_sequence = next_sequence.nested + begin + target, block, method, *arguments = current.expand_call_template(env, invoke_sequence) + target.send(method, *arguments, &block) + ensure + next_sequence = current + end + end + current.invoke_after(env) + skipped.pop.invoke_after(env) while skipped&.first + break env.value + end + end + + invoke_sequence.call + end + end + end + + private + # A hook invoked every time a before callback is halted. + # This can be overridden in ActiveSupport::Callbacks implementors in order + # to provide better debugging/logging. + def halted_callback_hook(filter, name) + end + + module Conditionals # :nodoc: all + class Value + def initialize(&block) + @block = block + end + def call(target, value); @block.call(value); end + end + end + + module Filters # :nodoc: all + Environment = Struct.new(:target, :halted, :value) + + class Before + def initialize(user_callback, user_conditions, chain_config, filter, name) + halted_lambda = chain_config[:terminator] + @user_callback, @user_conditions, @halted_lambda, @filter, @name = user_callback, user_conditions, halted_lambda, filter, name + freeze + end + attr_reader :user_callback, :user_conditions, :halted_lambda, :filter, :name + + def call(env) + target = env.target + value = env.value + halted = env.halted + + if !halted && user_conditions.all? { |c| c.call(target, value) } + result_lambda = -> { user_callback.call target, value } + env.halted = halted_lambda.call(target, result_lambda) + if env.halted + target.send :halted_callback_hook, filter, name + end + end + + env + end + + def apply(callback_sequence) + callback_sequence.before(self) + end + end + + class After + attr_reader :user_callback, :user_conditions, :halting + def initialize(user_callback, user_conditions, chain_config) + halting = chain_config[:skip_after_callbacks_if_terminated] + @user_callback, @user_conditions, @halting = user_callback, user_conditions, halting + freeze + end + + def call(env) + target = env.target + value = env.value + halted = env.halted + + if (!halted || !@halting) && user_conditions.all? { |c| c.call(target, value) } + user_callback.call target, value + end + + env + end + + def apply(callback_sequence) + callback_sequence.after(self) + end + end + + class Around + def initialize(user_callback, user_conditions) + @user_callback, @user_conditions = user_callback, user_conditions + freeze + end + + def apply(callback_sequence) + callback_sequence.around(@user_callback, @user_conditions) + end + end + end + + class Callback # :nodoc: + def self.build(chain, filter, kind, options) + if filter.is_a?(String) + raise ArgumentError, <<-MSG.squish + Passing string to define a callback is not supported. See the `.set_callback` + documentation to see supported values. + MSG + end + + new chain.name, filter, kind, options, chain.config + end + + attr_accessor :kind, :name + attr_reader :chain_config, :filter + + def initialize(name, filter, kind, options, chain_config) + @chain_config = chain_config + @name = name + @kind = kind + @filter = filter + @if = check_conditionals(options[:if]) + @unless = check_conditionals(options[:unless]) + + compiled + end + + def merge_conditional_options(chain, if_option:, unless_option:) + options = { + if: @if.dup, + unless: @unless.dup + } + + options[:if].concat Array(unless_option) + options[:unless].concat Array(if_option) + + self.class.build chain, @filter, @kind, options + end + + def matches?(_kind, _filter) + @kind == _kind && filter == _filter + end + + def duplicates?(other) + case @filter + when Symbol + matches?(other.kind, other.filter) + else + false + end + end + + def compiled + @compiled ||= + begin + user_conditions = conditions_lambdas + user_callback = CallTemplate.build(@filter, self) + + case kind + when :before + Filters::Before.new(user_callback.make_lambda, user_conditions, chain_config, @filter, name) + when :after + Filters::After.new(user_callback.make_lambda, user_conditions, chain_config) + when :around + Filters::Around.new(user_callback, user_conditions) + end + end + end + + # Wraps code with filter + def apply(callback_sequence) + compiled.apply(callback_sequence) + end + + def current_scopes + Array(chain_config[:scope]).map { |s| public_send(s) } + end + + private + EMPTY_ARRAY = [].freeze + private_constant :EMPTY_ARRAY + + def check_conditionals(conditionals) + return EMPTY_ARRAY if conditionals.blank? + + conditionals = Array(conditionals) + if conditionals.any?(String) + raise ArgumentError, <<-MSG.squish + Passing string to be evaluated in :if and :unless conditional + options is not supported. Pass a symbol for an instance method, + or a lambda, proc or block, instead. + MSG + end + + conditionals.freeze + end + + def conditions_lambdas + conditions = + @if.map { |c| CallTemplate.build(c, self).make_lambda } + + @unless.map { |c| CallTemplate.build(c, self).inverted_lambda } + conditions.empty? ? EMPTY_ARRAY : conditions + end + end + + # A future invocation of user-supplied code (either as a callback, + # or a condition filter). + module CallTemplate # :nodoc: all + class MethodCall + def initialize(method) + @method_name = method + end + + # Return the parts needed to make this call, with the given + # input values. + # + # Returns an array of the form: + # + # [target, block, method, *arguments] + # + # This array can be used as such: + # + # target.send(method, *arguments, &block) + # + # The actual invocation is left up to the caller to minimize + # call stack pollution. + def expand(target, value, block) + [target, block, @method_name] + end + + def make_lambda + lambda do |target, value, &block| + target.send(@method_name, &block) + end + end + + def inverted_lambda + lambda do |target, value, &block| + !target.send(@method_name, &block) + end + end + end + + class ObjectCall + def initialize(target, method) + @override_target = target + @method_name = method + end + + def expand(target, value, block) + [@override_target || target, block, @method_name, target] + end + + def make_lambda + lambda do |target, value, &block| + (@override_target || target).send(@method_name, target, &block) + end + end + + def inverted_lambda + lambda do |target, value, &block| + !(@override_target || target).send(@method_name, target, &block) + end + end + end + + class InstanceExec0 + def initialize(block) + @override_block = block + end + + def expand(target, value, block) + [target, @override_block, :instance_exec] + end + + def make_lambda + lambda do |target, value, &block| + target.instance_exec(&@override_block) + end + end + + def inverted_lambda + lambda do |target, value, &block| + !target.instance_exec(&@override_block) + end + end + end + + class InstanceExec1 + def initialize(block) + @override_block = block + end + + def expand(target, value, block) + [target, @override_block, :instance_exec, target] + end + + def make_lambda + lambda do |target, value, &block| + target.instance_exec(target, &@override_block) + end + end + + def inverted_lambda + lambda do |target, value, &block| + !target.instance_exec(target, &@override_block) + end + end + end + + class InstanceExec2 + def initialize(block) + @override_block = block + end + + def expand(target, value, block) + raise ArgumentError unless block + [target, @override_block || block, :instance_exec, target, block] + end + + def make_lambda + lambda do |target, value, &block| + raise ArgumentError unless block + target.instance_exec(target, block, &@override_block) + end + end + + def inverted_lambda + lambda do |target, value, &block| + raise ArgumentError unless block + !target.instance_exec(target, block, &@override_block) + end + end + end + + class ProcCall + def initialize(target) + @override_target = target + end + + def expand(target, value, block) + [@override_target || target, block, :call, target, value] + end + + def make_lambda + lambda do |target, value, &block| + (@override_target || target).call(target, value, &block) + end + end + + def inverted_lambda + lambda do |target, value, &block| + !(@override_target || target).call(target, value, &block) + end + end + end + + # Filters support: + # + # Symbols:: A method to call. + # Procs:: A proc to call with the object. + # Objects:: An object with a before_foo method on it to call. + # + # All of these objects are converted into a CallTemplate and handled + # the same after this point. + def self.build(filter, callback) + case filter + when Symbol + MethodCall.new(filter) + when Conditionals::Value + ProcCall.new(filter) + when ::Proc + case filter.arity + when 2 + InstanceExec2.new(filter) + when 1, -2 + InstanceExec1.new(filter) + else + InstanceExec0.new(filter) + end + else + ObjectCall.new(filter, callback.current_scopes.join("_").to_sym) + end + end + end + + # Execute before and after filters in a sequence instead of + # chaining them with nested lambda calls, see: + # https://github.com/rails/rails/issues/18011 + class CallbackSequence # :nodoc: + def initialize(nested = nil, call_template = nil, user_conditions = nil) + @nested = nested + @call_template = call_template + @user_conditions = user_conditions + + @before = nil + @after = nil + end + + def before(before) + @before ||= [] + @before.unshift(before) + self + end + + def after(after) + @after ||= [] + @after.push(after) + self + end + + def around(call_template, user_conditions) + CallbackSequence.new(self, call_template, user_conditions) + end + + def skip?(arg) + arg.halted || !@user_conditions.all? { |c| c.call(arg.target, arg.value) } + end + + attr_reader :nested + + def final? + !@call_template + end + + def expand_call_template(arg, block) + @call_template.expand(arg.target, arg.value, block) + end + + def invoke_before(arg) + @before&.each { |b| b.call(arg) } + end + + def invoke_after(arg) + @after&.each { |a| a.call(arg) } + end + end + + class CallbackChain # :nodoc: + include Enumerable + + attr_reader :name, :config + + def initialize(name, config) + @name = name + @config = { + scope: [:kind], + terminator: default_terminator + }.merge!(config) + @chain = [] + @all_callbacks = nil + @single_callbacks = {} + @mutex = Mutex.new + end + + def each(&block); @chain.each(&block); end + def index(o); @chain.index(o); end + def empty?; @chain.empty?; end + + def insert(index, o) + @all_callbacks = nil + @single_callbacks.clear + @chain.insert(index, o) + end + + def delete(o) + @all_callbacks = nil + @single_callbacks.clear + @chain.delete(o) + end + + def clear + @all_callbacks = nil + @single_callbacks.clear + @chain.clear + self + end + + def initialize_copy(other) + @all_callbacks = nil + @single_callbacks = {} + @chain = other.chain.dup + @mutex = Mutex.new + end + + def compile(type) + if type.nil? + @all_callbacks || @mutex.synchronize do + final_sequence = CallbackSequence.new + @all_callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback| + callback.apply(callback_sequence) + end + end + else + @single_callbacks[type] || @mutex.synchronize do + final_sequence = CallbackSequence.new + @single_callbacks[type] ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback| + type == callback.kind ? callback.apply(callback_sequence) : callback_sequence + end + end + end + end + + def append(*callbacks) + callbacks.each { |c| append_one(c) } + end + + def prepend(*callbacks) + callbacks.each { |c| prepend_one(c) } + end + + protected + attr_reader :chain + + private + def append_one(callback) + @all_callbacks = nil + @single_callbacks.clear + remove_duplicates(callback) + @chain.push(callback) + end + + def prepend_one(callback) + @all_callbacks = nil + @single_callbacks.clear + remove_duplicates(callback) + @chain.unshift(callback) + end + + def remove_duplicates(callback) + @all_callbacks = nil + @single_callbacks.clear + @chain.delete_if { |c| callback.duplicates?(c) } + end + + def default_terminator + Proc.new do |target, result_lambda| + terminate = true + catch(:abort) do + result_lambda.call + terminate = false + end + terminate + end + end + end + + module ClassMethods + def normalize_callback_params(filters, block) # :nodoc: + type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before + options = filters.extract_options! + filters.unshift(block) if block + [type, filters, options.dup] + end + + # This is used internally to append, prepend and skip callbacks to the + # CallbackChain. + def __update_callbacks(name) # :nodoc: + self.descendants.prepend(self).reverse_each do |target| + chain = target.get_callbacks name + yield target, chain.dup + end + end + + # Install a callback for the given event. + # + # set_callback :save, :before, :before_method + # set_callback :save, :after, :after_method, if: :condition + # set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff } + # + # The second argument indicates whether the callback is to be run +:before+, + # +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This + # means the first example above can also be written as: + # + # set_callback :save, :before_method + # + # The callback can be specified as a symbol naming an instance method; as a + # proc, lambda, or block; or as an object that responds to a certain method + # determined by the :scope argument to #define_callbacks. + # + # If a proc, lambda, or block is given, its body is evaluated in the context + # of the current object. It can also optionally accept the current object as + # an argument. + # + # Before and around callbacks are called in the order that they are set; + # after callbacks are called in the reverse order. + # + # Around callbacks can access the return value from the event, if it + # wasn't halted, from the +yield+ call. + # + # ===== Options + # + # * :if - A symbol or an array of symbols, each naming an instance + # method or a proc; the callback will be called only when they all return + # a true value. + # + # If a proc is given, its body is evaluated in the context of the + # current object. It can also optionally accept the current object as + # an argument. + # * :unless - A symbol or an array of symbols, each naming an + # instance method or a proc; the callback will be called only when they + # all return a false value. + # + # If a proc is given, its body is evaluated in the context of the + # current object. It can also optionally accept the current object as + # an argument. + # * :prepend - If +true+, the callback will be prepended to the + # existing chain rather than appended. + def set_callback(name, *filter_list, &block) + type, filters, options = normalize_callback_params(filter_list, block) + + self_chain = get_callbacks name + mapped = filters.map do |filter| + Callback.build(self_chain, filter, type, options) + end + + __update_callbacks(name) do |target, chain| + options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped) + target.set_callbacks name, chain + end + end + + # Skip a previously set callback. Like #set_callback, :if or + # :unless options may be passed in order to control when the + # callback is skipped. + # + # Note: this example uses +PersonRecord+ and +#saving_message+, which you + # can see defined here[rdoc-ref:ActiveSupport::Callbacks] + # + # class Writer < PersonRecord + # attr_accessor :age + # skip_callback :save, :before, :saving_message, if: -> { age > 18 } + # end + # + # When if option returns true, callback is skipped. + # + # writer = Writer.new + # writer.age = 20 + # writer.save + # + # Output: + # - save + # saved + # + # When if option returns false, callback is NOT skipped. + # + # young_writer = Writer.new + # young_writer.age = 17 + # young_writer.save + # + # Output: + # saving... + # - save + # saved + # + # An ArgumentError will be raised if the callback has not + # already been set (unless the :raise option is set to false). + def skip_callback(name, *filter_list, &block) + type, filters, options = normalize_callback_params(filter_list, block) + + options[:raise] = true unless options.key?(:raise) + + __update_callbacks(name) do |target, chain| + filters.each do |filter| + callback = chain.find { |c| c.matches?(type, filter) } + + if !callback && options[:raise] + raise ArgumentError, "#{type.to_s.capitalize} #{name} callback #{filter.inspect} has not been defined" + end + + if callback && (options.key?(:if) || options.key?(:unless)) + new_callback = callback.merge_conditional_options(chain, if_option: options[:if], unless_option: options[:unless]) + chain.insert(chain.index(callback), new_callback) + end + + chain.delete(callback) + end + target.set_callbacks name, chain + end + end + + # Remove all set callbacks for the given event. + def reset_callbacks(name) + callbacks = get_callbacks name + + self.descendants.each do |target| + chain = target.get_callbacks(name).dup + callbacks.each { |c| chain.delete(c) } + target.set_callbacks name, chain + end + + set_callbacks(name, callbacks.dup.clear) + end + + # Define sets of events in the object life cycle that support callbacks. + # + # define_callbacks :validate + # define_callbacks :initialize, :save, :destroy + # + # ===== Options + # + # * :terminator - Determines when a before filter will halt the + # callback chain, preventing following before and around callbacks from + # being called and the event from being triggered. + # This should be a lambda to be executed. + # The current object and the result lambda of the callback will be provided + # to the terminator lambda. + # + # define_callbacks :validate, terminator: ->(target, result_lambda) { result_lambda.call == false } + # + # In this example, if any before validate callbacks returns +false+, + # any successive before and around callback is not executed. + # + # The default terminator halts the chain when a callback throws +:abort+. + # + # * :skip_after_callbacks_if_terminated - Determines if after + # callbacks should be terminated by the :terminator option. By + # default after callbacks are executed no matter if callback chain was + # terminated or not. This option has no effect if :terminator + # option is set to +nil+. + # + # * :scope - Indicates which methods should be executed when an + # object is used as a callback. + # + # class Audit + # def before(caller) + # puts 'Audit: before is called' + # end + # + # def before_save(caller) + # puts 'Audit: before_save is called' + # end + # end + # + # class Account + # include ActiveSupport::Callbacks + # + # define_callbacks :save + # set_callback :save, :before, Audit.new + # + # def save + # run_callbacks :save do + # puts 'save in main' + # end + # end + # end + # + # In the above case whenever you save an account the method + # Audit#before will be called. On the other hand + # + # define_callbacks :save, scope: [:kind, :name] + # + # would trigger Audit#before_save instead. That's constructed + # by calling #{kind}_#{name} on the given instance. In this + # case "kind" is "before" and "name" is "save". In this context +:kind+ + # and +:name+ have special meanings: +:kind+ refers to the kind of + # callback (before/after/around) and +:name+ refers to the method on + # which callbacks are being defined. + # + # A declaration like + # + # define_callbacks :save, scope: [:name] + # + # would call Audit#save. + # + # ===== Notes + # + # +names+ passed to +define_callbacks+ must not end with + # !, ? or =. + # + # Calling +define_callbacks+ multiple times with the same +names+ will + # overwrite previous callbacks registered with #set_callback. + def define_callbacks(*names) + options = names.extract_options! + + names.each do |name| + name = name.to_sym + + ([self] + self.descendants).each do |target| + target.set_callbacks name, CallbackChain.new(name, options) + end + + module_eval <<-RUBY, __FILE__, __LINE__ + 1 + def _run_#{name}_callbacks(&block) + run_callbacks #{name.inspect}, &block + end + + def self._#{name}_callbacks + get_callbacks(#{name.inspect}) + end + + def self._#{name}_callbacks=(value) + set_callbacks(#{name.inspect}, value) + end + + def _#{name}_callbacks + __callbacks[#{name.inspect}] + end + RUBY + end + end + + protected + def get_callbacks(name) # :nodoc: + __callbacks[name.to_sym] + end + + def set_callbacks(name, callbacks) # :nodoc: + # HACK: We're making assumption on how `class_attribute` is implemented + # to save constantly duping the callback hash. If this desync with class_attribute + # we'll lose the optimization, but won't cause an actual behavior bug. + unless singleton_class.private_method_defined?(:__class_attr__callbacks, false) + self.__callbacks = __callbacks.dup + end + self.__callbacks[name.to_sym] = callbacks + self.__callbacks + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/class_attribute.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/class_attribute.rb new file mode 100644 index 00000000..8aac26e3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/class_attribute.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module ActiveSupport + module ClassAttribute # :nodoc: + class << self + def redefine(owner, name, namespaced_name, value) + if owner.singleton_class? + if owner.attached_object.is_a?(Module) + redefine_method(owner, namespaced_name, private: true) { value } + else + redefine_method(owner, name) { value } + end + end + + redefine_method(owner.singleton_class, namespaced_name, private: true) { value } + + redefine_method(owner.singleton_class, "#{namespaced_name}=", private: true) do |new_value| + if owner.equal?(self) + value = new_value + else + ::ActiveSupport::ClassAttribute.redefine(self, name, namespaced_name, new_value) + end + end + end + + def redefine_method(owner, name, private: false, &block) + owner.silence_redefinition_of_method(name) + owner.define_method(name, &block) + owner.send(:private, name) if private + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/code_generator.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/code_generator.rb new file mode 100644 index 00000000..26eabb1b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/code_generator.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +module ActiveSupport + class CodeGenerator # :nodoc: + class MethodSet + METHOD_CACHES = Hash.new { |h, k| h[k] = Module.new } + + def initialize(namespace) + @cache = METHOD_CACHES[namespace] + @sources = [] + @methods = {} + @canonical_methods = {} + end + + def define_cached_method(canonical_name, as: nil) + canonical_name = canonical_name.to_sym + as = (as || canonical_name).to_sym + + @methods.fetch(as) do + unless @cache.method_defined?(canonical_name) || @canonical_methods[canonical_name] + yield @sources + end + @canonical_methods[canonical_name] = true + @methods[as] = canonical_name + end + end + + def apply(owner, path, line) + unless @sources.empty? + @cache.module_eval("# frozen_string_literal: true\n" + @sources.join(";"), path, line) + end + @canonical_methods.clear + + @methods.each do |as, canonical_name| + owner.define_method(as, @cache.instance_method(canonical_name)) + end + end + end + + class << self + def batch(owner, path, line) + if owner.is_a?(CodeGenerator) + yield owner + else + instance = new(owner, path, line) + result = yield instance + instance.execute + result + end + end + end + + def initialize(owner, path, line) + @owner = owner + @path = path + @line = line + @namespaces = Hash.new { |h, k| h[k] = MethodSet.new(k) } + @sources = [] + end + + def class_eval + yield @sources + end + + def define_cached_method(canonical_name, namespace:, as: nil, &block) + @namespaces[namespace].define_cached_method(canonical_name, as: as, &block) + end + + def execute + @namespaces.each_value do |method_set| + method_set.apply(@owner, @path, @line - 1) + end + + unless @sources.empty? + @owner.class_eval("# frozen_string_literal: true\n" + @sources.join(";"), @path, @line - 1) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/concern.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/concern.rb new file mode 100644 index 00000000..7af9c75c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/concern.rb @@ -0,0 +1,217 @@ +# frozen_string_literal: true + +module ActiveSupport + # = Active Support \Concern + # + # A typical module looks like this: + # + # module M + # def self.included(base) + # base.extend ClassMethods + # base.class_eval do + # scope :disabled, -> { where(disabled: true) } + # end + # end + # + # module ClassMethods + # ... + # end + # end + # + # By using +ActiveSupport::Concern+ the above module could instead be + # written as: + # + # require "active_support/concern" + # + # module M + # extend ActiveSupport::Concern + # + # included do + # scope :disabled, -> { where(disabled: true) } + # end + # + # class_methods do + # ... + # end + # end + # + # Moreover, it gracefully handles module dependencies. Given a +Foo+ module + # and a +Bar+ module which depends on the former, we would typically write the + # following: + # + # module Foo + # def self.included(base) + # base.class_eval do + # def self.method_injected_by_foo + # ... + # end + # end + # end + # end + # + # module Bar + # def self.included(base) + # base.method_injected_by_foo + # end + # end + # + # class Host + # include Foo # We need to include this dependency for Bar + # include Bar # Bar is the module that Host really needs + # end + # + # But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We + # could try to hide these from +Host+ directly including +Foo+ in +Bar+: + # + # module Bar + # include Foo + # def self.included(base) + # base.method_injected_by_foo + # end + # end + # + # class Host + # include Bar + # end + # + # Unfortunately this won't work, since when +Foo+ is included, its base + # is the +Bar+ module, not the +Host+ class. With +ActiveSupport::Concern+, + # module dependencies are properly resolved: + # + # require "active_support/concern" + # + # module Foo + # extend ActiveSupport::Concern + # included do + # def self.method_injected_by_foo + # ... + # end + # end + # end + # + # module Bar + # extend ActiveSupport::Concern + # include Foo + # + # included do + # self.method_injected_by_foo + # end + # end + # + # class Host + # include Bar # It works, now Bar takes care of its dependencies + # end + # + # === Prepending concerns + # + # Just like include, concerns also support prepend with a corresponding + # prepended do callback. module ClassMethods or class_methods do are + # prepended as well. + # + # prepend is also used for any dependencies. + module Concern + class MultipleIncludedBlocks < StandardError # :nodoc: + def initialize + super "Cannot define multiple 'included' blocks for a Concern" + end + end + + class MultiplePrependBlocks < StandardError # :nodoc: + def initialize + super "Cannot define multiple 'prepended' blocks for a Concern" + end + end + + def self.extended(base) # :nodoc: + base.instance_variable_set(:@_dependencies, []) + end + + def append_features(base) # :nodoc: + if base.instance_variable_defined?(:@_dependencies) + base.instance_variable_get(:@_dependencies) << self + false + else + return false if base < self + @_dependencies.each { |dep| base.include(dep) } + super + base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods) + base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block) + end + end + + def prepend_features(base) # :nodoc: + if base.instance_variable_defined?(:@_dependencies) + base.instance_variable_get(:@_dependencies).unshift self + false + else + return false if base < self + @_dependencies.each { |dep| base.prepend(dep) } + super + base.singleton_class.prepend const_get(:ClassMethods) if const_defined?(:ClassMethods) + base.class_eval(&@_prepended_block) if instance_variable_defined?(:@_prepended_block) + end + end + + # Evaluate given block in context of base class, + # so that you can write class macros here. + # When you define more than one +included+ block, it raises an exception. + def included(base = nil, &block) + if base.nil? + if instance_variable_defined?(:@_included_block) + if @_included_block.source_location != block.source_location + raise MultipleIncludedBlocks + end + else + @_included_block = block + end + else + super + end + end + + # Evaluate given block in context of base class, + # so that you can write class macros here. + # When you define more than one +prepended+ block, it raises an exception. + def prepended(base = nil, &block) + if base.nil? + if instance_variable_defined?(:@_prepended_block) + if @_prepended_block.source_location != block.source_location + raise MultiplePrependBlocks + end + else + @_prepended_block = block + end + else + super + end + end + + # Define class methods from given block. + # You can define private class methods as well. + # + # module Example + # extend ActiveSupport::Concern + # + # class_methods do + # def foo; puts 'foo'; end + # + # private + # def bar; puts 'bar'; end + # end + # end + # + # class Buzz + # include Example + # end + # + # Buzz.foo # => "foo" + # Buzz.bar # => private method 'bar' called for Buzz:Class(NoMethodError) + def class_methods(&class_methods_module_definition) + mod = const_defined?(:ClassMethods, false) ? + const_get(:ClassMethods) : + const_set(:ClassMethods, Module.new) + + mod.module_eval(&class_methods_module_definition) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/concurrency/load_interlock_aware_monitor.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/concurrency/load_interlock_aware_monitor.rb new file mode 100644 index 00000000..84729e25 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/concurrency/load_interlock_aware_monitor.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require "monitor" + +module ActiveSupport + module Concurrency + module LoadInterlockAwareMonitorMixin # :nodoc: + EXCEPTION_NEVER = { Exception => :never }.freeze + EXCEPTION_IMMEDIATE = { Exception => :immediate }.freeze + private_constant :EXCEPTION_NEVER, :EXCEPTION_IMMEDIATE + + # Enters an exclusive section, but allows dependency loading while blocked + def mon_enter + mon_try_enter || + ActiveSupport::Dependencies.interlock.permit_concurrent_loads { super } + end + + def synchronize(&block) + Thread.handle_interrupt(EXCEPTION_NEVER) do + mon_enter + + begin + Thread.handle_interrupt(EXCEPTION_IMMEDIATE, &block) + ensure + mon_exit + end + end + end + end + # A monitor that will permit dependency loading while blocked waiting for + # the lock. + class LoadInterlockAwareMonitor < Monitor + include LoadInterlockAwareMonitorMixin + end + + class ThreadLoadInterlockAwareMonitor # :nodoc: + prepend LoadInterlockAwareMonitorMixin + + def initialize + @owner = nil + @count = 0 + @mutex = Mutex.new + end + + private + def mon_try_enter + if @owner != Thread.current + return false unless @mutex.try_lock + @owner = Thread.current + end + @count += 1 + end + + def mon_enter + @mutex.lock if @owner != Thread.current + @owner = Thread.current + @count += 1 + end + + def mon_exit + unless @owner == Thread.current + raise ThreadError, "current thread not owner" + end + + @count -= 1 + return unless @count == 0 + @owner = nil + @mutex.unlock + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/concurrency/null_lock.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/concurrency/null_lock.rb new file mode 100644 index 00000000..3810158f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/concurrency/null_lock.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module ActiveSupport + module Concurrency + module NullLock # :nodoc: + extend self + + def synchronize + yield + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/concurrency/share_lock.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/concurrency/share_lock.rb new file mode 100644 index 00000000..3bfb1aee --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/concurrency/share_lock.rb @@ -0,0 +1,225 @@ +# frozen_string_literal: true + +require "monitor" + +module ActiveSupport + module Concurrency + # A share/exclusive lock, otherwise known as a read/write lock. + # + # https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock + class ShareLock + include MonitorMixin + + # We track Thread objects, instead of just using counters, because + # we need exclusive locks to be reentrant, and we need to be able + # to upgrade share locks to exclusive. + + def raw_state # :nodoc: + synchronize do + threads = @sleeping.keys | @sharing.keys | @waiting.keys + threads |= [@exclusive_thread] if @exclusive_thread + + data = {} + + threads.each do |thread| + purpose, compatible = @waiting[thread] + + data[thread] = { + thread: thread, + sharing: @sharing[thread], + exclusive: @exclusive_thread == thread, + purpose: purpose, + compatible: compatible, + waiting: !!@waiting[thread], + sleeper: @sleeping[thread], + } + end + + # NB: Yields while holding our *internal* synchronize lock, + # which is supposed to be used only for a few instructions at + # a time. This allows the caller to inspect additional state + # without things changing out from underneath, but would have + # disastrous effects upon normal operation. Fortunately, this + # method is only intended to be called when things have + # already gone wrong. + yield data + end + end + + def initialize + super() + + @cv = new_cond + + @sharing = Hash.new(0) + @waiting = {} + @sleeping = {} + @exclusive_thread = nil + @exclusive_depth = 0 + end + + # Returns false if +no_wait+ is set and the lock is not + # immediately available. Otherwise, returns true after the lock + # has been acquired. + # + # +purpose+ and +compatible+ work together; while this thread is + # waiting for the exclusive lock, it will yield its share (if any) + # to any other attempt whose +purpose+ appears in this attempt's + # +compatible+ list. This allows a "loose" upgrade, which, being + # less strict, prevents some classes of deadlocks. + # + # For many resources, loose upgrades are sufficient: if a thread + # is awaiting a lock, it is not running any other code. With + # +purpose+ matching, it is possible to yield only to other + # threads whose activity will not interfere. + def start_exclusive(purpose: nil, compatible: [], no_wait: false) + synchronize do + unless @exclusive_thread == Thread.current + if busy_for_exclusive?(purpose) + return false if no_wait + + yield_shares(purpose: purpose, compatible: compatible, block_share: true) do + wait_for(:start_exclusive) { busy_for_exclusive?(purpose) } + end + end + @exclusive_thread = Thread.current + end + @exclusive_depth += 1 + + true + end + end + + # Relinquish the exclusive lock. Must only be called by the thread + # that called start_exclusive (and currently holds the lock). + def stop_exclusive(compatible: []) + synchronize do + raise "invalid unlock" if @exclusive_thread != Thread.current + + @exclusive_depth -= 1 + if @exclusive_depth == 0 + @exclusive_thread = nil + + if eligible_waiters?(compatible) + yield_shares(compatible: compatible, block_share: true) do + wait_for(:stop_exclusive) { @exclusive_thread || eligible_waiters?(compatible) } + end + end + @cv.broadcast + end + end + end + + def start_sharing + synchronize do + if @sharing[Thread.current] > 0 || @exclusive_thread == Thread.current + # We already hold a lock; nothing to wait for + elsif @waiting[Thread.current] + # We're nested inside a +yield_shares+ call: we'll resume as + # soon as there isn't an exclusive lock in our way + wait_for(:start_sharing) { @exclusive_thread } + else + # This is an initial / outermost share call: any outstanding + # requests for an exclusive lock get to go first + wait_for(:start_sharing) { busy_for_sharing?(false) } + end + @sharing[Thread.current] += 1 + end + end + + def stop_sharing + synchronize do + if @sharing[Thread.current] > 1 + @sharing[Thread.current] -= 1 + else + @sharing.delete Thread.current + @cv.broadcast + end + end + end + + # Execute the supplied block while holding the Exclusive lock. If + # +no_wait+ is set and the lock is not immediately available, + # returns +nil+ without yielding. Otherwise, returns the result of + # the block. + # + # See +start_exclusive+ for other options. + def exclusive(purpose: nil, compatible: [], after_compatible: [], no_wait: false) + if start_exclusive(purpose: purpose, compatible: compatible, no_wait: no_wait) + begin + yield + ensure + stop_exclusive(compatible: after_compatible) + end + end + end + + # Execute the supplied block while holding the Share lock. + def sharing + start_sharing + begin + yield + ensure + stop_sharing + end + end + + # Temporarily give up all held Share locks while executing the + # supplied block, allowing any +compatible+ exclusive lock request + # to proceed. + def yield_shares(purpose: nil, compatible: [], block_share: false) + loose_shares = previous_wait = nil + synchronize do + if loose_shares = @sharing.delete(Thread.current) + if previous_wait = @waiting[Thread.current] + purpose = nil unless purpose == previous_wait[0] + compatible &= previous_wait[1] + end + compatible |= [false] unless block_share + @waiting[Thread.current] = [purpose, compatible] + end + + @cv.broadcast + end + + begin + yield + ensure + synchronize do + wait_for(:yield_shares) { @exclusive_thread && @exclusive_thread != Thread.current } + + if previous_wait + @waiting[Thread.current] = previous_wait + else + @waiting.delete Thread.current + end + @sharing[Thread.current] = loose_shares if loose_shares + end + end + end + + private + # Must be called within synchronize + def busy_for_exclusive?(purpose) + busy_for_sharing?(purpose) || + @sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0) + end + + def busy_for_sharing?(purpose) + (@exclusive_thread && @exclusive_thread != Thread.current) || + @waiting.any? { |t, (_, c)| t != Thread.current && !c.include?(purpose) } + end + + def eligible_waiters?(compatible) + @waiting.any? { |t, (p, _)| compatible.include?(p) && @waiting.all? { |t2, (_, c2)| t == t2 || c2.include?(p) } } + end + + def wait_for(method, &block) + @sleeping[Thread.current] = method + @cv.wait_while(&block) + ensure + @sleeping.delete Thread.current + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/configurable.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/configurable.rb new file mode 100644 index 00000000..1e9b8028 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/configurable.rb @@ -0,0 +1,159 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/ordered_options" + +module ActiveSupport + # = Active Support \Configurable + # + # Configurable provides a config method to store and retrieve + # configuration options as an OrderedOptions. + module Configurable + extend ActiveSupport::Concern + + class Configuration < ActiveSupport::InheritableOptions + def compile_methods! + self.class.compile_methods!(keys) + end + + # Compiles reader methods so we don't have to go through method_missing. + def self.compile_methods!(keys) + keys.reject { |m| method_defined?(m) }.each do |key| + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{key}; _get(#{key.inspect}); end + RUBY + end + end + end + + module ClassMethods + def config + @_config ||= if respond_to?(:superclass) && superclass.respond_to?(:config) + superclass.config.inheritable_copy + else + # create a new "anonymous" class that will host the compiled reader methods + Class.new(Configuration).new + end + end + + def configure + yield config + end + + # Allows you to add shortcut so that you don't have to refer to attribute + # through config. Also look at the example for config to contrast. + # + # Defines both class and instance config accessors. + # + # class User + # include ActiveSupport::Configurable + # config_accessor :allowed_access + # end + # + # User.allowed_access # => nil + # User.allowed_access = false + # User.allowed_access # => false + # + # user = User.new + # user.allowed_access # => false + # user.allowed_access = true + # user.allowed_access # => true + # + # User.allowed_access # => false + # + # The attribute name must be a valid method name in Ruby. + # + # class User + # include ActiveSupport::Configurable + # config_accessor :"1_Badname" + # end + # # => NameError: invalid config attribute name + # + # To omit the instance writer method, pass instance_writer: false. + # To omit the instance reader method, pass instance_reader: false. + # + # class User + # include ActiveSupport::Configurable + # config_accessor :allowed_access, instance_reader: false, instance_writer: false + # end + # + # User.allowed_access = false + # User.allowed_access # => false + # + # User.new.allowed_access = true # => NoMethodError + # User.new.allowed_access # => NoMethodError + # + # Or pass instance_accessor: false, to omit both instance methods. + # + # class User + # include ActiveSupport::Configurable + # config_accessor :allowed_access, instance_accessor: false + # end + # + # User.allowed_access = false + # User.allowed_access # => false + # + # User.new.allowed_access = true # => NoMethodError + # User.new.allowed_access # => NoMethodError + # + # Also you can pass default or a block to set up the attribute with a default value. + # + # class User + # include ActiveSupport::Configurable + # config_accessor :allowed_access, default: false + # config_accessor :hair_colors do + # [:brown, :black, :blonde, :red] + # end + # end + # + # User.allowed_access # => false + # User.hair_colors # => [:brown, :black, :blonde, :red] + def config_accessor(*names, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil) # :doc: + names.each do |name| + raise NameError.new("invalid config attribute name") unless /\A[_A-Za-z]\w*\z/.match?(name) + + reader, reader_line = "def #{name}; config.#{name}; end", __LINE__ + writer, writer_line = "def #{name}=(value); config.#{name} = value; end", __LINE__ + + singleton_class.class_eval reader, __FILE__, reader_line + singleton_class.class_eval writer, __FILE__, writer_line + + if instance_accessor + class_eval reader, __FILE__, reader_line if instance_reader + class_eval writer, __FILE__, writer_line if instance_writer + end + + send("#{name}=", block_given? ? yield : default) + end + end + private :config_accessor + + private + def inherited(subclass) + super + subclass.class_eval do + @_config = nil + end + end + end + + # Reads and writes attributes from a configuration OrderedOptions. + # + # require "active_support/configurable" + # + # class User + # include ActiveSupport::Configurable + # end + # + # user = User.new + # + # user.config.allowed_access = true + # user.config.level = 1 + # + # user.config.allowed_access # => true + # user.config.level # => 1 + def config + @_config ||= self.class.config.inheritable_copy + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/configuration_file.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/configuration_file.rb new file mode 100644 index 00000000..78cb1c6d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/configuration_file.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module ActiveSupport + # Reads a YAML configuration file, evaluating any ERB, then + # parsing the resulting YAML. + # + # Warns in case of YAML confusing characters, like invisible + # non-breaking spaces. + class ConfigurationFile # :nodoc: + class FormatError < StandardError; end + + def initialize(content_path) + @content_path = content_path.to_s + @content = read content_path + end + + def self.parse(content_path, **options) + new(content_path).parse(**options) + end + + def parse(context: nil, **options) + source = @content.include?("<%") ? render(context) : @content + + if source == @content + if YAML.respond_to?(:unsafe_load) + YAML.unsafe_load_file(@content_path, **options) || {} + else + YAML.load_file(@content_path, **options) || {} + end + else + if YAML.respond_to?(:unsafe_load) + YAML.unsafe_load(source, **options) || {} + else + YAML.load(source, **options) || {} + end + end + rescue Psych::SyntaxError => error + raise "YAML syntax error occurred while parsing #{@content_path}. " \ + "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ + "Error: #{error.message}" + end + + private + def read(content_path) + require "yaml" unless defined?(YAML) + + File.read(content_path).tap do |content| + if content.include?("\u00A0") + warn "#{content_path} contains invisible non-breaking spaces, you may want to remove those" + end + end + end + + def render(context) + require "erb" unless defined?(ERB) + erb = ERB.new(@content).tap { |e| e.filename = @content_path } + context ? erb.result(context) : erb.result + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext.rb new file mode 100644 index 00000000..3f5d0818 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +Dir.glob(File.expand_path("core_ext/*.rb", __dir__)).sort.each do |path| + require path +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array.rb new file mode 100644 index 00000000..88b65677 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/wrap" +require "active_support/core_ext/array/access" +require "active_support/core_ext/array/conversions" +require "active_support/core_ext/array/extract" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/array/grouping" +require "active_support/core_ext/array/inquiry" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/access.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/access.rb new file mode 100644 index 00000000..8f36669c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/access.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +class Array + # Returns the tail of the array from +position+. + # + # %w( a b c d ).from(0) # => ["a", "b", "c", "d"] + # %w( a b c d ).from(2) # => ["c", "d"] + # %w( a b c d ).from(10) # => [] + # %w().from(0) # => [] + # %w( a b c d ).from(-2) # => ["c", "d"] + # %w( a b c ).from(-10) # => [] + def from(position) + self[position, length] || [] + end + + # Returns the beginning of the array up to +position+. + # + # %w( a b c d ).to(0) # => ["a"] + # %w( a b c d ).to(2) # => ["a", "b", "c"] + # %w( a b c d ).to(10) # => ["a", "b", "c", "d"] + # %w().to(0) # => [] + # %w( a b c d ).to(-2) # => ["a", "b", "c"] + # %w( a b c ).to(-10) # => [] + def to(position) + if position >= 0 + take position + 1 + else + self[0..position] + end + end + + # Returns a new array that includes the passed elements. + # + # [ 1, 2, 3 ].including(4, 5) # => [ 1, 2, 3, 4, 5 ] + # [ [ 0, 1 ] ].including([ [ 1, 0 ] ]) # => [ [ 0, 1 ], [ 1, 0 ] ] + def including(*elements) + self + elements.flatten(1) + end + + # Returns a copy of the Array excluding the specified elements. + # + # ["David", "Rafael", "Aaron", "Todd"].excluding("Aaron", "Todd") # => ["David", "Rafael"] + # [ [ 0, 1 ], [ 1, 0 ] ].excluding([ [ 1, 0 ] ]) # => [ [ 0, 1 ] ] + # + # Note: This is an optimization of Enumerable#excluding that uses Array#- + # instead of Array#reject for performance reasons. + def excluding(*elements) + self - elements.flatten(1) + end + alias :without :excluding + + # Equal to self[1]. + # + # %w( a b c d e ).second # => "b" + def second + self[1] + end + + # Equal to self[2]. + # + # %w( a b c d e ).third # => "c" + def third + self[2] + end + + # Equal to self[3]. + # + # %w( a b c d e ).fourth # => "d" + def fourth + self[3] + end + + # Equal to self[4]. + # + # %w( a b c d e ).fifth # => "e" + def fifth + self[4] + end + + # Equal to self[41]. Also known as accessing "the reddit". + # + # (1..42).to_a.forty_two # => 42 + def forty_two + self[41] + end + + # Equal to self[-3]. + # + # %w( a b c d e ).third_to_last # => "c" + def third_to_last + self[-3] + end + + # Equal to self[-2]. + # + # %w( a b c d e ).second_to_last # => "d" + def second_to_last + self[-2] + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/conversions.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/conversions.rb new file mode 100644 index 00000000..125a67d7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/conversions.rb @@ -0,0 +1,213 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/to_query" + +class Array + # Converts the array to a comma-separated sentence where the last element is + # joined by the connector word. + # + # You can pass the following options to change the default behavior. If you + # pass an option key that doesn't exist in the list below, it will raise an + # ArgumentError. + # + # ==== Options + # + # * :words_connector - The sign or word used to join all but the last + # element in arrays with three or more elements (default: ", "). + # * :last_word_connector - The sign or word used to join the last element + # in arrays with three or more elements (default: ", and "). + # * :two_words_connector - The sign or word used to join the elements + # in arrays with two elements (default: " and "). + # * :locale - If +i18n+ is available, you can set a locale and use + # the connector options defined on the 'support.array' namespace in the + # corresponding dictionary file. + # + # ==== Examples + # + # [].to_sentence # => "" + # ['one'].to_sentence # => "one" + # ['one', 'two'].to_sentence # => "one and two" + # ['one', 'two', 'three'].to_sentence # => "one, two, and three" + # + # ['one', 'two'].to_sentence(passing: 'invalid option') + # # => ArgumentError: Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale + # + # ['one', 'two'].to_sentence(two_words_connector: '-') + # # => "one-two" + # + # ['one', 'two', 'three'].to_sentence(words_connector: ' or ', last_word_connector: ' or at least ') + # # => "one or two or at least three" + # + # Using :locale option: + # + # # Given this locale dictionary: + # # + # # es: + # # support: + # # array: + # # words_connector: " o " + # # two_words_connector: " y " + # # last_word_connector: " o al menos " + # + # ['uno', 'dos'].to_sentence(locale: :es) + # # => "uno y dos" + # + # ['uno', 'dos', 'tres'].to_sentence(locale: :es) + # # => "uno o dos o al menos tres" + def to_sentence(options = {}) + options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale) + + default_connectors = { + words_connector: ", ", + two_words_connector: " and ", + last_word_connector: ", and " + } + if options[:locale] != false && defined?(I18n) + i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {}) + default_connectors.merge!(i18n_connectors) + end + options = default_connectors.merge!(options) + + case length + when 0 + +"" + when 1 + +"#{self[0]}" + when 2 + +"#{self[0]}#{options[:two_words_connector]}#{self[1]}" + else + +"#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}" + end + end + + # Extends Array#to_s to convert a collection of elements into a + # comma separated id list if :db argument is given as the format. + # + # This method is aliased to to_formatted_s. + # + # Blog.all.to_fs(:db) # => "1,2,3" + # Blog.none.to_fs(:db) # => "null" + # [1,2].to_fs # => "[1, 2]" + def to_fs(format = :default) + case format + when :db + if empty? + "null" + else + collect(&:id).join(",") + end + else + to_s + end + end + alias_method :to_formatted_s, :to_fs + + # Returns a string that represents the array in XML by invoking +to_xml+ + # on each element. Active Record collections delegate their representation + # in XML to this method. + # + # All elements are expected to respond to +to_xml+, if any of them does + # not then an exception is raised. + # + # The root node reflects the class name of the first element in plural + # if all elements belong to the same type and that's not Hash: + # + # customer.projects.to_xml + # + # + # + # + # 20000.0 + # 1567 + # 2008-04-09 + # ... + # + # + # 57230.0 + # 1567 + # 2008-04-15 + # ... + # + # + # + # Otherwise the root element is "objects": + # + # [{ foo: 1, bar: 2}, { baz: 3}].to_xml + # + # + # + # + # 2 + # 1 + # + # + # 3 + # + # + # + # If the collection is empty the root element is "nil-classes" by default: + # + # [].to_xml + # + # + # + # + # To ensure a meaningful root element use the :root option: + # + # customer_with_no_projects.projects.to_xml(root: 'projects') + # + # + # + # + # By default name of the node for the children of root is root.singularize. + # You can change it with the :children option. + # + # The +options+ hash is passed downwards: + # + # Message.all.to_xml(skip_types: true) + # + # + # + # + # 2008-03-07T09:58:18+01:00 + # 1 + # 1 + # 2008-03-07T09:58:18+01:00 + # 1 + # + # + # + def to_xml(options = {}) + require "active_support/builder" unless defined?(Builder::XmlMarkup) + + options = options.dup + options[:indent] ||= 2 + options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent]) + options[:root] ||= \ + if first.class != Hash && all?(first.class) + underscored = ActiveSupport::Inflector.underscore(first.class.name) + ActiveSupport::Inflector.pluralize(underscored).tr("/", "_") + else + "objects" + end + + builder = options[:builder] + builder.instruct! unless options.delete(:skip_instruct) + + root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options) + children = options.delete(:children) || root.singularize + attributes = options[:skip_types] ? {} : { type: "array" } + + if empty? + builder.tag!(root, attributes) + else + builder.tag!(root, attributes) do + each { |value| ActiveSupport::XmlMini.to_tag(children, value, options) } + yield builder if block_given? + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/extract.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/extract.rb new file mode 100644 index 00000000..cc5a8a3f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/extract.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class Array + # Removes and returns the elements for which the block returns a true value. + # If no block is given, an Enumerator is returned instead. + # + # numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + # odd_numbers = numbers.extract! { |number| number.odd? } # => [1, 3, 5, 7, 9] + # numbers # => [0, 2, 4, 6, 8] + def extract! + return to_enum(:extract!) { size } unless block_given? + + extracted_elements = [] + + reject! do |element| + extracted_elements << element if yield(element) + end + + extracted_elements + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/extract_options.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/extract_options.rb new file mode 100644 index 00000000..8c7cb2e7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/extract_options.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class Hash + # By default, only instances of Hash itself are extractable. + # Subclasses of Hash may implement this method and return + # true to declare themselves as extractable. If a Hash + # is extractable, Array#extract_options! pops it from + # the Array when it is the last element of the Array. + def extractable_options? + instance_of?(Hash) + end +end + +class Array + # Extracts options from a set of arguments. Removes and returns the last + # element in the array if it's a hash, otherwise returns a blank hash. + # + # def options(*args) + # args.extract_options! + # end + # + # options(1, 2) # => {} + # options(1, 2, a: :b) # => {:a=>:b} + def extract_options! + if last.is_a?(Hash) && last.extractable_options? + pop + else + {} + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/grouping.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/grouping.rb new file mode 100644 index 00000000..36993e01 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/grouping.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +class Array + # Splits or iterates over the array in groups of size +number+, + # padding any remaining slots with +fill_with+ unless it is +false+. + # + # %w(1 2 3 4 5 6 7 8 9 10).in_groups_of(3) {|group| p group} + # ["1", "2", "3"] + # ["4", "5", "6"] + # ["7", "8", "9"] + # ["10", nil, nil] + # + # %w(1 2 3 4 5).in_groups_of(2, ' ') {|group| p group} + # ["1", "2"] + # ["3", "4"] + # ["5", " "] + # + # %w(1 2 3 4 5).in_groups_of(2, false) {|group| p group} + # ["1", "2"] + # ["3", "4"] + # ["5"] + def in_groups_of(number, fill_with = nil, &block) + if number.to_i <= 0 + raise ArgumentError, + "Group size must be a positive integer, was #{number.inspect}" + end + + if fill_with == false + collection = self + else + # size % number gives how many extra we have; + # subtracting from number gives how many to add; + # modulo number ensures we don't add group of just fill. + padding = (number - size % number) % number + collection = dup.concat(Array.new(padding, fill_with)) + end + + if block_given? + collection.each_slice(number, &block) + else + collection.each_slice(number).to_a + end + end + + # Splits or iterates over the array in +number+ of groups, padding any + # remaining slots with +fill_with+ unless it is +false+. + # + # %w(1 2 3 4 5 6 7 8 9 10).in_groups(3) {|group| p group} + # ["1", "2", "3", "4"] + # ["5", "6", "7", nil] + # ["8", "9", "10", nil] + # + # %w(1 2 3 4 5 6 7 8 9 10).in_groups(3, ' ') {|group| p group} + # ["1", "2", "3", "4"] + # ["5", "6", "7", " "] + # ["8", "9", "10", " "] + # + # %w(1 2 3 4 5 6 7).in_groups(3, false) {|group| p group} + # ["1", "2", "3"] + # ["4", "5"] + # ["6", "7"] + def in_groups(number, fill_with = nil, &block) + # size.div number gives minor group size; + # size % number gives how many objects need extra accommodation; + # each group hold either division or division + 1 items. + division = size.div number + modulo = size % number + + # create a new array avoiding dup + groups = [] + start = 0 + + number.times do |index| + length = division + (modulo > 0 && modulo > index ? 1 : 0) + groups << last_group = slice(start, length) + last_group << fill_with if fill_with != false && + modulo > 0 && length == division + start += length + end + + if block_given? + groups.each(&block) + else + groups + end + end + + # Divides the array into one or more subarrays based on a delimiting +value+ + # or the result of an optional block. + # + # [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]] + # (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]] + def split(value = nil, &block) + arr = dup + result = [] + if block_given? + while (idx = arr.index(&block)) + result << arr.shift(idx) + arr.shift + end + else + while (idx = arr.index(value)) + result << arr.shift(idx) + arr.shift + end + end + result << arr + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/inquiry.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/inquiry.rb new file mode 100644 index 00000000..650b1067 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/inquiry.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "active_support/array_inquirer" + +class Array + # Wraps the array in an ActiveSupport::ArrayInquirer object, which gives a + # friendlier way to check its string-like contents. + # + # pets = [:cat, :dog].inquiry + # + # pets.cat? # => true + # pets.ferret? # => false + # + # pets.any?(:cat, :ferret) # => true + # pets.any?(:ferret, :alligator) # => false + def inquiry + ActiveSupport::ArrayInquirer.new(self) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/wrap.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/wrap.rb new file mode 100644 index 00000000..d62f97ed --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/array/wrap.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +class Array + # Wraps its argument in an array unless it is already an array (or array-like). + # + # Specifically: + # + # * If the argument is +nil+ an empty array is returned. + # * Otherwise, if the argument responds to +to_ary+ it is invoked, and its result returned. + # * Otherwise, returns an array with the argument as its single element. + # + # Array.wrap(nil) # => [] + # Array.wrap([1, 2, 3]) # => [1, 2, 3] + # Array.wrap(0) # => [0] + # + # This method is similar in purpose to Kernel#Array, but there are some differences: + # + # * If the argument responds to +to_ary+ the method is invoked. Kernel#Array + # moves on to try +to_a+ if the returned value is +nil+, but Array.wrap returns + # an array with the argument as its single element right away. + # * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, Kernel#Array + # raises an exception, while Array.wrap does not, it just returns the value. + # * It does not call +to_a+ on the argument, if the argument does not respond to +to_ary+ + # it returns an array with the argument as its single element. + # + # The last point is easily explained with some enumerables: + # + # Array(foo: :bar) # => [[:foo, :bar]] + # Array.wrap(foo: :bar) # => [{:foo=>:bar}] + # + # There's also a related idiom that uses the splat operator: + # + # [*object] + # + # which returns [] for +nil+, but calls to Array(object) otherwise. + # + # The differences with Kernel#Array explained above + # apply to the rest of objects. + def self.wrap(object) + if object.nil? + [] + elsif object.respond_to?(:to_ary) + object.to_ary || [object] + else + [object] + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/benchmark.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/benchmark.rb new file mode 100644 index 00000000..20675e30 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/benchmark.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require "benchmark" + +class << Benchmark + def ms(&block) # :nodoc + # NOTE: Please also remove the Active Support `benchmark` dependency when removing this + ActiveSupport.deprecator.warn <<~TEXT + `Benchmark.ms` is deprecated and will be removed in Rails 8.1 without replacement. + TEXT + ActiveSupport::Benchmark.realtime(:float_millisecond, &block) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/big_decimal.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/big_decimal.rb new file mode 100644 index 00000000..9e6a9d63 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/big_decimal.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "active_support/core_ext/big_decimal/conversions" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/big_decimal/conversions.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/big_decimal/conversions.rb new file mode 100644 index 00000000..76ad5843 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/big_decimal/conversions.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "bigdecimal" +require "bigdecimal/util" + +module ActiveSupport + module BigDecimalWithDefaultFormat # :nodoc: + def to_s(format = "F") + super(format) + end + end +end + +BigDecimal.prepend(ActiveSupport::BigDecimalWithDefaultFormat) diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/class.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/class.rb new file mode 100644 index 00000000..1c110fd0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/class.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +require "active_support/core_ext/class/attribute" +require "active_support/core_ext/class/subclasses" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/class/attribute.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/class/attribute.rb new file mode 100644 index 00000000..57264dfe --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/class/attribute.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/redefine_method" +require "active_support/class_attribute" + +class Class + # Declare a class-level attribute whose value is inheritable by subclasses. + # Subclasses can change their own value and it will not impact parent class. + # + # ==== Options + # + # * :instance_reader - Sets the instance reader method (defaults to true). + # * :instance_writer - Sets the instance writer method (defaults to true). + # * :instance_accessor - Sets both instance methods (defaults to true). + # * :instance_predicate - Sets a predicate method (defaults to true). + # * :default - Sets a default value for the attribute (defaults to nil). + # + # ==== Examples + # + # class Base + # class_attribute :setting + # end + # + # class Subclass < Base + # end + # + # Base.setting = true + # Subclass.setting # => true + # Subclass.setting = false + # Subclass.setting # => false + # Base.setting # => true + # + # In the above case as long as Subclass does not assign a value to setting + # by performing Subclass.setting = _something_, Subclass.setting + # would read value assigned to parent class. Once Subclass assigns a value then + # the value assigned by Subclass would be returned. + # + # This matches normal Ruby method inheritance: think of writing an attribute + # on a subclass as overriding the reader method. However, you need to be aware + # when using +class_attribute+ with mutable structures as +Array+ or +Hash+. + # In such cases, you don't want to do changes in place. Instead use setters: + # + # Base.setting = [] + # Base.setting # => [] + # Subclass.setting # => [] + # + # # Appending in child changes both parent and child because it is the same object: + # Subclass.setting << :foo + # Base.setting # => [:foo] + # Subclass.setting # => [:foo] + # + # # Use setters to not propagate changes: + # Base.setting = [] + # Subclass.setting += [:foo] + # Base.setting # => [] + # Subclass.setting # => [:foo] + # + # For convenience, an instance predicate method is defined as well. + # To skip it, pass instance_predicate: false. + # + # Subclass.setting? # => false + # + # Instances may overwrite the class value in the same way: + # + # Base.setting = true + # object = Base.new + # object.setting # => true + # object.setting = false + # object.setting # => false + # Base.setting # => true + # + # To opt out of the instance reader method, pass instance_reader: false. + # + # object.setting # => NoMethodError + # object.setting? # => NoMethodError + # + # To opt out of the instance writer method, pass instance_writer: false. + # + # object.setting = false # => NoMethodError + # + # To opt out of both instance methods, pass instance_accessor: false. + # + # To set a default value for the attribute, pass default:, like so: + # + # class_attribute :settings, default: {} + def class_attribute(*attrs, instance_accessor: true, + instance_reader: instance_accessor, instance_writer: instance_accessor, instance_predicate: true, default: nil + ) + class_methods, methods = [], [] + attrs.each do |name| + unless name.is_a?(Symbol) || name.is_a?(String) + raise TypeError, "#{name.inspect} is not a symbol nor a string" + end + + name = name.to_sym + namespaced_name = :"__class_attr_#{name}" + ::ActiveSupport::ClassAttribute.redefine(self, name, namespaced_name, default) + + delegators = [ + "def #{name}; #{namespaced_name}; end", + "def #{name}=(value); self.#{namespaced_name} = value; end", + ] + + class_methods.concat(delegators) + if singleton_class? + methods.concat(delegators) + else + methods << <<~RUBY if instance_reader + silence_redefinition_of_method def #{name} + if defined?(@#{name}) + @#{name} + else + self.class.#{name} + end + end + RUBY + end + + methods << <<~RUBY if instance_writer + silence_redefinition_of_method(:#{name}=) + attr_writer :#{name} + RUBY + + if instance_predicate + class_methods << "silence_redefinition_of_method def #{name}?; !!self.#{name}; end" + if instance_reader + methods << "silence_redefinition_of_method def #{name}?; !!self.#{name}; end" + end + end + end + + location = caller_locations(1, 1).first + class_eval(["class << self", *class_methods, "end", *methods].join(";").tr("\n", ";"), location.path, location.lineno) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/class/attribute_accessors.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/class/attribute_accessors.rb new file mode 100644 index 00000000..a77354e1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/class/attribute_accessors.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +# cattr_* became mattr_* aliases in 7dfbd91b0780fbd6a1dd9bfbc176e10894871d2d, +# but we keep this around for libraries that directly require it knowing they +# want cattr_*. No need to deprecate. +require "active_support/core_ext/module/attribute_accessors" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/class/subclasses.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/class/subclasses.rb new file mode 100644 index 00000000..dd5b9e33 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/class/subclasses.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "active_support/descendants_tracker" + +class Class + # Returns an array with all classes that are < than its receiver. + # + # class C; end + # C.descendants # => [] + # + # class B < C; end + # C.descendants # => [B] + # + # class A < B; end + # C.descendants # => [B, A] + # + # class D < C; end + # C.descendants # => [B, A, D] + def descendants + subclasses.concat(subclasses.flat_map(&:descendants)) + end + + prepend ActiveSupport::DescendantsTracker::ReloadedClassesFiltering +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date.rb new file mode 100644 index 00000000..cce73f2d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "active_support/core_ext/date/acts_like" +require "active_support/core_ext/date/blank" +require "active_support/core_ext/date/calculations" +require "active_support/core_ext/date/conversions" +require "active_support/core_ext/date/zones" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date/acts_like.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date/acts_like.rb new file mode 100644 index 00000000..c8077f37 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date/acts_like.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/acts_like" + +class Date + # Duck-types as a Date-like class. See Object#acts_like?. + def acts_like_date? + true + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date/blank.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date/blank.rb new file mode 100644 index 00000000..4419f86c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date/blank.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require "date" + +class Date # :nodoc: + # No Date is blank: + # + # Date.today.blank? # => false + # + # @return [false] + def blank? + false + end + + def present? + true + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date/calculations.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date/calculations.rb new file mode 100644 index 00000000..7dea4697 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date/calculations.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true + +require "date" +require "active_support/duration" +require "active_support/core_ext/object/acts_like" +require "active_support/core_ext/date/zones" +require "active_support/core_ext/time/zones" +require "active_support/core_ext/date_and_time/calculations" + +class Date + include DateAndTime::Calculations + + class << self + attr_accessor :beginning_of_week_default + + # Returns the week start (e.g. +:monday+) for the current request, if this has been set (via Date.beginning_of_week=). + # If Date.beginning_of_week has not been set for the current request, returns the week start specified in config.beginning_of_week. + # If no +config.beginning_of_week+ was specified, returns +:monday+. + def beginning_of_week + ::ActiveSupport::IsolatedExecutionState[:beginning_of_week] || beginning_of_week_default || :monday + end + + # Sets Date.beginning_of_week to a week start (e.g. +:monday+) for current request/thread. + # + # This method accepts any of the following day symbols: + # +:monday+, +:tuesday+, +:wednesday+, +:thursday+, +:friday+, +:saturday+, +:sunday+ + def beginning_of_week=(week_start) + ::ActiveSupport::IsolatedExecutionState[:beginning_of_week] = find_beginning_of_week!(week_start) + end + + # Returns week start day symbol (e.g. +:monday+), or raises an +ArgumentError+ for invalid day symbol. + def find_beginning_of_week!(week_start) + raise ArgumentError, "Invalid beginning of week: #{week_start}" unless ::Date::DAYS_INTO_WEEK.key?(week_start) + week_start + end + + # Returns a new Date representing the date 1 day ago (i.e. yesterday's date). + def yesterday + ::Date.current.yesterday + end + + # Returns a new Date representing the date 1 day after today (i.e. tomorrow's date). + def tomorrow + ::Date.current.tomorrow + end + + # Returns Time.zone.today when Time.zone or config.time_zone are set, otherwise just returns Date.today. + def current + ::Time.zone ? ::Time.zone.today : ::Date.today + end + end + + # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00) + # and then subtracts the specified number of seconds. + def ago(seconds) + in_time_zone.since(-seconds) + end + + # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00) + # and then adds the specified number of seconds + def since(seconds) + in_time_zone.since(seconds) + end + alias :in :since + + # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00) + def beginning_of_day + in_time_zone + end + alias :midnight :beginning_of_day + alias :at_midnight :beginning_of_day + alias :at_beginning_of_day :beginning_of_day + + # Converts Date to a Time (or DateTime if necessary) with the time portion set to the middle of the day (12:00) + def middle_of_day + in_time_zone.middle_of_day + end + alias :midday :middle_of_day + alias :noon :middle_of_day + alias :at_midday :middle_of_day + alias :at_noon :middle_of_day + alias :at_middle_of_day :middle_of_day + + # Converts Date to a Time (or DateTime if necessary) with the time portion set to the end of the day (23:59:59) + def end_of_day + in_time_zone.end_of_day + end + alias :at_end_of_day :end_of_day + + def plus_with_duration(other) # :nodoc: + if ActiveSupport::Duration === other + other.since(self) + else + plus_without_duration(other) + end + end + alias_method :plus_without_duration, :+ + alias_method :+, :plus_with_duration + + def minus_with_duration(other) # :nodoc: + if ActiveSupport::Duration === other + plus_with_duration(-other) + else + minus_without_duration(other) + end + end + alias_method :minus_without_duration, :- + alias_method :-, :minus_with_duration + + # Provides precise Date calculations for years, months, and days. The +options+ parameter takes a hash with + # any of these keys: :years, :months, :weeks, :days. + # + # The increments are applied in order of time units from largest to smallest. + # In other words, the date is incremented first by +:years+, then by + # +:months+, then by +:weeks+, then by +:days+. This order can affect the + # result around the end of a month. For example, incrementing first by months + # then by days: + # + # Date.new(2004, 9, 30).advance(months: 1, days: 1) + # # => Sun, 31 Oct 2004 + # + # Whereas incrementing first by days then by months yields a different result: + # + # Date.new(2004, 9, 30).advance(days: 1).advance(months: 1) + # # => Mon, 01 Nov 2004 + # + def advance(options) + d = self + + d = d >> options[:years] * 12 if options[:years] + d = d >> options[:months] if options[:months] + d = d + options[:weeks] * 7 if options[:weeks] + d = d + options[:days] if options[:days] + + d + end + + # Returns a new Date where one or more of the elements have been changed according to the +options+ parameter. + # The +options+ parameter is a hash with a combination of these keys: :year, :month, :day. + # + # Date.new(2007, 5, 12).change(day: 1) # => Date.new(2007, 5, 1) + # Date.new(2007, 5, 12).change(year: 2005, month: 1) # => Date.new(2005, 1, 12) + def change(options) + ::Date.new( + options.fetch(:year, year), + options.fetch(:month, month), + options.fetch(:day, day) + ) + end + + # Allow Date to be compared with Time by converting to DateTime and relying on the <=> from there. + def compare_with_coercion(other) + if other.is_a?(Time) + to_datetime <=> other + else + compare_without_coercion(other) + end + end + alias_method :compare_without_coercion, :<=> + alias_method :<=>, :compare_with_coercion +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date/conversions.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date/conversions.rb new file mode 100644 index 00000000..983a3bf3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date/conversions.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +require "date" +require "active_support/inflector/methods" +require "active_support/core_ext/date/zones" +require "active_support/core_ext/module/redefine_method" + +class Date + DATE_FORMATS = { + short: "%d %b", + long: "%B %d, %Y", + db: "%Y-%m-%d", + inspect: "%Y-%m-%d", + number: "%Y%m%d", + long_ordinal: lambda { |date| + day_format = ActiveSupport::Inflector.ordinalize(date.day) + date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007" + }, + rfc822: "%d %b %Y", + rfc2822: "%d %b %Y", + iso8601: lambda { |date| date.iso8601 } + } + + # Convert to a formatted string. See DATE_FORMATS for predefined formats. + # + # This method is aliased to to_formatted_s. + # + # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007 + # + # date.to_fs(:db) # => "2007-11-10" + # date.to_formatted_s(:db) # => "2007-11-10" + # + # date.to_fs(:short) # => "10 Nov" + # date.to_fs(:number) # => "20071110" + # date.to_fs(:long) # => "November 10, 2007" + # date.to_fs(:long_ordinal) # => "November 10th, 2007" + # date.to_fs(:rfc822) # => "10 Nov 2007" + # date.to_fs(:rfc2822) # => "10 Nov 2007" + # date.to_fs(:iso8601) # => "2007-11-10" + # + # == Adding your own date formats to to_fs + # You can add your own formats to the Date::DATE_FORMATS hash. + # Use the format name as the hash key and either a strftime string + # or Proc instance that takes a date argument as the value. + # + # # config/initializers/date_formats.rb + # Date::DATE_FORMATS[:month_and_year] = '%B %Y' + # Date::DATE_FORMATS[:short_ordinal] = ->(date) { date.strftime("%B #{date.day.ordinalize}") } + def to_fs(format = :default) + if formatter = DATE_FORMATS[format] + if formatter.respond_to?(:call) + formatter.call(self).to_s + else + strftime(formatter) + end + else + to_s + end + end + alias_method :to_formatted_s, :to_fs + + # Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005" + def readable_inspect + strftime("%a, %d %b %Y") + end + alias_method :default_inspect, :inspect + alias_method :inspect, :readable_inspect + + silence_redefinition_of_method :to_time + + # Converts a Date instance to a Time, where the time is set to the beginning of the day. + # The timezone can be either +:local+ or +:utc+ (default +:local+). + # + # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007 + # + # date.to_time # => 2007-11-10 00:00:00 0800 + # date.to_time(:local) # => 2007-11-10 00:00:00 0800 + # + # date.to_time(:utc) # => 2007-11-10 00:00:00 UTC + # + # NOTE: The +:local+ timezone is Ruby's *process* timezone, i.e. ENV['TZ']. + # If the application's timezone is needed, then use +in_time_zone+ instead. + def to_time(form = :local) + raise ArgumentError, "Expected :local or :utc, got #{form.inspect}." unless [:local, :utc].include?(form) + ::Time.public_send(form, year, month, day) + end + + silence_redefinition_of_method :xmlschema + + # Returns a string which represents the time in used time zone as DateTime + # defined by XML Schema: + # + # date = Date.new(2015, 05, 23) # => Sat, 23 May 2015 + # date.xmlschema # => "2015-05-23T00:00:00+04:00" + def xmlschema + in_time_zone.xmlschema + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date/zones.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date/zones.rb new file mode 100644 index 00000000..2dcf97cf --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date/zones.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "date" +require "active_support/core_ext/date_and_time/zones" + +class Date + include DateAndTime::Zones +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_and_time/calculations.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_and_time/calculations.rb new file mode 100644 index 00000000..e713cee1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_and_time/calculations.rb @@ -0,0 +1,374 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/try" +require "active_support/core_ext/date_time/conversions" + +module DateAndTime + module Calculations + DAYS_INTO_WEEK = { + sunday: 0, + monday: 1, + tuesday: 2, + wednesday: 3, + thursday: 4, + friday: 5, + saturday: 6 + } + WEEKEND_DAYS = [ 6, 0 ] + + # Returns a new date/time representing yesterday. + def yesterday + advance(days: -1) + end + + # Returns a new date/time representing tomorrow. + def tomorrow + advance(days: 1) + end + + # Returns true if the date/time is today. + def today? + to_date == ::Date.current + end + + # Returns true if the date/time is tomorrow. + def tomorrow? + to_date == ::Date.current.tomorrow + end + alias :next_day? :tomorrow? + + # Returns true if the date/time is yesterday. + def yesterday? + to_date == ::Date.current.yesterday + end + alias :prev_day? :yesterday? + + # Returns true if the date/time is in the past. + def past? + self < self.class.current + end + + # Returns true if the date/time is in the future. + def future? + self > self.class.current + end + + # Returns true if the date/time falls on a Saturday or Sunday. + def on_weekend? + WEEKEND_DAYS.include?(wday) + end + + # Returns true if the date/time does not fall on a Saturday or Sunday. + def on_weekday? + !WEEKEND_DAYS.include?(wday) + end + + # Returns true if the date/time falls before date_or_time. + def before?(date_or_time) + self < date_or_time + end + + # Returns true if the date/time falls after date_or_time. + def after?(date_or_time) + self > date_or_time + end + + # Returns a new date/time the specified number of days ago. + def days_ago(days) + advance(days: -days) + end + + # Returns a new date/time the specified number of days in the future. + def days_since(days) + advance(days: days) + end + + # Returns a new date/time the specified number of weeks ago. + def weeks_ago(weeks) + advance(weeks: -weeks) + end + + # Returns a new date/time the specified number of weeks in the future. + def weeks_since(weeks) + advance(weeks: weeks) + end + + # Returns a new date/time the specified number of months ago. + def months_ago(months) + advance(months: -months) + end + + # Returns a new date/time the specified number of months in the future. + def months_since(months) + advance(months: months) + end + + # Returns a new date/time the specified number of years ago. + def years_ago(years) + advance(years: -years) + end + + # Returns a new date/time the specified number of years in the future. + def years_since(years) + advance(years: years) + end + + # Returns a new date/time at the start of the month. + # + # today = Date.today # => Thu, 18 Jun 2015 + # today.beginning_of_month # => Mon, 01 Jun 2015 + # + # +DateTime+ objects will have a time set to 0:00. + # + # now = DateTime.current # => Thu, 18 Jun 2015 15:23:13 +0000 + # now.beginning_of_month # => Mon, 01 Jun 2015 00:00:00 +0000 + def beginning_of_month + first_hour(change(day: 1)) + end + alias :at_beginning_of_month :beginning_of_month + + # Returns a new date/time at the start of the quarter. + # + # today = Date.today # => Fri, 10 Jul 2015 + # today.beginning_of_quarter # => Wed, 01 Jul 2015 + # + # +DateTime+ objects will have a time set to 0:00. + # + # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000 + # now.beginning_of_quarter # => Wed, 01 Jul 2015 00:00:00 +0000 + def beginning_of_quarter + first_quarter_month = month - (2 + month) % 3 + beginning_of_month.change(month: first_quarter_month) + end + alias :at_beginning_of_quarter :beginning_of_quarter + + # Returns a new date/time at the end of the quarter. + # + # today = Date.today # => Fri, 10 Jul 2015 + # today.end_of_quarter # => Wed, 30 Sep 2015 + # + # +DateTime+ objects will have a time set to 23:59:59. + # + # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000 + # now.end_of_quarter # => Wed, 30 Sep 2015 23:59:59 +0000 + def end_of_quarter + last_quarter_month = month + (12 - month) % 3 + beginning_of_month.change(month: last_quarter_month).end_of_month + end + alias :at_end_of_quarter :end_of_quarter + + # Returns the quarter for a date/time. + # + # Date.new(2010, 1, 31).quarter # => 1 + # Date.new(2010, 4, 12).quarter # => 2 + # Date.new(2010, 9, 15).quarter # => 3 + # Date.new(2010, 12, 25).quarter # => 4 + def quarter + (month / 3.0).ceil + end + + # Returns a new date/time at the beginning of the year. + # + # today = Date.today # => Fri, 10 Jul 2015 + # today.beginning_of_year # => Thu, 01 Jan 2015 + # + # +DateTime+ objects will have a time set to 0:00. + # + # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000 + # now.beginning_of_year # => Thu, 01 Jan 2015 00:00:00 +0000 + def beginning_of_year + change(month: 1).beginning_of_month + end + alias :at_beginning_of_year :beginning_of_year + + # Returns a new date/time representing the given day in the next week. + # + # today = Date.today # => Thu, 07 May 2015 + # today.next_week # => Mon, 11 May 2015 + # + # The +given_day_in_next_week+ defaults to the beginning of the week + # which is determined by +Date.beginning_of_week+ or +config.beginning_of_week+ + # when set. + # + # today = Date.today # => Thu, 07 May 2015 + # today.next_week(:friday) # => Fri, 15 May 2015 + # + # +DateTime+ objects have their time set to 0:00 unless +same_time+ is true. + # + # now = DateTime.current # => Thu, 07 May 2015 13:31:16 +0000 + # now.next_week # => Mon, 11 May 2015 00:00:00 +0000 + def next_week(given_day_in_next_week = Date.beginning_of_week, same_time: false) + result = first_hour(weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week))) + same_time ? copy_time_to(result) : result + end + + # Returns a new date/time representing the next weekday. + def next_weekday + if next_day.on_weekend? + next_week(:monday, same_time: true) + else + next_day + end + end + + # Short-hand for months_since(3). + def next_quarter + months_since(3) + end + + # Returns a new date/time representing the given day in the previous week. + # Week is assumed to start on +start_day+, default is + # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. + # DateTime objects have their time set to 0:00 unless +same_time+ is true. + def prev_week(start_day = Date.beginning_of_week, same_time: false) + result = first_hour(weeks_ago(1).beginning_of_week.days_since(days_span(start_day))) + same_time ? copy_time_to(result) : result + end + alias_method :last_week, :prev_week + + # Returns a new date/time representing the previous weekday. + def prev_weekday + if prev_day.on_weekend? + copy_time_to(beginning_of_week(:friday)) + else + prev_day + end + end + alias_method :last_weekday, :prev_weekday + + # Short-hand for months_ago(1). + def last_month + months_ago(1) + end + + # Short-hand for months_ago(3). + def prev_quarter + months_ago(3) + end + alias_method :last_quarter, :prev_quarter + + # Short-hand for years_ago(1). + def last_year + years_ago(1) + end + + # Returns the number of days to the start of the week on the given day. + # Week is assumed to start on +start_day+, default is + # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. + def days_to_week_start(start_day = Date.beginning_of_week) + start_day_number = DAYS_INTO_WEEK.fetch(start_day) + (wday - start_day_number) % 7 + end + + # Returns a new date/time representing the start of this week on the given day. + # Week is assumed to start on +start_day+, default is + # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. + # +DateTime+ objects have their time set to 0:00. + def beginning_of_week(start_day = Date.beginning_of_week) + result = days_ago(days_to_week_start(start_day)) + acts_like?(:time) ? result.midnight : result + end + alias :at_beginning_of_week :beginning_of_week + + # Returns Monday of this week assuming that week starts on Monday. + # +DateTime+ objects have their time set to 0:00. + def monday + beginning_of_week(:monday) + end + + # Returns a new date/time representing the end of this week on the given day. + # Week is assumed to start on +start_day+, default is + # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. + # DateTime objects have their time set to 23:59:59. + def end_of_week(start_day = Date.beginning_of_week) + last_hour(days_since(6 - days_to_week_start(start_day))) + end + alias :at_end_of_week :end_of_week + + # Returns Sunday of this week assuming that week starts on Monday. + # +DateTime+ objects have their time set to 23:59:59. + def sunday + end_of_week(:monday) + end + + # Returns a new date/time representing the end of the month. + # DateTime objects will have a time set to 23:59:59. + def end_of_month + last_day = ::Time.days_in_month(month, year) + last_hour(days_since(last_day - day)) + end + alias :at_end_of_month :end_of_month + + # Returns a new date/time representing the end of the year. + # DateTime objects will have a time set to 23:59:59. + def end_of_year + change(month: 12).end_of_month + end + alias :at_end_of_year :end_of_year + + # Returns a Range representing the whole day of the current date/time. + def all_day + beginning_of_day..end_of_day + end + + # Returns a Range representing the whole week of the current date/time. + # Week starts on start_day, default is Date.beginning_of_week or config.beginning_of_week when set. + def all_week(start_day = Date.beginning_of_week) + beginning_of_week(start_day)..end_of_week(start_day) + end + + # Returns a Range representing the whole month of the current date/time. + def all_month + beginning_of_month..end_of_month + end + + # Returns a Range representing the whole quarter of the current date/time. + def all_quarter + beginning_of_quarter..end_of_quarter + end + + # Returns a Range representing the whole year of the current date/time. + def all_year + beginning_of_year..end_of_year + end + + # Returns a new date/time representing the next occurrence of the specified day of week. + # + # today = Date.today # => Thu, 14 Dec 2017 + # today.next_occurring(:monday) # => Mon, 18 Dec 2017 + # today.next_occurring(:thursday) # => Thu, 21 Dec 2017 + def next_occurring(day_of_week) + from_now = DAYS_INTO_WEEK.fetch(day_of_week) - wday + from_now += 7 unless from_now > 0 + advance(days: from_now) + end + + # Returns a new date/time representing the previous occurrence of the specified day of week. + # + # today = Date.today # => Thu, 14 Dec 2017 + # today.prev_occurring(:monday) # => Mon, 11 Dec 2017 + # today.prev_occurring(:thursday) # => Thu, 07 Dec 2017 + def prev_occurring(day_of_week) + ago = wday - DAYS_INTO_WEEK.fetch(day_of_week) + ago += 7 unless ago > 0 + advance(days: -ago) + end + + private + def first_hour(date_or_time) + date_or_time.acts_like?(:time) ? date_or_time.beginning_of_day : date_or_time + end + + def last_hour(date_or_time) + date_or_time.acts_like?(:time) ? date_or_time.end_of_day : date_or_time + end + + def days_span(day) + (DAYS_INTO_WEEK.fetch(day) - DAYS_INTO_WEEK.fetch(Date.beginning_of_week)) % 7 + end + + def copy_time_to(other) + other.change(hour: hour, min: min, sec: sec, nsec: try(:nsec)) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_and_time/compatibility.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_and_time/compatibility.rb new file mode 100644 index 00000000..68fb42c5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_and_time/compatibility.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/module/redefine_method" + +module DateAndTime + module Compatibility + # If true, +to_time+ preserves the timezone offset of receiver. + # + # NOTE: With Ruby 2.4+ the default for +to_time+ changed from + # converting to the local system time, to preserving the offset + # of the receiver. For backwards compatibility we're overriding + # this behavior, but new apps will have an initializer that sets + # this to true, because the new behavior is preferred. + mattr_accessor :preserve_timezone, instance_accessor: false, default: nil + + singleton_class.silence_redefinition_of_method :preserve_timezone + + #-- + # This re-implements the behaviour of the mattr_reader, instead + # of prepending on to it, to avoid overcomplicating a module that + # is in turn included in several places. This will all go away in + # Rails 8.0 anyway. + def self.preserve_timezone # :nodoc: + if @@preserve_timezone.nil? + # Only warn once, the first time the value is used (which should + # be the first time #to_time is called). + ActiveSupport.deprecator.warn( + "`to_time` will always preserve the receiver timezone rather than system local time in Rails 8.1." \ + "To opt in to the new behavior, set `config.active_support.to_time_preserves_timezone = :zone`." + ) + + @@preserve_timezone = false + end + + @@preserve_timezone + end + + def preserve_timezone # :nodoc: + Compatibility.preserve_timezone + end + + # Change the output of ActiveSupport::TimeZone.utc_to_local. + # + # When +true+, it returns local times with a UTC offset, with +false+ local + # times are returned as UTC. + # + # # Given this zone: + # zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"] + # + # # With `utc_to_local_returns_utc_offset_times = false`, local time is converted to UTC: + # zone.utc_to_local(Time.utc(2000, 1)) # => 1999-12-31 19:00:00 UTC + # + # # With `utc_to_local_returns_utc_offset_times = true`, local time is returned with UTC offset: + # zone.utc_to_local(Time.utc(2000, 1)) # => 1999-12-31 19:00:00 -0500 + mattr_accessor :utc_to_local_returns_utc_offset_times, instance_writer: false, default: false + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_and_time/zones.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_and_time/zones.rb new file mode 100644 index 00000000..fb6a27cb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_and_time/zones.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module DateAndTime + module Zones + # Returns the simultaneous time in Time.zone if a zone is given or + # if Time.zone_default is set. Otherwise, it returns the current time. + # + # Time.zone = 'Hawaii' # => 'Hawaii' + # Time.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00 + # + # This method is similar to Time#localtime, except that it uses Time.zone as the local zone + # instead of the operating system's time zone. + # + # You can also pass in a TimeZone instance or string that identifies a TimeZone as an argument, + # and the conversion will be based on that zone instead of Time.zone. + # + # Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00 + # Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00 + def in_time_zone(zone = ::Time.zone) + time_zone = ::Time.find_zone! zone + time = acts_like?(:time) ? self : nil + + if time_zone + time_with_zone(time, time_zone) + else + time || to_time + end + end + + private + def time_with_zone(time, zone) + if time + ActiveSupport::TimeWithZone.new(time.utc? ? time : time.getutc, zone) + else + ActiveSupport::TimeWithZone.new(nil, zone, to_time(:utc)) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_time.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_time.rb new file mode 100644 index 00000000..790dbeec --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_time.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "active_support/core_ext/date_time/acts_like" +require "active_support/core_ext/date_time/blank" +require "active_support/core_ext/date_time/calculations" +require "active_support/core_ext/date_time/compatibility" +require "active_support/core_ext/date_time/conversions" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_time/acts_like.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_time/acts_like.rb new file mode 100644 index 00000000..5dccdfe2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_time/acts_like.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "date" +require "active_support/core_ext/object/acts_like" + +class DateTime + # Duck-types as a Date-like class. See Object#acts_like?. + def acts_like_date? + true + end + + # Duck-types as a Time-like class. See Object#acts_like?. + def acts_like_time? + true + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_time/blank.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_time/blank.rb new file mode 100644 index 00000000..49290bf8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_time/blank.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require "date" + +class DateTime # :nodoc: + # No DateTime is ever blank: + # + # DateTime.now.blank? # => false + # + # @return [false] + def blank? + false + end + + def present? + true + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_time/calculations.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_time/calculations.rb new file mode 100644 index 00000000..a6350680 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_time/calculations.rb @@ -0,0 +1,215 @@ +# frozen_string_literal: true + +require "date" + +class DateTime + class << self + # Returns Time.zone.now.to_datetime when Time.zone or + # config.time_zone are set, otherwise returns + # Time.now.to_datetime. + def current + ::Time.zone ? ::Time.zone.now.to_datetime : ::Time.now.to_datetime + end + end + + # Returns the number of seconds since 00:00:00. + # + # DateTime.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0 + # DateTime.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296 + # DateTime.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399 + def seconds_since_midnight + sec + (min * 60) + (hour * 3600) + end + + # Returns the number of seconds until 23:59:59. + # + # DateTime.new(2012, 8, 29, 0, 0, 0).seconds_until_end_of_day # => 86399 + # DateTime.new(2012, 8, 29, 12, 34, 56).seconds_until_end_of_day # => 41103 + # DateTime.new(2012, 8, 29, 23, 59, 59).seconds_until_end_of_day # => 0 + def seconds_until_end_of_day + end_of_day.to_i - to_i + end + + # Returns the fraction of a second as a +Rational+ + # + # DateTime.new(2012, 8, 29, 0, 0, 0.5).subsec # => (1/2) + def subsec + sec_fraction + end + + # Returns a new DateTime where one or more of the elements have been changed + # according to the +options+ parameter. The time options (:hour, + # :min, :sec) reset cascadingly, so if only the hour is + # passed, then minute and sec is set to 0. If the hour and minute is passed, + # then sec is set to 0. The +options+ parameter takes a hash with any of these + # keys: :year, :month, :day, :hour, + # :min, :sec, :offset, :start. + # + # DateTime.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => DateTime.new(2012, 8, 1, 22, 35, 0) + # DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => DateTime.new(1981, 8, 1, 22, 35, 0) + # DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => DateTime.new(1981, 8, 29, 0, 0, 0) + def change(options) + if new_nsec = options[:nsec] + raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec] + new_fraction = Rational(new_nsec, 1000000000) + else + new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000)) + new_fraction = Rational(new_usec, 1000000) + end + + raise ArgumentError, "argument out of range" if new_fraction >= 1 + + ::DateTime.civil( + options.fetch(:year, year), + options.fetch(:month, month), + options.fetch(:day, day), + options.fetch(:hour, hour), + options.fetch(:min, options[:hour] ? 0 : min), + options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec) + new_fraction, + options.fetch(:offset, offset), + options.fetch(:start, start) + ) + end + + # Uses Date to provide precise Time calculations for years, months, and days. + # The +options+ parameter takes a hash with any of these keys: :years, + # :months, :weeks, :days, :hours, + # :minutes, :seconds. + # + # Just like Date#advance, increments are applied in order of time units from + # largest to smallest. This order can affect the result around the end of a + # month. + def advance(options) + unless options[:weeks].nil? + options[:weeks], partial_weeks = options[:weeks].divmod(1) + options[:days] = options.fetch(:days, 0) + 7 * partial_weeks + end + + unless options[:days].nil? + options[:days], partial_days = options[:days].divmod(1) + options[:hours] = options.fetch(:hours, 0) + 24 * partial_days + end + + d = to_date.advance(options) + datetime_advanced_by_date = change(year: d.year, month: d.month, day: d.day) + seconds_to_advance = \ + options.fetch(:seconds, 0) + + options.fetch(:minutes, 0) * 60 + + options.fetch(:hours, 0) * 3600 + + if seconds_to_advance.zero? + datetime_advanced_by_date + else + datetime_advanced_by_date.since(seconds_to_advance) + end + end + + # Returns a new DateTime representing the time a number of seconds ago. + # Do not use this method in combination with x.months, use months_ago instead! + def ago(seconds) + since(-seconds) + end + + # Returns a new DateTime representing the time a number of seconds since the + # instance time. Do not use this method in combination with x.months, use + # months_since instead! + def since(seconds) + self + Rational(seconds, 86400) + end + alias :in :since + + # Returns a new DateTime representing the start of the day (0:00). + def beginning_of_day + change(hour: 0) + end + alias :midnight :beginning_of_day + alias :at_midnight :beginning_of_day + alias :at_beginning_of_day :beginning_of_day + + # Returns a new DateTime representing the middle of the day (12:00) + def middle_of_day + change(hour: 12) + end + alias :midday :middle_of_day + alias :noon :middle_of_day + alias :at_midday :middle_of_day + alias :at_noon :middle_of_day + alias :at_middle_of_day :middle_of_day + + # Returns a new DateTime representing the end of the day (23:59:59). + def end_of_day + change(hour: 23, min: 59, sec: 59, usec: Rational(999999999, 1000)) + end + alias :at_end_of_day :end_of_day + + # Returns a new DateTime representing the start of the hour (hh:00:00). + def beginning_of_hour + change(min: 0) + end + alias :at_beginning_of_hour :beginning_of_hour + + # Returns a new DateTime representing the end of the hour (hh:59:59). + def end_of_hour + change(min: 59, sec: 59, usec: Rational(999999999, 1000)) + end + alias :at_end_of_hour :end_of_hour + + # Returns a new DateTime representing the start of the minute (hh:mm:00). + def beginning_of_minute + change(sec: 0) + end + alias :at_beginning_of_minute :beginning_of_minute + + # Returns a new DateTime representing the end of the minute (hh:mm:59). + def end_of_minute + change(sec: 59, usec: Rational(999999999, 1000)) + end + alias :at_end_of_minute :end_of_minute + + # Returns a Time instance of the simultaneous time in the system timezone. + def localtime(utc_offset = nil) + utc = new_offset(0) + + Time.utc( + utc.year, utc.month, utc.day, + utc.hour, utc.min, utc.sec + utc.sec_fraction + ).getlocal(utc_offset) + end + alias_method :getlocal, :localtime + + # Returns a Time instance of the simultaneous time in the UTC timezone. + # + # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600 + # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 UTC + def utc + utc = new_offset(0) + + Time.utc( + utc.year, utc.month, utc.day, + utc.hour, utc.min, utc.sec + utc.sec_fraction + ) + end + alias_method :getgm, :utc + alias_method :getutc, :utc + alias_method :gmtime, :utc + + # Returns +true+ if offset == 0. + def utc? + offset == 0 + end + + # Returns the offset value in seconds. + def utc_offset + (offset * 86400).to_i + end + + # Layers additional behavior on DateTime#<=> so that Time and + # ActiveSupport::TimeWithZone instances can be compared with a DateTime. + def <=>(other) + if other.respond_to? :to_datetime + super other.to_datetime rescue nil + else + super + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_time/compatibility.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_time/compatibility.rb new file mode 100644 index 00000000..7600a067 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_time/compatibility.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require "active_support/core_ext/date_and_time/compatibility" +require "active_support/core_ext/module/redefine_method" + +class DateTime + include DateAndTime::Compatibility + + silence_redefinition_of_method :to_time + + # Either return an instance of +Time+ with the same UTC offset + # as +self+ or an instance of +Time+ representing the same time + # in the local system timezone depending on the setting of + # on the setting of +ActiveSupport.to_time_preserves_timezone+. + def to_time + preserve_timezone ? getlocal(utc_offset) : getlocal + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_time/conversions.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_time/conversions.rb new file mode 100644 index 00000000..57c8d88d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/date_time/conversions.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require "date" +require "active_support/inflector/methods" +require "active_support/core_ext/time/conversions" +require "active_support/core_ext/date_time/calculations" +require "active_support/values/time_zone" + +class DateTime + # Convert to a formatted string. See Time::DATE_FORMATS for predefined formats. + # + # This method is aliased to to_formatted_s. + # + # === Examples + # datetime = DateTime.civil(2007, 12, 4, 0, 0, 0, 0) # => Tue, 04 Dec 2007 00:00:00 +0000 + # + # datetime.to_fs(:db) # => "2007-12-04 00:00:00" + # datetime.to_formatted_s(:db) # => "2007-12-04 00:00:00" + # datetime.to_fs(:number) # => "20071204000000" + # datetime.to_fs(:short) # => "04 Dec 00:00" + # datetime.to_fs(:long) # => "December 04, 2007 00:00" + # datetime.to_fs(:long_ordinal) # => "December 4th, 2007 00:00" + # datetime.to_fs(:rfc822) # => "Tue, 04 Dec 2007 00:00:00 +0000" + # datetime.to_fs(:iso8601) # => "2007-12-04T00:00:00+00:00" + # + # == Adding your own datetime formats to to_fs + # DateTime formats are shared with Time. You can add your own to the + # Time::DATE_FORMATS hash. Use the format name as the hash key and + # either a strftime string or Proc instance that takes a time or + # datetime argument as the value. + # + # # config/initializers/time_formats.rb + # Time::DATE_FORMATS[:month_and_year] = '%B %Y' + # Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") } + def to_fs(format = :default) + if formatter = ::Time::DATE_FORMATS[format] + formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) + else + to_s + end + end + alias_method :to_formatted_s, :to_fs + + + # Returns a formatted string of the offset from UTC, or an alternative + # string if the time zone is already UTC. + # + # datetime = DateTime.civil(2000, 1, 1, 0, 0, 0, Rational(-6, 24)) + # datetime.formatted_offset # => "-06:00" + # datetime.formatted_offset(false) # => "-0600" + def formatted_offset(colon = true, alternate_utc_string = nil) + utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon) + end + + # Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005 14:30:00 +0000". + def readable_inspect + to_fs(:rfc822) + end + alias_method :default_inspect, :inspect + alias_method :inspect, :readable_inspect + + # Returns DateTime with local offset for given year if format is local else + # offset is zero. + # + # DateTime.civil_from_format :local, 2012 + # # => Sun, 01 Jan 2012 00:00:00 +0300 + # DateTime.civil_from_format :local, 2012, 12, 17 + # # => Mon, 17 Dec 2012 00:00:00 +0000 + def self.civil_from_format(utc_or_local, year, month = 1, day = 1, hour = 0, min = 0, sec = 0) + if utc_or_local.to_sym == :local + offset = ::Time.local(year, month, day).utc_offset.to_r / 86400 + else + offset = 0 + end + civil(year, month, day, hour, min, sec, offset) + end + + # Converts +self+ to a floating-point number of seconds, including fractional microseconds, since the Unix epoch. + def to_f + seconds_since_unix_epoch.to_f + sec_fraction + end + + # Converts +self+ to an integer number of seconds since the Unix epoch. + def to_i + seconds_since_unix_epoch.to_i + end + + # Returns the fraction of a second as microseconds + def usec + (sec_fraction * 1_000_000).to_i + end + + # Returns the fraction of a second as nanoseconds + def nsec + (sec_fraction * 1_000_000_000).to_i + end + + private + def offset_in_seconds + (offset * 86400).to_i + end + + def seconds_since_unix_epoch + (jd - 2440588) * 86400 - offset_in_seconds + seconds_since_midnight + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/digest.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/digest.rb new file mode 100644 index 00000000..ce1427e1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/digest.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "active_support/core_ext/digest/uuid" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/digest/uuid.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/digest/uuid.rb new file mode 100644 index 00000000..0f06a979 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/digest/uuid.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require "securerandom" +require "openssl" + +module Digest + module UUID + DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc: + URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc: + OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc: + X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc: + + # Generates a v5 non-random UUID (Universally Unique IDentifier). + # + # Using OpenSSL::Digest::MD5 generates version 3 UUIDs; OpenSSL::Digest::SHA1 generates version 5 UUIDs. + # uuid_from_hash always generates the same UUID for a given name and namespace combination. + # + # See RFC 4122 for details of UUID at: https://www.ietf.org/rfc/rfc4122.txt + def self.uuid_from_hash(hash_class, namespace, name) + if hash_class == Digest::MD5 || hash_class == OpenSSL::Digest::MD5 + version = 3 + elsif hash_class == Digest::SHA1 || hash_class == OpenSSL::Digest::SHA1 + version = 5 + else + raise ArgumentError, "Expected OpenSSL::Digest::SHA1 or OpenSSL::Digest::MD5, got #{hash_class.name}." + end + + uuid_namespace = pack_uuid_namespace(namespace) + + hash = hash_class.new + hash.update(uuid_namespace) + hash.update(name) + + ary = hash.digest.unpack("NnnnnN") + ary[2] = (ary[2] & 0x0FFF) | (version << 12) + ary[3] = (ary[3] & 0x3FFF) | 0x8000 + + "%08x-%04x-%04x-%04x-%04x%08x" % ary + end + + # Convenience method for uuid_from_hash using OpenSSL::Digest::MD5. + def self.uuid_v3(uuid_namespace, name) + uuid_from_hash(OpenSSL::Digest::MD5, uuid_namespace, name) + end + + # Convenience method for uuid_from_hash using OpenSSL::Digest::SHA1. + def self.uuid_v5(uuid_namespace, name) + uuid_from_hash(OpenSSL::Digest::SHA1, uuid_namespace, name) + end + + # Convenience method for SecureRandom.uuid. + def self.uuid_v4 + SecureRandom.uuid + end + + # Returns the nil UUID. This is a special form of UUID that is specified to + # have all 128 bits set to zero. + def self.nil_uuid + "00000000-0000-0000-0000-000000000000" + end + + def self.pack_uuid_namespace(namespace) + if [DNS_NAMESPACE, OID_NAMESPACE, URL_NAMESPACE, X500_NAMESPACE].include?(namespace) + namespace + else + match_data = namespace.match(/\A(\h{8})-(\h{4})-(\h{4})-(\h{4})-(\h{4})(\h{8})\z/) + + raise ArgumentError, "Only UUIDs are valid namespace identifiers" unless match_data.present? + + match_data.captures.map { |s| s.to_i(16) }.pack("NnnnnN") + end + end + + private_class_method :pack_uuid_namespace + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/enumerable.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/enumerable.rb new file mode 100644 index 00000000..7ae0dc6e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/enumerable.rb @@ -0,0 +1,267 @@ +# frozen_string_literal: true + +module ActiveSupport + module EnumerableCoreExt # :nodoc: + module Constants + private + def const_missing(name) + if name == :SoleItemExpectedError + ::ActiveSupport::EnumerableCoreExt::SoleItemExpectedError + else + super + end + end + end + end +end + +module Enumerable + # Error generated by +sole+ when called on an enumerable that doesn't have + # exactly one item. + class SoleItemExpectedError < StandardError; end + + # HACK: For performance reasons, Enumerable shouldn't have any constants of its own. + # So we move SoleItemExpectedError into ActiveSupport::EnumerableCoreExt. + ActiveSupport::EnumerableCoreExt::SoleItemExpectedError = remove_const(:SoleItemExpectedError) + singleton_class.prepend(ActiveSupport::EnumerableCoreExt::Constants) + + # Calculates the minimum from the extracted elements. + # + # payments = [Payment.new(5), Payment.new(15), Payment.new(10)] + # payments.minimum(:price) # => 5 + def minimum(key) + map(&key).min + end + + # Calculates the maximum from the extracted elements. + # + # payments = [Payment.new(5), Payment.new(15), Payment.new(10)] + # payments.maximum(:price) # => 15 + def maximum(key) + map(&key).max + end + + # Convert an enumerable to a hash, using the block result as the key and the + # element as the value. + # + # people.index_by(&:login) + # # => { "nextangle" => , "chade-" => , ...} + # + # people.index_by { |person| "#{person.first_name} #{person.last_name}" } + # # => { "Chade- Fowlersburg-e" => , "David Heinemeier Hansson" => , ...} + def index_by + if block_given? + result = {} + each { |elem| result[yield(elem)] = elem } + result + else + to_enum(:index_by) { size if respond_to?(:size) } + end + end + + # Convert an enumerable to a hash, using the element as the key and the block + # result as the value. + # + # post = Post.new(title: "hey there", body: "what's up?") + # + # %i( title body ).index_with { |attr_name| post.public_send(attr_name) } + # # => { title: "hey there", body: "what's up?" } + # + # If an argument is passed instead of a block, it will be used as the value + # for all elements: + # + # %i( created_at updated_at ).index_with(Time.now) + # # => { created_at: 2020-03-09 22:31:47, updated_at: 2020-03-09 22:31:47 } + def index_with(default = (no_default = true)) + if block_given? + result = {} + each { |elem| result[elem] = yield(elem) } + result + elsif no_default + to_enum(:index_with) { size if respond_to?(:size) } + else + result = {} + each { |elem| result[elem] = default } + result + end + end + + # Returns +true+ if the enumerable has more than 1 element. Functionally + # equivalent to enum.to_a.size > 1. Can be called with a block too, + # much like any?, so people.many? { |p| p.age > 26 } returns +true+ + # if more than one person is over 26. + def many? + cnt = 0 + if block_given? + any? do |*args| + cnt += 1 if yield(*args) + cnt > 1 + end + else + any? { (cnt += 1) > 1 } + end + end + + # Returns a new array that includes the passed elements. + # + # [ 1, 2, 3 ].including(4, 5) + # # => [ 1, 2, 3, 4, 5 ] + # + # ["David", "Rafael"].including %w[ Aaron Todd ] + # # => ["David", "Rafael", "Aaron", "Todd"] + def including(*elements) + to_a.including(*elements) + end + + # The negative of the Enumerable#include?. Returns +true+ if the + # collection does not include the object. + def exclude?(object) + !include?(object) + end + + # Returns a copy of the enumerable excluding the specified elements. + # + # ["David", "Rafael", "Aaron", "Todd"].excluding "Aaron", "Todd" + # # => ["David", "Rafael"] + # + # ["David", "Rafael", "Aaron", "Todd"].excluding %w[ Aaron Todd ] + # # => ["David", "Rafael"] + # + # {foo: 1, bar: 2, baz: 3}.excluding :bar + # # => {foo: 1, baz: 3} + def excluding(*elements) + elements.flatten!(1) + reject { |element| elements.include?(element) } + end + alias :without :excluding + + # Extract the given key from each element in the enumerable. + # + # [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name) + # # => ["David", "Rafael", "Aaron"] + # + # [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name) + # # => [[1, "David"], [2, "Rafael"]] + def pluck(*keys) + if keys.many? + map { |element| keys.map { |key| element[key] } } + else + key = keys.first + map { |element| element[key] } + end + end + + # Extract the given key from the first element in the enumerable. + # + # [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pick(:name) + # # => "David" + # + # [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pick(:id, :name) + # # => [1, "David"] + def pick(*keys) + return if none? + + if keys.many? + keys.map { |key| first[key] } + else + first[keys.first] + end + end + + # Returns a new +Array+ without the blank items. + # Uses Object#blank? for determining if an item is blank. + # + # [1, "", nil, 2, " ", [], {}, false, true].compact_blank + # # => [1, 2, true] + # + # Set.new([nil, "", 1, false]).compact_blank + # # => [1] + # + # When called on a +Hash+, returns a new +Hash+ without the blank values. + # + # { a: "", b: 1, c: nil, d: [], e: false, f: true }.compact_blank + # # => { b: 1, f: true } + def compact_blank + reject(&:blank?) + end + + # Returns a new +Array+ where the order has been set to that provided in the +series+, based on the +key+ of the + # objects in the original enumerable. + # + # [ Person.find(5), Person.find(3), Person.find(1) ].in_order_of(:id, [ 1, 5, 3 ]) + # # => [ Person.find(1), Person.find(5), Person.find(3) ] + # + # If the +series+ include keys that have no corresponding element in the Enumerable, these are ignored. + # If the Enumerable has additional elements that aren't named in the +series+, these are not included in the result, unless + # the +filter+ option is set to +false+. + def in_order_of(key, series, filter: true) + if filter + group_by(&key).values_at(*series).flatten(1).compact + else + sort_by { |v| series.index(v.public_send(key)) || series.size }.compact + end + end + + # Returns the sole item in the enumerable. If there are no items, or more + # than one item, raises Enumerable::SoleItemExpectedError. + # + # ["x"].sole # => "x" + # Set.new.sole # => Enumerable::SoleItemExpectedError: no item found + # { a: 1, b: 2 }.sole # => Enumerable::SoleItemExpectedError: multiple items found + def sole + case count + when 1 then return first # rubocop:disable Style/RedundantReturn + when 0 then raise ActiveSupport::EnumerableCoreExt::SoleItemExpectedError, "no item found" + when 2.. then raise ActiveSupport::EnumerableCoreExt::SoleItemExpectedError, "multiple items found" + end + end +end + +class Hash + # Hash#reject has its own definition, so this needs one too. + def compact_blank # :nodoc: + reject { |_k, v| v.blank? } + end + + # Removes all blank values from the +Hash+ in place and returns self. + # Uses Object#blank? for determining if a value is blank. + # + # h = { a: "", b: 1, c: nil, d: [], e: false, f: true } + # h.compact_blank! + # # => { b: 1, f: true } + def compact_blank! + # use delete_if rather than reject! because it always returns self even if nothing changed + delete_if { |_k, v| v.blank? } + end +end + +class Range # :nodoc: + # Optimize range sum to use arithmetic progression if a block is not given and + # we have a range of numeric values. + def sum(initial_value = 0) + if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer)) + super + else + actual_last = exclude_end? ? (last - 1) : last + if actual_last >= first + sum = initial_value || 0 + sum + (actual_last - first + 1) * (actual_last + first) / 2 + else + initial_value || 0 + end + end + end +end + +class Array # :nodoc: + # Removes all blank elements from the +Array+ in place and returns self. + # Uses Object#blank? for determining if an item is blank. + # + # a = [1, "", nil, 2, " ", [], {}, false, true] + # a.compact_blank! + # # => [1, 2, true] + def compact_blank! + # use delete_if rather than reject! because it always returns self even if nothing changed + delete_if(&:blank?) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/erb/util.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/erb/util.rb new file mode 100644 index 00000000..06eed37b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/erb/util.rb @@ -0,0 +1,201 @@ +# frozen_string_literal: true + +require "erb" + +module ActiveSupport + module CoreExt + module ERBUtil + # HTML escapes strings but doesn't wrap them with an ActiveSupport::SafeBuffer. + # This method is not for public consumption! Seriously! + def html_escape(s) # :nodoc: + s = s.to_s + if s.html_safe? + s + else + super(ActiveSupport::Multibyte::Unicode.tidy_bytes(s)) + end + end + alias :unwrapped_html_escape :html_escape # :nodoc: + + # A utility method for escaping HTML tag characters. + # This method is also aliased as h. + # + # puts html_escape('is a > 0 & a < 10?') + # # => is a > 0 & a < 10? + def html_escape(s) # rubocop:disable Lint/DuplicateMethods + unwrapped_html_escape(s).html_safe + end + alias h html_escape + end + + module ERBUtilPrivate + include ERBUtil + private :unwrapped_html_escape, :html_escape, :h + end + end +end + +class ERB + module Util + HTML_ESCAPE = { "&" => "&", ">" => ">", "<" => "<", '"' => """, "'" => "'" } + HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/ + + # Following XML requirements: https://www.w3.org/TR/REC-xml/#NT-Name + TAG_NAME_START_CODEPOINTS = "@:A-Z_a-z\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{370}-\u{37D}\u{37F}-\u{1FFF}" \ + "\u{200C}-\u{200D}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}" \ + "\u{FDF0}-\u{FFFD}\u{10000}-\u{EFFFF}" + INVALID_TAG_NAME_START_REGEXP = /[^#{TAG_NAME_START_CODEPOINTS}]/ + TAG_NAME_FOLLOWING_CODEPOINTS = "#{TAG_NAME_START_CODEPOINTS}\\-.0-9\u{B7}\u{0300}-\u{036F}\u{203F}-\u{2040}" + INVALID_TAG_NAME_FOLLOWING_REGEXP = /[^#{TAG_NAME_FOLLOWING_CODEPOINTS}]/ + SAFE_XML_TAG_NAME_REGEXP = /\A[#{TAG_NAME_START_CODEPOINTS}][#{TAG_NAME_FOLLOWING_CODEPOINTS}]*\z/ + TAG_NAME_REPLACEMENT_CHAR = "_" + + prepend ActiveSupport::CoreExt::ERBUtilPrivate + singleton_class.prepend ActiveSupport::CoreExt::ERBUtil + + # A utility method for escaping HTML without affecting existing escaped entities. + # + # html_escape_once('1 < 2 & 3') + # # => "1 < 2 & 3" + # + # html_escape_once('<< Accept & Checkout') + # # => "<< Accept & Checkout" + def html_escape_once(s) + ActiveSupport::Multibyte::Unicode.tidy_bytes(s.to_s).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE).html_safe + end + + module_function :html_escape_once + + # A utility method for escaping HTML entities in JSON strings. Specifically, the + # &, > and < characters are replaced with their equivalent unicode escaped form - + # \u0026, \u003e, and \u003c. The Unicode sequences \u2028 and \u2029 are also + # escaped as they are treated as newline characters in some JavaScript engines. + # These sequences have identical meaning as the original characters inside the + # context of a JSON string, so assuming the input is a valid and well-formed + # JSON value, the output will have equivalent meaning when parsed: + # + # json = JSON.generate({ name: ""}) + # # => "{\"name\":\"\"}" + # + # json_escape(json) + # # => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}" + # + # JSON.parse(json) == JSON.parse(json_escape(json)) + # # => true + # + # The intended use case for this method is to escape JSON strings before including + # them inside a script tag to avoid XSS vulnerability: + # + # + # + # It is necessary to +raw+ the result of +json_escape+, so that quotation marks + # don't get converted to " entities. +json_escape+ doesn't + # automatically flag the result as HTML safe, since the raw value is unsafe to + # use inside HTML attributes. + # + # If your JSON is being used downstream for insertion into the DOM, be aware of + # whether or not it is being inserted via html(). Most jQuery plugins do this. + # If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated + # content returned by your JSON. + # + # If you need to output JSON elsewhere in your HTML, you can just do something + # like this, as any unsafe characters (including quotation marks) will be + # automatically escaped for you: + # + #
    ...
    + # + # WARNING: this helper only works with valid JSON. Using this on non-JSON values + # will open up serious XSS vulnerabilities. For example, if you replace the + # +current_user.to_json+ in the example above with user input instead, the browser + # will happily eval() that string as JavaScript. + # + # The escaping performed in this method is identical to those performed in the + # Active Support JSON encoder when +ActiveSupport.escape_html_entities_in_json+ is + # set to true. Because this transformation is idempotent, this helper can be + # applied even if +ActiveSupport.escape_html_entities_in_json+ is already true. + # + # Therefore, when you are unsure if +ActiveSupport.escape_html_entities_in_json+ + # is enabled, or if you are unsure where your JSON string originated from, it + # is recommended that you always apply this helper (other libraries, such as the + # JSON gem, do not provide this kind of protection by default; also some gems + # might override +to_json+ to bypass Active Support's encoder). + def json_escape(s) + result = s.to_s.dup + result.gsub!(">", '\u003e') + result.gsub!("<", '\u003c') + result.gsub!("&", '\u0026') + result.gsub!("\u2028", '\u2028') + result.gsub!("\u2029", '\u2029') + s.html_safe? ? result.html_safe : result + end + + module_function :json_escape + + # A utility method for escaping XML names of tags and names of attributes. + # + # xml_name_escape('1 < 2 & 3') + # # => "1___2___3" + # + # It follows the requirements of the specification: https://www.w3.org/TR/REC-xml/#NT-Name + def xml_name_escape(name) + name = name.to_s + return "" if name.blank? + return name if name.match?(SAFE_XML_TAG_NAME_REGEXP) + + starting_char = name[0] + starting_char.gsub!(INVALID_TAG_NAME_START_REGEXP, TAG_NAME_REPLACEMENT_CHAR) + + return starting_char if name.size == 1 + + following_chars = name[1..-1] + following_chars.gsub!(INVALID_TAG_NAME_FOLLOWING_REGEXP, TAG_NAME_REPLACEMENT_CHAR) + + starting_char << following_chars + end + module_function :xml_name_escape + + # Tokenizes a line of ERB. This is really just for error reporting and + # nobody should use it. + def self.tokenize(source) # :nodoc: + require "strscan" + source = StringScanner.new(source.chomp) + tokens = [] + + start_re = /<%(?:={1,2}|-|\#|%)?/m + finish_re = /(?:[-=])?%>/m + + while !source.eos? + pos = source.pos + source.scan_until(/(?:#{start_re}|#{finish_re})/) + raise NotImplementedError if source.matched.nil? + len = source.pos - source.matched.bytesize - pos + + case source.matched + when start_re + tokens << [:TEXT, source.string.byteslice(pos, len)] if len > 0 + tokens << [:OPEN, source.matched] + if source.scan(/(.*?)(?=#{finish_re}|\z)/m) + tokens << [:CODE, source.matched] unless source.matched.empty? + tokens << [:CLOSE, source.scan(finish_re)] unless source.eos? + else + raise NotImplementedError + end + when finish_re + tokens << [:CODE, source.string.byteslice(pos, len)] if len > 0 + tokens << [:CLOSE, source.matched] + else + raise NotImplementedError, source.matched + end + + unless source.eos? || source.exist?(start_re) || source.exist?(finish_re) + tokens << [:TEXT, source.rest] + source.terminate + end + end + + tokens + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/file.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/file.rb new file mode 100644 index 00000000..64553bfa --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/file.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "active_support/core_ext/file/atomic" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/file/atomic.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/file/atomic.rb new file mode 100644 index 00000000..b442ea31 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/file/atomic.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require "fileutils" + +class File + # Write to a file atomically. Useful for situations where you don't + # want other processes or threads to see half-written files. + # + # File.atomic_write('important.file') do |file| + # file.write('hello') + # end + # + # This method needs to create a temporary file. By default it will create it + # in the same directory as the destination file. If you don't like this + # behavior you can provide a different directory but it must be on the + # same physical filesystem as the file you're trying to write. + # + # File.atomic_write('/data/something.important', '/data/tmp') do |file| + # file.write('hello') + # end + def self.atomic_write(file_name, temp_dir = dirname(file_name)) + require "tempfile" unless defined?(Tempfile) + + Tempfile.open(".#{basename(file_name)}", temp_dir) do |temp_file| + temp_file.binmode + return_val = yield temp_file + temp_file.close + + old_stat = if exist?(file_name) + # Get original file permissions + stat(file_name) + else + # If not possible, probe which are the default permissions in the + # destination directory. + probe_stat_in(dirname(file_name)) + end + + if old_stat + # Set correct permissions on new file + begin + chown(old_stat.uid, old_stat.gid, temp_file.path) + # This operation will affect filesystem ACL's + chmod(old_stat.mode, temp_file.path) + rescue Errno::EPERM, Errno::EACCES + # Changing file ownership failed, moving on. + end + end + + # Overwrite original file with temp file + rename(temp_file.path, file_name) + return_val + end + end + + # Private utility method. + def self.probe_stat_in(dir) # :nodoc: + basename = [ + ".permissions_check", + Thread.current.object_id, + Process.pid, + rand(1000000) + ].join(".") + + file_name = join(dir, basename) + FileUtils.touch(file_name) + stat(file_name) + rescue Errno::ENOENT + file_name = nil + ensure + FileUtils.rm_f(file_name) if file_name + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash.rb new file mode 100644 index 00000000..2f0901d8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/conversions" +require "active_support/core_ext/hash/deep_merge" +require "active_support/core_ext/hash/deep_transform_values" +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/indifferent_access" +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/hash/reverse_merge" +require "active_support/core_ext/hash/slice" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/conversions.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/conversions.rb new file mode 100644 index 00000000..8ab2fd69 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/conversions.rb @@ -0,0 +1,262 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/blank" +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/object/try" +require "active_support/core_ext/array/wrap" +require "active_support/core_ext/hash/reverse_merge" +require "active_support/core_ext/string/inflections" + +class Hash + # Returns a string containing an XML representation of its receiver: + # + # { foo: 1, bar: 2 }.to_xml + # # => + # # + # # + # # 1 + # # 2 + # # + # + # To do so, the method loops over the pairs and builds nodes that depend on + # the _values_. Given a pair +key+, +value+: + # + # * If +value+ is a hash there's a recursive call with +key+ as :root. + # + # * If +value+ is an array there's a recursive call with +key+ as :root, + # and +key+ singularized as :children. + # + # * If +value+ is a callable object it must expect one or two arguments. Depending + # on the arity, the callable is invoked with the +options+ hash as first argument + # with +key+ as :root, and +key+ singularized as second argument. The + # callable can add nodes by using options[:builder]. + # + # {foo: lambda { |options, key| options[:builder].b(key) }}.to_xml + # # => "foo" + # + # * If +value+ responds to +to_xml+ the method is invoked with +key+ as :root. + # + # class Foo + # def to_xml(options) + # options[:builder].bar 'fooing!' + # end + # end + # + # { foo: Foo.new }.to_xml(skip_instruct: true) + # # => + # # + # # fooing! + # # + # + # * Otherwise, a node with +key+ as tag is created with a string representation of + # +value+ as text node. If +value+ is +nil+ an attribute "nil" set to "true" is added. + # Unless the option :skip_types exists and is true, an attribute "type" is + # added as well according to the following mapping: + # + # XML_TYPE_NAMES = { + # "Symbol" => "symbol", + # "Integer" => "integer", + # "BigDecimal" => "decimal", + # "Float" => "float", + # "TrueClass" => "boolean", + # "FalseClass" => "boolean", + # "Date" => "date", + # "DateTime" => "dateTime", + # "Time" => "dateTime" + # } + # + # By default the root node is "hash", but that's configurable via the :root option. + # + # The default XML builder is a fresh instance of +Builder::XmlMarkup+. You can + # configure your own builder with the :builder option. The method also accepts + # options like :dasherize and friends, they are forwarded to the builder. + def to_xml(options = {}) + require "active_support/builder" unless defined?(Builder::XmlMarkup) + + options = options.dup + options[:indent] ||= 2 + options[:root] ||= "hash" + options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent]) + + builder = options[:builder] + builder.instruct! unless options.delete(:skip_instruct) + + root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options) + + builder.tag!(root) do + each { |key, value| ActiveSupport::XmlMini.to_tag(key, value, options) } + yield builder if block_given? + end + end + + class << self + # Returns a Hash containing a collection of pairs when the key is the node name and the value is + # its content + # + # xml = <<-XML + # + # + # 1 + # 2 + # + # XML + # + # hash = Hash.from_xml(xml) + # # => {"hash"=>{"foo"=>1, "bar"=>2}} + # + # +DisallowedType+ is raised if the XML contains attributes with type="yaml" or + # type="symbol". Use Hash.from_trusted_xml to + # parse this XML. + # + # Custom +disallowed_types+ can also be passed in the form of an + # array. + # + # xml = <<-XML + # + # + # 1 + # "David" + # + # XML + # + # hash = Hash.from_xml(xml, ['integer']) + # # => ActiveSupport::XMLConverter::DisallowedType: Disallowed type attribute: "integer" + # + # Note that passing custom disallowed types will override the default types, + # which are Symbol and YAML. + def from_xml(xml, disallowed_types = nil) + ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h + end + + # Builds a Hash from XML just like Hash.from_xml, but also allows Symbol and YAML. + def from_trusted_xml(xml) + from_xml xml, [] + end + end +end + +module ActiveSupport + class XMLConverter # :nodoc: + # Raised if the XML contains attributes with type="yaml" or + # type="symbol". Read Hash#from_xml for more details. + class DisallowedType < StandardError + def initialize(type) + super "Disallowed type attribute: #{type.inspect}" + end + end + + DISALLOWED_TYPES = %w(symbol yaml) + + def initialize(xml, disallowed_types = nil) + @xml = normalize_keys(XmlMini.parse(xml)) + @disallowed_types = disallowed_types || DISALLOWED_TYPES + end + + def to_h + deep_to_h(@xml) + end + + private + def normalize_keys(params) + case params + when Hash + Hash[params.map { |k, v| [k.to_s.tr("-", "_"), normalize_keys(v)] } ] + when Array + params.map { |v| normalize_keys(v) } + else + params + end + end + + def deep_to_h(value) + case value + when Hash + process_hash(value) + when Array + process_array(value) + when String + value + else + raise "can't typecast #{value.class.name} - #{value.inspect}" + end + end + + def process_hash(value) + if value.include?("type") && !value["type"].is_a?(Hash) && @disallowed_types.include?(value["type"]) + raise DisallowedType, value["type"] + end + + if become_array?(value) + _, entries = Array.wrap(value.detect { |k, v| not v.is_a?(String) }) + if entries.nil? || value["__content__"].try(:empty?) + [] + else + case entries + when Array + entries.collect { |v| deep_to_h(v) } + when Hash + [deep_to_h(entries)] + else + raise "can't typecast #{entries.inspect}" + end + end + elsif become_content?(value) + process_content(value) + + elsif become_empty_string?(value) + "" + elsif become_hash?(value) + xml_value = value.transform_values { |v| deep_to_h(v) } + + # Turn { files: { file: # } } into { files: # } so it is compatible with + # how multipart uploaded files from HTML appear + xml_value["file"].is_a?(StringIO) ? xml_value["file"] : xml_value + end + end + + def become_content?(value) + value["type"] == "file" || (value["__content__"] && (value.keys.size == 1 || value["__content__"].present?)) + end + + def become_array?(value) + value["type"] == "array" + end + + def become_empty_string?(value) + # { "string" => true } + # No tests fail when the second term is removed. + value["type"] == "string" && value["nil"] != "true" + end + + def become_hash?(value) + !nothing?(value) && !garbage?(value) + end + + def nothing?(value) + # blank or nil parsed values are represented by nil + value.blank? || value["nil"] == "true" + end + + def garbage?(value) + # If the type is the only element which makes it then + # this still makes the value nil, except if type is + # an XML node(where type['value'] is a Hash) + value["type"] && !value["type"].is_a?(::Hash) && value.size == 1 + end + + def process_content(value) + content = value["__content__"] + if parser = ActiveSupport::XmlMini::PARSING[value["type"]] + parser.arity == 1 ? parser.call(content) : parser.call(content, value) + else + content + end + end + + def process_array(value) + value.map! { |i| deep_to_h(i) } + value.length > 1 ? value : value.first + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/deep_merge.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/deep_merge.rb new file mode 100644 index 00000000..9af3572a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/deep_merge.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "active_support/deep_mergeable" + +class Hash + include ActiveSupport::DeepMergeable + + ## + # :method: deep_merge + # :call-seq: deep_merge(other_hash, &block) + # + # Returns a new hash with +self+ and +other_hash+ merged recursively. + # + # h1 = { a: true, b: { c: [1, 2, 3] } } + # h2 = { a: false, b: { x: [3, 4, 5] } } + # + # h1.deep_merge(h2) # => { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } } + # + # Like with Hash#merge in the standard library, a block can be provided + # to merge values: + # + # h1 = { a: 100, b: 200, c: { c1: 100 } } + # h2 = { b: 250, c: { c1: 200 } } + # h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val } + # # => { a: 100, b: 450, c: { c1: 300 } } + # + #-- + # Implemented by ActiveSupport::DeepMergeable#deep_merge. + + ## + # :method: deep_merge! + # :call-seq: deep_merge!(other_hash, &block) + # + # Same as #deep_merge, but modifies +self+. + # + #-- + # Implemented by ActiveSupport::DeepMergeable#deep_merge!. + + ## + def deep_merge?(other) # :nodoc: + other.is_a?(Hash) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/deep_transform_values.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/deep_transform_values.rb new file mode 100644 index 00000000..f7aeae5b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/deep_transform_values.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +class Hash + # Returns a new hash with all values converted by the block operation. + # This includes the values from the root hash and from all + # nested hashes and arrays. + # + # hash = { person: { name: 'Rob', age: '28' } } + # + # hash.deep_transform_values{ |value| value.to_s.upcase } + # # => {person: {name: "ROB", age: "28"}} + def deep_transform_values(&block) + _deep_transform_values_in_object(self, &block) + end + + # Destructively converts all values by using the block operation. + # This includes the values from the root hash and from all + # nested hashes and arrays. + def deep_transform_values!(&block) + _deep_transform_values_in_object!(self, &block) + end + + private + # Support methods for deep transforming nested hashes and arrays. + def _deep_transform_values_in_object(object, &block) + case object + when Hash + object.transform_values { |value| _deep_transform_values_in_object(value, &block) } + when Array + object.map { |e| _deep_transform_values_in_object(e, &block) } + else + yield(object) + end + end + + def _deep_transform_values_in_object!(object, &block) + case object + when Hash + object.transform_values! { |value| _deep_transform_values_in_object!(value, &block) } + when Array + object.map! { |e| _deep_transform_values_in_object!(e, &block) } + else + yield(object) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/except.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/except.rb new file mode 100644 index 00000000..d94acd74 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/except.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class Hash + # Removes the given keys from hash and returns it. + # hash = { a: true, b: false, c: nil } + # hash.except!(:c) # => { a: true, b: false } + # hash # => { a: true, b: false } + def except!(*keys) + keys.each { |key| delete(key) } + self + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/indifferent_access.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/indifferent_access.rb new file mode 100644 index 00000000..4437363c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/indifferent_access.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "active_support/hash_with_indifferent_access" + +class Hash + # Returns an ActiveSupport::HashWithIndifferentAccess out of its receiver: + # + # { a: 1 }.with_indifferent_access['a'] # => 1 + def with_indifferent_access + ActiveSupport::HashWithIndifferentAccess.new(self) + end + + # Called when object is nested under an object that receives + # #with_indifferent_access. This method will be called on the current object + # by the enclosing object and is aliased to #with_indifferent_access by + # default. Subclasses of Hash may override this method to return +self+ if + # converting to an ActiveSupport::HashWithIndifferentAccess would not be + # desirable. + # + # b = { b: 1 } + # { a: b }.with_indifferent_access['a'] # calls b.nested_under_indifferent_access + # # => {"b"=>1} + alias nested_under_indifferent_access with_indifferent_access +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/keys.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/keys.rb new file mode 100644 index 00000000..fd74f030 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/keys.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +class Hash + # Returns a new hash with all keys converted to strings. + # + # hash = { name: 'Rob', age: '28' } + # + # hash.stringify_keys + # # => {"name"=>"Rob", "age"=>"28"} + def stringify_keys + transform_keys { |k| Symbol === k ? k.name : k.to_s } + end + + # Destructively converts all keys to strings. Same as + # +stringify_keys+, but modifies +self+. + def stringify_keys! + transform_keys! { |k| Symbol === k ? k.name : k.to_s } + end + + # Returns a new hash with all keys converted to symbols, as long as + # they respond to +to_sym+. + # + # hash = { 'name' => 'Rob', 'age' => '28' } + # + # hash.symbolize_keys + # # => {:name=>"Rob", :age=>"28"} + def symbolize_keys + transform_keys { |key| key.to_sym rescue key } + end + alias_method :to_options, :symbolize_keys + + # Destructively converts all keys to symbols, as long as they respond + # to +to_sym+. Same as +symbolize_keys+, but modifies +self+. + def symbolize_keys! + transform_keys! { |key| key.to_sym rescue key } + end + alias_method :to_options!, :symbolize_keys! + + # Validates all keys in a hash match *valid_keys, raising + # +ArgumentError+ on a mismatch. + # + # Note that keys are treated differently than HashWithIndifferentAccess, + # meaning that string and symbol keys will not match. + # + # { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: :years. Valid keys are: :name, :age" + # { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: :name. Valid keys are: 'name', 'age'" + # { name: 'Rob', age: '28' }.assert_valid_keys(:name, :age) # => passes, raises nothing + def assert_valid_keys(*valid_keys) + valid_keys.flatten! + each_key do |k| + unless valid_keys.include?(k) + raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}") + end + end + end + + # Returns a new hash with all keys converted by the block operation. + # This includes the keys from the root hash and from all + # nested hashes and arrays. + # + # hash = { person: { name: 'Rob', age: '28' } } + # + # hash.deep_transform_keys{ |key| key.to_s.upcase } + # # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}} + def deep_transform_keys(&block) + _deep_transform_keys_in_object(self, &block) + end + + # Destructively converts all keys by using the block operation. + # This includes the keys from the root hash and from all + # nested hashes and arrays. + def deep_transform_keys!(&block) + _deep_transform_keys_in_object!(self, &block) + end + + # Returns a new hash with all keys converted to strings. + # This includes the keys from the root hash and from all + # nested hashes and arrays. + # + # hash = { person: { name: 'Rob', age: '28' } } + # + # hash.deep_stringify_keys + # # => {"person"=>{"name"=>"Rob", "age"=>"28"}} + def deep_stringify_keys + deep_transform_keys { |k| Symbol === k ? k.name : k.to_s } + end + + # Destructively converts all keys to strings. + # This includes the keys from the root hash and from all + # nested hashes and arrays. + def deep_stringify_keys! + deep_transform_keys! { |k| Symbol === k ? k.name : k.to_s } + end + + # Returns a new hash with all keys converted to symbols, as long as + # they respond to +to_sym+. This includes the keys from the root hash + # and from all nested hashes and arrays. + # + # hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } } + # + # hash.deep_symbolize_keys + # # => {:person=>{:name=>"Rob", :age=>"28"}} + def deep_symbolize_keys + deep_transform_keys { |key| key.to_sym rescue key } + end + + # Destructively converts all keys to symbols, as long as they respond + # to +to_sym+. This includes the keys from the root hash and from all + # nested hashes and arrays. + def deep_symbolize_keys! + deep_transform_keys! { |key| key.to_sym rescue key } + end + + private + # Support methods for deep transforming nested hashes and arrays. + def _deep_transform_keys_in_object(object, &block) + case object + when Hash + object.each_with_object(self.class.new) do |(key, value), result| + result[yield(key)] = _deep_transform_keys_in_object(value, &block) + end + when Array + object.map { |e| _deep_transform_keys_in_object(e, &block) } + else + object + end + end + + def _deep_transform_keys_in_object!(object, &block) + case object + when Hash + object.keys.each do |key| + value = object.delete(key) + object[yield(key)] = _deep_transform_keys_in_object!(value, &block) + end + object + when Array + object.map! { |e| _deep_transform_keys_in_object!(e, &block) } + else + object + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/reverse_merge.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/reverse_merge.rb new file mode 100644 index 00000000..ef8d5928 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/reverse_merge.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class Hash + # Merges the caller into +other_hash+. For example, + # + # options = options.reverse_merge(size: 25, velocity: 10) + # + # is equivalent to + # + # options = { size: 25, velocity: 10 }.merge(options) + # + # This is particularly useful for initializing an options hash + # with default values. + def reverse_merge(other_hash) + other_hash.merge(self) + end + alias_method :with_defaults, :reverse_merge + + # Destructive +reverse_merge+. + def reverse_merge!(other_hash) + replace(reverse_merge(other_hash)) + end + alias_method :reverse_update, :reverse_merge! + alias_method :with_defaults!, :reverse_merge! +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/slice.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/slice.rb new file mode 100644 index 00000000..56bc5de3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/hash/slice.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class Hash + # Replaces the hash with only the given keys. + # Returns a hash containing the removed key/value pairs. + # + # hash = { a: 1, b: 2, c: 3, d: 4 } + # hash.slice!(:a, :b) # => {:c=>3, :d=>4} + # hash # => {:a=>1, :b=>2} + def slice!(*keys) + omit = slice(*self.keys - keys) + hash = slice(*keys) + hash.default = default + hash.default_proc = default_proc if default_proc + replace(hash) + omit + end + + # Removes and returns the key/value pairs matching the given keys. + # + # hash = { a: 1, b: 2, c: 3, d: 4 } + # hash.extract!(:a, :b) # => {:a=>1, :b=>2} + # hash # => {:c=>3, :d=>4} + def extract!(*keys) + keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) } + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/integer.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/integer.rb new file mode 100644 index 00000000..d2270130 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/integer.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require "active_support/core_ext/integer/multiple" +require "active_support/core_ext/integer/inflections" +require "active_support/core_ext/integer/time" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/integer/inflections.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/integer/inflections.rb new file mode 100644 index 00000000..c9879902 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/integer/inflections.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "active_support/inflector" + +class Integer + # Ordinalize turns a number into an ordinal string used to denote the + # position in an ordered sequence such as 1st, 2nd, 3rd, 4th. + # + # 1.ordinalize # => "1st" + # 2.ordinalize # => "2nd" + # 1002.ordinalize # => "1002nd" + # 1003.ordinalize # => "1003rd" + # -11.ordinalize # => "-11th" + # -1001.ordinalize # => "-1001st" + def ordinalize + ActiveSupport::Inflector.ordinalize(self) + end + + # Ordinal returns the suffix used to denote the position + # in an ordered sequence such as 1st, 2nd, 3rd, 4th. + # + # 1.ordinal # => "st" + # 2.ordinal # => "nd" + # 1002.ordinal # => "nd" + # 1003.ordinal # => "rd" + # -11.ordinal # => "th" + # -1001.ordinal # => "st" + def ordinal + ActiveSupport::Inflector.ordinal(self) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/integer/multiple.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/integer/multiple.rb new file mode 100644 index 00000000..bd57a909 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/integer/multiple.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class Integer + # Check whether the integer is evenly divisible by the argument. + # + # 0.multiple_of?(0) # => true + # 6.multiple_of?(5) # => false + # 10.multiple_of?(2) # => true + def multiple_of?(number) + number == 0 ? self == 0 : self % number == 0 + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/integer/time.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/integer/time.rb new file mode 100644 index 00000000..5efb89cf --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/integer/time.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "active_support/duration" +require "active_support/core_ext/numeric/time" + +class Integer + # Returns a Duration instance matching the number of months provided. + # + # 2.months # => 2 months + def months + ActiveSupport::Duration.months(self) + end + alias :month :months + + # Returns a Duration instance matching the number of years provided. + # + # 2.years # => 2 years + def years + ActiveSupport::Duration.years(self) + end + alias :year :years +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/kernel.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/kernel.rb new file mode 100644 index 00000000..77080693 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/kernel.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require "active_support/core_ext/kernel/concern" +require "active_support/core_ext/kernel/reporting" +require "active_support/core_ext/kernel/singleton_class" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/kernel/concern.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/kernel/concern.rb new file mode 100644 index 00000000..0b2baed7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/kernel/concern.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/concerning" + +module Kernel + module_function + + # A shortcut to define a toplevel concern, not within a module. + # + # See Module::Concerning for more. + def concern(topic, &module_definition) + Object.concern topic, &module_definition + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/kernel/reporting.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/kernel/reporting.rb new file mode 100644 index 00000000..1ae1ae8e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/kernel/reporting.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Kernel + module_function + + # Sets $VERBOSE to +nil+ for the duration of the block and back to its original + # value afterwards. + # + # silence_warnings do + # value = noisy_call # no warning voiced + # end + # + # noisy_call # warning voiced + def silence_warnings(&block) + with_warnings(nil, &block) + end + + # Sets $VERBOSE to +true+ for the duration of the block and back to its + # original value afterwards. + def enable_warnings(&block) + with_warnings(true, &block) + end + + # Sets $VERBOSE for the duration of the block and back to its original + # value afterwards. + def with_warnings(flag) + old_verbose, $VERBOSE = $VERBOSE, flag + yield + ensure + $VERBOSE = old_verbose + end + + # Blocks and ignores any exception passed as argument if raised within the block. + # + # suppress(ZeroDivisionError) do + # 1/0 + # puts 'This code is NOT reached' + # end + # + # puts 'This code gets executed and nothing related to ZeroDivisionError was seen' + def suppress(*exception_classes) + yield + rescue *exception_classes + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/kernel/singleton_class.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/kernel/singleton_class.rb new file mode 100644 index 00000000..31335b68 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/kernel/singleton_class.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Kernel + # class_eval on an object acts like +singleton_class.class_eval+. + def class_eval(*args, &block) + singleton_class.class_eval(*args, &block) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/load_error.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/load_error.rb new file mode 100644 index 00000000..03df2dda --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/load_error.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class LoadError + # Returns true if the given path name (except perhaps for the ".rb" + # extension) is the missing file which caused the exception to be raised. + def is_missing?(location) + location.delete_suffix(".rb") == path.to_s.delete_suffix(".rb") + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module.rb new file mode 100644 index 00000000..542af98c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/aliasing" +require "active_support/core_ext/module/introspection" +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/module/attribute_accessors_per_thread" +require "active_support/core_ext/module/attr_internal" +require "active_support/core_ext/module/concerning" +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/module/deprecation" +require "active_support/core_ext/module/redefine_method" +require "active_support/core_ext/module/remove_method" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/aliasing.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/aliasing.rb new file mode 100644 index 00000000..6f64d116 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/aliasing.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class Module + # Allows you to make aliases for attributes, which includes + # getter, setter, and a predicate. + # + # class Content < ActiveRecord::Base + # # has a title attribute + # end + # + # class Email < Content + # alias_attribute :subject, :title + # end + # + # e = Email.find(1) + # e.title # => "Superstars" + # e.subject # => "Superstars" + # e.subject? # => true + # e.subject = "Megastars" + # e.title # => "Megastars" + def alias_attribute(new_name, old_name) + # The following reader methods use an explicit `self` receiver in order to + # support aliases that start with an uppercase letter. Otherwise, they would + # be resolved as constants instead. + module_eval <<-STR, __FILE__, __LINE__ + 1 + def #{new_name}; self.#{old_name}; end # def subject; self.title; end + def #{new_name}?; self.#{old_name}?; end # def subject?; self.title?; end + def #{new_name}=(v); self.#{old_name} = v; end # def subject=(v); self.title = v; end + STR + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/anonymous.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/anonymous.rb new file mode 100644 index 00000000..d1c86b87 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/anonymous.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class Module + # A module may or may not have a name. + # + # module M; end + # M.name # => "M" + # + # m = Module.new + # m.name # => nil + # + # +anonymous?+ method returns true if module does not have a name, false otherwise: + # + # Module.new.anonymous? # => true + # + # module M; end + # M.anonymous? # => false + # + # A module gets a name when it is first assigned to a constant. Either + # via the +module+ or +class+ keyword or by an explicit assignment: + # + # m = Module.new # creates an anonymous module + # m.anonymous? # => true + # M = m # m gets a name here as a side-effect + # m.name # => "M" + # m.anonymous? # => false + def anonymous? + name.nil? + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/attr_internal.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/attr_internal.rb new file mode 100644 index 00000000..ecf57d8a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/attr_internal.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +class Module + # Declares an attribute reader backed by an internally-named instance variable. + def attr_internal_reader(*attrs) + attrs.each { |attr_name| attr_internal_define(attr_name, :reader) } + end + + # Declares an attribute writer backed by an internally-named instance variable. + def attr_internal_writer(*attrs) + attrs.each { |attr_name| attr_internal_define(attr_name, :writer) } + end + + # Declares an attribute reader and writer backed by an internally-named instance + # variable. + def attr_internal_accessor(*attrs) + attr_internal_reader(*attrs) + attr_internal_writer(*attrs) + end + alias_method :attr_internal, :attr_internal_accessor + + class << self + attr_reader :attr_internal_naming_format + + def attr_internal_naming_format=(format) + if format.start_with?("@") + raise ArgumentError, <<~MESSAGE.squish + Setting `attr_internal_naming_format` with a `@` prefix is not supported. + + You can simply replace #{format.inspect} by #{format.delete_prefix("@").inspect}. + MESSAGE + end + + @attr_internal_naming_format = format + end + end + self.attr_internal_naming_format = "_%s" + + private + def attr_internal_define(attr_name, type) + internal_name = Module.attr_internal_naming_format % attr_name + # use native attr_* methods as they are faster on some Ruby implementations + public_send("attr_#{type}", internal_name) + attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer + alias_method attr_name, internal_name + remove_method internal_name + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/attribute_accessors.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/attribute_accessors.rb new file mode 100644 index 00000000..8511e0f9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/attribute_accessors.rb @@ -0,0 +1,214 @@ +# frozen_string_literal: true + +# == Attribute Accessors +# +# Extends the module object with class/module and instance accessors for +# class/module attributes, just like the native attr* accessors for instance +# attributes. +class Module + # Defines a class attribute and creates a class and instance reader methods. + # The underlying class variable is set to +nil+, if it is not previously + # defined. All class and instance methods created will be public, even if + # this method is called with a private or protected access modifier. + # + # module HairColors + # mattr_reader :hair_colors + # end + # + # HairColors.hair_colors # => nil + # HairColors.class_variable_set("@@hair_colors", [:brown, :black]) + # HairColors.hair_colors # => [:brown, :black] + # + # The attribute name must be a valid method name in Ruby. + # + # module Foo + # mattr_reader :"1_Badname" + # end + # # => NameError: invalid attribute name: 1_Badname + # + # To omit the instance reader method, pass + # instance_reader: false or instance_accessor: false. + # + # module HairColors + # mattr_reader :hair_colors, instance_reader: false + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors # => NoMethodError + # + # You can set a default value for the attribute. + # + # module HairColors + # mattr_reader :hair_colors, default: [:brown, :black, :blonde, :red] + # mattr_reader(:hair_styles) { [:long, :short] } + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors # => [:brown, :black, :blonde, :red] + # Person.new.hair_styles # => [:long, :short] + def mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil, location: nil) + raise TypeError, "module attributes should be defined directly on class, not singleton" if singleton_class? + location ||= caller_locations(1, 1).first + + definition = [] + syms.each do |sym| + raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym) + + definition << "def self.#{sym}; @@#{sym}; end" + + if instance_reader && instance_accessor + definition << "def #{sym}; @@#{sym}; end" + end + + sym_default_value = (block_given? && default.nil?) ? yield : default + class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil? && class_variable_defined?("@@#{sym}") + end + + module_eval(definition.join(";"), location.path, location.lineno) + end + alias :cattr_reader :mattr_reader + + # Defines a class attribute and creates a class and instance writer methods to + # allow assignment to the attribute. All class and instance methods created + # will be public, even if this method is called with a private or protected + # access modifier. + # + # module HairColors + # mattr_writer :hair_colors + # end + # + # class Person + # include HairColors + # end + # + # HairColors.hair_colors = [:brown, :black] + # Person.class_variable_get("@@hair_colors") # => [:brown, :black] + # Person.new.hair_colors = [:blonde, :red] + # HairColors.class_variable_get("@@hair_colors") # => [:blonde, :red] + # + # To omit the instance writer method, pass + # instance_writer: false or instance_accessor: false. + # + # module HairColors + # mattr_writer :hair_colors, instance_writer: false + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors = [:blonde, :red] # => NoMethodError + # + # You can set a default value for the attribute. + # + # module HairColors + # mattr_writer :hair_colors, default: [:brown, :black, :blonde, :red] + # mattr_writer(:hair_styles) { [:long, :short] } + # end + # + # class Person + # include HairColors + # end + # + # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red] + # Person.class_variable_get("@@hair_styles") # => [:long, :short] + def mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil, location: nil) + raise TypeError, "module attributes should be defined directly on class, not singleton" if singleton_class? + location ||= caller_locations(1, 1).first + + definition = [] + syms.each do |sym| + raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym) + definition << "def self.#{sym}=(val); @@#{sym} = val; end" + + if instance_writer && instance_accessor + definition << "def #{sym}=(val); @@#{sym} = val; end" + end + + sym_default_value = (block_given? && default.nil?) ? yield : default + class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil? && class_variable_defined?("@@#{sym}") + end + + module_eval(definition.join(";"), location.path, location.lineno) + end + alias :cattr_writer :mattr_writer + + # Defines both class and instance accessors for class attributes. + # All class and instance methods created will be public, even if + # this method is called with a private or protected access modifier. + # + # module HairColors + # mattr_accessor :hair_colors + # end + # + # class Person + # include HairColors + # end + # + # HairColors.hair_colors = [:brown, :black, :blonde, :red] + # HairColors.hair_colors # => [:brown, :black, :blonde, :red] + # Person.new.hair_colors # => [:brown, :black, :blonde, :red] + # + # If a subclass changes the value then that would also change the value for + # parent class. Similarly if parent class changes the value then that would + # change the value of subclasses too. + # + # class Citizen < Person + # end + # + # Citizen.new.hair_colors << :blue + # Person.new.hair_colors # => [:brown, :black, :blonde, :red, :blue] + # + # To omit the instance writer method, pass instance_writer: false. + # To omit the instance reader method, pass instance_reader: false. + # + # module HairColors + # mattr_accessor :hair_colors, instance_writer: false, instance_reader: false + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors = [:brown] # => NoMethodError + # Person.new.hair_colors # => NoMethodError + # + # Or pass instance_accessor: false, to omit both instance methods. + # + # module HairColors + # mattr_accessor :hair_colors, instance_accessor: false + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors = [:brown] # => NoMethodError + # Person.new.hair_colors # => NoMethodError + # + # You can set a default value for the attribute. + # + # module HairColors + # mattr_accessor :hair_colors, default: [:brown, :black, :blonde, :red] + # mattr_accessor(:hair_styles) { [:long, :short] } + # end + # + # class Person + # include HairColors + # end + # + # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red] + # Person.class_variable_get("@@hair_styles") # => [:long, :short] + def mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil, &blk) + location = caller_locations(1, 1).first + mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, location: location, &blk) + mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor, default: default, location: location) + end + alias :cattr_accessor :mattr_accessor +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb new file mode 100644 index 00000000..628da9c9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb @@ -0,0 +1,175 @@ +# frozen_string_literal: true + +# == Attribute Accessors per Thread +# +# Extends the module object with class/module and instance accessors for +# class/module attributes, just like the native attr* accessors for instance +# attributes, but does so on a per-thread basis. +# +# So the values are scoped within the Thread.current space under the class name +# of the module. +# +# Note that it can also be scoped per-fiber if +Rails.application.config.active_support.isolation_level+ +# is set to +:fiber+. +class Module + # Defines a per-thread class attribute and creates class and instance reader methods. + # The underlying per-thread class variable is set to +nil+, if it is not previously defined. + # + # module Current + # thread_mattr_reader :user + # end + # + # Current.user = "DHH" + # Current.user # => "DHH" + # Thread.new { Current.user }.value # => nil + # + # The attribute name must be a valid method name in Ruby. + # + # module Foo + # thread_mattr_reader :"1_Badname" + # end + # # => NameError: invalid attribute name: 1_Badname + # + # To omit the instance reader method, pass + # instance_reader: false or instance_accessor: false. + # + # class Current + # thread_mattr_reader :user, instance_reader: false + # end + # + # Current.new.user # => NoMethodError + def thread_mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil) # :nodoc: + syms.each do |sym| + raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym) + + # The following generated method concatenates `object_id` because we want + # subclasses to maintain independent values. + if default.nil? + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def self.#{sym} + @__thread_mattr_#{sym} ||= "attr_#{sym}_\#{object_id}" + ::ActiveSupport::IsolatedExecutionState[@__thread_mattr_#{sym}] + end + EOS + else + default = default.dup.freeze unless default.frozen? + singleton_class.define_method("#{sym}_default_value") { default } + + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def self.#{sym} + @__thread_mattr_#{sym} ||= "attr_#{sym}_\#{object_id}" + value = ::ActiveSupport::IsolatedExecutionState[@__thread_mattr_#{sym}] + + if value.nil? && !::ActiveSupport::IsolatedExecutionState.key?(@__thread_mattr_#{sym}) + ::ActiveSupport::IsolatedExecutionState[@__thread_mattr_#{sym}] = #{sym}_default_value + else + value + end + end + EOS + end + + if instance_reader && instance_accessor + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{sym} + self.class.#{sym} + end + EOS + end + end + end + alias :thread_cattr_reader :thread_mattr_reader + + # Defines a per-thread class attribute and creates a class and instance writer methods to + # allow assignment to the attribute. + # + # module Current + # thread_mattr_writer :user + # end + # + # Current.user = "DHH" + # Thread.current[:attr_Current_user] # => "DHH" + # + # To omit the instance writer method, pass + # instance_writer: false or instance_accessor: false. + # + # class Current + # thread_mattr_writer :user, instance_writer: false + # end + # + # Current.new.user = "DHH" # => NoMethodError + def thread_mattr_writer(*syms, instance_writer: true, instance_accessor: true) # :nodoc: + syms.each do |sym| + raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym) + + # The following generated method concatenates `object_id` because we want + # subclasses to maintain independent values. + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def self.#{sym}=(obj) + @__thread_mattr_#{sym} ||= "attr_#{sym}_\#{object_id}" + ::ActiveSupport::IsolatedExecutionState[@__thread_mattr_#{sym}] = obj + end + EOS + + if instance_writer && instance_accessor + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{sym}=(obj) + self.class.#{sym} = obj + end + EOS + end + end + end + alias :thread_cattr_writer :thread_mattr_writer + + # Defines both class and instance accessors for class attributes. + # + # class Account + # thread_mattr_accessor :user + # end + # + # Account.user = "DHH" + # Account.user # => "DHH" + # Account.new.user # => "DHH" + # + # Unlike +mattr_accessor+, values are *not* shared with subclasses or parent classes. + # If a subclass changes the value, the parent class' value is not changed. + # If the parent class changes the value, the value of subclasses is not changed. + # + # class Customer < Account + # end + # + # Account.user # => "DHH" + # Customer.user # => nil + # Customer.user = "Rafael" + # Customer.user # => "Rafael" + # Account.user # => "DHH" + # + # To omit the instance writer method, pass instance_writer: false. + # To omit the instance reader method, pass instance_reader: false. + # + # class Current + # thread_mattr_accessor :user, instance_writer: false, instance_reader: false + # end + # + # Current.new.user = "DHH" # => NoMethodError + # Current.new.user # => NoMethodError + # + # Or pass instance_accessor: false, to omit both instance methods. + # + # class Current + # thread_mattr_accessor :user, instance_accessor: false + # end + # + # Current.new.user = "DHH" # => NoMethodError + # Current.new.user # => NoMethodError + # + # A default value may be specified using the +:default+ option. Because + # multiple threads can access the default value, non-frozen default values + # will be duped and frozen. + def thread_mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil) + thread_mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default) + thread_mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor) + end + alias :thread_cattr_accessor :thread_mattr_accessor +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/concerning.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/concerning.rb new file mode 100644 index 00000000..c8523201 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/concerning.rb @@ -0,0 +1,140 @@ +# frozen_string_literal: true + +require "active_support/concern" + +class Module + # == Bite-sized separation of concerns + # + # We often find ourselves with a medium-sized chunk of behavior that we'd + # like to extract, but only mix in to a single class. + # + # Extracting a plain old Ruby object to encapsulate it and collaborate or + # delegate to the original object is often a good choice, but when there's + # no additional state to encapsulate or we're making DSL-style declarations + # about the parent class, introducing new collaborators can obfuscate rather + # than simplify. + # + # The typical route is to just dump everything in a monolithic class, perhaps + # with a comment, as a least-bad alternative. Using modules in separate files + # means tedious sifting to get a big-picture view. + # + # == Dissatisfying ways to separate small concerns + # + # === Using comments: + # + # class Todo < ApplicationRecord + # # Other todo implementation + # # ... + # + # ## Event tracking + # has_many :events + # + # before_create :track_creation + # + # private + # def track_creation + # # ... + # end + # end + # + # === With an inline module: + # + # Noisy syntax. + # + # class Todo < ApplicationRecord + # # Other todo implementation + # # ... + # + # module EventTracking + # extend ActiveSupport::Concern + # + # included do + # has_many :events + # before_create :track_creation + # end + # + # private + # def track_creation + # # ... + # end + # end + # include EventTracking + # end + # + # === Mix-in noise exiled to its own file: + # + # Once our chunk of behavior starts pushing the scroll-to-understand-it + # boundary, we give in and move it to a separate file. At this size, the + # increased overhead can be a reasonable tradeoff even if it reduces our + # at-a-glance perception of how things work. + # + # class Todo < ApplicationRecord + # # Other todo implementation + # # ... + # + # include TodoEventTracking + # end + # + # == Introducing Module#concerning + # + # By quieting the mix-in noise, we arrive at a natural, low-ceremony way to + # separate bite-sized concerns. + # + # class Todo < ApplicationRecord + # # Other todo implementation + # # ... + # + # concerning :EventTracking do + # included do + # has_many :events + # before_create :track_creation + # end + # + # private + # def track_creation + # # ... + # end + # end + # end + # + # Todo.ancestors + # # => [Todo, Todo::EventTracking, ApplicationRecord, Object] + # + # This small step has some wonderful ripple effects. We can + # * grok the behavior of our class in one glance, + # * clean up monolithic junk-drawer classes by separating their concerns, and + # * stop leaning on protected/private for crude "this is internal stuff" modularity. + # + # === Prepending concerning + # + # concerning supports a prepend: true argument which will prepend the + # concern instead of using include for it. + module Concerning + # Define a new concern and mix it in. + def concerning(topic, prepend: false, &block) + method = prepend ? :prepend : :include + __send__(method, concern(topic, &block)) + end + + # A low-cruft shortcut to define a concern. + # + # concern :EventTracking do + # ... + # end + # + # is equivalent to + # + # module EventTracking + # extend ActiveSupport::Concern + # + # ... + # end + def concern(topic, &module_definition) + const_set topic, Module.new { + extend ::ActiveSupport::Concern + module_eval(&module_definition) + } + end + end + include Concerning +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/delegation.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/delegation.rb new file mode 100644 index 00000000..f306d55f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/delegation.rb @@ -0,0 +1,225 @@ +# frozen_string_literal: true + +class Module + require "active_support/delegation" + DelegationError = ActiveSupport::DelegationError # :nodoc: + + # Provides a +delegate+ class method to easily expose contained objects' + # public methods as your own. + # + # ==== Options + # * :to - Specifies the target object name as a symbol or string + # * :prefix - Prefixes the new method with the target name or a custom prefix + # * :allow_nil - If set to true, prevents a +ActiveSupport::DelegationError+ + # from being raised + # * :private - If set to true, changes method visibility to private + # + # The macro receives one or more method names (specified as symbols or + # strings) and the name of the target object via the :to option + # (also a symbol or string). + # + # Delegation is particularly useful with Active Record associations: + # + # class Greeter < ActiveRecord::Base + # def hello + # 'hello' + # end + # + # def goodbye + # 'goodbye' + # end + # end + # + # class Foo < ActiveRecord::Base + # belongs_to :greeter + # delegate :hello, to: :greeter + # end + # + # Foo.new.hello # => "hello" + # Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for # + # + # Multiple delegates to the same target are allowed: + # + # class Foo < ActiveRecord::Base + # belongs_to :greeter + # delegate :hello, :goodbye, to: :greeter + # end + # + # Foo.new.goodbye # => "goodbye" + # + # Methods can be delegated to instance variables, class variables, or constants + # by providing them as a symbols: + # + # class Foo + # CONSTANT_ARRAY = [0,1,2,3] + # @@class_array = [4,5,6,7] + # + # def initialize + # @instance_array = [8,9,10,11] + # end + # delegate :sum, to: :CONSTANT_ARRAY + # delegate :min, to: :@@class_array + # delegate :max, to: :@instance_array + # end + # + # Foo.new.sum # => 6 + # Foo.new.min # => 4 + # Foo.new.max # => 11 + # + # It's also possible to delegate a method to the class by using +:class+: + # + # class Foo + # def self.hello + # "world" + # end + # + # delegate :hello, to: :class + # end + # + # Foo.new.hello # => "world" + # + # Delegates can optionally be prefixed using the :prefix option. If the value + # is true, the delegate methods are prefixed with the name of the object being + # delegated to. + # + # Person = Struct.new(:name, :address) + # + # class Invoice < Struct.new(:client) + # delegate :name, :address, to: :client, prefix: true + # end + # + # john_doe = Person.new('John Doe', 'Vimmersvej 13') + # invoice = Invoice.new(john_doe) + # invoice.client_name # => "John Doe" + # invoice.client_address # => "Vimmersvej 13" + # + # It is also possible to supply a custom prefix. + # + # class Invoice < Struct.new(:client) + # delegate :name, :address, to: :client, prefix: :customer + # end + # + # invoice = Invoice.new(john_doe) + # invoice.customer_name # => 'John Doe' + # invoice.customer_address # => 'Vimmersvej 13' + # + # The delegated methods are public by default. + # Pass private: true to change that. + # + # class User < ActiveRecord::Base + # has_one :profile + # delegate :first_name, to: :profile + # delegate :date_of_birth, to: :profile, private: true + # + # def age + # Date.today.year - date_of_birth.year + # end + # end + # + # User.new.first_name # => "Tomas" + # User.new.date_of_birth # => NoMethodError: private method `date_of_birth' called for # + # User.new.age # => 2 + # + # If the target is +nil+ and does not respond to the delegated method a + # +ActiveSupport::DelegationError+ is raised. If you wish to instead return +nil+, + # use the :allow_nil option. + # + # class User < ActiveRecord::Base + # has_one :profile + # delegate :age, to: :profile + # end + # + # User.new.age + # # => ActiveSupport::DelegationError: User#age delegated to profile.age, but profile is nil + # + # But if not having a profile yet is fine and should not be an error + # condition: + # + # class User < ActiveRecord::Base + # has_one :profile + # delegate :age, to: :profile, allow_nil: true + # end + # + # User.new.age # nil + # + # Note that if the target is not +nil+ then the call is attempted regardless of the + # :allow_nil option, and thus an exception is still raised if said object + # does not respond to the method: + # + # class Foo + # def initialize(bar) + # @bar = bar + # end + # + # delegate :name, to: :@bar, allow_nil: true + # end + # + # Foo.new("Bar").name # raises NoMethodError: undefined method `name' + # + # The target method must be public, otherwise it will raise +NoMethodError+. + def delegate(*methods, to: nil, prefix: nil, allow_nil: nil, private: nil) + ::ActiveSupport::Delegation.generate( + self, + methods, + location: caller_locations(1, 1).first, + to: to, + prefix: prefix, + allow_nil: allow_nil, + private: private, + ) + end + + # When building decorators, a common pattern may emerge: + # + # class Partition + # def initialize(event) + # @event = event + # end + # + # def person + # detail.person || creator + # end + # + # private + # def respond_to_missing?(name, include_private = false) + # @event.respond_to?(name, include_private) + # end + # + # def method_missing(method, *args, &block) + # @event.send(method, *args, &block) + # end + # end + # + # With Module#delegate_missing_to, the above is condensed to: + # + # class Partition + # delegate_missing_to :@event + # + # def initialize(event) + # @event = event + # end + # + # def person + # detail.person || creator + # end + # end + # + # The target can be anything callable within the object, e.g. instance + # variables, methods, constants, etc. + # + # The delegated method must be public on the target, otherwise it will + # raise +ActiveSupport::DelegationError+. If you wish to instead return +nil+, + # use the :allow_nil option. + # + # The marshal_dump and _dump methods are exempt from + # delegation due to possible interference when calling + # Marshal.dump(object), should the delegation target method + # of object add or remove instance variables. + def delegate_missing_to(target, allow_nil: nil) + ::ActiveSupport::Delegation.generate_method_missing( + self, + target, + allow_nil: allow_nil, + ) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/deprecation.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/deprecation.rb new file mode 100644 index 00000000..420311b3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/deprecation.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class Module + # deprecate :foo, deprecator: MyLib.deprecator + # deprecate :foo, bar: "warning!", deprecator: MyLib.deprecator + # + # A deprecator is typically an instance of ActiveSupport::Deprecation, but you can also pass any object that responds + # to deprecation_warning(deprecated_method_name, message, caller_backtrace) where you can implement your + # custom warning behavior. + # + # class MyLib::Deprecator + # def deprecation_warning(deprecated_method_name, message, caller_backtrace = nil) + # message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}" + # Kernel.warn message + # end + # end + def deprecate(*method_names, deprecator:, **options) + if deprecator.is_a?(ActiveSupport::Deprecation) + deprecator.deprecate_methods(self, *method_names, **options) + elsif deprecator + # we just need any instance to call deprecate_methods, but the deprecation will be emitted by deprecator + ActiveSupport.deprecator.deprecate_methods(self, *method_names, **options, deprecator: deprecator) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/introspection.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/introspection.rb new file mode 100644 index 00000000..97ee421c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/introspection.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require "active_support/inflector" + +class Module + # Returns the name of the module containing this one. + # + # M::N.module_parent_name # => "M" + def module_parent_name + if defined?(@parent_name) + @parent_name + else + name = self.name + return if name.nil? + + parent_name = name =~ /::[^:]+\z/ ? -$` : nil + @parent_name = parent_name unless frozen? + parent_name + end + end + + # Returns the module which contains this one according to its name. + # + # module M + # module N + # end + # end + # X = M::N + # + # M::N.module_parent # => M + # X.module_parent # => M + # + # The parent of top-level and anonymous modules is Object. + # + # M.module_parent # => Object + # Module.new.module_parent # => Object + def module_parent + module_parent_name ? ActiveSupport::Inflector.constantize(module_parent_name) : Object + end + + # Returns all the parents of this module according to its name, ordered from + # nested outwards. The receiver is not contained within the result. + # + # module M + # module N + # end + # end + # X = M::N + # + # M.module_parents # => [Object] + # M::N.module_parents # => [M, Object] + # X.module_parents # => [M, Object] + def module_parents + parents = [] + if module_parent_name + parts = module_parent_name.split("::") + until parts.empty? + parents << ActiveSupport::Inflector.constantize(parts * "::") + parts.pop + end + end + parents << Object unless parents.include? Object + parents + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/redefine_method.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/redefine_method.rb new file mode 100644 index 00000000..5bd8e6e9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/redefine_method.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +class Module + # Marks the named method as intended to be redefined, if it exists. + # Suppresses the Ruby method redefinition warning. Prefer + # #redefine_method where possible. + def silence_redefinition_of_method(method) + if method_defined?(method) || private_method_defined?(method) + # This suppresses the "method redefined" warning; the self-alias + # looks odd, but means we don't need to generate a unique name + alias_method method, method + end + end + + # Replaces the existing method definition, if there is one, with the passed + # block as its body. + def redefine_method(method, &block) + visibility = method_visibility(method) + silence_redefinition_of_method(method) + define_method(method, &block) + send(visibility, method) + end + + # Replaces the existing singleton method definition, if there is one, with + # the passed block as its body. + def redefine_singleton_method(method, &block) + singleton_class.redefine_method(method, &block) + end + + def method_visibility(method) # :nodoc: + case + when private_method_defined?(method) + :private + when protected_method_defined?(method) + :protected + else + :public + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/remove_method.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/remove_method.rb new file mode 100644 index 00000000..97eb5f9e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/module/remove_method.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/redefine_method" + +class Module + # Removes the named method, if it exists. + def remove_possible_method(method) + if method_defined?(method) || private_method_defined?(method) + undef_method(method) + end + end + + # Removes the named singleton method, if it exists. + def remove_possible_singleton_method(method) + singleton_class.remove_possible_method(method) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/name_error.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/name_error.rb new file mode 100644 index 00000000..18ea2754 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/name_error.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +class NameError + # Extract the name of the missing constant from the exception message. + # + # begin + # HelloWorld + # rescue NameError => e + # e.missing_name + # end + # # => "HelloWorld" + def missing_name + # Since ruby v2.3.0 `did_you_mean` gem is loaded by default. + # It extends NameError#message with spell corrections which are SLOW. + # We should use original_message message instead. + message = respond_to?(:original_message) ? original_message : self.message + return unless message.start_with?("uninitialized constant ") + + receiver = begin + self.receiver + rescue ArgumentError + nil + end + + if receiver == Object + name.to_s + elsif receiver + "#{real_mod_name(receiver)}::#{self.name}" + else + if match = message.match(/((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/) + match[1] + end + end + end + + # Was this exception raised because the given name was missing? + # + # begin + # HelloWorld + # rescue NameError => e + # e.missing_name?("HelloWorld") + # end + # # => true + def missing_name?(name) + if name.is_a? Symbol + self.name == name + else + missing_name == name.to_s + end + end + + private + UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name) + private_constant :UNBOUND_METHOD_MODULE_NAME + + def real_mod_name(mod) + UNBOUND_METHOD_MODULE_NAME.bind_call(mod) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/numeric.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/numeric.rb new file mode 100644 index 00000000..fe778470 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/numeric.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require "active_support/core_ext/numeric/bytes" +require "active_support/core_ext/numeric/time" +require "active_support/core_ext/numeric/conversions" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/numeric/bytes.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/numeric/bytes.rb new file mode 100644 index 00000000..45b38f2b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/numeric/bytes.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +class Numeric + KILOBYTE = 1024 + MEGABYTE = KILOBYTE * 1024 + GIGABYTE = MEGABYTE * 1024 + TERABYTE = GIGABYTE * 1024 + PETABYTE = TERABYTE * 1024 + EXABYTE = PETABYTE * 1024 + ZETTABYTE = EXABYTE * 1024 + + # Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes + # + # 2.bytes # => 2 + def bytes + self + end + alias :byte :bytes + + # Returns the number of bytes equivalent to the kilobytes provided. + # + # 2.kilobytes # => 2048 + def kilobytes + self * KILOBYTE + end + alias :kilobyte :kilobytes + + # Returns the number of bytes equivalent to the megabytes provided. + # + # 2.megabytes # => 2_097_152 + def megabytes + self * MEGABYTE + end + alias :megabyte :megabytes + + # Returns the number of bytes equivalent to the gigabytes provided. + # + # 2.gigabytes # => 2_147_483_648 + def gigabytes + self * GIGABYTE + end + alias :gigabyte :gigabytes + + # Returns the number of bytes equivalent to the terabytes provided. + # + # 2.terabytes # => 2_199_023_255_552 + def terabytes + self * TERABYTE + end + alias :terabyte :terabytes + + # Returns the number of bytes equivalent to the petabytes provided. + # + # 2.petabytes # => 2_251_799_813_685_248 + def petabytes + self * PETABYTE + end + alias :petabyte :petabytes + + # Returns the number of bytes equivalent to the exabytes provided. + # + # 2.exabytes # => 2_305_843_009_213_693_952 + def exabytes + self * EXABYTE + end + alias :exabyte :exabytes + + # Returns the number of bytes equivalent to the zettabytes provided. + # + # 2.zettabytes # => 2_361_183_241_434_822_606_848 + def zettabytes + self * ZETTABYTE + end + alias :zettabyte :zettabytes +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/numeric/conversions.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/numeric/conversions.rb new file mode 100644 index 00000000..e9839931 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/numeric/conversions.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +require "active_support/core_ext/big_decimal/conversions" +require "active_support/number_helper" + +module ActiveSupport + module NumericWithFormat + # \Numeric With Format + # + # Provides options for converting numbers into formatted strings. + # Options are provided for phone numbers, currency, percentage, + # precision, positional notation, file size, and pretty printing. + # + # This method is aliased to to_formatted_s. + # + # ==== Options + # + # For details on which formats use which options, see ActiveSupport::NumberHelper + # + # ==== Examples + # + # Phone Numbers: + # 5551234.to_fs(:phone) # => "555-1234" + # 1235551234.to_fs(:phone) # => "123-555-1234" + # 1235551234.to_fs(:phone, area_code: true) # => "(123) 555-1234" + # 1235551234.to_fs(:phone, delimiter: ' ') # => "123 555 1234" + # 1235551234.to_fs(:phone, area_code: true, extension: 555) # => "(123) 555-1234 x 555" + # 1235551234.to_fs(:phone, country_code: 1) # => "+1-123-555-1234" + # 1235551234.to_fs(:phone, country_code: 1, extension: 1343, delimiter: '.') + # # => "+1.123.555.1234 x 1343" + # + # Currency: + # 1234567890.50.to_fs(:currency) # => "$1,234,567,890.50" + # 1234567890.506.to_fs(:currency) # => "$1,234,567,890.51" + # 1234567890.506.to_fs(:currency, precision: 3) # => "$1,234,567,890.506" + # 1234567890.506.to_fs(:currency, round_mode: :down) # => "$1,234,567,890.50" + # 1234567890.506.to_fs(:currency, locale: :fr) # => "1 234 567 890,51 €" + # -1234567890.50.to_fs(:currency, negative_format: '(%u%n)') + # # => "($1,234,567,890.50)" + # 1234567890.50.to_fs(:currency, unit: '£', separator: ',', delimiter: '') + # # => "£1234567890,50" + # 1234567890.50.to_fs(:currency, unit: '£', separator: ',', delimiter: '', format: '%n %u') + # # => "1234567890,50 £" + # + # Percentage: + # 100.to_fs(:percentage) # => "100.000%" + # 100.to_fs(:percentage, precision: 0) # => "100%" + # 1000.to_fs(:percentage, delimiter: '.', separator: ',') # => "1.000,000%" + # 302.24398923423.to_fs(:percentage, precision: 5) # => "302.24399%" + # 302.24398923423.to_fs(:percentage, round_mode: :down) # => "302.243%" + # 1000.to_fs(:percentage, locale: :fr) # => "1 000,000%" + # 100.to_fs(:percentage, format: '%n %') # => "100.000 %" + # + # Delimited: + # 12345678.to_fs(:delimited) # => "12,345,678" + # 12345678.05.to_fs(:delimited) # => "12,345,678.05" + # 12345678.to_fs(:delimited, delimiter: '.') # => "12.345.678" + # 12345678.to_fs(:delimited, delimiter: ',') # => "12,345,678" + # 12345678.05.to_fs(:delimited, separator: ' ') # => "12,345,678 05" + # 12345678.05.to_fs(:delimited, locale: :fr) # => "12 345 678,05" + # 98765432.98.to_fs(:delimited, delimiter: ' ', separator: ',') + # # => "98 765 432,98" + # + # Rounded: + # 111.2345.to_fs(:rounded) # => "111.235" + # 111.2345.to_fs(:rounded, precision: 2) # => "111.23" + # 111.2345.to_fs(:rounded, precision: 2, round_mode: :up) # => "111.24" + # 13.to_fs(:rounded, precision: 5) # => "13.00000" + # 389.32314.to_fs(:rounded, precision: 0) # => "389" + # 111.2345.to_fs(:rounded, significant: true) # => "111" + # 111.2345.to_fs(:rounded, precision: 1, significant: true) # => "100" + # 13.to_fs(:rounded, precision: 5, significant: true) # => "13.000" + # 111.234.to_fs(:rounded, locale: :fr) # => "111,234" + # 13.to_fs(:rounded, precision: 5, significant: true, strip_insignificant_zeros: true) + # # => "13" + # 389.32314.to_fs(:rounded, precision: 4, significant: true) # => "389.3" + # 1111.2345.to_fs(:rounded, precision: 2, separator: ',', delimiter: '.') + # # => "1.111,23" + # + # Human-friendly size in Bytes: + # 123.to_fs(:human_size) # => "123 Bytes" + # 1234.to_fs(:human_size) # => "1.21 KB" + # 12345.to_fs(:human_size) # => "12.1 KB" + # 1234567.to_fs(:human_size) # => "1.18 MB" + # 1234567890.to_fs(:human_size) # => "1.15 GB" + # 1234567890123.to_fs(:human_size) # => "1.12 TB" + # 1234567890123456.to_fs(:human_size) # => "1.1 PB" + # 1234567890123456789.to_fs(:human_size) # => "1.07 EB" + # 1234567.to_fs(:human_size, precision: 2) # => "1.2 MB" + # 1234567.to_fs(:human_size, precision: 2, round_mode: :up) # => "1.3 MB" + # 483989.to_fs(:human_size, precision: 2) # => "470 KB" + # 1234567.to_fs(:human_size, precision: 2, separator: ',') # => "1,2 MB" + # 1234567890123.to_fs(:human_size, precision: 5) # => "1.1228 TB" + # 524288000.to_fs(:human_size, precision: 5) # => "500 MB" + # + # Human-friendly format: + # 123.to_fs(:human) # => "123" + # 1234.to_fs(:human) # => "1.23 Thousand" + # 12345.to_fs(:human) # => "12.3 Thousand" + # 1234567.to_fs(:human) # => "1.23 Million" + # 1234567890.to_fs(:human) # => "1.23 Billion" + # 1234567890123.to_fs(:human) # => "1.23 Trillion" + # 1234567890123456.to_fs(:human) # => "1.23 Quadrillion" + # 1234567890123456789.to_fs(:human) # => "1230 Quadrillion" + # 489939.to_fs(:human, precision: 2) # => "490 Thousand" + # 489939.to_fs(:human, precision: 2, round_mode: :down) # => "480 Thousand" + # 489939.to_fs(:human, precision: 4) # => "489.9 Thousand" + # 1234567.to_fs(:human, precision: 4, + # significant: false) # => "1.2346 Million" + # 1234567.to_fs(:human, precision: 1, + # separator: ',', + # significant: false) # => "1,2 Million" + def to_fs(format = nil, options = nil) + return to_s if format.nil? + + case format + when Integer, String + to_s(format) + when :phone + ActiveSupport::NumberHelper.number_to_phone(self, options || {}) + when :currency + ActiveSupport::NumberHelper.number_to_currency(self, options || {}) + when :percentage + ActiveSupport::NumberHelper.number_to_percentage(self, options || {}) + when :delimited + ActiveSupport::NumberHelper.number_to_delimited(self, options || {}) + when :rounded + ActiveSupport::NumberHelper.number_to_rounded(self, options || {}) + when :human + ActiveSupport::NumberHelper.number_to_human(self, options || {}) + when :human_size + ActiveSupport::NumberHelper.number_to_human_size(self, options || {}) + when Symbol + to_s + else + to_s(format) + end + end + alias_method :to_formatted_s, :to_fs + end +end + +Integer.include ActiveSupport::NumericWithFormat +Float.include ActiveSupport::NumericWithFormat +BigDecimal.include ActiveSupport::NumericWithFormat diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/numeric/time.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/numeric/time.rb new file mode 100644 index 00000000..bc4627f7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/numeric/time.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require "active_support/duration" +require "active_support/core_ext/time/calculations" +require "active_support/core_ext/time/acts_like" +require "active_support/core_ext/date/calculations" +require "active_support/core_ext/date/acts_like" + +class Numeric + # Returns a Duration instance matching the number of seconds provided. + # + # 2.seconds # => 2 seconds + def seconds + ActiveSupport::Duration.seconds(self) + end + alias :second :seconds + + # Returns a Duration instance matching the number of minutes provided. + # + # 2.minutes # => 2 minutes + def minutes + ActiveSupport::Duration.minutes(self) + end + alias :minute :minutes + + # Returns a Duration instance matching the number of hours provided. + # + # 2.hours # => 2 hours + def hours + ActiveSupport::Duration.hours(self) + end + alias :hour :hours + + # Returns a Duration instance matching the number of days provided. + # + # 2.days # => 2 days + def days + ActiveSupport::Duration.days(self) + end + alias :day :days + + # Returns a Duration instance matching the number of weeks provided. + # + # 2.weeks # => 2 weeks + def weeks + ActiveSupport::Duration.weeks(self) + end + alias :week :weeks + + # Returns a Duration instance matching the number of fortnights provided. + # + # 2.fortnights # => 4 weeks + def fortnights + ActiveSupport::Duration.weeks(self * 2) + end + alias :fortnight :fortnights + + # Returns the number of milliseconds equivalent to the seconds provided. + # Used with the standard time durations. + # + # 2.in_milliseconds # => 2000 + # 1.hour.in_milliseconds # => 3600000 + def in_milliseconds + self * 1000 + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object.rb new file mode 100644 index 00000000..7a7f0d99 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/acts_like" +require "active_support/core_ext/object/blank" +require "active_support/core_ext/object/duplicable" +require "active_support/core_ext/object/deep_dup" +require "active_support/core_ext/object/try" +require "active_support/core_ext/object/inclusion" + +require "active_support/core_ext/object/conversions" +require "active_support/core_ext/object/instance_variables" + +require "active_support/core_ext/object/json" +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/object/with" +require "active_support/core_ext/object/with_options" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/acts_like.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/acts_like.rb new file mode 100644 index 00000000..292826c8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/acts_like.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +class Object + # Provides a way to check whether some class acts like some other class based on the existence of + # an appropriately-named marker method. + # + # A class that provides the same interface as SomeClass may define a marker method named + # acts_like_some_class? to signal its compatibility to callers of + # acts_like?(:some_class). + # + # For example, Active Support extends Date to define an acts_like_date? method, + # and extends Time to define acts_like_time?. As a result, developers can call + # x.acts_like?(:time) and x.acts_like?(:date) to test duck-type compatibility, + # and classes that are able to act like Time can also define an acts_like_time? + # method to interoperate. + # + # Note that the marker method is only expected to exist. It isn't called, so its body or return + # value are irrelevant. + # + # ==== Example: A class that provides the same interface as String + # + # This class may define: + # + # class Stringish + # def acts_like_string? + # end + # end + # + # Then client code can query for duck-type-safeness this way: + # + # Stringish.new.acts_like?(:string) # => true + # + def acts_like?(duck) + case duck + when :time + respond_to? :acts_like_time? + when :date + respond_to? :acts_like_date? + when :string + respond_to? :acts_like_string? + else + respond_to? :"acts_like_#{duck}?" + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/blank.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/blank.rb new file mode 100644 index 00000000..967c82ed --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/blank.rb @@ -0,0 +1,199 @@ +# frozen_string_literal: true + +require "concurrent/map" + +class Object + # An object is blank if it's false, empty, or a whitespace string. + # For example, +nil+, '', ' ', [], {}, and +false+ are all blank. + # + # This simplifies + # + # !address || address.empty? + # + # to + # + # address.blank? + # + # @return [true, false] + def blank? + respond_to?(:empty?) ? !!empty? : false + end + + # An object is present if it's not blank. + # + # @return [true, false] + def present? + !blank? + end + + # Returns the receiver if it's present otherwise returns +nil+. + # object.presence is equivalent to + # + # object.present? ? object : nil + # + # For example, something like + # + # state = params[:state] if params[:state].present? + # country = params[:country] if params[:country].present? + # region = state || country || 'US' + # + # becomes + # + # region = params[:state].presence || params[:country].presence || 'US' + # + # @return [Object] + def presence + self if present? + end +end + +class NilClass + # +nil+ is blank: + # + # nil.blank? # => true + # + # @return [true] + def blank? + true + end + + def present? # :nodoc: + false + end +end + +class FalseClass + # +false+ is blank: + # + # false.blank? # => true + # + # @return [true] + def blank? + true + end + + def present? # :nodoc: + false + end +end + +class TrueClass + # +true+ is not blank: + # + # true.blank? # => false + # + # @return [false] + def blank? + false + end + + def present? # :nodoc: + true + end +end + +class Array + # An array is blank if it's empty: + # + # [].blank? # => true + # [1,2,3].blank? # => false + # + # @return [true, false] + alias_method :blank?, :empty? + + def present? # :nodoc: + !empty? + end +end + +class Hash + # A hash is blank if it's empty: + # + # {}.blank? # => true + # { key: 'value' }.blank? # => false + # + # @return [true, false] + alias_method :blank?, :empty? + + def present? # :nodoc: + !empty? + end +end + +class Symbol + # A Symbol is blank if it's empty: + # + # :''.blank? # => true + # :symbol.blank? # => false + alias_method :blank?, :empty? + + def present? # :nodoc: + !empty? + end +end + +class String + BLANK_RE = /\A[[:space:]]*\z/ + ENCODED_BLANKS = Concurrent::Map.new do |h, enc| + h[enc] = Regexp.new(BLANK_RE.source.encode(enc), BLANK_RE.options | Regexp::FIXEDENCODING) + end + + # A string is blank if it's empty or contains whitespaces only: + # + # ''.blank? # => true + # ' '.blank? # => true + # "\t\n\r".blank? # => true + # ' blah '.blank? # => false + # + # Unicode whitespace is supported: + # + # "\u00a0".blank? # => true + # + # @return [true, false] + def blank? + # The regexp that matches blank strings is expensive. For the case of empty + # strings we can speed up this method (~3.5x) with an empty? call. The + # penalty for the rest of strings is marginal. + empty? || + begin + BLANK_RE.match?(self) + rescue Encoding::CompatibilityError + ENCODED_BLANKS[self.encoding].match?(self) + end + end + + def present? # :nodoc: + !blank? + end +end + +class Numeric # :nodoc: + # No number is blank: + # + # 1.blank? # => false + # 0.blank? # => false + # + # @return [false] + def blank? + false + end + + def present? + true + end +end + +class Time # :nodoc: + # No Time is blank: + # + # Time.now.blank? # => false + # + # @return [false] + def blank? + false + end + + def present? + true + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/conversions.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/conversions.rb new file mode 100644 index 00000000..624fb8d7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/conversions.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/array/conversions" +require "active_support/core_ext/hash/conversions" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/deep_dup.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/deep_dup.rb new file mode 100644 index 00000000..7d54d0cd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/deep_dup.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/duplicable" + +class Object + # Returns a deep copy of object if it's duplicable. If it's + # not duplicable, returns +self+. + # + # object = Object.new + # dup = object.deep_dup + # dup.instance_variable_set(:@a, 1) + # + # object.instance_variable_defined?(:@a) # => false + # dup.instance_variable_defined?(:@a) # => true + def deep_dup + duplicable? ? dup : self + end +end + +class Array + # Returns a deep copy of array. + # + # array = [1, [2, 3]] + # dup = array.deep_dup + # dup[1][2] = 4 + # + # array[1][2] # => nil + # dup[1][2] # => 4 + def deep_dup + map(&:deep_dup) + end +end + +class Hash + # Returns a deep copy of hash. + # + # hash = { a: { b: 'b' } } + # dup = hash.deep_dup + # dup[:a][:c] = 'c' + # + # hash[:a][:c] # => nil + # dup[:a][:c] # => "c" + def deep_dup + hash = dup + each_pair do |key, value| + if ::String === key || ::Symbol === key + hash[key] = value.deep_dup + else + hash.delete(key) + hash[key.deep_dup] = value.deep_dup + end + end + hash + end +end + +class Module + # Returns a copy of module or class if it's anonymous. If it's + # named, returns +self+. + # + # Object.deep_dup == Object # => true + # klass = Class.new + # klass.deep_dup == klass # => false + def deep_dup + if name.nil? + super + else + self + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/duplicable.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/duplicable.rb new file mode 100644 index 00000000..505455fe --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/duplicable.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +#-- +# Most objects are cloneable, but not all. For example you can't dup methods: +# +# method(:puts).dup # => TypeError: allocator undefined for Method +# +# Classes may signal their instances are not duplicable removing +dup+/+clone+ +# or raising exceptions from them. So, to dup an arbitrary object you normally +# use an optimistic approach and are ready to catch an exception, say: +# +# arbitrary_object.dup rescue object +# +# Rails dups objects in a few critical spots where they are not that arbitrary. +# That rescue is very expensive (like 40 times slower than a predicate), and it +# is often triggered. +# +# That's why we hardcode the following cases and check duplicable? instead of +# using that rescue idiom. +#++ +class Object + # Can you safely dup this object? + # + # False for method objects; + # true otherwise. + def duplicable? + true + end +end + +methods_are_duplicable = begin + Object.instance_method(:duplicable?).dup + true +rescue TypeError + false +end + +unless methods_are_duplicable + class Method + # Methods are not duplicable: + # + # method(:puts).duplicable? # => false + # method(:puts).dup # => TypeError: allocator undefined for Method + def duplicable? + false + end + end + + class UnboundMethod + # Unbound methods are not duplicable: + # + # method(:puts).unbind.duplicable? # => false + # method(:puts).unbind.dup # => TypeError: allocator undefined for UnboundMethod + def duplicable? + false + end + end +end + +require "singleton" + +module Singleton + # Singleton instances are not duplicable: + # + # Class.new.include(Singleton).instance.dup # TypeError (can't dup instance of singleton + def duplicable? + false + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/inclusion.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/inclusion.rb new file mode 100644 index 00000000..1b3cf2c3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/inclusion.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +class Object + # Returns true if this object is included in the argument. + # + # When argument is a +Range+, +#cover?+ is used to properly handle inclusion + # check within open ranges. Otherwise, argument must be any object which responds + # to +#include?+. Usage: + # + # characters = ["Konata", "Kagami", "Tsukasa"] + # "Konata".in?(characters) # => true + # + # For non +Range+ arguments, this will throw an +ArgumentError+ if the argument + # doesn't respond to +#include?+. + def in?(another_object) + case another_object + when Range + another_object.cover?(self) + else + another_object.include?(self) + end + rescue NoMethodError + raise ArgumentError.new("The parameter passed to #in? must respond to #include?") + end + + # Returns the receiver if it's included in the argument otherwise returns +nil+. + # Argument must be any object which responds to +#include?+. Usage: + # + # params[:bucket_type].presence_in %w( project calendar ) + # + # This will throw an +ArgumentError+ if the argument doesn't respond to +#include?+. + # + # @return [Object] + def presence_in(another_object) + in?(another_object) ? self : nil + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/instance_variables.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/instance_variables.rb new file mode 100644 index 00000000..98b341d3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/instance_variables.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class Object + # Returns a hash with string keys that maps instance variable names without "@" to their + # corresponding values. + # + # class C + # def initialize(x, y) + # @x, @y = x, y + # end + # end + # + # C.new(0, 1).instance_values # => {"x" => 0, "y" => 1} + def instance_values + instance_variables.to_h do |ivar| + [ivar[1..-1].freeze, instance_variable_get(ivar)] + end + end + + # Returns an array of instance variable names as strings including "@". + # + # class C + # def initialize(x, y) + # @x, @y = x, y + # end + # end + # + # C.new(0, 1).instance_variable_names # => ["@y", "@x"] + def instance_variable_names + instance_variables.map(&:name) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/json.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/json.rb new file mode 100644 index 00000000..3d6e536e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/json.rb @@ -0,0 +1,260 @@ +# frozen_string_literal: true + +# Hack to load JSON gem first so we can override its to_json. +require "json" +require "bigdecimal" +require "ipaddr" +require "uri/generic" +require "pathname" +require "active_support/core_ext/big_decimal/conversions" # for #to_s +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/object/instance_variables" +require "time" +require "active_support/core_ext/time/conversions" +require "active_support/core_ext/date_time/conversions" +require "active_support/core_ext/date/conversions" + +#-- +# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting +# their default behavior. That said, we need to define the basic to_json method in all of them, +# otherwise they will always use to_json gem implementation, which is backwards incompatible in +# several cases (for instance, the JSON implementation for Hash does not work) with inheritance. +# +# On the other hand, we should avoid conflict with ::JSON.{generate,dump}(obj). Unfortunately, the +# JSON gem's encoder relies on its own to_json implementation to encode objects. Since it always +# passes a ::JSON::State object as the only argument to to_json, we can detect that and forward the +# calls to the original to_json method. +# +# It should be noted that when using ::JSON.{generate,dump} directly, ActiveSupport's encoder is +# bypassed completely. This means that as_json won't be invoked and the JSON gem will simply +# ignore any options it does not natively understand. This also means that ::JSON.{generate,dump} +# should give exactly the same results with or without Active Support. + +module ActiveSupport + module ToJsonWithActiveSupportEncoder # :nodoc: + def to_json(options = nil) + if options.is_a?(::JSON::State) + # Called from JSON.{generate,dump}, forward it to JSON gem's to_json + super(options) + else + # to_json is being invoked directly, use ActiveSupport's encoder + ActiveSupport::JSON.encode(self, options) + end + end + end +end + +[Enumerable, Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].reverse_each do |klass| + klass.include(ActiveSupport::ToJsonWithActiveSupportEncoder) +end + +class Module + def as_json(options = nil) # :nodoc: + name + end +end + +class Object + def as_json(options = nil) # :nodoc: + if respond_to?(:to_hash) + to_hash.as_json(options) + else + instance_values.as_json(options) + end + end +end + +class Data # :nodoc: + def as_json(options = nil) + to_h.as_json(options) + end +end + +class Struct # :nodoc: + def as_json(options = nil) + to_h.as_json(options) + end +end + +class TrueClass + def as_json(options = nil) # :nodoc: + self + end +end + +class FalseClass + def as_json(options = nil) # :nodoc: + self + end +end + +class NilClass + def as_json(options = nil) # :nodoc: + self + end +end + +class String + def as_json(options = nil) # :nodoc: + self + end +end + +class Symbol + def as_json(options = nil) # :nodoc: + name + end +end + +class Numeric + def as_json(options = nil) # :nodoc: + self + end +end + +class Float + # Encoding Infinity or NaN to JSON should return "null". The default returns + # "Infinity" or "NaN" which are not valid JSON. + def as_json(options = nil) # :nodoc: + finite? ? self : nil + end +end + +class BigDecimal + # A BigDecimal would be naturally represented as a JSON number. Most libraries, + # however, parse non-integer JSON numbers directly as floats. Clients using + # those libraries would get in general a wrong number and no way to recover + # other than manually inspecting the string with the JSON code itself. + # + # That's why a JSON string is returned. The JSON literal is not numeric, but + # if the other end knows by contract that the data is supposed to be a + # BigDecimal, it still has the chance to post-process the string and get the + # real value. + def as_json(options = nil) # :nodoc: + finite? ? to_s : nil + end +end + +class Regexp + def as_json(options = nil) # :nodoc: + to_s + end +end + +module Enumerable + def as_json(options = nil) # :nodoc: + to_a.as_json(options) + end +end + +class IO + def as_json(options = nil) # :nodoc: + to_s + end +end + +class Range + def as_json(options = nil) # :nodoc: + to_s + end +end + +class Array + def as_json(options = nil) # :nodoc: + if options + options = options.dup.freeze unless options.frozen? + map { |v| v.as_json(options) } + else + map { |v| v.as_json } + end + end +end + +class Hash + def as_json(options = nil) # :nodoc: + # create a subset of the hash by applying :only or :except + subset = if options + if attrs = options[:only] + slice(*Array(attrs)) + elsif attrs = options[:except] + except(*Array(attrs)) + else + self + end + else + self + end + + result = {} + if options + options = options.dup.freeze unless options.frozen? + subset.each { |k, v| result[k.to_s] = v.as_json(options) } + else + subset.each { |k, v| result[k.to_s] = v.as_json } + end + result + end +end + +class Time + def as_json(options = nil) # :nodoc: + if ActiveSupport::JSON::Encoding.use_standard_json_time_format + xmlschema(ActiveSupport::JSON::Encoding.time_precision) + else + %(#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) + end + end +end + +class Date + def as_json(options = nil) # :nodoc: + if ActiveSupport::JSON::Encoding.use_standard_json_time_format + strftime("%Y-%m-%d") + else + strftime("%Y/%m/%d") + end + end +end + +class DateTime + def as_json(options = nil) # :nodoc: + if ActiveSupport::JSON::Encoding.use_standard_json_time_format + xmlschema(ActiveSupport::JSON::Encoding.time_precision) + else + strftime("%Y/%m/%d %H:%M:%S %z") + end + end +end + +class URI::Generic # :nodoc: + def as_json(options = nil) + to_s + end +end + +class Pathname # :nodoc: + def as_json(options = nil) + to_s + end +end + +unless IPAddr.method_defined?(:as_json, false) + class IPAddr # :nodoc: + def as_json(options = nil) + to_s + end + end +end + +class Process::Status # :nodoc: + def as_json(options = nil) + { exitstatus: exitstatus, pid: pid } + end +end + +class Exception + def as_json(options = nil) + to_s + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/to_param.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/to_param.rb new file mode 100644 index 00000000..6d2bdd70 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/to_param.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/to_query" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/to_query.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/to_query.rb new file mode 100644 index 00000000..7cccc03d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/to_query.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require "cgi" + +class Object + # Alias of to_s. + def to_param + to_s + end + + # Converts an object into a string suitable for use as a URL query string, + # using the given key as the param name. + def to_query(key) + "#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}" + end +end + +class NilClass + # Returns +self+. + def to_param + self + end +end + +class TrueClass + # Returns +self+. + def to_param + self + end +end + +class FalseClass + # Returns +self+. + def to_param + self + end +end + +class Array + # Calls to_param on all its elements and joins the result with + # slashes. This is used by url_for in Action Pack. + def to_param + collect(&:to_param).join "/" + end + + # Converts an array into a string suitable for use as a URL query string, + # using the given +key+ as the param name. + # + # ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding" + def to_query(key) + prefix = "#{key}[]" + + if empty? + nil.to_query(prefix) + else + collect { |value| value.to_query(prefix) }.join "&" + end + end +end + +class Hash + # Returns a string representation of the receiver suitable for use as a URL + # query string: + # + # {name: 'David', nationality: 'Danish'}.to_query + # # => "name=David&nationality=Danish" + # + # An optional namespace can be passed to enclose key names: + # + # {name: 'David', nationality: 'Danish'}.to_query('user') + # # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish" + # + # The string pairs "key=value" that conform the query string + # are sorted lexicographically in ascending order. + def to_query(namespace = nil) + query = filter_map do |key, value| + unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty? + value.to_query(namespace ? "#{namespace}[#{key}]" : key) + end + end + + query.sort! unless namespace.to_s.include?("[]") + query.join("&") + end + + alias_method :to_param, :to_query +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/try.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/try.rb new file mode 100644 index 00000000..c2c76254 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/try.rb @@ -0,0 +1,158 @@ +# frozen_string_literal: true + +require "delegate" + +module ActiveSupport + module Tryable # :nodoc: + def try(*args, &block) + if args.empty? && block_given? + if block.arity == 0 + instance_eval(&block) + else + yield self + end + elsif respond_to?(args.first) + public_send(*args, &block) + end + end + ruby2_keywords(:try) + + def try!(*args, &block) + if args.empty? && block_given? + if block.arity == 0 + instance_eval(&block) + else + yield self + end + else + public_send(*args, &block) + end + end + ruby2_keywords(:try!) + end +end + +class Object + include ActiveSupport::Tryable + + ## + # :method: try + # + # :call-seq: + # try(*args, &block) + # + # Invokes the public method whose name goes as first argument just like + # +public_send+ does, except that if the receiver does not respond to it the + # call returns +nil+ rather than raising an exception. + # + # This method is defined to be able to write + # + # @person.try(:name) + # + # instead of + # + # @person.name if @person + # + # +try+ calls can be chained: + # + # @person.try(:spouse).try(:name) + # + # instead of + # + # @person.spouse.name if @person && @person.spouse + # + # +try+ will also return +nil+ if the receiver does not respond to the method: + # + # @person.try(:non_existing_method) # => nil + # + # instead of + # + # @person.non_existing_method if @person.respond_to?(:non_existing_method) # => nil + # + # +try+ returns +nil+ when called on +nil+ regardless of whether it responds + # to the method: + # + # nil.try(:to_i) # => nil, rather than 0 + # + # Arguments and blocks are forwarded to the method if invoked: + # + # @posts.try(:each_slice, 2) do |a, b| + # ... + # end + # + # The number of arguments in the signature must match. If the object responds + # to the method the call is attempted and +ArgumentError+ is still raised + # in case of argument mismatch. + # + # If +try+ is called without arguments it yields the receiver to a given + # block unless it is +nil+: + # + # @person.try do |p| + # ... + # end + # + # You can also call try with a block without accepting an argument, and the block + # will be instance_eval'ed instead: + # + # @person.try { upcase.truncate(50) } + # + # Please also note that +try+ is defined on +Object+. Therefore, it won't work + # with instances of classes that do not have +Object+ among their ancestors, + # like direct subclasses of +BasicObject+. + + ## + # :method: try! + # + # :call-seq: + # try!(*args, &block) + # + # Same as #try, but raises a +NoMethodError+ exception if the receiver is + # not +nil+ and does not implement the tried method. + # + # "a".try!(:upcase) # => "A" + # nil.try!(:upcase) # => nil + # 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Integer +end + +class Delegator + include ActiveSupport::Tryable + + ## + # :method: try + # + # :call-seq: + # try(*args, &block) + # + # See Object#try + + ## + # :method: try! + # + # :call-seq: + # try!(*args, &block) + # + # See Object#try! +end + +class NilClass + # Calling +try+ on +nil+ always returns +nil+. + # It becomes especially helpful when navigating through associations that may return +nil+. + # + # nil.try(:name) # => nil + # + # Without +try+ + # @person && @person.children.any? && @person.children.first.name + # + # With +try+ + # @person.try(:children).try(:first).try(:name) + def try(*) + nil + end + + # Calling +try!+ on +nil+ always returns +nil+. + # + # nil.try!(:name) # => nil + def try!(*) + nil + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/with.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/with.rb new file mode 100644 index 00000000..29f61088 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/with.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +class Object + # Set and restore public attributes around a block. + # + # client.timeout # => 5 + # client.with(timeout: 1) do |c| + # c.timeout # => 1 + # end + # client.timeout # => 5 + # + # The receiver is yielded to the provided block. + # + # This method is a shorthand for the common begin/ensure pattern: + # + # old_value = object.attribute + # begin + # object.attribute = new_value + # # do things + # ensure + # object.attribute = old_value + # end + # + # It can be used on any object as long as both the reader and writer methods + # are public. + def with(**attributes) + old_values = {} + begin + attributes.each do |key, value| + old_values[key] = public_send(key) + public_send("#{key}=", value) + end + yield self + ensure + old_values.each do |key, old_value| + public_send("#{key}=", old_value) + end + end + end +end + +# #with isn't usable on immediates, so we might as well undefine the +# method in common immediate classes to avoid potential confusion. +[NilClass, TrueClass, FalseClass, Integer, Float, Symbol].each do |klass| + klass.undef_method(:with) +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/with_options.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/with_options.rb new file mode 100644 index 00000000..932aec22 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/object/with_options.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require "active_support/option_merger" + +class Object + # An elegant way to factor duplication out of options passed to a series of + # method calls. Each method called in the block, with the block variable as + # the receiver, will have its options merged with the default +options+ + # Hash or Hash-like object provided. Each method called on + # the block variable must take an options hash as its final argument. + # + # Without with_options, this code contains duplication: + # + # class Account < ActiveRecord::Base + # has_many :customers, dependent: :destroy + # has_many :products, dependent: :destroy + # has_many :invoices, dependent: :destroy + # has_many :expenses, dependent: :destroy + # end + # + # Using with_options, we can remove the duplication: + # + # class Account < ActiveRecord::Base + # with_options dependent: :destroy do |assoc| + # assoc.has_many :customers + # assoc.has_many :products + # assoc.has_many :invoices + # assoc.has_many :expenses + # end + # end + # + # It can also be used with an explicit receiver: + # + # I18n.with_options locale: user.locale, scope: 'newsletter' do |i18n| + # subject i18n.t :subject + # body i18n.t :body, user_name: user.name + # end + # + # When you don't pass an explicit receiver, it executes the whole block + # in merging options context: + # + # class Account < ActiveRecord::Base + # with_options dependent: :destroy do + # has_many :customers + # has_many :products + # has_many :invoices + # has_many :expenses + # end + # end + # + # with_options can also be nested since the call is forwarded to its receiver. + # + # NOTE: Each nesting level will merge inherited defaults in addition to their own. + # + # class Post < ActiveRecord::Base + # with_options if: :persisted?, length: { minimum: 50 } do + # validates :content, if: -> { content.present? } + # end + # end + # + # The code is equivalent to: + # + # validates :content, length: { minimum: 50 }, if: -> { content.present? } + # + # Hence the inherited default for +if+ key is ignored. + # + # NOTE: You cannot call class methods implicitly inside of +with_options+. + # You can access these methods using the class name instead: + # + # class Phone < ActiveRecord::Base + # enum :phone_number_type, { home: 0, office: 1, mobile: 2 } + # + # with_options presence: true do + # validates :phone_number_type, inclusion: { in: Phone.phone_number_types.keys } + # end + # end + # + # When the block argument is omitted, the decorated Object instance is returned: + # + # module MyStyledHelpers + # def styled + # with_options style: "color: red;" + # end + # end + # + # styled.link_to "I'm red", "/" + # # => I'm red + # + # styled.button_tag "I'm red too!" + # # => + # + def with_options(options, &block) + option_merger = ActiveSupport::OptionMerger.new(self, options) + + if block + block.arity.zero? ? option_merger.instance_eval(&block) : block.call(option_merger) + else + option_merger + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/pathname.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/pathname.rb new file mode 100644 index 00000000..10fa1903 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/pathname.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +require "active_support/core_ext/pathname/blank" +require "active_support/core_ext/pathname/existence" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/pathname/blank.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/pathname/blank.rb new file mode 100644 index 00000000..8a9be3ce --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/pathname/blank.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "pathname" + +class Pathname + # An Pathname is blank if it's empty: + # + # Pathname.new("").blank? # => true + # Pathname.new(" ").blank? # => false + # Pathname.new("test").blank? # => false + # + # @return [true, false] + def blank? + to_s.empty? + end + + def present? # :nodoc: + !to_s.empty? + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/pathname/existence.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/pathname/existence.rb new file mode 100644 index 00000000..848eb4dc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/pathname/existence.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "pathname" + +class Pathname + # Returns the receiver if the named file exists otherwise returns +nil+. + # pathname.existence is equivalent to + # + # pathname.exist? ? pathname : nil + # + # For example, something like + # + # content = pathname.read if pathname.exist? + # + # becomes + # + # content = pathname.existence&.read + # + # @return [Pathname] + def existence + self if exist? + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/range.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/range.rb new file mode 100644 index 00000000..10fad28d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/range.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +require "active_support/core_ext/range/conversions" +require "active_support/core_ext/range/compare_range" +require "active_support/core_ext/range/overlap" +require "active_support/core_ext/range/each" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/range/compare_range.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/range/compare_range.rb new file mode 100644 index 00000000..affbbebb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/range/compare_range.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module ActiveSupport + module CompareWithRange + # Extends the default Range#=== to support range comparisons. + # (1..5) === (1..5) # => true + # (1..5) === (2..3) # => true + # (1..5) === (1...6) # => true + # (1..5) === (2..6) # => false + # + # The native Range#=== behavior is untouched. + # ('a'..'f') === ('c') # => true + # (5..9) === (11) # => false + # + # The given range must be fully bounded, with both start and end. + def ===(value) + if value.is_a?(::Range) + is_backwards_op = value.exclude_end? ? :>= : :> + return false if value.begin && value.end && value.begin.public_send(is_backwards_op, value.end) + # 1...10 includes 1..9 but it does not include 1..10. + # 1..10 includes 1...11 but it does not include 1...12. + operator = exclude_end? && !value.exclude_end? ? :< : :<= + value_max = !exclude_end? && value.exclude_end? ? value.max : value.last + super(value.first) && (self.end.nil? || value_max.public_send(operator, last)) + else + super + end + end + + # Extends the default Range#include? to support range comparisons. + # (1..5).include?(1..5) # => true + # (1..5).include?(2..3) # => true + # (1..5).include?(1...6) # => true + # (1..5).include?(2..6) # => false + # + # The native Range#include? behavior is untouched. + # ('a'..'f').include?('c') # => true + # (5..9).include?(11) # => false + # + # The given range must be fully bounded, with both start and end. + def include?(value) + if value.is_a?(::Range) + is_backwards_op = value.exclude_end? ? :>= : :> + return false if value.begin && value.end && value.begin.public_send(is_backwards_op, value.end) + # 1...10 includes 1..9 but it does not include 1..10. + # 1..10 includes 1...11 but it does not include 1...12. + operator = exclude_end? && !value.exclude_end? ? :< : :<= + value_max = !exclude_end? && value.exclude_end? ? value.max : value.last + super(value.first) && (self.end.nil? || value_max.public_send(operator, last)) + else + super + end + end + end +end + +Range.prepend(ActiveSupport::CompareWithRange) diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/range/conversions.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/range/conversions.rb new file mode 100644 index 00000000..b6d81864 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/range/conversions.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module ActiveSupport + # = \Range With Format + module RangeWithFormat + RANGE_FORMATS = { + db: -> (start, stop) do + if start && stop + case start + when String then "BETWEEN '#{start}' AND '#{stop}'" + else + "BETWEEN '#{start.to_fs(:db)}' AND '#{stop.to_fs(:db)}'" + end + elsif start + case start + when String then ">= '#{start}'" + else + ">= '#{start.to_fs(:db)}'" + end + elsif stop + case stop + when String then "<= '#{stop}'" + else + "<= '#{stop.to_fs(:db)}'" + end + end + end + } + + # Convert range to a formatted string. See RANGE_FORMATS for predefined formats. + # + # This method is aliased to to_formatted_s. + # + # range = (1..100) # => 1..100 + # + # range.to_s # => "1..100" + # range.to_fs(:db) # => "BETWEEN '1' AND '100'" + # + # range = (1..) # => 1.. + # range.to_fs(:db) # => ">= '1'" + # + # range = (..100) # => ..100 + # range.to_fs(:db) # => "<= '100'" + # + # == Adding your own range formats to to_fs + # You can add your own formats to the Range::RANGE_FORMATS hash. + # Use the format name as the hash key and a Proc instance. + # + # # config/initializers/range_formats.rb + # Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_fs(:db)} and #{stop.to_fs(:db)}" } + def to_fs(format = :default) + if formatter = RANGE_FORMATS[format] + formatter.call(self.begin, self.end) + else + to_s + end + end + alias_method :to_formatted_s, :to_fs + end +end + +Range.prepend(ActiveSupport::RangeWithFormat) diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/range/each.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/range/each.rb new file mode 100644 index 00000000..1c44cc84 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/range/each.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "active_support/time_with_zone" + +module ActiveSupport + module EachTimeWithZone # :nodoc: + def each(&block) + ensure_iteration_allowed + super + end + + def step(n = 1, &block) + ensure_iteration_allowed + super + end + + private + def ensure_iteration_allowed + raise TypeError, "can't iterate from #{first.class}" if first.is_a?(TimeWithZone) + end + end +end + +Range.prepend(ActiveSupport::EachTimeWithZone) diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/range/overlap.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/range/overlap.rb new file mode 100644 index 00000000..b08cdb39 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/range/overlap.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +class Range + # Compare two ranges and see if they overlap each other + # (1..5).overlap?(4..6) # => true + # (1..5).overlap?(7..9) # => false + unless Range.method_defined?(:overlap?) # Ruby 3.3+ + def overlap?(other) + raise TypeError unless other.is_a? Range + + self_begin = self.begin + other_end = other.end + other_excl = other.exclude_end? + + return false if _empty_range?(self_begin, other_end, other_excl) + + other_begin = other.begin + self_end = self.end + self_excl = self.exclude_end? + + return false if _empty_range?(other_begin, self_end, self_excl) + return true if self_begin == other_begin + + return false if _empty_range?(self_begin, self_end, self_excl) + return false if _empty_range?(other_begin, other_end, other_excl) + + true + end + + private + def _empty_range?(b, e, excl) + return false if b.nil? || e.nil? + + comp = b <=> e + comp.nil? || comp > 0 || (comp == 0 && excl) + end + end + + alias :overlaps? :overlap? +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/regexp.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/regexp.rb new file mode 100644 index 00000000..15534ff5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/regexp.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class Regexp + # Returns +true+ if the regexp has the multiline flag set. + # + # (/./).multiline? # => false + # (/./m).multiline? # => true + # + # Regexp.new(".").multiline? # => false + # Regexp.new(".", Regexp::MULTILINE).multiline? # => true + def multiline? + options & MULTILINE == MULTILINE + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/securerandom.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/securerandom.rb new file mode 100644 index 00000000..aae75290 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/securerandom.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require "securerandom" + +module SecureRandom + BASE58_ALPHABET = ("0".."9").to_a + ("A".."Z").to_a + ("a".."z").to_a - ["0", "O", "I", "l"] + BASE36_ALPHABET = ("0".."9").to_a + ("a".."z").to_a + + # SecureRandom.base58 generates a random base58 string. + # + # The argument _n_ specifies the length of the random string to be generated. + # + # If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future. + # + # The result may contain alphanumeric characters except 0, O, I, and l. + # + # p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE" + # p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7" + if SecureRandom.method(:alphanumeric).parameters.size == 2 # Remove check when Ruby 3.3 is the minimum supported version + def self.base58(n = 16) + alphanumeric(n, chars: BASE58_ALPHABET) + end + else + def self.base58(n = 16) + SecureRandom.random_bytes(n).unpack("C*").map do |byte| + idx = byte % 64 + idx = SecureRandom.random_number(58) if idx >= 58 + BASE58_ALPHABET[idx] + end.join + end + end + + # SecureRandom.base36 generates a random base36 string in lowercase. + # + # The argument _n_ specifies the length of the random string to be generated. + # + # If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future. + # This method can be used over +base58+ if a deterministic case key is necessary. + # + # The result will contain alphanumeric characters in lowercase. + # + # p SecureRandom.base36 # => "4kugl2pdqmscqtje" + # p SecureRandom.base36(24) # => "77tmhrhjfvfdwodq8w7ev2m7" + if SecureRandom.method(:alphanumeric).parameters.size == 2 # Remove check when Ruby 3.3 is the minimum supported version + def self.base36(n = 16) + alphanumeric(n, chars: BASE36_ALPHABET) + end + else + def self.base36(n = 16) + SecureRandom.random_bytes(n).unpack("C*").map do |byte| + idx = byte % 64 + idx = SecureRandom.random_number(36) if idx >= 36 + BASE36_ALPHABET[idx] + end.join + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string.rb new file mode 100644 index 00000000..757d15c5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/conversions" +require "active_support/core_ext/string/filters" +require "active_support/core_ext/string/multibyte" +require "active_support/core_ext/string/starts_ends_with" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/string/access" +require "active_support/core_ext/string/behavior" +require "active_support/core_ext/string/output_safety" +require "active_support/core_ext/string/exclude" +require "active_support/core_ext/string/strip" +require "active_support/core_ext/string/inquiry" +require "active_support/core_ext/string/indent" +require "active_support/core_ext/string/zones" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/access.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/access.rb new file mode 100644 index 00000000..f6a14c08 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/access.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +class String + # If you pass a single integer, returns a substring of one character at that + # position. The first character of the string is at position 0, the next at + # position 1, and so on. If a range is supplied, a substring containing + # characters at offsets given by the range is returned. In both cases, if an + # offset is negative, it is counted from the end of the string. Returns +nil+ + # if the initial offset falls outside the string. Returns an empty string if + # the beginning of the range is greater than the end of the string. + # + # str = "hello" + # str.at(0) # => "h" + # str.at(1..3) # => "ell" + # str.at(-2) # => "l" + # str.at(-2..-1) # => "lo" + # str.at(5) # => nil + # str.at(5..-1) # => "" + # + # If a Regexp is given, the matching portion of the string is returned. + # If a String is given, that given string is returned if it occurs in + # the string. In both cases, +nil+ is returned if there is no match. + # + # str = "hello" + # str.at(/lo/) # => "lo" + # str.at(/ol/) # => nil + # str.at("lo") # => "lo" + # str.at("ol") # => nil + def at(position) + self[position] + end + + # Returns a substring from the given position to the end of the string. + # If the position is negative, it is counted from the end of the string. + # + # str = "hello" + # str.from(0) # => "hello" + # str.from(3) # => "lo" + # str.from(-2) # => "lo" + # + # You can mix it with +to+ method and do fun things like: + # + # str = "hello" + # str.from(0).to(-1) # => "hello" + # str.from(1).to(-2) # => "ell" + def from(position) + self[position, length] + end + + # Returns a substring from the beginning of the string to the given position. + # If the position is negative, it is counted from the end of the string. + # + # str = "hello" + # str.to(0) # => "h" + # str.to(3) # => "hell" + # str.to(-2) # => "hell" + # + # You can mix it with +from+ method and do fun things like: + # + # str = "hello" + # str.from(0).to(-1) # => "hello" + # str.from(1).to(-2) # => "ell" + def to(position) + position += size if position < 0 + self[0, position + 1] || +"" + end + + # Returns the first character. If a limit is supplied, returns a substring + # from the beginning of the string until it reaches the limit value. If the + # given limit is greater than or equal to the string length, returns a copy of self. + # + # str = "hello" + # str.first # => "h" + # str.first(1) # => "h" + # str.first(2) # => "he" + # str.first(0) # => "" + # str.first(6) # => "hello" + def first(limit = 1) + self[0, limit] || raise(ArgumentError, "negative limit") + end + + # Returns the last character of the string. If a limit is supplied, returns a substring + # from the end of the string until it reaches the limit value (counting backwards). If + # the given limit is greater than or equal to the string length, returns a copy of self. + # + # str = "hello" + # str.last # => "o" + # str.last(1) # => "o" + # str.last(2) # => "lo" + # str.last(0) # => "" + # str.last(6) # => "hello" + def last(limit = 1) + self[[length - limit, 0].max, limit] || raise(ArgumentError, "negative limit") + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/behavior.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/behavior.rb new file mode 100644 index 00000000..35a5aa78 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/behavior.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class String + # Enables more predictable duck-typing on String-like classes. See Object#acts_like?. + def acts_like_string? + true + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/conversions.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/conversions.rb new file mode 100644 index 00000000..107721c6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/conversions.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require "date" +require "active_support/core_ext/time/calculations" + +class String + # Converts a string to a Time value. + # The +form+ can be either +:utc+ or +:local+ (default +:local+). + # + # The time is parsed using Time.parse method. + # If +form+ is +:local+, then the time is in the system timezone. + # If the date part is missing then the current date is used and if + # the time part is missing then it is assumed to be 00:00:00. + # + # "13-12-2012".to_time # => 2012-12-13 00:00:00 +0100 + # "06:12".to_time # => 2012-12-13 06:12:00 +0100 + # "2012-12-13 06:12".to_time # => 2012-12-13 06:12:00 +0100 + # "2012-12-13T06:12".to_time # => 2012-12-13 06:12:00 +0100 + # "2012-12-13T06:12".to_time(:utc) # => 2012-12-13 06:12:00 UTC + # "12/13/2012".to_time # => ArgumentError: argument out of range + # "1604326192".to_time # => ArgumentError: argument out of range + def to_time(form = :local) + parts = Date._parse(self, false) + used_keys = %i(year mon mday hour min sec sec_fraction offset) + return if !parts.keys.intersect?(used_keys) + + now = Time.now + time = Time.new( + parts.fetch(:year, now.year), + parts.fetch(:mon, now.month), + parts.fetch(:mday, now.day), + parts.fetch(:hour, 0), + parts.fetch(:min, 0), + parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0), + parts.fetch(:offset, form == :utc ? 0 : nil) + ) + + form == :utc ? time.utc : time.to_time + end + + # Converts a string to a Date value. + # + # "1-1-2012".to_date # => Sun, 01 Jan 2012 + # "01/01/2012".to_date # => Sun, 01 Jan 2012 + # "2012-12-13".to_date # => Thu, 13 Dec 2012 + # "12/13/2012".to_date # => ArgumentError: invalid date + def to_date + ::Date.parse(self, false) unless blank? + end + + # Converts a string to a DateTime value. + # + # "1-1-2012".to_datetime # => Sun, 01 Jan 2012 00:00:00 +0000 + # "01/01/2012 23:59:59".to_datetime # => Sun, 01 Jan 2012 23:59:59 +0000 + # "2012-12-13 12:50".to_datetime # => Thu, 13 Dec 2012 12:50:00 +0000 + # "12/13/2012".to_datetime # => ArgumentError: invalid date + def to_datetime + ::DateTime.parse(self, false) unless blank? + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/exclude.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/exclude.rb new file mode 100644 index 00000000..8e462689 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/exclude.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class String + # The inverse of String#include?. Returns true if the string + # does not include the other string. + # + # "hello".exclude? "lo" # => false + # "hello".exclude? "ol" # => true + # "hello".exclude? ?h # => false + def exclude?(string) + !include?(string) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/filters.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/filters.rb new file mode 100644 index 00000000..9e7e5511 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/filters.rb @@ -0,0 +1,151 @@ +# frozen_string_literal: true + +class String + # Returns the string, first removing all whitespace on both ends of + # the string, and then changing remaining consecutive whitespace + # groups into one space each. + # + # Note that it handles both ASCII and Unicode whitespace. + # + # %{ Multi-line + # string }.squish # => "Multi-line string" + # " foo bar \n \t boo".squish # => "foo bar boo" + def squish + dup.squish! + end + + # Performs a destructive squish. See String#squish. + # str = " foo bar \n \t boo" + # str.squish! # => "foo bar boo" + # str # => "foo bar boo" + def squish! + gsub!(/[[:space:]]+/, " ") + strip! + self + end + + # Returns a new string with all occurrences of the patterns removed. + # str = "foo bar test" + # str.remove(" test") # => "foo bar" + # str.remove(" test", /bar/) # => "foo " + # str # => "foo bar test" + def remove(*patterns) + dup.remove!(*patterns) + end + + # Alters the string by removing all occurrences of the patterns. + # str = "foo bar test" + # str.remove!(" test", /bar/) # => "foo " + # str # => "foo " + def remove!(*patterns) + patterns.each do |pattern| + gsub! pattern, "" + end + + self + end + + # Truncates a given +text+ to length truncate_to if +text+ is longer than truncate_to: + # + # 'Once upon a time in a world far far away'.truncate(27) + # # => "Once upon a time in a wo..." + # + # Pass a string or regexp :separator to truncate +text+ at a natural break: + # + # 'Once upon a time in a world far far away'.truncate(27, separator: ' ') + # # => "Once upon a time in a..." + # + # 'Once upon a time in a world far far away'.truncate(27, separator: /\s/) + # # => "Once upon a time in a..." + # + # The last characters will be replaced with the :omission string (defaults to "..."). + # The total length will not exceed truncate_to unless both +text+ and :omission + # are longer than truncate_to: + # + # 'And they found that many people were sleeping better.'.truncate(25, omission: '... (continued)') + # # => "And they f... (continued)" + # + # 'And they found that many people were sleeping better.'.truncate(4, omission: '... (continued)') + # # => "... (continued)" + def truncate(truncate_to, options = {}) + return dup unless length > truncate_to + + omission = options[:omission] || "..." + length_with_room_for_omission = truncate_to - omission.length + stop = \ + if options[:separator] + rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission + else + length_with_room_for_omission + end + + +"#{self[0, stop]}#{omission}" + end + + # Truncates +text+ to at most truncate_to bytes in length without + # breaking string encoding by splitting multibyte characters or breaking + # grapheme clusters ("perceptual characters") by truncating at combining + # characters. + # + # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".size + # => 20 + # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".bytesize + # => 80 + # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".truncate_bytes(20) + # => "🔪🔪🔪🔪…" + # + # The truncated text ends with the :omission string, defaulting + # to "…", for a total length not exceeding truncate_to. + # + # Raises +ArgumentError+ when the bytesize of :omission exceeds truncate_to. + def truncate_bytes(truncate_to, omission: "…") + omission ||= "" + + case + when bytesize <= truncate_to + dup + when omission.bytesize > truncate_to + raise ArgumentError, "Omission #{omission.inspect} is #{omission.bytesize}, larger than the truncation length of #{truncate_to} bytes" + when omission.bytesize == truncate_to + omission.dup + else + self.class.new.force_encoding(encoding).tap do |cut| + cut_at = truncate_to - omission.bytesize + + each_grapheme_cluster do |grapheme| + if cut.bytesize + grapheme.bytesize <= cut_at + cut << grapheme + else + break + end + end + + cut << omission + end + end + end + + # Truncates a given +text+ after a given number of words (words_count): + # + # 'Once upon a time in a world far far away'.truncate_words(4) + # # => "Once upon a time..." + # + # Pass a string or regexp :separator to specify a different separator of words: + # + # 'Once
    upon
    a
    time
    in
    a
    world'.truncate_words(5, separator: '
    ') + # # => "Once
    upon
    a
    time
    in..." + # + # The last characters will be replaced with the :omission string (defaults to "..."): + # + # 'And they found that many people were sleeping better.'.truncate_words(5, omission: '... (continued)') + # # => "And they found that many... (continued)" + def truncate_words(words_count, options = {}) + sep = options[:separator] || /\s+/ + sep = Regexp.escape(sep.to_s) unless Regexp === sep + if self =~ /\A((?>.+?#{sep}){#{words_count - 1}}.+?)#{sep}.*/m + $1 + (options[:omission] || "...") + else + dup + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/indent.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/indent.rb new file mode 100644 index 00000000..9dcc4302 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/indent.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +class String + # Same as +indent+, except it indents the receiver in-place. + # + # Returns the indented string, or +nil+ if there was nothing to indent. + def indent!(amount, indent_string = nil, indent_empty_lines = false) + indent_string = indent_string || self[/^[ \t]/] || " " + re = indent_empty_lines ? /^/ : /^(?!$)/ + gsub!(re, indent_string * amount) + end + + # Indents the lines in the receiver: + # + # < + # def some_method + # some_code + # end + # + # The second argument, +indent_string+, specifies which indent string to + # use. The default is +nil+, which tells the method to make a guess by + # peeking at the first indented line, and fall back to a space if there is + # none. + # + # " foo".indent(2) # => " foo" + # "foo\n\t\tbar".indent(2) # => "\t\tfoo\n\t\t\t\tbar" + # "foo".indent(2, "\t") # => "\t\tfoo" + # + # While +indent_string+ is typically one space or tab, it may be any string. + # + # The third argument, +indent_empty_lines+, is a flag that says whether + # empty lines should be indented. Default is false. + # + # "foo\n\nbar".indent(2) # => " foo\n\n bar" + # "foo\n\nbar".indent(2, nil, true) # => " foo\n \n bar" + # + def indent(amount, indent_string = nil, indent_empty_lines = false) + dup.tap { |_| _.indent!(amount, indent_string, indent_empty_lines) } + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/inflections.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/inflections.rb new file mode 100644 index 00000000..da127c9a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/inflections.rb @@ -0,0 +1,300 @@ +# frozen_string_literal: true + +require "active_support/inflector/methods" +require "active_support/inflector/transliterate" + +# String inflections define new methods on the String class to transform names for different purposes. +# For instance, you can figure out the name of a table from the name of a class. +# +# 'ScaleScore'.tableize # => "scale_scores" +# +class String + # Returns the plural form of the word in the string. + # + # If the optional parameter +count+ is specified, + # the singular form will be returned if count == 1. + # For any other value of +count+ the plural will be returned. + # + # If the optional parameter +locale+ is specified, + # the word will be pluralized as a word of that language. + # By default, this parameter is set to :en. + # You must define your own inflection rules for languages other than English. + # + # 'post'.pluralize # => "posts" + # 'octopus'.pluralize # => "octopi" + # 'sheep'.pluralize # => "sheep" + # 'words'.pluralize # => "words" + # 'the blue mailman'.pluralize # => "the blue mailmen" + # 'CamelOctopus'.pluralize # => "CamelOctopi" + # 'apple'.pluralize(1) # => "apple" + # 'apple'.pluralize(2) # => "apples" + # 'ley'.pluralize(:es) # => "leyes" + # 'ley'.pluralize(1, :es) # => "ley" + # + # See ActiveSupport::Inflector.pluralize. + def pluralize(count = nil, locale = :en) + locale = count if count.is_a?(Symbol) + if count == 1 + dup + else + ActiveSupport::Inflector.pluralize(self, locale) + end + end + + # The reverse of +pluralize+, returns the singular form of a word in a string. + # + # If the optional parameter +locale+ is specified, + # the word will be singularized as a word of that language. + # By default, this parameter is set to :en. + # You must define your own inflection rules for languages other than English. + # + # 'posts'.singularize # => "post" + # 'octopi'.singularize # => "octopus" + # 'sheep'.singularize # => "sheep" + # 'word'.singularize # => "word" + # 'the blue mailmen'.singularize # => "the blue mailman" + # 'CamelOctopi'.singularize # => "CamelOctopus" + # 'leyes'.singularize(:es) # => "ley" + # + # See ActiveSupport::Inflector.singularize. + def singularize(locale = :en) + ActiveSupport::Inflector.singularize(self, locale) + end + + # +constantize+ tries to find a declared constant with the name specified + # in the string. It raises a NameError when the name is not in CamelCase + # or is not initialized. + # + # 'Module'.constantize # => Module + # 'Class'.constantize # => Class + # 'blargle'.constantize # => NameError: wrong constant name blargle + # + # See ActiveSupport::Inflector.constantize. + def constantize + ActiveSupport::Inflector.constantize(self) + end + + # +safe_constantize+ tries to find a declared constant with the name specified + # in the string. It returns +nil+ when the name is not in CamelCase + # or is not initialized. + # + # 'Module'.safe_constantize # => Module + # 'Class'.safe_constantize # => Class + # 'blargle'.safe_constantize # => nil + # + # See ActiveSupport::Inflector.safe_constantize. + def safe_constantize + ActiveSupport::Inflector.safe_constantize(self) + end + + # By default, +camelize+ converts strings to UpperCamelCase. If the argument to camelize + # is set to :lower then camelize produces lowerCamelCase. + # + # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces. + # + # 'active_record'.camelize # => "ActiveRecord" + # 'active_record'.camelize(:lower) # => "activeRecord" + # 'active_record/errors'.camelize # => "ActiveRecord::Errors" + # 'active_record/errors'.camelize(:lower) # => "activeRecord::Errors" + # + # See ActiveSupport::Inflector.camelize. + def camelize(first_letter = :upper) + case first_letter + when :upper + ActiveSupport::Inflector.camelize(self, true) + when :lower + ActiveSupport::Inflector.camelize(self, false) + else + raise ArgumentError, "Invalid option, use either :upper or :lower." + end + end + alias_method :camelcase, :camelize + + # Capitalizes all the words and replaces some characters in the string to create + # a nicer looking title. +titleize+ is meant for creating pretty output. It is not + # used in the \Rails internals. + # + # The trailing '_id','Id'.. can be kept and capitalized by setting the + # optional parameter +keep_id_suffix+ to true. + # By default, this parameter is false. + # + # 'man from the boondocks'.titleize # => "Man From The Boondocks" + # 'x-men: the last stand'.titleize # => "X Men: The Last Stand" + # 'string_ending_with_id'.titleize(keep_id_suffix: true) # => "String Ending With Id" + # + # See ActiveSupport::Inflector.titleize. + def titleize(keep_id_suffix: false) + ActiveSupport::Inflector.titleize(self, keep_id_suffix: keep_id_suffix) + end + alias_method :titlecase, :titleize + + # The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string. + # + # +underscore+ will also change '::' to '/' to convert namespaces to paths. + # + # 'ActiveModel'.underscore # => "active_model" + # 'ActiveModel::Errors'.underscore # => "active_model/errors" + # + # See ActiveSupport::Inflector.underscore. + def underscore + ActiveSupport::Inflector.underscore(self) + end + + # Replaces underscores with dashes in the string. + # + # 'puni_puni'.dasherize # => "puni-puni" + # + # See ActiveSupport::Inflector.dasherize. + def dasherize + ActiveSupport::Inflector.dasherize(self) + end + + # Removes the module part from the constant expression in the string. + # + # 'ActiveSupport::Inflector::Inflections'.demodulize # => "Inflections" + # 'Inflections'.demodulize # => "Inflections" + # '::Inflections'.demodulize # => "Inflections" + # ''.demodulize # => '' + # + # See ActiveSupport::Inflector.demodulize. + # + # See also +deconstantize+. + def demodulize + ActiveSupport::Inflector.demodulize(self) + end + + # Removes the rightmost segment from the constant expression in the string. + # + # 'Net::HTTP'.deconstantize # => "Net" + # '::Net::HTTP'.deconstantize # => "::Net" + # 'String'.deconstantize # => "" + # '::String'.deconstantize # => "" + # ''.deconstantize # => "" + # + # See ActiveSupport::Inflector.deconstantize. + # + # See also +demodulize+. + def deconstantize + ActiveSupport::Inflector.deconstantize(self) + end + + # Replaces special characters in a string so that it may be used as part of a 'pretty' URL. + # + # If the optional parameter +locale+ is specified, + # the word will be parameterized as a word of that language. + # By default, this parameter is set to nil and it will use + # the configured I18n.locale. + # + # class Person + # def to_param + # "#{id}-#{name.parameterize}" + # end + # end + # + # @person = Person.find(1) + # # => # + # + # <%= link_to(@person.name, person_path) %> + # # => Donald E. Knuth + # + # To preserve the case of the characters in a string, use the +preserve_case+ argument. + # + # class Person + # def to_param + # "#{id}-#{name.parameterize(preserve_case: true)}" + # end + # end + # + # @person = Person.find(1) + # # => # + # + # <%= link_to(@person.name, person_path) %> + # # => Donald E. Knuth + # + # See ActiveSupport::Inflector.parameterize. + def parameterize(separator: "-", preserve_case: false, locale: nil) + ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case, locale: locale) + end + + # Creates the name of a table like \Rails does for models to table names. This method + # uses the +pluralize+ method on the last word in the string. + # + # 'RawScaledScorer'.tableize # => "raw_scaled_scorers" + # 'ham_and_egg'.tableize # => "ham_and_eggs" + # 'fancyCategory'.tableize # => "fancy_categories" + # + # See ActiveSupport::Inflector.tableize. + def tableize + ActiveSupport::Inflector.tableize(self) + end + + # Creates a class name from a plural table name like \Rails does for table names to models. + # Note that this returns a string and not a class. (To convert to an actual class + # follow +classify+ with +constantize+.) + # + # 'ham_and_eggs'.classify # => "HamAndEgg" + # 'posts'.classify # => "Post" + # + # See ActiveSupport::Inflector.classify. + def classify + ActiveSupport::Inflector.classify(self) + end + + # Capitalizes the first word, turns underscores into spaces, and (by default) strips a + # trailing '_id' if present. + # Like +titleize+, this is meant for creating pretty output. + # + # The capitalization of the first word can be turned off by setting the + # optional parameter +capitalize+ to false. + # By default, this parameter is true. + # + # The trailing '_id' can be kept and capitalized by setting the + # optional parameter +keep_id_suffix+ to true. + # By default, this parameter is false. + # + # 'employee_salary'.humanize # => "Employee salary" + # 'author_id'.humanize # => "Author" + # 'author_id'.humanize(capitalize: false) # => "author" + # '_id'.humanize # => "Id" + # 'author_id'.humanize(keep_id_suffix: true) # => "Author id" + # + # See ActiveSupport::Inflector.humanize. + def humanize(capitalize: true, keep_id_suffix: false) + ActiveSupport::Inflector.humanize(self, capitalize: capitalize, keep_id_suffix: keep_id_suffix) + end + + # Converts the first character to uppercase. + # + # 'what a Lovely Day'.upcase_first # => "What a Lovely Day" + # 'w'.upcase_first # => "W" + # ''.upcase_first # => "" + # + # See ActiveSupport::Inflector.upcase_first. + def upcase_first + ActiveSupport::Inflector.upcase_first(self) + end + + # Converts the first character to lowercase. + # + # 'If they enjoyed The Matrix'.downcase_first # => "if they enjoyed The Matrix" + # 'I'.downcase_first # => "i" + # ''.downcase_first # => "" + # + # See ActiveSupport::Inflector.downcase_first. + def downcase_first + ActiveSupport::Inflector.downcase_first(self) + end + + # Creates a foreign key name from a class name. + # +separate_class_name_and_id_with_underscore+ sets whether + # the method should put '_' between the name and 'id'. + # + # 'Message'.foreign_key # => "message_id" + # 'Message'.foreign_key(false) # => "messageid" + # 'Admin::Post'.foreign_key # => "post_id" + # + # See ActiveSupport::Inflector.foreign_key. + def foreign_key(separate_class_name_and_id_with_underscore = true) + ActiveSupport::Inflector.foreign_key(self, separate_class_name_and_id_with_underscore) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/inquiry.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/inquiry.rb new file mode 100644 index 00000000..a3b42dad --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/inquiry.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "active_support/string_inquirer" +require "active_support/environment_inquirer" + +class String + # Wraps the current string in the ActiveSupport::StringInquirer class, + # which gives you a prettier way to test for equality. + # + # env = 'production'.inquiry + # env.production? # => true + # env.development? # => false + def inquiry + ActiveSupport::StringInquirer.new(self) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/multibyte.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/multibyte.rb new file mode 100644 index 00000000..b74de3f1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/multibyte.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require "active_support/multibyte" + +class String + # == Multibyte proxy + # + # +mb_chars+ is a multibyte safe proxy for string methods. + # + # It creates and returns an instance of the ActiveSupport::Multibyte::Chars class which + # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy + # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string. + # + # >> "lj".mb_chars.upcase.to_s + # => "LJ" + # + # NOTE: Ruby 2.4 and later support native Unicode case mappings: + # + # >> "lj".upcase + # => "LJ" + # + # == \Method chaining + # + # All the methods on the Chars proxy which normally return a string will return a Chars object. This allows + # method chaining on the result of any of these methods. + # + # name.mb_chars.reverse.length # => 12 + # + # == Interoperability and configuration + # + # The Chars object tries to be as interchangeable with String objects as possible: sorting and comparing between + # String and Char work like expected. The bang! methods change the internal string representation in the Chars + # object. Interoperability problems can be resolved easily with a +to_s+ call. + # + # For more information about the methods defined on the Chars proxy see ActiveSupport::Multibyte::Chars. For + # information about how to change the default Multibyte behavior see ActiveSupport::Multibyte. + def mb_chars + ActiveSupport::Multibyte.proxy_class.new(self) + end + + # Returns +true+ if string has utf_8 encoding. + # + # utf_8_str = "some string".encode "UTF-8" + # iso_str = "some string".encode "ISO-8859-1" + # + # utf_8_str.is_utf8? # => true + # iso_str.is_utf8? # => false + def is_utf8? + case encoding + when Encoding::UTF_8, Encoding::US_ASCII + valid_encoding? + when Encoding::ASCII_8BIT + dup.force_encoding(Encoding::UTF_8).valid_encoding? + else + false + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/output_safety.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/output_safety.rb new file mode 100644 index 00000000..8e44c045 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/output_safety.rb @@ -0,0 +1,228 @@ +# frozen_string_literal: true + +require "active_support/core_ext/erb/util" +require "active_support/multibyte/unicode" + +class Object + def html_safe? + false + end +end + +class Numeric + def html_safe? + true + end +end + +module ActiveSupport # :nodoc: + class SafeBuffer < String + UNSAFE_STRING_METHODS = %w( + capitalize chomp chop delete delete_prefix delete_suffix + downcase lstrip next reverse rstrip scrub squeeze strip + succ swapcase tr tr_s unicode_normalize upcase + ) + + UNSAFE_STRING_METHODS_WITH_BACKREF = %w(gsub sub) + + alias_method :original_concat, :concat + private :original_concat + + # Raised when ActiveSupport::SafeBuffer#safe_concat is called on unsafe buffers. + class SafeConcatError < StandardError + def initialize + super "Could not concatenate to the buffer because it is not HTML safe." + end + end + + def [](*args) + if html_safe? + new_string = super + + return unless new_string + + string_into_safe_buffer(new_string, true) + else + to_str[*args] + end + end + alias_method :slice, :[] + + def slice!(*args) + new_string = super + + return new_string if !html_safe? || new_string.nil? + + string_into_safe_buffer(new_string, true) + end + + def chr + return super unless html_safe? + + string_into_safe_buffer(super, true) + end + + def safe_concat(value) + raise SafeConcatError unless html_safe? + original_concat(value) + end + + def initialize(str = "") + @html_safe = true + super + end + + def initialize_copy(other) + super + @html_safe = other.html_safe? + end + + def concat(value) + unless value.nil? + super(implicit_html_escape_interpolated_argument(value)) + end + self + end + alias << concat + + def bytesplice(*args, value) + super(*args, implicit_html_escape_interpolated_argument(value)) + end + + def insert(index, value) + super(index, implicit_html_escape_interpolated_argument(value)) + end + + def prepend(value) + super(implicit_html_escape_interpolated_argument(value)) + end + + def replace(value) + super(implicit_html_escape_interpolated_argument(value)) + end + + def []=(arg1, arg2, arg3 = nil) + if arg3 + super(arg1, arg2, implicit_html_escape_interpolated_argument(arg3)) + else + super(arg1, implicit_html_escape_interpolated_argument(arg2)) + end + end + + def +(other) + dup.concat(other) + end + + def *(_) + new_string = super + new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string) + new_safe_buffer.instance_variable_set(:@html_safe, @html_safe) + new_safe_buffer + end + + def %(args) + case args + when Hash + escaped_args = args.transform_values { |arg| explicit_html_escape_interpolated_argument(arg) } + else + escaped_args = Array(args).map { |arg| explicit_html_escape_interpolated_argument(arg) } + end + + self.class.new(super(escaped_args)) + end + + attr_reader :html_safe + alias_method :html_safe?, :html_safe + remove_method :html_safe + + def to_s + self + end + + def to_param + to_str + end + + def encode_with(coder) + coder.represent_object nil, to_str + end + + UNSAFE_STRING_METHODS.each do |unsafe_method| + if unsafe_method.respond_to?(unsafe_method) + class_eval <<-EOT, __FILE__, __LINE__ + 1 + def #{unsafe_method}(*args, &block) # def capitalize(*args, &block) + to_str.#{unsafe_method}(*args, &block) # to_str.capitalize(*args, &block) + end # end + + def #{unsafe_method}!(*args) # def capitalize!(*args) + @html_safe = false # @html_safe = false + super # super + end # end + EOT + end + end + + UNSAFE_STRING_METHODS_WITH_BACKREF.each do |unsafe_method| + class_eval <<-EOT, __FILE__, __LINE__ + 1 + def #{unsafe_method}(*args, &block) # def gsub(*args, &block) + if block # if block + to_str.#{unsafe_method}(*args) { |*params| # to_str.gsub(*args) { |*params| + set_block_back_references(block, $~) # set_block_back_references(block, $~) + block.call(*params) # block.call(*params) + } # } + else # else + to_str.#{unsafe_method}(*args) # to_str.gsub(*args) + end # end + end # end + + def #{unsafe_method}!(*args, &block) # def gsub!(*args, &block) + @html_safe = false # @html_safe = false + if block # if block + super(*args) { |*params| # super(*args) { |*params| + set_block_back_references(block, $~) # set_block_back_references(block, $~) + block.call(*params) # block.call(*params) + } # } + else # else + super # super + end # end + end # end + EOT + end + + private + def explicit_html_escape_interpolated_argument(arg) + (!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s) + end + + def implicit_html_escape_interpolated_argument(arg) + if !html_safe? || arg.html_safe? + arg + else + CGI.escapeHTML(arg.to_str) + end + end + + def set_block_back_references(block, match_data) + block.binding.eval("proc { |m| $~ = m }").call(match_data) + rescue ArgumentError + # Can't create binding from C level Proc + end + + def string_into_safe_buffer(new_string, is_html_safe) + new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string) + new_safe_buffer.instance_variable_set :@html_safe, is_html_safe + new_safe_buffer + end + end +end + +class String + # Marks a string as trusted safe. It will be inserted into HTML with no + # additional escaping performed. It is your responsibility to ensure that the + # string contains no malicious content. This method is equivalent to the + # +raw+ helper in views. It is recommended that you use +sanitize+ instead of + # this method. It should never be called on user input. + def html_safe + ActiveSupport::SafeBuffer.new(self) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/starts_ends_with.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/starts_ends_with.rb new file mode 100644 index 00000000..1e216370 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/starts_ends_with.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class String + alias :starts_with? :start_with? + alias :ends_with? :end_with? +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/strip.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/strip.rb new file mode 100644 index 00000000..60e9952e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/strip.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class String + # Strips indentation in heredocs. + # + # For example in + # + # if options[:usage] + # puts <<-USAGE.strip_heredoc + # This command does such and such. + # + # Supported options are: + # -h This message + # ... + # USAGE + # end + # + # the user would see the usage message aligned against the left margin. + # + # Technically, it looks for the least indented non-empty line + # in the whole string, and removes that amount of leading whitespace. + def strip_heredoc + gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, "").tap do |stripped| + stripped.freeze if frozen? + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/zones.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/zones.rb new file mode 100644 index 00000000..55dc2314 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/string/zones.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/conversions" +require "active_support/core_ext/time/zones" + +class String + # Converts String to a TimeWithZone in the current zone if Time.zone or Time.zone_default + # is set, otherwise converts String to a Time via String#to_time + def in_time_zone(zone = ::Time.zone) + if zone + ::Time.find_zone!(zone).parse(self) + else + to_time + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/symbol.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/symbol.rb new file mode 100644 index 00000000..709fed20 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/symbol.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "active_support/core_ext/symbol/starts_ends_with" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/symbol/starts_ends_with.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/symbol/starts_ends_with.rb new file mode 100644 index 00000000..4f852175 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/symbol/starts_ends_with.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class Symbol + alias :starts_with? :start_with? + alias :ends_with? :end_with? +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/thread/backtrace/location.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/thread/backtrace/location.rb new file mode 100644 index 00000000..d21acffb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/thread/backtrace/location.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Thread::Backtrace::Location # :nodoc: + def spot(ex) + ErrorHighlight.spot(ex, backtrace_location: self) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/time.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/time.rb new file mode 100644 index 00000000..c809def0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/time.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "active_support/core_ext/time/acts_like" +require "active_support/core_ext/time/calculations" +require "active_support/core_ext/time/compatibility" +require "active_support/core_ext/time/conversions" +require "active_support/core_ext/time/zones" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/time/acts_like.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/time/acts_like.rb new file mode 100644 index 00000000..8572b496 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/time/acts_like.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/acts_like" + +class Time + # Duck-types as a Time-like class. See Object#acts_like?. + def acts_like_time? + true + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/time/calculations.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/time/calculations.rb new file mode 100644 index 00000000..471a56c9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/time/calculations.rb @@ -0,0 +1,386 @@ +# frozen_string_literal: true + +require "active_support/duration" +require "active_support/core_ext/time/conversions" +require "active_support/time_with_zone" +require "active_support/core_ext/time/zones" +require "active_support/core_ext/date_and_time/calculations" +require "active_support/core_ext/date/calculations" +require "active_support/core_ext/module/remove_method" + +class Time + include DateAndTime::Calculations + + COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + + class << self + # Overriding case equality method so that it returns true for ActiveSupport::TimeWithZone instances + def ===(other) + super || (self == Time && other.is_a?(ActiveSupport::TimeWithZone)) + end + + # Returns the number of days in the given month. + # If no year is specified, it will use the current year. + def days_in_month(month, year = current.year) + if month == 2 && ::Date.gregorian_leap?(year) + 29 + else + COMMON_YEAR_DAYS_IN_MONTH[month] + end + end + + # Returns the number of days in the given year. + # If no year is specified, it will use the current year. + def days_in_year(year = current.year) + days_in_month(2, year) + 337 + end + + # Returns Time.zone.now when Time.zone or config.time_zone are set, otherwise just returns Time.now. + def current + ::Time.zone ? ::Time.zone.now : ::Time.now + end + + # Layers additional behavior on Time.at so that ActiveSupport::TimeWithZone and DateTime + # instances can be used when called with a single argument + def at_with_coercion(time_or_number, *args) + if args.empty? + if time_or_number.is_a?(ActiveSupport::TimeWithZone) + at_without_coercion(time_or_number.to_r).getlocal + elsif time_or_number.is_a?(DateTime) + at_without_coercion(time_or_number.to_f).getlocal + else + at_without_coercion(time_or_number) + end + else + at_without_coercion(time_or_number, *args) + end + end + ruby2_keywords :at_with_coercion + alias_method :at_without_coercion, :at + alias_method :at, :at_with_coercion + + # Creates a +Time+ instance from an RFC 3339 string. + # + # Time.rfc3339('1999-12-31T14:00:00-10:00') # => 2000-01-01 00:00:00 -1000 + # + # If the time or offset components are missing then an +ArgumentError+ will be raised. + # + # Time.rfc3339('1999-12-31') # => ArgumentError: invalid date + def rfc3339(str) + parts = Date._rfc3339(str) + + raise ArgumentError, "invalid date" if parts.empty? + + Time.new( + parts.fetch(:year), + parts.fetch(:mon), + parts.fetch(:mday), + parts.fetch(:hour), + parts.fetch(:min), + parts.fetch(:sec) + parts.fetch(:sec_fraction, 0), + parts.fetch(:offset) + ) + end + end + + # Returns the number of seconds since 00:00:00. + # + # Time.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0.0 + # Time.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296.0 + # Time.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399.0 + def seconds_since_midnight + to_i - change(hour: 0).to_i + (usec / 1.0e+6) + end + + # Returns the number of seconds until 23:59:59. + # + # Time.new(2012, 8, 29, 0, 0, 0).seconds_until_end_of_day # => 86399 + # Time.new(2012, 8, 29, 12, 34, 56).seconds_until_end_of_day # => 41103 + # Time.new(2012, 8, 29, 23, 59, 59).seconds_until_end_of_day # => 0 + def seconds_until_end_of_day + end_of_day.to_i - to_i + end + + # Returns the fraction of a second as a +Rational+ + # + # Time.new(2012, 8, 29, 0, 0, 0.5).sec_fraction # => (1/2) + def sec_fraction + subsec + end + + # Returns a new Time where one or more of the elements have been changed according + # to the +options+ parameter. The time options (:hour, :min, + # :sec, :usec, :nsec) reset cascadingly, so if only + # the hour is passed, then minute, sec, usec, and nsec is set to 0. If the hour + # and minute is passed, then sec, usec, and nsec is set to 0. The +options+ parameter + # takes a hash with any of these keys: :year, :month, :day, + # :hour, :min, :sec, :usec, :nsec, + # :offset. Pass either :usec or :nsec, not both. + # + # Time.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => Time.new(2012, 8, 1, 22, 35, 0) + # Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => Time.new(1981, 8, 1, 22, 35, 0) + # Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => Time.new(1981, 8, 29, 0, 0, 0) + def change(options) + new_year = options.fetch(:year, year) + new_month = options.fetch(:month, month) + new_day = options.fetch(:day, day) + new_hour = options.fetch(:hour, hour) + new_min = options.fetch(:min, options[:hour] ? 0 : min) + new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec) + new_offset = options.fetch(:offset, nil) + + if new_nsec = options[:nsec] + raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec] + new_usec = Rational(new_nsec, 1000) + else + new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000)) + end + + raise ArgumentError, "argument out of range" if new_usec >= 1000000 + + new_sec += Rational(new_usec, 1000000) + + if new_offset + ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, new_offset) + elsif utc? + ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec) + elsif zone.respond_to?(:utc_to_local) + new_time = ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, zone) + + # Some versions of Ruby have a bug where Time.new with a zone object and + # fractional seconds will end up with a broken utc_offset. + # This is fixed in Ruby 3.3.1 and 3.2.4 + unless new_time.utc_offset.integer? + new_time += 0 + end + + # When there are two occurrences of a nominal time due to DST ending, + # `Time.new` chooses the first chronological occurrence (the one with a + # larger UTC offset). However, for `change`, we want to choose the + # occurrence that matches this time's UTC offset. + # + # If the new time's UTC offset is larger than this time's UTC offset, the + # new time might be a first chronological occurrence. So we add the offset + # difference to fast-forward the new time, and check if the result has the + # desired UTC offset (i.e. is the second chronological occurrence). + offset_difference = new_time.utc_offset - utc_offset + if offset_difference > 0 && (new_time_2 = new_time + offset_difference).utc_offset == utc_offset + new_time_2 + else + new_time + end + elsif zone + ::Time.local(new_sec, new_min, new_hour, new_day, new_month, new_year, nil, nil, isdst, nil) + else + ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, utc_offset) + end + end + + # Uses Date to provide precise Time calculations for years, months, and days + # according to the proleptic Gregorian calendar. The +options+ parameter + # takes a hash with any of these keys: :years, :months, + # :weeks, :days, :hours, :minutes, + # :seconds. + # + # Time.new(2015, 8, 1, 14, 35, 0).advance(seconds: 1) # => 2015-08-01 14:35:01 -0700 + # Time.new(2015, 8, 1, 14, 35, 0).advance(minutes: 1) # => 2015-08-01 14:36:00 -0700 + # Time.new(2015, 8, 1, 14, 35, 0).advance(hours: 1) # => 2015-08-01 15:35:00 -0700 + # Time.new(2015, 8, 1, 14, 35, 0).advance(days: 1) # => 2015-08-02 14:35:00 -0700 + # Time.new(2015, 8, 1, 14, 35, 0).advance(weeks: 1) # => 2015-08-08 14:35:00 -0700 + # + # Just like Date#advance, increments are applied in order of time units from + # largest to smallest. This order can affect the result around the end of a + # month. + def advance(options) + unless options[:weeks].nil? + options[:weeks], partial_weeks = options[:weeks].divmod(1) + options[:days] = options.fetch(:days, 0) + 7 * partial_weeks + end + + unless options[:days].nil? + options[:days], partial_days = options[:days].divmod(1) + options[:hours] = options.fetch(:hours, 0) + 24 * partial_days + end + + d = to_date.gregorian.advance(options) + time_advanced_by_date = change(year: d.year, month: d.month, day: d.day) + seconds_to_advance = \ + options.fetch(:seconds, 0) + + options.fetch(:minutes, 0) * 60 + + options.fetch(:hours, 0) * 3600 + + if seconds_to_advance.zero? + time_advanced_by_date + else + time_advanced_by_date.since(seconds_to_advance) + end + end + + # Returns a new Time representing the time a number of seconds ago, this is basically a wrapper around the Numeric extension + def ago(seconds) + since(-seconds) + end + + # Returns a new Time representing the time a number of seconds since the instance time + def since(seconds) + self + seconds + rescue TypeError + result = to_datetime.since(seconds) + ActiveSupport.deprecator.warn( + "Passing an instance of #{seconds.class} to #{self.class}#since is deprecated. This behavior will raise " \ + "a `TypeError` in Rails 8.1." + ) + result + end + alias :in :since + + # Returns a new Time representing the start of the day (0:00) + def beginning_of_day + change(hour: 0) + end + alias :midnight :beginning_of_day + alias :at_midnight :beginning_of_day + alias :at_beginning_of_day :beginning_of_day + + # Returns a new Time representing the middle of the day (12:00) + def middle_of_day + change(hour: 12) + end + alias :midday :middle_of_day + alias :noon :middle_of_day + alias :at_midday :middle_of_day + alias :at_noon :middle_of_day + alias :at_middle_of_day :middle_of_day + + # Returns a new Time representing the end of the day, 23:59:59.999999 + def end_of_day + change( + hour: 23, + min: 59, + sec: 59, + usec: Rational(999999999, 1000) + ) + end + alias :at_end_of_day :end_of_day + + # Returns a new Time representing the start of the hour (x:00) + def beginning_of_hour + change(min: 0) + end + alias :at_beginning_of_hour :beginning_of_hour + + # Returns a new Time representing the end of the hour, x:59:59.999999 + def end_of_hour + change( + min: 59, + sec: 59, + usec: Rational(999999999, 1000) + ) + end + alias :at_end_of_hour :end_of_hour + + # Returns a new Time representing the start of the minute (x:xx:00) + def beginning_of_minute + change(sec: 0) + end + alias :at_beginning_of_minute :beginning_of_minute + + # Returns a new Time representing the end of the minute, x:xx:59.999999 + def end_of_minute + change( + sec: 59, + usec: Rational(999999999, 1000) + ) + end + alias :at_end_of_minute :end_of_minute + + def plus_with_duration(other) # :nodoc: + if ActiveSupport::Duration === other + other.since(self) + else + plus_without_duration(other) + end + end + alias_method :plus_without_duration, :+ + alias_method :+, :plus_with_duration + + def minus_with_duration(other) # :nodoc: + if ActiveSupport::Duration === other + other.until(self) + else + minus_without_duration(other) + end + end + alias_method :minus_without_duration, :- + alias_method :-, :minus_with_duration + + # Time#- can also be used to determine the number of seconds between two Time instances. + # We're layering on additional behavior so that ActiveSupport::TimeWithZone instances + # are coerced into values that Time#- will recognize + def minus_with_coercion(other) + other = other.comparable_time if other.respond_to?(:comparable_time) + other.is_a?(DateTime) ? to_f - other.to_f : minus_without_coercion(other) + end + alias_method :minus_without_coercion, :- + alias_method :-, :minus_with_coercion # rubocop:disable Lint/DuplicateMethods + + # Layers additional behavior on Time#<=> so that DateTime and ActiveSupport::TimeWithZone instances + # can be chronologically compared with a Time + def compare_with_coercion(other) + # we're avoiding Time#to_datetime and Time#to_time because they're expensive + if other.class == Time + compare_without_coercion(other) + elsif other.is_a?(Time) + # also avoid ActiveSupport::TimeWithZone#to_time before Rails 8.0 + if other.respond_to?(:comparable_time) + compare_without_coercion(other.comparable_time) + else + compare_without_coercion(other.to_time) + end + else + to_datetime <=> other + end + end + alias_method :compare_without_coercion, :<=> + alias_method :<=>, :compare_with_coercion + + # Layers additional behavior on Time#eql? so that ActiveSupport::TimeWithZone instances + # can be eql? to an equivalent Time + def eql_with_coercion(other) + # if other is an ActiveSupport::TimeWithZone, coerce a Time instance from it so we can do eql? comparison + other = other.comparable_time if other.respond_to?(:comparable_time) + eql_without_coercion(other) + end + alias_method :eql_without_coercion, :eql? + alias_method :eql?, :eql_with_coercion + + # Returns a new time the specified number of days ago. + def prev_day(days = 1) + advance(days: -days) + end + + # Returns a new time the specified number of days in the future. + def next_day(days = 1) + advance(days: days) + end + + # Returns a new time the specified number of months ago. + def prev_month(months = 1) + advance(months: -months) + end + + # Returns a new time the specified number of months in the future. + def next_month(months = 1) + advance(months: months) + end + + # Returns a new time the specified number of years ago. + def prev_year(years = 1) + advance(years: -years) + end + + # Returns a new time the specified number of years in the future. + def next_year(years = 1) + advance(years: years) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/time/compatibility.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/time/compatibility.rb new file mode 100644 index 00000000..4e6c8ca3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/time/compatibility.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "active_support/core_ext/date_and_time/compatibility" +require "active_support/core_ext/module/redefine_method" + +class Time + include DateAndTime::Compatibility + + silence_redefinition_of_method :to_time + + # Either return +self+ or the time in the local system timezone depending + # on the setting of +ActiveSupport.to_time_preserves_timezone+. + def to_time + preserve_timezone ? self : getlocal + end + + def preserve_timezone # :nodoc: + system_local_time? || super + end + + private + def system_local_time? + if ::Time.equal?(self.class) + zone = self.zone + String === zone && + (zone != "UTC" || active_support_local_zone == "UTC") + end + end + + @@active_support_local_tz = nil + + def active_support_local_zone + @@active_support_local_zone = nil if @@active_support_local_tz != ENV["TZ"] + @@active_support_local_zone ||= + begin + @@active_support_local_tz = ENV["TZ"] + Time.new.zone + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/time/conversions.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/time/conversions.rb new file mode 100644 index 00000000..8913f3f0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/time/conversions.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require "time" +require "active_support/inflector/methods" +require "active_support/values/time_zone" + +class Time + DATE_FORMATS = { + db: "%Y-%m-%d %H:%M:%S", + inspect: "%Y-%m-%d %H:%M:%S.%9N %z", + number: "%Y%m%d%H%M%S", + nsec: "%Y%m%d%H%M%S%9N", + usec: "%Y%m%d%H%M%S%6N", + time: "%H:%M", + short: "%d %b %H:%M", + long: "%B %d, %Y %H:%M", + long_ordinal: lambda { |time| + day_format = ActiveSupport::Inflector.ordinalize(time.day) + time.strftime("%B #{day_format}, %Y %H:%M") + }, + rfc822: lambda { |time| + offset_format = time.formatted_offset(false) + time.strftime("%a, %d %b %Y %H:%M:%S #{offset_format}") + }, + rfc2822: lambda { |time| time.rfc2822 }, + iso8601: lambda { |time| time.iso8601 } + } + + # Converts to a formatted string. See DATE_FORMATS for built-in formats. + # + # This method is aliased to to_formatted_s. + # + # time = Time.now # => 2007-01-18 06:10:17 -06:00 + # + # time.to_fs(:time) # => "06:10" + # time.to_formatted_s(:time) # => "06:10" + # + # time.to_fs(:db) # => "2007-01-18 06:10:17" + # time.to_fs(:number) # => "20070118061017" + # time.to_fs(:short) # => "18 Jan 06:10" + # time.to_fs(:long) # => "January 18, 2007 06:10" + # time.to_fs(:long_ordinal) # => "January 18th, 2007 06:10" + # time.to_fs(:rfc822) # => "Thu, 18 Jan 2007 06:10:17 -0600" + # time.to_fs(:rfc2822) # => "Thu, 18 Jan 2007 06:10:17 -0600" + # time.to_fs(:iso8601) # => "2007-01-18T06:10:17-06:00" + # + # == Adding your own time formats to +to_fs+ + # You can add your own formats to the Time::DATE_FORMATS hash. + # Use the format name as the hash key and either a strftime string + # or Proc instance that takes a time argument as the value. + # + # # config/initializers/time_formats.rb + # Time::DATE_FORMATS[:month_and_year] = '%B %Y' + # Time::DATE_FORMATS[:short_ordinal] = ->(time) { time.strftime("%B #{time.day.ordinalize}") } + def to_fs(format = :default) + if formatter = DATE_FORMATS[format] + formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) + else + to_s + end + end + alias_method :to_formatted_s, :to_fs + + # Returns a formatted string of the offset from UTC, or an alternative + # string if the time zone is already UTC. + # + # Time.local(2000).formatted_offset # => "-06:00" + # Time.local(2000).formatted_offset(false) # => "-0600" + def formatted_offset(colon = true, alternate_utc_string = nil) + utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon) + end + + # Aliased to +xmlschema+ for compatibility with +DateTime+ + alias_method :rfc3339, :xmlschema +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/time/zones.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/time/zones.rb new file mode 100644 index 00000000..5f0d3c7f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/core_ext/time/zones.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require "active_support/time_with_zone" +require "active_support/core_ext/time/acts_like" +require "active_support/core_ext/date_and_time/zones" + +class Time + include DateAndTime::Zones + class << self + attr_accessor :zone_default + + # Returns the TimeZone for the current request, if this has been set (via Time.zone=). + # If Time.zone has not been set for the current request, returns the TimeZone specified in config.time_zone. + def zone + ::ActiveSupport::IsolatedExecutionState[:time_zone] || zone_default + end + + # Sets Time.zone to a TimeZone object for the current request/thread. + # + # This method accepts any of the following: + # + # * A \Rails TimeZone object. + # * An identifier for a \Rails TimeZone object (e.g., "Eastern \Time (US & Canada)", -5.hours). + # * A +TZInfo::Timezone+ object. + # * An identifier for a +TZInfo::Timezone+ object (e.g., "America/New_York"). + # + # Here's an example of how you might set Time.zone on a per request basis and reset it when the request is done. + # current_user.time_zone just needs to return a string identifying the user's preferred time zone: + # + # class ApplicationController < ActionController::Base + # around_action :set_time_zone + # + # def set_time_zone + # if logged_in? + # Time.use_zone(current_user.time_zone) { yield } + # else + # yield + # end + # end + # end + def zone=(time_zone) + ::ActiveSupport::IsolatedExecutionState[:time_zone] = find_zone!(time_zone) + end + + # Allows override of Time.zone locally inside supplied block; + # resets Time.zone to existing value when done. + # + # class ApplicationController < ActionController::Base + # around_action :set_time_zone + # + # private + # def set_time_zone + # Time.use_zone(current_user.timezone) { yield } + # end + # end + # + # NOTE: This won't affect any ActiveSupport::TimeWithZone + # objects that have already been created, e.g. any model timestamp + # attributes that have been read before the block will remain in + # the application's default timezone. + def use_zone(time_zone) + new_zone = find_zone!(time_zone) + begin + old_zone, ::Time.zone = ::Time.zone, new_zone + yield + ensure + ::Time.zone = old_zone + end + end + + # Returns a TimeZone instance matching the time zone provided. + # Accepts the time zone in any format supported by Time.zone=. + # Raises an +ArgumentError+ for invalid time zones. + # + # Time.find_zone! "America/New_York" # => # + # Time.find_zone! "EST" # => # + # Time.find_zone! -5.hours # => # + # Time.find_zone! nil # => nil + # Time.find_zone! false # => false + # Time.find_zone! "NOT-A-TIMEZONE" # => ArgumentError: Invalid Timezone: NOT-A-TIMEZONE + def find_zone!(time_zone) + return time_zone unless time_zone + + ActiveSupport::TimeZone[time_zone] || raise(ArgumentError, "Invalid Timezone: #{time_zone}") + end + + # Returns a TimeZone instance matching the time zone provided. + # Accepts the time zone in any format supported by Time.zone=. + # Returns +nil+ for invalid time zones. + # + # Time.find_zone "America/New_York" # => # + # Time.find_zone "NOT-A-TIMEZONE" # => nil + def find_zone(time_zone) + find_zone!(time_zone) rescue nil + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/current_attributes.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/current_attributes.rb new file mode 100644 index 00000000..43a503ea --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/current_attributes.rb @@ -0,0 +1,233 @@ +# frozen_string_literal: true + +require "active_support/callbacks" +require "active_support/core_ext/object/with" +require "active_support/core_ext/enumerable" +require "active_support/core_ext/module/delegation" + +module ActiveSupport + # = Current Attributes + # + # Abstract super class that provides a thread-isolated attributes singleton, which resets automatically + # before and after each request. This allows you to keep all the per-request attributes easily + # available to the whole system. + # + # The following full app-like example demonstrates how to use a Current class to + # facilitate easy access to the global, per-request attributes without passing them deeply + # around everywhere: + # + # # app/models/current.rb + # class Current < ActiveSupport::CurrentAttributes + # attribute :account, :user + # attribute :request_id, :user_agent, :ip_address + # + # resets { Time.zone = nil } + # + # def user=(user) + # super + # self.account = user.account + # Time.zone = user.time_zone + # end + # end + # + # # app/controllers/concerns/authentication.rb + # module Authentication + # extend ActiveSupport::Concern + # + # included do + # before_action :authenticate + # end + # + # private + # def authenticate + # if authenticated_user = User.find_by(id: cookies.encrypted[:user_id]) + # Current.user = authenticated_user + # else + # redirect_to new_session_url + # end + # end + # end + # + # # app/controllers/concerns/set_current_request_details.rb + # module SetCurrentRequestDetails + # extend ActiveSupport::Concern + # + # included do + # before_action do + # Current.request_id = request.uuid + # Current.user_agent = request.user_agent + # Current.ip_address = request.ip + # end + # end + # end + # + # class ApplicationController < ActionController::Base + # include Authentication + # include SetCurrentRequestDetails + # end + # + # class MessagesController < ApplicationController + # def create + # Current.account.messages.create(message_params) + # end + # end + # + # class Message < ApplicationRecord + # belongs_to :creator, default: -> { Current.user } + # after_create { |message| Event.create(record: message) } + # end + # + # class Event < ApplicationRecord + # before_create do + # self.request_id = Current.request_id + # self.user_agent = Current.user_agent + # self.ip_address = Current.ip_address + # end + # end + # + # A word of caution: It's easy to overdo a global singleton like Current and tangle your model as a result. + # Current should only be used for a few, top-level globals, like account, user, and request details. + # The attributes stuck in Current should be used by more or less all actions on all requests. If you start + # sticking controller-specific attributes in there, you're going to create a mess. + class CurrentAttributes + include ActiveSupport::Callbacks + define_callbacks :reset + + INVALID_ATTRIBUTE_NAMES = [:set, :reset, :resets, :instance, :before_reset, :after_reset, :reset_all, :clear_all] # :nodoc: + + NOT_SET = Object.new.freeze # :nodoc: + + class << self + # Returns singleton instance for this class in this thread. If none exists, one is created. + def instance + current_instances[current_instances_key] ||= new + end + + # Declares one or more attributes that will be given both class and instance accessor methods. + # + # ==== Options + # + # * :default - The default value for the attributes. If the value + # is a proc or lambda, it will be called whenever an instance is + # constructed. Otherwise, the value will be duplicated with +#dup+. + # Default values are re-assigned when the attributes are reset. + def attribute(*names, default: NOT_SET) + invalid_attribute_names = names.map(&:to_sym) & INVALID_ATTRIBUTE_NAMES + if invalid_attribute_names.any? + raise ArgumentError, "Restricted attribute names: #{invalid_attribute_names.join(", ")}" + end + + ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner| + names.each do |name| + owner.define_cached_method(name, namespace: :current_attributes) do |batch| + batch << + "def #{name}" << + "attributes[:#{name}]" << + "end" + end + owner.define_cached_method("#{name}=", namespace: :current_attributes) do |batch| + batch << + "def #{name}=(value)" << + "attributes[:#{name}] = value" << + "end" + end + end + end + + Delegation.generate(singleton_class, names, to: :instance, nilable: false, signature: "") + Delegation.generate(singleton_class, names.map { |n| "#{n}=" }, to: :instance, nilable: false, signature: "value") + + self.defaults = defaults.merge(names.index_with { default }) + end + + # Calls this callback before #reset is called on the instance. Used for resetting external collaborators that depend on current values. + def before_reset(*methods, &block) + set_callback :reset, :before, *methods, &block + end + + # Calls this callback after #reset is called on the instance. Used for resetting external collaborators, like Time.zone. + def resets(*methods, &block) + set_callback :reset, :after, *methods, &block + end + alias_method :after_reset, :resets + + delegate :set, :reset, to: :instance + + def reset_all # :nodoc: + current_instances.each_value(&:reset) + end + + def clear_all # :nodoc: + reset_all + current_instances.clear + end + + private + def generated_attribute_methods + @generated_attribute_methods ||= Module.new.tap { |mod| include mod } + end + + def current_instances + IsolatedExecutionState[:current_attributes_instances] ||= {} + end + + def current_instances_key + @current_instances_key ||= name.to_sym + end + + def method_missing(name, ...) + instance.public_send(name, ...) + end + + def respond_to_missing?(name, _) + instance.respond_to?(name) || super + end + + def method_added(name) + super + return if name == :initialize + return unless public_method_defined?(name) + return if singleton_class.method_defined?(name) || singleton_class.private_method_defined?(name) + Delegation.generate(singleton_class, [name], to: :instance, as: self, nilable: false) + end + end + + class_attribute :defaults, instance_writer: false, default: {}.freeze + + attr_accessor :attributes + + def initialize + @attributes = resolve_defaults + end + + # Expose one or more attributes within a block. Old values are returned after the block concludes. + # Example demonstrating the common use of needing to set Current attributes outside the request-cycle: + # + # class Chat::PublicationJob < ApplicationJob + # def perform(attributes, room_number, creator) + # Current.set(person: creator) do + # Chat::Publisher.publish(attributes: attributes, room_number: room_number) + # end + # end + # end + def set(attributes, &block) + with(**attributes, &block) + end + + # Reset all attributes. Should be called before and after actions, when used as a per-request singleton. + def reset + run_callbacks :reset do + self.attributes = resolve_defaults + end + end + + private + def resolve_defaults + defaults.each_with_object({}) do |(key, value), result| + if value != NOT_SET + result[key] = Proc === value ? value.call : value.dup + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/current_attributes/test_helper.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/current_attributes/test_helper.rb new file mode 100644 index 00000000..2016384a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/current_attributes/test_helper.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module ActiveSupport::CurrentAttributes::TestHelper # :nodoc: + def before_setup + ActiveSupport::CurrentAttributes.reset_all + super + end + + def after_teardown + super + ActiveSupport::CurrentAttributes.reset_all + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deep_mergeable.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deep_mergeable.rb new file mode 100644 index 00000000..c00a240b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deep_mergeable.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module ActiveSupport + # Provides +deep_merge+ and +deep_merge!+ methods. Expects the including class + # to provide a merge!(other, &block) method. + module DeepMergeable # :nodoc: + # Returns a new instance with the values from +other+ merged recursively. + # + # class Hash + # include ActiveSupport::DeepMergeable + # end + # + # hash_1 = { a: true, b: { c: [1, 2, 3] } } + # hash_2 = { a: false, b: { x: [3, 4, 5] } } + # + # hash_1.deep_merge(hash_2) + # # => { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } } + # + # A block can be provided to merge non-DeepMergeable values: + # + # hash_1 = { a: 100, b: 200, c: { c1: 100 } } + # hash_2 = { b: 250, c: { c1: 200 } } + # + # hash_1.deep_merge(hash_2) do |key, this_val, other_val| + # this_val + other_val + # end + # # => { a: 100, b: 450, c: { c1: 300 } } + # + def deep_merge(other, &block) + dup.deep_merge!(other, &block) + end + + # Same as #deep_merge, but modifies +self+. + def deep_merge!(other, &block) + merge!(other) do |key, this_val, other_val| + if this_val.is_a?(DeepMergeable) && this_val.deep_merge?(other_val) + this_val.deep_merge(other_val, &block) + elsif block_given? + block.call(key, this_val, other_val) + else + other_val + end + end + end + + # Returns true if +other+ can be deep merged into +self+. Classes may + # override this method to restrict or expand the domain of deep mergeable + # values. Defaults to checking that +other+ is of type +self.class+. + def deep_merge?(other) + other.is_a?(self.class) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/delegation.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/delegation.rb new file mode 100644 index 00000000..261cc0d4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/delegation.rb @@ -0,0 +1,200 @@ +# frozen_string_literal: true + +module ActiveSupport + # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+ + # option is not used. + class DelegationError < NoMethodError + class << self + def nil_target(method_name, target) # :nodoc: + new("#{method_name} delegated to #{target}, but #{target} is nil") + end + end + end + + module Delegation # :nodoc: + RUBY_RESERVED_KEYWORDS = %w(__ENCODING__ __LINE__ __FILE__ alias and BEGIN begin break + case class def defined? do else elsif END end ensure false for if in module next nil + not or redo rescue retry return self super then true undef unless until when while yield) + RESERVED_METHOD_NAMES = (RUBY_RESERVED_KEYWORDS + %w(_ arg args block)).to_set.freeze + + class << self + def generate(owner, methods, location: nil, to: nil, prefix: nil, allow_nil: nil, nilable: true, private: nil, as: nil, signature: nil) + unless to + raise ArgumentError, "Delegation needs a target. Supply a keyword argument 'to' (e.g. delegate :hello, to: :greeter)." + end + + if prefix == true && /^[^a-z_]/.match?(to) + raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method." + end + + method_prefix = \ + if prefix + "#{prefix == true ? to : prefix}_" + else + "" + end + + location ||= caller_locations(1, 1).first + file, line = location.path, location.lineno + + receiver = if to.is_a?(Module) + if to.name.nil? + raise ArgumentError, "Can't delegate to anonymous class or module: #{to}" + end + + unless Inflector.safe_constantize(to.name).equal?(to) + raise ArgumentError, "Can't delegate to detached class or module: #{to.name}" + end + + "::#{to.name}" + else + to.to_s + end + receiver = "self.#{receiver}" if RESERVED_METHOD_NAMES.include?(receiver) + + explicit_receiver = false + receiver_class = if as + explicit_receiver = true + as + elsif to.is_a?(Module) + to.singleton_class + elsif receiver == "self.class" + nilable = false # self.class can't possibly be nil + owner.singleton_class + end + + method_def = [] + method_names = [] + + method_def << "self.private" if private + + methods.each do |method| + method_name = prefix ? "#{method_prefix}#{method}" : method + method_names << method_name.to_sym + + # Attribute writer methods only accept one argument. Makes sure []= + # methods still accept two arguments. + definition = \ + if signature + signature + elsif /[^\]]=\z/.match?(method) + "arg" + else + method_object = if receiver_class + begin + receiver_class.public_instance_method(method) + rescue NameError + raise if explicit_receiver + # Do nothing. Fall back to `"..."` + end + end + + if method_object + parameters = method_object.parameters + + if parameters.map(&:first).intersect?([:opt, :rest, :keyreq, :key, :keyrest]) + "..." + else + defn = parameters.filter_map { |type, arg| arg if type == :req } + defn << "&" + defn.join(", ") + end + else + "..." + end + end + + # The following generated method calls the target exactly once, storing + # the returned value in a dummy variable. + # + # Reason is twofold: On one hand doing less calls is in general better. + # On the other hand it could be that the target has side-effects, + # whereas conceptually, from the user point of view, the delegator should + # be doing one call. + if nilable == false + method_def << + "def #{method_name}(#{definition})" << + " (#{receiver}).#{method}(#{definition})" << + "end" + elsif allow_nil + method = method.to_s + + method_def << + "def #{method_name}(#{definition})" << + " _ = #{receiver}" << + " if !_.nil? || nil.respond_to?(:#{method})" << + " _.#{method}(#{definition})" << + " end" << + "end" + else + method = method.to_s + method_name = method_name.to_s + + method_def << + "def #{method_name}(#{definition})" << + " _ = #{receiver}" << + " _.#{method}(#{definition})" << + "rescue NoMethodError => e" << + " if _.nil? && e.name == :#{method}" << + " raise ::ActiveSupport::DelegationError.nil_target(:#{method_name}, :'#{receiver}')" << + " else" << + " raise" << + " end" << + "end" + end + end + owner.module_eval(method_def.join(";"), file, line) + method_names + end + + def generate_method_missing(owner, target, allow_nil: nil) + target = target.to_s + target = "self.#{target}" if RESERVED_METHOD_NAMES.include?(target) || target == "__target" + + if allow_nil + owner.module_eval <<~RUBY, __FILE__, __LINE__ + 1 + def respond_to_missing?(name, include_private = false) + # It may look like an oversight, but we deliberately do not pass + # +include_private+, because they do not get delegated. + + return false if name == :marshal_dump || name == :_dump + #{target}.respond_to?(name) || super + end + + def method_missing(method, ...) + __target = #{target} + if __target.nil? && !nil.respond_to?(method) + nil + elsif __target.respond_to?(method) + __target.public_send(method, ...) + else + super + end + end + RUBY + else + owner.module_eval <<~RUBY, __FILE__, __LINE__ + 1 + def respond_to_missing?(name, include_private = false) + # It may look like an oversight, but we deliberately do not pass + # +include_private+, because they do not get delegated. + + return false if name == :marshal_dump || name == :_dump + #{target}.respond_to?(name) || super + end + + def method_missing(method, ...) + __target = #{target} + if __target.nil? && !nil.respond_to?(method) + raise ::ActiveSupport::DelegationError.nil_target(method, :'#{target}') + elsif __target.respond_to?(method) + __target.public_send(method, ...) + else + super + end + end + RUBY + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/dependencies.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/dependencies.rb new file mode 100644 index 00000000..aed69233 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/dependencies.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require "active_support/dependencies/interlock" + +module ActiveSupport # :nodoc: + module Dependencies # :nodoc: + require_relative "dependencies/require_dependency" + + singleton_class.attr_accessor :interlock + @interlock = Interlock.new + + # :doc: + + # Execute the supplied block without interference from any + # concurrent loads. + def self.run_interlock(&block) + interlock.running(&block) + end + + # Execute the supplied block while holding an exclusive lock, + # preventing any other thread from being inside a #run_interlock + # block at the same time. + def self.load_interlock(&block) + interlock.loading(&block) + end + + # Execute the supplied block while holding an exclusive lock, + # preventing any other thread from being inside a #run_interlock + # block at the same time. + def self.unload_interlock(&block) + interlock.unloading(&block) + end + + # :nodoc: + + # The array of directories from which we autoload and reload, if reloading + # is enabled. The public interface to push directories to this collection + # from applications or engines is config.autoload_paths. + # + # This collection is allowed to have intersection with autoload_once_paths. + # Common directories are not reloaded. + singleton_class.attr_accessor :autoload_paths + self.autoload_paths = [] + + # The array of directories from which we autoload and never reload, even if + # reloading is enabled. The public interface to push directories to this + # collection from applications or engines is config.autoload_once_paths. + singleton_class.attr_accessor :autoload_once_paths + self.autoload_once_paths = [] + + # This is a private set that collects all eager load paths during bootstrap. + # Useful for Zeitwerk integration. The public interface to push custom + # directories to this collection from applications or engines is + # config.eager_load_paths. + singleton_class.attr_accessor :_eager_load_paths + self._eager_load_paths = Set.new + + # If reloading is enabled, this private set holds autoloaded classes tracked + # by the descendants tracker. It is populated by an on_load callback in the + # main autoloader. Used to clear state. + singleton_class.attr_accessor :_autoloaded_tracked_classes + self._autoloaded_tracked_classes = Set.new + + # If reloading is enabled, this private attribute stores the main autoloader + # of a Rails application. It is `nil` otherwise. + # + # The public interface for this autoloader is `Rails.autoloaders.main`. + singleton_class.attr_accessor :autoloader + + # Private method that reloads constants autoloaded by the main autoloader. + # + # Rails.application.reloader.reload! is the public interface for application + # reload. That involves more things, like deleting unloaded classes from the + # internal state of the descendants tracker, or reloading routes. + def self.clear + unload_interlock do + _autoloaded_tracked_classes.clear + autoloader.reload + end + end + + # Private method used by require_dependency. + def self.search_for_file(relpath) + relpath += ".rb" unless relpath.end_with?(".rb") + autoload_paths.each do |autoload_path| + abspath = File.join(autoload_path, relpath) + return abspath if File.file?(abspath) + end + nil + end + + # Private method that helps configuring the autoloaders. + def self.eager_load?(path) + _eager_load_paths.member?(path) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/dependencies/autoload.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/dependencies/autoload.rb new file mode 100644 index 00000000..747f1fe4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/dependencies/autoload.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require "active_support/inflector/methods" + +module ActiveSupport + # = Active Support \Autoload + # + # Autoload and eager load conveniences for your library. + # + # This module allows you to define autoloads based on + # \Rails conventions (i.e. no need to define the path + # it is automatically guessed based on the filename) + # and also define a set of constants that needs to be + # eager loaded: + # + # module MyLib + # extend ActiveSupport::Autoload + # + # autoload :Model + # + # eager_autoload do + # autoload :Cache + # end + # end + # + # Then your library can be eager loaded by simply calling: + # + # MyLib.eager_load! + module Autoload + def autoload(const_name, path = @_at_path) + unless path + full = [name, @_under_path, const_name.to_s].compact.join("::") + path = Inflector.underscore(full) + end + + if @_eager_autoload + @_eagerloaded_constants ||= [] + @_eagerloaded_constants << const_name + end + + super const_name, path + end + + def autoload_under(path) + @_under_path, old_path = path, @_under_path + yield + ensure + @_under_path = old_path + end + + def autoload_at(path) + @_at_path, old_path = path, @_at_path + yield + ensure + @_at_path = old_path + end + + def eager_autoload + old_eager, @_eager_autoload = @_eager_autoload, true + yield + ensure + @_eager_autoload = old_eager + end + + def eager_load! + if @_eagerloaded_constants + @_eagerloaded_constants.each { |const_name| const_get(const_name) } + @_eagerloaded_constants = nil + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/dependencies/interlock.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/dependencies/interlock.rb new file mode 100644 index 00000000..e0e32e82 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/dependencies/interlock.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "active_support/concurrency/share_lock" + +module ActiveSupport # :nodoc: + module Dependencies # :nodoc: + class Interlock + def initialize # :nodoc: + @lock = ActiveSupport::Concurrency::ShareLock.new + end + + def loading(&block) + @lock.exclusive(purpose: :load, compatible: [:load], after_compatible: [:load], &block) + end + + def unloading(&block) + @lock.exclusive(purpose: :unload, compatible: [:load, :unload], after_compatible: [:load, :unload], &block) + end + + def start_unloading + @lock.start_exclusive(purpose: :unload, compatible: [:load, :unload]) + end + + def done_unloading + @lock.stop_exclusive(compatible: [:load, :unload]) + end + + def start_running + @lock.start_sharing + end + + def done_running + @lock.stop_sharing + end + + def running(&block) + @lock.sharing(&block) + end + + def permit_concurrent_loads(&block) + @lock.yield_shares(compatible: [:load], &block) + end + + def raw_state(&block) # :nodoc: + @lock.raw_state(&block) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/dependencies/require_dependency.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/dependencies/require_dependency.rb new file mode 100644 index 00000000..403f5fa4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/dependencies/require_dependency.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module ActiveSupport::Dependencies::RequireDependency + # Warning: This method is obsolete. The semantics of the autoloader + # match Ruby's and you do not need to be defensive with load order anymore. + # Just refer to classes and modules normally. + # + # Engines that do not control the mode in which their parent application runs + # should call +require_dependency+ where needed in case the runtime mode is + # +:classic+. + def require_dependency(filename) + filename = filename.to_path if filename.respond_to?(:to_path) + + unless filename.is_a?(String) + raise ArgumentError, "the file name must be either a String or implement #to_path -- you passed #{filename.inspect}" + end + + if abspath = ActiveSupport::Dependencies.search_for_file(filename) + require abspath + else + require filename + end + end + + # We could define require_dependency in Object directly, but a module makes + # the extension apparent if you list ancestors. + Object.prepend(self) +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation.rb new file mode 100644 index 00000000..1358d915 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +module ActiveSupport + # = Active Support \Deprecation + # + # \Deprecation specifies the API used by \Rails to deprecate methods, instance variables, objects, and constants. It's + # also available for gems or applications. + # + # For a gem, use Deprecation.new to create a Deprecation object and store it in your module or class (in order for + # users to be able to configure it). + # + # module MyLibrary + # def self.deprecator + # @deprecator ||= ActiveSupport::Deprecation.new("2.0", "MyLibrary") + # end + # end + # + # For a Railtie or Engine, you may also want to add it to the application's deprecators, so that the application's + # configuration can be applied to it. + # + # module MyLibrary + # class Railtie < Rails::Railtie + # initializer "my_library.deprecator" do |app| + # app.deprecators[:my_library] = MyLibrary.deprecator + # end + # end + # end + # + # With the above initializer, configuration settings like the following will affect +MyLibrary.deprecator+: + # + # # in config/environments/test.rb + # config.active_support.deprecation = :raise + class Deprecation + # active_support.rb sets an autoload for ActiveSupport::Deprecation. + # + # If these requires were at the top of the file the constant would not be + # defined by the time their files were loaded. Since some of them reopen + # ActiveSupport::Deprecation its autoload would be triggered, resulting in + # a circular require warning for active_support/deprecation.rb. + # + # So, we define the constant first, and load dependencies later. + require "active_support/deprecation/behaviors" + require "active_support/deprecation/reporting" + require "active_support/deprecation/disallowed" + require "active_support/deprecation/constant_accessor" + require "active_support/deprecation/method_wrappers" + require "active_support/deprecation/proxy_wrappers" + require "active_support/deprecation/deprecators" + require "active_support/core_ext/module/deprecation" + require "concurrent/atomic/thread_local_var" + + include Behavior + include Reporting + include Disallowed + include MethodWrapper + + MUTEX = Mutex.new # :nodoc: + private_constant :MUTEX + + def self._instance # :nodoc: + @_instance ||= MUTEX.synchronize { @_instance ||= new } + end + + # The version number in which the deprecated behavior will be removed, by default. + attr_accessor :deprecation_horizon + + # It accepts two parameters on initialization. The first is a version of library + # and the second is a library name. + # + # ActiveSupport::Deprecation.new('2.0', 'MyLibrary') + def initialize(deprecation_horizon = "8.1", gem_name = "Rails") + self.gem_name = gem_name + self.deprecation_horizon = deprecation_horizon + # By default, warnings are not silenced and debugging is off. + self.silenced = false + self.debug = false + @silence_counter = Concurrent::ThreadLocalVar.new(0) + @explicitly_allowed_warnings = Concurrent::ThreadLocalVar.new(nil) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/behaviors.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/behaviors.rb new file mode 100644 index 00000000..2473263f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/behaviors.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true + +require "active_support/notifications" + +module ActiveSupport + # Raised when ActiveSupport::Deprecation::Behavior#behavior is set with :raise. + # You would set :raise, as a behavior to raise errors and proactively report exceptions from deprecations. + class DeprecationException < StandardError + end + + class Deprecation + # Default warning behaviors per Rails.env. + DEFAULT_BEHAVIORS = { + raise: ->(message, callstack, deprecator) do + e = DeprecationException.new(message) + e.set_backtrace(callstack.map(&:to_s)) + raise e + end, + + stderr: ->(message, callstack, deprecator) do + $stderr.puts(message) + $stderr.puts callstack.join("\n ") if deprecator.debug + end, + + log: ->(message, callstack, deprecator) do + logger = + if defined?(Rails.logger) && Rails.logger + Rails.logger + else + require "active_support/logger" + ActiveSupport::Logger.new($stderr) + end + logger.warn message + logger.debug callstack.join("\n ") if deprecator.debug + end, + + notify: ->(message, callstack, deprecator) do + ActiveSupport::Notifications.instrument( + "deprecation.#{deprecator.gem_name.underscore.tr("/", "_")}", + message: message, + callstack: callstack, + gem_name: deprecator.gem_name, + deprecation_horizon: deprecator.deprecation_horizon, + ) + end, + + silence: ->(message, callstack, deprecator) { }, + + report: ->(message, callstack, deprecator) do + error = DeprecationException.new(message) + error.set_backtrace(callstack.map(&:to_s)) + ActiveSupport.error_reporter.report(error) + end + } + + # Behavior module allows to determine how to display deprecation messages. + # You can create a custom behavior or set any from the +DEFAULT_BEHAVIORS+ + # constant. Available behaviors are: + # + # [+:raise+] Raise ActiveSupport::DeprecationException. + # [+:stderr+] Log all deprecation warnings to $stderr. + # [+:log+] Log all deprecation warnings to +Rails.logger+. + # [+:notify+] Use ActiveSupport::Notifications to notify +deprecation.rails+. + # [+:report+] Use ActiveSupport::ErrorReporter to report deprecations. + # [+:silence+] Do nothing. On \Rails, set config.active_support.report_deprecations = false to disable all behaviors. + # + # Setting behaviors only affects deprecations that happen after boot time. + # For more information you can read the documentation of the #behavior= method. + module Behavior + # Whether to print a backtrace along with the warning. + attr_accessor :debug + + # Returns the current behavior or if one isn't set, defaults to +:stderr+. + def behavior + @behavior ||= [DEFAULT_BEHAVIORS[:stderr]] + end + + # Returns the current behavior for disallowed deprecations or if one isn't set, defaults to +:raise+. + def disallowed_behavior + @disallowed_behavior ||= [DEFAULT_BEHAVIORS[:raise]] + end + + # Sets the behavior to the specified value. Can be a single value, array, + # or an object that responds to +call+. + # + # Available behaviors: + # + # [+:raise+] Raise ActiveSupport::DeprecationException. + # [+:stderr+] Log all deprecation warnings to $stderr. + # [+:log+] Log all deprecation warnings to +Rails.logger+. + # [+:notify+] Use ActiveSupport::Notifications to notify +deprecation.rails+. + # [+:report+] Use ActiveSupport::ErrorReporter to report deprecations. + # [+:silence+] Do nothing. + # + # Setting behaviors only affects deprecations that happen after boot time. + # Deprecation warnings raised by gems are not affected by this setting + # because they happen before \Rails boots up. + # + # deprecator = ActiveSupport::Deprecation.new + # deprecator.behavior = :stderr + # deprecator.behavior = [:stderr, :log] + # deprecator.behavior = MyCustomHandler + # deprecator.behavior = ->(message, callstack, deprecation_horizon, gem_name) { + # # custom stuff + # } + # + # If you are using \Rails, you can set + # config.active_support.report_deprecations = false to disable + # all deprecation behaviors. This is similar to the +:silence+ option but + # more performant. + def behavior=(behavior) + @behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || arity_coerce(b) } + end + + # Sets the behavior for disallowed deprecations (those configured by + # ActiveSupport::Deprecation#disallowed_warnings=) to the specified + # value. As with #behavior=, this can be a single value, array, or an + # object that responds to +call+. + def disallowed_behavior=(behavior) + @disallowed_behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || arity_coerce(b) } + end + + private + def arity_coerce(behavior) + unless behavior.respond_to?(:call) + raise ArgumentError, "#{behavior.inspect} is not a valid deprecation behavior." + end + + case arity_of_callable(behavior) + when 2 + ->(message, callstack, deprecator) do + behavior.call(message, callstack) + end + when -2..3 + behavior + else + ->(message, callstack, deprecator) do + behavior.call(message, callstack, deprecator.deprecation_horizon, deprecator.gem_name) + end + end + end + + def arity_of_callable(callable) + callable.respond_to?(:arity) ? callable.arity : callable.method(:call).arity + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/constant_accessor.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/constant_accessor.rb new file mode 100644 index 00000000..d6ff5b4d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/constant_accessor.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +module ActiveSupport + class Deprecation + module DeprecatedConstantAccessor + def self.included(base) + require "active_support/inflector/methods" + + extension = Module.new do + def const_missing(missing_const_name) + if class_variable_defined?(:@@_deprecated_constants) + if (replacement = class_variable_get(:@@_deprecated_constants)[missing_const_name.to_s]) + replacement[:deprecator].warn(replacement[:message] || "#{name}::#{missing_const_name} is deprecated! Use #{replacement[:new]} instead.", caller_locations) + return ActiveSupport::Inflector.constantize(replacement[:new].to_s) + end + end + super + end + + # Provides a way to rename constants with a deprecation cycle in which + # both the old and new names work, but using the old one prints a + # deprecation message. + # + # In order to rename A::B to C::D, you need to delete the + # definition of A::B and declare the deprecation in +A+: + # + # require "active_support/deprecation" + # + # module A + # include ActiveSupport::Deprecation::DeprecatedConstantAccessor + # + # deprecate_constant "B", "C::D", deprecator: ActiveSupport::Deprecation.new + # end + # + # The first argument is a constant name (no colons). It is the name of + # the constant you want to deprecate in the enclosing class or module. + # + # The second argument is the constant path of the replacement. That + # has to be a full path even if the replacement is defined in the same + # namespace as the deprecated one was. + # + # In both cases, strings and symbols are supported. + # + # The +deprecator+ keyword argument is the object that will print the + # deprecation message, an instance of ActiveSupport::Deprecation. + # + # With that in place, references to A::B still work, they + # evaluate to C::D now, and trigger a deprecation warning: + # + # DEPRECATION WARNING: A::B is deprecated! Use C::D instead. + # (called from ...) + # + # The message can be customized with the optional +message+ keyword + # argument. + # + # For this to work, a +const_missing+ hook is installed. When client + # code references the deprecated constant, the callback prints the + # message and constantizes the replacement. + # + # Caveat: If the deprecated constant name is reachable in a different + # namespace and Ruby constant lookup finds it, the hook won't be + # called and the deprecation won't work as intended. This may happen, + # for example, if an ancestor of the enclosing namespace has a + # constant with the same name. This is an unsupported edge case. + def deprecate_constant(old_constant_name, new_constant_path, deprecator:, message: nil) + class_variable_set(:@@_deprecated_constants, {}) unless class_variable_defined?(:@@_deprecated_constants) + class_variable_get(:@@_deprecated_constants)[old_constant_name.to_s] = { new: new_constant_path, message: message, deprecator: deprecator } + end + end + base.singleton_class.prepend extension + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/deprecators.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/deprecators.rb new file mode 100644 index 00000000..08f3fae3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/deprecators.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +module ActiveSupport + class Deprecation + # A managed collection of deprecators. Configuration methods, such as + # #behavior=, affect all deprecators in the collection. Additionally, the + # #silence method silences all deprecators in the collection for the + # duration of a given block. + class Deprecators + def initialize + @options = {} + @deprecators = {} + end + + # Returns a deprecator added to this collection via #[]=. + def [](name) + @deprecators[name] + end + + # Adds a given +deprecator+ to this collection. The deprecator will be + # immediately configured with any options previously set on this + # collection. + # + # deprecators = ActiveSupport::Deprecation::Deprecators.new + # deprecators.debug = true + # + # foo_deprecator = ActiveSupport::Deprecation.new("2.0", "Foo") + # foo_deprecator.debug # => false + # + # deprecators[:foo] = foo_deprecator + # deprecators[:foo].debug # => true + # foo_deprecator.debug # => true + # + def []=(name, deprecator) + apply_options(deprecator) + @deprecators[name] = deprecator + end + + # Iterates over all deprecators in this collection. If no block is given, + # returns an +Enumerator+. + def each(&block) + return to_enum(__method__) unless block + @deprecators.each_value(&block) + end + + # Sets the silenced flag for all deprecators in this collection. + def silenced=(silenced) + set_option(:silenced, silenced) + end + + # Sets the debug flag for all deprecators in this collection. + def debug=(debug) + set_option(:debug, debug) + end + + # Sets the deprecation warning behavior for all deprecators in this + # collection. + # + # See ActiveSupport::Deprecation#behavior=. + def behavior=(behavior) + set_option(:behavior, behavior) + end + + # Sets the disallowed deprecation warning behavior for all deprecators in + # this collection. + # + # See ActiveSupport::Deprecation#disallowed_behavior=. + def disallowed_behavior=(disallowed_behavior) + set_option(:disallowed_behavior, disallowed_behavior) + end + + # Sets the disallowed deprecation warnings for all deprecators in this + # collection. + # + # See ActiveSupport::Deprecation#disallowed_warnings=. + def disallowed_warnings=(disallowed_warnings) + set_option(:disallowed_warnings, disallowed_warnings) + end + + # Silences all deprecators in this collection for the duration of the + # given block. + # + # See ActiveSupport::Deprecation#silence. + def silence(&block) + each { |deprecator| deprecator.begin_silence } + block.call + ensure + each { |deprecator| deprecator.end_silence } + end + + private + def set_option(name, value) + @options[name] = value + each { |deprecator| deprecator.public_send("#{name}=", value) } + end + + def apply_options(deprecator) + @options.each do |name, value| + deprecator.public_send("#{name}=", value) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/disallowed.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/disallowed.rb new file mode 100644 index 00000000..85fdf5d3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/disallowed.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module ActiveSupport + class Deprecation + module Disallowed + # Sets the criteria used to identify deprecation messages which should be + # disallowed. Can be an array containing strings, symbols, or regular + # expressions. (Symbols are treated as strings.) These are compared against + # the text of the generated deprecation warning. + # + # Additionally the scalar symbol +:all+ may be used to treat all + # deprecations as disallowed. + # + # Deprecations matching a substring or regular expression will be handled + # using the configured Behavior#disallowed_behavior rather than + # Behavior#behavior. + attr_writer :disallowed_warnings + + # Returns the configured criteria used to identify deprecation messages + # which should be treated as disallowed. + def disallowed_warnings + @disallowed_warnings ||= [] + end + + private + def deprecation_disallowed?(message) + return false if explicitly_allowed?(message) + return true if disallowed_warnings == :all + message && disallowed_warnings.any? do |rule| + case rule + when String, Symbol + message.include?(rule.to_s) + when Regexp + rule.match?(message) + end + end + end + + def explicitly_allowed?(message) + allowances = @explicitly_allowed_warnings.value + return false unless allowances + return true if allowances == :all + message && Array(allowances).any? do |rule| + case rule + when String, Symbol + message.include?(rule.to_s) + when Regexp + rule.match?(message) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/method_wrappers.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/method_wrappers.rb new file mode 100644 index 00000000..921168c9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/method_wrappers.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/module/redefine_method" + +module ActiveSupport + class Deprecation + module MethodWrapper + # Declare that a method has been deprecated. + # + # class Fred + # def aaa; end + # def bbb; end + # def ccc; end + # def ddd; end + # def eee; end + # end + # + # deprecator = ActiveSupport::Deprecation.new('next-release', 'MyGem') + # + # deprecator.deprecate_methods(Fred, :aaa, bbb: :zzz, ccc: 'use Bar#ccc instead') + # # => Fred + # + # Fred.new.aaa + # # DEPRECATION WARNING: aaa is deprecated and will be removed from MyGem next-release. (called from irb_binding at (irb):10) + # # => nil + # + # Fred.new.bbb + # # DEPRECATION WARNING: bbb is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):11) + # # => nil + # + # Fred.new.ccc + # # DEPRECATION WARNING: ccc is deprecated and will be removed from MyGem next-release (use Bar#ccc instead). (called from irb_binding at (irb):12) + # # => nil + def deprecate_methods(target_module, *method_names) + options = method_names.extract_options! + deprecator = options.delete(:deprecator) || self + method_names += options.keys + mod = nil + + method_names.each do |method_name| + message = options[method_name] + if target_module.method_defined?(method_name) || target_module.private_method_defined?(method_name) + method = target_module.instance_method(method_name) + target_module.module_eval do + redefine_method(method_name) do |*args, &block| + deprecator.deprecation_warning(method_name, message) + method.bind_call(self, *args, &block) + end + ruby2_keywords(method_name) + end + else + mod ||= Module.new + mod.module_eval do + define_method(method_name) do |*args, &block| + deprecator.deprecation_warning(method_name, message) + super(*args, &block) + end + ruby2_keywords(method_name) + end + end + end + + target_module.prepend(mod) if mod + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/proxy_wrappers.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/proxy_wrappers.rb new file mode 100644 index 00000000..80e4ab22 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/proxy_wrappers.rb @@ -0,0 +1,189 @@ +# frozen_string_literal: true + +module ActiveSupport + class Deprecation + class DeprecationProxy # :nodoc: + def self.new(*args, **kwargs, &block) + object = args.first + + return object unless object + super + end + + instance_methods.each { |m| undef_method m unless /^__|^object_id$/.match?(m) } + + # Don't give a deprecation warning on inspect since test/unit and error + # logs rely on it for diagnostics. + def inspect + target.inspect + end + + private + def method_missing(called, *args, &block) + warn caller_locations, called, args + target.__send__(called, *args, &block) + end + end + + # DeprecatedObjectProxy transforms an object into a deprecated one. It takes an object, a deprecation message, and + # a deprecator. + # + # deprecated_object = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(Object.new, "This object is now deprecated", ActiveSupport::Deprecation.new) + # # => # + # + # deprecated_object.to_s + # DEPRECATION WARNING: This object is now deprecated. + # (Backtrace) + # # => "#" + class DeprecatedObjectProxy < DeprecationProxy + def initialize(object, message, deprecator) + @object = object + @message = message + @deprecator = deprecator + end + + private + def target + @object + end + + def warn(callstack, called, args) + @deprecator.warn(@message, callstack) + end + end + + # DeprecatedInstanceVariableProxy transforms an instance variable into a deprecated one. It takes an instance of a + # class, a method on that class, an instance variable, and a deprecator as the last argument. + # + # Trying to use the deprecated instance variable will result in a deprecation warning, pointing to the method as a + # replacement. + # + # class Example + # def initialize + # @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request, ActiveSupport::Deprecation.new) + # @_request = :special_request + # end + # + # def request + # @_request + # end + # + # def old_request + # @request + # end + # end + # + # example = Example.new + # # => # + # + # example.old_request.to_s + # # => DEPRECATION WARNING: @request is deprecated! Call request.to_s instead of + # @request.to_s + # (Backtrace information…) + # "special_request" + # + # example.request.to_s + # # => "special_request" + class DeprecatedInstanceVariableProxy < DeprecationProxy + def initialize(instance, method, var = "@#{method}", deprecator:) + @instance = instance + @method = method + @var = var + @deprecator = deprecator + end + + private + def target + @instance.__send__(@method) + end + + def warn(callstack, called, args) + @deprecator.warn("#{@var} is deprecated! Call #{@method}.#{called} instead of #{@var}.#{called}. Args: #{args.inspect}", callstack) + end + end + + # DeprecatedConstantProxy transforms a constant into a deprecated one. It takes the full names of an old + # (deprecated) constant and of a new constant (both in string form) and a deprecator. The deprecated constant now + # returns the value of the new one. + # + # PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto) + # + # # (In a later update, the original implementation of `PLANETS` has been removed.) + # + # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune) + # PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("PLANETS", "PLANETS_POST_2006", ActiveSupport::Deprecation.new) + # + # PLANETS.map { |planet| planet.capitalize } + # # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead. + # (Backtrace information…) + # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] + class DeprecatedConstantProxy < Module + def self.new(*args, **options, &block) + object = args.first + + return object unless object + super + end + + def initialize(old_const, new_const, deprecator, message: "#{old_const} is deprecated! Use #{new_const} instead.") + Kernel.require "active_support/inflector/methods" + + @old_const = old_const + @new_const = new_const + @deprecator = deprecator + @message = message + end + + instance_methods.each { |m| undef_method m unless /^__|^object_id$/.match?(m) } + + # Don't give a deprecation warning on inspect since test/unit and error + # logs rely on it for diagnostics. + def inspect + target.inspect + end + + # Don't give a deprecation warning on methods that IRB may invoke + # during tab-completion. + delegate :hash, :instance_methods, :name, :respond_to?, to: :target + + # Returns the class of the new constant. + # + # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune) + # PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006') + # PLANETS.class # => Array + def class + target.class + end + + def append_features(base) + @deprecator.warn(@message, caller_locations) + base.include(target) + end + + def prepend_features(base) + @deprecator.warn(@message, caller_locations) + base.prepend(target) + end + + def extended(base) + @deprecator.warn(@message, caller_locations) + base.extend(target) + end + + private + def target + ActiveSupport::Inflector.constantize(@new_const.to_s) + end + + def const_missing(name) + @deprecator.warn(@message, caller_locations) + target.const_get(name) + end + + def method_missing(...) + @deprecator.warn(@message, caller_locations) + target.__send__(...) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/reporting.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/reporting.rb new file mode 100644 index 00000000..30a95a04 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/deprecation/reporting.rb @@ -0,0 +1,160 @@ +# frozen_string_literal: true + +require "rbconfig" + +module ActiveSupport + class Deprecation + module Reporting + # Whether to print a message (silent mode) + attr_writer :silenced + # Name of gem where method is deprecated + attr_accessor :gem_name + + # Outputs a deprecation warning to the output configured by + # ActiveSupport::Deprecation#behavior. + # + # ActiveSupport::Deprecation.new.warn('something broke!') + # # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)" + def warn(message = nil, callstack = nil) + return if silenced + + callstack ||= caller_locations(2) + deprecation_message(callstack, message).tap do |full_message| + if deprecation_disallowed?(message) + disallowed_behavior.each { |b| b.call(full_message, callstack, self) } + else + behavior.each { |b| b.call(full_message, callstack, self) } + end + end + end + + # Silence deprecation warnings within the block. + # + # deprecator = ActiveSupport::Deprecation.new + # deprecator.warn('something broke!') + # # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)" + # + # deprecator.silence do + # deprecator.warn('something broke!') + # end + # # => nil + def silence(&block) + begin_silence + block.call + ensure + end_silence + end + + def begin_silence # :nodoc: + @silence_counter.value += 1 + end + + def end_silence # :nodoc: + @silence_counter.value -= 1 + end + + def silenced + @silenced || @silence_counter.value.nonzero? + end + + # Allow previously disallowed deprecation warnings within the block. + # allowed_warnings can be an array containing strings, symbols, or regular + # expressions. (Symbols are treated as strings). These are compared against + # the text of deprecation warning messages generated within the block. + # Matching warnings will be exempt from the rules set by + # ActiveSupport::Deprecation#disallowed_warnings. + # + # The optional if: argument accepts a truthy/falsy value or an object that + # responds to .call. If truthy, then matching warnings will be allowed. + # If falsey then the method yields to the block without allowing the warning. + # + # deprecator = ActiveSupport::Deprecation.new + # deprecator.disallowed_behavior = :raise + # deprecator.disallowed_warnings = [ + # "something broke" + # ] + # + # deprecator.warn('something broke!') + # # => ActiveSupport::DeprecationException + # + # deprecator.allow ['something broke'] do + # deprecator.warn('something broke!') + # end + # # => nil + # + # deprecator.allow ['something broke'], if: Rails.env.production? do + # deprecator.warn('something broke!') + # end + # # => ActiveSupport::DeprecationException for dev/test, nil for production + def allow(allowed_warnings = :all, if: true, &block) + conditional = binding.local_variable_get(:if) + conditional = conditional.call if conditional.respond_to?(:call) + if conditional + @explicitly_allowed_warnings.bind(allowed_warnings, &block) + else + yield + end + end + + def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil) + caller_backtrace ||= caller_locations(2) + deprecated_method_warning(deprecated_method_name, message).tap do |msg| + warn(msg, caller_backtrace) + end + end + + private + # Outputs a deprecation warning message + # + # deprecated_method_warning(:method_name) + # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon}" + # deprecated_method_warning(:method_name, :another_method) + # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (use another_method instead)" + # deprecated_method_warning(:method_name, "Optional message") + # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (Optional message)" + def deprecated_method_warning(method_name, message = nil) + warning = "#{method_name} is deprecated and will be removed from #{gem_name} #{deprecation_horizon}" + case message + when Symbol then "#{warning} (use #{message} instead)" + when String then "#{warning} (#{message})" + else warning + end + end + + def deprecation_message(callstack, message = nil) + message ||= "You are using deprecated behavior which will be removed from the next major or minor release." + "DEPRECATION WARNING: #{message} #{deprecation_caller_message(callstack)}" + end + + def deprecation_caller_message(callstack) + file, line, method = extract_callstack(callstack) + if file + if line && method + "(called from #{method} at #{file}:#{line})" + else + "(called from #{file}:#{line})" + end + end + end + + def extract_callstack(callstack) + return [] if callstack.empty? + + offending_line = callstack.find { |frame| + # Code generated with `eval` doesn't have an `absolute_path`, e.g. templates. + path = frame.absolute_path || frame.path + path && !ignored_callstack?(path) + } || callstack.first + + [offending_line.path, offending_line.lineno, offending_line.label] + end + + RAILS_GEM_ROOT = File.expand_path("../../../..", __dir__) + "/" # :nodoc: + LIB_DIR = RbConfig::CONFIG["libdir"] # :nodoc: + + def ignored_callstack?(path) + path.start_with?(RAILS_GEM_ROOT, LIB_DIR) || path.include?("(other) + if Scalar === other || Duration === other + value <=> other.value + elsif Numeric === other + value <=> other + else + nil + end + end + + def +(other) + if Duration === other + seconds = value + other._parts.fetch(:seconds, 0) + new_parts = other._parts.merge(seconds: seconds) + new_value = value + other.value + + Duration.new(new_value, new_parts, other.variable?) + else + calculate(:+, other) + end + end + + def -(other) + if Duration === other + seconds = value - other._parts.fetch(:seconds, 0) + new_parts = other._parts.transform_values(&:-@) + new_parts = new_parts.merge(seconds: seconds) + new_value = value - other.value + + Duration.new(new_value, new_parts, other.variable?) + else + calculate(:-, other) + end + end + + def *(other) + if Duration === other + new_parts = other._parts.transform_values { |other_value| value * other_value } + new_value = value * other.value + + Duration.new(new_value, new_parts, other.variable?) + else + calculate(:*, other) + end + end + + def /(other) + if Duration === other + value / other.value + else + calculate(:/, other) + end + end + + def %(other) + if Duration === other + Duration.build(value % other.value) + else + calculate(:%, other) + end + end + + def variable? # :nodoc: + false + end + + private + def calculate(op, other) + if Scalar === other + Scalar.new(value.public_send(op, other.value)) + elsif Numeric === other + Scalar.new(value.public_send(op, other)) + else + raise_type_error(other) + end + end + + def raise_type_error(other) + raise TypeError, "no implicit conversion of #{other.class} into #{self.class}" + end + end + + SECONDS_PER_MINUTE = 60 + SECONDS_PER_HOUR = 3600 + SECONDS_PER_DAY = 86400 + SECONDS_PER_WEEK = 604800 + SECONDS_PER_MONTH = 2629746 # 1/12 of a gregorian year + SECONDS_PER_YEAR = 31556952 # length of a gregorian year (365.2425 days) + + PARTS_IN_SECONDS = { + seconds: 1, + minutes: SECONDS_PER_MINUTE, + hours: SECONDS_PER_HOUR, + days: SECONDS_PER_DAY, + weeks: SECONDS_PER_WEEK, + months: SECONDS_PER_MONTH, + years: SECONDS_PER_YEAR + }.freeze + + PARTS = [:years, :months, :weeks, :days, :hours, :minutes, :seconds].freeze + VARIABLE_PARTS = [:years, :months, :weeks, :days].freeze + + attr_reader :value + + autoload :ISO8601Parser, "active_support/duration/iso8601_parser" + autoload :ISO8601Serializer, "active_support/duration/iso8601_serializer" + + class << self + # Creates a new Duration from string formatted according to ISO 8601 Duration. + # + # See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information. + # This method allows negative parts to be present in pattern. + # If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+. + def parse(iso8601duration) + parts = ISO8601Parser.new(iso8601duration).parse! + new(calculate_total_seconds(parts), parts) + end + + def ===(other) # :nodoc: + other.is_a?(Duration) + rescue ::NoMethodError + false + end + + def seconds(value) # :nodoc: + new(value, { seconds: value }, false) + end + + def minutes(value) # :nodoc: + new(value * SECONDS_PER_MINUTE, { minutes: value }, false) + end + + def hours(value) # :nodoc: + new(value * SECONDS_PER_HOUR, { hours: value }, false) + end + + def days(value) # :nodoc: + new(value * SECONDS_PER_DAY, { days: value }, true) + end + + def weeks(value) # :nodoc: + new(value * SECONDS_PER_WEEK, { weeks: value }, true) + end + + def months(value) # :nodoc: + new(value * SECONDS_PER_MONTH, { months: value }, true) + end + + def years(value) # :nodoc: + new(value * SECONDS_PER_YEAR, { years: value }, true) + end + + # Creates a new Duration from a seconds value that is converted + # to the individual parts: + # + # ActiveSupport::Duration.build(31556952).parts # => {:years=>1} + # ActiveSupport::Duration.build(2716146).parts # => {:months=>1, :days=>1} + # + def build(value) + unless value.is_a?(::Numeric) + raise TypeError, "can't build an #{self.name} from a #{value.class.name}" + end + + parts = {} + remainder_sign = value <=> 0 + remainder = value.round(9).abs + variable = false + + PARTS.each do |part| + unless part == :seconds + part_in_seconds = PARTS_IN_SECONDS[part] + parts[part] = remainder.div(part_in_seconds) * remainder_sign + remainder %= part_in_seconds + + unless parts[part].zero? + variable ||= VARIABLE_PARTS.include?(part) + end + end + end unless value == 0 + + parts[:seconds] = remainder * remainder_sign + + new(value, parts, variable) + end + + private + def calculate_total_seconds(parts) + parts.inject(0) do |total, (part, value)| + total + value * PARTS_IN_SECONDS[part] + end + end + end + + Delegation.generate(self, [:to_f, :positive?, :negative?, :zero?, :abs], to: :@value, as: Integer, nilable: false) + + def initialize(value, parts, variable = nil) # :nodoc: + @value, @parts = value, parts + @parts.reject! { |k, v| v.zero? } unless value == 0 + @parts.freeze + @variable = variable + + if @variable.nil? + @variable = @parts.any? { |part, _| VARIABLE_PARTS.include?(part) } + end + end + + # Returns a copy of the parts hash that defines the duration. + # + # 5.minutes.parts # => {:minutes=>5} + # 3.years.parts # => {:years=>3} + def parts + @parts.dup + end + + def coerce(other) # :nodoc: + case other + when Scalar + [other, self] + when Duration + [Scalar.new(other.value), self] + else + [Scalar.new(other), self] + end + end + + # Compares one Duration with another or a Numeric to this Duration. + # Numeric values are treated as seconds. + def <=>(other) + if Duration === other + value <=> other.value + elsif Numeric === other + value <=> other + end + end + + # Adds another Duration or a Numeric to this Duration. Numeric values + # are treated as seconds. + def +(other) + if Duration === other + parts = @parts.merge(other._parts) do |_key, value, other_value| + value + other_value + end + Duration.new(value + other.value, parts, @variable || other.variable?) + else + seconds = @parts.fetch(:seconds, 0) + other + Duration.new(value + other, @parts.merge(seconds: seconds), @variable) + end + end + + # Subtracts another Duration or a Numeric from this Duration. Numeric + # values are treated as seconds. + def -(other) + self + (-other) + end + + # Multiplies this Duration by a Numeric and returns a new Duration. + def *(other) + if Scalar === other || Duration === other + Duration.new(value * other.value, @parts.transform_values { |number| number * other.value }, @variable || other.variable?) + elsif Numeric === other + Duration.new(value * other, @parts.transform_values { |number| number * other }, @variable) + else + raise_type_error(other) + end + end + + # Divides this Duration by a Numeric and returns a new Duration. + def /(other) + if Scalar === other + Duration.new(value / other.value, @parts.transform_values { |number| number / other.value }, @variable) + elsif Duration === other + value / other.value + elsif Numeric === other + Duration.new(value / other, @parts.transform_values { |number| number / other }, @variable) + else + raise_type_error(other) + end + end + + # Returns the modulo of this Duration by another Duration or Numeric. + # Numeric values are treated as seconds. + def %(other) + if Duration === other || Scalar === other + Duration.build(value % other.value) + elsif Numeric === other + Duration.build(value % other) + else + raise_type_error(other) + end + end + + def -@ # :nodoc: + Duration.new(-value, @parts.transform_values(&:-@), @variable) + end + + def +@ # :nodoc: + self + end + + def is_a?(klass) # :nodoc: + Duration == klass || value.is_a?(klass) + end + alias :kind_of? :is_a? + + def instance_of?(klass) # :nodoc: + Duration == klass || value.instance_of?(klass) + end + + # Returns +true+ if +other+ is also a Duration instance with the + # same +value+, or if other == value. + def ==(other) + if Duration === other + other.value == value + else + other == value + end + end + + # Returns the amount of seconds a duration covers as a string. + # For more information check to_i method. + # + # 1.day.to_s # => "86400" + def to_s + @value.to_s + end + + # Returns the number of seconds that this Duration represents. + # + # 1.minute.to_i # => 60 + # 1.hour.to_i # => 3600 + # 1.day.to_i # => 86400 + # + # Note that this conversion makes some assumptions about the + # duration of some periods, e.g. months are always 1/12 of year + # and years are 365.2425 days: + # + # # equivalent to (1.year / 12).to_i + # 1.month.to_i # => 2629746 + # + # # equivalent to 365.2425.days.to_i + # 1.year.to_i # => 31556952 + # + # In such cases, Ruby's core + # Date[https://docs.ruby-lang.org/en/master/Date.html] and + # Time[https://docs.ruby-lang.org/en/master/Time.html] should be used for precision + # date and time arithmetic. + def to_i + @value.to_i + end + alias :in_seconds :to_i + + # Returns the amount of minutes a duration covers as a float + # + # 1.day.in_minutes # => 1440.0 + def in_minutes + in_seconds / SECONDS_PER_MINUTE.to_f + end + + # Returns the amount of hours a duration covers as a float + # + # 1.day.in_hours # => 24.0 + def in_hours + in_seconds / SECONDS_PER_HOUR.to_f + end + + # Returns the amount of days a duration covers as a float + # + # 12.hours.in_days # => 0.5 + def in_days + in_seconds / SECONDS_PER_DAY.to_f + end + + # Returns the amount of weeks a duration covers as a float + # + # 2.months.in_weeks # => 8.696 + def in_weeks + in_seconds / SECONDS_PER_WEEK.to_f + end + + # Returns the amount of months a duration covers as a float + # + # 9.weeks.in_months # => 2.07 + def in_months + in_seconds / SECONDS_PER_MONTH.to_f + end + + # Returns the amount of years a duration covers as a float + # + # 30.days.in_years # => 0.082 + def in_years + in_seconds / SECONDS_PER_YEAR.to_f + end + + # Returns +true+ if +other+ is also a Duration instance, which has the + # same parts as this one. + def eql?(other) + Duration === other && other.value.eql?(value) + end + + def hash + @value.hash + end + + # Calculates a new Time or Date that is as far in the future + # as this Duration represents. + def since(time = ::Time.current) + sum(1, time) + end + alias :from_now :since + alias :after :since + + # Calculates a new Time or Date that is as far in the past + # as this Duration represents. + def ago(time = ::Time.current) + sum(-1, time) + end + alias :until :ago + alias :before :ago + + def inspect # :nodoc: + return "#{value} seconds" if @parts.empty? + + @parts. + sort_by { |unit, _ | PARTS.index(unit) }. + map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }. + to_sentence(locale: false) + end + + def as_json(options = nil) # :nodoc: + to_i + end + + def init_with(coder) # :nodoc: + initialize(coder["value"], coder["parts"]) + end + + def encode_with(coder) # :nodoc: + coder.map = { "value" => @value, "parts" => @parts } + end + + # Build ISO 8601 Duration string for this duration. + # The +precision+ parameter can be used to limit seconds' precision of duration. + def iso8601(precision: nil) + ISO8601Serializer.new(self, precision: precision).serialize + end + + def variable? # :nodoc: + @variable + end + + def _parts # :nodoc: + @parts + end + + private + def sum(sign, time = ::Time.current) + unless time.acts_like?(:time) || time.acts_like?(:date) + raise ::ArgumentError, "expected a time or date, got #{time.inspect}" + end + + if @parts.empty? + time.since(sign * value) + else + @parts.each do |type, number| + t = time + time = + if type == :seconds + t.since(sign * number) + elsif type == :minutes + t.since(sign * number * 60) + elsif type == :hours + t.since(sign * number * 3600) + else + t.advance(type => sign * number) + end + end + + time + end + end + + def respond_to_missing?(method, _) + value.respond_to?(method) + end + + def method_missing(...) + value.public_send(...) + end + + def raise_type_error(other) + raise TypeError, "no implicit conversion of #{other.class} into #{self.class}" + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/duration/iso8601_parser.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/duration/iso8601_parser.rb new file mode 100644 index 00000000..8ccd0cf4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/duration/iso8601_parser.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +require "strscan" + +module ActiveSupport + class Duration + # Parses a string formatted according to ISO 8601 Duration into the hash. + # + # See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information. + # + # This parser allows negative parts to be present in pattern. + class ISO8601Parser # :nodoc: + class ParsingError < ::ArgumentError; end + + PERIOD_OR_COMMA = /\.|,/ + PERIOD = "." + COMMA = "," + + SIGN_MARKER = /\A-|\+|/ + DATE_MARKER = /P/ + TIME_MARKER = /T/ + DATE_COMPONENT = /(-?\d+(?:[.,]\d+)?)(Y|M|D|W)/ + TIME_COMPONENT = /(-?\d+(?:[.,]\d+)?)(H|M|S)/ + + DATE_TO_PART = { "Y" => :years, "M" => :months, "W" => :weeks, "D" => :days } + TIME_TO_PART = { "H" => :hours, "M" => :minutes, "S" => :seconds } + + DATE_COMPONENTS = [:years, :months, :days] + TIME_COMPONENTS = [:hours, :minutes, :seconds] + + attr_reader :parts, :scanner + attr_accessor :mode, :sign + + def initialize(string) + @scanner = StringScanner.new(string) + @parts = {} + @mode = :start + @sign = 1 + end + + def parse! + while !finished? + case mode + when :start + if scan(SIGN_MARKER) + self.sign = (scanner.matched == "-") ? -1 : 1 + self.mode = :sign + else + raise_parsing_error + end + + when :sign + if scan(DATE_MARKER) + self.mode = :date + else + raise_parsing_error + end + + when :date + if scan(TIME_MARKER) + self.mode = :time + elsif scan(DATE_COMPONENT) + parts[DATE_TO_PART[scanner[2]]] = number * sign + else + raise_parsing_error + end + + when :time + if scan(TIME_COMPONENT) + parts[TIME_TO_PART[scanner[2]]] = number * sign + else + raise_parsing_error + end + + end + end + + validate! + parts + end + + private + def finished? + scanner.eos? + end + + # Parses number which can be a float with either comma or period. + def number + PERIOD_OR_COMMA.match?(scanner[1]) ? scanner[1].tr(COMMA, PERIOD).to_f : scanner[1].to_i + end + + def scan(pattern) + scanner.scan(pattern) + end + + def raise_parsing_error(reason = nil) + raise ParsingError, "Invalid ISO 8601 duration: #{scanner.string.inspect} #{reason}".strip + end + + # Checks for various semantic errors as stated in ISO 8601 standard. + def validate! + raise_parsing_error("is empty duration") if parts.empty? + + # Mixing any of Y, M, D with W is invalid. + if parts.key?(:weeks) && parts.keys.intersect?(DATE_COMPONENTS) + raise_parsing_error("mixing weeks with other date parts not allowed") + end + + # Specifying an empty T part is invalid. + if mode == :time && !parts.keys.intersect?(TIME_COMPONENTS) + raise_parsing_error("time part marker is present but time part is empty") + end + + fractions = parts.values.reject(&:zero?).select { |a| (a % 1) != 0 } + unless fractions.empty? || (fractions.size == 1 && fractions.last == @parts.values.reject(&:zero?).last) + raise_parsing_error "(only last part can be fractional)" + end + + true + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/duration/iso8601_serializer.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/duration/iso8601_serializer.rb new file mode 100644 index 00000000..ca9c9129 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/duration/iso8601_serializer.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module ActiveSupport + class Duration + # Serializes duration to string according to ISO 8601 Duration format. + class ISO8601Serializer # :nodoc: + DATE_COMPONENTS = %i(years months days) + + def initialize(duration, precision: nil) + @duration = duration + @precision = precision + end + + # Builds and returns output string. + def serialize + parts = normalize + return "PT0S" if parts.empty? + + output = +"P" + output << "#{parts[:years]}Y" if parts.key?(:years) + output << "#{parts[:months]}M" if parts.key?(:months) + output << "#{parts[:days]}D" if parts.key?(:days) + output << "#{parts[:weeks]}W" if parts.key?(:weeks) + time = +"" + time << "#{parts[:hours]}H" if parts.key?(:hours) + time << "#{parts[:minutes]}M" if parts.key?(:minutes) + if parts.key?(:seconds) + time << "#{format_seconds(parts[:seconds])}S" + end + output << "T#{time}" unless time.empty? + output + end + + private + # Return pair of duration's parts and whole duration sign. + # Parts are summarized (as they can become repetitive due to addition, etc). + # Zero parts are removed as not significant. + def normalize + parts = @duration.parts.each_with_object(Hash.new(0)) do |(k, v), p| + p[k] += v unless v.zero? + end + + # Convert weeks to days and remove weeks if mixed with date parts + if week_mixed_with_date?(parts) + parts[:days] += parts.delete(:weeks) * SECONDS_PER_WEEK / SECONDS_PER_DAY + end + + parts + end + + def week_mixed_with_date?(parts) + parts.key?(:weeks) && parts.keys.intersect?(DATE_COMPONENTS) + end + + def format_seconds(seconds) + if @precision + sprintf("%0.0#{@precision}f", seconds) + else + seconds.to_s + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/encrypted_configuration.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/encrypted_configuration.rb new file mode 100644 index 00000000..39c7636d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/encrypted_configuration.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +require "yaml" +require "active_support/encrypted_file" +require "active_support/ordered_options" +require "active_support/core_ext/object/inclusion" +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/module/delegation" + +module ActiveSupport + # = Encrypted Configuration + # + # Provides convenience methods on top of EncryptedFile to access values stored + # as encrypted YAML. + # + # Values can be accessed via +Hash+ methods, such as +fetch+ and +dig+, or via + # dynamic accessor methods, similar to OrderedOptions. + # + # my_config = ActiveSupport::EncryptedConfiguration.new(...) + # my_config.read # => "some_secret: 123\nsome_namespace:\n another_secret: 456" + # + # my_config[:some_secret] + # # => 123 + # my_config.some_secret + # # => 123 + # my_config.dig(:some_namespace, :another_secret) + # # => 456 + # my_config.some_namespace.another_secret + # # => 456 + # my_config.fetch(:foo) + # # => KeyError + # my_config.foo! + # # => KeyError + # + class EncryptedConfiguration < EncryptedFile + class InvalidContentError < RuntimeError + def initialize(content_path) + super "Invalid YAML in '#{content_path}'." + end + + def message + cause.is_a?(Psych::SyntaxError) ? "#{super}\n\n #{cause.message}" : super + end + end + + class InvalidKeyError < RuntimeError + def initialize(content_path, key) + super "Key '#{key}' is invalid, it must respond to '#to_sym' from configuration in '#{content_path}'." + end + end + + delegate_missing_to :options + + def initialize(config_path:, key_path:, env_key:, raise_if_missing_key:) + super content_path: config_path, key_path: key_path, + env_key: env_key, raise_if_missing_key: raise_if_missing_key + @config = nil + @options = nil + end + + # Reads the file and returns the decrypted content. See EncryptedFile#read. + def read + super + rescue ActiveSupport::EncryptedFile::MissingContentError + # Allow a config to be started without a file present + "" + end + + def validate! # :nodoc: + deserialize(read).each_key do |key| + key.to_sym + rescue NoMethodError + raise InvalidKeyError.new(content_path, key) + end + end + + # Returns the decrypted content as a Hash with symbolized keys. + # + # my_config = ActiveSupport::EncryptedConfiguration.new(...) + # my_config.read # => "some_secret: 123\nsome_namespace:\n another_secret: 456" + # + # my_config.config + # # => { some_secret: 123, some_namespace: { another_secret: 789 } } + # + def config + @config ||= deep_symbolize_keys(deserialize(read)) + end + + def inspect # :nodoc: + "#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>" + end + + private + def deep_symbolize_keys(hash) + hash.deep_transform_keys do |key| + key.to_sym + rescue NoMethodError + raise InvalidKeyError.new(content_path, key) + end + end + + def deep_transform(hash) + return hash unless hash.is_a?(Hash) + + h = ActiveSupport::OrderedOptions.new + hash.each do |k, v| + h[k] = deep_transform(v) + end + h + end + + def options + @options ||= deep_transform(config) + end + + def deserialize(content) + config = YAML.respond_to?(:unsafe_load) ? + YAML.unsafe_load(content, filename: content_path) : + YAML.load(content, filename: content_path) + + config.presence || {} + rescue Psych::SyntaxError + raise InvalidContentError.new(content_path) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/encrypted_file.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/encrypted_file.rb new file mode 100644 index 00000000..6522e00e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/encrypted_file.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +require "pathname" +require "tempfile" +require "active_support/message_encryptor" + +module ActiveSupport + class EncryptedFile + class MissingContentError < RuntimeError + def initialize(content_path) + super "Missing encrypted content file in #{content_path}." + end + end + + class MissingKeyError < RuntimeError + def initialize(key_path:, env_key:) + super \ + "Missing encryption key to decrypt file with. " + + "Ask your team for your master key and write it to #{key_path} or put it in the ENV['#{env_key}']." + end + end + + class InvalidKeyLengthError < RuntimeError + def initialize + super "Encryption key must be exactly #{EncryptedFile.expected_key_length} characters." + end + end + + CIPHER = "aes-128-gcm" + + def self.generate_key + SecureRandom.hex(ActiveSupport::MessageEncryptor.key_len(CIPHER)) + end + + def self.expected_key_length # :nodoc: + @expected_key_length ||= generate_key.length + end + + + attr_reader :content_path, :key_path, :env_key, :raise_if_missing_key + + def initialize(content_path:, key_path:, env_key:, raise_if_missing_key:) + @content_path = Pathname.new(content_path).yield_self { |path| path.symlink? ? path.realpath : path } + @key_path = Pathname.new(key_path) + @env_key, @raise_if_missing_key = env_key, raise_if_missing_key + end + + # Returns the encryption key, first trying the environment variable + # specified by +env_key+, then trying the key file specified by +key_path+. + # If +raise_if_missing_key+ is true, raises MissingKeyError if the + # environment variable is not set and the key file does not exist. + def key + read_env_key || read_key_file || handle_missing_key + end + + # Returns truthy if #key is truthy. Returns falsy otherwise. Unlike #key, + # does not raise MissingKeyError when +raise_if_missing_key+ is true. + def key? + read_env_key || read_key_file + end + + # Reads the file and returns the decrypted content. + # + # Raises: + # - MissingKeyError if the key is missing and +raise_if_missing_key+ is true. + # - MissingContentError if the encrypted file does not exist or otherwise + # if the key is missing. + # - ActiveSupport::MessageEncryptor::InvalidMessage if the content cannot be + # decrypted or verified. + def read + if !key.nil? && content_path.exist? + decrypt content_path.binread.strip + else + raise MissingContentError, content_path + end + end + + def write(contents) + IO.binwrite "#{content_path}.tmp", encrypt(contents) + FileUtils.mv "#{content_path}.tmp", content_path + end + + def change(&block) + writing read, &block + end + + + private + def writing(contents) + Tempfile.create(["", "-" + content_path.basename.to_s.chomp(".enc")]) do |tmp_file| + tmp_path = Pathname.new(tmp_file) + tmp_path.binwrite contents + + yield tmp_path + + updated_contents = tmp_path.binread + + write(updated_contents) if updated_contents != contents + end + end + + + def encrypt(contents) + check_key_length + encryptor.encrypt_and_sign contents + end + + def decrypt(contents) + encryptor.decrypt_and_verify contents + end + + def encryptor + @encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: CIPHER, serializer: Marshal) + end + + + def read_env_key + ENV[env_key].presence + end + + def read_key_file + @key_file_contents ||= (key_path.binread.strip if key_path.exist?) + end + + def handle_missing_key + raise MissingKeyError.new(key_path: key_path, env_key: env_key) if raise_if_missing_key + end + + def check_key_length + raise InvalidKeyLengthError if key&.length != self.class.expected_key_length + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/environment_inquirer.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/environment_inquirer.rb new file mode 100644 index 00000000..6fa4fa1d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/environment_inquirer.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "active_support/string_inquirer" +require "active_support/core_ext/object/inclusion" + +module ActiveSupport + class EnvironmentInquirer < StringInquirer # :nodoc: + # Optimization for the three default environments, so this inquirer doesn't need to rely on + # the slower delegation through method_missing that StringInquirer would normally entail. + DEFAULT_ENVIRONMENTS = %w[ development test production ] + + # Environments that'll respond true for #local? + LOCAL_ENVIRONMENTS = %w[ development test ] + + def initialize(env) + raise(ArgumentError, "'local' is a reserved environment name") if env == "local" + + super(env) + + DEFAULT_ENVIRONMENTS.each do |default| + instance_variable_set :"@#{default}", env == default + end + + @local = in? LOCAL_ENVIRONMENTS + end + + DEFAULT_ENVIRONMENTS.each do |env| + class_eval <<~RUBY, __FILE__, __LINE__ + 1 + def #{env}? + @#{env} + end + RUBY + end + + # Returns true if we're in the development or test environment. + def local? + @local + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/error_reporter.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/error_reporter.rb new file mode 100644 index 00000000..12ef41c8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/error_reporter.rb @@ -0,0 +1,274 @@ +# frozen_string_literal: true + +module ActiveSupport + # = Active Support \Error Reporter + # + # +ActiveSupport::ErrorReporter+ is a common interface for error reporting services. + # + # To rescue and report any unhandled error, you can use the #handle method: + # + # Rails.error.handle do + # do_something! + # end + # + # If an error is raised, it will be reported and swallowed. + # + # Alternatively, if you want to report the error but not swallow it, you can use #record: + # + # Rails.error.record do + # do_something! + # end + # + # Both methods can be restricted to handle only a specific error class: + # + # maybe_tags = Rails.error.handle(Redis::BaseError) { redis.get("tags") } + # + class ErrorReporter + SEVERITIES = %i(error warning info) + DEFAULT_SOURCE = "application" + DEFAULT_RESCUE = [StandardError].freeze + + attr_accessor :logger, :debug_mode + + UnexpectedError = Class.new(Exception) + + def initialize(*subscribers, logger: nil) + @subscribers = subscribers.flatten + @logger = logger + @debug_mode = false + end + + # Evaluates the given block, reporting and swallowing any unhandled error. + # If no error is raised, returns the return value of the block. Otherwise, + # returns the result of +fallback.call+, or +nil+ if +fallback+ is not + # specified. + # + # # Will report a TypeError to all subscribers and return nil. + # Rails.error.handle do + # 1 + '1' + # end + # + # Can be restricted to handle only specific error classes: + # + # maybe_tags = Rails.error.handle(Redis::BaseError) { redis.get("tags") } + # + # ==== Options + # + # * +:severity+ - This value is passed along to subscribers to indicate how + # important the error report is. Can be +:error+, +:warning+, or +:info+. + # Defaults to +:warning+. + # + # * +:context+ - Extra information that is passed along to subscribers. For + # example: + # + # Rails.error.handle(context: { section: "admin" }) do + # # ... + # end + # + # * +:fallback+ - A callable that provides +handle+'s return value when an + # unhandled error is raised. For example: + # + # user = Rails.error.handle(fallback: -> { User.anonymous }) do + # User.find_by(params) + # end + # + # * +:source+ - This value is passed along to subscribers to indicate the + # source of the error. Subscribers can use this value to ignore certain + # errors. Defaults to "application". + def handle(*error_classes, severity: :warning, context: {}, fallback: nil, source: DEFAULT_SOURCE) + error_classes = DEFAULT_RESCUE if error_classes.empty? + yield + rescue *error_classes => error + report(error, handled: true, severity: severity, context: context, source: source) + fallback.call if fallback + end + + # Evaluates the given block, reporting and re-raising any unhandled error. + # If no error is raised, returns the return value of the block. + # + # # Will report a TypeError to all subscribers and re-raise it. + # Rails.error.record do + # 1 + '1' + # end + # + # Can be restricted to handle only specific error classes: + # + # tags = Rails.error.record(Redis::BaseError) { redis.get("tags") } + # + # ==== Options + # + # * +:severity+ - This value is passed along to subscribers to indicate how + # important the error report is. Can be +:error+, +:warning+, or +:info+. + # Defaults to +:error+. + # + # * +:context+ - Extra information that is passed along to subscribers. For + # example: + # + # Rails.error.record(context: { section: "admin" }) do + # # ... + # end + # + # * +:source+ - This value is passed along to subscribers to indicate the + # source of the error. Subscribers can use this value to ignore certain + # errors. Defaults to "application". + def record(*error_classes, severity: :error, context: {}, source: DEFAULT_SOURCE) + error_classes = DEFAULT_RESCUE if error_classes.empty? + yield + rescue *error_classes => error + report(error, handled: false, severity: severity, context: context, source: source) + raise + end + + # Either report the given error when in production, or raise it when in development or test. + # + # When called in production, after the error is reported, this method will return + # nil and execution will continue. + # + # When called in development, the original error is wrapped in a different error class to ensure + # it's not being rescued higher in the stack and will be surfaced to the developer. + # + # This method is intended for reporting violated assertions about preconditions, or similar + # cases that can and should be gracefully handled in production, but that aren't supposed to happen. + # + # The error can be either an exception instance or a String. + # + # example: + # + # def edit + # if published? + # Rails.error.unexpected("[BUG] Attempting to edit a published article, that shouldn't be possible") + # return false + # end + # # ... + # end + # + def unexpected(error, severity: :warning, context: {}, source: DEFAULT_SOURCE) + error = RuntimeError.new(error) if error.is_a?(String) + + if @debug_mode + ensure_backtrace(error) + raise UnexpectedError, "#{error.class.name}: #{error.message}", error.backtrace, cause: error + else + report(error, handled: true, severity: severity, context: context, source: source) + end + end + + # Register a new error subscriber. The subscriber must respond to + # + # report(Exception, handled: Boolean, severity: (:error OR :warning OR :info), context: Hash, source: String) + # + # The +report+ method should never raise an error. + def subscribe(subscriber) + unless subscriber.respond_to?(:report) + raise ArgumentError, "Error subscribers must respond to #report" + end + @subscribers << subscriber + end + + # Unregister an error subscriber. Accepts either a subscriber or a class. + # + # subscriber = MyErrorSubscriber.new + # Rails.error.subscribe(subscriber) + # + # Rails.error.unsubscribe(subscriber) + # # or + # Rails.error.unsubscribe(MyErrorSubscriber) + def unsubscribe(subscriber) + @subscribers.delete_if { |s| subscriber === s } + end + + # Prevent a subscriber from being notified of errors for the + # duration of the block. You may pass in the subscriber itself, or its class. + # + # This can be helpful for error reporting service integrations, when they wish + # to handle any errors higher in the stack. + def disable(subscriber) + disabled_subscribers = (ActiveSupport::IsolatedExecutionState[self] ||= []) + disabled_subscribers << subscriber + begin + yield + ensure + disabled_subscribers.delete(subscriber) + end + end + + # Update the execution context that is accessible to error subscribers. Any + # context passed to #handle, #record, or #report will be merged with the + # context set here. + # + # Rails.error.set_context(section: "checkout", user_id: @user.id) + # + def set_context(...) + ActiveSupport::ExecutionContext.set(...) + end + + # Report an error directly to subscribers. You can use this method when the + # block-based #handle and #record methods are not suitable. + # + # Rails.error.report(error) + # + # The +error+ argument must be an instance of Exception. + # + # Rails.error.report(Exception.new("Something went wrong")) + # + # Otherwise you can use #unexpected to report an error which does accept a + # string argument. + def report(error, handled: true, severity: handled ? :warning : :error, context: {}, source: DEFAULT_SOURCE) + return if error.instance_variable_defined?(:@__rails_error_reported) + ensure_backtrace(error) + + unless SEVERITIES.include?(severity) + raise ArgumentError, "severity must be one of #{SEVERITIES.map(&:inspect).join(", ")}, got: #{severity.inspect}" + end + + full_context = ActiveSupport::ExecutionContext.to_h.merge(context) + disabled_subscribers = ActiveSupport::IsolatedExecutionState[self] + @subscribers.each do |subscriber| + unless disabled_subscribers&.any? { |s| s === subscriber } + subscriber.report(error, handled: handled, severity: severity, context: full_context, source: source) + end + rescue => subscriber_error + if logger + logger.fatal( + "Error subscriber raised an error: #{subscriber_error.message} (#{subscriber_error.class})\n" + + subscriber_error.backtrace.join("\n") + ) + else + raise + end + end + + while error + unless error.frozen? + error.instance_variable_set(:@__rails_error_reported, true) + end + error = error.cause + end + + nil + end + + private + def ensure_backtrace(error) + return if error.frozen? # re-raising won't add a backtrace + return unless error.backtrace.nil? + + begin + # We could use Exception#set_backtrace, but until Ruby 3.4 + # it only support setting `Exception#backtrace` and not + # `Exception#backtrace_locations`. So raising the exception + # is a good way to build a real backtrace. + raise error + rescue error.class => error + end + + count = 0 + while error.backtrace_locations.first&.path == __FILE__ + count += 1 + error.backtrace_locations.shift + end + + error.backtrace.shift(count) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/error_reporter/test_helper.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/error_reporter/test_helper.rb new file mode 100644 index 00000000..bc67efa2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/error_reporter/test_helper.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveSupport::ErrorReporter::TestHelper # :nodoc: + class ErrorSubscriber + attr_reader :events + + def initialize + @events = [] + end + + def report(error, handled:, severity:, source:, context:) + @events << [error, handled, severity, source, context] + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/evented_file_update_checker.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/evented_file_update_checker.rb new file mode 100644 index 00000000..4bfc42d4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/evented_file_update_checker.rb @@ -0,0 +1,181 @@ +# frozen_string_literal: true + +gem "listen", "~> 3.5" +require "listen" + +require "pathname" +require "concurrent/atomic/atomic_boolean" + +module ActiveSupport + # Allows you to "listen" to changes in a file system. + # The evented file updater does not hit disk when checking for updates. + # Instead, it uses platform-specific file system events to trigger a change + # in state. + # + # The file checker takes an array of files to watch or a hash specifying directories + # and file extensions to watch. It also takes a block that is called when + # EventedFileUpdateChecker#execute is run or when EventedFileUpdateChecker#execute_if_updated + # is run and there have been changes to the file system. + # + # Example: + # + # checker = ActiveSupport::EventedFileUpdateChecker.new(["/tmp/foo"]) { puts "changed" } + # checker.updated? + # # => false + # checker.execute_if_updated + # # => nil + # + # FileUtils.touch("/tmp/foo") + # + # checker.updated? + # # => true + # checker.execute_if_updated + # # => "changed" + # + class EventedFileUpdateChecker # :nodoc: all + def initialize(files, dirs = {}, &block) + unless block + raise ArgumentError, "A block is required to initialize an EventedFileUpdateChecker" + end + + @block = block + @core = Core.new(files, dirs) + ObjectSpace.define_finalizer(self, @core.finalizer) + end + + def inspect + "# error + error_reporter&.report(error, handled: false, source: source) + raise + ensure + instance.complete! + end + end + + def self.perform # :nodoc: + instance = new + instance.run + begin + yield + ensure + instance.complete + end + end + + def self.error_reporter # :nodoc: + ActiveSupport.error_reporter + end + + def self.active_key # :nodoc: + @active_key ||= :"active_execution_wrapper_#{object_id}" + end + + def self.active? # :nodoc: + IsolatedExecutionState.key?(active_key) + end + + def run! # :nodoc: + IsolatedExecutionState[self.class.active_key] = self + run + end + + def run # :nodoc: + run_callbacks(:run) + end + + # Complete this in-flight execution. This method *must* be called + # exactly once on the result of any call to +run!+. + # + # Where possible, prefer +wrap+. + def complete! + complete + ensure + IsolatedExecutionState.delete(self.class.active_key) + end + + def complete # :nodoc: + run_callbacks(:complete) + end + + private + def hook_state + @_hook_state ||= {} + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/executor.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/executor.rb new file mode 100644 index 00000000..ce391b07 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/executor.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "active_support/execution_wrapper" + +module ActiveSupport + class Executor < ExecutionWrapper + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/executor/test_helper.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/executor/test_helper.rb new file mode 100644 index 00000000..97f489dc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/executor/test_helper.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module ActiveSupport::Executor::TestHelper # :nodoc: + def run(...) + Rails.application.executor.perform { super } + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/file_update_checker.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/file_update_checker.rb new file mode 100644 index 00000000..608755d0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/file_update_checker.rb @@ -0,0 +1,164 @@ +# frozen_string_literal: true + +require "active_support/core_ext/time/calculations" + +module ActiveSupport + # = \File Update Checker + # + # FileUpdateChecker specifies the API used by \Rails to watch files + # and control reloading. The API depends on four methods: + # + # * +initialize+ which expects two parameters and one block as + # described below. + # + # * +updated?+ which returns a boolean if there were updates in + # the filesystem or not. + # + # * +execute+ which executes the given block on initialization + # and updates the latest watched files and timestamp. + # + # * +execute_if_updated+ which just executes the block if it was updated. + # + # After initialization, a call to +execute_if_updated+ must execute + # the block only if there was really a change in the filesystem. + # + # This class is used by \Rails to reload the I18n framework whenever + # they are changed upon a new request. + # + # i18n_reloader = ActiveSupport::FileUpdateChecker.new(paths) do + # I18n.reload! + # end + # + # ActiveSupport::Reloader.to_prepare do + # i18n_reloader.execute_if_updated + # end + class FileUpdateChecker + # It accepts two parameters on initialization. The first is an array + # of files and the second is an optional hash of directories. The hash must + # have directories as keys and the value is an array of extensions to be + # watched under that directory. + # + # This method must also receive a block that will be called once a path + # changes. The array of files and list of directories cannot be changed + # after FileUpdateChecker has been initialized. + def initialize(files, dirs = {}, &block) + unless block + raise ArgumentError, "A block is required to initialize a FileUpdateChecker" + end + + @files = files.freeze + @glob = compile_glob(dirs) + @block = block + + @watched = nil + @updated_at = nil + + @last_watched = watched + @last_update_at = updated_at(@last_watched) + end + + # Check if any of the entries were updated. If so, the watched and/or + # updated_at values are cached until the block is executed via +execute+ + # or +execute_if_updated+. + def updated? + current_watched = watched + if @last_watched.size != current_watched.size + @watched = current_watched + true + else + current_updated_at = updated_at(current_watched) + if @last_update_at < current_updated_at + @watched = current_watched + @updated_at = current_updated_at + true + else + false + end + end + end + + # Executes the given block and updates the latest watched files and + # timestamp. + def execute + @last_watched = watched + @last_update_at = updated_at(@last_watched) + @block.call + ensure + @watched = nil + @updated_at = nil + end + + # Execute the block given if updated. + def execute_if_updated + if updated? + yield if block_given? + execute + true + else + false + end + end + + private + def watched + @watched || begin + all = @files.select { |f| File.exist?(f) } + all.concat(Dir[@glob]) if @glob + all.tap(&:uniq!) + end + end + + def updated_at(paths) + @updated_at || max_mtime(paths) || Time.at(0) + end + + # This method returns the maximum mtime of the files in +paths+, or +nil+ + # if the array is empty. + # + # Files with a mtime in the future are ignored. Such abnormal situation + # can happen for example if the user changes the clock by hand. It is + # healthy to consider this edge case because with mtimes in the future + # reloading is not triggered. + def max_mtime(paths) + time_now = Time.now + max_mtime = nil + + # Time comparisons are performed with #compare_without_coercion because + # AS redefines these operators in a way that is much slower and does not + # bring any benefit in this particular code. + # + # Read t1.compare_without_coercion(t2) < 0 as t1 < t2. + paths.each do |path| + mtime = File.mtime(path) + + next if time_now.compare_without_coercion(mtime) < 0 + + if max_mtime.nil? || max_mtime.compare_without_coercion(mtime) < 0 + max_mtime = mtime + end + end + + max_mtime + end + + def compile_glob(hash) + hash.freeze # Freeze so changes aren't accidentally pushed + return if hash.empty? + + globs = hash.map do |key, value| + "#{escape(key)}/**/*#{compile_ext(value)}" + end + "{#{globs.join(",")}}" + end + + def escape(key) + key.gsub(",", '\,') + end + + def compile_ext(array) + array = Array(array) + return if array.empty? + ".{#{array.join(",")}}" + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/fork_tracker.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/fork_tracker.rb new file mode 100644 index 00000000..2ff8fc5b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/fork_tracker.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module ActiveSupport + module ForkTracker # :nodoc: + module CoreExt + def _fork + pid = super + if pid == 0 + ForkTracker.after_fork_callback + end + pid + end + end + + @pid = Process.pid + @callbacks = [] + + class << self + def after_fork_callback + new_pid = Process.pid + if @pid != new_pid + @callbacks.each(&:call) + @pid = new_pid + end + end + + def hook! + ::Process.singleton_class.prepend(CoreExt) + end + + def after_fork(&block) + @callbacks << block + block + end + + def unregister(callback) + @callbacks.delete(callback) + end + end + end +end + +ActiveSupport::ForkTracker.hook! diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/gem_version.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/gem_version.rb new file mode 100644 index 00000000..0386e59a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/gem_version.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveSupport + # Returns the currently loaded version of Active Support as a +Gem::Version+. + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 8 + MINOR = 0 + TINY = 2 + PRE = nil + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/gzip.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/gzip.rb new file mode 100644 index 00000000..f6ecc29c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/gzip.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "zlib" +require "stringio" + +module ActiveSupport + # = Active Support \Gzip + # + # A convenient wrapper for the zlib standard library that allows + # compression/decompression of strings with gzip. + # + # gzip = ActiveSupport::Gzip.compress('compress me!') + # # => "\x1F\x8B\b\x00o\x8D\xCDO\x00\x03K\xCE\xCF-(J-.V\xC8MU\x04\x00R>n\x83\f\x00\x00\x00" + # + # ActiveSupport::Gzip.decompress(gzip) + # # => "compress me!" + module Gzip + class Stream < StringIO + def initialize(*) + super + set_encoding "BINARY" + end + def close; rewind; end + end + + # Decompresses a gzipped string. + def self.decompress(source) + Zlib::GzipReader.wrap(StringIO.new(source), &:read) + end + + # Compresses a string using gzip. + def self.compress(source, level = Zlib::DEFAULT_COMPRESSION, strategy = Zlib::DEFAULT_STRATEGY) + output = Stream.new + gz = Zlib::GzipWriter.new(output, level, strategy) + gz.write(source) + gz.close + output.string + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/hash_with_indifferent_access.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/hash_with_indifferent_access.rb new file mode 100644 index 00000000..4fa4e3b0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/hash_with_indifferent_access.rb @@ -0,0 +1,441 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/hash/reverse_merge" +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/slice" + +module ActiveSupport + # = \Hash With Indifferent Access + # + # Implements a hash where keys :foo and "foo" are considered + # to be the same. + # + # rgb = ActiveSupport::HashWithIndifferentAccess.new + # + # rgb[:black] = '#000000' + # rgb[:black] # => '#000000' + # rgb['black'] # => '#000000' + # + # rgb['white'] = '#FFFFFF' + # rgb[:white] # => '#FFFFFF' + # rgb['white'] # => '#FFFFFF' + # + # Internally symbols are mapped to strings when used as keys in the entire + # writing interface (calling []=, merge, etc). This + # mapping belongs to the public interface. For example, given: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1) + # + # You are guaranteed that the key is returned as a string: + # + # hash.keys # => ["a"] + # + # Technically other types of keys are accepted: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1) + # hash[0] = 0 + # hash # => {"a"=>1, 0=>0} + # + # but this class is intended for use cases where strings or symbols are the + # expected keys and it is convenient to understand both as the same. For + # example the +params+ hash in Ruby on \Rails. + # + # Note that core extensions define Hash#with_indifferent_access: + # + # rgb = { black: '#000000', white: '#FFFFFF' }.with_indifferent_access + # + # which may be handy. + # + # To access this class outside of \Rails, require the core extension with: + # + # require "active_support/core_ext/hash/indifferent_access" + # + # which will, in turn, require this file. + class HashWithIndifferentAccess < Hash + # Returns +true+ so that Array#extract_options! finds members of + # this class. + def extractable_options? + true + end + + def with_indifferent_access + dup + end + + def nested_under_indifferent_access + self + end + + def initialize(constructor = nil) + if constructor.respond_to?(:to_hash) + super() + update(constructor) + + hash = constructor.is_a?(Hash) ? constructor : constructor.to_hash + self.default = hash.default if hash.default + self.default_proc = hash.default_proc if hash.default_proc + elsif constructor.nil? + super() + else + super(constructor) + end + end + + def self.[](*args) + new.merge!(Hash[*args]) + end + + alias_method :regular_writer, :[]= unless method_defined?(:regular_writer) + alias_method :regular_update, :update unless method_defined?(:regular_update) + + # Assigns a new value to the hash: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new + # hash[:key] = 'value' + # + # This value can be later fetched using either +:key+ or 'key'. + def []=(key, value) + regular_writer(convert_key(key), convert_value(value, conversion: :assignment)) + end + + alias_method :store, :[]= + + # Updates the receiver in-place, merging in the hashes passed as arguments: + # + # hash_1 = ActiveSupport::HashWithIndifferentAccess.new + # hash_1[:key] = 'value' + # + # hash_2 = ActiveSupport::HashWithIndifferentAccess.new + # hash_2[:key] = 'New Value!' + # + # hash_1.update(hash_2) # => {"key"=>"New Value!"} + # + # hash = ActiveSupport::HashWithIndifferentAccess.new + # hash.update({ "a" => 1 }, { "b" => 2 }) # => { "a" => 1, "b" => 2 } + # + # The arguments can be either an + # +ActiveSupport::HashWithIndifferentAccess+ or a regular +Hash+. + # In either case the merge respects the semantics of indifferent access. + # + # If the argument is a regular hash with keys +:key+ and "key" only one + # of the values end up in the receiver, but which one is unspecified. + # + # When given a block, the value for duplicated keys will be determined + # by the result of invoking the block with the duplicated key, the value + # in the receiver, and the value in +other_hash+. The rules for duplicated + # keys follow the semantics of indifferent access: + # + # hash_1[:key] = 10 + # hash_2['key'] = 12 + # hash_1.update(hash_2) { |key, old, new| old + new } # => {"key"=>22} + def update(*other_hashes, &block) + if other_hashes.size == 1 + update_with_single_argument(other_hashes.first, block) + else + other_hashes.each do |other_hash| + update_with_single_argument(other_hash, block) + end + end + self + end + + alias_method :merge!, :update + + # Checks the hash for a key matching the argument passed in: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new + # hash['key'] = 'value' + # hash.key?(:key) # => true + # hash.key?('key') # => true + def key?(key) + super(convert_key(key)) + end + + alias_method :include?, :key? + alias_method :has_key?, :key? + alias_method :member?, :key? + + # Same as Hash#[] where the key passed as argument can be + # either a string or a symbol: + # + # counters = ActiveSupport::HashWithIndifferentAccess.new + # counters[:foo] = 1 + # + # counters['foo'] # => 1 + # counters[:foo] # => 1 + # counters[:zoo] # => nil + def [](key) + super(convert_key(key)) + end + + # Same as Hash#assoc where the key passed as argument can be + # either a string or a symbol: + # + # counters = ActiveSupport::HashWithIndifferentAccess.new + # counters[:foo] = 1 + # + # counters.assoc('foo') # => ["foo", 1] + # counters.assoc(:foo) # => ["foo", 1] + # counters.assoc(:zoo) # => nil + def assoc(key) + super(convert_key(key)) + end + + # Same as Hash#fetch where the key passed as argument can be + # either a string or a symbol: + # + # counters = ActiveSupport::HashWithIndifferentAccess.new + # counters[:foo] = 1 + # + # counters.fetch('foo') # => 1 + # counters.fetch(:bar, 0) # => 0 + # counters.fetch(:bar) { |key| 0 } # => 0 + # counters.fetch(:zoo) # => KeyError: key not found: "zoo" + def fetch(key, *extras) + super(convert_key(key), *extras) + end + + # Same as Hash#dig where the key passed as argument can be + # either a string or a symbol: + # + # counters = ActiveSupport::HashWithIndifferentAccess.new + # counters[:foo] = { bar: 1 } + # + # counters.dig('foo', 'bar') # => 1 + # counters.dig(:foo, :bar) # => 1 + # counters.dig(:zoo) # => nil + def dig(*args) + args[0] = convert_key(args[0]) if args.size > 0 + super(*args) + end + + # Same as Hash#default where the key passed as argument can be + # either a string or a symbol: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new(1) + # hash.default # => 1 + # + # hash = ActiveSupport::HashWithIndifferentAccess.new { |hash, key| key } + # hash.default # => nil + # hash.default('foo') # => 'foo' + # hash.default(:foo) # => 'foo' + def default(key = (no_key = true)) + if no_key + super() + else + super(convert_key(key)) + end + end + + # Returns an array of the values at the specified indices: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new + # hash[:a] = 'x' + # hash[:b] = 'y' + # hash.values_at('a', 'b') # => ["x", "y"] + def values_at(*keys) + keys.map! { |key| convert_key(key) } + super + end + + # Returns an array of the values at the specified indices, but also + # raises an exception when one of the keys can't be found. + # + # hash = ActiveSupport::HashWithIndifferentAccess.new + # hash[:a] = 'x' + # hash[:b] = 'y' + # hash.fetch_values('a', 'b') # => ["x", "y"] + # hash.fetch_values('a', 'c') { |key| 'z' } # => ["x", "z"] + # hash.fetch_values('a', 'c') # => KeyError: key not found: "c" + def fetch_values(*indices, &block) + indices.map! { |key| convert_key(key) } + super + end + + # Returns a shallow copy of the hash. + # + # hash = ActiveSupport::HashWithIndifferentAccess.new({ a: { b: 'b' } }) + # dup = hash.dup + # dup[:a][:c] = 'c' + # + # hash[:a][:c] # => "c" + # dup[:a][:c] # => "c" + def dup + self.class.new(self).tap do |new_hash| + set_defaults(new_hash) + end + end + + # This method has the same semantics of +update+, except it does not + # modify the receiver but rather returns a new hash with indifferent + # access with the result of the merge. + def merge(*hashes, &block) + dup.update(*hashes, &block) + end + + # Like +merge+ but the other way around: Merges the receiver into the + # argument and returns a new hash with indifferent access as result: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new + # hash['a'] = nil + # hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1} + def reverse_merge(other_hash) + super(self.class.new(other_hash)) + end + alias_method :with_defaults, :reverse_merge + + # Same semantics as +reverse_merge+ but modifies the receiver in-place. + def reverse_merge!(other_hash) + super(self.class.new(other_hash)) + end + alias_method :with_defaults!, :reverse_merge! + + # Replaces the contents of this hash with other_hash. + # + # h = { "a" => 100, "b" => 200 } + # h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400} + def replace(other_hash) + super(self.class.new(other_hash)) + end + + # Removes the specified key from the hash. + def delete(key) + super(convert_key(key)) + end + + # Returns a hash with indifferent access that includes everything except given keys. + # hash = { a: "x", b: "y", c: 10 }.with_indifferent_access + # hash.except(:a, "b") # => {c: 10}.with_indifferent_access + # hash # => { a: "x", b: "y", c: 10 }.with_indifferent_access + def except(*keys) + dup.except!(*keys) + end + alias_method :without, :except + + undef :symbolize_keys! + undef :deep_symbolize_keys! + def symbolize_keys; to_hash.symbolize_keys! end + alias_method :to_options, :symbolize_keys + def deep_symbolize_keys; to_hash.deep_symbolize_keys! end + def to_options!; self end + + def select(*args, &block) + return to_enum(:select) unless block_given? + dup.tap { |hash| hash.select!(*args, &block) } + end + + def reject(*args, &block) + return to_enum(:reject) unless block_given? + dup.tap { |hash| hash.reject!(*args, &block) } + end + + def transform_values(&block) + return to_enum(:transform_values) unless block_given? + dup.tap { |hash| hash.transform_values!(&block) } + end + + NOT_GIVEN = Object.new # :nodoc: + + def transform_keys(hash = NOT_GIVEN, &block) + return to_enum(:transform_keys) if NOT_GIVEN.equal?(hash) && !block_given? + dup.tap { |h| h.transform_keys!(hash, &block) } + end + + def transform_keys!(hash = NOT_GIVEN, &block) + return to_enum(:transform_keys!) if NOT_GIVEN.equal?(hash) && !block_given? + + if hash.nil? + super + elsif NOT_GIVEN.equal?(hash) + keys.each { |key| self[yield(key)] = delete(key) } + elsif block_given? + keys.each { |key| self[hash[key] || yield(key)] = delete(key) } + else + keys.each { |key| self[hash[key] || key] = delete(key) } + end + + self + end + + def slice(*keys) + keys.map! { |key| convert_key(key) } + self.class.new(super) + end + + def slice!(*keys) + keys.map! { |key| convert_key(key) } + super + end + + def compact + dup.tap(&:compact!) + end + + # Convert to a regular hash with string keys. + def to_hash + copy = Hash[self] + copy.transform_values! { |v| convert_value_to_hash(v) } + set_defaults(copy) + copy + end + + def to_proc + proc { |key| self[key] } + end + + private + def convert_key(key) + Symbol === key ? key.name : key + end + + def convert_value(value, conversion: nil) + if value.is_a? Hash + value.nested_under_indifferent_access + elsif value.is_a?(Array) + if conversion != :assignment || value.frozen? + value = value.dup + end + value.map! { |e| convert_value(e, conversion: conversion) } + else + value + end + end + + def convert_value_to_hash(value) + if value.is_a? Hash + value.to_hash + elsif value.is_a?(Array) + value.map { |e| convert_value_to_hash(e) } + else + value + end + end + + + def set_defaults(target) + if default_proc + target.default_proc = default_proc.dup + else + target.default = default + end + end + + def update_with_single_argument(other_hash, block) + if other_hash.is_a? HashWithIndifferentAccess + regular_update(other_hash, &block) + else + other_hash.to_hash.each_pair do |key, value| + if block && key?(key) + value = block.call(convert_key(key), self[key], value) + end + regular_writer(convert_key(key), convert_value(value)) + end + end + end + end +end + +# :stopdoc: + +HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/html_safe_translation.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/html_safe_translation.rb new file mode 100644 index 00000000..69ff23dd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/html_safe_translation.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module ActiveSupport + module HtmlSafeTranslation # :nodoc: + extend self + + def translate(key, **options) + if html_safe_translation_key?(key) + html_safe_options = html_escape_translation_options(options) + + exception = false + + exception_handler = ->(*args) do + exception = true + I18n.exception_handler.call(*args) + end + + translation = I18n.translate(key, **html_safe_options, exception_handler: exception_handler) + + if exception + translation + else + html_safe_translation(translation) + end + else + I18n.translate(key, **options) + end + end + + def html_safe_translation_key?(key) + /(?:_|\b)html\z/.match?(key) + end + + private + def html_escape_translation_options(options) + options.each do |name, value| + unless i18n_option?(name) || (name == :count && value.is_a?(Numeric)) + options[name] = ERB::Util.html_escape(value.to_s) + end + end + end + + def i18n_option?(name) + (@i18n_option_names ||= I18n::RESERVED_KEYS.to_set).include?(name) + end + + + def html_safe_translation(translation) + if translation.respond_to?(:map) + translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element } + else + translation.respond_to?(:html_safe) ? translation.html_safe : translation + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/i18n.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/i18n.rb new file mode 100644 index 00000000..f4f06d43 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/i18n.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/deep_merge" +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/slice" +begin + require "i18n" + require "i18n/backend/fallbacks" +rescue LoadError => e + warn "The i18n gem is not available. Please add it to your Gemfile and run bundle install" + raise e +end +require "active_support/lazy_load_hooks" + +ActiveSupport.run_load_hooks(:i18n) +I18n.load_path << File.expand_path("locale/en.yml", __dir__) +I18n.load_path << File.expand_path("locale/en.rb", __dir__) diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/i18n_railtie.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/i18n_railtie.rb new file mode 100644 index 00000000..de2ac078 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/i18n_railtie.rb @@ -0,0 +1,140 @@ +# frozen_string_literal: true + +require "active_support" +require "active_support/core_ext/array/wrap" + +# :enddoc: + +module I18n + class Railtie < Rails::Railtie + config.i18n = ActiveSupport::OrderedOptions.new + config.i18n.railties_load_path = [] + config.i18n.load_path = [] + config.i18n.fallbacks = ActiveSupport::OrderedOptions.new + + config.eager_load_namespaces << I18n + + # Make sure i18n is ready before eager loading, in case any eager loaded + # code needs it. + config.before_eager_load do |app| + I18n::Railtie.initialize_i18n(app) + end + + # i18n initialization needs to run after application initialization, since + # initializers may configure i18n. + # + # If the application eager loaded, this was done on before_eager_load. The + # hook is still OK, though, because initialize_i18n is idempotent. + config.after_initialize do |app| + I18n::Railtie.initialize_i18n(app) + end + + @i18n_inited = false + + # Setup i18n configuration. + def self.initialize_i18n(app) + return if @i18n_inited + + fallbacks = app.config.i18n.delete(:fallbacks) + + # Avoid issues with setting the default_locale by disabling available locales + # check while configuring. + enforce_available_locales = app.config.i18n.delete(:enforce_available_locales) + enforce_available_locales = I18n.enforce_available_locales if enforce_available_locales.nil? + I18n.enforce_available_locales = false + + reloadable_paths = [] + app.config.i18n.each do |setting, value| + case setting + when :railties_load_path + reloadable_paths = value + app.config.i18n.load_path.unshift(*value.flat_map(&:existent)) + when :load_path + I18n.load_path += value + when :raise_on_missing_translations + strict = value == :strict + setup_raise_on_missing_translations_config(app, strict) + else + I18n.public_send("#{setting}=", value) + end + end + + init_fallbacks(fallbacks) if fallbacks && validate_fallbacks(fallbacks) + + # Restore available locales check so it will take place from now on. + I18n.enforce_available_locales = enforce_available_locales + + if app.config.reloading_enabled? + directories = watched_dirs_with_extensions(reloadable_paths) + root_load_paths = I18n.load_path.select { |path| path.to_s.start_with?(Rails.root.to_s) } + reloader = app.config.file_watcher.new(root_load_paths, directories) do + I18n.load_path.delete_if { |path| path.to_s.start_with?(Rails.root.to_s) && !File.exist?(path) } + I18n.load_path |= reloadable_paths.flat_map(&:existent) + end + + app.reloaders << reloader + app.reloader.to_run do + reloader.execute_if_updated { require_unload_lock! } + end + end + + @i18n_inited = true + end + + def self.setup_raise_on_missing_translations_config(app, strict) + ActiveSupport.on_load(:action_view) do + ActionView::Helpers::TranslationHelper.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations + end + + ActiveSupport.on_load(:active_model_translation) do + ActiveModel::Translation.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations if strict + end + + if app.config.i18n.raise_on_missing_translations && + I18n.exception_handler.is_a?(I18n::ExceptionHandler) # Only override the i18n gem's default exception handler. + + I18n.exception_handler = ->(exception, *) { + exception = exception.to_exception if exception.is_a?(I18n::MissingTranslation) + raise exception + } + end + end + + def self.include_fallbacks_module + I18n.backend.class.include(I18n::Backend::Fallbacks) + end + + def self.init_fallbacks(fallbacks) + include_fallbacks_module + + args = \ + case fallbacks + when ActiveSupport::OrderedOptions + [*(fallbacks[:defaults] || []) << fallbacks[:map]].compact + when Hash, Array + Array.wrap(fallbacks) + else # TrueClass + [I18n.default_locale] + end + + I18n.fallbacks = I18n::Locale::Fallbacks.new(*args) + end + + def self.validate_fallbacks(fallbacks) + case fallbacks + when ActiveSupport::OrderedOptions + !fallbacks.empty? + when TrueClass, Array, Hash + true + else + raise "Unexpected fallback type #{fallbacks.inspect}" + end + end + + def self.watched_dirs_with_extensions(paths) + paths.each_with_object({}) do |path, result| + result[path.absolute_current] = path.extensions + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/inflections.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/inflections.rb new file mode 100644 index 00000000..baf1cb30 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/inflections.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require "active_support/inflector/inflections" + +#-- +# Defines the standard inflection rules. These are the starting point for +# new projects and are not considered complete. The current set of inflection +# rules is frozen. This means, we do not change them to become more complete. +# This is a safety measure to keep existing applications from breaking. +#++ +module ActiveSupport + Inflector.inflections(:en) do |inflect| + inflect.plural(/$/, "s") + inflect.plural(/s$/i, "s") + inflect.plural(/^(ax|test)is$/i, '\1es') + inflect.plural(/(octop|vir)us$/i, '\1i') + inflect.plural(/(octop|vir)i$/i, '\1i') + inflect.plural(/(alias|status)$/i, '\1es') + inflect.plural(/(bu)s$/i, '\1ses') + inflect.plural(/(buffal|tomat)o$/i, '\1oes') + inflect.plural(/([ti])um$/i, '\1a') + inflect.plural(/([ti])a$/i, '\1a') + inflect.plural(/sis$/i, "ses") + inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves') + inflect.plural(/(hive)$/i, '\1s') + inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies') + inflect.plural(/(x|ch|ss|sh)$/i, '\1es') + inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices') + inflect.plural(/^(m|l)ouse$/i, '\1ice') + inflect.plural(/^(m|l)ice$/i, '\1ice') + inflect.plural(/^(ox)$/i, '\1en') + inflect.plural(/^(oxen)$/i, '\1') + inflect.plural(/(quiz)$/i, '\1zes') + + inflect.singular(/s$/i, "") + inflect.singular(/(ss)$/i, '\1') + inflect.singular(/(n)ews$/i, '\1ews') + inflect.singular(/([ti])a$/i, '\1um') + inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1sis') + inflect.singular(/(^analy)(sis|ses)$/i, '\1sis') + inflect.singular(/([^f])ves$/i, '\1fe') + inflect.singular(/(hive)s$/i, '\1') + inflect.singular(/(tive)s$/i, '\1') + inflect.singular(/([lr])ves$/i, '\1f') + inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y') + inflect.singular(/(s)eries$/i, '\1eries') + inflect.singular(/(m)ovies$/i, '\1ovie') + inflect.singular(/(x|ch|ss|sh)es$/i, '\1') + inflect.singular(/^(m|l)ice$/i, '\1ouse') + inflect.singular(/(bus)(es)?$/i, '\1') + inflect.singular(/(o)es$/i, '\1') + inflect.singular(/(shoe)s$/i, '\1') + inflect.singular(/(cris|test)(is|es)$/i, '\1is') + inflect.singular(/^(a)x[ie]s$/i, '\1xis') + inflect.singular(/(octop|vir)(us|i)$/i, '\1us') + inflect.singular(/(alias|status)(es)?$/i, '\1') + inflect.singular(/^(ox)en/i, '\1') + inflect.singular(/(vert|ind)ices$/i, '\1ex') + inflect.singular(/(matr)ices$/i, '\1ix') + inflect.singular(/(quiz)zes$/i, '\1') + inflect.singular(/(database)s$/i, '\1') + + inflect.irregular("person", "people") + inflect.irregular("man", "men") + inflect.irregular("child", "children") + inflect.irregular("sex", "sexes") + inflect.irregular("move", "moves") + inflect.irregular("zombie", "zombies") + + inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police)) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/inflector.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/inflector.rb new file mode 100644 index 00000000..d77f04c9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/inflector.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# in case active_support/inflector is required without the rest of active_support +require "active_support/inflector/inflections" +require "active_support/inflector/transliterate" +require "active_support/inflector/methods" + +require "active_support/inflections" +require "active_support/core_ext/string/inflections" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/inflector/inflections.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/inflector/inflections.rb new file mode 100644 index 00000000..5a1b64a5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/inflector/inflections.rb @@ -0,0 +1,273 @@ +# frozen_string_literal: true + +require "concurrent/map" +require "active_support/i18n" + +module ActiveSupport + module Inflector + extend self + + # = Active Support \Inflections + # + # A singleton instance of this class is yielded by Inflector.inflections, + # which can then be used to specify additional inflection rules. If passed + # an optional locale, rules for other languages can be specified. The + # default locale is :en. Only rules for English are provided. + # + # ActiveSupport::Inflector.inflections(:en) do |inflect| + # inflect.plural /^(ox)$/i, '\1\2en' + # inflect.singular /^(ox)en/i, '\1' + # + # inflect.irregular 'cactus', 'cacti' + # + # inflect.uncountable 'equipment' + # end + # + # New rules are added at the top. So in the example above, the irregular + # rule for cactus will now be the first of the pluralization and + # singularization rules that is runs. This guarantees that your rules run + # before any of the rules that may already have been loaded. + class Inflections + @__instance__ = Concurrent::Map.new + + class Uncountables < Array + def initialize + @regex_array = [] + super + end + + def delete(entry) + super entry + @regex_array.delete(to_regex(entry)) + end + + def <<(*word) + add(word) + end + + def add(words) + words = words.flatten.map(&:downcase) + concat(words) + @regex_array += words.map { |word| to_regex(word) } + self + end + + def uncountable?(str) + @regex_array.any? { |regex| regex.match? str } + end + + private + def to_regex(string) + /\b#{::Regexp.escape(string)}\Z/i + end + end + + def self.instance(locale = :en) + @__instance__[locale] ||= new + end + + def self.instance_or_fallback(locale) + I18n.fallbacks[locale].each do |k| + return @__instance__[k] if @__instance__.key?(k) + end + instance(locale) + end + + attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms + + attr_reader :acronyms_camelize_regex, :acronyms_underscore_regex # :nodoc: + + def initialize + @plurals, @singulars, @uncountables, @humans, @acronyms = [], [], Uncountables.new, [], {} + define_acronym_regex_patterns + end + + # Private, for the test suite. + def initialize_dup(orig) # :nodoc: + %w(plurals singulars uncountables humans acronyms).each do |scope| + instance_variable_set("@#{scope}", orig.public_send(scope).dup) + end + define_acronym_regex_patterns + end + + # Specifies a new acronym. An acronym must be specified as it will appear + # in a camelized string. An underscore string that contains the acronym + # will retain the acronym when passed to +camelize+, +humanize+, or + # +titleize+. A camelized string that contains the acronym will maintain + # the acronym when titleized or humanized, and will convert the acronym + # into a non-delimited single lowercase word when passed to +underscore+. + # + # acronym 'HTML' + # titleize 'html' # => 'HTML' + # camelize 'html' # => 'HTML' + # underscore 'MyHTML' # => 'my_html' + # + # The acronym, however, must occur as a delimited unit and not be part of + # another word for conversions to recognize it: + # + # acronym 'HTTP' + # camelize 'my_http_delimited' # => 'MyHTTPDelimited' + # camelize 'https' # => 'Https', not 'HTTPs' + # underscore 'HTTPS' # => 'http_s', not 'https' + # + # acronym 'HTTPS' + # camelize 'https' # => 'HTTPS' + # underscore 'HTTPS' # => 'https' + # + # Note: Acronyms that are passed to +pluralize+ will no longer be + # recognized, since the acronym will not occur as a delimited unit in the + # pluralized result. To work around this, you must specify the pluralized + # form as an acronym as well: + # + # acronym 'API' + # camelize(pluralize('api')) # => 'Apis' + # + # acronym 'APIs' + # camelize(pluralize('api')) # => 'APIs' + # + # +acronym+ may be used to specify any word that contains an acronym or + # otherwise needs to maintain a non-standard capitalization. The only + # restriction is that the word must begin with a capital letter. + # + # acronym 'RESTful' + # underscore 'RESTful' # => 'restful' + # underscore 'RESTfulController' # => 'restful_controller' + # titleize 'RESTfulController' # => 'RESTful Controller' + # camelize 'restful' # => 'RESTful' + # camelize 'restful_controller' # => 'RESTfulController' + # + # acronym 'McDonald' + # underscore 'McDonald' # => 'mcdonald' + # camelize 'mcdonald' # => 'McDonald' + def acronym(word) + @acronyms[word.downcase] = word + define_acronym_regex_patterns + end + + # Specifies a new pluralization rule and its replacement. The rule can + # either be a string or a regular expression. The replacement should + # always be a string that may include references to the matched data from + # the rule. + def plural(rule, replacement) + @uncountables.delete(rule) if rule.is_a?(String) + @uncountables.delete(replacement) + @plurals.prepend([rule, replacement]) + end + + # Specifies a new singularization rule and its replacement. The rule can + # either be a string or a regular expression. The replacement should + # always be a string that may include references to the matched data from + # the rule. + def singular(rule, replacement) + @uncountables.delete(rule) if rule.is_a?(String) + @uncountables.delete(replacement) + @singulars.prepend([rule, replacement]) + end + + # Specifies a new irregular that applies to both pluralization and + # singularization at the same time. This can only be used for strings, not + # regular expressions. You simply pass the irregular in singular and + # plural form. + # + # irregular 'cactus', 'cacti' + # irregular 'person', 'people' + def irregular(singular, plural) + @uncountables.delete(singular) + @uncountables.delete(plural) + + s0 = singular[0] + srest = singular[1..-1] + + p0 = plural[0] + prest = plural[1..-1] + + if s0.upcase == p0.upcase + plural(/(#{s0})#{srest}$/i, '\1' + prest) + plural(/(#{p0})#{prest}$/i, '\1' + prest) + + singular(/(#{s0})#{srest}$/i, '\1' + srest) + singular(/(#{p0})#{prest}$/i, '\1' + srest) + else + plural(/#{s0.upcase}(?i)#{srest}$/, p0.upcase + prest) + plural(/#{s0.downcase}(?i)#{srest}$/, p0.downcase + prest) + plural(/#{p0.upcase}(?i)#{prest}$/, p0.upcase + prest) + plural(/#{p0.downcase}(?i)#{prest}$/, p0.downcase + prest) + + singular(/#{s0.upcase}(?i)#{srest}$/, s0.upcase + srest) + singular(/#{s0.downcase}(?i)#{srest}$/, s0.downcase + srest) + singular(/#{p0.upcase}(?i)#{prest}$/, s0.upcase + srest) + singular(/#{p0.downcase}(?i)#{prest}$/, s0.downcase + srest) + end + end + + # Specifies words that are uncountable and should not be inflected. + # + # uncountable 'money' + # uncountable 'money', 'information' + # uncountable %w( money information rice ) + def uncountable(*words) + @uncountables.add(words) + end + + # Specifies a humanized form of a string by a regular expression rule or + # by a string mapping. When using a regular expression based replacement, + # the normal humanize formatting is called after the replacement. When a + # string is used, the human form should be specified as desired (example: + # 'The name', not 'the_name'). + # + # human /_cnt$/i, '\1_count' + # human 'legacy_col_person_name', 'Name' + def human(rule, replacement) + @humans.prepend([rule, replacement]) + end + + # Clears the loaded inflections within a given scope (default is + # :all). Give the scope as a symbol of the inflection type, the + # options are: :plurals, :singulars, :uncountables, + # :humans, :acronyms. + # + # clear :all + # clear :plurals + def clear(scope = :all) + case scope + when :all + clear(:acronyms) + clear(:plurals) + clear(:singulars) + clear(:uncountables) + clear(:humans) + when :acronyms + @acronyms = {} + define_acronym_regex_patterns + when :uncountables + @uncountables = Uncountables.new + when :plurals, :singulars, :humans + instance_variable_set "@#{scope}", [] + end + end + + private + def define_acronym_regex_patterns + @acronym_regex = @acronyms.empty? ? /(?=a)b/ : /#{@acronyms.values.join("|")}/ + @acronyms_camelize_regex = /^(?:#{@acronym_regex}(?=\b|[A-Z_])|\w)/ + @acronyms_underscore_regex = /(?:(?<=([A-Za-z\d]))|\b)(#{@acronym_regex})(?=\b|[^a-z])/ + end + end + + # Yields a singleton instance of Inflector::Inflections so you can specify + # additional inflector rules. If passed an optional locale, rules for other + # languages can be specified. If not specified, defaults to :en. + # Only rules for English are provided. + # + # ActiveSupport::Inflector.inflections(:en) do |inflect| + # inflect.uncountable 'rails' + # end + def inflections(locale = :en) + if block_given? + yield Inflections.instance(locale) + else + Inflections.instance_or_fallback(locale) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/inflector/methods.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/inflector/methods.rb new file mode 100644 index 00000000..e61afcf5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/inflector/methods.rb @@ -0,0 +1,387 @@ +# frozen_string_literal: true + +require "active_support/inflections" + +module ActiveSupport + # = Active Support \Inflector + # + # The Inflector transforms words from singular to plural, class names to table + # names, modularized class names to ones without, and class names to foreign + # keys. The default inflections for pluralization, singularization, and + # uncountable words are kept in inflections.rb. + # + # The \Rails core team has stated patches for the inflections library will not + # be accepted in order to avoid breaking legacy applications which may be + # relying on errant inflections. If you discover an incorrect inflection and + # require it for your application or wish to define rules for languages other + # than English, please correct or add them yourself (explained below). + module Inflector + extend self + + # Returns the plural form of the word in the string. + # + # If passed an optional +locale+ parameter, the word will be + # pluralized using rules defined for that language. By default, + # this parameter is set to :en. + # + # pluralize('post') # => "posts" + # pluralize('octopus') # => "octopi" + # pluralize('sheep') # => "sheep" + # pluralize('words') # => "words" + # pluralize('CamelOctopus') # => "CamelOctopi" + # pluralize('ley', :es) # => "leyes" + def pluralize(word, locale = :en) + apply_inflections(word, inflections(locale).plurals, locale) + end + + # The reverse of #pluralize, returns the singular form of a word in a + # string. + # + # If passed an optional +locale+ parameter, the word will be + # singularized using rules defined for that language. By default, + # this parameter is set to :en. + # + # singularize('posts') # => "post" + # singularize('octopi') # => "octopus" + # singularize('sheep') # => "sheep" + # singularize('word') # => "word" + # singularize('CamelOctopi') # => "CamelOctopus" + # singularize('leyes', :es) # => "ley" + def singularize(word, locale = :en) + apply_inflections(word, inflections(locale).singulars, locale) + end + + # Converts strings to UpperCamelCase. + # If the +uppercase_first_letter+ parameter is set to false, then produces + # lowerCamelCase. + # + # Also converts '/' to '::' which is useful for converting + # paths to namespaces. + # + # camelize('active_model') # => "ActiveModel" + # camelize('active_model', false) # => "activeModel" + # camelize('active_model/errors') # => "ActiveModel::Errors" + # camelize('active_model/errors', false) # => "activeModel::Errors" + # + # As a rule of thumb you can think of +camelize+ as the inverse of + # #underscore, though there are cases where that does not hold: + # + # camelize(underscore('SSLError')) # => "SslError" + def camelize(term, uppercase_first_letter = true) + string = term.to_s + # String#camelize takes a symbol (:upper or :lower), so here we also support :lower to keep the methods consistent. + if !uppercase_first_letter || uppercase_first_letter == :lower + string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase! || match } + elsif string.match?(/\A[a-z\d]*\z/) + return inflections.acronyms[string]&.dup || string.capitalize + else + string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize! || match } + end + string.gsub!(/(?:_|(\/))([a-z\d]*)/i) do + word = $2 + substituted = inflections.acronyms[word] || word.capitalize! || word + $1 ? "::#{substituted}" : substituted + end + string + end + + # Makes an underscored, lowercase form from the expression in the string. + # + # Changes '::' to '/' to convert namespaces to paths. + # + # underscore('ActiveModel') # => "active_model" + # underscore('ActiveModel::Errors') # => "active_model/errors" + # + # As a rule of thumb you can think of +underscore+ as the inverse of + # #camelize, though there are cases where that does not hold: + # + # camelize(underscore('SSLError')) # => "SslError" + def underscore(camel_cased_word) + return camel_cased_word.to_s.dup unless /[A-Z-]|::/.match?(camel_cased_word) + word = camel_cased_word.to_s.gsub("::", "/") + word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_' }#{$2.downcase}" } + word.gsub!(/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-z\d])(?=[A-Z])/, "_") + word.tr!("-", "_") + word.downcase! + word + end + + # Tweaks an attribute name for display to end users. + # + # Specifically, performs these transformations: + # + # * Applies human inflection rules to the argument. + # * Deletes leading underscores, if any. + # * Removes an "_id" suffix if present. + # * Replaces underscores with spaces, if any. + # * Downcases all words except acronyms. + # * Capitalizes the first word. + # The capitalization of the first word can be turned off by setting the + # +:capitalize+ option to false (default is true). + # + # The trailing '_id' can be kept and capitalized by setting the + # optional parameter +keep_id_suffix+ to true (default is false). + # + # humanize('employee_salary') # => "Employee salary" + # humanize('author_id') # => "Author" + # humanize('author_id', capitalize: false) # => "author" + # humanize('_id') # => "Id" + # humanize('author_id', keep_id_suffix: true) # => "Author id" + # + # If "SSL" was defined to be an acronym: + # + # humanize('ssl_error') # => "SSL error" + # + def humanize(lower_case_and_underscored_word, capitalize: true, keep_id_suffix: false) + result = lower_case_and_underscored_word.to_s.dup + + inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) } + + result.tr!("_", " ") + result.lstrip! + if !keep_id_suffix && lower_case_and_underscored_word&.end_with?("_id") + result.delete_suffix!(" id") + end + + result.gsub!(/([a-z\d]+)/i) do |match| + match.downcase! + inflections.acronyms[match] || match + end + + if capitalize + result.sub!(/\A\w/) do |match| + match.upcase! + match + end + end + + result + end + + # Converts the first character in the string to uppercase. + # + # upcase_first('what a Lovely Day') # => "What a Lovely Day" + # upcase_first('w') # => "W" + # upcase_first('') # => "" + def upcase_first(string) + string.length > 0 ? string[0].upcase.concat(string[1..-1]) : +"" + end + + # Converts the first character in the string to lowercase. + # + # downcase_first('If they enjoyed The Matrix') # => "if they enjoyed The Matrix" + # downcase_first('I') # => "i" + # downcase_first('') # => "" + def downcase_first(string) + string.length > 0 ? string[0].downcase.concat(string[1..-1]) : +"" + end + + # Capitalizes all the words and replaces some characters in the string to + # create a nicer looking title. +titleize+ is meant for creating pretty + # output. It is not used in the \Rails internals. + # + # The trailing '_id','Id'.. can be kept and capitalized by setting the + # optional parameter +keep_id_suffix+ to true. + # By default, this parameter is false. + # + # titleize('man from the boondocks') # => "Man From The Boondocks" + # titleize('x-men: the last stand') # => "X Men: The Last Stand" + # titleize('TheManWithoutAPast') # => "The Man Without A Past" + # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark" + # titleize('string_ending_with_id', keep_id_suffix: true) # => "String Ending With Id" + def titleize(word, keep_id_suffix: false) + humanize(underscore(word), keep_id_suffix: keep_id_suffix).gsub(/\b(? "raw_scaled_scorers" + # tableize('ham_and_egg') # => "ham_and_eggs" + # tableize('fancyCategory') # => "fancy_categories" + def tableize(class_name) + pluralize(underscore(class_name)) + end + + # Creates a class name from a plural table name like \Rails does for table + # names to models. Note that this returns a string and not a Class. (To + # convert to an actual class follow +classify+ with #constantize.) + # + # classify('ham_and_eggs') # => "HamAndEgg" + # classify('posts') # => "Post" + # + # Singular names are not handled correctly: + # + # classify('calculus') # => "Calculu" + def classify(table_name) + # strip out any leading schema name + camelize(singularize(table_name.to_s.sub(/.*\./, ""))) + end + + # Replaces underscores with dashes in the string. + # + # dasherize('puni_puni') # => "puni-puni" + def dasherize(underscored_word) + underscored_word.tr("_", "-") + end + + # Removes the module part from the expression in the string. + # + # demodulize('ActiveSupport::Inflector::Inflections') # => "Inflections" + # demodulize('Inflections') # => "Inflections" + # demodulize('::Inflections') # => "Inflections" + # demodulize('') # => "" + # + # See also #deconstantize. + def demodulize(path) + path = path.to_s + if i = path.rindex("::") + path[(i + 2), path.length] + else + path + end + end + + # Removes the rightmost segment from the constant expression in the string. + # + # deconstantize('Net::HTTP') # => "Net" + # deconstantize('::Net::HTTP') # => "::Net" + # deconstantize('String') # => "" + # deconstantize('::String') # => "" + # deconstantize('') # => "" + # + # See also #demodulize. + def deconstantize(path) + path.to_s[0, path.rindex("::") || 0] # implementation based on the one in facets' Module#spacename + end + + # Creates a foreign key name from a class name. + # +separate_class_name_and_id_with_underscore+ sets whether + # the method should put '_' between the name and 'id'. + # + # foreign_key('Message') # => "message_id" + # foreign_key('Message', false) # => "messageid" + # foreign_key('Admin::Post') # => "post_id" + def foreign_key(class_name, separate_class_name_and_id_with_underscore = true) + underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id") + end + + # Tries to find a constant with the name specified in the argument string. + # + # constantize('Module') # => Module + # constantize('Foo::Bar') # => Foo::Bar + # + # The name is assumed to be the one of a top-level constant, no matter + # whether it starts with "::" or not. No lexical context is taken into + # account: + # + # C = 'outside' + # module M + # C = 'inside' + # C # => 'inside' + # constantize('C') # => 'outside', same as ::C + # end + # + # NameError is raised when the name is not in CamelCase or the constant is + # unknown. + def constantize(camel_cased_word) + Object.const_get(camel_cased_word) + end + + # Tries to find a constant with the name specified in the argument string. + # + # safe_constantize('Module') # => Module + # safe_constantize('Foo::Bar') # => Foo::Bar + # + # The name is assumed to be the one of a top-level constant, no matter + # whether it starts with "::" or not. No lexical context is taken into + # account: + # + # C = 'outside' + # module M + # C = 'inside' + # C # => 'inside' + # safe_constantize('C') # => 'outside', same as ::C + # end + # + # +nil+ is returned when the name is not in CamelCase or the constant (or + # part of it) is unknown. + # + # safe_constantize('blargle') # => nil + # safe_constantize('UnknownModule') # => nil + # safe_constantize('UnknownModule::Foo::Bar') # => nil + def safe_constantize(camel_cased_word) + constantize(camel_cased_word) + rescue NameError => e + raise if e.name && !(camel_cased_word.to_s.split("::").include?(e.name.to_s) || + e.name.to_s == camel_cased_word.to_s) + rescue LoadError => e + message = e.respond_to?(:original_message) ? e.original_message : e.message + raise unless /Unable to autoload constant #{const_regexp(camel_cased_word)}/.match?(message) + end + + # Returns the suffix that should be added to a number to denote the position + # in an ordered sequence such as 1st, 2nd, 3rd, 4th. + # + # ordinal(1) # => "st" + # ordinal(2) # => "nd" + # ordinal(1002) # => "nd" + # ordinal(1003) # => "rd" + # ordinal(-11) # => "th" + # ordinal(-1021) # => "st" + def ordinal(number) + I18n.translate("number.nth.ordinals", number: number) + end + + # Turns a number into an ordinal string used to denote the position in an + # ordered sequence such as 1st, 2nd, 3rd, 4th. + # + # ordinalize(1) # => "1st" + # ordinalize(2) # => "2nd" + # ordinalize(1002) # => "1002nd" + # ordinalize(1003) # => "1003rd" + # ordinalize(-11) # => "-11th" + # ordinalize(-1021) # => "-1021st" + def ordinalize(number) + I18n.translate("number.nth.ordinalized", number: number) + end + + private + # Mounts a regular expression, returned as a string to ease interpolation, + # that will match part by part the given constant. + # + # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?" + # const_regexp("::") # => "::" + def const_regexp(camel_cased_word) + parts = camel_cased_word.split("::") + + return Regexp.escape(camel_cased_word) if parts.empty? + + last = parts.pop + + parts.reverse!.inject(last) do |acc, part| + part.empty? ? acc : "#{part}(::#{acc})?" + end + end + + # Applies inflection rules for +singularize+ and +pluralize+. + # + # If passed an optional +locale+ parameter, the uncountables will be + # found for that locale. + # + # apply_inflections('post', inflections.plurals, :en) # => "posts" + # apply_inflections('posts', inflections.singulars, :en) # => "post" + def apply_inflections(word, rules, locale = :en) + result = word.to_s.dup + + if word.empty? || inflections(locale).uncountables.uncountable?(result) + result + else + rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) } + result + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/inflector/transliterate.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/inflector/transliterate.rb new file mode 100644 index 00000000..926d32ee --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/inflector/transliterate.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/multibyte" +require "active_support/i18n" + +module ActiveSupport + module Inflector + ALLOWED_ENCODINGS_FOR_TRANSLITERATE = [Encoding::UTF_8, Encoding::US_ASCII, Encoding::GB18030].freeze + + # Replaces non-ASCII characters with an ASCII approximation, or if none + # exists, a replacement character which defaults to "?". + # + # transliterate('Ærøskøbing') + # # => "AEroskobing" + # + # Default approximations are provided for Western/Latin characters, + # e.g, "ø", "ñ", "é", "ß", etc. + # + # This method is I18n aware, so you can set up custom approximations for a + # locale. This can be useful, for example, to transliterate German's "ü" + # and "ö" to "ue" and "oe", or to add support for transliterating Russian + # to ASCII. + # + # In order to make your custom transliterations available, you must set + # them as the i18n.transliterate.rule i18n key: + # + # # Store the transliterations in locales/de.yml + # i18n: + # transliterate: + # rule: + # ü: "ue" + # ö: "oe" + # + # # Or set them using Ruby + # I18n.backend.store_translations(:de, i18n: { + # transliterate: { + # rule: { + # 'ü' => 'ue', + # 'ö' => 'oe' + # } + # } + # }) + # + # The value for i18n.transliterate.rule can be a simple Hash that + # maps characters to ASCII approximations as shown above, or, for more + # complex requirements, a Proc: + # + # I18n.backend.store_translations(:de, i18n: { + # transliterate: { + # rule: ->(string) { MyTransliterator.transliterate(string) } + # } + # }) + # + # Now you can have different transliterations for each locale: + # + # transliterate('Jürgen', locale: :en) + # # => "Jurgen" + # + # transliterate('Jürgen', locale: :de) + # # => "Juergen" + # + # Transliteration is restricted to UTF-8, US-ASCII, and GB18030 strings. + # Other encodings will raise an ArgumentError. + def transliterate(string, replacement = "?", locale: nil) + raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String) + raise ArgumentError, "Cannot transliterate strings with #{string.encoding} encoding" unless ALLOWED_ENCODINGS_FOR_TRANSLITERATE.include?(string.encoding) + + return string.dup if string.ascii_only? + string = string.dup if string.frozen? + + input_encoding = string.encoding + + # US-ASCII is a subset of UTF-8 so we'll force encoding as UTF-8 if + # US-ASCII is given. This way we can let tidy_bytes handle the string + # in the same way as we do for UTF-8 + string.force_encoding(Encoding::UTF_8) if string.encoding == Encoding::US_ASCII + + # GB18030 is Unicode compatible but is not a direct mapping so needs to be + # transcoded. Using invalid/undef :replace will result in loss of data in + # the event of invalid characters, but since tidy_bytes will replace + # invalid/undef with a "?" we're safe to do the same beforehand + string.encode!(Encoding::UTF_8, invalid: :replace, undef: :replace) if string.encoding == Encoding::GB18030 + + transliterated = I18n.transliterate( + ActiveSupport::Multibyte::Unicode.tidy_bytes(string).unicode_normalize(:nfc), + replacement: replacement, + locale: locale + ) + + # Restore the string encoding of the input if it was not UTF-8. + # Apply invalid/undef :replace as tidy_bytes does + transliterated.encode!(input_encoding, invalid: :replace, undef: :replace) if input_encoding != transliterated.encoding + + transliterated + end + + # Replaces special characters in a string so that it may be used as part of + # a 'pretty' URL. + # + # parameterize("Donald E. Knuth") # => "donald-e-knuth" + # parameterize("^très|Jolie-- ") # => "tres-jolie" + # + # To use a custom separator, override the +separator+ argument. + # + # parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth" + # parameterize("^très|Jolie__ ", separator: '_') # => "tres_jolie" + # + # To preserve the case of the characters in a string, use the +preserve_case+ argument. + # + # parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth" + # parameterize("^très|Jolie-- ", preserve_case: true) # => "tres-Jolie" + # + # It preserves dashes and underscores unless they are used as separators: + # + # parameterize("^très|Jolie__ ") # => "tres-jolie__" + # parameterize("^très|Jolie-- ", separator: "_") # => "tres_jolie--" + # parameterize("^très_Jolie-- ", separator: ".") # => "tres_jolie--" + # + # If the optional parameter +locale+ is specified, + # the word will be parameterized as a word of that language. + # By default, this parameter is set to nil and it will use + # the configured I18n.locale. + def parameterize(string, separator: "-", preserve_case: false, locale: nil) + # Replace accented chars with their ASCII equivalents. + parameterized_string = transliterate(string, locale: locale) + + # Turn unwanted chars into the separator. + parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator) + + unless separator.nil? || separator.empty? + if separator == "-" + re_duplicate_separator = /-{2,}/ + re_leading_trailing_separator = /^-|-$/i + else + re_sep = Regexp.escape(separator) + re_duplicate_separator = /#{re_sep}{2,}/ + re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i + end + # No more than one of the separator in a row. + parameterized_string.gsub!(re_duplicate_separator, separator) + # Remove leading/trailing separator. + parameterized_string.gsub!(re_leading_trailing_separator, "") + end + + parameterized_string.downcase! unless preserve_case + parameterized_string + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/isolated_execution_state.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/isolated_execution_state.rb new file mode 100644 index 00000000..082bdb83 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/isolated_execution_state.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +module ActiveSupport + module IsolatedExecutionState # :nodoc: + @isolation_level = nil + + Thread.attr_accessor :active_support_execution_state + Fiber.attr_accessor :active_support_execution_state + + class << self + attr_reader :isolation_level, :scope + + def isolation_level=(level) + return if level == @isolation_level + + unless %i(thread fiber).include?(level) + raise ArgumentError, "isolation_level must be `:thread` or `:fiber`, got: `#{level.inspect}`" + end + + clear if @isolation_level + + @scope = + case level + when :thread; Thread + when :fiber; Fiber + end + + @isolation_level = level + end + + def unique_id + self[:__id__] ||= Object.new + end + + def [](key) + state[key] + end + + def []=(key, value) + state[key] = value + end + + def key?(key) + state.key?(key) + end + + def delete(key) + state.delete(key) + end + + def clear + state.clear + end + + def context + scope.current + end + + def share_with(other) + # Action Controller streaming spawns a new thread and copy thread locals. + # We do the same here for backward compatibility, but this is very much a hack + # and streaming should be rethought. + context.active_support_execution_state = other.active_support_execution_state.dup + end + + private + def state + context.active_support_execution_state ||= {} + end + end + + self.isolation_level = :thread + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/json.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/json.rb new file mode 100644 index 00000000..d7887175 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/json.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +require "active_support/json/decoding" +require "active_support/json/encoding" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/json/decoding.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/json/decoding.rb new file mode 100644 index 00000000..78016bdd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/json/decoding.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/module/delegation" +require "json" + +module ActiveSupport + # Look for and parse JSON strings that look like ISO 8601 times. + mattr_accessor :parse_json_times + + module JSON + # matches YAML-formatted dates + DATE_REGEX = /\A\d{4}-\d{2}-\d{2}\z/ + DATETIME_REGEX = /\A(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)?)\z/ + + class << self + # Parses a JSON string (JavaScript Object Notation) into a hash. + # See http://www.json.org for more info. + # + # ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}") + # => {"team" => "rails", "players" => "36"} + def decode(json) + data = ::JSON.parse(json, quirks_mode: true) + + if ActiveSupport.parse_json_times + convert_dates_from(data) + else + data + end + end + alias_method :load, :decode + + # Returns the class of the error that will be raised when there is an + # error in decoding JSON. Using this method means you won't directly + # depend on the ActiveSupport's JSON implementation, in case it changes + # in the future. + # + # begin + # obj = ActiveSupport::JSON.decode(some_string) + # rescue ActiveSupport::JSON.parse_error + # Rails.logger.warn("Attempted to decode invalid JSON: #{some_string}") + # end + def parse_error + ::JSON::ParserError + end + + private + def convert_dates_from(data) + case data + when nil + nil + when DATE_REGEX + begin + Date.parse(data) + rescue ArgumentError + data + end + when DATETIME_REGEX + begin + Time.zone.parse(data) + rescue ArgumentError + data + end + when Array + data.map! { |d| convert_dates_from(d) } + when Hash + data.transform_values! do |value| + convert_dates_from(value) + end + else + data + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/json/encoding.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/json/encoding.rb new file mode 100644 index 00000000..832be756 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/json/encoding.rb @@ -0,0 +1,138 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/json" +require "active_support/core_ext/module/delegation" + +module ActiveSupport + class << self + delegate :use_standard_json_time_format, :use_standard_json_time_format=, + :time_precision, :time_precision=, + :escape_html_entities_in_json, :escape_html_entities_in_json=, + :json_encoder, :json_encoder=, + to: :'ActiveSupport::JSON::Encoding' + end + + module JSON + class << self + # Dumps objects in JSON (JavaScript Object Notation). + # See http://www.json.org for more info. + # + # ActiveSupport::JSON.encode({ team: 'rails', players: '36' }) + # # => "{\"team\":\"rails\",\"players\":\"36\"}" + # + # Generates JSON that is safe to include in JavaScript as it escapes + # U+2028 (Line Separator) and U+2029 (Paragraph Separator): + # + # ActiveSupport::JSON.encode({ key: "\u2028" }) + # # => "{\"key\":\"\\u2028\"}" + # + # By default, it also generates JSON that is safe to include in HTML, as + # it escapes <, >, and &: + # + # ActiveSupport::JSON.encode({ key: "<>&" }) + # # => "{\"key\":\"\\u003c\\u003e\\u0026\"}" + # + # This can be changed with the +escape_html_entities+ option, or the + # global escape_html_entities_in_json configuration option. + # + # ActiveSupport::JSON.encode({ key: "<>&" }, escape_html_entities: false) + # # => "{\"key\":\"<>&\"}" + def encode(value, options = nil) + Encoding.json_encoder.new(options).encode(value) + end + alias_method :dump, :encode + end + + module Encoding # :nodoc: + class JSONGemEncoder # :nodoc: + attr_reader :options + + def initialize(options = nil) + @options = options || {} + end + + # Encode the given object into a JSON string + def encode(value) + unless options.empty? + value = value.as_json(options.dup.freeze) + end + json = stringify(jsonify(value)) + + # Rails does more escaping than the JSON gem natively does (we + # escape \u2028 and \u2029 and optionally >, <, & to work around + # certain browser problems). + if @options.fetch(:escape_html_entities, Encoding.escape_html_entities_in_json) + json.gsub!(">", '\u003e') + json.gsub!("<", '\u003c') + json.gsub!("&", '\u0026') + end + json.gsub!("\u2028", '\u2028') + json.gsub!("\u2029", '\u2029') + json + end + + private + # Convert an object into a "JSON-ready" representation composed of + # primitives like Hash, Array, String, Symbol, Numeric, + # and +true+/+false+/+nil+. + # Recursively calls #as_json to the object to recursively build a + # fully JSON-ready object. + # + # This allows developers to implement #as_json without having to + # worry about what base types of objects they are allowed to return + # or having to remember to call #as_json recursively. + # + # Note: the +options+ hash passed to +object.to_json+ is only passed + # to +object.as_json+, not any of this method's recursive +#as_json+ + # calls. + def jsonify(value) + case value + when String, Integer, Symbol, nil, true, false + value + when Numeric + value.as_json + when Hash + result = {} + value.each do |k, v| + k = k.to_s unless Symbol === k || String === k + result[k] = jsonify(v) + end + result + when Array + value.map { |v| jsonify(v) } + else + jsonify value.as_json + end + end + + # Encode a "jsonified" Ruby data structure using the JSON gem + def stringify(jsonified) + ::JSON.generate(jsonified, quirks_mode: true, max_nesting: false) + end + end + + class << self + # If true, use ISO 8601 format for dates and times. Otherwise, fall back + # to the Active Support legacy format. + attr_accessor :use_standard_json_time_format + + # If true, encode >, <, & as escaped unicode sequences (e.g. > as \u003e) + # as a safety measure. + attr_accessor :escape_html_entities_in_json + + # Sets the precision of encoded time values. + # Defaults to 3 (equivalent to millisecond precision) + attr_accessor :time_precision + + # Sets the encoder used by \Rails to encode Ruby objects into JSON strings + # in +Object#to_json+ and +ActiveSupport::JSON.encode+. + attr_accessor :json_encoder + end + + self.use_standard_json_time_format = true + self.escape_html_entities_in_json = true + self.json_encoder = JSONGemEncoder + self.time_precision = 3 + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/key_generator.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/key_generator.rb new file mode 100644 index 00000000..8e75fba2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/key_generator.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require "concurrent/map" +require "openssl" + +module ActiveSupport + # = Key Generator + # + # KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2. + # It can be used to derive a number of keys for various purposes from a given secret. + # This lets \Rails applications have a single secure secret, but avoid reusing that + # key in multiple incompatible contexts. + class KeyGenerator + class << self + def hash_digest_class=(klass) + if klass.kind_of?(Class) && klass < OpenSSL::Digest + @hash_digest_class = klass + else + raise ArgumentError, "#{klass} is expected to be an OpenSSL::Digest subclass" + end + end + + def hash_digest_class + @hash_digest_class ||= OpenSSL::Digest::SHA1 + end + end + + def initialize(secret, options = {}) + @secret = secret + # The default iterations are higher than required for our key derivation uses + # on the off chance someone uses this for password storage + @iterations = options[:iterations] || 2**16 + # Also allow configuration here so people can use this to build a rotation + # scheme when switching the digest class. + @hash_digest_class = options[:hash_digest_class] || self.class.hash_digest_class + end + + # Returns a derived key suitable for use. The default +key_size+ is chosen + # to be compatible with the default settings of ActiveSupport::MessageVerifier. + # i.e. OpenSSL::Digest::SHA1#block_length + def generate_key(salt, key_size = 64) + OpenSSL::PKCS5.pbkdf2_hmac(@secret, salt, @iterations, key_size, @hash_digest_class.new) + end + + def inspect # :nodoc: + "#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>" + end + end + + # = Caching Key Generator + # + # CachingKeyGenerator is a wrapper around KeyGenerator which allows users to avoid + # re-executing the key generation process when it's called using the same +salt+ and + # +key_size+. + class CachingKeyGenerator + def initialize(key_generator) + @key_generator = key_generator + @cache_keys = Concurrent::Map.new + end + + # Returns a derived key suitable for use. + def generate_key(*args) + @cache_keys[args.join("|")] ||= @key_generator.generate_key(*args) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/lazy_load_hooks.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/lazy_load_hooks.rb new file mode 100644 index 00000000..39907156 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/lazy_load_hooks.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +module ActiveSupport + # = Lazy Load Hooks + # + # LazyLoadHooks allows \Rails to lazily load a lot of components and thus + # making the app boot faster. Because of this feature now there is no need to + # require +ActiveRecord::Base+ at boot time purely to apply + # configuration. Instead a hook is registered that applies configuration once + # +ActiveRecord::Base+ is loaded. Here +ActiveRecord::Base+ is + # used as example but this feature can be applied elsewhere too. + # + # Here is an example where on_load method is called to register a hook. + # + # initializer 'active_record.initialize_timezone' do + # ActiveSupport.on_load(:active_record) do + # self.time_zone_aware_attributes = true + # self.default_timezone = :utc + # end + # end + # + # When the entirety of +ActiveRecord::Base+ has been + # evaluated then run_load_hooks is invoked. The very last line of + # +ActiveRecord::Base+ is: + # + # ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base) + # + # run_load_hooks will then execute all the hooks that were registered + # with the on_load method. In the case of the above example, it will + # execute the block of code that is in the +initializer+. + # + # Registering a hook that has already run results in that hook executing + # immediately. This allows hooks to be nested for code that relies on + # multiple lazily loaded components: + # + # initializer "action_text.renderer" do + # ActiveSupport.on_load(:action_controller_base) do + # ActiveSupport.on_load(:action_text_content) do + # self.default_renderer = Class.new(ActionController::Base).renderer + # end + # end + # end + module LazyLoadHooks + def self.extended(base) # :nodoc: + base.class_eval do + @load_hooks = Hash.new { |h, k| h[k] = [] } + @loaded = Hash.new { |h, k| h[k] = [] } + @run_once = Hash.new { |h, k| h[k] = [] } + end + end + + # Declares a block that will be executed when a \Rails component is fully + # loaded. If the component has already loaded, the block is executed + # immediately. + # + # Options: + # + # * :yield - Yields the object that run_load_hooks to +block+. + # * :run_once - Given +block+ will run only once. + def on_load(name, options = {}, &block) + @loaded[name].each do |base| + execute_hook(name, base, options, block) + end + + @load_hooks[name] << [block, options] + end + + # Executes all blocks registered to +name+ via on_load, using +base+ as the + # evaluation context. + # + # ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base) + # + # In the case of the above example, it will execute all hooks registered + # for +:active_record+ within the class +ActiveRecord::Base+. + def run_load_hooks(name, base = Object) + @loaded[name] << base + @load_hooks[name].each do |hook, options| + execute_hook(name, base, options, hook) + end + end + + private + def with_execution_control(name, block, once) + unless @run_once[name].include?(block) + @run_once[name] << block if once + + yield + end + end + + def execute_hook(name, base, options, block) + with_execution_control(name, block, options[:run_once]) do + if options[:yield] + block.call(base) + else + if base.is_a?(Module) + base.class_eval(&block) + else + base.instance_eval(&block) + end + end + end + end + end + + extend LazyLoadHooks +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/locale/en.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/locale/en.rb new file mode 100644 index 00000000..29eb9dec --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/locale/en.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +{ + en: { + number: { + nth: { + ordinals: lambda do |_key, options| + number = options[:number] + case number + when 1; "st" + when 2; "nd" + when 3; "rd" + when 4, 5, 6, 7, 8, 9, 10, 11, 12, 13; "th" + else + num_modulo = number.to_i.abs % 100 + num_modulo %= 10 if num_modulo > 13 + case num_modulo + when 1; "st" + when 2; "nd" + when 3; "rd" + else "th" + end + end + end, + + ordinalized: lambda do |_key, options| + number = options[:number] + "#{number}#{ActiveSupport::Inflector.ordinal(number)}" + end + } + } + } +} diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/locale/en.yml b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/locale/en.yml new file mode 100644 index 00000000..52134208 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/locale/en.yml @@ -0,0 +1,141 @@ +en: + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%Y-%m-%d" + short: "%b %d" + long: "%B %d, %Y" + + day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] + abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December] + abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] + # Used in date_select and datetime_select. + order: + - year + - month + - day + + time: + formats: + default: "%a, %d %b %Y %H:%M:%S %z" + short: "%d %b %H:%M" + long: "%B %d, %Y %H:%M" + am: "am" + pm: "pm" + +# Used in array.to_sentence. + support: + array: + words_connector: ", " + two_words_connector: " and " + last_word_connector: ", and " + number: + # Used in NumberHelper.number_to_delimited() + # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' + format: + # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) + separator: "." + # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three) + delimiter: "," + # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00) + precision: 3 + # Determine how rounding is performed (see BigDecimal::mode) + round_mode: default + # If set to true, precision will mean the number of significant digits instead + # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2) + significant: false + # If set, the zeros after the decimal separator will always be stripped (e.g.: 1.200 will be 1.2) + strip_insignificant_zeros: false + + # Used in NumberHelper.number_to_currency() + currency: + format: + # Where is the currency sign? %u is the currency unit, %n is the number (default: $5.00) + format: "%u%n" + negative_format: "-%u%n" + unit: "$" + # These six are to override number.format and are optional + separator: "." + delimiter: "," + precision: 2 + # round_mode: + significant: false + strip_insignificant_zeros: false + + # Used in NumberHelper.number_to_percentage() + percentage: + format: + # These five are to override number.format and are optional + # separator: + delimiter: "" + # precision: + # significant: false + # strip_insignificant_zeros: false + format: "%n%" + + # Used in NumberHelper.number_to_rounded() + precision: + format: + # These five are to override number.format and are optional + # separator: + delimiter: "" + # precision: + # significant: false + # strip_insignificant_zeros: false + + # Used in NumberHelper.number_to_human_size() and NumberHelper.number_to_human() + human: + format: + # These six are to override number.format and are optional + # separator: + delimiter: "" + precision: 3 + # round_mode: + significant: true + strip_insignificant_zeros: true + # Used in number_to_human_size() + storage_units: + # Storage units output formatting. + # %u is the storage unit, %n is the number (default: 2 MB) + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + pb: "PB" + eb: "EB" + zb: "ZB" + # Used in NumberHelper.number_to_human() + decimal_units: + format: "%n %u" + # Decimal units output formatting + # By default we will only quantify some of the exponents + # but the commented ones might be defined or overridden + # by the user. + units: + # femto: Quadrillionth + # pico: Trillionth + # nano: Billionth + # micro: Millionth + # mili: Thousandth + # centi: Hundredth + # deci: Tenth + unit: "" + # ten: + # one: Ten + # other: Tens + # hundred: Hundred + thousand: Thousand + million: Million + billion: Billion + trillion: Trillion + quadrillion: Quadrillion diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/log_subscriber.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/log_subscriber.rb new file mode 100644 index 00000000..42df2a2f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/log_subscriber.rb @@ -0,0 +1,192 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/class/attribute" +require "active_support/core_ext/enumerable" +require "active_support/subscriber" +require "active_support/deprecation/proxy_wrappers" + +module ActiveSupport + # = Active Support Log \Subscriber + # + # +ActiveSupport::LogSubscriber+ is an object set to consume + # ActiveSupport::Notifications with the sole purpose of logging them. + # The log subscriber dispatches notifications to a registered object based + # on its given namespace. + # + # An example would be Active Record log subscriber responsible for logging + # queries: + # + # module ActiveRecord + # class LogSubscriber < ActiveSupport::LogSubscriber + # attach_to :active_record + # + # def sql(event) + # info "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}" + # end + # end + # end + # + # ActiveRecord::LogSubscriber.logger must be set as well, but it is assigned + # automatically in a \Rails environment. + # + # After configured, whenever a "sql.active_record" notification is + # published, it will properly dispatch the event + # (ActiveSupport::Notifications::Event) to the +sql+ method. + # + # Being an ActiveSupport::Notifications consumer, + # +ActiveSupport::LogSubscriber+ exposes a simple interface to check if + # instrumented code raises an exception. It is common to log a different + # message in case of an error, and this can be achieved by extending + # the previous example: + # + # module ActiveRecord + # class LogSubscriber < ActiveSupport::LogSubscriber + # def sql(event) + # exception = event.payload[:exception] + # + # if exception + # exception_object = event.payload[:exception_object] + # + # error "[ERROR] #{event.payload[:name]}: #{exception.join(', ')} " \ + # "(#{exception_object.backtrace.first})" + # else + # # standard logger code + # end + # end + # end + # end + # + # +ActiveSupport::LogSubscriber+ also has some helpers to deal with + # logging. For example, ActiveSupport::LogSubscriber.flush_all! will ensure + # that all logs are flushed, and it is called in Rails::Rack::Logger after a + # request finishes. + class LogSubscriber < Subscriber + # ANSI sequence modes + MODES = { + clear: 0, + bold: 1, + italic: 3, + underline: 4, + } + + # ANSI sequence colors + BLACK = "\e[30m" + RED = "\e[31m" + GREEN = "\e[32m" + YELLOW = "\e[33m" + BLUE = "\e[34m" + MAGENTA = "\e[35m" + CYAN = "\e[36m" + WHITE = "\e[37m" + + mattr_accessor :colorize_logging, default: true + class_attribute :log_levels, instance_accessor: false, default: {} # :nodoc: + + LEVEL_CHECKS = { + debug: -> (logger) { !logger.debug? }, + info: -> (logger) { !logger.info? }, + error: -> (logger) { !logger.error? }, + } + + class << self + def logger + @logger ||= if defined?(Rails) && Rails.respond_to?(:logger) + Rails.logger + end + end + + def attach_to(...) # :nodoc: + result = super + set_event_levels + result + end + + attr_writer :logger + + def log_subscribers + subscribers + end + + # Flush all log_subscribers' logger. + def flush_all! + logger.flush if logger.respond_to?(:flush) + end + + private + def fetch_public_methods(subscriber, inherit_all) + subscriber.public_methods(inherit_all) - LogSubscriber.public_instance_methods(true) + end + + def set_event_levels + if subscriber + subscriber.event_levels = log_levels.transform_keys { |k| "#{k}.#{namespace}" } + end + end + + def subscribe_log_level(method, level) + self.log_levels = log_levels.merge(method => LEVEL_CHECKS.fetch(level)) + set_event_levels + end + end + + def initialize + super + @event_levels = {} + end + + def logger + LogSubscriber.logger + end + + def silenced?(event) + logger.nil? || @event_levels[event]&.call(logger) + end + + def call(event) + super if logger + rescue => e + log_exception(event.name, e) + end + + def publish_event(event) + super if logger + rescue => e + log_exception(event.name, e) + end + + attr_writer :event_levels # :nodoc: + + private + %w(info debug warn error fatal unknown).each do |level| + class_eval <<-METHOD, __FILE__, __LINE__ + 1 + def #{level}(progname = nil, &block) + logger.#{level}(progname, &block) if logger + end + METHOD + end + + # Set color by using a symbol or one of the defined constants. Set modes + # by specifying bold, italic, or underline options. Inspired by Highline, + # this method will automatically clear formatting at the end of the returned String. + def color(text, color, mode_options = {}) # :doc: + return text unless colorize_logging + color = self.class.const_get(color.upcase) if color.is_a?(Symbol) + mode = mode_from(mode_options) + clear = "\e[#{MODES[:clear]}m" + "#{mode}#{color}#{text}#{clear}" + end + + def mode_from(options) + modes = MODES.values_at(*options.compact_blank.keys) + + "\e[#{modes.join(";")}m" if modes.any? + end + + def log_exception(name, e) + if logger + logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}" + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/log_subscriber/test_helper.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/log_subscriber/test_helper.rb new file mode 100644 index 00000000..b528a7fc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/log_subscriber/test_helper.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require "active_support/log_subscriber" +require "active_support/logger" +require "active_support/notifications" + +module ActiveSupport + class LogSubscriber + # Provides some helpers to deal with testing log subscribers by setting up + # notifications. Take for instance Active Record subscriber tests: + # + # class SyncLogSubscriberTest < ActiveSupport::TestCase + # include ActiveSupport::LogSubscriber::TestHelper + # + # setup do + # ActiveRecord::LogSubscriber.attach_to(:active_record) + # end + # + # def test_basic_query_logging + # Developer.all.to_a + # wait + # assert_equal 1, @logger.logged(:debug).size + # assert_match(/Developer Load/, @logger.logged(:debug).last) + # assert_match(/SELECT \* FROM "developers"/, @logger.logged(:debug).last) + # end + # end + # + # All you need to do is to ensure that your log subscriber is added to + # Rails::Subscriber, as in the second line of the code above. The test + # helpers are responsible for setting up the queue and subscriptions, and + # turning colors in logs off. + # + # The messages are available in the @logger instance, which is a logger with + # limited powers (it actually does not send anything to your output), and + # you can collect them doing @logger.logged(level), where level is the level + # used in logging, like info, debug, warn, and so on. + module TestHelper + def setup # :nodoc: + @logger = MockLogger.new + @notifier = ActiveSupport::Notifications::Fanout.new + + ActiveSupport::LogSubscriber.colorize_logging = false + + @old_notifier = ActiveSupport::Notifications.notifier + set_logger(@logger) + ActiveSupport::Notifications.notifier = @notifier + end + + def teardown # :nodoc: + set_logger(nil) + ActiveSupport::Notifications.notifier = @old_notifier + end + + class MockLogger + include ActiveSupport::Logger::Severity + + attr_reader :flush_count + attr_accessor :level + + def initialize(level = DEBUG) + @flush_count = 0 + @level = level + @logged = Hash.new { |h, k| h[k] = [] } + end + + def method_missing(level, message = nil) + if block_given? + @logged[level] << yield + else + @logged[level] << message + end + end + + def logged(level) + @logged[level].compact.map { |l| l.to_s.strip } + end + + def flush + @flush_count += 1 + end + + ActiveSupport::Logger::Severity.constants.each do |severity| + class_eval <<-EOT, __FILE__, __LINE__ + 1 + def #{severity.downcase}? + #{severity} >= @level + end + EOT + end + end + + # Wait notifications to be published. + def wait + @notifier.wait + end + + # Overwrite if you use another logger in your log subscriber. + # + # def logger + # ActiveRecord::Base.logger = @logger + # end + def set_logger(logger) + ActiveSupport::LogSubscriber.logger = logger + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/logger.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/logger.rb new file mode 100644 index 00000000..09ea81f5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/logger.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "active_support/logger_silence" +require "active_support/logger_thread_safe_level" +require "logger" + +module ActiveSupport + class Logger < ::Logger + include LoggerSilence + + # Returns true if the logger destination matches one of the sources + # + # logger = Logger.new(STDOUT) + # ActiveSupport::Logger.logger_outputs_to?(logger, STDOUT) + # # => true + # + # logger = Logger.new('/var/log/rails.log') + # ActiveSupport::Logger.logger_outputs_to?(logger, '/var/log/rails.log') + # # => true + def self.logger_outputs_to?(logger, *sources) + loggers = if logger.is_a?(BroadcastLogger) + logger.broadcasts + else + [logger] + end + + logdevs = loggers.map { |logger| logger.instance_variable_get(:@logdev) } + logger_sources = logdevs.filter_map { |logdev| logdev.try(:filename) || logdev.try(:dev) } + + normalize_sources(sources).intersect?(normalize_sources(logger_sources)) + end + + def initialize(*args, **kwargs) + super + @formatter ||= SimpleFormatter.new + end + + # Simple formatter which only displays the message. + class SimpleFormatter < ::Logger::Formatter + # This method is invoked when a log event occurs + def call(severity, timestamp, progname, msg) + "#{String === msg ? msg : msg.inspect}\n" + end + end + + private + def self.normalize_sources(sources) + sources.map do |source| + source = source.path if source.respond_to?(:path) + source = File.realpath(source) if source.is_a?(String) && File.exist?(source) + source + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/logger_silence.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/logger_silence.rb new file mode 100644 index 00000000..8567eff4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/logger_silence.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/logger_thread_safe_level" + +module ActiveSupport + module LoggerSilence + extend ActiveSupport::Concern + + included do + cattr_accessor :silencer, default: true + include ActiveSupport::LoggerThreadSafeLevel + end + + # Silences the logger for the duration of the block. + def silence(severity = Logger::ERROR) + silencer ? log_at(severity) { yield self } : yield(self) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/logger_thread_safe_level.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/logger_thread_safe_level.rb new file mode 100644 index 00000000..39d6fe9d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/logger_thread_safe_level.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "logger" + +module ActiveSupport + module LoggerThreadSafeLevel # :nodoc: + extend ActiveSupport::Concern + + def local_level + IsolatedExecutionState[local_level_key] + end + + def local_level=(level) + case level + when Integer + when Symbol + level = Logger::Severity.const_get(level.to_s.upcase) + when nil + else + raise ArgumentError, "Invalid log level: #{level.inspect}" + end + if level.nil? + IsolatedExecutionState.delete(local_level_key) + else + IsolatedExecutionState[local_level_key] = level + end + end + + def level + local_level || super + end + + # Change the thread-local level for the duration of the given block. + def log_at(level) + old_local_level, self.local_level = local_level, level + yield + ensure + self.local_level = old_local_level + end + + private + def local_level_key + @local_level_key ||= :"logger_thread_safe_level_#{object_id}" + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_encryptor.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_encryptor.rb new file mode 100644 index 00000000..af0b20e1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_encryptor.rb @@ -0,0 +1,374 @@ +# frozen_string_literal: true + +require "openssl" +require "base64" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/messages/codec" +require "active_support/messages/rotator" +require "active_support/message_verifier" + +module ActiveSupport + # = Active Support Message Encryptor + # + # MessageEncryptor is a simple way to encrypt values which get stored + # somewhere you don't trust. + # + # The cipher text and initialization vector are base64 encoded and returned + # to you. + # + # This can be used in situations similar to the MessageVerifier, but + # where you don't want users to be able to determine the value of the payload. + # + # len = ActiveSupport::MessageEncryptor.key_len + # salt = SecureRandom.random_bytes(len) + # key = ActiveSupport::KeyGenerator.new('password').generate_key(salt, len) # => "\x89\xE0\x156\xAC..." + # crypt = ActiveSupport::MessageEncryptor.new(key) # => # + # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..." + # crypt.decrypt_and_verify(encrypted_data) # => "my secret data" + # + # The +decrypt_and_verify+ method will raise an + # +ActiveSupport::MessageEncryptor::InvalidMessage+ exception if the data + # provided cannot be decrypted or verified. + # + # crypt.decrypt_and_verify('not encrypted data') # => ActiveSupport::MessageEncryptor::InvalidMessage + # + # === Confining messages to a specific purpose + # + # By default any message can be used throughout your app. But they can also be + # confined to a specific +:purpose+. + # + # token = crypt.encrypt_and_sign("this is the chair", purpose: :login) + # + # Then that same purpose must be passed when verifying to get the data back out: + # + # crypt.decrypt_and_verify(token, purpose: :login) # => "this is the chair" + # crypt.decrypt_and_verify(token, purpose: :shipping) # => nil + # crypt.decrypt_and_verify(token) # => nil + # + # Likewise, if a message has no purpose it won't be returned when verifying with + # a specific purpose. + # + # token = crypt.encrypt_and_sign("the conversation is lively") + # crypt.decrypt_and_verify(token, purpose: :scare_tactics) # => nil + # crypt.decrypt_and_verify(token) # => "the conversation is lively" + # + # === Making messages expire + # + # By default messages last forever and verifying one year from now will still + # return the original value. But messages can be set to expire at a given + # time with +:expires_in+ or +:expires_at+. + # + # crypt.encrypt_and_sign(parcel, expires_in: 1.month) + # crypt.encrypt_and_sign(doowad, expires_at: Time.now.end_of_year) + # + # Then the messages can be verified and returned up to the expire time. + # Thereafter, verifying returns +nil+. + # + # === Rotating keys + # + # MessageEncryptor also supports rotating out old configurations by falling + # back to a stack of encryptors. Call +rotate+ to build and add an encryptor + # so +decrypt_and_verify+ will also try the fallback. + # + # By default any rotated encryptors use the values of the primary + # encryptor unless specified otherwise. + # + # You'd give your encryptor the new defaults: + # + # crypt = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm") + # + # Then gradually rotate the old values out by adding them as fallbacks. Any message + # generated with the old values will then work until the rotation is removed. + # + # crypt.rotate old_secret # Fallback to an old secret instead of @secret. + # crypt.rotate cipher: "aes-256-cbc" # Fallback to an old cipher instead of aes-256-gcm. + # + # Though if both the secret and the cipher was changed at the same time, + # the above should be combined into: + # + # crypt.rotate old_secret, cipher: "aes-256-cbc" + class MessageEncryptor < Messages::Codec + prepend Messages::Rotator + + cattr_accessor :use_authenticated_message_encryption, instance_accessor: false, default: false + + class << self + def default_cipher # :nodoc: + if use_authenticated_message_encryption + "aes-256-gcm" + else + "aes-256-cbc" + end + end + end + + module NullSerializer # :nodoc: + def self.load(value) + value + end + + def self.dump(value) + value + end + end + + class InvalidMessage < StandardError; end + OpenSSLCipherError = OpenSSL::Cipher::CipherError + + AUTH_TAG_LENGTH = 16 # :nodoc: + SEPARATOR = "--" # :nodoc: + + # Initialize a new MessageEncryptor. +secret+ must be at least as long as + # the cipher key size. For the default 'aes-256-gcm' cipher, this is 256 + # bits. If you are using a user-entered secret, you can generate a suitable + # key by using ActiveSupport::KeyGenerator or a similar key + # derivation function. + # + # The first additional parameter is used as the signature key for + # MessageVerifier. This allows you to specify keys to encrypt and sign + # data. Ignored when using an AEAD cipher like 'aes-256-gcm'. + # + # ActiveSupport::MessageEncryptor.new('secret', 'signature_secret') + # + # ==== Options + # + # [+:cipher+] + # Cipher to use. Can be any cipher returned by +OpenSSL::Cipher.ciphers+. + # Default is 'aes-256-gcm'. + # + # [+:digest+] + # Digest used for signing. Ignored when using an AEAD cipher like + # 'aes-256-gcm'. + # + # [+:serializer+] + # The serializer used to serialize message data. You can specify any + # object that responds to +dump+ and +load+, or you can choose from + # several preconfigured serializers: +:marshal+, +:json_allow_marshal+, + # +:json+, +:message_pack_allow_marshal+, +:message_pack+. + # + # The preconfigured serializers include a fallback mechanism to support + # multiple deserialization formats. For example, the +:marshal+ serializer + # will serialize using +Marshal+, but can deserialize using +Marshal+, + # ActiveSupport::JSON, or ActiveSupport::MessagePack. This makes it easy + # to migrate between serializers. + # + # The +:marshal+, +:json_allow_marshal+, and +:message_pack_allow_marshal+ + # serializers support deserializing using +Marshal+, but the others do + # not. Beware that +Marshal+ is a potential vector for deserialization + # attacks in cases where a message signing secret has been leaked. If + # possible, choose a serializer that does not support +Marshal+. + # + # The +:message_pack+ and +:message_pack_allow_marshal+ serializers use + # ActiveSupport::MessagePack, which can roundtrip some Ruby types that are + # not supported by JSON, and may provide improved performance. However, + # these require the +msgpack+ gem. + # + # When using \Rails, the default depends on +config.active_support.message_serializer+. + # Otherwise, the default is +:marshal+. + # + # [+:url_safe+] + # By default, MessageEncryptor generates RFC 4648 compliant strings + # which are not URL-safe. In other words, they can contain "+" and "/". + # If you want to generate URL-safe strings (in compliance with "Base 64 + # Encoding with URL and Filename Safe Alphabet" in RFC 4648), you can + # pass +true+. + # + # [+:force_legacy_metadata_serializer+] + # Whether to use the legacy metadata serializer, which serializes the + # message first, then wraps it in an envelope which is also serialized. This + # was the default in \Rails 7.0 and below. + # + # If you don't pass a truthy value, the default is set using + # +config.active_support.use_message_serializer_for_metadata+. + def initialize(secret, sign_secret = nil, **options) + super(**options) + @secret = secret + @cipher = options[:cipher] || self.class.default_cipher + @aead_mode = new_cipher.authenticated? + @verifier = if !@aead_mode + MessageVerifier.new(sign_secret || secret, **options, serializer: NullSerializer) + end + end + + # Encrypt and sign a message. We need to sign the message in order to avoid + # padding attacks. Reference: https://www.limited-entropy.com/padding-oracle-attacks/. + # + # ==== Options + # + # [+:expires_at+] + # The datetime at which the message expires. After this datetime, + # verification of the message will fail. + # + # message = encryptor.encrypt_and_sign("hello", expires_at: Time.now.tomorrow) + # encryptor.decrypt_and_verify(message) # => "hello" + # # 24 hours later... + # encryptor.decrypt_and_verify(message) # => nil + # + # [+:expires_in+] + # The duration for which the message is valid. After this duration has + # elapsed, verification of the message will fail. + # + # message = encryptor.encrypt_and_sign("hello", expires_in: 24.hours) + # encryptor.decrypt_and_verify(message) # => "hello" + # # 24 hours later... + # encryptor.decrypt_and_verify(message) # => nil + # + # [+:purpose+] + # The purpose of the message. If specified, the same purpose must be + # specified when verifying the message; otherwise, verification will fail. + # (See #decrypt_and_verify.) + def encrypt_and_sign(value, **options) + create_message(value, **options) + end + + # Decrypt and verify a message. We need to verify the message in order to + # avoid padding attacks. Reference: https://www.limited-entropy.com/padding-oracle-attacks/. + # + # ==== Options + # + # [+:purpose+] + # The purpose that the message was generated with. If the purpose does not + # match, +decrypt_and_verify+ will return +nil+. + # + # message = encryptor.encrypt_and_sign("hello", purpose: "greeting") + # encryptor.decrypt_and_verify(message, purpose: "greeting") # => "hello" + # encryptor.decrypt_and_verify(message) # => nil + # + # message = encryptor.encrypt_and_sign("bye") + # encryptor.decrypt_and_verify(message) # => "bye" + # encryptor.decrypt_and_verify(message, purpose: "greeting") # => nil + # + def decrypt_and_verify(message, **options) + catch_and_raise :invalid_message_format, as: InvalidMessage do + catch_and_raise :invalid_message_serialization, as: InvalidMessage do + catch_and_ignore :invalid_message_content do + read_message(message, **options) + end + end + end + end + + # Given a cipher, returns the key length of the cipher to help generate the key of desired size + def self.key_len(cipher = default_cipher) + OpenSSL::Cipher.new(cipher).key_len + end + + def create_message(value, **options) # :nodoc: + sign(encrypt(serialize_with_metadata(value, **options))) + end + + def read_message(message, **options) # :nodoc: + deserialize_with_metadata(decrypt(verify(message)), **options) + end + + def inspect # :nodoc: + "#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>" + end + + private + def sign(data) + @verifier ? @verifier.create_message(data) : data + end + + def verify(data) + @verifier ? @verifier.read_message(data) : data + end + + def encrypt(data) + cipher = new_cipher + cipher.encrypt + cipher.key = @secret + + # Rely on OpenSSL for the initialization vector + iv = cipher.random_iv + cipher.auth_data = "" if aead_mode? + + encrypted_data = cipher.update(data) + encrypted_data << cipher.final + + parts = [encrypted_data, iv] + parts << cipher.auth_tag(AUTH_TAG_LENGTH) if aead_mode? + + join_parts(parts) + end + + def decrypt(encrypted_message) + cipher = new_cipher + encrypted_data, iv, auth_tag = extract_parts(encrypted_message) + + # Currently the OpenSSL bindings do not raise an error if auth_tag is + # truncated, which would allow an attacker to easily forge it. See + # https://github.com/ruby/openssl/issues/63 + if aead_mode? && auth_tag.bytesize != AUTH_TAG_LENGTH + throw :invalid_message_format, "truncated auth_tag" + end + + cipher.decrypt + cipher.key = @secret + cipher.iv = iv + if aead_mode? + cipher.auth_tag = auth_tag + cipher.auth_data = "" + end + + decrypted_data = cipher.update(encrypted_data) + decrypted_data << cipher.final + rescue OpenSSLCipherError => error + throw :invalid_message_format, error + end + + def length_after_encode(length_before_encode) + if @url_safe + (4 * length_before_encode / 3.0).ceil # length without padding + else + 4 * (length_before_encode / 3.0).ceil # length with padding + end + end + + def length_of_encoded_iv + @length_of_encoded_iv ||= length_after_encode(new_cipher.iv_len) + end + + def length_of_encoded_auth_tag + @length_of_encoded_auth_tag ||= length_after_encode(AUTH_TAG_LENGTH) + end + + def join_parts(parts) + parts.map! { |part| encode(part) }.join(SEPARATOR) + end + + def extract_part(encrypted_message, rindex, length) + index = rindex - length + + if encrypted_message[index - SEPARATOR.length, SEPARATOR.length] == SEPARATOR + encrypted_message[index, length] + else + throw :invalid_message_format, "missing separator" + end + end + + def extract_parts(encrypted_message) + parts = [] + rindex = encrypted_message.length + + if aead_mode? + parts << extract_part(encrypted_message, rindex, length_of_encoded_auth_tag) + rindex -= SEPARATOR.length + length_of_encoded_auth_tag + end + + parts << extract_part(encrypted_message, rindex, length_of_encoded_iv) + rindex -= SEPARATOR.length + length_of_encoded_iv + + parts << encrypted_message[0, rindex] + + parts.reverse!.map! { |part| decode(part) } + end + + def new_cipher + OpenSSL::Cipher.new(@cipher) + end + + attr_reader :aead_mode + alias :aead_mode? :aead_mode + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_encryptors.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_encryptors.rb new file mode 100644 index 00000000..aa327404 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_encryptors.rb @@ -0,0 +1,141 @@ +# frozen_string_literal: true + +require "active_support/messages/rotation_coordinator" + +module ActiveSupport + class MessageEncryptors < Messages::RotationCoordinator + ## + # :attr_accessor: transitional + # + # If true, the first two rotation option sets are swapped when building + # message encryptors. For example, with the following configuration, message + # encryptors will encrypt messages using serializer: Marshal, url_safe: true, + # and will able to decrypt messages that were encrypted using any of the + # three option sets: + # + # encryptors = ActiveSupport::MessageEncryptors.new { ... } + # encryptors.rotate(serializer: JSON, url_safe: true) + # encryptors.rotate(serializer: Marshal, url_safe: true) + # encryptors.rotate(serializer: Marshal, url_safe: false) + # encryptors.transitional = true + # + # This can be useful when performing a rolling deploy of an application, + # wherein servers that have not yet been updated must still be able to + # decrypt messages from updated servers. In such a scenario, first perform a + # rolling deploy with the new rotation (e.g. serializer: JSON, url_safe: true) + # as the first rotation and transitional = true. Then, after all + # servers have been updated, perform a second rolling deploy with + # transitional = false. + + ## + # :singleton-method: new + # :call-seq: new(&secret_generator) + # + # Initializes a new instance. +secret_generator+ must accept a salt and a + # +secret_length+ kwarg, and return a suitable secret (string) or secrets + # (array of strings). +secret_generator+ may also accept other arbitrary + # kwargs. If #rotate is called with any options matching those kwargs, those + # options will be passed to +secret_generator+ instead of to the message + # encryptor. + # + # encryptors = ActiveSupport::MessageEncryptors.new do |salt, secret_length:, base:| + # MySecretGenerator.new(base).generate(salt, secret_length) + # end + # + # encryptors.rotate(base: "...") + + ## + # :method: [] + # :call-seq: [](salt) + # + # Returns a MessageEncryptor configured with a secret derived from the + # given +salt+, and options from #rotate. MessageEncryptor instances will + # be memoized, so the same +salt+ will return the same instance. + + ## + # :method: []= + # :call-seq: []=(salt, encryptor) + # + # Overrides a MessageEncryptor instance associated with a given +salt+. + + ## + # :method: rotate + # :call-seq: + # rotate(**options) + # rotate(&block) + # + # Adds +options+ to the list of option sets. Messages will be encrypted + # using the first set in the list. When decrypting, however, each set will + # be tried, in order, until one succeeds. + # + # Notably, the +:secret_generator+ option can specify a different secret + # generator than the one initially specified. The secret generator must + # respond to +call+, accept a salt and a +secret_length+ kwarg, and return + # a suitable secret (string) or secrets (array of strings). The secret + # generator may also accept other arbitrary kwargs. + # + # If any options match the kwargs of the operative secret generator, those + # options will be passed to the secret generator instead of to the message + # encryptor. + # + # For fine-grained per-salt rotations, a block form is supported. The block + # will receive the salt, and should return an appropriate options Hash. The + # block may also return +nil+ to indicate that the rotation does not apply + # to the given salt. For example: + # + # encryptors = ActiveSupport::MessageEncryptors.new { ... } + # + # encryptors.rotate do |salt| + # case salt + # when :foo + # { serializer: JSON, url_safe: true } + # when :bar + # { serializer: Marshal, url_safe: true } + # end + # end + # + # encryptors.rotate(serializer: Marshal, url_safe: false) + # + # # Uses `serializer: JSON, url_safe: true`. + # # Falls back to `serializer: Marshal, url_safe: false`. + # encryptors[:foo] + # + # # Uses `serializer: Marshal, url_safe: true`. + # # Falls back to `serializer: Marshal, url_safe: false`. + # encryptors[:bar] + # + # # Uses `serializer: Marshal, url_safe: false`. + # encryptors[:baz] + + ## + # :method: rotate_defaults + # :call-seq: rotate_defaults + # + # Invokes #rotate with the default options. + + ## + # :method: clear_rotations + # :call-seq: clear_rotations + # + # Clears the list of option sets. + + ## + # :method: on_rotation + # :call-seq: on_rotation(&callback) + # + # Sets a callback to invoke when a message is decrypted using an option set + # other than the first. + # + # For example, this callback could log each time it is called, and thus + # indicate whether old option sets are still in use or can be removed from + # rotation. + + ## + private + def build(salt, secret_generator:, secret_generator_options:, **options) + secret_length = MessageEncryptor.key_len(*options[:cipher]) + secret = secret_generator.call(salt, secret_length: secret_length, **secret_generator_options) + MessageEncryptor.new(*Array(secret), **options) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_pack.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_pack.rb new file mode 100644 index 00000000..3ce2c07b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_pack.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +begin + gem "msgpack", ">= 1.7.0" + require "msgpack" +rescue LoadError => error + warn "ActiveSupport::MessagePack requires the msgpack gem, version 1.7.0 or later. " \ + "Please add it to your Gemfile: `gem \"msgpack\", \">= 1.7.0\"`" + raise error +end + +require_relative "message_pack/cache_serializer" +require_relative "message_pack/serializer" + +module ActiveSupport + module MessagePack + extend Serializer + + ## + # :singleton-method: dump + # :call-seq: dump(object) + # + # Dumps an object. Raises ActiveSupport::MessagePack::UnserializableObjectError + # if the object type is not supported. + # + #-- + # Implemented by Serializer#dump. + + ## + # :singleton-method: load + # :call-seq: load(dumped) + # + # Loads an object dump created by ::dump. + # + #-- + # Implemented by Serializer#load. + + ## + # :singleton-method: signature? + # :call-seq: signature?(dumped) + # + # Returns true if the given dump begins with an +ActiveSupport::MessagePack+ + # signature. + # + #-- + # Implemented by Serializer#signature?. + + ActiveSupport.run_load_hooks(:message_pack, self) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_pack/cache_serializer.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_pack/cache_serializer.rb new file mode 100644 index 00000000..a7f4958e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_pack/cache_serializer.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require_relative "serializer" + +module ActiveSupport + module MessagePack + module CacheSerializer + include Serializer + extend self + + def load(dumped) + super + rescue ActiveSupport::MessagePack::MissingClassError + # Treat missing class as cache miss => return nil + end + + private + def install_unregistered_type_handler + Extensions.install_unregistered_type_fallback(message_pack_factory) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_pack/extensions.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_pack/extensions.rb new file mode 100644 index 00000000..3e3ba64c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_pack/extensions.rb @@ -0,0 +1,305 @@ +# frozen_string_literal: true + +require "bigdecimal" +require "date" +require "ipaddr" +require "pathname" +require "uri/generic" +require "msgpack/bigint" +require "active_support/hash_with_indifferent_access" +require "active_support/time" + +module ActiveSupport + module MessagePack + class UnserializableObjectError < StandardError; end + class MissingClassError < StandardError; end # :nodoc: + + module Extensions # :nodoc: + extend self + + def install(registry) + registry.register_type 0, Symbol, + packer: :to_msgpack_ext, + unpacker: :from_msgpack_ext, + optimized_symbols_parsing: true + + registry.register_type 1, Integer, + packer: ::MessagePack::Bigint.method(:to_msgpack_ext), + unpacker: ::MessagePack::Bigint.method(:from_msgpack_ext), + oversized_integer_extension: true + + registry.register_type 2, BigDecimal, + packer: :_dump, + unpacker: :_load + + registry.register_type 3, Rational, + packer: method(:write_rational), + unpacker: method(:read_rational), + recursive: true + + registry.register_type 4, Complex, + packer: method(:write_complex), + unpacker: method(:read_complex), + recursive: true + + registry.register_type 5, DateTime, + packer: method(:write_datetime), + unpacker: method(:read_datetime), + recursive: true + + registry.register_type 6, Date, + packer: method(:write_date), + unpacker: method(:read_date), + recursive: true + + registry.register_type 7, Time, + packer: method(:write_time), + unpacker: method(:read_time), + recursive: true + + registry.register_type 8, ActiveSupport::TimeWithZone, + packer: method(:write_time_with_zone), + unpacker: method(:read_time_with_zone), + recursive: true + + registry.register_type 9, ActiveSupport::TimeZone, + packer: method(:dump_time_zone), + unpacker: method(:load_time_zone) + + registry.register_type 10, ActiveSupport::Duration, + packer: method(:write_duration), + unpacker: method(:read_duration), + recursive: true + + registry.register_type 11, Range, + packer: method(:write_range), + unpacker: method(:read_range), + recursive: true + + registry.register_type 12, Set, + packer: method(:write_set), + unpacker: method(:read_set), + recursive: true + + registry.register_type 13, URI::Generic, + packer: :to_s, + unpacker: URI.method(:parse) + + registry.register_type 14, IPAddr, + packer: method(:write_ipaddr), + unpacker: method(:read_ipaddr), + recursive: true + + registry.register_type 15, Pathname, + packer: :to_s, + unpacker: :new + + registry.register_type 16, Regexp, + packer: :to_s, + unpacker: :new + + registry.register_type 17, ActiveSupport::HashWithIndifferentAccess, + packer: method(:write_hash_with_indifferent_access), + unpacker: method(:read_hash_with_indifferent_access), + recursive: true + end + + def install_unregistered_type_error(registry) + registry.register_type 127, Object, + packer: method(:raise_unserializable), + unpacker: method(:raise_invalid_format) + end + + def install_unregistered_type_fallback(registry) + registry.register_type 127, Object, + packer: method(:write_object), + unpacker: method(:read_object), + recursive: true + end + + def write_rational(rational, packer) + packer.write(rational.numerator) + packer.write(rational.denominator) unless rational.numerator.zero? + end + + def read_rational(unpacker) + numerator = unpacker.read + Rational(numerator, numerator.zero? ? 1 : unpacker.read) + end + + def write_complex(complex, packer) + packer.write(complex.real) + packer.write(complex.imaginary) + end + + def read_complex(unpacker) + Complex(unpacker.read, unpacker.read) + end + + def write_datetime(datetime, packer) + packer.write(datetime.jd) + packer.write(datetime.hour) + packer.write(datetime.min) + packer.write(datetime.sec) + write_rational(datetime.sec_fraction, packer) + write_rational(datetime.offset, packer) + end + + def read_datetime(unpacker) + DateTime.jd(unpacker.read, unpacker.read, unpacker.read, unpacker.read + read_rational(unpacker), read_rational(unpacker)) + end + + def write_date(date, packer) + packer.write(date.jd) + end + + def read_date(unpacker) + Date.jd(unpacker.read) + end + + def write_time(time, packer) + packer.write(time.tv_sec) + packer.write(time.tv_nsec) + packer.write(time.utc_offset) + end + + def read_time(unpacker) + Time.at_without_coercion(unpacker.read, unpacker.read, :nanosecond, in: unpacker.read) + end + + def write_time_with_zone(twz, packer) + write_time(twz.utc, packer) + write_time_zone(twz.time_zone, packer) + end + + def read_time_with_zone(unpacker) + ActiveSupport::TimeWithZone.new(read_time(unpacker), read_time_zone(unpacker)) + end + + def dump_time_zone(time_zone) + time_zone.name + end + + def load_time_zone(name) + ActiveSupport::TimeZone[name] + end + + def write_time_zone(time_zone, packer) + packer.write(dump_time_zone(time_zone)) + end + + def read_time_zone(unpacker) + load_time_zone(unpacker.read) + end + + def write_duration(duration, packer) + packer.write(duration.value) + packer.write(duration._parts.values_at(*ActiveSupport::Duration::PARTS)) + end + + def read_duration(unpacker) + value = unpacker.read + parts = ActiveSupport::Duration::PARTS.zip(unpacker.read).to_h + parts.compact! + ActiveSupport::Duration.new(value, parts) + end + + def write_range(range, packer) + packer.write(range.begin) + packer.write(range.end) + packer.write(range.exclude_end?) + end + + def read_range(unpacker) + Range.new(unpacker.read, unpacker.read, unpacker.read) + end + + def write_set(set, packer) + packer.write(set.to_a) + end + + def read_set(unpacker) + Set.new(unpacker.read) + end + + def write_ipaddr(ipaddr, packer) + if ipaddr.prefix < 32 || (ipaddr.ipv6? && ipaddr.prefix < 128) + packer.write("#{ipaddr}/#{ipaddr.prefix}") + else + packer.write(ipaddr.to_s) + end + end + + def read_ipaddr(unpacker) + IPAddr.new(unpacker.read) + end + + def write_hash_with_indifferent_access(hwia, packer) + packer.write(hwia.to_h) + end + + def read_hash_with_indifferent_access(unpacker) + ActiveSupport::HashWithIndifferentAccess.new(unpacker.read) + end + + def raise_unserializable(object, *) + raise UnserializableObjectError, "Unsupported type #{object.class} for object #{object.inspect}" + end + + def raise_invalid_format(*) + raise "Invalid format" + end + + def dump_class(klass) + raise UnserializableObjectError, "Cannot serialize anonymous class" unless klass.name + klass.name + end + + def load_class(name) + Object.const_get(name) + rescue NameError => error + if error.name.to_s == name + raise MissingClassError, "Missing class: #{name}" + else + raise + end + end + + def write_class(klass, packer) + packer.write(dump_class(klass)) + end + + def read_class(unpacker) + load_class(unpacker.read) + end + + LOAD_WITH_MSGPACK_EXT = 0 + LOAD_WITH_JSON_CREATE = 1 + + def write_object(object, packer) + if object.class.respond_to?(:from_msgpack_ext) + packer.write(LOAD_WITH_MSGPACK_EXT) + write_class(object.class, packer) + packer.write(object.to_msgpack_ext) + elsif object.class.respond_to?(:json_create) + packer.write(LOAD_WITH_JSON_CREATE) + write_class(object.class, packer) + packer.write(object.as_json) + else + raise_unserializable(object) + end + end + + def read_object(unpacker) + case unpacker.read + when LOAD_WITH_MSGPACK_EXT + read_class(unpacker).from_msgpack_ext(unpacker.read) + when LOAD_WITH_JSON_CREATE + read_class(unpacker).json_create(unpacker.read) + else + raise_invalid_format + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_pack/serializer.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_pack/serializer.rb new file mode 100644 index 00000000..c5ce8e18 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_pack/serializer.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require_relative "extensions" + +module ActiveSupport + module MessagePack + module Serializer # :nodoc: + SIGNATURE = "\xCC\x80".b.freeze # == 128.to_msgpack + SIGNATURE_INT = 128 + + def dump(object) + message_pack_pool.packer do |packer| + packer.write(SIGNATURE_INT) + packer.write(object) + packer.full_pack + end + end + + def load(dumped) + message_pack_pool.unpacker do |unpacker| + unpacker.feed_reference(dumped) + raise "Invalid serialization format" unless unpacker.read == SIGNATURE_INT + unpacker.full_unpack + end + end + + def signature?(dumped) + dumped.getbyte(0) == SIGNATURE.getbyte(0) && dumped.getbyte(1) == SIGNATURE.getbyte(1) + end + + def message_pack_factory + @message_pack_factory ||= ::MessagePack::Factory.new + end + + def message_pack_factory=(factory) + @message_pack_pool = nil + @message_pack_factory = factory + end + + delegate :register_type, to: :message_pack_factory + + def warmup + message_pack_pool # eagerly compute + end + + private + def message_pack_pool + @message_pack_pool ||= begin + unless message_pack_factory.frozen? + Extensions.install(message_pack_factory) + install_unregistered_type_handler + message_pack_factory.freeze + end + message_pack_factory.pool(ENV.fetch("RAILS_MAX_THREADS", 5).to_i) + end + end + + def install_unregistered_type_handler + Extensions.install_unregistered_type_error(message_pack_factory) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_verifier.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_verifier.rb new file mode 100644 index 00000000..b89ed55f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_verifier.rb @@ -0,0 +1,377 @@ +# frozen_string_literal: true + +require "openssl" +require "base64" +require "active_support/core_ext/object/blank" +require "active_support/security_utils" +require "active_support/messages/codec" +require "active_support/messages/rotator" + +module ActiveSupport + # = Active Support Message Verifier + # + # +MessageVerifier+ makes it easy to generate and verify messages which are + # signed to prevent tampering. + # + # In a \Rails application, you can use +Rails.application.message_verifier+ + # to manage unique instances of verifiers for each use case. + # {Learn more}[link:classes/Rails/Application.html#method-i-message_verifier]. + # + # This is useful for cases like remember-me tokens and auto-unsubscribe links + # where the session store isn't suitable or available. + # + # First, generate a signed message: + # cookies[:remember_me] = Rails.application.message_verifier(:remember_me).generate([@user.id, 2.weeks.from_now]) + # + # Later verify that message: + # + # id, time = Rails.application.message_verifier(:remember_me).verify(cookies[:remember_me]) + # if time.future? + # self.current_user = User.find(id) + # end + # + # === Signing is not encryption + # + # The signed messages are not encrypted. The payload is merely encoded (Base64 by default) and can be decoded by + # anyone. The signature is just assuring that the message wasn't tampered with. For example: + # + # message = Rails.application.message_verifier('my_purpose').generate('never put secrets here') + # # => "BAhJIhtuZXZlciBwdXQgc2VjcmV0cyBoZXJlBjoGRVQ=--a0c1c0827919da5e949e989c971249355735e140" + # Base64.decode64(message.split("--").first) # no key needed + # # => 'never put secrets here' + # + # If you also need to encrypt the contents, you must use ActiveSupport::MessageEncryptor instead. + # + # === Confine messages to a specific purpose + # + # It's not recommended to use the same verifier for different purposes in your application. + # Doing so could allow a malicious actor to re-use a signed message to perform an unauthorized + # action. + # You can reduce this risk by confining signed messages to a specific +:purpose+. + # + # token = @verifier.generate("signed message", purpose: :login) + # + # Then that same purpose must be passed when verifying to get the data back out: + # + # @verifier.verified(token, purpose: :login) # => "signed message" + # @verifier.verified(token, purpose: :shipping) # => nil + # @verifier.verified(token) # => nil + # + # @verifier.verify(token, purpose: :login) # => "signed message" + # @verifier.verify(token, purpose: :shipping) # => raises ActiveSupport::MessageVerifier::InvalidSignature + # @verifier.verify(token) # => raises ActiveSupport::MessageVerifier::InvalidSignature + # + # Likewise, if a message has no purpose it won't be returned when verifying with + # a specific purpose. + # + # token = @verifier.generate("signed message") + # @verifier.verified(token, purpose: :redirect) # => nil + # @verifier.verified(token) # => "signed message" + # + # @verifier.verify(token, purpose: :redirect) # => raises ActiveSupport::MessageVerifier::InvalidSignature + # @verifier.verify(token) # => "signed message" + # + # === Expiring messages + # + # By default messages last forever and verifying one year from now will still + # return the original value. But messages can be set to expire at a given + # time with +:expires_in+ or +:expires_at+. + # + # @verifier.generate("signed message", expires_in: 1.month) + # @verifier.generate("signed message", expires_at: Time.now.end_of_year) + # + # Messages can then be verified and returned until expiry. + # Thereafter, the +verified+ method returns +nil+ while +verify+ raises + # +ActiveSupport::MessageVerifier::InvalidSignature+. + # + # === Rotating keys + # + # MessageVerifier also supports rotating out old configurations by falling + # back to a stack of verifiers. Call +rotate+ to build and add a verifier so + # either +verified+ or +verify+ will also try verifying with the fallback. + # + # By default any rotated verifiers use the values of the primary + # verifier unless specified otherwise. + # + # You'd give your verifier the new defaults: + # + # verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA512", serializer: JSON) + # + # Then gradually rotate the old values out by adding them as fallbacks. Any message + # generated with the old values will then work until the rotation is removed. + # + # verifier.rotate(old_secret) # Fallback to an old secret instead of @secret. + # verifier.rotate(digest: "SHA256") # Fallback to an old digest instead of SHA512. + # verifier.rotate(serializer: Marshal) # Fallback to an old serializer instead of JSON. + # + # Though the above would most likely be combined into one rotation: + # + # verifier.rotate(old_secret, digest: "SHA256", serializer: Marshal) + class MessageVerifier < Messages::Codec + prepend Messages::Rotator + + class InvalidSignature < StandardError; end + + SEPARATOR = "--" # :nodoc: + SEPARATOR_LENGTH = SEPARATOR.length # :nodoc: + + # Initialize a new MessageVerifier with a secret for the signature. + # + # ==== Options + # + # [+:digest+] + # Digest used for signing. The default is "SHA1". See + # +OpenSSL::Digest+ for alternatives. + # + # [+:serializer+] + # The serializer used to serialize message data. You can specify any + # object that responds to +dump+ and +load+, or you can choose from + # several preconfigured serializers: +:marshal+, +:json_allow_marshal+, + # +:json+, +:message_pack_allow_marshal+, +:message_pack+. + # + # The preconfigured serializers include a fallback mechanism to support + # multiple deserialization formats. For example, the +:marshal+ serializer + # will serialize using +Marshal+, but can deserialize using +Marshal+, + # ActiveSupport::JSON, or ActiveSupport::MessagePack. This makes it easy + # to migrate between serializers. + # + # The +:marshal+, +:json_allow_marshal+, and +:message_pack_allow_marshal+ + # serializers support deserializing using +Marshal+, but the others do + # not. Beware that +Marshal+ is a potential vector for deserialization + # attacks in cases where a message signing secret has been leaked. If + # possible, choose a serializer that does not support +Marshal+. + # + # The +:message_pack+ and +:message_pack_allow_marshal+ serializers use + # ActiveSupport::MessagePack, which can roundtrip some Ruby types that are + # not supported by JSON, and may provide improved performance. However, + # these require the +msgpack+ gem. + # + # When using \Rails, the default depends on +config.active_support.message_serializer+. + # Otherwise, the default is +:marshal+. + # + # [+:url_safe+] + # By default, MessageVerifier generates RFC 4648 compliant strings which are + # not URL-safe. In other words, they can contain "+" and "/". If you want to + # generate URL-safe strings (in compliance with "Base 64 Encoding with URL + # and Filename Safe Alphabet" in RFC 4648), you can pass +true+. + # Note that MessageVerifier will always accept both URL-safe and URL-unsafe + # encoded messages, to allow a smooth transition between the two settings. + # + # [+:force_legacy_metadata_serializer+] + # Whether to use the legacy metadata serializer, which serializes the + # message first, then wraps it in an envelope which is also serialized. This + # was the default in \Rails 7.0 and below. + # + # If you don't pass a truthy value, the default is set using + # +config.active_support.use_message_serializer_for_metadata+. + def initialize(secret, **options) + raise ArgumentError, "Secret should not be nil." unless secret + super(**options) + @secret = secret + @digest = options[:digest]&.to_s || "SHA1" + end + + # Checks if a signed message could have been generated by signing an object + # with the +MessageVerifier+'s secret. + # + # verifier = ActiveSupport::MessageVerifier.new("secret") + # signed_message = verifier.generate("signed message") + # verifier.valid_message?(signed_message) # => true + # + # tampered_message = signed_message.chop # editing the message invalidates the signature + # verifier.valid_message?(tampered_message) # => false + def valid_message?(message) + !!catch_and_ignore(:invalid_message_format) { extract_encoded(message) } + end + + # Decodes the signed message using the +MessageVerifier+'s secret. + # + # verifier = ActiveSupport::MessageVerifier.new("secret") + # + # signed_message = verifier.generate("signed message") + # verifier.verified(signed_message) # => "signed message" + # + # Returns +nil+ if the message was not signed with the same secret. + # + # other_verifier = ActiveSupport::MessageVerifier.new("different_secret") + # other_verifier.verified(signed_message) # => nil + # + # Returns +nil+ if the message is not Base64-encoded. + # + # invalid_message = "f--46a0120593880c733a53b6dad75b42ddc1c8996d" + # verifier.verified(invalid_message) # => nil + # + # Raises any error raised while decoding the signed message. + # + # incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff" + # verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format + # + # ==== Options + # + # [+:purpose+] + # The purpose that the message was generated with. If the purpose does not + # match, +verified+ will return +nil+. + # + # message = verifier.generate("hello", purpose: "greeting") + # verifier.verified(message, purpose: "greeting") # => "hello" + # verifier.verified(message, purpose: "chatting") # => nil + # verifier.verified(message) # => nil + # + # message = verifier.generate("bye") + # verifier.verified(message) # => "bye" + # verifier.verified(message, purpose: "greeting") # => nil + # + def verified(message, **options) + catch_and_ignore :invalid_message_format do + catch_and_raise :invalid_message_serialization do + catch_and_ignore :invalid_message_content do + read_message(message, **options) + end + end + end + end + + # Decodes the signed message using the +MessageVerifier+'s secret. + # + # verifier = ActiveSupport::MessageVerifier.new("secret") + # signed_message = verifier.generate("signed message") + # + # verifier.verify(signed_message) # => "signed message" + # + # Raises +InvalidSignature+ if the message was not signed with the same + # secret or was not Base64-encoded. + # + # other_verifier = ActiveSupport::MessageVerifier.new("different_secret") + # other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature + # + # ==== Options + # + # [+:purpose+] + # The purpose that the message was generated with. If the purpose does not + # match, +verify+ will raise ActiveSupport::MessageVerifier::InvalidSignature. + # + # message = verifier.generate("hello", purpose: "greeting") + # verifier.verify(message, purpose: "greeting") # => "hello" + # verifier.verify(message, purpose: "chatting") # => raises InvalidSignature + # verifier.verify(message) # => raises InvalidSignature + # + # message = verifier.generate("bye") + # verifier.verify(message) # => "bye" + # verifier.verify(message, purpose: "greeting") # => raises InvalidSignature + # + def verify(message, **options) + catch_and_raise :invalid_message_format, as: InvalidSignature do + catch_and_raise :invalid_message_serialization do + catch_and_raise :invalid_message_content, as: InvalidSignature do + read_message(message, **options) + end + end + end + end + + # Generates a signed message for the provided value. + # + # The message is signed with the +MessageVerifier+'s secret. + # Returns Base64-encoded message joined with the generated signature. + # + # verifier = ActiveSupport::MessageVerifier.new("secret") + # verifier.generate("signed message") # => "BAhJIhNzaWduZWQgbWVzc2FnZQY6BkVU--f67d5f27c3ee0b8483cebf2103757455e947493b" + # + # ==== Options + # + # [+:expires_at+] + # The datetime at which the message expires. After this datetime, + # verification of the message will fail. + # + # message = verifier.generate("hello", expires_at: Time.now.tomorrow) + # verifier.verified(message) # => "hello" + # # 24 hours later... + # verifier.verified(message) # => nil + # verifier.verify(message) # => raises ActiveSupport::MessageVerifier::InvalidSignature + # + # [+:expires_in+] + # The duration for which the message is valid. After this duration has + # elapsed, verification of the message will fail. + # + # message = verifier.generate("hello", expires_in: 24.hours) + # verifier.verified(message) # => "hello" + # # 24 hours later... + # verifier.verified(message) # => nil + # verifier.verify(message) # => raises ActiveSupport::MessageVerifier::InvalidSignature + # + # [+:purpose+] + # The purpose of the message. If specified, the same purpose must be + # specified when verifying the message; otherwise, verification will fail. + # (See #verified and #verify.) + def generate(value, **options) + create_message(value, **options) + end + + def create_message(value, **options) # :nodoc: + sign_encoded(encode(serialize_with_metadata(value, **options))) + end + + def read_message(message, **options) # :nodoc: + deserialize_with_metadata(decode(extract_encoded(message)), **options) + end + + def inspect # :nodoc: + "#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>" + end + + private + def decode(encoded, url_safe: @url_safe) + catch :invalid_message_format do + return super + end + super(encoded, url_safe: !url_safe) + end + + def sign_encoded(encoded) + digest = generate_digest(encoded) + encoded << SEPARATOR << digest + end + + def extract_encoded(signed) + if signed.nil? || !signed.valid_encoding? + throw :invalid_message_format, "invalid message string" + end + + if separator_index = separator_index_for(signed) + encoded = signed[0, separator_index] + digest = signed[separator_index + SEPARATOR_LENGTH, digest_length_in_hex] + end + + unless digest_matches_data?(digest, encoded) + throw :invalid_message_format, "mismatched digest" + end + + encoded + end + + def generate_digest(data) + OpenSSL::HMAC.hexdigest(@digest, @secret, data) + end + + def digest_length_in_hex + # In hexadecimal (AKA base16) it takes 4 bits to represent a character, + # hence we multiply the digest's length (in bytes) by 8 to get it in + # bits and divide by 4 to get its number of characters it hex. Well, 8 + # divided by 4 is 2. + @digest_length_in_hex ||= OpenSSL::Digest.new(@digest).digest_length * 2 + end + + def separator_at?(signed_message, index) + signed_message[index, SEPARATOR_LENGTH] == SEPARATOR + end + + def separator_index_for(signed_message) + index = signed_message.length - digest_length_in_hex - SEPARATOR_LENGTH + index unless index.negative? || !separator_at?(signed_message, index) + end + + def digest_matches_data?(digest, data) + data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data)) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_verifiers.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_verifiers.rb new file mode 100644 index 00000000..bbbd5b41 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/message_verifiers.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +require "active_support/messages/rotation_coordinator" + +module ActiveSupport + class MessageVerifiers < Messages::RotationCoordinator + ## + # :attr_accessor: transitional + # + # If true, the first two rotation option sets are swapped when building + # message verifiers. For example, with the following configuration, message + # verifiers will generate messages using serializer: Marshal, url_safe: true, + # and will able to verify messages that were generated using any of the + # three option sets: + # + # verifiers = ActiveSupport::MessageVerifiers.new { ... } + # verifiers.rotate(serializer: JSON, url_safe: true) + # verifiers.rotate(serializer: Marshal, url_safe: true) + # verifiers.rotate(serializer: Marshal, url_safe: false) + # verifiers.transitional = true + # + # This can be useful when performing a rolling deploy of an application, + # wherein servers that have not yet been updated must still be able to + # verify messages from updated servers. In such a scenario, first perform a + # rolling deploy with the new rotation (e.g. serializer: JSON, url_safe: true) + # as the first rotation and transitional = true. Then, after all + # servers have been updated, perform a second rolling deploy with + # transitional = false. + + ## + # :singleton-method: new + # :call-seq: new(&secret_generator) + # + # Initializes a new instance. +secret_generator+ must accept a salt, and + # return a suitable secret (string). +secret_generator+ may also accept + # arbitrary kwargs. If #rotate is called with any options matching those + # kwargs, those options will be passed to +secret_generator+ instead of to + # the message verifier. + # + # verifiers = ActiveSupport::MessageVerifiers.new do |salt, base:| + # MySecretGenerator.new(base).generate(salt) + # end + # + # verifiers.rotate(base: "...") + + ## + # :method: [] + # :call-seq: [](salt) + # + # Returns a MessageVerifier configured with a secret derived from the + # given +salt+, and options from #rotate. MessageVerifier instances will + # be memoized, so the same +salt+ will return the same instance. + + ## + # :method: []= + # :call-seq: []=(salt, verifier) + # + # Overrides a MessageVerifier instance associated with a given +salt+. + + ## + # :method: rotate + # :call-seq: + # rotate(**options) + # rotate(&block) + # + # Adds +options+ to the list of option sets. Messages will be signed using + # the first set in the list. When verifying, however, each set will be + # tried, in order, until one succeeds. + # + # Notably, the +:secret_generator+ option can specify a different secret + # generator than the one initially specified. The secret generator must + # respond to +call+, accept a salt, and return a suitable secret (string). + # The secret generator may also accept arbitrary kwargs. + # + # If any options match the kwargs of the operative secret generator, those + # options will be passed to the secret generator instead of to the message + # verifier. + # + # For fine-grained per-salt rotations, a block form is supported. The block + # will receive the salt, and should return an appropriate options Hash. The + # block may also return +nil+ to indicate that the rotation does not apply + # to the given salt. For example: + # + # verifiers = ActiveSupport::MessageVerifiers.new { ... } + # + # verifiers.rotate do |salt| + # case salt + # when :foo + # { serializer: JSON, url_safe: true } + # when :bar + # { serializer: Marshal, url_safe: true } + # end + # end + # + # verifiers.rotate(serializer: Marshal, url_safe: false) + # + # # Uses `serializer: JSON, url_safe: true`. + # # Falls back to `serializer: Marshal, url_safe: false`. + # verifiers[:foo] + # + # # Uses `serializer: Marshal, url_safe: true`. + # # Falls back to `serializer: Marshal, url_safe: false`. + # verifiers[:bar] + # + # # Uses `serializer: Marshal, url_safe: false`. + # verifiers[:baz] + + ## + # :method: rotate_defaults + # :call-seq: rotate_defaults + # + # Invokes #rotate with the default options. + + ## + # :method: clear_rotations + # :call-seq: clear_rotations + # + # Clears the list of option sets. + + ## + # :method: on_rotation + # :call-seq: on_rotation(&callback) + # + # Sets a callback to invoke when a message is verified using an option set + # other than the first. + # + # For example, this callback could log each time it is called, and thus + # indicate whether old option sets are still in use or can be removed from + # rotation. + + ## + private + def build(salt, secret_generator:, secret_generator_options:, **options) + MessageVerifier.new(secret_generator.call(salt, **secret_generator_options), **options) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/messages/codec.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/messages/codec.rb new file mode 100644 index 00000000..5d2c755e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/messages/codec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require "active_support/core_ext/class/attribute" +require_relative "metadata" +require_relative "serializer_with_fallback" + +module ActiveSupport + module Messages # :nodoc: + class Codec # :nodoc: + include Metadata + + class_attribute :default_serializer, default: :marshal, + instance_accessor: false, instance_predicate: false + + def initialize(**options) + @serializer = options[:serializer] || self.class.default_serializer + @serializer = SerializerWithFallback[@serializer] if @serializer.is_a?(Symbol) + @url_safe = options[:url_safe] + @force_legacy_metadata_serializer = options[:force_legacy_metadata_serializer] + end + + private + attr_reader :serializer + + def encode(data, url_safe: @url_safe) + url_safe ? ::Base64.urlsafe_encode64(data, padding: false) : ::Base64.strict_encode64(data) + end + + def decode(encoded, url_safe: @url_safe) + url_safe ? ::Base64.urlsafe_decode64(encoded) : ::Base64.strict_decode64(encoded) + rescue StandardError => error + throw :invalid_message_format, error + end + + def serialize(data) + serializer.dump(data) + end + + def deserialize(serialized) + serializer.load(serialized) + rescue StandardError => error + throw :invalid_message_serialization, error + end + + def catch_and_ignore(throwable, &block) + catch throwable do + return block.call + end + nil + end + + def catch_and_raise(throwable, as: nil, &block) + error = catch throwable do + return block.call + end + error = as.new(error.to_s) if as + raise error + end + + def use_message_serializer_for_metadata? + !@force_legacy_metadata_serializer && super + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/messages/metadata.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/messages/metadata.rb new file mode 100644 index 00000000..33b81aa9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/messages/metadata.rb @@ -0,0 +1,146 @@ +# frozen_string_literal: true + +require "time" +require "active_support/json" +require_relative "serializer_with_fallback" + +module ActiveSupport + module Messages # :nodoc: + module Metadata # :nodoc: + singleton_class.attr_accessor :use_message_serializer_for_metadata + + ENVELOPE_SERIALIZERS = [ + *SerializerWithFallback::SERIALIZERS.values, + ActiveSupport::JSON, + ::JSON, + Marshal, + ] + + TIMESTAMP_SERIALIZERS = [ + SerializerWithFallback::SERIALIZERS.fetch(:message_pack), + SerializerWithFallback::SERIALIZERS.fetch(:message_pack_allow_marshal), + ] + + ActiveSupport.on_load(:message_pack) do + ENVELOPE_SERIALIZERS << ActiveSupport::MessagePack + TIMESTAMP_SERIALIZERS << ActiveSupport::MessagePack + end + + private + def serialize_with_metadata(data, **metadata) + has_metadata = metadata.any? { |k, v| v } + + if has_metadata && !use_message_serializer_for_metadata? + data_string = serialize_to_json_safe_string(data) + envelope = wrap_in_metadata_legacy_envelope({ "message" => data_string }, **metadata) + serialize_to_json(envelope) + else + data = wrap_in_metadata_envelope({ "data" => data }, **metadata) if has_metadata + serialize(data) + end + end + + def deserialize_with_metadata(message, **expected_metadata) + if dual_serialized_metadata_envelope_json?(message) + envelope = deserialize_from_json(message) + extracted = extract_from_metadata_envelope(envelope, **expected_metadata) + deserialize_from_json_safe_string(extracted["message"]) + else + deserialized = deserialize(message) + if metadata_envelope?(deserialized) + extract_from_metadata_envelope(deserialized, **expected_metadata)["data"] + elsif expected_metadata.none? { |k, v| v } + deserialized + else + throw :invalid_message_content, "missing metadata" + end + end + end + + def use_message_serializer_for_metadata? + Metadata.use_message_serializer_for_metadata && Metadata::ENVELOPE_SERIALIZERS.include?(serializer) + end + + def wrap_in_metadata_envelope(hash, expires_at: nil, expires_in: nil, purpose: nil) + expiry = pick_expiry(expires_at, expires_in) + hash["exp"] = expiry if expiry + hash["pur"] = purpose.to_s if purpose + { "_rails" => hash } + end + + def wrap_in_metadata_legacy_envelope(hash, expires_at: nil, expires_in: nil, purpose: nil) + expiry = pick_expiry(expires_at, expires_in) + hash["exp"] = expiry + hash["pur"] = purpose + { "_rails" => hash } + end + + def extract_from_metadata_envelope(envelope, purpose: nil) + hash = envelope["_rails"] + + if hash["exp"] && Time.now.utc >= parse_expiry(hash["exp"]) + throw :invalid_message_content, "expired" + end + + if hash["pur"].to_s != purpose.to_s + throw :invalid_message_content, "mismatched purpose" + end + + hash + end + + def metadata_envelope?(object) + object.is_a?(Hash) && object.key?("_rails") + end + + def dual_serialized_metadata_envelope_json?(string) + string.start_with?('{"_rails":{"message":') + end + + def pick_expiry(expires_at, expires_in) + expiry = if expires_at + expires_at.utc + elsif expires_in + Time.now.utc.advance(seconds: expires_in) + end + + unless Metadata::TIMESTAMP_SERIALIZERS.include?(serializer) + expiry = expiry&.iso8601(3) + end + + expiry + end + + def parse_expiry(expires_at) + if !expires_at.is_a?(String) + expires_at + elsif ActiveSupport.use_standard_json_time_format + Time.iso8601(expires_at) + else + Time.parse(expires_at) + end + end + + def serialize_to_json(data) + ActiveSupport::JSON.encode(data) + end + + def deserialize_from_json(serialized) + ActiveSupport::JSON.decode(serialized) + rescue ::JSON::ParserError => error + # Throw :invalid_message_format instead of :invalid_message_serialization + # because here a parse error is due to a bad message rather than an + # incompatible `self.serializer`. + throw :invalid_message_format, error + end + + def serialize_to_json_safe_string(data) + encode(serialize(data), url_safe: false) + end + + def deserialize_from_json_safe_string(string) + deserialize(decode(string, url_safe: false)) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/messages/rotation_configuration.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/messages/rotation_configuration.rb new file mode 100644 index 00000000..eef05fe3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/messages/rotation_configuration.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module ActiveSupport + module Messages + class RotationConfiguration # :nodoc: + attr_reader :signed, :encrypted + + def initialize + @signed, @encrypted = [], [] + end + + def rotate(kind, *args, **options) + args << options unless options.empty? + case kind + when :signed + @signed << args + when :encrypted + @encrypted << args + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/messages/rotation_coordinator.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/messages/rotation_coordinator.rb new file mode 100644 index 00000000..131b479e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/messages/rotation_coordinator.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/slice" + +module ActiveSupport + module Messages + class RotationCoordinator # :nodoc: + attr_accessor :transitional + + def initialize(&secret_generator) + raise ArgumentError, "A secret generator block is required" unless secret_generator + @secret_generator = secret_generator + @rotate_options = [] + @on_rotation = nil + @codecs = {} + end + + def [](salt) + @codecs[salt] ||= build_with_rotations(salt) + end + + def []=(salt, codec) + @codecs[salt] = codec + end + + def rotate(**options, &block) + raise ArgumentError, "Options cannot be specified when using a block" if block && !options.empty? + changing_configuration! + + @rotate_options << (block || options) + + self + end + + def rotate_defaults + rotate() + end + + def clear_rotations + changing_configuration! + @rotate_options.clear + self + end + + def on_rotation(&callback) + changing_configuration! + @on_rotation = callback + end + + private + def changing_configuration! + if @codecs.any? + raise <<~MESSAGE + Cannot change #{self.class} configuration after it has already been applied. + + The configuration has been applied with the following salts: + #{@codecs.keys.map { |salt| "- #{salt.inspect}" }.join("\n")} + MESSAGE + end + end + + def normalize_options(options) + options = options.dup + + options[:secret_generator] ||= @secret_generator + + secret_generator_kwargs = options[:secret_generator].parameters. + filter_map { |type, name| name if type == :key || type == :keyreq } + options[:secret_generator_options] = options.extract!(*secret_generator_kwargs) + + options[:on_rotation] = @on_rotation + + options + end + + def build_with_rotations(salt) + rotate_options = @rotate_options.map { |options| options.is_a?(Proc) ? options.(salt) : options } + transitional = self.transitional && rotate_options.first + rotate_options.compact! + rotate_options[0..1] = rotate_options[0..1].reverse if transitional + rotate_options = rotate_options.map { |options| normalize_options(options) }.uniq + + raise "No options have been configured for #{salt}" if rotate_options.empty? + + rotate_options.map { |options| build(salt.to_s, **options) }.reduce(&:fall_back_to) + end + + def build(salt, secret_generator:, secret_generator_options:, **options) + raise NotImplementedError + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/messages/rotator.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/messages/rotator.rb new file mode 100644 index 00000000..d54a11e4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/messages/rotator.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module ActiveSupport + module Messages + module Rotator # :nodoc: + def initialize(*args, on_rotation: nil, **options) + super(*args, **options) + @args = args + @options = options + @rotations = [] + @on_rotation = on_rotation + end + + def rotate(*args, **options) + fall_back_to build_rotation(*args, **options) + end + + def on_rotation(&on_rotation) + @on_rotation = on_rotation + self + end + + def fall_back_to(fallback) + @rotations << fallback + self + end + + def read_message(message, on_rotation: @on_rotation, **options) + if @rotations.empty? + super(message, **options) + else + thrown, error = catch_rotation_error do + return super(message, **options) + end + + @rotations.each do |rotation| + catch_rotation_error do + value = rotation.read_message(message, **options) + on_rotation&.call + return value + end + end + + throw thrown, error + end + end + + private + def build_rotation(*args, **options) + self.class.new(*args, *@args.drop(args.length), **@options, **options) + end + + def catch_rotation_error(&block) + error = catch :invalid_message_format do + error = catch :invalid_message_serialization do + return [nil, block.call] + end + return [:invalid_message_serialization, error] + end + [:invalid_message_format, error] + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/messages/serializer_with_fallback.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/messages/serializer_with_fallback.rb new file mode 100644 index 00000000..dd96c6d7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/messages/serializer_with_fallback.rb @@ -0,0 +1,158 @@ +# frozen_string_literal: true + +require "active_support/core_ext/kernel/reporting" +require "active_support/notifications" + +module ActiveSupport + module Messages # :nodoc: + module SerializerWithFallback # :nodoc: + def self.[](format) + if format.to_s.include?("message_pack") && !defined?(ActiveSupport::MessagePack) + require "active_support/message_pack" + end + + SERIALIZERS.fetch(format) + end + + def load(dumped) + format = detect_format(dumped) + + if format == self.format + _load(dumped) + elsif format && fallback?(format) + payload = { serializer: SERIALIZERS.key(self), fallback: format, serialized: dumped } + ActiveSupport::Notifications.instrument("message_serializer_fallback.active_support", payload) do + payload[:deserialized] = SERIALIZERS[format]._load(dumped) + end + else + raise "Unsupported serialization format" + end + end + + private + def detect_format(dumped) + case + when MessagePackWithFallback.dumped?(dumped) + :message_pack + when MarshalWithFallback.dumped?(dumped) + :marshal + when JsonWithFallback.dumped?(dumped) + :json + end + end + + def fallback?(format) + format != :marshal + end + + module AllowMarshal + private + def fallback?(format) + super || format == :marshal + end + end + + module MarshalWithFallback + include SerializerWithFallback + extend self + + def format + :marshal + end + + def dump(object) + Marshal.dump(object) + end + + def _load(dumped) + Marshal.load(dumped) + end + + MARSHAL_SIGNATURE = "\x04\x08" + + def dumped?(dumped) + dumped.start_with?(MARSHAL_SIGNATURE) + end + end + + module JsonWithFallback + include SerializerWithFallback + extend self + + def format + :json + end + + def dump(object) + ActiveSupport::JSON.encode(object) + end + + def _load(dumped) + ActiveSupport::JSON.decode(dumped) + end + + JSON_START_WITH = /\A(?:[{\["]|-?\d|true|false|null)/ + + def dumped?(dumped) + JSON_START_WITH.match?(dumped) + end + + private + def detect_format(dumped) + # Assume JSON format if format could not be determined. + super || :json + end + end + + module JsonWithFallbackAllowMarshal + include JsonWithFallback + include AllowMarshal + extend self + end + + module MessagePackWithFallback + include SerializerWithFallback + extend self + + def format + :message_pack + end + + def dump(object) + ActiveSupport::MessagePack.dump(object) + end + + def _load(dumped) + ActiveSupport::MessagePack.load(dumped) + end + + def dumped?(dumped) + available? && ActiveSupport::MessagePack.signature?(dumped) + end + + private + def available? + return @available if defined?(@available) + silence_warnings { require "active_support/message_pack" } + @available = true + rescue LoadError + @available = false + end + end + + module MessagePackWithFallbackAllowMarshal + include MessagePackWithFallback + include AllowMarshal + extend self + end + + SERIALIZERS = { + marshal: MarshalWithFallback, + json: JsonWithFallback, + json_allow_marshal: JsonWithFallbackAllowMarshal, + message_pack: MessagePackWithFallback, + message_pack_allow_marshal: MessagePackWithFallbackAllowMarshal, + } + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/multibyte.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/multibyte.rb new file mode 100644 index 00000000..03663506 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/multibyte.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module ActiveSupport # :nodoc: + module Multibyte + autoload :Chars, "active_support/multibyte/chars" + autoload :Unicode, "active_support/multibyte/unicode" + + # The proxy class returned when calling mb_chars. You can use this accessor + # to configure your own proxy class so you can support other encodings. See + # the ActiveSupport::Multibyte::Chars implementation for an example how to + # do this. + # + # ActiveSupport::Multibyte.proxy_class = CharsForUTF32 + def self.proxy_class=(klass) + @proxy_class = klass + end + + # Returns the current proxy class. + def self.proxy_class + @proxy_class ||= ActiveSupport::Multibyte::Chars + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/multibyte/chars.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/multibyte/chars.rb new file mode 100644 index 00000000..05ced5dd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/multibyte/chars.rb @@ -0,0 +1,181 @@ +# frozen_string_literal: true + +require "active_support/json" +require "active_support/core_ext/string/access" +require "active_support/core_ext/string/behavior" +require "active_support/core_ext/module/delegation" + +module ActiveSupport # :nodoc: + module Multibyte # :nodoc: + # = Active Support \Multibyte \Chars + # + # Chars enables you to work transparently with UTF-8 encoding in the Ruby + # String class without having extensive knowledge about the encoding. A + # Chars object accepts a string upon initialization and proxies String + # methods in an encoding safe manner. All the normal String methods are also + # implemented on the proxy. + # + # String methods are proxied through the Chars object, and can be accessed + # through the +mb_chars+ method. Methods which would normally return a + # String object now return a Chars object so methods can be chained. + # + # 'The Perfect String '.mb_chars.downcase.strip + # # => # + # + # Chars objects are perfectly interchangeable with String objects as long as + # no explicit class checks are made. If certain methods do explicitly check + # the class, call +to_s+ before you pass chars objects to them. + # + # bad.explicit_checking_method 'T'.mb_chars.downcase.to_s + # + # The default Chars implementation assumes that the encoding of the string + # is UTF-8, if you want to handle different encodings you can write your own + # multibyte string handler and configure it through + # ActiveSupport::Multibyte.proxy_class. + # + # class CharsForUTF32 + # def size + # @wrapped_string.size / 4 + # end + # + # def self.accepts?(string) + # string.length % 4 == 0 + # end + # end + # + # ActiveSupport::Multibyte.proxy_class = CharsForUTF32 + class Chars + include Comparable + attr_reader :wrapped_string + alias to_s wrapped_string + alias to_str wrapped_string + + delegate :<=>, :=~, :match?, :acts_like_string?, to: :wrapped_string + + # Creates a new Chars instance by wrapping _string_. + def initialize(string) + @wrapped_string = string + if string.encoding != Encoding::UTF_8 + @wrapped_string = @wrapped_string.dup + @wrapped_string.force_encoding(Encoding::UTF_8) + end + end + + # Forward all undefined methods to the wrapped string. + def method_missing(method, ...) + result = @wrapped_string.__send__(method, ...) + if method.end_with?("!") + self if result + else + result.kind_of?(String) ? chars(result) : result + end + end + + # Returns +true+ if _obj_ responds to the given method. Private methods + # are included in the search only if the optional second parameter + # evaluates to +true+. + def respond_to_missing?(method, include_private) + @wrapped_string.respond_to?(method, include_private) + end + + # Works just like String#split, with the exception that the items + # in the resulting list are Chars instances instead of String. This makes + # chaining methods easier. + # + # 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"] + def split(*args) + @wrapped_string.split(*args).map { |i| self.class.new(i) } + end + + # Works like String#slice!, but returns an instance of + # Chars, or +nil+ if the string was not modified. The string will not be + # modified if the range given is out of bounds + # + # string = 'Welcome' + # string.mb_chars.slice!(3) # => # + # string # => 'Welome' + # string.mb_chars.slice!(0..3) # => # + # string # => 'me' + def slice!(*args) + string_sliced = @wrapped_string.slice!(*args) + if string_sliced + chars(string_sliced) + end + end + + # Reverses all characters in the string. + # + # 'Café'.mb_chars.reverse.to_s # => 'éfaC' + def reverse + chars(@wrapped_string.grapheme_clusters.reverse.join) + end + + # Limits the byte size of the string to a number of bytes without breaking + # characters. Usable when the storage for a string is limited for some + # reason. + # + # 'こんにちは'.mb_chars.limit(7).to_s # => "こん" + def limit(limit) + chars(@wrapped_string.truncate_bytes(limit, omission: nil)) + end + + # Capitalizes the first letter of every word, when possible. + # + # "ÉL QUE SE ENTERÓ".mb_chars.titleize.to_s # => "Él Que Se Enteró" + # "日本語".mb_chars.titleize.to_s # => "日本語" + def titleize + chars(downcase.to_s.gsub(/\b('?\S)/u) { $1.upcase }) + end + alias_method :titlecase, :titleize + + # Performs canonical decomposition on all the characters. + # + # 'é'.length # => 1 + # 'é'.mb_chars.decompose.to_s.length # => 2 + def decompose + chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack("U*")) + end + + # Performs composition on all the characters. + # + # 'é'.length # => 1 + # 'é'.mb_chars.compose.to_s.length # => 1 + def compose + chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack("U*")) + end + + # Returns the number of grapheme clusters in the string. + # + # 'क्षि'.mb_chars.length # => 4 + # 'क्षि'.mb_chars.grapheme_length # => 2 + def grapheme_length + @wrapped_string.grapheme_clusters.length + end + + # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent + # resulting in a valid UTF-8 string. + # + # Passing +true+ will forcibly tidy all bytes, assuming that the string's + # encoding is entirely CP1252 or ISO-8859-1. + def tidy_bytes(force = false) + chars(Unicode.tidy_bytes(@wrapped_string, force)) + end + + def as_json(options = nil) # :nodoc: + to_s.as_json(options) + end + + %w(reverse tidy_bytes).each do |method| + define_method("#{method}!") do |*args| + @wrapped_string = public_send(method, *args).to_s + self + end + end + + private + def chars(string) + self.class.new(string) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/multibyte/unicode.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/multibyte/unicode.rb new file mode 100644 index 00000000..c4cb642b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/multibyte/unicode.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module ActiveSupport + module Multibyte + module Unicode + extend self + + # The Unicode version that is supported by the implementation + UNICODE_VERSION = RbConfig::CONFIG["UNICODE_VERSION"] + + # Decompose composed characters to the decomposed form. + def decompose(type, codepoints) + if type == :compatibility + codepoints.pack("U*").unicode_normalize(:nfkd).codepoints + else + codepoints.pack("U*").unicode_normalize(:nfd).codepoints + end + end + + # Compose decomposed characters to the composed form. + def compose(codepoints) + codepoints.pack("U*").unicode_normalize(:nfc).codepoints + end + + # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent + # resulting in a valid UTF-8 string. + # + # Passing +true+ will forcibly tidy all bytes, assuming that the string's + # encoding is entirely CP1252 or ISO-8859-1. + def tidy_bytes(string, force = false) + return string if string.empty? || string.ascii_only? + return recode_windows1252_chars(string) if force + string.scrub { |bad| recode_windows1252_chars(bad) } + end + + private + def recode_windows1252_chars(string) + string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/notifications.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/notifications.rb new file mode 100644 index 00000000..1a54dc24 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/notifications.rb @@ -0,0 +1,281 @@ +# frozen_string_literal: true + +require "active_support/notifications/instrumenter" +require "active_support/notifications/fanout" + +module ActiveSupport + # = \Notifications + # + # +ActiveSupport::Notifications+ provides an instrumentation API for + # Ruby. + # + # == Instrumenters + # + # To instrument an event you just need to do: + # + # ActiveSupport::Notifications.instrument('render', extra: :information) do + # render plain: 'Foo' + # end + # + # That first executes the block and then notifies all subscribers once done. + # + # In the example above +render+ is the name of the event, and the rest is called + # the _payload_. The payload is a mechanism that allows instrumenters to pass + # extra information to subscribers. Payloads consist of a hash whose contents + # are arbitrary and generally depend on the event. + # + # == Subscribers + # + # You can consume those events and the information they provide by registering + # a subscriber. + # + # ActiveSupport::Notifications.subscribe('render') do |event| + # event.name # => "render" + # event.duration # => 10 (in milliseconds) + # event.payload # => { extra: :information } + # event.allocations # => 1826 (objects) + # end + # + # +Event+ objects record CPU time and allocations. If you don't need this + # it's also possible to pass a block that accepts five arguments: + # + # ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload| + # name # => String, name of the event (such as 'render' from above) + # start # => Time, when the instrumented block started execution + # finish # => Time, when the instrumented block ended execution + # id # => String, unique ID for the instrumenter that fired the event + # payload # => Hash, the payload + # end + # + # Here, the +start+ and +finish+ values represent wall-clock time. If you are + # concerned about accuracy, you can register a monotonic subscriber. + # + # ActiveSupport::Notifications.monotonic_subscribe('render') do |name, start, finish, id, payload| + # name # => String, name of the event (such as 'render' from above) + # start # => Float, monotonic time when the instrumented block started execution + # finish # => Float, monotonic time when the instrumented block ended execution + # id # => String, unique ID for the instrumenter that fired the event + # payload # => Hash, the payload + # end + # + # For instance, let's store all "render" events in an array: + # + # events = [] + # + # ActiveSupport::Notifications.subscribe('render') do |event| + # events << event + # end + # + # That code returns right away, you are just subscribing to "render" events. + # The block is saved and will be called whenever someone instruments "render": + # + # ActiveSupport::Notifications.instrument('render', extra: :information) do + # render plain: 'Foo' + # end + # + # event = events.first + # event.name # => "render" + # event.duration # => 10 (in milliseconds) + # event.payload # => { extra: :information } + # event.allocations # => 1826 (objects) + # + # If an exception happens during that particular instrumentation the payload will + # have a key :exception with an array of two elements as value: a string with + # the name of the exception class, and the exception message. + # The :exception_object key of the payload will have the exception + # itself as the value: + # + # event.payload[:exception] # => ["ArgumentError", "Invalid value"] + # event.payload[:exception_object] # => # + # + # As the earlier example depicts, the class ActiveSupport::Notifications::Event + # is able to take the arguments as they come and provide an object-oriented + # interface to that data. + # + # It is also possible to pass an object which responds to call method + # as the second parameter to the subscribe method instead of a block: + # + # module ActionController + # class PageRequest + # def call(name, started, finished, unique_id, payload) + # Rails.logger.debug ['notification:', name, started, finished, unique_id, payload].join(' ') + # end + # end + # end + # + # ActiveSupport::Notifications.subscribe('process_action.action_controller', ActionController::PageRequest.new) + # + # resulting in the following output within the logs including a hash with the payload: + # + # notification: process_action.action_controller 2012-04-13 01:08:35 +0300 2012-04-13 01:08:35 +0300 af358ed7fab884532ec7 { + # controller: "Devise::SessionsController", + # action: "new", + # params: {"action"=>"new", "controller"=>"devise/sessions"}, + # format: :html, + # method: "GET", + # path: "/login/sign_in", + # status: 200, + # view_runtime: 279.3080806732178, + # db_runtime: 40.053 + # } + # + # You can also subscribe to all events whose name matches a certain regexp: + # + # ActiveSupport::Notifications.subscribe(/render/) do |*args| + # ... + # end + # + # and even pass no argument to subscribe, in which case you are subscribing + # to all events. + # + # == Temporary Subscriptions + # + # Sometimes you do not want to subscribe to an event for the entire life of + # the application. There are two ways to unsubscribe. + # + # WARNING: The instrumentation framework is designed for long-running subscribers, + # use this feature sparingly because it wipes some internal caches and that has + # a negative impact on performance. + # + # === Subscribe While a Block Runs + # + # You can subscribe to some event temporarily while some block runs. For + # example, in + # + # callback = lambda {|event| ... } + # ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do + # ... + # end + # + # the callback will be called for all "sql.active_record" events instrumented + # during the execution of the block. The callback is unsubscribed automatically + # after that. + # + # To record +started+ and +finished+ values with monotonic time, + # specify the optional :monotonic option to the + # subscribed method. The :monotonic option is set + # to +false+ by default. + # + # callback = lambda {|name, started, finished, unique_id, payload| ... } + # ActiveSupport::Notifications.subscribed(callback, "sql.active_record", monotonic: true) do + # ... + # end + # + # === Manual Unsubscription + # + # The +subscribe+ method returns a subscriber object: + # + # subscriber = ActiveSupport::Notifications.subscribe("render") do |event| + # ... + # end + # + # To prevent that block from being called anymore, just unsubscribe passing + # that reference: + # + # ActiveSupport::Notifications.unsubscribe(subscriber) + # + # You can also unsubscribe by passing the name of the subscriber object. Note + # that this will unsubscribe all subscriptions with the given name: + # + # ActiveSupport::Notifications.unsubscribe("render") + # + # Subscribers using a regexp or other pattern-matching object will remain subscribed + # to all events that match their original pattern, unless those events match a string + # passed to +unsubscribe+: + # + # subscriber = ActiveSupport::Notifications.subscribe(/render/) { } + # ActiveSupport::Notifications.unsubscribe('render_template.action_view') + # subscriber.matches?('render_template.action_view') # => false + # subscriber.matches?('render_partial.action_view') # => true + # + # == Default Queue + # + # Notifications ships with a queue implementation that consumes and publishes events + # to all log subscribers. You can use any queue implementation you want. + # + module Notifications + class << self + attr_accessor :notifier + + def publish(name, *args) + notifier.publish(name, *args) + end + + def publish_event(event) # :nodoc: + notifier.publish_event(event) + end + + def instrument(name, payload = {}) + if notifier.listening?(name) + instrumenter.instrument(name, payload) { yield payload if block_given? } + else + yield payload if block_given? + end + end + + # Subscribe to a given event name with the passed +block+. + # + # You can subscribe to events by passing a String to match exact event + # names, or by passing a Regexp to match all events that match a pattern. + # + # If the block passed to the method only takes one argument, + # it will yield an +Event+ object to the block: + # + # ActiveSupport::Notifications.subscribe(/render/) do |event| + # @event = event + # end + # + # Otherwise the +block+ will receive five arguments with information + # about the event: + # + # ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload| + # name # => String, name of the event (such as 'render' from above) + # start # => Time, when the instrumented block started execution + # finish # => Time, when the instrumented block ended execution + # id # => String, unique ID for the instrumenter that fired the event + # payload # => Hash, the payload + # end + # + # Raises an error if invalid event name type is passed: + # + # ActiveSupport::Notifications.subscribe(:render) {|event| ...} + # #=> ArgumentError (pattern must be specified as a String, Regexp or empty) + # + def subscribe(pattern = nil, callback = nil, &block) + notifier.subscribe(pattern, callback, monotonic: false, &block) + end + + # Performs the same functionality as #subscribe, but the +start+ and + # +finish+ block arguments are in monotonic time instead of wall-clock + # time. Monotonic time will not jump forward or backward (due to NTP or + # Daylights Savings). Use +monotonic_subscribe+ when accuracy of time + # duration is important. For example, computing elapsed time between + # two events. + def monotonic_subscribe(pattern = nil, callback = nil, &block) + notifier.subscribe(pattern, callback, monotonic: true, &block) + end + + def subscribed(callback, pattern = nil, monotonic: false, &block) + subscriber = notifier.subscribe(pattern, callback, monotonic: monotonic) + yield + ensure + unsubscribe(subscriber) + end + + def unsubscribe(subscriber_or_name) + notifier.unsubscribe(subscriber_or_name) + end + + def instrumenter + registry[notifier] ||= Instrumenter.new(notifier) + end + + private + def registry + ActiveSupport::IsolatedExecutionState[:active_support_notifications_registry] ||= {} + end + end + + self.notifier = Fanout.new + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/notifications/fanout.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/notifications/fanout.rb new file mode 100644 index 00000000..ecf4b7c9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/notifications/fanout.rb @@ -0,0 +1,445 @@ +# frozen_string_literal: true + +require "concurrent/map" +require "active_support/core_ext/object/try" + +module ActiveSupport + module Notifications + class InstrumentationSubscriberError < RuntimeError + attr_reader :exceptions + + def initialize(exceptions) + @exceptions = exceptions + exception_class_names = exceptions.map { |e| e.class.name } + super "Exception(s) occurred within instrumentation subscribers: #{exception_class_names.join(', ')}" + end + end + + module FanoutIteration # :nodoc: + private + def iterate_guarding_exceptions(collection) + exceptions = nil + + collection.each do |s| + yield s + rescue Exception => e + exceptions ||= [] + exceptions << e + end + + if exceptions + exceptions = exceptions.flat_map do |exception| + exception.is_a?(InstrumentationSubscriberError) ? exception.exceptions : [exception] + end + if exceptions.size == 1 + raise exceptions.first + else + raise InstrumentationSubscriberError.new(exceptions), cause: exceptions.first + end + end + + collection + end + end + + # This is a default queue implementation that ships with Notifications. + # It just pushes events to all registered log subscribers. + # + # This class is thread safe. All methods are reentrant. + class Fanout + def initialize + @mutex = Mutex.new + @string_subscribers = Concurrent::Map.new { |h, k| h.compute_if_absent(k) { [] } } + @other_subscribers = [] + @all_listeners_for = Concurrent::Map.new + @groups_for = Concurrent::Map.new + @silenceable_groups_for = Concurrent::Map.new + end + + def inspect # :nodoc: + total_patterns = @string_subscribers.size + @other_subscribers.size + "#<#{self.class} (#{total_patterns} patterns)>" + end + + def subscribe(pattern = nil, callable = nil, monotonic: false, &block) + subscriber = Subscribers.new(pattern, callable || block, monotonic) + @mutex.synchronize do + case pattern + when String + @string_subscribers[pattern] << subscriber + clear_cache(pattern) + when NilClass, Regexp + @other_subscribers << subscriber + clear_cache + else + raise ArgumentError, "pattern must be specified as a String, Regexp or empty" + end + end + subscriber + end + + def unsubscribe(subscriber_or_name) + @mutex.synchronize do + case subscriber_or_name + when String + @string_subscribers[subscriber_or_name].clear + clear_cache(subscriber_or_name) + @other_subscribers.each { |sub| sub.unsubscribe!(subscriber_or_name) } + else + pattern = subscriber_or_name.try(:pattern) + if String === pattern + @string_subscribers[pattern].delete(subscriber_or_name) + clear_cache(pattern) + else + @other_subscribers.delete(subscriber_or_name) + clear_cache + end + end + end + end + + def clear_cache(key = nil) # :nodoc: + if key + @all_listeners_for.delete(key) + @groups_for.delete(key) + @silenceable_groups_for.delete(key) + else + @all_listeners_for.clear + @groups_for.clear + @silenceable_groups_for.clear + end + end + + class BaseGroup # :nodoc: + include FanoutIteration + + def initialize(listeners, name, id, payload) + @listeners = listeners + end + + def each(&block) + iterate_guarding_exceptions(@listeners, &block) + end + end + + class BaseTimeGroup < BaseGroup # :nodoc: + def start(name, id, payload) + @start_time = now + end + + def finish(name, id, payload) + stop_time = now + each do |listener| + listener.call(name, @start_time, stop_time, id, payload) + end + end + end + + class MonotonicTimedGroup < BaseTimeGroup # :nodoc: + private + def now + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end + end + + class TimedGroup < BaseTimeGroup # :nodoc: + private + def now + Time.now + end + end + + class EventedGroup < BaseGroup # :nodoc: + def start(name, id, payload) + each do |s| + s.start(name, id, payload) + end + end + + def finish(name, id, payload) + each do |s| + s.finish(name, id, payload) + end + end + end + + class EventObjectGroup < BaseGroup # :nodoc: + def start(name, id, payload) + @event = build_event(name, id, payload) + @event.start! + end + + def finish(name, id, payload) + @event.payload = payload + @event.finish! + + each do |s| + s.call(@event) + end + end + + private + def build_event(name, id, payload) + ActiveSupport::Notifications::Event.new name, nil, nil, id, payload + end + end + + def groups_for(name) # :nodoc: + groups = @groups_for.compute_if_absent(name) do + all_listeners_for(name).reject(&:silenceable).group_by(&:group_class).transform_values do |s| + s.map(&:delegate) + end + end + + silenceable_groups = @silenceable_groups_for.compute_if_absent(name) do + all_listeners_for(name).select(&:silenceable).group_by(&:group_class).transform_values do |s| + s.map(&:delegate) + end + end + + unless silenceable_groups.empty? + groups = groups.dup + silenceable_groups.each do |group_class, subscriptions| + active_subscriptions = subscriptions.reject { |s| s.silenced?(name) } + unless active_subscriptions.empty? + groups[group_class] = (groups[group_class] || []) + active_subscriptions + end + end + end + + groups + end + + # A +Handle+ is used to record the start and finish time of event. + # + # Both #start and #finish must each be called exactly once. + # + # Where possible, it's best to use the block form: ActiveSupport::Notifications.instrument. + # +Handle+ is a low-level API intended for cases where the block form can't be used. + # + # handle = ActiveSupport::Notifications.instrumenter.build_handle("my.event", {}) + # begin + # handle.start + # # work to be instrumented + # ensure + # handle.finish + # end + class Handle + include FanoutIteration + + def initialize(notifier, name, id, payload) # :nodoc: + @name = name + @id = id + @payload = payload + @groups = notifier.groups_for(name).map do |group_klass, grouped_listeners| + group_klass.new(grouped_listeners, name, id, payload) + end + @state = :initialized + end + + def start + ensure_state! :initialized + @state = :started + + iterate_guarding_exceptions(@groups) do |group| + group.start(@name, @id, @payload) + end + end + + def finish + finish_with_values(@name, @id, @payload) + end + + def finish_with_values(name, id, payload) # :nodoc: + ensure_state! :started + @state = :finished + + iterate_guarding_exceptions(@groups) do |group| + group.finish(name, id, payload) + end + end + + private + def ensure_state!(expected) + if @state != expected + raise ArgumentError, "expected state to be #{expected.inspect} but was #{@state.inspect}" + end + end + end + + include FanoutIteration + + def build_handle(name, id, payload) + Handle.new(self, name, id, payload) + end + + def start(name, id, payload) + handle_stack = (IsolatedExecutionState[:_fanout_handle_stack] ||= []) + handle = build_handle(name, id, payload) + handle_stack << handle + handle.start + end + + def finish(name, id, payload, listeners = nil) + handle_stack = IsolatedExecutionState[:_fanout_handle_stack] + handle = handle_stack.pop + handle.finish_with_values(name, id, payload) + end + + def publish(name, *args) + iterate_guarding_exceptions(listeners_for(name)) { |s| s.publish(name, *args) } + end + + def publish_event(event) + iterate_guarding_exceptions(listeners_for(event.name)) { |s| s.publish_event(event) } + end + + def all_listeners_for(name) + # this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics) + @all_listeners_for[name] || @mutex.synchronize do + # use synchronisation when accessing @subscribers + @all_listeners_for[name] ||= + @string_subscribers[name] + @other_subscribers.select { |s| s.subscribed_to?(name) } + end + end + + def listeners_for(name) + all_listeners_for(name).reject { |s| s.silenced?(name) } + end + + def listening?(name) + all_listeners_for(name).any? { |s| !s.silenced?(name) } + end + + # This is a sync queue, so there is no waiting. + def wait + end + + module Subscribers # :nodoc: + def self.new(pattern, listener, monotonic) + subscriber_class = monotonic ? MonotonicTimed : Timed + + if listener.respond_to?(:start) && listener.respond_to?(:finish) + subscriber_class = Evented + else + # Doing this to detect a single argument block or callable + # like `proc { |x| }` vs `proc { |*x| }`, `proc { |**x| }`, + # or `proc { |x, **y| }` + procish = listener.respond_to?(:parameters) ? listener : listener.method(:call) + + if procish.arity == 1 && procish.parameters.length == 1 + subscriber_class = EventObject + end + end + + subscriber_class.new(pattern, listener) + end + + class Matcher # :nodoc: + attr_reader :pattern, :exclusions + + def self.wrap(pattern) + if String === pattern + pattern + elsif pattern.nil? + AllMessages.new + else + new(pattern) + end + end + + def initialize(pattern) + @pattern = pattern + @exclusions = Set.new + end + + def unsubscribe!(name) + exclusions << -name if pattern === name + end + + def ===(name) + pattern === name && !exclusions.include?(name) + end + + class AllMessages + def ===(name) + true + end + + def unsubscribe!(*) + false + end + end + end + + class Evented # :nodoc: + attr_reader :pattern, :delegate, :silenceable + + def initialize(pattern, delegate) + @pattern = Matcher.wrap(pattern) + @delegate = delegate + @silenceable = delegate.respond_to?(:silenced?) + @can_publish = delegate.respond_to?(:publish) + @can_publish_event = delegate.respond_to?(:publish_event) + end + + def group_class + EventedGroup + end + + def publish(name, *args) + if @can_publish + @delegate.publish name, *args + end + end + + def publish_event(event) + if @can_publish_event + @delegate.publish_event event + else + publish(event.name, event.time, event.end, event.transaction_id, event.payload) + end + end + + def silenced?(name) + @silenceable && @delegate.silenced?(name) + end + + def subscribed_to?(name) + pattern === name + end + + def unsubscribe!(name) + pattern.unsubscribe!(name) + end + end + + class Timed < Evented # :nodoc: + def group_class + TimedGroup + end + + def publish(name, *args) + @delegate.call name, *args + end + end + + class MonotonicTimed < Timed # :nodoc: + def group_class + MonotonicTimedGroup + end + end + + class EventObject < Evented + def group_class + EventObjectGroup + end + + def publish_event(event) + @delegate.call event + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/notifications/instrumenter.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/notifications/instrumenter.rb new file mode 100644 index 00000000..6143d1b8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/notifications/instrumenter.rb @@ -0,0 +1,240 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/delegation" +require "securerandom" + +module ActiveSupport + module Notifications + # Instrumenters are stored in a thread local. + class Instrumenter + attr_reader :id + + def initialize(notifier) + unless notifier.respond_to?(:build_handle) + notifier = LegacyHandle::Wrapper.new(notifier) + end + + @id = unique_id + @notifier = notifier + end + + class LegacyHandle # :nodoc: + class Wrapper # :nodoc: + def initialize(notifier) + @notifier = notifier + end + + def build_handle(name, id, payload) + LegacyHandle.new(@notifier, name, id, payload) + end + + delegate :start, :finish, to: :@notifier + end + + def initialize(notifier, name, id, payload) + @notifier = notifier + @name = name + @id = id + @payload = payload + end + + def start + @listener_state = @notifier.start @name, @id, @payload + end + + def finish + @notifier.finish(@name, @id, @payload, @listener_state) + end + end + + # Given a block, instrument it by measuring the time taken to execute + # and publish it. Without a block, simply send a message via the + # notifier. Notice that events get sent even if an error occurs in the + # passed-in block. + def instrument(name, payload = {}) + handle = build_handle(name, payload) + handle.start + begin + yield payload if block_given? + rescue Exception => e + payload[:exception] = [e.class.name, e.message] + payload[:exception_object] = e + raise e + ensure + handle.finish + end + end + + # Returns a "handle" for an event with the given +name+ and +payload+. + # + # #start and #finish must each be called exactly once on the returned object. + # + # Where possible, it's best to use #instrument, which will record the + # start and finish of the event and correctly handle any exceptions. + # +build_handle+ is a low-level API intended for cases where using + # +instrument+ isn't possible. + # + # See ActiveSupport::Notifications::Fanout::Handle. + def build_handle(name, payload) + @notifier.build_handle(name, @id, payload) + end + + def new_event(name, payload = {}) # :nodoc: + Event.new(name, nil, nil, @id, payload) + end + + # Send a start notification with +name+ and +payload+. + def start(name, payload) + @notifier.start name, @id, payload + end + + # Send a finish notification with +name+ and +payload+. + def finish(name, payload) + @notifier.finish name, @id, payload + end + + def finish_with_state(listeners_state, name, payload) + @notifier.finish name, @id, payload, listeners_state + end + + private + def unique_id + SecureRandom.hex(10) + end + end + + class Event + attr_reader :name, :transaction_id + attr_accessor :payload + + def initialize(name, start, ending, transaction_id, payload) + @name = name + @payload = payload.dup + @time = start ? start.to_f * 1_000.0 : start + @transaction_id = transaction_id + @end = ending ? ending.to_f * 1_000.0 : ending + @cpu_time_start = 0.0 + @cpu_time_finish = 0.0 + @allocation_count_start = 0 + @allocation_count_finish = 0 + @gc_time_start = 0 + @gc_time_finish = 0 + end + + def time + @time / 1000.0 if @time + end + + def end + @end / 1000.0 if @end + end + + def record # :nodoc: + start! + begin + yield payload if block_given? + rescue Exception => e + payload[:exception] = [e.class.name, e.message] + payload[:exception_object] = e + raise e + ensure + finish! + end + end + + # Record information at the time this event starts + def start! + @time = now + @cpu_time_start = now_cpu + @gc_time_start = now_gc + @allocation_count_start = now_allocations + end + + # Record information at the time this event finishes + def finish! + @cpu_time_finish = now_cpu + @gc_time_finish = now_gc + @end = now + @allocation_count_finish = now_allocations + end + + # Returns the CPU time (in milliseconds) passed between the call to + # #start! and the call to #finish!. + def cpu_time + @cpu_time_finish - @cpu_time_start + end + + # Returns the idle time time (in milliseconds) passed between the call to + # #start! and the call to #finish!. + def idle_time + diff = duration - cpu_time + diff > 0.0 ? diff : 0.0 + end + + # Returns the number of allocations made between the call to #start! and + # the call to #finish!. + def allocations + @allocation_count_finish - @allocation_count_start + end + + # Returns the time spent in GC (in milliseconds) between the call to #start! + # and the call to #finish! + def gc_time + (@gc_time_finish - @gc_time_start) / 1_000_000.0 + end + + # Returns the difference in milliseconds between when the execution of the + # event started and when it ended. + # + # ActiveSupport::Notifications.subscribe('wait') do |event| + # @event = event + # end + # + # ActiveSupport::Notifications.instrument('wait') do + # sleep 1 + # end + # + # @event.duration # => 1000.138 + def duration + @end - @time + end + + private + def now + Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) + end + + begin + Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_millisecond) + + def now_cpu + Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_millisecond) + end + rescue + def now_cpu + 0.0 + end + end + + if GC.respond_to?(:total_time) + def now_gc + GC.total_time + end + else + def now_gc + 0 + end + end + + if GC.stat.key?(:total_allocated_objects) + def now_allocations + GC.stat(:total_allocated_objects) + end + else # Likely on JRuby, TruffleRuby + def now_allocations + 0 + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper.rb new file mode 100644 index 00000000..0c51306b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper.rb @@ -0,0 +1,479 @@ +# frozen_string_literal: true + +module ActiveSupport + # = Number Helper + # + # Provides methods for formatting numbers into currencies, percentages, + # phone numbers, and more. + # + # Example usage in a class: + # class Topic + # include ActiveSupport::NumberHelper + # + # def price + # number_to_currency(@price) + # end + # end + # + # Example usage in a module: + # require "active_support/number_helper" + # + # module NumberFormatting + # def format_price(price) + # ActiveSupport::NumberHelper.number_to_currency(price) + # end + # end + module NumberHelper + extend ActiveSupport::Autoload + + eager_autoload do + autoload :NumberConverter + autoload :RoundingHelper + autoload :NumberToRoundedConverter + autoload :NumberToDelimitedConverter + autoload :NumberToHumanConverter + autoload :NumberToHumanSizeConverter + autoload :NumberToPhoneConverter + autoload :NumberToCurrencyConverter + autoload :NumberToPercentageConverter + end + + extend self + + # Formats +number+ into a phone number. + # + # number_to_phone(5551234) # => "555-1234" + # number_to_phone("5551234") # => "555-1234" + # number_to_phone(1235551234) # => "123-555-1234" + # number_to_phone("12x34") # => "12x34" + # + # number_to_phone(1235551234, delimiter: ".", country_code: 1, extension: 1343) + # # => "+1.123.555.1234 x 1343" + # + # ==== Options + # + # [+:area_code+] + # Whether to use parentheses for the area code. Defaults to false. + # + # number_to_phone(1235551234, area_code: true) + # # => "(123) 555-1234" + # + # [+:delimiter+] + # The digit group delimiter to use. Defaults to "-". + # + # number_to_phone(1235551234, delimiter: " ") + # # => "123 555 1234" + # + # [+:country_code+] + # A country code to prepend. + # + # number_to_phone(1235551234, country_code: 1) + # # => "+1-123-555-1234" + # + # [+:extension+] + # An extension to append. + # + # number_to_phone(1235551234, extension: 555) + # # => "123-555-1234 x 555" + # + # [+:pattern+] + # A regexp that specifies how the digits should be grouped. The first + # three captures from the regexp are treated as digit groups. + # + # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/) + # # => "133-1234-5678" + # number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true) + # # => "(755) 6123-4567" + # + def number_to_phone(number, options = {}) + NumberToPhoneConverter.convert(number, options) + end + + # Formats a +number+ into a currency string. + # + # number_to_currency(1234567890.50) # => "$1,234,567,890.50" + # number_to_currency(1234567890.506) # => "$1,234,567,890.51" + # number_to_currency("12x34") # => "$12x34" + # + # number_to_currency(1234567890.50, unit: "£", separator: ",", delimiter: "") + # # => "£1234567890,50" + # + # The currency unit and number formatting of the current locale will be used + # unless otherwise specified via options. No currency conversion is + # performed. If the user is given a way to change their locale, they will + # also be able to change the relative value of the currency displayed with + # this helper. If your application will ever support multiple locales, you + # may want to specify a constant +:locale+ option or consider using a + # library capable of currency conversion. + # + # ==== Options + # + # [+:locale+] + # The locale to use for formatting. Defaults to the current locale. + # + # number_to_currency(1234567890.506, locale: :fr) + # # => "1 234 567 890,51 €" + # + # [+:precision+] + # The level of precision. Defaults to 2. + # + # number_to_currency(1234567890.123, precision: 3) # => "$1,234,567,890.123" + # number_to_currency(0.456789, precision: 0) # => "$0" + # + # [+:round_mode+] + # Specifies how rounding is performed. See +BigDecimal.mode+. Defaults to + # +:default+. + # + # number_to_currency(1234567890.01, precision: 0, round_mode: :up) + # # => "$1,234,567,891" + # + # [+:unit+] + # The denomination of the currency. Defaults to "$". + # + # [+:separator+] + # The decimal separator. Defaults to ".". + # + # [+:delimiter+] + # The thousands delimiter. Defaults to ",". + # + # [+:format+] + # The format for non-negative numbers. %u represents the currency, + # and %n represents the number. Defaults to "%u%n". + # + # number_to_currency(1234567890.50, format: "%n %u") + # # => "1,234,567,890.50 $" + # + # [+:negative_format+] + # The format for negative numbers. %u and %n behave the + # same as in +:format+, but %n represents the absolute value of + # the number. Defaults to the value of +:format+ prepended with -. + # + # number_to_currency(-1234567890.50, negative_format: "(%u%n)") + # # => "($1,234,567,890.50)" + # + # [+:strip_insignificant_zeros+] + # Whether to remove insignificant zeros after the decimal separator. + # Defaults to false. + # + # number_to_currency(1234567890.50, strip_insignificant_zeros: true) + # # => "$1,234,567,890.5" + # + def number_to_currency(number, options = {}) + NumberToCurrencyConverter.convert(number, options) + end + + # Formats +number+ as a percentage string. + # + # number_to_percentage(100) # => "100.000%" + # number_to_percentage("99") # => "99.000%" + # number_to_percentage("99x") # => "99x%" + # + # number_to_percentage(12345.6789, delimiter: ".", separator: ",", precision: 2) + # # => "12.345,68%" + # + # ==== Options + # + # [+:locale+] + # The locale to use for formatting. Defaults to the current locale. + # + # number_to_percentage(1000, locale: :fr) + # # => "1000,000%" + # + # [+:precision+] + # The level of precision, or +nil+ to preserve +number+'s precision. + # Defaults to 2. + # + # number_to_percentage(12.3456789, precision: 4) # => "12.3457%" + # number_to_percentage(99.999, precision: 0) # => "100%" + # number_to_percentage(99.999, precision: nil) # => "99.999%" + # + # [+:round_mode+] + # Specifies how rounding is performed. See +BigDecimal.mode+. Defaults to + # +:default+. + # + # number_to_percentage(12.3456789, precision: 4, round_mode: :down) + # # => "12.3456%" + # + # [+:significant+] + # Whether +:precision+ should be applied to significant digits instead of + # fractional digits. Defaults to false. + # + # number_to_percentage(12345.6789) # => "12345.679%" + # number_to_percentage(12345.6789, significant: true) # => "12300%" + # number_to_percentage(12345.6789, precision: 2) # => "12345.68%" + # number_to_percentage(12345.6789, precision: 2, significant: true) # => "12000%" + # + # [+:separator+] + # The decimal separator. Defaults to ".". + # + # [+:delimiter+] + # The thousands delimiter. Defaults to ",". + # + # [+:strip_insignificant_zeros+] + # Whether to remove insignificant zeros after the decimal separator. + # Defaults to false. + # + # [+:format+] + # The format of the output. %n represents the number. Defaults to + # "%n%". + # + # number_to_percentage(100, format: "%n %") + # # => "100.000 %" + # + def number_to_percentage(number, options = {}) + NumberToPercentageConverter.convert(number, options) + end + + # Formats +number+ by grouping thousands with a delimiter. + # + # number_to_delimited(12345678) # => "12,345,678" + # number_to_delimited("123456") # => "123,456" + # number_to_delimited(12345678.9876) # => "12,345,678.9876" + # number_to_delimited("12x34") # => "12x34" + # + # number_to_delimited(12345678.9876, delimiter: ".", separator: ",") + # # => "12.345.678,9876" + # + # ==== Options + # + # [+:locale+] + # The locale to use for formatting. Defaults to the current locale. + # + # number_to_delimited(12345678.05, locale: :fr) + # # => "12 345 678,05" + # + # [+:delimiter+] + # The thousands delimiter. Defaults to ",". + # + # number_to_delimited(12345678, delimiter: ".") + # # => "12.345.678" + # + # [+:separator+] + # The decimal separator. Defaults to ".". + # + # number_to_delimited(12345678.05, separator: " ") + # # => "12,345,678 05" + # + # [+:delimiter_pattern+] + # A regexp to determine the placement of delimiters. Helpful when using + # currency formats like INR. + # + # number_to_delimited("123456.78", delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/) + # # => "1,23,456.78" + # + def number_to_delimited(number, options = {}) + NumberToDelimitedConverter.convert(number, options) + end + + # Formats +number+ to a specific level of precision. + # + # number_to_rounded(12345.6789) # => "12345.679" + # number_to_rounded(12345.6789, precision: 2) # => "12345.68" + # number_to_rounded(12345.6789, precision: 0) # => "12345" + # number_to_rounded(12345, precision: 5) # => "12345.00000" + # + # ==== Options + # + # [+:locale+] + # The locale to use for formatting. Defaults to the current locale. + # + # number_to_rounded(111.234, locale: :fr) + # # => "111,234" + # + # [+:precision+] + # The level of precision, or +nil+ to preserve +number+'s precision. + # Defaults to 3. + # + # number_to_rounded(12345.6789, precision: nil) + # # => "12345.6789" + # + # [+:round_mode+] + # Specifies how rounding is performed. See +BigDecimal.mode+. Defaults to + # +:default+. + # + # number_to_rounded(12.34, precision: 0, round_mode: :up) + # # => "13" + # + # [+:significant+] + # Whether +:precision+ should be applied to significant digits instead of + # fractional digits. Defaults to false. + # + # number_to_rounded(12345.6789) # => "12345.679" + # number_to_rounded(12345.6789, significant: true) # => "12300" + # number_to_rounded(12345.6789, precision: 2) # => "12345.68" + # number_to_rounded(12345.6789, precision: 2, significant: true) # => "12000" + # + # [+:separator+] + # The decimal separator. Defaults to ".". + # + # [+:delimiter+] + # The thousands delimiter. Defaults to ",". + # + # [+:strip_insignificant_zeros+] + # Whether to remove insignificant zeros after the decimal separator. + # Defaults to false. + # + # number_to_rounded(12.34, strip_insignificant_zeros: false) # => "12.340" + # number_to_rounded(12.34, strip_insignificant_zeros: true) # => "12.34" + # number_to_rounded(12.3456, strip_insignificant_zeros: true) # => "12.346" + # + def number_to_rounded(number, options = {}) + NumberToRoundedConverter.convert(number, options) + end + + # Formats +number+ as bytes into a more human-friendly representation. + # Useful for reporting file sizes to users. + # + # number_to_human_size(123) # => "123 Bytes" + # number_to_human_size(1234) # => "1.21 KB" + # number_to_human_size(12345) # => "12.1 KB" + # number_to_human_size(1234567) # => "1.18 MB" + # number_to_human_size(1234567890) # => "1.15 GB" + # number_to_human_size(1234567890123) # => "1.12 TB" + # number_to_human_size(1234567890123456) # => "1.1 PB" + # number_to_human_size(1234567890123456789) # => "1.07 EB" + # + # See #number_to_human if you want to pretty-print a generic number. + # + # ==== Options + # + # [+:locale+] + # The locale to use for formatting. Defaults to the current locale. + # + # [+:precision+] + # The level of precision. Defaults to 3. + # + # number_to_human_size(123456, precision: 2) # => "120 KB" + # number_to_human_size(1234567, precision: 2) # => "1.2 MB" + # + # [+:round_mode+] + # Specifies how rounding is performed. See +BigDecimal.mode+. Defaults to + # +:default+. + # + # number_to_human_size(123456, precision: 2, round_mode: :up) + # # => "130 KB" + # + # [+:significant+] + # Whether +:precision+ should be applied to significant digits instead of + # fractional digits. Defaults to true. + # + # [+:separator+] + # The decimal separator. Defaults to ".". + # + # number_to_human_size(1234567, separator: ",") + # # => "1,18 MB" + # + # [+:delimiter+] + # The thousands delimiter. Defaults to ",". + # + # [+:strip_insignificant_zeros+] + # Whether to remove insignificant zeros after the decimal separator. + # Defaults to true. + # + def number_to_human_size(number, options = {}) + NumberToHumanSizeConverter.convert(number, options) + end + + # Formats +number+ into a more human-friendly representation. Useful for + # numbers that can become very large and too hard to read. + # + # number_to_human(123) # => "123" + # number_to_human(1234) # => "1.23 Thousand" + # number_to_human(12345) # => "12.3 Thousand" + # number_to_human(1234567) # => "1.23 Million" + # number_to_human(1234567890) # => "1.23 Billion" + # number_to_human(1234567890123) # => "1.23 Trillion" + # number_to_human(1234567890123456) # => "1.23 Quadrillion" + # number_to_human(1234567890123456789) # => "1230 Quadrillion" + # + # See #number_to_human_size if you want to pretty-print a file size. + # + # ==== Options + # + # [+:locale+] + # The locale to use for formatting. Defaults to the current locale. + # + # [+:precision+] + # The level of precision. Defaults to 3. + # + # number_to_human(123456, precision: 2) # => "120 Thousand" + # number_to_human(123456, precision: 4) # => "123.5 Thousand" + # + # [+:round_mode+] + # Specifies how rounding is performed. See +BigDecimal.mode+. Defaults to + # +:default+. + # + # number_to_human(123456, precision: 2, round_mode: :up) + # # => "130 Thousand" + # + # [+:significant+] + # Whether +:precision+ should be applied to significant digits instead of + # fractional digits. Defaults to true. + # + # [+:separator+] + # The decimal separator. Defaults to ".". + # + # number_to_human(123456, precision: 4, separator: ",") + # # => "123,5 Thousand" + # + # [+:delimiter+] + # The thousands delimiter. Defaults to ",". + # + # [+:strip_insignificant_zeros+] + # Whether to remove insignificant zeros after the decimal separator. + # Defaults to true. + # + # number_to_human(1000000) # => "1 Million" + # number_to_human(1000000, strip_insignificant_zeros: false) # => "1.00 Million" + # number_to_human(10.01) # => "10" + # number_to_human(10.01, strip_insignificant_zeros: false) # => "10.0" + # + # [+:format+] + # The format of the output. %n represents the number, and + # %u represents the quantifier (e.g., "Thousand"). Defaults to + # "%n %u". + # + # [+:units+] + # A Hash of custom unit quantifier names. + # + # number_to_human(1, units: { unit: "m", thousand: "km" }) # => "1 m" + # number_to_human(100, units: { unit: "m", thousand: "km" }) # => "100 m" + # number_to_human(1000, units: { unit: "m", thousand: "km" }) # => "1 km" + # number_to_human(100000, units: { unit: "m", thousand: "km" }) # => "100 km" + # number_to_human(10000000, units: { unit: "m", thousand: "km" }) # => "10000 km" + # + # The following keys are supported for integer units: +:unit+, +:ten+, + # +:hundred+, +:thousand+, +:million+, +:billion+, +:trillion+, + # +:quadrillion+. Additionally, the following keys are supported for + # fractional units: +:deci+, +:centi+, +:mili+, +:micro+, +:nano+, + # +:pico+, +:femto+. + # + # The Hash can also be defined as a scope in an I18n locale. For example: + # + # en: + # distance: + # centi: + # one: "centimeter" + # other: "centimeters" + # unit: + # one: "meter" + # other: "meters" + # thousand: + # one: "kilometer" + # other: "kilometers" + # + # Then it can be specified by name: + # + # number_to_human(1, units: :distance) # => "1 meter" + # number_to_human(100, units: :distance) # => "100 meters" + # number_to_human(1000, units: :distance) # => "1 kilometer" + # number_to_human(100000, units: :distance) # => "100 kilometers" + # number_to_human(10000000, units: :distance) # => "10000 kilometers" + # number_to_human(0.1, units: :distance) # => "10 centimeters" + # number_to_human(0.01, units: :distance) # => "1 centimeter" + # + def number_to_human(number, options = {}) + NumberToHumanConverter.convert(number, options) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_converter.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_converter.rb new file mode 100644 index 00000000..a68838c0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_converter.rb @@ -0,0 +1,190 @@ +# frozen_string_literal: true + +require "bigdecimal" +require "bigdecimal/util" +require "active_support/core_ext/big_decimal/conversions" +require "active_support/core_ext/hash/keys" +require "active_support/i18n" +require "active_support/core_ext/class/attribute" + +module ActiveSupport + module NumberHelper + class NumberConverter # :nodoc: + # Default and i18n option namespace per class + class_attribute :namespace + + # Does the object need a number that is a valid float? + class_attribute :validate_float + + attr_reader :number, :opts + + DEFAULTS = { + # Used in number_to_delimited + # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' + format: { + # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) + separator: ".", + # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three) + delimiter: ",", + # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00) + precision: 3, + # If set to true, precision will mean the number of significant digits instead + # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2) + significant: false, + # If set, the zeros after the decimal separator will always be stripped (e.g.: 1.200 will be 1.2) + strip_insignificant_zeros: false + }, + + # Used in number_to_currency + currency: { + format: { + format: "%u%n", + negative_format: "-%u%n", + unit: "$", + # These five are to override number.format and are optional + separator: ".", + delimiter: ",", + precision: 2, + significant: false, + strip_insignificant_zeros: false + } + }, + + # Used in number_to_percentage + percentage: { + format: { + delimiter: "", + format: "%n%" + } + }, + + # Used in number_to_rounded + precision: { + format: { + delimiter: "" + } + }, + + # Used in number_to_human_size and number_to_human + human: { + format: { + # These five are to override number.format and are optional + delimiter: "", + precision: 3, + significant: true, + strip_insignificant_zeros: true + }, + # Used in number_to_human_size + storage_units: { + # Storage units output formatting. + # %u is the storage unit, %n is the number (default: 2 MB) + format: "%n %u", + units: { + byte: "Bytes", + kb: "KB", + mb: "MB", + gb: "GB", + tb: "TB" + } + }, + # Used in number_to_human + decimal_units: { + format: "%n %u", + # Decimal units output formatting + # By default we will only quantify some of the exponents + # but the commented ones might be defined or overridden + # by the user. + units: { + # femto: Quadrillionth + # pico: Trillionth + # nano: Billionth + # micro: Millionth + # mili: Thousandth + # centi: Hundredth + # deci: Tenth + unit: "", + # ten: + # one: Ten + # other: Tens + # hundred: Hundred + thousand: "Thousand", + million: "Million", + billion: "Billion", + trillion: "Trillion", + quadrillion: "Quadrillion" + } + } + } + } + + def self.convert(number, options) + new(number, options).execute + end + + def initialize(number, options) + @number = number + @opts = options.symbolize_keys + @options = nil + end + + def execute + if !number + nil + elsif validate_float? && !valid_bigdecimal + number + else + convert + end + end + + private + def options + @options ||= format_options.merge(opts) + end + + def format_options + default_format_options.merge!(i18n_format_options) + end + + def default_format_options + options = DEFAULTS[:format].dup + options.merge!(DEFAULTS[namespace][:format]) if namespace + options + end + + def i18n_format_options + locale = opts[:locale] + options = I18n.translate(:'number.format', locale: locale, default: {}).dup + + if namespace + options.merge!(I18n.translate(:"number.#{namespace}.format", locale: locale, default: {})) + end + + options + end + + def translate_number_value_with_default(key, **i18n_options) + I18n.translate(key, default: default_value(key), scope: :number, **i18n_options) + end + + def translate_in_locale(key, **i18n_options) + translate_number_value_with_default(key, locale: options[:locale], **i18n_options) + end + + def default_value(key) + key.split(".").reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] } + end + + def valid_bigdecimal + case number + when Float, Rational + number.to_d(0) + when String + BigDecimal(number, exception: false) + else + number.to_d rescue nil + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_currency_converter.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_currency_converter.rb new file mode 100644 index 00000000..543c7b4f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_currency_converter.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require "active_support/number_helper/number_converter" + +module ActiveSupport + module NumberHelper + class NumberToCurrencyConverter < NumberConverter # :nodoc: + self.namespace = :currency + + def convert + format = options[:format] + + number_d = valid_bigdecimal + if number_d + if number_d.negative? + number_d = number_d.abs + format = options[:negative_format] if (number_d * 10**options[:precision]) >= 0.5 + end + number_s = NumberToRoundedConverter.convert(number_d, options) + else + number_s = number.to_s.strip + format = options[:negative_format] if number_s.sub!(/^-/, "") + end + + format.gsub("%n", number_s).gsub("%u", options[:unit]) + end + + private + def options + @options ||= begin + defaults = default_format_options.merge(i18n_opts) + # Override negative format if format options are given + defaults[:negative_format] = "-#{opts[:format]}" if opts[:format] + defaults.merge!(opts) + end + end + + def i18n_opts + # Set International negative format if it does not exist + i18n = i18n_format_options + i18n[:negative_format] ||= "-#{i18n[:format]}" if i18n[:format] + i18n + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_delimited_converter.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_delimited_converter.rb new file mode 100644 index 00000000..4fb2fb71 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_delimited_converter.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require "active_support/number_helper/number_converter" + +module ActiveSupport + module NumberHelper + class NumberToDelimitedConverter < NumberConverter # :nodoc: + self.validate_float = true + + DEFAULT_DELIMITER_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/ + + def convert + parts.join(options[:separator]) + end + + private + def parts + left, right = number.to_s.split(".") + left.gsub!(delimiter_pattern) do |digit_to_delimit| + "#{digit_to_delimit}#{options[:delimiter]}" + end + [left, right].compact + end + + def delimiter_pattern + options.fetch(:delimiter_pattern, DEFAULT_DELIMITER_REGEX) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_human_converter.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_human_converter.rb new file mode 100644 index 00000000..3f926285 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_human_converter.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require "active_support/number_helper/number_converter" + +module ActiveSupport + module NumberHelper + class NumberToHumanConverter < NumberConverter # :nodoc: + DECIMAL_UNITS = { 0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion, + -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto } + INVERTED_DECIMAL_UNITS = DECIMAL_UNITS.invert + + self.namespace = :human + self.validate_float = true + + def convert # :nodoc: + @number = RoundingHelper.new(options).round(number) + @number = Float(number) + + # For backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files. + unless options.key?(:strip_insignificant_zeros) + options[:strip_insignificant_zeros] = true + end + + units = opts[:units] + exponent = calculate_exponent(units) + @number = number / (10**exponent) + + rounded_number = NumberToRoundedConverter.convert(number, options) + unit = determine_unit(units, exponent) + format.gsub("%n", rounded_number).gsub("%u", unit).strip + end + + private + def format + options[:format] || translate_in_locale("human.decimal_units.format") + end + + def determine_unit(units, exponent) + exp = DECIMAL_UNITS[exponent] + case units + when Hash + units[exp] || "" + when String, Symbol + I18n.translate("#{units}.#{exp}", locale: options[:locale], count: number.to_i) + else + translate_in_locale("human.decimal_units.units.#{exp}", count: number.to_i) + end + end + + def calculate_exponent(units) + exponent = number != 0 ? Math.log10(number.abs).floor : 0 + unit_exponents(units).find { |e| exponent >= e } || 0 + end + + def unit_exponents(units) + case units + when Hash + units + when String, Symbol + I18n.translate(units.to_s, locale: options[:locale], raise: true) + when nil + translate_in_locale("human.decimal_units.units", raise: true) + else + raise ArgumentError, ":units must be a Hash or String translation scope." + end.keys.map { |e_name| INVERTED_DECIMAL_UNITS[e_name] }.sort_by(&:-@) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_human_size_converter.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_human_size_converter.rb new file mode 100644 index 00000000..66688a42 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_human_size_converter.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require "active_support/number_helper/number_converter" + +module ActiveSupport + module NumberHelper + class NumberToHumanSizeConverter < NumberConverter # :nodoc: + STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb, :pb, :eb, :zb] + + self.namespace = :human + self.validate_float = true + + def convert + @number = Float(number) + + # For backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files. + unless options.key?(:strip_insignificant_zeros) + options[:strip_insignificant_zeros] = true + end + + if smaller_than_base? + number_to_format = number.to_i.to_s + else + human_size = number / (base**exponent) + number_to_format = NumberToRoundedConverter.convert(human_size, options) + end + conversion_format.gsub("%n", number_to_format).gsub("%u", unit) + end + + private + def conversion_format + translate_number_value_with_default("human.storage_units.format", locale: options[:locale], raise: true) + end + + def unit + translate_number_value_with_default(storage_unit_key, locale: options[:locale], count: number.to_i, raise: true) + end + + def storage_unit_key + key_end = smaller_than_base? ? "byte" : STORAGE_UNITS[exponent] + "human.storage_units.units.#{key_end}" + end + + def exponent + max = STORAGE_UNITS.size - 1 + exp = (Math.log(number.abs) / Math.log(base)).to_i + exp = max if exp > max # avoid overflow for the highest unit + exp + end + + def smaller_than_base? + number.to_i.abs < base + end + + def base + 1024 + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_percentage_converter.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_percentage_converter.rb new file mode 100644 index 00000000..0c2e190f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_percentage_converter.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "active_support/number_helper/number_converter" + +module ActiveSupport + module NumberHelper + class NumberToPercentageConverter < NumberConverter # :nodoc: + self.namespace = :percentage + + def convert + rounded_number = NumberToRoundedConverter.convert(number, options) + options[:format].gsub("%n", rounded_number) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_phone_converter.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_phone_converter.rb new file mode 100644 index 00000000..d104449a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_phone_converter.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/blank" +require "active_support/number_helper/number_converter" + +module ActiveSupport + module NumberHelper + class NumberToPhoneConverter < NumberConverter # :nodoc: + def convert + str = country_code(opts[:country_code]).dup + str << convert_to_phone_number(number.to_s.strip) + str << phone_ext(opts[:extension]) + end + + private + def convert_to_phone_number(number) + if opts[:area_code] + convert_with_area_code(number) + else + convert_without_area_code(number) + end + end + + def convert_with_area_code(number) + default_pattern = /(\d{1,3})(\d{3})(\d{4}$)/ + number.gsub!(regexp_pattern(default_pattern), + "(\\1) \\2#{delimiter}\\3") + number + end + + def convert_without_area_code(number) + default_pattern = /(\d{0,3})(\d{3})(\d{4})$/ + number.gsub!(regexp_pattern(default_pattern), + "\\1#{delimiter}\\2#{delimiter}\\3") + number.slice!(0, 1) if start_with_delimiter?(number) + number + end + + def start_with_delimiter?(number) + delimiter.present? && number.start_with?(delimiter) + end + + def delimiter + opts[:delimiter] || "-" + end + + def country_code(code) + code.blank? ? "" : "+#{code}#{delimiter}" + end + + def phone_ext(ext) + ext.blank? ? "" : " x #{ext}" + end + + def regexp_pattern(default_pattern) + opts.fetch :pattern, default_pattern + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_rounded_converter.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_rounded_converter.rb new file mode 100644 index 00000000..f48a5158 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/number_to_rounded_converter.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require "active_support/number_helper/number_converter" + +module ActiveSupport + module NumberHelper + class NumberToRoundedConverter < NumberConverter # :nodoc: + self.namespace = :precision + self.validate_float = true + + def convert + helper = RoundingHelper.new(options) + rounded_number = helper.round(number) + + if precision = options[:precision] + if options[:significant] && precision > 0 + digits = helper.digit_count(rounded_number) + precision -= digits + precision = 0 if precision < 0 # don't let it be negative + end + + formatted_string = + if rounded_number.finite? + s = rounded_number.to_s("F") + a, b = s.split(".", 2) + if precision != 0 + b << "0" * precision + a << "." + a << b[0, precision] + end + a + else + # Infinity/NaN + "%f" % rounded_number + end + else + formatted_string = rounded_number + end + + delimited_number = NumberToDelimitedConverter.convert(formatted_string, options) + format_number(delimited_number) + end + + private + def strip_insignificant_zeros + options[:strip_insignificant_zeros] + end + + def format_number(number) + if strip_insignificant_zeros + escaped_separator = Regexp.escape(options[:separator]) + number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, "") + else + number + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/rounding_helper.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/rounding_helper.rb new file mode 100644 index 00000000..14deca13 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/number_helper/rounding_helper.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module ActiveSupport + module NumberHelper + class RoundingHelper # :nodoc: + attr_reader :options + + def initialize(options) + @options = options + end + + def round(number) + precision = absolute_precision(number) + return number unless precision + + rounded_number = convert_to_decimal(number).round(precision, options.fetch(:round_mode, :default).to_sym) + rounded_number.zero? ? rounded_number.abs : rounded_number # prevent showing negative zeros + end + + def digit_count(number) + return 1 if number.zero? + (Math.log10(number.abs) + 1).floor + end + + private + def convert_to_decimal(number) + case number + when Float, String + BigDecimal(number.to_s) + when Rational + BigDecimal(number, digit_count(number.to_i) + options[:precision]) + else + number.to_d + end + end + + def absolute_precision(number) + if options[:significant] && options[:precision] > 0 + options[:precision] - digit_count(convert_to_decimal(number)) + else + options[:precision] + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/option_merger.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/option_merger.rb new file mode 100644 index 00000000..36d99a6e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/option_merger.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/deep_merge" + +module ActiveSupport + class OptionMerger # :nodoc: + instance_methods.each do |method| + undef_method(method) unless method.start_with?("__", "instance_eval", "class", "object_id") + end + + def initialize(context, options) + @context, @options = context, options + end + + private + def method_missing(method, *arguments, &block) + options = nil + if arguments.size == 1 && arguments.first.is_a?(Proc) + proc = arguments.shift + arguments << lambda { |*args| @options.deep_merge(proc.call(*args)) } + elsif arguments.last.respond_to?(:to_hash) + options = @options.deep_merge(arguments.pop) + else + options = @options + end + + if options + @context.__send__(method, *arguments, **options, &block) + else + @context.__send__(method, *arguments, &block) + end + end + + def respond_to_missing?(...) + @context.respond_to?(...) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/ordered_hash.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/ordered_hash.rb new file mode 100644 index 00000000..07cde368 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/ordered_hash.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "yaml" + +YAML.add_builtin_type("omap") do |type, val| + ActiveSupport::OrderedHash[val.map { |v| v.to_a.first }] +end + +module ActiveSupport + # DEPRECATED: +ActiveSupport::OrderedHash+ implements a hash that preserves + # insertion order. + # + # oh = ActiveSupport::OrderedHash.new + # oh[:a] = 1 + # oh[:b] = 2 + # oh.keys # => [:a, :b], this order is guaranteed + # + # Also, maps the +omap+ feature for YAML files + # (See https://yaml.org/type/omap.html) to support ordered items + # when loading from YAML. + # + # +ActiveSupport::OrderedHash+ is namespaced to prevent conflicts + # with other implementations. + class OrderedHash < ::Hash # :nodoc: + def to_yaml_type + "!tag:yaml.org,2002:omap" + end + + def encode_with(coder) + coder.represent_seq "!omap", map { |k, v| { k => v } } + end + + def select(*args, &block) + dup.tap { |hash| hash.select!(*args, &block) } + end + + def reject(*args, &block) + dup.tap { |hash| hash.reject!(*args, &block) } + end + + def nested_under_indifferent_access + self + end + + # Returns true to make sure that this hash is extractable via Array#extract_options! + def extractable_options? + true + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/ordered_options.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/ordered_options.rb new file mode 100644 index 00000000..00048325 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/ordered_options.rb @@ -0,0 +1,147 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/blank" + +module ActiveSupport + # = Ordered Options + # + # +OrderedOptions+ inherits from +Hash+ and provides dynamic accessor methods. + # + # With a +Hash+, key-value pairs are typically managed like this: + # + # h = {} + # h[:boy] = 'John' + # h[:girl] = 'Mary' + # h[:boy] # => 'John' + # h[:girl] # => 'Mary' + # h[:dog] # => nil + # + # Using +OrderedOptions+, the above code can be written as: + # + # h = ActiveSupport::OrderedOptions.new + # h.boy = 'John' + # h.girl = 'Mary' + # h.boy # => 'John' + # h.girl # => 'Mary' + # h.dog # => nil + # + # To raise an exception when the value is blank, append a + # bang to the key name, like: + # + # h.dog! # => raises KeyError: :dog is blank + # + class OrderedOptions < Hash + alias_method :_get, :[] # preserve the original #[] method + protected :_get # make it protected + + def []=(key, value) + super(key.to_sym, value) + end + + def [](key) + super(key.to_sym) + end + + def dig(key, *identifiers) + super(key.to_sym, *identifiers) + end + + def method_missing(method, *args) + if method.end_with?("=") + self[method.name.chomp("=")] = args.first + elsif method.end_with?("!") + name_string = method.name.chomp("!") + self[name_string].presence || raise(KeyError.new(":#{name_string} is blank")) + else + self[method.name] + end + end + + def respond_to_missing?(name, include_private) + true + end + + def extractable_options? + true + end + + def inspect + "#<#{self.class.name} #{super}>" + end + end + + # = Inheritable Options + # + # +InheritableOptions+ provides a constructor to build an OrderedOptions + # hash inherited from another hash. + # + # Use this if you already have some hash and you want to create a new one based on it. + # + # h = ActiveSupport::InheritableOptions.new({ girl: 'Mary', boy: 'John' }) + # h.girl # => 'Mary' + # h.boy # => 'John' + # + # If the existing hash has string keys, call Hash#symbolize_keys on it. + # + # h = ActiveSupport::InheritableOptions.new({ 'girl' => 'Mary', 'boy' => 'John' }.symbolize_keys) + # h.girl # => 'Mary' + # h.boy # => 'John' + class InheritableOptions < OrderedOptions + def initialize(parent = nil) + @parent = parent + if @parent.kind_of?(OrderedOptions) + # use the faster _get when dealing with OrderedOptions + super() { |h, k| @parent._get(k) } + elsif @parent + super() { |h, k| @parent[k] } + else + super() + @parent = {} + end + end + + def to_h + @parent.merge(self) + end + + def ==(other) + to_h == other.to_h + end + + def inspect + "#<#{self.class.name} #{to_h.inspect}>" + end + + def to_s + to_h.to_s + end + + def pretty_print(pp) + pp.pp_hash(to_h) + end + + alias_method :own_key?, :key? + private :own_key? + + def key?(key) + super || @parent.key?(key) + end + + def overridden?(key) + !!(@parent && @parent.key?(key) && own_key?(key.to_sym)) + end + + def inheritable_copy + self.class.new(self) + end + + def to_a + entries + end + + def each(&block) + to_h.each(&block) + self + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/parameter_filter.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/parameter_filter.rb new file mode 100644 index 00000000..7eeae6b2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/parameter_filter.rb @@ -0,0 +1,157 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/duplicable" +require "active_support/core_ext/array/extract" + +module ActiveSupport + # = Active Support Parameter Filter + # + # +ParameterFilter+ replaces values in a Hash-like object if their + # keys match one of the specified filters. + # + # Matching based on nested keys is possible by using dot notation, e.g. + # "credit_card.number". + # + # If a proc is given as a filter, each key and value of the Hash-like + # and of any nested Hashes will be passed to it. The value or key can + # then be mutated as desired using methods such as String#replace. + # + # # Replaces values with "[FILTERED]" for keys that match /password/i. + # ActiveSupport::ParameterFilter.new([:password]) + # + # # Replaces values with "[FILTERED]" for keys that match /foo|bar/i. + # ActiveSupport::ParameterFilter.new([:foo, "bar"]) + # + # # Replaces values for the exact key "pin" and for keys that begin with + # # "pin_". Does not match keys that otherwise include "pin" as a + # # substring, such as "shipping_id". + # ActiveSupport::ParameterFilter.new([/\Apin\z/, /\Apin_/]) + # + # # Replaces the value for :code in `{ credit_card: { code: "xxxx" } }`. + # # Does not change `{ file: { code: "xxxx" } }`. + # ActiveSupport::ParameterFilter.new(["credit_card.code"]) + # + # # Reverses values for keys that match /secret/i. + # ActiveSupport::ParameterFilter.new([-> (k, v) do + # v.reverse! if /secret/i.match?(k) + # end]) + # + class ParameterFilter + FILTERED = "[FILTERED]" # :nodoc: + + # Precompiles an array of filters that otherwise would be passed directly to + # #initialize. Depending on the quantity and types of filters, + # precompilation can improve filtering performance, especially in the case + # where the ParameterFilter instance itself cannot be retained (but the + # precompiled filters can be retained). + # + # filters = [/foo/, :bar, "nested.baz", /nested\.qux/] + # + # precompiled = ActiveSupport::ParameterFilter.precompile_filters(filters) + # # => [/(?-mix:foo)|(?i:bar)/, /(?i:nested\.baz)|(?-mix:nested\.qux)/] + # + # ActiveSupport::ParameterFilter.new(precompiled) + # + def self.precompile_filters(filters) + filters, patterns = filters.partition { |filter| filter.is_a?(Proc) } + + patterns.map! do |pattern| + pattern.is_a?(Regexp) ? pattern : "(?i:#{Regexp.escape pattern.to_s})" + end + + deep_patterns = patterns.extract! { |pattern| pattern.to_s.include?("\\.") } + + filters << Regexp.new(patterns.join("|")) if patterns.any? + filters << Regexp.new(deep_patterns.join("|")) if deep_patterns.any? + + filters + end + + # Create instance with given filters. Supported type of filters are +String+, +Regexp+, and +Proc+. + # Other types of filters are treated as +String+ using +to_s+. + # For +Proc+ filters, key, value, and optional original hash is passed to block arguments. + # + # ==== Options + # + # * :mask - A replaced object when filtered. Defaults to "[FILTERED]". + def initialize(filters = [], mask: FILTERED) + @mask = mask + compile_filters!(filters) + end + + # Mask value of +params+ if key matches one of filters. + def filter(params) + @no_filters ? params.dup : call(params) + end + + # Returns filtered value for given key. For +Proc+ filters, third block argument is not populated. + def filter_param(key, value) + @no_filters ? value : value_for_key(key, value) + end + + private + def compile_filters!(filters) + @no_filters = filters.empty? + return if @no_filters + + @regexps, strings = [], [] + @deep_regexps, deep_strings = nil, nil + @blocks = nil + + filters.each do |item| + case item + when Proc + (@blocks ||= []) << item + when Regexp + if item.to_s.include?("\\.") + (@deep_regexps ||= []) << item + else + @regexps << item + end + else + s = Regexp.escape(item.to_s) + if s.include?("\\.") + (deep_strings ||= []) << s + else + strings << s + end + end + end + + @regexps << Regexp.new(strings.join("|"), true) unless strings.empty? + (@deep_regexps ||= []) << Regexp.new(deep_strings.join("|"), true) if deep_strings + end + + def call(params, full_parent_key = nil, original_params = params) + filtered_params = params.class.new + + params.each do |key, value| + filtered_params[key] = value_for_key(key, value, full_parent_key, original_params) + end + + filtered_params + end + + def value_for_key(key, value, full_parent_key = nil, original_params = nil) + if @deep_regexps + full_key = full_parent_key ? "#{full_parent_key}.#{key}" : key.to_s + end + + if @regexps.any? { |r| r.match?(key.to_s) } + value = @mask + elsif @deep_regexps&.any? { |r| r.match?(full_key) } + value = @mask + elsif value.is_a?(Hash) + value = call(value, full_key, original_params) + elsif value.is_a?(Array) + value = value.map { |v| value_for_key(key, v, full_parent_key, original_params) } + elsif @blocks + key = key.dup if key.duplicable? + value = value.dup if value.duplicable? + @blocks.each { |b| b.arity == 2 ? b.call(key, value) : b.call(key, value, original_params) } + end + + value + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/rails.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/rails.rb new file mode 100644 index 00000000..75676a2e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/rails.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# This is a private interface. +# +# Rails components cherry pick from Active Support as needed, but there are a +# few features that are used for sure in some way or another and it is not worth +# putting individual requires absolutely everywhere. Think blank? for example. +# +# This file is loaded by every Rails component except Active Support itself, +# but it does not belong to the Rails public interface. It is internal to +# Rails and can change anytime. + +# Defines Object#blank? and Object#present?. +require "active_support/core_ext/object/blank" + +# Support for ClassMethods and the included macro. +require "active_support/concern" + +# Defines Class#class_attribute. +require "active_support/core_ext/class/attribute" + +# Defines Module#delegate. +require "active_support/core_ext/module/delegation" + +# Defines ActiveSupport::Deprecation. +require "active_support/deprecation" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/railtie.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/railtie.rb new file mode 100644 index 00000000..eec12b37 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/railtie.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true + +require "active_support" +require "active_support/i18n_railtie" + +module ActiveSupport + class Railtie < Rails::Railtie # :nodoc: + config.active_support = ActiveSupport::OrderedOptions.new + + config.eager_load_namespaces << ActiveSupport + + initializer "active_support.deprecator", before: :load_environment_config do |app| + app.deprecators[:active_support] = ActiveSupport.deprecator + end + + initializer "active_support.isolation_level" do |app| + config.after_initialize do + if level = app.config.active_support.delete(:isolation_level) + ActiveSupport::IsolatedExecutionState.isolation_level = level + end + end + end + + initializer "active_support.raise_on_invalid_cache_expiration_time" do |app| + config.after_initialize do + if app.config.active_support.raise_on_invalid_cache_expiration_time + ActiveSupport::Cache::Store.raise_on_invalid_cache_expiration_time = true + end + end + end + + initializer "active_support.set_authenticated_message_encryption" do |app| + config.after_initialize do + unless app.config.active_support.use_authenticated_message_encryption.nil? + ActiveSupport::MessageEncryptor.use_authenticated_message_encryption = + app.config.active_support.use_authenticated_message_encryption + end + end + end + + initializer "active_support.reset_execution_context" do |app| + app.reloader.before_class_unload { ActiveSupport::ExecutionContext.clear } + app.executor.to_run { ActiveSupport::ExecutionContext.clear } + app.executor.to_complete { ActiveSupport::ExecutionContext.clear } + end + + initializer "active_support.reset_all_current_attributes_instances" do |app| + app.reloader.before_class_unload { ActiveSupport::CurrentAttributes.clear_all } + app.executor.to_run { ActiveSupport::CurrentAttributes.reset_all } + app.executor.to_complete { ActiveSupport::CurrentAttributes.reset_all } + + ActiveSupport.on_load(:active_support_test_case) do + if app.config.active_support.executor_around_test_case + require "active_support/executor/test_helper" + include ActiveSupport::Executor::TestHelper + else + require "active_support/current_attributes/test_helper" + include ActiveSupport::CurrentAttributes::TestHelper + + require "active_support/execution_context/test_helper" + include ActiveSupport::ExecutionContext::TestHelper + end + end + end + + initializer "active_support.deprecation_behavior" do |app| + if app.config.active_support.report_deprecations == false + app.deprecators.silenced = true + app.deprecators.behavior = :silence + app.deprecators.disallowed_behavior = :silence + else + if deprecation = app.config.active_support.deprecation + app.deprecators.behavior = deprecation + end + + if disallowed_deprecation = app.config.active_support.disallowed_deprecation + app.deprecators.disallowed_behavior = disallowed_deprecation + end + + if disallowed_warnings = app.config.active_support.disallowed_deprecation_warnings + app.deprecators.disallowed_warnings = disallowed_warnings + end + end + end + + # Sets the default value for Time.zone + # If assigned value cannot be matched to a TimeZone, an exception will be raised. + initializer "active_support.initialize_time_zone" do |app| + begin + TZInfo::DataSource.get + rescue TZInfo::DataSourceNotFound => e + raise e.exception('tzinfo-data is not present. Please add gem "tzinfo-data" to your Gemfile and run bundle install') + end + require "active_support/core_ext/time/zones" + Time.zone_default = Time.find_zone!(app.config.time_zone) + config.eager_load_namespaces << TZInfo + end + + initializer "active_support.to_time_preserves_timezone" do |app| + config.after_initialize do + ActiveSupport.to_time_preserves_timezone = app.config.active_support.to_time_preserves_timezone + end + end + + # Sets the default week start + # If assigned value is not a valid day symbol (e.g. :sunday, :monday, ...), an exception will be raised. + initializer "active_support.initialize_beginning_of_week" do |app| + require "active_support/core_ext/date/calculations" + beginning_of_week_default = Date.find_beginning_of_week!(app.config.beginning_of_week) + + Date.beginning_of_week_default = beginning_of_week_default + end + + initializer "active_support.require_master_key" do |app| + if app.config.respond_to?(:require_master_key) && app.config.require_master_key + begin + app.credentials.key + rescue ActiveSupport::EncryptedFile::MissingKeyError => error + $stderr.puts error.message + exit 1 + end + end + end + + initializer "active_support.set_configs" do |app| + app.config.active_support.each do |k, v| + k = "#{k}=" + ActiveSupport.public_send(k, v) if ActiveSupport.respond_to? k + end + end + + initializer "active_support.set_hash_digest_class" do |app| + config.after_initialize do + if klass = app.config.active_support.hash_digest_class + ActiveSupport::Digest.hash_digest_class = klass + end + end + end + + initializer "active_support.set_key_generator_hash_digest_class" do |app| + config.after_initialize do + if klass = app.config.active_support.key_generator_hash_digest_class + ActiveSupport::KeyGenerator.hash_digest_class = klass + end + end + end + + initializer "active_support.set_default_message_serializer" do |app| + config.after_initialize do + if message_serializer = app.config.active_support.message_serializer + ActiveSupport::Messages::Codec.default_serializer = message_serializer + end + end + end + + initializer "active_support.set_use_message_serializer_for_metadata" do |app| + config.after_initialize do + ActiveSupport::Messages::Metadata.use_message_serializer_for_metadata = + app.config.active_support.use_message_serializer_for_metadata + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/reloader.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/reloader.rb new file mode 100644 index 00000000..33257ed7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/reloader.rb @@ -0,0 +1,138 @@ +# frozen_string_literal: true + +require "active_support/execution_wrapper" +require "active_support/executor" + +module ActiveSupport + # = Active Support \Reloader + # + # This class defines several callbacks: + # + # to_prepare -- Run once at application startup, and also from + # +to_run+. + # + # to_run -- Run before a work run that is reloading. If + # +reload_classes_only_on_change+ is true (the default), the class + # unload will have already occurred. + # + # to_complete -- Run after a work run that has reloaded. If + # +reload_classes_only_on_change+ is false, the class unload will + # have occurred after the work run, but before this callback. + # + # before_class_unload -- Run immediately before the classes are + # unloaded. + # + # after_class_unload -- Run immediately after the classes are + # unloaded. + # + class Reloader < ExecutionWrapper + define_callbacks :prepare + + define_callbacks :class_unload + + # Registers a callback that will run once at application startup and every time the code is reloaded. + def self.to_prepare(*args, &block) + set_callback(:prepare, *args, &block) + end + + # Registers a callback that will run immediately before the classes are unloaded. + def self.before_class_unload(*args, &block) + set_callback(:class_unload, *args, &block) + end + + # Registers a callback that will run immediately after the classes are unloaded. + def self.after_class_unload(*args, &block) + set_callback(:class_unload, :after, *args, &block) + end + + to_run(:after) { self.class.prepare! } + + # Initiate a manual reload + def self.reload! + executor.wrap do + new.tap do |instance| + instance.run! + ensure + instance.complete! + end + end + prepare! + end + + def self.run!(reset: false) # :nodoc: + if check! + super + else + Null + end + end + + # Run the supplied block as a work unit, reloading code as needed + def self.wrap(**kwargs) + return yield if active? + + executor.wrap(**kwargs) do + instance = run! + begin + yield + ensure + instance.complete! + end + end + end + + class_attribute :executor, default: Executor + class_attribute :check, default: lambda { false } + + def self.check! # :nodoc: + @should_reload ||= check.call + end + + def self.reloaded! # :nodoc: + @should_reload = false + end + + def self.prepare! # :nodoc: + new.run_callbacks(:prepare) + end + + def initialize + super + @locked = false + end + + # Acquire the ActiveSupport::Dependencies::Interlock unload lock, + # ensuring it will be released automatically + def require_unload_lock! + unless @locked + ActiveSupport::Dependencies.interlock.start_unloading + @locked = true + end + end + + # Release the unload lock if it has been previously obtained + def release_unload_lock! + if @locked + @locked = false + ActiveSupport::Dependencies.interlock.done_unloading + end + end + + def run! # :nodoc: + super + release_unload_lock! + end + + def class_unload!(&block) # :nodoc: + require_unload_lock! + run_callbacks(:class_unload, &block) + end + + def complete! # :nodoc: + super + self.class.reloaded! + ensure + release_unload_lock! + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/rescuable.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/rescuable.rb new file mode 100644 index 00000000..470f01d9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/rescuable.rb @@ -0,0 +1,176 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/core_ext/class/attribute" +require "active_support/core_ext/string/inflections" + +module ActiveSupport + # = Active Support \Rescuable + # + # Rescuable module adds support for easier exception handling. + module Rescuable + extend Concern + + included do + class_attribute :rescue_handlers, default: [] + end + + module ClassMethods + # Registers exception classes with a handler to be called by rescue_with_handler. + # + # rescue_from receives a series of exception classes or class + # names, and an exception handler specified by a trailing :with + # option containing the name of a method or a Proc object. Alternatively, a block + # can be given as the handler. + # + # Handlers that take one argument will be called with the exception, so + # that the exception can be inspected when dealing with it. + # + # Handlers are inherited. They are searched from right to left, from + # bottom to top, and up the hierarchy. The handler of the first class for + # which exception.is_a?(klass) holds true is the one invoked, if + # any. + # + # class ApplicationController < ActionController::Base + # rescue_from User::NotAuthorized, with: :deny_access + # rescue_from ActiveRecord::RecordInvalid, with: :show_record_errors + # + # rescue_from "MyApp::BaseError" do |exception| + # redirect_to root_url, alert: exception.message + # end + # + # private + # def deny_access + # head :forbidden + # end + # + # def show_record_errors(exception) + # redirect_back_or_to root_url, alert: exception.record.errors.full_messages.to_sentence + # end + # end + # + # Exceptions raised inside exception handlers are not propagated up. + def rescue_from(*klasses, with: nil, &block) + unless with + if block_given? + with = block + else + raise ArgumentError, "Need a handler. Pass the with: keyword argument or provide a block." + end + end + + klasses.each do |klass| + key = if klass.is_a?(Module) && klass.respond_to?(:===) + klass.name + elsif klass.is_a?(String) + klass + else + raise ArgumentError, "#{klass.inspect} must be an Exception class or a String referencing an Exception class" + end + + # Put the new handler at the end because the list is read in reverse. + self.rescue_handlers += [[key, with]] + end + end + + # Matches an exception to a handler based on the exception class. + # + # If no handler matches the exception, check for a handler matching the + # (optional) +exception.cause+. If no handler matches the exception or its + # cause, this returns +nil+, so you can deal with unhandled exceptions. + # Be sure to re-raise unhandled exceptions if this is what you expect. + # + # begin + # # ... + # rescue => exception + # rescue_with_handler(exception) || raise + # end + # + # Returns the exception if it was handled and +nil+ if it was not. + def rescue_with_handler(exception, object: self, visited_exceptions: []) + visited_exceptions << exception + + if handler = handler_for_rescue(exception, object: object) + handler.call exception + exception + elsif exception + if visited_exceptions.include?(exception.cause) + nil + else + rescue_with_handler(exception.cause, object: object, visited_exceptions: visited_exceptions) + end + end + end + + def handler_for_rescue(exception, object: self) # :nodoc: + case rescuer = find_rescue_handler(exception) + when Symbol + method = object.method(rescuer) + if method.arity == 0 + -> e { method.call } + else + method + end + when Proc + if rescuer.arity == 0 + -> e { object.instance_exec(&rescuer) } + else + -> e { object.instance_exec(e, &rescuer) } + end + end + end + + private + def find_rescue_handler(exception) + if exception + # Handlers are in order of declaration but the most recently declared + # is the highest priority match, so we search for matching handlers + # in reverse. + _, handler = rescue_handlers.reverse_each.detect do |class_or_name, _| + if klass = constantize_rescue_handler_class(class_or_name) + klass === exception + end + end + + handler + end + end + + def constantize_rescue_handler_class(class_or_name) + case class_or_name + when String, Symbol + begin + # Try a lexical lookup first since we support + # + # class Super + # rescue_from 'Error', with: … + # end + # + # class Sub + # class Error < StandardError; end + # end + # + # so an Error raised in Sub will hit the 'Error' handler. + const_get class_or_name + rescue NameError + class_or_name.safe_constantize + end + else + class_or_name + end + end + end + + # Delegates to the class method, but uses the instance as the subject for + # rescue_from handlers (method calls, +instance_exec+ blocks). + def rescue_with_handler(exception) + self.class.rescue_with_handler exception, object: self + end + + # Internal handler lookup. Delegates to class method. Some libraries call + # this directly, so keeping it around for compatibility. + def handler_for_rescue(exception) # :nodoc: + self.class.handler_for_rescue exception, object: self + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/secure_compare_rotator.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/secure_compare_rotator.rb new file mode 100644 index 00000000..1c203898 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/secure_compare_rotator.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require "active_support/security_utils" +require "active_support/messages/rotator" + +module ActiveSupport + # = Secure Compare Rotator + # + # The ActiveSupport::SecureCompareRotator is a wrapper around ActiveSupport::SecurityUtils.secure_compare + # and allows you to rotate a previously defined value to a new one. + # + # It can be used as follow: + # + # rotator = ActiveSupport::SecureCompareRotator.new('new_production_value') + # rotator.rotate('previous_production_value') + # rotator.secure_compare!('previous_production_value') + # + # One real use case example would be to rotate a basic auth credentials: + # + # class MyController < ApplicationController + # def authenticate_request + # rotator = ActiveSupport::SecureCompareRotator.new('new_password') + # rotator.rotate('old_password') + # + # authenticate_or_request_with_http_basic do |username, password| + # rotator.secure_compare!(password) + # rescue ActiveSupport::SecureCompareRotator::InvalidMatch + # false + # end + # end + # end + class SecureCompareRotator + include SecurityUtils + + InvalidMatch = Class.new(StandardError) + + def initialize(value, on_rotation: nil) + @value = value + @rotate_values = [] + @on_rotation = on_rotation + end + + def rotate(previous_value) + @rotate_values << previous_value + end + + def secure_compare!(other_value, on_rotation: @on_rotation) + if secure_compare(@value, other_value) + true + elsif @rotate_values.any? { |value| secure_compare(value, other_value) } + on_rotation&.call + true + else + raise InvalidMatch + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/security_utils.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/security_utils.rb new file mode 100644 index 00000000..aa004744 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/security_utils.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module ActiveSupport + module SecurityUtils + # Constant time string comparison, for fixed length strings. + # + # The values compared should be of fixed length, such as strings + # that have already been processed by HMAC. Raises in case of length mismatch. + + if defined?(OpenSSL.fixed_length_secure_compare) + def fixed_length_secure_compare(a, b) + OpenSSL.fixed_length_secure_compare(a, b) + end + else + def fixed_length_secure_compare(a, b) + raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize + + l = a.unpack "C#{a.bytesize}" + + res = 0 + b.each_byte { |byte| res |= byte ^ l.shift } + res == 0 + end + end + module_function :fixed_length_secure_compare + + # Secure string comparison for strings of variable length. + # + # While a timing attack would not be able to discern the content of + # a secret compared via secure_compare, it is possible to determine + # the secret length. This should be considered when using secure_compare + # to compare weak, short secrets to user input. + def secure_compare(a, b) + a.bytesize == b.bytesize && fixed_length_secure_compare(a, b) + end + module_function :secure_compare + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/string_inquirer.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/string_inquirer.rb new file mode 100644 index 00000000..9b82df19 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/string_inquirer.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module ActiveSupport + # = \String Inquirer + # + # Wrapping a string in this class gives you a prettier way to test + # for equality. The value returned by Rails.env is wrapped + # in a StringInquirer object, so instead of calling this: + # + # Rails.env == 'production' + # + # you can call this: + # + # Rails.env.production? + # + # == Instantiating a new \StringInquirer + # + # vehicle = ActiveSupport::StringInquirer.new('car') + # vehicle.car? # => true + # vehicle.bike? # => false + class StringInquirer < String + private + def respond_to_missing?(method_name, include_private = false) + method_name.end_with?("?") || super + end + + def method_missing(method_name, ...) + if method_name.end_with?("?") + self == method_name[0..-2] + else + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/subscriber.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/subscriber.rb new file mode 100644 index 00000000..19c28906 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/subscriber.rb @@ -0,0 +1,146 @@ +# frozen_string_literal: true + +require "active_support/notifications" + +module ActiveSupport + # = Active Support \Subscriber + # + # +ActiveSupport::Subscriber+ is an object set to consume + # ActiveSupport::Notifications. The subscriber dispatches notifications to + # a registered object based on its given namespace. + # + # An example would be an Active Record subscriber responsible for collecting + # statistics about queries: + # + # module ActiveRecord + # class StatsSubscriber < ActiveSupport::Subscriber + # attach_to :active_record + # + # def sql(event) + # Statsd.timing("sql.#{event.payload[:name]}", event.duration) + # end + # end + # end + # + # After configured, whenever a "sql.active_record" notification is + # published, it will properly dispatch the event + # (ActiveSupport::Notifications::Event) to the +sql+ method. + # + # We can detach a subscriber as well: + # + # ActiveRecord::StatsSubscriber.detach_from(:active_record) + class Subscriber + class << self + # Attach the subscriber to a namespace. + def attach_to(namespace, subscriber = new, notifier = ActiveSupport::Notifications, inherit_all: false) + @namespace = namespace + @subscriber = subscriber + @notifier = notifier + @inherit_all = inherit_all + + subscribers << subscriber + + # Add event subscribers for all existing methods on the class. + fetch_public_methods(subscriber, inherit_all).each do |event| + add_event_subscriber(event) + end + end + + # Detach the subscriber from a namespace. + def detach_from(namespace, notifier = ActiveSupport::Notifications) + @namespace = namespace + @subscriber = find_attached_subscriber + @notifier = notifier + + return unless subscriber + + subscribers.delete(subscriber) + + # Remove event subscribers of all existing methods on the class. + fetch_public_methods(subscriber, true).each do |event| + remove_event_subscriber(event) + end + + # Reset notifier so that event subscribers will not add for new methods added to the class. + @notifier = nil + end + + # Adds event subscribers for all new methods added to the class. + def method_added(event) + super + # Only public methods are added as subscribers, and only if a notifier + # has been set up. This means that subscribers will only be set up for + # classes that call #attach_to. + if public_method_defined?(event) && notifier + add_event_subscriber(event) + end + end + + def subscribers + @@subscribers ||= [] + end + + private + attr_reader :subscriber, :notifier, :namespace + + def add_event_subscriber(event) # :doc: + return if invalid_event?(event) + + pattern = prepare_pattern(event) + + # Don't add multiple subscribers (e.g. if methods are redefined). + return if pattern_subscribed?(pattern) + + subscriber.patterns[pattern] = notifier.subscribe(pattern, subscriber) + end + + def remove_event_subscriber(event) # :doc: + return if invalid_event?(event) + + pattern = prepare_pattern(event) + + return unless pattern_subscribed?(pattern) + + notifier.unsubscribe(subscriber.patterns[pattern]) + subscriber.patterns.delete(pattern) + end + + def find_attached_subscriber + subscribers.find { |attached_subscriber| attached_subscriber.instance_of?(self) } + end + + def invalid_event?(event) + %i{ start finish }.include?(event.to_sym) + end + + def prepare_pattern(event) + "#{event}.#{namespace}" + end + + def pattern_subscribed?(pattern) + subscriber.patterns.key?(pattern) + end + + def fetch_public_methods(subscriber, inherit_all) + subscriber.public_methods(inherit_all) - Subscriber.public_instance_methods(true) + end + end + + attr_reader :patterns # :nodoc: + + def initialize + @patterns = {} + super + end + + def call(event) + method = event.name[0, event.name.index(".")] + send(method, event) + end + + def publish_event(event) # :nodoc: + method = event.name[0, event.name.index(".")] + send(method, event) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/syntax_error_proxy.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/syntax_error_proxy.rb new file mode 100644 index 00000000..f778013f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/syntax_error_proxy.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require "delegate" + +module ActiveSupport + # This is a class for wrapping syntax errors. The purpose of this class + # is to enhance the backtraces on SyntaxError exceptions to include the + # source location of the syntax error. That way we can display the error + # source on error pages in development. + class SyntaxErrorProxy < DelegateClass(SyntaxError) # :nodoc: + def backtrace + parse_message_for_trace + super + end + + class BacktraceLocation < Struct.new(:path, :lineno, :to_s) # :nodoc: + def spot(_) + end + + def label + end + end + + class BacktraceLocationProxy < DelegateClass(Thread::Backtrace::Location) # :nodoc: + def initialize(loc, ex) + super(loc) + @ex = ex + end + + def spot(_) + super(@ex.__getobj__) + end + end + + def backtrace_locations + return nil if super.nil? + + parse_message_for_trace.map { |trace| + file, line = trace.match(/^(.+?):(\d+).*$/, &:captures) || trace + BacktraceLocation.new(file, line.to_i, trace) + # We have to wrap these backtrace locations because we need the + # spot information to come from the originating exception, not the + # proxy object that's generating these + } + super.map { |loc| BacktraceLocationProxy.new(loc, self) } + end + + private + def parse_message_for_trace + if __getobj__.to_s.start_with?("(eval") + # If the exception is coming from a call to eval, we need to keep + # the path of the file in which eval was called to ensure we can + # return the right source fragment to show the location of the + # error + location = __getobj__.backtrace_locations[0] + ["#{location.path}:#{location.lineno}: #{__getobj__}"] + else + __getobj__.to_s.split("\n") + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/tagged_logging.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/tagged_logging.rb new file mode 100644 index 00000000..594ea30e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/tagged_logging.rb @@ -0,0 +1,157 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/object/blank" +require "active_support/logger" + +module ActiveSupport + # = Active Support Tagged Logging + # + # Wraps any standard Logger object to provide tagging capabilities. + # + # May be called with a block: + # + # logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) + # logger.tagged('BCX') { logger.info 'Stuff' } # Logs "[BCX] Stuff" + # logger.tagged('BCX', "Jason") { |tagged_logger| tagged_logger.info 'Stuff' } # Logs "[BCX] [Jason] Stuff" + # logger.tagged('BCX') { logger.tagged('Jason') { logger.info 'Stuff' } } # Logs "[BCX] [Jason] Stuff" + # + # If called without a block, a new logger will be returned with applied tags: + # + # logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) + # logger.tagged("BCX").info "Stuff" # Logs "[BCX] Stuff" + # logger.tagged("BCX", "Jason").info "Stuff" # Logs "[BCX] [Jason] Stuff" + # logger.tagged("BCX").tagged("Jason").info "Stuff" # Logs "[BCX] [Jason] Stuff" + # + # This is used by the default Rails.logger as configured by Railties to make + # it easy to stamp log lines with subdomains, request ids, and anything else + # to aid debugging of multi-user production applications. + module TaggedLogging + module Formatter # :nodoc: + # This method is invoked when a log event occurs. + def call(severity, timestamp, progname, msg) + super(severity, timestamp, progname, tag_stack.format_message(msg)) + end + + def tagged(*tags) + pushed_count = tag_stack.push_tags(tags).size + yield self + ensure + pop_tags(pushed_count) + end + + def push_tags(*tags) + tag_stack.push_tags(tags) + end + + def pop_tags(count = 1) + tag_stack.pop_tags(count) + end + + def clear_tags! + tag_stack.clear + end + + def tag_stack + # We use our object ID here to avoid conflicting with other instances + @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}" + IsolatedExecutionState[@thread_key] ||= TagStack.new + end + + def current_tags + tag_stack.tags + end + + def tags_text + tag_stack.format_message("") + end + end + + class TagStack # :nodoc: + attr_reader :tags + + def initialize + @tags = [] + @tags_string = nil + end + + def push_tags(tags) + @tags_string = nil + tags.flatten! + tags.reject!(&:blank?) + @tags.concat(tags) + tags + end + + def pop_tags(count) + @tags_string = nil + @tags.pop(count) + end + + def clear + @tags_string = nil + @tags.clear + end + + def format_message(message) + if @tags.empty? + message + elsif @tags.size == 1 + "[#{@tags[0]}] #{message}" + else + @tags_string ||= "[#{@tags.join("] [")}] " + "#{@tags_string}#{message}" + end + end + end + + module LocalTagStorage # :nodoc: + attr_accessor :tag_stack + + def self.extended(base) + base.tag_stack = TagStack.new + end + end + + # Returns an `ActiveSupport::Logger` that has already been wrapped with tagged logging concern. + def self.logger(*args, **kwargs) + new ActiveSupport::Logger.new(*args, **kwargs) + end + + def self.new(logger) + logger = logger.clone + + if logger.formatter + logger.formatter = logger.formatter.clone + + # Workaround for https://bugs.ruby-lang.org/issues/20250 + # Can be removed when Ruby 3.4 is the least supported version. + logger.formatter.object_id if logger.formatter.is_a?(Proc) + else + # Ensure we set a default formatter so we aren't extending nil! + logger.formatter = ActiveSupport::Logger::SimpleFormatter.new + end + + logger.formatter.extend Formatter + logger.extend(self) + end + + delegate :push_tags, :pop_tags, :clear_tags!, to: :formatter + + def tagged(*tags) + if block_given? + formatter.tagged(*tags) { yield self } + else + logger = ActiveSupport::TaggedLogging.new(self) + logger.formatter.extend LocalTagStorage + logger.push_tags(*formatter.current_tags, *tags) + logger + end + end + + def flush + clear_tags! + super if defined?(super) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/test_case.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/test_case.rb new file mode 100644 index 00000000..bd2847fc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/test_case.rb @@ -0,0 +1,304 @@ +# frozen_string_literal: true + +require "minitest" +require "active_support/testing/tagged_logging" +require "active_support/testing/setup_and_teardown" +require "active_support/testing/tests_without_assertions" +require "active_support/testing/assertions" +require "active_support/testing/error_reporter_assertions" +require "active_support/testing/deprecation" +require "active_support/testing/declarative" +require "active_support/testing/isolation" +require "active_support/testing/constant_lookup" +require "active_support/testing/time_helpers" +require "active_support/testing/constant_stubbing" +require "active_support/testing/file_fixtures" +require "active_support/testing/parallelization" +require "active_support/testing/parallelize_executor" +require "concurrent/utility/processor_counter" + +module ActiveSupport + class TestCase < ::Minitest::Test + Assertion = Minitest::Assertion + + class << self + # Sets the order in which test cases are run. + # + # ActiveSupport::TestCase.test_order = :random # => :random + # + # Valid values are: + # * +:random+ (to run tests in random order) + # * +:parallel+ (to run tests in parallel) + # * +:sorted+ (to run tests alphabetically by method name) + # * +:alpha+ (equivalent to +:sorted+) + def test_order=(new_order) + ActiveSupport.test_order = new_order + end + + # Returns the order in which test cases are run. + # + # ActiveSupport::TestCase.test_order # => :random + # + # Possible values are +:random+, +:parallel+, +:alpha+, +:sorted+. + # Defaults to +:random+. + def test_order + ActiveSupport.test_order ||= :random + end + + # Parallelizes the test suite. + # + # Takes a +workers+ argument that controls how many times the process + # is forked. For each process a new database will be created suffixed + # with the worker number. + # + # test-database-0 + # test-database-1 + # + # If ENV["PARALLEL_WORKERS"] is set the workers argument will be ignored + # and the environment variable will be used instead. This is useful for CI + # environments, or other environments where you may need more workers than + # you do for local testing. + # + # If the number of workers is set to +1+ or fewer, the tests will not be + # parallelized. + # + # If +workers+ is set to +:number_of_processors+, the number of workers will be + # set to the actual core count on the machine you are on. + # + # The default parallelization method is to fork processes. If you'd like to + # use threads instead you can pass with: :threads to the +parallelize+ + # method. Note the threaded parallelization does not create multiple + # databases and will not work with system tests. + # + # parallelize(workers: :number_of_processors, with: :threads) + # + # The threaded parallelization uses minitest's parallel executor directly. + # The processes parallelization uses a Ruby DRb server. + # + # Because parallelization presents an overhead, it is only enabled when the + # number of tests to run is above the +threshold+ param. The default value is + # 50, and it's configurable via +config.active_support.test_parallelization_threshold+. + def parallelize(workers: :number_of_processors, with: :processes, threshold: ActiveSupport.test_parallelization_threshold) + workers = Concurrent.processor_count if workers == :number_of_processors + workers = ENV["PARALLEL_WORKERS"].to_i if ENV["PARALLEL_WORKERS"] + + Minitest.parallel_executor = ActiveSupport::Testing::ParallelizeExecutor.new(size: workers, with: with, threshold: threshold) + end + + # Set up hook for parallel testing. This can be used if you have multiple + # databases or any behavior that needs to be run after the process is forked + # but before the tests run. + # + # Note: this feature is not available with the threaded parallelization. + # + # In your +test_helper.rb+ add the following: + # + # class ActiveSupport::TestCase + # parallelize_setup do + # # create databases + # end + # end + def parallelize_setup(&block) + ActiveSupport::Testing::Parallelization.after_fork_hook(&block) + end + + # Clean up hook for parallel testing. This can be used to drop databases + # if your app uses multiple write/read databases or other clean up before + # the tests finish. This runs before the forked process is closed. + # + # Note: this feature is not available with the threaded parallelization. + # + # In your +test_helper.rb+ add the following: + # + # class ActiveSupport::TestCase + # parallelize_teardown do + # # drop databases + # end + # end + def parallelize_teardown(&block) + ActiveSupport::Testing::Parallelization.run_cleanup_hook(&block) + end + + # :singleton-method: fixture_paths + # + # Returns the ActiveRecord::FixtureSet collection. + # + # In your +test_helper.rb+ you must have require "rails/test_help". + + # :singleton-method: fixture_paths= + # + # :call-seq: + # fixture_paths=(fixture_paths) + # + # Sets the given path to the fixture set. + # + # Can also append multiple paths. + # + # ActiveSupport::TestCase.fixture_paths << "component1/test/fixtures" + # + # In your +test_helper.rb+ you must have require "rails/test_help". + end + + alias_method :method_name, :name + + include ActiveSupport::Testing::TaggedLogging + prepend ActiveSupport::Testing::SetupAndTeardown + prepend ActiveSupport::Testing::TestsWithoutAssertions + include ActiveSupport::Testing::Assertions + include ActiveSupport::Testing::ErrorReporterAssertions + include ActiveSupport::Testing::Deprecation + include ActiveSupport::Testing::ConstantStubbing + include ActiveSupport::Testing::TimeHelpers + include ActiveSupport::Testing::FileFixtures + extend ActiveSupport::Testing::Declarative + + ## + # :method: assert_not_empty + # + # :call-seq: + # assert_not_empty(obj, msg = nil) + # + # Alias for: refute_empty[https://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-refute_empty] + + # + alias :assert_not_empty :refute_empty + + ## + # :method: assert_not_equal + # + # :call-seq: + # assert_not_equal(exp, act, msg = nil) + # + # Alias for: refute_equal[https://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-refute_equal] + + # + alias :assert_not_equal :refute_equal + + ## + # :method: assert_not_in_delta + # + # :call-seq: + # assert_not_in_delta(exp, act, delta = 0.001, msg = nil) + # + # Alias for: refute_in_delta[https://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-refute_in_delta] + + # + alias :assert_not_in_delta :refute_in_delta + + ## + # :method: assert_not_in_epsilon + # + # :call-seq: + # assert_not_in_epsilon(a, b, epsilon = 0.001, msg = nil) + # + # Alias for: refute_in_epsilon[https://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-refute_in_epsilon] + + # + alias :assert_not_in_epsilon :refute_in_epsilon + + ## + # :method: assert_not_includes + # + # :call-seq: + # assert_not_includes(collection, obj, msg = nil) + # + # Alias for: refute_includes[https://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-refute_includes] + + # + alias :assert_not_includes :refute_includes + + ## + # :method: assert_not_instance_of + # + # :call-seq: + # assert_not_instance_of(cls, obj, msg = nil) + # + # Alias for: refute_instance_of[https://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-refute_instance_of] + + # + alias :assert_not_instance_of :refute_instance_of + + ## + # :method: assert_not_kind_of + # + # :call-seq: + # assert_not_kind_of(cls, obj, msg = nil) + # + # Alias for: refute_kind_of[https://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-refute_kind_of] + + # + alias :assert_not_kind_of :refute_kind_of + + ## + # :method: assert_no_match + # + # :call-seq: + # assert_no_match(matcher, obj, msg = nil) + # + # Alias for: refute_match[https://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-refute_match] + + # + alias :assert_no_match :refute_match + + ## + # :method: assert_not_nil + # + # :call-seq: + # assert_not_nil(obj, msg = nil) + # + # Alias for: refute_nil[https://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-refute_nil] + + # + alias :assert_not_nil :refute_nil + + ## + # :method: assert_not_operator + # + # :call-seq: + # assert_not_operator(o1, op, o2 = UNDEFINED, msg = nil) + # + # Alias for: refute_operator[https://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-refute_operator] + + # + alias :assert_not_operator :refute_operator + + ## + # :method: assert_not_predicate + # + # :call-seq: + # assert_not_predicate(o1, op, msg = nil) + # + # Alias for: refute_predicate[https://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-refute_predicate] + + # + alias :assert_not_predicate :refute_predicate + + ## + # :method: assert_not_respond_to + # + # :call-seq: + # assert_not_respond_to(obj, meth, msg = nil) + # + # Alias for: refute_respond_to[https://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-refute_respond_to] + + # + alias :assert_not_respond_to :refute_respond_to + + ## + # :method: assert_not_same + # + # :call-seq: + # assert_not_same(exp, act, msg = nil) + # + # Alias for: refute_same[https://docs.seattlerb.org/minitest/Minitest/Assertions.html#method-i-refute_same] + + # + alias :assert_not_same :refute_same + + ActiveSupport.run_load_hooks(:active_support_test_case, self) + + def inspect # :nodoc: + Object.instance_method(:to_s).bind_call(self) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/assertions.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/assertions.rb new file mode 100644 index 00000000..38a9215d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/assertions.rb @@ -0,0 +1,339 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" + +module ActiveSupport + module Testing + module Assertions + UNTRACKED = Object.new # :nodoc: + + # Asserts that an expression is not truthy. Passes if +object+ is +nil+ or + # +false+. "Truthy" means "considered true in a conditional" like if + # foo. + # + # assert_not nil # => true + # assert_not false # => true + # assert_not 'foo' # => Expected "foo" to be nil or false + # + # An error message can be specified. + # + # assert_not foo, 'foo should be false' + def assert_not(object, message = nil) + message ||= -> { "Expected #{mu_pp(object)} to be nil or false" } + assert !object, message + end + + # Asserts that a block raises one of +exp+. This is an enhancement of the + # standard Minitest assertion method with the ability to test error + # messages. + # + # assert_raises(ArgumentError, match: /incorrect param/i) do + # perform_service(param: 'exception') + # end + # + def assert_raises(*exp, match: nil, &block) + error = super(*exp, &block) + assert_match(match, error.message) if match + error + end + alias :assert_raise :assert_raises + + # Assertion that the block should not raise an exception. + # + # Passes if evaluated code in the yielded block raises no exception. + # + # assert_nothing_raised do + # perform_service(param: 'no_exception') + # end + def assert_nothing_raised + yield.tap { assert(true) } + rescue => error + raise Minitest::UnexpectedError.new(error) + end + + # Test numeric difference between the return value of an expression as a + # result of what is evaluated in the yielded block. + # + # assert_difference 'Article.count' do + # post :create, params: { article: {...} } + # end + # + # An arbitrary expression is passed in and evaluated. + # + # assert_difference 'Article.last.comments(:reload).size' do + # post :create, params: { comment: {...} } + # end + # + # An arbitrary positive or negative difference can be specified. + # The default is +1+. + # + # assert_difference 'Article.count', -1 do + # post :delete, params: { id: ... } + # end + # + # An array of expressions can also be passed in and evaluated. + # + # assert_difference [ 'Article.count', 'Post.count' ], 2 do + # post :create, params: { article: {...} } + # end + # + # A hash of expressions/numeric differences can also be passed in and evaluated. + # + # assert_difference ->{ Article.count } => 1, ->{ Notification.count } => 2 do + # post :create, params: { article: {...} } + # end + # + # A lambda or a list of lambdas can be passed in and evaluated: + # + # assert_difference ->{ Article.count }, 2 do + # post :create, params: { article: {...} } + # end + # + # assert_difference [->{ Article.count }, ->{ Post.count }], 2 do + # post :create, params: { article: {...} } + # end + # + # An error message can be specified. + # + # assert_difference 'Article.count', -1, 'An Article should be destroyed' do + # post :delete, params: { id: ... } + # end + def assert_difference(expression, *args, &block) + expressions = + if expression.is_a?(Hash) + message = args[0] + expression + else + difference = args[0] || 1 + message = args[1] + Array(expression).index_with(difference) + end + + exps = expressions.keys.map { |e| + e.respond_to?(:call) ? e : lambda { eval(e, block.binding) } + } + before = exps.map(&:call) + + retval = _assert_nothing_raised_or_warn("assert_difference", &block) + + expressions.zip(exps, before) do |(code, diff), exp, before_value| + actual = exp.call + rich_message = -> do + code_string = code.respond_to?(:call) ? _callable_to_source_string(code) : code + error = "`#{code_string}` didn't change by #{diff}, but by #{actual - before_value}" + error = "#{message}.\n#{error}" if message + error + end + assert_equal(before_value + diff, actual, rich_message) + end + + retval + end + + # Assertion that the numeric result of evaluating an expression is not + # changed before and after invoking the passed in block. + # + # assert_no_difference 'Article.count' do + # post :create, params: { article: invalid_attributes } + # end + # + # A lambda can be passed in and evaluated. + # + # assert_no_difference -> { Article.count } do + # post :create, params: { article: invalid_attributes } + # end + # + # An error message can be specified. + # + # assert_no_difference 'Article.count', 'An Article should not be created' do + # post :create, params: { article: invalid_attributes } + # end + # + # An array of expressions can also be passed in and evaluated. + # + # assert_no_difference [ 'Article.count', -> { Post.count } ] do + # post :create, params: { article: invalid_attributes } + # end + def assert_no_difference(expression, message = nil, &block) + assert_difference expression, 0, message, &block + end + + # Assertion that the result of evaluating an expression is changed before + # and after invoking the passed in block. + # + # assert_changes 'Status.all_good?' do + # post :create, params: { status: { ok: false } } + # end + # + # You can pass the block as a string to be evaluated in the context of + # the block. A lambda can be passed for the block as well. + # + # assert_changes -> { Status.all_good? } do + # post :create, params: { status: { ok: false } } + # end + # + # The assertion is useful to test side effects. The passed block can be + # anything that can be converted to string with #to_s. + # + # assert_changes :@object do + # @object = 42 + # end + # + # The keyword arguments +:from+ and +:to+ can be given to specify the + # expected initial value and the expected value after the block was + # executed. + # + # assert_changes :@object, from: nil, to: :foo do + # @object = :foo + # end + # + # An error message can be specified. + # + # assert_changes -> { Status.all_good? }, 'Expected the status to be bad' do + # post :create, params: { status: { incident: true } } + # end + def assert_changes(expression, message = nil, from: UNTRACKED, to: UNTRACKED, &block) + exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) } + + before = exp.call + retval = _assert_nothing_raised_or_warn("assert_changes", &block) + + unless from == UNTRACKED + rich_message = -> do + error = "Expected change from #{from.inspect}, got #{before.inspect}" + error = "#{message}.\n#{error}" if message + error + end + assert from === before, rich_message + end + + after = exp.call + + rich_message = -> do + code_string = expression.respond_to?(:call) ? _callable_to_source_string(expression) : expression + error = "`#{code_string}` didn't change" + error = "#{error}. It was already #{to.inspect}" if before == to + error = "#{message}.\n#{error}" if message + error + end + refute_equal before, after, rich_message + + unless to == UNTRACKED + rich_message = -> do + error = "Expected change to #{to.inspect}, got #{after.inspect}\n" + error = "#{message}.\n#{error}" if message + error + end + assert to === after, rich_message + end + + retval + end + + # Assertion that the result of evaluating an expression is not changed before + # and after invoking the passed in block. + # + # assert_no_changes 'Status.all_good?' do + # post :create, params: { status: { ok: true } } + # end + # + # Provide the optional keyword argument +:from+ to specify the expected + # initial value. + # + # assert_no_changes -> { Status.all_good? }, from: true do + # post :create, params: { status: { ok: true } } + # end + # + # An error message can be specified. + # + # assert_no_changes -> { Status.all_good? }, 'Expected the status to be good' do + # post :create, params: { status: { ok: false } } + # end + def assert_no_changes(expression, message = nil, from: UNTRACKED, &block) + exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) } + + before = exp.call + retval = _assert_nothing_raised_or_warn("assert_no_changes", &block) + + unless from == UNTRACKED + rich_message = -> do + error = "Expected initial value of #{from.inspect}, got #{before.inspect}" + error = "#{message}.\n#{error}" if message + error + end + assert from === before, rich_message + end + + after = exp.call + + rich_message = -> do + code_string = expression.respond_to?(:call) ? _callable_to_source_string(expression) : expression + error = "`#{code_string}` changed" + error = "#{message}.\n#{error}" if message + error + end + + if before.nil? + assert_nil after, rich_message + else + assert_equal before, after, rich_message + end + + retval + end + + private + def _assert_nothing_raised_or_warn(assertion, &block) + assert_nothing_raised(&block) + rescue Minitest::UnexpectedError => e + if tagged_logger && tagged_logger.warn? + warning = <<~MSG + #{self.class} - #{name}: #{e.error.class} raised. + If you expected this exception, use `assert_raises` as near to the code that raises as possible. + Other block based assertions (e.g. `#{assertion}`) can be used, as long as `assert_raises` is inside their block. + MSG + tagged_logger.warn warning + end + + raise + end + + def _callable_to_source_string(callable) + if defined?(RubyVM::InstructionSequence) && callable.is_a?(Proc) + iseq = RubyVM::InstructionSequence.of(callable) + source = + if iseq.script_lines + iseq.script_lines.join("\n") + elsif File.readable?(iseq.absolute_path) + File.read(iseq.absolute_path) + end + + return callable unless source + + location = iseq.to_a[4][:code_location] + return callable unless location + + lines = source.lines[(location[0] - 1)..(location[2] - 1)] + lines[-1] = lines[-1].byteslice(...location[3]) + lines[0] = lines[0].byteslice(location[1]...) + source = lines.join.strip + + # We ignore procs defined with do/end as they are likely multi-line anyway. + if source.start_with?("{") + source.delete_suffix!("}") + source.delete_prefix!("{") + source.strip! + # It won't read nice if the callable contains multiple + # lines, and it should be a rare occurrence anyway. + # Same if it takes arguments. + if !source.include?("\n") && !source.start_with?("|") + return source + end + end + end + + callable + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/autorun.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/autorun.rb new file mode 100644 index 00000000..d5d5fc7a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/autorun.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require "minitest" + +Minitest.autorun diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/constant_lookup.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/constant_lookup.rb new file mode 100644 index 00000000..51167e92 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/constant_lookup.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/inflector" + +module ActiveSupport + module Testing + # Resolves a constant from a minitest spec name. + # + # Given the following spec-style test: + # + # describe WidgetsController, :index do + # describe "authenticated user" do + # describe "returns widgets" do + # it "has a controller that exists" do + # assert_kind_of WidgetsController, @controller + # end + # end + # end + # end + # + # The test will have the following name: + # + # "WidgetsController::index::authenticated user::returns widgets" + # + # The constant WidgetsController can be resolved from the name. + # The following code will resolve the constant: + # + # controller = determine_constant_from_test_name(name) do |constant| + # Class === constant && constant < ::ActionController::Metal + # end + module ConstantLookup + extend ::ActiveSupport::Concern + + module ClassMethods # :nodoc: + def determine_constant_from_test_name(test_name) + names = test_name.split "::" + while names.size > 0 do + names.last.sub!(/Test$/, "") + begin + constant = names.join("::").safe_constantize + break(constant) if yield(constant) + ensure + names.pop + end + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/constant_stubbing.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/constant_stubbing.rb new file mode 100644 index 00000000..e0bc6dcd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/constant_stubbing.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module ActiveSupport + module Testing + module ConstantStubbing + # Changes the value of a constant for the duration of a block. Example: + # + # # World::List::Import::LARGE_IMPORT_THRESHOLD = 5000 + # stub_const(World::List::Import, :LARGE_IMPORT_THRESHOLD, 1) do + # assert_equal 1, World::List::Import::LARGE_IMPORT_THRESHOLD + # end + # + # assert_equal 5000, World::List::Import::LARGE_IMPORT_THRESHOLD + # + # Using this method rather than forcing World::List::Import::LARGE_IMPORT_THRESHOLD = 5000 prevents + # warnings from being thrown, and ensures that the old value is returned after the test has completed. + # + # If the constant doesn't already exists, but you need it set for the duration of the block + # you can do so by passing `exists: false`. + # + # stub_const(object, :SOME_CONST, 1, exists: false) do + # assert_equal 1, SOME_CONST + # end + # + # Note: Stubbing a const will stub it across all threads. So if you have concurrent threads + # (like separate test suites running in parallel) that all depend on the same constant, it's possible + # divergent stubbing will trample on each other. + def stub_const(mod, constant, new_value, exists: true) + if exists + begin + old_value = mod.const_get(constant, false) + mod.send(:remove_const, constant) + mod.const_set(constant, new_value) + yield + ensure + mod.send(:remove_const, constant) + mod.const_set(constant, old_value) + end + else + if mod.const_defined?(constant) + raise NameError, "already defined constant #{constant} in #{mod.name}" + end + + begin + mod.const_set(constant, new_value) + yield + ensure + mod.send(:remove_const, constant) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/declarative.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/declarative.rb new file mode 100644 index 00000000..7c340368 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/declarative.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module ActiveSupport + module Testing + module Declarative + unless defined?(Spec) + # Helper to define a test method using a String. Under the hood, it replaces + # spaces with underscores and defines the test method. + # + # test "verify something" do + # ... + # end + def test(name, &block) + test_name = "test_#{name.gsub(/\s+/, '_')}".to_sym + defined = method_defined? test_name + raise "#{test_name} is already defined in #{self}" if defined + if block_given? + define_method(test_name, &block) + else + define_method(test_name) do + flunk "No implementation provided for #{name}" + end + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/deprecation.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/deprecation.rb new file mode 100644 index 00000000..ea307a0b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/deprecation.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require "active_support/deprecation" + +module ActiveSupport + module Testing + module Deprecation + ## + # :call-seq: + # assert_deprecated(deprecator, &block) + # assert_deprecated(match, deprecator, &block) + # + # Asserts that a matching deprecation warning was emitted by the given deprecator during the execution of the yielded block. + # + # assert_deprecated(/foo/, CustomDeprecator) do + # CustomDeprecator.warn "foo should no longer be used" + # end + # + # The +match+ object may be a +Regexp+, or +String+ appearing in the message. + # + # assert_deprecated('foo', CustomDeprecator) do + # CustomDeprecator.warn "foo should no longer be used" + # end + # + # If the +match+ is omitted (or explicitly +nil+), any deprecation warning will match. + # + # assert_deprecated(CustomDeprecator) do + # CustomDeprecator.warn "foo should no longer be used" + # end + def assert_deprecated(match = nil, deprecator = nil, &block) + match, deprecator = nil, match if match.is_a?(ActiveSupport::Deprecation) + + unless deprecator + raise ArgumentError, "No deprecator given" + end + + result, warnings = collect_deprecations(deprecator, &block) + assert !warnings.empty?, "Expected a deprecation warning within the block but received none" + if match + match = Regexp.new(Regexp.escape(match)) unless match.is_a?(Regexp) + assert warnings.any? { |w| match.match?(w) }, "No deprecation warning matched #{match}: #{warnings.join(', ')}" + end + result + end + + # Asserts that no deprecation warnings are emitted by the given deprecator during the execution of the yielded block. + # + # assert_not_deprecated(CustomDeprecator) do + # CustomDeprecator.warn "message" # fails assertion + # end + # + # assert_not_deprecated(ActiveSupport::Deprecation.new) do + # CustomDeprecator.warn "message" # passes assertion, different deprecator + # end + def assert_not_deprecated(deprecator, &block) + result, deprecations = collect_deprecations(deprecator, &block) + assert deprecations.empty?, "Expected no deprecation warning within the block but received #{deprecations.size}: \n #{deprecations * "\n "}" + result + end + + # Returns the return value of the block and an array of all the deprecation warnings emitted by the given + # +deprecator+ during the execution of the yielded block. + # + # collect_deprecations(CustomDeprecator) do + # CustomDeprecator.warn "message" + # ActiveSupport::Deprecation.new.warn "other message" + # :result + # end # => [:result, ["message"]] + def collect_deprecations(deprecator) + old_behavior = deprecator.behavior + deprecations = [] + deprecator.behavior = Proc.new do |message, callstack| + deprecations << message + end + result = yield + [result, deprecations] + ensure + deprecator.behavior = old_behavior + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/error_reporter_assertions.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/error_reporter_assertions.rb new file mode 100644 index 00000000..3b7ad7cd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/error_reporter_assertions.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +module ActiveSupport + module Testing + module ErrorReporterAssertions + module ErrorCollector # :nodoc: + @subscribed = false + @mutex = Mutex.new + + Report = Struct.new(:error, :handled, :severity, :context, :source, keyword_init: true) + class Report + alias_method :handled?, :handled + end + + class << self + def record + subscribe + recorders = ActiveSupport::IsolatedExecutionState[:active_support_error_reporter_assertions] ||= [] + reports = [] + recorders << reports + begin + yield + reports + ensure + recorders.delete_if { |r| reports.equal?(r) } + end + end + + def report(error, **kwargs) + report = Report.new(error: error, **kwargs) + ActiveSupport::IsolatedExecutionState[:active_support_error_reporter_assertions]&.each do |reports| + reports << report + end + true + end + + private + def subscribe + return if @subscribed + @mutex.synchronize do + return if @subscribed + + if ActiveSupport.error_reporter + ActiveSupport.error_reporter.subscribe(self) + @subscribed = true + else + raise Minitest::Assertion, "No error reporter is configured" + end + end + end + end + end + + # Assertion that the block should not cause an exception to be reported + # to +Rails.error+. + # + # Passes if evaluated code in the yielded block reports no exception. + # + # assert_no_error_reported do + # perform_service(param: 'no_exception') + # end + def assert_no_error_reported(&block) + reports = ErrorCollector.record do + _assert_nothing_raised_or_warn("assert_no_error_reported", &block) + end + assert_predicate(reports, :empty?) + end + + # Assertion that the block should cause at least one exception to be reported + # to +Rails.error+. + # + # Passes if the evaluated code in the yielded block reports a matching exception. + # + # assert_error_reported(IOError) do + # Rails.error.report(IOError.new("Oops")) + # end + # + # To test further details about the reported exception, you can use the return + # value. + # + # report = assert_error_reported(IOError) do + # # ... + # end + # assert_equal "Oops", report.error.message + # assert_equal "admin", report.context[:section] + # assert_equal :warning, report.severity + # assert_predicate report, :handled? + def assert_error_reported(error_class = StandardError, &block) + reports = ErrorCollector.record do + _assert_nothing_raised_or_warn("assert_error_reported", &block) + end + + if reports.empty? + assert(false, "Expected a #{error_class.name} to be reported, but there were no errors reported.") + elsif (report = reports.find { |r| error_class === r.error }) + self.assertions += 1 + report + else + message = "Expected a #{error_class.name} to be reported, but none of the " \ + "#{reports.size} reported errors matched: \n" \ + "#{reports.map { |r| r.error.class.name }.join("\n ")}" + assert(false, message) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/file_fixtures.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/file_fixtures.rb new file mode 100644 index 00000000..4eb7a885 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/file_fixtures.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "active_support/concern" + +module ActiveSupport + module Testing + # Adds simple access to sample files called file fixtures. + # File fixtures are normal files stored in + # ActiveSupport::TestCase.file_fixture_path. + # + # File fixtures are represented as +Pathname+ objects. + # This makes it easy to extract specific information: + # + # file_fixture("example.txt").read # get the file's content + # file_fixture("example.mp3").size # get the file size + module FileFixtures + extend ActiveSupport::Concern + + included do + class_attribute :file_fixture_path, instance_writer: false + end + + # Returns a +Pathname+ to the fixture file named +fixture_name+. + # + # Raises +ArgumentError+ if +fixture_name+ can't be found. + def file_fixture(fixture_name) + path = Pathname.new(File.join(file_fixture_path, fixture_name)) + + if path.exist? + path + else + msg = "the directory '%s' does not contain a file named '%s'" + raise ArgumentError, msg % [file_fixture_path, fixture_name] + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/isolation.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/isolation.rb new file mode 100644 index 00000000..ccba6ebb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/isolation.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +require "active_support/testing/parallelize_executor" + +module ActiveSupport + module Testing + module Isolation + SubprocessCrashed = Class.new(StandardError) + + def self.included(klass) # :nodoc: + klass.class_eval do + parallelize_me! unless Minitest.parallel_executor.is_a?(ActiveSupport::Testing::ParallelizeExecutor) + end + end + + def self.forking_env? + !ENV["NO_FORK"] && Process.respond_to?(:fork) + end + + def run + status, serialized = run_in_isolation do + super + end + + unless status&.success? + error = SubprocessCrashed.new("Subprocess exited with an error: #{status.inspect}\noutput: #{serialized.inspect}") + error.set_backtrace(caller) + self.failures << Minitest::UnexpectedError.new(error) + return defined?(Minitest::Result) ? Minitest::Result.from(self) : dup + end + + Marshal.load(serialized) + end + + module Forking + def run_in_isolation(&blk) + IO.pipe do |read, write| + read.binmode + write.binmode + + pid = fork do + read.close + yield + begin + if error? + failures.map! { |e| + begin + Marshal.dump e + e + rescue TypeError + ex = Exception.new e.message + ex.set_backtrace e.backtrace + Minitest::UnexpectedError.new ex + end + } + end + test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : dup + result = Marshal.dump(test_result) + end + + write.puts [result].pack("m") + exit!(0) + end + + write.close + result = read.read + _, status = Process.wait2(pid) + return status, result.unpack1("m") + end + end + end + + module Subprocess + ORIG_ARGV = ARGV.dup unless defined?(ORIG_ARGV) + + # Complicated H4X to get this working in Windows / JRuby with + # no forking. + def run_in_isolation(&blk) + require "tempfile" + + if ENV["ISOLATION_TEST"] + yield + test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : dup + File.open(ENV["ISOLATION_OUTPUT"], "w") do |file| + file.puts [Marshal.dump(test_result)].pack("m") + end + exit!(0) + else + Tempfile.open("isolation") do |tmpfile| + env = { + "ISOLATION_TEST" => self.class.name, + "ISOLATION_OUTPUT" => tmpfile.path + } + + test_opts = "-n#{self.class.name}##{name}" + + load_path_args = [] + $-I.each do |p| + load_path_args << "-I" + load_path_args << File.expand_path(p) + end + + child = IO.popen([env, Gem.ruby, *load_path_args, $0, *ORIG_ARGV, test_opts]) + + status = nil + begin + _, status = Process.wait2(child.pid) + rescue Errno::ECHILD # The child process may exit before we wait + nil + end + + return status, tmpfile.read.unpack1("m") + end + end + end + end + + include forking_env? ? Forking : Subprocess + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/method_call_assertions.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/method_call_assertions.rb new file mode 100644 index 00000000..2992164a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/method_call_assertions.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require "minitest/mock" + +module ActiveSupport + module Testing + module MethodCallAssertions # :nodoc: + private + def assert_called(object, method_name, message = nil, times: 1, returns: nil, &block) + times_called = 0 + + object.stub(method_name, proc { times_called += 1; returns }, &block) + + error = "Expected #{method_name} to be called #{times} times, " \ + "but was called #{times_called} times" + error = "#{message}.\n#{error}" if message + assert_equal times, times_called, error + end + + def assert_called_with(object, method_name, args, returns: false, **kwargs, &block) + mock = Minitest::Mock.new + expect_called_with(mock, args, returns: returns, **kwargs) + + object.stub(method_name, mock, &block) + + assert_mock(mock) + end + + def assert_not_called(object, method_name, message = nil, &block) + assert_called(object, method_name, message, times: 0, &block) + end + + def expect_called_with(mock, args, returns: false, **kwargs) + mock.expect(:call, returns, args, **kwargs) + end + + def assert_called_on_instance_of(klass, method_name, message = nil, times: 1, returns: nil) + times_called = 0 + klass.define_method("stubbed_#{method_name}") do |*| + times_called += 1 + + returns + end + + klass.alias_method "original_#{method_name}", method_name + klass.alias_method method_name, "stubbed_#{method_name}" + + yield + + error = "Expected #{method_name} to be called #{times} times, but was called #{times_called} times" + error = "#{message}.\n#{error}" if message + + assert_equal times, times_called, error + ensure + klass.alias_method method_name, "original_#{method_name}" + klass.undef_method "original_#{method_name}" + klass.undef_method "stubbed_#{method_name}" + end + + def assert_not_called_on_instance_of(klass, method_name, message = nil, &block) + assert_called_on_instance_of(klass, method_name, message, times: 0, &block) + end + + def stub_any_instance(klass, instance: klass.new) + klass.stub(:new, instance) { yield instance } + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/parallelization.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/parallelization.rb new file mode 100644 index 00000000..d1b2734c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/parallelization.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "drb" +require "drb/unix" unless Gem.win_platform? +require "active_support/core_ext/module/attribute_accessors" +require "active_support/testing/parallelization/server" +require "active_support/testing/parallelization/worker" + +module ActiveSupport + module Testing + class Parallelization # :nodoc: + @@after_fork_hooks = [] + + def self.after_fork_hook(&blk) + @@after_fork_hooks << blk + end + + cattr_reader :after_fork_hooks + + @@run_cleanup_hooks = [] + + def self.run_cleanup_hook(&blk) + @@run_cleanup_hooks << blk + end + + cattr_reader :run_cleanup_hooks + + def initialize(worker_count) + @worker_count = worker_count + @queue_server = Server.new + @worker_pool = [] + @url = DRb.start_service("drbunix:", @queue_server).uri + end + + def start + @worker_pool = @worker_count.times.map do |worker| + Worker.new(worker, @url).start + end + end + + def <<(work) + @queue_server << work + end + + def size + @worker_count + end + + def shutdown + @queue_server.shutdown + @worker_pool.each { |pid| Process.waitpid pid } + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/parallelization/server.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/parallelization/server.rb new file mode 100644 index 00000000..dc70c961 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/parallelization/server.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require "drb" +require "drb/unix" unless Gem.win_platform? + +module ActiveSupport + module Testing + class Parallelization # :nodoc: + PrerecordResultClass = Struct.new(:name) + + class Server + include DRb::DRbUndumped + + def initialize + @queue = Queue.new + @active_workers = Concurrent::Map.new + @in_flight = Concurrent::Map.new + end + + def record(reporter, result) + raise DRb::DRbConnError if result.is_a?(DRb::DRbUnknown) + + @in_flight.delete([result.klass, result.name]) + + reporter.synchronize do + reporter.prerecord(PrerecordResultClass.new(result.klass), result.name) + reporter.record(result) + end + end + + def <<(o) + o[2] = DRbObject.new(o[2]) if o + @queue << o + end + + def pop + if test = @queue.pop + @in_flight[[test[0].to_s, test[1]]] = test + test + end + end + + def start_worker(worker_id) + @active_workers[worker_id] = true + end + + def stop_worker(worker_id) + @active_workers.delete(worker_id) + end + + def active_workers? + @active_workers.size > 0 + end + + def interrupt + @queue.clear + end + + def shutdown + # Wait for initial queue to drain + while @queue.length != 0 + sleep 0.1 + end + + @queue.close + + # Wait until all workers have finished + while active_workers? + sleep 0.1 + end + + @in_flight.values.each do |(klass, name, reporter)| + result = Minitest::Result.from(klass.new(name)) + error = RuntimeError.new("result not reported") + error.set_backtrace([""]) + result.failures << Minitest::UnexpectedError.new(error) + reporter.synchronize do + reporter.record(result) + end + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/parallelization/worker.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/parallelization/worker.rb new file mode 100644 index 00000000..393355a2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/parallelization/worker.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +module ActiveSupport + module Testing + class Parallelization # :nodoc: + class Worker + def initialize(number, url) + @id = SecureRandom.uuid + @number = number + @url = url + @setup_exception = nil + end + + def start + fork do + set_process_title("(starting)") + + DRb.stop_service + + @queue = DRbObject.new_with_uri(@url) + @queue.start_worker(@id) + + begin + after_fork + rescue => @setup_exception; end + + work_from_queue + ensure + set_process_title("(stopping)") + + run_cleanup + @queue.stop_worker(@id) + end + end + + def work_from_queue + while job = @queue.pop + perform_job(job) + end + end + + def perform_job(job) + klass = job[0] + method = job[1] + reporter = job[2] + + set_process_title("#{klass}##{method}") + + result = klass.with_info_handler reporter do + Minitest.run_one_method(klass, method) + end + + safe_record(reporter, result) + end + + def safe_record(reporter, result) + add_setup_exception(result) if @setup_exception + + begin + @queue.record(reporter, result) + rescue DRb::DRbConnError + result.failures.map! do |failure| + if failure.respond_to?(:error) + # minitest >5.14.0 + error = DRb::DRbRemoteError.new(failure.error) + else + error = DRb::DRbRemoteError.new(failure.exception) + end + Minitest::UnexpectedError.new(error) + end + @queue.record(reporter, result) + rescue Interrupt + @queue.interrupt + raise + end + + set_process_title("(idle)") + end + + def after_fork + Parallelization.after_fork_hooks.each do |cb| + cb.call(@number) + end + end + + def run_cleanup + Parallelization.run_cleanup_hooks.each do |cb| + cb.call(@number) + end + end + + private + def add_setup_exception(result) + result.failures.prepend Minitest::UnexpectedError.new(@setup_exception) + end + + def set_process_title(status) + Process.setproctitle("Rails test worker #{@number} - #{status}") + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/parallelize_executor.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/parallelize_executor.rb new file mode 100644 index 00000000..127f26f9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/parallelize_executor.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +module ActiveSupport + module Testing + class ParallelizeExecutor # :nodoc: + attr_reader :size, :parallelize_with, :threshold + + def initialize(size:, with:, threshold: ActiveSupport.test_parallelization_threshold) + @size = size + @parallelize_with = with + @threshold = threshold + @parallelized = false + end + + def start + parallelize if should_parallelize? + show_execution_info + + parallel_executor.start if parallelized? + end + + def <<(work) + parallel_executor << work if parallelized? + end + + def shutdown + parallel_executor.shutdown if parallelized? + end + + private + def parallel_executor + @parallel_executor ||= build_parallel_executor + end + + def build_parallel_executor + case parallelize_with + when :processes + Testing::Parallelization.new(size) + when :threads + ActiveSupport::TestCase.lock_threads = false if defined?(ActiveSupport::TestCase.lock_threads) + Minitest::Parallel::Executor.new(size) + else + raise ArgumentError, "#{parallelize_with} is not a supported parallelization executor." + end + end + + def parallelize + @parallelized = true + Minitest::Test.parallelize_me! + end + + def parallelized? + @parallelized + end + + def should_parallelize? + (ENV["PARALLEL_WORKERS"] || tests_count > threshold) && many_workers? + end + + def many_workers? + size > 1 + end + + def tests_count + @tests_count ||= Minitest::Runnable.runnables.sum { |runnable| runnable.runnable_methods.size } + end + + def show_execution_info + puts execution_info + end + + def execution_info + if parallelized? + "Running #{tests_count} tests in parallel using #{parallel_executor.size} #{parallelize_with}" + elsif many_workers? + "Running #{tests_count} tests in a single process (parallelization threshold is #{threshold})" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/setup_and_teardown.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/setup_and_teardown.rb new file mode 100644 index 00000000..9c12dc2e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/setup_and_teardown.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require "active_support/callbacks" + +module ActiveSupport + module Testing + # Adds support for +setup+ and +teardown+ callbacks. + # These callbacks serve as a replacement to overwriting the + # #setup and #teardown methods of your TestCase. + # + # class ExampleTest < ActiveSupport::TestCase + # setup do + # # ... + # end + # + # teardown do + # # ... + # end + # end + module SetupAndTeardown + def self.prepended(klass) + klass.include ActiveSupport::Callbacks + klass.define_callbacks :setup, :teardown + klass.extend ClassMethods + end + + module ClassMethods + # Add a callback, which runs before TestCase#setup. + def setup(*args, &block) + set_callback(:setup, :before, *args, &block) + end + + # Add a callback, which runs after TestCase#teardown. + def teardown(*args, &block) + set_callback(:teardown, :after, *args, &block) + end + end + + def before_setup # :nodoc: + super + run_callbacks :setup + end + + def after_teardown # :nodoc: + begin + run_callbacks :teardown + rescue => e + self.failures << Minitest::UnexpectedError.new(e) + rescue Minitest::Assertion => e + self.failures << e + end + + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/stream.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/stream.rb new file mode 100644 index 00000000..0b8e0298 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/stream.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module ActiveSupport + module Testing + module Stream # :nodoc: + private + def silence_stream(stream) + old_stream = stream.dup + stream.reopen(IO::NULL) + stream.sync = true + yield + ensure + stream.reopen(old_stream) + old_stream.close + end + + def quietly(&block) + silence_stream(STDOUT) do + silence_stream(STDERR, &block) + end + end + + def capture(stream) + stream = stream.to_s + captured_stream = Tempfile.new(stream) + stream_io = eval("$#{stream}", binding, __FILE__, __LINE__) + origin_stream = stream_io.dup + stream_io.reopen(captured_stream) + + yield + + stream_io.rewind + captured_stream.read + ensure + captured_stream.close + captured_stream.unlink + stream_io.reopen(origin_stream) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/tagged_logging.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/tagged_logging.rb new file mode 100644 index 00000000..7d38268b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/tagged_logging.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module ActiveSupport + module Testing + # Logs a "PostsControllerTest: test name" heading before each test to + # make test.log easier to search and follow along with. + module TaggedLogging # :nodoc: + attr_writer :tagged_logger + + def before_setup + if tagged_logger && tagged_logger.info? + heading = "#{self.class}: #{name}" + divider = "-" * heading.size + tagged_logger.info divider + tagged_logger.info heading + tagged_logger.info divider + end + super + end + + private + def tagged_logger + @tagged_logger ||= (defined?(Rails.logger) && Rails.logger) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/tests_without_assertions.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/tests_without_assertions.rb new file mode 100644 index 00000000..adfe7d86 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/tests_without_assertions.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module ActiveSupport + module Testing + # Warns when a test case does not perform any assertions. + # + # This is helpful in detecting broken tests that do not perform intended assertions. + module TestsWithoutAssertions # :nodoc: + def after_teardown + super + + if assertions.zero? && !skipped? && !error? + file, line = method(name).source_location + warn "Test is missing assertions: `#{name}` #{file}:#{line}" + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/time_helpers.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/time_helpers.rb new file mode 100644 index 00000000..578184c5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/testing/time_helpers.rb @@ -0,0 +1,269 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/redefine_method" +require "active_support/core_ext/time/calculations" + +module ActiveSupport + module Testing + # Manages stubs for TimeHelpers + class SimpleStubs # :nodoc: + Stub = Struct.new(:object, :method_name, :original_method) + + def initialize + @stubs = Hash.new { |h, k| h[k] = {} } + end + + # Stubs object.method_name with the given block + # If the method is already stubbed, remove that stub + # so that removing this stub will restore the original implementation. + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # target = Time.zone.local(2004, 11, 24, 1, 4, 44) + # simple_stubs.stub_object(Time, :now) { at(target.to_i) } + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + def stub_object(object, method_name, &block) + if stub = stubbing(object, method_name) + unstub_object(stub) + end + + new_name = "__simple_stub__#{method_name}__#{object_id}" + + @stubs[object.object_id][method_name] = Stub.new(object, method_name, new_name) + + object.singleton_class.alias_method new_name, method_name + object.define_singleton_method(method_name, &block) + end + + # Remove all object-method stubs held by this instance + def unstub_all! + @stubs.each_value do |object_stubs| + object_stubs.each_value do |stub| + unstub_object(stub) + end + end + @stubs.clear + end + + # Returns the Stub for object#method_name + # (nil if it is not stubbed) + def stubbing(object, method_name) + @stubs[object.object_id][method_name] + end + + # Returns true if any stubs are set, false if there are none + def stubbed? + !@stubs.empty? + end + + private + # Restores the original object.method described by the Stub + def unstub_object(stub) + singleton_class = stub.object.singleton_class + singleton_class.silence_redefinition_of_method stub.method_name + singleton_class.alias_method stub.method_name, stub.original_method + singleton_class.undef_method stub.original_method + end + end + + # Contains helpers that help you test passage of time. + module TimeHelpers + def after_teardown + travel_back + super + end + + # Changes current time to the time in the future or in the past by a given time difference by + # stubbing +Time.now+, +Date.today+, and +DateTime.now+. The stubs are automatically removed + # at the end of the test. + # + # Note that the usec for the resulting time will be set to 0 to prevent rounding + # errors with external services, like MySQL (which will round instead of floor, + # leading to off-by-one-second errors), unless the with_usec argument + # is set to true. + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # travel 1.day + # Time.current # => Sun, 10 Nov 2013 15:34:49 EST -05:00 + # Date.current # => Sun, 10 Nov 2013 + # DateTime.current # => Sun, 10 Nov 2013 15:34:49 -0500 + # + # This method also accepts a block, which will return the current time back to its original + # state at the end of the block: + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # travel 1.day do + # User.create.created_at # => Sun, 10 Nov 2013 15:34:49 EST -05:00 + # end + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + def travel(duration, with_usec: false, &block) + travel_to Time.now + duration, with_usec: with_usec, &block + end + + # Changes current time to the given time by stubbing +Time.now+, +Time.new+, + # +Date.today+, and +DateTime.now+ to return the time or date passed into this method. + # The stubs are automatically removed at the end of the test. + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # travel_to Time.zone.local(2004, 11, 24, 1, 4, 44) + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + # Date.current # => Wed, 24 Nov 2004 + # DateTime.current # => Wed, 24 Nov 2004 01:04:44 -0500 + # + # Dates are taken as their timestamp at the beginning of the day in the + # application time zone. Time.current returns said timestamp, + # and Time.now its equivalent in the system time zone. Similarly, + # Date.current returns a date equal to the argument, and + # Date.today the date according to Time.now, which may + # be different. (Note that you rarely want to deal with Time.now, + # or Date.today, in order to honor the application time zone + # please always use Time.current and Date.current.) + # + # Note that the usec for the time passed will be set to 0 to prevent rounding + # errors with external services, like MySQL (which will round instead of floor, + # leading to off-by-one-second errors), unless the with_usec argument + # is set to true. + # + # This method also accepts a block, which will return the current time back to its original + # state at the end of the block: + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # travel_to Time.zone.local(2004, 11, 24, 1, 4, 44) do + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + # end + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + def travel_to(date_or_time, with_usec: false) + if block_given? && in_block + travel_to_nested_block_call = <<~MSG + + Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing. + + Instead of: + + travel_to 2.days.from_now do + # 2 days from today + travel_to 3.days.from_now do + # 5 days from today + end + end + + preferred way to achieve above is: + + travel 2.days do + # 2 days from today + end + + travel 5.days do + # 5 days from today + end + + MSG + raise travel_to_nested_block_call + end + + if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime) + now = date_or_time.midnight.to_time + elsif date_or_time.is_a?(String) + now = Time.zone.parse(date_or_time) + else + now = date_or_time + now = now.to_time unless now.is_a?(Time) + end + + now = now.change(usec: 0) unless with_usec + + # +now+ must be in local system timezone, because +Time.at(now)+ + # and +now.to_date+ (see stubs below) will use +now+'s timezone too! + now = now.getlocal + + stubs = simple_stubs + stubbed_time = Time.now if stubs.stubbing(Time, :now) + stubs.stub_object(Time, :now) { at(now) } + + stubs.stub_object(Time, :new) do |*args, **options| + if args.empty? && options.empty? + at(now) + else + stub = stubs.stubbing(Time, :new) + Time.send(stub.original_method, *args, **options) + end + end + + stubs.stub_object(Date, :today) { jd(now.to_date.jd) } + stubs.stub_object(DateTime, :now) { jd(now.to_date.jd, now.hour, now.min, now.sec, Rational(now.utc_offset, 86400)) } + + if block_given? + begin + self.in_block = true + yield + ensure + if stubbed_time + travel_to stubbed_time + else + travel_back + end + self.in_block = false + end + end + end + + # Returns the current time back to its original state, by removing the stubs added by + # +travel+, +travel_to+, and +freeze_time+. + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # + # travel_to Time.zone.local(2004, 11, 24, 1, 4, 44) + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + # + # travel_back + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # + # This method also accepts a block, which brings the stubs back at the end of the block: + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # + # travel_to Time.zone.local(2004, 11, 24, 1, 4, 44) + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + # + # travel_back do + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # end + # + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + def travel_back + stubbed_time = Time.current if block_given? && simple_stubs.stubbed? + + simple_stubs.unstub_all! + yield if block_given? + ensure + travel_to stubbed_time if stubbed_time + end + alias_method :unfreeze_time, :travel_back + + # Calls +travel_to+ with +Time.now+. Forwards optional with_usec argument. + # + # Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00 + # freeze_time + # sleep(1) + # Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00 + # + # This method also accepts a block, which will return the current time back to its original + # state at the end of the block: + # + # Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00 + # freeze_time do + # sleep(1) + # User.create.created_at # => Sun, 09 Jul 2017 15:34:49 EST -05:00 + # end + # Time.current # => Sun, 09 Jul 2017 15:34:50 EST -05:00 + def freeze_time(with_usec: false, &block) + travel_to Time.now, with_usec: with_usec, &block + end + + private + def simple_stubs + @simple_stubs ||= SimpleStubs.new + end + + attr_accessor :in_block + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/time.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/time.rb new file mode 100644 index 00000000..51854675 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/time.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module ActiveSupport + autoload :Duration, "active_support/duration" + autoload :TimeWithZone, "active_support/time_with_zone" + autoload :TimeZone, "active_support/values/time_zone" +end + +require "date" +require "time" + +require "active_support/core_ext/time" +require "active_support/core_ext/date" +require "active_support/core_ext/date_time" + +require "active_support/core_ext/integer/time" +require "active_support/core_ext/numeric/time" + +require "active_support/core_ext/string/conversions" +require "active_support/core_ext/string/zones" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/time_with_zone.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/time_with_zone.rb new file mode 100644 index 00000000..eb25fd46 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/time_with_zone.rb @@ -0,0 +1,609 @@ +# frozen_string_literal: true + +require "yaml" + +require "active_support/duration" +require "active_support/values/time_zone" +require "active_support/core_ext/object/acts_like" +require "active_support/core_ext/date_and_time/compatibility" + +module ActiveSupport + # = Active Support \Time With Zone + # + # A Time-like class that can represent a time in any time zone. Necessary + # because standard Ruby Time instances are limited to UTC and the + # system's ENV['TZ'] zone. + # + # You shouldn't ever need to create a TimeWithZone instance directly via +new+. + # Instead use methods +local+, +parse+, +at+, and +now+ on TimeZone instances, + # and +in_time_zone+ on Time and DateTime instances. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # Time.zone.local(2007, 2, 10, 15, 30, 45) # => Sat, 10 Feb 2007 15:30:45.000000000 EST -05:00 + # Time.zone.parse('2007-02-10 15:30:45') # => Sat, 10 Feb 2007 15:30:45.000000000 EST -05:00 + # Time.zone.at(1171139445) # => Sat, 10 Feb 2007 15:30:45.000000000 EST -05:00 + # Time.zone.now # => Sun, 18 May 2008 13:07:55.754107581 EDT -04:00 + # Time.utc(2007, 2, 10, 20, 30, 45).in_time_zone # => Sat, 10 Feb 2007 15:30:45.000000000 EST -05:00 + # + # See Time and TimeZone for further documentation of these methods. + # + # TimeWithZone instances implement the same API as Ruby Time instances, so + # that Time and TimeWithZone instances are interchangeable. + # + # t = Time.zone.now # => Sun, 18 May 2008 13:27:25.031505668 EDT -04:00 + # t.hour # => 13 + # t.dst? # => true + # t.utc_offset # => -14400 + # t.zone # => "EDT" + # t.to_fs(:rfc822) # => "Sun, 18 May 2008 13:27:25 -0400" + # t + 1.day # => Mon, 19 May 2008 13:27:25.031505668 EDT -04:00 + # t.beginning_of_year # => Tue, 01 Jan 2008 00:00:00.000000000 EST -05:00 + # t > Time.utc(1999) # => true + # t.is_a?(Time) # => true + # t.is_a?(ActiveSupport::TimeWithZone) # => true + class TimeWithZone + PRECISIONS = Hash.new { |h, n| h[n] = "%FT%T.%#{n}N" } + PRECISIONS[0] = "%FT%T" + + include Comparable, DateAndTime::Compatibility + attr_reader :time_zone + + def initialize(utc_time, time_zone, local_time = nil, period = nil) + @utc = utc_time ? transfer_time_values_to_utc_constructor(utc_time) : nil + @time_zone, @time = time_zone, local_time + @period = @utc ? period : get_period_and_ensure_valid_local_time(period) + end + + # Returns a Time instance that represents the time in +time_zone+. + def time + @time ||= incorporate_utc_offset(@utc, utc_offset) + end + + # Returns a Time instance of the simultaneous time in the UTC timezone. + def utc + @utc ||= incorporate_utc_offset(@time, -utc_offset) + end + alias_method :comparable_time, :utc + alias_method :getgm, :utc + alias_method :getutc, :utc + alias_method :gmtime, :utc + + # Returns the underlying +TZInfo::TimezonePeriod+. + def period + @period ||= time_zone.period_for_utc(@utc) + end + + # Returns the simultaneous time in Time.zone, or the specified zone. + def in_time_zone(new_zone = ::Time.zone) + return self if time_zone == new_zone + utc.in_time_zone(new_zone) + end + + # Returns a Time instance of the simultaneous time in the system timezone. + def localtime(utc_offset = nil) + utc.getlocal(utc_offset) + end + alias_method :getlocal, :localtime + + # Returns true if the current time is within Daylight Savings \Time for the + # specified time zone. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # Time.zone.parse("2012-5-30").dst? # => true + # Time.zone.parse("2012-11-30").dst? # => false + def dst? + period.dst? + end + alias_method :isdst, :dst? + + # Returns true if the current time zone is set to UTC. + # + # Time.zone = 'UTC' # => 'UTC' + # Time.zone.now.utc? # => true + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # Time.zone.now.utc? # => false + def utc? + zone == "UTC" || zone == "UCT" + end + alias_method :gmt?, :utc? + + # Returns the offset from current time to UTC time in seconds. + def utc_offset + period.observed_utc_offset + end + alias_method :gmt_offset, :utc_offset + alias_method :gmtoff, :utc_offset + + # Returns a formatted string of the offset from UTC, or an alternative + # string if the time zone is already UTC. + # + # Time.zone = 'Eastern Time (US & Canada)' # => "Eastern Time (US & Canada)" + # Time.zone.now.formatted_offset(true) # => "-05:00" + # Time.zone.now.formatted_offset(false) # => "-0500" + # Time.zone = 'UTC' # => "UTC" + # Time.zone.now.formatted_offset(true, "0") # => "0" + def formatted_offset(colon = true, alternate_utc_string = nil) + utc? && alternate_utc_string || TimeZone.seconds_to_utc_offset(utc_offset, colon) + end + + # Returns the time zone abbreviation. + # + # Time.zone = 'Eastern Time (US & Canada)' # => "Eastern Time (US & Canada)" + # Time.zone.now.zone # => "EST" + def zone + period.abbreviation + end + + # Returns a string of the object's date, time, zone, and offset from UTC. + # + # Time.zone.now.inspect # => "2024-11-13 07:00:10.528054960 UTC +00:00" + def inspect + "#{time.strftime('%F %H:%M:%S.%9N')} #{zone} #{formatted_offset}" + end + + # Returns a string of the object's date and time in the ISO 8601 standard + # format. + # + # Time.zone.now.xmlschema # => "2014-12-04T11:02:37-05:00" + def xmlschema(fraction_digits = 0) + "#{time.strftime(PRECISIONS[fraction_digits.to_i])}#{formatted_offset(true, 'Z')}" + end + alias_method :iso8601, :xmlschema + alias_method :rfc3339, :xmlschema + + # Coerces time to a string for JSON encoding. The default format is ISO 8601. + # You can get %Y/%m/%d %H:%M:%S +offset style by setting + # ActiveSupport::JSON::Encoding.use_standard_json_time_format + # to +false+. + # + # # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = true + # Time.utc(2005,2,1,15,15,10).in_time_zone("Hawaii").as_json + # # => "2005-02-01T05:15:10.000-10:00" + # + # # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = false + # Time.utc(2005,2,1,15,15,10).in_time_zone("Hawaii").as_json + # # => "2005/02/01 05:15:10 -1000" + def as_json(options = nil) + if ActiveSupport::JSON::Encoding.use_standard_json_time_format + xmlschema(ActiveSupport::JSON::Encoding.time_precision) + else + %(#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) + end + end + + def init_with(coder) # :nodoc: + initialize(coder["utc"], coder["zone"], coder["time"]) + end + + def encode_with(coder) # :nodoc: + coder.map = { "utc" => utc, "zone" => time_zone, "time" => time } + end + + # Returns a string of the object's date and time in the format used by + # HTTP requests. + # + # Time.zone.now.httpdate # => "Tue, 01 Jan 2013 04:39:43 GMT" + def httpdate + utc.httpdate + end + + # Returns a string of the object's date and time in the RFC 2822 standard + # format. + # + # Time.zone.now.rfc2822 # => "Tue, 01 Jan 2013 04:51:39 +0000" + def rfc2822 + to_fs(:rfc822) + end + alias_method :rfc822, :rfc2822 + + # Returns a string of the object's date and time. + def to_s + "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby Time#to_s format + end + + # Returns a string of the object's date and time. + # + # This method is aliased to to_formatted_s. + # + # Accepts an optional format: + # * :default - default value, mimics Ruby Time#to_s format. + # * :db - format outputs time in UTC :db time. See Time#to_fs(:db). + # * Any key in +Time::DATE_FORMATS+ can be used. See active_support/core_ext/time/conversions.rb. + def to_fs(format = :default) + if format == :db + utc.to_fs(format) + elsif formatter = ::Time::DATE_FORMATS[format] + formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) + else + to_s + end + end + alias_method :to_formatted_s, :to_fs + + # Replaces %Z directive with +zone before passing to Time#strftime, + # so that zone information is correct. + def strftime(format) + format = format.gsub(/((?:\A|[^%])(?:%%)*)%Z/, "\\1#{zone}") + getlocal(utc_offset).strftime(format) + end + + # Use the time in UTC for comparisons. + def <=>(other) + utc <=> other + end + alias_method :before?, :< + alias_method :after?, :> + + # Returns true if the current object's time is within the specified + # +min+ and +max+ time. + def between?(min, max) + utc.between?(min, max) + end + + # Returns true if the current object's time is in the past. + def past? + utc.past? + end + + # Returns true if the current object's time falls within + # the current day. + def today? + time.today? + end + + # Returns true if the current object's time falls within + # the next day (tomorrow). + def tomorrow? + time.tomorrow? + end + alias :next_day? :tomorrow? + + # Returns true if the current object's time falls within + # the previous day (yesterday). + def yesterday? + time.yesterday? + end + alias :prev_day? :yesterday? + + # Returns true if the current object's time is in the future. + def future? + utc.future? + end + + # Returns +true+ if +other+ is equal to current object. + def eql?(other) + other.eql?(utc) + end + + def hash + utc.hash + end + + # Adds an interval of time to the current object's time and returns that + # value as a new TimeWithZone object. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28.725182881 EDT -04:00 + # now + 1000 # => Sun, 02 Nov 2014 01:43:08.725182881 EDT -04:00 + # + # If we're adding a Duration of variable length (i.e., years, months, days), + # move forward from #time, otherwise move forward from #utc, for accuracy + # when moving across DST boundaries. + # + # For instance, a time + 24.hours will advance exactly 24 hours, while a + # time + 1.day will advance 23-25 hours, depending on the day. + # + # now + 24.hours # => Mon, 03 Nov 2014 00:26:28.725182881 EST -05:00 + # now + 1.day # => Mon, 03 Nov 2014 01:26:28.725182881 EST -05:00 + def +(other) + if duration_of_variable_length?(other) + method_missing(:+, other) + else + begin + result = utc + other + rescue TypeError + result = utc.to_datetime.since(other) + ActiveSupport.deprecator.warn( + "Adding an instance of #{other.class} to an instance of #{self.class} is deprecated. This behavior will raise " \ + "a `TypeError` in Rails 8.1." + ) + result.in_time_zone(time_zone) + end + result.in_time_zone(time_zone) + end + end + alias_method :since, :+ + alias_method :in, :+ + + # Subtracts an interval of time and returns a new TimeWithZone object unless + # the other value +acts_like?+ time. In which case, it will subtract the + # other time and return the difference in seconds as a Float. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28.725182881 EST -05:00 + # now - 1000 # => Mon, 03 Nov 2014 00:09:48.725182881 EST -05:00 + # + # If subtracting a Duration of variable length (i.e., years, months, days), + # move backward from #time, otherwise move backward from #utc, for accuracy + # when moving across DST boundaries. + # + # For instance, a time - 24.hours will go subtract exactly 24 hours, while a + # time - 1.day will subtract 23-25 hours, depending on the day. + # + # now - 24.hours # => Sun, 02 Nov 2014 01:26:28.725182881 EDT -04:00 + # now - 1.day # => Sun, 02 Nov 2014 00:26:28.725182881 EDT -04:00 + # + # If both the TimeWithZone object and the other value act like Time, a Float + # will be returned. + # + # Time.zone.now - 1.day.ago # => 86399.999967 + # + def -(other) + if other.acts_like?(:time) + getutc - other.getutc + elsif duration_of_variable_length?(other) + method_missing(:-, other) + else + result = utc - other + result.in_time_zone(time_zone) + end + end + + # Subtracts an interval of time from the current object's time and returns + # the result as a new TimeWithZone object. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28.725182881 EST -05:00 + # now.ago(1000) # => Mon, 03 Nov 2014 00:09:48.725182881 EST -05:00 + # + # If we're subtracting a Duration of variable length (i.e., years, months, + # days), move backward from #time, otherwise move backward from #utc, for + # accuracy when moving across DST boundaries. + # + # For instance, time.ago(24.hours) will move back exactly 24 hours, + # while time.ago(1.day) will move back 23-25 hours, depending on + # the day. + # + # now.ago(24.hours) # => Sun, 02 Nov 2014 01:26:28.725182881 EDT -04:00 + # now.ago(1.day) # => Sun, 02 Nov 2014 00:26:28.725182881 EDT -04:00 + def ago(other) + since(-other) + end + + # Returns a new +ActiveSupport::TimeWithZone+ where one or more of the elements have + # been changed according to the +options+ parameter. The time options (:hour, + # :min, :sec, :usec, :nsec) reset cascadingly, + # so if only the hour is passed, then minute, sec, usec, and nsec is set to 0. If the + # hour and minute is passed, then sec, usec, and nsec is set to 0. The +options+ + # parameter takes a hash with any of these keys: :year, :month, + # :day, :hour, :min, :sec, :usec, + # :nsec, :offset, :zone. Pass either :usec + # or :nsec, not both. Similarly, pass either :zone or + # :offset, not both. + # + # t = Time.zone.now # => Fri, 14 Apr 2017 11:45:15.116992711 EST -05:00 + # t.change(year: 2020) # => Tue, 14 Apr 2020 11:45:15.116992711 EST -05:00 + # t.change(hour: 12) # => Fri, 14 Apr 2017 12:00:00.000000000 EST -05:00 + # t.change(min: 30) # => Fri, 14 Apr 2017 11:30:00.000000000 EST -05:00 + # t.change(offset: "-10:00") # => Fri, 14 Apr 2017 11:45:15.116992711 HST -10:00 + # t.change(zone: "Hawaii") # => Fri, 14 Apr 2017 11:45:15.116992711 HST -10:00 + def change(options) + if options[:zone] && options[:offset] + raise ArgumentError, "Can't change both :offset and :zone at the same time: #{options.inspect}" + end + + new_time = time.change(options) + + if options[:zone] + new_zone = ::Time.find_zone(options[:zone]) + elsif options[:offset] + new_zone = ::Time.find_zone(new_time.utc_offset) + end + + new_zone ||= time_zone + periods = new_zone.periods_for_local(new_time) + + self.class.new(nil, new_zone, new_time, periods.include?(period) ? period : nil) + end + + # Uses Date to provide precise Time calculations for years, months, and days + # according to the proleptic Gregorian calendar. The result is returned as a + # new TimeWithZone object. + # + # The +options+ parameter takes a hash with any of these keys: + # :years, :months, :weeks, :days, + # :hours, :minutes, :seconds. + # + # If advancing by a value of variable length (i.e., years, weeks, months, + # days), move forward from #time, otherwise move forward from #utc, for + # accuracy when moving across DST boundaries. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28.558049687 EDT -04:00 + # now.advance(seconds: 1) # => Sun, 02 Nov 2014 01:26:29.558049687 EDT -04:00 + # now.advance(minutes: 1) # => Sun, 02 Nov 2014 01:27:28.558049687 EDT -04:00 + # now.advance(hours: 1) # => Sun, 02 Nov 2014 01:26:28.558049687 EST -05:00 + # now.advance(days: 1) # => Mon, 03 Nov 2014 01:26:28.558049687 EST -05:00 + # now.advance(weeks: 1) # => Sun, 09 Nov 2014 01:26:28.558049687 EST -05:00 + # now.advance(months: 1) # => Tue, 02 Dec 2014 01:26:28.558049687 EST -05:00 + # now.advance(years: 1) # => Mon, 02 Nov 2015 01:26:28.558049687 EST -05:00 + def advance(options) + # If we're advancing a value of variable length (i.e., years, weeks, months, days), advance from #time, + # otherwise advance from #utc, for accuracy when moving across DST boundaries + if options.values_at(:years, :weeks, :months, :days).any? + method_missing(:advance, options) + else + utc.advance(options).in_time_zone(time_zone) + end + end + + %w(year mon month day mday wday yday hour min sec usec nsec to_date).each do |method_name| + class_eval <<-EOV, __FILE__, __LINE__ + 1 + def #{method_name} # def month + time.#{method_name} # time.month + end # end + EOV + end + + # Returns Array of parts of Time in sequence of + # [seconds, minutes, hours, day, month, year, weekday, yearday, dst?, zone]. + # + # now = Time.zone.now # => Tue, 18 Aug 2015 02:29:27.485278555 UTC +00:00 + # now.to_a # => [27, 29, 2, 18, 8, 2015, 2, 230, false, "UTC"] + def to_a + [time.sec, time.min, time.hour, time.day, time.mon, time.year, time.wday, time.yday, dst?, zone] + end + + # Returns the object's date and time as a floating-point number of seconds + # since the Epoch (January 1, 1970 00:00 UTC). + # + # Time.zone.now.to_f # => 1417709320.285418 + def to_f + utc.to_f + end + + # Returns the object's date and time as an integer number of seconds + # since the Epoch (January 1, 1970 00:00 UTC). + # + # Time.zone.now.to_i # => 1417709320 + def to_i + utc.to_i + end + alias_method :tv_sec, :to_i + + # Returns the object's date and time as a rational number of seconds + # since the Epoch (January 1, 1970 00:00 UTC). + # + # Time.zone.now.to_r # => (708854548642709/500000) + def to_r + utc.to_r + end + + # Returns an instance of DateTime with the timezone's UTC offset + # + # Time.zone.now.to_datetime # => Tue, 18 Aug 2015 02:32:20 +0000 + # Time.current.in_time_zone('Hawaii').to_datetime # => Mon, 17 Aug 2015 16:32:20 -1000 + def to_datetime + @to_datetime ||= utc.to_datetime.new_offset(Rational(utc_offset, 86_400)) + end + + # Returns an instance of +Time+, either with the same timezone as +self+, + # with the same UTC offset as +self+ or in the local system timezone + # depending on the setting of +ActiveSupport.to_time_preserves_timezone+. + def to_time + if preserve_timezone == :zone + @to_time_with_timezone ||= getlocal(time_zone) + elsif preserve_timezone + @to_time_with_instance_offset ||= getlocal(utc_offset) + else + @to_time_with_system_offset ||= getlocal + end + end + + # So that +self+ acts_like?(:time). + def acts_like_time? + true + end + + # Say we're a Time to thwart type checking. + def is_a?(klass) + klass == ::Time || super + end + alias_method :kind_of?, :is_a? + + # An instance of ActiveSupport::TimeWithZone is never blank + def blank? + false + end + + def present? # :nodoc: + true + end + + def freeze + # preload instance variables before freezing + period; utc; time; to_datetime; to_time + super + end + + def marshal_dump + [utc, time_zone.name, time] + end + + def marshal_load(variables) + initialize(variables[0].utc, ::Time.find_zone(variables[1]), variables[2].utc) + end + + # respond_to_missing? is not called in some cases, such as when type conversion is + # performed with Kernel#String + def respond_to?(sym, include_priv = false) + # ensure that we're not going to throw and rescue from NoMethodError in method_missing which is slow + return false if sym.to_sym == :to_str + super + end + + # Ensure proxy class responds to all methods that underlying time instance + # responds to. + def respond_to_missing?(sym, include_priv) + time.respond_to?(sym, include_priv) + end + + # Send the missing method to +time+ instance, and wrap result in a new + # TimeWithZone with the existing +time_zone+. + def method_missing(...) + wrap_with_time_zone time.__send__(...) + rescue NoMethodError => e + raise e, e.message.sub(time.inspect, inspect).sub("Time", "ActiveSupport::TimeWithZone"), e.backtrace + end + + private + SECONDS_PER_DAY = 86400 + + def incorporate_utc_offset(time, offset) + if time.kind_of?(Date) + time + Rational(offset, SECONDS_PER_DAY) + else + time + offset + end + end + + def get_period_and_ensure_valid_local_time(period) + # we don't want a Time.local instance enforcing its own DST rules as well, + # so transfer time values to a utc constructor if necessary + @time = transfer_time_values_to_utc_constructor(@time) unless @time.utc? + begin + period || @time_zone.period_for_local(@time) + rescue ::TZInfo::PeriodNotFound + # time is in the "spring forward" hour gap, so we're moving the time forward one hour and trying again + @time += 1.hour + retry + end + end + + def transfer_time_values_to_utc_constructor(time) + # avoid creating another Time object if possible + return time if time.instance_of?(::Time) && time.utc? + ::Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec + time.subsec) + end + + def duration_of_variable_length?(obj) + ActiveSupport::Duration === obj && obj.variable? + end + + def wrap_with_time_zone(time) + if time.acts_like?(:time) + periods = time_zone.periods_for_local(time) + self.class.new(nil, time_zone, time, periods.include?(period) ? period : nil) + elsif time.is_a?(Range) + wrap_with_time_zone(time.begin)..wrap_with_time_zone(time.end) + else + time + end + end + end +end + +# These prevent Psych from calling `ActiveSupport::TimeWithZone.name` +# and triggering the deprecation warning about the change in Rails 7.1. +YAML.load_tags["!ruby/object:ActiveSupport::TimeWithZone"] = "ActiveSupport::TimeWithZone" +YAML.dump_tags[ActiveSupport::TimeWithZone] = "!ruby/object:ActiveSupport::TimeWithZone" diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/values/time_zone.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/values/time_zone.rb new file mode 100644 index 00000000..db98f37c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/values/time_zone.rb @@ -0,0 +1,614 @@ +# frozen_string_literal: true + +require "tzinfo" +require "concurrent/map" + +module ActiveSupport + # = Active Support \Time Zone + # + # The TimeZone class serves as a wrapper around +TZInfo::Timezone+ instances. + # It allows us to do the following: + # + # * Limit the set of zones provided by TZInfo to a meaningful subset of 134 + # zones. + # * Retrieve and display zones with a friendlier name + # (e.g., "Eastern \Time (US & Canada)" instead of "America/New_York"). + # * Lazily load +TZInfo::Timezone+ instances only when they're needed. + # * Create ActiveSupport::TimeWithZone instances via TimeZone's +local+, + # +parse+, +at+, and +now+ methods. + # + # If you set config.time_zone in the \Rails Application, you can + # access this TimeZone object via Time.zone: + # + # # application.rb: + # class Application < Rails::Application + # config.time_zone = 'Eastern Time (US & Canada)' + # end + # + # Time.zone # => # + # Time.zone.name # => "Eastern Time (US & Canada)" + # Time.zone.now # => Sun, 18 May 2008 14:30:44 EDT -04:00 + class TimeZone + # Keys are \Rails TimeZone names, values are TZInfo identifiers. + MAPPING = { + "International Date Line West" => "Etc/GMT+12", + "Midway Island" => "Pacific/Midway", + "American Samoa" => "Pacific/Pago_Pago", + "Hawaii" => "Pacific/Honolulu", + "Alaska" => "America/Juneau", + "Pacific Time (US & Canada)" => "America/Los_Angeles", + "Tijuana" => "America/Tijuana", + "Mountain Time (US & Canada)" => "America/Denver", + "Arizona" => "America/Phoenix", + "Chihuahua" => "America/Chihuahua", + "Mazatlan" => "America/Mazatlan", + "Central Time (US & Canada)" => "America/Chicago", + "Saskatchewan" => "America/Regina", + "Guadalajara" => "America/Mexico_City", + "Mexico City" => "America/Mexico_City", + "Monterrey" => "America/Monterrey", + "Central America" => "America/Guatemala", + "Eastern Time (US & Canada)" => "America/New_York", + "Indiana (East)" => "America/Indiana/Indianapolis", + "Bogota" => "America/Bogota", + "Lima" => "America/Lima", + "Quito" => "America/Lima", + "Atlantic Time (Canada)" => "America/Halifax", + "Caracas" => "America/Caracas", + "La Paz" => "America/La_Paz", + "Santiago" => "America/Santiago", + "Newfoundland" => "America/St_Johns", + "Brasilia" => "America/Sao_Paulo", + "Buenos Aires" => "America/Argentina/Buenos_Aires", + "Montevideo" => "America/Montevideo", + "Georgetown" => "America/Guyana", + "Puerto Rico" => "America/Puerto_Rico", + "Greenland" => "America/Godthab", + "Mid-Atlantic" => "Atlantic/South_Georgia", + "Azores" => "Atlantic/Azores", + "Cape Verde Is." => "Atlantic/Cape_Verde", + "Dublin" => "Europe/Dublin", + "Edinburgh" => "Europe/London", + "Lisbon" => "Europe/Lisbon", + "London" => "Europe/London", + "Casablanca" => "Africa/Casablanca", + "Monrovia" => "Africa/Monrovia", + "UTC" => "Etc/UTC", + "Belgrade" => "Europe/Belgrade", + "Bratislava" => "Europe/Bratislava", + "Budapest" => "Europe/Budapest", + "Ljubljana" => "Europe/Ljubljana", + "Prague" => "Europe/Prague", + "Sarajevo" => "Europe/Sarajevo", + "Skopje" => "Europe/Skopje", + "Warsaw" => "Europe/Warsaw", + "Zagreb" => "Europe/Zagreb", + "Brussels" => "Europe/Brussels", + "Copenhagen" => "Europe/Copenhagen", + "Madrid" => "Europe/Madrid", + "Paris" => "Europe/Paris", + "Amsterdam" => "Europe/Amsterdam", + "Berlin" => "Europe/Berlin", + "Bern" => "Europe/Zurich", + "Zurich" => "Europe/Zurich", + "Rome" => "Europe/Rome", + "Stockholm" => "Europe/Stockholm", + "Vienna" => "Europe/Vienna", + "West Central Africa" => "Africa/Algiers", + "Bucharest" => "Europe/Bucharest", + "Cairo" => "Africa/Cairo", + "Helsinki" => "Europe/Helsinki", + "Kyiv" => "Europe/Kiev", + "Riga" => "Europe/Riga", + "Sofia" => "Europe/Sofia", + "Tallinn" => "Europe/Tallinn", + "Vilnius" => "Europe/Vilnius", + "Athens" => "Europe/Athens", + "Istanbul" => "Europe/Istanbul", + "Minsk" => "Europe/Minsk", + "Jerusalem" => "Asia/Jerusalem", + "Harare" => "Africa/Harare", + "Pretoria" => "Africa/Johannesburg", + "Kaliningrad" => "Europe/Kaliningrad", + "Moscow" => "Europe/Moscow", + "St. Petersburg" => "Europe/Moscow", + "Volgograd" => "Europe/Volgograd", + "Samara" => "Europe/Samara", + "Kuwait" => "Asia/Kuwait", + "Riyadh" => "Asia/Riyadh", + "Nairobi" => "Africa/Nairobi", + "Baghdad" => "Asia/Baghdad", + "Tehran" => "Asia/Tehran", + "Abu Dhabi" => "Asia/Muscat", + "Muscat" => "Asia/Muscat", + "Baku" => "Asia/Baku", + "Tbilisi" => "Asia/Tbilisi", + "Yerevan" => "Asia/Yerevan", + "Kabul" => "Asia/Kabul", + "Ekaterinburg" => "Asia/Yekaterinburg", + "Islamabad" => "Asia/Karachi", + "Karachi" => "Asia/Karachi", + "Tashkent" => "Asia/Tashkent", + "Chennai" => "Asia/Kolkata", + "Kolkata" => "Asia/Kolkata", + "Mumbai" => "Asia/Kolkata", + "New Delhi" => "Asia/Kolkata", + "Kathmandu" => "Asia/Kathmandu", + "Dhaka" => "Asia/Dhaka", + "Sri Jayawardenepura" => "Asia/Colombo", + "Almaty" => "Asia/Almaty", + "Astana" => "Asia/Almaty", + "Novosibirsk" => "Asia/Novosibirsk", + "Rangoon" => "Asia/Rangoon", + "Bangkok" => "Asia/Bangkok", + "Hanoi" => "Asia/Bangkok", + "Jakarta" => "Asia/Jakarta", + "Krasnoyarsk" => "Asia/Krasnoyarsk", + "Beijing" => "Asia/Shanghai", + "Chongqing" => "Asia/Chongqing", + "Hong Kong" => "Asia/Hong_Kong", + "Urumqi" => "Asia/Urumqi", + "Kuala Lumpur" => "Asia/Kuala_Lumpur", + "Singapore" => "Asia/Singapore", + "Taipei" => "Asia/Taipei", + "Perth" => "Australia/Perth", + "Irkutsk" => "Asia/Irkutsk", + "Ulaanbaatar" => "Asia/Ulaanbaatar", + "Seoul" => "Asia/Seoul", + "Osaka" => "Asia/Tokyo", + "Sapporo" => "Asia/Tokyo", + "Tokyo" => "Asia/Tokyo", + "Yakutsk" => "Asia/Yakutsk", + "Darwin" => "Australia/Darwin", + "Adelaide" => "Australia/Adelaide", + "Canberra" => "Australia/Canberra", + "Melbourne" => "Australia/Melbourne", + "Sydney" => "Australia/Sydney", + "Brisbane" => "Australia/Brisbane", + "Hobart" => "Australia/Hobart", + "Vladivostok" => "Asia/Vladivostok", + "Guam" => "Pacific/Guam", + "Port Moresby" => "Pacific/Port_Moresby", + "Magadan" => "Asia/Magadan", + "Srednekolymsk" => "Asia/Srednekolymsk", + "Solomon Is." => "Pacific/Guadalcanal", + "New Caledonia" => "Pacific/Noumea", + "Fiji" => "Pacific/Fiji", + "Kamchatka" => "Asia/Kamchatka", + "Marshall Is." => "Pacific/Majuro", + "Auckland" => "Pacific/Auckland", + "Wellington" => "Pacific/Auckland", + "Nuku'alofa" => "Pacific/Tongatapu", + "Tokelau Is." => "Pacific/Fakaofo", + "Chatham Is." => "Pacific/Chatham", + "Samoa" => "Pacific/Apia" + } + + UTC_OFFSET_WITH_COLON = "%s%02d:%02d" # :nodoc: + UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.tr(":", "") # :nodoc: + private_constant :UTC_OFFSET_WITH_COLON, :UTC_OFFSET_WITHOUT_COLON + + @lazy_zones_map = Concurrent::Map.new + @country_zones = Concurrent::Map.new + + class << self + # Assumes self represents an offset from UTC in seconds (as returned from + # Time#utc_offset) and turns this into an +HH:MM formatted string. + # + # ActiveSupport::TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00" + def seconds_to_utc_offset(seconds, colon = true) + format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON + sign = (seconds < 0 ? "-" : "+") + hours = seconds.abs / 3600 + minutes = (seconds.abs % 3600) / 60 + format % [sign, hours, minutes] + end + + def find_tzinfo(name) + TZInfo::Timezone.get(MAPPING[name] || name) + end + + alias_method :create, :new # :nodoc: + + # Returns a TimeZone instance with the given name, or +nil+ if no + # such TimeZone instance exists. (This exists to support the use of + # this class with the +composed_of+ macro.) + def new(name) + self[name] + end + + # Returns an array of all TimeZone objects. There are multiple + # TimeZone objects per time zone, in many cases, to make it easier + # for users to find their own time zone. + def all + @zones ||= zones_map.values.sort + end + + # Locate a specific time zone object. If the argument is a string, it + # is interpreted to mean the name of the timezone to locate. If it is a + # numeric value it is either the hour offset, or the second offset, of the + # timezone to find. (The first one with that offset will be returned.) + # Returns +nil+ if no such time zone is known to the system. + def [](arg) + case arg + when self + arg + when String + begin + @lazy_zones_map[arg] ||= create(arg) + rescue TZInfo::InvalidTimezoneIdentifier + nil + end + when TZInfo::Timezone + @lazy_zones_map[arg.name] ||= create(arg.name, nil, arg) + when Numeric, ActiveSupport::Duration + arg *= 3600 if arg.abs <= 13 + all.find { |z| z.utc_offset == arg.to_i } + else + raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}" + end + end + + # A convenience method for returning a collection of TimeZone objects + # for time zones in the USA. + def us_zones + country_zones(:us) + end + + # A convenience method for returning a collection of TimeZone objects + # for time zones in the country specified by its ISO 3166-1 Alpha2 code. + def country_zones(country_code) + code = country_code.to_s.upcase + @country_zones[code] ||= load_country_zones(code) + end + + def clear # :nodoc: + @lazy_zones_map = Concurrent::Map.new + @country_zones = Concurrent::Map.new + @zones = nil + @zones_map = nil + end + + private + def load_country_zones(code) + country = TZInfo::Country.get(code) + country.zone_identifiers.flat_map do |tz_id| + if MAPPING.value?(tz_id) + MAPPING.inject([]) do |memo, (key, value)| + memo << self[key] if value == tz_id + memo + end + else + create(tz_id, nil, TZInfo::Timezone.get(tz_id)) + end + end.sort! + end + + def zones_map + @zones_map ||= MAPPING.each_with_object({}) do |(name, _), zones| + timezone = self[name] + zones[name] = timezone if timezone + end + end + end + + include Comparable + attr_reader :name + attr_reader :tzinfo + + ## + # :singleton-method: create + # :call-seq: create(name, utc_offset = nil, tzinfo = nil) + # + # Create a new TimeZone object with the given name and offset. The + # offset is the number of seconds that this time zone is offset from UTC + # (GMT). Seconds were chosen as the offset unit because that is the unit + # that Ruby uses to represent time zone offsets (see Time#utc_offset). + + # :stopdoc: + def initialize(name, utc_offset = nil, tzinfo = nil) + @name = name + @utc_offset = utc_offset + @tzinfo = tzinfo || TimeZone.find_tzinfo(name) + end + # :startdoc: + + # Returns the offset of this time zone from UTC in seconds. + def utc_offset + @utc_offset || tzinfo&.current_period&.base_utc_offset + end + + # Returns a formatted string of the offset from UTC, or an alternative + # string if the time zone is already UTC. + # + # zone = ActiveSupport::TimeZone['Central Time (US & Canada)'] + # zone.formatted_offset # => "-06:00" + # zone.formatted_offset(false) # => "-0600" + def formatted_offset(colon = true, alternate_utc_string = nil) + utc_offset == 0 && alternate_utc_string || self.class.seconds_to_utc_offset(utc_offset, colon) + end + + # Compare this time zone to the parameter. The two are compared first on + # their offsets, and then by name. + def <=>(zone) + return unless zone.respond_to? :utc_offset + result = (utc_offset <=> zone.utc_offset) + result = (name <=> zone.name) if result == 0 + result + end + + # Compare #name and TZInfo identifier to a supplied regexp, returning +true+ + # if a match is found. + def =~(re) + re === name || re === MAPPING[name] + end + + # Compare #name and TZInfo identifier to a supplied regexp, returning +true+ + # if a match is found. + def match?(re) + (re == name) || (re == MAPPING[name]) || + ((Regexp === re) && (re.match?(name) || re.match?(MAPPING[name]))) + end + + # Returns a textual representation of this time zone. + def to_s + "(GMT#{formatted_offset}) #{name}" + end + + # \Method for creating new ActiveSupport::TimeWithZone instance in time zone + # of +self+ from given values. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.local(2007, 2, 1, 15, 30, 45) # => Thu, 01 Feb 2007 15:30:45 HST -10:00 + def local(*args) + time = Time.utc(*args) + ActiveSupport::TimeWithZone.new(nil, self, time) + end + + # \Method for creating new ActiveSupport::TimeWithZone instance in time zone + # of +self+ from number of seconds since the Unix epoch. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.utc(2000).to_f # => 946684800.0 + # Time.zone.at(946684800.0) # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # + # A second argument can be supplied to specify sub-second precision. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.at(946684800, 123456.789).nsec # => 123456789 + def at(*args) + Time.at(*args).utc.in_time_zone(self) + end + + # \Method for creating new ActiveSupport::TimeWithZone instance in time zone + # of +self+ from an ISO 8601 string. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.iso8601('1999-12-31T14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # + # If the time components are missing then they will be set to zero. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.iso8601('1999-12-31') # => Fri, 31 Dec 1999 00:00:00 HST -10:00 + # + # If the string is invalid then an +ArgumentError+ will be raised unlike +parse+ + # which usually returns +nil+ when given an invalid date string. + def iso8601(str) + # Historically `Date._iso8601(nil)` returns `{}`, but in the `date` gem versions `3.2.1`, `3.1.2`, `3.0.2`, + # and `2.0.1`, `Date._iso8601(nil)` raises `TypeError` https://github.com/ruby/date/issues/39 + # Future `date` releases are expected to revert back to the original behavior. + raise ArgumentError, "invalid date" if str.nil? + + parts = Date._iso8601(str) + + year = parts.fetch(:year) + + if parts.key?(:yday) + ordinal_date = Date.ordinal(year, parts.fetch(:yday)) + month = ordinal_date.month + day = ordinal_date.day + else + month = parts.fetch(:mon) + day = parts.fetch(:mday) + end + + time = Time.new( + year, + month, + day, + parts.fetch(:hour, 0), + parts.fetch(:min, 0), + parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0), + parts.fetch(:offset, 0) + ) + + if parts[:offset] + TimeWithZone.new(time.utc, self) + else + TimeWithZone.new(nil, self, time) + end + + rescue Date::Error, KeyError + raise ArgumentError, "invalid date" + end + + # \Method for creating new ActiveSupport::TimeWithZone instance in time zone + # of +self+ from parsed string. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.parse('1999-12-31 14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # + # If upper components are missing from the string, they are supplied from + # TimeZone#now: + # + # Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # Time.zone.parse('22:30:00') # => Fri, 31 Dec 1999 22:30:00 HST -10:00 + # + # However, if the date component is not provided, but any other upper + # components are supplied, then the day of the month defaults to 1: + # + # Time.zone.parse('Mar 2000') # => Wed, 01 Mar 2000 00:00:00 HST -10:00 + # + # If the string is invalid then an +ArgumentError+ could be raised. + def parse(str, now = now()) + parts_to_time(Date._parse(str, false), now) + end + + # \Method for creating new ActiveSupport::TimeWithZone instance in time zone + # of +self+ from an RFC 3339 string. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.rfc3339('2000-01-01T00:00:00Z') # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # + # If the time or zone components are missing then an +ArgumentError+ will + # be raised. This is much stricter than either +parse+ or +iso8601+ which + # allow for missing components. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.rfc3339('1999-12-31') # => ArgumentError: invalid date + def rfc3339(str) + parts = Date._rfc3339(str) + + raise ArgumentError, "invalid date" if parts.empty? + + time = Time.new( + parts.fetch(:year), + parts.fetch(:mon), + parts.fetch(:mday), + parts.fetch(:hour), + parts.fetch(:min), + parts.fetch(:sec) + parts.fetch(:sec_fraction, 0), + parts.fetch(:offset) + ) + + TimeWithZone.new(time.utc, self) + end + + # Parses +str+ according to +format+ and returns an ActiveSupport::TimeWithZone. + # + # Assumes that +str+ is a time in the time zone +self+, + # unless +format+ includes an explicit time zone. + # (This is the same behavior as +parse+.) + # In either case, the returned TimeWithZone has the timezone of +self+. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.strptime('1999-12-31 14:00:00', '%Y-%m-%d %H:%M:%S') # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # + # If upper components are missing from the string, they are supplied from + # TimeZone#now: + # + # Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # Time.zone.strptime('22:30:00', '%H:%M:%S') # => Fri, 31 Dec 1999 22:30:00 HST -10:00 + # + # However, if the date component is not provided, but any other upper + # components are supplied, then the day of the month defaults to 1: + # + # Time.zone.strptime('Mar 2000', '%b %Y') # => Wed, 01 Mar 2000 00:00:00 HST -10:00 + def strptime(str, format, now = now()) + parts_to_time(DateTime._strptime(str, format), now) + end + + # Returns an ActiveSupport::TimeWithZone instance representing the current + # time in the time zone represented by +self+. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.now # => Wed, 23 Jan 2008 20:24:27 HST -10:00 + def now + time_now.utc.in_time_zone(self) + end + + # Returns the current date in this time zone. + def today + tzinfo.now.to_date + end + + # Returns the next date in this time zone. + def tomorrow + today + 1 + end + + # Returns the previous date in this time zone. + def yesterday + today - 1 + end + + # Adjust the given time to the simultaneous time in the time zone + # represented by +self+. Returns a local time with the appropriate offset + # -- if you want an ActiveSupport::TimeWithZone instance, use + # Time#in_time_zone() instead. + # + # As of tzinfo 2, utc_to_local returns a Time with a non-zero utc_offset. + # See the +utc_to_local_returns_utc_offset_times+ config for more info. + def utc_to_local(time) + tzinfo.utc_to_local(time).yield_self do |t| + ActiveSupport.utc_to_local_returns_utc_offset_times ? + t : Time.utc(t.year, t.month, t.day, t.hour, t.min, t.sec, t.sec_fraction * 1_000_000) + end + end + + # Adjust the given time to the simultaneous time in UTC. Returns a + # Time.utc() instance. + def local_to_utc(time, dst = true) + tzinfo.local_to_utc(time, dst) + end + + def period_for_utc(time) # :nodoc: + tzinfo.period_for_utc(time) + end + + def period_for_local(time, dst = true) # :nodoc: + tzinfo.period_for_local(time, dst) { |periods| periods.last } + end + + def periods_for_local(time) # :nodoc: + tzinfo.periods_for_local(time) + end + + def abbr(time) # :nodoc: + tzinfo.abbr(time) + end + + def dst?(time) # :nodoc: + tzinfo.dst?(time) + end + + def init_with(coder) # :nodoc: + initialize(coder["name"]) + end + + def encode_with(coder) # :nodoc: + coder.tag = "!ruby/object:#{self.class}" + coder.map = { "name" => tzinfo.name } + end + + private + def parts_to_time(parts, now) + raise ArgumentError, "invalid date" if parts.nil? + return if parts.empty? + + if parts[:seconds] + time = Time.at(parts[:seconds]) + else + time = Time.new( + parts.fetch(:year, now.year), + parts.fetch(:mon, now.month), + parts.fetch(:mday, parts[:year] || parts[:mon] ? 1 : now.day), + parts.fetch(:hour, 0), + parts.fetch(:min, 0), + parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0), + parts.fetch(:offset, 0) + ) + end + + if parts[:offset] || parts[:seconds] + TimeWithZone.new(time.utc, self) + else + TimeWithZone.new(nil, self, time) + end + end + + def time_now + Time.now + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/version.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/version.rb new file mode 100644 index 00000000..12f83379 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative "gem_version" + +module ActiveSupport + # Returns the currently loaded version of Active Support as a +Gem::Version+. + def self.version + gem_version + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini.rb new file mode 100644 index 00000000..380d0156 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini.rb @@ -0,0 +1,211 @@ +# frozen_string_literal: true + +require "time" +require "base64" +require "bigdecimal" +require "bigdecimal/util" +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/date_time/calculations" + +module ActiveSupport + # = \XmlMini + # + # To use the much faster libxml parser: + # gem "libxml-ruby" + # XmlMini.backend = 'LibXML' + module XmlMini + extend self + + # This module decorates files deserialized using Hash.from_xml with + # the original_filename and content_type methods. + module FileLike # :nodoc: + attr_writer :original_filename, :content_type + + def original_filename + @original_filename || "untitled" + end + + def content_type + @content_type || "application/octet-stream" + end + end + + DEFAULT_ENCODINGS = { + "binary" => "base64" + } unless defined?(DEFAULT_ENCODINGS) + + unless defined?(TYPE_NAMES) + TYPE_NAMES = { + "Symbol" => "symbol", + "Integer" => "integer", + "BigDecimal" => "decimal", + "Float" => "float", + "TrueClass" => "boolean", + "FalseClass" => "boolean", + "Date" => "date", + "DateTime" => "dateTime", + "Time" => "dateTime", + "ActiveSupport::Duration" => "duration", + "Array" => "array", + "Hash" => "hash" + } + end + TYPE_NAMES["ActiveSupport::TimeWithZone"] = TYPE_NAMES["Time"] + + FORMATTING = { + "symbol" => Proc.new { |symbol| symbol.to_s }, + "date" => Proc.new { |date| date.to_fs(:db) }, + "dateTime" => Proc.new { |time| time.xmlschema }, + "duration" => Proc.new { |duration| duration.iso8601 }, + "binary" => Proc.new { |binary| ::Base64.encode64(binary) }, + "yaml" => Proc.new { |yaml| yaml.to_yaml } + } unless defined?(FORMATTING) + + # TODO use regexp instead of Date.parse + unless defined?(PARSING) + PARSING = { + "symbol" => Proc.new { |symbol| symbol.to_s.to_sym }, + "date" => Proc.new { |date| ::Date.parse(date) }, + "datetime" => Proc.new { |time| Time.xmlschema(time).utc rescue ::DateTime.parse(time).utc }, + "duration" => Proc.new { |duration| Duration.parse(duration) }, + "integer" => Proc.new { |integer| integer.to_i }, + "float" => Proc.new { |float| float.to_f }, + "decimal" => Proc.new do |number| + if String === number + number.to_d + else + BigDecimal(number) + end + end, + "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.to_s.strip) }, + "string" => Proc.new { |string| string.to_s }, + "yaml" => Proc.new { |yaml| YAML.load(yaml) rescue yaml }, + "base64Binary" => Proc.new { |bin| ::Base64.decode64(bin) }, + "hexBinary" => Proc.new { |bin| _parse_hex_binary(bin) }, + "binary" => Proc.new { |bin, entity| _parse_binary(bin, entity) }, + "file" => Proc.new { |file, entity| _parse_file(file, entity) } + } + + PARSING.update( + "double" => PARSING["float"], + "dateTime" => PARSING["datetime"] + ) + end + + attr_accessor :depth + self.depth = 100 + + delegate :parse, to: :backend + + def backend + current_thread_backend || @backend + end + + def backend=(name) + backend = name && cast_backend_name_to_module(name) + self.current_thread_backend = backend if current_thread_backend + @backend = backend + end + + def with_backend(name) + old_backend = current_thread_backend + self.current_thread_backend = name && cast_backend_name_to_module(name) + yield + ensure + self.current_thread_backend = old_backend + end + + def to_tag(key, value, options) + type_name = options.delete(:type) + merged_options = options.merge(root: key, skip_instruct: true) + + if value.is_a?(::Method) || value.is_a?(::Proc) + if value.arity == 1 + value.call(merged_options) + else + value.call(merged_options, key.to_s.singularize) + end + elsif value.respond_to?(:to_xml) + value.to_xml(merged_options) + else + type_name ||= TYPE_NAMES[value.class.name] + type_name ||= value.class.name if value && !value.respond_to?(:to_str) + type_name = type_name.to_s if type_name + type_name = "dateTime" if type_name == "datetime" + + key = rename_key(key.to_s, options) + + attributes = options[:skip_types] || type_name.nil? ? {} : { type: type_name } + attributes[:nil] = true if value.nil? + + encoding = options[:encoding] || DEFAULT_ENCODINGS[type_name] + attributes[:encoding] = encoding if encoding + + formatted_value = FORMATTING[type_name] && !value.nil? ? + FORMATTING[type_name].call(value) : value + + options[:builder].tag!(key, formatted_value, attributes) + end + end + + def rename_key(key, options = {}) + camelize = options[:camelize] + dasherize = !options.has_key?(:dasherize) || options[:dasherize] + if camelize + key = true == camelize ? key.camelize : key.camelize(camelize) + end + key = _dasherize(key) if dasherize + key + end + + private + def _dasherize(key) + # $2 must be a non-greedy regex for this to work + left, middle, right = /\A(_*)(.*?)(_*)\Z/.match(key.strip)[1, 3] + "#{left}#{middle.tr('_ ', '--')}#{right}" + end + + def _parse_binary(bin, entity) + case entity["encoding"] + when "base64" + ::Base64.decode64(bin) + when "hex", "hexBinary" + _parse_hex_binary(bin) + else + bin + end + end + + def _parse_file(file, entity) + f = StringIO.new(::Base64.decode64(file)) + f.extend(FileLike) + f.original_filename = entity["name"] + f.content_type = entity["content_type"] + f + end + + def _parse_hex_binary(bin) + [bin].pack("H*") + end + + def current_thread_backend + IsolatedExecutionState[:xml_mini_backend] + end + + def current_thread_backend=(name) + IsolatedExecutionState[:xml_mini_backend] = name && cast_backend_name_to_module(name) + end + + def cast_backend_name_to_module(name) + if name.is_a?(Module) + name + else + require "active_support/xml_mini/#{name.downcase}" + ActiveSupport.const_get("XmlMini_#{name}") + end + end + end + + XmlMini.backend = "REXML" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini/jdom.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini/jdom.rb new file mode 100644 index 00000000..f1bc0fe7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini/jdom.rb @@ -0,0 +1,175 @@ +# frozen_string_literal: true + +raise "JRuby is required to use the JDOM backend for XmlMini" unless RUBY_PLATFORM.include?("java") + +require "jruby" +include Java + +require "active_support/core_ext/object/blank" + +java_import javax.xml.parsers.DocumentBuilder unless defined? DocumentBuilder +java_import javax.xml.parsers.DocumentBuilderFactory unless defined? DocumentBuilderFactory +java_import java.io.StringReader unless defined? StringReader +java_import org.xml.sax.InputSource unless defined? InputSource +java_import org.xml.sax.Attributes unless defined? Attributes +java_import org.w3c.dom.Node unless defined? Node + +module ActiveSupport + module XmlMini_JDOM # :nodoc: + extend self + + CONTENT_KEY = "__content__" + + # Parse an XML Document string or IO into a simple hash using Java's jdom. + # data:: + # XML Document string or IO to parse + def parse(data) + if data.respond_to?(:read) + data = data.read + end + + if data.blank? + {} + else + @dbf = DocumentBuilderFactory.new_instance + # secure processing of java xml + # https://archive.is/9xcQQ + @dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false) + @dbf.setFeature("http://xml.org/sax/features/external-general-entities", false) + @dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false) + @dbf.setFeature(javax.xml.XMLConstants::FEATURE_SECURE_PROCESSING, true) + xml_string_reader = StringReader.new(data) + xml_input_source = InputSource.new(xml_string_reader) + doc = @dbf.new_document_builder.parse(xml_input_source) + merge_element!({ CONTENT_KEY => "" }, doc.document_element, XmlMini.depth) + end + end + + private + # Convert an XML element and merge into the hash + # + # hash:: + # Hash to merge the converted element into. + # element:: + # XML element to merge into hash + def merge_element!(hash, element, depth) + raise "Document too deep!" if depth == 0 + delete_empty(hash) + merge!(hash, element.tag_name, collapse(element, depth)) + end + + def delete_empty(hash) + hash.delete(CONTENT_KEY) if hash[CONTENT_KEY] == "" + end + + # Actually converts an XML document element into a data structure. + # + # element:: + # The document element to be collapsed. + def collapse(element, depth) + hash = get_attributes(element) + + child_nodes = element.child_nodes + if child_nodes.length > 0 + (0...child_nodes.length).each do |i| + child = child_nodes.item(i) + merge_element!(hash, child, depth - 1) unless child.node_type == Node::TEXT_NODE + end + merge_texts!(hash, element) unless empty_content?(element) + hash + else + merge_texts!(hash, element) + end + end + + # Merge all the texts of an element into the hash + # + # hash:: + # Hash to add the converted element to. + # element:: + # XML element whose texts are to me merged into the hash + def merge_texts!(hash, element) + delete_empty(hash) + text_children = texts(element) + if text_children.join.empty? + hash + else + # must use value to prevent double-escaping + merge!(hash, CONTENT_KEY, text_children.join) + end + end + + # Adds a new key/value pair to an existing Hash. If the key to be added + # already exists and the existing value associated with key is not + # an Array, it will be wrapped in an Array. Then the new value is + # appended to that Array. + # + # hash:: + # Hash to add key/value pair to. + # key:: + # Key to be added. + # value:: + # Value to be associated with key. + def merge!(hash, key, value) + if hash.has_key?(key) + if hash[key].instance_of?(Array) + hash[key] << value + else + hash[key] = [hash[key], value] + end + elsif value.instance_of?(Array) + hash[key] = [value] + else + hash[key] = value + end + hash + end + + # Converts the attributes array of an XML element into a hash. + # Returns an empty Hash if node has no attributes. + # + # element:: + # XML element to extract attributes from. + def get_attributes(element) + attribute_hash = {} + attributes = element.attributes + (0...attributes.length).each do |i| + attribute_hash[CONTENT_KEY] ||= "" + attribute_hash[attributes.item(i).name] = attributes.item(i).value + end + attribute_hash + end + + # Determines if a document element has text content + # + # element:: + # XML element to be checked. + def texts(element) + texts = [] + child_nodes = element.child_nodes + (0...child_nodes.length).each do |i| + item = child_nodes.item(i) + if item.node_type == Node::TEXT_NODE + texts << item.get_data + end + end + texts + end + + # Determines if a document element has text content + # + # element:: + # XML element to be checked. + def empty_content?(element) + text = +"" + child_nodes = element.child_nodes + (0...child_nodes.length).each do |i| + item = child_nodes.item(i) + if item.node_type == Node::TEXT_NODE + text << item.get_data.strip + end + end + text.strip.length == 0 + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini/libxml.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini/libxml.rb new file mode 100644 index 00000000..65976a2b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini/libxml.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require "libxml" +require "active_support/core_ext/object/blank" +require "stringio" + +module ActiveSupport + module XmlMini_LibXML # :nodoc: + extend self + + # Parse an XML Document string or IO into a simple hash using libxml. + # data:: + # XML Document string or IO to parse + def parse(data) + if !data.respond_to?(:read) + data = StringIO.new(data || "") + end + + if data.eof? + {} + else + LibXML::XML::Parser.io(data).parse.to_hash + end + end + end +end + +module LibXML # :nodoc: + module Conversions # :nodoc: + module Document # :nodoc: + def to_hash + root.to_hash + end + end + + module Node # :nodoc: + CONTENT_ROOT = "__content__" + + # Convert XML document to hash. + # + # hash:: + # Hash to merge the converted element into. + def to_hash(hash = {}) + node_hash = {} + + # Insert node hash into parent hash correctly. + case hash[name] + when Array then hash[name] << node_hash + when Hash then hash[name] = [hash[name], node_hash] + when nil then hash[name] = node_hash + end + + # Handle child elements + each_child do |c| + if c.element? + c.to_hash(node_hash) + elsif c.text? || c.cdata? + node_hash[CONTENT_ROOT] ||= +"" + node_hash[CONTENT_ROOT] << c.content + end + end + + # Remove content node if it is blank + if node_hash.length > 1 && node_hash[CONTENT_ROOT].blank? + node_hash.delete(CONTENT_ROOT) + end + + # Handle attributes + each_attr { |a| node_hash[a.name] = a.value } + + hash + end + end + end +end + +# :enddoc: + +LibXML::XML::Document.include(LibXML::Conversions::Document) +LibXML::XML::Node.include(LibXML::Conversions::Node) diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini/libxmlsax.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini/libxmlsax.rb new file mode 100644 index 00000000..33d594c4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini/libxmlsax.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require "libxml" +require "active_support/core_ext/object/blank" +require "stringio" + +module ActiveSupport + module XmlMini_LibXMLSAX # :nodoc: + extend self + + # Class that will build the hash while the XML document + # is being parsed using SAX events. + class HashBuilder + include LibXML::XML::SaxParser::Callbacks + + CONTENT_KEY = "__content__" + HASH_SIZE_KEY = "__hash_size__" + + attr_reader :hash + + def current_hash + @hash_stack.last + end + + def on_start_document + @hash = { CONTENT_KEY => +"" } + @hash_stack = [@hash] + end + + def on_end_document + @hash = @hash_stack.pop + @hash.delete(CONTENT_KEY) + end + + def on_start_element(name, attrs = {}) + new_hash = { CONTENT_KEY => +"" }.merge!(attrs) + new_hash[HASH_SIZE_KEY] = new_hash.size + 1 + + case current_hash[name] + when Array then current_hash[name] << new_hash + when Hash then current_hash[name] = [current_hash[name], new_hash] + when nil then current_hash[name] = new_hash + end + + @hash_stack.push(new_hash) + end + + def on_end_element(name) + if current_hash.length > current_hash.delete(HASH_SIZE_KEY) && current_hash[CONTENT_KEY].blank? || current_hash[CONTENT_KEY] == "" + current_hash.delete(CONTENT_KEY) + end + @hash_stack.pop + end + + def on_characters(string) + current_hash[CONTENT_KEY] << string + end + + alias_method :on_cdata_block, :on_characters + end + + attr_accessor :document_class + self.document_class = HashBuilder + + def parse(data) + if !data.respond_to?(:read) + data = StringIO.new(data || "") + end + + if data.eof? + {} + else + LibXML::XML::Error.set_handler(&LibXML::XML::Error::QUIET_HANDLER) + parser = LibXML::XML::SaxParser.io(data) + document = document_class.new + + parser.callbacks = document + parser.parse + document.hash + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini/nokogiri.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini/nokogiri.rb new file mode 100644 index 00000000..3d9cf342 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini/nokogiri.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +begin + require "nokogiri" +rescue LoadError => e + warn "You don't have nokogiri installed in your application. Please add it to your Gemfile and run bundle install" + raise e +end +require "active_support/core_ext/object/blank" +require "stringio" + +module ActiveSupport + module XmlMini_Nokogiri # :nodoc: + extend self + + # Parse an XML Document string or IO into a simple hash using libxml / nokogiri. + # data:: + # XML Document string or IO to parse + def parse(data) + if !data.respond_to?(:read) + data = StringIO.new(data || "") + end + + if data.eof? + {} + else + doc = Nokogiri::XML(data) + raise doc.errors.first if doc.errors.length > 0 + doc.to_hash + end + end + + module Conversions # :nodoc: + module Document # :nodoc: + def to_hash + root.to_hash + end + end + + module Node # :nodoc: + CONTENT_ROOT = "__content__" + + # Convert XML document to hash. + # + # hash:: + # Hash to merge the converted element into. + def to_hash(hash = {}) + node_hash = {} + + # Insert node hash into parent hash correctly. + case hash[name] + when Array then hash[name] << node_hash + when Hash then hash[name] = [hash[name], node_hash] + when nil then hash[name] = node_hash + end + + # Handle child elements + children.each do |c| + if c.element? + c.to_hash(node_hash) + elsif c.text? || c.cdata? + node_hash[CONTENT_ROOT] ||= +"" + node_hash[CONTENT_ROOT] << c.content + end + end + + # Remove content node if it is blank and there are child tags + if node_hash.length > 1 && node_hash[CONTENT_ROOT].blank? + node_hash.delete(CONTENT_ROOT) + end + + # Handle attributes + attribute_nodes.each { |a| node_hash[a.node_name] = a.value } + + hash + end + end + end + + Nokogiri::XML::Document.include(Conversions::Document) + Nokogiri::XML::Node.include(Conversions::Node) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini/nokogirisax.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini/nokogirisax.rb new file mode 100644 index 00000000..9316f92b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini/nokogirisax.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +begin + require "nokogiri" +rescue LoadError => e + warn "You don't have nokogiri installed in your application. Please add it to your Gemfile and run bundle install" + raise e +end +require "active_support/core_ext/object/blank" +require "stringio" + +module ActiveSupport + module XmlMini_NokogiriSAX # :nodoc: + extend self + + # Class that will build the hash while the XML document + # is being parsed using SAX events. + class HashBuilder < Nokogiri::XML::SAX::Document + CONTENT_KEY = "__content__" + HASH_SIZE_KEY = "__hash_size__" + + attr_reader :hash + + def current_hash + @hash_stack.last + end + + def start_document + @hash = {} + @hash_stack = [@hash] + end + + def end_document + raise "Parse stack not empty!" if @hash_stack.size > 1 + end + + def error(error_message) + raise error_message + end + + def start_element(name, attrs = []) + new_hash = { CONTENT_KEY => +"" }.merge!(Hash[attrs]) + new_hash[HASH_SIZE_KEY] = new_hash.size + 1 + + case current_hash[name] + when Array then current_hash[name] << new_hash + when Hash then current_hash[name] = [current_hash[name], new_hash] + when nil then current_hash[name] = new_hash + end + + @hash_stack.push(new_hash) + end + + def end_element(name) + if current_hash.length > current_hash.delete(HASH_SIZE_KEY) && current_hash[CONTENT_KEY].blank? || current_hash[CONTENT_KEY] == "" + current_hash.delete(CONTENT_KEY) + end + @hash_stack.pop + end + + def characters(string) + current_hash[CONTENT_KEY] << string + end + + alias_method :cdata_block, :characters + end + + attr_accessor :document_class + self.document_class = HashBuilder + + def parse(data) + if !data.respond_to?(:read) + data = StringIO.new(data || "") + end + + if data.eof? + {} + else + document = document_class.new + parser = Nokogiri::XML::SAX::Parser.new(document) + parser.parse(data) + document.hash + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini/rexml.rb b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini/rexml.rb new file mode 100644 index 00000000..393749ab --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/activesupport-8.0.2/lib/active_support/xml_mini/rexml.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +require "active_support/core_ext/kernel/reporting" +require "active_support/core_ext/object/blank" +require "stringio" + +module ActiveSupport + module XmlMini_REXML # :nodoc: + extend self + + CONTENT_KEY = "__content__" + + # Parse an XML Document string or IO into a simple hash. + # + # Same as XmlSimple::xml_in but doesn't shoot itself in the foot, + # and uses the defaults from Active Support. + # + # data:: + # XML Document string or IO to parse + def parse(data) + if !data.respond_to?(:read) + data = StringIO.new(data || "") + end + + if data.eof? + {} + else + require_rexml unless defined?(REXML::Document) + doc = REXML::Document.new(data) + + if doc.root + merge_element!({}, doc.root, XmlMini.depth) + else + raise REXML::ParseException, + "The document #{doc.to_s.inspect} does not have a valid root" + end + end + end + + private + def require_rexml + silence_warnings { require "rexml/document" } + rescue LoadError => e + warn "You don't have rexml installed in your application. Please add it to your Gemfile and run bundle install" + raise e + end + + # Convert an XML element and merge into the hash + # + # hash:: + # Hash to merge the converted element into. + # element:: + # XML element to merge into hash + def merge_element!(hash, element, depth) + raise REXML::ParseException, "The document is too deep" if depth == 0 + merge!(hash, element.name, collapse(element, depth)) + end + + # Actually converts an XML document element into a data structure. + # + # element:: + # The document element to be collapsed. + def collapse(element, depth) + hash = get_attributes(element) + + if element.has_elements? + element.each_element { |child| merge_element!(hash, child, depth - 1) } + merge_texts!(hash, element) unless empty_content?(element) + hash + else + merge_texts!(hash, element) + end + end + + # Merge all the texts of an element into the hash + # + # hash:: + # Hash to add the converted element to. + # element:: + # XML element whose texts are to me merged into the hash + def merge_texts!(hash, element) + unless element.has_text? + hash + else + # must use value to prevent double-escaping + texts = +"" + element.texts.each { |t| texts << t.value } + merge!(hash, CONTENT_KEY, texts) + end + end + + # Adds a new key/value pair to an existing Hash. If the key to be added + # already exists and the existing value associated with key is not + # an Array, it will be wrapped in an Array. Then the new value is + # appended to that Array. + # + # hash:: + # Hash to add key/value pair to. + # key:: + # Key to be added. + # value:: + # Value to be associated with key. + def merge!(hash, key, value) + if hash.has_key?(key) + if hash[key].instance_of?(Array) + hash[key] << value + else + hash[key] = [hash[key], value] + end + elsif value.instance_of?(Array) + hash[key] = [value] + else + hash[key] = value + end + hash + end + + # Converts the attributes array of an XML element into a hash. + # Returns an empty Hash if node has no attributes. + # + # element:: + # XML element to extract attributes from. + def get_attributes(element) + attributes = {} + element.attributes.each { |n, v| attributes[n] = v } + attributes + end + + # Determines if a document element has text content + # + # element:: + # XML element to be checked. + def empty_content?(element) + element.texts.join.blank? + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/base64-0.3.0/BSDL b/vendor/bundle/ruby/3.2.0/gems/base64-0.3.0/BSDL new file mode 100644 index 00000000..66d93598 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/base64-0.3.0/BSDL @@ -0,0 +1,22 @@ +Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/vendor/bundle/ruby/3.2.0/gems/base64-0.3.0/COPYING b/vendor/bundle/ruby/3.2.0/gems/base64-0.3.0/COPYING new file mode 100644 index 00000000..48e5a96d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/base64-0.3.0/COPYING @@ -0,0 +1,56 @@ +Ruby is copyrighted free software by Yukihiro Matsumoto . +You can redistribute it and/or modify it under either the terms of the +2-clause BSDL (see the file BSDL), or the conditions below: + +1. You may make and give away verbatim copies of the source form of the + software without restriction, provided that you duplicate all of the + original copyright notices and associated disclaimers. + +2. You may modify your copy of the software in any way, provided that + you do at least ONE of the following: + + a. place your modifications in the Public Domain or otherwise + make them Freely Available, such as by posting said + modifications to Usenet or an equivalent medium, or by allowing + the author to include your modifications in the software. + + b. use the modified software only within your corporation or + organization. + + c. give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d. make other distribution arrangements with the author. + +3. You may distribute the software in object code or binary form, + provided that you do at least ONE of the following: + + a. distribute the binaries and library files of the software, + together with instructions (in the manual page or equivalent) + on where to get the original distribution. + + b. accompany the distribution with the machine-readable source of + the software. + + c. give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d. make other distribution arrangements with the author. + +4. You may modify and include the part of the software into any other + software (possibly commercial). But some files in the distribution + are not written by the author, so that they are not under these terms. + + For the list of those files and their copying conditions, see the + file LEGAL. + +5. The scripts and library files supplied as input to or produced as + output from the software do not automatically fall under the + copyright of the software, but belong to whomever generated them, + and may be sold commercially, and may be aggregated with this + software. + +6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. diff --git a/vendor/bundle/ruby/3.2.0/gems/base64-0.3.0/LEGAL b/vendor/bundle/ruby/3.2.0/gems/base64-0.3.0/LEGAL new file mode 100644 index 00000000..f2d80147 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/base64-0.3.0/LEGAL @@ -0,0 +1,60 @@ +# -*- rdoc -*- + += LEGAL NOTICE INFORMATION +-------------------------- + +All the files in this distribution are covered under either the Ruby's +license (see the file COPYING) or public-domain except some files +mentioned below. + +== MIT License +>>> + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +== Old-style BSD license +>>> + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + IMPORTANT NOTE:: + + From ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change + paragraph 3 above is now null and void. diff --git a/vendor/bundle/ruby/3.2.0/gems/base64-0.3.0/README.md b/vendor/bundle/ruby/3.2.0/gems/base64-0.3.0/README.md new file mode 100644 index 00000000..a29c58e7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/base64-0.3.0/README.md @@ -0,0 +1,48 @@ +# Base64 + +The Base64 module provides for the encoding (`#encode64`, `#strict_encode64`, +`#urlsafe_encode64`) and decoding (`#decode64`, `#strict_decode64`, +`#urlsafe_decode64`) of binary data using a Base64 representation. + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'base64' +``` + +And then execute: + + $ bundle install + +Or install it yourself as: + + $ gem install base64 + +## Usage + +A simple encoding and decoding. + +```ruby +require "base64" + +enc = Base64.encode64('Send reinforcements') + # -> "U2VuZCByZWluZm9yY2VtZW50cw==\n" +plain = Base64.decode64(enc) + # -> "Send reinforcements" +``` + +The purpose of using base64 to encode data is that it translates any +binary data into purely printable characters. + +## Development + +After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. + +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/base64. + diff --git a/vendor/bundle/ruby/3.2.0/gems/base64-0.3.0/lib/base64.rb b/vendor/bundle/ruby/3.2.0/gems/base64-0.3.0/lib/base64.rb new file mode 100644 index 00000000..8c0145d2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/base64-0.3.0/lib/base64.rb @@ -0,0 +1,381 @@ +# frozen_string_literal: true +# +# \Module \Base64 provides methods for: +# +# - \Encoding a binary string (containing non-ASCII characters) +# as a string of printable ASCII characters. +# - Decoding such an encoded string. +# +# \Base64 is commonly used in contexts where binary data +# is not allowed or supported: +# +# - Images in HTML or CSS files, or in URLs. +# - Email attachments. +# +# A \Base64-encoded string is about one-third larger that its source. +# See the {Wikipedia article}[https://en.wikipedia.org/wiki/Base64] +# for more information. +# +# This module provides three pairs of encode/decode methods. +# Your choices among these methods should depend on: +# +# - Which character set is to be used for encoding and decoding. +# - Whether "padding" is to be used. +# - Whether encoded strings are to contain newlines. +# +# Note: Examples on this page assume that the including program has executed: +# +# require 'base64' +# +# == \Encoding Character Sets +# +# A \Base64-encoded string consists only of characters from a 64-character set: +# +# - ('A'..'Z'). +# - ('a'..'z'). +# - ('0'..'9'). +# - =, the 'padding' character. +# - Either: +# - %w[+ /]: +# {RFC-2045-compliant}[https://datatracker.ietf.org/doc/html/rfc2045]; +# _not_ safe for URLs. +# - %w[- _]: +# {RFC-4648-compliant}[https://datatracker.ietf.org/doc/html/rfc4648]; +# safe for URLs. +# +# If you are working with \Base64-encoded strings that will come from +# or be put into URLs, you should choose this encoder-decoder pair +# of RFC-4648-compliant methods: +# +# - Base64.urlsafe_encode64 and Base64.urlsafe_decode64. +# +# Otherwise, you may choose any of the pairs in this module, +# including the pair above, or the RFC-2045-compliant pairs: +# +# - Base64.encode64 and Base64.decode64. +# - Base64.strict_encode64 and Base64.strict_decode64. +# +# == Padding +# +# \Base64-encoding changes a triplet of input bytes +# into a quartet of output characters. +# +# Padding in Encode Methods +# +# Padding -- extending an encoded string with zero, one, or two trailing +# = characters -- is performed by methods Base64.encode64, +# Base64.strict_encode64, and, by default, Base64.urlsafe_encode64: +# +# Base64.encode64('s') # => "cw==\n" +# Base64.strict_encode64('s') # => "cw==" +# Base64.urlsafe_encode64('s') # => "cw==" +# Base64.urlsafe_encode64('s', padding: false) # => "cw" +# +# When padding is performed, the encoded string is always of length 4n, +# where +n+ is a non-negative integer: +# +# - Input bytes of length 3n generate unpadded output characters +# of length 4n: +# +# # n = 1: 3 bytes => 4 characters. +# Base64.strict_encode64('123') # => "MDEy" +# # n = 2: 6 bytes => 8 characters. +# Base64.strict_encode64('123456') # => "MDEyMzQ1" +# +# - Input bytes of length 3n+1 generate padded output characters +# of length 4(n+1), with two padding characters at the end: +# +# # n = 1: 4 bytes => 8 characters. +# Base64.strict_encode64('1234') # => "MDEyMw==" +# # n = 2: 7 bytes => 12 characters. +# Base64.strict_encode64('1234567') # => "MDEyMzQ1Ng==" +# +# - Input bytes of length 3n+2 generate padded output characters +# of length 4(n+1), with one padding character at the end: +# +# # n = 1: 5 bytes => 8 characters. +# Base64.strict_encode64('12345') # => "MDEyMzQ=" +# # n = 2: 8 bytes => 12 characters. +# Base64.strict_encode64('12345678') # => "MDEyMzQ1Njc=" +# +# When padding is suppressed, for a positive integer n: +# +# - Input bytes of length 3n generate unpadded output characters +# of length 4n: +# +# # n = 1: 3 bytes => 4 characters. +# Base64.urlsafe_encode64('123', padding: false) # => "MDEy" +# # n = 2: 6 bytes => 8 characters. +# Base64.urlsafe_encode64('123456', padding: false) # => "MDEyMzQ1" +# +# - Input bytes of length 3n+1 generate unpadded output characters +# of length 4n+2, with two padding characters at the end: +# +# # n = 1: 4 bytes => 6 characters. +# Base64.urlsafe_encode64('1234', padding: false) # => "MDEyMw" +# # n = 2: 7 bytes => 10 characters. +# Base64.urlsafe_encode64('1234567', padding: false) # => "MDEyMzQ1Ng" +# +# - Input bytes of length 3n+2 generate unpadded output characters +# of length 4n+3, with one padding character at the end: +# +# # n = 1: 5 bytes => 7 characters. +# Base64.urlsafe_encode64('12345', padding: false) # => "MDEyMzQ" +# # m = 2: 8 bytes => 11 characters. +# Base64.urlsafe_encode64('12345678', padding: false) # => "MDEyMzQ1Njc" +# +# Padding in Decode Methods +# +# All of the \Base64 decode methods support (but do not require) padding. +# +# \Method Base64.decode64 does not check the size of the padding: +# +# Base64.decode64("MDEyMzQ1Njc") # => "01234567" +# Base64.decode64("MDEyMzQ1Njc=") # => "01234567" +# Base64.decode64("MDEyMzQ1Njc==") # => "01234567" +# +# \Method Base64.strict_decode64 strictly enforces padding size: +# +# Base64.strict_decode64("MDEyMzQ1Njc") # Raises ArgumentError +# Base64.strict_decode64("MDEyMzQ1Njc=") # => "01234567" +# Base64.strict_decode64("MDEyMzQ1Njc==") # Raises ArgumentError +# +# \Method Base64.urlsafe_decode64 allows padding in the encoded string, +# which if present, must be correct: +# see {Padding}[Base64.html#module-Base64-label-Padding], above: +# +# Base64.urlsafe_decode64("MDEyMzQ1Njc") # => "01234567" +# Base64.urlsafe_decode64("MDEyMzQ1Njc=") # => "01234567" +# Base64.urlsafe_decode64("MDEyMzQ1Njc==") # Raises ArgumentError. +# +# == Newlines +# +# An encoded string returned by Base64.encode64 or Base64.urlsafe_encode64 +# has an embedded newline character +# after each 60-character sequence, and, if non-empty, at the end: +# +# # No newline if empty. +# encoded = Base64.encode64("\x00" * 0) +# encoded.index("\n") # => nil +# +# # Newline at end of short output. +# encoded = Base64.encode64("\x00" * 1) +# encoded.size # => 4 +# encoded.index("\n") # => 4 +# +# # Newline at end of longer output. +# encoded = Base64.encode64("\x00" * 45) +# encoded.size # => 60 +# encoded.index("\n") # => 60 +# +# # Newlines embedded and at end of still longer output. +# encoded = Base64.encode64("\x00" * 46) +# encoded.size # => 65 +# encoded.rindex("\n") # => 65 +# encoded.split("\n").map {|s| s.size } # => [60, 4] +# +# The string to be encoded may itself contain newlines, +# which are encoded as \Base64: +# +# # Base64.encode64("\n\n\n") # => "CgoK\n" +# s = "This is line 1\nThis is line 2\n" +# Base64.encode64(s) # => "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK\n" +# +module Base64 + + VERSION = "0.3.0" + + module_function + + # :call-seq: + # Base64.encode64(string) -> encoded_string + # + # Returns a string containing the RFC-2045-compliant \Base64-encoding of +string+. + # + # Per RFC 2045, the returned string may contain the URL-unsafe characters + # + or /; + # see {Encoding Character Set}[Base64.html#module-Base64-label-Encoding+Character+Sets] above: + # + # Base64.encode64("\xFB\xEF\xBE") # => "++++\n" + # Base64.encode64("\xFF\xFF\xFF") # => "////\n" + # + # The returned string may include padding; + # see {Padding}[Base64.html#module-Base64-label-Padding] above. + # + # Base64.encode64('*') # => "Kg==\n" + # + # The returned string ends with a newline character, and if sufficiently long + # will have one or more embedded newline characters; + # see {Newlines}[Base64.html#module-Base64-label-Newlines] above: + # + # Base64.encode64('*') # => "Kg==\n" + # Base64.encode64('*' * 46) + # # => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq\nKg==\n" + # + # The string to be encoded may itself contain newlines, + # which will be encoded as ordinary \Base64: + # + # Base64.encode64("\n\n\n") # => "CgoK\n" + # s = "This is line 1\nThis is line 2\n" + # Base64.encode64(s) # => "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK\n" + # + def encode64(bin) + [bin].pack("m") + end + + # :call-seq: + # Base64.decode(encoded_string) -> decoded_string + # + # Returns a string containing the decoding of an RFC-2045-compliant + # \Base64-encoded string +encoded_string+: + # + # s = "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK\n" + # Base64.decode64(s) # => "This is line 1\nThis is line 2\n" + # + # Non-\Base64 characters in +encoded_string+ are ignored; + # see {Encoding Character Set}[Base64.html#module-Base64-label-Encoding+Character+Sets] above: + # these include newline characters and characters - and /: + # + # Base64.decode64("\x00\n-_") # => "" + # + # Padding in +encoded_string+ (even if incorrect) is ignored: + # + # Base64.decode64("MDEyMzQ1Njc") # => "01234567" + # Base64.decode64("MDEyMzQ1Njc=") # => "01234567" + # Base64.decode64("MDEyMzQ1Njc==") # => "01234567" + # + def decode64(str) + str.unpack1("m") + end + + # :call-seq: + # Base64.strict_encode64(string) -> encoded_string + # + # Returns a string containing the RFC-2045-compliant \Base64-encoding of +string+. + # + # Per RFC 2045, the returned string may contain the URL-unsafe characters + # + or /; + # see {Encoding Character Set}[Base64.html#module-Base64-label-Encoding+Character+Sets] above: + # + # Base64.strict_encode64("\xFB\xEF\xBE") # => "++++\n" + # Base64.strict_encode64("\xFF\xFF\xFF") # => "////\n" + # + # The returned string may include padding; + # see {Padding}[Base64.html#module-Base64-label-Padding] above. + # + # Base64.strict_encode64('*') # => "Kg==\n" + # + # The returned string will have no newline characters, regardless of its length; + # see {Newlines}[Base64.html#module-Base64-label-Newlines] above: + # + # Base64.strict_encode64('*') # => "Kg==" + # Base64.strict_encode64('*' * 46) + # # => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg==" + # + # The string to be encoded may itself contain newlines, + # which will be encoded as ordinary \Base64: + # + # Base64.strict_encode64("\n\n\n") # => "CgoK" + # s = "This is line 1\nThis is line 2\n" + # Base64.strict_encode64(s) # => "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK" + # + def strict_encode64(bin) + [bin].pack("m0") + end + + # :call-seq: + # Base64.strict_decode64(encoded_string) -> decoded_string + # + # Returns a string containing the decoding of an RFC-2045-compliant + # \Base64-encoded string +encoded_string+: + # + # s = "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK" + # Base64.strict_decode64(s) # => "This is line 1\nThis is line 2\n" + # + # Non-\Base64 characters in +encoded_string+ are not allowed; + # see {Encoding Character Set}[Base64.html#module-Base64-label-Encoding+Character+Sets] above: + # these include newline characters and characters - and /: + # + # Base64.strict_decode64("\n") # Raises ArgumentError + # Base64.strict_decode64('-') # Raises ArgumentError + # Base64.strict_decode64('_') # Raises ArgumentError + # + # Padding in +encoded_string+, if present, must be correct: + # + # Base64.strict_decode64("MDEyMzQ1Njc") # Raises ArgumentError + # Base64.strict_decode64("MDEyMzQ1Njc=") # => "01234567" + # Base64.strict_decode64("MDEyMzQ1Njc==") # Raises ArgumentError + # + def strict_decode64(str) + str.unpack1("m0") + end + + # :call-seq: + # Base64.urlsafe_encode64(string) -> encoded_string + # + # Returns the RFC-4648-compliant \Base64-encoding of +string+. + # + # Per RFC 4648, the returned string will not contain the URL-unsafe characters + # + or /, + # but instead may contain the URL-safe characters + # - and _; + # see {Encoding Character Set}[Base64.html#module-Base64-label-Encoding+Character+Sets] above: + # + # Base64.urlsafe_encode64("\xFB\xEF\xBE") # => "----" + # Base64.urlsafe_encode64("\xFF\xFF\xFF") # => "____" + # + # By default, the returned string may have padding; + # see {Padding}[Base64.html#module-Base64-label-Padding], above: + # + # Base64.urlsafe_encode64('*') # => "Kg==" + # + # Optionally, you can suppress padding: + # + # Base64.urlsafe_encode64('*', padding: false) # => "Kg" + # + # The returned string will have no newline characters, regardless of its length; + # see {Newlines}[Base64.html#module-Base64-label-Newlines] above: + # + # Base64.urlsafe_encode64('*') # => "Kg==" + # Base64.urlsafe_encode64('*' * 46) + # # => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg==" + # + def urlsafe_encode64(bin, padding: true) + str = strict_encode64(bin) + str.chomp!("==") or str.chomp!("=") unless padding + str.tr!("+/", "-_") + str + end + + # :call-seq: + # Base64.urlsafe_decode64(encoded_string) -> decoded_string + # + # Returns the decoding of an RFC-4648-compliant \Base64-encoded string +encoded_string+: + # + # +encoded_string+ may not contain non-Base64 characters; + # see {Encoding Character Set}[Base64.html#module-Base64-label-Encoding+Character+Sets] above: + # + # Base64.urlsafe_decode64('+') # Raises ArgumentError. + # Base64.urlsafe_decode64('/') # Raises ArgumentError. + # Base64.urlsafe_decode64("\n") # Raises ArgumentError. + # + # Padding in +encoded_string+, if present, must be correct: + # see {Padding}[Base64.html#module-Base64-label-Padding], above: + # + # Base64.urlsafe_decode64("MDEyMzQ1Njc") # => "01234567" + # Base64.urlsafe_decode64("MDEyMzQ1Njc=") # => "01234567" + # Base64.urlsafe_decode64("MDEyMzQ1Njc==") # Raises ArgumentError. + # + def urlsafe_decode64(str) + # NOTE: RFC 4648 does say nothing about unpadded input, but says that + # "the excess pad characters MAY also be ignored", so it is inferred that + # unpadded input is also acceptable. + if !str.end_with?("=") && str.length % 4 != 0 + str = str.ljust((str.length + 3) & ~3, "=") + str.tr!("-_", "+/") + else + str = str.tr("-_", "+/") + end + strict_decode64(str) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/base64-0.3.0/sig/base64.rbs b/vendor/bundle/ruby/3.2.0/gems/base64-0.3.0/sig/base64.rbs new file mode 100644 index 00000000..147e874c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/base64-0.3.0/sig/base64.rbs @@ -0,0 +1,355 @@ +# +# Module Base64 provides methods for: +# +# * Encoding a binary string (containing non-ASCII characters) as a string of +# printable ASCII characters. +# * Decoding such an encoded string. +# +# Base64 is commonly used in contexts where binary data is not allowed or +# supported: +# +# * Images in HTML or CSS files, or in URLs. +# * Email attachments. +# +# A Base64-encoded string is about one-third larger that its source. See the +# [Wikipedia article](https://en.wikipedia.org/wiki/Base64) for more +# information. +# +# This module provides three pairs of encode/decode methods. Your choices among +# these methods should depend on: +# +# * Which character set is to be used for encoding and decoding. +# * Whether "padding" is to be used. +# * Whether encoded strings are to contain newlines. +# +# Note: Examples on this page assume that the including program has executed: +# +# require 'base64' +# +# ## Encoding Character Sets +# +# A Base64-encoded string consists only of characters from a 64-character set: +# +# * `('A'..'Z')`. +# * `('a'..'z')`. +# * `('0'..'9')`. +# * `=`, the 'padding' character. +# * Either: +# * `%w[+ /]`: +# [RFC-2045-compliant](https://datatracker.ietf.org/doc/html/rfc2045); +# *not* safe for URLs. +# * `%w[- _]`: +# [RFC-4648-compliant](https://datatracker.ietf.org/doc/html/rfc4648); +# safe for URLs. +# +# If you are working with Base64-encoded strings that will come from or be put +# into URLs, you should choose this encoder-decoder pair of RFC-4648-compliant +# methods: +# +# * Base64.urlsafe_encode64 and Base64.urlsafe_decode64. +# +# Otherwise, you may choose any of the pairs in this module, including the pair +# above, or the RFC-2045-compliant pairs: +# +# * Base64.encode64 and Base64.decode64. +# * Base64.strict_encode64 and Base64.strict_decode64. +# +# ## Padding +# +# Base64-encoding changes a triplet of input bytes into a quartet of output +# characters. +# +# **Padding in Encode Methods** +# +# Padding -- extending an encoded string with zero, one, or two trailing `=` +# characters -- is performed by methods Base64.encode64, Base64.strict_encode64, +# and, by default, Base64.urlsafe_encode64: +# +# Base64.encode64('s') # => "cw==\n" +# Base64.strict_encode64('s') # => "cw==" +# Base64.urlsafe_encode64('s') # => "cw==" +# Base64.urlsafe_encode64('s', padding: false) # => "cw" +# +# When padding is performed, the encoded string is always of length *4n*, where +# `n` is a non-negative integer: +# +# * Input bytes of length *3n* generate unpadded output characters of length +# *4n*: +# +# # n = 1: 3 bytes => 4 characters. +# Base64.strict_encode64('123') # => "MDEy" +# # n = 2: 6 bytes => 8 characters. +# Base64.strict_encode64('123456') # => "MDEyMzQ1" +# +# * Input bytes of length *3n+1* generate padded output characters of length +# *4(n+1)*, with two padding characters at the end: +# +# # n = 1: 4 bytes => 8 characters. +# Base64.strict_encode64('1234') # => "MDEyMw==" +# # n = 2: 7 bytes => 12 characters. +# Base64.strict_encode64('1234567') # => "MDEyMzQ1Ng==" +# +# * Input bytes of length *3n+2* generate padded output characters of length +# *4(n+1)*, with one padding character at the end: +# +# # n = 1: 5 bytes => 8 characters. +# Base64.strict_encode64('12345') # => "MDEyMzQ=" +# # n = 2: 8 bytes => 12 characters. +# Base64.strict_encode64('12345678') # => "MDEyMzQ1Njc=" +# +# When padding is suppressed, for a positive integer *n*: +# +# * Input bytes of length *3n* generate unpadded output characters of length +# *4n*: +# +# # n = 1: 3 bytes => 4 characters. +# Base64.urlsafe_encode64('123', padding: false) # => "MDEy" +# # n = 2: 6 bytes => 8 characters. +# Base64.urlsafe_encode64('123456', padding: false) # => "MDEyMzQ1" +# +# * Input bytes of length *3n+1* generate unpadded output characters of length +# *4n+2*, with two padding characters at the end: +# +# # n = 1: 4 bytes => 6 characters. +# Base64.urlsafe_encode64('1234', padding: false) # => "MDEyMw" +# # n = 2: 7 bytes => 10 characters. +# Base64.urlsafe_encode64('1234567', padding: false) # => "MDEyMzQ1Ng" +# +# * Input bytes of length *3n+2* generate unpadded output characters of length +# *4n+3*, with one padding character at the end: +# +# # n = 1: 5 bytes => 7 characters. +# Base64.urlsafe_encode64('12345', padding: false) # => "MDEyMzQ" +# # m = 2: 8 bytes => 11 characters. +# Base64.urlsafe_encode64('12345678', padding: false) # => "MDEyMzQ1Njc" +# +# **Padding in Decode Methods** +# +# All of the Base64 decode methods support (but do not require) padding. +# +# Method Base64.decode64 does not check the size of the padding: +# +# Base64.decode64("MDEyMzQ1Njc") # => "01234567" +# Base64.decode64("MDEyMzQ1Njc=") # => "01234567" +# Base64.decode64("MDEyMzQ1Njc==") # => "01234567" +# +# Method Base64.strict_decode64 strictly enforces padding size: +# +# Base64.strict_decode64("MDEyMzQ1Njc") # Raises ArgumentError +# Base64.strict_decode64("MDEyMzQ1Njc=") # => "01234567" +# Base64.strict_decode64("MDEyMzQ1Njc==") # Raises ArgumentError +# +# Method Base64.urlsafe_decode64 allows padding in `str`, which if present, must +# be correct: see [Padding](Base64.html#module-Base64-label-Padding), above: +# +# Base64.urlsafe_decode64("MDEyMzQ1Njc") # => "01234567" +# Base64.urlsafe_decode64("MDEyMzQ1Njc=") # => "01234567" +# Base64.urlsafe_decode64("MDEyMzQ1Njc==") # Raises ArgumentError. +# +# ## Newlines +# +# An encoded string returned by Base64.encode64 or Base64.urlsafe_encode64 has +# an embedded newline character after each 60-character sequence, and, if +# non-empty, at the end: +# +# # No newline if empty. +# encoded = Base64.encode64("\x00" * 0) +# encoded.index("\n") # => nil +# +# # Newline at end of short output. +# encoded = Base64.encode64("\x00" * 1) +# encoded.size # => 4 +# encoded.index("\n") # => 4 +# +# # Newline at end of longer output. +# encoded = Base64.encode64("\x00" * 45) +# encoded.size # => 60 +# encoded.index("\n") # => 60 +# +# # Newlines embedded and at end of still longer output. +# encoded = Base64.encode64("\x00" * 46) +# encoded.size # => 65 +# encoded.rindex("\n") # => 65 +# encoded.split("\n").map {|s| s.size } # => [60, 4] +# +# The string to be encoded may itself contain newlines, which are encoded as +# Base64: +# +# # Base64.encode64("\n\n\n") # => "CgoK\n" +# s = "This is line 1\nThis is line 2\n" +# Base64.encode64(s) # => "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK\n" +# +module Base64 + # + # Returns a string containing the decoding of an RFC-2045-compliant + # Base64-encoded string `str`: + # + # s = "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK\n" + # Base64.decode64(s) # => "This is line 1\nThis is line 2\n" + # + # Non-Base64 characters in `str` are ignored; see [Encoding Character + # Set](Base64.html#module-Base64-label-Encoding+Character+Sets) above: these + # include newline characters and characters `-` and `/`: + # + # Base64.decode64("\x00\n-_") # => "" + # + # Padding in `str` (even if incorrect) is ignored: + # + # Base64.decode64("MDEyMzQ1Njc") # => "01234567" + # Base64.decode64("MDEyMzQ1Njc=") # => "01234567" + # Base64.decode64("MDEyMzQ1Njc==") # => "01234567" + # + def self?.decode64: (String str) -> String + + # + # Returns a string containing the RFC-2045-compliant Base64-encoding of `bin`. + # + # Per RFC 2045, the returned string may contain the URL-unsafe characters `+` or + # `/`; see [Encoding Character + # Set](Base64.html#module-Base64-label-Encoding+Character+Sets) above: + # + # Base64.encode64("\xFB\xEF\xBE") # => "++++\n" + # Base64.encode64("\xFF\xFF\xFF") # => "////\n" + # + # The returned string may include padding; see + # [Padding](Base64.html#module-Base64-label-Padding) above. + # + # Base64.encode64('*') # => "Kg==\n" + # + # The returned string ends with a newline character, and if sufficiently long + # will have one or more embedded newline characters; see + # [Newlines](Base64.html#module-Base64-label-Newlines) above: + # + # Base64.encode64('*') # => "Kg==\n" + # Base64.encode64('*' * 46) + # # => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq\nKg==\n" + # + # The string to be encoded may itself contain newlines, which will be encoded as + # ordinary Base64: + # + # Base64.encode64("\n\n\n") # => "CgoK\n" + # s = "This is line 1\nThis is line 2\n" + # Base64.encode64(s) # => "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK\n" + # + def self?.encode64: (String bin) -> String + + # + # Returns a string containing the decoding of an RFC-2045-compliant + # Base64-encoded string `str`: + # + # s = "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK" + # Base64.strict_decode64(s) # => "This is line 1\nThis is line 2\n" + # + # Non-Base64 characters in `str` not allowed; see [Encoding Character + # Set](Base64.html#module-Base64-label-Encoding+Character+Sets) above: these + # include newline characters and characters `-` and `/`: + # + # Base64.strict_decode64("\n") # Raises ArgumentError + # Base64.strict_decode64('-') # Raises ArgumentError + # Base64.strict_decode64('_') # Raises ArgumentError + # + # Padding in `str`, if present, must be correct: + # + # Base64.strict_decode64("MDEyMzQ1Njc") # Raises ArgumentError + # Base64.strict_decode64("MDEyMzQ1Njc=") # => "01234567" + # Base64.strict_decode64("MDEyMzQ1Njc==") # Raises ArgumentError + # + def self?.strict_decode64: (String str) -> String + + # + # Returns a string containing the RFC-2045-compliant Base64-encoding of `bin`. + # + # Per RFC 2045, the returned string may contain the URL-unsafe characters `+` or + # `/`; see [Encoding Character + # Set](Base64.html#module-Base64-label-Encoding+Character+Sets) above: + # + # Base64.strict_encode64("\xFB\xEF\xBE") # => "++++\n" + # Base64.strict_encode64("\xFF\xFF\xFF") # => "////\n" + # + # The returned string may include padding; see + # [Padding](Base64.html#module-Base64-label-Padding) above. + # + # Base64.strict_encode64('*') # => "Kg==\n" + # + # The returned string will have no newline characters, regardless of its length; + # see [Newlines](Base64.html#module-Base64-label-Newlines) above: + # + # Base64.strict_encode64('*') # => "Kg==" + # Base64.strict_encode64('*' * 46) + # # => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg==" + # + # The string to be encoded may itself contain newlines, which will be encoded as + # ordinary Base64: + # + # Base64.strict_encode64("\n\n\n") # => "CgoK" + # s = "This is line 1\nThis is line 2\n" + # Base64.strict_encode64(s) # => "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK" + # + def self?.strict_encode64: (String bin) -> String + + # + # Returns the decoding of an RFC-4648-compliant Base64-encoded string `str`: + # + # `str` may not contain non-Base64 characters; see [Encoding Character + # Set](Base64.html#module-Base64-label-Encoding+Character+Sets) above: + # + # Base64.urlsafe_decode64('+') # Raises ArgumentError. + # Base64.urlsafe_decode64('/') # Raises ArgumentError. + # Base64.urlsafe_decode64("\n") # Raises ArgumentError. + # + # Padding in `str`, if present, must be correct: see + # [Padding](Base64.html#module-Base64-label-Padding), above: + # + # Base64.urlsafe_decode64("MDEyMzQ1Njc") # => "01234567" + # Base64.urlsafe_decode64("MDEyMzQ1Njc=") # => "01234567" + # Base64.urlsafe_decode64("MDEyMzQ1Njc==") # Raises ArgumentError. + # + def self?.urlsafe_decode64: (String str) -> String + + # + # Returns the RFC-4648-compliant Base64-encoding of `bin`. + # + # Per RFC 4648, the returned string will not contain the URL-unsafe characters + # `+` or `/`, but instead may contain the URL-safe characters `-` and `_`; see + # [Encoding Character + # Set](Base64.html#module-Base64-label-Encoding+Character+Sets) above: + # + # Base64.urlsafe_encode64("\xFB\xEF\xBE") # => "----" + # Base64.urlsafe_encode64("\xFF\xFF\xFF") # => "____" + # + # By default, the returned string may have padding; see + # [Padding](Base64.html#module-Base64-label-Padding), above: + # + # Base64.urlsafe_encode64('*') # => "Kg==" + # + # Optionally, you can suppress padding: + # + # Base64.urlsafe_encode64('*', padding: false) # => "Kg" + # + # The returned string will have no newline characters, regardless of its length; + # see [Newlines](Base64.html#module-Base64-label-Newlines) above: + # + # Base64.urlsafe_encode64('*') # => "Kg==" + # Base64.urlsafe_encode64('*' * 46) + # # => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg==" + # + def self?.urlsafe_encode64: (String bin, ?padding: boolish) -> String +end diff --git a/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/.github/dependabot.yml b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/.github/dependabot.yml new file mode 100644 index 00000000..b18fd293 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'weekly' diff --git a/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/.github/workflows/push_gem.yml b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/.github/workflows/push_gem.yml new file mode 100644 index 00000000..5019826c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/.github/workflows/push_gem.yml @@ -0,0 +1,46 @@ +name: Publish gem to rubygems.org + +on: + push: + tags: + - 'v*' + +permissions: + contents: read + +jobs: + push: + if: github.repository == 'ruby/benchmark' + runs-on: ubuntu-latest + + environment: + name: rubygems.org + url: https://rubygems.org/gems/benchmark + + permissions: + contents: write + id-token: write + + steps: + - name: Harden Runner + uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + with: + egress-policy: audit + + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + + - name: Set up Ruby + uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # v1.190.0 + with: + bundler-cache: true + ruby-version: ruby + + - name: Publish to RubyGems + uses: rubygems/release-gem@a25424ba2ba8b387abc8ef40807c2c85b96cbe32 # v1.1.1 + + - name: Create GitHub release + run: | + tag_name="$(git describe --tags --abbrev=0)" + gh release create "${tag_name}" --verify-tag --generate-notes + env: + GITHUB_TOKEN: ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }} diff --git a/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/.github/workflows/test.yml b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/.github/workflows/test.yml new file mode 100644 index 00000000..74a4e7e0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/.github/workflows/test.yml @@ -0,0 +1,32 @@ +name: test + +on: [push, pull_request] + +jobs: + ruby-versions: + uses: ruby/actions/.github/workflows/ruby_versions.yml@master + with: + min_version: 2.5 + + test: + needs: ruby-versions + name: build (${{ matrix.ruby }} / ${{ matrix.os }}) + strategy: + matrix: + ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} + os: [ ubuntu-latest, macos-latest, windows-latest ] + exclude: + - { os: macos-latest, ruby: 2.5 } + - { os: windows-latest, ruby: truffleruby-head } + - { os: windows-latest, ruby: truffleruby } + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + - name: Install dependencies + run: bundle install + - name: Run test + run: rake test diff --git a/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/.gitignore b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/.gitignore new file mode 100644 index 00000000..4ea57987 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/.gitignore @@ -0,0 +1,9 @@ +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ +Gemfile.lock diff --git a/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/BSDL b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/BSDL new file mode 100644 index 00000000..66d93598 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/BSDL @@ -0,0 +1,22 @@ +Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/COPYING b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/COPYING new file mode 100644 index 00000000..48e5a96d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/COPYING @@ -0,0 +1,56 @@ +Ruby is copyrighted free software by Yukihiro Matsumoto . +You can redistribute it and/or modify it under either the terms of the +2-clause BSDL (see the file BSDL), or the conditions below: + +1. You may make and give away verbatim copies of the source form of the + software without restriction, provided that you duplicate all of the + original copyright notices and associated disclaimers. + +2. You may modify your copy of the software in any way, provided that + you do at least ONE of the following: + + a. place your modifications in the Public Domain or otherwise + make them Freely Available, such as by posting said + modifications to Usenet or an equivalent medium, or by allowing + the author to include your modifications in the software. + + b. use the modified software only within your corporation or + organization. + + c. give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d. make other distribution arrangements with the author. + +3. You may distribute the software in object code or binary form, + provided that you do at least ONE of the following: + + a. distribute the binaries and library files of the software, + together with instructions (in the manual page or equivalent) + on where to get the original distribution. + + b. accompany the distribution with the machine-readable source of + the software. + + c. give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d. make other distribution arrangements with the author. + +4. You may modify and include the part of the software into any other + software (possibly commercial). But some files in the distribution + are not written by the author, so that they are not under these terms. + + For the list of those files and their copying conditions, see the + file LEGAL. + +5. The scripts and library files supplied as input to or produced as + output from the software do not automatically fall under the + copyright of the software, but belong to whomever generated them, + and may be sold commercially, and may be aggregated with this + software. + +6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. diff --git a/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/Gemfile b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/Gemfile new file mode 100644 index 00000000..3dc28835 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/Gemfile @@ -0,0 +1,9 @@ +source "https://rubygems.org" + +gemspec + +group :development do + gem "bundler" + gem "rake" + gem "test-unit" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/README.md b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/README.md new file mode 100644 index 00000000..c5705939 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/README.md @@ -0,0 +1,138 @@ +# Benchmark + +The Benchmark module provides methods for benchmarking Ruby code, giving detailed reports on the time taken for each task. + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'benchmark' +``` + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install benchmark + +## Usage + +The Benchmark module provides methods to measure and report the time used to execute Ruby code. + +Measure the time to construct the string given by the expression "a"*1_000_000_000: + +```ruby +require 'benchmark' +puts Benchmark.measure { "a"*1_000_000_000 } +``` + +On my machine (OSX 10.8.3 on i5 1.7 GHz) this generates: + +``` +0.350000 0.400000 0.750000 ( 0.835234) +``` + +This report shows the user CPU time, system CPU time, the total time (sum of user CPU time, system CPU time, children's user CPU time, and children's system CPU time), and the elapsed real time. The unit of time is seconds. + +Do some experiments sequentially using the #bm method: + +```ruby +require 'benchmark' +n = 5000000 +Benchmark.bm do |x| + x.report { for i in 1..n; a = "1"; end } + x.report { n.times do ; a = "1"; end } + x.report { 1.upto(n) do ; a = "1"; end } +end +``` + +The result: + +``` + user system total real +1.010000 0.000000 1.010000 ( 1.014479) +1.000000 0.000000 1.000000 ( 0.998261) +0.980000 0.000000 0.980000 ( 0.981335) +``` + +Continuing the previous example, put a label in each report: + +```ruby +require 'benchmark' +n = 5000000 +Benchmark.bm(7) do |x| + x.report("for:") { for i in 1..n; a = "1"; end } + x.report("times:") { n.times do ; a = "1"; end } + x.report("upto:") { 1.upto(n) do ; a = "1"; end } +end +``` + +The result: + +``` + user system total real +for: 1.010000 0.000000 1.010000 ( 1.015688) +times: 1.000000 0.000000 1.000000 ( 1.003611) +upto: 1.030000 0.000000 1.030000 ( 1.028098) +``` + +The times for some benchmarks depend on the order in which items are run. These differences are due to the cost of memory allocation and garbage collection. To avoid these discrepancies, the #bmbm method is provided. For example, to compare ways to sort an array of floats: + +```ruby +require 'benchmark' +array = (1..1000000).map { rand } +Benchmark.bmbm do |x| + x.report("sort!") { array.dup.sort! } + x.report("sort") { array.dup.sort } +end +``` + +The result: + +``` +Rehearsal ----------------------------------------- +sort! 1.490000 0.010000 1.500000 ( 1.490520) +sort 1.460000 0.000000 1.460000 ( 1.463025) +-------------------------------- total: 2.960000sec + user system total real +sort! 1.460000 0.000000 1.460000 ( 1.460465) +sort 1.450000 0.010000 1.460000 ( 1.448327) +``` + +Report statistics of sequential experiments with unique labels, using the #benchmark method: + +```ruby +require 'benchmark' +include Benchmark # we need the CAPTION and FORMAT constants +n = 5000000 +Benchmark.benchmark(CAPTION, 7, FORMAT, ">total:", ">avg:") do |x| + tf = x.report("for:") { for i in 1..n; a = "1"; end } + tt = x.report("times:") { n.times do ; a = "1"; end } + tu = x.report("upto:") { 1.upto(n) do ; a = "1"; end } + [tf+tt+tu, (tf+tt+tu)/3] +end +``` + +The result: + +``` + user system total real +for: 0.950000 0.000000 0.950000 ( 0.952039) +times: 0.980000 0.000000 0.980000 ( 0.984938) +upto: 0.950000 0.000000 0.950000 ( 0.946787) +>total: 2.880000 0.000000 2.880000 ( 2.883764) +>avg: 0.960000 0.000000 0.960000 ( 0.961255) +``` + +## Development + +After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. + +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/benchmark. diff --git a/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/Rakefile b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/Rakefile new file mode 100644 index 00000000..8830e057 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/Rakefile @@ -0,0 +1,8 @@ +require "bundler/gem_tasks" +require "rake/testtask" + +Rake::TestTask.new(:test) do |t| + t.test_files = FileList["test/**/test_*.rb"] +end + +task :default => :test diff --git a/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/benchmark.gemspec b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/benchmark.gemspec new file mode 100644 index 00000000..35deff8d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/benchmark.gemspec @@ -0,0 +1,32 @@ +name = File.basename(__FILE__, ".gemspec") +version = ["lib", Array.new(name.count("-")+1, ".").join("/")].find do |dir| + break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| + /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 + end rescue nil +end + +Gem::Specification.new do |spec| + spec.name = name + spec.version = version + spec.authors = ["Yukihiro Matsumoto"] + spec.email = ["matz@ruby-lang.org"] + + spec.summary = %q{a performance benchmarking library} + spec.description = spec.summary + spec.homepage = "https://github.com/ruby/benchmark" + spec.licenses = ["Ruby", "BSD-2-Clause"] + + spec.required_ruby_version = ">= 2.1.0" + + spec.metadata["homepage_uri"] = spec.homepage + spec.metadata["source_code_uri"] = spec.homepage + + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do + `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + end + spec.bindir = "exe" + spec.executables = [] + spec.require_paths = ["lib"] +end diff --git a/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/bin/console b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/bin/console new file mode 100755 index 00000000..87e7b95e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/bin/console @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +require "benchmark" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require "irb" +IRB.start(__FILE__) diff --git a/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/bin/setup b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/bin/setup new file mode 100755 index 00000000..dce67d86 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/lib/benchmark.rb b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/lib/benchmark.rb new file mode 100644 index 00000000..0d1b8df6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/benchmark-0.4.1/lib/benchmark.rb @@ -0,0 +1,595 @@ +# frozen_string_literal: true +#-- +# benchmark.rb - a performance benchmarking library +# +# $Id$ +# +# Created by Gotoken (gotoken@notwork.org). +# +# Documentation by Gotoken (original RD), Lyle Johnson (RDoc conversion), and +# Gavin Sinclair (editing). +#++ +# +# == Overview +# +# The Benchmark module provides methods for benchmarking Ruby code, giving +# detailed reports on the time taken for each task. +# + +# The Benchmark module provides methods to measure and report the time +# used to execute Ruby code. +# +# * Measure the time to construct the string given by the expression +# "a"*1_000_000_000: +# +# require 'benchmark' +# +# puts Benchmark.measure { "a"*1_000_000_000 } +# +# On my machine (OSX 10.8.3 on i5 1.7 GHz) this generates: +# +# 0.350000 0.400000 0.750000 ( 0.835234) +# +# This report shows the user CPU time, system CPU time, the total time +# (sum of user CPU time, system CPU time, children's user CPU time, +# and children's system CPU time), and the elapsed real time. The unit +# of time is seconds. +# +# * Do some experiments sequentially using the #bm method: +# +# require 'benchmark' +# +# n = 5000000 +# Benchmark.bm do |x| +# x.report { for i in 1..n; a = "1"; end } +# x.report { n.times do ; a = "1"; end } +# x.report { 1.upto(n) do ; a = "1"; end } +# end +# +# The result: +# +# user system total real +# 1.010000 0.000000 1.010000 ( 1.014479) +# 1.000000 0.000000 1.000000 ( 0.998261) +# 0.980000 0.000000 0.980000 ( 0.981335) +# +# * Continuing the previous example, put a label in each report: +# +# require 'benchmark' +# +# n = 5000000 +# Benchmark.bm(7) do |x| +# x.report("for:") { for i in 1..n; a = "1"; end } +# x.report("times:") { n.times do ; a = "1"; end } +# x.report("upto:") { 1.upto(n) do ; a = "1"; end } +# end +# +# The result: +# +# user system total real +# for: 1.010000 0.000000 1.010000 ( 1.015688) +# times: 1.000000 0.000000 1.000000 ( 1.003611) +# upto: 1.030000 0.000000 1.030000 ( 1.028098) +# +# * The times for some benchmarks depend on the order in which items +# are run. These differences are due to the cost of memory +# allocation and garbage collection. To avoid these discrepancies, +# the #bmbm method is provided. For example, to compare ways to +# sort an array of floats: +# +# require 'benchmark' +# +# array = (1..1000000).map { rand } +# +# Benchmark.bmbm do |x| +# x.report("sort!") { array.dup.sort! } +# x.report("sort") { array.dup.sort } +# end +# +# The result: +# +# Rehearsal ----------------------------------------- +# sort! 1.490000 0.010000 1.500000 ( 1.490520) +# sort 1.460000 0.000000 1.460000 ( 1.463025) +# -------------------------------- total: 2.960000sec +# +# user system total real +# sort! 1.460000 0.000000 1.460000 ( 1.460465) +# sort 1.450000 0.010000 1.460000 ( 1.448327) +# +# * Report statistics of sequential experiments with unique labels, +# using the #benchmark method: +# +# require 'benchmark' +# include Benchmark # we need the CAPTION and FORMAT constants +# +# n = 5000000 +# Benchmark.benchmark(CAPTION, 7, FORMAT, ">total:", ">avg:") do |x| +# tf = x.report("for:") { for i in 1..n; a = "1"; end } +# tt = x.report("times:") { n.times do ; a = "1"; end } +# tu = x.report("upto:") { 1.upto(n) do ; a = "1"; end } +# [tf+tt+tu, (tf+tt+tu)/3] +# end +# +# The result: +# +# user system total real +# for: 0.950000 0.000000 0.950000 ( 0.952039) +# times: 0.980000 0.000000 0.980000 ( 0.984938) +# upto: 0.950000 0.000000 0.950000 ( 0.946787) +# >total: 2.880000 0.000000 2.880000 ( 2.883764) +# >avg: 0.960000 0.000000 0.960000 ( 0.961255) + +module Benchmark + + VERSION = "0.4.1" + + BENCHMARK_VERSION = "2002-04-25" # :nodoc: + + # Invokes the block with a Benchmark::Report object, which + # may be used to collect and report on the results of individual + # benchmark tests. Reserves +label_width+ leading spaces for + # labels on each line. Prints +caption+ at the top of the + # report, and uses +format+ to format each line. + # (Note: +caption+ must contain a terminating newline character, + # see the default Benchmark::Tms::CAPTION for an example.) + # + # Returns an array of Benchmark::Tms objects. + # + # If the block returns an array of + # Benchmark::Tms objects, these will be used to format + # additional lines of output. If +labels+ parameter are + # given, these are used to label these extra lines. + # + # _Note_: Other methods provide a simpler interface to this one, and are + # suitable for nearly all benchmarking requirements. See the examples in + # Benchmark, and the #bm and #bmbm methods. + # + # Example: + # + # require 'benchmark' + # include Benchmark # we need the CAPTION and FORMAT constants + # + # n = 5000000 + # Benchmark.benchmark(CAPTION, 7, FORMAT, ">total:", ">avg:") do |x| + # tf = x.report("for:") { for i in 1..n; a = "1"; end } + # tt = x.report("times:") { n.times do ; a = "1"; end } + # tu = x.report("upto:") { 1.upto(n) do ; a = "1"; end } + # [tf+tt+tu, (tf+tt+tu)/3] + # end + # + # Generates: + # + # user system total real + # for: 0.970000 0.000000 0.970000 ( 0.970493) + # times: 0.990000 0.000000 0.990000 ( 0.989542) + # upto: 0.970000 0.000000 0.970000 ( 0.972854) + # >total: 2.930000 0.000000 2.930000 ( 2.932889) + # >avg: 0.976667 0.000000 0.976667 ( 0.977630) + # + + def benchmark(caption = "", label_width = nil, format = nil, *labels) # :yield: report + sync = $stdout.sync + $stdout.sync = true + label_width ||= 0 + label_width += 1 + format ||= FORMAT + report = Report.new(label_width, format) + results = yield(report) + + print " " * report.width + caption unless caption.empty? + report.list.each { |i| + print i.label.to_s.ljust(report.width) + print i.format(report.format, *format) + } + + Array === results and results.grep(Tms).each {|t| + print((labels.shift || t.label || "").ljust(label_width), t.format(format)) + } + report.list + ensure + $stdout.sync = sync unless sync.nil? + end + + + # A simple interface to the #benchmark method, #bm generates sequential + # reports with labels. +label_width+ and +labels+ parameters have the same + # meaning as for #benchmark. + # + # require 'benchmark' + # + # n = 5000000 + # Benchmark.bm(7) do |x| + # x.report("for:") { for i in 1..n; a = "1"; end } + # x.report("times:") { n.times do ; a = "1"; end } + # x.report("upto:") { 1.upto(n) do ; a = "1"; end } + # end + # + # Generates: + # + # user system total real + # for: 0.960000 0.000000 0.960000 ( 0.957966) + # times: 0.960000 0.000000 0.960000 ( 0.960423) + # upto: 0.950000 0.000000 0.950000 ( 0.954864) + # + + def bm(label_width = 0, *labels, &blk) # :yield: report + benchmark(CAPTION, label_width, FORMAT, *labels, &blk) + end + + + # Sometimes benchmark results are skewed because code executed + # earlier encounters different garbage collection overheads than + # that run later. #bmbm attempts to minimize this effect by running + # the tests twice, the first time as a rehearsal in order to get the + # runtime environment stable, the second time for + # real. GC.start is executed before the start of each of + # the real timings; the cost of this is not included in the + # timings. In reality, though, there's only so much that #bmbm can + # do, and the results are not guaranteed to be isolated from garbage + # collection and other effects. + # + # Because #bmbm takes two passes through the tests, it can + # calculate the required label width. + # + # require 'benchmark' + # + # array = (1..1000000).map { rand } + # + # Benchmark.bmbm do |x| + # x.report("sort!") { array.dup.sort! } + # x.report("sort") { array.dup.sort } + # end + # + # Generates: + # + # Rehearsal ----------------------------------------- + # sort! 1.440000 0.010000 1.450000 ( 1.446833) + # sort 1.440000 0.000000 1.440000 ( 1.448257) + # -------------------------------- total: 2.890000sec + # + # user system total real + # sort! 1.460000 0.000000 1.460000 ( 1.458065) + # sort 1.450000 0.000000 1.450000 ( 1.455963) + # + # #bmbm yields a Benchmark::Job object and returns an array of + # Benchmark::Tms objects. + # + def bmbm(width = 0) # :yield: job + job = Job.new(width) + yield(job) + width = job.width + 1 + sync = $stdout.sync + $stdout.sync = true + + # rehearsal + puts 'Rehearsal '.ljust(width+CAPTION.length,'-') + ets = job.list.inject(Tms.new) { |sum,(label,item)| + print label.ljust(width) + res = Benchmark.measure(&item) + print res.format + sum + res + }.format("total: %tsec") + print " #{ets}\n\n".rjust(width+CAPTION.length+2,'-') + + # take + print ' '*width + CAPTION + job.list.map { |label,item| + GC.start + print label.ljust(width) + Benchmark.measure(label, &item).tap { |res| print res } + } + ensure + $stdout.sync = sync unless sync.nil? + end + + # + # Returns the time used to execute the given block as a + # Benchmark::Tms object. Takes +label+ option. + # + # require 'benchmark' + # + # n = 1000000 + # + # time = Benchmark.measure do + # n.times { a = "1" } + # end + # puts time + # + # Generates: + # + # 0.220000 0.000000 0.220000 ( 0.227313) + # + def measure(label = "") # :yield: + t0, r0 = Process.times, Process.clock_gettime(Process::CLOCK_MONOTONIC) + yield + t1, r1 = Process.times, Process.clock_gettime(Process::CLOCK_MONOTONIC) + Benchmark::Tms.new(t1.utime - t0.utime, + t1.stime - t0.stime, + t1.cutime - t0.cutime, + t1.cstime - t0.cstime, + r1 - r0, + label) + end + + # + # Returns the elapsed real time used to execute the given block. + # The unit of time is seconds. + # + # Benchmark.realtime { "a" * 1_000_000_000 } + # #=> 0.5098029999935534 + # + def realtime # :yield: + r0 = Process.clock_gettime(Process::CLOCK_MONOTONIC) + yield + Process.clock_gettime(Process::CLOCK_MONOTONIC) - r0 + end + + module_function :benchmark, :measure, :realtime, :bm, :bmbm + + # + # A Job is a sequence of labelled blocks to be processed by the + # Benchmark.bmbm method. It is of little direct interest to the user. + # + class Job # :nodoc: + # + # Returns an initialized Job instance. + # Usually, one doesn't call this method directly, as new + # Job objects are created by the #bmbm method. + # +width+ is a initial value for the label offset used in formatting; + # the #bmbm method passes its +width+ argument to this constructor. + # + def initialize(width) + @width = width + @list = [] + end + + # + # Registers the given label and block pair in the job list. + # + def item(label = "", &blk) # :yield: + raise ArgumentError, "no block" unless block_given? + label = label.to_s + w = label.length + @width = w if @width < w + @list << [label, blk] + self + end + + alias report item + + # An array of 2-element arrays, consisting of label and block pairs. + attr_reader :list + + # Length of the widest label in the #list. + attr_reader :width + end + + # + # This class is used by the Benchmark.benchmark and Benchmark.bm methods. + # It is of little direct interest to the user. + # + class Report # :nodoc: + # + # Returns an initialized Report instance. + # Usually, one doesn't call this method directly, as new + # Report objects are created by the #benchmark and #bm methods. + # +width+ and +format+ are the label offset and + # format string used by Tms#format. + # + def initialize(width = 0, format = nil) + @width, @format, @list = width, format, [] + end + + # + # Prints the +label+ and measured time for the block, + # formatted by +format+. See Tms#format for the + # formatting rules. + # + def item(label = "", *format, &blk) # :yield: + w = label.to_s.length + @width = w if @width < w + @list << res = Benchmark.measure(label, &blk) + res + end + + alias report item + + # An array of Benchmark::Tms objects representing each item. + attr_reader :width, :format, :list + end + + + + # + # A data object, representing the times associated with a benchmark + # measurement. + # + class Tms + + # Default caption, see also Benchmark::CAPTION + CAPTION = " user system total real\n" + + # Default format string, see also Benchmark::FORMAT + FORMAT = "%10.6u %10.6y %10.6t %10.6r\n" + + # User CPU time + attr_reader :utime + + # System CPU time + attr_reader :stime + + # User CPU time of children + attr_reader :cutime + + # System CPU time of children + attr_reader :cstime + + # Elapsed real time + attr_reader :real + + # Total time, that is +utime+ + +stime+ + +cutime+ + +cstime+ + attr_reader :total + + # Label + attr_reader :label + + # + # Returns an initialized Tms object which has + # +utime+ as the user CPU time, +stime+ as the system CPU time, + # +cutime+ as the children's user CPU time, +cstime+ as the children's + # system CPU time, +real+ as the elapsed real time and +label+ as the label. + # + def initialize(utime = 0.0, stime = 0.0, cutime = 0.0, cstime = 0.0, real = 0.0, label = nil) + @utime, @stime, @cutime, @cstime, @real, @label = utime, stime, cutime, cstime, real, label.to_s + @total = @utime + @stime + @cutime + @cstime + end + + # + # Returns a new Tms object whose times are the sum of the times for this + # Tms object, plus the time required to execute the code block (+blk+). + # + def add(&blk) # :yield: + self + Benchmark.measure(&blk) + end + + # + # An in-place version of #add. + # Changes the times of this Tms object by making it the sum of the times + # for this Tms object, plus the time required to execute + # the code block (+blk+). + # + def add!(&blk) + t = Benchmark.measure(&blk) + @utime = utime + t.utime + @stime = stime + t.stime + @cutime = cutime + t.cutime + @cstime = cstime + t.cstime + @real = real + t.real + self + end + + # + # Returns a new Tms object obtained by memberwise summation + # of the individual times for this Tms object with those of the +other+ + # Tms object. + # This method and #/() are useful for taking statistics. + # + def +(other); memberwise(:+, other) end + + # + # Returns a new Tms object obtained by memberwise subtraction + # of the individual times for the +other+ Tms object from those of this + # Tms object. + # + def -(other); memberwise(:-, other) end + + # + # Returns a new Tms object obtained by memberwise multiplication + # of the individual times for this Tms object by +x+. + # + def *(x); memberwise(:*, x) end + + # + # Returns a new Tms object obtained by memberwise division + # of the individual times for this Tms object by +x+. + # This method and #+() are useful for taking statistics. + # + def /(x); memberwise(:/, x) end + + # + # Returns the contents of this Tms object as + # a formatted string, according to a +format+ string + # like that passed to Kernel.format. In addition, #format + # accepts the following extensions: + # + # %u:: Replaced by the user CPU time, as reported by Tms#utime. + # %y:: Replaced by the system CPU time, as reported by Tms#stime (Mnemonic: y of "s*y*stem") + # %U:: Replaced by the children's user CPU time, as reported by Tms#cutime + # %Y:: Replaced by the children's system CPU time, as reported by Tms#cstime + # %t:: Replaced by the total CPU time, as reported by Tms#total + # %r:: Replaced by the elapsed real time, as reported by Tms#real + # %n:: Replaced by the label string, as reported by Tms#label (Mnemonic: n of "*n*ame") + # + # If +format+ is not given, FORMAT is used as default value, detailing the + # user, system, total and real elapsed time. + # + def format(format = nil, *args) + str = (format || FORMAT).dup + str.gsub!(/(%[-+.\d]*)n/) { "#{$1}s" % label } + str.gsub!(/(%[-+.\d]*)u/) { "#{$1}f" % utime } + str.gsub!(/(%[-+.\d]*)y/) { "#{$1}f" % stime } + str.gsub!(/(%[-+.\d]*)U/) { "#{$1}f" % cutime } + str.gsub!(/(%[-+.\d]*)Y/) { "#{$1}f" % cstime } + str.gsub!(/(%[-+.\d]*)t/) { "#{$1}f" % total } + str.gsub!(/(%[-+.\d]*)r/) { "(#{$1}f)" % real } + format ? str % args : str + end + + # + # Same as #format. + # + def to_s + format + end + + # + # Returns a new 6-element array, consisting of the + # label, user CPU time, system CPU time, children's + # user CPU time, children's system CPU time and elapsed + # real time. + # + def to_a + [@label, @utime, @stime, @cutime, @cstime, @real] + end + + # + # Returns a hash containing the same data as `to_a`. + # + def to_h + { + label: @label, + utime: @utime, + stime: @stime, + cutime: @cutime, + cstime: @cstime, + real: @real + } + end + + protected + + # + # Returns a new Tms object obtained by memberwise operation +op+ + # of the individual times for this Tms object with those of the other + # Tms object (+x+). + # + # +op+ can be a mathematical operation such as +, -, + # *, / + # + def memberwise(op, x) + case x + when Benchmark::Tms + Benchmark::Tms.new(utime.__send__(op, x.utime), + stime.__send__(op, x.stime), + cutime.__send__(op, x.cutime), + cstime.__send__(op, x.cstime), + real.__send__(op, x.real) + ) + else + Benchmark::Tms.new(utime.__send__(op, x), + stime.__send__(op, x), + cutime.__send__(op, x), + cstime.__send__(op, x), + real.__send__(op, x) + ) + end + end + end + + # The default caption string (heading above the output times). + CAPTION = Benchmark::Tms::CAPTION + + # The default format string used to display times. See also Benchmark::Tms#format. + FORMAT = Benchmark::Tms::FORMAT +end diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/LICENSE b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/LICENSE new file mode 100644 index 00000000..a1f19ff9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/LICENSE @@ -0,0 +1,56 @@ +Ruby is copyrighted free software by Yukihiro Matsumoto . +You can redistribute it and/or modify it under either the terms of the +2-clause BSDL (see the file BSDL), or the conditions below: + + 1. You may make and give away verbatim copies of the source form of the + software without restriction, provided that you duplicate all of the + original copyright notices and associated disclaimers. + + 2. You may modify your copy of the software in any way, provided that + you do at least ONE of the following: + + a) place your modifications in the Public Domain or otherwise + make them Freely Available, such as by posting said + modifications to Usenet or an equivalent medium, or by allowing + the author to include your modifications in the software. + + b) use the modified software only within your corporation or + organization. + + c) give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d) make other distribution arrangements with the author. + + 3. You may distribute the software in object code or binary form, + provided that you do at least ONE of the following: + + a) distribute the binaries and library files of the software, + together with instructions (in the manual page or equivalent) + on where to get the original distribution. + + b) accompany the distribution with the machine-readable source of + the software. + + c) give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d) make other distribution arrangements with the author. + + 4. You may modify and include the part of the software into any other + software (possibly commercial). But some files in the distribution + are not written by the author, so that they are not under these terms. + + For the list of those files and their copying conditions, see the + file LEGAL. + + 5. The scripts and library files supplied as input to or produced as + output from the software do not automatically fall under the + copyright of the software, but belong to whomever generated them, + and may be sold commercially, and may be aggregated with this + software. + + 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/bigdecimal.gemspec b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/bigdecimal.gemspec new file mode 100644 index 00000000..b6ef8fd9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/bigdecimal.gemspec @@ -0,0 +1,57 @@ +# coding: utf-8 + +name = File.basename(__FILE__, '.*') +source_version = ["", "ext/#{name}/"].find do |dir| + begin + break File.foreach(File.join(__dir__, "#{dir}#{name}.c")) {|line| + break $1.sub("-", ".") if /^#define\s+#{name.upcase}_VERSION\s+"(.+)"/o =~ line + } + rescue Errno::ENOENT + end +end or raise "can't find #{name.upcase}_VERSION" + +Gem::Specification.new do |s| + s.name = name + s.version = source_version + s.authors = ["Kenta Murata", "Zachary Scott", "Shigeo Kobayashi"] + s.email = ["mrkn@mrkn.jp"] + + s.summary = "Arbitrary-precision decimal floating-point number library." + s.description = "This library provides arbitrary-precision decimal floating-point number class." + s.homepage = "https://github.com/ruby/bigdecimal" + s.licenses = ["Ruby", "BSD-2-Clause"] + + s.require_paths = %w[lib] + s.files = %w[ + LICENSE + bigdecimal.gemspec + lib/bigdecimal.rb + lib/bigdecimal/jacobian.rb + lib/bigdecimal/ludcmp.rb + lib/bigdecimal/math.rb + lib/bigdecimal/newton.rb + lib/bigdecimal/util.rb + sample/linear.rb + sample/nlsolve.rb + sample/pi.rb + ] + if Gem::Platform === s.platform and s.platform =~ 'java' or RUBY_ENGINE == 'jruby' + s.platform = 'java' + else + s.extensions = %w[ext/bigdecimal/extconf.rb] + s.files += %w[ + ext/bigdecimal/bigdecimal.c + ext/bigdecimal/bigdecimal.h + ext/bigdecimal/bits.h + ext/bigdecimal/feature.h + ext/bigdecimal/missing.c + ext/bigdecimal/missing.h + ext/bigdecimal/missing/dtoa.c + ext/bigdecimal/static_assert.h + ] + end + + s.required_ruby_version = Gem::Requirement.new(">= 2.5.0") + + s.metadata["changelog_uri"] = s.homepage + "/blob/master/CHANGES.md" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/Makefile b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/Makefile new file mode 100644 index 00000000..3514c3c2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/Makefile @@ -0,0 +1,270 @@ + +SHELL = /bin/sh + +# V=0 quiet, V=1 verbose. other values don't work. +V = 0 +V0 = $(V:0=) +Q1 = $(V:1=) +Q = $(Q1:0=@) +ECHO1 = $(V:1=@ :) +ECHO = $(ECHO1:0=@ echo) +NULLCMD = : + +#### Start of system configuration section. #### + +srcdir = . +topdir = /usr/include/ruby-3.2.0 +hdrdir = $(topdir) +arch_hdrdir = /usr/include/x86_64-linux-gnu/ruby-3.2.0 +PATH_SEPARATOR = : +VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby +prefix = $(DESTDIR)/usr +rubysitearchprefix = $(sitearchlibdir)/$(RUBY_BASE_NAME) +rubyarchprefix = $(archlibdir)/$(RUBY_BASE_NAME) +rubylibprefix = $(libdir)/$(RUBY_BASE_NAME) +exec_prefix = $(prefix) +vendorarchhdrdir = $(sitearchincludedir)/$(RUBY_VERSION_NAME)/vendor_ruby +sitearchhdrdir = $(sitearchincludedir)/$(RUBY_VERSION_NAME)/site_ruby +rubyarchhdrdir = $(archincludedir)/$(RUBY_VERSION_NAME) +vendorhdrdir = $(rubyhdrdir)/vendor_ruby +sitehdrdir = $(rubyhdrdir)/site_ruby +rubyhdrdir = $(includedir)/$(RUBY_VERSION_NAME) +vendorarchdir = $(rubysitearchprefix)/vendor_ruby/$(ruby_version) +vendorlibdir = $(vendordir)/$(ruby_version) +vendordir = $(rubylibprefix)/vendor_ruby +sitearchdir = $(DESTDIR)/usr/local/lib/x86_64-linux-gnu/site_ruby +sitelibdir = $(sitedir)/$(ruby_version) +sitedir = $(DESTDIR)/usr/local/lib/site_ruby +rubyarchdir = $(rubyarchprefix)/$(ruby_version) +rubylibdir = $(rubylibprefix)/$(ruby_version) +sitearchincludedir = $(includedir)/$(sitearch) +archincludedir = $(includedir)/$(arch) +sitearchlibdir = $(libdir)/$(sitearch) +archlibdir = $(libdir)/$(arch) +ridir = $(datarootdir)/$(RI_BASE_NAME) +mandir = $(datarootdir)/man +localedir = $(datarootdir)/locale +libdir = $(exec_prefix)/lib +psdir = $(docdir) +pdfdir = $(docdir) +dvidir = $(docdir) +htmldir = $(docdir) +infodir = $(datarootdir)/info +docdir = $(datarootdir)/doc/$(PACKAGE) +oldincludedir = $(DESTDIR)/usr/include +includedir = $(prefix)/include +runstatedir = $(DESTDIR)/var/run +localstatedir = $(DESTDIR)/var +sharedstatedir = $(prefix)/com +sysconfdir = $(DESTDIR)/etc +datadir = $(datarootdir) +datarootdir = $(prefix)/share +libexecdir = $(exec_prefix)/libexec +sbindir = $(exec_prefix)/sbin +bindir = $(exec_prefix)/bin +archdir = $(rubyarchdir) + + +CC_WRAPPER = +CC = x86_64-linux-gnu-gcc +CXX = x86_64-linux-gnu-g++ +LIBRUBY = $(LIBRUBY_SO) +LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a +LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME) +LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static $(MAINLIBS) +empty = +OUTFLAG = -o $(empty) +COUTFLAG = -o $(empty) +CSRCFLAG = $(empty) + +RUBY_EXTCONF_H = +cflags = $(optflags) $(debugflags) $(warnflags) +cxxflags = +optflags = -O3 -fno-fast-math +debugflags = -ggdb3 +warnflags = -Wall -Wextra -Wdeprecated-declarations -Wdiv-by-zero -Wduplicated-cond -Wimplicit-function-declaration -Wimplicit-int -Wmisleading-indentation -Wpointer-arith -Wwrite-strings -Wold-style-definition -Wimplicit-fallthrough=0 -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-packed-bitfield-compat -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wsuggest-attribute=format -Wsuggest-attribute=noreturn -Wunused-variable -Wundef +cppflags = +CCDLFLAGS = -fPIC +CFLAGS = $(CCDLFLAGS) -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC $(ARCH_FLAG) +INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir) +DEFS = +CPPFLAGS = -DHAVE_BUILTIN___BUILTIN_CLZ -DHAVE_BUILTIN___BUILTIN_CLZL -DHAVE_BUILTIN___BUILTIN_CLZLL -DHAVE_FLOAT_H -DHAVE_MATH_H -DHAVE_STDBOOL_H -DHAVE_STDLIB_H -DHAVE_X86INTRIN_H -DHAVE_LABS -DHAVE_LLABS -DHAVE_FINITE -DHAVE_RUBY_ATOMIC_H -DHAVE_RUBY_INTERNAL_HAS_BUILTIN_H -DHAVE_RUBY_INTERNAL_STATIC_ASSERT_H -DHAVE_RB_RATIONAL_NUM -DHAVE_RB_RATIONAL_DEN -DHAVE_RB_COMPLEX_REAL -DHAVE_RB_COMPLEX_IMAG -DHAVE_RB_OPTS_EXCEPTION_P -DHAVE_RB_CATEGORY_WARN -DHAVE_CONST_RB_WARN_CATEGORY_DEPRECATED -Wdate-time -D_FORTIFY_SOURCE=3 $(DEFS) $(cppflags) +CXXFLAGS = $(CCDLFLAGS) -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 $(ARCH_FLAG) +ldflags = -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed +dldflags = -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now +ARCH_FLAG = +DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG) +LDSHARED = $(CC) -shared +LDSHAREDXX = $(CXX) -shared +AR = x86_64-linux-gnu-gcc-ar +EXEEXT = + +RUBY_INSTALL_NAME = $(RUBY_BASE_NAME)3.2 +RUBY_SO_NAME = ruby-3.2 +RUBYW_INSTALL_NAME = +RUBY_VERSION_NAME = $(RUBY_BASE_NAME)-$(ruby_version) +RUBYW_BASE_NAME = rubyw +RUBY_BASE_NAME = ruby + +arch = x86_64-linux-gnu +sitearch = $(arch) +ruby_version = 3.2.0 +ruby = $(bindir)/$(RUBY_BASE_NAME)3.2 +RUBY = $(ruby) +BUILTRUBY = $(bindir)/$(RUBY_BASE_NAME)3.2 +ruby_headers = $(hdrdir)/ruby.h $(hdrdir)/ruby/backward.h $(hdrdir)/ruby/ruby.h $(hdrdir)/ruby/defines.h $(hdrdir)/ruby/missing.h $(hdrdir)/ruby/intern.h $(hdrdir)/ruby/st.h $(hdrdir)/ruby/subst.h $(arch_hdrdir)/ruby/config.h + +RM = rm -f +RM_RF = rm -fr +RMDIRS = rmdir --ignore-fail-on-non-empty -p +MAKEDIRS = /bin/mkdir -p +INSTALL = /usr/bin/install -c +INSTALL_PROG = $(INSTALL) -m 0755 +INSTALL_DATA = $(INSTALL) -m 644 +COPY = cp +TOUCH = exit > + +#### End of system configuration section. #### + +preload = +libpath = . $(archlibdir) +LIBPATH = -L. -L$(archlibdir) +DEFFILE = + +CLEANFILES = mkmf.log +DISTCLEANFILES = +DISTCLEANDIRS = + +extout = +extout_prefix = +target_prefix = +LOCAL_LIBS = +LIBS = $(LIBRUBYARG_SHARED) -lm -lpthread -lc +ORIG_SRCS = bigdecimal.c missing.c +SRCS = $(ORIG_SRCS) +OBJS = bigdecimal.o missing.o +HDRS = $(srcdir)/bigdecimal.h $(srcdir)/bits.h $(srcdir)/feature.h $(srcdir)/missing.h $(srcdir)/static_assert.h +LOCAL_HDRS = +TARGET = bigdecimal +TARGET_NAME = bigdecimal +TARGET_ENTRY = Init_$(TARGET_NAME) +DLLIB = $(TARGET).so +EXTSTATIC = +STATIC_LIB = + +TIMESTAMP_DIR = . +BINDIR = $(bindir) +RUBYCOMMONDIR = $(sitedir)$(target_prefix) +RUBYLIBDIR = $(sitelibdir)$(target_prefix) +RUBYARCHDIR = $(sitearchdir)$(target_prefix) +HDRDIR = $(sitehdrdir)$(target_prefix) +ARCHHDRDIR = $(sitearchhdrdir)$(target_prefix) +TARGET_SO_DIR = +TARGET_SO = $(TARGET_SO_DIR)$(DLLIB) +CLEANLIBS = $(TARGET_SO) false +CLEANOBJS = $(OBJS) *.bak +TARGET_SO_DIR_TIMESTAMP = $(TIMESTAMP_DIR)/.sitearchdir.time +BIGDECIMAL_RB = $(srcdir)/../../lib/bigdecimal.rb + +all: $(DLLIB) +static: $(STATIC_LIB) +.PHONY: all install static install-so install-rb +.PHONY: clean clean-so clean-static clean-rb + +clean-static:: +clean-rb-default:: +clean-rb:: +clean-so:: +clean: clean-so clean-static clean-rb-default clean-rb + -$(Q)$(RM_RF) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) .*.time + +distclean-rb-default:: +distclean-rb:: +distclean-so:: +distclean-static:: +distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb + -$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log + -$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES) + -$(Q)$(RMDIRS) $(DISTCLEANDIRS) 2> /dev/null || true + +realclean: distclean +install: install-so install-rb + +install-so: $(DLLIB) $(TARGET_SO_DIR_TIMESTAMP) + $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR) +clean-static:: + -$(Q)$(RM) $(STATIC_LIB) +install-rb: pre-install-rb do-install-rb install-rb-default +install-rb-default: pre-install-rb-default do-install-rb-default +pre-install-rb: Makefile +pre-install-rb-default: Makefile +do-install-rb: +do-install-rb-default: +pre-install-rb-default: + @$(NULLCMD) +$(TARGET_SO_DIR_TIMESTAMP): + $(Q) $(MAKEDIRS) $(@D) $(RUBYARCHDIR) + $(Q) $(TOUCH) $@ + +site-install: site-install-so site-install-rb +site-install-so: install-so +site-install-rb: install-rb + +.SUFFIXES: .c .m .cc .mm .cxx .cpp .o .S + +.cc.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cc.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.mm.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.mm.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.cxx.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cxx.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.cpp.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cpp.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.c.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.c.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.m.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.m.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +$(TARGET_SO): $(OBJS) Makefile + $(ECHO) linking shared-object $(DLLIB) + -$(Q)$(RM) $(@) + $(Q) $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS) + + + +$(OBJS): $(HDRS) $(ruby_headers) diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/bigdecimal.c b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/bigdecimal.c new file mode 100644 index 00000000..486aee86 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/bigdecimal.c @@ -0,0 +1,7761 @@ +/* + * + * Ruby BigDecimal(Variable decimal precision) extension library. + * + * Copyright(C) 2002 by Shigeo Kobayashi(shigeo@tinyforest.gr.jp) + * + */ + +/* #define BIGDECIMAL_DEBUG 1 */ + +#include "bigdecimal.h" +#include "ruby/util.h" + +#ifndef BIGDECIMAL_DEBUG +# undef NDEBUG +# define NDEBUG +#endif +#include + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_IEEEFP_H +#include +#endif + +#include "bits.h" +#include "static_assert.h" + +#define BIGDECIMAL_VERSION "3.2.2" + +/* #define ENABLE_NUMERIC_STRING */ + +#define SIGNED_VALUE_MAX INTPTR_MAX +#define SIGNED_VALUE_MIN INTPTR_MIN +#define MUL_OVERFLOW_SIGNED_VALUE_P(a, b) MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, SIGNED_VALUE_MIN, SIGNED_VALUE_MAX) + +VALUE rb_cBigDecimal; +VALUE rb_mBigMath; + +static ID id_BigDecimal_exception_mode; +static ID id_BigDecimal_rounding_mode; +static ID id_BigDecimal_precision_limit; + +static ID id_up; +static ID id_down; +static ID id_truncate; +static ID id_half_up; +static ID id_default; +static ID id_half_down; +static ID id_half_even; +static ID id_banker; +static ID id_ceiling; +static ID id_ceil; +static ID id_floor; +static ID id_to_r; +static ID id_eq; +static ID id_half; + +#define RBD_NUM_ROUNDING_MODES 11 + +static struct { + ID id; + uint8_t mode; +} rbd_rounding_modes[RBD_NUM_ROUNDING_MODES]; + +/* MACRO's to guard objects from GC by keeping them in stack */ +#ifdef RBIMPL_ATTR_MAYBE_UNUSED +#define ENTER(n) RBIMPL_ATTR_MAYBE_UNUSED() volatile VALUE vStack[n];int iStack=0 +#else +#define ENTER(n) volatile VALUE RB_UNUSED_VAR(vStack[n]);int iStack=0 +#endif +#define PUSH(x) (vStack[iStack++] = (VALUE)(x)) +#define SAVE(p) PUSH((p)->obj) +#define GUARD_OBJ(p,y) ((p)=(y), SAVE(p)) + +#define BASE_FIG BIGDECIMAL_COMPONENT_FIGURES +#define BASE BIGDECIMAL_BASE + +#define HALF_BASE (BASE/2) +#define BASE1 (BASE/10) + +#define LOG10_2 0.3010299956639812 + +#ifndef RRATIONAL_ZERO_P +# define RRATIONAL_ZERO_P(x) (FIXNUM_P(rb_rational_num(x)) && \ + FIX2LONG(rb_rational_num(x)) == 0) +#endif + +#ifndef RRATIONAL_NEGATIVE_P +# define RRATIONAL_NEGATIVE_P(x) RTEST(rb_funcall((x), '<', 1, INT2FIX(0))) +#endif + +#ifndef DECIMAL_SIZE_OF_BITS +#define DECIMAL_SIZE_OF_BITS(n) (((n) * 3010 + 9998) / 9999) +/* an approximation of ceil(n * log10(2)), upto 65536 at least */ +#endif + +#ifdef PRIsVALUE +# define RB_OBJ_CLASSNAME(obj) rb_obj_class(obj) +# define RB_OBJ_STRING(obj) (obj) +#else +# define PRIsVALUE "s" +# define RB_OBJ_CLASSNAME(obj) rb_obj_classname(obj) +# define RB_OBJ_STRING(obj) StringValueCStr(obj) +#endif + +#ifndef MAYBE_UNUSED +# define MAYBE_UNUSED(x) x +#endif + +#define BIGDECIMAL_POSITIVE_P(bd) ((bd)->sign > 0) +#define BIGDECIMAL_NEGATIVE_P(bd) ((bd)->sign < 0) + +/* + * ================== Memory allocation ============================ + */ + +#ifdef BIGDECIMAL_DEBUG +static size_t rbd_allocation_count = 0; /* Memory allocation counter */ +static inline void +atomic_allocation_count_inc(void) +{ + RUBY_ATOMIC_SIZE_INC(rbd_allocation_count); +} +static inline void +atomic_allocation_count_dec_nounderflow(void) +{ + if (rbd_allocation_count == 0) return; + RUBY_ATOMIC_SIZE_DEC(rbd_allocation_count); +} +static void +check_allocation_count_nonzero(void) +{ + if (rbd_allocation_count != 0) return; + rb_bug("[bigdecimal][rbd_free_struct] Too many memory free calls"); +} +#else +# define atomic_allocation_count_inc() /* nothing */ +# define atomic_allocation_count_dec_nounderflow() /* nothing */ +# define check_allocation_count_nonzero() /* nothing */ +#endif /* BIGDECIMAL_DEBUG */ + +PUREFUNC(static inline size_t rbd_struct_size(size_t const)); + +static inline size_t +rbd_struct_size(size_t const internal_digits) +{ + size_t const frac_len = (internal_digits == 0) ? 1 : internal_digits; + return offsetof(Real, frac) + frac_len * sizeof(DECDIG); +} + +static inline Real * +rbd_allocate_struct(size_t const internal_digits) +{ + size_t const size = rbd_struct_size(internal_digits); + Real *real = ruby_xcalloc(1, size); + atomic_allocation_count_inc(); + real->MaxPrec = internal_digits; + return real; +} + +static size_t +rbd_calculate_internal_digits(size_t const digits, bool limit_precision) +{ + size_t const len = roomof(digits, BASE_FIG); + if (limit_precision) { + size_t const prec_limit = VpGetPrecLimit(); + if (prec_limit > 0) { + /* NOTE: 2 more digits for rounding and division */ + size_t const max_len = roomof(prec_limit, BASE_FIG) + 2; + if (len > max_len) + return max_len; + } + } + + return len; +} + +static inline Real * +rbd_allocate_struct_decimal_digits(size_t const decimal_digits, bool limit_precision) +{ + size_t const internal_digits = rbd_calculate_internal_digits(decimal_digits, limit_precision); + return rbd_allocate_struct(internal_digits); +} + +static VALUE BigDecimal_wrap_struct(VALUE obj, Real *vp); + +static Real * +rbd_reallocate_struct(Real *real, size_t const internal_digits) +{ + size_t const size = rbd_struct_size(internal_digits); + VALUE obj = real ? real->obj : 0; + Real *new_real = (Real *)ruby_xrealloc(real, size); + new_real->MaxPrec = internal_digits; + if (obj) { + new_real->obj = 0; + BigDecimal_wrap_struct(obj, new_real); + } + return new_real; +} + +static void +rbd_free_struct(Real *real) +{ + if (real != NULL) { + check_allocation_count_nonzero(); + ruby_xfree(real); + atomic_allocation_count_dec_nounderflow(); + } +} + +#define NewZero rbd_allocate_struct_zero +static Real * +rbd_allocate_struct_zero(int sign, size_t const digits, bool limit_precision) +{ + Real *real = rbd_allocate_struct_decimal_digits(digits, limit_precision); + VpSetZero(real, sign); + return real; +} + +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_zero_limited(int sign, size_t const digits)); +#define NewZeroLimited rbd_allocate_struct_zero_limited +static inline Real * +rbd_allocate_struct_zero_limited(int sign, size_t const digits) +{ + return rbd_allocate_struct_zero(sign, digits, true); +} + +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_zero_nolimit(int sign, size_t const digits)); +#define NewZeroNolimit rbd_allocate_struct_zero_nolimit +static inline Real * +rbd_allocate_struct_zero_nolimit(int sign, size_t const digits) +{ + return rbd_allocate_struct_zero(sign, digits, false); +} + +#define NewOne rbd_allocate_struct_one +static Real * +rbd_allocate_struct_one(int sign, size_t const digits, bool limit_precision) +{ + Real *real = rbd_allocate_struct_decimal_digits(digits, limit_precision); + VpSetOne(real); + if (sign < 0) + VpSetSign(real, VP_SIGN_NEGATIVE_FINITE); + return real; +} + +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_one_limited(int sign, size_t const digits)); +#define NewOneLimited rbd_allocate_struct_one_limited +static inline Real * +rbd_allocate_struct_one_limited(int sign, size_t const digits) +{ + return rbd_allocate_struct_one(sign, digits, true); +} + +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_one_nolimit(int sign, size_t const digits)); +#define NewOneNolimit rbd_allocate_struct_one_nolimit +static inline Real * +rbd_allocate_struct_one_nolimit(int sign, size_t const digits) +{ + return rbd_allocate_struct_one(sign, digits, false); +} + +/* + * ================== Ruby Interface part ========================== + */ +#define DoSomeOne(x,y,f) rb_num_coerce_bin(x,y,f) + +/* + * VP routines used in BigDecimal part + */ +static unsigned short VpGetException(void); +static void VpSetException(unsigned short f); +static void VpCheckException(Real *p, bool always); +static VALUE VpCheckGetValue(Real *p); +static void VpInternalRound(Real *c, size_t ixDigit, DECDIG vPrev, DECDIG v); +static int VpLimitRound(Real *c, size_t ixDigit); +static Real *VpCopy(Real *pv, Real const* const x); +static int VPrint(FILE *fp,const char *cntl_chr,Real *a); + +/* + * **** BigDecimal part **** + */ + +static VALUE BigDecimal_nan(void); +static VALUE BigDecimal_positive_infinity(void); +static VALUE BigDecimal_negative_infinity(void); +static VALUE BigDecimal_positive_zero(void); +static VALUE BigDecimal_negative_zero(void); + +static void +BigDecimal_delete(void *pv) +{ + rbd_free_struct(pv); +} + +static size_t +BigDecimal_memsize(const void *ptr) +{ + const Real *pv = ptr; + return (sizeof(*pv) + pv->MaxPrec * sizeof(DECDIG)); +} + +#ifndef HAVE_RB_EXT_RACTOR_SAFE +# undef RUBY_TYPED_FROZEN_SHAREABLE +# define RUBY_TYPED_FROZEN_SHAREABLE 0 +#endif + +static const rb_data_type_t BigDecimal_data_type = { + "BigDecimal", + { 0, BigDecimal_delete, BigDecimal_memsize, }, +#ifdef RUBY_TYPED_FREE_IMMEDIATELY + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_WB_PROTECTED +#endif +}; + +static Real * +rbd_allocate_struct_zero_wrap_klass(VALUE klass, int sign, size_t const digits, bool limit_precision) +{ + Real *real = rbd_allocate_struct_zero(sign, digits, limit_precision); + if (real != NULL) { + VALUE obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); + BigDecimal_wrap_struct(obj, real); + } + return real; +} + +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_zero_limited_wrap(int sign, size_t const digits)); +#define NewZeroWrapLimited rbd_allocate_struct_zero_limited_wrap +static inline Real * +rbd_allocate_struct_zero_limited_wrap(int sign, size_t const digits) +{ + return rbd_allocate_struct_zero_wrap_klass(rb_cBigDecimal, sign, digits, true); +} + +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_zero_nolimit_wrap(int sign, size_t const digits)); +#define NewZeroWrapNolimit rbd_allocate_struct_zero_nolimit_wrap +static inline Real * +rbd_allocate_struct_zero_nolimit_wrap(int sign, size_t const digits) +{ + return rbd_allocate_struct_zero_wrap_klass(rb_cBigDecimal, sign, digits, false); +} + +static Real * +rbd_allocate_struct_one_wrap_klass(VALUE klass, int sign, size_t const digits, bool limit_precision) +{ + Real *real = rbd_allocate_struct_one(sign, digits, limit_precision); + if (real != NULL) { + VALUE obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); + BigDecimal_wrap_struct(obj, real); + } + return real; +} + +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_one_limited_wrap(int sign, size_t const digits)); +#define NewOneWrapLimited rbd_allocate_struct_one_limited_wrap +static inline Real * +rbd_allocate_struct_one_limited_wrap(int sign, size_t const digits) +{ + return rbd_allocate_struct_one_wrap_klass(rb_cBigDecimal, sign, digits, true); +} + +MAYBE_UNUSED(static inline Real * rbd_allocate_struct_one_nolimit_wrap(int sign, size_t const digits)); +#define NewOneWrapNolimit rbd_allocate_struct_one_nolimit_wrap +static inline Real * +rbd_allocate_struct_one_nolimit_wrap(int sign, size_t const digits) +{ + return rbd_allocate_struct_one_wrap_klass(rb_cBigDecimal, sign, digits, false); +} + +static inline int +is_kind_of_BigDecimal(VALUE const v) +{ + return rb_typeddata_is_kind_of(v, &BigDecimal_data_type); +} + +NORETURN(static void cannot_be_coerced_into_BigDecimal(VALUE, VALUE)); + +static void +cannot_be_coerced_into_BigDecimal(VALUE exc_class, VALUE v) +{ + VALUE str; + + if (rb_special_const_p(v)) { + str = rb_inspect(v); + } + else { + str = rb_class_name(rb_obj_class(v)); + } + + str = rb_str_cat2(rb_str_dup(str), " can't be coerced into BigDecimal"); + rb_exc_raise(rb_exc_new3(exc_class, str)); +} + +static inline VALUE BigDecimal_div2(VALUE, VALUE, VALUE); +static VALUE rb_inum_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); +static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); +static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); +static VALUE rb_cstr_convert_to_BigDecimal(const char *c_str, size_t digs, int raise_exception); +static VALUE rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); + +static Real* +GetVpValueWithPrec(VALUE v, long prec, int must) +{ + const size_t digs = prec < 0 ? SIZE_MAX : (size_t)prec; + + switch(TYPE(v)) { + case T_FLOAT: + v = rb_float_convert_to_BigDecimal(v, digs, must); + break; + + case T_RATIONAL: + v = rb_rational_convert_to_BigDecimal(v, digs, must); + break; + + case T_DATA: + if (!is_kind_of_BigDecimal(v)) { + goto SomeOneMayDoIt; + } + break; + + case T_FIXNUM: { + char szD[128]; + snprintf(szD, 128, "%ld", FIX2LONG(v)); + v = rb_cstr_convert_to_BigDecimal(szD, VpBaseFig() * 2 + 1, must); + break; + } + +#ifdef ENABLE_NUMERIC_STRING + case T_STRING: { + const char *c_str = StringValueCStr(v); + v = rb_cstr_convert_to_BigDecimal(c_str, RSTRING_LEN(v) + VpBaseFig() + 1, must); + break; + } +#endif /* ENABLE_NUMERIC_STRING */ + + case T_BIGNUM: { + VALUE bg = rb_big2str(v, 10); + v = rb_cstr_convert_to_BigDecimal(RSTRING_PTR(bg), RSTRING_LEN(bg) + VpBaseFig() + 1, must); + RB_GC_GUARD(bg); + break; + } + + default: + goto SomeOneMayDoIt; + } + + Real *vp; + TypedData_Get_Struct(v, Real, &BigDecimal_data_type, vp); + return vp; + +SomeOneMayDoIt: + if (must) { + cannot_be_coerced_into_BigDecimal(rb_eTypeError, v); + } + return NULL; /* NULL means to coerce */ +} + +static inline Real* +GetVpValue(VALUE v, int must) +{ + return GetVpValueWithPrec(v, -1, must); +} + +/* call-seq: + * BigDecimal.double_fig -> integer + * + * Returns the number of digits a Float object is allowed to have; + * the result is system-dependent: + * + * BigDecimal.double_fig # => 16 + * + */ +static inline VALUE +BigDecimal_double_fig(VALUE self) +{ + return INT2FIX(VpDblFig()); +} + +/* call-seq: + * precs -> array + * + * Returns an Array of two Integer values that represent platform-dependent + * internal storage properties. + * + * This method is deprecated and will be removed in the future. + * Instead, use BigDecimal#n_significant_digits for obtaining the number of + * significant digits in scientific notation, and BigDecimal#precision for + * obtaining the number of digits in decimal notation. + * + */ + +static VALUE +BigDecimal_prec(VALUE self) +{ + ENTER(1); + Real *p; + VALUE obj; + + rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, + "BigDecimal#precs is deprecated and will be removed in the future; " + "use BigDecimal#precision instead."); + + GUARD_OBJ(p, GetVpValue(self, 1)); + obj = rb_assoc_new(SIZET2NUM(p->Prec*VpBaseFig()), + SIZET2NUM(p->MaxPrec*VpBaseFig())); + return obj; +} + +static void +VpCountPrecisionAndScale(Real *p, ssize_t *out_precision, ssize_t *out_scale) +{ + if (out_precision == NULL && out_scale == NULL) + return; + if (VpIsZero(p) || !VpIsDef(p)) { + zero: + if (out_precision) *out_precision = 0; + if (out_scale) *out_scale = 0; + return; + } + + DECDIG x; + + ssize_t n = p->Prec; /* The length of frac without zeros. */ + while (n > 0 && p->frac[n-1] == 0) --n; + if (n == 0) goto zero; + + int nlz = BASE_FIG; + for (x = p->frac[0]; x > 0; x /= 10) --nlz; + + int ntz = 0; + for (x = p->frac[n-1]; x > 0 && x % 10 == 0; x /= 10) ++ntz; + + /* + * Calculate the precision and the scale + * ------------------------------------- + * + * The most significant digit is frac[0], and the least significant digit + * is frac[Prec-1]. When the exponent is zero, the decimal point is + * located just before frac[0]. + * + * When the exponent is negative, the decimal point moves to leftward. + * In this case, the precision can be calculated by + * + * precision = BASE_FIG * (-exponent + n) - ntz, + * + * and the scale is the same as precision. + * + * 0 . 0000 0000 | frac[0] ... frac[n-1] | + * |<----------| exponent == -2 | + * |---------------------------------->| precision + * |---------------------------------->| scale + * + * + * Conversely, when the exponent is positive, the decimal point moves to + * rightward. In this case, the scale equals to + * + * BASE_FIG * (n - exponent) - ntz. + * + * the precision equals to + * + * scale + BASE_FIG * exponent - nlz. + * + * | frac[0] frac[1] . frac[2] ... frac[n-1] | + * |---------------->| exponent == 2 | + * | |---------------------->| scale + * |---------------------------------------->| precision + */ + + ssize_t ex = p->exponent; + + /* Count the number of decimal digits before frac[1]. */ + ssize_t n_digits_head = BASE_FIG; + if (ex < 0) { + n_digits_head += (-ex) * BASE_FIG; /* The number of leading zeros before frac[0]. */ + ex = 0; + } + else if (ex > 0) { + /* Count the number of decimal digits without the leading zeros in + * the most significant digit in the integral part. + */ + n_digits_head -= nlz; /* Make the number of digits */ + } + + if (out_precision) { + ssize_t precision = n_digits_head; + + /* Count the number of decimal digits after frac[0]. */ + if (ex > (ssize_t)n) { + /* In this case the number is an integer with some trailing zeros. */ + precision += (ex - 1) * BASE_FIG; + } + else if (n > 0) { + precision += (n - 1) * BASE_FIG; + + if (ex < (ssize_t)n) { + precision -= ntz; + } + } + + *out_precision = precision; + } + + if (out_scale) { + ssize_t scale = 0; + + if (p->exponent < 0) { + scale = n_digits_head + (n - 1) * BASE_FIG - ntz; + } + else if (n > p->exponent) { + scale = (n - p->exponent) * BASE_FIG - ntz; + } + + *out_scale = scale; + } +} + +static void +BigDecimal_count_precision_and_scale(VALUE self, ssize_t *out_precision, ssize_t *out_scale) +{ + ENTER(1); + Real *p; + GUARD_OBJ(p, GetVpValue(self, 1)); + VpCountPrecisionAndScale(p, out_precision, out_scale); +} + +/* + * call-seq: + * precision -> integer + * + * Returns the number of decimal digits in +self+: + * + * BigDecimal("0").precision # => 0 + * BigDecimal("1").precision # => 1 + * BigDecimal("1.1").precision # => 2 + * BigDecimal("3.1415").precision # => 5 + * BigDecimal("-1e20").precision # => 21 + * BigDecimal("1e-20").precision # => 20 + * BigDecimal("Infinity").precision # => 0 + * BigDecimal("-Infinity").precision # => 0 + * BigDecimal("NaN").precision # => 0 + * + */ +static VALUE +BigDecimal_precision(VALUE self) +{ + ssize_t precision; + BigDecimal_count_precision_and_scale(self, &precision, NULL); + return SSIZET2NUM(precision); +} + +/* + * call-seq: + * scale -> integer + * + * Returns the number of decimal digits following the decimal digits in +self+. + * + * BigDecimal("0").scale # => 0 + * BigDecimal("1").scale # => 0 + * BigDecimal("1.1").scale # => 1 + * BigDecimal("3.1415").scale # => 4 + * BigDecimal("-1e20").precision # => 0 + * BigDecimal("1e-20").precision # => 20 + * BigDecimal("Infinity").scale # => 0 + * BigDecimal("-Infinity").scale # => 0 + * BigDecimal("NaN").scale # => 0 + */ +static VALUE +BigDecimal_scale(VALUE self) +{ + ssize_t scale; + BigDecimal_count_precision_and_scale(self, NULL, &scale); + return SSIZET2NUM(scale); +} + +/* + * call-seq: + * precision_scale -> [integer, integer] + * + * Returns a 2-length array; the first item is the result of + * BigDecimal#precision and the second one is of BigDecimal#scale. + * + * See BigDecimal#precision. + * See BigDecimal#scale. + */ +static VALUE +BigDecimal_precision_scale(VALUE self) +{ + ssize_t precision, scale; + BigDecimal_count_precision_and_scale(self, &precision, &scale); + return rb_assoc_new(SSIZET2NUM(precision), SSIZET2NUM(scale)); +} + +/* + * call-seq: + * n_significant_digits -> integer + * + * Returns the number of decimal significant digits in +self+. + * + * BigDecimal("0").n_significant_digits # => 0 + * BigDecimal("1").n_significant_digits # => 1 + * BigDecimal("1.1").n_significant_digits # => 2 + * BigDecimal("3.1415").n_significant_digits # => 5 + * BigDecimal("-1e20").n_significant_digits # => 1 + * BigDecimal("1e-20").n_significant_digits # => 1 + * BigDecimal("Infinity").n_significant_digits # => 0 + * BigDecimal("-Infinity").n_significant_digits # => 0 + * BigDecimal("NaN").n_significant_digits # => 0 + */ +static VALUE +BigDecimal_n_significant_digits(VALUE self) +{ + ENTER(1); + + Real *p; + GUARD_OBJ(p, GetVpValue(self, 1)); + if (VpIsZero(p) || !VpIsDef(p)) { + return INT2FIX(0); + } + + ssize_t n = p->Prec; /* The length of frac without trailing zeros. */ + for (n = p->Prec; n > 0 && p->frac[n-1] == 0; --n); + if (n == 0) return INT2FIX(0); + + DECDIG x; + int nlz = BASE_FIG; + for (x = p->frac[0]; x > 0; x /= 10) --nlz; + + int ntz = 0; + for (x = p->frac[n-1]; x > 0 && x % 10 == 0; x /= 10) ++ntz; + + ssize_t n_significant_digits = BASE_FIG*n - nlz - ntz; + return SSIZET2NUM(n_significant_digits); +} + +/* + * call-seq: + * hash -> integer + * + * Returns the integer hash value for +self+. + * + * Two instances of \BigDecimal have the same hash value if and only if + * they have equal: + * + * - Sign. + * - Fractional part. + * - Exponent. + * + */ +static VALUE +BigDecimal_hash(VALUE self) +{ + ENTER(1); + Real *p; + st_index_t hash; + + GUARD_OBJ(p, GetVpValue(self, 1)); + hash = (st_index_t)p->sign; + /* hash!=2: the case for 0(1),NaN(0) or +-Infinity(3) is sign itself */ + if(hash == 2 || hash == (st_index_t)-2) { + hash ^= rb_memhash(p->frac, sizeof(DECDIG)*p->Prec); + hash += p->exponent; + } + return ST2FIX(hash); +} + +/* + * call-seq: + * _dump -> string + * + * Returns a string representing the marshalling of +self+. + * See module Marshal. + * + * inf = BigDecimal('Infinity') # => Infinity + * dumped = inf._dump # => "9:Infinity" + * BigDecimal._load(dumped) # => Infinity + * + */ +static VALUE +BigDecimal_dump(int argc, VALUE *argv, VALUE self) +{ + ENTER(5); + Real *vp; + char *psz; + VALUE dummy; + volatile VALUE dump; + size_t len; + + rb_scan_args(argc, argv, "01", &dummy); + GUARD_OBJ(vp,GetVpValue(self, 1)); + dump = rb_str_new(0, VpNumOfChars(vp, "E")+50); + psz = RSTRING_PTR(dump); + snprintf(psz, RSTRING_LEN(dump), "%"PRIuSIZE":", VpMaxPrec(vp)*VpBaseFig()); + len = strlen(psz); + VpToString(vp, psz+len, RSTRING_LEN(dump)-len, 0, 0); + rb_str_resize(dump, strlen(psz)); + return dump; +} + +/* + * Internal method used to provide marshalling support. See the Marshal module. + */ +static VALUE +BigDecimal_load(VALUE self, VALUE str) +{ + ENTER(2); + Real *pv; + unsigned char *pch; + unsigned char ch; + unsigned long m=0; + + pch = (unsigned char *)StringValueCStr(str); + /* First get max prec */ + while((*pch) != (unsigned char)'\0' && (ch = *pch++) != (unsigned char)':') { + if(!ISDIGIT(ch)) { + rb_raise(rb_eTypeError, "load failed: invalid character in the marshaled string"); + } + m = m*10 + (unsigned long)(ch-'0'); + } + if (m > VpBaseFig()) m -= VpBaseFig(); + GUARD_OBJ(pv, VpNewRbClass(m, (char *)pch, self, true, true)); + m /= VpBaseFig(); + if (m && pv->MaxPrec > m) { + pv->MaxPrec = m+1; + } + return VpCheckGetValue(pv); +} + +static unsigned short +check_rounding_mode_option(VALUE const opts) +{ + VALUE mode; + char const *s; + long l; + + assert(RB_TYPE_P(opts, T_HASH)); + + if (NIL_P(opts)) + goto no_opt; + + mode = rb_hash_lookup2(opts, ID2SYM(id_half), Qundef); + if (mode == Qundef || NIL_P(mode)) + goto no_opt; + + if (SYMBOL_P(mode)) + mode = rb_sym2str(mode); + else if (!RB_TYPE_P(mode, T_STRING)) { + VALUE str_mode = rb_check_string_type(mode); + if (NIL_P(str_mode)) + goto invalid; + mode = str_mode; + } + s = RSTRING_PTR(mode); + l = RSTRING_LEN(mode); + switch (l) { + case 2: + if (strncasecmp(s, "up", 2) == 0) + return VP_ROUND_HALF_UP; + break; + case 4: + if (strncasecmp(s, "even", 4) == 0) + return VP_ROUND_HALF_EVEN; + else if (strncasecmp(s, "down", 4) == 0) + return VP_ROUND_HALF_DOWN; + break; + default: + break; + } + + invalid: + rb_raise(rb_eArgError, "invalid rounding mode (%"PRIsVALUE")", mode); + + no_opt: + return VpGetRoundMode(); +} + +static unsigned short +check_rounding_mode(VALUE const v) +{ + unsigned short sw; + ID id; + if (RB_TYPE_P(v, T_SYMBOL)) { + int i; + id = SYM2ID(v); + for (i = 0; i < RBD_NUM_ROUNDING_MODES; ++i) { + if (rbd_rounding_modes[i].id == id) { + return rbd_rounding_modes[i].mode; + } + } + rb_raise(rb_eArgError, "invalid rounding mode (%"PRIsVALUE")", v); + } + else { + sw = NUM2USHORT(v); + if (!VpIsRoundMode(sw)) { + rb_raise(rb_eArgError, "invalid rounding mode (%"PRIsVALUE")", v); + } + return sw; + } +} + +/* call-seq: + * BigDecimal.mode(mode, setting = nil) -> integer + * + * Returns an integer representing the mode settings + * for exception handling and rounding. + * + * These modes control exception handling: + * + * - \BigDecimal::EXCEPTION_NaN. + * - \BigDecimal::EXCEPTION_INFINITY. + * - \BigDecimal::EXCEPTION_UNDERFLOW. + * - \BigDecimal::EXCEPTION_OVERFLOW. + * - \BigDecimal::EXCEPTION_ZERODIVIDE. + * - \BigDecimal::EXCEPTION_ALL. + * + * Values for +setting+ for exception handling: + * + * - +true+: sets the given +mode+ to +true+. + * - +false+: sets the given +mode+ to +false+. + * - +nil+: does not modify the mode settings. + * + * You can use method BigDecimal.save_exception_mode + * to temporarily change, and then automatically restore, exception modes. + * + * For clarity, some examples below begin by setting all + * exception modes to +false+. + * + * This mode controls the way rounding is to be performed: + * + * - \BigDecimal::ROUND_MODE + * + * You can use method BigDecimal.save_rounding_mode + * to temporarily change, and then automatically restore, the rounding mode. + * + * NaNs + * + * Mode \BigDecimal::EXCEPTION_NaN controls behavior + * when a \BigDecimal NaN is created. + * + * Settings: + * + * - +false+ (default): Returns BigDecimal('NaN'). + * - +true+: Raises FloatDomainError. + * + * Examples: + * + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + * BigDecimal('NaN') # => NaN + * BigDecimal.mode(BigDecimal::EXCEPTION_NaN, true) # => 2 + * BigDecimal('NaN') # Raises FloatDomainError + * + * Infinities + * + * Mode \BigDecimal::EXCEPTION_INFINITY controls behavior + * when a \BigDecimal Infinity or -Infinity is created. + * Settings: + * + * - +false+ (default): Returns BigDecimal('Infinity') + * or BigDecimal('-Infinity'). + * - +true+: Raises FloatDomainError. + * + * Examples: + * + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + * BigDecimal('Infinity') # => Infinity + * BigDecimal('-Infinity') # => -Infinity + * BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, true) # => 1 + * BigDecimal('Infinity') # Raises FloatDomainError + * BigDecimal('-Infinity') # Raises FloatDomainError + * + * Underflow + * + * Mode \BigDecimal::EXCEPTION_UNDERFLOW controls behavior + * when a \BigDecimal underflow occurs. + * Settings: + * + * - +false+ (default): Returns BigDecimal('0') + * or BigDecimal('-Infinity'). + * - +true+: Raises FloatDomainError. + * + * Examples: + * + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + * def flow_under + * x = BigDecimal('0.1') + * 100.times { x *= x } + * end + * flow_under # => 100 + * BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, true) # => 4 + * flow_under # Raises FloatDomainError + * + * Overflow + * + * Mode \BigDecimal::EXCEPTION_OVERFLOW controls behavior + * when a \BigDecimal overflow occurs. + * Settings: + * + * - +false+ (default): Returns BigDecimal('Infinity') + * or BigDecimal('-Infinity'). + * - +true+: Raises FloatDomainError. + * + * Examples: + * + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + * def flow_over + * x = BigDecimal('10') + * 100.times { x *= x } + * end + * flow_over # => 100 + * BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, true) # => 1 + * flow_over # Raises FloatDomainError + * + * Zero Division + * + * Mode \BigDecimal::EXCEPTION_ZERODIVIDE controls behavior + * when a zero-division occurs. + * Settings: + * + * - +false+ (default): Returns BigDecimal('Infinity') + * or BigDecimal('-Infinity'). + * - +true+: Raises FloatDomainError. + * + * Examples: + * + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + * one = BigDecimal('1') + * zero = BigDecimal('0') + * one / zero # => Infinity + * BigDecimal.mode(BigDecimal::EXCEPTION_ZERODIVIDE, true) # => 16 + * one / zero # Raises FloatDomainError + * + * All Exceptions + * + * Mode \BigDecimal::EXCEPTION_ALL controls all of the above: + * + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, true) # => 23 + * + * Rounding + * + * Mode \BigDecimal::ROUND_MODE controls the way rounding is to be performed; + * its +setting+ values are: + * + * - +ROUND_UP+: Round away from zero. + * Aliased as +:up+. + * - +ROUND_DOWN+: Round toward zero. + * Aliased as +:down+ and +:truncate+. + * - +ROUND_HALF_UP+: Round toward the nearest neighbor; + * if the neighbors are equidistant, round away from zero. + * Aliased as +:half_up+ and +:default+. + * - +ROUND_HALF_DOWN+: Round toward the nearest neighbor; + * if the neighbors are equidistant, round toward zero. + * Aliased as +:half_down+. + * - +ROUND_HALF_EVEN+ (Banker's rounding): Round toward the nearest neighbor; + * if the neighbors are equidistant, round toward the even neighbor. + * Aliased as +:half_even+ and +:banker+. + * - +ROUND_CEILING+: Round toward positive infinity. + * Aliased as +:ceiling+ and +:ceil+. + * - +ROUND_FLOOR+: Round toward negative infinity. + * Aliased as +:floor:+. + * + */ +static VALUE +BigDecimal_mode(int argc, VALUE *argv, VALUE self) +{ + VALUE which; + VALUE val; + unsigned long f,fo; + + rb_scan_args(argc, argv, "11", &which, &val); + f = (unsigned long)NUM2INT(which); + + if (f & VP_EXCEPTION_ALL) { + /* Exception mode setting */ + fo = VpGetException(); + if (val == Qnil) return INT2FIX(fo); + if (val != Qfalse && val!=Qtrue) { + rb_raise(rb_eArgError, "second argument must be true or false"); + return Qnil; /* Not reached */ + } + if (f & VP_EXCEPTION_INFINITY) { + VpSetException((unsigned short)((val == Qtrue) ? (fo | VP_EXCEPTION_INFINITY) : + (fo & (~VP_EXCEPTION_INFINITY)))); + } + fo = VpGetException(); + if (f & VP_EXCEPTION_NaN) { + VpSetException((unsigned short)((val == Qtrue) ? (fo | VP_EXCEPTION_NaN) : + (fo & (~VP_EXCEPTION_NaN)))); + } + fo = VpGetException(); + if (f & VP_EXCEPTION_UNDERFLOW) { + VpSetException((unsigned short)((val == Qtrue) ? (fo | VP_EXCEPTION_UNDERFLOW) : + (fo & (~VP_EXCEPTION_UNDERFLOW)))); + } + fo = VpGetException(); + if(f & VP_EXCEPTION_ZERODIVIDE) { + VpSetException((unsigned short)((val == Qtrue) ? (fo | VP_EXCEPTION_ZERODIVIDE) : + (fo & (~VP_EXCEPTION_ZERODIVIDE)))); + } + fo = VpGetException(); + return INT2FIX(fo); + } + if (VP_ROUND_MODE == f) { + /* Rounding mode setting */ + unsigned short sw; + fo = VpGetRoundMode(); + if (NIL_P(val)) return INT2FIX(fo); + sw = check_rounding_mode(val); + fo = VpSetRoundMode(sw); + return INT2FIX(fo); + } + rb_raise(rb_eTypeError, "first argument for BigDecimal.mode invalid"); + return Qnil; +} + +static size_t +GetAddSubPrec(Real *a, Real *b) +{ + size_t mxs; + size_t mx = a->Prec; + SIGNED_VALUE d; + + if (!VpIsDef(a) || !VpIsDef(b)) return (size_t)-1L; + if (mx < b->Prec) mx = b->Prec; + if (a->exponent != b->exponent) { + mxs = mx; + d = a->exponent - b->exponent; + if (d < 0) d = -d; + mx = mx + (size_t)d; + if (mx < mxs) { + return VpException(VP_EXCEPTION_INFINITY, "Exponent overflow", 0); + } + } + return mx; +} + +static inline SIGNED_VALUE +check_int_precision(VALUE v) +{ + SIGNED_VALUE n; +#if SIZEOF_VALUE <= SIZEOF_LONG + n = (SIGNED_VALUE)NUM2LONG(v); +#elif SIZEOF_VALUE <= SIZEOF_LONG_LONG + n = (SIGNED_VALUE)NUM2LL(v); +#else +# error SIZEOF_VALUE is too large +#endif + if (n < 0) { + rb_raise(rb_eArgError, "negative precision"); + } + return n; +} + +static VALUE +BigDecimal_wrap_struct(VALUE obj, Real *vp) +{ + assert(is_kind_of_BigDecimal(obj)); + assert(vp != NULL); + + if (vp->obj == obj && RTYPEDDATA_DATA(obj) == vp) + return obj; + + assert(RTYPEDDATA_DATA(obj) == NULL); + assert(vp->obj == 0); + + RTYPEDDATA_DATA(obj) = vp; + vp->obj = obj; + RB_OBJ_FREEZE(obj); + return obj; +} + +VP_EXPORT Real * +VpNewRbClass(size_t mx, const char *str, VALUE klass, bool strict_p, bool raise_exception) +{ + VALUE obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); + Real *pv = VpAlloc(mx, str, strict_p, raise_exception); + if (!pv) + return NULL; + BigDecimal_wrap_struct(obj, pv); + return pv; +} + +VP_EXPORT Real * +VpCreateRbObject(size_t mx, const char *str, bool raise_exception) +{ + return VpNewRbClass(mx, str, rb_cBigDecimal, true, raise_exception); +} + +static Real * +VpCopy(Real *pv, Real const* const x) +{ + assert(x != NULL); + + pv = rbd_reallocate_struct(pv, x->MaxPrec); + pv->MaxPrec = x->MaxPrec; + pv->Prec = x->Prec; + pv->exponent = x->exponent; + pv->sign = x->sign; + pv->flag = x->flag; + MEMCPY(pv->frac, x->frac, DECDIG, pv->MaxPrec); + + return pv; +} + +/* Returns True if the value is Not a Number. */ +static VALUE +BigDecimal_IsNaN(VALUE self) +{ + Real *p = GetVpValue(self, 1); + if (VpIsNaN(p)) return Qtrue; + return Qfalse; +} + +/* Returns nil, -1, or +1 depending on whether the value is finite, + * -Infinity, or +Infinity. + */ +static VALUE +BigDecimal_IsInfinite(VALUE self) +{ + Real *p = GetVpValue(self, 1); + if (VpIsPosInf(p)) return INT2FIX(1); + if (VpIsNegInf(p)) return INT2FIX(-1); + return Qnil; +} + +/* Returns True if the value is finite (not NaN or infinite). */ +static VALUE +BigDecimal_IsFinite(VALUE self) +{ + Real *p = GetVpValue(self, 1); + if (VpIsNaN(p)) return Qfalse; + if (VpIsInf(p)) return Qfalse; + return Qtrue; +} + +static void +BigDecimal_check_num(Real *p) +{ + VpCheckException(p, true); +} + +static VALUE BigDecimal_split(VALUE self); + +/* Returns the value as an Integer. + * + * If the BigDecimal is infinity or NaN, raises FloatDomainError. + */ +static VALUE +BigDecimal_to_i(VALUE self) +{ + ENTER(5); + ssize_t e, nf; + Real *p; + + GUARD_OBJ(p, GetVpValue(self, 1)); + BigDecimal_check_num(p); + + e = VpExponent10(p); + if (e <= 0) return INT2FIX(0); + nf = VpBaseFig(); + if (e <= nf) { + return LONG2NUM((long)(VpGetSign(p) * (DECDIG_DBL_SIGNED)p->frac[0])); + } + else { + VALUE a = BigDecimal_split(self); + VALUE digits = RARRAY_AREF(a, 1); + VALUE numerator = rb_funcall(digits, rb_intern("to_i"), 0); + VALUE ret; + ssize_t dpower = e - (ssize_t)RSTRING_LEN(digits); + + if (BIGDECIMAL_NEGATIVE_P(p)) { + numerator = rb_funcall(numerator, '*', 1, INT2FIX(-1)); + } + if (dpower < 0) { + ret = rb_funcall(numerator, rb_intern("div"), 1, + rb_funcall(INT2FIX(10), rb_intern("**"), 1, + INT2FIX(-dpower))); + } + else { + ret = rb_funcall(numerator, '*', 1, + rb_funcall(INT2FIX(10), rb_intern("**"), 1, + INT2FIX(dpower))); + } + if (RB_TYPE_P(ret, T_FLOAT)) { + rb_raise(rb_eFloatDomainError, "Infinity"); + } + return ret; + } +} + +/* Returns a new Float object having approximately the same value as the + * BigDecimal number. Normal accuracy limits and built-in errors of binary + * Float arithmetic apply. + */ +static VALUE +BigDecimal_to_f(VALUE self) +{ + ENTER(1); + Real *p; + double d; + SIGNED_VALUE e; + char *buf; + volatile VALUE str; + + GUARD_OBJ(p, GetVpValue(self, 1)); + if (VpVtoD(&d, &e, p) != 1) + return rb_float_new(d); + if (e > (SIGNED_VALUE)(DBL_MAX_10_EXP+BASE_FIG)) + goto overflow; + if (e < (SIGNED_VALUE)(DBL_MIN_10_EXP-BASE_FIG)) + goto underflow; + + str = rb_str_new(0, VpNumOfChars(p, "E")); + buf = RSTRING_PTR(str); + VpToString(p, buf, RSTRING_LEN(str), 0, 0); + errno = 0; + d = strtod(buf, 0); + if (errno == ERANGE) { + if (d == 0.0) goto underflow; + if (fabs(d) >= HUGE_VAL) goto overflow; + } + return rb_float_new(d); + +overflow: + VpException(VP_EXCEPTION_OVERFLOW, "BigDecimal to Float conversion", 0); + if (BIGDECIMAL_NEGATIVE_P(p)) + return rb_float_new(VpGetDoubleNegInf()); + else + return rb_float_new(VpGetDoublePosInf()); + +underflow: + VpException(VP_EXCEPTION_UNDERFLOW, "BigDecimal to Float conversion", 0); + if (BIGDECIMAL_NEGATIVE_P(p)) + return rb_float_new(-0.0); + else + return rb_float_new(0.0); +} + + +/* Converts a BigDecimal to a Rational. + */ +static VALUE +BigDecimal_to_r(VALUE self) +{ + Real *p; + ssize_t sign, power, denomi_power; + VALUE a, digits, numerator; + + p = GetVpValue(self, 1); + BigDecimal_check_num(p); + + sign = VpGetSign(p); + power = VpExponent10(p); + a = BigDecimal_split(self); + digits = RARRAY_AREF(a, 1); + denomi_power = power - RSTRING_LEN(digits); + numerator = rb_funcall(digits, rb_intern("to_i"), 0); + + if (sign < 0) { + numerator = rb_funcall(numerator, '*', 1, INT2FIX(-1)); + } + if (denomi_power < 0) { + return rb_Rational(numerator, + rb_funcall(INT2FIX(10), rb_intern("**"), 1, + INT2FIX(-denomi_power))); + } + else { + return rb_Rational1(rb_funcall(numerator, '*', 1, + rb_funcall(INT2FIX(10), rb_intern("**"), 1, + INT2FIX(denomi_power)))); + } +} + +/* The coerce method provides support for Ruby type coercion. It is not + * enabled by default. + * + * This means that binary operations like + * / or - can often be performed + * on a BigDecimal and an object of another type, if the other object can + * be coerced into a BigDecimal value. + * + * e.g. + * a = BigDecimal("1.0") + * b = a / 2.0 #=> 0.5 + * + * Note that coercing a String to a BigDecimal is not supported by default; + * it requires a special compile-time option when building Ruby. + */ +static VALUE +BigDecimal_coerce(VALUE self, VALUE other) +{ + ENTER(2); + VALUE obj; + Real *b; + + if (RB_TYPE_P(other, T_FLOAT)) { + GUARD_OBJ(b, GetVpValueWithPrec(other, 0, 1)); + obj = rb_assoc_new(VpCheckGetValue(b), self); + } + else { + if (RB_TYPE_P(other, T_RATIONAL)) { + Real* pv = DATA_PTR(self); + GUARD_OBJ(b, GetVpValueWithPrec(other, pv->Prec*VpBaseFig(), 1)); + } + else { + GUARD_OBJ(b, GetVpValue(other, 1)); + } + obj = rb_assoc_new(b->obj, self); + } + + return obj; +} + +/* + * call-seq: + * +big_decimal -> self + * + * Returns +self+: + * + * +BigDecimal(5) # => 0.5e1 + * +BigDecimal(-5) # => -0.5e1 + * + */ + +static VALUE +BigDecimal_uplus(VALUE self) +{ + return self; +} + + /* + * call-seq: + * self + value -> bigdecimal + * + * Returns the \BigDecimal sum of +self+ and +value+: + * + * b = BigDecimal('111111.111') # => 0.111111111e6 + * b + 2 # => 0.111113111e6 + * b + 2.0 # => 0.111113111e6 + * b + Rational(2, 1) # => 0.111113111e6 + * b + Complex(2, 0) # => (0.111113111e6+0i) + * + * See the {Note About Precision}[BigDecimal.html#class-BigDecimal-label-A+Note+About+Precision]. + * + */ + +static VALUE +BigDecimal_add(VALUE self, VALUE r) +{ + ENTER(5); + Real *c, *a, *b; + size_t mx; + + GUARD_OBJ(a, GetVpValue(self, 1)); + if (RB_TYPE_P(r, T_FLOAT)) { + b = GetVpValueWithPrec(r, 0, 1); + } + else if (RB_TYPE_P(r, T_RATIONAL)) { + b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); + } + else { + b = GetVpValue(r, 0); + } + + if (!b) return DoSomeOne(self,r,'+'); + SAVE(b); + + if (VpIsNaN(b)) return b->obj; + if (VpIsNaN(a)) return a->obj; + + mx = GetAddSubPrec(a, b); + if (mx == (size_t)-1L) { + GUARD_OBJ(c, NewZeroWrapLimited(1, VpBaseFig() + 1)); + VpAddSub(c, a, b, 1); + } + else { + GUARD_OBJ(c, NewZeroWrapLimited(1, mx * (VpBaseFig() + 1))); + if (!mx) { + VpSetInf(c, VpGetSign(a)); + } + else { + VpAddSub(c, a, b, 1); + } + } + return VpCheckGetValue(c); +} + + /* + * call-seq: + * self - value -> bigdecimal + * + * Returns the \BigDecimal difference of +self+ and +value+: + * + * b = BigDecimal('333333.333') # => 0.333333333e6 + * b - 2 # => 0.333331333e6 + * b - 2.0 # => 0.333331333e6 + * b - Rational(2, 1) # => 0.333331333e6 + * b - Complex(2, 0) # => (0.333331333e6+0i) + * + * See the {Note About Precision}[BigDecimal.html#class-BigDecimal-label-A+Note+About+Precision]. + * + */ +static VALUE +BigDecimal_sub(VALUE self, VALUE r) +{ + ENTER(5); + Real *c, *a, *b; + size_t mx; + + GUARD_OBJ(a, GetVpValue(self,1)); + if (RB_TYPE_P(r, T_FLOAT)) { + b = GetVpValueWithPrec(r, 0, 1); + } + else if (RB_TYPE_P(r, T_RATIONAL)) { + b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); + } + else { + b = GetVpValue(r,0); + } + + if (!b) return DoSomeOne(self,r,'-'); + SAVE(b); + + if (VpIsNaN(b)) return b->obj; + if (VpIsNaN(a)) return a->obj; + + mx = GetAddSubPrec(a,b); + if (mx == (size_t)-1L) { + GUARD_OBJ(c, NewZeroWrapLimited(1, VpBaseFig() + 1)); + VpAddSub(c, a, b, -1); + } + else { + GUARD_OBJ(c, NewZeroWrapLimited(1, mx *(VpBaseFig() + 1))); + if (!mx) { + VpSetInf(c,VpGetSign(a)); + } + else { + VpAddSub(c, a, b, -1); + } + } + return VpCheckGetValue(c); +} + +static VALUE +BigDecimalCmp(VALUE self, VALUE r,char op) +{ + ENTER(5); + SIGNED_VALUE e; + Real *a, *b=0; + GUARD_OBJ(a, GetVpValue(self, 1)); + switch (TYPE(r)) { + case T_DATA: + if (!is_kind_of_BigDecimal(r)) break; + /* fall through */ + case T_FIXNUM: + /* fall through */ + case T_BIGNUM: + GUARD_OBJ(b, GetVpValue(r, 0)); + break; + + case T_FLOAT: + GUARD_OBJ(b, GetVpValueWithPrec(r, 0, 0)); + break; + + case T_RATIONAL: + GUARD_OBJ(b, GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 0)); + break; + + default: + break; + } + if (b == NULL) { + ID f = 0; + + switch (op) { + case '*': + return rb_num_coerce_cmp(self, r, rb_intern("<=>")); + + case '=': + return RTEST(rb_num_coerce_cmp(self, r, rb_intern("=="))) ? Qtrue : Qfalse; + + case 'G': + f = rb_intern(">="); + break; + + case 'L': + f = rb_intern("<="); + break; + + case '>': + /* fall through */ + case '<': + f = (ID)op; + break; + + default: + break; + } + return rb_num_coerce_relop(self, r, f); + } + SAVE(b); + e = VpComp(a, b); + if (e == 999) + return (op == '*') ? Qnil : Qfalse; + switch (op) { + case '*': + return INT2FIX(e); /* any op */ + + case '=': + if (e == 0) return Qtrue; + return Qfalse; + + case 'G': + if (e >= 0) return Qtrue; + return Qfalse; + + case '>': + if (e > 0) return Qtrue; + return Qfalse; + + case 'L': + if (e <= 0) return Qtrue; + return Qfalse; + + case '<': + if (e < 0) return Qtrue; + return Qfalse; + + default: + break; + } + + rb_bug("Undefined operation in BigDecimalCmp()"); + + UNREACHABLE; +} + +/* Returns True if the value is zero. */ +static VALUE +BigDecimal_zero(VALUE self) +{ + Real *a = GetVpValue(self, 1); + return VpIsZero(a) ? Qtrue : Qfalse; +} + +/* Returns self if the value is non-zero, nil otherwise. */ +static VALUE +BigDecimal_nonzero(VALUE self) +{ + Real *a = GetVpValue(self, 1); + return VpIsZero(a) ? Qnil : self; +} + +/* The comparison operator. + * a <=> b is 0 if a == b, 1 if a > b, -1 if a < b. + */ +static VALUE +BigDecimal_comp(VALUE self, VALUE r) +{ + return BigDecimalCmp(self, r, '*'); +} + +/* + * Tests for value equality; returns true if the values are equal. + * + * The == and === operators and the eql? method have the same implementation + * for BigDecimal. + * + * Values may be coerced to perform the comparison: + * + * BigDecimal('1.0') == 1.0 #=> true + */ +static VALUE +BigDecimal_eq(VALUE self, VALUE r) +{ + return BigDecimalCmp(self, r, '='); +} + +/* call-seq: + * self < other -> true or false + * + * Returns +true+ if +self+ is less than +other+, +false+ otherwise: + * + * b = BigDecimal('1.5') # => 0.15e1 + * b < 2 # => true + * b < 2.0 # => true + * b < Rational(2, 1) # => true + * b < 1.5 # => false + * + * Raises an exception if the comparison cannot be made. + * + */ +static VALUE +BigDecimal_lt(VALUE self, VALUE r) +{ + return BigDecimalCmp(self, r, '<'); +} + +/* call-seq: + * self <= other -> true or false + * + * Returns +true+ if +self+ is less or equal to than +other+, +false+ otherwise: + * + * b = BigDecimal('1.5') # => 0.15e1 + * b <= 2 # => true + * b <= 2.0 # => true + * b <= Rational(2, 1) # => true + * b <= 1.5 # => true + * b < 1 # => false + * + * Raises an exception if the comparison cannot be made. + * + */ +static VALUE +BigDecimal_le(VALUE self, VALUE r) +{ + return BigDecimalCmp(self, r, 'L'); +} + +/* call-seq: + * self > other -> true or false + * + * Returns +true+ if +self+ is greater than +other+, +false+ otherwise: + * + * b = BigDecimal('1.5') + * b > 1 # => true + * b > 1.0 # => true + * b > Rational(1, 1) # => true + * b > 2 # => false + * + * Raises an exception if the comparison cannot be made. + * + */ +static VALUE +BigDecimal_gt(VALUE self, VALUE r) +{ + return BigDecimalCmp(self, r, '>'); +} + +/* call-seq: + * self >= other -> true or false + * + * Returns +true+ if +self+ is greater than or equal to +other+, +false+ otherwise: + * + * b = BigDecimal('1.5') + * b >= 1 # => true + * b >= 1.0 # => true + * b >= Rational(1, 1) # => true + * b >= 1.5 # => true + * b > 2 # => false + * + * Raises an exception if the comparison cannot be made. + * + */ +static VALUE +BigDecimal_ge(VALUE self, VALUE r) +{ + return BigDecimalCmp(self, r, 'G'); +} + +/* + * call-seq: + * -self -> bigdecimal + * + * Returns the \BigDecimal negation of self: + * + * b0 = BigDecimal('1.5') + * b1 = -b0 # => -0.15e1 + * b2 = -b1 # => 0.15e1 + * + */ + +static VALUE +BigDecimal_neg(VALUE self) +{ + ENTER(5); + Real *c, *a; + GUARD_OBJ(a, GetVpValue(self, 1)); + GUARD_OBJ(c, NewZeroWrapLimited(1, a->Prec *(VpBaseFig() + 1))); + VpAsgn(c, a, -1); + return VpCheckGetValue(c); +} + +/* + * call-seq: + * a * b -> bigdecimal + * + * Multiply by the specified value. + * + * The result precision will be the precision of the sum of each precision. + * + * See BigDecimal#mult. + */ + +static VALUE +BigDecimal_mult(VALUE self, VALUE r) +{ + ENTER(5); + Real *c, *a, *b; + size_t mx; + + GUARD_OBJ(a, GetVpValue(self, 1)); + if (RB_TYPE_P(r, T_FLOAT)) { + b = GetVpValueWithPrec(r, 0, 1); + } + else if (RB_TYPE_P(r, T_RATIONAL)) { + b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); + } + else { + b = GetVpValue(r,0); + } + + if (!b) return DoSomeOne(self, r, '*'); + SAVE(b); + + mx = a->Prec + b->Prec; + GUARD_OBJ(c, NewZeroWrapLimited(1, mx * (VpBaseFig() + 1))); + VpMult(c, a, b); + return VpCheckGetValue(c); +} + +static VALUE BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod); + +/* call-seq: + * a / b -> bigdecimal + * + * Divide by the specified value. + * + * The result precision will be the precision of the larger operand, + * but its minimum is 2*Float::DIG. + * + * See BigDecimal#div. + * See BigDecimal#quo. + */ +static VALUE +BigDecimal_div(VALUE self, VALUE r) +/* For c = self/r: with round operation */ +{ + if ( + !is_kind_of_BigDecimal(r) && + !RB_INTEGER_TYPE_P(r) && + !RB_TYPE_P(r, T_FLOAT) && + !RB_TYPE_P(r, T_RATIONAL) + ) { + return DoSomeOne(self, r, '/'); + } + return BigDecimal_div2(self, r, INT2FIX(0)); +} + +static VALUE BigDecimal_round(int argc, VALUE *argv, VALUE self); + +/* call-seq: + * quo(value) -> bigdecimal + * quo(value, digits) -> bigdecimal + * + * Divide by the specified value. + * + * digits:: If specified and less than the number of significant digits of + * the result, the result is rounded to the given number of digits, + * according to the rounding mode indicated by BigDecimal.mode. + * + * If digits is 0 or omitted, the result is the same as for the + * / operator. + * + * See BigDecimal#/. + * See BigDecimal#div. + */ +static VALUE +BigDecimal_quo(int argc, VALUE *argv, VALUE self) +{ + VALUE value, digits, result; + SIGNED_VALUE n = -1; + + argc = rb_scan_args(argc, argv, "11", &value, &digits); + if (argc > 1) { + n = check_int_precision(digits); + } + + if (n > 0) { + result = BigDecimal_div2(self, value, digits); + } + else { + result = BigDecimal_div(self, value); + } + + return result; +} + +/* + * %: mod = a%b = a - (a.to_f/b).floor * b + * div = (a.to_f/b).floor + */ +static VALUE +BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) +{ + ENTER(8); + Real *c=NULL, *d=NULL, *res=NULL; + Real *a, *b; + ssize_t a_prec, b_prec; + size_t mx; + + TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); + SAVE(a); + + VALUE rr = r; + if (is_kind_of_BigDecimal(rr)) { + /* do nothing */ + } + else if (RB_INTEGER_TYPE_P(r)) { + rr = rb_inum_convert_to_BigDecimal(r, 0, true); + } + else if (RB_TYPE_P(r, T_FLOAT)) { + rr = rb_float_convert_to_BigDecimal(r, 0, true); + } + else if (RB_TYPE_P(r, T_RATIONAL)) { + rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); + } + + if (!is_kind_of_BigDecimal(rr)) { + return Qfalse; + } + + TypedData_Get_Struct(rr, Real, &BigDecimal_data_type, b); + SAVE(b); + + if (VpIsNaN(a) || VpIsNaN(b)) goto NaN; + if (VpIsInf(a) && VpIsInf(b)) goto NaN; + if (VpIsZero(b)) { + rb_raise(rb_eZeroDivError, "divided by 0"); + } + if (VpIsInf(a)) { + if (VpGetSign(a) == VpGetSign(b)) { + VALUE inf = BigDecimal_positive_infinity(); + TypedData_Get_Struct(inf, Real, &BigDecimal_data_type, *div); + } + else { + VALUE inf = BigDecimal_negative_infinity(); + TypedData_Get_Struct(inf, Real, &BigDecimal_data_type, *div); + } + VALUE nan = BigDecimal_nan(); + TypedData_Get_Struct(nan, Real, &BigDecimal_data_type, *mod); + return Qtrue; + } + if (VpIsInf(b)) { + VALUE zero = BigDecimal_positive_zero(); + TypedData_Get_Struct(zero, Real, &BigDecimal_data_type, *div); + *mod = a; + return Qtrue; + } + if (VpIsZero(a)) { + VALUE zero = BigDecimal_positive_zero(); + TypedData_Get_Struct(zero, Real, &BigDecimal_data_type, *div); + TypedData_Get_Struct(zero, Real, &BigDecimal_data_type, *mod); + return Qtrue; + } + + BigDecimal_count_precision_and_scale(self, &a_prec, NULL); + BigDecimal_count_precision_and_scale(rr, &b_prec, NULL); + + mx = (a_prec > b_prec) ? a_prec : b_prec; + mx *= 2; + + if (2*BIGDECIMAL_DOUBLE_FIGURES > mx) + mx = 2*BIGDECIMAL_DOUBLE_FIGURES; + + GUARD_OBJ(c, NewZeroWrapLimited(1, mx + 2*BASE_FIG)); + GUARD_OBJ(res, NewZeroWrapNolimit(1, mx*2 + 2*BASE_FIG)); + VpDivd(c, res, a, b); + + mx = c->Prec * BASE_FIG; + GUARD_OBJ(d, NewZeroWrapLimited(1, mx)); + VpActiveRound(d, c, VP_ROUND_DOWN, 0); + + VpMult(res, d, b); + VpAddSub(c, a, res, -1); + + if (!VpIsZero(c) && (VpGetSign(a) * VpGetSign(b) < 0)) { + /* result adjustment for negative case */ + res = rbd_reallocate_struct(res, d->MaxPrec); + res->MaxPrec = d->MaxPrec; + VpAddSub(res, d, VpOne(), -1); + GUARD_OBJ(d, NewZeroWrapLimited(1, GetAddSubPrec(c, b) * 2*BASE_FIG)); + VpAddSub(d, c, b, 1); + *div = res; + *mod = d; + } + else { + *div = d; + *mod = c; + } + return Qtrue; + + NaN: + { + VALUE nan = BigDecimal_nan(); + TypedData_Get_Struct(nan, Real, &BigDecimal_data_type, *div); + TypedData_Get_Struct(nan, Real, &BigDecimal_data_type, *mod); + } + return Qtrue; +} + +/* call-seq: + * a % b + * a.modulo(b) + * + * Returns the modulus from dividing by b. + * + * See BigDecimal#divmod. + */ +static VALUE +BigDecimal_mod(VALUE self, VALUE r) /* %: a%b = a - (a.to_f/b).floor * b */ +{ + ENTER(3); + Real *div = NULL, *mod = NULL; + + if (BigDecimal_DoDivmod(self, r, &div, &mod)) { + SAVE(div); SAVE(mod); + return VpCheckGetValue(mod); + } + return DoSomeOne(self, r, '%'); +} + +static VALUE +BigDecimal_divremain(VALUE self, VALUE r, Real **dv, Real **rv) +{ + ENTER(10); + size_t mx; + Real *a = NULL, *b = NULL, *c = NULL, *res = NULL, *d = NULL, *rr = NULL, *ff = NULL; + Real *f = NULL; + + GUARD_OBJ(a, GetVpValue(self, 1)); + if (RB_TYPE_P(r, T_FLOAT)) { + b = GetVpValueWithPrec(r, 0, 1); + } + else if (RB_TYPE_P(r, T_RATIONAL)) { + b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); + } + else { + b = GetVpValue(r, 0); + } + + if (!b) return DoSomeOne(self, r, rb_intern("remainder")); + SAVE(b); + + if (VpIsPosInf(b) || VpIsNegInf(b)) { + GUARD_OBJ(*dv, NewZeroWrapLimited(1, 1)); + VpSetZero(*dv, 1); + *rv = a; + return Qnil; + } + + mx = (a->MaxPrec + b->MaxPrec) *VpBaseFig(); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); + GUARD_OBJ(res, NewZeroWrapNolimit(1, (mx+1) * 2 + (VpBaseFig() + 1))); + GUARD_OBJ(rr, NewZeroWrapNolimit(1, (mx+1) * 2 + (VpBaseFig() + 1))); + GUARD_OBJ(ff, NewZeroWrapNolimit(1, (mx+1) * 2 + (VpBaseFig() + 1))); + + VpDivd(c, res, a, b); + + mx = c->Prec *(VpBaseFig() + 1); + + GUARD_OBJ(d, NewZeroWrapLimited(1, mx)); + GUARD_OBJ(f, NewZeroWrapLimited(1, mx)); + + VpActiveRound(d, c, VP_ROUND_DOWN, 0); /* 0: round off */ + + VpFrac(f, c); + VpMult(rr, f, b); + VpAddSub(ff, res, rr, 1); + + *dv = d; + *rv = ff; + return Qnil; +} + +/* call-seq: + * remainder(value) + * + * Returns the remainder from dividing by the value. + * + * x.remainder(y) means x-y*(x/y).truncate + */ +static VALUE +BigDecimal_remainder(VALUE self, VALUE r) /* remainder */ +{ + VALUE f; + Real *d, *rv = 0; + f = BigDecimal_divremain(self, r, &d, &rv); + if (!NIL_P(f)) return f; + return VpCheckGetValue(rv); +} + +/* call-seq: + * divmod(value) + * + * Divides by the specified value, and returns the quotient and modulus + * as BigDecimal numbers. The quotient is rounded towards negative infinity. + * + * For example: + * + * require 'bigdecimal' + * + * a = BigDecimal("42") + * b = BigDecimal("9") + * + * q, m = a.divmod(b) + * + * c = q * b + m + * + * a == c #=> true + * + * The quotient q is (a/b).floor, and the modulus is the amount that must be + * added to q * b to get a. + */ +static VALUE +BigDecimal_divmod(VALUE self, VALUE r) +{ + ENTER(5); + Real *div = NULL, *mod = NULL; + + if (BigDecimal_DoDivmod(self, r, &div, &mod)) { + SAVE(div); SAVE(mod); + return rb_assoc_new(VpCheckGetValue(div), VpCheckGetValue(mod)); + } + return DoSomeOne(self,r,rb_intern("divmod")); +} + +/* + * Do the same manner as Float#div when n is nil. + * Do the same manner as BigDecimal#quo when n is 0. + */ +static inline VALUE +BigDecimal_div2(VALUE self, VALUE b, VALUE n) +{ + ENTER(5); + SIGNED_VALUE ix; + Real *res = NULL; + Real *av = NULL, *bv = NULL, *cv = NULL; + size_t mx, pl; + + if (NIL_P(n)) { /* div in Float sense */ + Real *div = NULL; + Real *mod; + if (BigDecimal_DoDivmod(self, b, &div, &mod)) { + return BigDecimal_to_i(VpCheckGetValue(div)); + } + return DoSomeOne(self, b, rb_intern("div")); + } + + /* div in BigDecimal sense */ + ix = check_int_precision(n); + + pl = VpSetPrecLimit(0); + if (ix == 0) ix = pl; + + GUARD_OBJ(av, GetVpValue(self, 1)); + if (RB_FLOAT_TYPE_P(b) && ix > BIGDECIMAL_DOUBLE_FIGURES) { + /* TODO: I want to refactor this precision control for a float value later + * by introducing an implicit conversion function instead of + * GetVpValueWithPrec. */ + GUARD_OBJ(bv, GetVpValueWithPrec(b, BIGDECIMAL_DOUBLE_FIGURES, 1)); + } + else { + GUARD_OBJ(bv, GetVpValueWithPrec(b, ix, 1)); + } + + if (ix == 0) { + ssize_t a_prec, b_prec; + VpCountPrecisionAndScale(av, &a_prec, NULL); + VpCountPrecisionAndScale(bv, &b_prec, NULL); + ix = ((a_prec > b_prec) ? a_prec : b_prec) + BIGDECIMAL_DOUBLE_FIGURES; + if (2 * BIGDECIMAL_DOUBLE_FIGURES > ix) + ix = 2 * BIGDECIMAL_DOUBLE_FIGURES; + } + + // VpDivd needs 2 extra DECDIGs. One more is needed for rounding. + GUARD_OBJ(cv, NewZeroWrapLimited(1, ix + 3 * VpBaseFig())); + + mx = bv->Prec + cv->MaxPrec - 1; + if (mx <= av->Prec) mx = av->Prec + 1; + GUARD_OBJ(res, NewZeroWrapNolimit(1, mx * VpBaseFig())); + VpDivd(cv, res, av, bv); + VpSetPrecLimit(pl); + if (!VpIsZero(res)) { + // Remainder value affects rounding result. + // ROUND_UP cv = 0.1e0 with ix=10 will be: + // 0.1e0 if remainder == 0 + // 0.1000000001e0 if remainder != 0 + size_t idx = roomof(ix, BASE_FIG); + while (cv->Prec <= idx) cv->frac[cv->Prec++] = 0; + if (cv->frac[idx] == 0 || cv->frac[idx] == HALF_BASE) cv->frac[idx]++; + } + VpLeftRound(cv, VpGetRoundMode(), ix); + return VpCheckGetValue(cv); +} + + /* + * Document-method: BigDecimal#div + * + * call-seq: + * div(value) -> integer + * div(value, digits) -> bigdecimal or integer + * + * Divide by the specified value. + * + * digits:: If specified and less than the number of significant digits of the + * result, the result is rounded to that number of digits, according + * to BigDecimal.mode. + * + * If digits is 0, the result is the same as for the / operator + * or #quo. + * + * If digits is not specified, the result is an integer, + * by analogy with Float#div; see also BigDecimal#divmod. + * + * See BigDecimal#/. + * See BigDecimal#quo. + * + * Examples: + * + * a = BigDecimal("4") + * b = BigDecimal("3") + * + * a.div(b, 3) # => 0.133e1 + * + * a.div(b, 0) # => 0.1333333333333333333e1 + * a / b # => 0.1333333333333333333e1 + * a.quo(b) # => 0.1333333333333333333e1 + * + * a.div(b) # => 1 + */ +static VALUE +BigDecimal_div3(int argc, VALUE *argv, VALUE self) +{ + VALUE b,n; + + rb_scan_args(argc, argv, "11", &b, &n); + + return BigDecimal_div2(self, b, n); +} + + /* + * call-seq: + * add(value, ndigits) -> new_bigdecimal + * + * Returns the \BigDecimal sum of +self+ and +value+ + * with a precision of +ndigits+ decimal digits. + * + * When +ndigits+ is less than the number of significant digits + * in the sum, the sum is rounded to that number of digits, + * according to the current rounding mode; see BigDecimal.mode. + * + * Examples: + * + * # Set the rounding mode. + * BigDecimal.mode(BigDecimal::ROUND_MODE, :half_up) + * b = BigDecimal('111111.111') + * b.add(1, 0) # => 0.111112111e6 + * b.add(1, 3) # => 0.111e6 + * b.add(1, 6) # => 0.111112e6 + * b.add(1, 15) # => 0.111112111e6 + * b.add(1.0, 15) # => 0.111112111e6 + * b.add(Rational(1, 1), 15) # => 0.111112111e6 + * + */ + +static VALUE +BigDecimal_add2(VALUE self, VALUE b, VALUE n) +{ + ENTER(2); + Real *cv; + SIGNED_VALUE mx = check_int_precision(n); + if (mx == 0) return BigDecimal_add(self, b); + else { + size_t pl = VpSetPrecLimit(0); + VALUE c = BigDecimal_add(self, b); + VpSetPrecLimit(pl); + GUARD_OBJ(cv, GetVpValue(c, 1)); + VpLeftRound(cv, VpGetRoundMode(), mx); + return VpCheckGetValue(cv); + } +} + +/* call-seq: + * sub(value, digits) -> bigdecimal + * + * Subtract the specified value. + * + * e.g. + * c = a.sub(b,n) + * + * digits:: If specified and less than the number of significant digits of the + * result, the result is rounded to that number of digits, according + * to BigDecimal.mode. + * + */ +static VALUE +BigDecimal_sub2(VALUE self, VALUE b, VALUE n) +{ + ENTER(2); + Real *cv; + SIGNED_VALUE mx = check_int_precision(n); + if (mx == 0) return BigDecimal_sub(self, b); + else { + size_t pl = VpSetPrecLimit(0); + VALUE c = BigDecimal_sub(self, b); + VpSetPrecLimit(pl); + GUARD_OBJ(cv, GetVpValue(c, 1)); + VpLeftRound(cv, VpGetRoundMode(), mx); + return VpCheckGetValue(cv); + } +} + + /* + * call-seq: + * mult(other, ndigits) -> bigdecimal + * + * Returns the \BigDecimal product of +self+ and +value+ + * with a precision of +ndigits+ decimal digits. + * + * When +ndigits+ is less than the number of significant digits + * in the sum, the sum is rounded to that number of digits, + * according to the current rounding mode; see BigDecimal.mode. + * + * Examples: + * + * # Set the rounding mode. + * BigDecimal.mode(BigDecimal::ROUND_MODE, :half_up) + * b = BigDecimal('555555.555') + * b.mult(3, 0) # => 0.1666666665e7 + * b.mult(3, 3) # => 0.167e7 + * b.mult(3, 6) # => 0.166667e7 + * b.mult(3, 15) # => 0.1666666665e7 + * b.mult(3.0, 0) # => 0.1666666665e7 + * b.mult(Rational(3, 1), 0) # => 0.1666666665e7 + * b.mult(Complex(3, 0), 0) # => (0.1666666665e7+0.0i) + * + */ + +static VALUE +BigDecimal_mult2(VALUE self, VALUE b, VALUE n) +{ + ENTER(2); + Real *cv; + SIGNED_VALUE mx = check_int_precision(n); + if (mx == 0) return BigDecimal_mult(self, b); + else { + size_t pl = VpSetPrecLimit(0); + VALUE c = BigDecimal_mult(self, b); + VpSetPrecLimit(pl); + GUARD_OBJ(cv, GetVpValue(c, 1)); + VpLeftRound(cv, VpGetRoundMode(), mx); + return VpCheckGetValue(cv); + } +} + +/* + * call-seq: + * abs -> bigdecimal + * + * Returns the \BigDecimal absolute value of +self+: + * + * BigDecimal('5').abs # => 0.5e1 + * BigDecimal('-3').abs # => 0.3e1 + * + */ + +static VALUE +BigDecimal_abs(VALUE self) +{ + ENTER(5); + Real *c, *a; + size_t mx; + + GUARD_OBJ(a, GetVpValue(self, 1)); + mx = a->Prec *(VpBaseFig() + 1); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); + VpAsgn(c, a, 1); + VpChangeSign(c, 1); + return VpCheckGetValue(c); +} + +/* call-seq: + * sqrt(n) + * + * Returns the square root of the value. + * + * Result has at least n significant digits. + */ +static VALUE +BigDecimal_sqrt(VALUE self, VALUE nFig) +{ + ENTER(5); + Real *c, *a; + size_t mx, n; + + GUARD_OBJ(a, GetVpValue(self, 1)); + mx = a->Prec * (VpBaseFig() + 1); + + n = check_int_precision(nFig); + n += VpDblFig() + VpBaseFig(); + if (mx <= n) mx = n; + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); + VpSqrt(c, a); + return VpCheckGetValue(c); +} + +/* Return the integer part of the number, as a BigDecimal. + */ +static VALUE +BigDecimal_fix(VALUE self) +{ + ENTER(5); + Real *c, *a; + size_t mx; + + GUARD_OBJ(a, GetVpValue(self, 1)); + mx = a->Prec *(VpBaseFig() + 1); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); + VpActiveRound(c, a, VP_ROUND_DOWN, 0); /* 0: round off */ + return VpCheckGetValue(c); +} + +/* call-seq: + * round(n, mode) + * + * Round to the nearest integer (by default), returning the result as a + * BigDecimal if n is specified and positive, or as an Integer if it isn't. + * + * BigDecimal('3.14159').round #=> 3 + * BigDecimal('8.7').round #=> 9 + * BigDecimal('-9.9').round #=> -10 + * + * BigDecimal('3.14159').round(2).class.name #=> "BigDecimal" + * BigDecimal('3.14159').round.class.name #=> "Integer" + * BigDecimal('3.14159').round(0).class.name #=> "Integer" + * + * If n is specified and positive, the fractional part of the result has no + * more than that many digits. + * + * If n is specified and negative, at least that many digits to the left of the + * decimal point will be 0 in the result, and return value will be an Integer. + * + * BigDecimal('3.14159').round(3) #=> 3.142 + * BigDecimal('13345.234').round(-2) #=> 13300 + * + * The value of the optional mode argument can be used to determine how + * rounding is performed; see BigDecimal.mode. + */ +static VALUE +BigDecimal_round(int argc, VALUE *argv, VALUE self) +{ + ENTER(5); + Real *c, *a; + int iLoc = 0; + VALUE vLoc; + VALUE vRound; + int round_to_int = 0; + size_t mx, pl; + + unsigned short sw = VpGetRoundMode(); + + switch (rb_scan_args(argc, argv, "02", &vLoc, &vRound)) { + case 0: + iLoc = 0; + round_to_int = 1; + break; + case 1: + if (RB_TYPE_P(vLoc, T_HASH)) { + sw = check_rounding_mode_option(vLoc); + } + else { + iLoc = NUM2INT(vLoc); + if (iLoc < 1) round_to_int = 1; + } + break; + case 2: + iLoc = NUM2INT(vLoc); + if (RB_TYPE_P(vRound, T_HASH)) { + sw = check_rounding_mode_option(vRound); + } + else { + sw = check_rounding_mode(vRound); + } + break; + default: + break; + } + + pl = VpSetPrecLimit(0); + GUARD_OBJ(a, GetVpValue(self, 1)); + mx = a->Prec * (VpBaseFig() + 1); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); + VpSetPrecLimit(pl); + VpActiveRound(c, a, sw, iLoc); + if (round_to_int) { + return BigDecimal_to_i(VpCheckGetValue(c)); + } + return VpCheckGetValue(c); +} + +/* call-seq: + * truncate(n) + * + * Truncate to the nearest integer (by default), returning the result as a + * BigDecimal. + * + * BigDecimal('3.14159').truncate #=> 3 + * BigDecimal('8.7').truncate #=> 8 + * BigDecimal('-9.9').truncate #=> -9 + * + * If n is specified and positive, the fractional part of the result has no + * more than that many digits. + * + * If n is specified and negative, at least that many digits to the left of the + * decimal point will be 0 in the result. + * + * BigDecimal('3.14159').truncate(3) #=> 3.141 + * BigDecimal('13345.234').truncate(-2) #=> 13300.0 + */ +static VALUE +BigDecimal_truncate(int argc, VALUE *argv, VALUE self) +{ + ENTER(5); + Real *c, *a; + int iLoc; + VALUE vLoc; + size_t mx, pl = VpSetPrecLimit(0); + + if (rb_scan_args(argc, argv, "01", &vLoc) == 0) { + iLoc = 0; + } + else { + iLoc = NUM2INT(vLoc); + } + + GUARD_OBJ(a, GetVpValue(self, 1)); + mx = a->Prec * (VpBaseFig() + 1); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); + VpSetPrecLimit(pl); + VpActiveRound(c, a, VP_ROUND_DOWN, iLoc); /* 0: truncate */ + if (argc == 0) { + return BigDecimal_to_i(VpCheckGetValue(c)); + } + return VpCheckGetValue(c); +} + +/* Return the fractional part of the number, as a BigDecimal. + */ +static VALUE +BigDecimal_frac(VALUE self) +{ + ENTER(5); + Real *c, *a; + size_t mx; + + GUARD_OBJ(a, GetVpValue(self, 1)); + mx = a->Prec * (VpBaseFig() + 1); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); + VpFrac(c, a); + return VpCheckGetValue(c); +} + +/* call-seq: + * floor(n) + * + * Return the largest integer less than or equal to the value, as a BigDecimal. + * + * BigDecimal('3.14159').floor #=> 3 + * BigDecimal('-9.1').floor #=> -10 + * + * If n is specified and positive, the fractional part of the result has no + * more than that many digits. + * + * If n is specified and negative, at least that + * many digits to the left of the decimal point will be 0 in the result. + * + * BigDecimal('3.14159').floor(3) #=> 3.141 + * BigDecimal('13345.234').floor(-2) #=> 13300.0 + */ +static VALUE +BigDecimal_floor(int argc, VALUE *argv, VALUE self) +{ + ENTER(5); + Real *c, *a; + int iLoc; + VALUE vLoc; + size_t mx, pl = VpSetPrecLimit(0); + + if (rb_scan_args(argc, argv, "01", &vLoc)==0) { + iLoc = 0; + } + else { + iLoc = NUM2INT(vLoc); + } + + GUARD_OBJ(a, GetVpValue(self, 1)); + mx = a->Prec * (VpBaseFig() + 1); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); + VpSetPrecLimit(pl); + VpActiveRound(c, a, VP_ROUND_FLOOR, iLoc); +#ifdef BIGDECIMAL_DEBUG + VPrint(stderr, "floor: c=%\n", c); +#endif + if (argc == 0) { + return BigDecimal_to_i(VpCheckGetValue(c)); + } + return VpCheckGetValue(c); +} + +/* call-seq: + * ceil(n) + * + * Return the smallest integer greater than or equal to the value, as a BigDecimal. + * + * BigDecimal('3.14159').ceil #=> 4 + * BigDecimal('-9.1').ceil #=> -9 + * + * If n is specified and positive, the fractional part of the result has no + * more than that many digits. + * + * If n is specified and negative, at least that + * many digits to the left of the decimal point will be 0 in the result. + * + * BigDecimal('3.14159').ceil(3) #=> 3.142 + * BigDecimal('13345.234').ceil(-2) #=> 13400.0 + */ +static VALUE +BigDecimal_ceil(int argc, VALUE *argv, VALUE self) +{ + ENTER(5); + Real *c, *a; + int iLoc; + VALUE vLoc; + size_t mx, pl = VpSetPrecLimit(0); + + if (rb_scan_args(argc, argv, "01", &vLoc) == 0) { + iLoc = 0; + } else { + iLoc = NUM2INT(vLoc); + } + + GUARD_OBJ(a, GetVpValue(self, 1)); + mx = a->Prec * (VpBaseFig() + 1); + GUARD_OBJ(c, NewZeroWrapLimited(1, mx)); + VpSetPrecLimit(pl); + VpActiveRound(c, a, VP_ROUND_CEIL, iLoc); + if (argc == 0) { + return BigDecimal_to_i(VpCheckGetValue(c)); + } + return VpCheckGetValue(c); +} + +/* call-seq: + * to_s(s) + * + * Converts the value to a string. + * + * The default format looks like 0.xxxxEnn. + * + * The optional parameter s consists of either an integer; or an optional '+' + * or ' ', followed by an optional number, followed by an optional 'E' or 'F'. + * + * If there is a '+' at the start of s, positive values are returned with + * a leading '+'. + * + * A space at the start of s returns positive values with a leading space. + * + * If s contains a number, a space is inserted after each group of that many + * digits, starting from '.' and counting outwards. + * + * If s ends with an 'E', engineering notation (0.xxxxEnn) is used. + * + * If s ends with an 'F', conventional floating point notation is used. + * + * Examples: + * + * BigDecimal('-1234567890123.45678901234567890').to_s('5F') + * #=> '-123 45678 90123.45678 90123 45678 9' + * + * BigDecimal('1234567890123.45678901234567890').to_s('+8F') + * #=> '+12345 67890123.45678901 23456789' + * + * BigDecimal('1234567890123.45678901234567890').to_s(' F') + * #=> ' 1234567890123.4567890123456789' + */ +static VALUE +BigDecimal_to_s(int argc, VALUE *argv, VALUE self) +{ + ENTER(5); + int fmt = 0; /* 0: E format, 1: F format */ + int fPlus = 0; /* 0: default, 1: set ' ' before digits, 2: set '+' before digits. */ + Real *vp; + volatile VALUE str; + char *psz; + char ch; + size_t nc, mc = 0; + SIGNED_VALUE m; + VALUE f; + + GUARD_OBJ(vp, GetVpValue(self, 1)); + + if (rb_scan_args(argc, argv, "01", &f) == 1) { + if (RB_TYPE_P(f, T_STRING)) { + psz = StringValueCStr(f); + if (*psz == ' ') { + fPlus = 1; + psz++; + } + else if (*psz == '+') { + fPlus = 2; + psz++; + } + while ((ch = *psz++) != 0) { + if (ISSPACE(ch)) { + continue; + } + if (!ISDIGIT(ch)) { + if (ch == 'F' || ch == 'f') { + fmt = 1; /* F format */ + } + break; + } + mc = mc*10 + ch - '0'; + } + } + else { + m = NUM2INT(f); + if (m <= 0) { + rb_raise(rb_eArgError, "argument must be positive"); + } + mc = (size_t)m; + } + } + if (fmt) { + nc = VpNumOfChars(vp, "F"); + } + else { + nc = VpNumOfChars(vp, "E"); + } + if (mc > 0) { + nc += (nc + mc - 1) / mc + 1; + } + + str = rb_usascii_str_new(0, nc); + psz = RSTRING_PTR(str); + + if (fmt) { + VpToFString(vp, psz, RSTRING_LEN(str), mc, fPlus); + } + else { + VpToString (vp, psz, RSTRING_LEN(str), mc, fPlus); + } + rb_str_resize(str, strlen(psz)); + return str; +} + +/* Splits a BigDecimal number into four parts, returned as an array of values. + * + * The first value represents the sign of the BigDecimal, and is -1 or 1, or 0 + * if the BigDecimal is Not a Number. + * + * The second value is a string representing the significant digits of the + * BigDecimal, with no leading zeros. + * + * The third value is the base used for arithmetic (currently always 10) as an + * Integer. + * + * The fourth value is an Integer exponent. + * + * If the BigDecimal can be represented as 0.xxxxxx*10**n, then xxxxxx is the + * string of significant digits with no leading zeros, and n is the exponent. + * + * From these values, you can translate a BigDecimal to a float as follows: + * + * sign, significant_digits, base, exponent = a.split + * f = sign * "0.#{significant_digits}".to_f * (base ** exponent) + * + * (Note that the to_f method is provided as a more convenient way to translate + * a BigDecimal to a Float.) + */ +static VALUE +BigDecimal_split(VALUE self) +{ + ENTER(5); + Real *vp; + VALUE obj,str; + ssize_t e, s; + char *psz1; + + GUARD_OBJ(vp, GetVpValue(self, 1)); + str = rb_str_new(0, VpNumOfChars(vp, "E")); + psz1 = RSTRING_PTR(str); + VpSzMantissa(vp, psz1, RSTRING_LEN(str)); + s = 1; + if(psz1[0] == '-') { + size_t len = strlen(psz1 + 1); + + memmove(psz1, psz1 + 1, len); + psz1[len] = '\0'; + s = -1; + } + if (psz1[0] == 'N') s = 0; /* NaN */ + e = VpExponent10(vp); + obj = rb_ary_new2(4); + rb_ary_push(obj, INT2FIX(s)); + rb_ary_push(obj, str); + rb_str_resize(str, strlen(psz1)); + rb_ary_push(obj, INT2FIX(10)); + rb_ary_push(obj, SSIZET2NUM(e)); + return obj; +} + +/* Returns the exponent of the BigDecimal number, as an Integer. + * + * If the number can be represented as 0.xxxxxx*10**n where xxxxxx is a string + * of digits with no leading zeros, then n is the exponent. + */ +static VALUE +BigDecimal_exponent(VALUE self) +{ + ssize_t e = VpExponent10(GetVpValue(self, 1)); + return SSIZET2NUM(e); +} + +/* Returns a string representation of self. + * + * BigDecimal("1234.5678").inspect + * #=> "0.12345678e4" + */ +static VALUE +BigDecimal_inspect(VALUE self) +{ + ENTER(5); + Real *vp; + volatile VALUE str; + size_t nc; + + GUARD_OBJ(vp, GetVpValue(self, 1)); + nc = VpNumOfChars(vp, "E"); + + str = rb_str_new(0, nc); + VpToString(vp, RSTRING_PTR(str), RSTRING_LEN(str), 0, 0); + rb_str_resize(str, strlen(RSTRING_PTR(str))); + return str; +} + +static VALUE BigMath_s_exp(VALUE, VALUE, VALUE); +static VALUE BigMath_s_log(VALUE, VALUE, VALUE); + +#define BigMath_exp(x, n) BigMath_s_exp(rb_mBigMath, (x), (n)) +#define BigMath_log(x, n) BigMath_s_log(rb_mBigMath, (x), (n)) + +inline static int +is_integer(VALUE x) +{ + return (RB_TYPE_P(x, T_FIXNUM) || RB_TYPE_P(x, T_BIGNUM)); +} + +inline static int +is_negative(VALUE x) +{ + if (FIXNUM_P(x)) { + return FIX2LONG(x) < 0; + } + else if (RB_TYPE_P(x, T_BIGNUM)) { + return FIX2INT(rb_big_cmp(x, INT2FIX(0))) < 0; + } + else if (RB_TYPE_P(x, T_FLOAT)) { + return RFLOAT_VALUE(x) < 0.0; + } + return RTEST(rb_funcall(x, '<', 1, INT2FIX(0))); +} + +#define is_positive(x) (!is_negative(x)) + +inline static int +is_zero(VALUE x) +{ + VALUE num; + + switch (TYPE(x)) { + case T_FIXNUM: + return FIX2LONG(x) == 0; + + case T_BIGNUM: + return Qfalse; + + case T_RATIONAL: + num = rb_rational_num(x); + return FIXNUM_P(num) && FIX2LONG(num) == 0; + + default: + break; + } + + return RTEST(rb_funcall(x, id_eq, 1, INT2FIX(0))); +} + +inline static int +is_one(VALUE x) +{ + VALUE num, den; + + switch (TYPE(x)) { + case T_FIXNUM: + return FIX2LONG(x) == 1; + + case T_BIGNUM: + return Qfalse; + + case T_RATIONAL: + num = rb_rational_num(x); + den = rb_rational_den(x); + return FIXNUM_P(den) && FIX2LONG(den) == 1 && + FIXNUM_P(num) && FIX2LONG(num) == 1; + + default: + break; + } + + return RTEST(rb_funcall(x, id_eq, 1, INT2FIX(1))); +} + +inline static int +is_even(VALUE x) +{ + switch (TYPE(x)) { + case T_FIXNUM: + return (FIX2LONG(x) % 2) == 0; + + case T_BIGNUM: + { + unsigned long l; + rb_big_pack(x, &l, 1); + return l % 2 == 0; + } + + default: + break; + } + + return 0; +} + +static VALUE +bigdecimal_power_by_bigdecimal(Real const* x, Real const* exp, ssize_t const n) +{ + VALUE log_x, multiplied, y; + volatile VALUE obj = exp->obj; + + if (VpIsZero(exp)) { + return VpCheckGetValue(NewOneWrapLimited(1, n)); + } + + log_x = BigMath_log(x->obj, SSIZET2NUM(n+1)); + multiplied = BigDecimal_mult2(exp->obj, log_x, SSIZET2NUM(n+1)); + y = BigMath_exp(multiplied, SSIZET2NUM(n)); + RB_GC_GUARD(obj); + + return y; +} + +/* call-seq: + * power(n) + * power(n, prec) + * + * Returns the value raised to the power of n. + * + * Note that n must be an Integer. + * + * Also available as the operator **. + */ +static VALUE +BigDecimal_power(int argc, VALUE*argv, VALUE self) +{ + ENTER(5); + VALUE vexp, prec; + Real* exp = NULL; + Real *x, *y; + ssize_t mp, ma, n; + SIGNED_VALUE int_exp; + double d; + + rb_scan_args(argc, argv, "11", &vexp, &prec); + + GUARD_OBJ(x, GetVpValue(self, 1)); + n = NIL_P(prec) ? (ssize_t)(x->Prec*VpBaseFig()) : NUM2SSIZET(prec); + + if (VpIsNaN(x)) { + y = NewZeroWrapLimited(1, n); + VpSetNaN(y); + RB_GC_GUARD(y->obj); + return VpCheckGetValue(y); + } + + retry: + switch (TYPE(vexp)) { + case T_FIXNUM: + break; + + case T_BIGNUM: + break; + + case T_FLOAT: + d = RFLOAT_VALUE(vexp); + if (d == round(d)) { + if (FIXABLE(d)) { + vexp = LONG2FIX((long)d); + } + else { + vexp = rb_dbl2big(d); + } + goto retry; + } + if (NIL_P(prec)) { + n += BIGDECIMAL_DOUBLE_FIGURES; + } + exp = GetVpValueWithPrec(vexp, 0, 1); + break; + + case T_RATIONAL: + if (is_zero(rb_rational_num(vexp))) { + if (is_positive(vexp)) { + vexp = INT2FIX(0); + goto retry; + } + } + else if (is_one(rb_rational_den(vexp))) { + vexp = rb_rational_num(vexp); + goto retry; + } + exp = GetVpValueWithPrec(vexp, n, 1); + if (NIL_P(prec)) { + n += n; + } + break; + + case T_DATA: + if (is_kind_of_BigDecimal(vexp)) { + VALUE zero = INT2FIX(0); + VALUE rounded = BigDecimal_round(1, &zero, vexp); + if (RTEST(BigDecimal_eq(vexp, rounded))) { + vexp = BigDecimal_to_i(vexp); + goto retry; + } + if (NIL_P(prec)) { + GUARD_OBJ(y, GetVpValue(vexp, 1)); + n += y->Prec*VpBaseFig(); + } + exp = DATA_PTR(vexp); + break; + } + /* fall through */ + default: + rb_raise(rb_eTypeError, + "wrong argument type %"PRIsVALUE" (expected scalar Numeric)", + RB_OBJ_CLASSNAME(vexp)); + } + + if (VpIsZero(x)) { + if (is_negative(vexp)) { + y = NewZeroWrapNolimit(1, n); + if (BIGDECIMAL_NEGATIVE_P(x)) { + if (is_integer(vexp)) { + if (is_even(vexp)) { + /* (-0) ** (-even_integer) -> Infinity */ + VpSetPosInf(y); + } + else { + /* (-0) ** (-odd_integer) -> -Infinity */ + VpSetNegInf(y); + } + } + else { + /* (-0) ** (-non_integer) -> Infinity */ + VpSetPosInf(y); + } + } + else { + /* (+0) ** (-num) -> Infinity */ + VpSetPosInf(y); + } + RB_GC_GUARD(y->obj); + return VpCheckGetValue(y); + } + else if (is_zero(vexp)) { + return VpCheckGetValue(NewOneWrapLimited(1, n)); + } + else { + return VpCheckGetValue(NewZeroWrapLimited(1, n)); + } + } + + if (is_zero(vexp)) { + return VpCheckGetValue(NewOneWrapLimited(1, n)); + } + else if (is_one(vexp)) { + return self; + } + + if (VpIsInf(x)) { + if (is_negative(vexp)) { + if (BIGDECIMAL_NEGATIVE_P(x)) { + if (is_integer(vexp)) { + if (is_even(vexp)) { + /* (-Infinity) ** (-even_integer) -> +0 */ + return VpCheckGetValue(NewZeroWrapLimited(1, n)); + } + else { + /* (-Infinity) ** (-odd_integer) -> -0 */ + return VpCheckGetValue(NewZeroWrapLimited(-1, n)); + } + } + else { + /* (-Infinity) ** (-non_integer) -> -0 */ + return VpCheckGetValue(NewZeroWrapLimited(-1, n)); + } + } + else { + return VpCheckGetValue(NewZeroWrapLimited(1, n)); + } + } + else { + y = NewZeroWrapLimited(1, n); + if (BIGDECIMAL_NEGATIVE_P(x)) { + if (is_integer(vexp)) { + if (is_even(vexp)) { + VpSetPosInf(y); + } + else { + VpSetNegInf(y); + } + } + else { + /* TODO: support complex */ + rb_raise(rb_eMathDomainError, + "a non-integral exponent for a negative base"); + } + } + else { + VpSetPosInf(y); + } + return VpCheckGetValue(y); + } + } + + if (exp != NULL) { + return bigdecimal_power_by_bigdecimal(x, exp, n); + } + else if (RB_TYPE_P(vexp, T_BIGNUM)) { + VALUE abs_value = BigDecimal_abs(self); + if (is_one(abs_value)) { + return VpCheckGetValue(NewOneWrapLimited(1, n)); + } + else if (RTEST(rb_funcall(abs_value, '<', 1, INT2FIX(1)))) { + if (is_negative(vexp)) { + y = NewZeroWrapLimited(1, n); + VpSetInf(y, (is_even(vexp) ? 1 : -1) * VpGetSign(x)); + return VpCheckGetValue(y); + } + else if (BIGDECIMAL_NEGATIVE_P(x) && is_even(vexp)) { + return VpCheckGetValue(NewZeroWrapLimited(-1, n)); + } + else { + return VpCheckGetValue(NewZeroWrapLimited(1, n)); + } + } + else { + if (is_positive(vexp)) { + y = NewZeroWrapLimited(1, n); + VpSetInf(y, (is_even(vexp) ? 1 : -1) * VpGetSign(x)); + return VpCheckGetValue(y); + } + else if (BIGDECIMAL_NEGATIVE_P(x) && is_even(vexp)) { + return VpCheckGetValue(NewZeroWrapLimited(-1, n)); + } + else { + return VpCheckGetValue(NewZeroWrapLimited(1, n)); + } + } + } + + int_exp = FIX2LONG(vexp); + ma = int_exp; + if (ma < 0) ma = -ma; + if (ma == 0) ma = 1; + + if (VpIsDef(x)) { + mp = x->Prec * (VpBaseFig() + 1); + GUARD_OBJ(y, NewZeroWrapLimited(1, mp * (ma + 1))); + } + else { + GUARD_OBJ(y, NewZeroWrapLimited(1, 1)); + } + VpPowerByInt(y, x, int_exp); + if (!NIL_P(prec) && VpIsDef(y)) { + VpMidRound(y, VpGetRoundMode(), n); + } + return VpCheckGetValue(y); +} + +/* call-seq: + * self ** other -> bigdecimal + * + * Returns the \BigDecimal value of +self+ raised to power +other+: + * + * b = BigDecimal('3.14') + * b ** 2 # => 0.98596e1 + * b ** 2.0 # => 0.98596e1 + * b ** Rational(2, 1) # => 0.98596e1 + * + * Related: BigDecimal#power. + * + */ +static VALUE +BigDecimal_power_op(VALUE self, VALUE exp) +{ + return BigDecimal_power(1, &exp, self); +} + +/* :nodoc: + * + * private method for dup and clone the provided BigDecimal +other+ + */ +static VALUE +BigDecimal_initialize_copy(VALUE self, VALUE other) +{ + Real *pv = rb_check_typeddata(self, &BigDecimal_data_type); + Real *x = rb_check_typeddata(other, &BigDecimal_data_type); + + if (self != other) { + DATA_PTR(self) = VpCopy(pv, x); + } + return self; +} + +/* :nodoc: */ +static VALUE +BigDecimal_clone(VALUE self) +{ + return self; +} + +#ifdef HAVE_RB_OPTS_EXCEPTION_P +int rb_opts_exception_p(VALUE opts, int default_value); +#define opts_exception_p(opts) rb_opts_exception_p((opts), 1) +#else +static int +opts_exception_p(VALUE opts) +{ + static ID kwds[1]; + VALUE exception; + if (!kwds[0]) { + kwds[0] = rb_intern_const("exception"); + } + if (!rb_get_kwargs(opts, kwds, 0, 1, &exception)) return 1; + switch (exception) { + case Qtrue: case Qfalse: + break; + default: + rb_raise(rb_eArgError, "true or false is expected as exception: %+"PRIsVALUE, + exception); + } + return exception != Qfalse; +} +#endif + +static VALUE +check_exception(VALUE bd) +{ + assert(is_kind_of_BigDecimal(bd)); + + Real *vp; + TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp); + VpCheckGetValue(vp); /* VpCheckGetValue performs exception check */ + + return bd; +} + +static VALUE +rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int raise_exception) +{ + VALUE obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); + + Real *vp; + if (uval == 0) { + vp = rbd_allocate_struct(1); + vp->MaxPrec = 1; + vp->Prec = 1; + vp->exponent = 1; + VpSetZero(vp, 1); + vp->frac[0] = 0; + } + else if (uval < BASE) { + vp = rbd_allocate_struct(1); + vp->MaxPrec = 1; + vp->Prec = 1; + vp->exponent = 1; + VpSetSign(vp, 1); + vp->frac[0] = (DECDIG)uval; + } + else { + DECDIG buf[BIGDECIMAL_INT64_MAX_LENGTH] = {0,}; + DECDIG r = uval % BASE; + size_t len = 0, ntz = 0; + if (r == 0) { + // Count and skip trailing zeros + for (; r == 0 && uval > 0; ++ntz) { + uval /= BASE; + r = uval % BASE; + } + } + for (; uval > 0; ++len) { + // Store digits + buf[BIGDECIMAL_INT64_MAX_LENGTH - len - 1] = r; + uval /= BASE; + r = uval % BASE; + } + + const size_t exp = len + ntz; + vp = rbd_allocate_struct(len); + vp->MaxPrec = len; + vp->Prec = len; + vp->exponent = exp; + VpSetSign(vp, 1); + MEMCPY(vp->frac, buf + BIGDECIMAL_INT64_MAX_LENGTH - len, DECDIG, len); + } + + return BigDecimal_wrap_struct(obj, vp); +} + +static VALUE +rb_int64_convert_to_BigDecimal(int64_t ival, size_t digs, int raise_exception) +{ + const uint64_t uval = (ival < 0) ? (((uint64_t)-(ival+1))+1) : (uint64_t)ival; + VALUE bd = rb_uint64_convert_to_BigDecimal(uval, digs, raise_exception); + if (ival < 0) { + Real *vp; + TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp); + VpSetSign(vp, -1); + } + return bd; +} + +static VALUE +rb_big_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_exception) +{ + assert(RB_TYPE_P(val, T_BIGNUM)); + + int leading_zeros; + size_t size = rb_absint_size(val, &leading_zeros); + int sign = FIX2INT(rb_big_cmp(val, INT2FIX(0))); + if (sign < 0 && leading_zeros == 0) { + size += 1; + } + if (size <= sizeof(long)) { + if (sign < 0) { + return rb_int64_convert_to_BigDecimal(NUM2LONG(val), digs, raise_exception); + } + else { + return rb_uint64_convert_to_BigDecimal(NUM2ULONG(val), digs, raise_exception); + } + } +#if defined(SIZEOF_LONG_LONG) && SIZEOF_LONG < SIZEOF_LONG_LONG + else if (size <= sizeof(LONG_LONG)) { + if (sign < 0) { + return rb_int64_convert_to_BigDecimal(NUM2LL(val), digs, raise_exception); + } + else { + return rb_uint64_convert_to_BigDecimal(NUM2ULL(val), digs, raise_exception); + } + } +#endif + else { + VALUE str = rb_big2str(val, 10); + Real *vp = VpCreateRbObject(RSTRING_LEN(str) + BASE_FIG + 1, + RSTRING_PTR(str), true); + RB_GC_GUARD(str); + return check_exception(vp->obj); + } +} + +static VALUE +rb_inum_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_exception) +{ + assert(RB_INTEGER_TYPE_P(val)); + if (FIXNUM_P(val)) { + return rb_int64_convert_to_BigDecimal(FIX2LONG(val), digs, raise_exception); + } + else { + return rb_big_convert_to_BigDecimal(val, digs, raise_exception); + } +} + +static VALUE +rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) +{ + assert(RB_FLOAT_TYPE_P(val)); + + double d = RFLOAT_VALUE(val); + + if (isnan(d)) { + VALUE obj = BigDecimal_nan(); + return check_exception(obj); + } + else if (isinf(d)) { + VALUE obj; + if (d > 0) { + obj = BigDecimal_positive_infinity(); + } + else { + obj = BigDecimal_negative_infinity(); + } + return check_exception(obj); + } + else if (d == 0.0) { + if (1/d < 0.0) { + return BigDecimal_negative_zero(); + } + else { + return BigDecimal_positive_zero(); + } + } + + if (digs == SIZE_MAX) { + if (!raise_exception) + return Qnil; + rb_raise(rb_eArgError, + "can't omit precision for a %"PRIsVALUE".", + CLASS_OF(val)); + } + else if (digs > BIGDECIMAL_DOUBLE_FIGURES) { + if (!raise_exception) + return Qnil; + rb_raise(rb_eArgError, "precision too large."); + } + + /* Use the same logic in flo_to_s to convert a float to a decimal string */ + char buf[BIGDECIMAL_DOUBLE_FIGURES + BASE_FIG + 2 + 1]; /* sizeof(buf) == 28 in the typical case */ + int decpt, negative_p; + char *e; + const int mode = digs == 0 ? 0 : 2; + char *p = BigDecimal_dtoa(d, mode, (int)digs, &decpt, &negative_p, &e); + int len10 = (int)(e - p); + if (len10 > BIGDECIMAL_DOUBLE_FIGURES) { + /* TODO: Presumably, rounding should be done here. */ + len10 = BIGDECIMAL_DOUBLE_FIGURES; + } + memcpy(buf, p, len10); + xfree(p); + + VALUE inum; + size_t RB_UNUSED_VAR(prec) = 0; + SIGNED_VALUE exp = 0; + if (decpt > 0) { + if (decpt < len10) { + /* + * len10 |---------------| + * : |-------| frac_len10 = len10 - decpt + * decpt |-------| |--| ntz10 = BASE_FIG - frac_len10 % BASE_FIG + * : : : + * 00 dd dddd.dddd dd 00 + * prec |-----.----.----.-----| prec = exp + roomof(frac_len, BASE_FIG) + * exp |-----.----| exp = roomof(decpt, BASE_FIG) + */ + const size_t frac_len10 = len10 - decpt; + const size_t ntz10 = BASE_FIG - frac_len10 % BASE_FIG; + memset(buf + len10, '0', ntz10); + buf[len10 + ntz10] = '\0'; + inum = rb_cstr_to_inum(buf, 10, false); + + exp = roomof(decpt, BASE_FIG); + prec = exp + roomof(frac_len10, BASE_FIG); + } + else { + /* + * decpt |-----------------------| + * len10 |----------| : + * : |------------| exp10 + * : : : + * 00 dd dddd dd 00 0000 0000.0 + * : : : : + * : |--| ntz10 = exp10 % BASE_FIG + * prec |-----.----.-----| : + * : |----.----| exp10 / BASE_FIG + * exp |-----.----.-----.----.----| + */ + const size_t exp10 = decpt - len10; + const size_t ntz10 = exp10 % BASE_FIG; + + memset(buf + len10, '0', ntz10); + buf[len10 + ntz10] = '\0'; + inum = rb_cstr_to_inum(buf, 10, false); + + prec = roomof(len10 + ntz10, BASE_FIG); + exp = prec + exp10 / BASE_FIG; + } + } + else if (decpt == 0) { + /* + * len10 |------------| + * : : + * 0.dddd dddd dd 00 + * : : : + * : |--| ntz10 = prec * BASE_FIG - len10 + * prec |----.----.-----| roomof(len10, BASE_FIG) + */ + prec = roomof(len10, BASE_FIG); + const size_t ntz10 = prec * BASE_FIG - len10; + + memset(buf + len10, '0', ntz10); + buf[len10 + ntz10] = '\0'; + inum = rb_cstr_to_inum(buf, 10, false); + } + else { + /* + * len10 |---------------| + * : : + * decpt |-------| |--| ntz10 = prec * BASE_FIG - nlz10 - len10 + * : : : + * 0.0000 00 dd dddd dddd dd 00 + * : : : + * nlz10 |--| : decpt % BASE_FIG + * prec |-----.----.----.-----| roomof(decpt + len10, BASE_FIG) - exp + * exp |----| decpt / BASE_FIG + */ + decpt = -decpt; + + const size_t nlz10 = decpt % BASE_FIG; + exp = decpt / BASE_FIG; + prec = roomof(decpt + len10, BASE_FIG) - exp; + const size_t ntz10 = prec * BASE_FIG - nlz10 - len10; + + if (nlz10 > 0) { + memmove(buf + nlz10, buf, len10); + memset(buf, '0', nlz10); + } + memset(buf + nlz10 + len10, '0', ntz10); + buf[nlz10 + len10 + ntz10] = '\0'; + inum = rb_cstr_to_inum(buf, 10, false); + + exp = -exp; + } + + VALUE bd = rb_inum_convert_to_BigDecimal(inum, SIZE_MAX, raise_exception); + Real *vp; + TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp); + assert(vp->Prec == prec); + vp->exponent = exp; + + if (negative_p) VpSetSign(vp, -1); + return bd; +} + +static VALUE +rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) +{ + assert(RB_TYPE_P(val, T_RATIONAL)); + + if (digs == SIZE_MAX) { + if (!raise_exception) + return Qnil; + rb_raise(rb_eArgError, + "can't omit precision for a %"PRIsVALUE".", + CLASS_OF(val)); + } + + VALUE num = rb_inum_convert_to_BigDecimal(rb_rational_num(val), 0, raise_exception); + VALUE d = BigDecimal_div2(num, rb_rational_den(val), SIZET2NUM(digs)); + return d; +} + +static VALUE +rb_cstr_convert_to_BigDecimal(const char *c_str, size_t digs, int raise_exception) +{ + if (digs == SIZE_MAX) + digs = 0; + + Real *vp = VpCreateRbObject(digs, c_str, raise_exception); + if (!vp) + return Qnil; + return VpCheckGetValue(vp); +} + +static inline VALUE +rb_str_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) +{ + const char *c_str = StringValueCStr(val); + return rb_cstr_convert_to_BigDecimal(c_str, digs, raise_exception); +} + +static VALUE +rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) +{ + switch (val) { + case Qnil: + case Qtrue: + case Qfalse: + if (raise_exception) { + const char *cname = NIL_P(val) ? "nil" : + val == Qtrue ? "true" : + val == Qfalse ? "false" : + NULL; + rb_raise(rb_eTypeError, + "can't convert %s into BigDecimal", cname); + } + return Qnil; + + default: + break; + } + + if (is_kind_of_BigDecimal(val)) { + if (digs == SIZE_MAX) + return check_exception(val); + + Real *vp; + TypedData_Get_Struct(val, Real, &BigDecimal_data_type, vp); + + VALUE copy = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); + vp = VpCopy(NULL, vp); + /* TODO: rounding */ + BigDecimal_wrap_struct(copy, vp); + return VpCheckGetValue(vp); + } + else if (RB_INTEGER_TYPE_P(val)) { + return rb_inum_convert_to_BigDecimal(val, digs, raise_exception); + } + else if (RB_FLOAT_TYPE_P(val)) { + return rb_float_convert_to_BigDecimal(val, digs, raise_exception); + } + else if (RB_TYPE_P(val, T_RATIONAL)) { + return rb_rational_convert_to_BigDecimal(val, digs, raise_exception); + } + else if (RB_TYPE_P(val, T_COMPLEX)) { + VALUE im = rb_complex_imag(val); + if (!is_zero(im)) { + /* TODO: handle raise_exception */ + rb_raise(rb_eArgError, + "Unable to make a BigDecimal from non-zero imaginary number"); + } + return rb_convert_to_BigDecimal(rb_complex_real(val), digs, raise_exception); + } + else if (RB_TYPE_P(val, T_STRING)) { + return rb_str_convert_to_BigDecimal(val, digs, raise_exception); + } + + /* TODO: chheck to_d */ + /* TODO: chheck to_int */ + + VALUE str = rb_check_convert_type(val, T_STRING, "String", "to_str"); + if (!RB_TYPE_P(str, T_STRING)) { + if (raise_exception) { + rb_raise(rb_eTypeError, + "can't convert %"PRIsVALUE" into BigDecimal", rb_obj_class(val)); + } + return Qnil; + } + return rb_str_convert_to_BigDecimal(str, digs, raise_exception); +} + +/* call-seq: + * BigDecimal(value, exception: true) -> bigdecimal + * BigDecimal(value, ndigits, exception: true) -> bigdecimal + * + * Returns the \BigDecimal converted from +value+ + * with a precision of +ndigits+ decimal digits. + * + * When +ndigits+ is less than the number of significant digits + * in the value, the result is rounded to that number of digits, + * according to the current rounding mode; see BigDecimal.mode. + * + * When +ndigits+ is 0, the number of digits to correctly represent a float number + * is determined automatically. + * + * Returns +value+ converted to a \BigDecimal, depending on the type of +value+: + * + * - Integer, Float, Rational, Complex, or BigDecimal: converted directly: + * + * # Integer, Complex, or BigDecimal value does not require ndigits; ignored if given. + * BigDecimal(2) # => 0.2e1 + * BigDecimal(Complex(2, 0)) # => 0.2e1 + * BigDecimal(BigDecimal(2)) # => 0.2e1 + * # Float or Rational value requires ndigits. + * BigDecimal(2.0, 0) # => 0.2e1 + * BigDecimal(Rational(2, 1), 0) # => 0.2e1 + * + * - String: converted by parsing if it contains an integer or floating-point literal; + * leading and trailing whitespace is ignored: + * + * # String does not require ndigits; ignored if given. + * BigDecimal('2') # => 0.2e1 + * BigDecimal('2.0') # => 0.2e1 + * BigDecimal('0.2e1') # => 0.2e1 + * BigDecimal(' 2.0 ') # => 0.2e1 + * + * - Other type that responds to method :to_str: + * first converted to a string, then converted to a \BigDecimal, as above. + * + * - Other type: + * + * - Raises an exception if keyword argument +exception+ is +true+. + * - Returns +nil+ if keyword argument +exception+ is +false+. + * + * Raises an exception if +value+ evaluates to a Float + * and +digits+ is larger than Float::DIG + 1. + * + */ +static VALUE +f_BigDecimal(int argc, VALUE *argv, VALUE self) +{ + VALUE val, digs_v, opts = Qnil; + argc = rb_scan_args(argc, argv, "11:", &val, &digs_v, &opts); + int exception = opts_exception_p(opts); + + size_t digs = SIZE_MAX; /* this means digs is omitted */ + if (argc > 1) { + digs_v = rb_to_int(digs_v); + if (FIXNUM_P(digs_v)) { + long n = FIX2LONG(digs_v); + if (n < 0) + goto negative_digs; + digs = (size_t)n; + } + else { + if (RBIGNUM_NEGATIVE_P(digs_v)) { + negative_digs: + if (!exception) + return Qnil; + rb_raise(rb_eArgError, "negative precision"); + } + digs = NUM2SIZET(digs_v); + } + } + + return rb_convert_to_BigDecimal(val, digs, exception); +} + +/* call-seq: + * BigDecimal.interpret_loosely(string) -> bigdecimal + * + * Returns the +BigDecimal+ converted loosely from +string+. + */ + +static VALUE +BigDecimal_s_interpret_loosely(VALUE klass, VALUE str) +{ + char const *c_str = StringValueCStr(str); + Real *vp = VpNewRbClass(0, c_str, klass, false, true); + if (!vp) + return Qnil; + else + return VpCheckGetValue(vp); +} + + /* + * call-seq: + * BigDecimal.limit(digits) + * + * Limit the number of significant digits in newly created BigDecimal + * numbers to the specified value. Rounding is performed as necessary, + * as specified by BigDecimal.mode. + * + * A limit of 0, the default, means no upper limit. + * + * The limit specified by this method takes less priority over any limit + * specified to instance methods such as ceil, floor, truncate, or round. + */ +static VALUE +BigDecimal_limit(int argc, VALUE *argv, VALUE self) +{ + VALUE nFig; + VALUE nCur = SIZET2NUM(VpGetPrecLimit()); + + if (rb_scan_args(argc, argv, "01", &nFig) == 1) { + int nf; + if (NIL_P(nFig)) return nCur; + nf = NUM2INT(nFig); + if (nf < 0) { + rb_raise(rb_eArgError, "argument must be positive"); + } + VpSetPrecLimit(nf); + } + return nCur; +} + +/* Returns the sign of the value. + * + * Returns a positive value if > 0, a negative value if < 0. + * It behaves the same with zeros - + * it returns a positive value for a positive zero (BigDecimal('0')) and + * a negative value for a negative zero (BigDecimal('-0')). + * + * The specific value returned indicates the type and sign of the BigDecimal, + * as follows: + * + * BigDecimal::SIGN_NaN:: value is Not a Number + * BigDecimal::SIGN_POSITIVE_ZERO:: value is +0 + * BigDecimal::SIGN_NEGATIVE_ZERO:: value is -0 + * BigDecimal::SIGN_POSITIVE_INFINITE:: value is +Infinity + * BigDecimal::SIGN_NEGATIVE_INFINITE:: value is -Infinity + * BigDecimal::SIGN_POSITIVE_FINITE:: value is positive + * BigDecimal::SIGN_NEGATIVE_FINITE:: value is negative + */ +static VALUE +BigDecimal_sign(VALUE self) +{ /* sign */ + int s = GetVpValue(self, 1)->sign; + return INT2FIX(s); +} + +/* + * call-seq: BigDecimal.save_exception_mode { ... } + * + * Execute the provided block, but preserve the exception mode + * + * BigDecimal.save_exception_mode do + * BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) + * BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) + * + * BigDecimal(BigDecimal('Infinity')) + * BigDecimal(BigDecimal('-Infinity')) + * BigDecimal(BigDecimal('NaN')) + * end + * + * For use with the BigDecimal::EXCEPTION_* + * + * See BigDecimal.mode + */ +static VALUE +BigDecimal_save_exception_mode(VALUE self) +{ + unsigned short const exception_mode = VpGetException(); + int state; + VALUE ret = rb_protect(rb_yield, Qnil, &state); + VpSetException(exception_mode); + if (state) rb_jump_tag(state); + return ret; +} + +/* + * call-seq: BigDecimal.save_rounding_mode { ... } + * + * Execute the provided block, but preserve the rounding mode + * + * BigDecimal.save_rounding_mode do + * BigDecimal.mode(BigDecimal::ROUND_MODE, :up) + * puts BigDecimal.mode(BigDecimal::ROUND_MODE) + * end + * + * For use with the BigDecimal::ROUND_* + * + * See BigDecimal.mode + */ +static VALUE +BigDecimal_save_rounding_mode(VALUE self) +{ + unsigned short const round_mode = VpGetRoundMode(); + int state; + VALUE ret = rb_protect(rb_yield, Qnil, &state); + VpSetRoundMode(round_mode); + if (state) rb_jump_tag(state); + return ret; +} + +/* + * call-seq: BigDecimal.save_limit { ... } + * + * Execute the provided block, but preserve the precision limit + * + * BigDecimal.limit(100) + * puts BigDecimal.limit + * BigDecimal.save_limit do + * BigDecimal.limit(200) + * puts BigDecimal.limit + * end + * puts BigDecimal.limit + * + */ +static VALUE +BigDecimal_save_limit(VALUE self) +{ + size_t const limit = VpGetPrecLimit(); + int state; + VALUE ret = rb_protect(rb_yield, Qnil, &state); + VpSetPrecLimit(limit); + if (state) rb_jump_tag(state); + return ret; +} + +/* call-seq: + * BigMath.exp(decimal, numeric) -> BigDecimal + * + * Computes the value of e (the base of natural logarithms) raised to the + * power of +decimal+, to the specified number of digits of precision. + * + * If +decimal+ is infinity, returns Infinity. + * + * If +decimal+ is NaN, returns NaN. + */ +static VALUE +BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) +{ + ssize_t prec, n, i; + Real* vx = NULL; + VALUE one, d, y; + int negative = 0; + int infinite = 0; + int nan = 0; + double flo; + + prec = NUM2SSIZET(vprec); + if (prec <= 0) { + rb_raise(rb_eArgError, "Zero or negative precision for exp"); + } + + /* TODO: the following switch statement is almost same as one in the + * BigDecimalCmp function. */ + switch (TYPE(x)) { + case T_DATA: + if (!is_kind_of_BigDecimal(x)) break; + vx = DATA_PTR(x); + negative = BIGDECIMAL_NEGATIVE_P(vx); + infinite = VpIsPosInf(vx) || VpIsNegInf(vx); + nan = VpIsNaN(vx); + break; + + case T_FIXNUM: + /* fall through */ + case T_BIGNUM: + vx = GetVpValue(x, 0); + break; + + case T_FLOAT: + flo = RFLOAT_VALUE(x); + negative = flo < 0; + infinite = isinf(flo); + nan = isnan(flo); + if (!infinite && !nan) { + vx = GetVpValueWithPrec(x, 0, 0); + } + break; + + case T_RATIONAL: + vx = GetVpValueWithPrec(x, prec, 0); + break; + + default: + break; + } + if (infinite) { + if (negative) { + return VpCheckGetValue(GetVpValueWithPrec(INT2FIX(0), prec, 1)); + } + else { + Real* vy = NewZeroWrapNolimit(1, prec); + VpSetInf(vy, VP_SIGN_POSITIVE_INFINITE); + RB_GC_GUARD(vy->obj); + return VpCheckGetValue(vy); + } + } + else if (nan) { + Real* vy = NewZeroWrapNolimit(1, prec); + VpSetNaN(vy); + RB_GC_GUARD(vy->obj); + return VpCheckGetValue(vy); + } + else if (vx == NULL) { + cannot_be_coerced_into_BigDecimal(rb_eArgError, x); + } + x = vx->obj; + + n = prec + BIGDECIMAL_DOUBLE_FIGURES; + negative = BIGDECIMAL_NEGATIVE_P(vx); + if (negative) { + VALUE x_zero = INT2NUM(1); + VALUE x_copy = f_BigDecimal(1, &x_zero, klass); + x = BigDecimal_initialize_copy(x_copy, x); + vx = DATA_PTR(x); + VpSetSign(vx, 1); + } + + one = VpCheckGetValue(NewOneWrapLimited(1, 1)); + y = one; + d = y; + i = 1; + + while (!VpIsZero((Real*)DATA_PTR(d))) { + SIGNED_VALUE const ey = VpExponent10(DATA_PTR(y)); + SIGNED_VALUE const ed = VpExponent10(DATA_PTR(d)); + ssize_t m = n - vabs(ey - ed); + + rb_thread_check_ints(); + + if (m <= 0) { + break; + } + else if ((size_t)m < BIGDECIMAL_DOUBLE_FIGURES) { + m = BIGDECIMAL_DOUBLE_FIGURES; + } + + d = BigDecimal_mult(d, x); /* d <- d * x */ + d = BigDecimal_div2(d, SSIZET2NUM(i), SSIZET2NUM(m)); /* d <- d / i */ + y = BigDecimal_add(y, d); /* y <- y + d */ + ++i; /* i <- i + 1 */ + } + + if (negative) { + return BigDecimal_div2(one, y, vprec); + } + else { + vprec = SSIZET2NUM(prec - VpExponent10(DATA_PTR(y))); + return BigDecimal_round(1, &vprec, y); + } + + RB_GC_GUARD(one); + RB_GC_GUARD(x); + RB_GC_GUARD(y); + RB_GC_GUARD(d); +} + +/* call-seq: + * BigMath.log(decimal, numeric) -> BigDecimal + * + * Computes the natural logarithm of +decimal+ to the specified number of + * digits of precision, +numeric+. + * + * If +decimal+ is zero or negative, raises Math::DomainError. + * + * If +decimal+ is positive infinity, returns Infinity. + * + * If +decimal+ is NaN, returns NaN. + */ +static VALUE +BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) +{ + ssize_t prec, n, i; + SIGNED_VALUE expo; + Real* vx = NULL; + VALUE vn, one, two, w, x2, y, d; + int zero = 0; + int negative = 0; + int infinite = 0; + int nan = 0; + double flo; + long fix; + + if (!is_integer(vprec)) { + rb_raise(rb_eArgError, "precision must be an Integer"); + } + + prec = NUM2SSIZET(vprec); + if (prec <= 0) { + rb_raise(rb_eArgError, "Zero or negative precision for exp"); + } + + /* TODO: the following switch statement is almost same as one in the + * BigDecimalCmp function. */ + switch (TYPE(x)) { + case T_DATA: + if (!is_kind_of_BigDecimal(x)) break; + vx = DATA_PTR(x); + zero = VpIsZero(vx); + negative = BIGDECIMAL_NEGATIVE_P(vx); + infinite = VpIsPosInf(vx) || VpIsNegInf(vx); + nan = VpIsNaN(vx); + break; + + case T_FIXNUM: + fix = FIX2LONG(x); + zero = fix == 0; + negative = fix < 0; + goto get_vp_value; + + case T_BIGNUM: + i = FIX2INT(rb_big_cmp(x, INT2FIX(0))); + zero = i == 0; + negative = i < 0; +get_vp_value: + if (zero || negative) break; + vx = GetVpValue(x, 0); + break; + + case T_FLOAT: + flo = RFLOAT_VALUE(x); + zero = flo == 0; + negative = flo < 0; + infinite = isinf(flo); + nan = isnan(flo); + if (!zero && !negative && !infinite && !nan) { + vx = GetVpValueWithPrec(x, 0, 1); + } + break; + + case T_RATIONAL: + zero = RRATIONAL_ZERO_P(x); + negative = RRATIONAL_NEGATIVE_P(x); + if (zero || negative) break; + vx = GetVpValueWithPrec(x, prec, 1); + break; + + case T_COMPLEX: + rb_raise(rb_eMathDomainError, + "Complex argument for BigMath.log"); + + default: + break; + } + if (infinite && !negative) { + Real *vy = NewZeroWrapNolimit(1, prec); + RB_GC_GUARD(vy->obj); + VpSetInf(vy, VP_SIGN_POSITIVE_INFINITE); + return VpCheckGetValue(vy); + } + else if (nan) { + Real* vy = NewZeroWrapNolimit(1, prec); + RB_GC_GUARD(vy->obj); + VpSetNaN(vy); + return VpCheckGetValue(vy); + } + else if (zero || negative) { + rb_raise(rb_eMathDomainError, + "Zero or negative argument for log"); + } + else if (vx == NULL) { + cannot_be_coerced_into_BigDecimal(rb_eArgError, x); + } + x = VpCheckGetValue(vx); + + one = VpCheckGetValue(NewOneWrapLimited(1, 1)); + two = VpCheckGetValue(VpCreateRbObject(1, "2", true)); + + n = prec + BIGDECIMAL_DOUBLE_FIGURES; + vn = SSIZET2NUM(n); + expo = VpExponent10(vx); + if (expo < 0 || expo >= 3) { + char buf[DECIMAL_SIZE_OF_BITS(SIZEOF_VALUE * CHAR_BIT) + 4]; + snprintf(buf, sizeof(buf), "1E%"PRIdVALUE, -expo); + x = BigDecimal_mult2(x, VpCheckGetValue(VpCreateRbObject(1, buf, true)), vn); + } + else { + expo = 0; + } + w = BigDecimal_sub(x, one); + x = BigDecimal_div2(w, BigDecimal_add(x, one), vn); + x2 = BigDecimal_mult2(x, x, vn); + y = x; + d = y; + i = 1; + while (!VpIsZero((Real*)DATA_PTR(d))) { + SIGNED_VALUE const ey = VpExponent10(DATA_PTR(y)); + SIGNED_VALUE const ed = VpExponent10(DATA_PTR(d)); + ssize_t m = n - vabs(ey - ed); + if (m <= 0) { + break; + } + else if ((size_t)m < BIGDECIMAL_DOUBLE_FIGURES) { + m = BIGDECIMAL_DOUBLE_FIGURES; + } + + x = BigDecimal_mult2(x2, x, vn); + i += 2; + d = BigDecimal_div2(x, SSIZET2NUM(i), SSIZET2NUM(m)); + y = BigDecimal_add(y, d); + } + + y = BigDecimal_mult(y, two); + if (expo != 0) { + VALUE log10, vexpo, dy; + log10 = BigMath_s_log(klass, INT2FIX(10), vprec); + vexpo = VpCheckGetValue(GetVpValue(SSIZET2NUM(expo), 1)); + dy = BigDecimal_mult(log10, vexpo); + y = BigDecimal_add(y, dy); + } + + RB_GC_GUARD(one); + RB_GC_GUARD(two); + RB_GC_GUARD(vn); + RB_GC_GUARD(x2); + RB_GC_GUARD(y); + RB_GC_GUARD(d); + + return y; +} + +static VALUE BIGDECIMAL_NAN = Qnil; + +static VALUE +BigDecimal_nan(void) +{ + return BIGDECIMAL_NAN; +} + +static VALUE BIGDECIMAL_POSITIVE_INFINITY = Qnil; + +static VALUE +BigDecimal_positive_infinity(void) +{ + return BIGDECIMAL_POSITIVE_INFINITY; +} + +static VALUE BIGDECIMAL_NEGATIVE_INFINITY = Qnil; + +static VALUE +BigDecimal_negative_infinity(void) +{ + return BIGDECIMAL_NEGATIVE_INFINITY; +} + +static VALUE BIGDECIMAL_POSITIVE_ZERO = Qnil; + +static VALUE +BigDecimal_positive_zero(void) +{ + return BIGDECIMAL_POSITIVE_ZERO; +} + +static VALUE BIGDECIMAL_NEGATIVE_ZERO = Qnil; + +static VALUE +BigDecimal_negative_zero(void) +{ + return BIGDECIMAL_NEGATIVE_ZERO; +} + +static inline VALUE +BigDecimal_literal(const char *str) +{ + VALUE arg = rb_str_new_cstr(str); + VALUE val = f_BigDecimal(1, &arg, rb_cBigDecimal); + rb_gc_register_mark_object(val); + return val; +} + +#define BIGDECIMAL_LITERAL(var, val) (BIGDECIMAL_ ## var = BigDecimal_literal(#val)) + +/* Document-class: BigDecimal + * BigDecimal provides arbitrary-precision floating point decimal arithmetic. + * + * == Introduction + * + * Ruby provides built-in support for arbitrary precision integer arithmetic. + * + * For example: + * + * 42**13 #=> 1265437718438866624512 + * + * BigDecimal provides similar support for very large or very accurate floating + * point numbers. + * + * Decimal arithmetic is also useful for general calculation, because it + * provides the correct answers people expect--whereas normal binary floating + * point arithmetic often introduces subtle errors because of the conversion + * between base 10 and base 2. + * + * For example, try: + * + * sum = 0 + * 10_000.times do + * sum = sum + 0.0001 + * end + * print sum #=> 0.9999999999999062 + * + * and contrast with the output from: + * + * require 'bigdecimal' + * + * sum = BigDecimal("0") + * 10_000.times do + * sum = sum + BigDecimal("0.0001") + * end + * print sum #=> 0.1E1 + * + * Similarly: + * + * (BigDecimal("1.2") - BigDecimal("1.0")) == BigDecimal("0.2") #=> true + * + * (1.2 - 1.0) == 0.2 #=> false + * + * == A Note About Precision + * + * For a calculation using a \BigDecimal and another +value+, + * the precision of the result depends on the type of +value+: + * + * - If +value+ is a \Float, + * the precision is Float::DIG + 1. + * - If +value+ is a \Rational, the precision is larger than Float::DIG + 1. + * - If +value+ is a \BigDecimal, the precision is +value+'s precision in the + * internal representation, which is platform-dependent. + * - If +value+ is other object, the precision is determined by the result of +BigDecimal(value)+. + * + * == Special features of accurate decimal arithmetic + * + * Because BigDecimal is more accurate than normal binary floating point + * arithmetic, it requires some special values. + * + * === Infinity + * + * BigDecimal sometimes needs to return infinity, for example if you divide + * a value by zero. + * + * BigDecimal("1.0") / BigDecimal("0.0") #=> Infinity + * BigDecimal("-1.0") / BigDecimal("0.0") #=> -Infinity + * + * You can represent infinite numbers to BigDecimal using the strings + * 'Infinity', '+Infinity' and + * '-Infinity' (case-sensitive) + * + * === Not a Number + * + * When a computation results in an undefined value, the special value +NaN+ + * (for 'not a number') is returned. + * + * Example: + * + * BigDecimal("0.0") / BigDecimal("0.0") #=> NaN + * + * You can also create undefined values. + * + * NaN is never considered to be the same as any other value, even NaN itself: + * + * n = BigDecimal('NaN') + * n == 0.0 #=> false + * n == n #=> false + * + * === Positive and negative zero + * + * If a computation results in a value which is too small to be represented as + * a BigDecimal within the currently specified limits of precision, zero must + * be returned. + * + * If the value which is too small to be represented is negative, a BigDecimal + * value of negative zero is returned. + * + * BigDecimal("1.0") / BigDecimal("-Infinity") #=> -0.0 + * + * If the value is positive, a value of positive zero is returned. + * + * BigDecimal("1.0") / BigDecimal("Infinity") #=> 0.0 + * + * (See BigDecimal.mode for how to specify limits of precision.) + * + * Note that +-0.0+ and +0.0+ are considered to be the same for the purposes of + * comparison. + * + * Note also that in mathematics, there is no particular concept of negative + * or positive zero; true mathematical zero has no sign. + * + * == bigdecimal/util + * + * When you require +bigdecimal/util+, the #to_d method will be + * available on BigDecimal and the native Integer, Float, Rational, + * and String classes: + * + * require 'bigdecimal/util' + * + * 42.to_d # => 0.42e2 + * 0.5.to_d # => 0.5e0 + * (2/3r).to_d(3) # => 0.667e0 + * "0.5".to_d # => 0.5e0 + * + * == Methods for Working with \JSON + * + * - {::json_create}[https://docs.ruby-lang.org/en/master/BigDecimal.html#method-c-json_create]: + * Returns a new \BigDecimal object constructed from the given object. + * - {#as_json}[https://docs.ruby-lang.org/en/master/BigDecimal.html#method-i-as_json]: + * Returns a 2-element hash representing +self+. + * - {#to_json}[https://docs.ruby-lang.org/en/master/BigDecimal.html#method-i-to_json]: + * Returns a \JSON string representing +self+. + * + * These methods are provided by the {JSON gem}[https://github.com/flori/json]. To make these methods available: + * + * require 'json/add/bigdecimal' + * + * * == License + * + * Copyright (C) 2002 by Shigeo Kobayashi . + * + * BigDecimal is released under the Ruby and 2-clause BSD licenses. + * See LICENSE.txt for details. + * + * Maintained by mrkn and ruby-core members. + * + * Documented by zzak , mathew , and + * many other contributors. + */ +void +Init_bigdecimal(void) +{ +#ifdef HAVE_RB_EXT_RACTOR_SAFE + rb_ext_ractor_safe(true); +#endif + + id_BigDecimal_exception_mode = rb_intern_const("BigDecimal.exception_mode"); + id_BigDecimal_rounding_mode = rb_intern_const("BigDecimal.rounding_mode"); + id_BigDecimal_precision_limit = rb_intern_const("BigDecimal.precision_limit"); + + /* Initialize VP routines */ + VpInit(0UL); + + /* Class and method registration */ + rb_cBigDecimal = rb_define_class("BigDecimal", rb_cNumeric); + + /* Global function */ + rb_define_global_function("BigDecimal", f_BigDecimal, -1); + + /* Class methods */ + rb_undef_alloc_func(rb_cBigDecimal); + rb_undef_method(CLASS_OF(rb_cBigDecimal), "new"); + rb_define_singleton_method(rb_cBigDecimal, "interpret_loosely", BigDecimal_s_interpret_loosely, 1); + rb_define_singleton_method(rb_cBigDecimal, "mode", BigDecimal_mode, -1); + rb_define_singleton_method(rb_cBigDecimal, "limit", BigDecimal_limit, -1); + rb_define_singleton_method(rb_cBigDecimal, "double_fig", BigDecimal_double_fig, 0); + rb_define_singleton_method(rb_cBigDecimal, "_load", BigDecimal_load, 1); + + rb_define_singleton_method(rb_cBigDecimal, "save_exception_mode", BigDecimal_save_exception_mode, 0); + rb_define_singleton_method(rb_cBigDecimal, "save_rounding_mode", BigDecimal_save_rounding_mode, 0); + rb_define_singleton_method(rb_cBigDecimal, "save_limit", BigDecimal_save_limit, 0); + + /* Constants definition */ + + /* + * The version of bigdecimal library + */ + rb_define_const(rb_cBigDecimal, "VERSION", rb_str_new2(BIGDECIMAL_VERSION)); + + /* + * Base value used in internal calculations. On a 32 bit system, BASE + * is 10000, indicating that calculation is done in groups of 4 digits. + * (If it were larger, BASE**2 wouldn't fit in 32 bits, so you couldn't + * guarantee that two groups could always be multiplied together without + * overflow.) + */ + rb_define_const(rb_cBigDecimal, "BASE", INT2FIX((SIGNED_VALUE)VpBaseVal())); + + /* Exceptions */ + + /* + * 0xff: Determines whether overflow, underflow or zero divide result in + * an exception being thrown. See BigDecimal.mode. + */ + rb_define_const(rb_cBigDecimal, "EXCEPTION_ALL", INT2FIX(VP_EXCEPTION_ALL)); + + /* + * 0x02: Determines what happens when the result of a computation is not a + * number (NaN). See BigDecimal.mode. + */ + rb_define_const(rb_cBigDecimal, "EXCEPTION_NaN", INT2FIX(VP_EXCEPTION_NaN)); + + /* + * 0x01: Determines what happens when the result of a computation is + * infinity. See BigDecimal.mode. + */ + rb_define_const(rb_cBigDecimal, "EXCEPTION_INFINITY", INT2FIX(VP_EXCEPTION_INFINITY)); + + /* + * 0x04: Determines what happens when the result of a computation is an + * underflow (a result too small to be represented). See BigDecimal.mode. + */ + rb_define_const(rb_cBigDecimal, "EXCEPTION_UNDERFLOW", INT2FIX(VP_EXCEPTION_UNDERFLOW)); + + /* + * 0x01: Determines what happens when the result of a computation is an + * overflow (a result too large to be represented). See BigDecimal.mode. + */ + rb_define_const(rb_cBigDecimal, "EXCEPTION_OVERFLOW", INT2FIX(VP_EXCEPTION_OVERFLOW)); + + /* + * 0x10: Determines what happens when a division by zero is performed. + * See BigDecimal.mode. + */ + rb_define_const(rb_cBigDecimal, "EXCEPTION_ZERODIVIDE", INT2FIX(VP_EXCEPTION_ZERODIVIDE)); + + /* + * 0x100: Determines what happens when a result must be rounded in order to + * fit in the appropriate number of significant digits. See + * BigDecimal.mode. + */ + rb_define_const(rb_cBigDecimal, "ROUND_MODE", INT2FIX(VP_ROUND_MODE)); + + /* 1: Indicates that values should be rounded away from zero. See + * BigDecimal.mode. + */ + rb_define_const(rb_cBigDecimal, "ROUND_UP", INT2FIX(VP_ROUND_UP)); + + /* 2: Indicates that values should be rounded towards zero. See + * BigDecimal.mode. + */ + rb_define_const(rb_cBigDecimal, "ROUND_DOWN", INT2FIX(VP_ROUND_DOWN)); + + /* 3: Indicates that digits >= 5 should be rounded up, others rounded down. + * See BigDecimal.mode. */ + rb_define_const(rb_cBigDecimal, "ROUND_HALF_UP", INT2FIX(VP_ROUND_HALF_UP)); + + /* 4: Indicates that digits >= 6 should be rounded up, others rounded down. + * See BigDecimal.mode. + */ + rb_define_const(rb_cBigDecimal, "ROUND_HALF_DOWN", INT2FIX(VP_ROUND_HALF_DOWN)); + /* 5: Round towards +Infinity. See BigDecimal.mode. */ + rb_define_const(rb_cBigDecimal, "ROUND_CEILING", INT2FIX(VP_ROUND_CEIL)); + + /* 6: Round towards -Infinity. See BigDecimal.mode. */ + rb_define_const(rb_cBigDecimal, "ROUND_FLOOR", INT2FIX(VP_ROUND_FLOOR)); + + /* 7: Round towards the even neighbor. See BigDecimal.mode. */ + rb_define_const(rb_cBigDecimal, "ROUND_HALF_EVEN", INT2FIX(VP_ROUND_HALF_EVEN)); + + /* 0: Indicates that a value is not a number. See BigDecimal.sign. */ + rb_define_const(rb_cBigDecimal, "SIGN_NaN", INT2FIX(VP_SIGN_NaN)); + + /* 1: Indicates that a value is +0. See BigDecimal.sign. */ + rb_define_const(rb_cBigDecimal, "SIGN_POSITIVE_ZERO", INT2FIX(VP_SIGN_POSITIVE_ZERO)); + + /* -1: Indicates that a value is -0. See BigDecimal.sign. */ + rb_define_const(rb_cBigDecimal, "SIGN_NEGATIVE_ZERO", INT2FIX(VP_SIGN_NEGATIVE_ZERO)); + + /* 2: Indicates that a value is positive and finite. See BigDecimal.sign. */ + rb_define_const(rb_cBigDecimal, "SIGN_POSITIVE_FINITE", INT2FIX(VP_SIGN_POSITIVE_FINITE)); + + /* -2: Indicates that a value is negative and finite. See BigDecimal.sign. */ + rb_define_const(rb_cBigDecimal, "SIGN_NEGATIVE_FINITE", INT2FIX(VP_SIGN_NEGATIVE_FINITE)); + + /* 3: Indicates that a value is positive and infinite. See BigDecimal.sign. */ + rb_define_const(rb_cBigDecimal, "SIGN_POSITIVE_INFINITE", INT2FIX(VP_SIGN_POSITIVE_INFINITE)); + + /* -3: Indicates that a value is negative and infinite. See BigDecimal.sign. */ + rb_define_const(rb_cBigDecimal, "SIGN_NEGATIVE_INFINITE", INT2FIX(VP_SIGN_NEGATIVE_INFINITE)); + + /* Positive zero value. */ + BIGDECIMAL_LITERAL(POSITIVE_ZERO, +0); + + /* Negative zero value. */ + BIGDECIMAL_LITERAL(NEGATIVE_ZERO, -0); + + /* Positive infinity[rdoc-ref:BigDecimal@Infinity] value. */ + rb_define_const(rb_cBigDecimal, "INFINITY", BIGDECIMAL_LITERAL(POSITIVE_INFINITY, +Infinity)); + + /* Negative infinity value. */ + BIGDECIMAL_LITERAL(NEGATIVE_INFINITY, -Infinity); + + /* '{Not a Number}[rdoc-ref:BigDecimal@Not+a+Number]' value. */ + rb_define_const(rb_cBigDecimal, "NAN", BIGDECIMAL_LITERAL(NAN, NaN)); + + /* instance methods */ + rb_define_method(rb_cBigDecimal, "precs", BigDecimal_prec, 0); + rb_define_method(rb_cBigDecimal, "precision", BigDecimal_precision, 0); + rb_define_method(rb_cBigDecimal, "scale", BigDecimal_scale, 0); + rb_define_method(rb_cBigDecimal, "precision_scale", BigDecimal_precision_scale, 0); + rb_define_method(rb_cBigDecimal, "n_significant_digits", BigDecimal_n_significant_digits, 0); + + rb_define_method(rb_cBigDecimal, "add", BigDecimal_add2, 2); + rb_define_method(rb_cBigDecimal, "sub", BigDecimal_sub2, 2); + rb_define_method(rb_cBigDecimal, "mult", BigDecimal_mult2, 2); + rb_define_method(rb_cBigDecimal, "div", BigDecimal_div3, -1); + rb_define_method(rb_cBigDecimal, "hash", BigDecimal_hash, 0); + rb_define_method(rb_cBigDecimal, "to_s", BigDecimal_to_s, -1); + rb_define_method(rb_cBigDecimal, "to_i", BigDecimal_to_i, 0); + rb_define_method(rb_cBigDecimal, "to_int", BigDecimal_to_i, 0); + rb_define_method(rb_cBigDecimal, "to_r", BigDecimal_to_r, 0); + rb_define_method(rb_cBigDecimal, "split", BigDecimal_split, 0); + rb_define_method(rb_cBigDecimal, "+", BigDecimal_add, 1); + rb_define_method(rb_cBigDecimal, "-", BigDecimal_sub, 1); + rb_define_method(rb_cBigDecimal, "+@", BigDecimal_uplus, 0); + rb_define_method(rb_cBigDecimal, "-@", BigDecimal_neg, 0); + rb_define_method(rb_cBigDecimal, "*", BigDecimal_mult, 1); + rb_define_method(rb_cBigDecimal, "/", BigDecimal_div, 1); + rb_define_method(rb_cBigDecimal, "quo", BigDecimal_quo, -1); + rb_define_method(rb_cBigDecimal, "%", BigDecimal_mod, 1); + rb_define_method(rb_cBigDecimal, "modulo", BigDecimal_mod, 1); + rb_define_method(rb_cBigDecimal, "remainder", BigDecimal_remainder, 1); + rb_define_method(rb_cBigDecimal, "divmod", BigDecimal_divmod, 1); + rb_define_method(rb_cBigDecimal, "clone", BigDecimal_clone, 0); + rb_define_method(rb_cBigDecimal, "dup", BigDecimal_clone, 0); + rb_define_method(rb_cBigDecimal, "to_f", BigDecimal_to_f, 0); + rb_define_method(rb_cBigDecimal, "abs", BigDecimal_abs, 0); + rb_define_method(rb_cBigDecimal, "sqrt", BigDecimal_sqrt, 1); + rb_define_method(rb_cBigDecimal, "fix", BigDecimal_fix, 0); + rb_define_method(rb_cBigDecimal, "round", BigDecimal_round, -1); + rb_define_method(rb_cBigDecimal, "frac", BigDecimal_frac, 0); + rb_define_method(rb_cBigDecimal, "floor", BigDecimal_floor, -1); + rb_define_method(rb_cBigDecimal, "ceil", BigDecimal_ceil, -1); + rb_define_method(rb_cBigDecimal, "power", BigDecimal_power, -1); + rb_define_method(rb_cBigDecimal, "**", BigDecimal_power_op, 1); + rb_define_method(rb_cBigDecimal, "<=>", BigDecimal_comp, 1); + rb_define_method(rb_cBigDecimal, "==", BigDecimal_eq, 1); + rb_define_method(rb_cBigDecimal, "===", BigDecimal_eq, 1); + rb_define_method(rb_cBigDecimal, "eql?", BigDecimal_eq, 1); + rb_define_method(rb_cBigDecimal, "<", BigDecimal_lt, 1); + rb_define_method(rb_cBigDecimal, "<=", BigDecimal_le, 1); + rb_define_method(rb_cBigDecimal, ">", BigDecimal_gt, 1); + rb_define_method(rb_cBigDecimal, ">=", BigDecimal_ge, 1); + rb_define_method(rb_cBigDecimal, "zero?", BigDecimal_zero, 0); + rb_define_method(rb_cBigDecimal, "nonzero?", BigDecimal_nonzero, 0); + rb_define_method(rb_cBigDecimal, "coerce", BigDecimal_coerce, 1); + rb_define_method(rb_cBigDecimal, "inspect", BigDecimal_inspect, 0); + rb_define_method(rb_cBigDecimal, "exponent", BigDecimal_exponent, 0); + rb_define_method(rb_cBigDecimal, "sign", BigDecimal_sign, 0); + rb_define_method(rb_cBigDecimal, "nan?", BigDecimal_IsNaN, 0); + rb_define_method(rb_cBigDecimal, "infinite?", BigDecimal_IsInfinite, 0); + rb_define_method(rb_cBigDecimal, "finite?", BigDecimal_IsFinite, 0); + rb_define_method(rb_cBigDecimal, "truncate", BigDecimal_truncate, -1); + rb_define_method(rb_cBigDecimal, "_dump", BigDecimal_dump, -1); + + rb_mBigMath = rb_define_module("BigMath"); + rb_define_singleton_method(rb_mBigMath, "exp", BigMath_s_exp, 2); + rb_define_singleton_method(rb_mBigMath, "log", BigMath_s_log, 2); + +#define ROUNDING_MODE(i, name, value) \ + id_##name = rb_intern_const(#name); \ + rbd_rounding_modes[i].id = id_##name; \ + rbd_rounding_modes[i].mode = value; + + ROUNDING_MODE(0, up, RBD_ROUND_UP); + ROUNDING_MODE(1, down, RBD_ROUND_DOWN); + ROUNDING_MODE(2, half_up, RBD_ROUND_HALF_UP); + ROUNDING_MODE(3, half_down, RBD_ROUND_HALF_DOWN); + ROUNDING_MODE(4, ceil, RBD_ROUND_CEIL); + ROUNDING_MODE(5, floor, RBD_ROUND_FLOOR); + ROUNDING_MODE(6, half_even, RBD_ROUND_HALF_EVEN); + + ROUNDING_MODE(7, default, RBD_ROUND_DEFAULT); + ROUNDING_MODE(8, truncate, RBD_ROUND_TRUNCATE); + ROUNDING_MODE(9, banker, RBD_ROUND_BANKER); + ROUNDING_MODE(10, ceiling, RBD_ROUND_CEILING); + +#undef ROUNDING_MODE + + id_to_r = rb_intern_const("to_r"); + id_eq = rb_intern_const("=="); + id_half = rb_intern_const("half"); + + (void)VPrint; /* suppress unused warning */ +} + +/* + * + * ============================================================================ + * + * vp_ routines begin from here. + * + * ============================================================================ + * + */ +#ifdef BIGDECIMAL_DEBUG +static int gfDebug = 1; /* Debug switch */ +#if 0 +static int gfCheckVal = 1; /* Value checking flag in VpNmlz() */ +#endif +#endif /* BIGDECIMAL_DEBUG */ + +static Real *VpConstOne; /* constant 1.0 */ +static Real *VpConstPt5; /* constant 0.5 */ +#define maxnr 100UL /* Maximum iterations for calculating sqrt. */ + /* used in VpSqrt() */ + +/* ETC */ +#define MemCmp(x,y,z) memcmp(x,y,z) +#define StrCmp(x,y) strcmp(x,y) + +enum op_sw { + OP_SW_ADD = 1, /* + */ + OP_SW_SUB, /* - */ + OP_SW_MULT, /* * */ + OP_SW_DIV /* / */ +}; + +static int VpIsDefOP(Real *c, Real *a, Real *b, enum op_sw sw); +static int AddExponent(Real *a, SIGNED_VALUE n); +static DECDIG VpAddAbs(Real *a,Real *b,Real *c); +static DECDIG VpSubAbs(Real *a,Real *b,Real *c); +static size_t VpSetPTR(Real *a, Real *b, Real *c, size_t *a_pos, size_t *b_pos, size_t *c_pos, DECDIG *av, DECDIG *bv); +static int VpNmlz(Real *a); +static void VpFormatSt(char *psz, size_t fFmt); +static int VpRdup(Real *m, size_t ind_m); + +#ifdef BIGDECIMAL_DEBUG +# ifdef HAVE_RB_EXT_RACTOR_SAFE +# error Need to make rewiting gnAlloc atomic +# endif +static int gnAlloc = 0; /* Memory allocation counter */ +#endif /* BIGDECIMAL_DEBUG */ + +/* + * EXCEPTION Handling. + */ + +#define bigdecimal_set_thread_local_exception_mode(mode) \ + rb_thread_local_aset( \ + rb_thread_current(), \ + id_BigDecimal_exception_mode, \ + INT2FIX((int)(mode)) \ + ) + +static unsigned short +VpGetException (void) +{ + VALUE const vmode = rb_thread_local_aref( + rb_thread_current(), + id_BigDecimal_exception_mode + ); + + if (NIL_P(vmode)) { + bigdecimal_set_thread_local_exception_mode(BIGDECIMAL_EXCEPTION_MODE_DEFAULT); + return BIGDECIMAL_EXCEPTION_MODE_DEFAULT; + } + + return NUM2USHORT(vmode); +} + +static void +VpSetException(unsigned short f) +{ + bigdecimal_set_thread_local_exception_mode(f); +} + +static void +VpCheckException(Real *p, bool always) +{ + if (VpIsNaN(p)) { + VpException(VP_EXCEPTION_NaN, "Computation results in 'NaN' (Not a Number)", always); + } + else if (VpIsPosInf(p)) { + VpException(VP_EXCEPTION_INFINITY, "Computation results in 'Infinity'", always); + } + else if (VpIsNegInf(p)) { + VpException(VP_EXCEPTION_INFINITY, "Computation results in '-Infinity'", always); + } +} + +static VALUE +VpCheckGetValue(Real *p) +{ + VpCheckException(p, false); + return p->obj; +} + +/* + * Precision limit. + */ + +#define bigdecimal_set_thread_local_precision_limit(limit) \ + rb_thread_local_aset( \ + rb_thread_current(), \ + id_BigDecimal_precision_limit, \ + SIZET2NUM(limit) \ + ) +#define BIGDECIMAL_PRECISION_LIMIT_DEFAULT ((size_t)0) + +/* These 2 functions added at v1.1.7 */ +VP_EXPORT size_t +VpGetPrecLimit(void) +{ + VALUE const vlimit = rb_thread_local_aref( + rb_thread_current(), + id_BigDecimal_precision_limit + ); + + if (NIL_P(vlimit)) { + bigdecimal_set_thread_local_precision_limit(BIGDECIMAL_PRECISION_LIMIT_DEFAULT); + return BIGDECIMAL_PRECISION_LIMIT_DEFAULT; + } + + return NUM2SIZET(vlimit); +} + +VP_EXPORT size_t +VpSetPrecLimit(size_t n) +{ + size_t const s = VpGetPrecLimit(); + bigdecimal_set_thread_local_precision_limit(n); + return s; +} + +/* + * Rounding mode. + */ + +#define bigdecimal_set_thread_local_rounding_mode(mode) \ + rb_thread_local_aset( \ + rb_thread_current(), \ + id_BigDecimal_rounding_mode, \ + INT2FIX((int)(mode)) \ + ) + +VP_EXPORT unsigned short +VpGetRoundMode(void) +{ + VALUE const vmode = rb_thread_local_aref( + rb_thread_current(), + id_BigDecimal_rounding_mode + ); + + if (NIL_P(vmode)) { + bigdecimal_set_thread_local_rounding_mode(BIGDECIMAL_ROUNDING_MODE_DEFAULT); + return BIGDECIMAL_ROUNDING_MODE_DEFAULT; + } + + return NUM2USHORT(vmode); +} + +VP_EXPORT int +VpIsRoundMode(unsigned short n) +{ + switch (n) { + case VP_ROUND_UP: + case VP_ROUND_DOWN: + case VP_ROUND_HALF_UP: + case VP_ROUND_HALF_DOWN: + case VP_ROUND_CEIL: + case VP_ROUND_FLOOR: + case VP_ROUND_HALF_EVEN: + return 1; + + default: + return 0; + } +} + +VP_EXPORT unsigned short +VpSetRoundMode(unsigned short n) +{ + if (VpIsRoundMode(n)) { + bigdecimal_set_thread_local_rounding_mode(n); + return n; + } + + return VpGetRoundMode(); +} + +/* + * 0.0 & 1.0 generator + * These gZero_..... and gOne_..... can be any name + * referenced from nowhere except Zero() and One(). + * gZero_..... and gOne_..... must have global scope + * (to let the compiler know they may be changed in outside + * (... but not actually..)). + */ +volatile const double gOne_ABCED9B4_CE73__00400511F31D = 1.0; + +static double +One(void) +{ + return gOne_ABCED9B4_CE73__00400511F31D; +} + +/* + ---------------------------------------------------------------- + Value of sign in Real structure is reserved for future use. + short sign; + ==0 : NaN + 1 : Positive zero + -1 : Negative zero + 2 : Positive number + -2 : Negative number + 3 : Positive infinite number + -3 : Negative infinite number + ---------------------------------------------------------------- +*/ + +VP_EXPORT double +VpGetDoubleNaN(void) /* Returns the value of NaN */ +{ + return nan(""); +} + +VP_EXPORT double +VpGetDoublePosInf(void) /* Returns the value of +Infinity */ +{ + return HUGE_VAL; +} + +VP_EXPORT double +VpGetDoubleNegInf(void) /* Returns the value of -Infinity */ +{ + return -HUGE_VAL; +} + +VP_EXPORT double +VpGetDoubleNegZero(void) /* Returns the value of -0 */ +{ + static double nzero = 1000.0; + if (nzero != 0.0) nzero = (One()/VpGetDoubleNegInf()); + return nzero; +} + +#if 0 /* unused */ +VP_EXPORT int +VpIsNegDoubleZero(double v) +{ + double z = VpGetDoubleNegZero(); + return MemCmp(&v,&z,sizeof(v))==0; +} +#endif + +VP_EXPORT int +VpException(unsigned short f, const char *str,int always) +{ + unsigned short const exception_mode = VpGetException(); + + if (f == VP_EXCEPTION_OP) always = 1; + + if (always || (exception_mode & f)) { + switch(f) { + /* case VP_EXCEPTION_OVERFLOW: */ + case VP_EXCEPTION_ZERODIVIDE: + case VP_EXCEPTION_INFINITY: + case VP_EXCEPTION_NaN: + case VP_EXCEPTION_UNDERFLOW: + case VP_EXCEPTION_OP: + rb_raise(rb_eFloatDomainError, "%s", str); + break; + default: + rb_fatal("%s", str); + } + } + return 0; /* 0 Means VpException() raised no exception */ +} + +/* Throw exception or returns 0,when resulting c is Inf or NaN */ +/* sw=1:+ 2:- 3:* 4:/ */ +static int +VpIsDefOP(Real *c, Real *a, Real *b, enum op_sw sw) +{ + if (VpIsNaN(a) || VpIsNaN(b)) { + /* at least a or b is NaN */ + VpSetNaN(c); + goto NaN; + } + + if (VpIsInf(a)) { + if (VpIsInf(b)) { + switch(sw) { + case OP_SW_ADD: /* + */ + if (VpGetSign(a) == VpGetSign(b)) { + VpSetInf(c, VpGetSign(a)); + goto Inf; + } + else { + VpSetNaN(c); + goto NaN; + } + case OP_SW_SUB: /* - */ + if (VpGetSign(a) != VpGetSign(b)) { + VpSetInf(c, VpGetSign(a)); + goto Inf; + } + else { + VpSetNaN(c); + goto NaN; + } + case OP_SW_MULT: /* * */ + VpSetInf(c, VpGetSign(a)*VpGetSign(b)); + goto Inf; + case OP_SW_DIV: /* / */ + VpSetNaN(c); + goto NaN; + } + VpSetNaN(c); + goto NaN; + } + /* Inf op Finite */ + switch(sw) { + case OP_SW_ADD: /* + */ + case OP_SW_SUB: /* - */ + VpSetInf(c, VpGetSign(a)); + break; + case OP_SW_MULT: /* * */ + if (VpIsZero(b)) { + VpSetNaN(c); + goto NaN; + } + VpSetInf(c, VpGetSign(a)*VpGetSign(b)); + break; + case OP_SW_DIV: /* / */ + VpSetInf(c, VpGetSign(a)*VpGetSign(b)); + } + goto Inf; + } + + if (VpIsInf(b)) { + switch(sw) { + case OP_SW_ADD: /* + */ + VpSetInf(c, VpGetSign(b)); + break; + case OP_SW_SUB: /* - */ + VpSetInf(c, -VpGetSign(b)); + break; + case OP_SW_MULT: /* * */ + if (VpIsZero(a)) { + VpSetNaN(c); + goto NaN; + } + VpSetInf(c, VpGetSign(a)*VpGetSign(b)); + break; + case OP_SW_DIV: /* / */ + VpSetZero(c, VpGetSign(a)*VpGetSign(b)); + } + goto Inf; + } + return 1; /* Results OK */ + +Inf: + if (VpIsPosInf(c)) { + return VpException(VP_EXCEPTION_INFINITY, "Computation results to 'Infinity'", 0); + } + else { + return VpException(VP_EXCEPTION_INFINITY, "Computation results to '-Infinity'", 0); + } + +NaN: + return VpException(VP_EXCEPTION_NaN, "Computation results to 'NaN'", 0); +} + +/* + ---------------------------------------------------------------- +*/ + +/* + * returns number of chars needed to represent vp in specified format. + */ +VP_EXPORT size_t +VpNumOfChars(Real *vp,const char *pszFmt) +{ + SIGNED_VALUE ex; + size_t nc; + + if (vp == NULL) return BASE_FIG*2+6; + if (!VpIsDef(vp)) return 32; /* not sure,may be OK */ + + switch(*pszFmt) { + case 'F': + nc = BASE_FIG*(vp->Prec + 1)+2; + ex = vp->exponent; + if (ex < 0) { + nc += BASE_FIG*(size_t)(-ex); + } + else { + if ((size_t)ex > vp->Prec) { + nc += BASE_FIG*((size_t)ex - vp->Prec); + } + } + break; + case 'E': + /* fall through */ + default: + nc = BASE_FIG*(vp->Prec + 2)+6; /* 3: sign + exponent chars */ + } + return nc; +} + +/* + * Initializer for Vp routines and constants used. + * [Input] + * BaseVal: Base value(assigned to BASE) for Vp calculation. + * It must be the form BaseVal=10**n.(n=1,2,3,...) + * If Base <= 0L,then the BASE will be calculated so + * that BASE is as large as possible satisfying the + * relation MaxVal <= BASE*(BASE+1). Where the value + * MaxVal is the largest value which can be represented + * by one DECDIG word in the computer used. + * + * [Returns] + * BIGDECIMAL_DOUBLE_FIGURES ... OK + */ +VP_EXPORT size_t +VpInit(DECDIG BaseVal) +{ + /* Setup +/- Inf NaN -0 */ + VpGetDoubleNegZero(); + + /* Const 1.0 */ + VpConstOne = NewOneNolimit(1, 1); + + /* Const 0.5 */ + VpConstPt5 = NewOneNolimit(1, 1); + VpConstPt5->exponent = 0; + VpConstPt5->frac[0] = 5*BASE1; + +#ifdef BIGDECIMAL_DEBUG + gnAlloc = 0; +#endif /* BIGDECIMAL_DEBUG */ + +#ifdef BIGDECIMAL_DEBUG + if (gfDebug) { + printf("VpInit: BaseVal = %"PRIuDECDIG"\n", BaseVal); + printf("\tBASE = %"PRIuDECDIG"\n", BASE); + printf("\tHALF_BASE = %"PRIuDECDIG"\n", HALF_BASE); + printf("\tBASE1 = %"PRIuDECDIG"\n", BASE1); + printf("\tBASE_FIG = %u\n", BASE_FIG); + printf("\tBIGDECIMAL_DOUBLE_FIGURES = %d\n", BIGDECIMAL_DOUBLE_FIGURES); + } +#endif /* BIGDECIMAL_DEBUG */ + + return BIGDECIMAL_DOUBLE_FIGURES; +} + +VP_EXPORT Real * +VpOne(void) +{ + return VpConstOne; +} + +/* If exponent overflows,then raise exception or returns 0 */ +static int +AddExponent(Real *a, SIGNED_VALUE n) +{ + SIGNED_VALUE e = a->exponent; + SIGNED_VALUE m = e+n; + SIGNED_VALUE eb, mb; + if (e > 0) { + if (n > 0) { + if (MUL_OVERFLOW_SIGNED_VALUE_P(m, (SIGNED_VALUE)BASE_FIG) || + MUL_OVERFLOW_SIGNED_VALUE_P(e, (SIGNED_VALUE)BASE_FIG)) + goto overflow; + mb = m*(SIGNED_VALUE)BASE_FIG; + eb = e*(SIGNED_VALUE)BASE_FIG; + if (eb - mb > 0) goto overflow; + } + } + else if (n < 0) { + if (MUL_OVERFLOW_SIGNED_VALUE_P(m, (SIGNED_VALUE)BASE_FIG) || + MUL_OVERFLOW_SIGNED_VALUE_P(e, (SIGNED_VALUE)BASE_FIG)) + goto underflow; + mb = m*(SIGNED_VALUE)BASE_FIG; + eb = e*(SIGNED_VALUE)BASE_FIG; + if (mb - eb > 0) goto underflow; + } + a->exponent = m; + return 1; + +/* Overflow/Underflow ==> Raise exception or returns 0 */ +underflow: + VpSetZero(a, VpGetSign(a)); + return VpException(VP_EXCEPTION_UNDERFLOW, "Exponent underflow", 0); + +overflow: + VpSetInf(a, VpGetSign(a)); + return VpException(VP_EXCEPTION_OVERFLOW, "Exponent overflow", 0); +} + +Real * +bigdecimal_parse_special_string(const char *str) +{ + static const struct { + const char *str; + size_t len; + int sign; + } table[] = { + { SZ_INF, sizeof(SZ_INF) - 1, VP_SIGN_POSITIVE_INFINITE }, + { SZ_PINF, sizeof(SZ_PINF) - 1, VP_SIGN_POSITIVE_INFINITE }, + { SZ_NINF, sizeof(SZ_NINF) - 1, VP_SIGN_NEGATIVE_INFINITE }, + { SZ_NaN, sizeof(SZ_NaN) - 1, VP_SIGN_NaN } + }; + static const size_t table_length = sizeof(table) / sizeof(table[0]); + size_t i; + + for (i = 0; i < table_length; ++i) { + const char *p; + if (strncmp(str, table[i].str, table[i].len) != 0) { + continue; + } + + p = str + table[i].len; + while (*p && ISSPACE(*p)) ++p; + if (*p == '\0') { + Real *vp = rbd_allocate_struct(1); + vp->MaxPrec = 1; + switch (table[i].sign) { + default: + UNREACHABLE; break; + case VP_SIGN_POSITIVE_INFINITE: + VpSetPosInf(vp); + return vp; + case VP_SIGN_NEGATIVE_INFINITE: + VpSetNegInf(vp); + return vp; + case VP_SIGN_NaN: + VpSetNaN(vp); + return vp; + } + } + } + + return NULL; +} + +struct VpCtoV_args { + Real *a; + const char *int_chr; + size_t ni; + const char *frac; + size_t nf; + const char *exp_chr; + size_t ne; +}; + +static VALUE +call_VpCtoV(VALUE arg) +{ + struct VpCtoV_args *x = (struct VpCtoV_args *)arg; + return (VALUE)VpCtoV(x->a, x->int_chr, x->ni, x->frac, x->nf, x->exp_chr, x->ne); +} + +static int +protected_VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, const char *exp_chr, size_t ne, int free_on_error) +{ + struct VpCtoV_args args; + int state = 0; + + args.a = a; + args.int_chr = int_chr; + args.ni = ni; + args.frac = frac; + args.nf = nf; + args.exp_chr = exp_chr; + args.ne = ne; + + VALUE result = rb_protect(call_VpCtoV, (VALUE)&args, &state); + if (state) { + if (free_on_error) { + rbd_free_struct(a); + } + rb_jump_tag(state); + } + + return (int)result; +} + +/* + * Allocates variable. + * [Input] + * mx ... The number of decimal digits to be allocated, if zero then mx is determined by szVal. + * The mx will be the number of significant digits can to be stored. + * szVal ... The value assigned(char). If szVal==NULL, then zero is assumed. + * If szVal[0]=='#' then MaxPrec is not affected by the precision limit + * so that the full precision specified by szVal is allocated. + * + * [Returns] + * Pointer to the newly allocated variable, or + * NULL be returned if memory allocation is failed,or any error. + */ +VP_EXPORT Real * +VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) +{ + const char *orig_szVal = szVal; + size_t i, j, ni, ipf, nf, ipe, ne, exp_seen, nalloc; + size_t len; + char v, *psz; + int sign=1; + Real *vp = NULL; + VALUE buf; + + if (szVal == NULL) { + return_zero: + /* necessary to be able to store */ + /* at least mx digits. */ + /* szVal==NULL ==> allocate zero value. */ + vp = rbd_allocate_struct(mx); + vp->MaxPrec = rbd_calculate_internal_digits(mx, false); /* Must false */ + VpSetZero(vp, 1); /* initialize vp to zero. */ + return vp; + } + + /* Skipping leading spaces */ + while (ISSPACE(*szVal)) szVal++; + + /* Check on Inf & NaN */ + if ((vp = bigdecimal_parse_special_string(szVal)) != NULL) { + return vp; + } + + /* Processing the leading one `#` */ + if (*szVal != '#') { + len = rbd_calculate_internal_digits(mx, true); + } + else { + len = rbd_calculate_internal_digits(mx, false); + ++szVal; + } + + /* Scanning digits */ + + /* A buffer for keeping scanned digits */ + buf = rb_str_tmp_new(strlen(szVal) + 1); + psz = RSTRING_PTR(buf); + + /* cursor: i for psz, and j for szVal */ + i = j = 0; + + /* Scanning: sign part */ + v = psz[i] = szVal[j]; + if ((v == '-') || (v == '+')) { + sign = -(v == '-'); + ++i; + ++j; + } + + /* Scanning: integer part */ + ni = 0; /* number of digits in the integer part */ + while ((v = psz[i] = szVal[j]) != '\0') { + if (!strict_p && ISSPACE(v)) { + v = psz[i] = '\0'; + break; + } + if (v == '_') { + if (ni > 0) { + v = szVal[j+1]; + if (v == '\0' || ISSPACE(v) || ISDIGIT(v)) { + ++j; + continue; + } + if (!strict_p) { + v = psz[i] = '\0'; + break; + } + } + goto invalid_value; + } + if (!ISDIGIT(v)) { + break; + } + ++ni; + ++i; + ++j; + } + + /* Scanning: fractional part */ + nf = 0; /* number of digits in the fractional part */ + ne = 0; /* number of digits in the exponential part */ + ipf = 0; /* index of the beginning of the fractional part */ + ipe = 0; /* index of the beginning of the exponential part */ + exp_seen = 0; + + if (v != '\0') { + /* Scanning fractional part */ + if ((psz[i] = szVal[j]) == '.') { + ++i; + ++j; + ipf = i; + while ((v = psz[i] = szVal[j]) != '\0') { + if (!strict_p && ISSPACE(v)) { + v = psz[i] = '\0'; + break; + } + if (v == '_') { + if (nf > 0 && ISDIGIT(szVal[j+1])) { + ++j; + continue; + } + if (!strict_p) { + v = psz[i] = '\0'; + break; + } + goto invalid_value; + } + if (!ISDIGIT(v)) break; + ++i; + ++j; + ++nf; + } + } + + /* Scanning exponential part */ + if (v != '\0') { + switch ((psz[i] = szVal[j])) { + case '\0': + break; + case 'e': case 'E': + case 'd': case 'D': + exp_seen = 1; + ++i; + ++j; + ipe = i; + v = psz[i] = szVal[j]; + if ((v == '-') || (v == '+')) { + ++i; + ++j; + } + while ((v = psz[i] = szVal[j]) != '\0') { + if (!strict_p && ISSPACE(v)) { + v = psz[i] = '\0'; + break; + } + if (v == '_') { + if (ne > 0 && ISDIGIT(szVal[j+1])) { + ++j; + continue; + } + if (!strict_p) { + v = psz[i] = '\0'; + if (ne == 0) { + exp_seen = 0; + } + break; + } + goto invalid_value; + } + if (!ISDIGIT(v)) break; + ++i; + ++j; + ++ne; + } + break; + default: + break; + } + } + + if (v != '\0') { + /* Scanning trailing spaces */ + while (ISSPACE(szVal[j])) ++j; + + /* Invalid character */ + if (szVal[j] && strict_p) { + goto invalid_value; + } + } + } + + psz[i] = '\0'; + + if (strict_p && ((ni == 0 && nf == 0) || (exp_seen && ne == 0))) { + VALUE str; + invalid_value: + if (!strict_p) { + goto return_zero; + } + if (!exc) { + return NULL; + } + str = rb_str_new2(orig_szVal); + rb_raise(rb_eArgError, "invalid value for BigDecimal(): \"%"PRIsVALUE"\"", str); + } + + nalloc = (ni + nf + BASE_FIG - 1) / BASE_FIG + 1; /* set effective allocation */ + /* units for szVal[] */ + if (len == 0) len = 1; + nalloc = Max(nalloc, len); + len = nalloc; + vp = rbd_allocate_struct(len); + vp->MaxPrec = len; /* set max precision */ + VpSetZero(vp, sign); + protected_VpCtoV(vp, psz, ni, psz + ipf, nf, psz + ipe, ne, true); + rb_str_resize(buf, 0); + return vp; +} + +/* + * Assignment(c=a). + * [Input] + * a ... RHSV + * isw ... switch for assignment. + * c = a when isw > 0 + * c = -a when isw < 0 + * if c->MaxPrec < a->Prec,then round operation + * will be performed. + * [Output] + * c ... LHSV + */ +VP_EXPORT size_t +VpAsgn(Real *c, Real *a, int isw) +{ + size_t n; + if (VpIsNaN(a)) { + VpSetNaN(c); + return 0; + } + if (VpIsInf(a)) { + VpSetInf(c, isw * VpGetSign(a)); + return 0; + } + + /* check if the RHS is zero */ + if (!VpIsZero(a)) { + c->exponent = a->exponent; /* store exponent */ + VpSetSign(c, isw * VpGetSign(a)); /* set sign */ + n = (a->Prec < c->MaxPrec) ? (a->Prec) : (c->MaxPrec); + c->Prec = n; + memcpy(c->frac, a->frac, n * sizeof(DECDIG)); + /* Needs round ? */ + if (isw != 10) { + /* Not in ActiveRound */ + if(c->Prec < a->Prec) { + VpInternalRound(c, n, (n>0) ? a->frac[n-1] : 0, a->frac[n]); + } + else { + VpLimitRound(c,0); + } + } + } + else { + /* The value of 'a' is zero. */ + VpSetZero(c, isw * VpGetSign(a)); + return 1; + } + return c->Prec * BASE_FIG; +} + +/* + * c = a + b when operation = 1 or 2 + * c = a - b when operation = -1 or -2. + * Returns number of significant digits of c + */ +VP_EXPORT size_t +VpAddSub(Real *c, Real *a, Real *b, int operation) +{ + short sw, isw; + Real *a_ptr, *b_ptr; + size_t n, na, nb, i; + DECDIG mrv; + +#ifdef BIGDECIMAL_DEBUG + if (gfDebug) { + VPrint(stdout, "VpAddSub(enter) a=% \n", a); + VPrint(stdout, " b=% \n", b); + printf(" operation=%d\n", operation); + } +#endif /* BIGDECIMAL_DEBUG */ + + if (!VpIsDefOP(c, a, b, (operation > 0) ? OP_SW_ADD : OP_SW_SUB)) return 0; /* No significant digits */ + + /* check if a or b is zero */ + if (VpIsZero(a)) { + /* a is zero,then assign b to c */ + if (!VpIsZero(b)) { + VpAsgn(c, b, operation); + } + else { + /* Both a and b are zero. */ + if (VpGetSign(a) < 0 && operation * VpGetSign(b) < 0) { + /* -0 -0 */ + VpSetZero(c, -1); + } + else { + VpSetZero(c, 1); + } + return 1; /* 0: 1 significant digits */ + } + return c->Prec * BASE_FIG; + } + if (VpIsZero(b)) { + /* b is zero,then assign a to c. */ + VpAsgn(c, a, 1); + return c->Prec*BASE_FIG; + } + + if (operation < 0) sw = -1; + else sw = 1; + + /* compare absolute value. As a result,|a_ptr|>=|b_ptr| */ + if (a->exponent > b->exponent) { + a_ptr = a; + b_ptr = b; + } /* |a|>|b| */ + else if (a->exponent < b->exponent) { + a_ptr = b; + b_ptr = a; + } /* |a|<|b| */ + else { + /* Exponent part of a and b is the same,then compare fraction */ + /* part */ + na = a->Prec; + nb = b->Prec; + n = Min(na, nb); + for (i=0; i < n; ++i) { + if (a->frac[i] > b->frac[i]) { + a_ptr = a; + b_ptr = b; + goto end_if; + } + else if (a->frac[i] < b->frac[i]) { + a_ptr = b; + b_ptr = a; + goto end_if; + } + } + if (na > nb) { + a_ptr = a; + b_ptr = b; + goto end_if; + } + else if (na < nb) { + a_ptr = b; + b_ptr = a; + goto end_if; + } + /* |a| == |b| */ + if (VpGetSign(a) + sw *VpGetSign(b) == 0) { + VpSetZero(c, 1); /* abs(a)=abs(b) and operation = '-' */ + return c->Prec * BASE_FIG; + } + a_ptr = a; + b_ptr = b; + } + +end_if: + isw = VpGetSign(a) + sw *VpGetSign(b); + /* + * isw = 0 ...( 1)+(-1),( 1)-( 1),(-1)+(1),(-1)-(-1) + * = 2 ...( 1)+( 1),( 1)-(-1) + * =-2 ...(-1)+(-1),(-1)-( 1) + * If isw==0, then c =(Sign a_ptr)(|a_ptr|-|b_ptr|) + * else c =(Sign ofisw)(|a_ptr|+|b_ptr|) + */ + if (isw) { /* addition */ + VpSetSign(c, 1); + mrv = VpAddAbs(a_ptr, b_ptr, c); + VpSetSign(c, isw / 2); + } + else { /* subtraction */ + VpSetSign(c, 1); + mrv = VpSubAbs(a_ptr, b_ptr, c); + if (a_ptr == a) { + VpSetSign(c,VpGetSign(a)); + } + else { + VpSetSign(c, VpGetSign(a_ptr) * sw); + } + } + VpInternalRound(c, 0, (c->Prec > 0) ? c->frac[c->Prec-1] : 0, mrv); + +#ifdef BIGDECIMAL_DEBUG + if (gfDebug) { + VPrint(stdout, "VpAddSub(result) c=% \n", c); + VPrint(stdout, " a=% \n", a); + VPrint(stdout, " b=% \n", b); + printf(" operation=%d\n", operation); + } +#endif /* BIGDECIMAL_DEBUG */ + return c->Prec * BASE_FIG; +} + +/* + * Addition of two values with variable precision + * a and b assuming abs(a)>abs(b). + * c = abs(a) + abs(b) ; where |a|>=|b| + */ +static DECDIG +VpAddAbs(Real *a, Real *b, Real *c) +{ + size_t word_shift; + size_t ap; + size_t bp; + size_t cp; + size_t a_pos; + size_t b_pos, b_pos_with_word_shift; + size_t c_pos; + DECDIG av, bv, carry, mrv; + +#ifdef BIGDECIMAL_DEBUG + if (gfDebug) { + VPrint(stdout, "VpAddAbs called: a = %\n", a); + VPrint(stdout, " b = %\n", b); + } +#endif /* BIGDECIMAL_DEBUG */ + + word_shift = VpSetPTR(a, b, c, &ap, &bp, &cp, &av, &bv); + a_pos = ap; + b_pos = bp; + c_pos = cp; + + if (word_shift == (size_t)-1L) return 0; /* Overflow */ + if (b_pos == (size_t)-1L) goto Assign_a; + + mrv = av + bv; /* Most right val. Used for round. */ + + /* Just assign the last few digits of b to c because a has no */ + /* corresponding digits to be added. */ + if (b_pos > 0) { + while (b_pos > 0 && b_pos + word_shift > a_pos) { + c->frac[--c_pos] = b->frac[--b_pos]; + } + } + if (b_pos == 0 && word_shift > a_pos) { + while (word_shift-- > a_pos) { + c->frac[--c_pos] = 0; + } + } + + /* Just assign the last few digits of a to c because b has no */ + /* corresponding digits to be added. */ + b_pos_with_word_shift = b_pos + word_shift; + while (a_pos > b_pos_with_word_shift) { + c->frac[--c_pos] = a->frac[--a_pos]; + } + carry = 0; /* set first carry be zero */ + + /* Now perform addition until every digits of b will be */ + /* exhausted. */ + while (b_pos > 0) { + c->frac[--c_pos] = a->frac[--a_pos] + b->frac[--b_pos] + carry; + if (c->frac[c_pos] >= BASE) { + c->frac[c_pos] -= BASE; + carry = 1; + } + else { + carry = 0; + } + } + + /* Just assign the first few digits of a with considering */ + /* the carry obtained so far because b has been exhausted. */ + while (a_pos > 0) { + c->frac[--c_pos] = a->frac[--a_pos] + carry; + if (c->frac[c_pos] >= BASE) { + c->frac[c_pos] -= BASE; + carry = 1; + } + else { + carry = 0; + } + } + if (c_pos) c->frac[c_pos - 1] += carry; + goto Exit; + +Assign_a: + VpAsgn(c, a, 1); + mrv = 0; + +Exit: + +#ifdef BIGDECIMAL_DEBUG + if (gfDebug) { + VPrint(stdout, "VpAddAbs exit: c=% \n", c); + } +#endif /* BIGDECIMAL_DEBUG */ + return mrv; +} + +/* + * c = abs(a) - abs(b) + */ +static DECDIG +VpSubAbs(Real *a, Real *b, Real *c) +{ + size_t word_shift; + size_t ap; + size_t bp; + size_t cp; + size_t a_pos; + size_t b_pos, b_pos_with_word_shift; + size_t c_pos; + DECDIG av, bv, borrow, mrv; + +#ifdef BIGDECIMAL_DEBUG + if (gfDebug) { + VPrint(stdout, "VpSubAbs called: a = %\n", a); + VPrint(stdout, " b = %\n", b); + } +#endif /* BIGDECIMAL_DEBUG */ + + word_shift = VpSetPTR(a, b, c, &ap, &bp, &cp, &av, &bv); + a_pos = ap; + b_pos = bp; + c_pos = cp; + if (word_shift == (size_t)-1L) return 0; /* Overflow */ + if (b_pos == (size_t)-1L) goto Assign_a; + + if (av >= bv) { + mrv = av - bv; + borrow = 0; + } + else { + mrv = 0; + borrow = 1; + } + + /* Just assign the values which are the BASE subtracted by */ + /* each of the last few digits of the b because the a has no */ + /* corresponding digits to be subtracted. */ + if (b_pos + word_shift > a_pos) { + while (b_pos > 0 && b_pos + word_shift > a_pos) { + c->frac[--c_pos] = BASE - b->frac[--b_pos] - borrow; + borrow = 1; + } + if (b_pos == 0) { + while (word_shift > a_pos) { + --word_shift; + c->frac[--c_pos] = BASE - borrow; + borrow = 1; + } + } + } + /* Just assign the last few digits of a to c because b has no */ + /* corresponding digits to subtract. */ + + b_pos_with_word_shift = b_pos + word_shift; + while (a_pos > b_pos_with_word_shift) { + c->frac[--c_pos] = a->frac[--a_pos]; + } + + /* Now perform subtraction until every digits of b will be */ + /* exhausted. */ + while (b_pos > 0) { + --c_pos; + if (a->frac[--a_pos] < b->frac[--b_pos] + borrow) { + c->frac[c_pos] = BASE + a->frac[a_pos] - b->frac[b_pos] - borrow; + borrow = 1; + } + else { + c->frac[c_pos] = a->frac[a_pos] - b->frac[b_pos] - borrow; + borrow = 0; + } + } + + /* Just assign the first few digits of a with considering */ + /* the borrow obtained so far because b has been exhausted. */ + while (a_pos > 0) { + --c_pos; + if (a->frac[--a_pos] < borrow) { + c->frac[c_pos] = BASE + a->frac[a_pos] - borrow; + borrow = 1; + } + else { + c->frac[c_pos] = a->frac[a_pos] - borrow; + borrow = 0; + } + } + if (c_pos) c->frac[c_pos - 1] -= borrow; + goto Exit; + +Assign_a: + VpAsgn(c, a, 1); + mrv = 0; + +Exit: +#ifdef BIGDECIMAL_DEBUG + if (gfDebug) { + VPrint(stdout, "VpSubAbs exit: c=% \n", c); + } +#endif /* BIGDECIMAL_DEBUG */ + return mrv; +} + +/* + * Note: If(av+bv)>= HALF_BASE,then 1 will be added to the least significant + * digit of c(In case of addition). + * ------------------------- figure of output ----------------------------------- + * a = xxxxxxxxxxx + * b = xxxxxxxxxx + * c =xxxxxxxxxxxxxxx + * word_shift = | | + * right_word = | | (Total digits in RHSV) + * left_word = | | (Total digits in LHSV) + * a_pos = | + * b_pos = | + * c_pos = | + */ +static size_t +VpSetPTR(Real *a, Real *b, Real *c, size_t *a_pos, size_t *b_pos, size_t *c_pos, DECDIG *av, DECDIG *bv) +{ + size_t left_word, right_word, word_shift; + + size_t const round_limit = (VpGetPrecLimit() + BASE_FIG - 1) / BASE_FIG; + + assert(a->exponent >= b->exponent); + + c->frac[0] = 0; + *av = *bv = 0; + + word_shift = (a->exponent - b->exponent); + left_word = b->Prec + word_shift; + right_word = Max(a->Prec, left_word); + left_word = c->MaxPrec - 1; /* -1 ... prepare for round up */ + + /* + * check if 'round' is needed. + */ + if (right_word > left_word) { /* round ? */ + /*--------------------------------- + * Actual size of a = xxxxxxAxx + * Actual size of b = xxxBxxxxx + * Max. size of c = xxxxxx + * Round off = |-----| + * c_pos = | + * right_word = | + * a_pos = | + */ + *c_pos = right_word = left_word + 1; /* Set resulting precision */ + /* be equal to that of c */ + if (a->Prec >= c->MaxPrec) { + /* + * a = xxxxxxAxxx + * c = xxxxxx + * a_pos = | + */ + *a_pos = left_word; + if (*a_pos <= round_limit) { + *av = a->frac[*a_pos]; /* av is 'A' shown in above. */ + } + } + else { + /* + * a = xxxxxxx + * c = xxxxxxxxxx + * a_pos = | + */ + *a_pos = a->Prec; + } + if (b->Prec + word_shift >= c->MaxPrec) { + /* + * a = xxxxxxxxx + * b = xxxxxxxBxxx + * c = xxxxxxxxxxx + * b_pos = | + */ + if (c->MaxPrec >= word_shift + 1) { + *b_pos = c->MaxPrec - word_shift - 1; + if (*b_pos + word_shift <= round_limit) { + *bv = b->frac[*b_pos]; + } + } + else { + *b_pos = -1L; + } + } + else { + /* + * a = xxxxxxxxxxxxxxxx + * b = xxxxxx + * c = xxxxxxxxxxxxx + * b_pos = | + */ + *b_pos = b->Prec; + } + } + else { /* The MaxPrec of c - 1 > The Prec of a + b */ + /* + * a = xxxxxxx + * b = xxxxxx + * c = xxxxxxxxxxx + * c_pos = | + */ + *b_pos = b->Prec; + *a_pos = a->Prec; + *c_pos = right_word + 1; + } + c->Prec = *c_pos; + c->exponent = a->exponent; + if (!AddExponent(c, 1)) return (size_t)-1L; + return word_shift; +} + +/* + * Return number of significant digits + * c = a * b , Where a = a0a1a2 ... an + * b = b0b1b2 ... bm + * c = c0c1c2 ... cl + * a0 a1 ... an * bm + * a0 a1 ... an * bm-1 + * . . . + * . . . + * a0 a1 .... an * b0 + * +_____________________________ + * c0 c1 c2 ...... cl + * nc <---| + * MaxAB |--------------------| + */ +VP_EXPORT size_t +VpMult(Real *c, Real *a, Real *b) +{ + size_t MxIndA, MxIndB, MxIndAB, MxIndC; + size_t ind_c, i, ii, nc; + size_t ind_as, ind_ae, ind_bs; + DECDIG carry; + DECDIG_DBL s; + Real *w; + +#ifdef BIGDECIMAL_DEBUG + if (gfDebug) { + VPrint(stdout, "VpMult(Enter): a=% \n", a); + VPrint(stdout, " b=% \n", b); + } +#endif /* BIGDECIMAL_DEBUG */ + + if (!VpIsDefOP(c, a, b, OP_SW_MULT)) return 0; /* No significant digit */ + + if (VpIsZero(a) || VpIsZero(b)) { + /* at least a or b is zero */ + VpSetZero(c, VpGetSign(a) * VpGetSign(b)); + return 1; /* 0: 1 significant digit */ + } + + if (VpIsOne(a)) { + VpAsgn(c, b, VpGetSign(a)); + goto Exit; + } + if (VpIsOne(b)) { + VpAsgn(c, a, VpGetSign(b)); + goto Exit; + } + if (b->Prec > a->Prec) { + /* Adjust so that digits(a)>digits(b) */ + w = a; + a = b; + b = w; + } + w = NULL; + MxIndA = a->Prec - 1; + MxIndB = b->Prec - 1; + MxIndC = c->MaxPrec - 1; + MxIndAB = a->Prec + b->Prec - 1; + + if (MxIndC < MxIndAB) { /* The Max. prec. of c < Prec(a)+Prec(b) */ + w = c; + c = NewZeroNolimit(1, (size_t)((MxIndAB + 1) * BASE_FIG)); + MxIndC = MxIndAB; + } + + /* set LHSV c info */ + + c->exponent = a->exponent; /* set exponent */ + if (!AddExponent(c, b->exponent)) { + if (w) rbd_free_struct(c); + return 0; + } + VpSetSign(c, VpGetSign(a) * VpGetSign(b)); /* set sign */ + carry = 0; + nc = ind_c = MxIndAB; + memset(c->frac, 0, (nc + 1) * sizeof(DECDIG)); /* Initialize c */ + c->Prec = nc + 1; /* set precision */ + for (nc = 0; nc < MxIndAB; ++nc, --ind_c) { + if (nc < MxIndB) { /* The left triangle of the Fig. */ + ind_as = MxIndA - nc; + ind_ae = MxIndA; + ind_bs = MxIndB; + } + else if (nc <= MxIndA) { /* The middle rectangular of the Fig. */ + ind_as = MxIndA - nc; + ind_ae = MxIndA - (nc - MxIndB); + ind_bs = MxIndB; + } + else /* if (nc > MxIndA) */ { /* The right triangle of the Fig. */ + ind_as = 0; + ind_ae = MxIndAB - nc - 1; + ind_bs = MxIndB - (nc - MxIndA); + } + + for (i = ind_as; i <= ind_ae; ++i) { + s = (DECDIG_DBL)a->frac[i] * b->frac[ind_bs--]; + carry = (DECDIG)(s / BASE); + s -= (DECDIG_DBL)carry * BASE; + c->frac[ind_c] += (DECDIG)s; + if (c->frac[ind_c] >= BASE) { + s = c->frac[ind_c] / BASE; + carry += (DECDIG)s; + c->frac[ind_c] -= (DECDIG)(s * BASE); + } + if (carry) { + ii = ind_c; + while (ii-- > 0) { + c->frac[ii] += carry; + if (c->frac[ii] >= BASE) { + carry = c->frac[ii] / BASE; + c->frac[ii] -= (carry * BASE); + } + else { + break; + } + } + } + } + } + if (w != NULL) { /* free work variable */ + VpNmlz(c); + VpAsgn(w, c, 1); + rbd_free_struct(c); + c = w; + } + else { + VpLimitRound(c,0); + } + +Exit: +#ifdef BIGDECIMAL_DEBUG + if (gfDebug) { + VPrint(stdout, "VpMult(c=a*b): c=% \n", c); + VPrint(stdout, " a=% \n", a); + VPrint(stdout, " b=% \n", b); + } +#endif /*BIGDECIMAL_DEBUG */ + return c->Prec*BASE_FIG; +} + +/* + * c = a / b, remainder = r + */ +VP_EXPORT size_t +VpDivd(Real *c, Real *r, Real *a, Real *b) +{ + size_t word_a, word_b, word_c, word_r; + size_t i, n, ind_a, ind_b, ind_c, ind_r; + size_t nLoop; + DECDIG_DBL q, b1, b1p1, b1b2, b1b2p1, r1r2; + DECDIG borrow, borrow1, borrow2; + DECDIG_DBL qb; + +#ifdef BIGDECIMAL_DEBUG + if (gfDebug) { + VPrint(stdout, " VpDivd(c=a/b) a=% \n", a); + VPrint(stdout, " b=% \n", b); + } +#endif /*BIGDECIMAL_DEBUG */ + + VpSetNaN(r); + if (!VpIsDefOP(c, a, b, OP_SW_DIV)) goto Exit; + if (VpIsZero(a) && VpIsZero(b)) { + VpSetNaN(c); + return VpException(VP_EXCEPTION_NaN, "Computation results to 'NaN'", 0); + } + if (VpIsZero(b)) { + VpSetInf(c, VpGetSign(a) * VpGetSign(b)); + return VpException(VP_EXCEPTION_ZERODIVIDE, "Divide by zero", 0); + } + if (VpIsZero(a)) { + /* numerator a is zero */ + VpSetZero(c, VpGetSign(a) * VpGetSign(b)); + VpSetZero(r, VpGetSign(a) * VpGetSign(b)); + goto Exit; + } + if (VpIsOne(b)) { + /* divide by one */ + VpAsgn(c, a, VpGetSign(b)); + VpSetZero(r, VpGetSign(a)); + goto Exit; + } + + word_a = a->Prec; + word_b = b->Prec; + word_c = c->MaxPrec; + word_r = r->MaxPrec; + + if (word_a >= word_r || word_b + word_c - 2 >= word_r) goto space_error; + + ind_r = 1; + r->frac[0] = 0; + while (ind_r <= word_a) { + r->frac[ind_r] = a->frac[ind_r - 1]; + ++ind_r; + } + while (ind_r < word_r) r->frac[ind_r++] = 0; + + ind_c = 0; + while (ind_c < word_c) c->frac[ind_c++] = 0; + + /* initial procedure */ + b1 = b1p1 = b->frac[0]; + if (b->Prec <= 1) { + b1b2p1 = b1b2 = b1p1 * BASE; + } + else { + b1p1 = b1 + 1; + b1b2p1 = b1b2 = b1 * BASE + b->frac[1]; + if (b->Prec > 2) ++b1b2p1; + } + + /* */ + /* loop start */ + ind_c = word_r - 1; + nLoop = Min(word_c,ind_c); + ind_c = 1; + while (ind_c < nLoop) { + if (r->frac[ind_c] == 0) { + ++ind_c; + continue; + } + r1r2 = (DECDIG_DBL)r->frac[ind_c] * BASE + r->frac[ind_c + 1]; + if (r1r2 == b1b2) { + /* The first two word digits is the same */ + ind_b = 2; + ind_a = ind_c + 2; + while (ind_b < word_b) { + if (r->frac[ind_a] < b->frac[ind_b]) goto div_b1p1; + if (r->frac[ind_a] > b->frac[ind_b]) break; + ++ind_a; + ++ind_b; + } + /* The first few word digits of r and b is the same and */ + /* the first different word digit of w is greater than that */ + /* of b, so quotient is 1 and just subtract b from r. */ + borrow = 0; /* quotient=1, then just r-b */ + ind_b = b->Prec - 1; + ind_r = ind_c + ind_b; + if (ind_r >= word_r) goto space_error; + n = ind_b; + for (i = 0; i <= n; ++i) { + if (r->frac[ind_r] < b->frac[ind_b] + borrow) { + r->frac[ind_r] += (BASE - (b->frac[ind_b] + borrow)); + borrow = 1; + } + else { + r->frac[ind_r] = r->frac[ind_r] - b->frac[ind_b] - borrow; + borrow = 0; + } + --ind_r; + --ind_b; + } + ++c->frac[ind_c]; + goto carry; + } + /* The first two word digits is not the same, */ + /* then compare magnitude, and divide actually. */ + if (r1r2 >= b1b2p1) { + q = r1r2 / b1b2p1; /* q == (DECDIG)q */ + c->frac[ind_c] += (DECDIG)q; + ind_r = b->Prec + ind_c - 1; + goto sub_mult; + } + +div_b1p1: + if (ind_c + 1 >= word_c) goto out_side; + q = r1r2 / b1p1; /* q == (DECDIG)q */ + c->frac[ind_c + 1] += (DECDIG)q; + ind_r = b->Prec + ind_c; + +sub_mult: + borrow1 = borrow2 = 0; + ind_b = word_b - 1; + if (ind_r >= word_r) goto space_error; + n = ind_b; + for (i = 0; i <= n; ++i) { + /* now, perform r = r - q * b */ + qb = q * b->frac[ind_b]; + if (qb < BASE) borrow1 = 0; + else { + borrow1 = (DECDIG)(qb / BASE); + qb -= (DECDIG_DBL)borrow1 * BASE; /* get qb < BASE */ + } + if(r->frac[ind_r] < qb) { + r->frac[ind_r] += (DECDIG)(BASE - qb); + borrow2 = borrow2 + borrow1 + 1; + } + else { + r->frac[ind_r] -= (DECDIG)qb; + borrow2 += borrow1; + } + if (borrow2) { + if(r->frac[ind_r - 1] < borrow2) { + r->frac[ind_r - 1] += (BASE - borrow2); + borrow2 = 1; + } + else { + r->frac[ind_r - 1] -= borrow2; + borrow2 = 0; + } + } + --ind_r; + --ind_b; + } + + r->frac[ind_r] -= borrow2; +carry: + ind_r = ind_c; + while (c->frac[ind_r] >= BASE) { + c->frac[ind_r] -= BASE; + --ind_r; + ++c->frac[ind_r]; + } + } + /* End of operation, now final arrangement */ +out_side: + c->Prec = word_c; + c->exponent = a->exponent; + if (!AddExponent(c, 2)) return 0; + if (!AddExponent(c, -(b->exponent))) return 0; + + VpSetSign(c, VpGetSign(a) * VpGetSign(b)); + VpNmlz(c); /* normalize c */ + r->Prec = word_r; + r->exponent = a->exponent; + if (!AddExponent(r, 1)) return 0; + VpSetSign(r, VpGetSign(a)); + VpNmlz(r); /* normalize r(remainder) */ + goto Exit; + +space_error: +#ifdef BIGDECIMAL_DEBUG + if (gfDebug) { + printf(" word_a=%"PRIuSIZE"\n", word_a); + printf(" word_b=%"PRIuSIZE"\n", word_b); + printf(" word_c=%"PRIuSIZE"\n", word_c); + printf(" word_r=%"PRIuSIZE"\n", word_r); + printf(" ind_r =%"PRIuSIZE"\n", ind_r); + } +#endif /* BIGDECIMAL_DEBUG */ + rb_bug("ERROR(VpDivd): space for remainder too small."); + +Exit: +#ifdef BIGDECIMAL_DEBUG + if (gfDebug) { + VPrint(stdout, " VpDivd(c=a/b), c=% \n", c); + VPrint(stdout, " r=% \n", r); + } +#endif /* BIGDECIMAL_DEBUG */ + return c->Prec * BASE_FIG; +} + +/* + * Input a = 00000xxxxxxxx En(5 preceding zeros) + * Output a = xxxxxxxx En-5 + */ +static int +VpNmlz(Real *a) +{ + size_t ind_a, i; + + if (!VpIsDef(a)) goto NoVal; + if (VpIsZero(a)) goto NoVal; + + ind_a = a->Prec; + while (ind_a--) { + if (a->frac[ind_a]) { + a->Prec = ind_a + 1; + i = 0; + while (a->frac[i] == 0) ++i; /* skip the first few zeros */ + if (i) { + a->Prec -= i; + if (!AddExponent(a, -(SIGNED_VALUE)i)) return 0; + memmove(&a->frac[0], &a->frac[i], a->Prec*sizeof(DECDIG)); + } + return 1; + } + } + /* a is zero(no non-zero digit) */ + VpSetZero(a, VpGetSign(a)); + return 0; + +NoVal: + a->frac[0] = 0; + a->Prec = 1; + return 0; +} + +/* + * VpComp = 0 ... if a=b, + * Pos ... a>b, + * Neg ... asign - b->sign; + else e = a->sign; + + if (e > 0) return 1; + else if (e < 0) return -1; + else return 0; + } + if (!VpIsDef(b)) { + e = -b->sign; + if (e > 0) return 1; + else return -1; + } + /* Zero check */ + if (VpIsZero(a)) { + if (VpIsZero(b)) return 0; /* both zero */ + val = -VpGetSign(b); + goto Exit; + } + if (VpIsZero(b)) { + val = VpGetSign(a); + goto Exit; + } + + /* compare sign */ + if (VpGetSign(a) > VpGetSign(b)) { + val = 1; /* a>b */ + goto Exit; + } + if (VpGetSign(a) < VpGetSign(b)) { + val = -1; /* aexponent > b->exponent) { + val = VpGetSign(a); + goto Exit; + } + if (a->exponent < b->exponent) { + val = -VpGetSign(b); + goto Exit; + } + + /* a and b have same exponent, then compare their significand. */ + mx = (a->Prec < b->Prec) ? a->Prec : b->Prec; + ind = 0; + while (ind < mx) { + if (a->frac[ind] > b->frac[ind]) { + val = VpGetSign(a); + goto Exit; + } + if (a->frac[ind] < b->frac[ind]) { + val = -VpGetSign(b); + goto Exit; + } + ++ind; + } + if (a->Prec > b->Prec) { + val = VpGetSign(a); + } + else if (a->Prec < b->Prec) { + val = -VpGetSign(b); + } + +Exit: + if (val > 1) val = 1; + else if (val < -1) val = -1; + +#ifdef BIGDECIMAL_DEBUG + if (gfDebug) { + VPrint(stdout, " VpComp a=%\n", a); + VPrint(stdout, " b=%\n", b); + printf(" ans=%d\n", val); + } +#endif /* BIGDECIMAL_DEBUG */ + return (int)val; +} + +/* + * cntl_chr ... ASCIIZ Character, print control characters + * Available control codes: + * % ... VP variable. To print '%', use '%%'. + * \n ... new line + * \b ... backspace + * \t ... tab + * Note: % must not appear more than once + * a ... VP variable to be printed + */ +static int +VPrint(FILE *fp, const char *cntl_chr, Real *a) +{ + size_t i, j, nc, nd, ZeroSup, sep = 10; + DECDIG m, e, nn; + + j = 0; + nd = nc = 0; /* nd : number of digits in fraction part(every 10 digits, */ + /* nd<=10). */ + /* nc : number of characters printed */ + ZeroSup = 1; /* Flag not to print the leading zeros as 0.00xxxxEnn */ + while (*(cntl_chr + j)) { + if (*(cntl_chr + j) == '%' && *(cntl_chr + j + 1) != '%') { + nc = 0; + if (VpIsNaN(a)) { + fprintf(fp, SZ_NaN); + nc += 8; + } + else if (VpIsPosInf(a)) { + fprintf(fp, SZ_INF); + nc += 8; + } + else if (VpIsNegInf(a)) { + fprintf(fp, SZ_NINF); + nc += 9; + } + else if (!VpIsZero(a)) { + if (BIGDECIMAL_NEGATIVE_P(a)) { + fprintf(fp, "-"); + ++nc; + } + nc += fprintf(fp, "0."); + switch (*(cntl_chr + j + 1)) { + default: + break; + + case '0': case 'z': + ZeroSup = 0; + ++j; + sep = cntl_chr[j] == 'z' ? BIGDECIMAL_COMPONENT_FIGURES : 10; + break; + } + for (i = 0; i < a->Prec; ++i) { + m = BASE1; + e = a->frac[i]; + while (m) { + nn = e / m; + if (!ZeroSup || nn) { + nc += fprintf(fp, "%lu", (unsigned long)nn); /* The leading zero(s) */ + /* as 0.00xx will not */ + /* be printed. */ + ++nd; + ZeroSup = 0; /* Set to print succeeding zeros */ + } + if (nd >= sep) { /* print ' ' after every 10 digits */ + nd = 0; + nc += fprintf(fp, " "); + } + e = e - nn * m; + m /= 10; + } + } + nc += fprintf(fp, "E%"PRIdSIZE, VpExponent10(a)); + nc += fprintf(fp, " (%"PRIdVALUE", %"PRIuSIZE", %"PRIuSIZE")", a->exponent, a->Prec, a->MaxPrec); + } + else { + nc += fprintf(fp, "0.0"); + } + } + else { + ++nc; + if (*(cntl_chr + j) == '\\') { + switch (*(cntl_chr + j + 1)) { + case 'n': + fprintf(fp, "\n"); + ++j; + break; + case 't': + fprintf(fp, "\t"); + ++j; + break; + case 'b': + fprintf(fp, "\n"); + ++j; + break; + default: + fprintf(fp, "%c", *(cntl_chr + j)); + break; + } + } + else { + fprintf(fp, "%c", *(cntl_chr + j)); + if (*(cntl_chr + j) == '%') ++j; + } + } + j++; + } + + return (int)nc; +} + +static void +VpFormatSt(char *psz, size_t fFmt) +{ + size_t ie, i, nf = 0; + char ch; + + if (fFmt == 0) return; + + ie = strlen(psz); + for (i = 0; i < ie; ++i) { + ch = psz[i]; + if (!ch) break; + if (ISSPACE(ch) || ch=='-' || ch=='+') continue; + if (ch == '.') { nf = 0; continue; } + if (ch == 'E' || ch == 'e') break; + + if (++nf > fFmt) { + memmove(psz + i + 1, psz + i, ie - i + 1); + ++ie; + nf = 0; + psz[i] = ' '; + } + } +} + +VP_EXPORT ssize_t +VpExponent10(Real *a) +{ + ssize_t ex; + size_t n; + + if (!VpHasVal(a)) return 0; + + ex = a->exponent * (ssize_t)BASE_FIG; + n = BASE1; + while ((a->frac[0] / n) == 0) { + --ex; + n /= 10; + } + return ex; +} + +VP_EXPORT void +VpSzMantissa(Real *a, char *buf, size_t buflen) +{ + size_t i, n, ZeroSup; + DECDIG_DBL m, e, nn; + + if (VpIsNaN(a)) { + snprintf(buf, buflen, SZ_NaN); + return; + } + if (VpIsPosInf(a)) { + snprintf(buf, buflen, SZ_INF); + return; + } + if (VpIsNegInf(a)) { + snprintf(buf, buflen, SZ_NINF); + return; + } + + ZeroSup = 1; /* Flag not to print the leading zeros as 0.00xxxxEnn */ + if (!VpIsZero(a)) { + if (BIGDECIMAL_NEGATIVE_P(a)) *buf++ = '-'; + n = a->Prec; + for (i = 0; i < n; ++i) { + m = BASE1; + e = a->frac[i]; + while (m) { + nn = e / m; + if (!ZeroSup || nn) { + snprintf(buf, buflen, "%lu", (unsigned long)nn); /* The leading zero(s) */ + buf += strlen(buf); + /* as 0.00xx will be ignored. */ + ZeroSup = 0; /* Set to print succeeding zeros */ + } + e = e - nn * m; + m /= 10; + } + } + *buf = 0; + while (buf[-1] == '0') *(--buf) = 0; + } + else { + if (VpIsPosZero(a)) snprintf(buf, buflen, "0"); + else snprintf(buf, buflen, "-0"); + } +} + +VP_EXPORT int +VpToSpecialString(Real *a, char *buf, size_t buflen, int fPlus) +/* fPlus = 0: default, 1: set ' ' before digits, 2: set '+' before digits. */ +{ + if (VpIsNaN(a)) { + snprintf(buf, buflen, SZ_NaN); + return 1; + } + + if (VpIsPosInf(a)) { + if (fPlus == 1) { + *buf++ = ' '; + } + else if (fPlus == 2) { + *buf++ = '+'; + } + snprintf(buf, buflen, SZ_INF); + return 1; + } + if (VpIsNegInf(a)) { + snprintf(buf, buflen, SZ_NINF); + return 1; + } + if (VpIsZero(a)) { + if (VpIsPosZero(a)) { + if (fPlus == 1) snprintf(buf, buflen, " 0.0"); + else if (fPlus == 2) snprintf(buf, buflen, "+0.0"); + else snprintf(buf, buflen, "0.0"); + } + else snprintf(buf, buflen, "-0.0"); + return 1; + } + return 0; +} + +VP_EXPORT void +VpToString(Real *a, char *buf, size_t buflen, size_t fFmt, int fPlus) +/* fPlus = 0: default, 1: set ' ' before digits, 2: set '+' before digits. */ +{ + size_t i, n, ZeroSup; + DECDIG shift, m, e, nn; + char *p = buf; + size_t plen = buflen; + ssize_t ex; + + if (VpToSpecialString(a, buf, buflen, fPlus)) return; + + ZeroSup = 1; /* Flag not to print the leading zeros as 0.00xxxxEnn */ + +#define ADVANCE(n) do { \ + if (plen < n) goto overflow; \ + p += n; \ + plen -= n; \ +} while (0) + + if (BIGDECIMAL_NEGATIVE_P(a)) { + *p = '-'; + ADVANCE(1); + } + else if (fPlus == 1) { + *p = ' '; + ADVANCE(1); + } + else if (fPlus == 2) { + *p = '+'; + ADVANCE(1); + } + + *p = '0'; ADVANCE(1); + *p = '.'; ADVANCE(1); + + n = a->Prec; + for (i = 0; i < n; ++i) { + m = BASE1; + e = a->frac[i]; + while (m) { + nn = e / m; + if (!ZeroSup || nn) { + /* The reading zero(s) */ + size_t n = (size_t)snprintf(p, plen, "%lu", (unsigned long)nn); + if (n > plen) goto overflow; + ADVANCE(n); + /* as 0.00xx will be ignored. */ + ZeroSup = 0; /* Set to print succeeding zeros */ + } + e = e - nn * m; + m /= 10; + } + } + + ex = a->exponent * (ssize_t)BASE_FIG; + shift = BASE1; + while (a->frac[0] / shift == 0) { + --ex; + shift /= 10; + } + while (p - 1 > buf && p[-1] == '0') { + *(--p) = '\0'; + ++plen; + } + snprintf(p, plen, "e%"PRIdSIZE, ex); + if (fFmt) VpFormatSt(buf, fFmt); + + overflow: + return; +#undef ADVANCE +} + +VP_EXPORT void +VpToFString(Real *a, char *buf, size_t buflen, size_t fFmt, int fPlus) +/* fPlus = 0: default, 1: set ' ' before digits, 2: set '+' before digits. */ +{ + size_t i, n; + DECDIG m, e; + char *p = buf; + size_t plen = buflen, delim = fFmt; + ssize_t ex; + + if (VpToSpecialString(a, buf, buflen, fPlus)) return; + +#define APPEND(c, group) do { \ + if (plen < 1) goto overflow; \ + if (group && delim == 0) { \ + *p = ' '; \ + p += 1; \ + plen -= 1; \ + } \ + if (plen < 1) goto overflow; \ + *p = c; \ + p += 1; \ + plen -= 1; \ + if (group) delim = (delim + 1) % fFmt; \ +} while (0) + + + if (BIGDECIMAL_NEGATIVE_P(a)) { + APPEND('-', false); + } + else if (fPlus == 1) { + APPEND(' ', false); + } + else if (fPlus == 2) { + APPEND('+', false); + } + + n = a->Prec; + ex = a->exponent; + if (ex <= 0) { + APPEND('0', false); + APPEND('.', false); + } + while (ex < 0) { + for (i=0; i < BASE_FIG; ++i) { + APPEND('0', fFmt > 0); + } + ++ex; + } + + for (i = 0; i < n; ++i) { + m = BASE1; + e = a->frac[i]; + if (i == 0 && ex > 0) { + for (delim = 0; e / m == 0; delim++) { + m /= 10; + } + if (fFmt > 0) { + delim = 2*fFmt - (ex * BASE_FIG - delim) % fFmt; + } + } + while (m && (e || (i < n - 1) || ex > 0)) { + APPEND((char)(e / m + '0'), fFmt > 0); + e %= m; + m /= 10; + } + if (--ex == 0) { + APPEND('.', false); + delim = fFmt; + } + } + + while (ex > 0) { + for (i=0; i < BASE_FIG; ++i) { + APPEND('0', fFmt > 0); + } + if (--ex == 0) { + APPEND('.', false); + } + } + + *p = '\0'; + if (p - 1 > buf && p[-1] == '.') { + snprintf(p, plen, "0"); + } + + overflow: + return; +#undef APPEND +} + +/* + * [Output] + * a[] ... variable to be assigned the value. + * [Input] + * int_chr[] ... integer part(may include '+/-'). + * ni ... number of characters in int_chr[],not including '+/-'. + * frac[] ... fraction part. + * nf ... number of characters in frac[]. + * exp_chr[] ... exponent part(including '+/-'). + * ne ... number of characters in exp_chr[],not including '+/-'. + */ +VP_EXPORT int +VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, const char *exp_chr, size_t ne) +{ + size_t i, j, ind_a, ma, mi, me; + SIGNED_VALUE e, es, eb, ef; + int sign, signe, exponent_overflow; + + /* get exponent part */ + e = 0; + ma = a->MaxPrec; + mi = ni; + me = ne; + signe = 1; + exponent_overflow = 0; + memset(a->frac, 0, ma * sizeof(DECDIG)); + if (ne > 0) { + i = 0; + if (exp_chr[0] == '-') { + signe = -1; + ++i; + ++me; + } + else if (exp_chr[0] == '+') { + ++i; + ++me; + } + while (i < me) { + if (MUL_OVERFLOW_SIGNED_VALUE_P(e, (SIGNED_VALUE)BASE_FIG)) { + es = e; + goto exp_overflow; + } + es = e * (SIGNED_VALUE)BASE_FIG; + if (MUL_OVERFLOW_SIGNED_VALUE_P(e, 10) || + SIGNED_VALUE_MAX - (exp_chr[i] - '0') < e * 10) + goto exp_overflow; + e = e * 10 + exp_chr[i] - '0'; + if (MUL_OVERFLOW_SIGNED_VALUE_P(e, (SIGNED_VALUE)BASE_FIG)) + goto exp_overflow; + if (es > (SIGNED_VALUE)(e * BASE_FIG)) { + exp_overflow: + exponent_overflow = 1; + e = es; /* keep sign */ + break; + } + ++i; + } + } + + /* get integer part */ + i = 0; + sign = 1; + if (1 /*ni >= 0*/) { + if (int_chr[0] == '-') { + sign = -1; + ++i; + ++mi; + } + else if (int_chr[0] == '+') { + ++i; + ++mi; + } + } + + e = signe * e; /* e: The value of exponent part. */ + e = e + ni; /* set actual exponent size. */ + + if (e > 0) signe = 1; + else signe = -1; + + /* Adjust the exponent so that it is the multiple of BASE_FIG. */ + j = 0; + ef = 1; + while (ef) { + if (e >= 0) eb = e; + else eb = -e; + ef = eb / (SIGNED_VALUE)BASE_FIG; + ef = eb - ef * (SIGNED_VALUE)BASE_FIG; + if (ef) { + ++j; /* Means to add one more preceding zero */ + ++e; + } + } + + eb = e / (SIGNED_VALUE)BASE_FIG; + + if (exponent_overflow) { + int zero = 1; + for ( ; i < mi && zero; i++) zero = int_chr[i] == '0'; + for (i = 0; i < nf && zero; i++) zero = frac[i] == '0'; + if (!zero && signe > 0) { + VpSetInf(a, sign); + VpException(VP_EXCEPTION_INFINITY, "exponent overflow",0); + } + else VpSetZero(a, sign); + return 1; + } + + ind_a = 0; + while (i < mi) { + a->frac[ind_a] = 0; + while (j < BASE_FIG && i < mi) { + a->frac[ind_a] = a->frac[ind_a] * 10 + int_chr[i] - '0'; + ++j; + ++i; + } + if (i < mi) { + ++ind_a; + if (ind_a >= ma) goto over_flow; + j = 0; + } + } + + /* get fraction part */ + + i = 0; + while (i < nf) { + while (j < BASE_FIG && i < nf) { + a->frac[ind_a] = a->frac[ind_a] * 10 + frac[i] - '0'; + ++j; + ++i; + } + if (i < nf) { + ++ind_a; + if (ind_a >= ma) goto over_flow; + j = 0; + } + } + goto Final; + +over_flow: + rb_warn("Conversion from String to BigDecimal overflow (last few digits discarded)."); + +Final: + if (ind_a >= ma) ind_a = ma - 1; + while (j < BASE_FIG) { + a->frac[ind_a] = a->frac[ind_a] * 10; + ++j; + } + a->Prec = ind_a + 1; + a->exponent = eb; + VpSetSign(a, sign); + VpNmlz(a); + return 1; +} + +/* + * [Input] + * *m ... Real + * [Output] + * *d ... fraction part of m(d = 0.xxxxxxx). where # of 'x's is fig. + * *e ... exponent of m. + * BIGDECIMAL_DOUBLE_FIGURES ... Number of digits in a double variable. + * + * m -> d*10**e, 0Prec); + *d = 0.0; + div = 1.; + while (ind_m < mm) { + div /= (double)BASE; + *d = *d + (double)m->frac[ind_m++] * div; + } + *e = m->exponent * (SIGNED_VALUE)BASE_FIG; + *d *= VpGetSign(m); + +Exit: +#ifdef BIGDECIMAL_DEBUG + if (gfDebug) { + VPrint(stdout, " VpVtoD: m=%\n", m); + printf(" d=%e * 10 **%ld\n", *d, *e); + printf(" BIGDECIMAL_DOUBLE_FIGURES = %d\n", BIGDECIMAL_DOUBLE_FIGURES); + } +#endif /*BIGDECIMAL_DEBUG */ + return f; +} + +/* + * m <- d + */ +VP_EXPORT void +VpDtoV(Real *m, double d) +{ + size_t ind_m, mm; + SIGNED_VALUE ne; + DECDIG i; + double val, val2; + + if (isnan(d)) { + VpSetNaN(m); + goto Exit; + } + if (isinf(d)) { + if (d > 0.0) VpSetPosInf(m); + else VpSetNegInf(m); + goto Exit; + } + + if (d == 0.0) { + VpSetZero(m, 1); + goto Exit; + } + val = (d > 0.) ? d : -d; + ne = 0; + if (val >= 1.0) { + while (val >= 1.0) { + val /= (double)BASE; + ++ne; + } + } + else { + val2 = 1.0 / (double)BASE; + while (val < val2) { + val *= (double)BASE; + --ne; + } + } + /* Now val = 0.xxxxx*BASE**ne */ + + mm = m->MaxPrec; + memset(m->frac, 0, mm * sizeof(DECDIG)); + for (ind_m = 0; val > 0.0 && ind_m < mm; ind_m++) { + val *= (double)BASE; + i = (DECDIG)val; + val -= (double)i; + m->frac[ind_m] = i; + } + if (ind_m >= mm) ind_m = mm - 1; + VpSetSign(m, (d > 0.0) ? 1 : -1); + m->Prec = ind_m + 1; + m->exponent = ne; + + VpInternalRound(m, 0, (m->Prec > 0) ? m->frac[m->Prec-1] : 0, + (DECDIG)(val*(double)BASE)); + +Exit: +#ifdef BIGDECIMAL_DEBUG + if (gfDebug) { + printf("VpDtoV d=%30.30e\n", d); + VPrint(stdout, " m=%\n", m); + } +#endif /* BIGDECIMAL_DEBUG */ + return; +} + +/* + * m <- ival + */ +#if 0 /* unused */ +VP_EXPORT void +VpItoV(Real *m, SIGNED_VALUE ival) +{ + size_t mm, ind_m; + size_t val, v1, v2, v; + int isign; + SIGNED_VALUE ne; + + if (ival == 0) { + VpSetZero(m, 1); + goto Exit; + } + isign = 1; + val = ival; + if (ival < 0) { + isign = -1; + val =(size_t)(-ival); + } + ne = 0; + ind_m = 0; + mm = m->MaxPrec; + while (ind_m < mm) { + m->frac[ind_m] = 0; + ++ind_m; + } + ind_m = 0; + while (val > 0) { + if (val) { + v1 = val; + v2 = 1; + while (v1 >= BASE) { + v1 /= BASE; + v2 *= BASE; + } + val = val - v2 * v1; + v = v1; + } + else { + v = 0; + } + m->frac[ind_m] = v; + ++ind_m; + ++ne; + } + m->Prec = ind_m - 1; + m->exponent = ne; + VpSetSign(m, isign); + VpNmlz(m); + +Exit: +#ifdef BIGDECIMAL_DEBUG + if (gfDebug) { + printf(" VpItoV i=%d\n", ival); + VPrint(stdout, " m=%\n", m); + } +#endif /* BIGDECIMAL_DEBUG */ + return; +} +#endif + +/* + * y = SQRT(x), y*y - x =>0 + */ +VP_EXPORT int +VpSqrt(Real *y, Real *x) +{ + Real *f = NULL; + Real *r = NULL; + size_t y_prec; + SIGNED_VALUE n, e; + ssize_t nr; + double val; + + /* Zero or +Infinity ? */ + if (VpIsZero(x) || VpIsPosInf(x)) { + VpAsgn(y,x,1); + goto Exit; + } + + /* Negative ? */ + if (BIGDECIMAL_NEGATIVE_P(x)) { + VpSetNaN(y); + return VpException(VP_EXCEPTION_OP, "sqrt of negative value", 0); + } + + /* NaN ? */ + if (VpIsNaN(x)) { + VpSetNaN(y); + return VpException(VP_EXCEPTION_OP, "sqrt of 'NaN'(Not a Number)", 0); + } + + /* One ? */ + if (VpIsOne(x)) { + VpSetOne(y); + goto Exit; + } + + n = (SIGNED_VALUE)y->MaxPrec; + if (x->MaxPrec > (size_t)n) n = (ssize_t)x->MaxPrec; + + /* allocate temporally variables */ + /* TODO: reconsider MaxPrec of f and r */ + f = NewOneNolimit(1, y->MaxPrec * (BASE_FIG + 2)); + r = NewOneNolimit(1, (n + n) * (BASE_FIG + 2)); + + nr = 0; + y_prec = y->MaxPrec; + + VpVtoD(&val, &e, x); /* val <- x */ + e /= (SIGNED_VALUE)BASE_FIG; + n = e / 2; + if (e - n * 2 != 0) { + val /= BASE; + n = (e + 1) / 2; + } + VpDtoV(y, sqrt(val)); /* y <- sqrt(val) */ + y->exponent += n; + n = (SIGNED_VALUE)roomof(BIGDECIMAL_DOUBLE_FIGURES, BASE_FIG); + y->MaxPrec = Min((size_t)n , y_prec); + f->MaxPrec = y->MaxPrec + 1; + n = (SIGNED_VALUE)(y_prec * BASE_FIG); + if (n > (SIGNED_VALUE)maxnr) n = (SIGNED_VALUE)maxnr; + + /* + * Perform: y_{n+1} = (y_n - x/y_n) / 2 + */ + do { + y->MaxPrec *= 2; + if (y->MaxPrec > y_prec) y->MaxPrec = y_prec; + f->MaxPrec = y->MaxPrec; + VpDivd(f, r, x, y); /* f = x/y */ + VpAddSub(r, f, y, -1); /* r = f - y */ + VpMult(f, VpConstPt5, r); /* f = 0.5*r */ + if (VpIsZero(f)) + goto converge; + VpAddSub(r, f, y, 1); /* r = y + f */ + VpAsgn(y, r, 1); /* y = r */ + } while (++nr < n); + +#ifdef BIGDECIMAL_DEBUG + if (gfDebug) { + printf("ERROR(VpSqrt): did not converge within %ld iterations.\n", nr); + } +#endif /* BIGDECIMAL_DEBUG */ + y->MaxPrec = y_prec; + +converge: + VpChangeSign(y, 1); +#ifdef BIGDECIMAL_DEBUG + if (gfDebug) { + VpMult(r, y, y); + VpAddSub(f, x, r, -1); + printf("VpSqrt: iterations = %"PRIdSIZE"\n", nr); + VPrint(stdout, " y =% \n", y); + VPrint(stdout, " x =% \n", x); + VPrint(stdout, " x-y*y = % \n", f); + } +#endif /* BIGDECIMAL_DEBUG */ + y->MaxPrec = y_prec; + +Exit: + rbd_free_struct(f); + rbd_free_struct(r); + return 1; +} + +/* + * Round relatively from the decimal point. + * f: rounding mode + * nf: digit location to round from the decimal point. + */ +VP_EXPORT int +VpMidRound(Real *y, unsigned short f, ssize_t nf) +{ + /* fracf: any positive digit under rounding position? */ + /* fracf_1further: any positive digits under one further than the rounding position? */ + /* exptoadd: number of digits needed to compensate negative nf */ + int fracf, fracf_1further; + ssize_t n,i,ix,ioffset, exptoadd; + DECDIG v, shifter; + DECDIG div; + + nf += y->exponent * (ssize_t)BASE_FIG; + exptoadd=0; + if (nf < 0) { + /* rounding position too left(large). */ + if (f != VP_ROUND_CEIL && f != VP_ROUND_FLOOR) { + VpSetZero(y, VpGetSign(y)); /* truncate everything */ + return 0; + } + exptoadd = -nf; + nf = 0; + } + + ix = nf / (ssize_t)BASE_FIG; + if ((size_t)ix >= y->Prec) return 0; /* rounding position too right(small). */ + v = y->frac[ix]; + + ioffset = nf - ix*(ssize_t)BASE_FIG; + n = (ssize_t)BASE_FIG - ioffset - 1; + for (shifter = 1, i = 0; i < n; ++i) shifter *= 10; + + /* so the representation used (in y->frac) is an array of DECDIG, where + each DECDIG contains a value between 0 and BASE-1, consisting of BASE_FIG + decimal places. + + (that numbers of decimal places are typed as ssize_t is somewhat confusing) + + nf is now position (in decimal places) of the digit from the start of + the array. + + ix is the position (in DECDIGs) of the DECDIG containing the decimal digit, + from the start of the array. + + v is the value of this DECDIG + + ioffset is the number of extra decimal places along of this decimal digit + within v. + + n is the number of decimal digits remaining within v after this decimal digit + shifter is 10**n, + + v % shifter are the remaining digits within v + v % (shifter * 10) are the digit together with the remaining digits within v + v / shifter are the digit's predecessors together with the digit + div = v / shifter / 10 is just the digit's precessors + (v / shifter) - div*10 is just the digit, which is what v ends up being reassigned to. + */ + + fracf = (v % (shifter * 10) > 0); + fracf_1further = ((v % shifter) > 0); + + v /= shifter; + div = v / 10; + v = v - div*10; + /* now v is just the digit required. + now fracf is whether the digit or any of the remaining digits within v are non-zero + now fracf_1further is whether any of the remaining digits within v are non-zero + */ + + /* now check all the remaining DECDIGs for zero-ness a whole DECDIG at a time. + if we spot any non-zeroness, that means that we found a positive digit under + rounding position, and we also found a positive digit under one further than + the rounding position, so both searches (to see if any such non-zero digit exists) + can stop */ + + for (i = ix + 1; (size_t)i < y->Prec; i++) { + if (y->frac[i] % BASE) { + fracf = fracf_1further = 1; + break; + } + } + + /* now fracf = does any positive digit exist under the rounding position? + now fracf_1further = does any positive digit exist under one further than the + rounding position? + now v = the first digit under the rounding position */ + + /* drop digits after pointed digit */ + memset(y->frac + ix + 1, 0, (y->Prec - (ix + 1)) * sizeof(DECDIG)); + + switch (f) { + case VP_ROUND_DOWN: /* Truncate */ + break; + case VP_ROUND_UP: /* Roundup */ + if (fracf) ++div; + break; + case VP_ROUND_HALF_UP: + if (v>=5) ++div; + break; + case VP_ROUND_HALF_DOWN: + if (v > 5 || (v == 5 && fracf_1further)) ++div; + break; + case VP_ROUND_CEIL: + if (fracf && BIGDECIMAL_POSITIVE_P(y)) ++div; + break; + case VP_ROUND_FLOOR: + if (fracf && BIGDECIMAL_NEGATIVE_P(y)) ++div; + break; + case VP_ROUND_HALF_EVEN: /* Banker's rounding */ + if (v > 5) ++div; + else if (v == 5) { + if (fracf_1further) { + ++div; + } + else { + if (ioffset == 0) { + /* v is the first decimal digit of its DECDIG; + need to grab the previous DECDIG if present + to check for evenness of the previous decimal + digit (which is same as that of the DECDIG since + base 10 has a factor of 2) */ + if (ix && (y->frac[ix-1] % 2)) ++div; + } + else { + if (div % 2) ++div; + } + } + } + break; + } + for (i = 0; i <= n; ++i) div *= 10; + if (div >= BASE) { + if (ix) { + y->frac[ix] = 0; + VpRdup(y, ix); + } + else { + short s = VpGetSign(y); + SIGNED_VALUE e = y->exponent; + VpSetOne(y); + VpSetSign(y, s); + y->exponent = e + 1; + } + } + else { + y->frac[ix] = div; + VpNmlz(y); + } + if (exptoadd > 0) { + y->exponent += (SIGNED_VALUE)(exptoadd / BASE_FIG); + exptoadd %= (ssize_t)BASE_FIG; + for (i = 0; i < exptoadd; i++) { + y->frac[0] *= 10; + if (y->frac[0] >= BASE) { + y->frac[0] /= BASE; + y->exponent++; + } + } + } + return 1; +} + +VP_EXPORT int +VpLeftRound(Real *y, unsigned short f, ssize_t nf) +/* + * Round from the left hand side of the digits. + */ +{ + DECDIG v; + if (!VpHasVal(y)) return 0; /* Unable to round */ + v = y->frac[0]; + nf -= VpExponent(y) * (ssize_t)BASE_FIG; + while ((v /= 10) != 0) nf--; + nf += (ssize_t)BASE_FIG-1; + return VpMidRound(y, f, nf); +} + +VP_EXPORT int +VpActiveRound(Real *y, Real *x, unsigned short f, ssize_t nf) +{ + /* First,assign whole value in truncation mode */ + if (VpAsgn(y, x, 10) <= 1) return 0; /* Zero,NaN,or Infinity */ + return VpMidRound(y, f, nf); +} + +static int +VpLimitRound(Real *c, size_t ixDigit) +{ + size_t ix = VpGetPrecLimit(); + if (!VpNmlz(c)) return -1; + if (!ix) return 0; + if (!ixDigit) ixDigit = c->Prec-1; + if ((ix + BASE_FIG - 1) / BASE_FIG > ixDigit + 1) return 0; + return VpLeftRound(c, VpGetRoundMode(), (ssize_t)ix); +} + +/* If I understand correctly, this is only ever used to round off the final decimal + digit of precision */ +static void +VpInternalRound(Real *c, size_t ixDigit, DECDIG vPrev, DECDIG v) +{ + int f = 0; + + unsigned short const rounding_mode = VpGetRoundMode(); + + if (VpLimitRound(c, ixDigit)) return; + if (!v) return; + + v /= BASE1; + switch (rounding_mode) { + case VP_ROUND_DOWN: + break; + case VP_ROUND_UP: + if (v) f = 1; + break; + case VP_ROUND_HALF_UP: + if (v >= 5) f = 1; + break; + case VP_ROUND_HALF_DOWN: + /* this is ok - because this is the last digit of precision, + the case where v == 5 and some further digits are nonzero + will never occur */ + if (v >= 6) f = 1; + break; + case VP_ROUND_CEIL: + if (v && BIGDECIMAL_POSITIVE_P(c)) f = 1; + break; + case VP_ROUND_FLOOR: + if (v && BIGDECIMAL_NEGATIVE_P(c)) f = 1; + break; + case VP_ROUND_HALF_EVEN: /* Banker's rounding */ + /* as per VP_ROUND_HALF_DOWN, because this is the last digit of precision, + there is no case to worry about where v == 5 and some further digits are nonzero */ + if (v > 5) f = 1; + else if (v == 5 && vPrev % 2) f = 1; + break; + } + if (f) { + VpRdup(c, ixDigit); + VpNmlz(c); + } +} + +/* + * Rounds up m(plus one to final digit of m). + */ +static int +VpRdup(Real *m, size_t ind_m) +{ + DECDIG carry; + + if (!ind_m) ind_m = m->Prec; + + carry = 1; + while (carry > 0 && ind_m--) { + m->frac[ind_m] += carry; + if (m->frac[ind_m] >= BASE) m->frac[ind_m] -= BASE; + else carry = 0; + } + if (carry > 0) { /* Overflow,count exponent and set fraction part be 1 */ + if (!AddExponent(m, 1)) return 0; + m->Prec = m->frac[0] = 1; + } + else { + VpNmlz(m); + } + return 1; +} + +/* + * y = x - fix(x) + */ +VP_EXPORT void +VpFrac(Real *y, Real *x) +{ + size_t my, ind_y, ind_x; + + if (!VpHasVal(x)) { + VpAsgn(y, x, 1); + goto Exit; + } + + if (x->exponent > 0 && (size_t)x->exponent >= x->Prec) { + VpSetZero(y, VpGetSign(x)); + goto Exit; + } + else if (x->exponent <= 0) { + VpAsgn(y, x, 1); + goto Exit; + } + + /* satisfy: x->exponent > 0 */ + + y->Prec = x->Prec - (size_t)x->exponent; + y->Prec = Min(y->Prec, y->MaxPrec); + y->exponent = 0; + VpSetSign(y, VpGetSign(x)); + ind_y = 0; + my = y->Prec; + ind_x = x->exponent; + while (ind_y < my) { + y->frac[ind_y] = x->frac[ind_x]; + ++ind_y; + ++ind_x; + } + VpNmlz(y); + +Exit: +#ifdef BIGDECIMAL_DEBUG + if (gfDebug) { + VPrint(stdout, "VpFrac y=%\n", y); + VPrint(stdout, " x=%\n", x); + } +#endif /* BIGDECIMAL_DEBUG */ + return; +} + +/* + * y = x ** n + */ +VP_EXPORT int +VpPowerByInt(Real *y, Real *x, SIGNED_VALUE n) +{ + size_t s, ss; + ssize_t sign; + Real *w1 = NULL; + Real *w2 = NULL; + + if (VpIsZero(x)) { + if (n == 0) { + VpSetOne(y); + goto Exit; + } + sign = VpGetSign(x); + if (n < 0) { + n = -n; + if (sign < 0) sign = (n % 2) ? -1 : 1; + VpSetInf(y, sign); + } + else { + if (sign < 0) sign = (n % 2) ? -1 : 1; + VpSetZero(y,sign); + } + goto Exit; + } + if (VpIsNaN(x)) { + VpSetNaN(y); + goto Exit; + } + if (VpIsInf(x)) { + if (n == 0) { + VpSetOne(y); + goto Exit; + } + if (n > 0) { + VpSetInf(y, (n % 2 == 0 || VpIsPosInf(x)) ? 1 : -1); + goto Exit; + } + VpSetZero(y, (n % 2 == 0 || VpIsPosInf(x)) ? 1 : -1); + goto Exit; + } + + if (x->exponent == 1 && x->Prec == 1 && x->frac[0] == 1) { + /* abs(x) = 1 */ + VpSetOne(y); + if (BIGDECIMAL_POSITIVE_P(x)) goto Exit; + if ((n % 2) == 0) goto Exit; + VpSetSign(y, -1); + goto Exit; + } + + if (n > 0) sign = 1; + else if (n < 0) { + sign = -1; + n = -n; + } + else { + VpSetOne(y); + goto Exit; + } + + /* Allocate working variables */ + /* TODO: reconsider MaxPrec of w1 and w2 */ + w1 = NewZeroNolimit(1, (y->MaxPrec + 2) * BASE_FIG); + w2 = NewZeroNolimit(1, (w1->MaxPrec * 2 + 1) * BASE_FIG); + + /* calculation start */ + + VpAsgn(y, x, 1); + --n; + while (n > 0) { + VpAsgn(w1, x, 1); + s = 1; + while (ss = s, (s += s) <= (size_t)n) { + VpMult(w2, w1, w1); + VpAsgn(w1, w2, 1); + } + n -= (SIGNED_VALUE)ss; + VpMult(w2, y, w1); + VpAsgn(y, w2, 1); + } + if (sign < 0) { + VpDivd(w1, w2, VpConstOne, y); + VpAsgn(y, w1, 1); + } + +Exit: +#ifdef BIGDECIMAL_DEBUG + if (gfDebug) { + VPrint(stdout, "VpPowerByInt y=%\n", y); + VPrint(stdout, "VpPowerByInt x=%\n", x); + printf(" n=%"PRIdVALUE"\n", n); + } +#endif /* BIGDECIMAL_DEBUG */ + rbd_free_struct(w2); + rbd_free_struct(w1); + return 1; +} + +#ifdef BIGDECIMAL_DEBUG +int +VpVarCheck(Real * v) +/* + * Checks the validity of the Real variable v. + * [Input] + * v ... Real *, variable to be checked. + * [Returns] + * 0 ... correct v. + * other ... error + */ +{ + size_t i; + + if (v->MaxPrec == 0) { + printf("ERROR(VpVarCheck): Illegal Max. Precision(=%"PRIuSIZE")\n", + v->MaxPrec); + return 1; + } + if (v->Prec == 0 || v->Prec > v->MaxPrec) { + printf("ERROR(VpVarCheck): Illegal Precision(=%"PRIuSIZE")\n", v->Prec); + printf(" Max. Prec.=%"PRIuSIZE"\n", v->MaxPrec); + return 2; + } + for (i = 0; i < v->Prec; ++i) { + if (v->frac[i] >= BASE) { + printf("ERROR(VpVarCheck): Illegal fraction\n"); + printf(" Frac[%"PRIuSIZE"]=%"PRIuDECDIG"\n", i, v->frac[i]); + printf(" Prec. =%"PRIuSIZE"\n", v->Prec); + printf(" Exp. =%"PRIdVALUE"\n", v->exponent); + printf(" BASE =%"PRIuDECDIG"\n", BASE); + return 3; + } + } + return 0; +} +#endif /* BIGDECIMAL_DEBUG */ diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/bigdecimal.h b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/bigdecimal.h new file mode 100644 index 00000000..54fed811 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/bigdecimal.h @@ -0,0 +1,313 @@ +/* + * + * Ruby BigDecimal(Variable decimal precision) extension library. + * + * Copyright(C) 2002 by Shigeo Kobayashi(shigeo@tinyforest.gr.jp) + * + */ + +#ifndef RUBY_BIG_DECIMAL_H +#define RUBY_BIG_DECIMAL_H 1 + +#define RUBY_NO_OLD_COMPATIBILITY +#include "ruby/ruby.h" +#include "missing.h" + +#ifdef HAVE_FLOAT_H +# include +#endif + +#ifdef HAVE_INT64_T +# define DECDIG uint32_t +# define DECDIG_DBL uint64_t +# define DECDIG_DBL_SIGNED int64_t +# define SIZEOF_DECDIG 4 +# define PRI_DECDIG_PREFIX "" +# ifdef PRI_LL_PREFIX +# define PRI_DECDIG_DBL_PREFIX PRI_LL_PREFIX +# else +# define PRI_DECDIG_DBL_PREFIX "l" +# endif +#else +# define DECDIG uint16_t +# define DECDIG_DBL uint32_t +# define DECDIG_DBL_SIGNED int32_t +# define SIZEOF_DECDIG 2 +# define PRI_DECDIG_PREFIX "h" +# define PRI_DECDIG_DBL_PREFIX "" +#endif + +#define PRIdDECDIG PRI_DECDIG_PREFIX"d" +#define PRIiDECDIG PRI_DECDIG_PREFIX"i" +#define PRIoDECDIG PRI_DECDIG_PREFIX"o" +#define PRIuDECDIG PRI_DECDIG_PREFIX"u" +#define PRIxDECDIG PRI_DECDIG_PREFIX"x" +#define PRIXDECDIG PRI_DECDIG_PREFIX"X" + +#define PRIdDECDIG_DBL PRI_DECDIG_DBL_PREFIX"d" +#define PRIiDECDIG_DBL PRI_DECDIG_DBL_PREFIX"i" +#define PRIoDECDIG_DBL PRI_DECDIG_DBL_PREFIX"o" +#define PRIuDECDIG_DBL PRI_DECDIG_DBL_PREFIX"u" +#define PRIxDECDIG_DBL PRI_DECDIG_DBL_PREFIX"x" +#define PRIXDECDIG_DBL PRI_DECDIG_DBL_PREFIX"X" + +#if SIZEOF_DECDIG == 4 +# define BIGDECIMAL_BASE ((DECDIG)1000000000U) +# define BIGDECIMAL_COMPONENT_FIGURES 9 +/* + * The number of components required for a 64-bit integer. + * + * INT64_MAX: 9_223372036_854775807 + * UINT64_MAX: 18_446744073_709551615 + */ +# define BIGDECIMAL_INT64_MAX_LENGTH 3 + +#elif SIZEOF_DECDIG == 2 +# define BIGDECIMAL_BASE ((DECDIG)10000U) +# define BIGDECIMAL_COMPONENT_FIGURES 4 +/* + * The number of components required for a 64-bit integer. + * + * INT64_MAX: 922_3372_0368_5477_5807 + * UINT64_MAX: 1844_6744_0737_0955_1615 + */ +# define BIGDECIMAL_INT64_MAX_LENGTH 5 + +#else +# error Unknown size of DECDIG +#endif + +#define BIGDECIMAL_DOUBLE_FIGURES (1+DBL_DIG) + +#if defined(__cplusplus) +extern "C" { +#if 0 +} /* satisfy cc-mode */ +#endif +#endif + +extern VALUE rb_cBigDecimal; + +/* + * NaN & Infinity + */ +#define SZ_NaN "NaN" +#define SZ_INF "Infinity" +#define SZ_PINF "+Infinity" +#define SZ_NINF "-Infinity" + +/* + * #define VP_EXPORT other than static to let VP_ routines + * be called from outside of this module. + */ +#define VP_EXPORT static + +/* Exception mode */ +#define VP_EXCEPTION_ALL ((unsigned short)0x00FF) +#define VP_EXCEPTION_INFINITY ((unsigned short)0x0001) +#define VP_EXCEPTION_NaN ((unsigned short)0x0002) +#define VP_EXCEPTION_UNDERFLOW ((unsigned short)0x0004) +#define VP_EXCEPTION_OVERFLOW ((unsigned short)0x0001) /* 0x0008) */ +#define VP_EXCEPTION_ZERODIVIDE ((unsigned short)0x0010) + +/* Following 2 exceptions can't controlled by user */ +#define VP_EXCEPTION_OP ((unsigned short)0x0020) + +#define BIGDECIMAL_EXCEPTION_MODE_DEFAULT 0U + +/* This is used in BigDecimal#mode */ +#define VP_ROUND_MODE ((unsigned short)0x0100) + +/* Rounding mode */ +#define VP_ROUND_UP RBD_ROUND_UP +#define VP_ROUND_DOWN RBD_ROUND_DOWN +#define VP_ROUND_HALF_UP RBD_ROUND_HALF_UP +#define VP_ROUND_HALF_DOWN RBD_ROUND_HALF_DOWN +#define VP_ROUND_CEIL RBD_ROUND_CEIL +#define VP_ROUND_FLOOR RBD_ROUND_FLOOR +#define VP_ROUND_HALF_EVEN RBD_ROUND_HALF_EVEN + +enum rbd_rounding_mode { + RBD_ROUND_UP = 1, + RBD_ROUND_DOWN = 2, + RBD_ROUND_HALF_UP = 3, + RBD_ROUND_HALF_DOWN = 4, + RBD_ROUND_CEIL = 5, + RBD_ROUND_FLOOR = 6, + RBD_ROUND_HALF_EVEN = 7, + + RBD_ROUND_DEFAULT = RBD_ROUND_HALF_UP, + RBD_ROUND_TRUNCATE = RBD_ROUND_DOWN, + RBD_ROUND_BANKER = RBD_ROUND_HALF_EVEN, + RBD_ROUND_CEILING = RBD_ROUND_CEIL +}; + +#define BIGDECIMAL_ROUNDING_MODE_DEFAULT VP_ROUND_HALF_UP + +/* Sign flag */ +#define VP_SIGN_NaN 0 /* NaN */ +#define VP_SIGN_POSITIVE_ZERO 1 /* Positive zero */ +#define VP_SIGN_NEGATIVE_ZERO -1 /* Negative zero */ +#define VP_SIGN_POSITIVE_FINITE 2 /* Positive finite number */ +#define VP_SIGN_NEGATIVE_FINITE -2 /* Negative finite number */ +#define VP_SIGN_POSITIVE_INFINITE 3 /* Positive infinite number */ +#define VP_SIGN_NEGATIVE_INFINITE -3 /* Negative infinite number */ + +/* The size of fraction part array */ +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +#define FLEXIBLE_ARRAY_SIZE /* */ +#elif defined(__GNUC__) && !defined(__STRICT_ANSI__) +#define FLEXIBLE_ARRAY_SIZE 0 +#else +#define FLEXIBLE_ARRAY_SIZE 1 +#endif + +/* + * VP representation + * r = 0.xxxxxxxxx *BASE**exponent + */ +typedef struct { + VALUE obj; /* Back pointer(VALUE) for Ruby object. */ + size_t MaxPrec; /* Maximum precision size */ + /* This is the actual size of frac[] */ + /*(frac[0] to frac[MaxPrec] are available). */ + size_t Prec; /* Current precision size. */ + /* This indicates how much the */ + /* array frac[] is actually used. */ + SIGNED_VALUE exponent; /* Exponent part. */ + short sign; /* Attributes of the value. */ + /* + * ==0 : NaN + * 1 : Positive zero + * -1 : Negative zero + * 2 : Positive number + * -2 : Negative number + * 3 : Positive infinite number + * -3 : Negative infinite number + */ + short flag; /* Not used in vp_routines,space for user. */ + DECDIG frac[FLEXIBLE_ARRAY_SIZE]; /* Array of fraction part. */ +} Real; + +/* + * ------------------ + * EXPORTables. + * ------------------ + */ + +VP_EXPORT Real *VpNewRbClass(size_t mx, char const *str, VALUE klass, bool strict_p, bool raise_exception); + +VP_EXPORT Real *VpCreateRbObject(size_t mx, const char *str, bool raise_exception); + +#define VpBaseFig() BIGDECIMAL_COMPONENT_FIGURES +#define VpDblFig() BIGDECIMAL_DOUBLE_FIGURES +#define VpBaseVal() BIGDECIMAL_BASE + +/* Zero,Inf,NaN (isinf(),isnan() used to check) */ +VP_EXPORT double VpGetDoubleNaN(void); +VP_EXPORT double VpGetDoublePosInf(void); +VP_EXPORT double VpGetDoubleNegInf(void); +VP_EXPORT double VpGetDoubleNegZero(void); + +/* These 2 functions added at v1.1.7 */ +VP_EXPORT size_t VpGetPrecLimit(void); +VP_EXPORT size_t VpSetPrecLimit(size_t n); + +/* Round mode */ +VP_EXPORT int VpIsRoundMode(unsigned short n); +VP_EXPORT unsigned short VpGetRoundMode(void); +VP_EXPORT unsigned short VpSetRoundMode(unsigned short n); + +VP_EXPORT int VpException(unsigned short f,const char *str,int always); +#if 0 /* unused */ +VP_EXPORT int VpIsNegDoubleZero(double v); +#endif +VP_EXPORT size_t VpNumOfChars(Real *vp,const char *pszFmt); +VP_EXPORT size_t VpInit(DECDIG BaseVal); +VP_EXPORT Real *VpAlloc(size_t mx, const char *szVal, int strict_p, int exc); +VP_EXPORT size_t VpAsgn(Real *c, Real *a, int isw); +VP_EXPORT size_t VpAddSub(Real *c,Real *a,Real *b,int operation); +VP_EXPORT size_t VpMult(Real *c,Real *a,Real *b); +VP_EXPORT size_t VpDivd(Real *c,Real *r,Real *a,Real *b); +VP_EXPORT int VpComp(Real *a,Real *b); +VP_EXPORT ssize_t VpExponent10(Real *a); +VP_EXPORT void VpSzMantissa(Real *a, char *buf, size_t bufsize); +VP_EXPORT int VpToSpecialString(Real *a, char *buf, size_t bufsize, int fPlus); +VP_EXPORT void VpToString(Real *a, char *buf, size_t bufsize, size_t fFmt, int fPlus); +VP_EXPORT void VpToFString(Real *a, char *buf, size_t bufsize, size_t fFmt, int fPlus); +VP_EXPORT int VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, const char *exp_chr, size_t ne); +VP_EXPORT int VpVtoD(double *d, SIGNED_VALUE *e, Real *m); +VP_EXPORT void VpDtoV(Real *m,double d); +#if 0 /* unused */ +VP_EXPORT void VpItoV(Real *m,S_INT ival); +#endif +VP_EXPORT int VpSqrt(Real *y,Real *x); +VP_EXPORT int VpActiveRound(Real *y, Real *x, unsigned short f, ssize_t il); +VP_EXPORT int VpMidRound(Real *y, unsigned short f, ssize_t nf); +VP_EXPORT int VpLeftRound(Real *y, unsigned short f, ssize_t nf); +VP_EXPORT void VpFrac(Real *y, Real *x); +VP_EXPORT int VpPowerByInt(Real *y, Real *x, SIGNED_VALUE n); +#define VpPower VpPowerByInt + +/* VP constants */ +VP_EXPORT Real *VpOne(void); + +/* + * ------------------ + * MACRO definitions. + * ------------------ + */ +#define Abs(a) (((a)>= 0)?(a):(-(a))) +#define Max(a, b) (((a)>(b))?(a):(b)) +#define Min(a, b) (((a)>(b))?(b):(a)) + +#define VpMaxPrec(a) ((a)->MaxPrec) +#define VpPrec(a) ((a)->Prec) +#define VpGetFlag(a) ((a)->flag) + +/* Sign */ + +/* VpGetSign(a) returns 1,-1 if a>0,a<0 respectively */ +#define VpGetSign(a) (((a)->sign>0)?1:(-1)) +/* Change sign of a to a>0,a<0 if s = 1,-1 respectively */ +#define VpChangeSign(a,s) {if((s)>0) (a)->sign=(short)Abs((ssize_t)(a)->sign);else (a)->sign=-(short)Abs((ssize_t)(a)->sign);} +/* Sets sign of a to a>0,a<0 if s = 1,-1 respectively */ +#define VpSetSign(a,s) {if((s)>0) (a)->sign=(short)VP_SIGN_POSITIVE_FINITE;else (a)->sign=(short)VP_SIGN_NEGATIVE_FINITE;} + +/* 1 */ +#define VpSetOne(a) {(a)->Prec=(a)->exponent=(a)->frac[0]=1;(a)->sign=VP_SIGN_POSITIVE_FINITE;} + +/* ZEROs */ +#define VpIsPosZero(a) ((a)->sign==VP_SIGN_POSITIVE_ZERO) +#define VpIsNegZero(a) ((a)->sign==VP_SIGN_NEGATIVE_ZERO) +#define VpIsZero(a) (VpIsPosZero(a) || VpIsNegZero(a)) +#define VpSetPosZero(a) ((a)->frac[0]=0,(a)->Prec=1,(a)->sign=VP_SIGN_POSITIVE_ZERO) +#define VpSetNegZero(a) ((a)->frac[0]=0,(a)->Prec=1,(a)->sign=VP_SIGN_NEGATIVE_ZERO) +#define VpSetZero(a,s) (void)(((s)>0)?VpSetPosZero(a):VpSetNegZero(a)) + +/* NaN */ +#define VpIsNaN(a) ((a)->sign==VP_SIGN_NaN) +#define VpSetNaN(a) ((a)->frac[0]=0,(a)->Prec=1,(a)->sign=VP_SIGN_NaN) + +/* Infinity */ +#define VpIsPosInf(a) ((a)->sign==VP_SIGN_POSITIVE_INFINITE) +#define VpIsNegInf(a) ((a)->sign==VP_SIGN_NEGATIVE_INFINITE) +#define VpIsInf(a) (VpIsPosInf(a) || VpIsNegInf(a)) +#define VpIsDef(a) ( !(VpIsNaN(a)||VpIsInf(a)) ) +#define VpSetPosInf(a) ((a)->frac[0]=0,(a)->Prec=1,(a)->sign=VP_SIGN_POSITIVE_INFINITE) +#define VpSetNegInf(a) ((a)->frac[0]=0,(a)->Prec=1,(a)->sign=VP_SIGN_NEGATIVE_INFINITE) +#define VpSetInf(a,s) (void)(((s)>0)?VpSetPosInf(a):VpSetNegInf(a)) +#define VpHasVal(a) (a->frac[0]) +#define VpIsOne(a) ((a->Prec==1)&&(a->frac[0]==1)&&(a->exponent==1)) +#define VpExponent(a) (a->exponent) +#ifdef BIGDECIMAL_DEBUG +int VpVarCheck(Real * v); +#endif /* BIGDECIMAL_DEBUG */ + +#if defined(__cplusplus) +#if 0 +{ /* satisfy cc-mode */ +#endif +} /* extern "C" { */ +#endif +#endif /* RUBY_BIG_DECIMAL_H */ diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/bits.h b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/bits.h new file mode 100644 index 00000000..6e1e4776 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/bits.h @@ -0,0 +1,141 @@ +#ifndef BIGDECIMAL_BITS_H +#define BIGDECIMAL_BITS_H + +#include "feature.h" +#include "static_assert.h" + +#if defined(__x86_64__) && defined(HAVE_X86INTRIN_H) +# include /* for _lzcnt_u64, etc. */ +#elif defined(_MSC_VER) && defined(HAVE_INTRIN_H) +# include /* for the following intrinsics */ +#endif + +#if defined(_MSC_VER) && defined(__AVX2__) +# pragma intrinsic(__lzcnt) +# pragma intrinsic(__lzcnt64) +#endif + +#define numberof(array) ((int)(sizeof(array) / sizeof((array)[0]))) +#define roomof(x, y) (((x) + (y) - 1) / (y)) +#define type_roomof(x, y) roomof(sizeof(x), sizeof(y)) + +#define MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, min, max) ( \ + (a) == 0 ? 0 : \ + (a) == -1 ? (b) < -(max) : \ + (a) > 0 ? \ + ((b) > 0 ? (max) / (a) < (b) : (min) / (a) > (b)) : \ + ((b) > 0 ? (min) / (a) < (b) : (max) / (a) > (b))) + +#ifdef HAVE_UINT128_T +# define bit_length(x) \ + (unsigned int) \ + (sizeof(x) <= sizeof(int32_t) ? 32 - nlz_int32((uint32_t)(x)) : \ + sizeof(x) <= sizeof(int64_t) ? 64 - nlz_int64((uint64_t)(x)) : \ + 128 - nlz_int128((uint128_t)(x))) +#else +# define bit_length(x) \ + (unsigned int) \ + (sizeof(x) <= sizeof(int32_t) ? 32 - nlz_int32((uint32_t)(x)) : \ + 64 - nlz_int64((uint64_t)(x))) +#endif + +static inline unsigned nlz_int32(uint32_t x); +static inline unsigned nlz_int64(uint64_t x); +#ifdef HAVE_UINT128_T +static inline unsigned nlz_int128(uint128_t x); +#endif + +static inline unsigned int +nlz_int32(uint32_t x) +{ +#if defined(_MSC_VER) && defined(__AVX2__) && defined(HAVE___LZCNT) + /* Note: It seems there is no such thing like __LZCNT__ predefined in MSVC. + * AMD CPUs have had this instruction for decades (since K10) but for + * Intel, Haswell is the oldest one. We need to use __AVX2__ for maximum + * safety. */ + return (unsigned int)__lzcnt(x); + +#elif defined(__x86_64__) && defined(__LZCNT__) && defined(HAVE__LZCNT_U32) + return (unsigned int)_lzcnt_u32(x); + +#elif defined(_MSC_VER) && defined(HAVE__BITSCANREVERSE) + unsigned long r; + return _BitScanReverse(&r, x) ? (31 - (int)r) : 32; + +#elif __has_builtin(__builtin_clz) + STATIC_ASSERT(sizeof_int, sizeof(int) * CHAR_BIT == 32); + return x ? (unsigned int)__builtin_clz(x) : 32; + +#else + uint32_t y; + unsigned n = 32; + y = x >> 16; if (y) {n -= 16; x = y;} + y = x >> 8; if (y) {n -= 8; x = y;} + y = x >> 4; if (y) {n -= 4; x = y;} + y = x >> 2; if (y) {n -= 2; x = y;} + y = x >> 1; if (y) {return n - 2;} + return (unsigned int)(n - x); +#endif +} + +static inline unsigned int +nlz_int64(uint64_t x) +{ +#if defined(_MSC_VER) && defined(__AVX2__) && defined(HAVE___LZCNT64) + return (unsigned int)__lzcnt64(x); + +#elif defined(__x86_64__) && defined(__LZCNT__) && defined(HAVE__LZCNT_U64) + return (unsigned int)_lzcnt_u64(x); + +#elif defined(_WIN64) && defined(_MSC_VER) && defined(HAVE__BITSCANREVERSE64) + unsigned long r; + return _BitScanReverse64(&r, x) ? (63u - (unsigned int)r) : 64; + +#elif __has_builtin(__builtin_clzl) && __has_builtin(__builtin_clzll) && !(defined(__sun) && defined(__sparc)) + if (x == 0) { + return 64; + } + else if (sizeof(long) * CHAR_BIT == 64) { + return (unsigned int)__builtin_clzl((unsigned long)x); + } + else if (sizeof(long long) * CHAR_BIT == 64) { + return (unsigned int)__builtin_clzll((unsigned long long)x); + } + else { + /* :FIXME: Is there a way to make this branch a compile-time error? */ + __builtin_unreachable(); + } + +#else + uint64_t y; + unsigned int n = 64; + y = x >> 32; if (y) {n -= 32; x = y;} + y = x >> 16; if (y) {n -= 16; x = y;} + y = x >> 8; if (y) {n -= 8; x = y;} + y = x >> 4; if (y) {n -= 4; x = y;} + y = x >> 2; if (y) {n -= 2; x = y;} + y = x >> 1; if (y) {return n - 2;} + return (unsigned int)(n - x); + +#endif +} + +#ifdef HAVE_UINT128_T +static inline unsigned int +nlz_int128(uint128_t x) +{ + uint64_t y = (uint64_t)(x >> 64); + + if (x == 0) { + return 128; + } + else if (y == 0) { + return (unsigned int)nlz_int64(x) + 64; + } + else { + return (unsigned int)nlz_int64(y); + } +} +#endif + +#endif /* BIGDECIMAL_BITS_H */ diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/extconf.rb b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/extconf.rb new file mode 100644 index 00000000..cf4290f5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/extconf.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: false +require 'mkmf' + +def have_builtin_func(name, check_expr, opt = "", &b) + checking_for checking_message(name.funcall_style, nil, opt) do + if try_compile(< +#endif + +#ifdef RBIMPL_HAS_BUILTIN +# define BIGDECIMAL_HAS_BUILTIN(...) RBIMPL_HAS_BUILTIN(__VA_ARGS__) + +#else +# /* The following section is copied from CRuby's builtin.h */ +# +# ifdef __has_builtin +# if defined(__INTEL_COMPILER) +# /* :TODO: Intel C Compiler has __has_builtin (since 19.1 maybe?), and is +# * reportedly broken. We have to skip them. However the situation can +# * change. They might improve someday. We need to revisit here later. */ +# elif defined(__GNUC__) && ! __has_builtin(__builtin_alloca) +# /* FreeBSD's defines its own *broken* version of +# * __has_builtin. Cygwin copied that content to be a victim of the +# * broken-ness. We don't take them into account. */ +# else +# define HAVE___HAS_BUILTIN 1 +# endif +# endif +# +# if defined(HAVE___HAS_BUILTIN) +# define BIGDECIMAL_HAS_BUILTIN(_) __has_builtin(_) +# +# elif defined(__GNUC__) +# define BIGDECIMAL_HAS_BUILTIN(_) BIGDECIMAL_HAS_BUILTIN_ ## _ +# if defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 6)) +# define BIGDECIMAL_HAS_BUILTIN___builtin_clz 1 +# define BIGDECIMAL_HAS_BUILTIN___builtin_clzl 1 +# else +# define BIGDECIMAL_HAS_BUILTIN___builtin_clz 0 +# define BIGDECIMAL_HAS_BUILTIN___builtin_clzl 0 +# endif +# elif defined(_MSC_VER) +# define BIGDECIMAL_HAS_BUILTIN(_) 0 +# +# else +# define BIGDECIMAL_HAS_BUILTIN(_) BIGDECIMAL_HAS_BUILTIN_ ## _ +# define BIGDECIMAL_HAS_BUILTIN___builtin_clz HAVE_BUILTIN___BUILTIN_CLZ +# define BIGDECIMAL_HAS_BUILTIN___builtin_clzl HAVE_BUILTIN___BUILTIN_CLZL +# endif +#endif /* RBIMPL_HAS_BUILTIN */ + +#ifndef __has_builtin +# define __has_builtin(...) BIGDECIMAL_HAS_BUILTIN(__VA_ARGS__) +#endif + +#endif /* BIGDECIMAL_HAS_FEATURE_H */ diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/missing.c b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/missing.c new file mode 100644 index 00000000..1454c281 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/missing.c @@ -0,0 +1,28 @@ +#include + +#ifdef HAVE_RUBY_ATOMIC_H +# include +#endif + +#ifdef RUBY_ATOMIC_PTR_CAS +# define ATOMIC_PTR_CAS(var, old, new) RUBY_ATOMIC_PTR_CAS(var, old, new) +#endif + +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +/* GCC warns about unknown sanitizer, which is annoying. */ +# undef NO_SANITIZE +# define NO_SANITIZE(x, y) \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wattributes\"") \ + __attribute__((__no_sanitize__(x))) y; \ + _Pragma("GCC diagnostic pop") \ + y +#endif + +#undef strtod +#define strtod BigDecimal_strtod +#undef dtoa +#define dtoa BigDecimal_dtoa +#undef hdtoa +#define hdtoa BigDecimal_hdtoa +#include "missing/dtoa.c" diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/missing.h b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/missing.h new file mode 100644 index 00000000..325554b5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/missing.h @@ -0,0 +1,196 @@ +#ifndef MISSING_H +#define MISSING_H 1 + +#if defined(__cplusplus) +extern "C" { +#if 0 +} /* satisfy cc-mode */ +#endif +#endif + +#ifdef HAVE_STDLIB_H +# include +#endif + +#ifdef HAVE_MATH_H +# include +#endif + +#ifndef RB_UNUSED_VAR +# if defined(_MSC_VER) && _MSC_VER >= 1911 +# define RB_UNUSED_VAR(x) x [[maybe_unused]] + +# elif defined(__has_cpp_attribute) && __has_cpp_attribute(maybe_unused) +# define RB_UNUSED_VAR(x) x [[maybe_unused]] + +# elif defined(__has_c_attribute) && __has_c_attribute(maybe_unused) +# define RB_UNUSED_VAR(x) x [[maybe_unused]] + +# elif defined(__GNUC__) +# define RB_UNUSED_VAR(x) x __attribute__ ((unused)) + +# else +# define RB_UNUSED_VAR(x) x +# endif +#endif /* RB_UNUSED_VAR */ + +#if defined(_MSC_VER) && _MSC_VER >= 1310 +# define HAVE___ASSUME 1 + +#elif defined(__INTEL_COMPILER) && __INTEL_COMPILER >= 1300 +# define HAVE___ASSUME 1 +#endif + +#ifndef UNREACHABLE +# if __has_builtin(__builtin_unreachable) +# define UNREACHABLE __builtin_unreachable() + +# elif defined(HAVE___ASSUME) +# define UNREACHABLE __assume(0) + +# else +# define UNREACHABLE /* unreachable */ +# endif +#endif /* UNREACHABLE */ + +/* bool */ + +#if defined(__bool_true_false_are_defined) +# /* Take that. */ + +#elif defined(HAVE_STDBOOL_H) +# include + +#else +typedef unsigned char _Bool; +# define bool _Bool +# define true ((_Bool)+1) +# define false ((_Bool)-1) +# define __bool_true_false_are_defined +#endif + +/* abs */ + +#ifndef HAVE_LABS +static inline long +labs(long const x) +{ + if (x < 0) return -x; + return x; +} +#endif + +#ifndef HAVE_LLABS +static inline LONG_LONG +llabs(LONG_LONG const x) +{ + if (x < 0) return -x; + return x; +} +#endif + +#ifdef vabs +# undef vabs +#endif +#if SIZEOF_VALUE <= SIZEOF_INT +# define vabs abs +#elif SIZEOF_VALUE <= SIZEOF_LONG +# define vabs labs +#elif SIZEOF_VALUE <= SIZEOF_LONG_LONG +# define vabs llabs +#endif + +/* finite */ + +#ifndef HAVE_FINITE +static int +finite(double) +{ + return !isnan(n) && !isinf(n); +} +#endif + +#ifndef isfinite +# ifndef HAVE_ISFINITE +# define HAVE_ISFINITE 1 +# define isfinite(x) finite(x) +# endif +#endif + +/* dtoa */ +char *BigDecimal_dtoa(double d_, int mode, int ndigits, int *decpt, int *sign, char **rve); + +/* rational */ + +#ifndef HAVE_RB_RATIONAL_NUM +static inline VALUE +rb_rational_num(VALUE rat) +{ +#ifdef RRATIONAL + return RRATIONAL(rat)->num; +#else + return rb_funcall(rat, rb_intern("numerator"), 0); +#endif +} +#endif + +#ifndef HAVE_RB_RATIONAL_DEN +static inline VALUE +rb_rational_den(VALUE rat) +{ +#ifdef RRATIONAL + return RRATIONAL(rat)->den; +#else + return rb_funcall(rat, rb_intern("denominator"), 0); +#endif +} +#endif + +/* complex */ + +#ifndef HAVE_RB_COMPLEX_REAL +static inline VALUE +rb_complex_real(VALUE cmp) +{ +#ifdef RCOMPLEX + return RCOMPLEX(cmp)->real; +#else + return rb_funcall(cmp, rb_intern("real"), 0); +#endif +} +#endif + +#ifndef HAVE_RB_COMPLEX_IMAG +static inline VALUE +rb_complex_imag(VALUE cmp) +{ +# ifdef RCOMPLEX + return RCOMPLEX(cmp)->imag; +# else + return rb_funcall(cmp, rb_intern("imag"), 0); +# endif +} +#endif + +/* st */ + +#ifndef ST2FIX +# undef RB_ST2FIX +# define RB_ST2FIX(h) LONG2FIX((long)(h)) +# define ST2FIX(h) RB_ST2FIX(h) +#endif + +/* warning */ + +#if !defined(HAVE_RB_CATEGORY_WARN) || !defined(HAVE_CONST_RB_WARN_CATEGORY_DEPRECATED) +# define rb_category_warn(category, ...) rb_warn(__VA_ARGS__) +#endif + +#if defined(__cplusplus) +#if 0 +{ /* satisfy cc-mode */ +#endif +} /* extern "C" { */ +#endif + +#endif /* MISSING_H */ diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/missing/dtoa.c b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/missing/dtoa.c new file mode 100644 index 00000000..41b0a221 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/missing/dtoa.c @@ -0,0 +1,3462 @@ +/**************************************************************** + * + * The author of this software is David M. Gay. + * + * Copyright (c) 1991, 2000, 2001 by Lucent Technologies. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose without fee is hereby granted, provided that this entire notice + * is included in all copies of any software which is or includes a copy + * or modification of this software and in all copies of the supporting + * documentation for such software. + * + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + * + ***************************************************************/ + +/* Please send bug reports to David M. Gay (dmg at acm dot org, + * with " at " changed at "@" and " dot " changed to "."). */ + +/* On a machine with IEEE extended-precision registers, it is + * necessary to specify double-precision (53-bit) rounding precision + * before invoking strtod or dtoa. If the machine uses (the equivalent + * of) Intel 80x87 arithmetic, the call + * _control87(PC_53, MCW_PC); + * does this with many compilers. Whether this or another call is + * appropriate depends on the compiler; for this to work, it may be + * necessary to #include "float.h" or another system-dependent header + * file. + */ + +/* strtod for IEEE-, VAX-, and IBM-arithmetic machines. + * + * This strtod returns a nearest machine number to the input decimal + * string (or sets errno to ERANGE). With IEEE arithmetic, ties are + * broken by the IEEE round-even rule. Otherwise ties are broken by + * biased rounding (add half and chop). + * + * Inspired loosely by William D. Clinger's paper "How to Read Floating + * Point Numbers Accurately" [Proc. ACM SIGPLAN '90, pp. 92-101]. + * + * Modifications: + * + * 1. We only require IEEE, IBM, or VAX double-precision + * arithmetic (not IEEE double-extended). + * 2. We get by with floating-point arithmetic in a case that + * Clinger missed -- when we're computing d * 10^n + * for a small integer d and the integer n is not too + * much larger than 22 (the maximum integer k for which + * we can represent 10^k exactly), we may be able to + * compute (d*10^k) * 10^(e-k) with just one roundoff. + * 3. Rather than a bit-at-a-time adjustment of the binary + * result in the hard case, we use floating-point + * arithmetic to determine the adjustment to within + * one bit; only in really hard cases do we need to + * compute a second residual. + * 4. Because of 3., we don't need a large table of powers of 10 + * for ten-to-e (just some small tables, e.g. of 10^k + * for 0 <= k <= 22). + */ + +/* + * #define IEEE_LITTLE_ENDIAN for IEEE-arithmetic machines where the least + * significant byte has the lowest address. + * #define IEEE_BIG_ENDIAN for IEEE-arithmetic machines where the most + * significant byte has the lowest address. + * #define Long int on machines with 32-bit ints and 64-bit longs. + * #define IBM for IBM mainframe-style floating-point arithmetic. + * #define VAX for VAX-style floating-point arithmetic (D_floating). + * #define No_leftright to omit left-right logic in fast floating-point + * computation of dtoa. + * #define Honor_FLT_ROUNDS if FLT_ROUNDS can assume the values 2 or 3 + * and strtod and dtoa should round accordingly. + * #define Check_FLT_ROUNDS if FLT_ROUNDS can assume the values 2 or 3 + * and Honor_FLT_ROUNDS is not #defined. + * #define RND_PRODQUOT to use rnd_prod and rnd_quot (assembly routines + * that use extended-precision instructions to compute rounded + * products and quotients) with IBM. + * #define ROUND_BIASED for IEEE-format with biased rounding. + * #define Inaccurate_Divide for IEEE-format with correctly rounded + * products but inaccurate quotients, e.g., for Intel i860. + * #define NO_LONG_LONG on machines that do not have a "long long" + * integer type (of >= 64 bits). On such machines, you can + * #define Just_16 to store 16 bits per 32-bit Long when doing + * high-precision integer arithmetic. Whether this speeds things + * up or slows things down depends on the machine and the number + * being converted. If long long is available and the name is + * something other than "long long", #define Llong to be the name, + * and if "unsigned Llong" does not work as an unsigned version of + * Llong, #define #ULLong to be the corresponding unsigned type. + * #define KR_headers for old-style C function headers. + * #define Bad_float_h if your system lacks a float.h or if it does not + * define some or all of DBL_DIG, DBL_MAX_10_EXP, DBL_MAX_EXP, + * FLT_RADIX, FLT_ROUNDS, and DBL_MAX. + * #define MALLOC your_malloc, where your_malloc(n) acts like malloc(n) + * if memory is available and otherwise does something you deem + * appropriate. If MALLOC is undefined, malloc will be invoked + * directly -- and assumed always to succeed. + * #define Omit_Private_Memory to omit logic (added Jan. 1998) for making + * memory allocations from a private pool of memory when possible. + * When used, the private pool is PRIVATE_MEM bytes long: 2304 bytes, + * unless #defined to be a different length. This default length + * suffices to get rid of MALLOC calls except for unusual cases, + * such as decimal-to-binary conversion of a very long string of + * digits. The longest string dtoa can return is about 751 bytes + * long. For conversions by strtod of strings of 800 digits and + * all dtoa conversions in single-threaded executions with 8-byte + * pointers, PRIVATE_MEM >= 7400 appears to suffice; with 4-byte + * pointers, PRIVATE_MEM >= 7112 appears adequate. + * #define INFNAN_CHECK on IEEE systems to cause strtod to check for + * Infinity and NaN (case insensitively). On some systems (e.g., + * some HP systems), it may be necessary to #define NAN_WORD0 + * appropriately -- to the most significant word of a quiet NaN. + * (On HP Series 700/800 machines, -DNAN_WORD0=0x7ff40000 works.) + * When INFNAN_CHECK is #defined and No_Hex_NaN is not #defined, + * strtod also accepts (case insensitively) strings of the form + * NaN(x), where x is a string of hexadecimal digits and spaces; + * if there is only one string of hexadecimal digits, it is taken + * for the 52 fraction bits of the resulting NaN; if there are two + * or more strings of hex digits, the first is for the high 20 bits, + * the second and subsequent for the low 32 bits, with intervening + * white space ignored; but if this results in none of the 52 + * fraction bits being on (an IEEE Infinity symbol), then NAN_WORD0 + * and NAN_WORD1 are used instead. + * #define MULTIPLE_THREADS if the system offers preemptively scheduled + * multiple threads. In this case, you must provide (or suitably + * #define) two locks, acquired by ACQUIRE_DTOA_LOCK(n) and freed + * by FREE_DTOA_LOCK(n) for n = 0 or 1. (The second lock, accessed + * in pow5mult, ensures lazy evaluation of only one copy of high + * powers of 5; omitting this lock would introduce a small + * probability of wasting memory, but would otherwise be harmless.) + * You must also invoke freedtoa(s) to free the value s returned by + * dtoa. You may do so whether or not MULTIPLE_THREADS is #defined. + * #define NO_IEEE_Scale to disable new (Feb. 1997) logic in strtod that + * avoids underflows on inputs whose result does not underflow. + * If you #define NO_IEEE_Scale on a machine that uses IEEE-format + * floating-point numbers and flushes underflows to zero rather + * than implementing gradual underflow, then you must also #define + * Sudden_Underflow. + * #define YES_ALIAS to permit aliasing certain double values with + * arrays of ULongs. This leads to slightly better code with + * some compilers and was always used prior to 19990916, but it + * is not strictly legal and can cause trouble with aggressively + * optimizing compilers (e.g., gcc 2.95.1 under -O2). + * #define USE_LOCALE to use the current locale's decimal_point value. + * #define SET_INEXACT if IEEE arithmetic is being used and extra + * computation should be done to set the inexact flag when the + * result is inexact and avoid setting inexact when the result + * is exact. In this case, dtoa.c must be compiled in + * an environment, perhaps provided by #include "dtoa.c" in a + * suitable wrapper, that defines two functions, + * int get_inexact(void); + * void clear_inexact(void); + * such that get_inexact() returns a nonzero value if the + * inexact bit is already set, and clear_inexact() sets the + * inexact bit to 0. When SET_INEXACT is #defined, strtod + * also does extra computations to set the underflow and overflow + * flags when appropriate (i.e., when the result is tiny and + * inexact or when it is a numeric value rounded to +-infinity). + * #define NO_ERRNO if strtod should not assign errno = ERANGE when + * the result overflows to +-Infinity or underflows to 0. + */ + +#ifdef WORDS_BIGENDIAN +#define IEEE_BIG_ENDIAN +#else +#define IEEE_LITTLE_ENDIAN +#endif + +#ifdef __vax__ +#define VAX +#undef IEEE_BIG_ENDIAN +#undef IEEE_LITTLE_ENDIAN +#endif + +#if defined(__arm__) && !defined(__VFP_FP__) +#define IEEE_BIG_ENDIAN +#undef IEEE_LITTLE_ENDIAN +#endif + +#undef Long +#undef ULong + +#include + +#if (INT_MAX >> 30) && !(INT_MAX >> 31) +#define Long int +#define ULong unsigned int +#elif (LONG_MAX >> 30) && !(LONG_MAX >> 31) +#define Long long int +#define ULong unsigned long int +#else +#error No 32bit integer +#endif + +#if HAVE_LONG_LONG +#define Llong LONG_LONG +#else +#define NO_LONG_LONG +#endif + +#ifdef DEBUG +#include +#define Bug(x) {fprintf(stderr, "%s\n", (x)); exit(EXIT_FAILURE);} +#endif + +#ifndef ISDIGIT +#include +#define ISDIGIT(c) isdigit(c) +#endif +#include +#include +#include + +#ifdef USE_LOCALE +#include +#endif + +#ifdef MALLOC +extern void *MALLOC(size_t); +#else +#define MALLOC xmalloc +#endif +#ifdef FREE +extern void FREE(void*); +#else +#define FREE xfree +#endif +#ifndef NO_SANITIZE +#define NO_SANITIZE(x, y) y +#endif + +#ifndef Omit_Private_Memory +#ifndef PRIVATE_MEM +#define PRIVATE_MEM 2304 +#endif +#define PRIVATE_mem ((PRIVATE_MEM+sizeof(double)-1)/sizeof(double)) +static double private_mem[PRIVATE_mem], *pmem_next = private_mem; +#endif + +#undef IEEE_Arith +#undef Avoid_Underflow +#ifdef IEEE_BIG_ENDIAN +#define IEEE_Arith +#endif +#ifdef IEEE_LITTLE_ENDIAN +#define IEEE_Arith +#endif + +#ifdef Bad_float_h + +#ifdef IEEE_Arith +#define DBL_DIG 15 +#define DBL_MAX_10_EXP 308 +#define DBL_MAX_EXP 1024 +#define FLT_RADIX 2 +#endif /*IEEE_Arith*/ + +#ifdef IBM +#define DBL_DIG 16 +#define DBL_MAX_10_EXP 75 +#define DBL_MAX_EXP 63 +#define FLT_RADIX 16 +#define DBL_MAX 7.2370055773322621e+75 +#endif + +#ifdef VAX +#define DBL_DIG 16 +#define DBL_MAX_10_EXP 38 +#define DBL_MAX_EXP 127 +#define FLT_RADIX 2 +#define DBL_MAX 1.7014118346046923e+38 +#endif + +#ifndef LONG_MAX +#define LONG_MAX 2147483647 +#endif + +#else /* ifndef Bad_float_h */ +#include +#endif /* Bad_float_h */ + +#include + +#ifdef __cplusplus +extern "C" { +#if 0 +} /* satisfy cc-mode */ +#endif +#endif + +#ifndef hexdigit +static const char hexdigit[] = "0123456789abcdef0123456789ABCDEF"; +#endif + +#if defined(IEEE_LITTLE_ENDIAN) + defined(IEEE_BIG_ENDIAN) + defined(VAX) + defined(IBM) != 1 +Exactly one of IEEE_LITTLE_ENDIAN, IEEE_BIG_ENDIAN, VAX, or IBM should be defined. +#endif + +typedef union { double d; ULong L[2]; } U; + +#ifdef YES_ALIAS +typedef double double_u; +# define dval(x) (x) +# ifdef IEEE_LITTLE_ENDIAN +# define word0(x) (((ULong *)&(x))[1]) +# define word1(x) (((ULong *)&(x))[0]) +# else +# define word0(x) (((ULong *)&(x))[0]) +# define word1(x) (((ULong *)&(x))[1]) +# endif +#else +typedef U double_u; +# ifdef IEEE_LITTLE_ENDIAN +# define word0(x) ((x).L[1]) +# define word1(x) ((x).L[0]) +# else +# define word0(x) ((x).L[0]) +# define word1(x) ((x).L[1]) +# endif +# define dval(x) ((x).d) +#endif + +/* The following definition of Storeinc is appropriate for MIPS processors. + * An alternative that might be better on some machines is + * #define Storeinc(a,b,c) (*a++ = b << 16 | c & 0xffff) + */ +#if defined(IEEE_LITTLE_ENDIAN) + defined(VAX) + defined(__arm__) +#define Storeinc(a,b,c) (((unsigned short *)(a))[1] = (unsigned short)(b), \ +((unsigned short *)(a))[0] = (unsigned short)(c), (a)++) +#else +#define Storeinc(a,b,c) (((unsigned short *)(a))[0] = (unsigned short)(b), \ +((unsigned short *)(a))[1] = (unsigned short)(c), (a)++) +#endif + +/* #define P DBL_MANT_DIG */ +/* Ten_pmax = floor(P*log(2)/log(5)) */ +/* Bletch = (highest power of 2 < DBL_MAX_10_EXP) / 16 */ +/* Quick_max = floor((P-1)*log(FLT_RADIX)/log(10) - 1) */ +/* Int_max = floor(P*log(FLT_RADIX)/log(10) - 1) */ + +#ifdef IEEE_Arith +#define Exp_shift 20 +#define Exp_shift1 20 +#define Exp_msk1 0x100000 +#define Exp_msk11 0x100000 +#define Exp_mask 0x7ff00000 +#define P 53 +#define Bias 1023 +#define Emin (-1022) +#define Exp_1 0x3ff00000 +#define Exp_11 0x3ff00000 +#define Ebits 11 +#define Frac_mask 0xfffff +#define Frac_mask1 0xfffff +#define Ten_pmax 22 +#define Bletch 0x10 +#define Bndry_mask 0xfffff +#define Bndry_mask1 0xfffff +#define LSB 1 +#define Sign_bit 0x80000000 +#define Log2P 1 +#define Tiny0 0 +#define Tiny1 1 +#define Quick_max 14 +#define Int_max 14 +#ifndef NO_IEEE_Scale +#define Avoid_Underflow +#ifdef Flush_Denorm /* debugging option */ +#undef Sudden_Underflow +#endif +#endif + +#ifndef Flt_Rounds +#ifdef FLT_ROUNDS +#define Flt_Rounds FLT_ROUNDS +#else +#define Flt_Rounds 1 +#endif +#endif /*Flt_Rounds*/ + +#ifdef Honor_FLT_ROUNDS +#define Rounding rounding +#undef Check_FLT_ROUNDS +#define Check_FLT_ROUNDS +#else +#define Rounding Flt_Rounds +#endif + +#else /* ifndef IEEE_Arith */ +#undef Check_FLT_ROUNDS +#undef Honor_FLT_ROUNDS +#undef SET_INEXACT +#undef Sudden_Underflow +#define Sudden_Underflow +#ifdef IBM +#undef Flt_Rounds +#define Flt_Rounds 0 +#define Exp_shift 24 +#define Exp_shift1 24 +#define Exp_msk1 0x1000000 +#define Exp_msk11 0x1000000 +#define Exp_mask 0x7f000000 +#define P 14 +#define Bias 65 +#define Exp_1 0x41000000 +#define Exp_11 0x41000000 +#define Ebits 8 /* exponent has 7 bits, but 8 is the right value in b2d */ +#define Frac_mask 0xffffff +#define Frac_mask1 0xffffff +#define Bletch 4 +#define Ten_pmax 22 +#define Bndry_mask 0xefffff +#define Bndry_mask1 0xffffff +#define LSB 1 +#define Sign_bit 0x80000000 +#define Log2P 4 +#define Tiny0 0x100000 +#define Tiny1 0 +#define Quick_max 14 +#define Int_max 15 +#else /* VAX */ +#undef Flt_Rounds +#define Flt_Rounds 1 +#define Exp_shift 23 +#define Exp_shift1 7 +#define Exp_msk1 0x80 +#define Exp_msk11 0x800000 +#define Exp_mask 0x7f80 +#define P 56 +#define Bias 129 +#define Exp_1 0x40800000 +#define Exp_11 0x4080 +#define Ebits 8 +#define Frac_mask 0x7fffff +#define Frac_mask1 0xffff007f +#define Ten_pmax 24 +#define Bletch 2 +#define Bndry_mask 0xffff007f +#define Bndry_mask1 0xffff007f +#define LSB 0x10000 +#define Sign_bit 0x8000 +#define Log2P 1 +#define Tiny0 0x80 +#define Tiny1 0 +#define Quick_max 15 +#define Int_max 15 +#endif /* IBM, VAX */ +#endif /* IEEE_Arith */ + +#ifndef IEEE_Arith +#define ROUND_BIASED +#endif + +#ifdef RND_PRODQUOT +#define rounded_product(a,b) ((a) = rnd_prod((a), (b))) +#define rounded_quotient(a,b) ((a) = rnd_quot((a), (b))) +extern double rnd_prod(double, double), rnd_quot(double, double); +#else +#define rounded_product(a,b) ((a) *= (b)) +#define rounded_quotient(a,b) ((a) /= (b)) +#endif + +#define Big0 (Frac_mask1 | Exp_msk1*(DBL_MAX_EXP+Bias-1)) +#define Big1 0xffffffff + +#ifndef Pack_32 +#define Pack_32 +#endif + +#define FFFFFFFF 0xffffffffUL + +#ifdef NO_LONG_LONG +#undef ULLong +#ifdef Just_16 +#undef Pack_32 +/* When Pack_32 is not defined, we store 16 bits per 32-bit Long. + * This makes some inner loops simpler and sometimes saves work + * during multiplications, but it often seems to make things slightly + * slower. Hence the default is now to store 32 bits per Long. + */ +#endif +#else /* long long available */ +#ifndef Llong +#define Llong long long +#endif +#ifndef ULLong +#define ULLong unsigned Llong +#endif +#endif /* NO_LONG_LONG */ + +#define MULTIPLE_THREADS 1 + +#ifndef MULTIPLE_THREADS +#define ACQUIRE_DTOA_LOCK(n) /*nothing*/ +#define FREE_DTOA_LOCK(n) /*nothing*/ +#else +#define ACQUIRE_DTOA_LOCK(n) /*unused right now*/ +#define FREE_DTOA_LOCK(n) /*unused right now*/ +#endif + +#ifndef ATOMIC_PTR_CAS +#define ATOMIC_PTR_CAS(var, old, new) ((var) = (new), (old)) +#endif +#ifndef LIKELY +#define LIKELY(x) (x) +#endif +#ifndef UNLIKELY +#define UNLIKELY(x) (x) +#endif +#ifndef ASSUME +#define ASSUME(x) (void)(x) +#endif + +#define Kmax 15 + +struct Bigint { + struct Bigint *next; + int k, maxwds, sign, wds; + ULong x[1]; +}; + +typedef struct Bigint Bigint; + +static Bigint *freelist[Kmax+1]; + +static Bigint * +Balloc(int k) +{ + int x; + Bigint *rv; +#ifndef Omit_Private_Memory + size_t len; +#endif + + rv = 0; + ACQUIRE_DTOA_LOCK(0); + if (k <= Kmax) { + rv = freelist[k]; + while (rv) { + Bigint *rvn = rv; + rv = ATOMIC_PTR_CAS(freelist[k], rv, rv->next); + if (LIKELY(rvn == rv)) { + ASSUME(rv); + break; + } + } + } + if (!rv) { + x = 1 << k; +#ifdef Omit_Private_Memory + rv = (Bigint *)MALLOC(sizeof(Bigint) + (x-1)*sizeof(ULong)); +#else + len = (sizeof(Bigint) + (x-1)*sizeof(ULong) + sizeof(double) - 1) + /sizeof(double); + if (k <= Kmax) { + double *pnext = pmem_next; + while (pnext - private_mem + len <= PRIVATE_mem) { + double *p = pnext; + pnext = ATOMIC_PTR_CAS(pmem_next, pnext, pnext + len); + if (LIKELY(p == pnext)) { + rv = (Bigint*)pnext; + ASSUME(rv); + break; + } + } + } + if (!rv) + rv = (Bigint*)MALLOC(len*sizeof(double)); +#endif + rv->k = k; + rv->maxwds = x; + } + FREE_DTOA_LOCK(0); + rv->sign = rv->wds = 0; + return rv; +} + +static void +Bfree(Bigint *v) +{ + Bigint *vn; + if (v) { + if (v->k > Kmax) { + FREE(v); + return; + } + ACQUIRE_DTOA_LOCK(0); + do { + vn = v->next = freelist[v->k]; + } while (UNLIKELY(ATOMIC_PTR_CAS(freelist[v->k], vn, v) != vn)); + FREE_DTOA_LOCK(0); + } +} + +#define Bcopy(x,y) memcpy((char *)&(x)->sign, (char *)&(y)->sign, \ +(y)->wds*sizeof(Long) + 2*sizeof(int)) + +static Bigint * +multadd(Bigint *b, int m, int a) /* multiply by m and add a */ +{ + int i, wds; + ULong *x; +#ifdef ULLong + ULLong carry, y; +#else + ULong carry, y; +#ifdef Pack_32 + ULong xi, z; +#endif +#endif + Bigint *b1; + + wds = b->wds; + x = b->x; + i = 0; + carry = a; + do { +#ifdef ULLong + y = *x * (ULLong)m + carry; + carry = y >> 32; + *x++ = (ULong)(y & FFFFFFFF); +#else +#ifdef Pack_32 + xi = *x; + y = (xi & 0xffff) * m + carry; + z = (xi >> 16) * m + (y >> 16); + carry = z >> 16; + *x++ = (z << 16) + (y & 0xffff); +#else + y = *x * m + carry; + carry = y >> 16; + *x++ = y & 0xffff; +#endif +#endif + } while (++i < wds); + if (carry) { + if (wds >= b->maxwds) { + b1 = Balloc(b->k+1); + Bcopy(b1, b); + Bfree(b); + b = b1; + } + b->x[wds++] = (ULong)carry; + b->wds = wds; + } + return b; +} + +static Bigint * +s2b(const char *s, int nd0, int nd, ULong y9) +{ + Bigint *b; + int i, k; + Long x, y; + + x = (nd + 8) / 9; + for (k = 0, y = 1; x > y; y <<= 1, k++) ; +#ifdef Pack_32 + b = Balloc(k); + b->x[0] = y9; + b->wds = 1; +#else + b = Balloc(k+1); + b->x[0] = y9 & 0xffff; + b->wds = (b->x[1] = y9 >> 16) ? 2 : 1; +#endif + + i = 9; + if (9 < nd0) { + s += 9; + do { + b = multadd(b, 10, *s++ - '0'); + } while (++i < nd0); + s++; + } + else + s += 10; + for (; i < nd; i++) + b = multadd(b, 10, *s++ - '0'); + return b; +} + +static int +hi0bits(register ULong x) +{ + register int k = 0; + + if (!(x & 0xffff0000)) { + k = 16; + x <<= 16; + } + if (!(x & 0xff000000)) { + k += 8; + x <<= 8; + } + if (!(x & 0xf0000000)) { + k += 4; + x <<= 4; + } + if (!(x & 0xc0000000)) { + k += 2; + x <<= 2; + } + if (!(x & 0x80000000)) { + k++; + if (!(x & 0x40000000)) + return 32; + } + return k; +} + +static int +lo0bits(ULong *y) +{ + register int k; + register ULong x = *y; + + if (x & 7) { + if (x & 1) + return 0; + if (x & 2) { + *y = x >> 1; + return 1; + } + *y = x >> 2; + return 2; + } + k = 0; + if (!(x & 0xffff)) { + k = 16; + x >>= 16; + } + if (!(x & 0xff)) { + k += 8; + x >>= 8; + } + if (!(x & 0xf)) { + k += 4; + x >>= 4; + } + if (!(x & 0x3)) { + k += 2; + x >>= 2; + } + if (!(x & 1)) { + k++; + x >>= 1; + if (!x) + return 32; + } + *y = x; + return k; +} + +static Bigint * +i2b(int i) +{ + Bigint *b; + + b = Balloc(1); + b->x[0] = i; + b->wds = 1; + return b; +} + +static Bigint * +mult(Bigint *a, Bigint *b) +{ + Bigint *c; + int k, wa, wb, wc; + ULong *x, *xa, *xae, *xb, *xbe, *xc, *xc0; + ULong y; +#ifdef ULLong + ULLong carry, z; +#else + ULong carry, z; +#ifdef Pack_32 + ULong z2; +#endif +#endif + + if (a->wds < b->wds) { + c = a; + a = b; + b = c; + } + k = a->k; + wa = a->wds; + wb = b->wds; + wc = wa + wb; + if (wc > a->maxwds) + k++; + c = Balloc(k); + for (x = c->x, xa = x + wc; x < xa; x++) + *x = 0; + xa = a->x; + xae = xa + wa; + xb = b->x; + xbe = xb + wb; + xc0 = c->x; +#ifdef ULLong + for (; xb < xbe; xc0++) { + if ((y = *xb++) != 0) { + x = xa; + xc = xc0; + carry = 0; + do { + z = *x++ * (ULLong)y + *xc + carry; + carry = z >> 32; + *xc++ = (ULong)(z & FFFFFFFF); + } while (x < xae); + *xc = (ULong)carry; + } + } +#else +#ifdef Pack_32 + for (; xb < xbe; xb++, xc0++) { + if ((y = *xb & 0xffff) != 0) { + x = xa; + xc = xc0; + carry = 0; + do { + z = (*x & 0xffff) * y + (*xc & 0xffff) + carry; + carry = z >> 16; + z2 = (*x++ >> 16) * y + (*xc >> 16) + carry; + carry = z2 >> 16; + Storeinc(xc, z2, z); + } while (x < xae); + *xc = (ULong)carry; + } + if ((y = *xb >> 16) != 0) { + x = xa; + xc = xc0; + carry = 0; + z2 = *xc; + do { + z = (*x & 0xffff) * y + (*xc >> 16) + carry; + carry = z >> 16; + Storeinc(xc, z, z2); + z2 = (*x++ >> 16) * y + (*xc & 0xffff) + carry; + carry = z2 >> 16; + } while (x < xae); + *xc = z2; + } + } +#else + for (; xb < xbe; xc0++) { + if (y = *xb++) { + x = xa; + xc = xc0; + carry = 0; + do { + z = *x++ * y + *xc + carry; + carry = z >> 16; + *xc++ = z & 0xffff; + } while (x < xae); + *xc = (ULong)carry; + } + } +#endif +#endif + for (xc0 = c->x, xc = xc0 + wc; wc > 0 && !*--xc; --wc) ; + c->wds = wc; + return c; +} + +static Bigint *p5s; + +static Bigint * +pow5mult(Bigint *b, int k) +{ + Bigint *b1, *p5, *p51; + Bigint *p5tmp; + int i; + static const int p05[3] = { 5, 25, 125 }; + + if ((i = k & 3) != 0) + b = multadd(b, p05[i-1], 0); + + if (!(k >>= 2)) + return b; + if (!(p5 = p5s)) { + /* first time */ + ACQUIRE_DTOA_LOCK(1); + if (!(p5 = p5s)) { + p5 = i2b(625); + p5->next = 0; + p5tmp = ATOMIC_PTR_CAS(p5s, NULL, p5); + if (UNLIKELY(p5tmp)) { + Bfree(p5); + p5 = p5tmp; + } + } + FREE_DTOA_LOCK(1); + } + for (;;) { + if (k & 1) { + b1 = mult(b, p5); + Bfree(b); + b = b1; + } + if (!(k >>= 1)) + break; + if (!(p51 = p5->next)) { + ACQUIRE_DTOA_LOCK(1); + if (!(p51 = p5->next)) { + p51 = mult(p5,p5); + p51->next = 0; + p5tmp = ATOMIC_PTR_CAS(p5->next, NULL, p51); + if (UNLIKELY(p5tmp)) { + Bfree(p51); + p51 = p5tmp; + } + } + FREE_DTOA_LOCK(1); + } + p5 = p51; + } + return b; +} + +static Bigint * +lshift(Bigint *b, int k) +{ + int i, k1, n, n1; + Bigint *b1; + ULong *x, *x1, *xe, z; + +#ifdef Pack_32 + n = k >> 5; +#else + n = k >> 4; +#endif + k1 = b->k; + n1 = n + b->wds + 1; + for (i = b->maxwds; n1 > i; i <<= 1) + k1++; + b1 = Balloc(k1); + x1 = b1->x; + for (i = 0; i < n; i++) + *x1++ = 0; + x = b->x; + xe = x + b->wds; +#ifdef Pack_32 + if (k &= 0x1f) { + k1 = 32 - k; + z = 0; + do { + *x1++ = *x << k | z; + z = *x++ >> k1; + } while (x < xe); + if ((*x1 = z) != 0) + ++n1; + } +#else + if (k &= 0xf) { + k1 = 16 - k; + z = 0; + do { + *x1++ = *x << k & 0xffff | z; + z = *x++ >> k1; + } while (x < xe); + if (*x1 = z) + ++n1; + } +#endif + else + do { + *x1++ = *x++; + } while (x < xe); + b1->wds = n1 - 1; + Bfree(b); + return b1; +} + +static int +cmp(Bigint *a, Bigint *b) +{ + ULong *xa, *xa0, *xb, *xb0; + int i, j; + + i = a->wds; + j = b->wds; +#ifdef DEBUG + if (i > 1 && !a->x[i-1]) + Bug("cmp called with a->x[a->wds-1] == 0"); + if (j > 1 && !b->x[j-1]) + Bug("cmp called with b->x[b->wds-1] == 0"); +#endif + if (i -= j) + return i; + xa0 = a->x; + xa = xa0 + j; + xb0 = b->x; + xb = xb0 + j; + for (;;) { + if (*--xa != *--xb) + return *xa < *xb ? -1 : 1; + if (xa <= xa0) + break; + } + return 0; +} + +NO_SANITIZE("unsigned-integer-overflow", static Bigint * diff(Bigint *a, Bigint *b)); +static Bigint * +diff(Bigint *a, Bigint *b) +{ + Bigint *c; + int i, wa, wb; + ULong *xa, *xae, *xb, *xbe, *xc; +#ifdef ULLong + ULLong borrow, y; +#else + ULong borrow, y; +#ifdef Pack_32 + ULong z; +#endif +#endif + + i = cmp(a,b); + if (!i) { + c = Balloc(0); + c->wds = 1; + c->x[0] = 0; + return c; + } + if (i < 0) { + c = a; + a = b; + b = c; + i = 1; + } + else + i = 0; + c = Balloc(a->k); + c->sign = i; + wa = a->wds; + xa = a->x; + xae = xa + wa; + wb = b->wds; + xb = b->x; + xbe = xb + wb; + xc = c->x; + borrow = 0; +#ifdef ULLong + do { + y = (ULLong)*xa++ - *xb++ - borrow; + borrow = y >> 32 & (ULong)1; + *xc++ = (ULong)(y & FFFFFFFF); + } while (xb < xbe); + while (xa < xae) { + y = *xa++ - borrow; + borrow = y >> 32 & (ULong)1; + *xc++ = (ULong)(y & FFFFFFFF); + } +#else +#ifdef Pack_32 + do { + y = (*xa & 0xffff) - (*xb & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*xa++ >> 16) - (*xb++ >> 16) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(xc, z, y); + } while (xb < xbe); + while (xa < xae) { + y = (*xa & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*xa++ >> 16) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(xc, z, y); + } +#else + do { + y = *xa++ - *xb++ - borrow; + borrow = (y & 0x10000) >> 16; + *xc++ = y & 0xffff; + } while (xb < xbe); + while (xa < xae) { + y = *xa++ - borrow; + borrow = (y & 0x10000) >> 16; + *xc++ = y & 0xffff; + } +#endif +#endif + while (!*--xc) + wa--; + c->wds = wa; + return c; +} + +static double +ulp(double x_) +{ + register Long L; + double_u x, a; + dval(x) = x_; + + L = (word0(x) & Exp_mask) - (P-1)*Exp_msk1; +#ifndef Avoid_Underflow +#ifndef Sudden_Underflow + if (L > 0) { +#endif +#endif +#ifdef IBM + L |= Exp_msk1 >> 4; +#endif + word0(a) = L; + word1(a) = 0; +#ifndef Avoid_Underflow +#ifndef Sudden_Underflow + } + else { + L = -L >> Exp_shift; + if (L < Exp_shift) { + word0(a) = 0x80000 >> L; + word1(a) = 0; + } + else { + word0(a) = 0; + L -= Exp_shift; + word1(a) = L >= 31 ? 1 : 1 << 31 - L; + } + } +#endif +#endif + return dval(a); +} + +static double +b2d(Bigint *a, int *e) +{ + ULong *xa, *xa0, w, y, z; + int k; + double_u d; +#ifdef VAX + ULong d0, d1; +#else +#define d0 word0(d) +#define d1 word1(d) +#endif + + xa0 = a->x; + xa = xa0 + a->wds; + y = *--xa; +#ifdef DEBUG + if (!y) Bug("zero y in b2d"); +#endif + k = hi0bits(y); + *e = 32 - k; +#ifdef Pack_32 + if (k < Ebits) { + d0 = Exp_1 | y >> (Ebits - k); + w = xa > xa0 ? *--xa : 0; + d1 = y << ((32-Ebits) + k) | w >> (Ebits - k); + goto ret_d; + } + z = xa > xa0 ? *--xa : 0; + if (k -= Ebits) { + d0 = Exp_1 | y << k | z >> (32 - k); + y = xa > xa0 ? *--xa : 0; + d1 = z << k | y >> (32 - k); + } + else { + d0 = Exp_1 | y; + d1 = z; + } +#else + if (k < Ebits + 16) { + z = xa > xa0 ? *--xa : 0; + d0 = Exp_1 | y << k - Ebits | z >> Ebits + 16 - k; + w = xa > xa0 ? *--xa : 0; + y = xa > xa0 ? *--xa : 0; + d1 = z << k + 16 - Ebits | w << k - Ebits | y >> 16 + Ebits - k; + goto ret_d; + } + z = xa > xa0 ? *--xa : 0; + w = xa > xa0 ? *--xa : 0; + k -= Ebits + 16; + d0 = Exp_1 | y << k + 16 | z << k | w >> 16 - k; + y = xa > xa0 ? *--xa : 0; + d1 = w << k + 16 | y << k; +#endif +ret_d: +#ifdef VAX + word0(d) = d0 >> 16 | d0 << 16; + word1(d) = d1 >> 16 | d1 << 16; +#else +#undef d0 +#undef d1 +#endif + return dval(d); +} + +static Bigint * +d2b(double d_, int *e, int *bits) +{ + double_u d; + Bigint *b; + int de, k; + ULong *x, y, z; +#ifndef Sudden_Underflow + int i; +#endif +#ifdef VAX + ULong d0, d1; +#endif + dval(d) = d_; +#ifdef VAX + d0 = word0(d) >> 16 | word0(d) << 16; + d1 = word1(d) >> 16 | word1(d) << 16; +#else +#define d0 word0(d) +#define d1 word1(d) +#endif + +#ifdef Pack_32 + b = Balloc(1); +#else + b = Balloc(2); +#endif + x = b->x; + + z = d0 & Frac_mask; + d0 &= 0x7fffffff; /* clear sign bit, which we ignore */ +#ifdef Sudden_Underflow + de = (int)(d0 >> Exp_shift); +#ifndef IBM + z |= Exp_msk11; +#endif +#else + if ((de = (int)(d0 >> Exp_shift)) != 0) + z |= Exp_msk1; +#endif +#ifdef Pack_32 + if ((y = d1) != 0) { + if ((k = lo0bits(&y)) != 0) { + x[0] = y | z << (32 - k); + z >>= k; + } + else + x[0] = y; +#ifndef Sudden_Underflow + i = +#endif + b->wds = (x[1] = z) ? 2 : 1; + } + else { +#ifdef DEBUG + if (!z) + Bug("Zero passed to d2b"); +#endif + k = lo0bits(&z); + x[0] = z; +#ifndef Sudden_Underflow + i = +#endif + b->wds = 1; + k += 32; + } +#else + if (y = d1) { + if (k = lo0bits(&y)) + if (k >= 16) { + x[0] = y | z << 32 - k & 0xffff; + x[1] = z >> k - 16 & 0xffff; + x[2] = z >> k; + i = 2; + } + else { + x[0] = y & 0xffff; + x[1] = y >> 16 | z << 16 - k & 0xffff; + x[2] = z >> k & 0xffff; + x[3] = z >> k+16; + i = 3; + } + else { + x[0] = y & 0xffff; + x[1] = y >> 16; + x[2] = z & 0xffff; + x[3] = z >> 16; + i = 3; + } + } + else { +#ifdef DEBUG + if (!z) + Bug("Zero passed to d2b"); +#endif + k = lo0bits(&z); + if (k >= 16) { + x[0] = z; + i = 0; + } + else { + x[0] = z & 0xffff; + x[1] = z >> 16; + i = 1; + } + k += 32; + } + while (!x[i]) + --i; + b->wds = i + 1; +#endif +#ifndef Sudden_Underflow + if (de) { +#endif +#ifdef IBM + *e = (de - Bias - (P-1) << 2) + k; + *bits = 4*P + 8 - k - hi0bits(word0(d) & Frac_mask); +#else + *e = de - Bias - (P-1) + k; + *bits = P - k; +#endif +#ifndef Sudden_Underflow + } + else { + *e = de - Bias - (P-1) + 1 + k; +#ifdef Pack_32 + *bits = 32*i - hi0bits(x[i-1]); +#else + *bits = (i+2)*16 - hi0bits(x[i]); +#endif + } +#endif + return b; +} +#undef d0 +#undef d1 + +static double +ratio(Bigint *a, Bigint *b) +{ + double_u da, db; + int k, ka, kb; + + dval(da) = b2d(a, &ka); + dval(db) = b2d(b, &kb); +#ifdef Pack_32 + k = ka - kb + 32*(a->wds - b->wds); +#else + k = ka - kb + 16*(a->wds - b->wds); +#endif +#ifdef IBM + if (k > 0) { + word0(da) += (k >> 2)*Exp_msk1; + if (k &= 3) + dval(da) *= 1 << k; + } + else { + k = -k; + word0(db) += (k >> 2)*Exp_msk1; + if (k &= 3) + dval(db) *= 1 << k; + } +#else + if (k > 0) + word0(da) += k*Exp_msk1; + else { + k = -k; + word0(db) += k*Exp_msk1; + } +#endif + return dval(da) / dval(db); +} + +static const double +tens[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, + 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, + 1e20, 1e21, 1e22 +#ifdef VAX + , 1e23, 1e24 +#endif +}; + +static const double +#ifdef IEEE_Arith +bigtens[] = { 1e16, 1e32, 1e64, 1e128, 1e256 }; +static const double tinytens[] = { 1e-16, 1e-32, 1e-64, 1e-128, +#ifdef Avoid_Underflow + 9007199254740992.*9007199254740992.e-256 + /* = 2^106 * 1e-53 */ +#else + 1e-256 +#endif +}; +/* The factor of 2^53 in tinytens[4] helps us avoid setting the underflow */ +/* flag unnecessarily. It leads to a song and dance at the end of strtod. */ +#define Scale_Bit 0x10 +#define n_bigtens 5 +#else +#ifdef IBM +bigtens[] = { 1e16, 1e32, 1e64 }; +static const double tinytens[] = { 1e-16, 1e-32, 1e-64 }; +#define n_bigtens 3 +#else +bigtens[] = { 1e16, 1e32 }; +static const double tinytens[] = { 1e-16, 1e-32 }; +#define n_bigtens 2 +#endif +#endif + +#ifndef IEEE_Arith +#undef INFNAN_CHECK +#endif + +#ifdef INFNAN_CHECK + +#ifndef NAN_WORD0 +#define NAN_WORD0 0x7ff80000 +#endif + +#ifndef NAN_WORD1 +#define NAN_WORD1 0 +#endif + +static int +match(const char **sp, char *t) +{ + int c, d; + const char *s = *sp; + + while (d = *t++) { + if ((c = *++s) >= 'A' && c <= 'Z') + c += 'a' - 'A'; + if (c != d) + return 0; + } + *sp = s + 1; + return 1; +} + +#ifndef No_Hex_NaN +static void +hexnan(double *rvp, const char **sp) +{ + ULong c, x[2]; + const char *s; + int havedig, udx0, xshift; + + x[0] = x[1] = 0; + havedig = xshift = 0; + udx0 = 1; + s = *sp; + while (c = *(const unsigned char*)++s) { + if (c >= '0' && c <= '9') + c -= '0'; + else if (c >= 'a' && c <= 'f') + c += 10 - 'a'; + else if (c >= 'A' && c <= 'F') + c += 10 - 'A'; + else if (c <= ' ') { + if (udx0 && havedig) { + udx0 = 0; + xshift = 1; + } + continue; + } + else if (/*(*/ c == ')' && havedig) { + *sp = s + 1; + break; + } + else + return; /* invalid form: don't change *sp */ + havedig = 1; + if (xshift) { + xshift = 0; + x[0] = x[1]; + x[1] = 0; + } + if (udx0) + x[0] = (x[0] << 4) | (x[1] >> 28); + x[1] = (x[1] << 4) | c; + } + if ((x[0] &= 0xfffff) || x[1]) { + word0(*rvp) = Exp_mask | x[0]; + word1(*rvp) = x[1]; + } +} +#endif /*No_Hex_NaN*/ +#endif /* INFNAN_CHECK */ + +NO_SANITIZE("unsigned-integer-overflow", double strtod(const char *s00, char **se)); +double +strtod(const char *s00, char **se) +{ +#ifdef Avoid_Underflow + int scale; +#endif + int bb2, bb5, bbe, bd2, bd5, bbbits, bs2, c, dsign, + e, e1, esign, i, j, k, nd, nd0, nf, nz, nz0, sign; + const char *s, *s0, *s1; + double aadj, adj; + double_u aadj1, rv, rv0; + Long L; + ULong y, z; + Bigint *bb, *bb1, *bd, *bd0, *bs, *delta; +#ifdef SET_INEXACT + int inexact, oldinexact; +#endif +#ifdef Honor_FLT_ROUNDS + int rounding; +#endif +#ifdef USE_LOCALE + const char *s2; +#endif + + errno = 0; + sign = nz0 = nz = 0; + dval(rv) = 0.; + for (s = s00;;s++) + switch (*s) { + case '-': + sign = 1; + /* no break */ + case '+': + if (*++s) + goto break2; + /* no break */ + case 0: + goto ret0; + case '\t': + case '\n': + case '\v': + case '\f': + case '\r': + case ' ': + continue; + default: + goto break2; + } +break2: + if (*s == '0') { + if (s[1] == 'x' || s[1] == 'X') { + s0 = ++s; + adj = 0; + aadj = 1.0; + nd0 = -4; + + if (!*++s || !(s1 = strchr(hexdigit, *s))) goto ret0; + if (*s == '0') { + while (*++s == '0'); + s1 = strchr(hexdigit, *s); + } + if (s1 != NULL) { + do { + adj += aadj * ((s1 - hexdigit) & 15); + nd0 += 4; + aadj /= 16; + } while (*++s && (s1 = strchr(hexdigit, *s))); + } + + if (*s == '.') { + dsign = 1; + if (!*++s || !(s1 = strchr(hexdigit, *s))) goto ret0; + if (nd0 < 0) { + while (*s == '0') { + s++; + nd0 -= 4; + } + } + for (; *s && (s1 = strchr(hexdigit, *s)); ++s) { + adj += aadj * ((s1 - hexdigit) & 15); + if ((aadj /= 16) == 0.0) { + while (strchr(hexdigit, *++s)); + break; + } + } + } + else { + dsign = 0; + } + + if (*s == 'P' || *s == 'p') { + dsign = 0x2C - *++s; /* +: 2B, -: 2D */ + if (abs(dsign) == 1) s++; + else dsign = 1; + + nd = 0; + c = *s; + if (c < '0' || '9' < c) goto ret0; + do { + nd *= 10; + nd += c; + nd -= '0'; + c = *++s; + /* Float("0x0."+("0"*267)+"1fp2095") */ + if (nd + dsign * nd0 > 2095) { + while ('0' <= c && c <= '9') c = *++s; + break; + } + } while ('0' <= c && c <= '9'); + nd0 += nd * dsign; + } + else { + if (dsign) goto ret0; + } + dval(rv) = ldexp(adj, nd0); + goto ret; + } + nz0 = 1; + while (*++s == '0') ; + if (!*s) + goto ret; + } + s0 = s; + y = z = 0; + for (nd = nf = 0; (c = *s) >= '0' && c <= '9'; nd++, s++) + if (nd < 9) + y = 10*y + c - '0'; + else if (nd < DBL_DIG + 2) + z = 10*z + c - '0'; + nd0 = nd; +#ifdef USE_LOCALE + s1 = localeconv()->decimal_point; + if (c == *s1) { + c = '.'; + if (*++s1) { + s2 = s; + for (;;) { + if (*++s2 != *s1) { + c = 0; + break; + } + if (!*++s1) { + s = s2; + break; + } + } + } + } +#endif + if (c == '.') { + if (!ISDIGIT(s[1])) + goto dig_done; + c = *++s; + if (!nd) { + for (; c == '0'; c = *++s) + nz++; + if (c > '0' && c <= '9') { + s0 = s; + nf += nz; + nz = 0; + goto have_dig; + } + goto dig_done; + } + for (; c >= '0' && c <= '9'; c = *++s) { +have_dig: + nz++; + if (nd > DBL_DIG * 4) { + continue; + } + if (c -= '0') { + nf += nz; + for (i = 1; i < nz; i++) + if (nd++ < 9) + y *= 10; + else if (nd <= DBL_DIG + 2) + z *= 10; + if (nd++ < 9) + y = 10*y + c; + else if (nd <= DBL_DIG + 2) + z = 10*z + c; + nz = 0; + } + } + } +dig_done: + e = 0; + if (c == 'e' || c == 'E') { + if (!nd && !nz && !nz0) { + goto ret0; + } + s00 = s; + esign = 0; + switch (c = *++s) { + case '-': + esign = 1; + case '+': + c = *++s; + } + if (c >= '0' && c <= '9') { + while (c == '0') + c = *++s; + if (c > '0' && c <= '9') { + L = c - '0'; + s1 = s; + while ((c = *++s) >= '0' && c <= '9') + L = 10*L + c - '0'; + if (s - s1 > 8 || L > 19999) + /* Avoid confusion from exponents + * so large that e might overflow. + */ + e = 19999; /* safe for 16 bit ints */ + else + e = (int)L; + if (esign) + e = -e; + } + else + e = 0; + } + else + s = s00; + } + if (!nd) { + if (!nz && !nz0) { +#ifdef INFNAN_CHECK + /* Check for Nan and Infinity */ + switch (c) { + case 'i': + case 'I': + if (match(&s,"nf")) { + --s; + if (!match(&s,"inity")) + ++s; + word0(rv) = 0x7ff00000; + word1(rv) = 0; + goto ret; + } + break; + case 'n': + case 'N': + if (match(&s, "an")) { + word0(rv) = NAN_WORD0; + word1(rv) = NAN_WORD1; +#ifndef No_Hex_NaN + if (*s == '(') /*)*/ + hexnan(&rv, &s); +#endif + goto ret; + } + } +#endif /* INFNAN_CHECK */ +ret0: + s = s00; + sign = 0; + } + goto ret; + } + e1 = e -= nf; + + /* Now we have nd0 digits, starting at s0, followed by a + * decimal point, followed by nd-nd0 digits. The number we're + * after is the integer represented by those digits times + * 10**e */ + + if (!nd0) + nd0 = nd; + k = nd < DBL_DIG + 2 ? nd : DBL_DIG + 2; + dval(rv) = y; + if (k > 9) { +#ifdef SET_INEXACT + if (k > DBL_DIG) + oldinexact = get_inexact(); +#endif + dval(rv) = tens[k - 9] * dval(rv) + z; + } + bd0 = bb = bd = bs = delta = 0; + if (nd <= DBL_DIG +#ifndef RND_PRODQUOT +#ifndef Honor_FLT_ROUNDS + && Flt_Rounds == 1 +#endif +#endif + ) { + if (!e) + goto ret; + if (e > 0) { + if (e <= Ten_pmax) { +#ifdef VAX + goto vax_ovfl_check; +#else +#ifdef Honor_FLT_ROUNDS + /* round correctly FLT_ROUNDS = 2 or 3 */ + if (sign) { + dval(rv) = -dval(rv); + sign = 0; + } +#endif + /* rv = */ rounded_product(dval(rv), tens[e]); + goto ret; +#endif + } + i = DBL_DIG - nd; + if (e <= Ten_pmax + i) { + /* A fancier test would sometimes let us do + * this for larger i values. + */ +#ifdef Honor_FLT_ROUNDS + /* round correctly FLT_ROUNDS = 2 or 3 */ + if (sign) { + dval(rv) = -dval(rv); + sign = 0; + } +#endif + e -= i; + dval(rv) *= tens[i]; +#ifdef VAX + /* VAX exponent range is so narrow we must + * worry about overflow here... + */ +vax_ovfl_check: + word0(rv) -= P*Exp_msk1; + /* rv = */ rounded_product(dval(rv), tens[e]); + if ((word0(rv) & Exp_mask) + > Exp_msk1*(DBL_MAX_EXP+Bias-1-P)) + goto ovfl; + word0(rv) += P*Exp_msk1; +#else + /* rv = */ rounded_product(dval(rv), tens[e]); +#endif + goto ret; + } + } +#ifndef Inaccurate_Divide + else if (e >= -Ten_pmax) { +#ifdef Honor_FLT_ROUNDS + /* round correctly FLT_ROUNDS = 2 or 3 */ + if (sign) { + dval(rv) = -dval(rv); + sign = 0; + } +#endif + /* rv = */ rounded_quotient(dval(rv), tens[-e]); + goto ret; + } +#endif + } + e1 += nd - k; + +#ifdef IEEE_Arith +#ifdef SET_INEXACT + inexact = 1; + if (k <= DBL_DIG) + oldinexact = get_inexact(); +#endif +#ifdef Avoid_Underflow + scale = 0; +#endif +#ifdef Honor_FLT_ROUNDS + if ((rounding = Flt_Rounds) >= 2) { + if (sign) + rounding = rounding == 2 ? 0 : 2; + else + if (rounding != 2) + rounding = 0; + } +#endif +#endif /*IEEE_Arith*/ + + /* Get starting approximation = rv * 10**e1 */ + + if (e1 > 0) { + if ((i = e1 & 15) != 0) + dval(rv) *= tens[i]; + if (e1 &= ~15) { + if (e1 > DBL_MAX_10_EXP) { +ovfl: +#ifndef NO_ERRNO + errno = ERANGE; +#endif + /* Can't trust HUGE_VAL */ +#ifdef IEEE_Arith +#ifdef Honor_FLT_ROUNDS + switch (rounding) { + case 0: /* toward 0 */ + case 3: /* toward -infinity */ + word0(rv) = Big0; + word1(rv) = Big1; + break; + default: + word0(rv) = Exp_mask; + word1(rv) = 0; + } +#else /*Honor_FLT_ROUNDS*/ + word0(rv) = Exp_mask; + word1(rv) = 0; +#endif /*Honor_FLT_ROUNDS*/ +#ifdef SET_INEXACT + /* set overflow bit */ + dval(rv0) = 1e300; + dval(rv0) *= dval(rv0); +#endif +#else /*IEEE_Arith*/ + word0(rv) = Big0; + word1(rv) = Big1; +#endif /*IEEE_Arith*/ + if (bd0) + goto retfree; + goto ret; + } + e1 >>= 4; + for (j = 0; e1 > 1; j++, e1 >>= 1) + if (e1 & 1) + dval(rv) *= bigtens[j]; + /* The last multiplication could overflow. */ + word0(rv) -= P*Exp_msk1; + dval(rv) *= bigtens[j]; + if ((z = word0(rv) & Exp_mask) + > Exp_msk1*(DBL_MAX_EXP+Bias-P)) + goto ovfl; + if (z > Exp_msk1*(DBL_MAX_EXP+Bias-1-P)) { + /* set to largest number */ + /* (Can't trust DBL_MAX) */ + word0(rv) = Big0; + word1(rv) = Big1; + } + else + word0(rv) += P*Exp_msk1; + } + } + else if (e1 < 0) { + e1 = -e1; + if ((i = e1 & 15) != 0) + dval(rv) /= tens[i]; + if (e1 >>= 4) { + if (e1 >= 1 << n_bigtens) + goto undfl; +#ifdef Avoid_Underflow + if (e1 & Scale_Bit) + scale = 2*P; + for (j = 0; e1 > 0; j++, e1 >>= 1) + if (e1 & 1) + dval(rv) *= tinytens[j]; + if (scale && (j = 2*P + 1 - ((word0(rv) & Exp_mask) + >> Exp_shift)) > 0) { + /* scaled rv is denormal; zap j low bits */ + if (j >= 32) { + word1(rv) = 0; + if (j >= 53) + word0(rv) = (P+2)*Exp_msk1; + else + word0(rv) &= 0xffffffff << (j-32); + } + else + word1(rv) &= 0xffffffff << j; + } +#else + for (j = 0; e1 > 1; j++, e1 >>= 1) + if (e1 & 1) + dval(rv) *= tinytens[j]; + /* The last multiplication could underflow. */ + dval(rv0) = dval(rv); + dval(rv) *= tinytens[j]; + if (!dval(rv)) { + dval(rv) = 2.*dval(rv0); + dval(rv) *= tinytens[j]; +#endif + if (!dval(rv)) { +undfl: + dval(rv) = 0.; +#ifndef NO_ERRNO + errno = ERANGE; +#endif + if (bd0) + goto retfree; + goto ret; + } +#ifndef Avoid_Underflow + word0(rv) = Tiny0; + word1(rv) = Tiny1; + /* The refinement below will clean + * this approximation up. + */ + } +#endif + } + } + + /* Now the hard part -- adjusting rv to the correct value.*/ + + /* Put digits into bd: true value = bd * 10^e */ + + bd0 = s2b(s0, nd0, nd, y); + + for (;;) { + bd = Balloc(bd0->k); + Bcopy(bd, bd0); + bb = d2b(dval(rv), &bbe, &bbbits); /* rv = bb * 2^bbe */ + bs = i2b(1); + + if (e >= 0) { + bb2 = bb5 = 0; + bd2 = bd5 = e; + } + else { + bb2 = bb5 = -e; + bd2 = bd5 = 0; + } + if (bbe >= 0) + bb2 += bbe; + else + bd2 -= bbe; + bs2 = bb2; +#ifdef Honor_FLT_ROUNDS + if (rounding != 1) + bs2++; +#endif +#ifdef Avoid_Underflow + j = bbe - scale; + i = j + bbbits - 1; /* logb(rv) */ + if (i < Emin) /* denormal */ + j += P - Emin; + else + j = P + 1 - bbbits; +#else /*Avoid_Underflow*/ +#ifdef Sudden_Underflow +#ifdef IBM + j = 1 + 4*P - 3 - bbbits + ((bbe + bbbits - 1) & 3); +#else + j = P + 1 - bbbits; +#endif +#else /*Sudden_Underflow*/ + j = bbe; + i = j + bbbits - 1; /* logb(rv) */ + if (i < Emin) /* denormal */ + j += P - Emin; + else + j = P + 1 - bbbits; +#endif /*Sudden_Underflow*/ +#endif /*Avoid_Underflow*/ + bb2 += j; + bd2 += j; +#ifdef Avoid_Underflow + bd2 += scale; +#endif + i = bb2 < bd2 ? bb2 : bd2; + if (i > bs2) + i = bs2; + if (i > 0) { + bb2 -= i; + bd2 -= i; + bs2 -= i; + } + if (bb5 > 0) { + bs = pow5mult(bs, bb5); + bb1 = mult(bs, bb); + Bfree(bb); + bb = bb1; + } + if (bb2 > 0) + bb = lshift(bb, bb2); + if (bd5 > 0) + bd = pow5mult(bd, bd5); + if (bd2 > 0) + bd = lshift(bd, bd2); + if (bs2 > 0) + bs = lshift(bs, bs2); + delta = diff(bb, bd); + dsign = delta->sign; + delta->sign = 0; + i = cmp(delta, bs); +#ifdef Honor_FLT_ROUNDS + if (rounding != 1) { + if (i < 0) { + /* Error is less than an ulp */ + if (!delta->x[0] && delta->wds <= 1) { + /* exact */ +#ifdef SET_INEXACT + inexact = 0; +#endif + break; + } + if (rounding) { + if (dsign) { + adj = 1.; + goto apply_adj; + } + } + else if (!dsign) { + adj = -1.; + if (!word1(rv) + && !(word0(rv) & Frac_mask)) { + y = word0(rv) & Exp_mask; +#ifdef Avoid_Underflow + if (!scale || y > 2*P*Exp_msk1) +#else + if (y) +#endif + { + delta = lshift(delta,Log2P); + if (cmp(delta, bs) <= 0) + adj = -0.5; + } + } +apply_adj: +#ifdef Avoid_Underflow + if (scale && (y = word0(rv) & Exp_mask) + <= 2*P*Exp_msk1) + word0(adj) += (2*P+1)*Exp_msk1 - y; +#else +#ifdef Sudden_Underflow + if ((word0(rv) & Exp_mask) <= + P*Exp_msk1) { + word0(rv) += P*Exp_msk1; + dval(rv) += adj*ulp(dval(rv)); + word0(rv) -= P*Exp_msk1; + } + else +#endif /*Sudden_Underflow*/ +#endif /*Avoid_Underflow*/ + dval(rv) += adj*ulp(dval(rv)); + } + break; + } + adj = ratio(delta, bs); + if (adj < 1.) + adj = 1.; + if (adj <= 0x7ffffffe) { + /* adj = rounding ? ceil(adj) : floor(adj); */ + y = adj; + if (y != adj) { + if (!((rounding>>1) ^ dsign)) + y++; + adj = y; + } + } +#ifdef Avoid_Underflow + if (scale && (y = word0(rv) & Exp_mask) <= 2*P*Exp_msk1) + word0(adj) += (2*P+1)*Exp_msk1 - y; +#else +#ifdef Sudden_Underflow + if ((word0(rv) & Exp_mask) <= P*Exp_msk1) { + word0(rv) += P*Exp_msk1; + adj *= ulp(dval(rv)); + if (dsign) + dval(rv) += adj; + else + dval(rv) -= adj; + word0(rv) -= P*Exp_msk1; + goto cont; + } +#endif /*Sudden_Underflow*/ +#endif /*Avoid_Underflow*/ + adj *= ulp(dval(rv)); + if (dsign) + dval(rv) += adj; + else + dval(rv) -= adj; + goto cont; + } +#endif /*Honor_FLT_ROUNDS*/ + + if (i < 0) { + /* Error is less than half an ulp -- check for + * special case of mantissa a power of two. + */ + if (dsign || word1(rv) || word0(rv) & Bndry_mask +#ifdef IEEE_Arith +#ifdef Avoid_Underflow + || (word0(rv) & Exp_mask) <= (2*P+1)*Exp_msk1 +#else + || (word0(rv) & Exp_mask) <= Exp_msk1 +#endif +#endif + ) { +#ifdef SET_INEXACT + if (!delta->x[0] && delta->wds <= 1) + inexact = 0; +#endif + break; + } + if (!delta->x[0] && delta->wds <= 1) { + /* exact result */ +#ifdef SET_INEXACT + inexact = 0; +#endif + break; + } + delta = lshift(delta,Log2P); + if (cmp(delta, bs) > 0) + goto drop_down; + break; + } + if (i == 0) { + /* exactly half-way between */ + if (dsign) { + if ((word0(rv) & Bndry_mask1) == Bndry_mask1 + && word1(rv) == ( +#ifdef Avoid_Underflow + (scale && (y = word0(rv) & Exp_mask) <= 2*P*Exp_msk1) + ? (0xffffffff & (0xffffffff << (2*P+1-(y>>Exp_shift)))) : +#endif + 0xffffffff)) { + /*boundary case -- increment exponent*/ + word0(rv) = (word0(rv) & Exp_mask) + + Exp_msk1 +#ifdef IBM + | Exp_msk1 >> 4 +#endif + ; + word1(rv) = 0; +#ifdef Avoid_Underflow + dsign = 0; +#endif + break; + } + } + else if (!(word0(rv) & Bndry_mask) && !word1(rv)) { +drop_down: + /* boundary case -- decrement exponent */ +#ifdef Sudden_Underflow /*{{*/ + L = word0(rv) & Exp_mask; +#ifdef IBM + if (L < Exp_msk1) +#else +#ifdef Avoid_Underflow + if (L <= (scale ? (2*P+1)*Exp_msk1 : Exp_msk1)) +#else + if (L <= Exp_msk1) +#endif /*Avoid_Underflow*/ +#endif /*IBM*/ + goto undfl; + L -= Exp_msk1; +#else /*Sudden_Underflow}{*/ +#ifdef Avoid_Underflow + if (scale) { + L = word0(rv) & Exp_mask; + if (L <= (2*P+1)*Exp_msk1) { + if (L > (P+2)*Exp_msk1) + /* round even ==> */ + /* accept rv */ + break; + /* rv = smallest denormal */ + goto undfl; + } + } +#endif /*Avoid_Underflow*/ + L = (word0(rv) & Exp_mask) - Exp_msk1; +#endif /*Sudden_Underflow}}*/ + word0(rv) = L | Bndry_mask1; + word1(rv) = 0xffffffff; +#ifdef IBM + goto cont; +#else + break; +#endif + } +#ifndef ROUND_BIASED + if (!(word1(rv) & LSB)) + break; +#endif + if (dsign) + dval(rv) += ulp(dval(rv)); +#ifndef ROUND_BIASED + else { + dval(rv) -= ulp(dval(rv)); +#ifndef Sudden_Underflow + if (!dval(rv)) + goto undfl; +#endif + } +#ifdef Avoid_Underflow + dsign = 1 - dsign; +#endif +#endif + break; + } + if ((aadj = ratio(delta, bs)) <= 2.) { + if (dsign) + aadj = dval(aadj1) = 1.; + else if (word1(rv) || word0(rv) & Bndry_mask) { +#ifndef Sudden_Underflow + if (word1(rv) == Tiny1 && !word0(rv)) + goto undfl; +#endif + aadj = 1.; + dval(aadj1) = -1.; + } + else { + /* special case -- power of FLT_RADIX to be */ + /* rounded down... */ + + if (aadj < 2./FLT_RADIX) + aadj = 1./FLT_RADIX; + else + aadj *= 0.5; + dval(aadj1) = -aadj; + } + } + else { + aadj *= 0.5; + dval(aadj1) = dsign ? aadj : -aadj; +#ifdef Check_FLT_ROUNDS + switch (Rounding) { + case 2: /* towards +infinity */ + dval(aadj1) -= 0.5; + break; + case 0: /* towards 0 */ + case 3: /* towards -infinity */ + dval(aadj1) += 0.5; + } +#else + if (Flt_Rounds == 0) + dval(aadj1) += 0.5; +#endif /*Check_FLT_ROUNDS*/ + } + y = word0(rv) & Exp_mask; + + /* Check for overflow */ + + if (y == Exp_msk1*(DBL_MAX_EXP+Bias-1)) { + dval(rv0) = dval(rv); + word0(rv) -= P*Exp_msk1; + adj = dval(aadj1) * ulp(dval(rv)); + dval(rv) += adj; + if ((word0(rv) & Exp_mask) >= + Exp_msk1*(DBL_MAX_EXP+Bias-P)) { + if (word0(rv0) == Big0 && word1(rv0) == Big1) + goto ovfl; + word0(rv) = Big0; + word1(rv) = Big1; + goto cont; + } + else + word0(rv) += P*Exp_msk1; + } + else { +#ifdef Avoid_Underflow + if (scale && y <= 2*P*Exp_msk1) { + if (aadj <= 0x7fffffff) { + if ((z = (int)aadj) <= 0) + z = 1; + aadj = z; + dval(aadj1) = dsign ? aadj : -aadj; + } + word0(aadj1) += (2*P+1)*Exp_msk1 - y; + } + adj = dval(aadj1) * ulp(dval(rv)); + dval(rv) += adj; +#else +#ifdef Sudden_Underflow + if ((word0(rv) & Exp_mask) <= P*Exp_msk1) { + dval(rv0) = dval(rv); + word0(rv) += P*Exp_msk1; + adj = dval(aadj1) * ulp(dval(rv)); + dval(rv) += adj; +#ifdef IBM + if ((word0(rv) & Exp_mask) < P*Exp_msk1) +#else + if ((word0(rv) & Exp_mask) <= P*Exp_msk1) +#endif + { + if (word0(rv0) == Tiny0 && word1(rv0) == Tiny1) + goto undfl; + word0(rv) = Tiny0; + word1(rv) = Tiny1; + goto cont; + } + else + word0(rv) -= P*Exp_msk1; + } + else { + adj = dval(aadj1) * ulp(dval(rv)); + dval(rv) += adj; + } +#else /*Sudden_Underflow*/ + /* Compute adj so that the IEEE rounding rules will + * correctly round rv + adj in some half-way cases. + * If rv * ulp(rv) is denormalized (i.e., + * y <= (P-1)*Exp_msk1), we must adjust aadj to avoid + * trouble from bits lost to denormalization; + * example: 1.2e-307 . + */ + if (y <= (P-1)*Exp_msk1 && aadj > 1.) { + dval(aadj1) = (double)(int)(aadj + 0.5); + if (!dsign) + dval(aadj1) = -dval(aadj1); + } + adj = dval(aadj1) * ulp(dval(rv)); + dval(rv) += adj; +#endif /*Sudden_Underflow*/ +#endif /*Avoid_Underflow*/ + } + z = word0(rv) & Exp_mask; +#ifndef SET_INEXACT +#ifdef Avoid_Underflow + if (!scale) +#endif + if (y == z) { + /* Can we stop now? */ + L = (Long)aadj; + aadj -= L; + /* The tolerances below are conservative. */ + if (dsign || word1(rv) || word0(rv) & Bndry_mask) { + if (aadj < .4999999 || aadj > .5000001) + break; + } + else if (aadj < .4999999/FLT_RADIX) + break; + } +#endif +cont: + Bfree(bb); + Bfree(bd); + Bfree(bs); + Bfree(delta); + } +#ifdef SET_INEXACT + if (inexact) { + if (!oldinexact) { + word0(rv0) = Exp_1 + (70 << Exp_shift); + word1(rv0) = 0; + dval(rv0) += 1.; + } + } + else if (!oldinexact) + clear_inexact(); +#endif +#ifdef Avoid_Underflow + if (scale) { + word0(rv0) = Exp_1 - 2*P*Exp_msk1; + word1(rv0) = 0; + dval(rv) *= dval(rv0); +#ifndef NO_ERRNO + /* try to avoid the bug of testing an 8087 register value */ + if (word0(rv) == 0 && word1(rv) == 0) + errno = ERANGE; +#endif + } +#endif /* Avoid_Underflow */ +#ifdef SET_INEXACT + if (inexact && !(word0(rv) & Exp_mask)) { + /* set underflow bit */ + dval(rv0) = 1e-300; + dval(rv0) *= dval(rv0); + } +#endif +retfree: + Bfree(bb); + Bfree(bd); + Bfree(bs); + Bfree(bd0); + Bfree(delta); +ret: + if (se) + *se = (char *)s; + return sign ? -dval(rv) : dval(rv); +} + +NO_SANITIZE("unsigned-integer-overflow", static int quorem(Bigint *b, Bigint *S)); +static int +quorem(Bigint *b, Bigint *S) +{ + int n; + ULong *bx, *bxe, q, *sx, *sxe; +#ifdef ULLong + ULLong borrow, carry, y, ys; +#else + ULong borrow, carry, y, ys; +#ifdef Pack_32 + ULong si, z, zs; +#endif +#endif + + n = S->wds; +#ifdef DEBUG + /*debug*/ if (b->wds > n) + /*debug*/ Bug("oversize b in quorem"); +#endif + if (b->wds < n) + return 0; + sx = S->x; + sxe = sx + --n; + bx = b->x; + bxe = bx + n; + q = *bxe / (*sxe + 1); /* ensure q <= true quotient */ +#ifdef DEBUG + /*debug*/ if (q > 9) + /*debug*/ Bug("oversized quotient in quorem"); +#endif + if (q) { + borrow = 0; + carry = 0; + do { +#ifdef ULLong + ys = *sx++ * (ULLong)q + carry; + carry = ys >> 32; + y = *bx - (ys & FFFFFFFF) - borrow; + borrow = y >> 32 & (ULong)1; + *bx++ = (ULong)(y & FFFFFFFF); +#else +#ifdef Pack_32 + si = *sx++; + ys = (si & 0xffff) * q + carry; + zs = (si >> 16) * q + (ys >> 16); + carry = zs >> 16; + y = (*bx & 0xffff) - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*bx >> 16) - (zs & 0xffff) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(bx, z, y); +#else + ys = *sx++ * q + carry; + carry = ys >> 16; + y = *bx - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + *bx++ = y & 0xffff; +#endif +#endif + } while (sx <= sxe); + if (!*bxe) { + bx = b->x; + while (--bxe > bx && !*bxe) + --n; + b->wds = n; + } + } + if (cmp(b, S) >= 0) { + q++; + borrow = 0; + carry = 0; + bx = b->x; + sx = S->x; + do { +#ifdef ULLong + ys = *sx++ + carry; + carry = ys >> 32; + y = *bx - (ys & FFFFFFFF) - borrow; + borrow = y >> 32 & (ULong)1; + *bx++ = (ULong)(y & FFFFFFFF); +#else +#ifdef Pack_32 + si = *sx++; + ys = (si & 0xffff) + carry; + zs = (si >> 16) + (ys >> 16); + carry = zs >> 16; + y = (*bx & 0xffff) - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*bx >> 16) - (zs & 0xffff) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(bx, z, y); +#else + ys = *sx++ + carry; + carry = ys >> 16; + y = *bx - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + *bx++ = y & 0xffff; +#endif +#endif + } while (sx <= sxe); + bx = b->x; + bxe = bx + n; + if (!*bxe) { + while (--bxe > bx && !*bxe) + --n; + b->wds = n; + } + } + return q; +} + +#ifndef MULTIPLE_THREADS +static char *dtoa_result; +#endif + +#ifndef MULTIPLE_THREADS +static char * +rv_alloc(int i) +{ + return dtoa_result = MALLOC(i); +} +#else +#define rv_alloc(i) MALLOC(i) +#endif + +static char * +nrv_alloc(const char *s, char **rve, size_t n) +{ + char *rv, *t; + + t = rv = rv_alloc(n); + while ((*t = *s++) != 0) t++; + if (rve) + *rve = t; + return rv; +} + +#define rv_strdup(s, rve) nrv_alloc((s), (rve), strlen(s)+1) + +#ifndef MULTIPLE_THREADS +/* freedtoa(s) must be used to free values s returned by dtoa + * when MULTIPLE_THREADS is #defined. It should be used in all cases, + * but for consistency with earlier versions of dtoa, it is optional + * when MULTIPLE_THREADS is not defined. + */ + +static void +freedtoa(char *s) +{ + FREE(s); +} +#endif + +static const char INFSTR[] = "Infinity"; +static const char NANSTR[] = "NaN"; +static const char ZEROSTR[] = "0"; + +/* dtoa for IEEE arithmetic (dmg): convert double to ASCII string. + * + * Inspired by "How to Print Floating-Point Numbers Accurately" by + * Guy L. Steele, Jr. and Jon L. White [Proc. ACM SIGPLAN '90, pp. 112-126]. + * + * Modifications: + * 1. Rather than iterating, we use a simple numeric overestimate + * to determine k = floor(log10(d)). We scale relevant + * quantities using O(log2(k)) rather than O(k) multiplications. + * 2. For some modes > 2 (corresponding to ecvt and fcvt), we don't + * try to generate digits strictly left to right. Instead, we + * compute with fewer bits and propagate the carry if necessary + * when rounding the final digit up. This is often faster. + * 3. Under the assumption that input will be rounded nearest, + * mode 0 renders 1e23 as 1e23 rather than 9.999999999999999e22. + * That is, we allow equality in stopping tests when the + * round-nearest rule will give the same floating-point value + * as would satisfaction of the stopping test with strict + * inequality. + * 4. We remove common factors of powers of 2 from relevant + * quantities. + * 5. When converting floating-point integers less than 1e16, + * we use floating-point arithmetic rather than resorting + * to multiple-precision integers. + * 6. When asked to produce fewer than 15 digits, we first try + * to get by with floating-point arithmetic; we resort to + * multiple-precision integer arithmetic only if we cannot + * guarantee that the floating-point calculation has given + * the correctly rounded result. For k requested digits and + * "uniformly" distributed input, the probability is + * something like 10^(k-15) that we must resort to the Long + * calculation. + */ + +char * +dtoa(double d_, int mode, int ndigits, int *decpt, int *sign, char **rve) +{ + /* Arguments ndigits, decpt, sign are similar to those + of ecvt and fcvt; trailing zeros are suppressed from + the returned string. If not null, *rve is set to point + to the end of the return value. If d is +-Infinity or NaN, + then *decpt is set to 9999. + + mode: + 0 ==> shortest string that yields d when read in + and rounded to nearest. + 1 ==> like 0, but with Steele & White stopping rule; + e.g. with IEEE P754 arithmetic , mode 0 gives + 1e23 whereas mode 1 gives 9.999999999999999e22. + 2 ==> max(1,ndigits) significant digits. This gives a + return value similar to that of ecvt, except + that trailing zeros are suppressed. + 3 ==> through ndigits past the decimal point. This + gives a return value similar to that from fcvt, + except that trailing zeros are suppressed, and + ndigits can be negative. + 4,5 ==> similar to 2 and 3, respectively, but (in + round-nearest mode) with the tests of mode 0 to + possibly return a shorter string that rounds to d. + With IEEE arithmetic and compilation with + -DHonor_FLT_ROUNDS, modes 4 and 5 behave the same + as modes 2 and 3 when FLT_ROUNDS != 1. + 6-9 ==> Debugging modes similar to mode - 4: don't try + fast floating-point estimate (if applicable). + + Values of mode other than 0-9 are treated as mode 0. + + Sufficient space is allocated to the return value + to hold the suppressed trailing zeros. + */ + + int bbits, b2, b5, be, dig, i, ieps, ilim, ilim0, ilim1, + j, j1, k, k0, k_check, leftright, m2, m5, s2, s5, + spec_case, try_quick, half = 0; + Long L; +#ifndef Sudden_Underflow + int denorm; + ULong x; +#endif + Bigint *b, *b1, *delta, *mlo = 0, *mhi = 0, *S; + double ds; + double_u d, d2, eps; + char *s, *s0; +#ifdef Honor_FLT_ROUNDS + int rounding; +#endif +#ifdef SET_INEXACT + int inexact, oldinexact; +#endif + + dval(d) = d_; + +#ifndef MULTIPLE_THREADS + if (dtoa_result) { + freedtoa(dtoa_result); + dtoa_result = 0; + } +#endif + + if (word0(d) & Sign_bit) { + /* set sign for everything, including 0's and NaNs */ + *sign = 1; + word0(d) &= ~Sign_bit; /* clear sign bit */ + } + else + *sign = 0; + +#if defined(IEEE_Arith) + defined(VAX) +#ifdef IEEE_Arith + if ((word0(d) & Exp_mask) == Exp_mask) +#else + if (word0(d) == 0x8000) +#endif + { + /* Infinity or NaN */ + *decpt = 9999; +#ifdef IEEE_Arith + if (!word1(d) && !(word0(d) & 0xfffff)) + return rv_strdup(INFSTR, rve); +#endif + return rv_strdup(NANSTR, rve); + } +#endif +#ifdef IBM + dval(d) += 0; /* normalize */ +#endif + if (!dval(d)) { + *decpt = 1; + return rv_strdup(ZEROSTR, rve); + } + +#ifdef SET_INEXACT + try_quick = oldinexact = get_inexact(); + inexact = 1; +#endif +#ifdef Honor_FLT_ROUNDS + if ((rounding = Flt_Rounds) >= 2) { + if (*sign) + rounding = rounding == 2 ? 0 : 2; + else + if (rounding != 2) + rounding = 0; + } +#endif + + b = d2b(dval(d), &be, &bbits); +#ifdef Sudden_Underflow + i = (int)(word0(d) >> Exp_shift1 & (Exp_mask>>Exp_shift1)); +#else + if ((i = (int)(word0(d) >> Exp_shift1 & (Exp_mask>>Exp_shift1))) != 0) { +#endif + dval(d2) = dval(d); + word0(d2) &= Frac_mask1; + word0(d2) |= Exp_11; +#ifdef IBM + if (j = 11 - hi0bits(word0(d2) & Frac_mask)) + dval(d2) /= 1 << j; +#endif + + /* log(x) ~=~ log(1.5) + (x-1.5)/1.5 + * log10(x) = log(x) / log(10) + * ~=~ log(1.5)/log(10) + (x-1.5)/(1.5*log(10)) + * log10(d) = (i-Bias)*log(2)/log(10) + log10(d2) + * + * This suggests computing an approximation k to log10(d) by + * + * k = (i - Bias)*0.301029995663981 + * + ( (d2-1.5)*0.289529654602168 + 0.176091259055681 ); + * + * We want k to be too large rather than too small. + * The error in the first-order Taylor series approximation + * is in our favor, so we just round up the constant enough + * to compensate for any error in the multiplication of + * (i - Bias) by 0.301029995663981; since |i - Bias| <= 1077, + * and 1077 * 0.30103 * 2^-52 ~=~ 7.2e-14, + * adding 1e-13 to the constant term more than suffices. + * Hence we adjust the constant term to 0.1760912590558. + * (We could get a more accurate k by invoking log10, + * but this is probably not worthwhile.) + */ + + i -= Bias; +#ifdef IBM + i <<= 2; + i += j; +#endif +#ifndef Sudden_Underflow + denorm = 0; + } + else { + /* d is denormalized */ + + i = bbits + be + (Bias + (P-1) - 1); + x = i > 32 ? word0(d) << (64 - i) | word1(d) >> (i - 32) + : word1(d) << (32 - i); + dval(d2) = x; + word0(d2) -= 31*Exp_msk1; /* adjust exponent */ + i -= (Bias + (P-1) - 1) + 1; + denorm = 1; + } +#endif + ds = (dval(d2)-1.5)*0.289529654602168 + 0.1760912590558 + i*0.301029995663981; + k = (int)ds; + if (ds < 0. && ds != k) + k--; /* want k = floor(ds) */ + k_check = 1; + if (k >= 0 && k <= Ten_pmax) { + if (dval(d) < tens[k]) + k--; + k_check = 0; + } + j = bbits - i - 1; + if (j >= 0) { + b2 = 0; + s2 = j; + } + else { + b2 = -j; + s2 = 0; + } + if (k >= 0) { + b5 = 0; + s5 = k; + s2 += k; + } + else { + b2 -= k; + b5 = -k; + s5 = 0; + } + if (mode < 0 || mode > 9) + mode = 0; + +#ifndef SET_INEXACT +#ifdef Check_FLT_ROUNDS + try_quick = Rounding == 1; +#else + try_quick = 1; +#endif +#endif /*SET_INEXACT*/ + + if (mode > 5) { + mode -= 4; + try_quick = 0; + } + leftright = 1; + ilim = ilim1 = -1; + switch (mode) { + case 0: + case 1: + i = 18; + ndigits = 0; + break; + case 2: + leftright = 0; + /* no break */ + case 4: + if (ndigits <= 0) + ndigits = 1; + ilim = ilim1 = i = ndigits; + break; + case 3: + leftright = 0; + /* no break */ + case 5: + i = ndigits + k + 1; + ilim = i; + ilim1 = i - 1; + if (i <= 0) + i = 1; + } + s = s0 = rv_alloc(i+1); + +#ifdef Honor_FLT_ROUNDS + if (mode > 1 && rounding != 1) + leftright = 0; +#endif + + if (ilim >= 0 && ilim <= Quick_max && try_quick) { + + /* Try to get by with floating-point arithmetic. */ + + i = 0; + dval(d2) = dval(d); + k0 = k; + ilim0 = ilim; + ieps = 2; /* conservative */ + if (k > 0) { + ds = tens[k&0xf]; + j = k >> 4; + if (j & Bletch) { + /* prevent overflows */ + j &= Bletch - 1; + dval(d) /= bigtens[n_bigtens-1]; + ieps++; + } + for (; j; j >>= 1, i++) + if (j & 1) { + ieps++; + ds *= bigtens[i]; + } + dval(d) /= ds; + } + else if ((j1 = -k) != 0) { + dval(d) *= tens[j1 & 0xf]; + for (j = j1 >> 4; j; j >>= 1, i++) + if (j & 1) { + ieps++; + dval(d) *= bigtens[i]; + } + } + if (k_check && dval(d) < 1. && ilim > 0) { + if (ilim1 <= 0) + goto fast_failed; + ilim = ilim1; + k--; + dval(d) *= 10.; + ieps++; + } + dval(eps) = ieps*dval(d) + 7.; + word0(eps) -= (P-1)*Exp_msk1; + if (ilim == 0) { + S = mhi = 0; + dval(d) -= 5.; + if (dval(d) > dval(eps)) + goto one_digit; + if (dval(d) < -dval(eps)) + goto no_digits; + goto fast_failed; + } +#ifndef No_leftright + if (leftright) { + /* Use Steele & White method of only + * generating digits needed. + */ + dval(eps) = 0.5/tens[ilim-1] - dval(eps); + for (i = 0;;) { + L = (int)dval(d); + dval(d) -= L; + *s++ = '0' + (int)L; + if (dval(d) < dval(eps)) + goto ret1; + if (1. - dval(d) < dval(eps)) + goto bump_up; + if (++i >= ilim) + break; + dval(eps) *= 10.; + dval(d) *= 10.; + } + } + else { +#endif + /* Generate ilim digits, then fix them up. */ + dval(eps) *= tens[ilim-1]; + for (i = 1;; i++, dval(d) *= 10.) { + L = (Long)(dval(d)); + if (!(dval(d) -= L)) + ilim = i; + *s++ = '0' + (int)L; + if (i == ilim) { + if (dval(d) > 0.5 + dval(eps)) + goto bump_up; + else if (dval(d) < 0.5 - dval(eps)) { + while (*--s == '0') ; + s++; + goto ret1; + } + half = 1; + if ((*(s-1) - '0') & 1) { + goto bump_up; + } + break; + } + } +#ifndef No_leftright + } +#endif +fast_failed: + s = s0; + dval(d) = dval(d2); + k = k0; + ilim = ilim0; + } + + /* Do we have a "small" integer? */ + + if (be >= 0 && k <= Int_max) { + /* Yes. */ + ds = tens[k]; + if (ndigits < 0 && ilim <= 0) { + S = mhi = 0; + if (ilim < 0 || dval(d) <= 5*ds) + goto no_digits; + goto one_digit; + } + for (i = 1;; i++, dval(d) *= 10.) { + L = (Long)(dval(d) / ds); + dval(d) -= L*ds; +#ifdef Check_FLT_ROUNDS + /* If FLT_ROUNDS == 2, L will usually be high by 1 */ + if (dval(d) < 0) { + L--; + dval(d) += ds; + } +#endif + *s++ = '0' + (int)L; + if (!dval(d)) { +#ifdef SET_INEXACT + inexact = 0; +#endif + break; + } + if (i == ilim) { +#ifdef Honor_FLT_ROUNDS + if (mode > 1) + switch (rounding) { + case 0: goto ret1; + case 2: goto bump_up; + } +#endif + dval(d) += dval(d); + if (dval(d) > ds || (dval(d) == ds && (L & 1))) { +bump_up: + while (*--s == '9') + if (s == s0) { + k++; + *s = '0'; + break; + } + ++*s++; + } + break; + } + } + goto ret1; + } + + m2 = b2; + m5 = b5; + if (leftright) { + i = +#ifndef Sudden_Underflow + denorm ? be + (Bias + (P-1) - 1 + 1) : +#endif +#ifdef IBM + 1 + 4*P - 3 - bbits + ((bbits + be - 1) & 3); +#else + 1 + P - bbits; +#endif + b2 += i; + s2 += i; + mhi = i2b(1); + } + if (m2 > 0 && s2 > 0) { + i = m2 < s2 ? m2 : s2; + b2 -= i; + m2 -= i; + s2 -= i; + } + if (b5 > 0) { + if (leftright) { + if (m5 > 0) { + mhi = pow5mult(mhi, m5); + b1 = mult(mhi, b); + Bfree(b); + b = b1; + } + if ((j = b5 - m5) != 0) + b = pow5mult(b, j); + } + else + b = pow5mult(b, b5); + } + S = i2b(1); + if (s5 > 0) + S = pow5mult(S, s5); + + /* Check for special case that d is a normalized power of 2. */ + + spec_case = 0; + if ((mode < 2 || leftright) +#ifdef Honor_FLT_ROUNDS + && rounding == 1 +#endif + ) { + if (!word1(d) && !(word0(d) & Bndry_mask) +#ifndef Sudden_Underflow + && word0(d) & (Exp_mask & ~Exp_msk1) +#endif + ) { + /* The special case */ + b2 += Log2P; + s2 += Log2P; + spec_case = 1; + } + } + + /* Arrange for convenient computation of quotients: + * shift left if necessary so divisor has 4 leading 0 bits. + * + * Perhaps we should just compute leading 28 bits of S once + * and for all and pass them and a shift to quorem, so it + * can do shifts and ors to compute the numerator for q. + */ +#ifdef Pack_32 + if ((i = ((s5 ? 32 - hi0bits(S->x[S->wds-1]) : 1) + s2) & 0x1f) != 0) + i = 32 - i; +#else + if ((i = ((s5 ? 32 - hi0bits(S->x[S->wds-1]) : 1) + s2) & 0xf) != 0) + i = 16 - i; +#endif + if (i > 4) { + i -= 4; + b2 += i; + m2 += i; + s2 += i; + } + else if (i < 4) { + i += 28; + b2 += i; + m2 += i; + s2 += i; + } + if (b2 > 0) + b = lshift(b, b2); + if (s2 > 0) + S = lshift(S, s2); + if (k_check) { + if (cmp(b,S) < 0) { + k--; + b = multadd(b, 10, 0); /* we botched the k estimate */ + if (leftright) + mhi = multadd(mhi, 10, 0); + ilim = ilim1; + } + } + if (ilim <= 0 && (mode == 3 || mode == 5)) { + if (ilim < 0 || cmp(b,S = multadd(S,5,0)) <= 0) { + /* no digits, fcvt style */ +no_digits: + k = -1 - ndigits; + goto ret; + } +one_digit: + *s++ = '1'; + k++; + goto ret; + } + if (leftright) { + if (m2 > 0) + mhi = lshift(mhi, m2); + + /* Compute mlo -- check for special case + * that d is a normalized power of 2. + */ + + mlo = mhi; + if (spec_case) { + mhi = Balloc(mhi->k); + Bcopy(mhi, mlo); + mhi = lshift(mhi, Log2P); + } + + for (i = 1;;i++) { + dig = quorem(b,S) + '0'; + /* Do we yet have the shortest decimal string + * that will round to d? + */ + j = cmp(b, mlo); + delta = diff(S, mhi); + j1 = delta->sign ? 1 : cmp(b, delta); + Bfree(delta); +#ifndef ROUND_BIASED + if (j1 == 0 && mode != 1 && !(word1(d) & 1) +#ifdef Honor_FLT_ROUNDS + && rounding >= 1 +#endif + ) { + if (dig == '9') + goto round_9_up; + if (j > 0) + dig++; +#ifdef SET_INEXACT + else if (!b->x[0] && b->wds <= 1) + inexact = 0; +#endif + *s++ = dig; + goto ret; + } +#endif + if (j < 0 || (j == 0 && mode != 1 +#ifndef ROUND_BIASED + && !(word1(d) & 1) +#endif + )) { + if (!b->x[0] && b->wds <= 1) { +#ifdef SET_INEXACT + inexact = 0; +#endif + goto accept_dig; + } +#ifdef Honor_FLT_ROUNDS + if (mode > 1) + switch (rounding) { + case 0: goto accept_dig; + case 2: goto keep_dig; + } +#endif /*Honor_FLT_ROUNDS*/ + if (j1 > 0) { + b = lshift(b, 1); + j1 = cmp(b, S); + if ((j1 > 0 || (j1 == 0 && (dig & 1))) && dig++ == '9') + goto round_9_up; + } +accept_dig: + *s++ = dig; + goto ret; + } + if (j1 > 0) { +#ifdef Honor_FLT_ROUNDS + if (!rounding) + goto accept_dig; +#endif + if (dig == '9') { /* possible if i == 1 */ +round_9_up: + *s++ = '9'; + goto roundoff; + } + *s++ = dig + 1; + goto ret; + } +#ifdef Honor_FLT_ROUNDS +keep_dig: +#endif + *s++ = dig; + if (i == ilim) + break; + b = multadd(b, 10, 0); + if (mlo == mhi) + mlo = mhi = multadd(mhi, 10, 0); + else { + mlo = multadd(mlo, 10, 0); + mhi = multadd(mhi, 10, 0); + } + } + } + else + for (i = 1;; i++) { + *s++ = dig = quorem(b,S) + '0'; + if (!b->x[0] && b->wds <= 1) { +#ifdef SET_INEXACT + inexact = 0; +#endif + goto ret; + } + if (i >= ilim) + break; + b = multadd(b, 10, 0); + } + + /* Round off last digit */ + +#ifdef Honor_FLT_ROUNDS + switch (rounding) { + case 0: goto trimzeros; + case 2: goto roundoff; + } +#endif + b = lshift(b, 1); + j = cmp(b, S); + if (j > 0 || (j == 0 && (dig & 1))) { + roundoff: + while (*--s == '9') + if (s == s0) { + k++; + *s++ = '1'; + goto ret; + } + if (!half || (*s - '0') & 1) + ++*s; + } + else { + while (*--s == '0') ; + } + s++; +ret: + Bfree(S); + if (mhi) { + if (mlo && mlo != mhi) + Bfree(mlo); + Bfree(mhi); + } +ret1: +#ifdef SET_INEXACT + if (inexact) { + if (!oldinexact) { + word0(d) = Exp_1 + (70 << Exp_shift); + word1(d) = 0; + dval(d) += 1.; + } + } + else if (!oldinexact) + clear_inexact(); +#endif + Bfree(b); + *s = 0; + *decpt = k + 1; + if (rve) + *rve = s; + return s0; +} + +/*- + * Copyright (c) 2004-2008 David Schultz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define DBL_MANH_SIZE 20 +#define DBL_MANL_SIZE 32 +#define DBL_ADJ (DBL_MAX_EXP - 2) +#define SIGFIGS ((DBL_MANT_DIG + 3) / 4 + 1) +#define dexp_get(u) ((int)(word0(u) >> Exp_shift) & ~Exp_msk1) +#define dexp_set(u,v) (word0(u) = (((int)(word0(u)) & ~Exp_mask) | ((v) << Exp_shift))) +#define dmanh_get(u) ((uint32_t)(word0(u) & Frac_mask)) +#define dmanl_get(u) ((uint32_t)word1(u)) + + +/* + * This procedure converts a double-precision number in IEEE format + * into a string of hexadecimal digits and an exponent of 2. Its + * behavior is bug-for-bug compatible with dtoa() in mode 2, with the + * following exceptions: + * + * - An ndigits < 0 causes it to use as many digits as necessary to + * represent the number exactly. + * - The additional xdigs argument should point to either the string + * "0123456789ABCDEF" or the string "0123456789abcdef", depending on + * which case is desired. + * - This routine does not repeat dtoa's mistake of setting decpt + * to 9999 in the case of an infinity or NaN. INT_MAX is used + * for this purpose instead. + * + * Note that the C99 standard does not specify what the leading digit + * should be for non-zero numbers. For instance, 0x1.3p3 is the same + * as 0x2.6p2 is the same as 0x4.cp3. This implementation always makes + * the leading digit a 1. This ensures that the exponent printed is the + * actual base-2 exponent, i.e., ilogb(d). + * + * Inputs: d, xdigs, ndigits + * Outputs: decpt, sign, rve + */ +char * +hdtoa(double d, const char *xdigs, int ndigits, int *decpt, int *sign, char **rve) +{ + U u; + char *s, *s0; + int bufsize; + uint32_t manh, manl; + + u.d = d; + if (word0(u) & Sign_bit) { + /* set sign for everything, including 0's and NaNs */ + *sign = 1; + word0(u) &= ~Sign_bit; /* clear sign bit */ + } + else + *sign = 0; + + if (isinf(d)) { /* FP_INFINITE */ + *decpt = INT_MAX; + return rv_strdup(INFSTR, rve); + } + else if (isnan(d)) { /* FP_NAN */ + *decpt = INT_MAX; + return rv_strdup(NANSTR, rve); + } + else if (d == 0.0) { /* FP_ZERO */ + *decpt = 1; + return rv_strdup(ZEROSTR, rve); + } + else if (dexp_get(u)) { /* FP_NORMAL */ + *decpt = dexp_get(u) - DBL_ADJ; + } + else { /* FP_SUBNORMAL */ + u.d *= 5.363123171977039e+154 /* 0x1p514 */; + *decpt = dexp_get(u) - (514 + DBL_ADJ); + } + + if (ndigits == 0) /* dtoa() compatibility */ + ndigits = 1; + + /* + * If ndigits < 0, we are expected to auto-size, so we allocate + * enough space for all the digits. + */ + bufsize = (ndigits > 0) ? ndigits : SIGFIGS; + s0 = rv_alloc(bufsize+1); + + /* Round to the desired number of digits. */ + if (SIGFIGS > ndigits && ndigits > 0) { + float redux = 1.0f; + int offset = 4 * ndigits + DBL_MAX_EXP - 4 - DBL_MANT_DIG; + dexp_set(u, offset); + u.d += redux; + u.d -= redux; + *decpt += dexp_get(u) - offset; + } + + manh = dmanh_get(u); + manl = dmanl_get(u); + *s0 = '1'; + for (s = s0 + 1; s < s0 + bufsize; s++) { + *s = xdigs[(manh >> (DBL_MANH_SIZE - 4)) & 0xf]; + manh = (manh << 4) | (manl >> (DBL_MANL_SIZE - 4)); + manl <<= 4; + } + + /* If ndigits < 0, we are expected to auto-size the precision. */ + if (ndigits < 0) { + for (ndigits = SIGFIGS; s0[ndigits - 1] == '0'; ndigits--) + ; + } + + s = s0 + ndigits; + *s = '\0'; + if (rve != NULL) + *rve = s; + return (s0); +} + +#ifdef __cplusplus +#if 0 +{ /* satisfy cc-mode */ +#endif +} +#endif diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/static_assert.h b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/static_assert.h new file mode 100644 index 00000000..9295729b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/ext/bigdecimal/static_assert.h @@ -0,0 +1,54 @@ +#ifndef BIGDECIMAL_STATIC_ASSERT_H +#define BIGDECIMAL_STATIC_ASSERT_H + +#include "feature.h" + +#ifdef HAVE_RUBY_INTERNAL_STATIC_ASSERT_H +# include +#endif + +#ifdef RBIMPL_STATIC_ASSERT +# define STATIC_ASSERT RBIMPL_STATIC_ASSERT +#endif + +#ifndef STATIC_ASSERT +# /* The following section is copied from CRuby's static_assert.h */ + +# if defined(__cplusplus) && defined(__cpp_static_assert) +# /* https://isocpp.org/std/standing-documents/sd-6-sg10-feature-test-recommendations */ +# define BIGDECIMAL_STATIC_ASSERT0 static_assert + +# elif defined(__cplusplus) && defined(_MSC_VER) && _MSC_VER >= 1600 +# define BIGDECIMAL_STATIC_ASSERT0 static_assert + +# elif defined(__INTEL_CXX11_MODE__) +# define BIGDECIMAL_STATIC_ASSERT0 static_assert + +# elif defined(__cplusplus) && __cplusplus >= 201103L +# define BIGDECIMAL_STATIC_ASSERT0 static_assert + +# elif defined(__cplusplus) && __has_extension(cxx_static_assert) +# define BIGDECIMAL_STATIC_ASSERT0 __extension__ static_assert + +# elif defined(__STDC_VERSION__) && __has_extension(c_static_assert) +# define BIGDECIMAL_STATIC_ASSERT0 __extension__ _Static_assert + +# elif defined(__STDC_VERSION__) && defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# define BIGDECIMAL_STATIC_ASSERT0 __extension__ _Static_assert +#endif + +# if defined(__DOXYGEN__) +# define STATIC_ASSERT static_assert + +# elif defined(BIGDECIMAL_STATIC_ASSERT0) +# define STATIC_ASSERT(name, expr) \ + BIGDECIMAL_STATIC_ASSERT0(expr, #name ": " #expr) + +# else +# define STATIC_ASSERT(name, expr) \ + typedef int static_assert_ ## name ## _check[1 - 2 * !(expr)] +# endif +#endif /* STATIC_ASSERT */ + + +#endif /* BIGDECIMAL_STATIC_ASSERT_H */ diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal.rb b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal.rb new file mode 100644 index 00000000..82b3e1b7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal.rb @@ -0,0 +1,5 @@ +if RUBY_ENGINE == 'jruby' + JRuby::Util.load_ext("org.jruby.ext.bigdecimal.BigDecimalLibrary") +else + require 'bigdecimal.so' +end diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal.so b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal.so new file mode 100755 index 00000000..ef74012e Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal.so differ diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal/jacobian.rb b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal/jacobian.rb new file mode 100644 index 00000000..4448024c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal/jacobian.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: false + +require 'bigdecimal' + +# require 'bigdecimal/jacobian' +# +# Provides methods to compute the Jacobian matrix of a set of equations at a +# point x. In the methods below: +# +# f is an Object which is used to compute the Jacobian matrix of the equations. +# It must provide the following methods: +# +# f.values(x):: returns the values of all functions at x +# +# f.zero:: returns 0.0 +# f.one:: returns 1.0 +# f.two:: returns 2.0 +# f.ten:: returns 10.0 +# +# f.eps:: returns the convergence criterion (epsilon value) used to determine whether two values are considered equal. If |a-b| < epsilon, the two values are considered equal. +# +# x is the point at which to compute the Jacobian. +# +# fx is f.values(x). +# +module Jacobian + module_function + + # Determines the equality of two numbers by comparing to zero, or using the epsilon value + def isEqual(a,b,zero=0.0,e=1.0e-8) + aa = a.abs + bb = b.abs + if aa == zero && bb == zero then + true + else + if ((a-b)/(aa+bb)).abs < e then + true + else + false + end + end + end + + + # Computes the derivative of +f[i]+ at +x[i]+. + # +fx+ is the value of +f+ at +x+. + def dfdxi(f,fx,x,i) + nRetry = 0 + n = x.size + xSave = x[i] + ok = 0 + ratio = f.ten*f.ten*f.ten + dx = x[i].abs/ratio + dx = fx[i].abs/ratio if isEqual(dx,f.zero,f.zero,f.eps) + dx = f.one/f.ten if isEqual(dx,f.zero,f.zero,f.eps) + until ok>0 do + deriv = [] + nRetry += 1 + if nRetry > 100 + raise "Singular Jacobian matrix. No change at x[" + i.to_s + "]" + end + dx = dx*f.two + x[i] += dx + fxNew = f.values(x) + for j in 0...n do + if !isEqual(fxNew[j],fx[j],f.zero,f.eps) then + ok += 1 + deriv <<= (fxNew[j]-fx[j])/dx + else + deriv <<= f.zero + end + end + x[i] = xSave + end + deriv + end + + # Computes the Jacobian of +f+ at +x+. +fx+ is the value of +f+ at +x+. + def jacobian(f,fx,x) + n = x.size + dfdx = Array.new(n*n) + for i in 0...n do + df = dfdxi(f,fx,x,i) + for j in 0...n do + dfdx[j*n+i] = df[j] + end + end + dfdx + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal/ludcmp.rb b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal/ludcmp.rb new file mode 100644 index 00000000..dd265e48 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal/ludcmp.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: false +require 'bigdecimal' + +# +# Solves a*x = b for x, using LU decomposition. +# +module LUSolve + module_function + + # Performs LU decomposition of the n by n matrix a. + def ludecomp(a,n,zero=0,one=1) + prec = BigDecimal.limit(nil) + ps = [] + scales = [] + for i in 0...n do # pick up largest(abs. val.) element in each row. + ps <<= i + nrmrow = zero + ixn = i*n + for j in 0...n do + biggst = a[ixn+j].abs + nrmrow = biggst if biggst>nrmrow + end + if nrmrow>zero then + scales <<= one.div(nrmrow,prec) + else + raise "Singular matrix" + end + end + n1 = n - 1 + for k in 0...n1 do # Gaussian elimination with partial pivoting. + biggst = zero; + for i in k...n do + size = a[ps[i]*n+k].abs*scales[ps[i]] + if size>biggst then + biggst = size + pividx = i + end + end + raise "Singular matrix" if biggst<=zero + if pividx!=k then + j = ps[k] + ps[k] = ps[pividx] + ps[pividx] = j + end + pivot = a[ps[k]*n+k] + for i in (k+1)...n do + psin = ps[i]*n + a[psin+k] = mult = a[psin+k].div(pivot,prec) + if mult!=zero then + pskn = ps[k]*n + for j in (k+1)...n do + a[psin+j] -= mult.mult(a[pskn+j],prec) + end + end + end + end + raise "Singular matrix" if a[ps[n1]*n+n1] == zero + ps + end + + # Solves a*x = b for x, using LU decomposition. + # + # a is a matrix, b is a constant vector, x is the solution vector. + # + # ps is the pivot, a vector which indicates the permutation of rows performed + # during LU decomposition. + def lusolve(a,b,ps,zero=0.0) + prec = BigDecimal.limit(nil) + n = ps.size + x = [] + for i in 0...n do + dot = zero + psin = ps[i]*n + for j in 0...i do + dot = a[psin+j].mult(x[j],prec) + dot + end + x <<= b[ps[i]] - dot + end + (n-1).downto(0) do |i| + dot = zero + psin = ps[i]*n + for j in (i+1)...n do + dot = a[psin+j].mult(x[j],prec) + dot + end + x[i] = (x[i]-dot).div(a[psin+i],prec) + end + x + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal/math.rb b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal/math.rb new file mode 100644 index 00000000..3ab84d84 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal/math.rb @@ -0,0 +1,231 @@ +# frozen_string_literal: false +require 'bigdecimal' + +# +#-- +# Contents: +# sqrt(x, prec) +# sin (x, prec) +# cos (x, prec) +# atan(x, prec) +# PI (prec) +# E (prec) == exp(1.0,prec) +# +# where: +# x ... BigDecimal number to be computed. +# prec ... Number of digits to be obtained. +#++ +# +# Provides mathematical functions. +# +# Example: +# +# require "bigdecimal/math" +# +# include BigMath +# +# a = BigDecimal((PI(100)/2).to_s) +# puts sin(a,100) # => 0.99999999999999999999......e0 +# +module BigMath + module_function + + # call-seq: + # sqrt(decimal, numeric) -> BigDecimal + # + # Computes the square root of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # BigMath.sqrt(BigDecimal('2'), 16).to_s + # #=> "0.1414213562373095048801688724e1" + # + def sqrt(x, prec) + x.sqrt(prec) + end + + # call-seq: + # sin(decimal, numeric) -> BigDecimal + # + # Computes the sine of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is Infinity or NaN, returns NaN. + # + # BigMath.sin(BigMath.PI(5)/4, 5).to_s + # #=> "0.70710678118654752440082036563292800375e0" + # + def sin(x, prec) + raise ArgumentError, "Zero or negative precision for sin" if prec <= 0 + return BigDecimal("NaN") if x.infinite? || x.nan? + n = prec + BigDecimal.double_fig + one = BigDecimal("1") + two = BigDecimal("2") + x = -x if neg = x < 0 + if x > (twopi = two * BigMath.PI(prec)) + if x > 30 + x %= twopi + else + x -= twopi while x > twopi + end + end + x1 = x + x2 = x.mult(x,n) + sign = 1 + y = x + d = y + i = one + z = one + while d.nonzero? && ((m = n - (y.exponent - d.exponent).abs) > 0) + m = BigDecimal.double_fig if m < BigDecimal.double_fig + sign = -sign + x1 = x2.mult(x1,n) + i += two + z *= (i-one) * i + d = sign * x1.div(z,m) + y += d + end + neg ? -y : y + end + + # call-seq: + # cos(decimal, numeric) -> BigDecimal + # + # Computes the cosine of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is Infinity or NaN, returns NaN. + # + # BigMath.cos(BigMath.PI(4), 16).to_s + # #=> "-0.999999999999999999999999999999856613163740061349e0" + # + def cos(x, prec) + raise ArgumentError, "Zero or negative precision for cos" if prec <= 0 + return BigDecimal("NaN") if x.infinite? || x.nan? + n = prec + BigDecimal.double_fig + one = BigDecimal("1") + two = BigDecimal("2") + x = -x if x < 0 + if x > (twopi = two * BigMath.PI(prec)) + if x > 30 + x %= twopi + else + x -= twopi while x > twopi + end + end + x1 = one + x2 = x.mult(x,n) + sign = 1 + y = one + d = y + i = BigDecimal("0") + z = one + while d.nonzero? && ((m = n - (y.exponent - d.exponent).abs) > 0) + m = BigDecimal.double_fig if m < BigDecimal.double_fig + sign = -sign + x1 = x2.mult(x1,n) + i += two + z *= (i-one) * i + d = sign * x1.div(z,m) + y += d + end + y + end + + # call-seq: + # atan(decimal, numeric) -> BigDecimal + # + # Computes the arctangent of +decimal+ to the specified number of digits of + # precision, +numeric+. + # + # If +decimal+ is NaN, returns NaN. + # + # BigMath.atan(BigDecimal('-1'), 16).to_s + # #=> "-0.785398163397448309615660845819878471907514682065e0" + # + def atan(x, prec) + raise ArgumentError, "Zero or negative precision for atan" if prec <= 0 + return BigDecimal("NaN") if x.nan? + pi = PI(prec) + x = -x if neg = x < 0 + return pi.div(neg ? -2 : 2, prec) if x.infinite? + return pi / (neg ? -4 : 4) if x.round(prec) == 1 + x = BigDecimal("1").div(x, prec) if inv = x > 1 + x = (-1 + sqrt(1 + x**2, prec))/x if dbl = x > 0.5 + n = prec + BigDecimal.double_fig + y = x + d = y + t = x + r = BigDecimal("3") + x2 = x.mult(x,n) + while d.nonzero? && ((m = n - (y.exponent - d.exponent).abs) > 0) + m = BigDecimal.double_fig if m < BigDecimal.double_fig + t = -t.mult(x2,n) + d = t.div(r,m) + y += d + r += 2 + end + y *= 2 if dbl + y = pi / 2 - y if inv + y = -y if neg + y + end + + # call-seq: + # PI(numeric) -> BigDecimal + # + # Computes the value of pi to the specified number of digits of precision, + # +numeric+. + # + # BigMath.PI(10).to_s + # #=> "0.3141592653589793238462643388813853786957412e1" + # + def PI(prec) + raise ArgumentError, "Zero or negative precision for PI" if prec <= 0 + n = prec + BigDecimal.double_fig + zero = BigDecimal("0") + one = BigDecimal("1") + two = BigDecimal("2") + + m25 = BigDecimal("-0.04") + m57121 = BigDecimal("-57121") + + pi = zero + + d = one + k = one + t = BigDecimal("-80") + while d.nonzero? && ((m = n - (pi.exponent - d.exponent).abs) > 0) + m = BigDecimal.double_fig if m < BigDecimal.double_fig + t = t*m25 + d = t.div(k,m) + k = k+two + pi = pi + d + end + + d = one + k = one + t = BigDecimal("956") + while d.nonzero? && ((m = n - (pi.exponent - d.exponent).abs) > 0) + m = BigDecimal.double_fig if m < BigDecimal.double_fig + t = t.div(m57121,n) + d = t.div(k,m) + pi = pi + d + k = k+two + end + pi + end + + # call-seq: + # E(numeric) -> BigDecimal + # + # Computes e (the base of natural logarithms) to the specified number of + # digits of precision, +numeric+. + # + # BigMath.E(10).to_s + # #=> "0.271828182845904523536028752390026306410273e1" + # + def E(prec) + raise ArgumentError, "Zero or negative precision for E" if prec <= 0 + BigMath.exp(1, prec) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal/newton.rb b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal/newton.rb new file mode 100644 index 00000000..85bacb7f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal/newton.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: false +require "bigdecimal/ludcmp" +require "bigdecimal/jacobian" + +# +# newton.rb +# +# Solves the nonlinear algebraic equation system f = 0 by Newton's method. +# This program is not dependent on BigDecimal. +# +# To call: +# n = nlsolve(f,x) +# where n is the number of iterations required, +# x is the initial value vector +# f is an Object which is used to compute the values of the equations to be solved. +# It must provide the following methods: +# +# f.values(x):: returns the values of all functions at x +# +# f.zero:: returns 0.0 +# f.one:: returns 1.0 +# f.two:: returns 2.0 +# f.ten:: returns 10.0 +# +# f.eps:: returns the convergence criterion (epsilon value) used to determine whether two values are considered equal. If |a-b| < epsilon, the two values are considered equal. +# +# On exit, x is the solution vector. +# +module Newton + include LUSolve + include Jacobian + module_function + + def norm(fv,zero=0.0) # :nodoc: + s = zero + n = fv.size + for i in 0...n do + s += fv[i]*fv[i] + end + s + end + + # See also Newton + def nlsolve(f,x) + nRetry = 0 + n = x.size + + f0 = f.values(x) + zero = f.zero + one = f.one + two = f.two + p5 = one/two + d = norm(f0,zero) + minfact = f.ten*f.ten*f.ten + minfact = one/minfact + e = f.eps + while d >= e do + nRetry += 1 + # Not yet converged. => Compute Jacobian matrix + dfdx = jacobian(f,f0,x) + # Solve dfdx*dx = -f0 to estimate dx + dx = lusolve(dfdx,f0,ludecomp(dfdx,n,zero,one),zero) + fact = two + xs = x.dup + begin + fact *= p5 + if fact < minfact then + raise "Failed to reduce function values." + end + for i in 0...n do + x[i] = xs[i] - dx[i]*fact + end + f0 = f.values(x) + dn = norm(f0,zero) + end while(dn>=d) + d = dn + end + nRetry + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal/util.rb b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal/util.rb new file mode 100644 index 00000000..cb514435 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/lib/bigdecimal/util.rb @@ -0,0 +1,185 @@ +# frozen_string_literal: false +# +#-- +# bigdecimal/util extends various native classes to provide the #to_d method, +# and provides BigDecimal#to_d and BigDecimal#to_digits. +#++ + +require 'bigdecimal' + +class Integer < Numeric + # call-seq: + # int.to_d -> bigdecimal + # + # Returns the value of +int+ as a BigDecimal. + # + # require 'bigdecimal' + # require 'bigdecimal/util' + # + # 42.to_d # => 0.42e2 + # + # See also Kernel.BigDecimal. + # + def to_d + BigDecimal(self) + end +end + + +class Float < Numeric + # call-seq: + # float.to_d -> bigdecimal + # float.to_d(precision) -> bigdecimal + # + # Returns the value of +float+ as a BigDecimal. + # The +precision+ parameter is used to determine the number of + # significant digits for the result. When +precision+ is set to +0+, + # the number of digits to represent the float being converted is determined + # automatically. + # The default +precision+ is +0+. + # + # require 'bigdecimal' + # require 'bigdecimal/util' + # + # 0.5.to_d # => 0.5e0 + # 1.234.to_d # => 0.1234e1 + # 1.234.to_d(2) # => 0.12e1 + # + # See also Kernel.BigDecimal. + # + def to_d(precision=0) + BigDecimal(self, precision) + end +end + + +class String + # call-seq: + # str.to_d -> bigdecimal + # + # Returns the result of interpreting leading characters in +str+ + # as a BigDecimal. + # + # require 'bigdecimal' + # require 'bigdecimal/util' + # + # "0.5".to_d # => 0.5e0 + # "123.45e1".to_d # => 0.12345e4 + # "45.67 degrees".to_d # => 0.4567e2 + # + # See also Kernel.BigDecimal. + # + def to_d + BigDecimal.interpret_loosely(self) + end +end + + +class BigDecimal < Numeric + # call-seq: + # a.to_digits -> string + # + # Converts a BigDecimal to a String of the form "nnnnnn.mmm". + # This method is deprecated; use BigDecimal#to_s("F") instead. + # + # require 'bigdecimal/util' + # + # d = BigDecimal("3.14") + # d.to_digits # => "3.14" + # + def to_digits + if self.nan? || self.infinite? || self.zero? + self.to_s + else + i = self.to_i.to_s + _,f,_,z = self.frac.split + i + "." + ("0"*(-z)) + f + end + end + + # call-seq: + # a.to_d -> bigdecimal + # + # Returns self. + # + # require 'bigdecimal/util' + # + # d = BigDecimal("3.14") + # d.to_d # => 0.314e1 + # + def to_d + self + end +end + + +class Rational < Numeric + # call-seq: + # rat.to_d(precision) -> bigdecimal + # + # Returns the value as a BigDecimal. + # + # The required +precision+ parameter is used to determine the number of + # significant digits for the result. + # + # require 'bigdecimal' + # require 'bigdecimal/util' + # + # Rational(22, 7).to_d(3) # => 0.314e1 + # + # See also Kernel.BigDecimal. + # + def to_d(precision) + BigDecimal(self, precision) + end +end + + +class Complex < Numeric + # call-seq: + # cmp.to_d -> bigdecimal + # cmp.to_d(precision) -> bigdecimal + # + # Returns the value as a BigDecimal. + # + # The +precision+ parameter is required for a rational complex number. + # This parameter is used to determine the number of significant digits + # for the result. + # + # require 'bigdecimal' + # require 'bigdecimal/util' + # + # Complex(0.1234567, 0).to_d(4) # => 0.1235e0 + # Complex(Rational(22, 7), 0).to_d(3) # => 0.314e1 + # + # See also Kernel.BigDecimal. + # + def to_d(*args) + BigDecimal(self) unless self.imag.zero? # to raise error + + if args.length == 0 + case self.real + when Rational + BigDecimal(self.real) # to raise error + end + end + self.real.to_d(*args) + end +end + + +class NilClass + # call-seq: + # nil.to_d -> bigdecimal + # + # Returns nil represented as a BigDecimal. + # + # require 'bigdecimal' + # require 'bigdecimal/util' + # + # nil.to_d # => 0.0 + # + def to_d + BigDecimal(0) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/sample/linear.rb b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/sample/linear.rb new file mode 100644 index 00000000..516c2473 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/sample/linear.rb @@ -0,0 +1,74 @@ +#!/usr/local/bin/ruby +# frozen_string_literal: false + +# +# linear.rb +# +# Solves linear equation system(A*x = b) by LU decomposition method. +# where A is a coefficient matrix,x is an answer vector,b is a constant vector. +# +# USAGE: +# ruby linear.rb [input file solved] +# + +# :stopdoc: +require "bigdecimal" +require "bigdecimal/ludcmp" + +# +# NOTE: +# Change following BigDecimal.limit() if needed. +BigDecimal.limit(100) +# + +include LUSolve +def rd_order(na) + printf("Number of equations ?") if(na <= 0) + n = ARGF.gets().to_i +end + +na = ARGV.size +zero = BigDecimal("0.0") +one = BigDecimal("1.0") + +while (n=rd_order(na))>0 + a = [] + as= [] + b = [] + if na <= 0 + # Read data from console. + printf("\nEnter coefficient matrix element A[i,j]\n") + for i in 0...n do + for j in 0...n do + printf("A[%d,%d]? ",i,j); s = ARGF.gets + a << BigDecimal(s) + as << BigDecimal(s) + end + printf("Contatant vector element b[%d] ? ",i) + b << BigDecimal(ARGF.gets) + end + else + # Read data from specified file. + printf("Coefficient matrix and constant vector.\n") + for i in 0...n do + s = ARGF.gets + printf("%d) %s",i,s) + s = s.split + for j in 0...n do + a << BigDecimal(s[j]) + as << BigDecimal(s[j]) + end + b << BigDecimal(s[n]) + end + end + x = lusolve(a,b,ludecomp(a,n,zero,one),zero) + printf("Answer(x[i] & (A*x-b)[i]) follows\n") + for i in 0...n do + printf("x[%d]=%s ",i,x[i].to_s) + s = zero + for j in 0...n do + s = s + as[i*n+j]*x[j] + end + printf(" & %s\n",(s-b[i]).to_s) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/sample/nlsolve.rb b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/sample/nlsolve.rb new file mode 100644 index 00000000..c2227dac --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/sample/nlsolve.rb @@ -0,0 +1,40 @@ +#!/usr/local/bin/ruby +# frozen_string_literal: false + +# +# nlsolve.rb +# An example for solving nonlinear algebraic equation system. +# + +require "bigdecimal" +require "bigdecimal/newton" +include Newton + +class Function # :nodoc: all + def initialize() + @zero = BigDecimal("0.0") + @one = BigDecimal("1.0") + @two = BigDecimal("2.0") + @ten = BigDecimal("10.0") + @eps = BigDecimal("1.0e-16") + end + def zero;@zero;end + def one ;@one ;end + def two ;@two ;end + def ten ;@ten ;end + def eps ;@eps ;end + def values(x) # <= defines functions solved + f = [] + f1 = x[0]*x[0] + x[1]*x[1] - @two # f1 = x**2 + y**2 - 2 => 0 + f2 = x[0] - x[1] # f2 = x - y => 0 + f <<= f1 + f <<= f2 + f + end +end + +f = BigDecimal.limit(100) +f = Function.new +x = [f.zero,f.zero] # Initial values +n = nlsolve(f,x) +p x diff --git a/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/sample/pi.rb b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/sample/pi.rb new file mode 100644 index 00000000..ea966389 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/bigdecimal-3.2.2/sample/pi.rb @@ -0,0 +1,21 @@ +#!/usr/local/bin/ruby +# frozen_string_literal: false + +# +# pi.rb +# +# Calculates 3.1415.... (the number of times that a circle's diameter +# will fit around the circle) using J. Machin's formula. +# + +require "bigdecimal" +require "bigdecimal/math.rb" + +include BigMath + +if ARGV.size == 1 + print "PI("+ARGV[0]+"):\n" + p PI(ARGV[0].to_i) +else + print "TRY: ruby pi.rb 1000 \n" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/CHANGES b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/CHANGES new file mode 100644 index 00000000..aec4a79c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/CHANGES @@ -0,0 +1,128 @@ += Change Log + +== Version 3.3.0 (2024-06-06) + +* Fix broken call to `File.exists?` in `tags.rake`. +* Use BasicObject instead of BlankSlate for better compatibility with modern rubies. + +== Version 3.2.4 (2019-12-10) + +* Strings were frozen by adding `# frozen_string_literal: true` to all files + +== Version 3.2.3 (2017-01-13) + +* Support for Ruby 2.4 + +== Version 3.2.2 (2013-06-01 + +* Misc minor fixes + +== Version 3.2.1 (2013-05-30) + +* Travis & Rdoc fixes + +== Version 3.2.0 (2013-02-23) + +* Ruby 2.0 compatibility changes. + +* Allow single quoted attributes. + +== Version 3.1.0 + +* Included the to_xs arity patch needed for weird Rails compatibility + issue. + +* Escaping newlines in attributes now. + +* Allow method caching + +== Version 3.0.0 + +* Ruby 1.9 compatiblity issues. + +== Version 2.2.0 + +* Applied patch from Thijs van der Vossen to allow UTF-8 encoded + output when the encoding is UTF-8 and $KCODE is UTF8. + +== Version 2.1.2 + +* Fixed bug where private methods in kernel could leak through using + tag!(). Thanks to Hagen Overdick for finding and diagnosing this + bug. + +== Version 2.1.1 + +* Fixed typo in XmlMarkup class docs (ident => indent). (from Martin + Fowler). +* Removed extra directory indirection from legacy CVS to SVN move. +* Removed some extraneous tabs from source. +* Fixed test on private methods in blankslate to differentiate between + targetted and untargetted private methods. +* Removed legacy capture of @self in XmlBase (@self was used back when + we used instance eval). +* Added additional tests for global functions (both direct and included). + +== Version 2.1.0 + +* Fixed bug in BlankSlate where including a module into Object could + cause methods to leak into BlankSlate. +* Made BlankSlate available as its own gem. Currently the builder gem + still directly includes the BlankSlate code. +* Added reveal capability to BlankSlate. + +== Version 2.0.0 + +* Added doc directory +* Added unit tests for XmlEvents. +* Added XChar module and used it in the _escape method. +* Attributes are now quoted by default when strings. Use Symbol + attribute values for unquoted behavior. + +== Version 1.2.4 + +* Added a cdata! command to an XML Builder (from Josh Knowles). + +== Version 1.2.3 + +The attributes in the instruction will be ordered: +version, encoding, standalone. + +== Version 1.2.2 + +Another fix for BlankSlate. The Kernal/Object traps added in 1.2.1 +failed when a method was defined late more than once. Since the +method was already marked as removed, another attempt to undefine it +raised an error. The fix was to check the list of instance methods +before attempting the undef operation. Thanks to Florian Gross and +David Heinemeier Hansson for the patch. + +== Version 1.2.1 + +BlankSlate now traps method definitions in Kernel and Object to avoid +late method definitions inadvertently becoming part of the definition +of BlankSlate as well. + +== Version 1.2.0 + +Improved support for entity declarations by allowing nested +declarations and removal of the attribute processing. + +Added namespace support. + +== Version 1.1.0 + +Added support for comments, entity declarations and processing instructions. + +== Version 1.0.0 + +Removed use of instace_eval making the use of XmlMarkup much +less prone to error. + +== Version 0.1.1 + +Bug fix. + +== Version 0.1.0 + +Initial version release. diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/Gemfile b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/Gemfile new file mode 100644 index 00000000..ddc0b994 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/Gemfile @@ -0,0 +1,8 @@ +# frozen_string_literal: true +source "https://rubygems.org" + +# Specify your gem's dependencies in heap-profiler.gemspec +gemspec + +gem "rake" +gem "minitest" diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/MIT-LICENSE b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/MIT-LICENSE new file mode 100644 index 00000000..7d9be510 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2003-2012 Jim Weirich (jim.weirich@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/README.md b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/README.md new file mode 100644 index 00000000..f5936d81 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/README.md @@ -0,0 +1,258 @@ +# Project: Builder + +## Goal + +Provide a simple way to create XML markup and data structures. + +## Classes + +Builder::XmlMarkup:: Generate XML markup notation +Builder::XmlEvents:: Generate XML events (i.e. SAX-like) + +**Notes:** + +* An Builder::XmlTree class to generate XML tree + (i.e. DOM-like) structures is also planned, but not yet implemented. + Also, the events builder is currently lagging the markup builder in + features. + +## Usage + +```ruby + require 'rubygems' + require_gem 'builder', '~> 2.0' + + builder = Builder::XmlMarkup.new + xml = builder.person { |b| b.name("Jim"); b.phone("555-1234") } + xml #=> Jim555-1234 +``` + +or + +```ruby + require 'rubygems' + require_gem 'builder' + + builder = Builder::XmlMarkup.new(:target=>STDOUT, :indent=>2) + builder.person { |b| b.name("Jim"); b.phone("555-1234") } + # + # Prints: + # + # Jim + # 555-1234 + # +``` + +## Compatibility + +### Version 2.0.0 Compatibility Changes + +Version 2.0.0 introduces automatically escaped attribute values for +the first time. Versions prior to 2.0.0 did not insert escape +characters into attribute values in the XML markup. This allowed +attribute values to explicitly reference entities, which was +occasionally used by a small number of developers. Since strings +could always be explicitly escaped by hand, this was not a major +restriction in functionality. + +However, it did surprise most users of builder. Since the body text is +normally escaped, everybody expected the attribute values to be +escaped as well. Escaped attribute values were the number one support +request on the 1.x Builder series. + +Starting with Builder version 2.0.0, all attribute values expressed as +strings will be processed and the appropriate characters will be +escaped (e.g. "&" will be translated to "&amp;"). Attribute values +that are expressed as Symbol values will not be processed for escaped +characters and will be unchanged in output. (Yes, this probably counts +as Symbol abuse, but the convention is convenient and flexible). + +Example: + +```ruby + xml = Builder::XmlMarkup.new + xml.sample(:escaped=>"This&That", :unescaped=>:"Here&There") + xml.target! => + +``` + +### Version 1.0.0 Compatibility Changes + +Version 1.0.0 introduces some changes that are not backwards +compatible with earlier releases of builder. The main areas of +incompatibility are: + +* Keyword based arguments to +new+ (rather than positional based). It + was found that a developer would often like to specify indentation + without providing an explicit target, or specify a target without + indentation. Keyword based arguments handle this situation nicely. + +* Builder must now be an explicit target for markup tags. Instead of + writing + +```ruby + xml_markup = Builder::XmlMarkup.new + xml_markup.div { strong("text") } +``` + + you need to write + +```ruby + xml_markup = Builder::XmlMarkup.new + xml_markup.div { xml_markup.strong("text") } +``` + +* The builder object is passed as a parameter to all nested markup + blocks. This allows you to create a short alias for the builder + object that can be used within the block. For example, the previous + example can be written as: + +```ruby + xml_markup = Builder::XmlMarkup.new + xml_markup.div { |xml| xml.strong("text") } +``` + +* If you have both a pre-1.0 and a post-1.0 gem of builder installed, + you can choose which version to use through the RubyGems + +require_gem+ facility. + +```ruby + require_gem 'builder', "~> 0.0" # Gets the old version + require_gem 'builder', "~> 1.0" # Gets the new version +``` + +## Features + +* XML Comments are supported ... + +```ruby + xml_markup.comment! "This is a comment" + #=> +``` + +* XML processing instructions are supported ... + +```ruby + xml_markup.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8" + #=> +``` + + If the processing instruction is omitted, it defaults to "xml". + When the processing instruction is "xml", the defaults attributes + are: + + version: 1.0 + encoding: "UTF-8" + + (NOTE: if the encoding is set to "UTF-8" and $KCODE is set to + "UTF8", then Builder will emit UTF-8 encoded strings rather than + encoding non-ASCII characters as entities.) + +* XML entity declarations are now supported to a small degree. + +```ruby + xml_markup.declare! :DOCTYPE, :chapter, :SYSTEM, "../dtds/chapter.dtd" + #=> +``` + + The parameters to a declare! method must be either symbols or + strings. Symbols are inserted without quotes, and strings are + inserted with double quotes. Attribute-like arguments in hashes are + not allowed. + + If you need to have an argument to declare! be inserted without + quotes, but the argument does not conform to the typical Ruby + syntax for symbols, then use the :"string" form to specify a symbol. + + For example: + +```ruby + xml_markup.declare! :ELEMENT, :chapter, :"(title,para+)" + #=> +``` + + Nested entity declarations are allowed. For example: + +```ruby + @xml_markup.declare! :DOCTYPE, :chapter do |x| + x.declare! :ELEMENT, :chapter, :"(title,para+)" + x.declare! :ELEMENT, :title, :"(#PCDATA)" + x.declare! :ELEMENT, :para, :"(#PCDATA)" + end + + #=> + + + + + ]> +``` + +* Some support for XML namespaces is now available. If the first + argument to a tag call is a symbol, it will be joined to the tag to + produce a namespace:tag combination. It is easier to show this than + describe it. + +```ruby + xml.SOAP :Envelope do ... end +``` + + Just put a space before the colon in a namespace to produce the + right form for builder (e.g. "SOAP:Envelope" => + "xml.SOAP :Envelope") + +* String attribute values are now escaped by default by + Builder (NOTE: this is _new_ behavior as of version 2.0). + + However, occasionally you need to use entities in attribute values. + Using a symbol (rather than a string) for an attribute value will + cause Builder to not run its quoting/escaping algorithm on that + particular value. + + (Note: The +escape_attrs+ option for builder is now + obsolete). + + Example: + +```ruby + xml = Builder::XmlMarkup.new + xml.sample(:escaped=>"This&That", :unescaped=>:"Here&There") + xml.target! => + +``` + +* UTF-8 Support + + Builder correctly translates UTF-8 characters into valid XML. (New + in version 2.0.0). Thanks to Sam Ruby for the translation code. + + You can get UTF-8 encoded output by making sure that the XML + encoding is set to "UTF-8" and that the $KCODE variable is set to + "UTF8". + +```ruby + $KCODE = 'UTF8' + xml = Builder::Markup.new + xml.instruct!(:xml, :encoding => "UTF-8") + xml.sample("Iñtërnâtiônàl") + xml.target! => + "Iñtërnâtiônàl" +``` + +## Links + +| Description | Link | +| :----: | :----: | +| Documents | http://builder.rubyforge.org/ | +| Github Clone | git://github.com/rails/builder.git | +| Issue / Bug Reports | https://github.com/rails/builder/issues?state=open | + +## Contact + +| Description | Value | +| :----: | :----: | +| Author | Jim Weirich | +| Email | jim.weirich@gmail.com | +| Home Page | http://onestepback.org | +| License | MIT Licence (http://www.opensource.org/licenses/mit-license.html) | diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/Rakefile b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/Rakefile new file mode 100644 index 00000000..9fce31f8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/Rakefile @@ -0,0 +1,154 @@ +# frozen_string_literal: true +# Rakefile for rake -*- ruby -*- + +# Copyright 2004, 2005, 2006 by Jim Weirich (jim@weirichhouse.org). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. + +require 'bundler/gem_tasks' +require 'rake/clean' +require 'rake/testtask' +begin + require 'rubygems' + require 'rubygems/package_task' + require 'rdoc/task' +rescue Exception + nil +end + +require './lib/builder/version' + +# Determine the current version of the software + +CLOBBER.include('pkg', 'html') +CLEAN.include('pkg/builder-*').exclude('pkg/*.gem') + +PKG_VERSION = Builder::VERSION + +SRC_RB = FileList['lib/**/*.rb'] + +# The default task is run if rake is given no explicit arguments. + +desc "Default Task" +task :default => :test_all + +# Test Tasks --------------------------------------------------------- + +desc "Run all tests" +task :test_all => [:test_units] +task :ta => [:test_all] + +task :tu => [:test_units] + +Rake::TestTask.new("test_units") do |t| + t.test_files = FileList['test/test*.rb'] + t.libs << "." << "test" + t.verbose = false +end + +# Create a task to build the RDOC documentation tree. + +if defined?(RDoc) + rd = RDoc::Task.new("rdoc") { |rdoc| + rdoc.rdoc_dir = 'html' + rdoc.title = "Builder for Markup" + rdoc.options << '--line-numbers' << '--inline-source' << '--main' << 'README.rdoc' + rdoc.rdoc_files.include('lib/**/*.rb', '[A-Z]*', 'doc/**/*.rdoc').exclude("TAGS") + rdoc.template = 'doc/jamis.rb' + } +else + rd = Struct.new(:rdoc_files).new([]) +end + +# ==================================================================== +# Create a task that will package the Rake software into distributable +# gem files. + +PKG_FILES = FileList[ + '[A-Z]*', + 'doc/**/*', + 'lib/**/*.rb', + 'test/**/*.rb', + 'rakelib/**/*' +] +PKG_FILES.exclude('test/test_cssbuilder.rb') +PKG_FILES.exclude('lib/builder/css.rb') +PKG_FILES.exclude('TAGS') + +if ! defined?(Gem) + puts "Package Target requires RubyGEMs" +else + spec = Gem::Specification.new do |s| + + #### Basic information. + + s.name = 'builder' + s.version = PKG_VERSION + s.summary = "Builders for MarkUp." + s.description = %{\ +Builder provides a number of builder objects that make creating structured data +simple to do. Currently the following builder objects are supported: + +* XML Markup +* XML Events +} + + s.files = PKG_FILES.to_a + s.require_path = 'lib' + + s.test_files = PKG_FILES.select { |fn| fn =~ /^test\/test/ } + + s.extra_rdoc_files = rd.rdoc_files.reject { |fn| fn =~ /\.rb$/ }.to_a + s.rdoc_options << + '--title' << 'Builder -- Easy XML Building' << + '--main' << 'README.rdoc' << + '--line-numbers' + + s.author = "Jim Weirich" + s.email = "jim.weirich@gmail.com" + s.homepage = "http://onestepback.org" + s.license = 'MIT' + end + + namespace 'builder' do + Gem::PackageTask.new(spec) do |t| + t.need_tar = false + end + end + + task :package => [:remove_tags, 'builder:package'] +end + +task :remove_tags do + rm "TAGS" rescue nil +end + +# RCov --------------------------------------------------------------- +begin + require 'rcov/rcovtask' + + Rcov::RcovTask.new do |t| + t.libs << "test" + t.rcov_opts = [ + '-xRakefile', '--text-report' + ] + t.test_files = FileList[ + 'test/test*.rb' + ] + t.output_dir = 'coverage' + t.verbose = true + end +rescue LoadError + # No rcov available +end + +desc "Install the jamis RDoc template" +task :install_jamis_template do + require 'rbconfig' + dest_dir = File.join(Config::CONFIG['rubylibdir'], "rdoc/generators/template/html") + fail "Unabled to write to #{dest_dir}" unless File.writable?(dest_dir) + install "doc/jamis.rb", dest_dir, :verbose => true +end diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/builder.blurb b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/builder.blurb new file mode 100644 index 00000000..9593f728 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/builder.blurb @@ -0,0 +1,27 @@ +name: builder +document: http://builder.rubyforge.org +download: http://rubyforge.org/frs/?group_id=415 +description: > +

    This package contains Builder, a simple ruby library for + building XML document quickly and easily.

    + +

    Here's an example:

    + +
    +      xml = Builder::XmlMarkup.new(:indent=>2)
    +      xml.person {
    +        xml.first_name("Jim")
    +        xml.last_name("Weirich")
    +      }
    +      puts xml.target!
    +    
    + +

    Produces:

    + +
    +      <person>
    +        <first_name>Jim</first_name>
    +        <last_name>Weirich</last_name>
    +      </person>
    +    
    + diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/builder.gemspec b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/builder.gemspec new file mode 100644 index 00000000..2b11193e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/builder.gemspec @@ -0,0 +1,49 @@ +# frozen_string_literal: true +require './lib/builder/version' + +Gem::Specification.new do |s| + + #### Basic information. + + s.name = 'builder' + s.version = Builder::VERSION + s.summary = "Builders for MarkUp." + s.description = %{\ +Builder provides a number of builder objects that make creating structured data +simple to do. Currently the following builder objects are supported: + +* XML Markup +* XML Events +} + + pkg_files = Dir[ + '[A-Z]*', + 'doc/**/*', + 'lib/**/*.rb', + 'test/**/*.rb', + 'rakelib/**/*' + ] + + s.files = pkg_files + s.require_path = 'lib' + + s.test_files = pkg_files.select { |fn| fn =~ /^test\/test/ } + + # s.extra_rdoc_files = rd.rdoc_files.reject { |fn| fn =~ /\.rb$/ }.to_a + s.rdoc_options << + '--title' << 'Builder -- Easy XML Building' << + '--main' << 'README.rdoc' << + '--line-numbers' + + s.authors = ["Jim Weirich", "Aaron Patterson"] + s.email = "aron.patterson@gmail.com" + s.homepage = "https://github.com/rails/builder" + s.license = 'MIT' + s.metadata = { + "bug_tracker_uri" => "#{s.homepage}/issues", + "changelog_uri" => "#{s.homepage}/blob/master/CHANGES", + "documentation_uri" => "https://www.rubydoc.info/gems/builder/#{s.version}", + "homepage_uri" => s.homepage, + "source_code_uri" => "#{s.homepage}/tree/v#{s.version}" + } +end diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/doc/jamis.rb b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/doc/jamis.rb new file mode 100644 index 00000000..4d15f33f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/doc/jamis.rb @@ -0,0 +1,592 @@ +# frozen_string_literal: true +module RDoc +module Page + +FONTS = "\"Bitstream Vera Sans\", Verdana, Arial, Helvetica, sans-serif" + +STYLE = < pre { + padding: 0.5em; + border: 1px dotted black; + background: #FFE; +} + +CSS + +XHTML_PREAMBLE = %{ + +} + +HEADER = XHTML_PREAMBLE + < + + %title% + + + + + + + +ENDHEADER + +FILE_PAGE = < +
    +
    + + + +
    File
    %short_name%
    + + + + + + + + + +
    Path:%full_path% +IF:cvsurl +  (CVS) +ENDIF:cvsurl +
    Modified:%dtm_modified%
    +
    +

    +HTML + +################################################################### + +CLASS_PAGE = < + %classmod%
    %full_name% + + + + + + +IF:parent + + + + +ENDIF:parent +
    In: +START:infiles +HREF:full_path_url:full_path: +IF:cvsurl + (CVS) +ENDIF:cvsurl +END:infiles +
    Parent: +IF:par_url + +ENDIF:par_url +%parent% +IF:par_url + +ENDIF:par_url +
    + + + +HTML + +################################################################### + +METHOD_LIST = < +IF:diagram +
    + %diagram% +
    +ENDIF:diagram + +IF:description +
    %description%
    +ENDIF:description + +IF:requires +
    Required Files
    +
      +START:requires +
    • HREF:aref:name:
    • +END:requires +
    +ENDIF:requires + +IF:toc +
    Contents
    + +ENDIF:toc + +IF:methods +
    Methods
    +
      +START:methods +
    • HREF:aref:name:
    • +END:methods +
    +ENDIF:methods + +IF:includes +
    Included Modules
    +
      +START:includes +
    • HREF:aref:name:
    • +END:includes +
    +ENDIF:includes + +START:sections +IF:sectitle + +IF:seccomment +
    +%seccomment% +
    +ENDIF:seccomment +ENDIF:sectitle + +IF:classlist +
    Classes and Modules
    + %classlist% +ENDIF:classlist + +IF:constants +
    Constants
    + +START:constants + + + + + +IF:desc + + + + +ENDIF:desc +END:constants +
    %name%=%value%
     %desc%
    +ENDIF:constants + +IF:attributes +
    Attributes
    + +START:attributes + + + + + +END:attributes +
    +IF:rw +[%rw%] +ENDIF:rw + %name%%a_desc%
    +ENDIF:attributes + +IF:method_list +START:method_list +IF:methods +
    %type% %category% methods
    +START:methods +
    +
    +IF:callseq + %callseq% +ENDIF:callseq +IFNOT:callseq + %name%%params% +ENDIF:callseq +IF:codeurl +[ source ] +ENDIF:codeurl +
    +IF:m_desc +
    + %m_desc% +
    +ENDIF:m_desc +IF:aka +
    + This method is also aliased as +START:aka + %name% +END:aka +
    +ENDIF:aka +IF:sourcecode +
    + +
    +
    +%sourcecode%
    +
    +
    +
    +ENDIF:sourcecode +
    +END:methods +ENDIF:methods +END:method_list +ENDIF:method_list +END:sections +
    +HTML + +FOOTER = < + +ENDFOOTER + +BODY = HEADER + < + +
    + #{METHOD_LIST} +
    + + #{FOOTER} +ENDBODY + +########################## Source code ########################## + +SRC_PAGE = XHTML_PREAMBLE + < +%title% + + + + +
    %code%
    + + +HTML + +########################## Index ################################ + +FR_INDEX_BODY = < + + + + + + + +
    +START:entries +%name%
    +END:entries +
    + +HTML + +CLASS_INDEX = FILE_INDEX +METHOD_INDEX = FILE_INDEX + +INDEX = XHTML_PREAMBLE + < + + %title% + + + + + + + + + +IF:inline_source + +ENDIF:inline_source +IFNOT:inline_source + + + + +ENDIF:inline_source + + <body bgcolor="white"> + Click <a href="html/index.html">here</a> for a non-frames + version of this page. + </body> + + + + +HTML + +end +end + + diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/doc/releases/builder-1.2.4.rdoc b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/doc/releases/builder-1.2.4.rdoc new file mode 100644 index 00000000..a1cf54fd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/doc/releases/builder-1.2.4.rdoc @@ -0,0 +1,31 @@ += Builder 1.2.4 Released. + +Added a "CDATA" method to the XML Markup builder (from Josh Knowles). + +== What is Builder? + +Builder::XmlMarkup allows easy programmatic creation of XML markup. +For example: + + builder = Builder::XmlMarkup.new(:target=>STDOUT, :indent=>2) + builder.person { |b| b.name("Jim"); b.phone("555-1234") } + puts builder.target! + +will generate: + + + Jim + 555-1234 + + +== Availability + +The easiest way to get and install builder is via RubyGems ... + + gem install builder (you may need root/admin privileges) + +== Thanks + +* Josh Knowles for the cdata! patch. + +-- Jim Weirich diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/doc/releases/builder-2.0.0.rdoc b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/doc/releases/builder-2.0.0.rdoc new file mode 100644 index 00000000..ed9e086d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/doc/releases/builder-2.0.0.rdoc @@ -0,0 +1,46 @@ += Builder 2.0.0 Released. + +== Changes in 2.0.0 + +* UTF-8 characters in data are now correctly translated to their XML + equivalents. (Thanks to Sam Ruby) + +* Attribute values are now escaped by default. See the README + file for details. + +NOTE: The escaping attribute values by default is different +than in previous releases of Builder. This makes version 2.0.0 +somewhat incompatible with the 1.x series of Builder. If you use "&", +"<", or ">" in attributes values, you may have to change your +code. (Essentially you remove the manual escaping. The new way is +easier, believe me). + +== What is Builder? + +Builder::XmlMarkup is a library that allows easy programmatic creation +of XML markup. For example: + + builder = Builder::XmlMarkup.new(:target=>STDOUT, :indent=>2) + builder.person { |b| b.name("Jim"); b.phone("555-1234") } + +will generate: + + + Jim + 555-1234 + + +== Availability + +The easiest way to get and install builder is via RubyGems ... + + gem install builder (you may need root/admin privileges) + +== Thanks + +* Sam Ruby for the XChar module and the related UTF-8 translation + tools. +* Also to Sam Ruby for gently persuading me to start quoting attribute + values. + +-- Jim Weirich diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/doc/releases/builder-2.1.1.rdoc b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/doc/releases/builder-2.1.1.rdoc new file mode 100644 index 00000000..dbbf1213 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/doc/releases/builder-2.1.1.rdoc @@ -0,0 +1,58 @@ += Builder 2.1.1 Released. + +Release 2.1.1 of Builder is mainly a bug fix release. + +== Changes in 2.1.1 + +* Added reveal capability to BlankSlate. + +* Fixed a bug in BlankSlate where including a module into Object could + cause methods to leak into BlankSlate. + +* Fixed typo in XmlMarkup class docs (from Martin Fowler). + +* Fixed test on private methods to differentiate between targetted and + untargetted private methods. + +* Removed legacy capture of @self in XmlBase (@self was used back when + we used instance eval). + +* Added additional tests for global functions (both direct and + included). + +* Several misc internal cleanups, including rearranging the source + code tree. + +NOTE: The escaping attribute values by default is different +than in previous releases of Builder. This makes version 2.0.x +somewhat incompatible with the 1.x series of Builder. If you use "&", +"<", or ">" in attributes values, you may have to change your +code. (Essentially you remove the manual escaping. The new way is +easier, believe me). + +== What is Builder? + +Builder::XmlMarkup is a library that allows easy programmatic creation +of XML markup. For example: + + builder = Builder::XmlMarkup.new(:target=>STDOUT, :indent=>2) + builder.person { |b| b.name("Jim"); b.phone("555-1234") } + +will generate: + + + Jim + 555-1234 + + +== Availability + +The easiest way to get and install builder is via RubyGems ... + + gem install builder (you may need root/admin privileges) + +== Thanks + +* Martin Fowler for spotting some typos in the documentation. + +-- Jim Weirich diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/lib/builder.rb b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/lib/builder.rb new file mode 100644 index 00000000..20558dfc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/lib/builder.rb @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +#-- +# Copyright 2004 by Jim Weirich (jim@weirichhouse.org). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +require 'builder/xmlmarkup' +require 'builder/xmlevents' diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/lib/builder/version.rb b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/lib/builder/version.rb new file mode 100644 index 00000000..ac300e7a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/lib/builder/version.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true +module Builder + VERSION_NUMBERS = [ + VERSION_MAJOR = 3, + VERSION_MINOR = 3, + VERSION_BUILD = 0, + ] + VERSION = VERSION_NUMBERS.join(".") +end diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/lib/builder/xchar.rb b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/lib/builder/xchar.rb new file mode 100644 index 00000000..f904ca58 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/lib/builder/xchar.rb @@ -0,0 +1,198 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# The XChar library is provided courtesy of Sam Ruby (See +# http://intertwingly.net/stories/2005/09/28/xchar.rb) + +# -------------------------------------------------------------------- + +# If the Builder::XChar module is not currently defined, fail on any +# name clashes in standard library classes. + +module Builder + def self.check_for_name_collision(klass, method_name, defined_constant=nil) + if klass.method_defined?(method_name.to_s) + fail RuntimeError, + "Name Collision: Method '#{method_name}' is already defined in #{klass}" + end + end +end + +if ! defined?(Builder::XChar) and ! String.method_defined?(:encode) + Builder.check_for_name_collision(String, "to_xs") + Builder.check_for_name_collision(Integer, "xchr") +end + +###################################################################### +module Builder + + #################################################################### + # XML Character converter, from Sam Ruby: + # (see http://intertwingly.net/stories/2005/09/28/xchar.rb). + # + module XChar # :nodoc: + + # See + # http://intertwingly.net/stories/2004/04/14/i18n.html#CleaningWindows + # for details. + CP1252 = { # :nodoc: + 128 => 8364, # euro sign + 130 => 8218, # single low-9 quotation mark + 131 => 402, # latin small letter f with hook + 132 => 8222, # double low-9 quotation mark + 133 => 8230, # horizontal ellipsis + 134 => 8224, # dagger + 135 => 8225, # double dagger + 136 => 710, # modifier letter circumflex accent + 137 => 8240, # per mille sign + 138 => 352, # latin capital letter s with caron + 139 => 8249, # single left-pointing angle quotation mark + 140 => 338, # latin capital ligature oe + 142 => 381, # latin capital letter z with caron + 145 => 8216, # left single quotation mark + 146 => 8217, # right single quotation mark + 147 => 8220, # left double quotation mark + 148 => 8221, # right double quotation mark + 149 => 8226, # bullet + 150 => 8211, # en dash + 151 => 8212, # em dash + 152 => 732, # small tilde + 153 => 8482, # trade mark sign + 154 => 353, # latin small letter s with caron + 155 => 8250, # single right-pointing angle quotation mark + 156 => 339, # latin small ligature oe + 158 => 382, # latin small letter z with caron + 159 => 376, # latin capital letter y with diaeresis + } + + # See http://www.w3.org/TR/REC-xml/#dt-chardata for details. + PREDEFINED = { + 38 => '&', # ampersand + 60 => '<', # left angle bracket + 62 => '>', # right angle bracket + } + + # See http://www.w3.org/TR/REC-xml/#charsets for details. + VALID = [ + 0x9, 0xA, 0xD, + (0x20..0xD7FF), + (0xE000..0xFFFD), + (0x10000..0x10FFFF) + ] + + # http://www.fileformat.info/info/unicode/char/fffd/index.htm + REPLACEMENT_CHAR = + if String.method_defined?(:encode) + "\uFFFD" + elsif $KCODE == 'UTF8' + "\xEF\xBF\xBD" + else + '*' + end + end + +end + + +if String.method_defined?(:encode) + module Builder + module XChar # :nodoc: + CP1252_DIFFERENCES, UNICODE_EQUIVALENT = Builder::XChar::CP1252.each. + inject([[],[]]) {|(domain,range),(key,value)| + [domain << key,range << value] + }.map {|seq| seq.pack('U*').force_encoding('utf-8')} + + XML_PREDEFINED = Regexp.new('[' + + Builder::XChar::PREDEFINED.keys.pack('U*').force_encoding('utf-8') + + ']') + + INVALID_XML_CHAR = Regexp.new('[^'+ + Builder::XChar::VALID.map { |item| + case item + when Integer + [item].pack('U').force_encoding('utf-8') + when Range + [item.first, '-'.ord, item.last].pack('UUU').force_encoding('utf-8') + end + }.join + + ']') + + ENCODING_BINARY = Encoding.find('BINARY') + ENCODING_UTF8 = Encoding.find('UTF-8') + ENCODING_ISO1 = Encoding.find('ISO-8859-1') + + # convert a string to valid UTF-8, compensating for a number of + # common errors. + def XChar.unicode(string) + if string.encoding == ENCODING_BINARY + if string.ascii_only? + string + else + string = string.clone.force_encoding(ENCODING_UTF8) + if string.valid_encoding? + string + else + string.encode(ENCODING_UTF8, ENCODING_ISO1) + end + end + + elsif string.encoding == ENCODING_UTF8 + if string.valid_encoding? + string + else + string.encode(ENCODING_UTF8, ENCODING_ISO1) + end + + else + string.encode(ENCODING_UTF8) + end + end + + # encode a string per XML rules + def XChar.encode(string) + unicode(string). + tr(CP1252_DIFFERENCES, UNICODE_EQUIVALENT). + gsub(INVALID_XML_CHAR, REPLACEMENT_CHAR). + gsub(XML_PREDEFINED) {|c| PREDEFINED[c.ord]} + end + end + end + +else + + ###################################################################### + # Enhance the Integer class with a XML escaped character conversion. + # + class Integer + XChar = Builder::XChar if ! defined?(XChar) + + # XML escaped version of chr. When escape is set to false + # the CP1252 fix is still applied but utf-8 characters are not + # converted to character entities. + def xchr(escape=true) + n = XChar::CP1252[self] || self + case n when *XChar::VALID + XChar::PREDEFINED[n] or + (n<128 ? n.chr : (escape ? "&##{n};" : [n].pack('U*'))) + else + Builder::XChar::REPLACEMENT_CHAR + end + end + end + + + ###################################################################### + # Enhance the String class with a XML escaped character version of + # to_s. + # + class String + # XML escaped version of to_s. When escape is set to false + # the CP1252 fix is still applied but utf-8 characters are not + # converted to character entities. + def to_xs(escape=true) + unpack('U*').map {|n| n.xchr(escape)}.join # ASCII, UTF-8 + rescue + unpack('C*').map {|n| n.xchr}.join # ISO-8859-1, WIN-1252 + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/lib/builder/xmlbase.rb b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/lib/builder/xmlbase.rb new file mode 100644 index 00000000..2cddbec2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/lib/builder/xmlbase.rb @@ -0,0 +1,198 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +module Builder + + # Generic error for builder + class IllegalBlockError < RuntimeError; end + + # XmlBase is a base class for building XML builders. See + # Builder::XmlMarkup and Builder::XmlEvents for examples. + class XmlBase < BasicObject + + class << self + attr_accessor :cache_method_calls + end + + # Create an XML markup builder. + # + # out :: Object receiving the markup. +out+ must respond to + # <<. + # indent :: Number of spaces used for indentation (0 implies no + # indentation and no line breaks). + # initial :: Level of initial indentation. + # encoding :: When encoding and $KCODE are set to 'utf-8' + # characters aren't converted to character entities in + # the output stream. + def initialize(indent=0, initial=0, encoding='utf-8') + @indent = indent + @level = initial + @encoding = encoding.downcase + end + + def explicit_nil_handling? + @explicit_nil_handling + end + + # Create a tag named +sym+. Other than the first argument which + # is the tag name, the arguments are the same as the tags + # implemented via method_missing. + def tag!(sym, *args, &block) + text = nil + attrs = nil + sym = "#{sym}:#{args.shift}" if args.first.kind_of?(::Symbol) + sym = sym.to_sym unless sym.class == ::Symbol + args.each do |arg| + case arg + when ::Hash + attrs ||= {} + attrs.merge!(arg) + when nil + attrs ||= {} + attrs.merge!({:nil => true}) if explicit_nil_handling? + else + text ||= ''.dup + text << arg.to_s + end + end + if block + unless text.nil? + ::Kernel::raise ::ArgumentError, + "XmlMarkup cannot mix a text argument with a block" + end + _indent + _start_tag(sym, attrs) + _newline + begin + _nested_structures(block) + ensure + _indent + _end_tag(sym) + _newline + end + elsif text.nil? + _indent + _start_tag(sym, attrs, true) + _newline + else + _indent + _start_tag(sym, attrs) + text! text + _end_tag(sym) + _newline + end + @target + end + + # Create XML markup based on the name of the method. This method + # is never invoked directly, but is called for each markup method + # in the markup block that isn't cached. + def method_missing(sym, *args, &block) + cache_method_call(sym) if ::Builder::XmlBase.cache_method_calls + tag!(sym, *args, &block) + end + + # Append text to the output target. Escape any markup. May be + # used within the markup brackets as: + # + # builder.p { |b| b.br; b.text! "HI" } #=>


    HI

    + def text!(text) + _text(_escape(text)) + end + + # Append text to the output target without escaping any markup. + # May be used within the markup brackets as: + # + # builder.p { |x| x << "
    HI" } #=>


    HI

    + # + # This is useful when using non-builder enabled software that + # generates strings. Just insert the string directly into the + # builder without changing the inserted markup. + # + # It is also useful for stacking builder objects. Builders only + # use << to append to the target, so by supporting this + # method/operation builders can use other builders as their + # targets. + def <<(text) + _text(text) + end + + # For some reason, nil? is sent to the XmlMarkup object. If nil? + # is not defined and method_missing is invoked, some strange kind + # of recursion happens. Since nil? won't ever be an XML tag, it + # is pretty safe to define it here. (Note: this is an example of + # cargo cult programming, + # cf. http://fishbowl.pastiche.org/2004/10/13/cargo_cult_programming). + def nil? + false + end + + private + + require 'builder/xchar' + if ::String.method_defined?(:encode) + def _escape(text) + result = XChar.encode(text) + begin + encoding = ::Encoding::find(@encoding) + raise Exception if encoding.dummy? + result.encode(encoding) + rescue + # if the encoding can't be supported, use numeric character references + result. + gsub(/[^\u0000-\u007F]/) {|c| "&##{c.ord};"}. + force_encoding('ascii') + end + end + else + def _escape(text) + if (text.method(:to_xs).arity == 0) + text.to_xs + else + text.to_xs((@encoding != 'utf-8' or $KCODE != 'UTF8')) + end + end + end + + def _escape_attribute(text) + _escape(text).gsub("\n", " ").gsub("\r", " "). + gsub(%r{"}, '"') # " WART + end + + def _newline + return if @indent == 0 + text! "\n" + end + + def _indent + return if @indent == 0 || @level == 0 + text!(" " * (@level * @indent)) + end + + def _nested_structures(block) + @level += 1 + block.call(self) + ensure + @level -= 1 + end + + # If XmlBase.cache_method_calls = true, we dynamicly create the method + # missed as an instance method on the XMLBase object. Because XML + # documents are usually very repetative in nature, the next node will + # be handled by the new method instead of method_missing. As + # method_missing is very slow, this speeds up document generation + # significantly. + def cache_method_call(sym) + class << self; self; end.class_eval do + unless method_defined?(sym) + define_method(sym) do |*args, &block| + tag!(sym, *args, &block) + end + end + end + end + end + + XmlBase.cache_method_calls = true + +end diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/lib/builder/xmlevents.rb b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/lib/builder/xmlevents.rb new file mode 100644 index 00000000..1b62c0cd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/lib/builder/xmlevents.rb @@ -0,0 +1,64 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +#-- +# Copyright 2004 by Jim Weirich (jim@weirichhouse.org). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +require 'builder/xmlmarkup' + +module Builder + + # Create a series of SAX-like XML events (e.g. start_tag, end_tag) + # from the markup code. XmlEvent objects are used in a way similar + # to XmlMarkup objects, except that a series of events are generated + # and passed to a handler rather than generating character-based + # markup. + # + # Usage: + # xe = Builder::XmlEvents.new(hander) + # xe.title("HI") # Sends start_tag/end_tag/text messages to the handler. + # + # Indentation may also be selected by providing value for the + # indentation size and initial indentation level. + # + # xe = Builder::XmlEvents.new(handler, indent_size, initial_indent_level) + # + # == XML Event Handler + # + # The handler object must expect the following events. + # + # [start_tag(tag, attrs)] + # Announces that a new tag has been found. +tag+ is the name of + # the tag and +attrs+ is a hash of attributes for the tag. + # + # [end_tag(tag)] + # Announces that an end tag for +tag+ has been found. + # + # [text(text)] + # Announces that a string of characters (+text+) has been found. + # A series of characters may be broken up into more than one + # +text+ call, so the client cannot assume that a single + # callback contains all the text data. + # + class XmlEvents < XmlMarkup + def text!(text) + @target.text(text) + end + + def _start_tag(sym, attrs, end_too=false) + @target.start_tag(sym, attrs) + _end_tag(sym) if end_too + end + + def _end_tag(sym) + @target.end_tag(sym) + end + end + +end diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/lib/builder/xmlmarkup.rb b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/lib/builder/xmlmarkup.rb new file mode 100644 index 00000000..cccf8d03 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/lib/builder/xmlmarkup.rb @@ -0,0 +1,345 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true +#-- +# Copyright 2004, 2005 by Jim Weirich (jim@weirichhouse.org). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +# Provide a flexible and easy to use Builder for creating XML markup. +# See XmlBuilder for usage details. + +require 'builder/xmlbase' + +module Builder + + # Create XML markup easily. All (well, almost all) methods sent to + # an XmlMarkup object will be translated to the equivalent XML + # markup. Any method with a block will be treated as an XML markup + # tag with nested markup in the block. + # + # Examples will demonstrate this easier than words. In the + # following, +xm+ is an +XmlMarkup+ object. + # + # xm.em("emphasized") # => emphasized + # xm.em { xm.b("emp & bold") } # => emph & bold + # xm.a("A Link", "href"=>"http://onestepback.org") + # # => A Link + # xm.div { xm.br } # =>

    + # xm.target("name"=>"compile", "option"=>"fast") + # # => + # # NOTE: order of attributes is not specified. + # + # xm.instruct! # + # xm.html { # + # xm.head { # + # xm.title("History") # History + # } # + # xm.body { # + # xm.comment! "HI" # + # xm.h1("Header") #

    Header

    + # xm.p("paragraph") #

    paragraph

    + # } # + # } # + # + # == Notes: + # + # * The order that attributes are inserted in markup tags is + # undefined. + # + # * Sometimes you wish to insert text without enclosing tags. Use + # the text! method to accomplish this. + # + # Example: + # + # xm.div { #
    + # xm.text! "line"; xm.br # line
    + # xm.text! "another line"; xmbr # another line
    + # } #
    + # + # * The special XML characters <, >, and & are converted to <, + # > and & automatically. Use the << operation to + # insert text without modification. + # + # * Sometimes tags use special characters not allowed in ruby + # identifiers. Use the tag! method to handle these + # cases. + # + # Example: + # + # xml.tag!("SOAP:Envelope") { ... } + # + # will produce ... + # + # ... " + # + # tag! will also take text and attribute arguments (after + # the tag name) like normal markup methods. (But see the next + # bullet item for a better way to handle XML namespaces). + # + # * Direct support for XML namespaces is now available. If the + # first argument to a tag call is a symbol, it will be joined to + # the tag to produce a namespace:tag combination. It is easier to + # show this than describe it. + # + # xml.SOAP :Envelope do ... end + # + # Just put a space before the colon in a namespace to produce the + # right form for builder (e.g. "SOAP:Envelope" => + # "xml.SOAP :Envelope") + # + # * XmlMarkup builds the markup in any object (called a _target_) + # that accepts the << method. If no target is given, + # then XmlMarkup defaults to a string target. + # + # Examples: + # + # xm = Builder::XmlMarkup.new + # result = xm.title("yada") + # # result is a string containing the markup. + # + # buffer = "" + # xm = Builder::XmlMarkup.new(buffer) + # # The markup is appended to buffer (using <<) + # + # xm = Builder::XmlMarkup.new(STDOUT) + # # The markup is written to STDOUT (using <<) + # + # xm = Builder::XmlMarkup.new + # x2 = Builder::XmlMarkup.new(:target=>xm) + # # Markup written to +x2+ will be send to +xm+. + # + # * Indentation is enabled by providing the number of spaces to + # indent for each level as a second argument to XmlBuilder.new. + # Initial indentation may be specified using a third parameter. + # + # Example: + # + # xm = Builder::XmlMarkup.new(:indent=>2) + # # xm will produce nicely formatted and indented XML. + # + # xm = Builder::XmlMarkup.new(:indent=>2, :margin=>4) + # # xm will produce nicely formatted and indented XML with 2 + # # spaces per indent and an over all indentation level of 4. + # + # builder = Builder::XmlMarkup.new(:target=>$stdout, :indent=>2) + # builder.name { |b| b.first("Jim"); b.last("Weirich") } + # # prints: + # # + # # Jim + # # Weirich + # # + # + # * The instance_eval implementation which forces self to refer to + # the message receiver as self is now obsolete. We now use normal + # block calls to execute the markup block. This means that all + # markup methods must now be explicitly send to the xml builder. + # For instance, instead of + # + # xml.div { strong("text") } + # + # you need to write: + # + # xml.div { xml.strong("text") } + # + # Although more verbose, the subtle change in semantics within the + # block was found to be prone to error. To make this change a + # little less cumbersome, the markup block now gets the markup + # object sent as an argument, allowing you to use a shorter alias + # within the block. + # + # For example: + # + # xml_builder = Builder::XmlMarkup.new + # xml_builder.div { |xml| + # xml.strong("text") + # } + # + class XmlMarkup < XmlBase + + # Create an XML markup builder. Parameters are specified by an + # option hash. + # + # :target => target_object:: + # Object receiving the markup. +target_object+ must respond to + # the <<(a_string) operator and return + # itself. The default target is a plain string target. + # + # :indent => indentation:: + # Number of spaces used for indentation. The default is no + # indentation and no line breaks. + # + # :margin => initial_indentation_level:: + # Amount of initial indentation (specified in levels, not + # spaces). + # + # :quote => :single:: + # Use single quotes for attributes rather than double quotes. + # + # :escape_attrs => OBSOLETE:: + # The :escape_attrs option is no longer supported by builder + # (and will be quietly ignored). String attribute values are + # now automatically escaped. If you need unescaped attribute + # values (perhaps you are using entities in the attribute + # values), then give the value as a Symbol. This allows much + # finer control over escaping attribute values. + # + def initialize(options={}) + indent = options[:indent] || 0 + margin = options[:margin] || 0 + @quote = (options[:quote] == :single) ? "'" : '"' + @explicit_nil_handling = options[:explicit_nil_handling] + super(indent, margin) + @target = options[:target] || "".dup + end + + # Return the target of the builder. + def target! + @target + end + + def comment!(comment_text) + _ensure_no_block ::Kernel::block_given? + _special("", comment_text, nil) + end + + # Insert an XML declaration into the XML markup. + # + # For example: + # + # xml.declare! :ELEMENT, :blah, "yada" + # # => + def declare!(inst, *args, &block) + _indent + @target << "" + _newline + end + + # Insert a processing instruction into the XML markup. E.g. + # + # For example: + # + # xml.instruct! + # #=> + # xml.instruct! :aaa, :bbb=>"ccc" + # #=> + # + # Note: If the encoding is setup to "UTF-8" and the value of + # $KCODE is "UTF8", then builder will emit UTF-8 encoded strings + # rather than the entity encoding normally used. + def instruct!(directive_tag=:xml, attrs={}) + _ensure_no_block ::Kernel::block_given? + if directive_tag == :xml + a = { :version=>"1.0", :encoding=>"UTF-8" } + attrs = a.merge attrs + @encoding = attrs[:encoding].downcase + end + _special( + "", + nil, + attrs, + [:version, :encoding, :standalone]) + end + + # Insert a CDATA section into the XML markup. + # + # For example: + # + # xml.cdata!("text to be included in cdata") + # #=> + # + def cdata!(text) + _ensure_no_block ::Kernel::block_given? + _special("", text.gsub(']]>', ']]]]>'), nil) + end + + def cdata_value!(open, text) + _ensure_no_block ::Kernel::block_given? + _special("<#{open}>", "", "', ']]]]>')}]]>", nil) + end + + private + + # NOTE: All private methods of a builder object are prefixed when + # a "_" character to avoid possible conflict with XML tag names. + + # Insert text directly in to the builder's target. + def _text(text) + @target << text + end + + # Insert special instruction. + def _special(open, close, data=nil, attrs=nil, order=[]) + _indent + @target << open + @target << data if data + _insert_attributes(attrs, order) if attrs + @target << close + _newline + end + + # Start an XML tag. If end_too is true, then the start + # tag is also the end tag (e.g.
    + def _start_tag(sym, attrs, end_too=false) + @target << "<#{sym}" + _insert_attributes(attrs) + @target << "/" if end_too + @target << ">" + end + + # Insert an ending tag. + def _end_tag(sym) + @target << "" + end + + # Insert the attributes (given in the hash). + def _insert_attributes(attrs, order=[]) + return if attrs.nil? + order.each do |k| + v = attrs[k] + @target << %{ #{k}=#{@quote}#{_attr_value(v)}#{@quote}} if v + end + attrs.each do |k, v| + @target << %{ #{k}=#{@quote}#{_attr_value(v)}#{@quote}} unless order.member?(k) # " WART + end + end + + def _attr_value(value) + case value + when ::Symbol + value.to_s + else + _escape_attribute(value.to_s) + end + end + + def _ensure_no_block(got_block) + if got_block + ::Kernel::raise IllegalBlockError.new( + "Blocks are not allowed on XML instructions" + ) + end + end + + end + +end diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/rakelib/publish.rake b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/rakelib/publish.rake new file mode 100644 index 00000000..e99586ff --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/rakelib/publish.rake @@ -0,0 +1,21 @@ +# frozen_string_literal: true +# Optional publish task for Rake + +begin +require 'rake/contrib/sshpublisher' +require 'rake/contrib/rubyforgepublisher' + +publisher = Rake::CompositePublisher.new +publisher.add Rake::RubyForgePublisher.new('builder', 'jimweirich') +publisher.add Rake::SshFilePublisher.new( + 'linode', + 'htdocs/software/builder', + '.', + 'builder.blurb') + +desc "Publish the Documentation to RubyForge." +task :publish => [:rdoc] do + publisher.upload +end +rescue LoadError +end diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/rakelib/tags.rake b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/rakelib/tags.rake new file mode 100755 index 00000000..2743f026 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/rakelib/tags.rake @@ -0,0 +1,63 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +module Tags + extend Rake::DSL if defined?(Rake::DSL) + + PROG = ENV['TAGS'] || 'ctags' + + RAKEFILES = FileList['Rakefile', '**/*.rake'] + + FILES = FileList['**/*.rb', '**/*.js'] + RAKEFILES + FILES.exclude('pkg', 'dist') + + PROJECT_DIR = ['.'] + + RVM_GEMDIR = File.join(`rvm gemdir`.strip, "gems") rescue nil + SYSTEM_DIRS = RVM_GEMDIR && File.exist?(RVM_GEMDIR) ? RVM_GEMDIR : [] + + module_function + + # Convert key_word to --key-word. + def keyword(key) + k = key.to_s.gsub(/_/, '-') + (k.length == 1) ? "-#{k}" : "--#{k}" + end + + # Run ctags command + def run(*args) + opts = { + :e => true, + :totals => true, + :recurse => true, + } + opts = opts.merge(args.pop) if args.last.is_a?(Hash) + command_args = opts.map { |k, v| + (v == true) ? keyword(k) : "#{keyword(k)}=#{v}" + }.join(" ") + sh %{#{Tags::PROG} #{command_args} #{args.join(' ')}} + end +end + +namespace "tags" do + desc "Generate an Emacs TAGS file" + task :emacs, [:all] => Tags::FILES do |t, args| + puts "Making Emacs TAGS file" + verbose(true) do + Tags.run(Tags::PROJECT_DIR) + Tags.run(Tags::RAKEFILES, + :language_force => "ruby", + :append => true) + if args.all + Tags::SYSTEM_DIRS.each do |dir| + Tags.run(dir, + :language_force => "ruby", + :append => true) + end + end + end + end +end + +desc "Generate the TAGS file" +task :tags, [:all] => ["tags:emacs"] diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/rakelib/testing.rake b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/rakelib/testing.rake new file mode 100644 index 00000000..dc4888c2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/rakelib/testing.rake @@ -0,0 +1,8 @@ +# frozen_string_literal: true +require 'rake/testtask' + +Rake::TestTask.new do |t| + t.libs << "test" + t.test_files = FileList['test/test*.rb'] + t.verbose = true +end diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/helper.rb b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/helper.rb new file mode 100644 index 00000000..f0d582e5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/helper.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true +require 'minitest/autorun' + +module Builder + class Test < Minitest::Test + alias :assert_raise :assert_raises + alias :assert_not_nil :refute_nil + + def assert_nothing_raised + yield + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/performance.rb b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/performance.rb new file mode 100644 index 00000000..71362f66 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/performance.rb @@ -0,0 +1,42 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true +# encoding: iso-8859-1 + +#-- +# Portions copyright 2004 by Jim Weirich (jim@weirichhouse.org). +# Portions copyright 2005 by Sam Ruby (rubys@intertwingly.net). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +require 'builder/xmlmarkup' +require 'benchmark' + +text = "This is a test of the new xml markup. I�t�rn�ti�n�liz�ti�n\n" * 10000 + +include Benchmark # we need the CAPTION and FMTSTR constants +include Builder +n = 50 +Benchmark.benchmark do |bm| + tf = bm.report("base") { + n.times do + x = XmlMarkup.new + x.text(text) + x.target! + end + } + def XmlMarkup._escape(text) + text.to_xs + end + tf = bm.report("to_xs") { + n.times do + x = XmlMarkup.new + x.text(text) + x.target! + end + } +end + diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/test_eventbuilder.rb b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/test_eventbuilder.rb new file mode 100644 index 00000000..0148712f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/test_eventbuilder.rb @@ -0,0 +1,150 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +#-- +# Portions copyright 2004 by Jim Weirich (jim@weirichhouse.org). +# Portions copyright 2005 by Sam Ruby (rubys@intertwingly.net). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +require 'helper' +require 'builder' +require 'builder/xmlevents' + +class TestEvents < Builder::Test + + class Target + attr_reader :events + + def initialize + @events = [] + end + + def start_tag(tag, attrs) + @events << [:start_tag, tag, attrs] + end + + def end_tag(tag) + @events << [:end_tag, tag] + end + + def text(string) + @events << [:text, string] + end + + end + + + def setup + @target = Target.new + @xml = Builder::XmlEvents.new(:target=>@target) + end + + def test_simple + @xml.one + expect [:start_tag, :one, nil] + expect [:end_tag, :one] + expect_done + end + + def test_nested + @xml.one { @xml.two } + expect [:start_tag, :one, nil] + expect [:start_tag, :two, nil] + expect [:end_tag, :two] + expect [:end_tag, :one] + expect_done + end + + def test_text + @xml.one("a") + expect [:start_tag, :one, nil] + expect [:text, "a"] + expect [:end_tag, :one] + expect_done + end + + def test_special_text + @xml.one("H&R") + expect [:start_tag, :one, nil] + expect [:text, "H&R"] + expect [:end_tag, :one] + expect_done + end + + def test_text_with_entity + @xml.one("H&R") + expect [:start_tag, :one, nil] + expect [:text, "H&R"] + expect [:end_tag, :one] + expect_done + end + + def test_attributes + @xml.a(:b=>"c", :x=>"y") + expect [:start_tag, :a, {:x => "y", :b => "c"}] + expect [:end_tag, :a] + expect_done + end + + def test_moderately_complex + @xml.tag! "address-book" do |x| + x.entry :id=>"1" do + x.name { + x.first "Bill" + x.last "Smith" + } + x.address "Cincinnati" + end + x.entry :id=>"2" do + x.name { + x.first "John" + x.last "Doe" + } + x.address "Columbus" + end + end + expect [:start_tag, "address-book".intern, nil] + expect [:start_tag, :entry, {:id => "1"}] + expect [:start_tag, :name, nil] + expect [:start_tag, :first, nil] + expect [:text, "Bill"] + expect [:end_tag, :first] + expect [:start_tag, :last, nil] + expect [:text, "Smith"] + expect [:end_tag, :last] + expect [:end_tag, :name] + expect [:start_tag, :address, nil] + expect [:text, "Cincinnati"] + expect [:end_tag, :address] + expect [:end_tag, :entry] + expect [:start_tag, :entry, {:id => "2"}] + expect [:start_tag, :name, nil] + expect [:start_tag, :first, nil] + expect [:text, "John"] + expect [:end_tag, :first] + expect [:start_tag, :last, nil] + expect [:text, "Doe"] + expect [:end_tag, :last] + expect [:end_tag, :name] + expect [:start_tag, :address, nil] + expect [:text, "Columbus"] + expect [:end_tag, :address] + expect [:end_tag, :entry] + expect [:end_tag, "address-book".intern] + expect_done + end + + def expect(value) + assert_equal value, @target.events.shift + end + + def expect_done + assert_nil @target.events.shift + end + +end diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/test_markupbuilder.rb b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/test_markupbuilder.rb new file mode 100644 index 00000000..9e0b432c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/test_markupbuilder.rb @@ -0,0 +1,616 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +#-- +# Portions copyright 2004 by Jim Weirich (jim@weirichhouse.org). +# Portions copyright 2005 by Sam Ruby (rubys@intertwingly.net). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +require 'helper' +require 'builder' +require 'builder/xmlmarkup' + +class TestMarkup < Builder::Test + def setup + @xml = Builder::XmlMarkup.new + end + + def test_create + assert_not_nil @xml + end + + def test_simple + @xml.simple + assert_equal "", @xml.target! + end + + def test_value + @xml.value("hi") + assert_equal "hi", @xml.target! + end + + def test_empty_value + @xml.value("") + assert_equal "", @xml.target! + end + + def test_nil_value + @xml.value(nil) + assert_equal "", @xml.target! + end + + def test_no_value + @xml.value() + assert_equal "", @xml.target! + end + + def test_nested + @xml.outer { |x| x.inner("x") } + assert_equal "x", @xml.target! + end + + def test_attributes + @xml.ref(:id => 12) + assert_equal %{}, @xml.target! + end + + def test_single_quotes_for_attrs + @xml = Builder::XmlMarkup.new(:quote => :single) + @xml.ref(:id => 12) + assert_equal %{}, @xml.target! + end + + def test_mixed_quotes_for_attrs + @xml = Builder::XmlMarkup.new(:quote => :single) + x = Builder::XmlMarkup.new(:target=>@xml, :quote => :double) + @xml.ref(:id => 12) do + x.link(:id => 13) + end + assert_equal %{}, @xml.target! + end + + def test_string_attributes_are_escaped_by_default + @xml.ref(:id => "H&R") + assert_equal %{}, @xml.target! + end + + def test_symbol_attributes_are_unescaped_by_default + @xml.ref(:id => :"H&R") + assert_equal %{}, @xml.target! + end + + def test_attributes_escaping_can_be_turned_on + @xml = Builder::XmlMarkup.new + @xml.ref(:id => "") + assert_equal %{}, @xml.target! + end + + def test_mixed_attribute_escaping_with_nested_builders + x = Builder::XmlMarkup.new(:target=>@xml) + @xml.ref(:id=>:"H&R") { + x.element(:tag=>"Long&Short") + } + assert_equal "", + @xml.target! + end + + def test_multiple_attributes + @xml.ref(:id => 12, :name => "bill") + assert_match %r{^$}, @xml.target! + end + + def test_attributes_with_text + @xml.a("link", :href=>"http://onestepback.org") + assert_equal %{link}, @xml.target! + end + + def test_attributes_with_newlines + @xml.abbr("W3C", :title=>"World\nWide\rWeb\r\nConsortium") + assert_equal %{W3C}, + @xml.target! + end + + def test_complex + @xml.body(:bg=>"#ffffff") { |x| + x.title("T", :style=>"red") + } + assert_equal %{T}, @xml.target! + end + + def test_funky_symbol + @xml.tag!("non-ruby-token", :id=>1) { |x| x.ok } + assert_equal %{}, @xml.target! + end + + def test_tag_can_handle_private_method + @xml.tag!("loop", :id=>1) { |x| x.ok } + assert_equal %{}, @xml.target! + end + + def test_no_explicit_marker + @xml.p { |x| x.b("HI") } + assert_equal "

    HI

    ", @xml.target! + end + + def test_reference_local_vars + n = 3 + @xml.ol { |x| n.times { x.li(n) } } + assert_equal "
    1. 3
    2. 3
    3. 3
    ", @xml.target! + end + + def test_reference_methods + @xml.title { |x| x.a { x.b(_name) } } + assert_equal "<a><b>bob</b></a>", @xml.target! + end + + def test_append_text + @xml.p { |x| x.br; x.text! "HI" } + assert_equal "


    HI

    ", @xml.target! + end + + def test_ambiguous_markup + ex = assert_raise(ArgumentError) { + @xml.h1("data1") { b } + } + assert_match(/\btext\b/, ex.message) + assert_match(/\bblock\b/, ex.message) + end + + def test_capitalized_method + @xml.P { |x| x.B("hi"); x.BR(); x.EM { x.text! "world" } } + assert_equal "

    hi
    world

    ", @xml.target! + end + + def test_escaping + @xml.div { |x| x.text! ""; x.em("H&R Block") } + assert_equal %{
    <hi>H&R Block
    }, @xml.target! + end + + def test_nil + b = Builder::XmlMarkup.new + b.tag! "foo", nil + assert_equal %{}, b.target! + end + + def test_nil_without_explicit_nil_handling + b = Builder::XmlMarkup.new(:explicit_nil_handling => false) + b.tag! "foo", nil + assert_equal %{}, b.target! + end + + def test_nil_with_explicit_nil_handling + b = Builder::XmlMarkup.new(:explicit_nil_handling => true) + b.tag! "foo", nil + assert_equal %{}, b.target! + end + + def test_non_escaping + @xml.div("ns:xml"=>:"&xml;") { |x| x << ""; x.em("H&R Block") } + assert_equal %{
    H&R Block
    }, @xml.target! + end + + def test_return_value + str = @xml.x("men") + assert_equal @xml.target!, str + end + + def test_stacked_builders + b = Builder::XmlMarkup.new( :target => @xml ) + b.div { @xml.span { @xml.a("text", :href=>"ref") } } + assert_equal "", @xml.target! + end + + def _name + "bob" + end +end + +class TestAttributeEscaping < Builder::Test + + def setup + @xml = Builder::XmlMarkup.new + end + + def test_element_gt + @xml.title('1<2') + assert_equal '1<2', @xml.target! + end + + def test_element_amp + @xml.title('AT&T') + assert_equal 'AT&T', @xml.target! + end + + def test_element_amp2 + @xml.title('&') + assert_equal '&amp;', @xml.target! + end + + def test_attr_less + @xml.a(:title => '2>1') + assert_equal '', @xml.target! + end + + def test_attr_amp + @xml.a(:title => 'AT&T') + assert_equal '', @xml.target! + end + + def test_attr_quot + @xml.a(:title => '"x"') + assert_equal '', @xml.target! + end + +end + +class TestNameSpaces < Builder::Test + def setup + @xml = Builder::XmlMarkup.new(:indent=>2) + end + + def test_simple_name_spaces + @xml.rdf :RDF + assert_equal "\n", @xml.target! + end + + def test_long + xml = Builder::XmlMarkup.new(:indent=>2) + xml.instruct! + xml.rdf :RDF, + "xmlns:rdf" => :"&rdf;", + "xmlns:rdfs" => :"&rdfs;", + "xmlns:xsd" => :"&xsd;", + "xmlns:owl" => :"&owl;" do + xml.owl :Class, :'rdf:ID'=>'Bird' do + xml.rdfs :label, 'bird' + xml.rdfs :subClassOf do + xml.owl :Restriction do + xml.owl :onProperty, 'rdf:resource'=>'#wingspan' + xml.owl :maxCardinality,1,'rdf:datatype'=>'&xsd;nonNegativeInteger' + end + end + end + end + assert_match(/^<\?xml/, xml.target!) + assert_match(/\n/m, xml.target!) + end + + def test_ensure + xml = Builder::XmlMarkup.new + xml.html do + xml.body do + begin + xml.p do + raise Exception.new('boom') + end + rescue Exception => e + xml.pre e + end + end + end + assert_match %r{

    }, xml.target! + assert_match %r{

    }, xml.target! + end +end + +class TestDeclarations < Builder::Test + def setup + @xml = Builder::XmlMarkup.new(:indent=>2) + end + + def test_declare + @xml.declare! :element + assert_equal "\n", @xml.target! + end + + def test_bare_arg + @xml.declare! :element, :arg + assert_equal"\n", @xml.target! + end + + def test_string_arg + @xml.declare! :element, "string" + assert_equal"\n", @xml.target! + end + + def test_mixed_args + @xml.declare! :element, :x, "y", :z, "-//OASIS//DTD DocBook XML//EN" + assert_equal "\n", @xml.target! + end + + def test_nested_declarations + @xml = Builder::XmlMarkup.new + @xml.declare! :DOCTYPE, :chapter do |x| + x.declare! :ELEMENT, :chapter, "(title,para+)".intern + end + assert_equal "]>", @xml.target! + end + + def test_nested_indented_declarations + @xml.declare! :DOCTYPE, :chapter do |x| + x.declare! :ELEMENT, :chapter, "(title,para+)".intern + end + assert_equal "\n]>\n", @xml.target! + end + + def test_complex_declaration + @xml.declare! :DOCTYPE, :chapter do |x| + x.declare! :ELEMENT, :chapter, "(title,para+)".intern + x.declare! :ELEMENT, :title, "(#PCDATA)".intern + x.declare! :ELEMENT, :para, "(#PCDATA)".intern + end + expected = %{ + + +]> +} + assert_equal expected, @xml.target! + end +end + + +class TestSpecialMarkup < Builder::Test + def setup + @xml = Builder::XmlMarkup.new(:indent=>2) + end + + def test_comment + @xml.comment!("COMMENT") + assert_equal "\n", @xml.target! + end + + def test_indented_comment + @xml.p { @xml.comment! "OK" } + assert_equal "

    \n \n

    \n", @xml.target! + end + + def test_instruct + @xml.instruct! :abc, :version=>"0.9" + assert_equal "\n", @xml.target! + end + + def test_indented_instruct + @xml.p { @xml.instruct! :xml } + assert_match %r{

    \n <\?xml version="1.0" encoding="UTF-8"\?>\n

    \n}, + @xml.target! + end + + def test_instruct_without_attributes + @xml.instruct! :zz + assert_equal "\n", @xml.target! + end + + def test_xml_instruct + @xml.instruct! + assert_match(/^<\?xml version="1.0" encoding="UTF-8"\?>$/, @xml.target!) + end + + def test_xml_instruct_with_overrides + @xml.instruct! :xml, :encoding=>"UCS-2" + assert_match(/^<\?xml version="1.0" encoding="UCS-2"\?>$/, @xml.target!) + end + + def test_xml_instruct_with_standalong + @xml.instruct! :xml, :encoding=>"UCS-2", :standalone=>"yes" + assert_match(/^<\?xml version="1.0" encoding="UCS-2" standalone="yes"\?>$/, @xml.target!) + end + + def test_no_blocks + assert_raise(Builder::IllegalBlockError) do + @xml.instruct! { |x| x.hi } + end + assert_raise(Builder::IllegalBlockError) do + @xml.comment!(:element) { |x| x.hi } + end + end + + def test_cdata + @xml.cdata!("TEST") + assert_equal "\n", @xml.target! + end + + def test_cdata_value + @xml.cdata_value!("content:encoded", "

    TEST

    ") + assert_equal "TEST

    ]]>
    \n", @xml.target! + end + + def test_cdata_with_ampersand + @xml.cdata!("TEST&CHECK") + assert_equal "\n", @xml.target! + end + + def test_cdata_with_included_close + @xml.cdata!("TEST]]>CHECK") + assert_equal "CHECK]]>\n", @xml.target! + end +end + +class TestIndentedXmlMarkup < Builder::Test + def setup + @xml = Builder::XmlMarkup.new(:indent=>2) + end + + def test_one_level + @xml.ol { |x| x.li "text" } + assert_equal "
      \n
    1. text
    2. \n
    \n", @xml.target! + end + + def test_two_levels + @xml.p { |x| + x.ol { x.li "text" } + x.br + } + assert_equal "

    \n

      \n
    1. text
    2. \n
    \n
    \n

    \n", @xml.target! + end + + def test_initial_level + @xml = Builder::XmlMarkup.new(:indent=>2, :margin=>4) + @xml.name { |x| x.first("Jim") } + assert_equal " \n Jim\n \n", @xml.target! + end + + class TestUtfMarkup < Builder::Test + if ! String.method_defined?(:encode) + def setup + @old_kcode = $KCODE + end + + def teardown + $KCODE = @old_kcode + end + + def test_use_entities_if_no_encoding_is_given_and_kcode_is_none + $KCODE = 'NONE' + xml = Builder::XmlMarkup.new + xml.p("\xE2\x80\x99") + assert_match(%r(

    ), xml.target!) # + end + + def test_use_entities_if_encoding_is_utf_but_kcode_is_not + $KCODE = 'NONE' + xml = Builder::XmlMarkup.new + xml.instruct!(:xml, :encoding => 'UTF-8') + xml.p("\xE2\x80\x99") + assert_match(%r(

    ), xml.target!) # + end + else + # change in behavior. As there is no $KCODE anymore, the default + # moves from "does not understand utf-8" to "supports utf-8". + + def test_use_entities_if_no_encoding_is_given_and_kcode_is_none + xml = Builder::XmlMarkup.new + xml.p("\xE2\x80\x99") + assert_match("

    \u2019

    ", xml.target!) # + end + + def test_use_entities_if_encoding_is_utf_but_kcode_is_not + xml = Builder::XmlMarkup.new + xml.instruct!(:xml, :encoding => 'UTF-8') + xml.p("\xE2\x80\x99") + assert_match("

    \u2019

    ", xml.target!) # + end + end + + def encode string, encoding + if !String.method_defined?(:encode) + $KCODE = encoding + string + elsif encoding == 'UTF8' + string.dup.force_encoding('UTF-8') + else + string + end + end + + def test_use_entities_if_kcode_is_utf_but_encoding_is_dummy_encoding + xml = Builder::XmlMarkup.new + xml.instruct!(:xml, :encoding => 'UTF-16') + xml.p(encode("\xE2\x80\x99", 'UTF8')) + assert_match(%r(

    ), xml.target!) # + end + + def test_use_entities_if_kcode_is_utf_but_encoding_is_unsupported_encoding + xml = Builder::XmlMarkup.new + xml.instruct!(:xml, :encoding => 'UCS-2') + xml.p(encode("\xE2\x80\x99", 'UTF8')) + assert_match(%r(

    ), xml.target!) # + end + + def test_use_utf8_if_encoding_defaults_and_kcode_is_utf8 + xml = Builder::XmlMarkup.new + xml.p(encode("\xE2\x80\x99",'UTF8')) + assert_equal encode("

    \xE2\x80\x99

    ",'UTF8'), xml.target! + end + + def test_use_utf8_if_both_encoding_and_kcode_are_utf8 + xml = Builder::XmlMarkup.new + xml.instruct!(:xml, :encoding => 'UTF-8') + xml.p(encode("\xE2\x80\x99",'UTF8')) + assert_match encode("

    \xE2\x80\x99

    ",'UTF8'), xml.target! + end + + def test_use_utf8_if_both_encoding_and_kcode_are_utf8_with_lowercase + xml = Builder::XmlMarkup.new + xml.instruct!(:xml, :encoding => 'utf-8') + xml.p(encode("\xE2\x80\x99",'UTF8')) + assert_match encode("

    \xE2\x80\x99

    ",'UTF8'), xml.target! + end + end + + class TestXmlEvents < Builder::Test + def setup + @handler = EventHandler.new + @xe = Builder::XmlEvents.new(:target=>@handler) + end + + def test_simple + @xe.p + assert_equal [:start, :p, nil], @handler.events.shift + assert_equal [:end, :p], @handler.events.shift + end + + def test_text + @xe.p("HI") + assert_equal [:start, :p, nil], @handler.events.shift + assert_equal [:text, "HI"], @handler.events.shift + assert_equal [:end, :p], @handler.events.shift + end + + def test_attributes + @xe.p("id"=>"2") + ev = @handler.events.shift + assert_equal [:start, :p], ev[0,2] + assert_equal "2", ev[2]['id'] + assert_equal [:end, :p], @handler.events.shift + end + + def test_indented + @xml = Builder::XmlEvents.new(:indent=>2, :target=>@handler) + @xml.p { |x| x.b("HI") } + assert_equal [:start, :p, nil], @handler.events.shift + assert_equal "\n ", pop_text + assert_equal [:start, :b, nil], @handler.events.shift + assert_equal "HI", pop_text + assert_equal [:end, :b], @handler.events.shift + assert_equal "\n", pop_text + assert_equal [:end, :p], @handler.events.shift + end + + def pop_text + result = ''.dup + while ! @handler.events.empty? && @handler.events[0][0] == :text + result << @handler.events[0][1] + @handler.events.shift + end + result + end + + class EventHandler + attr_reader :events + def initialize + @events = [] + end + + def start_tag(sym, attrs) + @events << [:start, sym, attrs] + end + + def end_tag(sym) + @events << [:end, sym] + end + + def text(txt) + @events << [:text, txt] + end + end + end + +end diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/test_method_caching.rb b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/test_method_caching.rb new file mode 100644 index 00000000..c7c88028 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/test_method_caching.rb @@ -0,0 +1,62 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +#-- +# Portions copyright 2011 by Bart ten Brinke (info@retrosync.com). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +require 'helper' +require 'builder' + +class TestMethodCaching < Builder::Test + + # We can directly ask if xml object responds to the cache_me or + # do_not_cache_me methods because xml is derived from BasicObject + # (and repond_to? is not defined in BasicObject). + # + # Instead we are going to stub out method_missing so that it throws + # an error, and then make sure that error is either thrown or not + # thrown as appropriate. + + def teardown + super + Builder::XmlBase.cache_method_calls = true + end + + def test_caching_does_not_break_weird_symbols + xml = Builder::XmlMarkup.new + xml.__send__("work-order", 1) + assert_equal "1", xml.target! + end + + def test_method_call_caching + xml = Builder::XmlMarkup.new + xml.cache_me + + def xml.method_missing(*args) + ::Kernel.fail StandardError, "SHOULD NOT BE CALLED" + end + assert_nothing_raised do + xml.cache_me + end + end + + def test_method_call_caching_disabled + Builder::XmlBase.cache_method_calls = false + xml = Builder::XmlMarkup.new + xml.do_not_cache_me + + def xml.method_missing(*args) + ::Kernel.fail StandardError, "SHOULD BE CALLED" + end + assert_raise(StandardError, "SHOULD BE CALLED") do + xml.do_not_cache_me + end + end + +end diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/test_namecollision.rb b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/test_namecollision.rb new file mode 100644 index 00000000..5b24c916 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/test_namecollision.rb @@ -0,0 +1,40 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +#-- +# Portions copyright 2004 by Jim Weirich (jim@weirichhouse.org). +# Portions copyright 2005 by Sam Ruby (rubys@intertwingly.net). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +require 'helper' +require 'builder/xchar' + +class TestNameCollisions < Builder::Test + module Collide + def xchr + end + end + + def test_no_collision + assert_nothing_raised do + Builder.check_for_name_collision(Collide, :not_defined) + end + end + + def test_collision + assert_raise RuntimeError do + Builder.check_for_name_collision(Collide, "xchr") + end + end + + def test_collision_with_symbol + assert_raise RuntimeError do + Builder.check_for_name_collision(Collide, :xchr) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/test_xchar.rb b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/test_xchar.rb new file mode 100644 index 00000000..745589b7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/builder-3.3.0/test/test_xchar.rb @@ -0,0 +1,85 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true +# encoding: us-ascii + +#-- +# Portions copyright 2004 by Jim Weirich (jim@weirichhouse.org). +# Portions copyright 2005 by Sam Ruby (rubys@intertwingly.net). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +#!/usr/bin/env ruby + +require 'helper' +require 'builder/xchar' + +if String.method_defined?(:encode) + class String + ENCODING_BINARY = Encoding.find('BINARY') + + # shim method for testing purposes + def to_xs(escape=true) + # raise NameError.new('to_xs') unless caller[0].index(__FILE__) + + result = Builder::XChar.encode(self.dup) + if escape + result.gsub(/[^\u0000-\u007F]/) {|c| "&##{c.ord};"} + else + # really only useful for testing purposes + result.force_encoding(ENCODING_BINARY) + end + end + end +end + +class TestXmlEscaping < Builder::Test + REPLACEMENT_CHAR = Builder::XChar::REPLACEMENT_CHAR.to_xs + + def test_ascii + assert_equal 'abc', 'abc'.to_xs + end + + def test_predefined + assert_equal '&', '&'.to_xs # ampersand + assert_equal '<', '<'.to_xs # left angle bracket + assert_equal '>', '>'.to_xs # right angle bracket + end + + def test_invalid + assert_equal REPLACEMENT_CHAR, "\x00".to_xs # null + assert_equal REPLACEMENT_CHAR, "\x0C".to_xs # form feed + assert_equal REPLACEMENT_CHAR, "\xEF\xBF\xBF".to_xs # U+FFFF + end + + def test_iso_8859_1 + assert_equal 'ç', "\xE7".to_xs # small c cedilla + assert_equal '©', "\xA9".to_xs # copyright symbol + end + + def test_win_1252 + assert_equal '’', "\x92".to_xs # smart quote + assert_equal '€', "\x80".to_xs # euro + end + + def test_utf8 + assert_equal '’', "\xE2\x80\x99".to_xs # right single quote + assert_equal '©', "\xC2\xA9".to_xs # copy + end + + def test_utf8_verbatim + assert_equal binary("\xE2\x80\x99"), "\xE2\x80\x99".to_xs(false) # right single quote + assert_equal binary("\xC2\xA9"), "\xC2\xA9".to_xs(false) # copy + assert_equal binary("\xC2\xA9&\xC2\xA9"), + "\xC2\xA9&\xC2\xA9".to_xs(false) # copy with ampersand + end + + private + + def binary(string) + string.dup.force_encoding(Encoding::BINARY) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/CHANGELOG.md b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/CHANGELOG.md new file mode 100644 index 00000000..30810b72 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/CHANGELOG.md @@ -0,0 +1,603 @@ +## Current + +## Release v1.3.5, edge v0.7.2 (15 January 2025) + +concurrent-ruby: + +* (#1062) Remove dependency on logger. + +concurrent-ruby-edge: + +* (#1062) Remove dependency on logger. + +## Release v1.3.4 (10 August 2024) + +* (#1060) Fix bug with return value of `Concurrent.available_processor_count` when `cpu.cfs_quota_us` is -1. +* (#1058) Add `Concurrent.cpu_shares` that is cgroups aware. + +## Release v1.3.3 (9 June 2024) + +* (#1053) Improve the speed of `Concurrent.physical_processor_count` on Windows. + +## Release v1.3.2, edge v0.7.1 (7 June 2024) + +concurrent-ruby: + +* (#1051) Remove dependency on `win32ole`. + +concurrent-ruby-edge: + +* (#1052) Fix dependency on `concurrent-ruby` to allow the latest release. + +## Release v1.3.1 (29 May 2024) + +* Release 1.3.0 was broken when pushed to RubyGems. 1.3.1 is a packaging fix. + +## Release v1.3.0 (28 May 2024) + +* (#1042) Align Java Executor Service behavior for `shuttingdown?`, `shutdown?` +* (#1038) Add `Concurrent.available_processor_count` that is cgroups aware. + +## Release v1.2.3 (16 Jan 2024) + +* See [the GitHub release](https://github.com/ruby-concurrency/concurrent-ruby/releases/tag/v1.2.3) for details. + +## Release v1.2.2 (24 Feb 2023) + +* (#993) Fix arguments passed to `Concurrent::Map`'s `default_proc`. + +## Release v1.2.1 (24 Feb 2023) + +* (#990) Add missing `require 'fiber'` for `FiberLocalVar`. +* (#989) Optimize `Concurrent::Map#[]` on CRuby by letting the backing Hash handle the `default_proc`. + +## Release v1.2.0 (23 Jan 2023) + +* (#962) Fix ReentrantReadWriteLock to use the same granularity for locals as for Mutex it uses. +* (#983) Add FiberLocalVar +* (#934) concurrent-ruby now supports requiring individual classes (public classes listed in the docs), e.g., `require 'concurrent/map'` +* (#976) Let `Promises.any_fulfilled_future` take an `Event` +* Improve documentation of various classes +* (#975) Set the Ruby compatibility version at 2.3 +* (#972) Remove Rubinius-related code + +## Release v1.1.10 (22 Mar 2022) + +concurrent-ruby: + +* (#951) Set the Ruby compatibility version at 2.2 +* (#939, #933) The `caller_runs` fallback policy no longer blocks reads from the job queue by worker threads +* (#938, #761, #652) You can now explicitly `prune_pool` a thread pool (Sylvain Joyeux) +* (#937, #757, #670) We switched the Yahoo stock API for demos to Alpha Vantage (Gustavo Caso) +* (#932, #931) We changed how `SafeTaskExecutor` handles local jump errors (Aaron Jensen) +* (#927) You can use keyword arguments in your initialize when using `Async` (Matt Larraz) +* (#926, #639) We removed timeout from `TimerTask` because it wasn't sound, and now it's a no-op with a warning (Jacob Atzen) +* (#919) If you double-lock a re-entrant read-write lock, we promote to locked for writing (zp yuan) +* (#915) `monotonic_time` now accepts an optional unit parameter, as Ruby's `clock_gettime` (Jean Boussier) + +## Release v1.1.9 (5 Jun 2021) + +concurrent-ruby: + +* (#866) Child promise state not set to :pending immediately after #execute when parent has completed +* (#905, #872) Fix RubyNonConcurrentPriorityQueue#delete method +* (2df0337d) Make sure locks are not shared on shared when objects are dup/cloned +* (#900, #906, #796, #847, #911) Fix Concurrent::Set tread-safety issues on CRuby +* (#907) Add new ConcurrentMap backend for TruffleRuby + +## Release v1.1.8 (20 January 2021) + +concurrent-ruby: + +* (#885) Fix race condition in TVar for stale reads +* (#884) RubyThreadLocalVar: Do not iterate over hash which might conflict with new pair addition + +## Release v1.1.7 (6 August 2020) + +concurrent-ruby: + +* (#879) Consider falsy value on `Concurrent::Map#compute_if_absent` for fast non-blocking path +* (#876) Reset Async queue on forking, makes Async fork-safe +* (#856) Avoid running problematic code in RubyThreadLocalVar on MRI that occasionally results in segfault +* (#853) Introduce ThreadPoolExecutor without a Queue + +## Release v1.1.6, edge v0.6.0 (10 Feb 2020) + +concurrent-ruby: + +* (#841) Concurrent.disable_at_exit_handlers! is no longer needed and was deprecated. +* (#841) AbstractExecutorService#auto_terminate= was deprecated and has no effect. + Set :auto_terminate option instead when executor is initialized. + +## Release v1.1.6.pre1, edge v0.6.0.pre1 (26 Jan 2020) + +concurrent-ruby: + +* (#828) Allow to name executors, the name is also used to name their threads +* (#838) Implement #dup and #clone for structs +* (#821) Safer finalizers for thread local variables +* Documentation fixes +* (#814) Use Ruby's Etc.nprocessors if available +* (#812) Fix directory structure not to mess with packaging tools +* (#840) Fix termination of pools on JRuby + +concurrent-ruby-edge: + +* Add WrappingExecutor (#830) + +## Release v1.1.5, edge v0.5.0 (10 Mar 2019) + +concurrent-ruby: + +* fix potential leak of context on JRuby and Java 7 + +concurrent-ruby-edge: + +* Add finalized Concurrent::Cancellation +* Add finalized Concurrent::Throttle +* Add finalized Concurrent::Promises::Channel +* Add new Concurrent::ErlangActor + +## Release v1.1.4 (14 Dec 2018) + +* (#780) Remove java_alias of 'submit' method of Runnable to let executor service work on java 11 +* (#776) Fix NameError on defining a struct with a name which is already taken in an ancestor + +## Release v1.1.3 (7 Nov 2018) + +* (#775) fix partial require of the gem (although not officially supported) + +## Release v1.1.2 (6 Nov 2018) + +* (#773) more defensive 1.9.3 support + +## Release v1.1.1, edge v0.4.1 (1 Nov 2018) + +* (#768) add support for 1.9.3 back + +## Release v1.1.0, edge v0.4.0 (31 OCt 2018) (yanked) + +* (#768) yanked because of issues with removed 1.9.3 support + +## Release v1.1.0.pre2, edge v0.4.0.pre2 (18 Sep 2018) + +concurrent-ruby: + +* fixed documentation and README links +* fix Set for TruffleRuby and Rubinius +* use properly supported TruffleRuby APIs + +concurrent-ruby-edge: + +* add Promises.zip_futures_over_on + +## Release v1.1.0.pre1, edge v0.4.0.pre1 (15 Aug 2018) + +concurrent-ruby: + +* requires at least Ruby 2.0 +* [Promises](http://ruby-concurrency.github.io/concurrent-ruby/1.1.0/Concurrent/Promises.html) + are moved from `concurrent-ruby-edge` to `concurrent-ruby` +* Add support for TruffleRuby + * (#734) Fix Array/Hash/Set construction broken on TruffleRuby + * AtomicReference fixed +* CI stabilization +* remove sharp dependency edge -> core +* remove warnings +* documentation updates +* Exchanger is no longer documented as edge since it was already available in + `concurrent-ruby` +* (#644) Fix Map#each and #each_pair not returning enumerator outside of MRI +* (#659) Edge promises fail during error handling +* (#741) Raise on recursive Delay#value call +* (#727) #717 fix global IO executor on JRuby +* (#740) Drop support for CRuby 1.9, JRuby 1.7, Rubinius. +* (#737) Move AtomicMarkableReference out of Edge +* (#708) Prefer platform specific memory barriers +* (#735) Fix wrong expected exception in channel spec assertion +* (#729) Allow executor option in `Promise#then` +* (#725) fix timeout check to use timeout_interval +* (#719) update engine detection +* (#660) Add specs for Promise#zip/Promise.zip ordering +* (#654) Promise.zip execution changes +* (#666) Add thread safe set implementation +* (#651) #699 #to_s, #inspect should not output negative object IDs. +* (#685) Avoid RSpec warnings about raise_error +* (#680) Avoid RSpec monkey patching, persist spec results locally, use RSpec + v3.7.0 +* (#665) Initialize the monitor for new subarrays on Rubinius +* (#661) Fix error handling in edge promises + +concurrent-ruby-edge: + +* (#659) Edge promises fail during error handling +* Edge files clearly separated in `lib-edge` +* added ReInclude + +## Release v1.0.5, edge v0.3.1 (26 Feb 2017) + +concurrent-ruby: + +* Documentation for Event and Semaphore +* Use Unsafe#fullFence and #loadFence directly since the shortcuts were removed in JRuby +* Do not depend on org.jruby.util.unsafe.UnsafeHolder + +concurrent-ruby-edge: + +* (#620) Actors on Pool raise an error +* (#624) Delayed promises did not interact correctly with flatting + * Fix arguments yielded by callback methods +* Overridable default executor in promises factory methods +* Asking actor to terminate will always resolve to `true` + +## Release v1.0.4, edge v0.3.0 (27 Dec 2016) + +concurrent-ruby: + +* Nothing + +concurrent-ruby-edge: + +* New promises' API renamed, lots of improvements, edge bumped to 0.3.0 + * **Incompatible** with previous 0.2.3 version + * see https://github.com/ruby-concurrency/concurrent-ruby/pull/522 + +## Release v1.0.3 (17 Dec 2016) + +* Trigger execution of flattened delayed futures +* Avoid forking for processor_count if possible +* Semaphore Mutex and JRuby parity +* Adds Map#each as alias to Map#each_pair +* Fix uninitialized instance variables +* Make Fixnum, Bignum merger ready +* Allows Promise#then to receive an executor +* TimerSet now survives a fork +* Reject promise on any exception +* Allow ThreadLocalVar to be initialized with a block +* Support Alpha with `Concurrent::processor_count` +* Fixes format-security error when compiling ruby_193_compatible.h +* Concurrent::Atom#swap fixed: reraise the exceptions from block + +## Release v1.0.2 (2 May 2016) + +* Fix bug with `Concurrent::Map` MRI backend `#inspect` method +* Fix bug with `Concurrent::Map` MRI backend using `Hash#value?` +* Improved documentation and examples +* Minor updates to Edge + +## Release v1.0.1 (27 February 2016) + +* Fix "uninitialized constant Concurrent::ReentrantReadWriteLock" error. +* Better handling of `autoload` vs. `require`. +* Improved API for Edge `Future` zipping. +* Fix reference leak in Edge `Future` constructor . +* Fix bug which prevented thread pools from surviving a `fork`. +* Fix bug in which `TimerTask` did not correctly specify all its dependencies. +* Improved support for JRuby+Truffle +* Improved error messages. +* Improved documentation. +* Updated README and CONTRIBUTING. + +## Release v1.0.0 (13 November 2015) + +* Rename `attr_volatile_with_cas` to `attr_atomic` +* Add `clear_each` to `LockFreeStack` +* Update `AtomicReference` documentation +* Further updates and improvements to the synchronization layer. +* Performance and memory usage performance with `Actor` logging. +* Fixed `ThreadPoolExecutor` task count methods. +* Improved `Async` performance for both short and long-lived objects. +* Fixed bug in `LockFreeLinkedSet`. +* Fixed bug in which `Agent#await` triggered a validation failure. +* Further `Channel` updates. +* Adopted a project Code of Conduct +* Cleared interpreter warnings +* Fixed bug in `ThreadPoolExecutor` task count methods +* Fixed bug in 'LockFreeLinkedSet' +* Improved Java extension loading +* Handle Exception children in Edge::Future +* Continued improvements to channel +* Removed interpreter warnings. +* Shared constants now in `lib/concurrent/constants.rb` +* Refactored many tests. +* Improved synchronization layer/memory model documentation. +* Bug fix in Edge `Future#flat` +* Brand new `Channel` implementation in Edge gem. +* Simplification of `RubySingleThreadExecutor` +* `Async` improvements + - Each object uses its own `SingleThreadExecutor` instead of the global thread pool. + - No longer supports executor injection + - Much better documentation +* `Atom` updates + - No longer `Dereferenceable` + - Now `Observable` + - Added a `#reset` method +* Brand new `Agent` API and implementation. Now functionally equivalent to Clojure. +* Continued improvements to the synchronization layer +* Merged in the `thread_safe` gem + - `Concurrent::Array` + - `Concurrent::Hash` + - `Concurrent::Map` (formerly ThreadSafe::Cache) + - `Concurrent::Tuple` +* Minor improvements to Concurrent::Map +* Complete rewrite of `Exchanger` +* Removed all deprecated code (classes, methods, constants, etc.) +* Updated Agent, MutexAtomic, and BufferedChannel to inherit from Synchronization::Object. +* Many improved tests +* Some internal reorganization + +## Release v0.9.1 (09 August 2015) + +* Fixed a Rubiniux bug in synchronization object +* Fixed all interpreter warnings (except circular references) +* Fixed require statements when requiring `Atom` alone +* Significantly improved `ThreadLocalVar` on non-JRuby platforms +* Fixed error handling in Edge `Concurrent.zip` +* `AtomicFixnum` methods `#increment` and `#decrement` now support optional delta +* New `AtomicFixnum#update` method +* Minor optimizations in `ReadWriteLock` +* New `ReentrantReadWriteLock` class +* `ThreadLocalVar#bind` method is now public +* Refactored many tests + +## Release v0.9.0 (10 July 2015) + +* Updated `AtomicReference` + - `AtomicReference#try_update` now simply returns instead of raising exception + - `AtomicReference#try_update!` was added to raise exceptions if an update + fails. Note: this is the same behavior as the old `try_update` +* Pure Java implementations of + - `AtomicBoolean` + - `AtomicFixnum` + - `Semaphore` +* Fixed bug when pruning Ruby thread pools +* Fixed bug in time calculations within `ScheduledTask` +* Default `count` in `CountDownLatch` to 1 +* Use monotonic clock for all timers via `Concurrent.monotonic_time` + - Use `Process.clock_gettime(Process::CLOCK_MONOTONIC)` when available + - Fallback to `java.lang.System.nanoTime()` on unsupported JRuby versions + - Pure Ruby implementation for everything else + - Effects `Concurrent.timer`, `Concurrent.timeout`, `TimerSet`, `TimerTask`, and `ScheduledTask` +* Deprecated all clock-time based timer scheduling + - Only support scheduling by delay + - Effects `Concurrent.timer`, `TimerSet`, and `ScheduledTask` +* Added new `ReadWriteLock` class +* Consistent `at_exit` behavior for Java and Ruby thread pools. +* Added `at_exit` handler to Ruby thread pools (already in Java thread pools) + - Ruby handler stores the object id and retrieves from `ObjectSpace` + - JRuby disables `ObjectSpace` by default so that handler stores the object reference +* Added a `:stop_on_exit` option to thread pools to enable/disable `at_exit` handler +* Updated thread pool docs to better explain shutting down thread pools +* Simpler `:executor` option syntax for all abstractions which support this option +* Added `Executor#auto_terminate?` predicate method (for thread pools) +* Added `at_exit` handler to `TimerSet` +* Simplified auto-termination of the global executors + - Can now disable auto-termination of global executors + - Added shutdown/kill/wait_for_termination variants for global executors +* Can now disable auto-termination for *all* executors (the nuclear option) +* Simplified auto-termination of the global executors +* Deprecated terms "task pool" and "operation pool" + - New terms are "io executor" and "fast executor" + - New functions added with new names + - Deprecation warnings added to functions referencing old names +* Moved all thread pool related functions from `Concurrent::Configuration` to `Concurrent` + - Old functions still exist with deprecation warnings + - New functions have updated names as appropriate +* All high-level abstractions default to the "io executor" +* Fixed bug in `Actor` causing it to prematurely warm global thread pools on gem load + - This also fixed a `RejectedExecutionError` bug when running with minitest/autorun via JRuby +* Moved global logger up to the `Concurrent` namespace and refactored the code +* Optimized the performance of `Delay` + - Fixed a bug in which no executor option on construction caused block execution on a global thread pool +* Numerous improvements and bug fixes to `TimerSet` +* Fixed deadlock of `Future` when the handler raises Exception +* Added shared specs for more classes +* New concurrency abstractions including: + - `Atom` + - `Maybe` + - `ImmutableStruct` + - `MutableStruct` + - `SettableStruct` +* Created an Edge gem for unstable abstractions including + - `Actor` + - `Agent` + - `Channel` + - `Exchanger` + - `LazyRegister` + - **new Future Framework** - unified + implementation of Futures and Promises which combines Features of previous `Future`, + `Promise`, `IVar`, `Event`, `Probe`, `dataflow`, `Delay`, `TimerTask` into single framework. It uses extensively + new synchronization layer to make all the paths **lock-free** with exception of blocking threads on `#wait`. + It offers better performance and does not block threads when not required. +* Actor framework changes: + - fixed reset loop in Pool + - Pool can use any actor as a worker, abstract worker class is no longer needed. + - Actor events not have format `[:event_name, *payload]` instead of just the Symbol. + - Actor now uses new Future/Promise Framework instead of `IVar` for better interoperability + - Behaviour definition array was simplified to `[BehaviourClass1, [BehaviourClass2, *initialization_args]]` + - Linking behavior responds to :linked message by returning array of linked actors + - Supervised behavior is removed in favour of just Linking + - RestartingContext is supervised by default now, `supervise: true` is not required any more + - Events can be private and public, so far only difference is that Linking will + pass to linked actors only public messages. Adding private :restarting and + :resetting events which are send before the actor restarts or resets allowing + to add callbacks to cleanup current child actors. + - Print also object_id in Reference to_s + - Add AbstractContext#default_executor to be able to override executor class wide + - Add basic IO example + - Documentation somewhat improved + - All messages should have same priority. It's now possible to send `actor << job1 << job2 << :terminate!` and + be sure that both jobs are processed first. +* Refactored `Channel` to use newer synchronization objects +* Added `#reset` and `#cancel` methods to `TimerSet` +* Added `#cancel` method to `Future` and `ScheduledTask` +* Refactored `TimerSet` to use `ScheduledTask` +* Updated `Async` with a factory that initializes the object +* Deprecated `Concurrent.timer` and `Concurrent.timeout` +* Reduced max threads on pure-Ruby thread pools (abends around 14751 threads) +* Moved many private/internal classes/modules into "namespace" modules +* Removed brute-force killing of threads in tests +* Fixed a thread pool bug when the operating system cannot allocate more threads + +## Release v0.8.0 (25 January 2015) + +* C extension for MRI have been extracted into the `concurrent-ruby-ext` companion gem. + Please see the README for more detail. +* Better variable isolation in `Promise` and `Future` via an `:args` option +* Continued to update intermittently failing tests + +## Release v0.7.2 (24 January 2015) + +* New `Semaphore` class based on [java.util.concurrent.Semaphore](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Semaphore.html) +* New `Promise.all?` and `Promise.any?` class methods +* Renamed `:overflow_policy` on thread pools to `:fallback_policy` +* Thread pools still accept the `:overflow_policy` option but display a warning +* Thread pools now implement `fallback_policy` behavior when not running (rather than universally rejecting tasks) +* Fixed minor `set_deref_options` constructor bug in `Promise` class +* Fixed minor `require` bug in `ThreadLocalVar` class +* Fixed race condition bug in `TimerSet` class +* Fixed race condition bug in `TimerSet` class +* Fixed signal bug in `TimerSet#post` method +* Numerous non-functional updates to clear warning when running in debug mode +* Fixed more intermittently failing tests +* Tests now run on new Travis build environment +* Multiple documentation updates + +## Release v0.7.1 (4 December 2014) + +Please see the [roadmap](https://github.com/ruby-concurrency/concurrent-ruby/issues/142) for more information on the next planned release. + +* Added `flat_map` method to `Promise` +* Added `zip` method to `Promise` +* Fixed bug with logging in `Actor` +* Improvements to `Promise` tests +* Removed actor-experimental warning +* Added an `IndirectImmediateExecutor` class +* Allow disabling auto termination of global executors +* Fix thread leaking in `ThreadLocalVar` (uses `Ref` gem on non-JRuby systems) +* Fix thread leaking when pruning pure-Ruby thread pools +* Prevent `Actor` from using an `ImmediateExecutor` (causes deadlock) +* Added missing synchronizations to `TimerSet` +* Fixed bug with return value of `Concurrent::Actor::Utils::Pool#ask` +* Fixed timing bug in `TimerTask` +* Fixed bug when creating a `JavaThreadPoolExecutor` with minimum pool size of zero +* Removed confusing warning when not using native extensions +* Improved documentation + +## Release v0.7.0 (13 August 2014) + +* Merge the [atomic](https://github.com/ruby-concurrency/atomic) gem + - Pure Ruby `MutexAtomic` atomic reference class + - Platform native atomic reference classes `CAtomic`, `JavaAtomic`, and `RbxAtomic` + - Automated [build process](https://github.com/ruby-concurrency/rake-compiler-dev-box) + - Fat binary releases for [multiple platforms](https://rubygems.org/gems/concurrent-ruby/versions) including Windows (32/64), Linux (32/64), OS X (64-bit), Solaris (64-bit), and JRuby +* C native `CAtomicBoolean` +* C native `CAtomicFixnum` +* Refactored intermittently failing tests +* Added `dataflow!` and `dataflow_with!` methods to match `Future#value!` method +* Better handling of timeout in `Agent` +* Actor Improvements + - Fine-grained implementation using chain of behaviors. Each behavior is responsible for single aspect like: `Termination`, `Pausing`, `Linking`, `Supervising`, etc. Users can create custom Actors easily based on their needs. + - Supervision was added. `RestartingContext` will pause on error waiting on its supervisor to decide what to do next ( options are `:terminate!`, `:resume!`, `:reset!`, `:restart!`). Supervising behavior also supports strategies `:one_for_one` and `:one_for_all`. + - Linking was added to be able to monitor actor's events like: `:terminated`, `:paused`, `:restarted`, etc. + - Dead letter routing added. Rejected envelopes are collected in a configurable actor (default: `Concurrent::Actor.root.ask!(:dead_letter_routing)`) + - Old `Actor` class removed and replaced by new implementation previously called `Actress`. `Actress` was kept as an alias for `Actor` to keep compatibility. + - `Utils::Broadcast` actor which allows Publish–subscribe pattern. +* More executors for managing serialized operations + - `SerializedExecution` mixin module + - `SerializedExecutionDelegator` for serializing *any* executor +* Updated `Async` with serialized execution +* Updated `ImmediateExecutor` and `PerThreadExecutor` with full executor service lifecycle +* Added a `Delay` to root `Actress` initialization +* Minor bug fixes to thread pools +* Refactored many intermittently failing specs +* Removed Java interop warning `executor.rb:148 warning: ambiguous Java methods found, using submit(java.lang.Runnable)` +* Fixed minor bug in `RubyCachedThreadPool` overflow policy +* Updated tests to use [RSpec 3.0](http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3) +* Removed deprecated `Actor` class +* Better support for Rubinius + +## Release v0.6.1 (14 June 2014) + +* Many improvements to `Concurrent::Actress` +* Bug fixes to `Concurrent::RubyThreadPoolExecutor` +* Fixed several brittle tests +* Moved documentation to http://ruby-concurrency.github.io/concurrent-ruby/frames.html + +## Release v0.6.0 (25 May 2014) + +* Added `Concurrent::Observable` to encapsulate our thread safe observer sets +* Improvements to new `Channel` +* Major improvements to `CachedThreadPool` and `FixedThreadPool` +* Added `SingleThreadExecutor` +* Added `Current::timer` function +* Added `TimerSet` executor +* Added `AtomicBoolean` +* `ScheduledTask` refactoring +* Pure Ruby and JRuby-optimized `PriorityQueue` classes +* Updated `Agent` behavior to more closely match Clojure +* Observer sets support block callbacks to the `add_observer` method +* New algorithm for thread creation in `RubyThreadPoolExecutor` +* Minor API updates to `Event` +* Rewritten `TimerTask` now an `Executor` instead of a `Runnable` +* Fixed many brittle specs +* Renamed `FixedThreadPool` and `CachedThreadPool` to `RubyFixedThreadPool` and `RubyCachedThreadPool` +* Created JRuby optimized `JavaFixedThreadPool` and `JavaCachedThreadPool` +* Consolidated fixed thread pool tests into `spec/concurrent/fixed_thread_pool_shared.rb` and `spec/concurrent/cached_thread_pool_shared.rb` +* `FixedThreadPool` now subclasses `RubyFixedThreadPool` or `JavaFixedThreadPool` as appropriate +* `CachedThreadPool` now subclasses `RubyCachedThreadPool` or `JavaCachedThreadPool` as appropriate +* New `Delay` class +* `Concurrent::processor_count` helper function +* New `Async` module +* Renamed `NullThreadPool` to `PerThreadExecutor` +* Deprecated `Channel` (we are planning a new implementation based on [Go](http://golangtutorials.blogspot.com/2011/06/channels-in-go.html)) +* Added gem-level [configuration](http://robots.thoughtbot.com/mygem-configure-block) +* Deprecated `$GLOBAL_THREAD_POOL` in lieu of gem-level configuration +* Removed support for Ruby [1.9.2](https://www.ruby-lang.org/en/news/2013/12/17/maintenance-of-1-8-7-and-1-9-2/) +* New `RubyThreadPoolExecutor` and `JavaThreadPoolExecutor` classes +* All thread pools now extend the appropriate thread pool executor classes +* All thread pools now support `:overflow_policy` (based on Java's [reject policies](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html)) +* Deprecated `UsesGlobalThreadPool` in lieu of explicit `:executor` option (dependency injection) on `Future`, `Promise`, and `Agent` +* Added `Concurrent::dataflow_with(executor, *inputs)` method to support executor dependency injection for dataflow +* Software transactional memory with `TVar` and `Concurrent::atomically` +* First implementation of [new, high-performance](https://github.com/ruby-concurrency/concurrent-ruby/pull/49) `Channel` +* `Actor` is deprecated in favor of new experimental actor implementation [#73](https://github.com/ruby-concurrency/concurrent-ruby/pull/73). To avoid namespace collision it is living in `Actress` namespace until `Actor` is removed in next release. + +## Release v0.5.0 + +This is the most significant release of this gem since its inception. This release includes many improvements and optimizations. It also includes several bug fixes. The major areas of focus for this release were: + +* Stability improvements on Ruby versions with thread-level parallelism ([JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/)) +* Creation of new low-level concurrency abstractions +* Internal refactoring to use the new low-level abstractions + +Most of these updates had no effect on the gem API. There are a few notable exceptions which were unavoidable. Please read the [release notes](API-Updates-in-v0.5.0) for more information. + +Specific changes include: + +* New class `IVar` +* New class `MVar` +* New class `ThreadLocalVar` +* New class `AtomicFixnum` +* New class method `dataflow` +* New class `Condition` +* New class `CountDownLatch` +* New class `DependencyCounter` +* New class `SafeTaskExecutor` +* New class `CopyOnNotifyObserverSet` +* New class `CopyOnWriteObserverSet` +* `Future` updated with `execute` API +* `ScheduledTask` updated with `execute` API +* New `Promise` API +* `Future` now extends `IVar` +* `Postable#post?` now returns an `IVar` +* Thread safety fixes to `Dereferenceable` +* Thread safety fixes to `Obligation` +* Thread safety fixes to `Supervisor` +* Thread safety fixes to `Event` +* Various other thread safety (race condition) fixes +* Refactored brittle tests +* Implemented pending tests +* Added JRuby and Rubinius as Travis CI build targets +* Added [CodeClimate](https://codeclimate.com/) code review +* Improved YARD documentation diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/Gemfile b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/Gemfile new file mode 100644 index 00000000..1786c8b2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/Gemfile @@ -0,0 +1,36 @@ +source 'https://rubygems.org' + +version = File.read("#{__dir__}/lib/concurrent-ruby/concurrent/version.rb")[/'(.+)'/, 1] or raise +edge_version = File.read("#{__dir__}/lib/concurrent-ruby-edge/concurrent/edge/version.rb")[/'(.+)'/, 1] or raise + +no_path = ENV['NO_PATH'] +options = no_path ? {} : { path: '.' } + +gem 'concurrent-ruby', version, options +gem 'concurrent-ruby-edge', edge_version, options +gem 'concurrent-ruby-ext', version, options.merge(platform: :mri) + +group :development do + gem 'rake', '~> 13.0' + gem 'rake-compiler', '~> 1.0', '>= 1.0.7', '!= 1.2.4' + gem 'rake-compiler-dock', '~> 1.0' + gem 'pry', '~> 0.11', platforms: :mri +end + +group :documentation, optional: true do + gem 'yard', '~> 0.9.0', require: false + gem 'redcarpet', '~> 3.0', platforms: :mri # understands github markdown + gem 'md-ruby-eval', '~> 0.6' +end + +group :testing do + gem 'rspec', '~> 3.7' + gem 'timecop', '~> 0.9' + gem 'sigdump', require: false +end + +# made opt-in since it will not install on jruby 1.7 +group :coverage, optional: !ENV['COVERAGE'] do + gem 'simplecov', '~> 0.16.0', require: false + gem 'coveralls', '~> 0.8.2', require: false +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/LICENSE.txt b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/LICENSE.txt new file mode 100644 index 00000000..1026f28d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/LICENSE.txt @@ -0,0 +1,21 @@ +Copyright (c) Jerry D'Antonio -- released under the MIT license. + +http://www.opensource.org/licenses/mit-license.php + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/README.md b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/README.md new file mode 100644 index 00000000..63ee743c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/README.md @@ -0,0 +1,407 @@ +# Concurrent Ruby + +[![Gem Version](https://badge.fury.io/rb/concurrent-ruby.svg)](http://badge.fury.io/rb/concurrent-ruby) +[![License](https://img.shields.io/badge/license-MIT-green.svg)](http://opensource.org/licenses/MIT) +[![Gitter chat](https://img.shields.io/badge/IRC%20(gitter)-devs%20%26%20users-brightgreen.svg)](https://gitter.im/ruby-concurrency/concurrent-ruby) + +Modern concurrency tools for Ruby. Inspired by +[Erlang](http://www.erlang.org/doc/reference_manual/processes.html), +[Clojure](http://clojure.org/concurrent_programming), +[Scala](http://akka.io/), +[Haskell](http://www.haskell.org/haskellwiki/Applications_and_libraries/Concurrency_and_parallelism#Concurrent_Haskell), +[F#](http://blogs.msdn.com/b/dsyme/archive/2010/02/15/async-and-parallel-design-patterns-in-f-part-3-agents.aspx), +[C#](http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx), +[Java](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html), +and classic concurrency patterns. + + + +The design goals of this gem are: + +* Be an 'unopinionated' toolbox that provides useful utilities without debating which is better + or why +* Remain free of external gem dependencies +* Stay true to the spirit of the languages providing inspiration +* But implement in a way that makes sense for Ruby +* Keep the semantics as idiomatic Ruby as possible +* Support features that make sense in Ruby +* Exclude features that don't make sense in Ruby +* Be small, lean, and loosely coupled +* Thread-safety +* Backward compatibility + +## Contributing + +**This gem depends on +[contributions](https://github.com/ruby-concurrency/concurrent-ruby/graphs/contributors) and we +appreciate your help. Would you like to contribute? Great! Have a look at +[issues with `looking-for-contributor` label](https://github.com/ruby-concurrency/concurrent-ruby/issues?q=is%3Aissue+is%3Aopen+label%3Alooking-for-contributor).** And if you pick something up let us know on the issue. + +You can also get started by triaging issues which may include reproducing bug reports or asking for vital information, such as version numbers or reproduction instructions. If you would like to start triaging issues, one easy way to get started is to [subscribe to concurrent-ruby on CodeTriage](https://www.codetriage.com/ruby-concurrency/concurrent-ruby). [![Open Source Helpers](https://www.codetriage.com/ruby-concurrency/concurrent-ruby/badges/users.svg)](https://www.codetriage.com/ruby-concurrency/concurrent-ruby) + +## Thread Safety + +*Concurrent Ruby makes one of the strongest thread safety guarantees of any Ruby concurrency +library, providing consistent behavior and guarantees on all three main Ruby interpreters +(MRI/CRuby, JRuby, TruffleRuby).* + +Every abstraction in this library is thread safe. Specific thread safety guarantees are documented +with each abstraction. + +It is critical to remember, however, that Ruby is a language of mutable references. *No* +concurrency library for Ruby can ever prevent the user from making thread safety mistakes (such as +sharing a mutable object between threads and modifying it on both threads) or from creating +deadlocks through incorrect use of locks. All the library can do is provide safe abstractions which +encourage safe practices. Concurrent Ruby provides more safe concurrency abstractions than any +other Ruby library, many of which support the mantra of +["Do not communicate by sharing memory; instead, share memory by communicating"](https://blog.golang.org/share-memory-by-communicating). +Concurrent Ruby is also the only Ruby library which provides a full suite of thread safe and +immutable variable types and data structures. + +We've also initiated discussion to document the [memory model](docs-source/synchronization.md) of Ruby which +would provide consistent behaviour and guarantees on all three main Ruby interpreters +(MRI/CRuby, JRuby, TruffleRuby). + +## Features & Documentation + +**The primary site for documentation is the automatically generated +[API documentation](http://ruby-concurrency.github.io/concurrent-ruby/index.html) which is up to +date with latest release.** This readme matches the master so may contain new stuff not yet +released. + +We also have a [IRC (gitter)](https://gitter.im/ruby-concurrency/concurrent-ruby). + +### Versioning + +* `concurrent-ruby` uses [Semantic Versioning](http://semver.org/) +* `concurrent-ruby-ext` has always same version as `concurrent-ruby` +* `concurrent-ruby-edge` will always be 0.y.z therefore following + [point 4](http://semver.org/#spec-item-4) applies *"Major version zero + (0.y.z) is for initial development. Anything may change at any time. The + public API should not be considered stable."* However we additionally use + following rules: + * Minor version increment means incompatible changes were made + * Patch version increment means only compatible changes were made + + +#### General-purpose Concurrency Abstractions + +* [Async](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Async.html): + A mixin module that provides simple asynchronous behavior to a class. Loosely based on Erlang's + [gen_server](http://www.erlang.org/doc/man/gen_server.html). +* [ScheduledTask](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ScheduledTask.html): + Like a Future scheduled for a specific future time. +* [TimerTask](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/TimerTask.html): + A Thread that periodically wakes up to perform work at regular intervals. +* [Promises](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Promises.html): + Unified implementation of futures and promises which combines features of previous `Future`, + `Promise`, `IVar`, `Event`, `dataflow`, `Delay`, and (partially) `TimerTask` into a single + framework. It extensively uses the new synchronization layer to make all the features + **non-blocking** and **lock-free**, with the exception of obviously blocking operations like + `#wait`, `#value`. It also offers better performance. + +#### Thread-safe Value Objects, Structures, and Collections + +Collection classes that were originally part of the (deprecated) `thread_safe` gem: + +* [Array](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Array.html) A thread-safe + subclass of Ruby's standard [Array](http://ruby-doc.org/core/Array.html). +* [Hash](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Hash.html) A thread-safe + subclass of Ruby's standard [Hash](http://ruby-doc.org/core/Hash.html). +* [Set](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Set.html) A thread-safe + subclass of Ruby's standard [Set](http://ruby-doc.org/stdlib-2.4.0/libdoc/set/rdoc/Set.html). +* [Map](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Map.html) A hash-like object + that should have much better performance characteristics, especially under high concurrency, + than `Concurrent::Hash`. +* [Tuple](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Tuple.html) A fixed size + array with volatile (synchronized, thread safe) getters/setters. + +Value objects inspired by other languages: + +* [Maybe](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Maybe.html) A thread-safe, + immutable object representing an optional value, based on + [Haskell Data.Maybe](https://hackage.haskell.org/package/base-4.2.0.1/docs/Data-Maybe.html). + +Structure classes derived from Ruby's [Struct](http://ruby-doc.org/core/Struct.html): + +* [ImmutableStruct](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ImmutableStruct.html) + Immutable struct where values are set at construction and cannot be changed later. +* [MutableStruct](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/MutableStruct.html) + Synchronized, mutable struct where values can be safely changed at any time. +* [SettableStruct](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/SettableStruct.html) + Synchronized, write-once struct where values can be set at most once, either at construction + or any time thereafter. + +Thread-safe variables: + +* [Agent](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Agent.html): A way to + manage shared, mutable, *asynchronous*, independent state. Based on Clojure's + [Agent](http://clojure.org/agents). +* [Atom](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Atom.html): A way to manage + shared, mutable, *synchronous*, independent state. Based on Clojure's + [Atom](http://clojure.org/atoms). +* [AtomicBoolean](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/AtomicBoolean.html) + A boolean value that can be updated atomically. +* [AtomicFixnum](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/AtomicFixnum.html) + A numeric value that can be updated atomically. +* [AtomicReference](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/AtomicReference.html) + An object reference that may be updated atomically. +* [Exchanger](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Exchanger.html) + A synchronization point at which threads can pair and swap elements within pairs. Based on + Java's [Exchanger](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Exchanger.html). +* [MVar](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/MVar.html) A synchronized + single element container. Based on Haskell's + [MVar](https://hackage.haskell.org/package/base-4.8.1.0/docs/Control-Concurrent-MVar.html) and + Scala's [MVar](http://docs.typelevel.org/api/scalaz/nightly/index.html#scalaz.concurrent.MVar$). +* [ThreadLocalVar](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ThreadLocalVar.html) + A variable where the value is different for each thread. +* [TVar](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/TVar.html) A transactional + variable implementing software transactional memory (STM). Based on Clojure's + [Ref](http://clojure.org/refs). + +#### Java-inspired ThreadPools and Other Executors + +* See the [thread pool](http://ruby-concurrency.github.io/concurrent-ruby/master/file.thread_pools.html) + overview, which also contains a list of other Executors available. + +#### Thread Synchronization Classes and Algorithms + +* [CountDownLatch](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/CountDownLatch.html) + A synchronization object that allows one thread to wait on multiple other threads. +* [CyclicBarrier](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/CyclicBarrier.html) + A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. +* [Event](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Event.html) Old school + kernel-style event. +* [ReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ReadWriteLock.html) + A lock that supports multiple readers but only one writer. +* [ReentrantReadWriteLock](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ReentrantReadWriteLock.html) + A read/write lock with reentrant and upgrade features. +* [Semaphore](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Semaphore.html) + A counting-based locking mechanism that uses permits. +* [AtomicMarkableReference](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/AtomicMarkableReference.html) + +#### Deprecated + +Deprecated features are still available and bugs are being fixed, but new features will not be added. + +* ~~[Future](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Future.html): + An asynchronous operation that produces a value.~~ Replaced by + [Promises](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Promises.html). + * ~~[.dataflow](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent.html#dataflow-class_method): + Built on Futures, Dataflow allows you to create a task that will be scheduled when all of + its data dependencies are available.~~ Replaced by + [Promises](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Promises.html). +* ~~[Promise](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Promise.html): Similar + to Futures, with more features.~~ Replaced by + [Promises](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Promises.html). +* ~~[Delay](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Delay.html) Lazy evaluation + of a block yielding an immutable result. Based on Clojure's + [delay](https://clojuredocs.org/clojure.core/delay).~~ Replaced by + [Promises](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Promises.html). +* ~~[IVar](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/IVar.html) Similar to a + "future" but can be manually assigned once, after which it becomes immutable.~~ Replaced by + [Promises](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Promises.html). + +### Edge Features + +These are available in the `concurrent-ruby-edge` companion gem. + +These features are under active development and may change frequently. They are expected not to +keep backward compatibility (there may also lack tests and documentation). Semantic versions will +be obeyed though. Features developed in `concurrent-ruby-edge` are expected to move to +`concurrent-ruby` when final. + +* [Actor](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Actor.html): Implements + the Actor Model, where concurrent actors exchange messages. + *Status: Partial documentation and tests; depends on new future/promise framework; stability is good.* +* [Channel](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Channel.html): + Communicating Sequential Processes ([CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes)). + Functionally equivalent to Go [channels](https://tour.golang.org/concurrency/2) with additional + inspiration from Clojure [core.async](https://clojure.github.io/core.async/). + *Status: Partial documentation and tests.* +* [LazyRegister](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/LazyRegister.html) +* [LockFreeLinkedSet](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Edge/LockFreeLinkedSet.html) + *Status: will be moved to core soon.* +* [LockFreeStack](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/LockFreeStack.html) + *Status: missing documentation and tests.* +* [Promises::Channel](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Promises/Channel.html) + A first in first out channel that accepts messages with push family of methods and returns + messages with pop family of methods. + Pop and push operations can be represented as futures, see `#pop_op` and `#push_op`. + The capacity of the channel can be limited to support back pressure, use capacity option in `#initialize`. + `#pop` method blocks ans `#pop_op` returns pending future if there is no message in the channel. + If the capacity is limited the `#push` method blocks and `#push_op` returns pending future. +* [Cancellation](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Cancellation.html) + The Cancellation abstraction provides cooperative cancellation. + + The standard methods `Thread#raise` of `Thread#kill` available in Ruby + are very dangerous (see linked the blog posts bellow). + Therefore concurrent-ruby provides an alternative. + + * + * + * + + It provides an object which represents a task which can be executed, + the task has to get the reference to the object and periodically cooperatively check that it is not cancelled. + Good practices to make tasks cancellable: + * check cancellation every cycle of a loop which does significant work, + * do all blocking actions in a loop with a timeout then on timeout check cancellation + and if ok block again with the timeout +* [Throttle](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/Throttle.html) + A tool managing concurrency level of tasks. +* [ErlangActor](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ErlangActor.html) + Actor implementation which precisely matches Erlang actor behaviour. + Requires at least Ruby 2.1 otherwise it's not loaded. +* [WrappingExecutor](http://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/WrappingExecutor.html) + A delegating executor which modifies each task before the task is given to + the target executor it delegates to. + +## Supported Ruby versions + +* MRI 2.3 and above +* Latest JRuby 9000 +* Latest TruffleRuby + +## Usage + +Everything within this gem can be loaded simply by requiring it: + +```ruby +require 'concurrent' +``` + +You can also require a specific abstraction [part of the public documentation](https://ruby-concurrency.github.io/concurrent-ruby/master/index.html) since concurrent-ruby 1.2.0, for example: +```ruby +require 'concurrent/map' +require 'concurrent/atomic/atomic_reference' +require 'concurrent/executor/fixed_thread_pool' +``` + +To use the tools in the Edge gem it must be required separately: + +```ruby +require 'concurrent-edge' +``` + +If the library does not behave as expected, `Concurrent.use_simple_logger(:DEBUG)` could +help to reveal the problem. + +## Installation + +```shell +gem install concurrent-ruby +``` + +or add the following line to Gemfile: + +```ruby +gem 'concurrent-ruby', require: 'concurrent' +``` + +and run `bundle install` from your shell. + +### Edge Gem Installation + +The Edge gem must be installed separately from the core gem: + +```shell +gem install concurrent-ruby-edge +``` + +or add the following line to Gemfile: + +```ruby +gem 'concurrent-ruby-edge', require: 'concurrent-edge' +``` + +and run `bundle install` from your shell. + + +### C Extensions for MRI + +Potential performance improvements may be achieved under MRI by installing optional C extensions. +To minimise installation errors the C extensions are available in the `concurrent-ruby-ext` +extension gem. `concurrent-ruby` and `concurrent-ruby-ext` are always released together with same +version. Simply install the extension gem too: + +```ruby +gem install concurrent-ruby-ext +``` + +or add the following line to Gemfile: + +```ruby +gem 'concurrent-ruby-ext' +``` + +and run `bundle install` from your shell. + +In code it is only necessary to + +```ruby +require 'concurrent' +``` + +The `concurrent-ruby` gem will automatically detect the presence of the `concurrent-ruby-ext` gem +and load the appropriate C extensions. + +#### Note For gem developers + +No gems should depend on `concurrent-ruby-ext`. Doing so will force C extensions on your users. The +best practice is to depend on `concurrent-ruby` and let users to decide if they want C extensions. + +## Building the gem + +### Requirements + +* Recent CRuby +* JRuby, `rbenv install jruby-9.2.17.0` +* Set env variable `CONCURRENT_JRUBY_HOME` to point to it, e.g. `/usr/local/opt/rbenv/versions/jruby-9.2.17.0` +* Install Docker, required for Windows builds + +### Publishing the Gem + +* Update `version.rb` +* Update the CHANGELOG +* Add the new version to `docs-source/signpost.md`. Needs to be done only if there are visible changes in the documentation. +* Commit (and push) the changes. +* Use `bundle exec rake release` to release the gem. + It consists of `['release:checks', 'release:build', 'release:test', 'release:publish']` steps. + It will ask at the end before publishing anything. Steps can also be executed individually. + +## Maintainers + +* [Benoit Daloze](https://github.com/eregon) +* [Matthew Draper](https://github.com/matthewd) +* [Rafael França](https://github.com/rafaelfranca) +* [Charles Oliver Nutter](https://github.com/headius) +* [Ben Sheldon](https://github.com/bensheldon) +* [Samuel Williams](https://github.com/ioquatix) + +### Special Thanks to + +* [Jerry D'Antonio](https://github.com/jdantonio) for creating the gem +* [Brian Durand](https://github.com/bdurand) for the `ref` gem +* [Charles Oliver Nutter](https://github.com/headius) for the `atomic` and `thread_safe` gems +* [thedarkone](https://github.com/thedarkone) for the `thread_safe` gem + +to the past maintainers + +* [Chris Seaton](https://github.com/chrisseaton) +* [Petr Chalupa](https://github.com/pitr-ch) +* [Michele Della Torre](https://github.com/mighe) +* [Paweł Obrok](https://github.com/obrok) +* [Lucas Allan](https://github.com/lucasallan) + +and to [Ruby Association](https://www.ruby.or.jp/en/) for sponsoring a project +["Enhancing Ruby’s concurrency tooling"](https://www.ruby.or.jp/en/news/20181106) in 2018. + +## License and Copyright + +*Concurrent Ruby* is free software released under the +[MIT License](http://www.opensource.org/licenses/MIT). + +The *Concurrent Ruby* [logo](https://raw.githubusercontent.com/ruby-concurrency/concurrent-ruby/master/docs-source/logo/concurrent-ruby-logo-300x300.png) was +designed by [David Jones](https://twitter.com/zombyboy). It is Copyright © 2014 +[Jerry D'Antonio](https://twitter.com/jerrydantonio). All Rights Reserved. diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/Rakefile b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/Rakefile new file mode 100644 index 00000000..3d157d47 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/Rakefile @@ -0,0 +1,344 @@ +version = File.read("#{__dir__}/lib/concurrent-ruby/concurrent/version.rb")[/'(.+)'/, 1] or raise +edge_version = File.read("#{__dir__}/lib/concurrent-ruby-edge/concurrent/edge/version.rb")[/'(.+)'/, 1] or raise + +core_gemspec = Gem::Specification.load File.join(__dir__, 'concurrent-ruby.gemspec') +ext_gemspec = Gem::Specification.load File.join(__dir__, 'concurrent-ruby-ext.gemspec') +edge_gemspec = Gem::Specification.load File.join(__dir__, 'concurrent-ruby-edge.gemspec') + +require 'rake/javaextensiontask' + +ENV['JRUBY_HOME'] = ENV['CONCURRENT_JRUBY_HOME'] if ENV['CONCURRENT_JRUBY_HOME'] && RUBY_ENGINE != 'jruby' + +Rake::JavaExtensionTask.new('concurrent_ruby', core_gemspec) do |ext| + ext.ext_dir = 'ext/concurrent-ruby' + ext.lib_dir = 'lib/concurrent-ruby/concurrent' + ext.source_version = '8' + ext.target_version = '8' +end + +if RUBY_ENGINE == 'ruby' + require 'rake/extensiontask' + + Rake::ExtensionTask.new('concurrent_ruby_ext', ext_gemspec) do |ext| + ext.ext_dir = 'ext/concurrent-ruby-ext' + ext.lib_dir = 'lib/concurrent-ruby/concurrent' + ext.source_pattern = '*.{c,h}' + + ext.cross_compile = true + ext.cross_platform = ['x86-mingw32', 'x64-mingw32'] + end +end + +def which?(executable) + !`which #{executable} 2>/dev/null`.empty? +end + +require 'rake_compiler_dock' +namespace :repackage do + desc '* with Windows fat distributions' + task :all do + Dir.chdir(__dir__) do + # store gems in vendor cache for docker + Bundler.with_original_env do + sh 'bundle package' + end + + # build only the jar file not the whole gem for java platform, the jar is part the concurrent-ruby-x.y.z.gem + Rake::Task['lib/concurrent-ruby/concurrent/concurrent_ruby.jar'].invoke + + # build all gem files + rack_compiler_dock_kwargs = {} + if which?('podman') and (!which?('docker') || `docker --version`.include?('podman')) + # podman and only podman available, so RakeCompilerDock will use podman, otherwise it uses docker + rack_compiler_dock_kwargs = { + options: ['--privileged'], # otherwise the directory in the image is empty + runas: false + } + end + %w[x86-mingw32 x64-mingw32].each do |plat| + RakeCompilerDock.sh( + "bundle install --local && bundle exec rake native:#{plat} gem --trace", + platform: plat, + **rack_compiler_dock_kwargs) + end + end + end +end + +require 'rubygems' +require 'rubygems/package_task' + +Gem::PackageTask.new(core_gemspec) {} if core_gemspec +Gem::PackageTask.new(ext_gemspec) {} if ext_gemspec && RUBY_ENGINE != 'jruby' +Gem::PackageTask.new(edge_gemspec) {} if edge_gemspec + +CLEAN.include( + 'lib/concurrent-ruby/concurrent/concurrent_ruby_ext.*', + 'lib/concurrent-ruby/concurrent/2.*', + 'lib/concurrent-ruby/concurrent/*.jar') + +begin + require 'rspec' + require 'rspec/core/rake_task' + + RSpec::Core::RakeTask.new(:spec) + + namespace :spec do + desc '* Configured for ci' + RSpec::Core::RakeTask.new(:ci) do |t| + options = %w[ --color + --backtrace + --order defined + --format documentation ] + t.rspec_opts = [*options].join(' ') + end + + desc '* test packaged and installed gems instead of local files' + task :installed do + Bundler.with_original_env do + Dir.chdir(__dir__) do + sh "gem install pkg/concurrent-ruby-#{version}.gem" + sh "gem install pkg/concurrent-ruby-ext-#{version}.gem" if RUBY_ENGINE == 'ruby' + sh "gem install pkg/concurrent-ruby-edge-#{edge_version}.gem" + ENV['NO_PATH'] = 'true' + sh 'bundle update' + sh 'bundle exec rake spec:ci' + end + end + end + end + + desc 'executed in CI' + task :ci => [:compile, 'spec:ci'] + + desc 'run each spec file in a separate process to help find missing requires' + task 'spec:isolated' do + glob = "#{ENV['DIR'] || 'spec'}/**/*_spec.rb" + from = ENV['FROM'] + env = { 'ISOLATED' => 'true' } + Dir[glob].each do |spec| + next if from and from != spec + from = nil if from == spec + + sh env, 'rspec', spec + end + end + + task :default => [:clobber, :compile, :spec] +rescue LoadError => e + puts 'RSpec is not installed, skipping test task definitions: ' + e.message +end + +current_yard_version_name = version + +begin + require 'yard' + require 'md_ruby_eval' + require_relative 'support/yard_full_types' + + common_yard_options = ['--no-yardopts', + '--no-document', + '--no-private', + '--embed-mixins', + '--markup', 'markdown', + '--title', 'Concurrent Ruby', + '--template', 'default', + '--template-path', 'yard-template', + '--default-return', 'undocumented'] + + desc 'Generate YARD Documentation (signpost, master)' + task :yard => ['yard:signpost', 'yard:master'] + + namespace :yard do + + desc '* eval markdown files' + task :eval_md do + Dir.chdir File.join(__dir__, 'docs-source') do + sh 'bundle exec md-ruby-eval --auto' + end + end + + task :update_readme do + Dir.chdir __dir__ do + content = File.read(File.join('README.md')). + gsub(/\[([\w ]+)\]\(http:\/\/ruby-concurrency\.github\.io\/concurrent-ruby\/master\/.*\)/) do |_| + case $1 + when 'LockFreeLinkedSet' + "{Concurrent::Edge::#{$1} #{$1}}" + when '.dataflow' + '{Concurrent.dataflow Concurrent.dataflow}' + when 'thread pool' + '{file:thread_pools.md thread pool}' + else + "{Concurrent::#{$1} #{$1}}" + end + end + FileUtils.mkpath 'tmp' + File.write 'tmp/README.md', content + end + end + + define_yard_task = -> name do + output_dir = "docs/#{name}" + + removal_name = "remove.#{name}" + task removal_name do + Dir.chdir __dir__ do + FileUtils.rm_rf output_dir + end + end + + desc "* of #{name} into subdir #{name}" + YARD::Rake::YardocTask.new(name) do |yard| + yard.options.push( + '--output-dir', output_dir, + '--main', 'tmp/README.md', + *common_yard_options) + yard.files = ['./lib/concurrent-ruby/**/*.rb', + './lib/concurrent-ruby-edge/**/*.rb', + './ext/concurrent_ruby_ext/**/*.c', + '-', + 'docs-source/thread_pools.md', + 'docs-source/promises.out.md', + 'docs-source/medium-example.out.rb', + 'LICENSE.txt', + 'CHANGELOG.md'] + end + Rake::Task[name].prerequisites.push removal_name, + # 'yard:eval_md', + 'yard:update_readme' + end + + define_yard_task.call current_yard_version_name + define_yard_task.call 'master' + + desc "* signpost for versions" + YARD::Rake::YardocTask.new(:signpost) do |yard| + yard.options.push( + '--output-dir', 'docs', + '--main', 'docs-source/signpost.md', + *common_yard_options) + yard.files = ['no-lib'] + end + end + +rescue LoadError => e + puts 'YARD is not installed, skipping documentation task definitions: ' + e.message +end + +desc 'build, test, and publish the gem' +task :release => ['release:checks', 'release:build', 'release:test', 'release:publish'] + +namespace :release do + # Depends on environment of @pitr-ch + + task :checks do + raise '$CONCURRENT_JRUBY_HOME must be set' unless ENV['CONCURRENT_JRUBY_HOME'] + + Dir.chdir(__dir__) do + sh 'test -z "$(git status --porcelain)"' do |ok, res| + unless ok + begin + status = `git status --porcelain` + STDOUT.puts 'There are local changes that you might want to commit.', status, 'Continue? (y/n)' + input = STDIN.gets.strip.downcase + end until %w(y n).include?(input) + exit 1 if input == 'n' + end + end + sh 'git fetch' + sh 'test $(git show-ref --verify --hash refs/heads/master) = ' + + '$(git show-ref --verify --hash refs/remotes/origin/master)' do |ok, res| + unless ok + begin + STDOUT.puts 'Local master branch is not pushed to origin.', 'Continue? (y/n)' + input = STDIN.gets.strip.downcase + end until %w(y n).include?(input) + exit 1 if input == 'n' + end + end + end + end + + desc '* build all *.gem files necessary for release' + task :build => [:clobber, 'repackage:all'] + + desc '* test actual installed gems instead of cloned repository on MRI and JRuby' + task :test do + raise '$CONCURRENT_JRUBY_HOME must be set' unless ENV['CONCURRENT_JRUBY_HOME'] + + Dir.chdir(__dir__) do + puts "Testing with the installed gem" + + Bundler.with_original_env do + sh 'ruby -v' + sh 'bundle install' + sh 'bundle exec rake spec:installed' + + env = { "PATH" => "#{ENV.fetch('CONCURRENT_JRUBY_HOME')}/bin:#{ENV['PATH']}" } + sh env, 'ruby -v' + sh env, 'bundle install' + sh env, 'bundle exec rake spec:installed' + end + + puts 'Windows build is untested' + end + end + + desc '* do all nested steps' + task :publish => ['publish:ask', 'publish:tag', 'publish:rubygems', 'publish:post_steps'] + + namespace :publish do + publish_base = nil + publish_edge = nil + + task :ask do + begin + STDOUT.puts 'Do you want to publish anything now? (y/n)' + input = STDIN.gets.strip.downcase + end until %w(y n).include?(input) + exit 1 if input == 'n' + + begin + STDOUT.puts 'Do you want to publish `concurrent-ruby`? (y/n)' + input = STDIN.gets.strip.downcase + end until %w(y n).include?(input) + publish_base = input == 'y' + + begin + STDOUT.puts 'Do you want to publish `concurrent-ruby-edge`? (y/n)' + input = STDIN.gets.strip.downcase + end until %w(y n).include?(input) + publish_edge = input == 'y' + end + + desc '** tag HEAD with current version and push to github' + task :tag => :ask do + Dir.chdir(__dir__) do + sh "git tag v#{version}" if publish_base + sh "git push origin v#{version}" if publish_base + sh "git tag edge-v#{edge_version}" if publish_edge + sh "git push origin edge-v#{edge_version}" if publish_edge + end + end + + desc '** push all *.gem files to rubygems' + task :rubygems => :ask do + Dir.chdir(__dir__) do + sh "gem push pkg/concurrent-ruby-#{version}.gem" if publish_base + sh "gem push pkg/concurrent-ruby-edge-#{edge_version}.gem" if publish_edge + sh "gem push pkg/concurrent-ruby-ext-#{version}.gem" if publish_base + sh "gem push pkg/concurrent-ruby-ext-#{version}-x64-mingw32.gem" if publish_base + sh "gem push pkg/concurrent-ruby-ext-#{version}-x86-mingw32.gem" if publish_base + end + end + + desc '** print post release steps' + task :post_steps do + # TODO: (petr 05-Jun-2021) automate and renew the process + puts 'Manually: create a release on GitHub with relevant changelog part' + puts 'Manually: send email same as release with relevant changelog part' + puts 'Manually: tweet' + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/ConcurrentRubyService.java b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/ConcurrentRubyService.java new file mode 100644 index 00000000..fb6be96d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/ConcurrentRubyService.java @@ -0,0 +1,17 @@ +import org.jruby.Ruby; +import org.jruby.runtime.load.BasicLibraryService; + +import java.io.IOException; + +public class ConcurrentRubyService implements BasicLibraryService { + + public boolean basicLoad(final Ruby runtime) throws IOException { + new com.concurrent_ruby.ext.AtomicReferenceLibrary().load(runtime, false); + new com.concurrent_ruby.ext.JavaAtomicBooleanLibrary().load(runtime, false); + new com.concurrent_ruby.ext.JavaAtomicFixnumLibrary().load(runtime, false); + new com.concurrent_ruby.ext.JavaSemaphoreLibrary().load(runtime, false); + new com.concurrent_ruby.ext.SynchronizationLibrary().load(runtime, false); + new com.concurrent_ruby.ext.JRubyMapBackendLibrary().load(runtime, false); + return true; + } +} diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java new file mode 100644 index 00000000..dfa9e770 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java @@ -0,0 +1,175 @@ +package com.concurrent_ruby.ext; + +import java.lang.reflect.Field; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import org.jruby.Ruby; +import org.jruby.RubyClass; +import org.jruby.RubyModule; +import org.jruby.RubyNumeric; +import org.jruby.RubyObject; +import org.jruby.anno.JRubyClass; +import org.jruby.anno.JRubyMethod; +import org.jruby.runtime.ObjectAllocator; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.runtime.load.Library; + +/** + * This library adds an atomic reference type to JRuby for use in the atomic + * library. We do a native version to avoid the implicit value coercion that + * normally happens through JI. + * + * @author headius + */ +public class AtomicReferenceLibrary implements Library { + public void load(Ruby runtime, boolean wrap) throws IOException { + RubyModule concurrentMod = runtime.defineModule("Concurrent"); + RubyClass atomicCls = concurrentMod.defineClassUnder("JavaAtomicReference", runtime.getObject(), JRUBYREFERENCE_ALLOCATOR); + try { + sun.misc.Unsafe.class.getMethod("getAndSetObject", Object.class); + atomicCls.setAllocator(JRUBYREFERENCE8_ALLOCATOR); + } catch (Exception e) { + // leave it as Java 6/7 version + } + atomicCls.defineAnnotatedMethods(JRubyReference.class); + } + + private static final ObjectAllocator JRUBYREFERENCE_ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new JRubyReference(runtime, klazz); + } + }; + + private static final ObjectAllocator JRUBYREFERENCE8_ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new JRubyReference8(runtime, klazz); + } + }; + + @JRubyClass(name="JRubyReference", parent="Object") + public static class JRubyReference extends RubyObject { + volatile IRubyObject reference; + + static final sun.misc.Unsafe UNSAFE; + static final long referenceOffset; + + static { + try { + UNSAFE = UnsafeHolder.U; + Class k = JRubyReference.class; + referenceOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("reference")); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public JRubyReference(Ruby runtime, RubyClass klass) { + super(runtime, klass); + } + + @JRubyMethod + public IRubyObject initialize(ThreadContext context) { + UNSAFE.putObject(this, referenceOffset, context.nil); + return context.nil; + } + + @JRubyMethod + public IRubyObject initialize(ThreadContext context, IRubyObject value) { + UNSAFE.putObject(this, referenceOffset, value); + return context.nil; + } + + @JRubyMethod(name = {"get", "value"}) + public IRubyObject get() { + return reference; + } + + @JRubyMethod(name = {"set", "value="}) + public IRubyObject set(IRubyObject newValue) { + UNSAFE.putObjectVolatile(this, referenceOffset, newValue); + return newValue; + } + + @JRubyMethod(name = {"compare_and_set", "compare_and_swap"}) + public IRubyObject compare_and_set(ThreadContext context, IRubyObject expectedValue, IRubyObject newValue) { + Ruby runtime = context.runtime; + + if (expectedValue instanceof RubyNumeric) { + // numerics are not always idempotent in Ruby, so we need to do slower logic + return compareAndSetNumeric(context, expectedValue, newValue); + } + + return runtime.newBoolean(UNSAFE.compareAndSwapObject(this, referenceOffset, expectedValue, newValue)); + } + + @JRubyMethod(name = {"get_and_set", "swap"}) + public IRubyObject get_and_set(ThreadContext context, IRubyObject newValue) { + // less-efficient version for Java 6 and 7 + while (true) { + IRubyObject oldValue = get(); + if (UNSAFE.compareAndSwapObject(this, referenceOffset, oldValue, newValue)) { + return oldValue; + } + } + } + + private IRubyObject compareAndSetNumeric(ThreadContext context, IRubyObject expectedValue, IRubyObject newValue) { + Ruby runtime = context.runtime; + + // loop until: + // * reference CAS would succeed for same-valued objects + // * current and expected have different values as determined by #equals + while (true) { + IRubyObject current = reference; + + if (!(current instanceof RubyNumeric)) { + // old value is not numeric, CAS fails + return runtime.getFalse(); + } + + RubyNumeric currentNumber = (RubyNumeric)current; + if (!currentNumber.equals(expectedValue)) { + // current number does not equal expected, fail CAS + return runtime.getFalse(); + } + + // check that current has not changed, or else allow loop to repeat + boolean success = UNSAFE.compareAndSwapObject(this, referenceOffset, current, newValue); + if (success) { + // value is same and did not change in interim...success + return runtime.getTrue(); + } + } + } + } + + private static final class UnsafeHolder { + private UnsafeHolder(){} + + public static final sun.misc.Unsafe U = loadUnsafe(); + + private static sun.misc.Unsafe loadUnsafe() { + try { + Class unsafeClass = Class.forName("sun.misc.Unsafe"); + Field f = unsafeClass.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return (sun.misc.Unsafe) f.get(null); + } catch (Exception e) { + return null; + } + } + } + + public static class JRubyReference8 extends JRubyReference { + public JRubyReference8(Ruby runtime, RubyClass klass) { + super(runtime, klass); + } + + @Override + public IRubyObject get_and_set(ThreadContext context, IRubyObject newValue) { + // efficient version for Java 8 + return (IRubyObject)UNSAFE.getAndSetObject(this, referenceOffset, newValue); + } + } +} diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java new file mode 100644 index 00000000..a09f9162 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java @@ -0,0 +1,248 @@ +package com.concurrent_ruby.ext; + +import org.jruby.*; +import org.jruby.anno.JRubyClass; +import org.jruby.anno.JRubyMethod; +import com.concurrent_ruby.ext.jsr166e.ConcurrentHashMap; +import com.concurrent_ruby.ext.jsr166e.ConcurrentHashMapV8; +import com.concurrent_ruby.ext.jsr166e.nounsafe.*; +import org.jruby.runtime.Block; +import org.jruby.runtime.ObjectAllocator; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.runtime.load.Library; + +import java.io.IOException; +import java.util.Map; + +import static org.jruby.runtime.Visibility.PRIVATE; + +/** + * Native Java implementation to avoid the JI overhead. + * + * @author thedarkone + */ +public class JRubyMapBackendLibrary implements Library { + public void load(Ruby runtime, boolean wrap) throws IOException { + + RubyModule concurrentMod = runtime.defineModule("Concurrent"); + RubyModule thread_safeMod = concurrentMod.defineModuleUnder("Collection"); + RubyClass jrubyRefClass = thread_safeMod.defineClassUnder("JRubyMapBackend", runtime.getObject(), BACKEND_ALLOCATOR); + jrubyRefClass.setAllocator(BACKEND_ALLOCATOR); + jrubyRefClass.defineAnnotatedMethods(JRubyMapBackend.class); + } + + private static final ObjectAllocator BACKEND_ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new JRubyMapBackend(runtime, klazz); + } + }; + + @JRubyClass(name="JRubyMapBackend", parent="Object") + public static class JRubyMapBackend extends RubyObject { + // Defaults used by the CHM + static final int DEFAULT_INITIAL_CAPACITY = 16; + static final float DEFAULT_LOAD_FACTOR = 0.75f; + + public static final boolean CAN_USE_UNSAFE_CHM = canUseUnsafeCHM(); + + private ConcurrentHashMap map; + + private static ConcurrentHashMap newCHM(int initialCapacity, float loadFactor) { + if (CAN_USE_UNSAFE_CHM) { + return new ConcurrentHashMapV8(initialCapacity, loadFactor); + } else { + return new com.concurrent_ruby.ext.jsr166e.nounsafe.ConcurrentHashMapV8(initialCapacity, loadFactor); + } + } + + private static ConcurrentHashMap newCHM() { + return newCHM(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); + } + + private static boolean canUseUnsafeCHM() { + try { + new com.concurrent_ruby.ext.jsr166e.ConcurrentHashMapV8(); // force class load and initialization + return true; + } catch (Throwable t) { // ensuring we really do catch everything + // Doug's Unsafe setup errors always have this "Could not ini.." message + if (isCausedBySecurityException(t)) { + return false; + } + throw (t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t)); + } + } + + private static boolean isCausedBySecurityException(Throwable t) { + while (t != null) { + if ((t.getMessage() != null && t.getMessage().contains("Could not initialize intrinsics")) || t instanceof SecurityException) { + return true; + } + t = t.getCause(); + } + return false; + } + + public JRubyMapBackend(Ruby runtime, RubyClass klass) { + super(runtime, klass); + } + + @JRubyMethod + public IRubyObject initialize(ThreadContext context) { + map = newCHM(); + return context.getRuntime().getNil(); + } + + @JRubyMethod + public IRubyObject initialize(ThreadContext context, IRubyObject options) { + map = toCHM(context, options); + return context.getRuntime().getNil(); + } + + private ConcurrentHashMap toCHM(ThreadContext context, IRubyObject options) { + Ruby runtime = context.getRuntime(); + if (!options.isNil() && options.respondsTo("[]")) { + IRubyObject rInitialCapacity = options.callMethod(context, "[]", runtime.newSymbol("initial_capacity")); + IRubyObject rLoadFactor = options.callMethod(context, "[]", runtime.newSymbol("load_factor")); + int initialCapacity = !rInitialCapacity.isNil() ? RubyNumeric.num2int(rInitialCapacity.convertToInteger()) : DEFAULT_INITIAL_CAPACITY; + float loadFactor = !rLoadFactor.isNil() ? (float)RubyNumeric.num2dbl(rLoadFactor.convertToFloat()) : DEFAULT_LOAD_FACTOR; + return newCHM(initialCapacity, loadFactor); + } else { + return newCHM(); + } + } + + @JRubyMethod(name = "[]", required = 1) + public IRubyObject op_aref(ThreadContext context, IRubyObject key) { + IRubyObject value; + return ((value = map.get(key)) == null) ? context.getRuntime().getNil() : value; + } + + @JRubyMethod(name = {"[]="}, required = 2) + public IRubyObject op_aset(IRubyObject key, IRubyObject value) { + map.put(key, value); + return value; + } + + @JRubyMethod + public IRubyObject put_if_absent(IRubyObject key, IRubyObject value) { + IRubyObject result = map.putIfAbsent(key, value); + return result == null ? getRuntime().getNil() : result; + } + + @JRubyMethod + public IRubyObject compute_if_absent(final ThreadContext context, final IRubyObject key, final Block block) { + return map.computeIfAbsent(key, new ConcurrentHashMap.Fun() { + @Override + public IRubyObject apply(IRubyObject key) { + return block.yieldSpecific(context); + } + }); + } + + @JRubyMethod + public IRubyObject compute_if_present(final ThreadContext context, final IRubyObject key, final Block block) { + IRubyObject result = map.computeIfPresent(key, new ConcurrentHashMap.BiFun() { + @Override + public IRubyObject apply(IRubyObject key, IRubyObject oldValue) { + IRubyObject result = block.yieldSpecific(context, oldValue == null ? context.getRuntime().getNil() : oldValue); + return result.isNil() ? null : result; + } + }); + return result == null ? context.getRuntime().getNil() : result; + } + + @JRubyMethod + public IRubyObject compute(final ThreadContext context, final IRubyObject key, final Block block) { + IRubyObject result = map.compute(key, new ConcurrentHashMap.BiFun() { + @Override + public IRubyObject apply(IRubyObject key, IRubyObject oldValue) { + IRubyObject result = block.yieldSpecific(context, oldValue == null ? context.getRuntime().getNil() : oldValue); + return result.isNil() ? null : result; + } + }); + return result == null ? context.getRuntime().getNil() : result; + } + + @JRubyMethod + public IRubyObject merge_pair(final ThreadContext context, final IRubyObject key, final IRubyObject value, final Block block) { + IRubyObject result = map.merge(key, value, new ConcurrentHashMap.BiFun() { + @Override + public IRubyObject apply(IRubyObject oldValue, IRubyObject newValue) { + IRubyObject result = block.yieldSpecific(context, oldValue == null ? context.getRuntime().getNil() : oldValue); + return result.isNil() ? null : result; + } + }); + return result == null ? context.getRuntime().getNil() : result; + } + + @JRubyMethod + public RubyBoolean replace_pair(IRubyObject key, IRubyObject oldValue, IRubyObject newValue) { + return getRuntime().newBoolean(map.replace(key, oldValue, newValue)); + } + + @JRubyMethod(name = "key?", required = 1) + public RubyBoolean has_key_p(IRubyObject key) { + return map.containsKey(key) ? getRuntime().getTrue() : getRuntime().getFalse(); + } + + @JRubyMethod + public IRubyObject key(IRubyObject value) { + final IRubyObject key = map.findKey(value); + return key == null ? getRuntime().getNil() : key; + } + + @JRubyMethod + public IRubyObject replace_if_exists(IRubyObject key, IRubyObject value) { + IRubyObject result = map.replace(key, value); + return result == null ? getRuntime().getNil() : result; + } + + @JRubyMethod + public IRubyObject get_and_set(IRubyObject key, IRubyObject value) { + IRubyObject result = map.put(key, value); + return result == null ? getRuntime().getNil() : result; + } + + @JRubyMethod + public IRubyObject delete(IRubyObject key) { + IRubyObject result = map.remove(key); + return result == null ? getRuntime().getNil() : result; + } + + @JRubyMethod + public RubyBoolean delete_pair(IRubyObject key, IRubyObject value) { + return getRuntime().newBoolean(map.remove(key, value)); + } + + @JRubyMethod + public IRubyObject clear() { + map.clear(); + return this; + } + + @JRubyMethod + public IRubyObject each_pair(ThreadContext context, Block block) { + for (Map.Entry entry : map.entrySet()) { + block.yieldSpecific(context, entry.getKey(), entry.getValue()); + } + return this; + } + + @JRubyMethod + public RubyFixnum size(ThreadContext context) { + return context.getRuntime().newFixnum(map.size()); + } + + @JRubyMethod + public IRubyObject get_or_default(IRubyObject key, IRubyObject defaultValue) { + return map.getValueOrDefault(key, defaultValue); + } + + @JRubyMethod(visibility = PRIVATE) + public JRubyMapBackend initialize_copy(ThreadContext context, IRubyObject other) { + map = newCHM(); + return this; + } + } +} diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java new file mode 100644 index 00000000..b5660762 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java @@ -0,0 +1,93 @@ +package com.concurrent_ruby.ext; + +import org.jruby.Ruby; +import org.jruby.RubyBoolean; +import org.jruby.RubyClass; +import org.jruby.RubyModule; +import org.jruby.RubyNil; +import org.jruby.RubyObject; +import org.jruby.anno.JRubyClass; +import org.jruby.anno.JRubyMethod; +import org.jruby.runtime.ObjectAllocator; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.runtime.load.Library; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; + +public class JavaAtomicBooleanLibrary implements Library { + + public void load(Ruby runtime, boolean wrap) throws IOException { + RubyModule concurrentMod = runtime.defineModule("Concurrent"); + RubyClass atomicCls = concurrentMod.defineClassUnder("JavaAtomicBoolean", runtime.getObject(), JRUBYREFERENCE_ALLOCATOR); + atomicCls.defineAnnotatedMethods(JavaAtomicBoolean.class); + } + + private static final ObjectAllocator JRUBYREFERENCE_ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new JavaAtomicBoolean(runtime, klazz); + } + }; + + @JRubyClass(name = "JavaAtomicBoolean", parent = "Object") + public static class JavaAtomicBoolean extends RubyObject { + + private AtomicBoolean atomicBoolean; + + public JavaAtomicBoolean(Ruby runtime, RubyClass metaClass) { + super(runtime, metaClass); + } + + @JRubyMethod + public IRubyObject initialize(ThreadContext context, IRubyObject value) { + atomicBoolean = new AtomicBoolean(convertRubyBooleanToJavaBoolean(value)); + return context.nil; + } + + @JRubyMethod + public IRubyObject initialize(ThreadContext context) { + atomicBoolean = new AtomicBoolean(); + return context.nil; + } + + @JRubyMethod(name = "value") + public IRubyObject value() { + return getRuntime().newBoolean(atomicBoolean.get()); + } + + @JRubyMethod(name = "true?") + public IRubyObject isAtomicTrue() { + return getRuntime().newBoolean(atomicBoolean.get()); + } + + @JRubyMethod(name = "false?") + public IRubyObject isAtomicFalse() { + return getRuntime().newBoolean((atomicBoolean.get() == false)); + } + + @JRubyMethod(name = "value=") + public IRubyObject setAtomic(ThreadContext context, IRubyObject newValue) { + atomicBoolean.set(convertRubyBooleanToJavaBoolean(newValue)); + return context.nil; + } + + @JRubyMethod(name = "make_true") + public IRubyObject makeTrue() { + return getRuntime().newBoolean(atomicBoolean.compareAndSet(false, true)); + } + + @JRubyMethod(name = "make_false") + public IRubyObject makeFalse() { + return getRuntime().newBoolean(atomicBoolean.compareAndSet(true, false)); + } + + private boolean convertRubyBooleanToJavaBoolean(IRubyObject newValue) { + if (newValue instanceof RubyBoolean.False || newValue instanceof RubyNil) { + return false; + } else { + return true; + } + } + } +} diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java new file mode 100644 index 00000000..672bfc04 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java @@ -0,0 +1,113 @@ +package com.concurrent_ruby.ext; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicLong; +import org.jruby.Ruby; +import org.jruby.RubyClass; +import org.jruby.RubyFixnum; +import org.jruby.RubyModule; +import org.jruby.RubyObject; +import org.jruby.anno.JRubyClass; +import org.jruby.anno.JRubyMethod; +import org.jruby.runtime.ObjectAllocator; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.runtime.load.Library; +import org.jruby.runtime.Block; + +public class JavaAtomicFixnumLibrary implements Library { + + public void load(Ruby runtime, boolean wrap) throws IOException { + RubyModule concurrentMod = runtime.defineModule("Concurrent"); + RubyClass atomicCls = concurrentMod.defineClassUnder("JavaAtomicFixnum", runtime.getObject(), JRUBYREFERENCE_ALLOCATOR); + + atomicCls.defineAnnotatedMethods(JavaAtomicFixnum.class); + } + + private static final ObjectAllocator JRUBYREFERENCE_ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new JavaAtomicFixnum(runtime, klazz); + } + }; + + @JRubyClass(name = "JavaAtomicFixnum", parent = "Object") + public static class JavaAtomicFixnum extends RubyObject { + + private AtomicLong atomicLong; + + public JavaAtomicFixnum(Ruby runtime, RubyClass metaClass) { + super(runtime, metaClass); + } + + @JRubyMethod + public IRubyObject initialize(ThreadContext context) { + this.atomicLong = new AtomicLong(0); + return context.nil; + } + + @JRubyMethod + public IRubyObject initialize(ThreadContext context, IRubyObject value) { + this.atomicLong = new AtomicLong(rubyFixnumToLong(value)); + return context.nil; + } + + @JRubyMethod(name = "value") + public IRubyObject getValue() { + return getRuntime().newFixnum(atomicLong.get()); + } + + @JRubyMethod(name = "value=") + public IRubyObject setValue(ThreadContext context, IRubyObject newValue) { + atomicLong.set(rubyFixnumToLong(newValue)); + return context.nil; + } + + @JRubyMethod(name = {"increment", "up"}) + public IRubyObject increment() { + return getRuntime().newFixnum(atomicLong.incrementAndGet()); + } + + @JRubyMethod(name = {"increment", "up"}) + public IRubyObject increment(IRubyObject value) { + long delta = rubyFixnumToLong(value); + return getRuntime().newFixnum(atomicLong.addAndGet(delta)); + } + + @JRubyMethod(name = {"decrement", "down"}) + public IRubyObject decrement() { + return getRuntime().newFixnum(atomicLong.decrementAndGet()); + } + + @JRubyMethod(name = {"decrement", "down"}) + public IRubyObject decrement(IRubyObject value) { + long delta = rubyFixnumToLong(value); + return getRuntime().newFixnum(atomicLong.addAndGet(-delta)); + } + + @JRubyMethod(name = "compare_and_set") + public IRubyObject compareAndSet(ThreadContext context, IRubyObject expect, IRubyObject update) { + return getRuntime().newBoolean(atomicLong.compareAndSet(rubyFixnumToLong(expect), rubyFixnumToLong(update))); + } + + @JRubyMethod + public IRubyObject update(ThreadContext context, Block block) { + for (;;) { + long _oldValue = atomicLong.get(); + IRubyObject oldValue = getRuntime().newFixnum(_oldValue); + IRubyObject newValue = block.yield(context, oldValue); + if (atomicLong.compareAndSet(_oldValue, rubyFixnumToLong(newValue))) { + return newValue; + } + } + } + + private long rubyFixnumToLong(IRubyObject value) { + if (value instanceof RubyFixnum) { + RubyFixnum fixNum = (RubyFixnum) value; + return fixNum.getLongValue(); + } else { + throw getRuntime().newArgumentError("value must be a Fixnum"); + } + } + } +} diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java new file mode 100644 index 00000000..d887f250 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java @@ -0,0 +1,189 @@ +package com.concurrent_ruby.ext; + +import java.io.IOException; +import java.util.concurrent.Semaphore; +import org.jruby.Ruby; +import org.jruby.RubyClass; +import org.jruby.RubyFixnum; +import org.jruby.RubyModule; +import org.jruby.RubyNumeric; +import org.jruby.RubyObject; +import org.jruby.anno.JRubyClass; +import org.jruby.anno.JRubyMethod; +import org.jruby.runtime.Block; +import org.jruby.runtime.ObjectAllocator; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; + +public class JavaSemaphoreLibrary { + + public void load(Ruby runtime, boolean wrap) throws IOException { + RubyModule concurrentMod = runtime.defineModule("Concurrent"); + RubyClass atomicCls = concurrentMod.defineClassUnder("JavaSemaphore", runtime.getObject(), JRUBYREFERENCE_ALLOCATOR); + + atomicCls.defineAnnotatedMethods(JavaSemaphore.class); + } + + private static final ObjectAllocator JRUBYREFERENCE_ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new JavaSemaphore(runtime, klazz); + } + }; + + @JRubyClass(name = "JavaSemaphore", parent = "Object") + public static class JavaSemaphore extends RubyObject { + + private JRubySemaphore semaphore; + + public JavaSemaphore(Ruby runtime, RubyClass metaClass) { + super(runtime, metaClass); + } + + @JRubyMethod + public IRubyObject initialize(ThreadContext context, IRubyObject value) { + this.semaphore = new JRubySemaphore(rubyFixnumInt(value, "count")); + return context.nil; + } + + @JRubyMethod + public IRubyObject acquire(ThreadContext context, final Block block) throws InterruptedException { + return this.acquire(context, 1, block); + } + + @JRubyMethod + public IRubyObject acquire(ThreadContext context, IRubyObject permits, final Block block) throws InterruptedException { + return this.acquire(context, rubyFixnumToPositiveInt(permits, "permits"), block); + } + + @JRubyMethod(name = "available_permits") + public IRubyObject availablePermits(ThreadContext context) { + return getRuntime().newFixnum(this.semaphore.availablePermits()); + } + + @JRubyMethod(name = "drain_permits") + public IRubyObject drainPermits(ThreadContext context) { + return getRuntime().newFixnum(this.semaphore.drainPermits()); + } + + @JRubyMethod(name = "try_acquire") + public IRubyObject tryAcquire(ThreadContext context, final Block block) throws InterruptedException { + int permitsInt = 1; + boolean acquired = semaphore.tryAcquire(permitsInt); + + return triedAcquire(context, permitsInt, acquired, block); + } + + @JRubyMethod(name = "try_acquire") + public IRubyObject tryAcquire(ThreadContext context, IRubyObject permits, final Block block) throws InterruptedException { + int permitsInt = rubyFixnumToPositiveInt(permits, "permits"); + boolean acquired = semaphore.tryAcquire(permitsInt); + + return triedAcquire(context, permitsInt, acquired, block); + } + + @JRubyMethod(name = "try_acquire") + public IRubyObject tryAcquire(ThreadContext context, IRubyObject permits, IRubyObject timeout, final Block block) throws InterruptedException { + int permitsInt = rubyFixnumToPositiveInt(permits, "permits"); + boolean acquired = semaphore.tryAcquire( + permitsInt, + rubyNumericToLong(timeout, "timeout"), + java.util.concurrent.TimeUnit.SECONDS + ); + + return triedAcquire(context, permitsInt, acquired, block); + } + + @JRubyMethod + public IRubyObject release(ThreadContext context) { + this.semaphore.release(1); + return getRuntime().newBoolean(true); + } + + @JRubyMethod + public IRubyObject release(ThreadContext context, IRubyObject permits) { + this.semaphore.release(rubyFixnumToPositiveInt(permits, "permits")); + return getRuntime().newBoolean(true); + } + + @JRubyMethod(name = "reduce_permits") + public IRubyObject reducePermits(ThreadContext context, IRubyObject reduction) throws InterruptedException { + this.semaphore.publicReducePermits(rubyFixnumToNonNegativeInt(reduction, "reduction")); + return context.nil; + } + + private IRubyObject acquire(ThreadContext context, int permits, final Block block) throws InterruptedException { + this.semaphore.acquire(permits); + + if (!block.isGiven()) return context.nil; + + try { + return block.yieldSpecific(context); + } finally { + this.semaphore.release(permits); + } + } + + private IRubyObject triedAcquire(ThreadContext context, int permits, boolean acquired, final Block block) { + if (!block.isGiven()) return getRuntime().newBoolean(acquired); + if (!acquired) return context.nil; + + try { + return block.yieldSpecific(context); + } finally { + this.semaphore.release(permits); + } + } + + private int rubyFixnumInt(IRubyObject value, String paramName) { + if (value instanceof RubyFixnum) { + RubyFixnum fixNum = (RubyFixnum) value; + return (int) fixNum.getLongValue(); + } else { + throw getRuntime().newArgumentError(paramName + " must be integer"); + } + } + + private int rubyFixnumToNonNegativeInt(IRubyObject value, String paramName) { + if (value instanceof RubyFixnum && ((RubyFixnum) value).getLongValue() >= 0) { + RubyFixnum fixNum = (RubyFixnum) value; + return (int) fixNum.getLongValue(); + } else { + throw getRuntime().newArgumentError(paramName + " must be a non-negative integer"); + } + } + + private int rubyFixnumToPositiveInt(IRubyObject value, String paramName) { + if (value instanceof RubyFixnum && ((RubyFixnum) value).getLongValue() > 0) { + RubyFixnum fixNum = (RubyFixnum) value; + return (int) fixNum.getLongValue(); + } else { + throw getRuntime().newArgumentError(paramName + " must be an integer greater than zero"); + } + } + + private long rubyNumericToLong(IRubyObject value, String paramName) { + if (value instanceof RubyNumeric && ((RubyNumeric) value).getDoubleValue() > 0) { + RubyNumeric fixNum = (RubyNumeric) value; + return fixNum.getLongValue(); + } else { + throw getRuntime().newArgumentError(paramName + " must be a float greater than zero"); + } + } + + class JRubySemaphore extends Semaphore { + + public JRubySemaphore(int permits) { + super(permits); + } + + public JRubySemaphore(int permits, boolean value) { + super(permits, value); + } + + public void publicReducePermits(int i) { + reducePermits(i); + } + + } + } +} diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java new file mode 100644 index 00000000..f0c75ee4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java @@ -0,0 +1,292 @@ +package com.concurrent_ruby.ext; + +import org.jruby.Ruby; +import org.jruby.RubyBasicObject; +import org.jruby.RubyClass; +import org.jruby.RubyModule; +import org.jruby.RubyObject; +import org.jruby.RubyThread; +import org.jruby.anno.JRubyClass; +import org.jruby.anno.JRubyMethod; +import org.jruby.runtime.Block; +import org.jruby.runtime.ObjectAllocator; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.Visibility; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.runtime.load.Library; +import sun.misc.Unsafe; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class SynchronizationLibrary implements Library { + + private static final Unsafe UNSAFE = loadUnsafe(); + private static final boolean FULL_FENCE = supportsFences(); + + private static Unsafe loadUnsafe() { + try { + Class ncdfe = Class.forName("sun.misc.Unsafe"); + Field f = ncdfe.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return (Unsafe) f.get((java.lang.Object) null); + } catch (Exception var2) { + return null; + } catch (NoClassDefFoundError var3) { + return null; + } + } + + private static boolean supportsFences() { + if (UNSAFE == null) { + return false; + } else { + try { + Method m = UNSAFE.getClass().getDeclaredMethod("fullFence", new Class[0]); + if (m != null) { + return true; + } + } catch (Exception var1) { + // nothing + } + + return false; + } + } + + private static final ObjectAllocator OBJECT_ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new Object(runtime, klazz); + } + }; + + private static final ObjectAllocator ABSTRACT_LOCKABLE_OBJECT_ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new AbstractLockableObject(runtime, klazz); + } + }; + + private static final ObjectAllocator JRUBY_LOCKABLE_OBJECT_ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new JRubyLockableObject(runtime, klazz); + } + }; + + public void load(Ruby runtime, boolean wrap) throws IOException { + RubyModule synchronizationModule = runtime. + defineModule("Concurrent"). + defineModuleUnder("Synchronization"); + + RubyModule jrubyAttrVolatileModule = synchronizationModule.defineModuleUnder("JRubyAttrVolatile"); + jrubyAttrVolatileModule.defineAnnotatedMethods(JRubyAttrVolatile.class); + + defineClass(runtime, synchronizationModule, "AbstractObject", "Object", + Object.class, OBJECT_ALLOCATOR); + + defineClass(runtime, synchronizationModule, "Object", "AbstractLockableObject", + AbstractLockableObject.class, ABSTRACT_LOCKABLE_OBJECT_ALLOCATOR); + + defineClass(runtime, synchronizationModule, "AbstractLockableObject", "JRubyLockableObject", + JRubyLockableObject.class, JRUBY_LOCKABLE_OBJECT_ALLOCATOR); + + defineClass(runtime, synchronizationModule, "Object", "JRuby", + JRuby.class, new ObjectAllocator() { + @Override + public IRubyObject allocate(Ruby runtime, RubyClass klazz) { + return new JRuby(runtime, klazz); + } + }); + } + + private RubyClass defineClass( + Ruby runtime, + RubyModule namespace, + String parentName, + String name, + Class javaImplementation, + ObjectAllocator allocator) { + final RubyClass parentClass = namespace.getClass(parentName); + + if (parentClass == null) { + System.out.println("not found " + parentName); + throw runtime.newRuntimeError(namespace.toString() + "::" + parentName + " is missing"); + } + + final RubyClass newClass = namespace.defineClassUnder(name, parentClass, allocator); + newClass.defineAnnotatedMethods(javaImplementation); + return newClass; + } + + // Facts: + // - all ivar reads are without any synchronisation of fences see + // https://github.com/jruby/jruby/blob/master/core/src/main/java/org/jruby/runtime/ivars/VariableAccessor.java#L110-110 + // - writes depend on UnsafeHolder.U, null -> SynchronizedVariableAccessor, !null -> StampedVariableAccessor + // SynchronizedVariableAccessor wraps with synchronized block, StampedVariableAccessor uses fullFence or + // volatilePut + // TODO (pitr 16-Sep-2015): what do we do in Java 9 ? + + // module JRubyAttrVolatile + public static class JRubyAttrVolatile { + + // volatile threadContext is used as a memory barrier per the JVM memory model happens-before semantic + // on volatile fields. any volatile field could have been used but using the thread context is an + // attempt to avoid code elimination. + private static volatile int volatileField; + + @JRubyMethod(name = "full_memory_barrier", visibility = Visibility.PUBLIC, module = true) + public static IRubyObject fullMemoryBarrier(ThreadContext context, IRubyObject module) { + // Prevent reordering of ivar writes with publication of this instance + if (!FULL_FENCE) { + // Assuming that following volatile read and write is not eliminated it simulates fullFence. + // If it's eliminated it'll cause problems only on non-x86 platforms. + // http://shipilev.net/blog/2014/jmm-pragmatics/#_happens_before_test_your_understanding + final int volatileRead = volatileField; + volatileField = context.getLine(); + } else { + UNSAFE.fullFence(); + } + return context.nil; + } + + @JRubyMethod(name = "instance_variable_get_volatile", visibility = Visibility.PUBLIC, module = true) + public static IRubyObject instanceVariableGetVolatile( + ThreadContext context, + IRubyObject module, + IRubyObject self, + IRubyObject name) { + // Ensure we ses latest value with loadFence + if (!FULL_FENCE) { + // piggybacking on volatile read, simulating loadFence + final int volatileRead = volatileField; + return ((RubyBasicObject) self).instance_variable_get(context, name); + } else { + UNSAFE.loadFence(); + return ((RubyBasicObject) self).instance_variable_get(context, name); + } + } + + @JRubyMethod(name = "instance_variable_set_volatile", visibility = Visibility.PUBLIC, module = true) + public static IRubyObject InstanceVariableSetVolatile( + ThreadContext context, + IRubyObject module, + IRubyObject self, + IRubyObject name, + IRubyObject value) { + // Ensure we make last update visible + if (!FULL_FENCE) { + // piggybacking on volatile write, simulating storeFence + final IRubyObject result = ((RubyBasicObject) self).instance_variable_set(name, value); + volatileField = context.getLine(); + return result; + } else { + // JRuby uses StampedVariableAccessor which calls fullFence + // so no additional steps needed. + // See https://github.com/jruby/jruby/blob/master/core/src/main/java/org/jruby/runtime/ivars/StampedVariableAccessor.java#L151-L159 + return ((RubyBasicObject) self).instance_variable_set(name, value); + } + } + } + + @JRubyClass(name = "Object", parent = "AbstractObject") + public static class Object extends RubyObject { + + public Object(Ruby runtime, RubyClass metaClass) { + super(runtime, metaClass); + } + } + + @JRubyClass(name = "AbstractLockableObject", parent = "Object") + public static class AbstractLockableObject extends Object { + + public AbstractLockableObject(Ruby runtime, RubyClass metaClass) { + super(runtime, metaClass); + } + } + + @JRubyClass(name = "JRubyLockableObject", parent = "AbstractLockableObject") + public static class JRubyLockableObject extends AbstractLockableObject { + + public JRubyLockableObject(Ruby runtime, RubyClass metaClass) { + super(runtime, metaClass); + } + + @JRubyMethod(name = "synchronize", visibility = Visibility.PROTECTED) + public IRubyObject rubySynchronize(ThreadContext context, Block block) { + synchronized (this) { + return block.yield(context, null); + } + } + + @JRubyMethod(name = "ns_wait", optional = 1, visibility = Visibility.PROTECTED) + public IRubyObject nsWait(ThreadContext context, IRubyObject[] args) { + Ruby runtime = context.runtime; + if (args.length > 1) { + throw runtime.newArgumentError(args.length, 1); + } + Double timeout = null; + if (args.length > 0 && !args[0].isNil()) { + timeout = args[0].convertToFloat().getDoubleValue(); + if (timeout < 0) { + throw runtime.newArgumentError("time interval must be positive"); + } + } + if (Thread.interrupted()) { + throw runtime.newConcurrencyError("thread interrupted"); + } + boolean success = false; + try { + success = context.getThread().wait_timeout(this, timeout); + } catch (InterruptedException ie) { + throw runtime.newConcurrencyError(ie.getLocalizedMessage()); + } finally { + // An interrupt or timeout may have caused us to miss + // a notify that we consumed, so do another notify in + // case someone else is available to pick it up. + if (!success) { + this.notify(); + } + } + return this; + } + + @JRubyMethod(name = "ns_signal", visibility = Visibility.PROTECTED) + public IRubyObject nsSignal(ThreadContext context) { + notify(); + return this; + } + + @JRubyMethod(name = "ns_broadcast", visibility = Visibility.PROTECTED) + public IRubyObject nsBroadcast(ThreadContext context) { + notifyAll(); + return this; + } + } + + @JRubyClass(name = "JRuby") + public static class JRuby extends RubyObject { + public JRuby(Ruby runtime, RubyClass metaClass) { + super(runtime, metaClass); + } + + @JRubyMethod(name = "sleep_interruptibly", visibility = Visibility.PUBLIC, module = true) + public static IRubyObject sleepInterruptibly(final ThreadContext context, IRubyObject receiver, final Block block) { + try { + context.getThread().executeBlockingTask(new RubyThread.BlockingTask() { + @Override + public void run() throws InterruptedException { + block.call(context); + } + + @Override + public void wakeup() { + context.getThread().getNativeThread().interrupt(); + } + }); + } catch (InterruptedException e) { + throw context.runtime.newThreadError("interrupted in Concurrent::Synchronization::JRuby.sleep_interruptibly"); + } + return context.nil; + } + } +} diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java new file mode 100644 index 00000000..e11e15aa --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java @@ -0,0 +1,31 @@ +package com.concurrent_ruby.ext.jsr166e; + +import java.util.Map; +import java.util.Set; + +public interface ConcurrentHashMap { + /** Interface describing a function of one argument */ + public interface Fun { T apply(A a); } + /** Interface describing a function of two arguments */ + public interface BiFun { T apply(A a, B b); } + + public V get(K key); + public V put(K key, V value); + public V putIfAbsent(K key, V value); + public V computeIfAbsent(K key, Fun mf); + public V computeIfPresent(K key, BiFun mf); + public V compute(K key, BiFun mf); + public V merge(K key, V value, BiFun mf); + public boolean replace(K key, V oldVal, V newVal); + public V replace(K key, V value); + public boolean containsKey(K key); + public boolean remove(Object key, Object value); + public V remove(K key); + public void clear(); + public Set> entrySet(); + public int size(); + public V getValueOrDefault(Object key, V defaultValue); + + public boolean containsValue(V value); + public K findKey(V value); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java new file mode 100644 index 00000000..dc9901fb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java @@ -0,0 +1,3863 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This is based on the 1.79 version. + +package com.concurrent_ruby.ext.jsr166e; + +import org.jruby.RubyClass; +import org.jruby.RubyNumeric; +import org.jruby.RubyObject; +import org.jruby.exceptions.RaiseException; +import com.concurrent_ruby.ext.jsr166y.ThreadLocalRandom; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; + +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.Collection; +import java.util.Hashtable; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Enumeration; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.AbstractQueuedSynchronizer; + +import java.io.Serializable; + +/** + * A hash table supporting full concurrency of retrievals and + * high expected concurrency for updates. This class obeys the + * same functional specification as {@link java.util.Hashtable}, and + * includes versions of methods corresponding to each method of + * {@code Hashtable}. However, even though all operations are + * thread-safe, retrieval operations do not entail locking, + * and there is not any support for locking the entire table + * in a way that prevents all access. This class is fully + * interoperable with {@code Hashtable} in programs that rely on its + * thread safety but not on its synchronization details. + * + *

    Retrieval operations (including {@code get}) generally do not + * block, so may overlap with update operations (including {@code put} + * and {@code remove}). Retrievals reflect the results of the most + * recently completed update operations holding upon their + * onset. (More formally, an update operation for a given key bears a + * happens-before relation with any (non-null) retrieval for + * that key reporting the updated value.) For aggregate operations + * such as {@code putAll} and {@code clear}, concurrent retrievals may + * reflect insertion or removal of only some entries. Similarly, + * Iterators and Enumerations return elements reflecting the state of + * the hash table at some point at or since the creation of the + * iterator/enumeration. They do not throw {@link + * ConcurrentModificationException}. However, iterators are designed + * to be used by only one thread at a time. Bear in mind that the + * results of aggregate status methods including {@code size}, {@code + * isEmpty}, and {@code containsValue} are typically useful only when + * a map is not undergoing concurrent updates in other threads. + * Otherwise the results of these methods reflect transient states + * that may be adequate for monitoring or estimation purposes, but not + * for program control. + * + *

    The table is dynamically expanded when there are too many + * collisions (i.e., keys that have distinct hash codes but fall into + * the same slot modulo the table size), with the expected average + * effect of maintaining roughly two bins per mapping (corresponding + * to a 0.75 load factor threshold for resizing). There may be much + * variance around this average as mappings are added and removed, but + * overall, this maintains a commonly accepted time/space tradeoff for + * hash tables. However, resizing this or any other kind of hash + * table may be a relatively slow operation. When possible, it is a + * good idea to provide a size estimate as an optional {@code + * initialCapacity} constructor argument. An additional optional + * {@code loadFactor} constructor argument provides a further means of + * customizing initial table capacity by specifying the table density + * to be used in calculating the amount of space to allocate for the + * given number of elements. Also, for compatibility with previous + * versions of this class, constructors may optionally specify an + * expected {@code concurrencyLevel} as an additional hint for + * internal sizing. Note that using many keys with exactly the same + * {@code hashCode()} is a sure way to slow down performance of any + * hash table. + * + *

    A {@link Set} projection of a ConcurrentHashMapV8 may be created + * (using {@link #newKeySet()} or {@link #newKeySet(int)}), or viewed + * (using {@link #keySet(Object)} when only keys are of interest, and the + * mapped values are (perhaps transiently) not used or all take the + * same mapping value. + * + *

    A ConcurrentHashMapV8 can be used as scalable frequency map (a + * form of histogram or multiset) by using {@link LongAdder} values + * and initializing via {@link #computeIfAbsent}. For example, to add + * a count to a {@code ConcurrentHashMapV8 freqs}, you + * can use {@code freqs.computeIfAbsent(k -> new + * LongAdder()).increment();} + * + *

    This class and its views and iterators implement all of the + * optional methods of the {@link Map} and {@link Iterator} + * interfaces. + * + *

    Like {@link Hashtable} but unlike {@link HashMap}, this class + * does not allow {@code null} to be used as a key or value. + * + *

    ConcurrentHashMapV8s support parallel operations using the {@link + * ForkJoinPool#commonPool}. (Tasks that may be used in other contexts + * are available in class {@link ForkJoinTasks}). These operations are + * designed to be safely, and often sensibly, applied even with maps + * that are being concurrently updated by other threads; for example, + * when computing a snapshot summary of the values in a shared + * registry. There are three kinds of operation, each with four + * forms, accepting functions with Keys, Values, Entries, and (Key, + * Value) arguments and/or return values. (The first three forms are + * also available via the {@link #keySet()}, {@link #values()} and + * {@link #entrySet()} views). Because the elements of a + * ConcurrentHashMapV8 are not ordered in any particular way, and may be + * processed in different orders in different parallel executions, the + * correctness of supplied functions should not depend on any + * ordering, or on any other objects or values that may transiently + * change while computation is in progress; and except for forEach + * actions, should ideally be side-effect-free. + * + *

      + *
    • forEach: Perform a given action on each element. + * A variant form applies a given transformation on each element + * before performing the action.
    • + * + *
    • search: Return the first available non-null result of + * applying a given function on each element; skipping further + * search when a result is found.
    • + * + *
    • reduce: Accumulate each element. The supplied reduction + * function cannot rely on ordering (more formally, it should be + * both associative and commutative). There are five variants: + * + *
        + * + *
      • Plain reductions. (There is not a form of this method for + * (key, value) function arguments since there is no corresponding + * return type.)
      • + * + *
      • Mapped reductions that accumulate the results of a given + * function applied to each element.
      • + * + *
      • Reductions to scalar doubles, longs, and ints, using a + * given basis value.
      • + * + * + *
      + *
    + * + *

    The concurrency properties of bulk operations follow + * from those of ConcurrentHashMapV8: Any non-null result returned + * from {@code get(key)} and related access methods bears a + * happens-before relation with the associated insertion or + * update. The result of any bulk operation reflects the + * composition of these per-element relations (but is not + * necessarily atomic with respect to the map as a whole unless it + * is somehow known to be quiescent). Conversely, because keys + * and values in the map are never null, null serves as a reliable + * atomic indicator of the current lack of any result. To + * maintain this property, null serves as an implicit basis for + * all non-scalar reduction operations. For the double, long, and + * int versions, the basis should be one that, when combined with + * any other value, returns that other value (more formally, it + * should be the identity element for the reduction). Most common + * reductions have these properties; for example, computing a sum + * with basis 0 or a minimum with basis MAX_VALUE. + * + *

    Search and transformation functions provided as arguments + * should similarly return null to indicate the lack of any result + * (in which case it is not used). In the case of mapped + * reductions, this also enables transformations to serve as + * filters, returning null (or, in the case of primitive + * specializations, the identity basis) if the element should not + * be combined. You can create compound transformations and + * filterings by composing them yourself under this "null means + * there is nothing there now" rule before using them in search or + * reduce operations. + * + *

    Methods accepting and/or returning Entry arguments maintain + * key-value associations. They may be useful for example when + * finding the key for the greatest value. Note that "plain" Entry + * arguments can be supplied using {@code new + * AbstractMap.SimpleEntry(k,v)}. + * + *

    Bulk operations may complete abruptly, throwing an + * exception encountered in the application of a supplied + * function. Bear in mind when handling such exceptions that other + * concurrently executing functions could also have thrown + * exceptions, or would have done so if the first exception had + * not occurred. + * + *

    Parallel speedups for bulk operations compared to sequential + * processing are common but not guaranteed. Operations involving + * brief functions on small maps may execute more slowly than + * sequential loops if the underlying work to parallelize the + * computation is more expensive than the computation itself. + * Similarly, parallelization may not lead to much actual parallelism + * if all processors are busy performing unrelated tasks. + * + *

    All arguments to all task methods must be non-null. + * + *

    jsr166e note: During transition, this class + * uses nested functional interfaces with different names but the + * same forms as those expected for JDK8. + * + *

    This class is a member of the + * + * Java Collections Framework. + * + * @since 1.5 + * @author Doug Lea + * @param the type of keys maintained by this map + * @param the type of mapped values + */ +public class ConcurrentHashMapV8 + implements ConcurrentMap, Serializable, ConcurrentHashMap { + private static final long serialVersionUID = 7249069246763182397L; + + /** + * A partitionable iterator. A Spliterator can be traversed + * directly, but can also be partitioned (before traversal) by + * creating another Spliterator that covers a non-overlapping + * portion of the elements, and so may be amenable to parallel + * execution. + * + *

    This interface exports a subset of expected JDK8 + * functionality. + * + *

    Sample usage: Here is one (of the several) ways to compute + * the sum of the values held in a map using the ForkJoin + * framework. As illustrated here, Spliterators are well suited to + * designs in which a task repeatedly splits off half its work + * into forked subtasks until small enough to process directly, + * and then joins these subtasks. Variants of this style can also + * be used in completion-based designs. + * + *

    +     * {@code ConcurrentHashMapV8 m = ...
    +     * // split as if have 8 * parallelism, for load balance
    +     * int n = m.size();
    +     * int p = aForkJoinPool.getParallelism() * 8;
    +     * int split = (n < p)? n : p;
    +     * long sum = aForkJoinPool.invoke(new SumValues(m.valueSpliterator(), split, null));
    +     * // ...
    +     * static class SumValues extends RecursiveTask {
    +     *   final Spliterator s;
    +     *   final int split;             // split while > 1
    +     *   final SumValues nextJoin;    // records forked subtasks to join
    +     *   SumValues(Spliterator s, int depth, SumValues nextJoin) {
    +     *     this.s = s; this.depth = depth; this.nextJoin = nextJoin;
    +     *   }
    +     *   public Long compute() {
    +     *     long sum = 0;
    +     *     SumValues subtasks = null; // fork subtasks
    +     *     for (int s = split >>> 1; s > 0; s >>>= 1)
    +     *       (subtasks = new SumValues(s.split(), s, subtasks)).fork();
    +     *     while (s.hasNext())        // directly process remaining elements
    +     *       sum += s.next();
    +     *     for (SumValues t = subtasks; t != null; t = t.nextJoin)
    +     *       sum += t.join();         // collect subtask results
    +     *     return sum;
    +     *   }
    +     * }
    +     * }
    + */ + public static interface Spliterator extends Iterator { + /** + * Returns a Spliterator covering approximately half of the + * elements, guaranteed not to overlap with those subsequently + * returned by this Spliterator. After invoking this method, + * the current Spliterator will not produce any of + * the elements of the returned Spliterator, but the two + * Spliterators together will produce all of the elements that + * would have been produced by this Spliterator had this + * method not been called. The exact number of elements + * produced by the returned Spliterator is not guaranteed, and + * may be zero (i.e., with {@code hasNext()} reporting {@code + * false}) if this Spliterator cannot be further split. + * + * @return a Spliterator covering approximately half of the + * elements + * @throws IllegalStateException if this Spliterator has + * already commenced traversing elements + */ + Spliterator split(); + } + + + /* + * Overview: + * + * The primary design goal of this hash table is to maintain + * concurrent readability (typically method get(), but also + * iterators and related methods) while minimizing update + * contention. Secondary goals are to keep space consumption about + * the same or better than java.util.HashMap, and to support high + * initial insertion rates on an empty table by many threads. + * + * Each key-value mapping is held in a Node. Because Node fields + * can contain special values, they are defined using plain Object + * types. Similarly in turn, all internal methods that use them + * work off Object types. And similarly, so do the internal + * methods of auxiliary iterator and view classes. All public + * generic typed methods relay in/out of these internal methods, + * supplying null-checks and casts as needed. This also allows + * many of the public methods to be factored into a smaller number + * of internal methods (although sadly not so for the five + * variants of put-related operations). The validation-based + * approach explained below leads to a lot of code sprawl because + * retry-control precludes factoring into smaller methods. + * + * The table is lazily initialized to a power-of-two size upon the + * first insertion. Each bin in the table normally contains a + * list of Nodes (most often, the list has only zero or one Node). + * Table accesses require volatile/atomic reads, writes, and + * CASes. Because there is no other way to arrange this without + * adding further indirections, we use intrinsics + * (sun.misc.Unsafe) operations. The lists of nodes within bins + * are always accurately traversable under volatile reads, so long + * as lookups check hash code and non-nullness of value before + * checking key equality. + * + * We use the top two bits of Node hash fields for control + * purposes -- they are available anyway because of addressing + * constraints. As explained further below, these top bits are + * used as follows: + * 00 - Normal + * 01 - Locked + * 11 - Locked and may have a thread waiting for lock + * 10 - Node is a forwarding node + * + * The lower 30 bits of each Node's hash field contain a + * transformation of the key's hash code, except for forwarding + * nodes, for which the lower bits are zero (and so always have + * hash field == MOVED). + * + * Insertion (via put or its variants) of the first node in an + * empty bin is performed by just CASing it to the bin. This is + * by far the most common case for put operations under most + * key/hash distributions. Other update operations (insert, + * delete, and replace) require locks. We do not want to waste + * the space required to associate a distinct lock object with + * each bin, so instead use the first node of a bin list itself as + * a lock. Blocking support for these locks relies on the builtin + * "synchronized" monitors. However, we also need a tryLock + * construction, so we overlay these by using bits of the Node + * hash field for lock control (see above), and so normally use + * builtin monitors only for blocking and signalling using + * wait/notifyAll constructions. See Node.tryAwaitLock. + * + * Using the first node of a list as a lock does not by itself + * suffice though: When a node is locked, any update must first + * validate that it is still the first node after locking it, and + * retry if not. Because new nodes are always appended to lists, + * once a node is first in a bin, it remains first until deleted + * or the bin becomes invalidated (upon resizing). However, + * operations that only conditionally update may inspect nodes + * until the point of update. This is a converse of sorts to the + * lazy locking technique described by Herlihy & Shavit. + * + * The main disadvantage of per-bin locks is that other update + * operations on other nodes in a bin list protected by the same + * lock can stall, for example when user equals() or mapping + * functions take a long time. However, statistically, under + * random hash codes, this is not a common problem. Ideally, the + * frequency of nodes in bins follows a Poisson distribution + * (http://en.wikipedia.org/wiki/Poisson_distribution) with a + * parameter of about 0.5 on average, given the resizing threshold + * of 0.75, although with a large variance because of resizing + * granularity. Ignoring variance, the expected occurrences of + * list size k are (exp(-0.5) * pow(0.5, k) / factorial(k)). The + * first values are: + * + * 0: 0.60653066 + * 1: 0.30326533 + * 2: 0.07581633 + * 3: 0.01263606 + * 4: 0.00157952 + * 5: 0.00015795 + * 6: 0.00001316 + * 7: 0.00000094 + * 8: 0.00000006 + * more: less than 1 in ten million + * + * Lock contention probability for two threads accessing distinct + * elements is roughly 1 / (8 * #elements) under random hashes. + * + * Actual hash code distributions encountered in practice + * sometimes deviate significantly from uniform randomness. This + * includes the case when N > (1<<30), so some keys MUST collide. + * Similarly for dumb or hostile usages in which multiple keys are + * designed to have identical hash codes. Also, although we guard + * against the worst effects of this (see method spread), sets of + * hashes may differ only in bits that do not impact their bin + * index for a given power-of-two mask. So we use a secondary + * strategy that applies when the number of nodes in a bin exceeds + * a threshold, and at least one of the keys implements + * Comparable. These TreeBins use a balanced tree to hold nodes + * (a specialized form of red-black trees), bounding search time + * to O(log N). Each search step in a TreeBin is around twice as + * slow as in a regular list, but given that N cannot exceed + * (1<<64) (before running out of addresses) this bounds search + * steps, lock hold times, etc, to reasonable constants (roughly + * 100 nodes inspected per operation worst case) so long as keys + * are Comparable (which is very common -- String, Long, etc). + * TreeBin nodes (TreeNodes) also maintain the same "next" + * traversal pointers as regular nodes, so can be traversed in + * iterators in the same way. + * + * The table is resized when occupancy exceeds a percentage + * threshold (nominally, 0.75, but see below). Only a single + * thread performs the resize (using field "sizeCtl", to arrange + * exclusion), but the table otherwise remains usable for reads + * and updates. Resizing proceeds by transferring bins, one by + * one, from the table to the next table. Because we are using + * power-of-two expansion, the elements from each bin must either + * stay at same index, or move with a power of two offset. We + * eliminate unnecessary node creation by catching cases where old + * nodes can be reused because their next fields won't change. On + * average, only about one-sixth of them need cloning when a table + * doubles. The nodes they replace will be garbage collectable as + * soon as they are no longer referenced by any reader thread that + * may be in the midst of concurrently traversing table. Upon + * transfer, the old table bin contains only a special forwarding + * node (with hash field "MOVED") that contains the next table as + * its key. On encountering a forwarding node, access and update + * operations restart, using the new table. + * + * Each bin transfer requires its bin lock. However, unlike other + * cases, a transfer can skip a bin if it fails to acquire its + * lock, and revisit it later (unless it is a TreeBin). Method + * rebuild maintains a buffer of TRANSFER_BUFFER_SIZE bins that + * have been skipped because of failure to acquire a lock, and + * blocks only if none are available (i.e., only very rarely). + * The transfer operation must also ensure that all accessible + * bins in both the old and new table are usable by any traversal. + * When there are no lock acquisition failures, this is arranged + * simply by proceeding from the last bin (table.length - 1) up + * towards the first. Upon seeing a forwarding node, traversals + * (see class Iter) arrange to move to the new table + * without revisiting nodes. However, when any node is skipped + * during a transfer, all earlier table bins may have become + * visible, so are initialized with a reverse-forwarding node back + * to the old table until the new ones are established. (This + * sometimes requires transiently locking a forwarding node, which + * is possible under the above encoding.) These more expensive + * mechanics trigger only when necessary. + * + * The traversal scheme also applies to partial traversals of + * ranges of bins (via an alternate Traverser constructor) + * to support partitioned aggregate operations. Also, read-only + * operations give up if ever forwarded to a null table, which + * provides support for shutdown-style clearing, which is also not + * currently implemented. + * + * Lazy table initialization minimizes footprint until first use, + * and also avoids resizings when the first operation is from a + * putAll, constructor with map argument, or deserialization. + * These cases attempt to override the initial capacity settings, + * but harmlessly fail to take effect in cases of races. + * + * The element count is maintained using a LongAdder, which avoids + * contention on updates but can encounter cache thrashing if read + * too frequently during concurrent access. To avoid reading so + * often, resizing is attempted either when a bin lock is + * contended, or upon adding to a bin already holding two or more + * nodes (checked before adding in the xIfAbsent methods, after + * adding in others). Under uniform hash distributions, the + * probability of this occurring at threshold is around 13%, + * meaning that only about 1 in 8 puts check threshold (and after + * resizing, many fewer do so). But this approximation has high + * variance for small table sizes, so we check on any collision + * for sizes <= 64. The bulk putAll operation further reduces + * contention by only committing count updates upon these size + * checks. + * + * Maintaining API and serialization compatibility with previous + * versions of this class introduces several oddities. Mainly: We + * leave untouched but unused constructor arguments referring to + * concurrencyLevel. We accept a loadFactor constructor argument, + * but apply it only to initial table capacity (which is the only + * time that we can guarantee to honor it.) We also declare an + * unused "Segment" class that is instantiated in minimal form + * only when serializing. + */ + + /* ---------------- Constants -------------- */ + + /** + * The largest possible table capacity. This value must be + * exactly 1<<30 to stay within Java array allocation and indexing + * bounds for power of two table sizes, and is further required + * because the top two bits of 32bit hash fields are used for + * control purposes. + */ + private static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * The default initial table capacity. Must be a power of 2 + * (i.e., at least 1) and at most MAXIMUM_CAPACITY. + */ + private static final int DEFAULT_CAPACITY = 16; + + /** + * The largest possible (non-power of two) array size. + * Needed by toArray and related methods. + */ + static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + + /** + * The default concurrency level for this table. Unused but + * defined for compatibility with previous versions of this class. + */ + private static final int DEFAULT_CONCURRENCY_LEVEL = 16; + + /** + * The load factor for this table. Overrides of this value in + * constructors affect only the initial table capacity. The + * actual floating point value isn't normally used -- it is + * simpler to use expressions such as {@code n - (n >>> 2)} for + * the associated resizing threshold. + */ + private static final float LOAD_FACTOR = 0.75f; + + /** + * The buffer size for skipped bins during transfers. The + * value is arbitrary but should be large enough to avoid + * most locking stalls during resizes. + */ + private static final int TRANSFER_BUFFER_SIZE = 32; + + /** + * The bin count threshold for using a tree rather than list for a + * bin. The value reflects the approximate break-even point for + * using tree-based operations. + * Note that Doug's version defaults to 8, but when dealing with + * Ruby objects it is actually beneficial to avoid TreeNodes + * as long as possible as it usually means going into Ruby land. + */ + private static final int TREE_THRESHOLD = 16; + + /* + * Encodings for special uses of Node hash fields. See above for + * explanation. + */ + static final int MOVED = 0x80000000; // hash field for forwarding nodes + static final int LOCKED = 0x40000000; // set/tested only as a bit + static final int WAITING = 0xc0000000; // both bits set/tested together + static final int HASH_BITS = 0x3fffffff; // usable bits of normal node hash + + /* ---------------- Fields -------------- */ + + /** + * The array of bins. Lazily initialized upon first insertion. + * Size is always a power of two. Accessed directly by iterators. + */ + transient volatile Node[] table; + + /** + * The counter maintaining number of elements. + */ + private transient final LongAdder counter; + + /** + * Table initialization and resizing control. When negative, the + * table is being initialized or resized. Otherwise, when table is + * null, holds the initial table size to use upon creation, or 0 + * for default. After initialization, holds the next element count + * value upon which to resize the table. + */ + private transient volatile int sizeCtl; + + // views + private transient KeySetView keySet; + private transient ValuesView values; + private transient EntrySetView entrySet; + + /** For serialization compatibility. Null unless serialized; see below */ + private Segment[] segments; + + /* ---------------- Table element access -------------- */ + + /* + * Volatile access methods are used for table elements as well as + * elements of in-progress next table while resizing. Uses are + * null checked by callers, and implicitly bounds-checked, relying + * on the invariants that tab arrays have non-zero size, and all + * indices are masked with (tab.length - 1) which is never + * negative and always less than length. Note that, to be correct + * wrt arbitrary concurrency errors by users, bounds checks must + * operate on local variables, which accounts for some odd-looking + * inline assignments below. + */ + + static final Node tabAt(Node[] tab, int i) { // used by Iter + return (Node)UNSAFE.getObjectVolatile(tab, ((long)i< 1 ? 64 : 1; + + /** + * Spins a while if LOCKED bit set and this node is the first + * of its bin, and then sets WAITING bits on hash field and + * blocks (once) if they are still set. It is OK for this + * method to return even if lock is not available upon exit, + * which enables these simple single-wait mechanics. + * + * The corresponding signalling operation is performed within + * callers: Upon detecting that WAITING has been set when + * unlocking lock (via a failed CAS from non-waiting LOCKED + * state), unlockers acquire the sync lock and perform a + * notifyAll. + * + * The initial sanity check on tab and bounds is not currently + * necessary in the only usages of this method, but enables + * use in other future contexts. + */ + final void tryAwaitLock(Node[] tab, int i) { + if (tab != null && i >= 0 && i < tab.length) { // sanity check + int r = ThreadLocalRandom.current().nextInt(); // randomize spins + int spins = MAX_SPINS, h; + while (tabAt(tab, i) == this && ((h = hash) & LOCKED) != 0) { + if (spins >= 0) { + r ^= r << 1; r ^= r >>> 3; r ^= r << 10; // xorshift + if (r >= 0 && --spins == 0) + Thread.yield(); // yield before block + } + else if (casHash(h, h | WAITING)) { + synchronized (this) { + if (tabAt(tab, i) == this && + (hash & WAITING) == WAITING) { + try { + wait(); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + } + else + notifyAll(); // possibly won race vs signaller + } + break; + } + } + } + } + + // Unsafe mechanics for casHash + private static final sun.misc.Unsafe UNSAFE; + private static final long hashOffset; + + static { + try { + UNSAFE = getUnsafe(); + Class k = Node.class; + hashOffset = UNSAFE.objectFieldOffset + (k.getDeclaredField("hash")); + } catch (Exception e) { + throw new Error(e); + } + } + } + + /* ---------------- TreeBins -------------- */ + + /** + * Nodes for use in TreeBins + */ + static final class TreeNode extends Node { + TreeNode parent; // red-black tree links + TreeNode left; + TreeNode right; + TreeNode prev; // needed to unlink next upon deletion + boolean red; + + TreeNode(int hash, Object key, Object val, Node next, TreeNode parent) { + super(hash, key, val, next); + this.parent = parent; + } + } + + /** + * A specialized form of red-black tree for use in bins + * whose size exceeds a threshold. + * + * TreeBins use a special form of comparison for search and + * related operations (which is the main reason we cannot use + * existing collections such as TreeMaps). TreeBins contain + * Comparable elements, but may contain others, as well as + * elements that are Comparable but not necessarily Comparable + * for the same T, so we cannot invoke compareTo among them. To + * handle this, the tree is ordered primarily by hash value, then + * by getClass().getName() order, and then by Comparator order + * among elements of the same class. On lookup at a node, if + * elements are not comparable or compare as 0, both left and + * right children may need to be searched in the case of tied hash + * values. (This corresponds to the full list search that would be + * necessary if all elements were non-Comparable and had tied + * hashes.) The red-black balancing code is updated from + * pre-jdk-collections + * (http://gee.cs.oswego.edu/dl/classes/collections/RBCell.java) + * based in turn on Cormen, Leiserson, and Rivest "Introduction to + * Algorithms" (CLR). + * + * TreeBins also maintain a separate locking discipline than + * regular bins. Because they are forwarded via special MOVED + * nodes at bin heads (which can never change once established), + * we cannot use those nodes as locks. Instead, TreeBin + * extends AbstractQueuedSynchronizer to support a simple form of + * read-write lock. For update operations and table validation, + * the exclusive form of lock behaves in the same way as bin-head + * locks. However, lookups use shared read-lock mechanics to allow + * multiple readers in the absence of writers. Additionally, + * these lookups do not ever block: While the lock is not + * available, they proceed along the slow traversal path (via + * next-pointers) until the lock becomes available or the list is + * exhausted, whichever comes first. (These cases are not fast, + * but maximize aggregate expected throughput.) The AQS mechanics + * for doing this are straightforward. The lock state is held as + * AQS getState(). Read counts are negative; the write count (1) + * is positive. There are no signalling preferences among readers + * and writers. Since we don't need to export full Lock API, we + * just override the minimal AQS methods and use them directly. + */ + static final class TreeBin extends AbstractQueuedSynchronizer { + private static final long serialVersionUID = 2249069246763182397L; + transient TreeNode root; // root of tree + transient TreeNode first; // head of next-pointer list + + /* AQS overrides */ + public final boolean isHeldExclusively() { return getState() > 0; } + public final boolean tryAcquire(int ignore) { + if (compareAndSetState(0, 1)) { + setExclusiveOwnerThread(Thread.currentThread()); + return true; + } + return false; + } + public final boolean tryRelease(int ignore) { + setExclusiveOwnerThread(null); + setState(0); + return true; + } + public final int tryAcquireShared(int ignore) { + for (int c;;) { + if ((c = getState()) > 0) + return -1; + if (compareAndSetState(c, c -1)) + return 1; + } + } + public final boolean tryReleaseShared(int ignore) { + int c; + do {} while (!compareAndSetState(c = getState(), c + 1)); + return c == -1; + } + + /** From CLR */ + private void rotateLeft(TreeNode p) { + if (p != null) { + TreeNode r = p.right, pp, rl; + if ((rl = p.right = r.left) != null) + rl.parent = p; + if ((pp = r.parent = p.parent) == null) + root = r; + else if (pp.left == p) + pp.left = r; + else + pp.right = r; + r.left = p; + p.parent = r; + } + } + + /** From CLR */ + private void rotateRight(TreeNode p) { + if (p != null) { + TreeNode l = p.left, pp, lr; + if ((lr = p.left = l.right) != null) + lr.parent = p; + if ((pp = l.parent = p.parent) == null) + root = l; + else if (pp.right == p) + pp.right = l; + else + pp.left = l; + l.right = p; + p.parent = l; + } + } + + @SuppressWarnings("unchecked") final TreeNode getTreeNode + (int h, Object k, TreeNode p) { + return getTreeNode(h, (RubyObject)k, p); + } + + /** + * Returns the TreeNode (or null if not found) for the given key + * starting at given root. + */ + @SuppressWarnings("unchecked") final TreeNode getTreeNode + (int h, RubyObject k, TreeNode p) { + RubyClass c = k.getMetaClass(); boolean kNotComparable = !k.respondsTo("<=>"); + while (p != null) { + int dir, ph; RubyObject pk; RubyClass pc; + if ((ph = p.hash) == h) { + if ((pk = (RubyObject)p.key) == k || k.equals(pk)) + return p; + if (c != (pc = (RubyClass)pk.getMetaClass()) || + kNotComparable || + (dir = rubyCompare(k, pk)) == 0) { + dir = (c == pc) ? 0 : c.getName().compareTo(pc.getName()); + if (dir == 0) { // if still stuck, need to check both sides + TreeNode r = null, pl, pr; + // try to recurse on the right + if ((pr = p.right) != null && h >= pr.hash && (r = getTreeNode(h, k, pr)) != null) + return r; + // try to continue iterating on the left side + else if ((pl = p.left) != null && h <= pl.hash) + dir = -1; + else // no matching node found + return null; + } + } + } + else + dir = (h < ph) ? -1 : 1; + p = (dir > 0) ? p.right : p.left; + } + return null; + } + + int rubyCompare(RubyObject l, RubyObject r) { + ThreadContext context = l.getMetaClass().getRuntime().getCurrentContext(); + IRubyObject result; + try { + result = l.callMethod(context, "<=>", r); + } catch (RaiseException e) { + // handle objects "lying" about responding to <=>, ie: an Array containing non-comparable keys + if (context.runtime.getNoMethodError().isInstance(e.getException())) { + return 0; + } + throw e; + } + + return result.isNil() ? 0 : RubyNumeric.num2int(result.convertToInteger()); + } + + /** + * Wrapper for getTreeNode used by CHM.get. Tries to obtain + * read-lock to call getTreeNode, but during failure to get + * lock, searches along next links. + */ + final Object getValue(int h, Object k) { + Node r = null; + int c = getState(); // Must read lock state first + for (Node e = first; e != null; e = e.next) { + if (c <= 0 && compareAndSetState(c, c - 1)) { + try { + r = getTreeNode(h, k, root); + } finally { + releaseShared(0); + } + break; + } + else if ((e.hash & HASH_BITS) == h && k.equals(e.key)) { + r = e; + break; + } + else + c = getState(); + } + return r == null ? null : r.val; + } + + @SuppressWarnings("unchecked") final TreeNode putTreeNode + (int h, Object k, Object v) { + return putTreeNode(h, (RubyObject)k, v); + } + + /** + * Finds or adds a node. + * @return null if added + */ + @SuppressWarnings("unchecked") final TreeNode putTreeNode + (int h, RubyObject k, Object v) { + RubyClass c = k.getMetaClass(); + boolean kNotComparable = !k.respondsTo("<=>"); + TreeNode pp = root, p = null; + int dir = 0; + while (pp != null) { // find existing node or leaf to insert at + int ph; RubyObject pk; RubyClass pc; + p = pp; + if ((ph = p.hash) == h) { + if ((pk = (RubyObject)p.key) == k || k.equals(pk)) + return p; + if (c != (pc = pk.getMetaClass()) || + kNotComparable || + (dir = rubyCompare(k, pk)) == 0) { + dir = (c == pc) ? 0 : c.getName().compareTo(pc.getName()); + if (dir == 0) { // if still stuck, need to check both sides + TreeNode r = null, pr; + // try to recurse on the right + if ((pr = p.right) != null && h >= pr.hash && (r = getTreeNode(h, k, pr)) != null) + return r; + else // continue descending down the left subtree + dir = -1; + } + } + } + else + dir = (h < ph) ? -1 : 1; + pp = (dir > 0) ? p.right : p.left; + } + + TreeNode f = first; + TreeNode x = first = new TreeNode(h, (Object)k, v, f, p); + if (p == null) + root = x; + else { // attach and rebalance; adapted from CLR + TreeNode xp, xpp; + if (f != null) + f.prev = x; + if (dir <= 0) + p.left = x; + else + p.right = x; + x.red = true; + while (x != null && (xp = x.parent) != null && xp.red && + (xpp = xp.parent) != null) { + TreeNode xppl = xpp.left; + if (xp == xppl) { + TreeNode y = xpp.right; + if (y != null && y.red) { + y.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } + else { + if (x == xp.right) { + rotateLeft(x = xp); + xpp = (xp = x.parent) == null ? null : xp.parent; + } + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + rotateRight(xpp); + } + } + } + } + else { + TreeNode y = xppl; + if (y != null && y.red) { + y.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } + else { + if (x == xp.left) { + rotateRight(x = xp); + xpp = (xp = x.parent) == null ? null : xp.parent; + } + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + rotateLeft(xpp); + } + } + } + } + } + TreeNode r = root; + if (r != null && r.red) + r.red = false; + } + return null; + } + + /** + * Removes the given node, that must be present before this + * call. This is messier than typical red-black deletion code + * because we cannot swap the contents of an interior node + * with a leaf successor that is pinned by "next" pointers + * that are accessible independently of lock. So instead we + * swap the tree linkages. + */ + final void deleteTreeNode(TreeNode p) { + TreeNode next = (TreeNode)p.next; // unlink traversal pointers + TreeNode pred = p.prev; + if (pred == null) + first = next; + else + pred.next = next; + if (next != null) + next.prev = pred; + TreeNode replacement; + TreeNode pl = p.left; + TreeNode pr = p.right; + if (pl != null && pr != null) { + TreeNode s = pr, sl; + while ((sl = s.left) != null) // find successor + s = sl; + boolean c = s.red; s.red = p.red; p.red = c; // swap colors + TreeNode sr = s.right; + TreeNode pp = p.parent; + if (s == pr) { // p was s's direct parent + p.parent = s; + s.right = p; + } + else { + TreeNode sp = s.parent; + if ((p.parent = sp) != null) { + if (s == sp.left) + sp.left = p; + else + sp.right = p; + } + if ((s.right = pr) != null) + pr.parent = s; + } + p.left = null; + if ((p.right = sr) != null) + sr.parent = p; + if ((s.left = pl) != null) + pl.parent = s; + if ((s.parent = pp) == null) + root = s; + else if (p == pp.left) + pp.left = s; + else + pp.right = s; + replacement = sr; + } + else + replacement = (pl != null) ? pl : pr; + TreeNode pp = p.parent; + if (replacement == null) { + if (pp == null) { + root = null; + return; + } + replacement = p; + } + else { + replacement.parent = pp; + if (pp == null) + root = replacement; + else if (p == pp.left) + pp.left = replacement; + else + pp.right = replacement; + p.left = p.right = p.parent = null; + } + if (!p.red) { // rebalance, from CLR + TreeNode x = replacement; + while (x != null) { + TreeNode xp, xpl; + if (x.red || (xp = x.parent) == null) { + x.red = false; + break; + } + if (x == (xpl = xp.left)) { + TreeNode sib = xp.right; + if (sib != null && sib.red) { + sib.red = false; + xp.red = true; + rotateLeft(xp); + sib = (xp = x.parent) == null ? null : xp.right; + } + if (sib == null) + x = xp; + else { + TreeNode sl = sib.left, sr = sib.right; + if ((sr == null || !sr.red) && + (sl == null || !sl.red)) { + sib.red = true; + x = xp; + } + else { + if (sr == null || !sr.red) { + if (sl != null) + sl.red = false; + sib.red = true; + rotateRight(sib); + sib = (xp = x.parent) == null ? null : xp.right; + } + if (sib != null) { + sib.red = (xp == null) ? false : xp.red; + if ((sr = sib.right) != null) + sr.red = false; + } + if (xp != null) { + xp.red = false; + rotateLeft(xp); + } + x = root; + } + } + } + else { // symmetric + TreeNode sib = xpl; + if (sib != null && sib.red) { + sib.red = false; + xp.red = true; + rotateRight(xp); + sib = (xp = x.parent) == null ? null : xp.left; + } + if (sib == null) + x = xp; + else { + TreeNode sl = sib.left, sr = sib.right; + if ((sl == null || !sl.red) && + (sr == null || !sr.red)) { + sib.red = true; + x = xp; + } + else { + if (sl == null || !sl.red) { + if (sr != null) + sr.red = false; + sib.red = true; + rotateLeft(sib); + sib = (xp = x.parent) == null ? null : xp.left; + } + if (sib != null) { + sib.red = (xp == null) ? false : xp.red; + if ((sl = sib.left) != null) + sl.red = false; + } + if (xp != null) { + xp.red = false; + rotateRight(xp); + } + x = root; + } + } + } + } + } + if (p == replacement && (pp = p.parent) != null) { + if (p == pp.left) // detach pointers + pp.left = null; + else if (p == pp.right) + pp.right = null; + p.parent = null; + } + } + } + + /* ---------------- Collision reduction methods -------------- */ + + /** + * Spreads higher bits to lower, and also forces top 2 bits to 0. + * Because the table uses power-of-two masking, sets of hashes + * that vary only in bits above the current mask will always + * collide. (Among known examples are sets of Float keys holding + * consecutive whole numbers in small tables.) To counter this, + * we apply a transform that spreads the impact of higher bits + * downward. There is a tradeoff between speed, utility, and + * quality of bit-spreading. Because many common sets of hashes + * are already reasonably distributed across bits (so don't benefit + * from spreading), and because we use trees to handle large sets + * of collisions in bins, we don't need excessively high quality. + */ + private static final int spread(int h) { + h ^= (h >>> 18) ^ (h >>> 12); + return (h ^ (h >>> 10)) & HASH_BITS; + } + + /** + * Replaces a list bin with a tree bin. Call only when locked. + * Fails to replace if the given key is non-comparable or table + * is, or needs, resizing. + */ + private final void replaceWithTreeBin(Node[] tab, int index, Object key) { + if ((key instanceof Comparable) && + (tab.length >= MAXIMUM_CAPACITY || counter.sum() < (long)sizeCtl)) { + TreeBin t = new TreeBin(); + for (Node e = tabAt(tab, index); e != null; e = e.next) + t.putTreeNode(e.hash & HASH_BITS, e.key, e.val); + setTabAt(tab, index, new Node(MOVED, t, null, null)); + } + } + + /* ---------------- Internal access and update methods -------------- */ + + /** Implementation for get and containsKey */ + private final Object internalGet(Object k) { + int h = spread(k.hashCode()); + retry: for (Node[] tab = table; tab != null;) { + Node e, p; Object ek, ev; int eh; // locals to read fields once + for (e = tabAt(tab, (tab.length - 1) & h); e != null; e = e.next) { + if ((eh = e.hash) == MOVED) { + if ((ek = e.key) instanceof TreeBin) // search TreeBin + return ((TreeBin)ek).getValue(h, k); + else { // restart with new table + tab = (Node[])ek; + continue retry; + } + } + else if ((eh & HASH_BITS) == h && (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) + return ev; + } + break; + } + return null; + } + + /** + * Implementation for the four public remove/replace methods: + * Replaces node value with v, conditional upon match of cv if + * non-null. If resulting value is null, delete. + */ + private final Object internalReplace(Object k, Object v, Object cv) { + int h = spread(k.hashCode()); + Object oldVal = null; + for (Node[] tab = table;;) { + Node f; int i, fh; Object fk; + if (tab == null || + (f = tabAt(tab, i = (tab.length - 1) & h)) == null) + break; + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + boolean validated = false; + boolean deleted = false; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + validated = true; + TreeNode p = t.getTreeNode(h, k, t.root); + if (p != null) { + Object pv = p.val; + if (cv == null || cv == pv || cv.equals(pv)) { + oldVal = pv; + if ((p.val = v) == null) { + deleted = true; + t.deleteTreeNode(p); + } + } + } + } + } finally { + t.release(0); + } + if (validated) { + if (deleted) + counter.add(-1L); + break; + } + } + else + tab = (Node[])fk; + } + else if ((fh & HASH_BITS) != h && f.next == null) // precheck + break; // rules out possible existence + else if ((fh & LOCKED) != 0) { + checkForResize(); // try resizing if can't get lock + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + boolean validated = false; + boolean deleted = false; + try { + if (tabAt(tab, i) == f) { + validated = true; + for (Node e = f, pred = null;;) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + ((ev = e.val) != null) && + ((ek = e.key) == k || k.equals(ek))) { + if (cv == null || cv == ev || cv.equals(ev)) { + oldVal = ev; + if ((e.val = v) == null) { + deleted = true; + Node en = e.next; + if (pred != null) + pred.next = en; + else + setTabAt(tab, i, en); + } + } + break; + } + pred = e; + if ((e = e.next) == null) + break; + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (validated) { + if (deleted) + counter.add(-1L); + break; + } + } + } + return oldVal; + } + + /* + * Internal versions of the six insertion methods, each a + * little more complicated than the last. All have + * the same basic structure as the first (internalPut): + * 1. If table uninitialized, create + * 2. If bin empty, try to CAS new node + * 3. If bin stale, use new table + * 4. if bin converted to TreeBin, validate and relay to TreeBin methods + * 5. Lock and validate; if valid, scan and add or update + * + * The others interweave other checks and/or alternative actions: + * * Plain put checks for and performs resize after insertion. + * * putIfAbsent prescans for mapping without lock (and fails to add + * if present), which also makes pre-emptive resize checks worthwhile. + * * computeIfAbsent extends form used in putIfAbsent with additional + * mechanics to deal with, calls, potential exceptions and null + * returns from function call. + * * compute uses the same function-call mechanics, but without + * the prescans + * * merge acts as putIfAbsent in the absent case, but invokes the + * update function if present + * * putAll attempts to pre-allocate enough table space + * and more lazily performs count updates and checks. + * + * Someday when details settle down a bit more, it might be worth + * some factoring to reduce sprawl. + */ + + /** Implementation for put */ + private final Object internalPut(Object k, Object v) { + int h = spread(k.hashCode()); + int count = 0; + for (Node[] tab = table;;) { + int i; Node f; int fh; Object fk; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length - 1) & h)) == null) { + if (casTabAt(tab, i, null, new Node(h, k, v, null))) + break; // no lock when adding to empty bin + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + Object oldVal = null; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + count = 2; + TreeNode p = t.putTreeNode(h, k, v); + if (p != null) { + oldVal = p.val; + p.val = v; + } + } + } finally { + t.release(0); + } + if (count != 0) { + if (oldVal != null) + return oldVal; + break; + } + } + else + tab = (Node[])fk; + } + else if ((fh & LOCKED) != 0) { + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + Object oldVal = null; + try { // needed in case equals() throws + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + oldVal = ev; + e.val = v; + break; + } + Node last = e; + if ((e = e.next) == null) { + last.next = new Node(h, k, v, null); + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + break; + } + } + } + } finally { // unlock and signal if needed + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (oldVal != null) + return oldVal; + if (tab.length <= 64) + count = 2; + break; + } + } + } + counter.add(1L); + if (count > 1) + checkForResize(); + return null; + } + + /** Implementation for putIfAbsent */ + private final Object internalPutIfAbsent(Object k, Object v) { + int h = spread(k.hashCode()); + int count = 0; + for (Node[] tab = table;;) { + int i; Node f; int fh; Object fk, fv; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length - 1) & h)) == null) { + if (casTabAt(tab, i, null, new Node(h, k, v, null))) + break; + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + Object oldVal = null; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + count = 2; + TreeNode p = t.putTreeNode(h, k, v); + if (p != null) + oldVal = p.val; + } + } finally { + t.release(0); + } + if (count != 0) { + if (oldVal != null) + return oldVal; + break; + } + } + else + tab = (Node[])fk; + } + else if ((fh & HASH_BITS) == h && (fv = f.val) != null && + ((fk = f.key) == k || k.equals(fk))) + return fv; + else { + Node g = f.next; + if (g != null) { // at least 2 nodes -- search and maybe resize + for (Node e = g;;) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) + return ev; + if ((e = e.next) == null) { + checkForResize(); + break; + } + } + } + if (((fh = f.hash) & LOCKED) != 0) { + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (tabAt(tab, i) == f && f.casHash(fh, fh | LOCKED)) { + Object oldVal = null; + try { + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + oldVal = ev; + break; + } + Node last = e; + if ((e = e.next) == null) { + last.next = new Node(h, k, v, null); + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + break; + } + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (oldVal != null) + return oldVal; + if (tab.length <= 64) + count = 2; + break; + } + } + } + } + counter.add(1L); + if (count > 1) + checkForResize(); + return null; + } + + /** Implementation for computeIfAbsent */ + private final Object internalComputeIfAbsent(K k, + Fun mf) { + int h = spread(k.hashCode()); + Object val = null; + int count = 0; + for (Node[] tab = table;;) { + Node f; int i, fh; Object fk, fv; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length - 1) & h)) == null) { + Node node = new Node(fh = h | LOCKED, k, null, null); + if (casTabAt(tab, i, null, node)) { + count = 1; + try { + if ((val = mf.apply(k)) != null) + node.val = val; + } finally { + if (val == null) + setTabAt(tab, i, null); + if (!node.casHash(fh, h)) { + node.hash = h; + synchronized (node) { node.notifyAll(); }; + } + } + } + if (count != 0) + break; + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + boolean added = false; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + count = 1; + TreeNode p = t.getTreeNode(h, k, t.root); + if (p != null) + val = p.val; + else if ((val = mf.apply(k)) != null) { + added = true; + count = 2; + t.putTreeNode(h, k, val); + } + } + } finally { + t.release(0); + } + if (count != 0) { + if (!added) + return val; + break; + } + } + else + tab = (Node[])fk; + } + else if ((fh & HASH_BITS) == h && (fv = f.val) != null && + ((fk = f.key) == k || k.equals(fk))) + return fv; + else { + Node g = f.next; + if (g != null) { + for (Node e = g;;) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) + return ev; + if ((e = e.next) == null) { + checkForResize(); + break; + } + } + } + if (((fh = f.hash) & LOCKED) != 0) { + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (tabAt(tab, i) == f && f.casHash(fh, fh | LOCKED)) { + boolean added = false; + try { + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + val = ev; + break; + } + Node last = e; + if ((e = e.next) == null) { + if ((val = mf.apply(k)) != null) { + added = true; + last.next = new Node(h, k, val, null); + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + } + break; + } + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (!added) + return val; + if (tab.length <= 64) + count = 2; + break; + } + } + } + } + if (val != null) { + counter.add(1L); + if (count > 1) + checkForResize(); + } + return val; + } + + /** Implementation for compute */ + @SuppressWarnings("unchecked") private final Object internalCompute + (K k, boolean onlyIfPresent, BiFun mf) { + int h = spread(k.hashCode()); + Object val = null; + int delta = 0; + int count = 0; + for (Node[] tab = table;;) { + Node f; int i, fh; Object fk; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length - 1) & h)) == null) { + if (onlyIfPresent) + break; + Node node = new Node(fh = h | LOCKED, k, null, null); + if (casTabAt(tab, i, null, node)) { + try { + count = 1; + if ((val = mf.apply(k, null)) != null) { + node.val = val; + delta = 1; + } + } finally { + if (delta == 0) + setTabAt(tab, i, null); + if (!node.casHash(fh, h)) { + node.hash = h; + synchronized (node) { node.notifyAll(); }; + } + } + } + if (count != 0) + break; + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + count = 1; + TreeNode p = t.getTreeNode(h, k, t.root); + Object pv; + if (p == null) { + if (onlyIfPresent) + break; + pv = null; + } else + pv = p.val; + if ((val = mf.apply(k, (V)pv)) != null) { + if (p != null) + p.val = val; + else { + count = 2; + delta = 1; + t.putTreeNode(h, k, val); + } + } + else if (p != null) { + delta = -1; + t.deleteTreeNode(p); + } + } + } finally { + t.release(0); + } + if (count != 0) + break; + } + else + tab = (Node[])fk; + } + else if ((fh & LOCKED) != 0) { + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + try { + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f, pred = null;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + val = mf.apply(k, (V)ev); + if (val != null) + e.val = val; + else { + delta = -1; + Node en = e.next; + if (pred != null) + pred.next = en; + else + setTabAt(tab, i, en); + } + break; + } + pred = e; + if ((e = e.next) == null) { + if (!onlyIfPresent && (val = mf.apply(k, null)) != null) { + pred.next = new Node(h, k, val, null); + delta = 1; + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + } + break; + } + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (tab.length <= 64) + count = 2; + break; + } + } + } + if (delta != 0) { + counter.add((long)delta); + if (count > 1) + checkForResize(); + } + return val; + } + + /** Implementation for merge */ + @SuppressWarnings("unchecked") private final Object internalMerge + (K k, V v, BiFun mf) { + int h = spread(k.hashCode()); + Object val = null; + int delta = 0; + int count = 0; + for (Node[] tab = table;;) { + int i; Node f; int fh; Object fk, fv; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length - 1) & h)) == null) { + if (casTabAt(tab, i, null, new Node(h, k, v, null))) { + delta = 1; + val = v; + break; + } + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + count = 1; + TreeNode p = t.getTreeNode(h, k, t.root); + val = (p == null) ? v : mf.apply((V)p.val, v); + if (val != null) { + if (p != null) + p.val = val; + else { + count = 2; + delta = 1; + t.putTreeNode(h, k, val); + } + } + else if (p != null) { + delta = -1; + t.deleteTreeNode(p); + } + } + } finally { + t.release(0); + } + if (count != 0) + break; + } + else + tab = (Node[])fk; + } + else if ((fh & LOCKED) != 0) { + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + try { + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f, pred = null;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + val = mf.apply((V)ev, v); + if (val != null) + e.val = val; + else { + delta = -1; + Node en = e.next; + if (pred != null) + pred.next = en; + else + setTabAt(tab, i, en); + } + break; + } + pred = e; + if ((e = e.next) == null) { + val = v; + pred.next = new Node(h, k, val, null); + delta = 1; + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + break; + } + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (tab.length <= 64) + count = 2; + break; + } + } + } + if (delta != 0) { + counter.add((long)delta); + if (count > 1) + checkForResize(); + } + return val; + } + + /** Implementation for putAll */ + private final void internalPutAll(Map m) { + tryPresize(m.size()); + long delta = 0L; // number of uncommitted additions + boolean npe = false; // to throw exception on exit for nulls + try { // to clean up counts on other exceptions + for (Map.Entry entry : m.entrySet()) { + Object k, v; + if (entry == null || (k = entry.getKey()) == null || + (v = entry.getValue()) == null) { + npe = true; + break; + } + int h = spread(k.hashCode()); + for (Node[] tab = table;;) { + int i; Node f; int fh; Object fk; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length - 1) & h)) == null){ + if (casTabAt(tab, i, null, new Node(h, k, v, null))) { + ++delta; + break; + } + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + boolean validated = false; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + validated = true; + TreeNode p = t.getTreeNode(h, k, t.root); + if (p != null) + p.val = v; + else { + t.putTreeNode(h, k, v); + ++delta; + } + } + } finally { + t.release(0); + } + if (validated) + break; + } + else + tab = (Node[])fk; + } + else if ((fh & LOCKED) != 0) { + counter.add(delta); + delta = 0L; + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + int count = 0; + try { + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + e.val = v; + break; + } + Node last = e; + if ((e = e.next) == null) { + ++delta; + last.next = new Node(h, k, v, null); + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + break; + } + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (count > 1) { + counter.add(delta); + delta = 0L; + checkForResize(); + } + break; + } + } + } + } + } finally { + if (delta != 0) + counter.add(delta); + } + if (npe) + throw new NullPointerException(); + } + + /* ---------------- Table Initialization and Resizing -------------- */ + + /** + * Returns a power of two table size for the given desired capacity. + * See Hackers Delight, sec 3.2 + */ + private static final int tableSizeFor(int c) { + int n = c - 1; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; + } + + /** + * Initializes table, using the size recorded in sizeCtl. + */ + private final Node[] initTable() { + Node[] tab; int sc; + while ((tab = table) == null) { + if ((sc = sizeCtl) < 0) + Thread.yield(); // lost initialization race; just spin + else if (UNSAFE.compareAndSwapInt(this, sizeCtlOffset, sc, -1)) { + try { + if ((tab = table) == null) { + int n = (sc > 0) ? sc : DEFAULT_CAPACITY; + tab = table = new Node[n]; + sc = n - (n >>> 2); + } + } finally { + sizeCtl = sc; + } + break; + } + } + return tab; + } + + /** + * If table is too small and not already resizing, creates next + * table and transfers bins. Rechecks occupancy after a transfer + * to see if another resize is already needed because resizings + * are lagging additions. + */ + private final void checkForResize() { + Node[] tab; int n, sc; + while ((tab = table) != null && + (n = tab.length) < MAXIMUM_CAPACITY && + (sc = sizeCtl) >= 0 && counter.sum() >= (long)sc && + UNSAFE.compareAndSwapInt(this, sizeCtlOffset, sc, -1)) { + try { + if (tab == table) { + table = rebuild(tab); + sc = (n << 1) - (n >>> 1); + } + } finally { + sizeCtl = sc; + } + } + } + + /** + * Tries to presize table to accommodate the given number of elements. + * + * @param size number of elements (doesn't need to be perfectly accurate) + */ + private final void tryPresize(int size) { + int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : + tableSizeFor(size + (size >>> 1) + 1); + int sc; + while ((sc = sizeCtl) >= 0) { + Node[] tab = table; int n; + if (tab == null || (n = tab.length) == 0) { + n = (sc > c) ? sc : c; + if (UNSAFE.compareAndSwapInt(this, sizeCtlOffset, sc, -1)) { + try { + if (table == tab) { + table = new Node[n]; + sc = n - (n >>> 2); + } + } finally { + sizeCtl = sc; + } + } + } + else if (c <= sc || n >= MAXIMUM_CAPACITY) + break; + else if (UNSAFE.compareAndSwapInt(this, sizeCtlOffset, sc, -1)) { + try { + if (table == tab) { + table = rebuild(tab); + sc = (n << 1) - (n >>> 1); + } + } finally { + sizeCtl = sc; + } + } + } + } + + /* + * Moves and/or copies the nodes in each bin to new table. See + * above for explanation. + * + * @return the new table + */ + private static final Node[] rebuild(Node[] tab) { + int n = tab.length; + Node[] nextTab = new Node[n << 1]; + Node fwd = new Node(MOVED, nextTab, null, null); + int[] buffer = null; // holds bins to revisit; null until needed + Node rev = null; // reverse forwarder; null until needed + int nbuffered = 0; // the number of bins in buffer list + int bufferIndex = 0; // buffer index of current buffered bin + int bin = n - 1; // current non-buffered bin or -1 if none + + for (int i = bin;;) { // start upwards sweep + int fh; Node f; + if ((f = tabAt(tab, i)) == null) { + if (bin >= 0) { // Unbuffered; no lock needed (or available) + if (!casTabAt(tab, i, f, fwd)) + continue; + } + else { // transiently use a locked forwarding node + Node g = new Node(MOVED|LOCKED, nextTab, null, null); + if (!casTabAt(tab, i, f, g)) + continue; + setTabAt(nextTab, i, null); + setTabAt(nextTab, i + n, null); + setTabAt(tab, i, fwd); + if (!g.casHash(MOVED|LOCKED, MOVED)) { + g.hash = MOVED; + synchronized (g) { g.notifyAll(); } + } + } + } + else if ((fh = f.hash) == MOVED) { + Object fk = f.key; + if (fk instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + boolean validated = false; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + validated = true; + splitTreeBin(nextTab, i, t); + setTabAt(tab, i, fwd); + } + } finally { + t.release(0); + } + if (!validated) + continue; + } + } + else if ((fh & LOCKED) == 0 && f.casHash(fh, fh|LOCKED)) { + boolean validated = false; + try { // split to lo and hi lists; copying as needed + if (tabAt(tab, i) == f) { + validated = true; + splitBin(nextTab, i, f); + setTabAt(tab, i, fwd); + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (!validated) + continue; + } + else { + if (buffer == null) // initialize buffer for revisits + buffer = new int[TRANSFER_BUFFER_SIZE]; + if (bin < 0 && bufferIndex > 0) { + int j = buffer[--bufferIndex]; + buffer[bufferIndex] = i; + i = j; // swap with another bin + continue; + } + if (bin < 0 || nbuffered >= TRANSFER_BUFFER_SIZE) { + f.tryAwaitLock(tab, i); + continue; // no other options -- block + } + if (rev == null) // initialize reverse-forwarder + rev = new Node(MOVED, tab, null, null); + if (tabAt(tab, i) != f || (f.hash & LOCKED) == 0) + continue; // recheck before adding to list + buffer[nbuffered++] = i; + setTabAt(nextTab, i, rev); // install place-holders + setTabAt(nextTab, i + n, rev); + } + + if (bin > 0) + i = --bin; + else if (buffer != null && nbuffered > 0) { + bin = -1; + i = buffer[bufferIndex = --nbuffered]; + } + else + return nextTab; + } + } + + /** + * Splits a normal bin with list headed by e into lo and hi parts; + * installs in given table. + */ + private static void splitBin(Node[] nextTab, int i, Node e) { + int bit = nextTab.length >>> 1; // bit to split on + int runBit = e.hash & bit; + Node lastRun = e, lo = null, hi = null; + for (Node p = e.next; p != null; p = p.next) { + int b = p.hash & bit; + if (b != runBit) { + runBit = b; + lastRun = p; + } + } + if (runBit == 0) + lo = lastRun; + else + hi = lastRun; + for (Node p = e; p != lastRun; p = p.next) { + int ph = p.hash & HASH_BITS; + Object pk = p.key, pv = p.val; + if ((ph & bit) == 0) + lo = new Node(ph, pk, pv, lo); + else + hi = new Node(ph, pk, pv, hi); + } + setTabAt(nextTab, i, lo); + setTabAt(nextTab, i + bit, hi); + } + + /** + * Splits a tree bin into lo and hi parts; installs in given table. + */ + private static void splitTreeBin(Node[] nextTab, int i, TreeBin t) { + int bit = nextTab.length >>> 1; + TreeBin lt = new TreeBin(); + TreeBin ht = new TreeBin(); + int lc = 0, hc = 0; + for (Node e = t.first; e != null; e = e.next) { + int h = e.hash & HASH_BITS; + Object k = e.key, v = e.val; + if ((h & bit) == 0) { + ++lc; + lt.putTreeNode(h, k, v); + } + else { + ++hc; + ht.putTreeNode(h, k, v); + } + } + Node ln, hn; // throw away trees if too small + if (lc <= (TREE_THRESHOLD >>> 1)) { + ln = null; + for (Node p = lt.first; p != null; p = p.next) + ln = new Node(p.hash, p.key, p.val, ln); + } + else + ln = new Node(MOVED, lt, null, null); + setTabAt(nextTab, i, ln); + if (hc <= (TREE_THRESHOLD >>> 1)) { + hn = null; + for (Node p = ht.first; p != null; p = p.next) + hn = new Node(p.hash, p.key, p.val, hn); + } + else + hn = new Node(MOVED, ht, null, null); + setTabAt(nextTab, i + bit, hn); + } + + /** + * Implementation for clear. Steps through each bin, removing all + * nodes. + */ + private final void internalClear() { + long delta = 0L; // negative number of deletions + int i = 0; + Node[] tab = table; + while (tab != null && i < tab.length) { + int fh; Object fk; + Node f = tabAt(tab, i); + if (f == null) + ++i; + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + for (Node p = t.first; p != null; p = p.next) { + if (p.val != null) { // (currently always true) + p.val = null; + --delta; + } + } + t.first = null; + t.root = null; + ++i; + } + } finally { + t.release(0); + } + } + else + tab = (Node[])fk; + } + else if ((fh & LOCKED) != 0) { + counter.add(delta); // opportunistically update count + delta = 0L; + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + try { + if (tabAt(tab, i) == f) { + for (Node e = f; e != null; e = e.next) { + if (e.val != null) { // (currently always true) + e.val = null; + --delta; + } + } + setTabAt(tab, i, null); + ++i; + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + } + } + if (delta != 0) + counter.add(delta); + } + + /* ----------------Table Traversal -------------- */ + + /** + * Encapsulates traversal for methods such as containsValue; also + * serves as a base class for other iterators and bulk tasks. + * + * At each step, the iterator snapshots the key ("nextKey") and + * value ("nextVal") of a valid node (i.e., one that, at point of + * snapshot, has a non-null user value). Because val fields can + * change (including to null, indicating deletion), field nextVal + * might not be accurate at point of use, but still maintains the + * weak consistency property of holding a value that was once + * valid. To support iterator.remove, the nextKey field is not + * updated (nulled out) when the iterator cannot advance. + * + * Internal traversals directly access these fields, as in: + * {@code while (it.advance() != null) { process(it.nextKey); }} + * + * Exported iterators must track whether the iterator has advanced + * (in hasNext vs next) (by setting/checking/nulling field + * nextVal), and then extract key, value, or key-value pairs as + * return values of next(). + * + * The iterator visits once each still-valid node that was + * reachable upon iterator construction. It might miss some that + * were added to a bin after the bin was visited, which is OK wrt + * consistency guarantees. Maintaining this property in the face + * of possible ongoing resizes requires a fair amount of + * bookkeeping state that is difficult to optimize away amidst + * volatile accesses. Even so, traversal maintains reasonable + * throughput. + * + * Normally, iteration proceeds bin-by-bin traversing lists. + * However, if the table has been resized, then all future steps + * must traverse both the bin at the current index as well as at + * (index + baseSize); and so on for further resizings. To + * paranoically cope with potential sharing by users of iterators + * across threads, iteration terminates if a bounds checks fails + * for a table read. + * + * This class extends ForkJoinTask to streamline parallel + * iteration in bulk operations (see BulkTask). This adds only an + * int of space overhead, which is close enough to negligible in + * cases where it is not needed to not worry about it. Because + * ForkJoinTask is Serializable, but iterators need not be, we + * need to add warning suppressions. + */ + @SuppressWarnings("serial") static class Traverser { + final ConcurrentHashMapV8 map; + Node next; // the next entry to use + K nextKey; // cached key field of next + V nextVal; // cached val field of next + Node[] tab; // current table; updated if resized + int index; // index of bin to use next + int baseIndex; // current index of initial table + int baseLimit; // index bound for initial table + int baseSize; // initial table size + + /** Creates iterator for all entries in the table. */ + Traverser(ConcurrentHashMapV8 map) { + this.map = map; + } + + /** Creates iterator for split() methods */ + Traverser(Traverser it) { + ConcurrentHashMapV8 m; Node[] t; + if ((m = this.map = it.map) == null) + t = null; + else if ((t = it.tab) == null && // force parent tab initialization + (t = it.tab = m.table) != null) + it.baseLimit = it.baseSize = t.length; + this.tab = t; + this.baseSize = it.baseSize; + it.baseLimit = this.index = this.baseIndex = + ((this.baseLimit = it.baseLimit) + it.baseIndex + 1) >>> 1; + } + + /** + * Advances next; returns nextVal or null if terminated. + * See above for explanation. + */ + final V advance() { + Node e = next; + V ev = null; + outer: do { + if (e != null) // advance past used/skipped node + e = e.next; + while (e == null) { // get to next non-null bin + ConcurrentHashMapV8 m; + Node[] t; int b, i, n; Object ek; // checks must use locals + if ((t = tab) != null) + n = t.length; + else if ((m = map) != null && (t = tab = m.table) != null) + n = baseLimit = baseSize = t.length; + else + break outer; + if ((b = baseIndex) >= baseLimit || + (i = index) < 0 || i >= n) + break outer; + if ((e = tabAt(t, i)) != null && e.hash == MOVED) { + if ((ek = e.key) instanceof TreeBin) + e = ((TreeBin)ek).first; + else { + tab = (Node[])ek; + continue; // restarts due to null val + } + } // visit upper slots if present + index = (i += baseSize) < n ? i : (baseIndex = b + 1); + } + nextKey = (K) e.key; + } while ((ev = (V) e.val) == null); // skip deleted or special nodes + next = e; + return nextVal = ev; + } + + public final void remove() { + Object k = nextKey; + if (k == null && (advance() == null || (k = nextKey) == null)) + throw new IllegalStateException(); + map.internalReplace(k, null, null); + } + + public final boolean hasNext() { + return nextVal != null || advance() != null; + } + + public final boolean hasMoreElements() { return hasNext(); } + public final void setRawResult(Object x) { } + public R getRawResult() { return null; } + public boolean exec() { return true; } + } + + /* ---------------- Public operations -------------- */ + + /** + * Creates a new, empty map with the default initial table size (16). + */ + public ConcurrentHashMapV8() { + this.counter = new LongAdder(); + } + + /** + * Creates a new, empty map with an initial table size + * accommodating the specified number of elements without the need + * to dynamically resize. + * + * @param initialCapacity The implementation performs internal + * sizing to accommodate this many elements. + * @throws IllegalArgumentException if the initial capacity of + * elements is negative + */ + public ConcurrentHashMapV8(int initialCapacity) { + if (initialCapacity < 0) + throw new IllegalArgumentException(); + int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? + MAXIMUM_CAPACITY : + tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1)); + this.counter = new LongAdder(); + this.sizeCtl = cap; + } + + /** + * Creates a new map with the same mappings as the given map. + * + * @param m the map + */ + public ConcurrentHashMapV8(Map m) { + this.counter = new LongAdder(); + this.sizeCtl = DEFAULT_CAPACITY; + internalPutAll(m); + } + + /** + * Creates a new, empty map with an initial table size based on + * the given number of elements ({@code initialCapacity}) and + * initial table density ({@code loadFactor}). + * + * @param initialCapacity the initial capacity. The implementation + * performs internal sizing to accommodate this many elements, + * given the specified load factor. + * @param loadFactor the load factor (table density) for + * establishing the initial table size + * @throws IllegalArgumentException if the initial capacity of + * elements is negative or the load factor is nonpositive + * + * @since 1.6 + */ + public ConcurrentHashMapV8(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, 1); + } + + /** + * Creates a new, empty map with an initial table size based on + * the given number of elements ({@code initialCapacity}), table + * density ({@code loadFactor}), and number of concurrently + * updating threads ({@code concurrencyLevel}). + * + * @param initialCapacity the initial capacity. The implementation + * performs internal sizing to accommodate this many elements, + * given the specified load factor. + * @param loadFactor the load factor (table density) for + * establishing the initial table size + * @param concurrencyLevel the estimated number of concurrently + * updating threads. The implementation may use this value as + * a sizing hint. + * @throws IllegalArgumentException if the initial capacity is + * negative or the load factor or concurrencyLevel are + * nonpositive + */ + public ConcurrentHashMapV8(int initialCapacity, + float loadFactor, int concurrencyLevel) { + if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0) + throw new IllegalArgumentException(); + if (initialCapacity < concurrencyLevel) // Use at least as many bins + initialCapacity = concurrencyLevel; // as estimated threads + long size = (long)(1.0 + (long)initialCapacity / loadFactor); + int cap = (size >= (long)MAXIMUM_CAPACITY) ? + MAXIMUM_CAPACITY : tableSizeFor((int)size); + this.counter = new LongAdder(); + this.sizeCtl = cap; + } + + /** + * Creates a new {@link Set} backed by a ConcurrentHashMapV8 + * from the given type to {@code Boolean.TRUE}. + * + * @return the new set + */ + public static KeySetView newKeySet() { + return new KeySetView(new ConcurrentHashMapV8(), + Boolean.TRUE); + } + + /** + * Creates a new {@link Set} backed by a ConcurrentHashMapV8 + * from the given type to {@code Boolean.TRUE}. + * + * @param initialCapacity The implementation performs internal + * sizing to accommodate this many elements. + * @throws IllegalArgumentException if the initial capacity of + * elements is negative + * @return the new set + */ + public static KeySetView newKeySet(int initialCapacity) { + return new KeySetView(new ConcurrentHashMapV8(initialCapacity), + Boolean.TRUE); + } + + /** + * {@inheritDoc} + */ + public boolean isEmpty() { + return counter.sum() <= 0L; // ignore transient negative values + } + + /** + * {@inheritDoc} + */ + public int size() { + long n = counter.sum(); + return ((n < 0L) ? 0 : + (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE : + (int)n); + } + + /** + * Returns the number of mappings. This method should be used + * instead of {@link #size} because a ConcurrentHashMapV8 may + * contain more mappings than can be represented as an int. The + * value returned is a snapshot; the actual count may differ if + * there are ongoing concurrent insertions or removals. + * + * @return the number of mappings + */ + public long mappingCount() { + long n = counter.sum(); + return (n < 0L) ? 0L : n; // ignore transient negative values + } + + /** + * Returns the value to which the specified key is mapped, + * or {@code null} if this map contains no mapping for the key. + * + *

    More formally, if this map contains a mapping from a key + * {@code k} to a value {@code v} such that {@code key.equals(k)}, + * then this method returns {@code v}; otherwise it returns + * {@code null}. (There can be at most one such mapping.) + * + * @throws NullPointerException if the specified key is null + */ + @SuppressWarnings("unchecked") public V get(Object key) { + if (key == null) + throw new NullPointerException(); + return (V)internalGet(key); + } + + /** + * Returns the value to which the specified key is mapped, + * or the given defaultValue if this map contains no mapping for the key. + * + * @param key the key + * @param defaultValue the value to return if this map contains + * no mapping for the given key + * @return the mapping for the key, if present; else the defaultValue + * @throws NullPointerException if the specified key is null + */ + @SuppressWarnings("unchecked") public V getValueOrDefault(Object key, V defaultValue) { + if (key == null) + throw new NullPointerException(); + V v = (V) internalGet(key); + return v == null ? defaultValue : v; + } + + /** + * Tests if the specified object is a key in this table. + * + * @param key possible key + * @return {@code true} if and only if the specified object + * is a key in this table, as determined by the + * {@code equals} method; {@code false} otherwise + * @throws NullPointerException if the specified key is null + */ + public boolean containsKey(Object key) { + if (key == null) + throw new NullPointerException(); + return internalGet(key) != null; + } + + /** + * Returns {@code true} if this map maps one or more keys to the + * specified value. Note: This method may require a full traversal + * of the map, and is much slower than method {@code containsKey}. + * + * @param value value whose presence in this map is to be tested + * @return {@code true} if this map maps one or more keys to the + * specified value + * @throws NullPointerException if the specified value is null + */ + public boolean containsValue(Object value) { + if (value == null) + throw new NullPointerException(); + Object v; + Traverser it = new Traverser(this); + while ((v = it.advance()) != null) { + if (v == value || value.equals(v)) + return true; + } + return false; + } + + public K findKey(Object value) { + if (value == null) + throw new NullPointerException(); + Object v; + Traverser it = new Traverser(this); + while ((v = it.advance()) != null) { + if (v == value || value.equals(v)) + return it.nextKey; + } + return null; + } + + /** + * Legacy method testing if some key maps into the specified value + * in this table. This method is identical in functionality to + * {@link #containsValue}, and exists solely to ensure + * full compatibility with class {@link java.util.Hashtable}, + * which supported this method prior to introduction of the + * Java Collections framework. + * + * @param value a value to search for + * @return {@code true} if and only if some key maps to the + * {@code value} argument in this table as + * determined by the {@code equals} method; + * {@code false} otherwise + * @throws NullPointerException if the specified value is null + */ + public boolean contains(Object value) { + return containsValue(value); + } + + /** + * Maps the specified key to the specified value in this table. + * Neither the key nor the value can be null. + * + *

    The value can be retrieved by calling the {@code get} method + * with a key that is equal to the original key. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key} + * @throws NullPointerException if the specified key or value is null + */ + @SuppressWarnings("unchecked") public V put(K key, V value) { + if (key == null || value == null) + throw new NullPointerException(); + return (V)internalPut(key, value); + } + + /** + * {@inheritDoc} + * + * @return the previous value associated with the specified key, + * or {@code null} if there was no mapping for the key + * @throws NullPointerException if the specified key or value is null + */ + @SuppressWarnings("unchecked") public V putIfAbsent(K key, V value) { + if (key == null || value == null) + throw new NullPointerException(); + return (V)internalPutIfAbsent(key, value); + } + + /** + * Copies all of the mappings from the specified map to this one. + * These mappings replace any mappings that this map had for any of the + * keys currently in the specified map. + * + * @param m mappings to be stored in this map + */ + public void putAll(Map m) { + internalPutAll(m); + } + + /** + * If the specified key is not already associated with a value, + * computes its value using the given mappingFunction and enters + * it into the map unless null. This is equivalent to + *

     {@code
    +     * if (map.containsKey(key))
    +     *   return map.get(key);
    +     * value = mappingFunction.apply(key);
    +     * if (value != null)
    +     *   map.put(key, value);
    +     * return value;}
    + * + * except that the action is performed atomically. If the + * function returns {@code null} no mapping is recorded. If the + * function itself throws an (unchecked) exception, the exception + * is rethrown to its caller, and no mapping is recorded. Some + * attempted update operations on this map by other threads may be + * blocked while computation is in progress, so the computation + * should be short and simple, and must not attempt to update any + * other mappings of this Map. The most appropriate usage is to + * construct a new object serving as an initial mapped value, or + * memoized result, as in: + * + *
     {@code
    +     * map.computeIfAbsent(key, new Fun() {
    +     *   public V map(K k) { return new Value(f(k)); }});}
    + * + * @param key key with which the specified value is to be associated + * @param mappingFunction the function to compute a value + * @return the current (existing or computed) value associated with + * the specified key, or null if the computed value is null + * @throws NullPointerException if the specified key or mappingFunction + * is null + * @throws IllegalStateException if the computation detectably + * attempts a recursive update to this map that would + * otherwise never complete + * @throws RuntimeException or Error if the mappingFunction does so, + * in which case the mapping is left unestablished + */ + @SuppressWarnings("unchecked") public V computeIfAbsent + (K key, Fun mappingFunction) { + if (key == null || mappingFunction == null) + throw new NullPointerException(); + return (V)internalComputeIfAbsent(key, mappingFunction); + } + + /** + * If the given key is present, computes a new mapping value given a key and + * its current mapped value. This is equivalent to + *
     {@code
    +     *   if (map.containsKey(key)) {
    +     *     value = remappingFunction.apply(key, map.get(key));
    +     *     if (value != null)
    +     *       map.put(key, value);
    +     *     else
    +     *       map.remove(key);
    +     *   }
    +     * }
    + * + * except that the action is performed atomically. If the + * function returns {@code null}, the mapping is removed. If the + * function itself throws an (unchecked) exception, the exception + * is rethrown to its caller, and the current mapping is left + * unchanged. Some attempted update operations on this map by + * other threads may be blocked while computation is in progress, + * so the computation should be short and simple, and must not + * attempt to update any other mappings of this Map. For example, + * to either create or append new messages to a value mapping: + * + * @param key key with which the specified value is to be associated + * @param remappingFunction the function to compute a value + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key or remappingFunction + * is null + * @throws IllegalStateException if the computation detectably + * attempts a recursive update to this map that would + * otherwise never complete + * @throws RuntimeException or Error if the remappingFunction does so, + * in which case the mapping is unchanged + */ + @SuppressWarnings("unchecked") public V computeIfPresent + (K key, BiFun remappingFunction) { + if (key == null || remappingFunction == null) + throw new NullPointerException(); + return (V)internalCompute(key, true, remappingFunction); + } + + /** + * Computes a new mapping value given a key and + * its current mapped value (or {@code null} if there is no current + * mapping). This is equivalent to + *
     {@code
    +     *   value = remappingFunction.apply(key, map.get(key));
    +     *   if (value != null)
    +     *     map.put(key, value);
    +     *   else
    +     *     map.remove(key);
    +     * }
    + * + * except that the action is performed atomically. If the + * function returns {@code null}, the mapping is removed. If the + * function itself throws an (unchecked) exception, the exception + * is rethrown to its caller, and the current mapping is left + * unchanged. Some attempted update operations on this map by + * other threads may be blocked while computation is in progress, + * so the computation should be short and simple, and must not + * attempt to update any other mappings of this Map. For example, + * to either create or append new messages to a value mapping: + * + *
     {@code
    +     * Map map = ...;
    +     * final String msg = ...;
    +     * map.compute(key, new BiFun() {
    +     *   public String apply(Key k, String v) {
    +     *    return (v == null) ? msg : v + msg;});}}
    + * + * @param key key with which the specified value is to be associated + * @param remappingFunction the function to compute a value + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key or remappingFunction + * is null + * @throws IllegalStateException if the computation detectably + * attempts a recursive update to this map that would + * otherwise never complete + * @throws RuntimeException or Error if the remappingFunction does so, + * in which case the mapping is unchanged + */ + @SuppressWarnings("unchecked") public V compute + (K key, BiFun remappingFunction) { + if (key == null || remappingFunction == null) + throw new NullPointerException(); + return (V)internalCompute(key, false, remappingFunction); + } + + /** + * If the specified key is not already associated + * with a value, associate it with the given value. + * Otherwise, replace the value with the results of + * the given remapping function. This is equivalent to: + *
     {@code
    +     *   if (!map.containsKey(key))
    +     *     map.put(value);
    +     *   else {
    +     *     newValue = remappingFunction.apply(map.get(key), value);
    +     *     if (value != null)
    +     *       map.put(key, value);
    +     *     else
    +     *       map.remove(key);
    +     *   }
    +     * }
    + * except that the action is performed atomically. If the + * function returns {@code null}, the mapping is removed. If the + * function itself throws an (unchecked) exception, the exception + * is rethrown to its caller, and the current mapping is left + * unchanged. Some attempted update operations on this map by + * other threads may be blocked while computation is in progress, + * so the computation should be short and simple, and must not + * attempt to update any other mappings of this Map. + */ + @SuppressWarnings("unchecked") public V merge + (K key, V value, BiFun remappingFunction) { + if (key == null || value == null || remappingFunction == null) + throw new NullPointerException(); + return (V)internalMerge(key, value, remappingFunction); + } + + /** + * Removes the key (and its corresponding value) from this map. + * This method does nothing if the key is not in the map. + * + * @param key the key that needs to be removed + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key} + * @throws NullPointerException if the specified key is null + */ + @SuppressWarnings("unchecked") public V remove(Object key) { + if (key == null) + throw new NullPointerException(); + return (V)internalReplace(key, null, null); + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException if the specified key is null + */ + public boolean remove(Object key, Object value) { + if (key == null) + throw new NullPointerException(); + if (value == null) + return false; + return internalReplace(key, null, value) != null; + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException if any of the arguments are null + */ + public boolean replace(K key, V oldValue, V newValue) { + if (key == null || oldValue == null || newValue == null) + throw new NullPointerException(); + return internalReplace(key, newValue, oldValue) != null; + } + + /** + * {@inheritDoc} + * + * @return the previous value associated with the specified key, + * or {@code null} if there was no mapping for the key + * @throws NullPointerException if the specified key or value is null + */ + @SuppressWarnings("unchecked") public V replace(K key, V value) { + if (key == null || value == null) + throw new NullPointerException(); + return (V)internalReplace(key, value, null); + } + + /** + * Removes all of the mappings from this map. + */ + public void clear() { + internalClear(); + } + + /** + * Returns a {@link Set} view of the keys contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. + * + * @return the set view + */ + public KeySetView keySet() { + KeySetView ks = keySet; + return (ks != null) ? ks : (keySet = new KeySetView(this, null)); + } + + /** + * Returns a {@link Set} view of the keys in this map, using the + * given common mapped value for any additions (i.e., {@link + * Collection#add} and {@link Collection#addAll}). This is of + * course only appropriate if it is acceptable to use the same + * value for all additions from this view. + * + * @param mappedValue the mapped value to use for any + * additions. + * @return the set view + * @throws NullPointerException if the mappedValue is null + */ + public KeySetView keySet(V mappedValue) { + if (mappedValue == null) + throw new NullPointerException(); + return new KeySetView(this, mappedValue); + } + + /** + * Returns a {@link Collection} view of the values contained in this map. + * The collection is backed by the map, so changes to the map are + * reflected in the collection, and vice-versa. + */ + public ValuesView values() { + ValuesView vs = values; + return (vs != null) ? vs : (values = new ValuesView(this)); + } + + /** + * Returns a {@link Set} view of the mappings contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. The set supports element + * removal, which removes the corresponding mapping from the map, + * via the {@code Iterator.remove}, {@code Set.remove}, + * {@code removeAll}, {@code retainAll}, and {@code clear} + * operations. It does not support the {@code add} or + * {@code addAll} operations. + * + *

    The view's {@code iterator} is a "weakly consistent" iterator + * that will never throw {@link ConcurrentModificationException}, + * and guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not guaranteed to) + * reflect any modifications subsequent to construction. + */ + public Set> entrySet() { + EntrySetView es = entrySet; + return (es != null) ? es : (entrySet = new EntrySetView(this)); + } + + /** + * Returns an enumeration of the keys in this table. + * + * @return an enumeration of the keys in this table + * @see #keySet() + */ + public Enumeration keys() { + return new KeyIterator(this); + } + + /** + * Returns an enumeration of the values in this table. + * + * @return an enumeration of the values in this table + * @see #values() + */ + public Enumeration elements() { + return new ValueIterator(this); + } + + /** + * Returns a partitionable iterator of the keys in this map. + * + * @return a partitionable iterator of the keys in this map + */ + public Spliterator keySpliterator() { + return new KeyIterator(this); + } + + /** + * Returns a partitionable iterator of the values in this map. + * + * @return a partitionable iterator of the values in this map + */ + public Spliterator valueSpliterator() { + return new ValueIterator(this); + } + + /** + * Returns a partitionable iterator of the entries in this map. + * + * @return a partitionable iterator of the entries in this map + */ + public Spliterator> entrySpliterator() { + return new EntryIterator(this); + } + + /** + * Returns the hash code value for this {@link Map}, i.e., + * the sum of, for each key-value pair in the map, + * {@code key.hashCode() ^ value.hashCode()}. + * + * @return the hash code value for this map + */ + public int hashCode() { + int h = 0; + Traverser it = new Traverser(this); + Object v; + while ((v = it.advance()) != null) { + h += it.nextKey.hashCode() ^ v.hashCode(); + } + return h; + } + + /** + * Returns a string representation of this map. The string + * representation consists of a list of key-value mappings (in no + * particular order) enclosed in braces ("{@code {}}"). Adjacent + * mappings are separated by the characters {@code ", "} (comma + * and space). Each key-value mapping is rendered as the key + * followed by an equals sign ("{@code =}") followed by the + * associated value. + * + * @return a string representation of this map + */ + public String toString() { + Traverser it = new Traverser(this); + StringBuilder sb = new StringBuilder(); + sb.append('{'); + Object v; + if ((v = it.advance()) != null) { + for (;;) { + Object k = it.nextKey; + sb.append(k == this ? "(this Map)" : k); + sb.append('='); + sb.append(v == this ? "(this Map)" : v); + if ((v = it.advance()) == null) + break; + sb.append(',').append(' '); + } + } + return sb.append('}').toString(); + } + + /** + * Compares the specified object with this map for equality. + * Returns {@code true} if the given object is a map with the same + * mappings as this map. This operation may return misleading + * results if either map is concurrently modified during execution + * of this method. + * + * @param o object to be compared for equality with this map + * @return {@code true} if the specified object is equal to this map + */ + public boolean equals(Object o) { + if (o != this) { + if (!(o instanceof Map)) + return false; + Map m = (Map) o; + Traverser it = new Traverser(this); + Object val; + while ((val = it.advance()) != null) { + Object v = m.get(it.nextKey); + if (v == null || (v != val && !v.equals(val))) + return false; + } + for (Map.Entry e : m.entrySet()) { + Object mk, mv, v; + if ((mk = e.getKey()) == null || + (mv = e.getValue()) == null || + (v = internalGet(mk)) == null || + (mv != v && !mv.equals(v))) + return false; + } + } + return true; + } + + /* ----------------Iterators -------------- */ + + @SuppressWarnings("serial") static final class KeyIterator extends Traverser + implements Spliterator, Enumeration { + KeyIterator(ConcurrentHashMapV8 map) { super(map); } + KeyIterator(Traverser it) { + super(it); + } + public KeyIterator split() { + if (nextKey != null) + throw new IllegalStateException(); + return new KeyIterator(this); + } + @SuppressWarnings("unchecked") public final K next() { + if (nextVal == null && advance() == null) + throw new NoSuchElementException(); + Object k = nextKey; + nextVal = null; + return (K) k; + } + + public final K nextElement() { return next(); } + } + + @SuppressWarnings("serial") static final class ValueIterator extends Traverser + implements Spliterator, Enumeration { + ValueIterator(ConcurrentHashMapV8 map) { super(map); } + ValueIterator(Traverser it) { + super(it); + } + public ValueIterator split() { + if (nextKey != null) + throw new IllegalStateException(); + return new ValueIterator(this); + } + + @SuppressWarnings("unchecked") public final V next() { + Object v; + if ((v = nextVal) == null && (v = advance()) == null) + throw new NoSuchElementException(); + nextVal = null; + return (V) v; + } + + public final V nextElement() { return next(); } + } + + @SuppressWarnings("serial") static final class EntryIterator extends Traverser + implements Spliterator> { + EntryIterator(ConcurrentHashMapV8 map) { super(map); } + EntryIterator(Traverser it) { + super(it); + } + public EntryIterator split() { + if (nextKey != null) + throw new IllegalStateException(); + return new EntryIterator(this); + } + + @SuppressWarnings("unchecked") public final Map.Entry next() { + Object v; + if ((v = nextVal) == null && (v = advance()) == null) + throw new NoSuchElementException(); + Object k = nextKey; + nextVal = null; + return new MapEntry((K)k, (V)v, map); + } + } + + /** + * Exported Entry for iterators + */ + static final class MapEntry implements Map.Entry { + final K key; // non-null + V val; // non-null + final ConcurrentHashMapV8 map; + MapEntry(K key, V val, ConcurrentHashMapV8 map) { + this.key = key; + this.val = val; + this.map = map; + } + public final K getKey() { return key; } + public final V getValue() { return val; } + public final int hashCode() { return key.hashCode() ^ val.hashCode(); } + public final String toString(){ return key + "=" + val; } + + public final boolean equals(Object o) { + Object k, v; Map.Entry e; + return ((o instanceof Map.Entry) && + (k = (e = (Map.Entry)o).getKey()) != null && + (v = e.getValue()) != null && + (k == key || k.equals(key)) && + (v == val || v.equals(val))); + } + + /** + * Sets our entry's value and writes through to the map. The + * value to return is somewhat arbitrary here. Since we do not + * necessarily track asynchronous changes, the most recent + * "previous" value could be different from what we return (or + * could even have been removed in which case the put will + * re-establish). We do not and cannot guarantee more. + */ + public final V setValue(V value) { + if (value == null) throw new NullPointerException(); + V v = val; + val = value; + map.put(key, value); + return v; + } + } + + /* ---------------- Serialization Support -------------- */ + + /** + * Stripped-down version of helper class used in previous version, + * declared for the sake of serialization compatibility + */ + static class Segment implements Serializable { + private static final long serialVersionUID = 2249069246763182397L; + final float loadFactor; + Segment(float lf) { this.loadFactor = lf; } + } + + /** + * Saves the state of the {@code ConcurrentHashMapV8} instance to a + * stream (i.e., serializes it). + * @param s the stream + * @serialData + * the key (Object) and value (Object) + * for each key-value mapping, followed by a null pair. + * The key-value mappings are emitted in no particular order. + */ + @SuppressWarnings("unchecked") private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + if (segments == null) { // for serialization compatibility + segments = (Segment[]) + new Segment[DEFAULT_CONCURRENCY_LEVEL]; + for (int i = 0; i < segments.length; ++i) + segments[i] = new Segment(LOAD_FACTOR); + } + s.defaultWriteObject(); + Traverser it = new Traverser(this); + Object v; + while ((v = it.advance()) != null) { + s.writeObject(it.nextKey); + s.writeObject(v); + } + s.writeObject(null); + s.writeObject(null); + segments = null; // throw away + } + + /** + * Reconstitutes the instance from a stream (that is, deserializes it). + * @param s the stream + */ + @SuppressWarnings("unchecked") private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); + this.segments = null; // unneeded + // initialize transient final field + UNSAFE.putObjectVolatile(this, counterOffset, new LongAdder()); + + // Create all nodes, then place in table once size is known + long size = 0L; + Node p = null; + for (;;) { + K k = (K) s.readObject(); + V v = (V) s.readObject(); + if (k != null && v != null) { + int h = spread(k.hashCode()); + p = new Node(h, k, v, p); + ++size; + } + else + break; + } + if (p != null) { + boolean init = false; + int n; + if (size >= (long)(MAXIMUM_CAPACITY >>> 1)) + n = MAXIMUM_CAPACITY; + else { + int sz = (int)size; + n = tableSizeFor(sz + (sz >>> 1) + 1); + } + int sc = sizeCtl; + boolean collide = false; + if (n > sc && + UNSAFE.compareAndSwapInt(this, sizeCtlOffset, sc, -1)) { + try { + if (table == null) { + init = true; + Node[] tab = new Node[n]; + int mask = n - 1; + while (p != null) { + int j = p.hash & mask; + Node next = p.next; + Node q = p.next = tabAt(tab, j); + setTabAt(tab, j, p); + if (!collide && q != null && q.hash == p.hash) + collide = true; + p = next; + } + table = tab; + counter.add(size); + sc = n - (n >>> 2); + } + } finally { + sizeCtl = sc; + } + if (collide) { // rescan and convert to TreeBins + Node[] tab = table; + for (int i = 0; i < tab.length; ++i) { + int c = 0; + for (Node e = tabAt(tab, i); e != null; e = e.next) { + if (++c > TREE_THRESHOLD && + (e.key instanceof Comparable)) { + replaceWithTreeBin(tab, i, e.key); + break; + } + } + } + } + } + if (!init) { // Can only happen if unsafely published. + while (p != null) { + internalPut(p.key, p.val); + p = p.next; + } + } + } + } + + + // ------------------------------------------------------- + + // Sams + /** Interface describing a void action of one argument */ + public interface Action { void apply(A a); } + /** Interface describing a void action of two arguments */ + public interface BiAction { void apply(A a, B b); } + /** Interface describing a function of one argument */ + public interface Generator { T apply(); } + /** Interface describing a function mapping its argument to a double */ + public interface ObjectToDouble { double apply(A a); } + /** Interface describing a function mapping its argument to a long */ + public interface ObjectToLong { long apply(A a); } + /** Interface describing a function mapping its argument to an int */ + public interface ObjectToInt {int apply(A a); } + /** Interface describing a function mapping two arguments to a double */ + public interface ObjectByObjectToDouble { double apply(A a, B b); } + /** Interface describing a function mapping two arguments to a long */ + public interface ObjectByObjectToLong { long apply(A a, B b); } + /** Interface describing a function mapping two arguments to an int */ + public interface ObjectByObjectToInt {int apply(A a, B b); } + /** Interface describing a function mapping a double to a double */ + public interface DoubleToDouble { double apply(double a); } + /** Interface describing a function mapping a long to a long */ + public interface LongToLong { long apply(long a); } + /** Interface describing a function mapping an int to an int */ + public interface IntToInt { int apply(int a); } + /** Interface describing a function mapping two doubles to a double */ + public interface DoubleByDoubleToDouble { double apply(double a, double b); } + /** Interface describing a function mapping two longs to a long */ + public interface LongByLongToLong { long apply(long a, long b); } + /** Interface describing a function mapping two ints to an int */ + public interface IntByIntToInt { int apply(int a, int b); } + + + /* ----------------Views -------------- */ + + /** + * Base class for views. + */ + static abstract class CHMView { + final ConcurrentHashMapV8 map; + CHMView(ConcurrentHashMapV8 map) { this.map = map; } + + /** + * Returns the map backing this view. + * + * @return the map backing this view + */ + public ConcurrentHashMapV8 getMap() { return map; } + + public final int size() { return map.size(); } + public final boolean isEmpty() { return map.isEmpty(); } + public final void clear() { map.clear(); } + + // implementations below rely on concrete classes supplying these + abstract public Iterator iterator(); + abstract public boolean contains(Object o); + abstract public boolean remove(Object o); + + private static final String oomeMsg = "Required array size too large"; + + public final Object[] toArray() { + long sz = map.mappingCount(); + if (sz > (long)(MAX_ARRAY_SIZE)) + throw new OutOfMemoryError(oomeMsg); + int n = (int)sz; + Object[] r = new Object[n]; + int i = 0; + Iterator it = iterator(); + while (it.hasNext()) { + if (i == n) { + if (n >= MAX_ARRAY_SIZE) + throw new OutOfMemoryError(oomeMsg); + if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) + n = MAX_ARRAY_SIZE; + else + n += (n >>> 1) + 1; + r = Arrays.copyOf(r, n); + } + r[i++] = it.next(); + } + return (i == n) ? r : Arrays.copyOf(r, i); + } + + @SuppressWarnings("unchecked") public final T[] toArray(T[] a) { + long sz = map.mappingCount(); + if (sz > (long)(MAX_ARRAY_SIZE)) + throw new OutOfMemoryError(oomeMsg); + int m = (int)sz; + T[] r = (a.length >= m) ? a : + (T[])java.lang.reflect.Array + .newInstance(a.getClass().getComponentType(), m); + int n = r.length; + int i = 0; + Iterator it = iterator(); + while (it.hasNext()) { + if (i == n) { + if (n >= MAX_ARRAY_SIZE) + throw new OutOfMemoryError(oomeMsg); + if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) + n = MAX_ARRAY_SIZE; + else + n += (n >>> 1) + 1; + r = Arrays.copyOf(r, n); + } + r[i++] = (T)it.next(); + } + if (a == r && i < n) { + r[i] = null; // null-terminate + return r; + } + return (i == n) ? r : Arrays.copyOf(r, i); + } + + public final int hashCode() { + int h = 0; + for (Iterator it = iterator(); it.hasNext();) + h += it.next().hashCode(); + return h; + } + + public final String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('['); + Iterator it = iterator(); + if (it.hasNext()) { + for (;;) { + Object e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (!it.hasNext()) + break; + sb.append(',').append(' '); + } + } + return sb.append(']').toString(); + } + + public final boolean containsAll(Collection c) { + if (c != this) { + for (Iterator it = c.iterator(); it.hasNext();) { + Object e = it.next(); + if (e == null || !contains(e)) + return false; + } + } + return true; + } + + public final boolean removeAll(Collection c) { + boolean modified = false; + for (Iterator it = iterator(); it.hasNext();) { + if (c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + public final boolean retainAll(Collection c) { + boolean modified = false; + for (Iterator it = iterator(); it.hasNext();) { + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + } + + /** + * A view of a ConcurrentHashMapV8 as a {@link Set} of keys, in + * which additions may optionally be enabled by mapping to a + * common value. This class cannot be directly instantiated. See + * {@link #keySet}, {@link #keySet(Object)}, {@link #newKeySet()}, + * {@link #newKeySet(int)}. + */ + public static class KeySetView extends CHMView implements Set, java.io.Serializable { + private static final long serialVersionUID = 7249069246763182397L; + private final V value; + KeySetView(ConcurrentHashMapV8 map, V value) { // non-public + super(map); + this.value = value; + } + + /** + * Returns the default mapped value for additions, + * or {@code null} if additions are not supported. + * + * @return the default mapped value for additions, or {@code null} + * if not supported. + */ + public V getMappedValue() { return value; } + + // implement Set API + + public boolean contains(Object o) { return map.containsKey(o); } + public boolean remove(Object o) { return map.remove(o) != null; } + + /** + * Returns a "weakly consistent" iterator that will never + * throw {@link ConcurrentModificationException}, and + * guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not + * guaranteed to) reflect any modifications subsequent to + * construction. + * + * @return an iterator over the keys of this map + */ + public Iterator iterator() { return new KeyIterator(map); } + public boolean add(K e) { + V v; + if ((v = value) == null) + throw new UnsupportedOperationException(); + if (e == null) + throw new NullPointerException(); + return map.internalPutIfAbsent(e, v) == null; + } + public boolean addAll(Collection c) { + boolean added = false; + V v; + if ((v = value) == null) + throw new UnsupportedOperationException(); + for (K e : c) { + if (e == null) + throw new NullPointerException(); + if (map.internalPutIfAbsent(e, v) == null) + added = true; + } + return added; + } + public boolean equals(Object o) { + Set c; + return ((o instanceof Set) && + ((c = (Set)o) == this || + (containsAll(c) && c.containsAll(this)))); + } + } + + /** + * A view of a ConcurrentHashMapV8 as a {@link Collection} of + * values, in which additions are disabled. This class cannot be + * directly instantiated. See {@link #values}, + * + *

    The view's {@code iterator} is a "weakly consistent" iterator + * that will never throw {@link ConcurrentModificationException}, + * and guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not guaranteed to) + * reflect any modifications subsequent to construction. + */ + public static final class ValuesView extends CHMView + implements Collection { + ValuesView(ConcurrentHashMapV8 map) { super(map); } + public final boolean contains(Object o) { return map.containsValue(o); } + public final boolean remove(Object o) { + if (o != null) { + Iterator it = new ValueIterator(map); + while (it.hasNext()) { + if (o.equals(it.next())) { + it.remove(); + return true; + } + } + } + return false; + } + + /** + * Returns a "weakly consistent" iterator that will never + * throw {@link ConcurrentModificationException}, and + * guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not + * guaranteed to) reflect any modifications subsequent to + * construction. + * + * @return an iterator over the values of this map + */ + public final Iterator iterator() { + return new ValueIterator(map); + } + public final boolean add(V e) { + throw new UnsupportedOperationException(); + } + public final boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + } + + /** + * A view of a ConcurrentHashMapV8 as a {@link Set} of (key, value) + * entries. This class cannot be directly instantiated. See + * {@link #entrySet}. + */ + public static final class EntrySetView extends CHMView + implements Set> { + EntrySetView(ConcurrentHashMapV8 map) { super(map); } + public final boolean contains(Object o) { + Object k, v, r; Map.Entry e; + return ((o instanceof Map.Entry) && + (k = (e = (Map.Entry)o).getKey()) != null && + (r = map.get(k)) != null && + (v = e.getValue()) != null && + (v == r || v.equals(r))); + } + public final boolean remove(Object o) { + Object k, v; Map.Entry e; + return ((o instanceof Map.Entry) && + (k = (e = (Map.Entry)o).getKey()) != null && + (v = e.getValue()) != null && + map.remove(k, v)); + } + + /** + * Returns a "weakly consistent" iterator that will never + * throw {@link ConcurrentModificationException}, and + * guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not + * guaranteed to) reflect any modifications subsequent to + * construction. + * + * @return an iterator over the entries of this map + */ + public final Iterator> iterator() { + return new EntryIterator(map); + } + + public final boolean add(Entry e) { + K key = e.getKey(); + V value = e.getValue(); + if (key == null || value == null) + throw new NullPointerException(); + return map.internalPut(key, value) == null; + } + public final boolean addAll(Collection> c) { + boolean added = false; + for (Entry e : c) { + if (add(e)) + added = true; + } + return added; + } + public boolean equals(Object o) { + Set c; + return ((o instanceof Set) && + ((c = (Set)o) == this || + (containsAll(c) && c.containsAll(this)))); + } + } + + // Unsafe mechanics + private static final sun.misc.Unsafe UNSAFE; + private static final long counterOffset; + private static final long sizeCtlOffset; + private static final long ABASE; + private static final int ASHIFT; + + static { + int ss; + try { + UNSAFE = getUnsafe(); + Class k = ConcurrentHashMapV8.class; + counterOffset = UNSAFE.objectFieldOffset + (k.getDeclaredField("counter")); + sizeCtlOffset = UNSAFE.objectFieldOffset + (k.getDeclaredField("sizeCtl")); + Class sc = Node[].class; + ABASE = UNSAFE.arrayBaseOffset(sc); + ss = UNSAFE.arrayIndexScale(sc); + } catch (Exception e) { + throw new Error(e); + } + if ((ss & (ss-1)) != 0) + throw new Error("data type scale not a power of two"); + ASHIFT = 31 - Integer.numberOfLeadingZeros(ss); + } + + /** + * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. + * Replace with a simple call to Unsafe.getUnsafe when integrating + * into a jdk. + * + * @return a sun.misc.Unsafe + */ + private static sun.misc.Unsafe getUnsafe() { + try { + return sun.misc.Unsafe.getUnsafe(); + } catch (SecurityException se) { + try { + return java.security.AccessController.doPrivileged + (new java.security + .PrivilegedExceptionAction() { + public sun.misc.Unsafe run() throws Exception { + java.lang.reflect.Field f = sun.misc + .Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return (sun.misc.Unsafe) f.get(null); + }}); + } catch (java.security.PrivilegedActionException e) { + throw new RuntimeException("Could not initialize intrinsics", + e.getCause()); + } + } + } +} diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java new file mode 100644 index 00000000..47a923c8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java @@ -0,0 +1,203 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This is based on 1.9 version. + +package com.concurrent_ruby.ext.jsr166e; +import java.util.concurrent.atomic.AtomicLong; +import java.io.IOException; +import java.io.Serializable; +import java.io.ObjectInputStream; + +/** + * One or more variables that together maintain an initially zero + * {@code long} sum. When updates (method {@link #add}) are contended + * across threads, the set of variables may grow dynamically to reduce + * contention. Method {@link #sum} (or, equivalently, {@link + * #longValue}) returns the current total combined across the + * variables maintaining the sum. + * + *

    This class is usually preferable to {@link AtomicLong} when + * multiple threads update a common sum that is used for purposes such + * as collecting statistics, not for fine-grained synchronization + * control. Under low update contention, the two classes have similar + * characteristics. But under high contention, expected throughput of + * this class is significantly higher, at the expense of higher space + * consumption. + * + *

    This class extends {@link Number}, but does not define + * methods such as {@code hashCode} and {@code compareTo} because + * instances are expected to be mutated, and so are not useful as + * collection keys. + * + *

    jsr166e note: This class is targeted to be placed in + * java.util.concurrent.atomic. + * + * @since 1.8 + * @author Doug Lea + */ +public class LongAdder extends Striped64 implements Serializable { + private static final long serialVersionUID = 7249069246863182397L; + + /** + * Version of plus for use in retryUpdate + */ + final long fn(long v, long x) { return v + x; } + + /** + * Creates a new adder with initial sum of zero. + */ + public LongAdder() { + } + + /** + * Adds the given value. + * + * @param x the value to add + */ + public void add(long x) { + Cell[] as; long b, v; HashCode hc; Cell a; int n; + if ((as = cells) != null || !casBase(b = base, b + x)) { + boolean uncontended = true; + int h = (hc = threadHashCode.get()).code; + if (as == null || (n = as.length) < 1 || + (a = as[(n - 1) & h]) == null || + !(uncontended = a.cas(v = a.value, v + x))) + retryUpdate(x, hc, uncontended); + } + } + + /** + * Equivalent to {@code add(1)}. + */ + public void increment() { + add(1L); + } + + /** + * Equivalent to {@code add(-1)}. + */ + public void decrement() { + add(-1L); + } + + /** + * Returns the current sum. The returned value is NOT an + * atomic snapshot: Invocation in the absence of concurrent + * updates returns an accurate result, but concurrent updates that + * occur while the sum is being calculated might not be + * incorporated. + * + * @return the sum + */ + public long sum() { + long sum = base; + Cell[] as = cells; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) + sum += a.value; + } + } + return sum; + } + + /** + * Resets variables maintaining the sum to zero. This method may + * be a useful alternative to creating a new adder, but is only + * effective if there are no concurrent updates. Because this + * method is intrinsically racy, it should only be used when it is + * known that no threads are concurrently updating. + */ + public void reset() { + internalReset(0L); + } + + /** + * Equivalent in effect to {@link #sum} followed by {@link + * #reset}. This method may apply for example during quiescent + * points between multithreaded computations. If there are + * updates concurrent with this method, the returned value is + * not guaranteed to be the final value occurring before + * the reset. + * + * @return the sum + */ + public long sumThenReset() { + long sum = base; + Cell[] as = cells; + base = 0L; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) { + sum += a.value; + a.value = 0L; + } + } + } + return sum; + } + + /** + * Returns the String representation of the {@link #sum}. + * @return the String representation of the {@link #sum} + */ + public String toString() { + return Long.toString(sum()); + } + + /** + * Equivalent to {@link #sum}. + * + * @return the sum + */ + public long longValue() { + return sum(); + } + + /** + * Returns the {@link #sum} as an {@code int} after a narrowing + * primitive conversion. + */ + public int intValue() { + return (int)sum(); + } + + /** + * Returns the {@link #sum} as a {@code float} + * after a widening primitive conversion. + */ + public float floatValue() { + return (float)sum(); + } + + /** + * Returns the {@link #sum} as a {@code double} after a widening + * primitive conversion. + */ + public double doubleValue() { + return (double)sum(); + } + + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + s.defaultWriteObject(); + s.writeLong(sum()); + } + + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { + s.defaultReadObject(); + busy = 0; + cells = null; + base = s.readLong(); + } + +} diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java new file mode 100644 index 00000000..93a277fb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java @@ -0,0 +1,342 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This is based on 1.5 version. + +package com.concurrent_ruby.ext.jsr166e; +import java.util.Random; + +/** + * A package-local class holding common representation and mechanics + * for classes supporting dynamic striping on 64bit values. The class + * extends Number so that concrete subclasses must publicly do so. + */ +abstract class Striped64 extends Number { + /* + * This class maintains a lazily-initialized table of atomically + * updated variables, plus an extra "base" field. The table size + * is a power of two. Indexing uses masked per-thread hash codes. + * Nearly all declarations in this class are package-private, + * accessed directly by subclasses. + * + * Table entries are of class Cell; a variant of AtomicLong padded + * to reduce cache contention on most processors. Padding is + * overkill for most Atomics because they are usually irregularly + * scattered in memory and thus don't interfere much with each + * other. But Atomic objects residing in arrays will tend to be + * placed adjacent to each other, and so will most often share + * cache lines (with a huge negative performance impact) without + * this precaution. + * + * In part because Cells are relatively large, we avoid creating + * them until they are needed. When there is no contention, all + * updates are made to the base field. Upon first contention (a + * failed CAS on base update), the table is initialized to size 2. + * The table size is doubled upon further contention until + * reaching the nearest power of two greater than or equal to the + * number of CPUS. Table slots remain empty (null) until they are + * needed. + * + * A single spinlock ("busy") is used for initializing and + * resizing the table, as well as populating slots with new Cells. + * There is no need for a blocking lock: When the lock is not + * available, threads try other slots (or the base). During these + * retries, there is increased contention and reduced locality, + * which is still better than alternatives. + * + * Per-thread hash codes are initialized to random values. + * Contention and/or table collisions are indicated by failed + * CASes when performing an update operation (see method + * retryUpdate). Upon a collision, if the table size is less than + * the capacity, it is doubled in size unless some other thread + * holds the lock. If a hashed slot is empty, and lock is + * available, a new Cell is created. Otherwise, if the slot + * exists, a CAS is tried. Retries proceed by "double hashing", + * using a secondary hash (Marsaglia XorShift) to try to find a + * free slot. + * + * The table size is capped because, when there are more threads + * than CPUs, supposing that each thread were bound to a CPU, + * there would exist a perfect hash function mapping threads to + * slots that eliminates collisions. When we reach capacity, we + * search for this mapping by randomly varying the hash codes of + * colliding threads. Because search is random, and collisions + * only become known via CAS failures, convergence can be slow, + * and because threads are typically not bound to CPUS forever, + * may not occur at all. However, despite these limitations, + * observed contention rates are typically low in these cases. + * + * It is possible for a Cell to become unused when threads that + * once hashed to it terminate, as well as in the case where + * doubling the table causes no thread to hash to it under + * expanded mask. We do not try to detect or remove such cells, + * under the assumption that for long-running instances, observed + * contention levels will recur, so the cells will eventually be + * needed again; and for short-lived ones, it does not matter. + */ + + /** + * Padded variant of AtomicLong supporting only raw accesses plus CAS. + * The value field is placed between pads, hoping that the JVM doesn't + * reorder them. + * + * JVM intrinsics note: It would be possible to use a release-only + * form of CAS here, if it were provided. + */ + static final class Cell { + volatile long p0, p1, p2, p3, p4, p5, p6; + volatile long value; + volatile long q0, q1, q2, q3, q4, q5, q6; + Cell(long x) { value = x; } + + final boolean cas(long cmp, long val) { + return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val); + } + + // Unsafe mechanics + private static final sun.misc.Unsafe UNSAFE; + private static final long valueOffset; + static { + try { + UNSAFE = getUnsafe(); + Class ak = Cell.class; + valueOffset = UNSAFE.objectFieldOffset + (ak.getDeclaredField("value")); + } catch (Exception e) { + throw new Error(e); + } + } + + } + + /** + * Holder for the thread-local hash code. The code is initially + * random, but may be set to a different value upon collisions. + */ + static final class HashCode { + static final Random rng = new Random(); + int code; + HashCode() { + int h = rng.nextInt(); // Avoid zero to allow xorShift rehash + code = (h == 0) ? 1 : h; + } + } + + /** + * The corresponding ThreadLocal class + */ + static final class ThreadHashCode extends ThreadLocal { + public HashCode initialValue() { return new HashCode(); } + } + + /** + * Static per-thread hash codes. Shared across all instances to + * reduce ThreadLocal pollution and because adjustments due to + * collisions in one table are likely to be appropriate for + * others. + */ + static final ThreadHashCode threadHashCode = new ThreadHashCode(); + + /** Number of CPUS, to place bound on table size */ + static final int NCPU = Runtime.getRuntime().availableProcessors(); + + /** + * Table of cells. When non-null, size is a power of 2. + */ + transient volatile Cell[] cells; + + /** + * Base value, used mainly when there is no contention, but also as + * a fallback during table initialization races. Updated via CAS. + */ + transient volatile long base; + + /** + * Spinlock (locked via CAS) used when resizing and/or creating Cells. + */ + transient volatile int busy; + + /** + * Package-private default constructor + */ + Striped64() { + } + + /** + * CASes the base field. + */ + final boolean casBase(long cmp, long val) { + return UNSAFE.compareAndSwapLong(this, baseOffset, cmp, val); + } + + /** + * CASes the busy field from 0 to 1 to acquire lock. + */ + final boolean casBusy() { + return UNSAFE.compareAndSwapInt(this, busyOffset, 0, 1); + } + + /** + * Computes the function of current and new value. Subclasses + * should open-code this update function for most uses, but the + * virtualized form is needed within retryUpdate. + * + * @param currentValue the current value (of either base or a cell) + * @param newValue the argument from a user update call + * @return result of the update function + */ + abstract long fn(long currentValue, long newValue); + + /** + * Handles cases of updates involving initialization, resizing, + * creating new Cells, and/or contention. See above for + * explanation. This method suffers the usual non-modularity + * problems of optimistic retry code, relying on rechecked sets of + * reads. + * + * @param x the value + * @param hc the hash code holder + * @param wasUncontended false if CAS failed before call + */ + final void retryUpdate(long x, HashCode hc, boolean wasUncontended) { + int h = hc.code; + boolean collide = false; // True if last slot nonempty + for (;;) { + Cell[] as; Cell a; int n; long v; + if ((as = cells) != null && (n = as.length) > 0) { + if ((a = as[(n - 1) & h]) == null) { + if (busy == 0) { // Try to attach new Cell + Cell r = new Cell(x); // Optimistically create + if (busy == 0 && casBusy()) { + boolean created = false; + try { // Recheck under lock + Cell[] rs; int m, j; + if ((rs = cells) != null && + (m = rs.length) > 0 && + rs[j = (m - 1) & h] == null) { + rs[j] = r; + created = true; + } + } finally { + busy = 0; + } + if (created) + break; + continue; // Slot is now non-empty + } + } + collide = false; + } + else if (!wasUncontended) // CAS already known to fail + wasUncontended = true; // Continue after rehash + else if (a.cas(v = a.value, fn(v, x))) + break; + else if (n >= NCPU || cells != as) + collide = false; // At max size or stale + else if (!collide) + collide = true; + else if (busy == 0 && casBusy()) { + try { + if (cells == as) { // Expand table unless stale + Cell[] rs = new Cell[n << 1]; + for (int i = 0; i < n; ++i) + rs[i] = as[i]; + cells = rs; + } + } finally { + busy = 0; + } + collide = false; + continue; // Retry with expanded table + } + h ^= h << 13; // Rehash + h ^= h >>> 17; + h ^= h << 5; + } + else if (busy == 0 && cells == as && casBusy()) { + boolean init = false; + try { // Initialize table + if (cells == as) { + Cell[] rs = new Cell[2]; + rs[h & 1] = new Cell(x); + cells = rs; + init = true; + } + } finally { + busy = 0; + } + if (init) + break; + } + else if (casBase(v = base, fn(v, x))) + break; // Fall back on using base + } + hc.code = h; // Record index for next time + } + + + /** + * Sets base and all cells to the given value. + */ + final void internalReset(long initialValue) { + Cell[] as = cells; + base = initialValue; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) + a.value = initialValue; + } + } + } + + // Unsafe mechanics + private static final sun.misc.Unsafe UNSAFE; + private static final long baseOffset; + private static final long busyOffset; + static { + try { + UNSAFE = getUnsafe(); + Class sk = Striped64.class; + baseOffset = UNSAFE.objectFieldOffset + (sk.getDeclaredField("base")); + busyOffset = UNSAFE.objectFieldOffset + (sk.getDeclaredField("busy")); + } catch (Exception e) { + throw new Error(e); + } + } + + /** + * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. + * Replace with a simple call to Unsafe.getUnsafe when integrating + * into a jdk. + * + * @return a sun.misc.Unsafe + */ + private static sun.misc.Unsafe getUnsafe() { + try { + return sun.misc.Unsafe.getUnsafe(); + } catch (SecurityException se) { + try { + return java.security.AccessController.doPrivileged + (new java.security + .PrivilegedExceptionAction() { + public sun.misc.Unsafe run() throws Exception { + java.lang.reflect.Field f = sun.misc + .Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return (sun.misc.Unsafe) f.get(null); + }}); + } catch (java.security.PrivilegedActionException e) { + throw new RuntimeException("Could not initialize intrinsics", + e.getCause()); + } + } + } + +} diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java new file mode 100644 index 00000000..a4e73ea1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java @@ -0,0 +1,3800 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This is based on the 1.79 version. + +package com.concurrent_ruby.ext.jsr166e.nounsafe; + +import org.jruby.RubyClass; +import org.jruby.RubyNumeric; +import org.jruby.RubyObject; +import org.jruby.exceptions.RaiseException; +import com.concurrent_ruby.ext.jsr166e.ConcurrentHashMap; +import com.concurrent_ruby.ext.jsr166y.ThreadLocalRandom; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; + +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.Collection; +import java.util.Hashtable; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Enumeration; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.concurrent.locks.AbstractQueuedSynchronizer; + +import java.io.Serializable; + +/** + * A hash table supporting full concurrency of retrievals and + * high expected concurrency for updates. This class obeys the + * same functional specification as {@link java.util.Hashtable}, and + * includes versions of methods corresponding to each method of + * {@code Hashtable}. However, even though all operations are + * thread-safe, retrieval operations do not entail locking, + * and there is not any support for locking the entire table + * in a way that prevents all access. This class is fully + * interoperable with {@code Hashtable} in programs that rely on its + * thread safety but not on its synchronization details. + * + *

    Retrieval operations (including {@code get}) generally do not + * block, so may overlap with update operations (including {@code put} + * and {@code remove}). Retrievals reflect the results of the most + * recently completed update operations holding upon their + * onset. (More formally, an update operation for a given key bears a + * happens-before relation with any (non-null) retrieval for + * that key reporting the updated value.) For aggregate operations + * such as {@code putAll} and {@code clear}, concurrent retrievals may + * reflect insertion or removal of only some entries. Similarly, + * Iterators and Enumerations return elements reflecting the state of + * the hash table at some point at or since the creation of the + * iterator/enumeration. They do not throw {@link + * ConcurrentModificationException}. However, iterators are designed + * to be used by only one thread at a time. Bear in mind that the + * results of aggregate status methods including {@code size}, {@code + * isEmpty}, and {@code containsValue} are typically useful only when + * a map is not undergoing concurrent updates in other threads. + * Otherwise the results of these methods reflect transient states + * that may be adequate for monitoring or estimation purposes, but not + * for program control. + * + *

    The table is dynamically expanded when there are too many + * collisions (i.e., keys that have distinct hash codes but fall into + * the same slot modulo the table size), with the expected average + * effect of maintaining roughly two bins per mapping (corresponding + * to a 0.75 load factor threshold for resizing). There may be much + * variance around this average as mappings are added and removed, but + * overall, this maintains a commonly accepted time/space tradeoff for + * hash tables. However, resizing this or any other kind of hash + * table may be a relatively slow operation. When possible, it is a + * good idea to provide a size estimate as an optional {@code + * initialCapacity} constructor argument. An additional optional + * {@code loadFactor} constructor argument provides a further means of + * customizing initial table capacity by specifying the table density + * to be used in calculating the amount of space to allocate for the + * given number of elements. Also, for compatibility with previous + * versions of this class, constructors may optionally specify an + * expected {@code concurrencyLevel} as an additional hint for + * internal sizing. Note that using many keys with exactly the same + * {@code hashCode()} is a sure way to slow down performance of any + * hash table. + * + *

    A {@link Set} projection of a ConcurrentHashMapV8 may be created + * (using {@link #newKeySet()} or {@link #newKeySet(int)}), or viewed + * (using {@link #keySet(Object)} when only keys are of interest, and the + * mapped values are (perhaps transiently) not used or all take the + * same mapping value. + * + *

    A ConcurrentHashMapV8 can be used as scalable frequency map (a + * form of histogram or multiset) by using {@link LongAdder} values + * and initializing via {@link #computeIfAbsent}. For example, to add + * a count to a {@code ConcurrentHashMapV8 freqs}, you + * can use {@code freqs.computeIfAbsent(k -> new + * LongAdder()).increment();} + * + *

    This class and its views and iterators implement all of the + * optional methods of the {@link Map} and {@link Iterator} + * interfaces. + * + *

    Like {@link Hashtable} but unlike {@link HashMap}, this class + * does not allow {@code null} to be used as a key or value. + * + *

    ConcurrentHashMapV8s support parallel operations using the {@link + * ForkJoinPool#commonPool}. (Tasks that may be used in other contexts + * are available in class {@link ForkJoinTasks}). These operations are + * designed to be safely, and often sensibly, applied even with maps + * that are being concurrently updated by other threads; for example, + * when computing a snapshot summary of the values in a shared + * registry. There are three kinds of operation, each with four + * forms, accepting functions with Keys, Values, Entries, and (Key, + * Value) arguments and/or return values. (The first three forms are + * also available via the {@link #keySet()}, {@link #values()} and + * {@link #entrySet()} views). Because the elements of a + * ConcurrentHashMapV8 are not ordered in any particular way, and may be + * processed in different orders in different parallel executions, the + * correctness of supplied functions should not depend on any + * ordering, or on any other objects or values that may transiently + * change while computation is in progress; and except for forEach + * actions, should ideally be side-effect-free. + * + *

    + * + *

    The concurrency properties of bulk operations follow + * from those of ConcurrentHashMapV8: Any non-null result returned + * from {@code get(key)} and related access methods bears a + * happens-before relation with the associated insertion or + * update. The result of any bulk operation reflects the + * composition of these per-element relations (but is not + * necessarily atomic with respect to the map as a whole unless it + * is somehow known to be quiescent). Conversely, because keys + * and values in the map are never null, null serves as a reliable + * atomic indicator of the current lack of any result. To + * maintain this property, null serves as an implicit basis for + * all non-scalar reduction operations. For the double, long, and + * int versions, the basis should be one that, when combined with + * any other value, returns that other value (more formally, it + * should be the identity element for the reduction). Most common + * reductions have these properties; for example, computing a sum + * with basis 0 or a minimum with basis MAX_VALUE. + * + *

    Search and transformation functions provided as arguments + * should similarly return null to indicate the lack of any result + * (in which case it is not used). In the case of mapped + * reductions, this also enables transformations to serve as + * filters, returning null (or, in the case of primitive + * specializations, the identity basis) if the element should not + * be combined. You can create compound transformations and + * filterings by composing them yourself under this "null means + * there is nothing there now" rule before using them in search or + * reduce operations. + * + *

    Methods accepting and/or returning Entry arguments maintain + * key-value associations. They may be useful for example when + * finding the key for the greatest value. Note that "plain" Entry + * arguments can be supplied using {@code new + * AbstractMap.SimpleEntry(k,v)}. + * + *

    Bulk operations may complete abruptly, throwing an + * exception encountered in the application of a supplied + * function. Bear in mind when handling such exceptions that other + * concurrently executing functions could also have thrown + * exceptions, or would have done so if the first exception had + * not occurred. + * + *

    Parallel speedups for bulk operations compared to sequential + * processing are common but not guaranteed. Operations involving + * brief functions on small maps may execute more slowly than + * sequential loops if the underlying work to parallelize the + * computation is more expensive than the computation itself. + * Similarly, parallelization may not lead to much actual parallelism + * if all processors are busy performing unrelated tasks. + * + *

    All arguments to all task methods must be non-null. + * + *

    jsr166e note: During transition, this class + * uses nested functional interfaces with different names but the + * same forms as those expected for JDK8. + * + *

    This class is a member of the + * + * Java Collections Framework. + * + * @since 1.5 + * @author Doug Lea + * @param the type of keys maintained by this map + * @param the type of mapped values + */ +public class ConcurrentHashMapV8 + implements ConcurrentMap, Serializable, ConcurrentHashMap { + private static final long serialVersionUID = 7249069246763182397L; + + /** + * A partitionable iterator. A Spliterator can be traversed + * directly, but can also be partitioned (before traversal) by + * creating another Spliterator that covers a non-overlapping + * portion of the elements, and so may be amenable to parallel + * execution. + * + *

    This interface exports a subset of expected JDK8 + * functionality. + * + *

    Sample usage: Here is one (of the several) ways to compute + * the sum of the values held in a map using the ForkJoin + * framework. As illustrated here, Spliterators are well suited to + * designs in which a task repeatedly splits off half its work + * into forked subtasks until small enough to process directly, + * and then joins these subtasks. Variants of this style can also + * be used in completion-based designs. + * + *

    +     * {@code ConcurrentHashMapV8 m = ...
    +     * // split as if have 8 * parallelism, for load balance
    +     * int n = m.size();
    +     * int p = aForkJoinPool.getParallelism() * 8;
    +     * int split = (n < p)? n : p;
    +     * long sum = aForkJoinPool.invoke(new SumValues(m.valueSpliterator(), split, null));
    +     * // ...
    +     * static class SumValues extends RecursiveTask {
    +     *   final Spliterator s;
    +     *   final int split;             // split while > 1
    +     *   final SumValues nextJoin;    // records forked subtasks to join
    +     *   SumValues(Spliterator s, int depth, SumValues nextJoin) {
    +     *     this.s = s; this.depth = depth; this.nextJoin = nextJoin;
    +     *   }
    +     *   public Long compute() {
    +     *     long sum = 0;
    +     *     SumValues subtasks = null; // fork subtasks
    +     *     for (int s = split >>> 1; s > 0; s >>>= 1)
    +     *       (subtasks = new SumValues(s.split(), s, subtasks)).fork();
    +     *     while (s.hasNext())        // directly process remaining elements
    +     *       sum += s.next();
    +     *     for (SumValues t = subtasks; t != null; t = t.nextJoin)
    +     *       sum += t.join();         // collect subtask results
    +     *     return sum;
    +     *   }
    +     * }
    +     * }
    + */ + public static interface Spliterator extends Iterator { + /** + * Returns a Spliterator covering approximately half of the + * elements, guaranteed not to overlap with those subsequently + * returned by this Spliterator. After invoking this method, + * the current Spliterator will not produce any of + * the elements of the returned Spliterator, but the two + * Spliterators together will produce all of the elements that + * would have been produced by this Spliterator had this + * method not been called. The exact number of elements + * produced by the returned Spliterator is not guaranteed, and + * may be zero (i.e., with {@code hasNext()} reporting {@code + * false}) if this Spliterator cannot be further split. + * + * @return a Spliterator covering approximately half of the + * elements + * @throws IllegalStateException if this Spliterator has + * already commenced traversing elements + */ + Spliterator split(); + } + + + /* + * Overview: + * + * The primary design goal of this hash table is to maintain + * concurrent readability (typically method get(), but also + * iterators and related methods) while minimizing update + * contention. Secondary goals are to keep space consumption about + * the same or better than java.util.HashMap, and to support high + * initial insertion rates on an empty table by many threads. + * + * Each key-value mapping is held in a Node. Because Node fields + * can contain special values, they are defined using plain Object + * types. Similarly in turn, all internal methods that use them + * work off Object types. And similarly, so do the internal + * methods of auxiliary iterator and view classes. All public + * generic typed methods relay in/out of these internal methods, + * supplying null-checks and casts as needed. This also allows + * many of the public methods to be factored into a smaller number + * of internal methods (although sadly not so for the five + * variants of put-related operations). The validation-based + * approach explained below leads to a lot of code sprawl because + * retry-control precludes factoring into smaller methods. + * + * The table is lazily initialized to a power-of-two size upon the + * first insertion. Each bin in the table normally contains a + * list of Nodes (most often, the list has only zero or one Node). + * Table accesses require volatile/atomic reads, writes, and + * CASes. Because there is no other way to arrange this without + * adding further indirections, we use intrinsics + * (sun.misc.Unsafe) operations. The lists of nodes within bins + * are always accurately traversable under volatile reads, so long + * as lookups check hash code and non-nullness of value before + * checking key equality. + * + * We use the top two bits of Node hash fields for control + * purposes -- they are available anyway because of addressing + * constraints. As explained further below, these top bits are + * used as follows: + * 00 - Normal + * 01 - Locked + * 11 - Locked and may have a thread waiting for lock + * 10 - Node is a forwarding node + * + * The lower 30 bits of each Node's hash field contain a + * transformation of the key's hash code, except for forwarding + * nodes, for which the lower bits are zero (and so always have + * hash field == MOVED). + * + * Insertion (via put or its variants) of the first node in an + * empty bin is performed by just CASing it to the bin. This is + * by far the most common case for put operations under most + * key/hash distributions. Other update operations (insert, + * delete, and replace) require locks. We do not want to waste + * the space required to associate a distinct lock object with + * each bin, so instead use the first node of a bin list itself as + * a lock. Blocking support for these locks relies on the builtin + * "synchronized" monitors. However, we also need a tryLock + * construction, so we overlay these by using bits of the Node + * hash field for lock control (see above), and so normally use + * builtin monitors only for blocking and signalling using + * wait/notifyAll constructions. See Node.tryAwaitLock. + * + * Using the first node of a list as a lock does not by itself + * suffice though: When a node is locked, any update must first + * validate that it is still the first node after locking it, and + * retry if not. Because new nodes are always appended to lists, + * once a node is first in a bin, it remains first until deleted + * or the bin becomes invalidated (upon resizing). However, + * operations that only conditionally update may inspect nodes + * until the point of update. This is a converse of sorts to the + * lazy locking technique described by Herlihy & Shavit. + * + * The main disadvantage of per-bin locks is that other update + * operations on other nodes in a bin list protected by the same + * lock can stall, for example when user equals() or mapping + * functions take a long time. However, statistically, under + * random hash codes, this is not a common problem. Ideally, the + * frequency of nodes in bins follows a Poisson distribution + * (http://en.wikipedia.org/wiki/Poisson_distribution) with a + * parameter of about 0.5 on average, given the resizing threshold + * of 0.75, although with a large variance because of resizing + * granularity. Ignoring variance, the expected occurrences of + * list size k are (exp(-0.5) * pow(0.5, k) / factorial(k)). The + * first values are: + * + * 0: 0.60653066 + * 1: 0.30326533 + * 2: 0.07581633 + * 3: 0.01263606 + * 4: 0.00157952 + * 5: 0.00015795 + * 6: 0.00001316 + * 7: 0.00000094 + * 8: 0.00000006 + * more: less than 1 in ten million + * + * Lock contention probability for two threads accessing distinct + * elements is roughly 1 / (8 * #elements) under random hashes. + * + * Actual hash code distributions encountered in practice + * sometimes deviate significantly from uniform randomness. This + * includes the case when N > (1<<30), so some keys MUST collide. + * Similarly for dumb or hostile usages in which multiple keys are + * designed to have identical hash codes. Also, although we guard + * against the worst effects of this (see method spread), sets of + * hashes may differ only in bits that do not impact their bin + * index for a given power-of-two mask. So we use a secondary + * strategy that applies when the number of nodes in a bin exceeds + * a threshold, and at least one of the keys implements + * Comparable. These TreeBins use a balanced tree to hold nodes + * (a specialized form of red-black trees), bounding search time + * to O(log N). Each search step in a TreeBin is around twice as + * slow as in a regular list, but given that N cannot exceed + * (1<<64) (before running out of addresses) this bounds search + * steps, lock hold times, etc, to reasonable constants (roughly + * 100 nodes inspected per operation worst case) so long as keys + * are Comparable (which is very common -- String, Long, etc). + * TreeBin nodes (TreeNodes) also maintain the same "next" + * traversal pointers as regular nodes, so can be traversed in + * iterators in the same way. + * + * The table is resized when occupancy exceeds a percentage + * threshold (nominally, 0.75, but see below). Only a single + * thread performs the resize (using field "sizeCtl", to arrange + * exclusion), but the table otherwise remains usable for reads + * and updates. Resizing proceeds by transferring bins, one by + * one, from the table to the next table. Because we are using + * power-of-two expansion, the elements from each bin must either + * stay at same index, or move with a power of two offset. We + * eliminate unnecessary node creation by catching cases where old + * nodes can be reused because their next fields won't change. On + * average, only about one-sixth of them need cloning when a table + * doubles. The nodes they replace will be garbage collectable as + * soon as they are no longer referenced by any reader thread that + * may be in the midst of concurrently traversing table. Upon + * transfer, the old table bin contains only a special forwarding + * node (with hash field "MOVED") that contains the next table as + * its key. On encountering a forwarding node, access and update + * operations restart, using the new table. + * + * Each bin transfer requires its bin lock. However, unlike other + * cases, a transfer can skip a bin if it fails to acquire its + * lock, and revisit it later (unless it is a TreeBin). Method + * rebuild maintains a buffer of TRANSFER_BUFFER_SIZE bins that + * have been skipped because of failure to acquire a lock, and + * blocks only if none are available (i.e., only very rarely). + * The transfer operation must also ensure that all accessible + * bins in both the old and new table are usable by any traversal. + * When there are no lock acquisition failures, this is arranged + * simply by proceeding from the last bin (table.length - 1) up + * towards the first. Upon seeing a forwarding node, traversals + * (see class Iter) arrange to move to the new table + * without revisiting nodes. However, when any node is skipped + * during a transfer, all earlier table bins may have become + * visible, so are initialized with a reverse-forwarding node back + * to the old table until the new ones are established. (This + * sometimes requires transiently locking a forwarding node, which + * is possible under the above encoding.) These more expensive + * mechanics trigger only when necessary. + * + * The traversal scheme also applies to partial traversals of + * ranges of bins (via an alternate Traverser constructor) + * to support partitioned aggregate operations. Also, read-only + * operations give up if ever forwarded to a null table, which + * provides support for shutdown-style clearing, which is also not + * currently implemented. + * + * Lazy table initialization minimizes footprint until first use, + * and also avoids resizings when the first operation is from a + * putAll, constructor with map argument, or deserialization. + * These cases attempt to override the initial capacity settings, + * but harmlessly fail to take effect in cases of races. + * + * The element count is maintained using a LongAdder, which avoids + * contention on updates but can encounter cache thrashing if read + * too frequently during concurrent access. To avoid reading so + * often, resizing is attempted either when a bin lock is + * contended, or upon adding to a bin already holding two or more + * nodes (checked before adding in the xIfAbsent methods, after + * adding in others). Under uniform hash distributions, the + * probability of this occurring at threshold is around 13%, + * meaning that only about 1 in 8 puts check threshold (and after + * resizing, many fewer do so). But this approximation has high + * variance for small table sizes, so we check on any collision + * for sizes <= 64. The bulk putAll operation further reduces + * contention by only committing count updates upon these size + * checks. + * + * Maintaining API and serialization compatibility with previous + * versions of this class introduces several oddities. Mainly: We + * leave untouched but unused constructor arguments referring to + * concurrencyLevel. We accept a loadFactor constructor argument, + * but apply it only to initial table capacity (which is the only + * time that we can guarantee to honor it.) We also declare an + * unused "Segment" class that is instantiated in minimal form + * only when serializing. + */ + + /* ---------------- Constants -------------- */ + + /** + * The largest possible table capacity. This value must be + * exactly 1<<30 to stay within Java array allocation and indexing + * bounds for power of two table sizes, and is further required + * because the top two bits of 32bit hash fields are used for + * control purposes. + */ + private static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * The default initial table capacity. Must be a power of 2 + * (i.e., at least 1) and at most MAXIMUM_CAPACITY. + */ + private static final int DEFAULT_CAPACITY = 16; + + /** + * The largest possible (non-power of two) array size. + * Needed by toArray and related methods. + */ + static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + + /** + * The default concurrency level for this table. Unused but + * defined for compatibility with previous versions of this class. + */ + private static final int DEFAULT_CONCURRENCY_LEVEL = 16; + + /** + * The load factor for this table. Overrides of this value in + * constructors affect only the initial table capacity. The + * actual floating point value isn't normally used -- it is + * simpler to use expressions such as {@code n - (n >>> 2)} for + * the associated resizing threshold. + */ + private static final float LOAD_FACTOR = 0.75f; + + /** + * The buffer size for skipped bins during transfers. The + * value is arbitrary but should be large enough to avoid + * most locking stalls during resizes. + */ + private static final int TRANSFER_BUFFER_SIZE = 32; + + /** + * The bin count threshold for using a tree rather than list for a + * bin. The value reflects the approximate break-even point for + * using tree-based operations. + * Note that Doug's version defaults to 8, but when dealing with + * Ruby objects it is actually beneficial to avoid TreeNodes + * as long as possible as it usually means going into Ruby land. + */ + private static final int TREE_THRESHOLD = 16; + + /* + * Encodings for special uses of Node hash fields. See above for + * explanation. + */ + static final int MOVED = 0x80000000; // hash field for forwarding nodes + static final int LOCKED = 0x40000000; // set/tested only as a bit + static final int WAITING = 0xc0000000; // both bits set/tested together + static final int HASH_BITS = 0x3fffffff; // usable bits of normal node hash + + /* ---------------- Fields -------------- */ + + /** + * The array of bins. Lazily initialized upon first insertion. + * Size is always a power of two. Accessed directly by iterators. + */ + transient volatile AtomicReferenceArray table; + + /** + * The counter maintaining number of elements. + */ + private transient LongAdder counter; + + /** + * Table initialization and resizing control. When negative, the + * table is being initialized or resized. Otherwise, when table is + * null, holds the initial table size to use upon creation, or 0 + * for default. After initialization, holds the next element count + * value upon which to resize the table. + */ + private transient volatile int sizeCtl; + + // views + private transient KeySetView keySet; + private transient ValuesView values; + private transient EntrySetView entrySet; + + /** For serialization compatibility. Null unless serialized; see below */ + private Segment[] segments; + + static AtomicIntegerFieldUpdater SIZE_CTRL_UPDATER = AtomicIntegerFieldUpdater.newUpdater(ConcurrentHashMapV8.class, "sizeCtl"); + + /* ---------------- Table element access -------------- */ + + /* + * Volatile access methods are used for table elements as well as + * elements of in-progress next table while resizing. Uses are + * null checked by callers, and implicitly bounds-checked, relying + * on the invariants that tab arrays have non-zero size, and all + * indices are masked with (tab.length - 1) which is never + * negative and always less than length. Note that, to be correct + * wrt arbitrary concurrency errors by users, bounds checks must + * operate on local variables, which accounts for some odd-looking + * inline assignments below. + */ + + static final Node tabAt(AtomicReferenceArray tab, int i) { // used by Iter + return tab.get(i); + } + + private static final boolean casTabAt(AtomicReferenceArray tab, int i, Node c, Node v) { + return tab.compareAndSet(i, c, v); + } + + private static final void setTabAt(AtomicReferenceArray tab, int i, Node v) { + tab.set(i, v); + } + + /* ---------------- Nodes -------------- */ + + /** + * Key-value entry. Note that this is never exported out as a + * user-visible Map.Entry (see MapEntry below). Nodes with a hash + * field of MOVED are special, and do not contain user keys or + * values. Otherwise, keys are never null, and null val fields + * indicate that a node is in the process of being deleted or + * created. For purposes of read-only access, a key may be read + * before a val, but can only be used after checking val to be + * non-null. + */ + static class Node { + volatile int hash; + final Object key; + volatile Object val; + volatile Node next; + + static AtomicIntegerFieldUpdater HASH_UPDATER = AtomicIntegerFieldUpdater.newUpdater(Node.class, "hash"); + + Node(int hash, Object key, Object val, Node next) { + this.hash = hash; + this.key = key; + this.val = val; + this.next = next; + } + + /** CompareAndSet the hash field */ + final boolean casHash(int cmp, int val) { + return HASH_UPDATER.compareAndSet(this, cmp, val); + } + + /** The number of spins before blocking for a lock */ + static final int MAX_SPINS = + Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1; + + /** + * Spins a while if LOCKED bit set and this node is the first + * of its bin, and then sets WAITING bits on hash field and + * blocks (once) if they are still set. It is OK for this + * method to return even if lock is not available upon exit, + * which enables these simple single-wait mechanics. + * + * The corresponding signalling operation is performed within + * callers: Upon detecting that WAITING has been set when + * unlocking lock (via a failed CAS from non-waiting LOCKED + * state), unlockers acquire the sync lock and perform a + * notifyAll. + * + * The initial sanity check on tab and bounds is not currently + * necessary in the only usages of this method, but enables + * use in other future contexts. + */ + final void tryAwaitLock(AtomicReferenceArray tab, int i) { + if (tab != null && i >= 0 && i < tab.length()) { // sanity check + int r = ThreadLocalRandom.current().nextInt(); // randomize spins + int spins = MAX_SPINS, h; + while (tabAt(tab, i) == this && ((h = hash) & LOCKED) != 0) { + if (spins >= 0) { + r ^= r << 1; r ^= r >>> 3; r ^= r << 10; // xorshift + if (r >= 0 && --spins == 0) + Thread.yield(); // yield before block + } + else if (casHash(h, h | WAITING)) { + synchronized (this) { + if (tabAt(tab, i) == this && + (hash & WAITING) == WAITING) { + try { + wait(); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + } + else + notifyAll(); // possibly won race vs signaller + } + break; + } + } + } + } + } + + /* ---------------- TreeBins -------------- */ + + /** + * Nodes for use in TreeBins + */ + static final class TreeNode extends Node { + TreeNode parent; // red-black tree links + TreeNode left; + TreeNode right; + TreeNode prev; // needed to unlink next upon deletion + boolean red; + + TreeNode(int hash, Object key, Object val, Node next, TreeNode parent) { + super(hash, key, val, next); + this.parent = parent; + } + } + + /** + * A specialized form of red-black tree for use in bins + * whose size exceeds a threshold. + * + * TreeBins use a special form of comparison for search and + * related operations (which is the main reason we cannot use + * existing collections such as TreeMaps). TreeBins contain + * Comparable elements, but may contain others, as well as + * elements that are Comparable but not necessarily Comparable + * for the same T, so we cannot invoke compareTo among them. To + * handle this, the tree is ordered primarily by hash value, then + * by getClass().getName() order, and then by Comparator order + * among elements of the same class. On lookup at a node, if + * elements are not comparable or compare as 0, both left and + * right children may need to be searched in the case of tied hash + * values. (This corresponds to the full list search that would be + * necessary if all elements were non-Comparable and had tied + * hashes.) The red-black balancing code is updated from + * pre-jdk-collections + * (http://gee.cs.oswego.edu/dl/classes/collections/RBCell.java) + * based in turn on Cormen, Leiserson, and Rivest "Introduction to + * Algorithms" (CLR). + * + * TreeBins also maintain a separate locking discipline than + * regular bins. Because they are forwarded via special MOVED + * nodes at bin heads (which can never change once established), + * we cannot use those nodes as locks. Instead, TreeBin + * extends AbstractQueuedSynchronizer to support a simple form of + * read-write lock. For update operations and table validation, + * the exclusive form of lock behaves in the same way as bin-head + * locks. However, lookups use shared read-lock mechanics to allow + * multiple readers in the absence of writers. Additionally, + * these lookups do not ever block: While the lock is not + * available, they proceed along the slow traversal path (via + * next-pointers) until the lock becomes available or the list is + * exhausted, whichever comes first. (These cases are not fast, + * but maximize aggregate expected throughput.) The AQS mechanics + * for doing this are straightforward. The lock state is held as + * AQS getState(). Read counts are negative; the write count (1) + * is positive. There are no signalling preferences among readers + * and writers. Since we don't need to export full Lock API, we + * just override the minimal AQS methods and use them directly. + */ + static final class TreeBin extends AbstractQueuedSynchronizer { + private static final long serialVersionUID = 2249069246763182397L; + transient TreeNode root; // root of tree + transient TreeNode first; // head of next-pointer list + + /* AQS overrides */ + public final boolean isHeldExclusively() { return getState() > 0; } + public final boolean tryAcquire(int ignore) { + if (compareAndSetState(0, 1)) { + setExclusiveOwnerThread(Thread.currentThread()); + return true; + } + return false; + } + public final boolean tryRelease(int ignore) { + setExclusiveOwnerThread(null); + setState(0); + return true; + } + public final int tryAcquireShared(int ignore) { + for (int c;;) { + if ((c = getState()) > 0) + return -1; + if (compareAndSetState(c, c -1)) + return 1; + } + } + public final boolean tryReleaseShared(int ignore) { + int c; + do {} while (!compareAndSetState(c = getState(), c + 1)); + return c == -1; + } + + /** From CLR */ + private void rotateLeft(TreeNode p) { + if (p != null) { + TreeNode r = p.right, pp, rl; + if ((rl = p.right = r.left) != null) + rl.parent = p; + if ((pp = r.parent = p.parent) == null) + root = r; + else if (pp.left == p) + pp.left = r; + else + pp.right = r; + r.left = p; + p.parent = r; + } + } + + /** From CLR */ + private void rotateRight(TreeNode p) { + if (p != null) { + TreeNode l = p.left, pp, lr; + if ((lr = p.left = l.right) != null) + lr.parent = p; + if ((pp = l.parent = p.parent) == null) + root = l; + else if (pp.right == p) + pp.right = l; + else + pp.left = l; + l.right = p; + p.parent = l; + } + } + + @SuppressWarnings("unchecked") final TreeNode getTreeNode + (int h, Object k, TreeNode p) { + return getTreeNode(h, (RubyObject)k, p); + } + + /** + * Returns the TreeNode (or null if not found) for the given key + * starting at given root. + */ + @SuppressWarnings("unchecked") final TreeNode getTreeNode + (int h, RubyObject k, TreeNode p) { + RubyClass c = k.getMetaClass(); boolean kNotComparable = !k.respondsTo("<=>"); + while (p != null) { + int dir, ph; RubyObject pk; RubyClass pc; + if ((ph = p.hash) == h) { + if ((pk = (RubyObject)p.key) == k || k.equals(pk)) + return p; + if (c != (pc = (RubyClass)pk.getMetaClass()) || + kNotComparable || + (dir = rubyCompare(k, pk)) == 0) { + dir = (c == pc) ? 0 : c.getName().compareTo(pc.getName()); + if (dir == 0) { // if still stuck, need to check both sides + TreeNode r = null, pl, pr; + // try to recurse on the right + if ((pr = p.right) != null && h >= pr.hash && (r = getTreeNode(h, k, pr)) != null) + return r; + // try to continue iterating on the left side + else if ((pl = p.left) != null && h <= pl.hash) + dir = -1; + else // no matching node found + return null; + } + } + } + else + dir = (h < ph) ? -1 : 1; + p = (dir > 0) ? p.right : p.left; + } + return null; + } + + int rubyCompare(RubyObject l, RubyObject r) { + ThreadContext context = l.getMetaClass().getRuntime().getCurrentContext(); + IRubyObject result; + try { + result = l.callMethod(context, "<=>", r); + } catch (RaiseException e) { + // handle objects "lying" about responding to <=>, ie: an Array containing non-comparable keys + if (context.runtime.getNoMethodError().isInstance(e.getException())) { + return 0; + } + throw e; + } + + return result.isNil() ? 0 : RubyNumeric.num2int(result.convertToInteger()); + } + + /** + * Wrapper for getTreeNode used by CHM.get. Tries to obtain + * read-lock to call getTreeNode, but during failure to get + * lock, searches along next links. + */ + final Object getValue(int h, Object k) { + Node r = null; + int c = getState(); // Must read lock state first + for (Node e = first; e != null; e = e.next) { + if (c <= 0 && compareAndSetState(c, c - 1)) { + try { + r = getTreeNode(h, k, root); + } finally { + releaseShared(0); + } + break; + } + else if ((e.hash & HASH_BITS) == h && k.equals(e.key)) { + r = e; + break; + } + else + c = getState(); + } + return r == null ? null : r.val; + } + + @SuppressWarnings("unchecked") final TreeNode putTreeNode + (int h, Object k, Object v) { + return putTreeNode(h, (RubyObject)k, v); + } + + /** + * Finds or adds a node. + * @return null if added + */ + @SuppressWarnings("unchecked") final TreeNode putTreeNode + (int h, RubyObject k, Object v) { + RubyClass c = k.getMetaClass(); + boolean kNotComparable = !k.respondsTo("<=>"); + TreeNode pp = root, p = null; + int dir = 0; + while (pp != null) { // find existing node or leaf to insert at + int ph; RubyObject pk; RubyClass pc; + p = pp; + if ((ph = p.hash) == h) { + if ((pk = (RubyObject)p.key) == k || k.equals(pk)) + return p; + if (c != (pc = pk.getMetaClass()) || + kNotComparable || + (dir = rubyCompare(k, pk)) == 0) { + dir = (c == pc) ? 0 : c.getName().compareTo(pc.getName()); + if (dir == 0) { // if still stuck, need to check both sides + TreeNode r = null, pr; + // try to recurse on the right + if ((pr = p.right) != null && h >= pr.hash && (r = getTreeNode(h, k, pr)) != null) + return r; + else // continue descending down the left subtree + dir = -1; + } + } + } + else + dir = (h < ph) ? -1 : 1; + pp = (dir > 0) ? p.right : p.left; + } + + TreeNode f = first; + TreeNode x = first = new TreeNode(h, (Object)k, v, f, p); + if (p == null) + root = x; + else { // attach and rebalance; adapted from CLR + TreeNode xp, xpp; + if (f != null) + f.prev = x; + if (dir <= 0) + p.left = x; + else + p.right = x; + x.red = true; + while (x != null && (xp = x.parent) != null && xp.red && + (xpp = xp.parent) != null) { + TreeNode xppl = xpp.left; + if (xp == xppl) { + TreeNode y = xpp.right; + if (y != null && y.red) { + y.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } + else { + if (x == xp.right) { + rotateLeft(x = xp); + xpp = (xp = x.parent) == null ? null : xp.parent; + } + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + rotateRight(xpp); + } + } + } + } + else { + TreeNode y = xppl; + if (y != null && y.red) { + y.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } + else { + if (x == xp.left) { + rotateRight(x = xp); + xpp = (xp = x.parent) == null ? null : xp.parent; + } + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + rotateLeft(xpp); + } + } + } + } + } + TreeNode r = root; + if (r != null && r.red) + r.red = false; + } + return null; + } + + /** + * Removes the given node, that must be present before this + * call. This is messier than typical red-black deletion code + * because we cannot swap the contents of an interior node + * with a leaf successor that is pinned by "next" pointers + * that are accessible independently of lock. So instead we + * swap the tree linkages. + */ + final void deleteTreeNode(TreeNode p) { + TreeNode next = (TreeNode)p.next; // unlink traversal pointers + TreeNode pred = p.prev; + if (pred == null) + first = next; + else + pred.next = next; + if (next != null) + next.prev = pred; + TreeNode replacement; + TreeNode pl = p.left; + TreeNode pr = p.right; + if (pl != null && pr != null) { + TreeNode s = pr, sl; + while ((sl = s.left) != null) // find successor + s = sl; + boolean c = s.red; s.red = p.red; p.red = c; // swap colors + TreeNode sr = s.right; + TreeNode pp = p.parent; + if (s == pr) { // p was s's direct parent + p.parent = s; + s.right = p; + } + else { + TreeNode sp = s.parent; + if ((p.parent = sp) != null) { + if (s == sp.left) + sp.left = p; + else + sp.right = p; + } + if ((s.right = pr) != null) + pr.parent = s; + } + p.left = null; + if ((p.right = sr) != null) + sr.parent = p; + if ((s.left = pl) != null) + pl.parent = s; + if ((s.parent = pp) == null) + root = s; + else if (p == pp.left) + pp.left = s; + else + pp.right = s; + replacement = sr; + } + else + replacement = (pl != null) ? pl : pr; + TreeNode pp = p.parent; + if (replacement == null) { + if (pp == null) { + root = null; + return; + } + replacement = p; + } + else { + replacement.parent = pp; + if (pp == null) + root = replacement; + else if (p == pp.left) + pp.left = replacement; + else + pp.right = replacement; + p.left = p.right = p.parent = null; + } + if (!p.red) { // rebalance, from CLR + TreeNode x = replacement; + while (x != null) { + TreeNode xp, xpl; + if (x.red || (xp = x.parent) == null) { + x.red = false; + break; + } + if (x == (xpl = xp.left)) { + TreeNode sib = xp.right; + if (sib != null && sib.red) { + sib.red = false; + xp.red = true; + rotateLeft(xp); + sib = (xp = x.parent) == null ? null : xp.right; + } + if (sib == null) + x = xp; + else { + TreeNode sl = sib.left, sr = sib.right; + if ((sr == null || !sr.red) && + (sl == null || !sl.red)) { + sib.red = true; + x = xp; + } + else { + if (sr == null || !sr.red) { + if (sl != null) + sl.red = false; + sib.red = true; + rotateRight(sib); + sib = (xp = x.parent) == null ? null : xp.right; + } + if (sib != null) { + sib.red = (xp == null) ? false : xp.red; + if ((sr = sib.right) != null) + sr.red = false; + } + if (xp != null) { + xp.red = false; + rotateLeft(xp); + } + x = root; + } + } + } + else { // symmetric + TreeNode sib = xpl; + if (sib != null && sib.red) { + sib.red = false; + xp.red = true; + rotateRight(xp); + sib = (xp = x.parent) == null ? null : xp.left; + } + if (sib == null) + x = xp; + else { + TreeNode sl = sib.left, sr = sib.right; + if ((sl == null || !sl.red) && + (sr == null || !sr.red)) { + sib.red = true; + x = xp; + } + else { + if (sl == null || !sl.red) { + if (sr != null) + sr.red = false; + sib.red = true; + rotateLeft(sib); + sib = (xp = x.parent) == null ? null : xp.left; + } + if (sib != null) { + sib.red = (xp == null) ? false : xp.red; + if ((sl = sib.left) != null) + sl.red = false; + } + if (xp != null) { + xp.red = false; + rotateRight(xp); + } + x = root; + } + } + } + } + } + if (p == replacement && (pp = p.parent) != null) { + if (p == pp.left) // detach pointers + pp.left = null; + else if (p == pp.right) + pp.right = null; + p.parent = null; + } + } + } + + /* ---------------- Collision reduction methods -------------- */ + + /** + * Spreads higher bits to lower, and also forces top 2 bits to 0. + * Because the table uses power-of-two masking, sets of hashes + * that vary only in bits above the current mask will always + * collide. (Among known examples are sets of Float keys holding + * consecutive whole numbers in small tables.) To counter this, + * we apply a transform that spreads the impact of higher bits + * downward. There is a tradeoff between speed, utility, and + * quality of bit-spreading. Because many common sets of hashes + * are already reasonably distributed across bits (so don't benefit + * from spreading), and because we use trees to handle large sets + * of collisions in bins, we don't need excessively high quality. + */ + private static final int spread(int h) { + h ^= (h >>> 18) ^ (h >>> 12); + return (h ^ (h >>> 10)) & HASH_BITS; + } + + /** + * Replaces a list bin with a tree bin. Call only when locked. + * Fails to replace if the given key is non-comparable or table + * is, or needs, resizing. + */ + private final void replaceWithTreeBin(AtomicReferenceArray tab, int index, Object key) { + if ((key instanceof Comparable) && + (tab.length() >= MAXIMUM_CAPACITY || counter.sum() < (long)sizeCtl)) { + TreeBin t = new TreeBin(); + for (Node e = tabAt(tab, index); e != null; e = e.next) + t.putTreeNode(e.hash & HASH_BITS, e.key, e.val); + setTabAt(tab, index, new Node(MOVED, t, null, null)); + } + } + + /* ---------------- Internal access and update methods -------------- */ + + /** Implementation for get and containsKey */ + private final Object internalGet(Object k) { + int h = spread(k.hashCode()); + retry: for (AtomicReferenceArray tab = table; tab != null;) { + Node e, p; Object ek, ev; int eh; // locals to read fields once + for (e = tabAt(tab, (tab.length() - 1) & h); e != null; e = e.next) { + if ((eh = e.hash) == MOVED) { + if ((ek = e.key) instanceof TreeBin) // search TreeBin + return ((TreeBin)ek).getValue(h, k); + else { // restart with new table + tab = (AtomicReferenceArray)ek; + continue retry; + } + } + else if ((eh & HASH_BITS) == h && (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) + return ev; + } + break; + } + return null; + } + + /** + * Implementation for the four public remove/replace methods: + * Replaces node value with v, conditional upon match of cv if + * non-null. If resulting value is null, delete. + */ + private final Object internalReplace(Object k, Object v, Object cv) { + int h = spread(k.hashCode()); + Object oldVal = null; + for (AtomicReferenceArray tab = table;;) { + Node f; int i, fh; Object fk; + if (tab == null || + (f = tabAt(tab, i = (tab.length() - 1) & h)) == null) + break; + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + boolean validated = false; + boolean deleted = false; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + validated = true; + TreeNode p = t.getTreeNode(h, k, t.root); + if (p != null) { + Object pv = p.val; + if (cv == null || cv == pv || cv.equals(pv)) { + oldVal = pv; + if ((p.val = v) == null) { + deleted = true; + t.deleteTreeNode(p); + } + } + } + } + } finally { + t.release(0); + } + if (validated) { + if (deleted) + counter.add(-1L); + break; + } + } + else + tab = (AtomicReferenceArray)fk; + } + else if ((fh & HASH_BITS) != h && f.next == null) // precheck + break; // rules out possible existence + else if ((fh & LOCKED) != 0) { + checkForResize(); // try resizing if can't get lock + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + boolean validated = false; + boolean deleted = false; + try { + if (tabAt(tab, i) == f) { + validated = true; + for (Node e = f, pred = null;;) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + ((ev = e.val) != null) && + ((ek = e.key) == k || k.equals(ek))) { + if (cv == null || cv == ev || cv.equals(ev)) { + oldVal = ev; + if ((e.val = v) == null) { + deleted = true; + Node en = e.next; + if (pred != null) + pred.next = en; + else + setTabAt(tab, i, en); + } + } + break; + } + pred = e; + if ((e = e.next) == null) + break; + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (validated) { + if (deleted) + counter.add(-1L); + break; + } + } + } + return oldVal; + } + + /* + * Internal versions of the six insertion methods, each a + * little more complicated than the last. All have + * the same basic structure as the first (internalPut): + * 1. If table uninitialized, create + * 2. If bin empty, try to CAS new node + * 3. If bin stale, use new table + * 4. if bin converted to TreeBin, validate and relay to TreeBin methods + * 5. Lock and validate; if valid, scan and add or update + * + * The others interweave other checks and/or alternative actions: + * * Plain put checks for and performs resize after insertion. + * * putIfAbsent prescans for mapping without lock (and fails to add + * if present), which also makes pre-emptive resize checks worthwhile. + * * computeIfAbsent extends form used in putIfAbsent with additional + * mechanics to deal with, calls, potential exceptions and null + * returns from function call. + * * compute uses the same function-call mechanics, but without + * the prescans + * * merge acts as putIfAbsent in the absent case, but invokes the + * update function if present + * * putAll attempts to pre-allocate enough table space + * and more lazily performs count updates and checks. + * + * Someday when details settle down a bit more, it might be worth + * some factoring to reduce sprawl. + */ + + /** Implementation for put */ + private final Object internalPut(Object k, Object v) { + int h = spread(k.hashCode()); + int count = 0; + for (AtomicReferenceArray tab = table;;) { + int i; Node f; int fh; Object fk; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length() - 1) & h)) == null) { + if (casTabAt(tab, i, null, new Node(h, k, v, null))) + break; // no lock when adding to empty bin + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + Object oldVal = null; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + count = 2; + TreeNode p = t.putTreeNode(h, k, v); + if (p != null) { + oldVal = p.val; + p.val = v; + } + } + } finally { + t.release(0); + } + if (count != 0) { + if (oldVal != null) + return oldVal; + break; + } + } + else + tab = (AtomicReferenceArray)fk; + } + else if ((fh & LOCKED) != 0) { + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + Object oldVal = null; + try { // needed in case equals() throws + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + oldVal = ev; + e.val = v; + break; + } + Node last = e; + if ((e = e.next) == null) { + last.next = new Node(h, k, v, null); + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + break; + } + } + } + } finally { // unlock and signal if needed + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (oldVal != null) + return oldVal; + if (tab.length() <= 64) + count = 2; + break; + } + } + } + counter.add(1L); + if (count > 1) + checkForResize(); + return null; + } + + /** Implementation for putIfAbsent */ + private final Object internalPutIfAbsent(Object k, Object v) { + int h = spread(k.hashCode()); + int count = 0; + for (AtomicReferenceArray tab = table;;) { + int i; Node f; int fh; Object fk, fv; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length() - 1) & h)) == null) { + if (casTabAt(tab, i, null, new Node(h, k, v, null))) + break; + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + Object oldVal = null; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + count = 2; + TreeNode p = t.putTreeNode(h, k, v); + if (p != null) + oldVal = p.val; + } + } finally { + t.release(0); + } + if (count != 0) { + if (oldVal != null) + return oldVal; + break; + } + } + else + tab = (AtomicReferenceArray)fk; + } + else if ((fh & HASH_BITS) == h && (fv = f.val) != null && + ((fk = f.key) == k || k.equals(fk))) + return fv; + else { + Node g = f.next; + if (g != null) { // at least 2 nodes -- search and maybe resize + for (Node e = g;;) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) + return ev; + if ((e = e.next) == null) { + checkForResize(); + break; + } + } + } + if (((fh = f.hash) & LOCKED) != 0) { + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (tabAt(tab, i) == f && f.casHash(fh, fh | LOCKED)) { + Object oldVal = null; + try { + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + oldVal = ev; + break; + } + Node last = e; + if ((e = e.next) == null) { + last.next = new Node(h, k, v, null); + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + break; + } + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (oldVal != null) + return oldVal; + if (tab.length() <= 64) + count = 2; + break; + } + } + } + } + counter.add(1L); + if (count > 1) + checkForResize(); + return null; + } + + /** Implementation for computeIfAbsent */ + private final Object internalComputeIfAbsent(K k, + Fun mf) { + int h = spread(k.hashCode()); + Object val = null; + int count = 0; + for (AtomicReferenceArray tab = table;;) { + Node f; int i, fh; Object fk, fv; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length() - 1) & h)) == null) { + Node node = new Node(fh = h | LOCKED, k, null, null); + if (casTabAt(tab, i, null, node)) { + count = 1; + try { + if ((val = mf.apply(k)) != null) + node.val = val; + } finally { + if (val == null) + setTabAt(tab, i, null); + if (!node.casHash(fh, h)) { + node.hash = h; + synchronized (node) { node.notifyAll(); }; + } + } + } + if (count != 0) + break; + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + boolean added = false; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + count = 1; + TreeNode p = t.getTreeNode(h, k, t.root); + if (p != null) + val = p.val; + else if ((val = mf.apply(k)) != null) { + added = true; + count = 2; + t.putTreeNode(h, k, val); + } + } + } finally { + t.release(0); + } + if (count != 0) { + if (!added) + return val; + break; + } + } + else + tab = (AtomicReferenceArray)fk; + } + else if ((fh & HASH_BITS) == h && (fv = f.val) != null && + ((fk = f.key) == k || k.equals(fk))) + return fv; + else { + Node g = f.next; + if (g != null) { + for (Node e = g;;) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) + return ev; + if ((e = e.next) == null) { + checkForResize(); + break; + } + } + } + if (((fh = f.hash) & LOCKED) != 0) { + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (tabAt(tab, i) == f && f.casHash(fh, fh | LOCKED)) { + boolean added = false; + try { + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + val = ev; + break; + } + Node last = e; + if ((e = e.next) == null) { + if ((val = mf.apply(k)) != null) { + added = true; + last.next = new Node(h, k, val, null); + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + } + break; + } + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (!added) + return val; + if (tab.length() <= 64) + count = 2; + break; + } + } + } + } + if (val != null) { + counter.add(1L); + if (count > 1) + checkForResize(); + } + return val; + } + + /** Implementation for compute */ + @SuppressWarnings("unchecked") private final Object internalCompute + (K k, boolean onlyIfPresent, BiFun mf) { + int h = spread(k.hashCode()); + Object val = null; + int delta = 0; + int count = 0; + for (AtomicReferenceArray tab = table;;) { + Node f; int i, fh; Object fk; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length() - 1) & h)) == null) { + if (onlyIfPresent) + break; + Node node = new Node(fh = h | LOCKED, k, null, null); + if (casTabAt(tab, i, null, node)) { + try { + count = 1; + if ((val = mf.apply(k, null)) != null) { + node.val = val; + delta = 1; + } + } finally { + if (delta == 0) + setTabAt(tab, i, null); + if (!node.casHash(fh, h)) { + node.hash = h; + synchronized (node) { node.notifyAll(); }; + } + } + } + if (count != 0) + break; + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + count = 1; + TreeNode p = t.getTreeNode(h, k, t.root); + Object pv; + if (p == null) { + if (onlyIfPresent) + break; + pv = null; + } else + pv = p.val; + if ((val = mf.apply(k, (V)pv)) != null) { + if (p != null) + p.val = val; + else { + count = 2; + delta = 1; + t.putTreeNode(h, k, val); + } + } + else if (p != null) { + delta = -1; + t.deleteTreeNode(p); + } + } + } finally { + t.release(0); + } + if (count != 0) + break; + } + else + tab = (AtomicReferenceArray)fk; + } + else if ((fh & LOCKED) != 0) { + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + try { + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f, pred = null;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + val = mf.apply(k, (V)ev); + if (val != null) + e.val = val; + else { + delta = -1; + Node en = e.next; + if (pred != null) + pred.next = en; + else + setTabAt(tab, i, en); + } + break; + } + pred = e; + if ((e = e.next) == null) { + if (!onlyIfPresent && (val = mf.apply(k, null)) != null) { + pred.next = new Node(h, k, val, null); + delta = 1; + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + } + break; + } + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (tab.length() <= 64) + count = 2; + break; + } + } + } + if (delta != 0) { + counter.add((long)delta); + if (count > 1) + checkForResize(); + } + return val; + } + + /** Implementation for merge */ + @SuppressWarnings("unchecked") private final Object internalMerge + (K k, V v, BiFun mf) { + int h = spread(k.hashCode()); + Object val = null; + int delta = 0; + int count = 0; + for (AtomicReferenceArray tab = table;;) { + int i; Node f; int fh; Object fk, fv; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length() - 1) & h)) == null) { + if (casTabAt(tab, i, null, new Node(h, k, v, null))) { + delta = 1; + val = v; + break; + } + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + count = 1; + TreeNode p = t.getTreeNode(h, k, t.root); + val = (p == null) ? v : mf.apply((V)p.val, v); + if (val != null) { + if (p != null) + p.val = val; + else { + count = 2; + delta = 1; + t.putTreeNode(h, k, val); + } + } + else if (p != null) { + delta = -1; + t.deleteTreeNode(p); + } + } + } finally { + t.release(0); + } + if (count != 0) + break; + } + else + tab = (AtomicReferenceArray)fk; + } + else if ((fh & LOCKED) != 0) { + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + try { + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f, pred = null;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + val = mf.apply((V)ev, v); + if (val != null) + e.val = val; + else { + delta = -1; + Node en = e.next; + if (pred != null) + pred.next = en; + else + setTabAt(tab, i, en); + } + break; + } + pred = e; + if ((e = e.next) == null) { + val = v; + pred.next = new Node(h, k, val, null); + delta = 1; + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + break; + } + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (tab.length() <= 64) + count = 2; + break; + } + } + } + if (delta != 0) { + counter.add((long)delta); + if (count > 1) + checkForResize(); + } + return val; + } + + /** Implementation for putAll */ + private final void internalPutAll(Map m) { + tryPresize(m.size()); + long delta = 0L; // number of uncommitted additions + boolean npe = false; // to throw exception on exit for nulls + try { // to clean up counts on other exceptions + for (Map.Entry entry : m.entrySet()) { + Object k, v; + if (entry == null || (k = entry.getKey()) == null || + (v = entry.getValue()) == null) { + npe = true; + break; + } + int h = spread(k.hashCode()); + for (AtomicReferenceArray tab = table;;) { + int i; Node f; int fh; Object fk; + if (tab == null) + tab = initTable(); + else if ((f = tabAt(tab, i = (tab.length() - 1) & h)) == null){ + if (casTabAt(tab, i, null, new Node(h, k, v, null))) { + ++delta; + break; + } + } + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + boolean validated = false; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + validated = true; + TreeNode p = t.getTreeNode(h, k, t.root); + if (p != null) + p.val = v; + else { + t.putTreeNode(h, k, v); + ++delta; + } + } + } finally { + t.release(0); + } + if (validated) + break; + } + else + tab = (AtomicReferenceArray)fk; + } + else if ((fh & LOCKED) != 0) { + counter.add(delta); + delta = 0L; + checkForResize(); + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + int count = 0; + try { + if (tabAt(tab, i) == f) { + count = 1; + for (Node e = f;; ++count) { + Object ek, ev; + if ((e.hash & HASH_BITS) == h && + (ev = e.val) != null && + ((ek = e.key) == k || k.equals(ek))) { + e.val = v; + break; + } + Node last = e; + if ((e = e.next) == null) { + ++delta; + last.next = new Node(h, k, v, null); + if (count >= TREE_THRESHOLD) + replaceWithTreeBin(tab, i, k); + break; + } + } + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (count != 0) { + if (count > 1) { + counter.add(delta); + delta = 0L; + checkForResize(); + } + break; + } + } + } + } + } finally { + if (delta != 0) + counter.add(delta); + } + if (npe) + throw new NullPointerException(); + } + + /* ---------------- Table Initialization and Resizing -------------- */ + + /** + * Returns a power of two table size for the given desired capacity. + * See Hackers Delight, sec 3.2 + */ + private static final int tableSizeFor(int c) { + int n = c - 1; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; + } + + /** + * Initializes table, using the size recorded in sizeCtl. + */ + private final AtomicReferenceArray initTable() { + AtomicReferenceArray tab; int sc; + while ((tab = table) == null) { + if ((sc = sizeCtl) < 0) + Thread.yield(); // lost initialization race; just spin + else if (SIZE_CTRL_UPDATER.compareAndSet(this, sc, -1)) { + try { + if ((tab = table) == null) { + int n = (sc > 0) ? sc : DEFAULT_CAPACITY; + tab = table = new AtomicReferenceArray(n); + sc = n - (n >>> 2); + } + } finally { + sizeCtl = sc; + } + break; + } + } + return tab; + } + + /** + * If table is too small and not already resizing, creates next + * table and transfers bins. Rechecks occupancy after a transfer + * to see if another resize is already needed because resizings + * are lagging additions. + */ + private final void checkForResize() { + AtomicReferenceArray tab; int n, sc; + while ((tab = table) != null && + (n = tab.length()) < MAXIMUM_CAPACITY && + (sc = sizeCtl) >= 0 && counter.sum() >= (long)sc && + SIZE_CTRL_UPDATER.compareAndSet(this, sc, -1)) { + try { + if (tab == table) { + table = rebuild(tab); + sc = (n << 1) - (n >>> 1); + } + } finally { + sizeCtl = sc; + } + } + } + + /** + * Tries to presize table to accommodate the given number of elements. + * + * @param size number of elements (doesn't need to be perfectly accurate) + */ + private final void tryPresize(int size) { + int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : + tableSizeFor(size + (size >>> 1) + 1); + int sc; + while ((sc = sizeCtl) >= 0) { + AtomicReferenceArray tab = table; int n; + if (tab == null || (n = tab.length()) == 0) { + n = (sc > c) ? sc : c; + if (SIZE_CTRL_UPDATER.compareAndSet(this, sc, -1)) { + try { + if (table == tab) { + table = new AtomicReferenceArray(n); + sc = n - (n >>> 2); + } + } finally { + sizeCtl = sc; + } + } + } + else if (c <= sc || n >= MAXIMUM_CAPACITY) + break; + else if (SIZE_CTRL_UPDATER.compareAndSet(this, sc, -1)) { + try { + if (table == tab) { + table = rebuild(tab); + sc = (n << 1) - (n >>> 1); + } + } finally { + sizeCtl = sc; + } + } + } + } + + /* + * Moves and/or copies the nodes in each bin to new table. See + * above for explanation. + * + * @return the new table + */ + private static final AtomicReferenceArray rebuild(AtomicReferenceArray tab) { + int n = tab.length(); + AtomicReferenceArray nextTab = new AtomicReferenceArray(n << 1); + Node fwd = new Node(MOVED, nextTab, null, null); + int[] buffer = null; // holds bins to revisit; null until needed + Node rev = null; // reverse forwarder; null until needed + int nbuffered = 0; // the number of bins in buffer list + int bufferIndex = 0; // buffer index of current buffered bin + int bin = n - 1; // current non-buffered bin or -1 if none + + for (int i = bin;;) { // start upwards sweep + int fh; Node f; + if ((f = tabAt(tab, i)) == null) { + if (bin >= 0) { // Unbuffered; no lock needed (or available) + if (!casTabAt(tab, i, f, fwd)) + continue; + } + else { // transiently use a locked forwarding node + Node g = new Node(MOVED|LOCKED, nextTab, null, null); + if (!casTabAt(tab, i, f, g)) + continue; + setTabAt(nextTab, i, null); + setTabAt(nextTab, i + n, null); + setTabAt(tab, i, fwd); + if (!g.casHash(MOVED|LOCKED, MOVED)) { + g.hash = MOVED; + synchronized (g) { g.notifyAll(); } + } + } + } + else if ((fh = f.hash) == MOVED) { + Object fk = f.key; + if (fk instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + boolean validated = false; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + validated = true; + splitTreeBin(nextTab, i, t); + setTabAt(tab, i, fwd); + } + } finally { + t.release(0); + } + if (!validated) + continue; + } + } + else if ((fh & LOCKED) == 0 && f.casHash(fh, fh|LOCKED)) { + boolean validated = false; + try { // split to lo and hi lists; copying as needed + if (tabAt(tab, i) == f) { + validated = true; + splitBin(nextTab, i, f); + setTabAt(tab, i, fwd); + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + if (!validated) + continue; + } + else { + if (buffer == null) // initialize buffer for revisits + buffer = new int[TRANSFER_BUFFER_SIZE]; + if (bin < 0 && bufferIndex > 0) { + int j = buffer[--bufferIndex]; + buffer[bufferIndex] = i; + i = j; // swap with another bin + continue; + } + if (bin < 0 || nbuffered >= TRANSFER_BUFFER_SIZE) { + f.tryAwaitLock(tab, i); + continue; // no other options -- block + } + if (rev == null) // initialize reverse-forwarder + rev = new Node(MOVED, tab, null, null); + if (tabAt(tab, i) != f || (f.hash & LOCKED) == 0) + continue; // recheck before adding to list + buffer[nbuffered++] = i; + setTabAt(nextTab, i, rev); // install place-holders + setTabAt(nextTab, i + n, rev); + } + + if (bin > 0) + i = --bin; + else if (buffer != null && nbuffered > 0) { + bin = -1; + i = buffer[bufferIndex = --nbuffered]; + } + else + return nextTab; + } + } + + /** + * Splits a normal bin with list headed by e into lo and hi parts; + * installs in given table. + */ + private static void splitBin(AtomicReferenceArray nextTab, int i, Node e) { + int bit = nextTab.length() >>> 1; // bit to split on + int runBit = e.hash & bit; + Node lastRun = e, lo = null, hi = null; + for (Node p = e.next; p != null; p = p.next) { + int b = p.hash & bit; + if (b != runBit) { + runBit = b; + lastRun = p; + } + } + if (runBit == 0) + lo = lastRun; + else + hi = lastRun; + for (Node p = e; p != lastRun; p = p.next) { + int ph = p.hash & HASH_BITS; + Object pk = p.key, pv = p.val; + if ((ph & bit) == 0) + lo = new Node(ph, pk, pv, lo); + else + hi = new Node(ph, pk, pv, hi); + } + setTabAt(nextTab, i, lo); + setTabAt(nextTab, i + bit, hi); + } + + /** + * Splits a tree bin into lo and hi parts; installs in given table. + */ + private static void splitTreeBin(AtomicReferenceArray nextTab, int i, TreeBin t) { + int bit = nextTab.length() >>> 1; + TreeBin lt = new TreeBin(); + TreeBin ht = new TreeBin(); + int lc = 0, hc = 0; + for (Node e = t.first; e != null; e = e.next) { + int h = e.hash & HASH_BITS; + Object k = e.key, v = e.val; + if ((h & bit) == 0) { + ++lc; + lt.putTreeNode(h, k, v); + } + else { + ++hc; + ht.putTreeNode(h, k, v); + } + } + Node ln, hn; // throw away trees if too small + if (lc <= (TREE_THRESHOLD >>> 1)) { + ln = null; + for (Node p = lt.first; p != null; p = p.next) + ln = new Node(p.hash, p.key, p.val, ln); + } + else + ln = new Node(MOVED, lt, null, null); + setTabAt(nextTab, i, ln); + if (hc <= (TREE_THRESHOLD >>> 1)) { + hn = null; + for (Node p = ht.first; p != null; p = p.next) + hn = new Node(p.hash, p.key, p.val, hn); + } + else + hn = new Node(MOVED, ht, null, null); + setTabAt(nextTab, i + bit, hn); + } + + /** + * Implementation for clear. Steps through each bin, removing all + * nodes. + */ + private final void internalClear() { + long delta = 0L; // negative number of deletions + int i = 0; + AtomicReferenceArray tab = table; + while (tab != null && i < tab.length()) { + int fh; Object fk; + Node f = tabAt(tab, i); + if (f == null) + ++i; + else if ((fh = f.hash) == MOVED) { + if ((fk = f.key) instanceof TreeBin) { + TreeBin t = (TreeBin)fk; + t.acquire(0); + try { + if (tabAt(tab, i) == f) { + for (Node p = t.first; p != null; p = p.next) { + if (p.val != null) { // (currently always true) + p.val = null; + --delta; + } + } + t.first = null; + t.root = null; + ++i; + } + } finally { + t.release(0); + } + } + else + tab = (AtomicReferenceArray)fk; + } + else if ((fh & LOCKED) != 0) { + counter.add(delta); // opportunistically update count + delta = 0L; + f.tryAwaitLock(tab, i); + } + else if (f.casHash(fh, fh | LOCKED)) { + try { + if (tabAt(tab, i) == f) { + for (Node e = f; e != null; e = e.next) { + if (e.val != null) { // (currently always true) + e.val = null; + --delta; + } + } + setTabAt(tab, i, null); + ++i; + } + } finally { + if (!f.casHash(fh | LOCKED, fh)) { + f.hash = fh; + synchronized (f) { f.notifyAll(); }; + } + } + } + } + if (delta != 0) + counter.add(delta); + } + + /* ----------------Table Traversal -------------- */ + + /** + * Encapsulates traversal for methods such as containsValue; also + * serves as a base class for other iterators and bulk tasks. + * + * At each step, the iterator snapshots the key ("nextKey") and + * value ("nextVal") of a valid node (i.e., one that, at point of + * snapshot, has a non-null user value). Because val fields can + * change (including to null, indicating deletion), field nextVal + * might not be accurate at point of use, but still maintains the + * weak consistency property of holding a value that was once + * valid. To support iterator.remove, the nextKey field is not + * updated (nulled out) when the iterator cannot advance. + * + * Internal traversals directly access these fields, as in: + * {@code while (it.advance() != null) { process(it.nextKey); }} + * + * Exported iterators must track whether the iterator has advanced + * (in hasNext vs next) (by setting/checking/nulling field + * nextVal), and then extract key, value, or key-value pairs as + * return values of next(). + * + * The iterator visits once each still-valid node that was + * reachable upon iterator construction. It might miss some that + * were added to a bin after the bin was visited, which is OK wrt + * consistency guarantees. Maintaining this property in the face + * of possible ongoing resizes requires a fair amount of + * bookkeeping state that is difficult to optimize away amidst + * volatile accesses. Even so, traversal maintains reasonable + * throughput. + * + * Normally, iteration proceeds bin-by-bin traversing lists. + * However, if the table has been resized, then all future steps + * must traverse both the bin at the current index as well as at + * (index + baseSize); and so on for further resizings. To + * paranoically cope with potential sharing by users of iterators + * across threads, iteration terminates if a bounds checks fails + * for a table read. + * + * This class extends ForkJoinTask to streamline parallel + * iteration in bulk operations (see BulkTask). This adds only an + * int of space overhead, which is close enough to negligible in + * cases where it is not needed to not worry about it. Because + * ForkJoinTask is Serializable, but iterators need not be, we + * need to add warning suppressions. + */ + @SuppressWarnings("serial") static class Traverser { + final ConcurrentHashMapV8 map; + Node next; // the next entry to use + K nextKey; // cached key field of next + V nextVal; // cached val field of next + AtomicReferenceArray tab; // current table; updated if resized + int index; // index of bin to use next + int baseIndex; // current index of initial table + int baseLimit; // index bound for initial table + int baseSize; // initial table size + + /** Creates iterator for all entries in the table. */ + Traverser(ConcurrentHashMapV8 map) { + this.map = map; + } + + /** Creates iterator for split() methods */ + Traverser(Traverser it) { + ConcurrentHashMapV8 m; AtomicReferenceArray t; + if ((m = this.map = it.map) == null) + t = null; + else if ((t = it.tab) == null && // force parent tab initialization + (t = it.tab = m.table) != null) + it.baseLimit = it.baseSize = t.length(); + this.tab = t; + this.baseSize = it.baseSize; + it.baseLimit = this.index = this.baseIndex = + ((this.baseLimit = it.baseLimit) + it.baseIndex + 1) >>> 1; + } + + /** + * Advances next; returns nextVal or null if terminated. + * See above for explanation. + */ + final V advance() { + Node e = next; + V ev = null; + outer: do { + if (e != null) // advance past used/skipped node + e = e.next; + while (e == null) { // get to next non-null bin + ConcurrentHashMapV8 m; + AtomicReferenceArray t; int b, i, n; Object ek; // checks must use locals + if ((t = tab) != null) + n = t.length(); + else if ((m = map) != null && (t = tab = m.table) != null) + n = baseLimit = baseSize = t.length(); + else + break outer; + if ((b = baseIndex) >= baseLimit || + (i = index) < 0 || i >= n) + break outer; + if ((e = tabAt(t, i)) != null && e.hash == MOVED) { + if ((ek = e.key) instanceof TreeBin) + e = ((TreeBin)ek).first; + else { + tab = (AtomicReferenceArray)ek; + continue; // restarts due to null val + } + } // visit upper slots if present + index = (i += baseSize) < n ? i : (baseIndex = b + 1); + } + nextKey = (K) e.key; + } while ((ev = (V) e.val) == null); // skip deleted or special nodes + next = e; + return nextVal = ev; + } + + public final void remove() { + Object k = nextKey; + if (k == null && (advance() == null || (k = nextKey) == null)) + throw new IllegalStateException(); + map.internalReplace(k, null, null); + } + + public final boolean hasNext() { + return nextVal != null || advance() != null; + } + + public final boolean hasMoreElements() { return hasNext(); } + public final void setRawResult(Object x) { } + public R getRawResult() { return null; } + public boolean exec() { return true; } + } + + /* ---------------- Public operations -------------- */ + + /** + * Creates a new, empty map with the default initial table size (16). + */ + public ConcurrentHashMapV8() { + this.counter = new LongAdder(); + } + + /** + * Creates a new, empty map with an initial table size + * accommodating the specified number of elements without the need + * to dynamically resize. + * + * @param initialCapacity The implementation performs internal + * sizing to accommodate this many elements. + * @throws IllegalArgumentException if the initial capacity of + * elements is negative + */ + public ConcurrentHashMapV8(int initialCapacity) { + if (initialCapacity < 0) + throw new IllegalArgumentException(); + int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? + MAXIMUM_CAPACITY : + tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1)); + this.counter = new LongAdder(); + this.sizeCtl = cap; + } + + /** + * Creates a new map with the same mappings as the given map. + * + * @param m the map + */ + public ConcurrentHashMapV8(Map m) { + this.counter = new LongAdder(); + this.sizeCtl = DEFAULT_CAPACITY; + internalPutAll(m); + } + + /** + * Creates a new, empty map with an initial table size based on + * the given number of elements ({@code initialCapacity}) and + * initial table density ({@code loadFactor}). + * + * @param initialCapacity the initial capacity. The implementation + * performs internal sizing to accommodate this many elements, + * given the specified load factor. + * @param loadFactor the load factor (table density) for + * establishing the initial table size + * @throws IllegalArgumentException if the initial capacity of + * elements is negative or the load factor is nonpositive + * + * @since 1.6 + */ + public ConcurrentHashMapV8(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, 1); + } + + /** + * Creates a new, empty map with an initial table size based on + * the given number of elements ({@code initialCapacity}), table + * density ({@code loadFactor}), and number of concurrently + * updating threads ({@code concurrencyLevel}). + * + * @param initialCapacity the initial capacity. The implementation + * performs internal sizing to accommodate this many elements, + * given the specified load factor. + * @param loadFactor the load factor (table density) for + * establishing the initial table size + * @param concurrencyLevel the estimated number of concurrently + * updating threads. The implementation may use this value as + * a sizing hint. + * @throws IllegalArgumentException if the initial capacity is + * negative or the load factor or concurrencyLevel are + * nonpositive + */ + public ConcurrentHashMapV8(int initialCapacity, + float loadFactor, int concurrencyLevel) { + if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0) + throw new IllegalArgumentException(); + if (initialCapacity < concurrencyLevel) // Use at least as many bins + initialCapacity = concurrencyLevel; // as estimated threads + long size = (long)(1.0 + (long)initialCapacity / loadFactor); + int cap = (size >= (long)MAXIMUM_CAPACITY) ? + MAXIMUM_CAPACITY : tableSizeFor((int)size); + this.counter = new LongAdder(); + this.sizeCtl = cap; + } + + /** + * Creates a new {@link Set} backed by a ConcurrentHashMapV8 + * from the given type to {@code Boolean.TRUE}. + * + * @return the new set + */ + public static KeySetView newKeySet() { + return new KeySetView(new ConcurrentHashMapV8(), + Boolean.TRUE); + } + + /** + * Creates a new {@link Set} backed by a ConcurrentHashMapV8 + * from the given type to {@code Boolean.TRUE}. + * + * @param initialCapacity The implementation performs internal + * sizing to accommodate this many elements. + * @throws IllegalArgumentException if the initial capacity of + * elements is negative + * @return the new set + */ + public static KeySetView newKeySet(int initialCapacity) { + return new KeySetView(new ConcurrentHashMapV8(initialCapacity), + Boolean.TRUE); + } + + /** + * {@inheritDoc} + */ + public boolean isEmpty() { + return counter.sum() <= 0L; // ignore transient negative values + } + + /** + * {@inheritDoc} + */ + public int size() { + long n = counter.sum(); + return ((n < 0L) ? 0 : + (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE : + (int)n); + } + + /** + * Returns the number of mappings. This method should be used + * instead of {@link #size} because a ConcurrentHashMapV8 may + * contain more mappings than can be represented as an int. The + * value returned is a snapshot; the actual count may differ if + * there are ongoing concurrent insertions or removals. + * + * @return the number of mappings + */ + public long mappingCount() { + long n = counter.sum(); + return (n < 0L) ? 0L : n; // ignore transient negative values + } + + /** + * Returns the value to which the specified key is mapped, + * or {@code null} if this map contains no mapping for the key. + * + *

    More formally, if this map contains a mapping from a key + * {@code k} to a value {@code v} such that {@code key.equals(k)}, + * then this method returns {@code v}; otherwise it returns + * {@code null}. (There can be at most one such mapping.) + * + * @throws NullPointerException if the specified key is null + */ + @SuppressWarnings("unchecked") public V get(Object key) { + if (key == null) + throw new NullPointerException(); + return (V)internalGet(key); + } + + /** + * Returns the value to which the specified key is mapped, + * or the given defaultValue if this map contains no mapping for the key. + * + * @param key the key + * @param defaultValue the value to return if this map contains + * no mapping for the given key + * @return the mapping for the key, if present; else the defaultValue + * @throws NullPointerException if the specified key is null + */ + @SuppressWarnings("unchecked") public V getValueOrDefault(Object key, V defaultValue) { + if (key == null) + throw new NullPointerException(); + V v = (V) internalGet(key); + return v == null ? defaultValue : v; + } + + /** + * Tests if the specified object is a key in this table. + * + * @param key possible key + * @return {@code true} if and only if the specified object + * is a key in this table, as determined by the + * {@code equals} method; {@code false} otherwise + * @throws NullPointerException if the specified key is null + */ + public boolean containsKey(Object key) { + if (key == null) + throw new NullPointerException(); + return internalGet(key) != null; + } + + /** + * Returns {@code true} if this map maps one or more keys to the + * specified value. Note: This method may require a full traversal + * of the map, and is much slower than method {@code containsKey}. + * + * @param value value whose presence in this map is to be tested + * @return {@code true} if this map maps one or more keys to the + * specified value + * @throws NullPointerException if the specified value is null + */ + public boolean containsValue(Object value) { + if (value == null) + throw new NullPointerException(); + Object v; + Traverser it = new Traverser(this); + while ((v = it.advance()) != null) { + if (v == value || value.equals(v)) + return true; + } + return false; + } + + public K findKey(Object value) { + if (value == null) + throw new NullPointerException(); + Object v; + Traverser it = new Traverser(this); + while ((v = it.advance()) != null) { + if (v == value || value.equals(v)) + return it.nextKey; + } + return null; + } + + /** + * Legacy method testing if some key maps into the specified value + * in this table. This method is identical in functionality to + * {@link #containsValue}, and exists solely to ensure + * full compatibility with class {@link java.util.Hashtable}, + * which supported this method prior to introduction of the + * Java Collections framework. + * + * @param value a value to search for + * @return {@code true} if and only if some key maps to the + * {@code value} argument in this table as + * determined by the {@code equals} method; + * {@code false} otherwise + * @throws NullPointerException if the specified value is null + */ + public boolean contains(Object value) { + return containsValue(value); + } + + /** + * Maps the specified key to the specified value in this table. + * Neither the key nor the value can be null. + * + *

    The value can be retrieved by calling the {@code get} method + * with a key that is equal to the original key. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key} + * @throws NullPointerException if the specified key or value is null + */ + @SuppressWarnings("unchecked") public V put(K key, V value) { + if (key == null || value == null) + throw new NullPointerException(); + return (V)internalPut(key, value); + } + + /** + * {@inheritDoc} + * + * @return the previous value associated with the specified key, + * or {@code null} if there was no mapping for the key + * @throws NullPointerException if the specified key or value is null + */ + @SuppressWarnings("unchecked") public V putIfAbsent(K key, V value) { + if (key == null || value == null) + throw new NullPointerException(); + return (V)internalPutIfAbsent(key, value); + } + + /** + * Copies all of the mappings from the specified map to this one. + * These mappings replace any mappings that this map had for any of the + * keys currently in the specified map. + * + * @param m mappings to be stored in this map + */ + public void putAll(Map m) { + internalPutAll(m); + } + + /** + * If the specified key is not already associated with a value, + * computes its value using the given mappingFunction and enters + * it into the map unless null. This is equivalent to + *

     {@code
    +     * if (map.containsKey(key))
    +     *   return map.get(key);
    +     * value = mappingFunction.apply(key);
    +     * if (value != null)
    +     *   map.put(key, value);
    +     * return value;}
    + * + * except that the action is performed atomically. If the + * function returns {@code null} no mapping is recorded. If the + * function itself throws an (unchecked) exception, the exception + * is rethrown to its caller, and no mapping is recorded. Some + * attempted update operations on this map by other threads may be + * blocked while computation is in progress, so the computation + * should be short and simple, and must not attempt to update any + * other mappings of this Map. The most appropriate usage is to + * construct a new object serving as an initial mapped value, or + * memoized result, as in: + * + *
     {@code
    +     * map.computeIfAbsent(key, new Fun() {
    +     *   public V map(K k) { return new Value(f(k)); }});}
    + * + * @param key key with which the specified value is to be associated + * @param mappingFunction the function to compute a value + * @return the current (existing or computed) value associated with + * the specified key, or null if the computed value is null + * @throws NullPointerException if the specified key or mappingFunction + * is null + * @throws IllegalStateException if the computation detectably + * attempts a recursive update to this map that would + * otherwise never complete + * @throws RuntimeException or Error if the mappingFunction does so, + * in which case the mapping is left unestablished + */ + @SuppressWarnings("unchecked") public V computeIfAbsent + (K key, Fun mappingFunction) { + if (key == null || mappingFunction == null) + throw new NullPointerException(); + return (V)internalComputeIfAbsent(key, mappingFunction); + } + + /** + * If the given key is present, computes a new mapping value given a key and + * its current mapped value. This is equivalent to + *
     {@code
    +     *   if (map.containsKey(key)) {
    +     *     value = remappingFunction.apply(key, map.get(key));
    +     *     if (value != null)
    +     *       map.put(key, value);
    +     *     else
    +     *       map.remove(key);
    +     *   }
    +     * }
    + * + * except that the action is performed atomically. If the + * function returns {@code null}, the mapping is removed. If the + * function itself throws an (unchecked) exception, the exception + * is rethrown to its caller, and the current mapping is left + * unchanged. Some attempted update operations on this map by + * other threads may be blocked while computation is in progress, + * so the computation should be short and simple, and must not + * attempt to update any other mappings of this Map. For example, + * to either create or append new messages to a value mapping: + * + * @param key key with which the specified value is to be associated + * @param remappingFunction the function to compute a value + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key or remappingFunction + * is null + * @throws IllegalStateException if the computation detectably + * attempts a recursive update to this map that would + * otherwise never complete + * @throws RuntimeException or Error if the remappingFunction does so, + * in which case the mapping is unchanged + */ + @SuppressWarnings("unchecked") public V computeIfPresent + (K key, BiFun remappingFunction) { + if (key == null || remappingFunction == null) + throw new NullPointerException(); + return (V)internalCompute(key, true, remappingFunction); + } + + /** + * Computes a new mapping value given a key and + * its current mapped value (or {@code null} if there is no current + * mapping). This is equivalent to + *
     {@code
    +     *   value = remappingFunction.apply(key, map.get(key));
    +     *   if (value != null)
    +     *     map.put(key, value);
    +     *   else
    +     *     map.remove(key);
    +     * }
    + * + * except that the action is performed atomically. If the + * function returns {@code null}, the mapping is removed. If the + * function itself throws an (unchecked) exception, the exception + * is rethrown to its caller, and the current mapping is left + * unchanged. Some attempted update operations on this map by + * other threads may be blocked while computation is in progress, + * so the computation should be short and simple, and must not + * attempt to update any other mappings of this Map. For example, + * to either create or append new messages to a value mapping: + * + *
     {@code
    +     * Map map = ...;
    +     * final String msg = ...;
    +     * map.compute(key, new BiFun() {
    +     *   public String apply(Key k, String v) {
    +     *    return (v == null) ? msg : v + msg;});}}
    + * + * @param key key with which the specified value is to be associated + * @param remappingFunction the function to compute a value + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key or remappingFunction + * is null + * @throws IllegalStateException if the computation detectably + * attempts a recursive update to this map that would + * otherwise never complete + * @throws RuntimeException or Error if the remappingFunction does so, + * in which case the mapping is unchanged + */ + @SuppressWarnings("unchecked") public V compute + (K key, BiFun remappingFunction) { + if (key == null || remappingFunction == null) + throw new NullPointerException(); + return (V)internalCompute(key, false, remappingFunction); + } + + /** + * If the specified key is not already associated + * with a value, associate it with the given value. + * Otherwise, replace the value with the results of + * the given remapping function. This is equivalent to: + *
     {@code
    +     *   if (!map.containsKey(key))
    +     *     map.put(value);
    +     *   else {
    +     *     newValue = remappingFunction.apply(map.get(key), value);
    +     *     if (value != null)
    +     *       map.put(key, value);
    +     *     else
    +     *       map.remove(key);
    +     *   }
    +     * }
    + * except that the action is performed atomically. If the + * function returns {@code null}, the mapping is removed. If the + * function itself throws an (unchecked) exception, the exception + * is rethrown to its caller, and the current mapping is left + * unchanged. Some attempted update operations on this map by + * other threads may be blocked while computation is in progress, + * so the computation should be short and simple, and must not + * attempt to update any other mappings of this Map. + */ + @SuppressWarnings("unchecked") public V merge + (K key, V value, BiFun remappingFunction) { + if (key == null || value == null || remappingFunction == null) + throw new NullPointerException(); + return (V)internalMerge(key, value, remappingFunction); + } + + /** + * Removes the key (and its corresponding value) from this map. + * This method does nothing if the key is not in the map. + * + * @param key the key that needs to be removed + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key} + * @throws NullPointerException if the specified key is null + */ + @SuppressWarnings("unchecked") public V remove(Object key) { + if (key == null) + throw new NullPointerException(); + return (V)internalReplace(key, null, null); + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException if the specified key is null + */ + public boolean remove(Object key, Object value) { + if (key == null) + throw new NullPointerException(); + if (value == null) + return false; + return internalReplace(key, null, value) != null; + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException if any of the arguments are null + */ + public boolean replace(K key, V oldValue, V newValue) { + if (key == null || oldValue == null || newValue == null) + throw new NullPointerException(); + return internalReplace(key, newValue, oldValue) != null; + } + + /** + * {@inheritDoc} + * + * @return the previous value associated with the specified key, + * or {@code null} if there was no mapping for the key + * @throws NullPointerException if the specified key or value is null + */ + @SuppressWarnings("unchecked") public V replace(K key, V value) { + if (key == null || value == null) + throw new NullPointerException(); + return (V)internalReplace(key, value, null); + } + + /** + * Removes all of the mappings from this map. + */ + public void clear() { + internalClear(); + } + + /** + * Returns a {@link Set} view of the keys contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. + * + * @return the set view + */ + public KeySetView keySet() { + KeySetView ks = keySet; + return (ks != null) ? ks : (keySet = new KeySetView(this, null)); + } + + /** + * Returns a {@link Set} view of the keys in this map, using the + * given common mapped value for any additions (i.e., {@link + * Collection#add} and {@link Collection#addAll}). This is of + * course only appropriate if it is acceptable to use the same + * value for all additions from this view. + * + * @param mappedValue the mapped value to use for any + * additions. + * @return the set view + * @throws NullPointerException if the mappedValue is null + */ + public KeySetView keySet(V mappedValue) { + if (mappedValue == null) + throw new NullPointerException(); + return new KeySetView(this, mappedValue); + } + + /** + * Returns a {@link Collection} view of the values contained in this map. + * The collection is backed by the map, so changes to the map are + * reflected in the collection, and vice-versa. + */ + public ValuesView values() { + ValuesView vs = values; + return (vs != null) ? vs : (values = new ValuesView(this)); + } + + /** + * Returns a {@link Set} view of the mappings contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. The set supports element + * removal, which removes the corresponding mapping from the map, + * via the {@code Iterator.remove}, {@code Set.remove}, + * {@code removeAll}, {@code retainAll}, and {@code clear} + * operations. It does not support the {@code add} or + * {@code addAll} operations. + * + *

    The view's {@code iterator} is a "weakly consistent" iterator + * that will never throw {@link ConcurrentModificationException}, + * and guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not guaranteed to) + * reflect any modifications subsequent to construction. + */ + public Set> entrySet() { + EntrySetView es = entrySet; + return (es != null) ? es : (entrySet = new EntrySetView(this)); + } + + /** + * Returns an enumeration of the keys in this table. + * + * @return an enumeration of the keys in this table + * @see #keySet() + */ + public Enumeration keys() { + return new KeyIterator(this); + } + + /** + * Returns an enumeration of the values in this table. + * + * @return an enumeration of the values in this table + * @see #values() + */ + public Enumeration elements() { + return new ValueIterator(this); + } + + /** + * Returns a partitionable iterator of the keys in this map. + * + * @return a partitionable iterator of the keys in this map + */ + public Spliterator keySpliterator() { + return new KeyIterator(this); + } + + /** + * Returns a partitionable iterator of the values in this map. + * + * @return a partitionable iterator of the values in this map + */ + public Spliterator valueSpliterator() { + return new ValueIterator(this); + } + + /** + * Returns a partitionable iterator of the entries in this map. + * + * @return a partitionable iterator of the entries in this map + */ + public Spliterator> entrySpliterator() { + return new EntryIterator(this); + } + + /** + * Returns the hash code value for this {@link Map}, i.e., + * the sum of, for each key-value pair in the map, + * {@code key.hashCode() ^ value.hashCode()}. + * + * @return the hash code value for this map + */ + public int hashCode() { + int h = 0; + Traverser it = new Traverser(this); + Object v; + while ((v = it.advance()) != null) { + h += it.nextKey.hashCode() ^ v.hashCode(); + } + return h; + } + + /** + * Returns a string representation of this map. The string + * representation consists of a list of key-value mappings (in no + * particular order) enclosed in braces ("{@code {}}"). Adjacent + * mappings are separated by the characters {@code ", "} (comma + * and space). Each key-value mapping is rendered as the key + * followed by an equals sign ("{@code =}") followed by the + * associated value. + * + * @return a string representation of this map + */ + public String toString() { + Traverser it = new Traverser(this); + StringBuilder sb = new StringBuilder(); + sb.append('{'); + Object v; + if ((v = it.advance()) != null) { + for (;;) { + Object k = it.nextKey; + sb.append(k == this ? "(this Map)" : k); + sb.append('='); + sb.append(v == this ? "(this Map)" : v); + if ((v = it.advance()) == null) + break; + sb.append(',').append(' '); + } + } + return sb.append('}').toString(); + } + + /** + * Compares the specified object with this map for equality. + * Returns {@code true} if the given object is a map with the same + * mappings as this map. This operation may return misleading + * results if either map is concurrently modified during execution + * of this method. + * + * @param o object to be compared for equality with this map + * @return {@code true} if the specified object is equal to this map + */ + public boolean equals(Object o) { + if (o != this) { + if (!(o instanceof Map)) + return false; + Map m = (Map) o; + Traverser it = new Traverser(this); + Object val; + while ((val = it.advance()) != null) { + Object v = m.get(it.nextKey); + if (v == null || (v != val && !v.equals(val))) + return false; + } + for (Map.Entry e : m.entrySet()) { + Object mk, mv, v; + if ((mk = e.getKey()) == null || + (mv = e.getValue()) == null || + (v = internalGet(mk)) == null || + (mv != v && !mv.equals(v))) + return false; + } + } + return true; + } + + /* ----------------Iterators -------------- */ + + @SuppressWarnings("serial") static final class KeyIterator extends Traverser + implements Spliterator, Enumeration { + KeyIterator(ConcurrentHashMapV8 map) { super(map); } + KeyIterator(Traverser it) { + super(it); + } + public KeyIterator split() { + if (nextKey != null) + throw new IllegalStateException(); + return new KeyIterator(this); + } + @SuppressWarnings("unchecked") public final K next() { + if (nextVal == null && advance() == null) + throw new NoSuchElementException(); + Object k = nextKey; + nextVal = null; + return (K) k; + } + + public final K nextElement() { return next(); } + } + + @SuppressWarnings("serial") static final class ValueIterator extends Traverser + implements Spliterator, Enumeration { + ValueIterator(ConcurrentHashMapV8 map) { super(map); } + ValueIterator(Traverser it) { + super(it); + } + public ValueIterator split() { + if (nextKey != null) + throw new IllegalStateException(); + return new ValueIterator(this); + } + + @SuppressWarnings("unchecked") public final V next() { + Object v; + if ((v = nextVal) == null && (v = advance()) == null) + throw new NoSuchElementException(); + nextVal = null; + return (V) v; + } + + public final V nextElement() { return next(); } + } + + @SuppressWarnings("serial") static final class EntryIterator extends Traverser + implements Spliterator> { + EntryIterator(ConcurrentHashMapV8 map) { super(map); } + EntryIterator(Traverser it) { + super(it); + } + public EntryIterator split() { + if (nextKey != null) + throw new IllegalStateException(); + return new EntryIterator(this); + } + + @SuppressWarnings("unchecked") public final Map.Entry next() { + Object v; + if ((v = nextVal) == null && (v = advance()) == null) + throw new NoSuchElementException(); + Object k = nextKey; + nextVal = null; + return new MapEntry((K)k, (V)v, map); + } + } + + /** + * Exported Entry for iterators + */ + static final class MapEntry implements Map.Entry { + final K key; // non-null + V val; // non-null + final ConcurrentHashMapV8 map; + MapEntry(K key, V val, ConcurrentHashMapV8 map) { + this.key = key; + this.val = val; + this.map = map; + } + public final K getKey() { return key; } + public final V getValue() { return val; } + public final int hashCode() { return key.hashCode() ^ val.hashCode(); } + public final String toString(){ return key + "=" + val; } + + public final boolean equals(Object o) { + Object k, v; Map.Entry e; + return ((o instanceof Map.Entry) && + (k = (e = (Map.Entry)o).getKey()) != null && + (v = e.getValue()) != null && + (k == key || k.equals(key)) && + (v == val || v.equals(val))); + } + + /** + * Sets our entry's value and writes through to the map. The + * value to return is somewhat arbitrary here. Since we do not + * necessarily track asynchronous changes, the most recent + * "previous" value could be different from what we return (or + * could even have been removed in which case the put will + * re-establish). We do not and cannot guarantee more. + */ + public final V setValue(V value) { + if (value == null) throw new NullPointerException(); + V v = val; + val = value; + map.put(key, value); + return v; + } + } + + /* ---------------- Serialization Support -------------- */ + + /** + * Stripped-down version of helper class used in previous version, + * declared for the sake of serialization compatibility + */ + static class Segment implements Serializable { + private static final long serialVersionUID = 2249069246763182397L; + final float loadFactor; + Segment(float lf) { this.loadFactor = lf; } + } + + /** + * Saves the state of the {@code ConcurrentHashMapV8} instance to a + * stream (i.e., serializes it). + * @param s the stream + * @serialData + * the key (Object) and value (Object) + * for each key-value mapping, followed by a null pair. + * The key-value mappings are emitted in no particular order. + */ + @SuppressWarnings("unchecked") private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + if (segments == null) { // for serialization compatibility + segments = (Segment[]) + new Segment[DEFAULT_CONCURRENCY_LEVEL]; + for (int i = 0; i < segments.length; ++i) + segments[i] = new Segment(LOAD_FACTOR); + } + s.defaultWriteObject(); + Traverser it = new Traverser(this); + Object v; + while ((v = it.advance()) != null) { + s.writeObject(it.nextKey); + s.writeObject(v); + } + s.writeObject(null); + s.writeObject(null); + segments = null; // throw away + } + + /** + * Reconstitutes the instance from a stream (that is, deserializes it). + * @param s the stream + */ + @SuppressWarnings("unchecked") private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); + this.segments = null; // unneeded + // initialize transient final field + this.counter = new LongAdder(); + + // Create all nodes, then place in table once size is known + long size = 0L; + Node p = null; + for (;;) { + K k = (K) s.readObject(); + V v = (V) s.readObject(); + if (k != null && v != null) { + int h = spread(k.hashCode()); + p = new Node(h, k, v, p); + ++size; + } + else + break; + } + if (p != null) { + boolean init = false; + int n; + if (size >= (long)(MAXIMUM_CAPACITY >>> 1)) + n = MAXIMUM_CAPACITY; + else { + int sz = (int)size; + n = tableSizeFor(sz + (sz >>> 1) + 1); + } + int sc = sizeCtl; + boolean collide = false; + if (n > sc && + SIZE_CTRL_UPDATER.compareAndSet(this, sc, -1)) { + try { + if (table == null) { + init = true; + AtomicReferenceArray tab = new AtomicReferenceArray(n); + int mask = n - 1; + while (p != null) { + int j = p.hash & mask; + Node next = p.next; + Node q = p.next = tabAt(tab, j); + setTabAt(tab, j, p); + if (!collide && q != null && q.hash == p.hash) + collide = true; + p = next; + } + table = tab; + counter.add(size); + sc = n - (n >>> 2); + } + } finally { + sizeCtl = sc; + } + if (collide) { // rescan and convert to TreeBins + AtomicReferenceArray tab = table; + for (int i = 0; i < tab.length(); ++i) { + int c = 0; + for (Node e = tabAt(tab, i); e != null; e = e.next) { + if (++c > TREE_THRESHOLD && + (e.key instanceof Comparable)) { + replaceWithTreeBin(tab, i, e.key); + break; + } + } + } + } + } + if (!init) { // Can only happen if unsafely published. + while (p != null) { + internalPut(p.key, p.val); + p = p.next; + } + } + } + } + + + // ------------------------------------------------------- + + // Sams + /** Interface describing a void action of one argument */ + public interface Action { void apply(A a); } + /** Interface describing a void action of two arguments */ + public interface BiAction { void apply(A a, B b); } + /** Interface describing a function of one argument */ + public interface Generator { T apply(); } + /** Interface describing a function mapping its argument to a double */ + public interface ObjectToDouble { double apply(A a); } + /** Interface describing a function mapping its argument to a long */ + public interface ObjectToLong { long apply(A a); } + /** Interface describing a function mapping its argument to an int */ + public interface ObjectToInt {int apply(A a); } + /** Interface describing a function mapping two arguments to a double */ + public interface ObjectByObjectToDouble { double apply(A a, B b); } + /** Interface describing a function mapping two arguments to a long */ + public interface ObjectByObjectToLong { long apply(A a, B b); } + /** Interface describing a function mapping two arguments to an int */ + public interface ObjectByObjectToInt {int apply(A a, B b); } + /** Interface describing a function mapping a double to a double */ + public interface DoubleToDouble { double apply(double a); } + /** Interface describing a function mapping a long to a long */ + public interface LongToLong { long apply(long a); } + /** Interface describing a function mapping an int to an int */ + public interface IntToInt { int apply(int a); } + /** Interface describing a function mapping two doubles to a double */ + public interface DoubleByDoubleToDouble { double apply(double a, double b); } + /** Interface describing a function mapping two longs to a long */ + public interface LongByLongToLong { long apply(long a, long b); } + /** Interface describing a function mapping two ints to an int */ + public interface IntByIntToInt { int apply(int a, int b); } + + + /* ----------------Views -------------- */ + + /** + * Base class for views. + */ + static abstract class CHMView { + final ConcurrentHashMapV8 map; + CHMView(ConcurrentHashMapV8 map) { this.map = map; } + + /** + * Returns the map backing this view. + * + * @return the map backing this view + */ + public ConcurrentHashMapV8 getMap() { return map; } + + public final int size() { return map.size(); } + public final boolean isEmpty() { return map.isEmpty(); } + public final void clear() { map.clear(); } + + // implementations below rely on concrete classes supplying these + abstract public Iterator iterator(); + abstract public boolean contains(Object o); + abstract public boolean remove(Object o); + + private static final String oomeMsg = "Required array size too large"; + + public final Object[] toArray() { + long sz = map.mappingCount(); + if (sz > (long)(MAX_ARRAY_SIZE)) + throw new OutOfMemoryError(oomeMsg); + int n = (int)sz; + Object[] r = new Object[n]; + int i = 0; + Iterator it = iterator(); + while (it.hasNext()) { + if (i == n) { + if (n >= MAX_ARRAY_SIZE) + throw new OutOfMemoryError(oomeMsg); + if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) + n = MAX_ARRAY_SIZE; + else + n += (n >>> 1) + 1; + r = Arrays.copyOf(r, n); + } + r[i++] = it.next(); + } + return (i == n) ? r : Arrays.copyOf(r, i); + } + + @SuppressWarnings("unchecked") public final T[] toArray(T[] a) { + long sz = map.mappingCount(); + if (sz > (long)(MAX_ARRAY_SIZE)) + throw new OutOfMemoryError(oomeMsg); + int m = (int)sz; + T[] r = (a.length >= m) ? a : + (T[])java.lang.reflect.Array + .newInstance(a.getClass().getComponentType(), m); + int n = r.length; + int i = 0; + Iterator it = iterator(); + while (it.hasNext()) { + if (i == n) { + if (n >= MAX_ARRAY_SIZE) + throw new OutOfMemoryError(oomeMsg); + if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) + n = MAX_ARRAY_SIZE; + else + n += (n >>> 1) + 1; + r = Arrays.copyOf(r, n); + } + r[i++] = (T)it.next(); + } + if (a == r && i < n) { + r[i] = null; // null-terminate + return r; + } + return (i == n) ? r : Arrays.copyOf(r, i); + } + + public final int hashCode() { + int h = 0; + for (Iterator it = iterator(); it.hasNext();) + h += it.next().hashCode(); + return h; + } + + public final String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('['); + Iterator it = iterator(); + if (it.hasNext()) { + for (;;) { + Object e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (!it.hasNext()) + break; + sb.append(',').append(' '); + } + } + return sb.append(']').toString(); + } + + public final boolean containsAll(Collection c) { + if (c != this) { + for (Iterator it = c.iterator(); it.hasNext();) { + Object e = it.next(); + if (e == null || !contains(e)) + return false; + } + } + return true; + } + + public final boolean removeAll(Collection c) { + boolean modified = false; + for (Iterator it = iterator(); it.hasNext();) { + if (c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + public final boolean retainAll(Collection c) { + boolean modified = false; + for (Iterator it = iterator(); it.hasNext();) { + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + } + + /** + * A view of a ConcurrentHashMapV8 as a {@link Set} of keys, in + * which additions may optionally be enabled by mapping to a + * common value. This class cannot be directly instantiated. See + * {@link #keySet}, {@link #keySet(Object)}, {@link #newKeySet()}, + * {@link #newKeySet(int)}. + */ + public static class KeySetView extends CHMView implements Set, java.io.Serializable { + private static final long serialVersionUID = 7249069246763182397L; + private final V value; + KeySetView(ConcurrentHashMapV8 map, V value) { // non-public + super(map); + this.value = value; + } + + /** + * Returns the default mapped value for additions, + * or {@code null} if additions are not supported. + * + * @return the default mapped value for additions, or {@code null} + * if not supported. + */ + public V getMappedValue() { return value; } + + // implement Set API + + public boolean contains(Object o) { return map.containsKey(o); } + public boolean remove(Object o) { return map.remove(o) != null; } + + /** + * Returns a "weakly consistent" iterator that will never + * throw {@link ConcurrentModificationException}, and + * guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not + * guaranteed to) reflect any modifications subsequent to + * construction. + * + * @return an iterator over the keys of this map + */ + public Iterator iterator() { return new KeyIterator(map); } + public boolean add(K e) { + V v; + if ((v = value) == null) + throw new UnsupportedOperationException(); + if (e == null) + throw new NullPointerException(); + return map.internalPutIfAbsent(e, v) == null; + } + public boolean addAll(Collection c) { + boolean added = false; + V v; + if ((v = value) == null) + throw new UnsupportedOperationException(); + for (K e : c) { + if (e == null) + throw new NullPointerException(); + if (map.internalPutIfAbsent(e, v) == null) + added = true; + } + return added; + } + public boolean equals(Object o) { + Set c; + return ((o instanceof Set) && + ((c = (Set)o) == this || + (containsAll(c) && c.containsAll(this)))); + } + } + + /** + * A view of a ConcurrentHashMapV8 as a {@link Collection} of + * values, in which additions are disabled. This class cannot be + * directly instantiated. See {@link #values}, + * + *

    The view's {@code iterator} is a "weakly consistent" iterator + * that will never throw {@link ConcurrentModificationException}, + * and guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not guaranteed to) + * reflect any modifications subsequent to construction. + */ + public static final class ValuesView extends CHMView + implements Collection { + ValuesView(ConcurrentHashMapV8 map) { super(map); } + public final boolean contains(Object o) { return map.containsValue(o); } + public final boolean remove(Object o) { + if (o != null) { + Iterator it = new ValueIterator(map); + while (it.hasNext()) { + if (o.equals(it.next())) { + it.remove(); + return true; + } + } + } + return false; + } + + /** + * Returns a "weakly consistent" iterator that will never + * throw {@link ConcurrentModificationException}, and + * guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not + * guaranteed to) reflect any modifications subsequent to + * construction. + * + * @return an iterator over the values of this map + */ + public final Iterator iterator() { + return new ValueIterator(map); + } + public final boolean add(V e) { + throw new UnsupportedOperationException(); + } + public final boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + } + + /** + * A view of a ConcurrentHashMapV8 as a {@link Set} of (key, value) + * entries. This class cannot be directly instantiated. See + * {@link #entrySet}. + */ + public static final class EntrySetView extends CHMView + implements Set> { + EntrySetView(ConcurrentHashMapV8 map) { super(map); } + public final boolean contains(Object o) { + Object k, v, r; Map.Entry e; + return ((o instanceof Map.Entry) && + (k = (e = (Map.Entry)o).getKey()) != null && + (r = map.get(k)) != null && + (v = e.getValue()) != null && + (v == r || v.equals(r))); + } + public final boolean remove(Object o) { + Object k, v; Map.Entry e; + return ((o instanceof Map.Entry) && + (k = (e = (Map.Entry)o).getKey()) != null && + (v = e.getValue()) != null && + map.remove(k, v)); + } + + /** + * Returns a "weakly consistent" iterator that will never + * throw {@link ConcurrentModificationException}, and + * guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not + * guaranteed to) reflect any modifications subsequent to + * construction. + * + * @return an iterator over the entries of this map + */ + public final Iterator> iterator() { + return new EntryIterator(map); + } + + public final boolean add(Entry e) { + K key = e.getKey(); + V value = e.getValue(); + if (key == null || value == null) + throw new NullPointerException(); + return map.internalPut(key, value) == null; + } + public final boolean addAll(Collection> c) { + boolean added = false; + for (Entry e : c) { + if (add(e)) + added = true; + } + return added; + } + public boolean equals(Object o) { + Set c; + return ((o instanceof Set) && + ((c = (Set)o) == this || + (containsAll(c) && c.containsAll(this)))); + } + } +} diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java new file mode 100644 index 00000000..ecf552a2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java @@ -0,0 +1,204 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This is based on 1.9 version. + +package com.concurrent_ruby.ext.jsr166e.nounsafe; + +import java.util.concurrent.atomic.AtomicLong; +import java.io.IOException; +import java.io.Serializable; +import java.io.ObjectInputStream; + +/** + * One or more variables that together maintain an initially zero + * {@code long} sum. When updates (method {@link #add}) are contended + * across threads, the set of variables may grow dynamically to reduce + * contention. Method {@link #sum} (or, equivalently, {@link + * #longValue}) returns the current total combined across the + * variables maintaining the sum. + * + *

    This class is usually preferable to {@link AtomicLong} when + * multiple threads update a common sum that is used for purposes such + * as collecting statistics, not for fine-grained synchronization + * control. Under low update contention, the two classes have similar + * characteristics. But under high contention, expected throughput of + * this class is significantly higher, at the expense of higher space + * consumption. + * + *

    This class extends {@link Number}, but does not define + * methods such as {@code hashCode} and {@code compareTo} because + * instances are expected to be mutated, and so are not useful as + * collection keys. + * + *

    jsr166e note: This class is targeted to be placed in + * java.util.concurrent.atomic. + * + * @since 1.8 + * @author Doug Lea + */ +public class LongAdder extends Striped64 implements Serializable { + private static final long serialVersionUID = 7249069246863182397L; + + /** + * Version of plus for use in retryUpdate + */ + final long fn(long v, long x) { return v + x; } + + /** + * Creates a new adder with initial sum of zero. + */ + public LongAdder() { + } + + /** + * Adds the given value. + * + * @param x the value to add + */ + public void add(long x) { + Cell[] as; long b, v; HashCode hc; Cell a; int n; + if ((as = cells) != null || !casBase(b = base, b + x)) { + boolean uncontended = true; + int h = (hc = threadHashCode.get()).code; + if (as == null || (n = as.length) < 1 || + (a = as[(n - 1) & h]) == null || + !(uncontended = a.cas(v = a.value, v + x))) + retryUpdate(x, hc, uncontended); + } + } + + /** + * Equivalent to {@code add(1)}. + */ + public void increment() { + add(1L); + } + + /** + * Equivalent to {@code add(-1)}. + */ + public void decrement() { + add(-1L); + } + + /** + * Returns the current sum. The returned value is NOT an + * atomic snapshot: Invocation in the absence of concurrent + * updates returns an accurate result, but concurrent updates that + * occur while the sum is being calculated might not be + * incorporated. + * + * @return the sum + */ + public long sum() { + long sum = base; + Cell[] as = cells; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) + sum += a.value; + } + } + return sum; + } + + /** + * Resets variables maintaining the sum to zero. This method may + * be a useful alternative to creating a new adder, but is only + * effective if there are no concurrent updates. Because this + * method is intrinsically racy, it should only be used when it is + * known that no threads are concurrently updating. + */ + public void reset() { + internalReset(0L); + } + + /** + * Equivalent in effect to {@link #sum} followed by {@link + * #reset}. This method may apply for example during quiescent + * points between multithreaded computations. If there are + * updates concurrent with this method, the returned value is + * not guaranteed to be the final value occurring before + * the reset. + * + * @return the sum + */ + public long sumThenReset() { + long sum = base; + Cell[] as = cells; + base = 0L; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) { + sum += a.value; + a.value = 0L; + } + } + } + return sum; + } + + /** + * Returns the String representation of the {@link #sum}. + * @return the String representation of the {@link #sum} + */ + public String toString() { + return Long.toString(sum()); + } + + /** + * Equivalent to {@link #sum}. + * + * @return the sum + */ + public long longValue() { + return sum(); + } + + /** + * Returns the {@link #sum} as an {@code int} after a narrowing + * primitive conversion. + */ + public int intValue() { + return (int)sum(); + } + + /** + * Returns the {@link #sum} as a {@code float} + * after a widening primitive conversion. + */ + public float floatValue() { + return (float)sum(); + } + + /** + * Returns the {@link #sum} as a {@code double} after a widening + * primitive conversion. + */ + public double doubleValue() { + return (double)sum(); + } + + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + s.defaultWriteObject(); + s.writeLong(sum()); + } + + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { + s.defaultReadObject(); + busy = 0; + cells = null; + base = s.readLong(); + } + +} diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java new file mode 100644 index 00000000..f5216424 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java @@ -0,0 +1,291 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This is based on 1.5 version. + +package com.concurrent_ruby.ext.jsr166e.nounsafe; + +import java.util.Random; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; + +/** + * A package-local class holding common representation and mechanics + * for classes supporting dynamic striping on 64bit values. The class + * extends Number so that concrete subclasses must publicly do so. + */ +abstract class Striped64 extends Number { + /* + * This class maintains a lazily-initialized table of atomically + * updated variables, plus an extra "base" field. The table size + * is a power of two. Indexing uses masked per-thread hash codes. + * Nearly all declarations in this class are package-private, + * accessed directly by subclasses. + * + * Table entries are of class Cell; a variant of AtomicLong padded + * to reduce cache contention on most processors. Padding is + * overkill for most Atomics because they are usually irregularly + * scattered in memory and thus don't interfere much with each + * other. But Atomic objects residing in arrays will tend to be + * placed adjacent to each other, and so will most often share + * cache lines (with a huge negative performance impact) without + * this precaution. + * + * In part because Cells are relatively large, we avoid creating + * them until they are needed. When there is no contention, all + * updates are made to the base field. Upon first contention (a + * failed CAS on base update), the table is initialized to size 2. + * The table size is doubled upon further contention until + * reaching the nearest power of two greater than or equal to the + * number of CPUS. Table slots remain empty (null) until they are + * needed. + * + * A single spinlock ("busy") is used for initializing and + * resizing the table, as well as populating slots with new Cells. + * There is no need for a blocking lock: When the lock is not + * available, threads try other slots (or the base). During these + * retries, there is increased contention and reduced locality, + * which is still better than alternatives. + * + * Per-thread hash codes are initialized to random values. + * Contention and/or table collisions are indicated by failed + * CASes when performing an update operation (see method + * retryUpdate). Upon a collision, if the table size is less than + * the capacity, it is doubled in size unless some other thread + * holds the lock. If a hashed slot is empty, and lock is + * available, a new Cell is created. Otherwise, if the slot + * exists, a CAS is tried. Retries proceed by "double hashing", + * using a secondary hash (Marsaglia XorShift) to try to find a + * free slot. + * + * The table size is capped because, when there are more threads + * than CPUs, supposing that each thread were bound to a CPU, + * there would exist a perfect hash function mapping threads to + * slots that eliminates collisions. When we reach capacity, we + * search for this mapping by randomly varying the hash codes of + * colliding threads. Because search is random, and collisions + * only become known via CAS failures, convergence can be slow, + * and because threads are typically not bound to CPUS forever, + * may not occur at all. However, despite these limitations, + * observed contention rates are typically low in these cases. + * + * It is possible for a Cell to become unused when threads that + * once hashed to it terminate, as well as in the case where + * doubling the table causes no thread to hash to it under + * expanded mask. We do not try to detect or remove such cells, + * under the assumption that for long-running instances, observed + * contention levels will recur, so the cells will eventually be + * needed again; and for short-lived ones, it does not matter. + */ + + /** + * Padded variant of AtomicLong supporting only raw accesses plus CAS. + * The value field is placed between pads, hoping that the JVM doesn't + * reorder them. + * + * JVM intrinsics note: It would be possible to use a release-only + * form of CAS here, if it were provided. + */ + static final class Cell { + volatile long p0, p1, p2, p3, p4, p5, p6; + volatile long value; + volatile long q0, q1, q2, q3, q4, q5, q6; + + static AtomicLongFieldUpdater VALUE_UPDATER = AtomicLongFieldUpdater.newUpdater(Cell.class, "value"); + + Cell(long x) { value = x; } + + final boolean cas(long cmp, long val) { + return VALUE_UPDATER.compareAndSet(this, cmp, val); + } + + } + + /** + * Holder for the thread-local hash code. The code is initially + * random, but may be set to a different value upon collisions. + */ + static final class HashCode { + static final Random rng = new Random(); + int code; + HashCode() { + int h = rng.nextInt(); // Avoid zero to allow xorShift rehash + code = (h == 0) ? 1 : h; + } + } + + /** + * The corresponding ThreadLocal class + */ + static final class ThreadHashCode extends ThreadLocal { + public HashCode initialValue() { return new HashCode(); } + } + + /** + * Static per-thread hash codes. Shared across all instances to + * reduce ThreadLocal pollution and because adjustments due to + * collisions in one table are likely to be appropriate for + * others. + */ + static final ThreadHashCode threadHashCode = new ThreadHashCode(); + + /** Number of CPUS, to place bound on table size */ + static final int NCPU = Runtime.getRuntime().availableProcessors(); + + /** + * Table of cells. When non-null, size is a power of 2. + */ + transient volatile Cell[] cells; + + /** + * Base value, used mainly when there is no contention, but also as + * a fallback during table initialization races. Updated via CAS. + */ + transient volatile long base; + + /** + * Spinlock (locked via CAS) used when resizing and/or creating Cells. + */ + transient volatile int busy; + + AtomicLongFieldUpdater BASE_UPDATER = AtomicLongFieldUpdater.newUpdater(Striped64.class, "base"); + AtomicIntegerFieldUpdater BUSY_UPDATER = AtomicIntegerFieldUpdater.newUpdater(Striped64.class, "busy"); + + /** + * Package-private default constructor + */ + Striped64() { + } + + /** + * CASes the base field. + */ + final boolean casBase(long cmp, long val) { + return BASE_UPDATER.compareAndSet(this, cmp, val); + } + + /** + * CASes the busy field from 0 to 1 to acquire lock. + */ + final boolean casBusy() { + return BUSY_UPDATER.compareAndSet(this, 0, 1); + } + + /** + * Computes the function of current and new value. Subclasses + * should open-code this update function for most uses, but the + * virtualized form is needed within retryUpdate. + * + * @param currentValue the current value (of either base or a cell) + * @param newValue the argument from a user update call + * @return result of the update function + */ + abstract long fn(long currentValue, long newValue); + + /** + * Handles cases of updates involving initialization, resizing, + * creating new Cells, and/or contention. See above for + * explanation. This method suffers the usual non-modularity + * problems of optimistic retry code, relying on rechecked sets of + * reads. + * + * @param x the value + * @param hc the hash code holder + * @param wasUncontended false if CAS failed before call + */ + final void retryUpdate(long x, HashCode hc, boolean wasUncontended) { + int h = hc.code; + boolean collide = false; // True if last slot nonempty + for (;;) { + Cell[] as; Cell a; int n; long v; + if ((as = cells) != null && (n = as.length) > 0) { + if ((a = as[(n - 1) & h]) == null) { + if (busy == 0) { // Try to attach new Cell + Cell r = new Cell(x); // Optimistically create + if (busy == 0 && casBusy()) { + boolean created = false; + try { // Recheck under lock + Cell[] rs; int m, j; + if ((rs = cells) != null && + (m = rs.length) > 0 && + rs[j = (m - 1) & h] == null) { + rs[j] = r; + created = true; + } + } finally { + busy = 0; + } + if (created) + break; + continue; // Slot is now non-empty + } + } + collide = false; + } + else if (!wasUncontended) // CAS already known to fail + wasUncontended = true; // Continue after rehash + else if (a.cas(v = a.value, fn(v, x))) + break; + else if (n >= NCPU || cells != as) + collide = false; // At max size or stale + else if (!collide) + collide = true; + else if (busy == 0 && casBusy()) { + try { + if (cells == as) { // Expand table unless stale + Cell[] rs = new Cell[n << 1]; + for (int i = 0; i < n; ++i) + rs[i] = as[i]; + cells = rs; + } + } finally { + busy = 0; + } + collide = false; + continue; // Retry with expanded table + } + h ^= h << 13; // Rehash + h ^= h >>> 17; + h ^= h << 5; + } + else if (busy == 0 && cells == as && casBusy()) { + boolean init = false; + try { // Initialize table + if (cells == as) { + Cell[] rs = new Cell[2]; + rs[h & 1] = new Cell(x); + cells = rs; + init = true; + } + } finally { + busy = 0; + } + if (init) + break; + } + else if (casBase(v = base, fn(v, x))) + break; // Fall back on using base + } + hc.code = h; // Record index for next time + } + + + /** + * Sets base and all cells to the given value. + */ + final void internalReset(long initialValue) { + Cell[] as = cells; + base = initialValue; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) + a.value = initialValue; + } + } + } +} diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java new file mode 100644 index 00000000..3ea409ff --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java @@ -0,0 +1,199 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This is based on 1.16 version + +package com.concurrent_ruby.ext.jsr166y; + +import java.util.Random; + +/** + * A random number generator isolated to the current thread. Like the + * global {@link java.util.Random} generator used by the {@link + * java.lang.Math} class, a {@code ThreadLocalRandom} is initialized + * with an internally generated seed that may not otherwise be + * modified. When applicable, use of {@code ThreadLocalRandom} rather + * than shared {@code Random} objects in concurrent programs will + * typically encounter much less overhead and contention. Use of + * {@code ThreadLocalRandom} is particularly appropriate when multiple + * tasks (for example, each a {@link ForkJoinTask}) use random numbers + * in parallel in thread pools. + * + *

    Usages of this class should typically be of the form: + * {@code ThreadLocalRandom.current().nextX(...)} (where + * {@code X} is {@code Int}, {@code Long}, etc). + * When all usages are of this form, it is never possible to + * accidently share a {@code ThreadLocalRandom} across multiple threads. + * + *

    This class also provides additional commonly used bounded random + * generation methods. + * + * @since 1.7 + * @author Doug Lea + */ +public class ThreadLocalRandom extends Random { + // same constants as Random, but must be redeclared because private + private static final long multiplier = 0x5DEECE66DL; + private static final long addend = 0xBL; + private static final long mask = (1L << 48) - 1; + + /** + * The random seed. We can't use super.seed. + */ + private long rnd; + + /** + * Initialization flag to permit calls to setSeed to succeed only + * while executing the Random constructor. We can't allow others + * since it would cause setting seed in one part of a program to + * unintentionally impact other usages by the thread. + */ + boolean initialized; + + // Padding to help avoid memory contention among seed updates in + // different TLRs in the common case that they are located near + // each other. + private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7; + + /** + * The actual ThreadLocal + */ + private static final ThreadLocal localRandom = + new ThreadLocal() { + protected ThreadLocalRandom initialValue() { + return new ThreadLocalRandom(); + } + }; + + + /** + * Constructor called only by localRandom.initialValue. + */ + ThreadLocalRandom() { + super(); + initialized = true; + } + + /** + * Returns the current thread's {@code ThreadLocalRandom}. + * + * @return the current thread's {@code ThreadLocalRandom} + */ + public static ThreadLocalRandom current() { + return localRandom.get(); + } + + /** + * Throws {@code UnsupportedOperationException}. Setting seeds in + * this generator is not supported. + * + * @throws UnsupportedOperationException always + */ + public void setSeed(long seed) { + if (initialized) + throw new UnsupportedOperationException(); + rnd = (seed ^ multiplier) & mask; + } + + protected int next(int bits) { + rnd = (rnd * multiplier + addend) & mask; + return (int) (rnd >>> (48-bits)); + } + + /** + * Returns a pseudorandom, uniformly distributed value between the + * given least value (inclusive) and bound (exclusive). + * + * @param least the least value returned + * @param bound the upper bound (exclusive) + * @throws IllegalArgumentException if least greater than or equal + * to bound + * @return the next value + */ + public int nextInt(int least, int bound) { + if (least >= bound) + throw new IllegalArgumentException(); + return nextInt(bound - least) + least; + } + + /** + * Returns a pseudorandom, uniformly distributed value + * between 0 (inclusive) and the specified value (exclusive). + * + * @param n the bound on the random number to be returned. Must be + * positive. + * @return the next value + * @throws IllegalArgumentException if n is not positive + */ + public long nextLong(long n) { + if (n <= 0) + throw new IllegalArgumentException("n must be positive"); + // Divide n by two until small enough for nextInt. On each + // iteration (at most 31 of them but usually much less), + // randomly choose both whether to include high bit in result + // (offset) and whether to continue with the lower vs upper + // half (which makes a difference only if odd). + long offset = 0; + while (n >= Integer.MAX_VALUE) { + int bits = next(2); + long half = n >>> 1; + long nextn = ((bits & 2) == 0) ? half : n - half; + if ((bits & 1) == 0) + offset += n - nextn; + n = nextn; + } + return offset + nextInt((int) n); + } + + /** + * Returns a pseudorandom, uniformly distributed value between the + * given least value (inclusive) and bound (exclusive). + * + * @param least the least value returned + * @param bound the upper bound (exclusive) + * @return the next value + * @throws IllegalArgumentException if least greater than or equal + * to bound + */ + public long nextLong(long least, long bound) { + if (least >= bound) + throw new IllegalArgumentException(); + return nextLong(bound - least) + least; + } + + /** + * Returns a pseudorandom, uniformly distributed {@code double} value + * between 0 (inclusive) and the specified value (exclusive). + * + * @param n the bound on the random number to be returned. Must be + * positive. + * @return the next value + * @throws IllegalArgumentException if n is not positive + */ + public double nextDouble(double n) { + if (n <= 0) + throw new IllegalArgumentException("n must be positive"); + return nextDouble() * n; + } + + /** + * Returns a pseudorandom, uniformly distributed value between the + * given least value (inclusive) and bound (exclusive). + * + * @param least the least value returned + * @param bound the upper bound (exclusive) + * @return the next value + * @throws IllegalArgumentException if least greater than or equal + * to bound + */ + public double nextDouble(double least, double bound) { + if (least >= bound) + throw new IllegalArgumentException(); + return nextDouble() * (bound - least) + least; + } + + private static final long serialVersionUID = -5851777807851030925L; +} diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent-ruby.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent-ruby.rb new file mode 100644 index 00000000..e9a3dea4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent-ruby.rb @@ -0,0 +1,5 @@ +# This file is here so that there is a file with the same name as the gem that +# can be required by Bundler.require. Applications should normally +# require 'concurrent'. + +require_relative "concurrent" diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent.rb new file mode 100644 index 00000000..87de46f1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent.rb @@ -0,0 +1,134 @@ +require 'concurrent/version' +require 'concurrent/constants' +require 'concurrent/errors' +require 'concurrent/configuration' + +require 'concurrent/atomics' +require 'concurrent/executors' +require 'concurrent/synchronization' + +require 'concurrent/atomic/atomic_markable_reference' +require 'concurrent/atomic/atomic_reference' +require 'concurrent/agent' +require 'concurrent/atom' +require 'concurrent/array' +require 'concurrent/hash' +require 'concurrent/set' +require 'concurrent/map' +require 'concurrent/tuple' +require 'concurrent/async' +require 'concurrent/dataflow' +require 'concurrent/delay' +require 'concurrent/exchanger' +require 'concurrent/future' +require 'concurrent/immutable_struct' +require 'concurrent/ivar' +require 'concurrent/maybe' +require 'concurrent/mutable_struct' +require 'concurrent/mvar' +require 'concurrent/promise' +require 'concurrent/scheduled_task' +require 'concurrent/settable_struct' +require 'concurrent/timer_task' +require 'concurrent/tvar' +require 'concurrent/promises' + +require 'concurrent/thread_safe/synchronized_delegator' +require 'concurrent/thread_safe/util' + +require 'concurrent/options' + +# @!macro internal_implementation_note +# +# @note **Private Implementation:** This abstraction is a private, internal +# implementation detail. It should never be used directly. + +# @!macro monotonic_clock_warning +# +# @note Time calculations on all platforms and languages are sensitive to +# changes to the system clock. To alleviate the potential problems +# associated with changing the system clock while an application is running, +# most modern operating systems provide a monotonic clock that operates +# independently of the system clock. A monotonic clock cannot be used to +# determine human-friendly clock times. A monotonic clock is used exclusively +# for calculating time intervals. Not all Ruby platforms provide access to an +# operating system monotonic clock. On these platforms a pure-Ruby monotonic +# clock will be used as a fallback. An operating system monotonic clock is both +# faster and more reliable than the pure-Ruby implementation. The pure-Ruby +# implementation should be fast and reliable enough for most non-realtime +# operations. At this time the common Ruby platforms that provide access to an +# operating system monotonic clock are MRI 2.1 and above and JRuby (all versions). +# +# @see http://linux.die.net/man/3/clock_gettime Linux clock_gettime(3) + +# @!macro copy_options +# +# ## Copy Options +# +# Object references in Ruby are mutable. This can lead to serious +# problems when the {#value} of an object is a mutable reference. Which +# is always the case unless the value is a `Fixnum`, `Symbol`, or similar +# "primitive" data type. Each instance can be configured with a few +# options that can help protect the program from potentially dangerous +# operations. Each of these options can be optionally set when the object +# instance is created: +# +# * `:dup_on_deref` When true the object will call the `#dup` method on +# the `value` object every time the `#value` method is called +# (default: false) +# * `:freeze_on_deref` When true the object will call the `#freeze` +# method on the `value` object every time the `#value` method is called +# (default: false) +# * `:copy_on_deref` When given a `Proc` object the `Proc` will be run +# every time the `#value` method is called. The `Proc` will be given +# the current `value` as its only argument and the result returned by +# the block will be the return value of the `#value` call. When `nil` +# this option will be ignored (default: nil) +# +# When multiple deref options are set the order of operations is strictly defined. +# The order of deref operations is: +# * `:copy_on_deref` +# * `:dup_on_deref` +# * `:freeze_on_deref` +# +# Because of this ordering there is no need to `#freeze` an object created by a +# provided `:copy_on_deref` block. Simply set `:freeze_on_deref` to `true`. +# Setting both `:dup_on_deref` to `true` and `:freeze_on_deref` to `true` is +# as close to the behavior of a "pure" functional language (like Erlang, Clojure, +# or Haskell) as we are likely to get in Ruby. + +# @!macro deref_options +# +# @option opts [Boolean] :dup_on_deref (false) Call `#dup` before +# returning the data from {#value} +# @option opts [Boolean] :freeze_on_deref (false) Call `#freeze` before +# returning the data from {#value} +# @option opts [Proc] :copy_on_deref (nil) When calling the {#value} +# method, call the given proc passing the internal value as the sole +# argument then return the new value returned from the proc. + +# @!macro executor_and_deref_options +# +# @param [Hash] opts the options used to define the behavior at update and deref +# and to specify the executor on which to perform actions +# @option opts [Executor] :executor when set use the given `Executor` instance. +# Three special values are also supported: `:io` returns the global pool for +# long, blocking (IO) tasks, `:fast` returns the global pool for short, fast +# operations, and `:immediate` returns the global `ImmediateExecutor` object. +# @!macro deref_options + +# @!macro warn.edge +# @api Edge +# @note **Edge Features** are under active development and may change frequently. +# +# - Deprecations are not added before incompatible changes. +# - Edge version: _major_ is always 0, _minor_ bump means incompatible change, +# _patch_ bump means compatible change. +# - Edge features may also lack tests and documentation. +# - Features developed in `concurrent-ruby-edge` are expected to move +# to `concurrent-ruby` when finalised. + + +# {include:file:README.md} +module Concurrent +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/agent.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/agent.rb new file mode 100644 index 00000000..dc8a2600 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/agent.rb @@ -0,0 +1,588 @@ +require 'concurrent/configuration' +require 'concurrent/atomic/atomic_reference' +require 'concurrent/atomic/count_down_latch' +require 'concurrent/atomic/thread_local_var' +require 'concurrent/collection/copy_on_write_observer_set' +require 'concurrent/concern/observable' +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # `Agent` is inspired by Clojure's [agent](http://clojure.org/agents) + # function. An agent is a shared, mutable variable providing independent, + # uncoordinated, *asynchronous* change of individual values. Best used when + # the value will undergo frequent, complex updates. Suitable when the result + # of an update does not need to be known immediately. `Agent` is (mostly) + # functionally equivalent to Clojure's agent, except where the runtime + # prevents parity. + # + # Agents are reactive, not autonomous - there is no imperative message loop + # and no blocking receive. The state of an Agent should be itself immutable + # and the `#value` of an Agent is always immediately available for reading by + # any thread without any messages, i.e. observation does not require + # cooperation or coordination. + # + # Agent action dispatches are made using the various `#send` methods. These + # methods always return immediately. At some point later, in another thread, + # the following will happen: + # + # 1. The given `action` will be applied to the state of the Agent and the + # `args`, if any were supplied. + # 2. The return value of `action` will be passed to the validator lambda, + # if one has been set on the Agent. + # 3. If the validator succeeds or if no validator was given, the return value + # of the given `action` will become the new `#value` of the Agent. See + # `#initialize` for details. + # 4. If any observers were added to the Agent, they will be notified. See + # `#add_observer` for details. + # 5. If during the `action` execution any other dispatches are made (directly + # or indirectly), they will be held until after the `#value` of the Agent + # has been changed. + # + # If any exceptions are thrown by an action function, no nested dispatches + # will occur, and the exception will be cached in the Agent itself. When an + # Agent has errors cached, any subsequent interactions will immediately throw + # an exception, until the agent's errors are cleared. Agent errors can be + # examined with `#error` and the agent restarted with `#restart`. + # + # The actions of all Agents get interleaved amongst threads in a thread pool. + # At any point in time, at most one action for each Agent is being executed. + # Actions dispatched to an agent from another single agent or thread will + # occur in the order they were sent, potentially interleaved with actions + # dispatched to the same agent from other sources. The `#send` method should + # be used for actions that are CPU limited, while the `#send_off` method is + # appropriate for actions that may block on IO. + # + # Unlike in Clojure, `Agent` cannot participate in `Concurrent::TVar` transactions. + # + # ## Example + # + # ``` + # def next_fibonacci(set = nil) + # return [0, 1] if set.nil? + # set + [set[-2..-1].reduce{|sum,x| sum + x }] + # end + # + # # create an agent with an initial value + # agent = Concurrent::Agent.new(next_fibonacci) + # + # # send a few update requests + # 5.times do + # agent.send{|set| next_fibonacci(set) } + # end + # + # # wait for them to complete + # agent.await + # + # # get the current value + # agent.value #=> [0, 1, 1, 2, 3, 5, 8] + # ``` + # + # ## Observation + # + # Agents support observers through the {Concurrent::Observable} mixin module. + # Notification of observers occurs every time an action dispatch returns and + # the new value is successfully validated. Observation will *not* occur if the + # action raises an exception, if validation fails, or when a {#restart} occurs. + # + # When notified the observer will receive three arguments: `time`, `old_value`, + # and `new_value`. The `time` argument is the time at which the value change + # occurred. The `old_value` is the value of the Agent when the action began + # processing. The `new_value` is the value to which the Agent was set when the + # action completed. Note that `old_value` and `new_value` may be the same. + # This is not an error. It simply means that the action returned the same + # value. + # + # ## Nested Actions + # + # It is possible for an Agent action to post further actions back to itself. + # The nested actions will be enqueued normally then processed *after* the + # outer action completes, in the order they were sent, possibly interleaved + # with action dispatches from other threads. Nested actions never deadlock + # with one another and a failure in a nested action will never affect the + # outer action. + # + # Nested actions can be called using the Agent reference from the enclosing + # scope or by passing the reference in as a "send" argument. Nested actions + # cannot be post using `self` from within the action block/proc/lambda; `self` + # in this context will not reference the Agent. The preferred method for + # dispatching nested actions is to pass the Agent as an argument. This allows + # Ruby to more effectively manage the closing scope. + # + # Prefer this: + # + # ``` + # agent = Concurrent::Agent.new(0) + # agent.send(agent) do |value, this| + # this.send {|v| v + 42 } + # 3.14 + # end + # agent.value #=> 45.14 + # ``` + # + # Over this: + # + # ``` + # agent = Concurrent::Agent.new(0) + # agent.send do |value| + # agent.send {|v| v + 42 } + # 3.14 + # end + # ``` + # + # @!macro agent_await_warning + # + # **NOTE** Never, *under any circumstances*, call any of the "await" methods + # ({#await}, {#await_for}, {#await_for!}, and {#wait}) from within an action + # block/proc/lambda. The call will block the Agent and will always fail. + # Calling either {#await} or {#wait} (with a timeout of `nil`) will + # hopelessly deadlock the Agent with no possibility of recovery. + # + # @!macro thread_safe_variable_comparison + # + # @see http://clojure.org/Agents Clojure Agents + # @see http://clojure.org/state Values and Change - Clojure's approach to Identity and State + class Agent < Synchronization::LockableObject + include Concern::Observable + + ERROR_MODES = [:continue, :fail].freeze + private_constant :ERROR_MODES + + AWAIT_FLAG = ::Object.new + private_constant :AWAIT_FLAG + + AWAIT_ACTION = ->(value, latch) { latch.count_down; AWAIT_FLAG } + private_constant :AWAIT_ACTION + + DEFAULT_ERROR_HANDLER = ->(agent, error) { nil } + private_constant :DEFAULT_ERROR_HANDLER + + DEFAULT_VALIDATOR = ->(value) { true } + private_constant :DEFAULT_VALIDATOR + + Job = Struct.new(:action, :args, :executor, :caller) + private_constant :Job + + # Raised during action processing or any other time in an Agent's lifecycle. + class Error < StandardError + def initialize(message = nil) + message ||= 'agent must be restarted before jobs can post' + super(message) + end + end + + # Raised when a new value obtained during action processing or at `#restart` + # fails validation. + class ValidationError < Error + def initialize(message = nil) + message ||= 'invalid value' + super(message) + end + end + + # The error mode this Agent is operating in. See {#initialize} for details. + attr_reader :error_mode + + # Create a new `Agent` with the given initial value and options. + # + # The `:validator` option must be `nil` or a side-effect free proc/lambda + # which takes one argument. On any intended value change the validator, if + # provided, will be called. If the new value is invalid the validator should + # return `false` or raise an error. + # + # The `:error_handler` option must be `nil` or a proc/lambda which takes two + # arguments. When an action raises an error or validation fails, either by + # returning false or raising an error, the error handler will be called. The + # arguments to the error handler will be a reference to the agent itself and + # the error object which was raised. + # + # The `:error_mode` may be either `:continue` (the default if an error + # handler is given) or `:fail` (the default if error handler nil or not + # given). + # + # If an action being run by the agent throws an error or doesn't pass + # validation the error handler, if present, will be called. After the + # handler executes if the error mode is `:continue` the Agent will continue + # as if neither the action that caused the error nor the error itself ever + # happened. + # + # If the mode is `:fail` the Agent will become {#failed?} and will stop + # accepting new action dispatches. Any previously queued actions will be + # held until {#restart} is called. The {#value} method will still work, + # returning the value of the Agent before the error. + # + # @param [Object] initial the initial value + # @param [Hash] opts the configuration options + # + # @option opts [Symbol] :error_mode either `:continue` or `:fail` + # @option opts [nil, Proc] :error_handler the (optional) error handler + # @option opts [nil, Proc] :validator the (optional) validation procedure + def initialize(initial, opts = {}) + super() + synchronize { ns_initialize(initial, opts) } + end + + # The current value (state) of the Agent, irrespective of any pending or + # in-progress actions. The value is always available and is non-blocking. + # + # @return [Object] the current value + def value + @current.value # TODO (pitr 12-Sep-2015): broken unsafe read? + end + + alias_method :deref, :value + + # When {#failed?} and {#error_mode} is `:fail`, returns the error object + # which caused the failure, else `nil`. When {#error_mode} is `:continue` + # will *always* return `nil`. + # + # @return [nil, Error] the error which caused the failure when {#failed?} + def error + @error.value + end + + alias_method :reason, :error + + # @!macro agent_send + # + # Dispatches an action to the Agent and returns immediately. Subsequently, + # in a thread from a thread pool, the {#value} will be set to the return + # value of the action. Action dispatches are only allowed when the Agent + # is not {#failed?}. + # + # The action must be a block/proc/lambda which takes 1 or more arguments. + # The first argument is the current {#value} of the Agent. Any arguments + # passed to the send method via the `args` parameter will be passed to the + # action as the remaining arguments. The action must return the new value + # of the Agent. + # + # * {#send} and {#send!} should be used for actions that are CPU limited + # * {#send_off}, {#send_off!}, and {#<<} are appropriate for actions that + # may block on IO + # * {#send_via} and {#send_via!} are used when a specific executor is to + # be used for the action + # + # @param [Array] args zero or more arguments to be passed to + # the action + # @param [Proc] action the action dispatch to be enqueued + # + # @yield [agent, value, *args] process the old value and return the new + # @yieldparam [Object] value the current {#value} of the Agent + # @yieldparam [Array] args zero or more arguments to pass to the + # action + # @yieldreturn [Object] the new value of the Agent + # + # @!macro send_return + # @return [Boolean] true if the action is successfully enqueued, false if + # the Agent is {#failed?} + def send(*args, &action) + enqueue_action_job(action, args, Concurrent.global_fast_executor) + end + + # @!macro agent_send + # + # @!macro send_bang_return_and_raise + # @return [Boolean] true if the action is successfully enqueued + # @raise [Concurrent::Agent::Error] if the Agent is {#failed?} + def send!(*args, &action) + raise Error.new unless send(*args, &action) + true + end + + # @!macro agent_send + # @!macro send_return + def send_off(*args, &action) + enqueue_action_job(action, args, Concurrent.global_io_executor) + end + + alias_method :post, :send_off + + # @!macro agent_send + # @!macro send_bang_return_and_raise + def send_off!(*args, &action) + raise Error.new unless send_off(*args, &action) + true + end + + # @!macro agent_send + # @!macro send_return + # @param [Concurrent::ExecutorService] executor the executor on which the + # action is to be dispatched + def send_via(executor, *args, &action) + enqueue_action_job(action, args, executor) + end + + # @!macro agent_send + # @!macro send_bang_return_and_raise + # @param [Concurrent::ExecutorService] executor the executor on which the + # action is to be dispatched + def send_via!(executor, *args, &action) + raise Error.new unless send_via(executor, *args, &action) + true + end + + # Dispatches an action to the Agent and returns immediately. Subsequently, + # in a thread from a thread pool, the {#value} will be set to the return + # value of the action. Appropriate for actions that may block on IO. + # + # @param [Proc] action the action dispatch to be enqueued + # @return [Concurrent::Agent] self + # @see #send_off + def <<(action) + send_off(&action) + self + end + + # Blocks the current thread (indefinitely!) until all actions dispatched + # thus far, from this thread or nested by the Agent, have occurred. Will + # block when {#failed?}. Will never return if a failed Agent is {#restart} + # with `:clear_actions` true. + # + # Returns a reference to `self` to support method chaining: + # + # ``` + # current_value = agent.await.value + # ``` + # + # @return [Boolean] self + # + # @!macro agent_await_warning + def await + wait(nil) + self + end + + # Blocks the current thread until all actions dispatched thus far, from this + # thread or nested by the Agent, have occurred, or the timeout (in seconds) + # has elapsed. + # + # @param [Float] timeout the maximum number of seconds to wait + # @return [Boolean] true if all actions complete before timeout else false + # + # @!macro agent_await_warning + def await_for(timeout) + wait(timeout.to_f) + end + + # Blocks the current thread until all actions dispatched thus far, from this + # thread or nested by the Agent, have occurred, or the timeout (in seconds) + # has elapsed. + # + # @param [Float] timeout the maximum number of seconds to wait + # @return [Boolean] true if all actions complete before timeout + # + # @raise [Concurrent::TimeoutError] when timeout is reached + # + # @!macro agent_await_warning + def await_for!(timeout) + raise Concurrent::TimeoutError unless wait(timeout.to_f) + true + end + + # Blocks the current thread until all actions dispatched thus far, from this + # thread or nested by the Agent, have occurred, or the timeout (in seconds) + # has elapsed. Will block indefinitely when timeout is nil or not given. + # + # Provided mainly for consistency with other classes in this library. Prefer + # the various `await` methods instead. + # + # @param [Float] timeout the maximum number of seconds to wait + # @return [Boolean] true if all actions complete before timeout else false + # + # @!macro agent_await_warning + def wait(timeout = nil) + latch = Concurrent::CountDownLatch.new(1) + enqueue_await_job(latch) + latch.wait(timeout) + end + + # Is the Agent in a failed state? + # + # @see #restart + def failed? + !@error.value.nil? + end + + alias_method :stopped?, :failed? + + # When an Agent is {#failed?}, changes the Agent {#value} to `new_value` + # then un-fails the Agent so that action dispatches are allowed again. If + # the `:clear_actions` option is give and true, any actions queued on the + # Agent that were being held while it was failed will be discarded, + # otherwise those held actions will proceed. The `new_value` must pass the + # validator if any, or `restart` will raise an exception and the Agent will + # remain failed with its old {#value} and {#error}. Observers, if any, will + # not be notified of the new state. + # + # @param [Object] new_value the new value for the Agent once restarted + # @param [Hash] opts the configuration options + # @option opts [Symbol] :clear_actions true if all enqueued but unprocessed + # actions should be discarded on restart, else false (default: false) + # @return [Boolean] true + # + # @raise [Concurrent:AgentError] when not failed + def restart(new_value, opts = {}) + clear_actions = opts.fetch(:clear_actions, false) + synchronize do + raise Error.new('agent is not failed') unless failed? + raise ValidationError unless ns_validate(new_value) + @current.value = new_value + @error.value = nil + @queue.clear if clear_actions + ns_post_next_job unless @queue.empty? + end + true + end + + class << self + + # Blocks the current thread (indefinitely!) until all actions dispatched + # thus far to all the given Agents, from this thread or nested by the + # given Agents, have occurred. Will block when any of the agents are + # failed. Will never return if a failed Agent is restart with + # `:clear_actions` true. + # + # @param [Array] agents the Agents on which to wait + # @return [Boolean] true + # + # @!macro agent_await_warning + def await(*agents) + agents.each { |agent| agent.await } + true + end + + # Blocks the current thread until all actions dispatched thus far to all + # the given Agents, from this thread or nested by the given Agents, have + # occurred, or the timeout (in seconds) has elapsed. + # + # @param [Float] timeout the maximum number of seconds to wait + # @param [Array] agents the Agents on which to wait + # @return [Boolean] true if all actions complete before timeout else false + # + # @!macro agent_await_warning + def await_for(timeout, *agents) + end_at = Concurrent.monotonic_time + timeout.to_f + ok = agents.length.times do |i| + break false if (delay = end_at - Concurrent.monotonic_time) < 0 + break false unless agents[i].await_for(delay) + end + !!ok + end + + # Blocks the current thread until all actions dispatched thus far to all + # the given Agents, from this thread or nested by the given Agents, have + # occurred, or the timeout (in seconds) has elapsed. + # + # @param [Float] timeout the maximum number of seconds to wait + # @param [Array] agents the Agents on which to wait + # @return [Boolean] true if all actions complete before timeout + # + # @raise [Concurrent::TimeoutError] when timeout is reached + # @!macro agent_await_warning + def await_for!(timeout, *agents) + raise Concurrent::TimeoutError unless await_for(timeout, *agents) + true + end + end + + private + + def ns_initialize(initial, opts) + @error_mode = opts[:error_mode] + @error_handler = opts[:error_handler] + + if @error_mode && !ERROR_MODES.include?(@error_mode) + raise ArgumentError.new('unrecognized error mode') + elsif @error_mode.nil? + @error_mode = @error_handler ? :continue : :fail + end + + @error_handler ||= DEFAULT_ERROR_HANDLER + @validator = opts.fetch(:validator, DEFAULT_VALIDATOR) + @current = Concurrent::AtomicReference.new(initial) + @error = Concurrent::AtomicReference.new(nil) + @caller = Concurrent::ThreadLocalVar.new(nil) + @queue = [] + + self.observers = Collection::CopyOnNotifyObserverSet.new + end + + def enqueue_action_job(action, args, executor) + raise ArgumentError.new('no action given') unless action + job = Job.new(action, args, executor, @caller.value || Thread.current.object_id) + synchronize { ns_enqueue_job(job) } + end + + def enqueue_await_job(latch) + synchronize do + if (index = ns_find_last_job_for_thread) + job = Job.new(AWAIT_ACTION, [latch], Concurrent.global_immediate_executor, + Thread.current.object_id) + ns_enqueue_job(job, index+1) + else + latch.count_down + true + end + end + end + + def ns_enqueue_job(job, index = nil) + # a non-nil index means this is an await job + return false if index.nil? && failed? + index ||= @queue.length + @queue.insert(index, job) + # if this is the only job, post to executor + ns_post_next_job if @queue.length == 1 + true + end + + def ns_post_next_job + @queue.first.executor.post { execute_next_job } + end + + def execute_next_job + job = synchronize { @queue.first } + old_value = @current.value + + @caller.value = job.caller # for nested actions + new_value = job.action.call(old_value, *job.args) + @caller.value = nil + + return if new_value == AWAIT_FLAG + + if ns_validate(new_value) + @current.value = new_value + observers.notify_observers(Time.now, old_value, new_value) + else + handle_error(ValidationError.new) + end + rescue => error + handle_error(error) + ensure + synchronize do + @queue.shift + unless failed? || @queue.empty? + ns_post_next_job + end + end + end + + def ns_validate(value) + @validator.call(value) + rescue + false + end + + def handle_error(error) + # stop new jobs from posting + @error.value = error if @error_mode == :fail + @error_handler.call(self, error) + rescue + # do nothing + end + + def ns_find_last_job_for_thread + @queue.rindex { |job| job.caller == Thread.current.object_id } + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/array.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/array.rb new file mode 100644 index 00000000..c8761af8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/array.rb @@ -0,0 +1,56 @@ +require 'concurrent/utility/engine' +require 'concurrent/thread_safe/util' + +module Concurrent + + # @!macro concurrent_array + # + # A thread-safe subclass of Array. This version locks against the object + # itself for every method call, ensuring only one thread can be reading + # or writing at a time. This includes iteration methods like `#each`. + # + # @note `a += b` is **not** a **thread-safe** operation on + # `Concurrent::Array`. It reads array `a`, then it creates new `Concurrent::Array` + # which is concatenation of `a` and `b`, then it writes the concatenation to `a`. + # The read and write are independent operations they do not form a single atomic + # operation therefore when two `+=` operations are executed concurrently updates + # may be lost. Use `#concat` instead. + # + # @see http://ruby-doc.org/core/Array.html Ruby standard library `Array` + + # @!macro internal_implementation_note + ArrayImplementation = case + when Concurrent.on_cruby? + # Array is not fully thread-safe on CRuby, see + # https://github.com/ruby-concurrency/concurrent-ruby/issues/929 + # So we will need to add synchronization here + ::Array + + when Concurrent.on_jruby? + require 'jruby/synchronized' + + class JRubyArray < ::Array + include JRuby::Synchronized + end + JRubyArray + + when Concurrent.on_truffleruby? + require 'concurrent/thread_safe/util/data_structures' + + class TruffleRubyArray < ::Array + end + + ThreadSafe::Util.make_synchronized_on_truffleruby TruffleRubyArray + TruffleRubyArray + + else + warn 'Possibly unsupported Ruby implementation' + ::Array + end + private_constant :ArrayImplementation + + # @!macro concurrent_array + class Array < ArrayImplementation + end + +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/async.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/async.rb new file mode 100644 index 00000000..97c5a6b2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/async.rb @@ -0,0 +1,449 @@ +require 'concurrent/configuration' +require 'concurrent/ivar' +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # A mixin module that provides simple asynchronous behavior to a class, + # turning it into a simple actor. Loosely based on Erlang's + # [gen_server](http://www.erlang.org/doc/man/gen_server.html), but without + # supervision or linking. + # + # A more feature-rich {Concurrent::Actor} is also available when the + # capabilities of `Async` are too limited. + # + # ```cucumber + # Feature: + # As a stateful, plain old Ruby class + # I want safe, asynchronous behavior + # So my long-running methods don't block the main thread + # ``` + # + # The `Async` module is a way to mix simple yet powerful asynchronous + # capabilities into any plain old Ruby object or class, turning each object + # into a simple Actor. Method calls are processed on a background thread. The + # caller is free to perform other actions while processing occurs in the + # background. + # + # Method calls to the asynchronous object are made via two proxy methods: + # `async` (alias `cast`) and `await` (alias `call`). These proxy methods post + # the method call to the object's background thread and return a "future" + # which will eventually contain the result of the method call. + # + # This behavior is loosely patterned after Erlang's `gen_server` behavior. + # When an Erlang module implements the `gen_server` behavior it becomes + # inherently asynchronous. The `start` or `start_link` function spawns a + # process (similar to a thread but much more lightweight and efficient) and + # returns the ID of the process. Using the process ID, other processes can + # send messages to the `gen_server` via the `cast` and `call` methods. Unlike + # Erlang's `gen_server`, however, `Async` classes do not support linking or + # supervision trees. + # + # ## Basic Usage + # + # When this module is mixed into a class, objects of the class become inherently + # asynchronous. Each object gets its own background thread on which to post + # asynchronous method calls. Asynchronous method calls are executed in the + # background one at a time in the order they are received. + # + # To create an asynchronous class, simply mix in the `Concurrent::Async` module: + # + # ``` + # class Hello + # include Concurrent::Async + # + # def hello(name) + # "Hello, #{name}!" + # end + # end + # ``` + # + # Mixing this module into a class provides each object two proxy methods: + # `async` and `await`. These methods are thread safe with respect to the + # enclosing object. The former proxy allows methods to be called + # asynchronously by posting to the object's internal thread. The latter proxy + # allows a method to be called synchronously but does so safely with respect + # to any pending asynchronous method calls and ensures proper ordering. Both + # methods return a {Concurrent::IVar} which can be inspected for the result + # of the proxied method call. Calling a method with `async` will return a + # `:pending` `IVar` whereas `await` will return a `:complete` `IVar`. + # + # ``` + # class Echo + # include Concurrent::Async + # + # def echo(msg) + # print "#{msg}\n" + # end + # end + # + # horn = Echo.new + # horn.echo('zero') # synchronous, not thread-safe + # # returns the actual return value of the method + # + # horn.async.echo('one') # asynchronous, non-blocking, thread-safe + # # returns an IVar in the :pending state + # + # horn.await.echo('two') # synchronous, blocking, thread-safe + # # returns an IVar in the :complete state + # ``` + # + # ## Let It Fail + # + # The `async` and `await` proxy methods have built-in error protection based + # on Erlang's famous "let it fail" philosophy. Instance methods should not be + # programmed defensively. When an exception is raised by a delegated method + # the proxy will rescue the exception, expose it to the caller as the `reason` + # attribute of the returned future, then process the next method call. + # + # ## Calling Methods Internally + # + # External method calls should *always* use the `async` and `await` proxy + # methods. When one method calls another method, the `async` proxy should + # rarely be used and the `await` proxy should *never* be used. + # + # When an object calls one of its own methods using the `await` proxy the + # second call will be enqueued *behind* the currently running method call. + # Any attempt to wait on the result will fail as the second call will never + # run until after the current call completes. + # + # Calling a method using the `await` proxy from within a method that was + # itself called using `async` or `await` will irreversibly deadlock the + # object. Do *not* do this, ever. + # + # ## Instance Variables and Attribute Accessors + # + # Instance variables do not need to be thread-safe so long as they are private. + # Asynchronous method calls are processed in the order they are received and + # are processed one at a time. Therefore private instance variables can only + # be accessed by one thread at a time. This is inherently thread-safe. + # + # When using private instance variables within asynchronous methods, the best + # practice is to read the instance variable into a local variable at the start + # of the method then update the instance variable at the *end* of the method. + # This way, should an exception be raised during method execution the internal + # state of the object will not have been changed. + # + # ### Reader Attributes + # + # The use of `attr_reader` is discouraged. Internal state exposed externally, + # when necessary, should be done through accessor methods. The instance + # variables exposed by these methods *must* be thread-safe, or they must be + # called using the `async` and `await` proxy methods. These two approaches are + # subtly different. + # + # When internal state is accessed via the `async` and `await` proxy methods, + # the returned value represents the object's state *at the time the call is + # processed*, which may *not* be the state of the object at the time the call + # is made. + # + # To get the state *at the current* time, irrespective of an enqueued method + # calls, a reader method must be called directly. This is inherently unsafe + # unless the instance variable is itself thread-safe, preferably using one + # of the thread-safe classes within this library. Because the thread-safe + # classes within this library are internally-locking or non-locking, they can + # be safely used from within asynchronous methods without causing deadlocks. + # + # Generally speaking, the best practice is to *not* expose internal state via + # reader methods. The best practice is to simply use the method's return value. + # + # ### Writer Attributes + # + # Writer attributes should never be used with asynchronous classes. Changing + # the state externally, even when done in the thread-safe way, is not logically + # consistent. Changes to state need to be timed with respect to all asynchronous + # method calls which my be in-process or enqueued. The only safe practice is to + # pass all necessary data to each method as arguments and let the method update + # the internal state as necessary. + # + # ## Class Constants, Variables, and Methods + # + # ### Class Constants + # + # Class constants do not need to be thread-safe. Since they are read-only and + # immutable they may be safely read both externally and from within + # asynchronous methods. + # + # ### Class Variables + # + # Class variables should be avoided. Class variables represent shared state. + # Shared state is anathema to concurrency. Should there be a need to share + # state using class variables they *must* be thread-safe, preferably + # using the thread-safe classes within this library. When updating class + # variables, never assign a new value/object to the variable itself. Assignment + # is not thread-safe in Ruby. Instead, use the thread-safe update functions + # of the variable itself to change the value. + # + # The best practice is to *never* use class variables with `Async` classes. + # + # ### Class Methods + # + # Class methods which are pure functions are safe. Class methods which modify + # class variables should be avoided, for all the reasons listed above. + # + # ## An Important Note About Thread Safe Guarantees + # + # > Thread safe guarantees can only be made when asynchronous method calls + # > are not mixed with direct method calls. Use only direct method calls + # > when the object is used exclusively on a single thread. Use only + # > `async` and `await` when the object is shared between threads. Once you + # > call a method using `async` or `await`, you should no longer call methods + # > directly on the object. Use `async` and `await` exclusively from then on. + # + # @example + # + # class Echo + # include Concurrent::Async + # + # def echo(msg) + # print "#{msg}\n" + # end + # end + # + # horn = Echo.new + # horn.echo('zero') # synchronous, not thread-safe + # # returns the actual return value of the method + # + # horn.async.echo('one') # asynchronous, non-blocking, thread-safe + # # returns an IVar in the :pending state + # + # horn.await.echo('two') # synchronous, blocking, thread-safe + # # returns an IVar in the :complete state + # + # @see Concurrent::Actor + # @see https://en.wikipedia.org/wiki/Actor_model "Actor Model" at Wikipedia + # @see http://www.erlang.org/doc/man/gen_server.html Erlang gen_server + # @see http://c2.com/cgi/wiki?LetItCrash "Let It Crash" at http://c2.com/ + module Async + + # @!method self.new(*args, &block) + # + # Instantiate a new object and ensure proper initialization of the + # synchronization mechanisms. + # + # @param [Array] args Zero or more arguments to be passed to the + # object's initializer. + # @param [Proc] block Optional block to pass to the object's initializer. + # @return [Object] A properly initialized object of the asynchronous class. + + # Check for the presence of a method on an object and determine if a given + # set of arguments matches the required arity. + # + # @param [Object] obj the object to check against + # @param [Symbol] method the method to check the object for + # @param [Array] args zero or more arguments for the arity check + # + # @raise [NameError] the object does not respond to `method` method + # @raise [ArgumentError] the given `args` do not match the arity of `method` + # + # @note This check is imperfect because of the way Ruby reports the arity of + # methods with a variable number of arguments. It is possible to determine + # if too few arguments are given but impossible to determine if too many + # arguments are given. This check may also fail to recognize dynamic behavior + # of the object, such as methods simulated with `method_missing`. + # + # @see http://www.ruby-doc.org/core-2.1.1/Method.html#method-i-arity Method#arity + # @see http://ruby-doc.org/core-2.1.0/Object.html#method-i-respond_to-3F Object#respond_to? + # @see http://www.ruby-doc.org/core-2.1.0/BasicObject.html#method-i-method_missing BasicObject#method_missing + # + # @!visibility private + def self.validate_argc(obj, method, *args) + argc = args.length + arity = obj.method(method).arity + + if arity >= 0 && argc != arity + raise ArgumentError.new("wrong number of arguments (#{argc} for #{arity})") + elsif arity < 0 && (arity = (arity + 1).abs) > argc + raise ArgumentError.new("wrong number of arguments (#{argc} for #{arity}..*)") + end + end + + # @!visibility private + def self.included(base) + base.singleton_class.send(:alias_method, :original_new, :new) + base.extend(ClassMethods) + super(base) + end + + # @!visibility private + module ClassMethods + def new(*args, &block) + obj = original_new(*args, &block) + obj.send(:init_synchronization) + obj + end + ruby2_keywords :new if respond_to?(:ruby2_keywords, true) + end + private_constant :ClassMethods + + # Delegates asynchronous, thread-safe method calls to the wrapped object. + # + # @!visibility private + class AsyncDelegator < Synchronization::LockableObject + safe_initialization! + + # Create a new delegator object wrapping the given delegate. + # + # @param [Object] delegate the object to wrap and delegate method calls to + def initialize(delegate) + super() + @delegate = delegate + @queue = [] + @executor = Concurrent.global_io_executor + @ruby_pid = $$ + end + + # Delegates method calls to the wrapped object. + # + # @param [Symbol] method the method being called + # @param [Array] args zero or more arguments to the method + # + # @return [IVar] the result of the method call + # + # @raise [NameError] the object does not respond to `method` method + # @raise [ArgumentError] the given `args` do not match the arity of `method` + def method_missing(method, *args, &block) + super unless @delegate.respond_to?(method) + Async::validate_argc(@delegate, method, *args) + + ivar = Concurrent::IVar.new + synchronize do + reset_if_forked + @queue.push [ivar, method, args, block] + @executor.post { perform } if @queue.length == 1 + end + + ivar + end + + # Check whether the method is responsive + # + # @param [Symbol] method the method being called + def respond_to_missing?(method, include_private = false) + @delegate.respond_to?(method) || super + end + + # Perform all enqueued tasks. + # + # This method must be called from within the executor. It must not be + # called while already running. It will loop until the queue is empty. + def perform + loop do + ivar, method, args, block = synchronize { @queue.first } + break unless ivar # queue is empty + + begin + ivar.set(@delegate.send(method, *args, &block)) + rescue => error + ivar.fail(error) + end + + synchronize do + @queue.shift + return if @queue.empty? + end + end + end + + def reset_if_forked + if $$ != @ruby_pid + @queue.clear + @ruby_pid = $$ + end + end + end + private_constant :AsyncDelegator + + # Delegates synchronous, thread-safe method calls to the wrapped object. + # + # @!visibility private + class AwaitDelegator + + # Create a new delegator object wrapping the given delegate. + # + # @param [AsyncDelegator] delegate the object to wrap and delegate method calls to + def initialize(delegate) + @delegate = delegate + end + + # Delegates method calls to the wrapped object. + # + # @param [Symbol] method the method being called + # @param [Array] args zero or more arguments to the method + # + # @return [IVar] the result of the method call + # + # @raise [NameError] the object does not respond to `method` method + # @raise [ArgumentError] the given `args` do not match the arity of `method` + def method_missing(method, *args, &block) + ivar = @delegate.send(method, *args, &block) + ivar.wait + ivar + end + + # Check whether the method is responsive + # + # @param [Symbol] method the method being called + def respond_to_missing?(method, include_private = false) + @delegate.respond_to?(method) || super + end + end + private_constant :AwaitDelegator + + # Causes the chained method call to be performed asynchronously on the + # object's thread. The delegated method will return a future in the + # `:pending` state and the method call will have been scheduled on the + # object's thread. The final disposition of the method call can be obtained + # by inspecting the returned future. + # + # @!macro async_thread_safety_warning + # @note The method call is guaranteed to be thread safe with respect to + # all other method calls against the same object that are called with + # either `async` or `await`. The mutable nature of Ruby references + # (and object orientation in general) prevent any other thread safety + # guarantees. Do NOT mix direct method calls with delegated method calls. + # Use *only* delegated method calls when sharing the object between threads. + # + # @return [Concurrent::IVar] the pending result of the asynchronous operation + # + # @raise [NameError] the object does not respond to the requested method + # @raise [ArgumentError] the given `args` do not match the arity of + # the requested method + def async + @__async_delegator__ + end + alias_method :cast, :async + + # Causes the chained method call to be performed synchronously on the + # current thread. The delegated will return a future in either the + # `:fulfilled` or `:rejected` state and the delegated method will have + # completed. The final disposition of the delegated method can be obtained + # by inspecting the returned future. + # + # @!macro async_thread_safety_warning + # + # @return [Concurrent::IVar] the completed result of the synchronous operation + # + # @raise [NameError] the object does not respond to the requested method + # @raise [ArgumentError] the given `args` do not match the arity of the + # requested method + def await + @__await_delegator__ + end + alias_method :call, :await + + # Initialize the internal serializer and other stnchronization mechanisms. + # + # @note This method *must* be called immediately upon object construction. + # This is the only way thread-safe initialization can be guaranteed. + # + # @!visibility private + def init_synchronization + return self if defined?(@__async_initialized__) && @__async_initialized__ + @__async_initialized__ = true + @__async_delegator__ = AsyncDelegator.new(self) + @__await_delegator__ = AwaitDelegator.new(@__async_delegator__) + self + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atom.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atom.rb new file mode 100644 index 00000000..f590a23d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atom.rb @@ -0,0 +1,222 @@ +require 'concurrent/atomic/atomic_reference' +require 'concurrent/collection/copy_on_notify_observer_set' +require 'concurrent/concern/observable' +require 'concurrent/synchronization/object' + +# @!macro thread_safe_variable_comparison +# +# ## Thread-safe Variable Classes +# +# Each of the thread-safe variable classes is designed to solve a different +# problem. In general: +# +# * *{Concurrent::Agent}:* Shared, mutable variable providing independent, +# uncoordinated, *asynchronous* change of individual values. Best used when +# the value will undergo frequent, complex updates. Suitable when the result +# of an update does not need to be known immediately. +# * *{Concurrent::Atom}:* Shared, mutable variable providing independent, +# uncoordinated, *synchronous* change of individual values. Best used when +# the value will undergo frequent reads but only occasional, though complex, +# updates. Suitable when the result of an update must be known immediately. +# * *{Concurrent::AtomicReference}:* A simple object reference that can be updated +# atomically. Updates are synchronous but fast. Best used when updates a +# simple set operations. Not suitable when updates are complex. +# {Concurrent::AtomicBoolean} and {Concurrent::AtomicFixnum} are similar +# but optimized for the given data type. +# * *{Concurrent::Exchanger}:* Shared, stateless synchronization point. Used +# when two or more threads need to exchange data. The threads will pair then +# block on each other until the exchange is complete. +# * *{Concurrent::MVar}:* Shared synchronization point. Used when one thread +# must give a value to another, which must take the value. The threads will +# block on each other until the exchange is complete. +# * *{Concurrent::ThreadLocalVar}:* Shared, mutable, isolated variable which +# holds a different value for each thread which has access. Often used as +# an instance variable in objects which must maintain different state +# for different threads. +# * *{Concurrent::TVar}:* Shared, mutable variables which provide +# *coordinated*, *synchronous*, change of *many* stated. Used when multiple +# value must change together, in an all-or-nothing transaction. + + +module Concurrent + + # Atoms provide a way to manage shared, synchronous, independent state. + # + # An atom is initialized with an initial value and an optional validation + # proc. At any time the value of the atom can be synchronously and safely + # changed. If a validator is given at construction then any new value + # will be checked against the validator and will be rejected if the + # validator returns false or raises an exception. + # + # There are two ways to change the value of an atom: {#compare_and_set} and + # {#swap}. The former will set the new value if and only if it validates and + # the current value matches the new value. The latter will atomically set the + # new value to the result of running the given block if and only if that + # value validates. + # + # ## Example + # + # ``` + # def next_fibonacci(set = nil) + # return [0, 1] if set.nil? + # set + [set[-2..-1].reduce{|sum,x| sum + x }] + # end + # + # # create an atom with an initial value + # atom = Concurrent::Atom.new(next_fibonacci) + # + # # send a few update requests + # 5.times do + # atom.swap{|set| next_fibonacci(set) } + # end + # + # # get the current value + # atom.value #=> [0, 1, 1, 2, 3, 5, 8] + # ``` + # + # ## Observation + # + # Atoms support observers through the {Concurrent::Observable} mixin module. + # Notification of observers occurs every time the value of the Atom changes. + # When notified the observer will receive three arguments: `time`, `old_value`, + # and `new_value`. The `time` argument is the time at which the value change + # occurred. The `old_value` is the value of the Atom when the change began + # The `new_value` is the value to which the Atom was set when the change + # completed. Note that `old_value` and `new_value` may be the same. This is + # not an error. It simply means that the change operation returned the same + # value. + # + # Unlike in Clojure, `Atom` cannot participate in {Concurrent::TVar} transactions. + # + # @!macro thread_safe_variable_comparison + # + # @see http://clojure.org/atoms Clojure Atoms + # @see http://clojure.org/state Values and Change - Clojure's approach to Identity and State + class Atom < Synchronization::Object + include Concern::Observable + + safe_initialization! + attr_atomic(:value) + private :value=, :swap_value, :compare_and_set_value, :update_value + public :value + alias_method :deref, :value + + # @!method value + # The current value of the atom. + # + # @return [Object] The current value. + + # Create a new atom with the given initial value. + # + # @param [Object] value The initial value + # @param [Hash] opts The options used to configure the atom + # @option opts [Proc] :validator (nil) Optional proc used to validate new + # values. It must accept one and only one argument which will be the + # intended new value. The validator will return true if the new value + # is acceptable else return false (preferably) or raise an exception. + # + # @!macro deref_options + # + # @raise [ArgumentError] if the validator is not a `Proc` (when given) + def initialize(value, opts = {}) + super() + @Validator = opts.fetch(:validator, -> v { true }) + self.observers = Collection::CopyOnNotifyObserverSet.new + self.value = value + end + + # Atomically swaps the value of atom using the given block. The current + # value will be passed to the block, as will any arguments passed as + # arguments to the function. The new value will be validated against the + # (optional) validator proc given at construction. If validation fails the + # value will not be changed. + # + # Internally, {#swap} reads the current value, applies the block to it, and + # attempts to compare-and-set it in. Since another thread may have changed + # the value in the intervening time, it may have to retry, and does so in a + # spin loop. The net effect is that the value will always be the result of + # the application of the supplied block to a current value, atomically. + # However, because the block might be called multiple times, it must be free + # of side effects. + # + # @note The given block may be called multiple times, and thus should be free + # of side effects. + # + # @param [Object] args Zero or more arguments passed to the block. + # + # @yield [value, args] Calculates a new value for the atom based on the + # current value and any supplied arguments. + # @yieldparam value [Object] The current value of the atom. + # @yieldparam args [Object] All arguments passed to the function, in order. + # @yieldreturn [Object] The intended new value of the atom. + # + # @return [Object] The final value of the atom after all operations and + # validations are complete. + # + # @raise [ArgumentError] When no block is given. + def swap(*args) + raise ArgumentError.new('no block given') unless block_given? + + loop do + old_value = value + new_value = yield(old_value, *args) + begin + break old_value unless valid?(new_value) + break new_value if compare_and_set(old_value, new_value) + rescue + break old_value + end + end + end + + # Atomically sets the value of atom to the new value if and only if the + # current value of the atom is identical to the old value and the new + # value successfully validates against the (optional) validator given + # at construction. + # + # @param [Object] old_value The expected current value. + # @param [Object] new_value The intended new value. + # + # @return [Boolean] True if the value is changed else false. + def compare_and_set(old_value, new_value) + if valid?(new_value) && compare_and_set_value(old_value, new_value) + observers.notify_observers(Time.now, old_value, new_value) + true + else + false + end + end + + # Atomically sets the value of atom to the new value without regard for the + # current value so long as the new value successfully validates against the + # (optional) validator given at construction. + # + # @param [Object] new_value The intended new value. + # + # @return [Object] The final value of the atom after all operations and + # validations are complete. + def reset(new_value) + old_value = value + if valid?(new_value) + self.value = new_value + observers.notify_observers(Time.now, old_value, new_value) + new_value + else + old_value + end + end + + private + + # Is the new value valid? + # + # @param [Object] new_value The intended new value. + # @return [Boolean] false if the validator function returns false or raises + # an exception else true + def valid?(new_value) + @Validator.call(new_value) + rescue + false + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb new file mode 100644 index 00000000..f775691a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb @@ -0,0 +1,127 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first + +require 'concurrent/atomic/mutex_atomic_boolean' + +module Concurrent + + ################################################################### + + # @!macro atomic_boolean_method_initialize + # + # Creates a new `AtomicBoolean` with the given initial value. + # + # @param [Boolean] initial the initial value + + # @!macro atomic_boolean_method_value_get + # + # Retrieves the current `Boolean` value. + # + # @return [Boolean] the current value + + # @!macro atomic_boolean_method_value_set + # + # Explicitly sets the value. + # + # @param [Boolean] value the new value to be set + # + # @return [Boolean] the current value + + # @!macro atomic_boolean_method_true_question + # + # Is the current value `true` + # + # @return [Boolean] true if the current value is `true`, else false + + # @!macro atomic_boolean_method_false_question + # + # Is the current value `false` + # + # @return [Boolean] true if the current value is `false`, else false + + # @!macro atomic_boolean_method_make_true + # + # Explicitly sets the value to true. + # + # @return [Boolean] true if value has changed, otherwise false + + # @!macro atomic_boolean_method_make_false + # + # Explicitly sets the value to false. + # + # @return [Boolean] true if value has changed, otherwise false + + ################################################################### + + # @!macro atomic_boolean_public_api + # + # @!method initialize(initial = false) + # @!macro atomic_boolean_method_initialize + # + # @!method value + # @!macro atomic_boolean_method_value_get + # + # @!method value=(value) + # @!macro atomic_boolean_method_value_set + # + # @!method true? + # @!macro atomic_boolean_method_true_question + # + # @!method false? + # @!macro atomic_boolean_method_false_question + # + # @!method make_true + # @!macro atomic_boolean_method_make_true + # + # @!method make_false + # @!macro atomic_boolean_method_make_false + + ################################################################### + + # @!visibility private + # @!macro internal_implementation_note + AtomicBooleanImplementation = case + when Concurrent.on_cruby? && Concurrent.c_extensions_loaded? + CAtomicBoolean + when Concurrent.on_jruby? + JavaAtomicBoolean + else + MutexAtomicBoolean + end + private_constant :AtomicBooleanImplementation + + # @!macro atomic_boolean + # + # A boolean value that can be updated atomically. Reads and writes to an atomic + # boolean and thread-safe and guaranteed to succeed. Reads and writes may block + # briefly but no explicit locking is required. + # + # @!macro thread_safe_variable_comparison + # + # Performance: + # + # ``` + # Testing with ruby 2.1.2 + # Testing with Concurrent::MutexAtomicBoolean... + # 2.790000 0.000000 2.790000 ( 2.791454) + # Testing with Concurrent::CAtomicBoolean... + # 0.740000 0.000000 0.740000 ( 0.740206) + # + # Testing with jruby 1.9.3 + # Testing with Concurrent::MutexAtomicBoolean... + # 5.240000 2.520000 7.760000 ( 3.683000) + # Testing with Concurrent::JavaAtomicBoolean... + # 3.340000 0.010000 3.350000 ( 0.855000) + # ``` + # + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicBoolean.html java.util.concurrent.atomic.AtomicBoolean + # + # @!macro atomic_boolean_public_api + class AtomicBoolean < AtomicBooleanImplementation + # @return [String] Short string representation. + def to_s + format '%s value:%s>', super[0..-2], value + end + + alias_method :inspect, :to_s + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb new file mode 100644 index 00000000..26cd05d8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb @@ -0,0 +1,144 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first + +require 'concurrent/atomic/mutex_atomic_fixnum' + +module Concurrent + + ################################################################### + + # @!macro atomic_fixnum_method_initialize + # + # Creates a new `AtomicFixnum` with the given initial value. + # + # @param [Fixnum] initial the initial value + # @raise [ArgumentError] if the initial value is not a `Fixnum` + + # @!macro atomic_fixnum_method_value_get + # + # Retrieves the current `Fixnum` value. + # + # @return [Fixnum] the current value + + # @!macro atomic_fixnum_method_value_set + # + # Explicitly sets the value. + # + # @param [Fixnum] value the new value to be set + # + # @return [Fixnum] the current value + # + # @raise [ArgumentError] if the new value is not a `Fixnum` + + # @!macro atomic_fixnum_method_increment + # + # Increases the current value by the given amount (defaults to 1). + # + # @param [Fixnum] delta the amount by which to increase the current value + # + # @return [Fixnum] the current value after incrementation + + # @!macro atomic_fixnum_method_decrement + # + # Decreases the current value by the given amount (defaults to 1). + # + # @param [Fixnum] delta the amount by which to decrease the current value + # + # @return [Fixnum] the current value after decrementation + + # @!macro atomic_fixnum_method_compare_and_set + # + # Atomically sets the value to the given updated value if the current + # value == the expected value. + # + # @param [Fixnum] expect the expected value + # @param [Fixnum] update the new value + # + # @return [Boolean] true if the value was updated else false + + # @!macro atomic_fixnum_method_update + # + # Pass the current value to the given block, replacing it + # with the block's result. May retry if the value changes + # during the block's execution. + # + # @yield [Object] Calculate a new value for the atomic reference using + # given (old) value + # @yieldparam [Object] old_value the starting value of the atomic reference + # + # @return [Object] the new value + + ################################################################### + + # @!macro atomic_fixnum_public_api + # + # @!method initialize(initial = 0) + # @!macro atomic_fixnum_method_initialize + # + # @!method value + # @!macro atomic_fixnum_method_value_get + # + # @!method value=(value) + # @!macro atomic_fixnum_method_value_set + # + # @!method increment(delta = 1) + # @!macro atomic_fixnum_method_increment + # + # @!method decrement(delta = 1) + # @!macro atomic_fixnum_method_decrement + # + # @!method compare_and_set(expect, update) + # @!macro atomic_fixnum_method_compare_and_set + # + # @!method update + # @!macro atomic_fixnum_method_update + + ################################################################### + + # @!visibility private + # @!macro internal_implementation_note + AtomicFixnumImplementation = case + when Concurrent.on_cruby? && Concurrent.c_extensions_loaded? + CAtomicFixnum + when Concurrent.on_jruby? + JavaAtomicFixnum + else + MutexAtomicFixnum + end + private_constant :AtomicFixnumImplementation + + # @!macro atomic_fixnum + # + # A numeric value that can be updated atomically. Reads and writes to an atomic + # fixnum and thread-safe and guaranteed to succeed. Reads and writes may block + # briefly but no explicit locking is required. + # + # @!macro thread_safe_variable_comparison + # + # Performance: + # + # ``` + # Testing with ruby 2.1.2 + # Testing with Concurrent::MutexAtomicFixnum... + # 3.130000 0.000000 3.130000 ( 3.136505) + # Testing with Concurrent::CAtomicFixnum... + # 0.790000 0.000000 0.790000 ( 0.785550) + # + # Testing with jruby 1.9.3 + # Testing with Concurrent::MutexAtomicFixnum... + # 5.460000 2.460000 7.920000 ( 3.715000) + # Testing with Concurrent::JavaAtomicFixnum... + # 4.520000 0.030000 4.550000 ( 1.187000) + # ``` + # + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicLong.html java.util.concurrent.atomic.AtomicLong + # + # @!macro atomic_fixnum_public_api + class AtomicFixnum < AtomicFixnumImplementation + # @return [String] Short string representation. + def to_s + format '%s value:%s>', super[0..-2], value + end + + alias_method :inspect, :to_s + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb new file mode 100644 index 00000000..e16be657 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb @@ -0,0 +1,167 @@ +require 'concurrent/errors' +require 'concurrent/synchronization/object' + +module Concurrent + # An atomic reference which maintains an object reference along with a mark bit + # that can be updated atomically. + # + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicMarkableReference.html + # java.util.concurrent.atomic.AtomicMarkableReference + class AtomicMarkableReference < ::Concurrent::Synchronization::Object + + attr_atomic(:reference) + private :reference, :reference=, :swap_reference, :compare_and_set_reference, :update_reference + + def initialize(value = nil, mark = false) + super() + self.reference = immutable_array(value, mark) + end + + # Atomically sets the value and mark to the given updated value and + # mark given both: + # - the current value == the expected value && + # - the current mark == the expected mark + # + # @param [Object] expected_val the expected value + # @param [Object] new_val the new value + # @param [Boolean] expected_mark the expected mark + # @param [Boolean] new_mark the new mark + # + # @return [Boolean] `true` if successful. A `false` return indicates + # that the actual value was not equal to the expected value or the + # actual mark was not equal to the expected mark + def compare_and_set(expected_val, new_val, expected_mark, new_mark) + # Memoize a valid reference to the current AtomicReference for + # later comparison. + current = reference + curr_val, curr_mark = current + + # Ensure that that the expected marks match. + return false unless expected_mark == curr_mark + + if expected_val.is_a? Numeric + # If the object is a numeric, we need to ensure we are comparing + # the numerical values + return false unless expected_val == curr_val + else + # Otherwise, we need to ensure we are comparing the object identity. + # Theoretically, this could be incorrect if a user monkey-patched + # `Object#equal?`, but they should know that they are playing with + # fire at that point. + return false unless expected_val.equal? curr_val + end + + prospect = immutable_array(new_val, new_mark) + + compare_and_set_reference current, prospect + end + + alias_method :compare_and_swap, :compare_and_set + + # Gets the current reference and marked values. + # + # @return [Array] the current reference and marked values + def get + reference + end + + # Gets the current value of the reference + # + # @return [Object] the current value of the reference + def value + reference[0] + end + + # Gets the current marked value + # + # @return [Boolean] the current marked value + def mark + reference[1] + end + + alias_method :marked?, :mark + + # _Unconditionally_ sets to the given value of both the reference and + # the mark. + # + # @param [Object] new_val the new value + # @param [Boolean] new_mark the new mark + # + # @return [Array] both the new value and the new mark + def set(new_val, new_mark) + self.reference = immutable_array(new_val, new_mark) + end + + # Pass the current value and marked state to the given block, replacing it + # with the block's results. May retry if the value changes during the + # block's execution. + # + # @yield [Object] Calculate a new value and marked state for the atomic + # reference using given (old) value and (old) marked + # @yieldparam [Object] old_val the starting value of the atomic reference + # @yieldparam [Boolean] old_mark the starting state of marked + # + # @return [Array] the new value and new mark + def update + loop do + old_val, old_mark = reference + new_val, new_mark = yield old_val, old_mark + + if compare_and_set old_val, new_val, old_mark, new_mark + return immutable_array(new_val, new_mark) + end + end + end + + # Pass the current value to the given block, replacing it + # with the block's result. Raise an exception if the update + # fails. + # + # @yield [Object] Calculate a new value and marked state for the atomic + # reference using given (old) value and (old) marked + # @yieldparam [Object] old_val the starting value of the atomic reference + # @yieldparam [Boolean] old_mark the starting state of marked + # + # @return [Array] the new value and marked state + # + # @raise [Concurrent::ConcurrentUpdateError] if the update fails + def try_update! + old_val, old_mark = reference + new_val, new_mark = yield old_val, old_mark + + unless compare_and_set old_val, new_val, old_mark, new_mark + fail ::Concurrent::ConcurrentUpdateError, + 'AtomicMarkableReference: Update failed due to race condition.', + 'Note: If you would like to guarantee an update, please use ' + + 'the `AtomicMarkableReference#update` method.' + end + + immutable_array(new_val, new_mark) + end + + # Pass the current value to the given block, replacing it with the + # block's result. Simply return nil if update fails. + # + # @yield [Object] Calculate a new value and marked state for the atomic + # reference using given (old) value and (old) marked + # @yieldparam [Object] old_val the starting value of the atomic reference + # @yieldparam [Boolean] old_mark the starting state of marked + # + # @return [Array] the new value and marked state, or nil if + # the update failed + def try_update + old_val, old_mark = reference + new_val, new_mark = yield old_val, old_mark + + return unless compare_and_set old_val, new_val, old_mark, new_mark + + immutable_array(new_val, new_mark) + end + + private + + def immutable_array(*args) + args.freeze + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb new file mode 100644 index 00000000..bb5fb774 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb @@ -0,0 +1,135 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first + +require 'concurrent/atomic_reference/atomic_direct_update' +require 'concurrent/atomic_reference/numeric_cas_wrapper' +require 'concurrent/atomic_reference/mutex_atomic' + +# Shim for TruffleRuby::AtomicReference +if Concurrent.on_truffleruby? && !defined?(TruffleRuby::AtomicReference) + # @!visibility private + module TruffleRuby + AtomicReference = Truffle::AtomicReference + end +end + +module Concurrent + + # @!macro internal_implementation_note + AtomicReferenceImplementation = case + when Concurrent.on_cruby? && Concurrent.c_extensions_loaded? + # @!visibility private + # @!macro internal_implementation_note + class CAtomicReference + include AtomicDirectUpdate + include AtomicNumericCompareAndSetWrapper + alias_method :compare_and_swap, :compare_and_set + end + CAtomicReference + when Concurrent.on_jruby? + # @!visibility private + # @!macro internal_implementation_note + class JavaAtomicReference + include AtomicDirectUpdate + end + JavaAtomicReference + when Concurrent.on_truffleruby? + class TruffleRubyAtomicReference < TruffleRuby::AtomicReference + include AtomicDirectUpdate + alias_method :value, :get + alias_method :value=, :set + alias_method :compare_and_swap, :compare_and_set + alias_method :swap, :get_and_set + end + TruffleRubyAtomicReference + else + MutexAtomicReference + end + private_constant :AtomicReferenceImplementation + + # An object reference that may be updated atomically. All read and write + # operations have java volatile semantic. + # + # @!macro thread_safe_variable_comparison + # + # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html + # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html + # + # @!method initialize(value = nil) + # @!macro atomic_reference_method_initialize + # @param [Object] value The initial value. + # + # @!method get + # @!macro atomic_reference_method_get + # Gets the current value. + # @return [Object] the current value + # + # @!method set(new_value) + # @!macro atomic_reference_method_set + # Sets to the given value. + # @param [Object] new_value the new value + # @return [Object] the new value + # + # @!method get_and_set(new_value) + # @!macro atomic_reference_method_get_and_set + # Atomically sets to the given value and returns the old value. + # @param [Object] new_value the new value + # @return [Object] the old value + # + # @!method compare_and_set(old_value, new_value) + # @!macro atomic_reference_method_compare_and_set + # + # Atomically sets the value to the given updated value if + # the current value == the expected value. + # + # @param [Object] old_value the expected value + # @param [Object] new_value the new value + # + # @return [Boolean] `true` if successful. A `false` return indicates + # that the actual value was not equal to the expected value. + # + # @!method update + # Pass the current value to the given block, replacing it + # with the block's result. May retry if the value changes + # during the block's execution. + # + # @yield [Object] Calculate a new value for the atomic reference using + # given (old) value + # @yieldparam [Object] old_value the starting value of the atomic reference + # @return [Object] the new value + # + # @!method try_update + # Pass the current value to the given block, replacing it + # with the block's result. Return nil if the update fails. + # + # @yield [Object] Calculate a new value for the atomic reference using + # given (old) value + # @yieldparam [Object] old_value the starting value of the atomic reference + # @note This method was altered to avoid raising an exception by default. + # Instead, this method now returns `nil` in case of failure. For more info, + # please see: https://github.com/ruby-concurrency/concurrent-ruby/pull/336 + # @return [Object] the new value, or nil if update failed + # + # @!method try_update! + # Pass the current value to the given block, replacing it + # with the block's result. Raise an exception if the update + # fails. + # + # @yield [Object] Calculate a new value for the atomic reference using + # given (old) value + # @yieldparam [Object] old_value the starting value of the atomic reference + # @note This behavior mimics the behavior of the original + # `AtomicReference#try_update` API. The reason this was changed was to + # avoid raising exceptions (which are inherently slow) by default. For more + # info: https://github.com/ruby-concurrency/concurrent-ruby/pull/336 + # @return [Object] the new value + # @raise [Concurrent::ConcurrentUpdateError] if the update fails + class AtomicReference < AtomicReferenceImplementation + + # @return [String] Short string representation. + def to_s + format '%s value:%s>', super[0..-2], get + end + + alias_method :inspect, :to_s + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/count_down_latch.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/count_down_latch.rb new file mode 100644 index 00000000..d883aed6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/count_down_latch.rb @@ -0,0 +1,100 @@ +require 'concurrent/utility/engine' +require 'concurrent/atomic/mutex_count_down_latch' +require 'concurrent/atomic/java_count_down_latch' + +module Concurrent + + ################################################################### + + # @!macro count_down_latch_method_initialize + # + # Create a new `CountDownLatch` with the initial `count`. + # + # @param [new] count the initial count + # + # @raise [ArgumentError] if `count` is not an integer or is less than zero + + # @!macro count_down_latch_method_wait + # + # Block on the latch until the counter reaches zero or until `timeout` is reached. + # + # @param [Fixnum] timeout the number of seconds to wait for the counter or `nil` + # to block indefinitely + # @return [Boolean] `true` if the `count` reaches zero else false on `timeout` + + # @!macro count_down_latch_method_count_down + # + # Signal the latch to decrement the counter. Will signal all blocked threads when + # the `count` reaches zero. + + # @!macro count_down_latch_method_count + # + # The current value of the counter. + # + # @return [Fixnum] the current value of the counter + + ################################################################### + + # @!macro count_down_latch_public_api + # + # @!method initialize(count = 1) + # @!macro count_down_latch_method_initialize + # + # @!method wait(timeout = nil) + # @!macro count_down_latch_method_wait + # + # @!method count_down + # @!macro count_down_latch_method_count_down + # + # @!method count + # @!macro count_down_latch_method_count + + ################################################################### + + # @!visibility private + # @!macro internal_implementation_note + CountDownLatchImplementation = case + when Concurrent.on_jruby? + JavaCountDownLatch + else + MutexCountDownLatch + end + private_constant :CountDownLatchImplementation + + # @!macro count_down_latch + # + # A synchronization object that allows one thread to wait on multiple other threads. + # The thread that will wait creates a `CountDownLatch` and sets the initial value + # (normally equal to the number of other threads). The initiating thread passes the + # latch to the other threads then waits for the other threads by calling the `#wait` + # method. Each of the other threads calls `#count_down` when done with its work. + # When the latch counter reaches zero the waiting thread is unblocked and continues + # with its work. A `CountDownLatch` can be used only once. Its value cannot be reset. + # + # @!macro count_down_latch_public_api + # @example Waiter and Decrementer + # latch = Concurrent::CountDownLatch.new(3) + # + # waiter = Thread.new do + # latch.wait() + # puts ("Waiter released") + # end + # + # decrementer = Thread.new do + # sleep(1) + # latch.count_down + # puts latch.count + # + # sleep(1) + # latch.count_down + # puts latch.count + # + # sleep(1) + # latch.count_down + # puts latch.count + # end + # + # [waiter, decrementer].each(&:join) + class CountDownLatch < CountDownLatchImplementation + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb new file mode 100644 index 00000000..9ebe29dd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb @@ -0,0 +1,128 @@ +require 'concurrent/synchronization/lockable_object' +require 'concurrent/utility/native_integer' + +module Concurrent + + # A synchronization aid that allows a set of threads to all wait for each + # other to reach a common barrier point. + # @example + # barrier = Concurrent::CyclicBarrier.new(3) + # jobs = Array.new(3) { |i| -> { sleep i; p done: i } } + # process = -> (i) do + # # waiting to start at the same time + # barrier.wait + # # execute job + # jobs[i].call + # # wait for others to finish + # barrier.wait + # end + # threads = 2.times.map do |i| + # Thread.new(i, &process) + # end + # + # # use main as well + # process.call 2 + # + # # here we can be sure that all jobs are processed + class CyclicBarrier < Synchronization::LockableObject + + # @!visibility private + Generation = Struct.new(:status) + private_constant :Generation + + # Create a new `CyclicBarrier` that waits for `parties` threads + # + # @param [Fixnum] parties the number of parties + # @yield an optional block that will be executed that will be executed after + # the last thread arrives and before the others are released + # + # @raise [ArgumentError] if `parties` is not an integer or is less than zero + def initialize(parties, &block) + Utility::NativeInteger.ensure_integer_and_bounds parties + Utility::NativeInteger.ensure_positive_and_no_zero parties + + super(&nil) + synchronize { ns_initialize parties, &block } + end + + # @return [Fixnum] the number of threads needed to pass the barrier + def parties + synchronize { @parties } + end + + # @return [Fixnum] the number of threads currently waiting on the barrier + def number_waiting + synchronize { @number_waiting } + end + + # Blocks on the barrier until the number of waiting threads is equal to + # `parties` or until `timeout` is reached or `reset` is called + # If a block has been passed to the constructor, it will be executed once by + # the last arrived thread before releasing the others + # @param [Fixnum] timeout the number of seconds to wait for the counter or + # `nil` to block indefinitely + # @return [Boolean] `true` if the `count` reaches zero else false on + # `timeout` or on `reset` or if the barrier is broken + def wait(timeout = nil) + synchronize do + + return false unless @generation.status == :waiting + + @number_waiting += 1 + + if @number_waiting == @parties + @action.call if @action + ns_generation_done @generation, :fulfilled + true + else + generation = @generation + if ns_wait_until(timeout) { generation.status != :waiting } + generation.status == :fulfilled + else + ns_generation_done generation, :broken, false + false + end + end + end + end + + # resets the barrier to its initial state + # If there is at least one waiting thread, it will be woken up, the `wait` + # method will return false and the barrier will be broken + # If the barrier is broken, this method restores it to the original state + # + # @return [nil] + def reset + synchronize { ns_generation_done @generation, :reset } + end + + # A barrier can be broken when: + # - a thread called the `reset` method while at least one other thread was waiting + # - at least one thread timed out on `wait` method + # + # A broken barrier can be restored using `reset` it's safer to create a new one + # @return [Boolean] true if the barrier is broken otherwise false + def broken? + synchronize { @generation.status != :waiting } + end + + protected + + def ns_generation_done(generation, status, continue = true) + generation.status = status + ns_next_generation if continue + ns_broadcast + end + + def ns_next_generation + @generation = Generation.new(:waiting) + @number_waiting = 0 + end + + def ns_initialize(parties, &block) + @parties = parties + @action = block + ns_next_generation + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/event.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/event.rb new file mode 100644 index 00000000..ccf84c9d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/event.rb @@ -0,0 +1,109 @@ +require 'thread' +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # Old school kernel-style event reminiscent of Win32 programming in C++. + # + # When an `Event` is created it is in the `unset` state. Threads can choose to + # `#wait` on the event, blocking until released by another thread. When one + # thread wants to alert all blocking threads it calls the `#set` method which + # will then wake up all listeners. Once an `Event` has been set it remains set. + # New threads calling `#wait` will return immediately. An `Event` may be + # `#reset` at any time once it has been set. + # + # @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms682655.aspx + # @example + # event = Concurrent::Event.new + # + # t1 = Thread.new do + # puts "t1 is waiting" + # event.wait(1) + # puts "event occurred" + # end + # + # t2 = Thread.new do + # puts "t2 calling set" + # event.set + # end + # + # [t1, t2].each(&:join) + # + # # prints: + # # t1 is waiting + # # t2 calling set + # # event occurred + class Event < Synchronization::LockableObject + + # Creates a new `Event` in the unset state. Threads calling `#wait` on the + # `Event` will block. + def initialize + super + synchronize { ns_initialize } + end + + # Is the object in the set state? + # + # @return [Boolean] indicating whether or not the `Event` has been set + def set? + synchronize { @set } + end + + # Trigger the event, setting the state to `set` and releasing all threads + # waiting on the event. Has no effect if the `Event` has already been set. + # + # @return [Boolean] should always return `true` + def set + synchronize { ns_set } + end + + def try? + synchronize { @set ? false : ns_set } + end + + # Reset a previously set event back to the `unset` state. + # Has no effect if the `Event` has not yet been set. + # + # @return [Boolean] should always return `true` + def reset + synchronize do + if @set + @set = false + @iteration +=1 + end + true + end + end + + # Wait a given number of seconds for the `Event` to be set by another + # thread. Will wait forever when no `timeout` value is given. Returns + # immediately if the `Event` has already been set. + # + # @return [Boolean] true if the `Event` was set before timeout else false + def wait(timeout = nil) + synchronize do + unless @set + iteration = @iteration + ns_wait_until(timeout) { iteration < @iteration || @set } + else + true + end + end + end + + protected + + def ns_set + unless @set + @set = true + ns_broadcast + end + true + end + + def ns_initialize + @set = false + @iteration = 0 + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/fiber_local_var.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/fiber_local_var.rb new file mode 100644 index 00000000..e90fc24f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/fiber_local_var.rb @@ -0,0 +1,109 @@ +require 'concurrent/constants' +require_relative 'locals' + +module Concurrent + + # A `FiberLocalVar` is a variable where the value is different for each fiber. + # Each variable may have a default value, but when you modify the variable only + # the current fiber will ever see that change. + # + # This is similar to Ruby's built-in fiber-local variables (`Thread.current[:name]`), + # but with these major advantages: + # * `FiberLocalVar` has its own identity, it doesn't need a Symbol. + # * Each Ruby's built-in fiber-local variable leaks some memory forever (it's a Symbol held forever on the fiber), + # so it's only OK to create a small amount of them. + # `FiberLocalVar` has no such issue and it is fine to create many of them. + # * Ruby's built-in fiber-local variables leak forever the value set on each fiber (unless set to nil explicitly). + # `FiberLocalVar` automatically removes the mapping for each fiber once the `FiberLocalVar` instance is GC'd. + # + # @example + # v = FiberLocalVar.new(14) + # v.value #=> 14 + # v.value = 2 + # v.value #=> 2 + # + # @example + # v = FiberLocalVar.new(14) + # + # Fiber.new do + # v.value #=> 14 + # v.value = 1 + # v.value #=> 1 + # end.resume + # + # Fiber.new do + # v.value #=> 14 + # v.value = 2 + # v.value #=> 2 + # end.resume + # + # v.value #=> 14 + class FiberLocalVar + LOCALS = FiberLocals.new + + # Creates a fiber local variable. + # + # @param [Object] default the default value when otherwise unset + # @param [Proc] default_block Optional block that gets called to obtain the + # default value for each fiber + def initialize(default = nil, &default_block) + if default && block_given? + raise ArgumentError, "Cannot use both value and block as default value" + end + + if block_given? + @default_block = default_block + @default = nil + else + @default_block = nil + @default = default + end + + @index = LOCALS.next_index(self) + end + + # Returns the value in the current fiber's copy of this fiber-local variable. + # + # @return [Object] the current value + def value + LOCALS.fetch(@index) { default } + end + + # Sets the current fiber's copy of this fiber-local variable to the specified value. + # + # @param [Object] value the value to set + # @return [Object] the new value + def value=(value) + LOCALS.set(@index, value) + end + + # Bind the given value to fiber local storage during + # execution of the given block. + # + # @param [Object] value the value to bind + # @yield the operation to be performed with the bound variable + # @return [Object] the value + def bind(value) + if block_given? + old_value = self.value + self.value = value + begin + yield + ensure + self.value = old_value + end + end + end + + protected + + # @!visibility private + def default + if @default_block + self.value = @default_block.call + else + @default + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb new file mode 100644 index 00000000..3c119bc3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb @@ -0,0 +1,43 @@ +if Concurrent.on_jruby? + require 'concurrent/utility/native_extension_loader' + + module Concurrent + + # @!macro count_down_latch + # @!visibility private + # @!macro internal_implementation_note + class JavaCountDownLatch + + # @!macro count_down_latch_method_initialize + def initialize(count = 1) + Utility::NativeInteger.ensure_integer_and_bounds(count) + Utility::NativeInteger.ensure_positive(count) + @latch = java.util.concurrent.CountDownLatch.new(count) + end + + # @!macro count_down_latch_method_wait + def wait(timeout = nil) + result = nil + if timeout.nil? + Synchronization::JRuby.sleep_interruptibly { @latch.await } + result = true + else + Synchronization::JRuby.sleep_interruptibly do + result = @latch.await(1000 * timeout, java.util.concurrent.TimeUnit::MILLISECONDS) + end + end + result + end + + # @!macro count_down_latch_method_count_down + def count_down + @latch.countDown + end + + # @!macro count_down_latch_method_count + def count + @latch.getCount + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/locals.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/locals.rb new file mode 100644 index 00000000..0a276aed --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/locals.rb @@ -0,0 +1,189 @@ +require 'fiber' +require 'concurrent/utility/engine' +require 'concurrent/constants' + +module Concurrent + # @!visibility private + # @!macro internal_implementation_note + # + # An abstract implementation of local storage, with sub-classes for + # per-thread and per-fiber locals. + # + # Each execution context (EC, thread or fiber) has a lazily initialized array + # of local variable values. Each time a new local variable is created, we + # allocate an "index" for it. + # + # For example, if the allocated index is 1, that means slot #1 in EVERY EC's + # locals array will be used for the value of that variable. + # + # The good thing about using a per-EC structure to hold values, rather than + # a global, is that no synchronization is needed when reading and writing + # those values (since the structure is only ever accessed by a single + # thread). + # + # Of course, when a local variable is GC'd, 1) we need to recover its index + # for use by other new local variables (otherwise the locals arrays could + # get bigger and bigger with time), and 2) we need to null out all the + # references held in the now-unused slots (both to avoid blocking GC of those + # objects, and also to prevent "stale" values from being passed on to a new + # local when the index is reused). + # + # Because we need to null out freed slots, we need to keep references to + # ALL the locals arrays, so we can null out the appropriate slots in all of + # them. This is why we need to use a finalizer to clean up the locals array + # when the EC goes out of scope. + class AbstractLocals + def initialize + @free = [] + @lock = Mutex.new + @all_arrays = {} + @next = 0 + end + + def synchronize + @lock.synchronize { yield } + end + + if Concurrent.on_cruby? + def weak_synchronize + yield + end + else + alias_method :weak_synchronize, :synchronize + end + + def next_index(local) + index = synchronize do + if @free.empty? + @next += 1 + else + @free.pop + end + end + + # When the local goes out of scope, we should free the associated index + # and all values stored into it. + ObjectSpace.define_finalizer(local, local_finalizer(index)) + + index + end + + def free_index(index) + weak_synchronize do + # The cost of GC'ing a TLV is linear in the number of ECs using local + # variables. But that is natural! More ECs means more storage is used + # per local variable. So naturally more CPU time is required to free + # more storage. + # + # DO NOT use each_value which might conflict with new pair assignment + # into the hash in #set method. + @all_arrays.values.each do |locals| + locals[index] = nil + end + + # free index has to be published after the arrays are cleared: + @free << index + end + end + + def fetch(index) + locals = self.locals + value = locals ? locals[index] : nil + + if nil == value + yield + elsif NULL.equal?(value) + nil + else + value + end + end + + def set(index, value) + locals = self.locals! + locals[index] = (nil == value ? NULL : value) + + value + end + + private + + # When the local goes out of scope, clean up that slot across all locals currently assigned. + def local_finalizer(index) + proc do + free_index(index) + end + end + + # When a thread/fiber goes out of scope, remove the array from @all_arrays. + def thread_fiber_finalizer(array_object_id) + proc do + weak_synchronize do + @all_arrays.delete(array_object_id) + end + end + end + + # Returns the locals for the current scope, or nil if none exist. + def locals + raise NotImplementedError + end + + # Returns the locals for the current scope, creating them if necessary. + def locals! + raise NotImplementedError + end + end + + # @!visibility private + # @!macro internal_implementation_note + # An array-backed storage of indexed variables per thread. + class ThreadLocals < AbstractLocals + def locals + Thread.current.thread_variable_get(:concurrent_thread_locals) + end + + def locals! + thread = Thread.current + locals = thread.thread_variable_get(:concurrent_thread_locals) + + unless locals + locals = thread.thread_variable_set(:concurrent_thread_locals, []) + weak_synchronize do + @all_arrays[locals.object_id] = locals + end + # When the thread goes out of scope, we should delete the associated locals: + ObjectSpace.define_finalizer(thread, thread_fiber_finalizer(locals.object_id)) + end + + locals + end + end + + # @!visibility private + # @!macro internal_implementation_note + # An array-backed storage of indexed variables per fiber. + class FiberLocals < AbstractLocals + def locals + Thread.current[:concurrent_fiber_locals] + end + + def locals! + thread = Thread.current + locals = thread[:concurrent_fiber_locals] + + unless locals + locals = thread[:concurrent_fiber_locals] = [] + weak_synchronize do + @all_arrays[locals.object_id] = locals + end + # When the fiber goes out of scope, we should delete the associated locals: + ObjectSpace.define_finalizer(Fiber.current, thread_fiber_finalizer(locals.object_id)) + end + + locals + end + end + + private_constant :AbstractLocals, :ThreadLocals, :FiberLocals +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/lock_local_var.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/lock_local_var.rb new file mode 100644 index 00000000..ebf23a24 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/lock_local_var.rb @@ -0,0 +1,28 @@ +require 'concurrent/utility/engine' +require_relative 'fiber_local_var' +require_relative 'thread_local_var' + +module Concurrent + # @!visibility private + def self.mutex_owned_per_thread? + return false if Concurrent.on_jruby? || Concurrent.on_truffleruby? + + mutex = Mutex.new + # Lock the mutex: + mutex.synchronize do + # Check if the mutex is still owned in a child fiber: + Fiber.new { mutex.owned? }.resume + end + end + + if mutex_owned_per_thread? + LockLocalVar = ThreadLocalVar + else + LockLocalVar = FiberLocalVar + end + + # Either {FiberLocalVar} or {ThreadLocalVar} depending on whether Mutex (and Monitor) + # are held, respectively, per Fiber or per Thread. + class LockLocalVar + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb new file mode 100644 index 00000000..015996b0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb @@ -0,0 +1,68 @@ +require 'concurrent/synchronization/safe_initialization' + +module Concurrent + + # @!macro atomic_boolean + # @!visibility private + # @!macro internal_implementation_note + class MutexAtomicBoolean + extend Concurrent::Synchronization::SafeInitialization + + # @!macro atomic_boolean_method_initialize + def initialize(initial = false) + super() + @Lock = ::Mutex.new + @value = !!initial + end + + # @!macro atomic_boolean_method_value_get + def value + synchronize { @value } + end + + # @!macro atomic_boolean_method_value_set + def value=(value) + synchronize { @value = !!value } + end + + # @!macro atomic_boolean_method_true_question + def true? + synchronize { @value } + end + + # @!macro atomic_boolean_method_false_question + def false? + synchronize { !@value } + end + + # @!macro atomic_boolean_method_make_true + def make_true + synchronize { ns_make_value(true) } + end + + # @!macro atomic_boolean_method_make_false + def make_false + synchronize { ns_make_value(false) } + end + + protected + + # @!visibility private + def synchronize + if @Lock.owned? + yield + else + @Lock.synchronize { yield } + end + end + + private + + # @!visibility private + def ns_make_value(value) + old = @value + @value = value + old != @value + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb new file mode 100644 index 00000000..0ca39557 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb @@ -0,0 +1,81 @@ +require 'concurrent/synchronization/safe_initialization' +require 'concurrent/utility/native_integer' + +module Concurrent + + # @!macro atomic_fixnum + # @!visibility private + # @!macro internal_implementation_note + class MutexAtomicFixnum + extend Concurrent::Synchronization::SafeInitialization + + # @!macro atomic_fixnum_method_initialize + def initialize(initial = 0) + super() + @Lock = ::Mutex.new + ns_set(initial) + end + + # @!macro atomic_fixnum_method_value_get + def value + synchronize { @value } + end + + # @!macro atomic_fixnum_method_value_set + def value=(value) + synchronize { ns_set(value) } + end + + # @!macro atomic_fixnum_method_increment + def increment(delta = 1) + synchronize { ns_set(@value + delta.to_i) } + end + + alias_method :up, :increment + + # @!macro atomic_fixnum_method_decrement + def decrement(delta = 1) + synchronize { ns_set(@value - delta.to_i) } + end + + alias_method :down, :decrement + + # @!macro atomic_fixnum_method_compare_and_set + def compare_and_set(expect, update) + synchronize do + if @value == expect.to_i + @value = update.to_i + true + else + false + end + end + end + + # @!macro atomic_fixnum_method_update + def update + synchronize do + @value = yield @value + end + end + + protected + + # @!visibility private + def synchronize + if @Lock.owned? + yield + else + @Lock.synchronize { yield } + end + end + + private + + # @!visibility private + def ns_set(value) + Utility::NativeInteger.ensure_integer_and_bounds value + @value = value + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb new file mode 100644 index 00000000..29aa1caa --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb @@ -0,0 +1,44 @@ +require 'concurrent/synchronization/lockable_object' +require 'concurrent/utility/native_integer' + +module Concurrent + + # @!macro count_down_latch + # @!visibility private + # @!macro internal_implementation_note + class MutexCountDownLatch < Synchronization::LockableObject + + # @!macro count_down_latch_method_initialize + def initialize(count = 1) + Utility::NativeInteger.ensure_integer_and_bounds count + Utility::NativeInteger.ensure_positive count + + super() + synchronize { ns_initialize count } + end + + # @!macro count_down_latch_method_wait + def wait(timeout = nil) + synchronize { ns_wait_until(timeout) { @count == 0 } } + end + + # @!macro count_down_latch_method_count_down + def count_down + synchronize do + @count -= 1 if @count > 0 + ns_broadcast if @count == 0 + end + end + + # @!macro count_down_latch_method_count + def count + synchronize { @count } + end + + protected + + def ns_initialize(count) + @count = count + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb new file mode 100644 index 00000000..4347289f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb @@ -0,0 +1,131 @@ +require 'concurrent/synchronization/lockable_object' +require 'concurrent/utility/native_integer' + +module Concurrent + + # @!macro semaphore + # @!visibility private + # @!macro internal_implementation_note + class MutexSemaphore < Synchronization::LockableObject + + # @!macro semaphore_method_initialize + def initialize(count) + Utility::NativeInteger.ensure_integer_and_bounds count + + super() + synchronize { ns_initialize count } + end + + # @!macro semaphore_method_acquire + def acquire(permits = 1) + Utility::NativeInteger.ensure_integer_and_bounds permits + Utility::NativeInteger.ensure_positive permits + + synchronize do + try_acquire_timed(permits, nil) + end + + return unless block_given? + + begin + yield + ensure + release(permits) + end + end + + # @!macro semaphore_method_available_permits + def available_permits + synchronize { @free } + end + + # @!macro semaphore_method_drain_permits + # + # Acquires and returns all permits that are immediately available. + # + # @return [Integer] + def drain_permits + synchronize do + @free.tap { |_| @free = 0 } + end + end + + # @!macro semaphore_method_try_acquire + def try_acquire(permits = 1, timeout = nil) + Utility::NativeInteger.ensure_integer_and_bounds permits + Utility::NativeInteger.ensure_positive permits + + acquired = synchronize do + if timeout.nil? + try_acquire_now(permits) + else + try_acquire_timed(permits, timeout) + end + end + + return acquired unless block_given? + return unless acquired + + begin + yield + ensure + release(permits) + end + end + + # @!macro semaphore_method_release + def release(permits = 1) + Utility::NativeInteger.ensure_integer_and_bounds permits + Utility::NativeInteger.ensure_positive permits + + synchronize do + @free += permits + permits.times { ns_signal } + end + nil + end + + # Shrinks the number of available permits by the indicated reduction. + # + # @param [Fixnum] reduction Number of permits to remove. + # + # @raise [ArgumentError] if `reduction` is not an integer or is negative + # + # @raise [ArgumentError] if `@free` - `@reduction` is less than zero + # + # @return [nil] + # + # @!visibility private + def reduce_permits(reduction) + Utility::NativeInteger.ensure_integer_and_bounds reduction + Utility::NativeInteger.ensure_positive reduction + + synchronize { @free -= reduction } + nil + end + + protected + + # @!visibility private + def ns_initialize(count) + @free = count + end + + private + + # @!visibility private + def try_acquire_now(permits) + if @free >= permits + @free -= permits + true + else + false + end + end + + # @!visibility private + def try_acquire_timed(permits, timeout) + ns_wait_until(timeout) { try_acquire_now(permits) } + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb new file mode 100644 index 00000000..b26bd17a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb @@ -0,0 +1,255 @@ +require 'thread' +require 'concurrent/atomic/atomic_fixnum' +require 'concurrent/errors' +require 'concurrent/synchronization/object' +require 'concurrent/synchronization/lock' + +module Concurrent + + # Ruby read-write lock implementation + # + # Allows any number of concurrent readers, but only one concurrent writer + # (And if the "write" lock is taken, any readers who come along will have to wait) + # + # If readers are already active when a writer comes along, the writer will wait for + # all the readers to finish before going ahead. + # Any additional readers that come when the writer is already waiting, will also + # wait (so writers are not starved). + # + # This implementation is based on `java.util.concurrent.ReentrantReadWriteLock`. + # + # @example + # lock = Concurrent::ReadWriteLock.new + # lock.with_read_lock { data.retrieve } + # lock.with_write_lock { data.modify! } + # + # @note Do **not** try to acquire the write lock while already holding a read lock + # **or** try to acquire the write lock while you already have it. + # This will lead to deadlock + # + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html java.util.concurrent.ReentrantReadWriteLock + class ReadWriteLock < Synchronization::Object + + # @!visibility private + WAITING_WRITER = 1 << 15 + + # @!visibility private + RUNNING_WRITER = 1 << 29 + + # @!visibility private + MAX_READERS = WAITING_WRITER - 1 + + # @!visibility private + MAX_WRITERS = RUNNING_WRITER - MAX_READERS - 1 + + safe_initialization! + + # Implementation notes: + # A goal is to make the uncontended path for both readers/writers lock-free + # Only if there is reader-writer or writer-writer contention, should locks be used + # Internal state is represented by a single integer ("counter"), and updated + # using atomic compare-and-swap operations + # When the counter is 0, the lock is free + # Each reader increments the counter by 1 when acquiring a read lock + # (and decrements by 1 when releasing the read lock) + # The counter is increased by (1 << 15) for each writer waiting to acquire the + # write lock, and by (1 << 29) if the write lock is taken + + # Create a new `ReadWriteLock` in the unlocked state. + def initialize + super() + @Counter = AtomicFixnum.new(0) # single integer which represents lock state + @ReadLock = Synchronization::Lock.new + @WriteLock = Synchronization::Lock.new + end + + # Execute a block operation within a read lock. + # + # @yield the task to be performed within the lock. + # + # @return [Object] the result of the block operation. + # + # @raise [ArgumentError] when no block is given. + # @raise [Concurrent::ResourceLimitError] if the maximum number of readers + # is exceeded. + def with_read_lock + raise ArgumentError.new('no block given') unless block_given? + acquire_read_lock + begin + yield + ensure + release_read_lock + end + end + + # Execute a block operation within a write lock. + # + # @yield the task to be performed within the lock. + # + # @return [Object] the result of the block operation. + # + # @raise [ArgumentError] when no block is given. + # @raise [Concurrent::ResourceLimitError] if the maximum number of readers + # is exceeded. + def with_write_lock + raise ArgumentError.new('no block given') unless block_given? + acquire_write_lock + begin + yield + ensure + release_write_lock + end + end + + # Acquire a read lock. If a write lock has been acquired will block until + # it is released. Will not block if other read locks have been acquired. + # + # @return [Boolean] true if the lock is successfully acquired + # + # @raise [Concurrent::ResourceLimitError] if the maximum number of readers + # is exceeded. + def acquire_read_lock + while true + c = @Counter.value + raise ResourceLimitError.new('Too many reader threads') if max_readers?(c) + + # If a writer is waiting when we first queue up, we need to wait + if waiting_writer?(c) + @ReadLock.wait_until { !waiting_writer? } + + # after a reader has waited once, they are allowed to "barge" ahead of waiting writers + # but if a writer is *running*, the reader still needs to wait (naturally) + while true + c = @Counter.value + if running_writer?(c) + @ReadLock.wait_until { !running_writer? } + else + return if @Counter.compare_and_set(c, c+1) + end + end + else + break if @Counter.compare_and_set(c, c+1) + end + end + true + end + + # Release a previously acquired read lock. + # + # @return [Boolean] true if the lock is successfully released + def release_read_lock + while true + c = @Counter.value + if @Counter.compare_and_set(c, c-1) + # If one or more writers were waiting, and we were the last reader, wake a writer up + if waiting_writer?(c) && running_readers(c) == 1 + @WriteLock.signal + end + break + end + end + true + end + + # Acquire a write lock. Will block and wait for all active readers and writers. + # + # @return [Boolean] true if the lock is successfully acquired + # + # @raise [Concurrent::ResourceLimitError] if the maximum number of writers + # is exceeded. + def acquire_write_lock + while true + c = @Counter.value + raise ResourceLimitError.new('Too many writer threads') if max_writers?(c) + + if c == 0 # no readers OR writers running + # if we successfully swap the RUNNING_WRITER bit on, then we can go ahead + break if @Counter.compare_and_set(0, RUNNING_WRITER) + elsif @Counter.compare_and_set(c, c+WAITING_WRITER) + while true + # Now we have successfully incremented, so no more readers will be able to increment + # (they will wait instead) + # However, readers OR writers could decrement right here, OR another writer could increment + @WriteLock.wait_until do + # So we have to do another check inside the synchronized section + # If a writer OR reader is running, then go to sleep + c = @Counter.value + !running_writer?(c) && !running_readers?(c) + end + + # We just came out of a wait + # If we successfully turn the RUNNING_WRITER bit on with an atomic swap, + # Then we are OK to stop waiting and go ahead + # Otherwise go back and wait again + c = @Counter.value + break if !running_writer?(c) && !running_readers?(c) && @Counter.compare_and_set(c, c+RUNNING_WRITER-WAITING_WRITER) + end + break + end + end + true + end + + # Release a previously acquired write lock. + # + # @return [Boolean] true if the lock is successfully released + def release_write_lock + return true unless running_writer? + c = @Counter.update { |counter| counter - RUNNING_WRITER } + @ReadLock.broadcast + @WriteLock.signal if waiting_writers(c) > 0 + true + end + + # Queries if the write lock is held by any thread. + # + # @return [Boolean] true if the write lock is held else false` + def write_locked? + @Counter.value >= RUNNING_WRITER + end + + # Queries whether any threads are waiting to acquire the read or write lock. + # + # @return [Boolean] true if any threads are waiting for a lock else false + def has_waiters? + waiting_writer?(@Counter.value) + end + + private + + # @!visibility private + def running_readers(c = @Counter.value) + c & MAX_READERS + end + + # @!visibility private + def running_readers?(c = @Counter.value) + (c & MAX_READERS) > 0 + end + + # @!visibility private + def running_writer?(c = @Counter.value) + c >= RUNNING_WRITER + end + + # @!visibility private + def waiting_writers(c = @Counter.value) + (c & MAX_WRITERS) / WAITING_WRITER + end + + # @!visibility private + def waiting_writer?(c = @Counter.value) + c >= WAITING_WRITER + end + + # @!visibility private + def max_readers?(c = @Counter.value) + (c & MAX_READERS) == MAX_READERS + end + + # @!visibility private + def max_writers?(c = @Counter.value) + (c & MAX_WRITERS) == MAX_WRITERS + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb new file mode 100644 index 00000000..6d72a3a0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb @@ -0,0 +1,379 @@ +require 'thread' +require 'concurrent/atomic/atomic_reference' +require 'concurrent/atomic/atomic_fixnum' +require 'concurrent/errors' +require 'concurrent/synchronization/object' +require 'concurrent/synchronization/lock' +require 'concurrent/atomic/lock_local_var' + +module Concurrent + + # Re-entrant read-write lock implementation + # + # Allows any number of concurrent readers, but only one concurrent writer + # (And while the "write" lock is taken, no read locks can be obtained either. + # Hence, the write lock can also be called an "exclusive" lock.) + # + # If another thread has taken a read lock, any thread which wants a write lock + # will block until all the readers release their locks. However, once a thread + # starts waiting to obtain a write lock, any additional readers that come along + # will also wait (so writers are not starved). + # + # A thread can acquire both a read and write lock at the same time. A thread can + # also acquire a read lock OR a write lock more than once. Only when the read (or + # write) lock is released as many times as it was acquired, will the thread + # actually let it go, allowing other threads which might have been waiting + # to proceed. Therefore the lock can be upgraded by first acquiring + # read lock and then write lock and that the lock can be downgraded by first + # having both read and write lock a releasing just the write lock. + # + # If both read and write locks are acquired by the same thread, it is not strictly + # necessary to release them in the same order they were acquired. In other words, + # the following code is legal: + # + # @example + # lock = Concurrent::ReentrantReadWriteLock.new + # lock.acquire_write_lock + # lock.acquire_read_lock + # lock.release_write_lock + # # At this point, the current thread is holding only a read lock, not a write + # # lock. So other threads can take read locks, but not a write lock. + # lock.release_read_lock + # # Now the current thread is not holding either a read or write lock, so + # # another thread could potentially acquire a write lock. + # + # This implementation was inspired by `java.util.concurrent.ReentrantReadWriteLock`. + # + # @example + # lock = Concurrent::ReentrantReadWriteLock.new + # lock.with_read_lock { data.retrieve } + # lock.with_write_lock { data.modify! } + # + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html java.util.concurrent.ReentrantReadWriteLock + class ReentrantReadWriteLock < Synchronization::Object + + # Implementation notes: + # + # A goal is to make the uncontended path for both readers/writers mutex-free + # Only if there is reader-writer or writer-writer contention, should mutexes be used + # Otherwise, a single CAS operation is all we need to acquire/release a lock + # + # Internal state is represented by a single integer ("counter"), and updated + # using atomic compare-and-swap operations + # When the counter is 0, the lock is free + # Each thread which has one OR MORE read locks increments the counter by 1 + # (and decrements by 1 when releasing the read lock) + # The counter is increased by (1 << 15) for each writer waiting to acquire the + # write lock, and by (1 << 29) if the write lock is taken + # + # Additionally, each thread uses a thread-local variable to count how many times + # it has acquired a read lock, AND how many times it has acquired a write lock. + # It uses a similar trick; an increment of 1 means a read lock was taken, and + # an increment of (1 << 15) means a write lock was taken + # This is what makes re-entrancy possible + # + # 2 rules are followed to ensure good liveness properties: + # 1) Once a writer has queued up and is waiting for a write lock, no other thread + # can take a lock without waiting + # 2) When a write lock is released, readers are given the "first chance" to wake + # up and acquire a read lock + # Following these rules means readers and writers tend to "take turns", so neither + # can starve the other, even under heavy contention + + # @!visibility private + READER_BITS = 15 + # @!visibility private + WRITER_BITS = 14 + + # Used with @Counter: + # @!visibility private + WAITING_WRITER = 1 << READER_BITS + # @!visibility private + RUNNING_WRITER = 1 << (READER_BITS + WRITER_BITS) + # @!visibility private + MAX_READERS = WAITING_WRITER - 1 + # @!visibility private + MAX_WRITERS = RUNNING_WRITER - MAX_READERS - 1 + + # Used with @HeldCount: + # @!visibility private + WRITE_LOCK_HELD = 1 << READER_BITS + # @!visibility private + READ_LOCK_MASK = WRITE_LOCK_HELD - 1 + # @!visibility private + WRITE_LOCK_MASK = MAX_WRITERS + + safe_initialization! + + # Create a new `ReentrantReadWriteLock` in the unlocked state. + def initialize + super() + @Counter = AtomicFixnum.new(0) # single integer which represents lock state + @ReadQueue = Synchronization::Lock.new # used to queue waiting readers + @WriteQueue = Synchronization::Lock.new # used to queue waiting writers + @HeldCount = LockLocalVar.new(0) # indicates # of R & W locks held by this thread + end + + # Execute a block operation within a read lock. + # + # @yield the task to be performed within the lock. + # + # @return [Object] the result of the block operation. + # + # @raise [ArgumentError] when no block is given. + # @raise [Concurrent::ResourceLimitError] if the maximum number of readers + # is exceeded. + def with_read_lock + raise ArgumentError.new('no block given') unless block_given? + acquire_read_lock + begin + yield + ensure + release_read_lock + end + end + + # Execute a block operation within a write lock. + # + # @yield the task to be performed within the lock. + # + # @return [Object] the result of the block operation. + # + # @raise [ArgumentError] when no block is given. + # @raise [Concurrent::ResourceLimitError] if the maximum number of readers + # is exceeded. + def with_write_lock + raise ArgumentError.new('no block given') unless block_given? + acquire_write_lock + begin + yield + ensure + release_write_lock + end + end + + # Acquire a read lock. If a write lock is held by another thread, will block + # until it is released. + # + # @return [Boolean] true if the lock is successfully acquired + # + # @raise [Concurrent::ResourceLimitError] if the maximum number of readers + # is exceeded. + def acquire_read_lock + if (held = @HeldCount.value) > 0 + # If we already have a lock, there's no need to wait + if held & READ_LOCK_MASK == 0 + # But we do need to update the counter, if we were holding a write + # lock but not a read lock + @Counter.update { |c| c + 1 } + end + @HeldCount.value = held + 1 + return true + end + + while true + c = @Counter.value + raise ResourceLimitError.new('Too many reader threads') if max_readers?(c) + + # If a writer is waiting OR running when we first queue up, we need to wait + if waiting_or_running_writer?(c) + # Before going to sleep, check again with the ReadQueue mutex held + @ReadQueue.synchronize do + @ReadQueue.ns_wait if waiting_or_running_writer? + end + # Note: the above 'synchronize' block could have used #wait_until, + # but that waits repeatedly in a loop, checking the wait condition + # each time it wakes up (to protect against spurious wakeups) + # But we are already in a loop, which is only broken when we successfully + # acquire the lock! So we don't care about spurious wakeups, and would + # rather not pay the extra overhead of using #wait_until + + # After a reader has waited once, they are allowed to "barge" ahead of waiting writers + # But if a writer is *running*, the reader still needs to wait (naturally) + while true + c = @Counter.value + if running_writer?(c) + @ReadQueue.synchronize do + @ReadQueue.ns_wait if running_writer? + end + elsif @Counter.compare_and_set(c, c+1) + @HeldCount.value = held + 1 + return true + end + end + elsif @Counter.compare_and_set(c, c+1) + @HeldCount.value = held + 1 + return true + end + end + end + + # Try to acquire a read lock and return true if we succeed. If it cannot be + # acquired immediately, return false. + # + # @return [Boolean] true if the lock is successfully acquired + def try_read_lock + if (held = @HeldCount.value) > 0 + if held & READ_LOCK_MASK == 0 + # If we hold a write lock, but not a read lock... + @Counter.update { |c| c + 1 } + end + @HeldCount.value = held + 1 + return true + else + c = @Counter.value + if !waiting_or_running_writer?(c) && @Counter.compare_and_set(c, c+1) + @HeldCount.value = held + 1 + return true + end + end + false + end + + # Release a previously acquired read lock. + # + # @return [Boolean] true if the lock is successfully released + def release_read_lock + held = @HeldCount.value = @HeldCount.value - 1 + rlocks_held = held & READ_LOCK_MASK + if rlocks_held == 0 + c = @Counter.update { |counter| counter - 1 } + # If one or more writers were waiting, and we were the last reader, wake a writer up + if waiting_or_running_writer?(c) && running_readers(c) == 0 + @WriteQueue.signal + end + elsif rlocks_held == READ_LOCK_MASK + raise IllegalOperationError, "Cannot release a read lock which is not held" + end + true + end + + # Acquire a write lock. Will block and wait for all active readers and writers. + # + # @return [Boolean] true if the lock is successfully acquired + # + # @raise [Concurrent::ResourceLimitError] if the maximum number of writers + # is exceeded. + def acquire_write_lock + if (held = @HeldCount.value) >= WRITE_LOCK_HELD + # if we already have a write (exclusive) lock, there's no need to wait + @HeldCount.value = held + WRITE_LOCK_HELD + return true + end + + while true + c = @Counter.value + raise ResourceLimitError.new('Too many writer threads') if max_writers?(c) + + # To go ahead and take the lock without waiting, there must be no writer + # running right now, AND no writers who came before us still waiting to + # acquire the lock + # Additionally, if any read locks have been taken, we must hold all of them + if held > 0 && @Counter.compare_and_set(1, c+RUNNING_WRITER) + # If we are the only one reader and successfully swap the RUNNING_WRITER bit on, then we can go ahead + @HeldCount.value = held + WRITE_LOCK_HELD + return true + elsif @Counter.compare_and_set(c, c+WAITING_WRITER) + while true + # Now we have successfully incremented, so no more readers will be able to increment + # (they will wait instead) + # However, readers OR writers could decrement right here + @WriteQueue.synchronize do + # So we have to do another check inside the synchronized section + # If a writer OR another reader is running, then go to sleep + c = @Counter.value + @WriteQueue.ns_wait if running_writer?(c) || running_readers(c) != held + end + # Note: if you are thinking of replacing the above 'synchronize' block + # with #wait_until, read the comment in #acquire_read_lock first! + + # We just came out of a wait + # If we successfully turn the RUNNING_WRITER bit on with an atomic swap, + # then we are OK to stop waiting and go ahead + # Otherwise go back and wait again + c = @Counter.value + if !running_writer?(c) && + running_readers(c) == held && + @Counter.compare_and_set(c, c+RUNNING_WRITER-WAITING_WRITER) + @HeldCount.value = held + WRITE_LOCK_HELD + return true + end + end + end + end + end + + # Try to acquire a write lock and return true if we succeed. If it cannot be + # acquired immediately, return false. + # + # @return [Boolean] true if the lock is successfully acquired + def try_write_lock + if (held = @HeldCount.value) >= WRITE_LOCK_HELD + @HeldCount.value = held + WRITE_LOCK_HELD + return true + else + c = @Counter.value + if !waiting_or_running_writer?(c) && + running_readers(c) == held && + @Counter.compare_and_set(c, c+RUNNING_WRITER) + @HeldCount.value = held + WRITE_LOCK_HELD + return true + end + end + false + end + + # Release a previously acquired write lock. + # + # @return [Boolean] true if the lock is successfully released + def release_write_lock + held = @HeldCount.value = @HeldCount.value - WRITE_LOCK_HELD + wlocks_held = held & WRITE_LOCK_MASK + if wlocks_held == 0 + c = @Counter.update { |counter| counter - RUNNING_WRITER } + @ReadQueue.broadcast + @WriteQueue.signal if waiting_writers(c) > 0 + elsif wlocks_held == WRITE_LOCK_MASK + raise IllegalOperationError, "Cannot release a write lock which is not held" + end + true + end + + private + + # @!visibility private + def running_readers(c = @Counter.value) + c & MAX_READERS + end + + # @!visibility private + def running_readers?(c = @Counter.value) + (c & MAX_READERS) > 0 + end + + # @!visibility private + def running_writer?(c = @Counter.value) + c >= RUNNING_WRITER + end + + # @!visibility private + def waiting_writers(c = @Counter.value) + (c & MAX_WRITERS) >> READER_BITS + end + + # @!visibility private + def waiting_or_running_writer?(c = @Counter.value) + c >= WAITING_WRITER + end + + # @!visibility private + def max_readers?(c = @Counter.value) + (c & MAX_READERS) == MAX_READERS + end + + # @!visibility private + def max_writers?(c = @Counter.value) + (c & MAX_WRITERS) == MAX_WRITERS + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/semaphore.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/semaphore.rb new file mode 100644 index 00000000..f0799f0f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/semaphore.rb @@ -0,0 +1,163 @@ +require 'concurrent/atomic/mutex_semaphore' + +module Concurrent + + ################################################################### + + # @!macro semaphore_method_initialize + # + # Create a new `Semaphore` with the initial `count`. + # + # @param [Fixnum] count the initial count + # + # @raise [ArgumentError] if `count` is not an integer + + # @!macro semaphore_method_acquire + # + # Acquires the given number of permits from this semaphore, + # blocking until all are available. If a block is given, + # yields to it and releases the permits afterwards. + # + # @param [Fixnum] permits Number of permits to acquire + # + # @raise [ArgumentError] if `permits` is not an integer or is less than zero + # + # @return [nil, BasicObject] Without a block, `nil` is returned. If a block + # is given, its return value is returned. + + # @!macro semaphore_method_available_permits + # + # Returns the current number of permits available in this semaphore. + # + # @return [Integer] + + # @!macro semaphore_method_drain_permits + # + # Acquires and returns all permits that are immediately available. + # + # @return [Integer] + + # @!macro semaphore_method_try_acquire + # + # Acquires the given number of permits from this semaphore, + # only if all are available at the time of invocation or within + # `timeout` interval. If a block is given, yields to it if the permits + # were successfully acquired, and releases them afterward, returning the + # block's return value. + # + # @param [Fixnum] permits the number of permits to acquire + # + # @param [Fixnum] timeout the number of seconds to wait for the counter + # or `nil` to return immediately + # + # @raise [ArgumentError] if `permits` is not an integer or is less than zero + # + # @return [true, false, nil, BasicObject] `false` if no permits are + # available, `true` when acquired a permit. If a block is given, the + # block's return value is returned if the permits were acquired; if not, + # `nil` is returned. + + # @!macro semaphore_method_release + # + # Releases the given number of permits, returning them to the semaphore. + # + # @param [Fixnum] permits Number of permits to return to the semaphore. + # + # @raise [ArgumentError] if `permits` is not a number or is less than zero + # + # @return [nil] + + ################################################################### + + # @!macro semaphore_public_api + # + # @!method initialize(count) + # @!macro semaphore_method_initialize + # + # @!method acquire(permits = 1) + # @!macro semaphore_method_acquire + # + # @!method available_permits + # @!macro semaphore_method_available_permits + # + # @!method drain_permits + # @!macro semaphore_method_drain_permits + # + # @!method try_acquire(permits = 1, timeout = nil) + # @!macro semaphore_method_try_acquire + # + # @!method release(permits = 1) + # @!macro semaphore_method_release + + ################################################################### + + # @!visibility private + # @!macro internal_implementation_note + SemaphoreImplementation = if Concurrent.on_jruby? + require 'concurrent/utility/native_extension_loader' + JavaSemaphore + else + MutexSemaphore + end + private_constant :SemaphoreImplementation + + # @!macro semaphore + # + # A counting semaphore. Conceptually, a semaphore maintains a set of + # permits. Each {#acquire} blocks if necessary until a permit is + # available, and then takes it. Each {#release} adds a permit, potentially + # releasing a blocking acquirer. + # However, no actual permit objects are used; the Semaphore just keeps a + # count of the number available and acts accordingly. + # Alternatively, permits may be acquired within a block, and automatically + # released after the block finishes executing. + # + # @!macro semaphore_public_api + # @example + # semaphore = Concurrent::Semaphore.new(2) + # + # t1 = Thread.new do + # semaphore.acquire + # puts "Thread 1 acquired semaphore" + # end + # + # t2 = Thread.new do + # semaphore.acquire + # puts "Thread 2 acquired semaphore" + # end + # + # t3 = Thread.new do + # semaphore.acquire + # puts "Thread 3 acquired semaphore" + # end + # + # t4 = Thread.new do + # sleep(2) + # puts "Thread 4 releasing semaphore" + # semaphore.release + # end + # + # [t1, t2, t3, t4].each(&:join) + # + # # prints: + # # Thread 3 acquired semaphore + # # Thread 2 acquired semaphore + # # Thread 4 releasing semaphore + # # Thread 1 acquired semaphore + # + # @example + # semaphore = Concurrent::Semaphore.new(1) + # + # puts semaphore.available_permits + # semaphore.acquire do + # puts semaphore.available_permits + # end + # puts semaphore.available_permits + # + # # prints: + # # 1 + # # 0 + # # 1 + class Semaphore < SemaphoreImplementation + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb new file mode 100644 index 00000000..3b7e12b5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb @@ -0,0 +1,111 @@ +require 'concurrent/constants' +require_relative 'locals' + +module Concurrent + + # A `ThreadLocalVar` is a variable where the value is different for each thread. + # Each variable may have a default value, but when you modify the variable only + # the current thread will ever see that change. + # + # This is similar to Ruby's built-in thread-local variables (`Thread#thread_variable_get`), + # but with these major advantages: + # * `ThreadLocalVar` has its own identity, it doesn't need a Symbol. + # * Each Ruby's built-in thread-local variable leaks some memory forever (it's a Symbol held forever on the thread), + # so it's only OK to create a small amount of them. + # `ThreadLocalVar` has no such issue and it is fine to create many of them. + # * Ruby's built-in thread-local variables leak forever the value set on each thread (unless set to nil explicitly). + # `ThreadLocalVar` automatically removes the mapping for each thread once the `ThreadLocalVar` instance is GC'd. + # + # @!macro thread_safe_variable_comparison + # + # @example + # v = ThreadLocalVar.new(14) + # v.value #=> 14 + # v.value = 2 + # v.value #=> 2 + # + # @example + # v = ThreadLocalVar.new(14) + # + # t1 = Thread.new do + # v.value #=> 14 + # v.value = 1 + # v.value #=> 1 + # end + # + # t2 = Thread.new do + # v.value #=> 14 + # v.value = 2 + # v.value #=> 2 + # end + # + # v.value #=> 14 + class ThreadLocalVar + LOCALS = ThreadLocals.new + + # Creates a thread local variable. + # + # @param [Object] default the default value when otherwise unset + # @param [Proc] default_block Optional block that gets called to obtain the + # default value for each thread + def initialize(default = nil, &default_block) + if default && block_given? + raise ArgumentError, "Cannot use both value and block as default value" + end + + if block_given? + @default_block = default_block + @default = nil + else + @default_block = nil + @default = default + end + + @index = LOCALS.next_index(self) + end + + # Returns the value in the current thread's copy of this thread-local variable. + # + # @return [Object] the current value + def value + LOCALS.fetch(@index) { default } + end + + # Sets the current thread's copy of this thread-local variable to the specified value. + # + # @param [Object] value the value to set + # @return [Object] the new value + def value=(value) + LOCALS.set(@index, value) + end + + # Bind the given value to thread local storage during + # execution of the given block. + # + # @param [Object] value the value to bind + # @yield the operation to be performed with the bound variable + # @return [Object] the value + def bind(value) + if block_given? + old_value = self.value + self.value = value + begin + yield + ensure + self.value = old_value + end + end + end + + protected + + # @!visibility private + def default + if @default_block + self.value = @default_block.call + else + @default + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic_reference/atomic_direct_update.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic_reference/atomic_direct_update.rb new file mode 100644 index 00000000..5d2d7edd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic_reference/atomic_direct_update.rb @@ -0,0 +1,37 @@ +require 'concurrent/errors' + +module Concurrent + + # Define update methods that use direct paths + # + # @!visibility private + # @!macro internal_implementation_note + module AtomicDirectUpdate + def update + true until compare_and_set(old_value = get, new_value = yield(old_value)) + new_value + end + + def try_update + old_value = get + new_value = yield old_value + + return unless compare_and_set old_value, new_value + + new_value + end + + def try_update! + old_value = get + new_value = yield old_value + unless compare_and_set(old_value, new_value) + if $VERBOSE + raise ConcurrentUpdateError, "Update failed" + else + raise ConcurrentUpdateError, "Update failed", ConcurrentUpdateError::CONC_UP_ERR_BACKTRACE + end + end + new_value + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb new file mode 100644 index 00000000..e5e2a637 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb @@ -0,0 +1,67 @@ +require 'concurrent/atomic_reference/atomic_direct_update' +require 'concurrent/atomic_reference/numeric_cas_wrapper' +require 'concurrent/synchronization/safe_initialization' + +module Concurrent + + # @!visibility private + # @!macro internal_implementation_note + class MutexAtomicReference + extend Concurrent::Synchronization::SafeInitialization + include AtomicDirectUpdate + include AtomicNumericCompareAndSetWrapper + alias_method :compare_and_swap, :compare_and_set + + # @!macro atomic_reference_method_initialize + def initialize(value = nil) + super() + @Lock = ::Mutex.new + @value = value + end + + # @!macro atomic_reference_method_get + def get + synchronize { @value } + end + alias_method :value, :get + + # @!macro atomic_reference_method_set + def set(new_value) + synchronize { @value = new_value } + end + alias_method :value=, :set + + # @!macro atomic_reference_method_get_and_set + def get_and_set(new_value) + synchronize do + old_value = @value + @value = new_value + old_value + end + end + alias_method :swap, :get_and_set + + # @!macro atomic_reference_method_compare_and_set + def _compare_and_set(old_value, new_value) + synchronize do + if @value.equal? old_value + @value = new_value + true + else + false + end + end + end + + protected + + # @!visibility private + def synchronize + if @Lock.owned? + yield + else + @Lock.synchronize { yield } + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic_reference/numeric_cas_wrapper.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic_reference/numeric_cas_wrapper.rb new file mode 100644 index 00000000..709a3822 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomic_reference/numeric_cas_wrapper.rb @@ -0,0 +1,28 @@ +module Concurrent + + # Special "compare and set" handling of numeric values. + # + # @!visibility private + # @!macro internal_implementation_note + module AtomicNumericCompareAndSetWrapper + + # @!macro atomic_reference_method_compare_and_set + def compare_and_set(old_value, new_value) + if old_value.kind_of? Numeric + while true + old = get + + return false unless old.kind_of? Numeric + + return false unless old == old_value + + result = _compare_and_set(old, new_value) + return result if result + end + else + _compare_and_set(old_value, new_value) + end + end + + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomics.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomics.rb new file mode 100644 index 00000000..16cbe661 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/atomics.rb @@ -0,0 +1,10 @@ +require 'concurrent/atomic/atomic_reference' +require 'concurrent/atomic/atomic_boolean' +require 'concurrent/atomic/atomic_fixnum' +require 'concurrent/atomic/cyclic_barrier' +require 'concurrent/atomic/count_down_latch' +require 'concurrent/atomic/event' +require 'concurrent/atomic/read_write_lock' +require 'concurrent/atomic/reentrant_read_write_lock' +require 'concurrent/atomic/semaphore' +require 'concurrent/atomic/thread_local_var' diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb new file mode 100644 index 00000000..7c700bd7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb @@ -0,0 +1,107 @@ +require 'concurrent/synchronization/lockable_object' + +module Concurrent + module Collection + + # A thread safe observer set implemented using copy-on-read approach: + # observers are added and removed from a thread safe collection; every time + # a notification is required the internal data structure is copied to + # prevent concurrency issues + # + # @api private + class CopyOnNotifyObserverSet < Synchronization::LockableObject + + def initialize + super() + synchronize { ns_initialize } + end + + # @!macro observable_add_observer + def add_observer(observer = nil, func = :update, &block) + if observer.nil? && block.nil? + raise ArgumentError, 'should pass observer as a first argument or block' + elsif observer && block + raise ArgumentError.new('cannot provide both an observer and a block') + end + + if block + observer = block + func = :call + end + + synchronize do + @observers[observer] = func + observer + end + end + + # @!macro observable_delete_observer + def delete_observer(observer) + synchronize do + @observers.delete(observer) + observer + end + end + + # @!macro observable_delete_observers + def delete_observers + synchronize do + @observers.clear + self + end + end + + # @!macro observable_count_observers + def count_observers + synchronize { @observers.count } + end + + # Notifies all registered observers with optional args + # @param [Object] args arguments to be passed to each observer + # @return [CopyOnWriteObserverSet] self + def notify_observers(*args, &block) + observers = duplicate_observers + notify_to(observers, *args, &block) + self + end + + # Notifies all registered observers with optional args and deletes them. + # + # @param [Object] args arguments to be passed to each observer + # @return [CopyOnWriteObserverSet] self + def notify_and_delete_observers(*args, &block) + observers = duplicate_and_clear_observers + notify_to(observers, *args, &block) + self + end + + protected + + def ns_initialize + @observers = {} + end + + private + + def duplicate_and_clear_observers + synchronize do + observers = @observers.dup + @observers.clear + observers + end + end + + def duplicate_observers + synchronize { @observers.dup } + end + + def notify_to(observers, *args) + raise ArgumentError.new('cannot give arguments and a block') if block_given? && !args.empty? + observers.each do |observer, function| + args = yield if block_given? + observer.send(function, *args) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb new file mode 100644 index 00000000..bcb6750d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb @@ -0,0 +1,111 @@ +require 'concurrent/synchronization/lockable_object' + +module Concurrent + module Collection + + # A thread safe observer set implemented using copy-on-write approach: + # every time an observer is added or removed the whole internal data structure is + # duplicated and replaced with a new one. + # + # @api private + class CopyOnWriteObserverSet < Synchronization::LockableObject + + def initialize + super() + synchronize { ns_initialize } + end + + # @!macro observable_add_observer + def add_observer(observer = nil, func = :update, &block) + if observer.nil? && block.nil? + raise ArgumentError, 'should pass observer as a first argument or block' + elsif observer && block + raise ArgumentError.new('cannot provide both an observer and a block') + end + + if block + observer = block + func = :call + end + + synchronize do + new_observers = @observers.dup + new_observers[observer] = func + @observers = new_observers + observer + end + end + + # @!macro observable_delete_observer + def delete_observer(observer) + synchronize do + new_observers = @observers.dup + new_observers.delete(observer) + @observers = new_observers + observer + end + end + + # @!macro observable_delete_observers + def delete_observers + self.observers = {} + self + end + + # @!macro observable_count_observers + def count_observers + observers.count + end + + # Notifies all registered observers with optional args + # @param [Object] args arguments to be passed to each observer + # @return [CopyOnWriteObserverSet] self + def notify_observers(*args, &block) + notify_to(observers, *args, &block) + self + end + + # Notifies all registered observers with optional args and deletes them. + # + # @param [Object] args arguments to be passed to each observer + # @return [CopyOnWriteObserverSet] self + def notify_and_delete_observers(*args, &block) + old = clear_observers_and_return_old + notify_to(old, *args, &block) + self + end + + protected + + def ns_initialize + @observers = {} + end + + private + + def notify_to(observers, *args) + raise ArgumentError.new('cannot give arguments and a block') if block_given? && !args.empty? + observers.each do |observer, function| + args = yield if block_given? + observer.send(function, *args) + end + end + + def observers + synchronize { @observers } + end + + def observers=(new_set) + synchronize { @observers = new_set } + end + + def clear_observers_and_return_old + synchronize do + old_observers = @observers + @observers = {} + old_observers + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/java_non_concurrent_priority_queue.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/java_non_concurrent_priority_queue.rb new file mode 100644 index 00000000..2be9e437 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/java_non_concurrent_priority_queue.rb @@ -0,0 +1,84 @@ +if Concurrent.on_jruby? + + module Concurrent + module Collection + + + # @!macro priority_queue + # + # @!visibility private + # @!macro internal_implementation_note + class JavaNonConcurrentPriorityQueue + + # @!macro priority_queue_method_initialize + def initialize(opts = {}) + order = opts.fetch(:order, :max) + if [:min, :low].include?(order) + @queue = java.util.PriorityQueue.new(11) # 11 is the default initial capacity + else + @queue = java.util.PriorityQueue.new(11, java.util.Collections.reverseOrder()) + end + end + + # @!macro priority_queue_method_clear + def clear + @queue.clear + true + end + + # @!macro priority_queue_method_delete + def delete(item) + found = false + while @queue.remove(item) do + found = true + end + found + end + + # @!macro priority_queue_method_empty + def empty? + @queue.size == 0 + end + + # @!macro priority_queue_method_include + def include?(item) + @queue.contains(item) + end + alias_method :has_priority?, :include? + + # @!macro priority_queue_method_length + def length + @queue.size + end + alias_method :size, :length + + # @!macro priority_queue_method_peek + def peek + @queue.peek + end + + # @!macro priority_queue_method_pop + def pop + @queue.poll + end + alias_method :deq, :pop + alias_method :shift, :pop + + # @!macro priority_queue_method_push + def push(item) + raise ArgumentError.new('cannot enqueue nil') if item.nil? + @queue.add(item) + end + alias_method :<<, :push + alias_method :enq, :push + + # @!macro priority_queue_method_from_list + def self.from_list(list, opts = {}) + queue = new(opts) + list.each{|item| queue << item } + queue + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb new file mode 100644 index 00000000..3704410b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb @@ -0,0 +1,160 @@ +require 'concurrent/synchronization/object' + +module Concurrent + + # @!macro warn.edge + class LockFreeStack < Synchronization::Object + + safe_initialization! + + class Node + # TODO (pitr-ch 20-Dec-2016): Could be unified with Stack class? + + # @return [Node] + attr_reader :next_node + + # @return [Object] + attr_reader :value + + # @!visibility private + # allow to nil-ify to free GC when the entry is no longer relevant, not synchronised + attr_writer :value + + def initialize(value, next_node) + @value = value + @next_node = next_node + end + + singleton_class.send :alias_method, :[], :new + end + + # The singleton for empty node + EMPTY = Node[nil, nil] + def EMPTY.next_node + self + end + + attr_atomic(:head) + private :head, :head=, :swap_head, :compare_and_set_head, :update_head + + # @!visibility private + def self.of1(value) + new Node[value, EMPTY] + end + + # @!visibility private + def self.of2(value1, value2) + new Node[value1, Node[value2, EMPTY]] + end + + # @param [Node] head + def initialize(head = EMPTY) + super() + self.head = head + end + + # @param [Node] head + # @return [true, false] + def empty?(head = head()) + head.equal? EMPTY + end + + # @param [Node] head + # @param [Object] value + # @return [true, false] + def compare_and_push(head, value) + compare_and_set_head head, Node[value, head] + end + + # @param [Object] value + # @return [self] + def push(value) + while true + current_head = head + return self if compare_and_set_head current_head, Node[value, current_head] + end + end + + # @return [Node] + def peek + head + end + + # @param [Node] head + # @return [true, false] + def compare_and_pop(head) + compare_and_set_head head, head.next_node + end + + # @return [Object] + def pop + while true + current_head = head + return current_head.value if compare_and_set_head current_head, current_head.next_node + end + end + + # @param [Node] head + # @return [true, false] + def compare_and_clear(head) + compare_and_set_head head, EMPTY + end + + include Enumerable + + # @param [Node] head + # @return [self] + def each(head = nil) + return to_enum(:each, head) unless block_given? + it = head || peek + until it.equal?(EMPTY) + yield it.value + it = it.next_node + end + self + end + + # @return [true, false] + def clear + while true + current_head = head + return false if current_head == EMPTY + return true if compare_and_set_head current_head, EMPTY + end + end + + # @param [Node] head + # @return [true, false] + def clear_if(head) + compare_and_set_head head, EMPTY + end + + # @param [Node] head + # @param [Node] new_head + # @return [true, false] + def replace_if(head, new_head) + compare_and_set_head head, new_head + end + + # @return [self] + # @yield over the cleared stack + # @yieldparam [Object] value + def clear_each(&block) + while true + current_head = head + return self if current_head == EMPTY + if compare_and_set_head current_head, EMPTY + each current_head, &block + return self + end + end + end + + # @return [String] Short string representation. + def to_s + format '%s %s>', super[0..-2], to_a.to_s + end + + alias_method :inspect, :to_s + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb new file mode 100644 index 00000000..e0cf9990 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb @@ -0,0 +1,66 @@ +require 'thread' +require 'concurrent/collection/map/non_concurrent_map_backend' + +module Concurrent + + # @!visibility private + module Collection + + # @!visibility private + class MriMapBackend < NonConcurrentMapBackend + + def initialize(options = nil, &default_proc) + super(options, &default_proc) + @write_lock = Mutex.new + end + + def []=(key, value) + @write_lock.synchronize { super } + end + + def compute_if_absent(key) + if NULL != (stored_value = @backend.fetch(key, NULL)) # fast non-blocking path for the most likely case + stored_value + else + @write_lock.synchronize { super } + end + end + + def compute_if_present(key) + @write_lock.synchronize { super } + end + + def compute(key) + @write_lock.synchronize { super } + end + + def merge_pair(key, value) + @write_lock.synchronize { super } + end + + def replace_pair(key, old_value, new_value) + @write_lock.synchronize { super } + end + + def replace_if_exists(key, new_value) + @write_lock.synchronize { super } + end + + def get_and_set(key, value) + @write_lock.synchronize { super } + end + + def delete(key) + @write_lock.synchronize { super } + end + + def delete_pair(key, value) + @write_lock.synchronize { super } + end + + def clear + @write_lock.synchronize { super } + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb new file mode 100644 index 00000000..ca5fd9b4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb @@ -0,0 +1,148 @@ +require 'concurrent/constants' + +module Concurrent + + # @!visibility private + module Collection + + # @!visibility private + class NonConcurrentMapBackend + + # WARNING: all public methods of the class must operate on the @backend + # directly without calling each other. This is important because of the + # SynchronizedMapBackend which uses a non-reentrant mutex for performance + # reasons. + def initialize(options = nil, &default_proc) + validate_options_hash!(options) if options.kind_of?(::Hash) + set_backend(default_proc) + @default_proc = default_proc + end + + def [](key) + @backend[key] + end + + def []=(key, value) + @backend[key] = value + end + + def compute_if_absent(key) + if NULL != (stored_value = @backend.fetch(key, NULL)) + stored_value + else + @backend[key] = yield + end + end + + def replace_pair(key, old_value, new_value) + if pair?(key, old_value) + @backend[key] = new_value + true + else + false + end + end + + def replace_if_exists(key, new_value) + if NULL != (stored_value = @backend.fetch(key, NULL)) + @backend[key] = new_value + stored_value + end + end + + def compute_if_present(key) + if NULL != (stored_value = @backend.fetch(key, NULL)) + store_computed_value(key, yield(stored_value)) + end + end + + def compute(key) + store_computed_value(key, yield(get_or_default(key, nil))) + end + + def merge_pair(key, value) + if NULL == (stored_value = @backend.fetch(key, NULL)) + @backend[key] = value + else + store_computed_value(key, yield(stored_value)) + end + end + + def get_and_set(key, value) + stored_value = get_or_default(key, nil) + @backend[key] = value + stored_value + end + + def key?(key) + @backend.key?(key) + end + + def delete(key) + @backend.delete(key) + end + + def delete_pair(key, value) + if pair?(key, value) + @backend.delete(key) + true + else + false + end + end + + def clear + @backend.clear + self + end + + def each_pair + dupped_backend.each_pair do |k, v| + yield k, v + end + self + end + + def size + @backend.size + end + + def get_or_default(key, default_value) + @backend.fetch(key, default_value) + end + + private + + def set_backend(default_proc) + if default_proc + @backend = ::Hash.new { |_h, key| default_proc.call(self, key) } + else + @backend = {} + end + end + + def initialize_copy(other) + super + set_backend(@default_proc) + self + end + + def dupped_backend + @backend.dup + end + + def pair?(key, expected_value) + NULL != (stored_value = @backend.fetch(key, NULL)) && expected_value.equal?(stored_value) + end + + def store_computed_value(key, new_value) + if new_value.nil? + @backend.delete(key) + nil + else + @backend[key] = new_value + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb new file mode 100644 index 00000000..efa161ed --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb @@ -0,0 +1,85 @@ +require 'concurrent/collection/map/non_concurrent_map_backend' + +module Concurrent + + # @!visibility private + module Collection + + # @!visibility private + class SynchronizedMapBackend < NonConcurrentMapBackend + + def initialize(*args, &block) + super + + # WARNING: Mutex is a non-reentrant lock, so the synchronized methods are + # not allowed to call each other. + @mutex = Mutex.new + end + + def [](key) + @mutex.synchronize { super } + end + + def []=(key, value) + @mutex.synchronize { super } + end + + def compute_if_absent(key) + @mutex.synchronize { super } + end + + def compute_if_present(key) + @mutex.synchronize { super } + end + + def compute(key) + @mutex.synchronize { super } + end + + def merge_pair(key, value) + @mutex.synchronize { super } + end + + def replace_pair(key, old_value, new_value) + @mutex.synchronize { super } + end + + def replace_if_exists(key, new_value) + @mutex.synchronize { super } + end + + def get_and_set(key, value) + @mutex.synchronize { super } + end + + def key?(key) + @mutex.synchronize { super } + end + + def delete(key) + @mutex.synchronize { super } + end + + def delete_pair(key, value) + @mutex.synchronize { super } + end + + def clear + @mutex.synchronize { super } + end + + def size + @mutex.synchronize { super } + end + + def get_or_default(key, default_value) + @mutex.synchronize { super } + end + + private + def dupped_backend + @mutex.synchronize { super } + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb new file mode 100644 index 00000000..68a1b388 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb @@ -0,0 +1,14 @@ +module Concurrent + + # @!visibility private + module Collection + + # @!visibility private + class TruffleRubyMapBackend < TruffleRuby::ConcurrentMap + def initialize(options = nil) + options ||= {} + super(initial_capacity: options[:initial_capacity], load_factor: options[:load_factor]) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/non_concurrent_priority_queue.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/non_concurrent_priority_queue.rb new file mode 100644 index 00000000..694cd7ac --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/non_concurrent_priority_queue.rb @@ -0,0 +1,143 @@ +require 'concurrent/utility/engine' +require 'concurrent/collection/java_non_concurrent_priority_queue' +require 'concurrent/collection/ruby_non_concurrent_priority_queue' + +module Concurrent + module Collection + + # @!visibility private + # @!macro internal_implementation_note + NonConcurrentPriorityQueueImplementation = case + when Concurrent.on_jruby? + JavaNonConcurrentPriorityQueue + else + RubyNonConcurrentPriorityQueue + end + private_constant :NonConcurrentPriorityQueueImplementation + + # @!macro priority_queue + # + # A queue collection in which the elements are sorted based on their + # comparison (spaceship) operator `<=>`. Items are added to the queue + # at a position relative to their priority. On removal the element + # with the "highest" priority is removed. By default the sort order is + # from highest to lowest, but a lowest-to-highest sort order can be + # set on construction. + # + # The API is based on the `Queue` class from the Ruby standard library. + # + # The pure Ruby implementation, `RubyNonConcurrentPriorityQueue` uses a heap algorithm + # stored in an array. The algorithm is based on the work of Robert Sedgewick + # and Kevin Wayne. + # + # The JRuby native implementation is a thin wrapper around the standard + # library `java.util.NonConcurrentPriorityQueue`. + # + # When running under JRuby the class `NonConcurrentPriorityQueue` extends `JavaNonConcurrentPriorityQueue`. + # When running under all other interpreters it extends `RubyNonConcurrentPriorityQueue`. + # + # @note This implementation is *not* thread safe. + # + # @see http://en.wikipedia.org/wiki/Priority_queue + # @see http://ruby-doc.org/stdlib-2.0.0/libdoc/thread/rdoc/Queue.html + # + # @see http://algs4.cs.princeton.edu/24pq/index.php#2.6 + # @see http://algs4.cs.princeton.edu/24pq/MaxPQ.java.html + # + # @see http://docs.oracle.com/javase/7/docs/api/java/util/PriorityQueue.html + # + # @!visibility private + class NonConcurrentPriorityQueue < NonConcurrentPriorityQueueImplementation + + alias_method :has_priority?, :include? + + alias_method :size, :length + + alias_method :deq, :pop + alias_method :shift, :pop + + alias_method :<<, :push + alias_method :enq, :push + + # @!method initialize(opts = {}) + # @!macro priority_queue_method_initialize + # + # Create a new priority queue with no items. + # + # @param [Hash] opts the options for creating the queue + # @option opts [Symbol] :order (:max) dictates the order in which items are + # stored: from highest to lowest when `:max` or `:high`; from lowest to + # highest when `:min` or `:low` + + # @!method clear + # @!macro priority_queue_method_clear + # + # Removes all of the elements from this priority queue. + + # @!method delete(item) + # @!macro priority_queue_method_delete + # + # Deletes all items from `self` that are equal to `item`. + # + # @param [Object] item the item to be removed from the queue + # @return [Object] true if the item is found else false + + # @!method empty? + # @!macro priority_queue_method_empty + # + # Returns `true` if `self` contains no elements. + # + # @return [Boolean] true if there are no items in the queue else false + + # @!method include?(item) + # @!macro priority_queue_method_include + # + # Returns `true` if the given item is present in `self` (that is, if any + # element == `item`), otherwise returns false. + # + # @param [Object] item the item to search for + # + # @return [Boolean] true if the item is found else false + + # @!method length + # @!macro priority_queue_method_length + # + # The current length of the queue. + # + # @return [Fixnum] the number of items in the queue + + # @!method peek + # @!macro priority_queue_method_peek + # + # Retrieves, but does not remove, the head of this queue, or returns `nil` + # if this queue is empty. + # + # @return [Object] the head of the queue or `nil` when empty + + # @!method pop + # @!macro priority_queue_method_pop + # + # Retrieves and removes the head of this queue, or returns `nil` if this + # queue is empty. + # + # @return [Object] the head of the queue or `nil` when empty + + # @!method push(item) + # @!macro priority_queue_method_push + # + # Inserts the specified element into this priority queue. + # + # @param [Object] item the item to insert onto the queue + + # @!method self.from_list(list, opts = {}) + # @!macro priority_queue_method_from_list + # + # Create a new priority queue from the given list. + # + # @param [Enumerable] list the list to build the queue from + # @param [Hash] opts the options for creating the queue + # + # @return [NonConcurrentPriorityQueue] the newly created and populated queue + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/ruby_non_concurrent_priority_queue.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/ruby_non_concurrent_priority_queue.rb new file mode 100644 index 00000000..322b4ac2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/ruby_non_concurrent_priority_queue.rb @@ -0,0 +1,160 @@ +module Concurrent + module Collection + + # @!macro priority_queue + # + # @!visibility private + # @!macro internal_implementation_note + class RubyNonConcurrentPriorityQueue + + # @!macro priority_queue_method_initialize + def initialize(opts = {}) + order = opts.fetch(:order, :max) + @comparator = [:min, :low].include?(order) ? -1 : 1 + clear + end + + # @!macro priority_queue_method_clear + def clear + @queue = [nil] + @length = 0 + true + end + + # @!macro priority_queue_method_delete + def delete(item) + return false if empty? + original_length = @length + k = 1 + while k <= @length + if @queue[k] == item + swap(k, @length) + @length -= 1 + sink(k) || swim(k) + @queue.pop + else + k += 1 + end + end + @length != original_length + end + + # @!macro priority_queue_method_empty + def empty? + size == 0 + end + + # @!macro priority_queue_method_include + def include?(item) + @queue.include?(item) + end + alias_method :has_priority?, :include? + + # @!macro priority_queue_method_length + def length + @length + end + alias_method :size, :length + + # @!macro priority_queue_method_peek + def peek + empty? ? nil : @queue[1] + end + + # @!macro priority_queue_method_pop + def pop + return nil if empty? + max = @queue[1] + swap(1, @length) + @length -= 1 + sink(1) + @queue.pop + max + end + alias_method :deq, :pop + alias_method :shift, :pop + + # @!macro priority_queue_method_push + def push(item) + raise ArgumentError.new('cannot enqueue nil') if item.nil? + @length += 1 + @queue << item + swim(@length) + true + end + alias_method :<<, :push + alias_method :enq, :push + + # @!macro priority_queue_method_from_list + def self.from_list(list, opts = {}) + queue = new(opts) + list.each{|item| queue << item } + queue + end + + private + + # Exchange the values at the given indexes within the internal array. + # + # @param [Integer] x the first index to swap + # @param [Integer] y the second index to swap + # + # @!visibility private + def swap(x, y) + temp = @queue[x] + @queue[x] = @queue[y] + @queue[y] = temp + end + + # Are the items at the given indexes ordered based on the priority + # order specified at construction? + # + # @param [Integer] x the first index from which to retrieve a comparable value + # @param [Integer] y the second index from which to retrieve a comparable value + # + # @return [Boolean] true if the two elements are in the correct priority order + # else false + # + # @!visibility private + def ordered?(x, y) + (@queue[x] <=> @queue[y]) == @comparator + end + + # Percolate down to maintain heap invariant. + # + # @param [Integer] k the index at which to start the percolation + # + # @!visibility private + def sink(k) + success = false + + while (j = (2 * k)) <= @length do + j += 1 if j < @length && ! ordered?(j, j+1) + break if ordered?(k, j) + swap(k, j) + success = true + k = j + end + + success + end + + # Percolate up to maintain heap invariant. + # + # @param [Integer] k the index at which to start the percolation + # + # @!visibility private + def swim(k) + success = false + + while k > 1 && ! ordered?(k/2, k) do + swap(k, k/2) + k = k/2 + success = true + end + + success + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/concern/deprecation.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/concern/deprecation.rb new file mode 100644 index 00000000..35ae4b2c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/concern/deprecation.rb @@ -0,0 +1,34 @@ +require 'concurrent/concern/logging' + +module Concurrent + module Concern + + # @!visibility private + # @!macro internal_implementation_note + module Deprecation + # TODO require additional parameter: a version. Display when it'll be removed based on that. Error if not removed. + include Concern::Logging + + def deprecated(message, strip = 2) + caller_line = caller(strip).first if strip > 0 + klass = if Module === self + self + else + self.class + end + message = if strip > 0 + format("[DEPRECATED] %s\ncalled on: %s", message, caller_line) + else + format('[DEPRECATED] %s', message) + end + log WARN, klass.to_s, message + end + + def deprecated_method(old_name, new_name) + deprecated "`#{old_name}` is deprecated and it'll removed in next release, use `#{new_name}` instead", 3 + end + + extend self + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/concern/dereferenceable.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/concern/dereferenceable.rb new file mode 100644 index 00000000..dc172ba7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/concern/dereferenceable.rb @@ -0,0 +1,73 @@ +module Concurrent + module Concern + + # Object references in Ruby are mutable. This can lead to serious problems when + # the `#value` of a concurrent object is a mutable reference. Which is always the + # case unless the value is a `Fixnum`, `Symbol`, or similar "primitive" data type. + # Most classes in this library that expose a `#value` getter method do so using the + # `Dereferenceable` mixin module. + # + # @!macro copy_options + module Dereferenceable + # NOTE: This module is going away in 2.0. In the mean time we need it to + # play nicely with the synchronization layer. This means that the + # including class SHOULD be synchronized and it MUST implement a + # `#synchronize` method. Not doing so will lead to runtime errors. + + # Return the value this object represents after applying the options specified + # by the `#set_deref_options` method. + # + # @return [Object] the current value of the object + def value + synchronize { apply_deref_options(@value) } + end + alias_method :deref, :value + + protected + + # Set the internal value of this object + # + # @param [Object] value the new value + def value=(value) + synchronize{ @value = value } + end + + # @!macro dereferenceable_set_deref_options + # Set the options which define the operations #value performs before + # returning data to the caller (dereferencing). + # + # @note Most classes that include this module will call `#set_deref_options` + # from within the constructor, thus allowing these options to be set at + # object creation. + # + # @param [Hash] opts the options defining dereference behavior. + # @option opts [String] :dup_on_deref (false) call `#dup` before returning the data + # @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data + # @option opts [String] :copy_on_deref (nil) call the given `Proc` passing + # the internal value and returning the value returned from the proc + def set_deref_options(opts = {}) + synchronize{ ns_set_deref_options(opts) } + end + + # @!macro dereferenceable_set_deref_options + # @!visibility private + def ns_set_deref_options(opts) + @dup_on_deref = opts[:dup_on_deref] || opts[:dup] + @freeze_on_deref = opts[:freeze_on_deref] || opts[:freeze] + @copy_on_deref = opts[:copy_on_deref] || opts[:copy] + @do_nothing_on_deref = !(@dup_on_deref || @freeze_on_deref || @copy_on_deref) + nil + end + + # @!visibility private + def apply_deref_options(value) + return nil if value.nil? + return value if @do_nothing_on_deref + value = @copy_on_deref.call(value) if @copy_on_deref + value = value.dup if @dup_on_deref + value = value.freeze if @freeze_on_deref + value + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/concern/logging.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/concern/logging.rb new file mode 100644 index 00000000..d1aae81a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/concern/logging.rb @@ -0,0 +1,121 @@ +require 'concurrent/atomic/atomic_reference' + +module Concurrent + module Concern + + # Include where logging is needed + # + # @!visibility private + module Logging + # The same as Logger::Severity but we copy it here to avoid a dependency on the logger gem just for these 7 constants + DEBUG, INFO, WARN, ERROR, FATAL, UNKNOWN = 0, 1, 2, 3, 4, 5 + SEV_LABEL = %w[DEBUG INFO WARN ERROR FATAL ANY].freeze + + # Logs through {Concurrent.global_logger}, it can be overridden by setting @logger + # @param [Integer] level one of Concurrent::Concern::Logging constants + # @param [String] progname e.g. a path of an Actor + # @param [String, nil] message when nil block is used to generate the message + # @yieldreturn [String] a message + def log(level, progname, message = nil, &block) + logger = if defined?(@logger) && @logger + @logger + else + Concurrent.global_logger + end + logger.call level, progname, message, &block + rescue => error + $stderr.puts "`Concurrent.global_logger` failed to log #{[level, progname, message, block]}\n" + + "#{error.message} (#{error.class})\n#{error.backtrace.join "\n"}" + end + end + end +end + +module Concurrent + extend Concern::Logging + + # Create a simple logger with provided level and output. + def self.create_simple_logger(level = :FATAL, output = $stderr) + level = Concern::Logging.const_get(level) unless level.is_a?(Integer) + + # TODO (pitr-ch 24-Dec-2016): figure out why it had to be replaced, stdlogger was deadlocking + lambda do |severity, progname, message = nil, &block| + return false if severity < level + + message = block ? block.call : message + formatted_message = case message + when String + message + when Exception + format "%s (%s)\n%s", + message.message, message.class, (message.backtrace || []).join("\n") + else + message.inspect + end + + output.print format "[%s] %5s -- %s: %s\n", + Time.now.strftime('%Y-%m-%d %H:%M:%S.%L'), + Concern::Logging::SEV_LABEL[severity], + progname, + formatted_message + true + end + end + + # Use logger created by #create_simple_logger to log concurrent-ruby messages. + def self.use_simple_logger(level = :FATAL, output = $stderr) + Concurrent.global_logger = create_simple_logger level, output + end + + # Create a stdlib logger with provided level and output. + # If you use this deprecated method you might need to add logger to your Gemfile to avoid warnings from Ruby 3.3.5+. + # @deprecated + def self.create_stdlib_logger(level = :FATAL, output = $stderr) + require 'logger' + logger = Logger.new(output) + logger.level = level + logger.formatter = lambda do |severity, datetime, progname, msg| + formatted_message = case msg + when String + msg + when Exception + format "%s (%s)\n%s", + msg.message, msg.class, (msg.backtrace || []).join("\n") + else + msg.inspect + end + format "[%s] %5s -- %s: %s\n", + datetime.strftime('%Y-%m-%d %H:%M:%S.%L'), + severity, + progname, + formatted_message + end + + lambda do |loglevel, progname, message = nil, &block| + logger.add loglevel, message, progname, &block + end + end + + # Use logger created by #create_stdlib_logger to log concurrent-ruby messages. + # @deprecated + def self.use_stdlib_logger(level = :FATAL, output = $stderr) + Concurrent.global_logger = create_stdlib_logger level, output + end + + # TODO (pitr-ch 27-Dec-2016): remove deadlocking stdlib_logger methods + + # Suppresses all output when used for logging. + NULL_LOGGER = lambda { |level, progname, message = nil, &block| } + + # @!visibility private + GLOBAL_LOGGER = AtomicReference.new(create_simple_logger(:WARN)) + private_constant :GLOBAL_LOGGER + + def self.global_logger + GLOBAL_LOGGER.value + end + + def self.global_logger=(value) + GLOBAL_LOGGER.value = value + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/concern/obligation.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/concern/obligation.rb new file mode 100644 index 00000000..2c9ac120 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/concern/obligation.rb @@ -0,0 +1,220 @@ +require 'thread' +require 'timeout' + +require 'concurrent/atomic/event' +require 'concurrent/concern/dereferenceable' + +module Concurrent + module Concern + + module Obligation + include Concern::Dereferenceable + # NOTE: The Dereferenceable module is going away in 2.0. In the mean time + # we need it to place nicely with the synchronization layer. This means + # that the including class SHOULD be synchronized and it MUST implement a + # `#synchronize` method. Not doing so will lead to runtime errors. + + # Has the obligation been fulfilled? + # + # @return [Boolean] + def fulfilled? + state == :fulfilled + end + alias_method :realized?, :fulfilled? + + # Has the obligation been rejected? + # + # @return [Boolean] + def rejected? + state == :rejected + end + + # Is obligation completion still pending? + # + # @return [Boolean] + def pending? + state == :pending + end + + # Is the obligation still unscheduled? + # + # @return [Boolean] + def unscheduled? + state == :unscheduled + end + + # Has the obligation completed processing? + # + # @return [Boolean] + def complete? + [:fulfilled, :rejected].include? state + end + + # Is the obligation still awaiting completion of processing? + # + # @return [Boolean] + def incomplete? + ! complete? + end + + # The current value of the obligation. Will be `nil` while the state is + # pending or the operation has been rejected. + # + # @param [Numeric] timeout the maximum time in seconds to wait. + # @return [Object] see Dereferenceable#deref + def value(timeout = nil) + wait timeout + deref + end + + # Wait until obligation is complete or the timeout has been reached. + # + # @param [Numeric] timeout the maximum time in seconds to wait. + # @return [Obligation] self + def wait(timeout = nil) + event.wait(timeout) if timeout != 0 && incomplete? + self + end + + # Wait until obligation is complete or the timeout is reached. Will re-raise + # any exceptions raised during processing (but will not raise an exception + # on timeout). + # + # @param [Numeric] timeout the maximum time in seconds to wait. + # @return [Obligation] self + # @raise [Exception] raises the reason when rejected + def wait!(timeout = nil) + wait(timeout).tap { raise self if rejected? } + end + alias_method :no_error!, :wait! + + # The current value of the obligation. Will be `nil` while the state is + # pending or the operation has been rejected. Will re-raise any exceptions + # raised during processing (but will not raise an exception on timeout). + # + # @param [Numeric] timeout the maximum time in seconds to wait. + # @return [Object] see Dereferenceable#deref + # @raise [Exception] raises the reason when rejected + def value!(timeout = nil) + wait(timeout) + if rejected? + raise self + else + deref + end + end + + # The current state of the obligation. + # + # @return [Symbol] the current state + def state + synchronize { @state } + end + + # If an exception was raised during processing this will return the + # exception object. Will return `nil` when the state is pending or if + # the obligation has been successfully fulfilled. + # + # @return [Exception] the exception raised during processing or `nil` + def reason + synchronize { @reason } + end + + # @example allows Obligation to be risen + # rejected_ivar = Ivar.new.fail + # raise rejected_ivar + def exception(*args) + raise 'obligation is not rejected' unless rejected? + reason.exception(*args) + end + + protected + + # @!visibility private + def get_arguments_from(opts = {}) + [*opts.fetch(:args, [])] + end + + # @!visibility private + def init_obligation + @event = Event.new + @value = @reason = nil + end + + # @!visibility private + def event + @event + end + + # @!visibility private + def set_state(success, value, reason) + if success + @value = value + @state = :fulfilled + else + @reason = reason + @state = :rejected + end + end + + # @!visibility private + def state=(value) + synchronize { ns_set_state(value) } + end + + # Atomic compare and set operation + # State is set to `next_state` only if `current state == expected_current`. + # + # @param [Symbol] next_state + # @param [Symbol] expected_current + # + # @return [Boolean] true is state is changed, false otherwise + # + # @!visibility private + def compare_and_set_state(next_state, *expected_current) + synchronize do + if expected_current.include? @state + @state = next_state + true + else + false + end + end + end + + # Executes the block within mutex if current state is included in expected_states + # + # @return block value if executed, false otherwise + # + # @!visibility private + def if_state(*expected_states) + synchronize do + raise ArgumentError.new('no block given') unless block_given? + + if expected_states.include? @state + yield + else + false + end + end + end + + protected + + # Am I in the current state? + # + # @param [Symbol] expected The state to check against + # @return [Boolean] true if in the expected state else false + # + # @!visibility private + def ns_check_state?(expected) + @state == expected + end + + # @!visibility private + def ns_set_state(value) + @state = value + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/concern/observable.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/concern/observable.rb new file mode 100644 index 00000000..b5132714 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/concern/observable.rb @@ -0,0 +1,110 @@ +require 'concurrent/collection/copy_on_notify_observer_set' +require 'concurrent/collection/copy_on_write_observer_set' + +module Concurrent + module Concern + + # The [observer pattern](http://en.wikipedia.org/wiki/Observer_pattern) is one + # of the most useful design patterns. + # + # The workflow is very simple: + # - an `observer` can register itself to a `subject` via a callback + # - many `observers` can be registered to the same `subject` + # - the `subject` notifies all registered observers when its status changes + # - an `observer` can deregister itself when is no more interested to receive + # event notifications + # + # In a single threaded environment the whole pattern is very easy: the + # `subject` can use a simple data structure to manage all its subscribed + # `observer`s and every `observer` can react directly to every event without + # caring about synchronization. + # + # In a multi threaded environment things are more complex. The `subject` must + # synchronize the access to its data structure and to do so currently we're + # using two specialized ObserverSet: {Concurrent::Concern::CopyOnWriteObserverSet} + # and {Concurrent::Concern::CopyOnNotifyObserverSet}. + # + # When implementing and `observer` there's a very important rule to remember: + # **there are no guarantees about the thread that will execute the callback** + # + # Let's take this example + # ``` + # class Observer + # def initialize + # @count = 0 + # end + # + # def update + # @count += 1 + # end + # end + # + # obs = Observer.new + # [obj1, obj2, obj3, obj4].each { |o| o.add_observer(obs) } + # # execute [obj1, obj2, obj3, obj4] + # ``` + # + # `obs` is wrong because the variable `@count` can be accessed by different + # threads at the same time, so it should be synchronized (using either a Mutex + # or an AtomicFixum) + module Observable + + # @!macro observable_add_observer + # + # Adds an observer to this set. If a block is passed, the observer will be + # created by this method and no other params should be passed. + # + # @param [Object] observer the observer to add + # @param [Symbol] func the function to call on the observer during notification. + # Default is :update + # @return [Object] the added observer + def add_observer(observer = nil, func = :update, &block) + observers.add_observer(observer, func, &block) + end + + # As `#add_observer` but can be used for chaining. + # + # @param [Object] observer the observer to add + # @param [Symbol] func the function to call on the observer during notification. + # @return [Observable] self + def with_observer(observer = nil, func = :update, &block) + add_observer(observer, func, &block) + self + end + + # @!macro observable_delete_observer + # + # Remove `observer` as an observer on this object so that it will no + # longer receive notifications. + # + # @param [Object] observer the observer to remove + # @return [Object] the deleted observer + def delete_observer(observer) + observers.delete_observer(observer) + end + + # @!macro observable_delete_observers + # + # Remove all observers associated with this object. + # + # @return [Observable] self + def delete_observers + observers.delete_observers + self + end + + # @!macro observable_count_observers + # + # Return the number of observers associated with this object. + # + # @return [Integer] the observers count + def count_observers + observers.count_observers + end + + protected + + attr_accessor :observers + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/concurrent_ruby.jar b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/concurrent_ruby.jar new file mode 100644 index 00000000..d5b8d275 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/concurrent_ruby.jar differ diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/configuration.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/configuration.rb new file mode 100644 index 00000000..5571d39b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/configuration.rb @@ -0,0 +1,105 @@ +require 'thread' +require 'concurrent/delay' +require 'concurrent/errors' +require 'concurrent/concern/deprecation' +require 'concurrent/executor/immediate_executor' +require 'concurrent/executor/fixed_thread_pool' +require 'concurrent/executor/cached_thread_pool' +require 'concurrent/utility/processor_counter' + +module Concurrent + extend Concern::Deprecation + + autoload :Options, 'concurrent/options' + autoload :TimerSet, 'concurrent/executor/timer_set' + autoload :ThreadPoolExecutor, 'concurrent/executor/thread_pool_executor' + + # @!visibility private + GLOBAL_FAST_EXECUTOR = Delay.new { Concurrent.new_fast_executor } + private_constant :GLOBAL_FAST_EXECUTOR + + # @!visibility private + GLOBAL_IO_EXECUTOR = Delay.new { Concurrent.new_io_executor } + private_constant :GLOBAL_IO_EXECUTOR + + # @!visibility private + GLOBAL_TIMER_SET = Delay.new { TimerSet.new } + private_constant :GLOBAL_TIMER_SET + + # @!visibility private + GLOBAL_IMMEDIATE_EXECUTOR = ImmediateExecutor.new + private_constant :GLOBAL_IMMEDIATE_EXECUTOR + + # Disables AtExit handlers including pool auto-termination handlers. + # When disabled it will be the application programmer's responsibility + # to ensure that the handlers are shutdown properly prior to application + # exit by calling `AtExit.run` method. + # + # @note this option should be needed only because of `at_exit` ordering + # issues which may arise when running some of the testing frameworks. + # E.g. Minitest's test-suite runs itself in `at_exit` callback which + # executes after the pools are already terminated. Then auto termination + # needs to be disabled and called manually after test-suite ends. + # @note This method should *never* be called + # from within a gem. It should *only* be used from within the main + # application and even then it should be used only when necessary. + # @deprecated Has no effect since it is no longer needed, see https://github.com/ruby-concurrency/concurrent-ruby/pull/841. + # + def self.disable_at_exit_handlers! + deprecated "Method #disable_at_exit_handlers! has no effect since it is no longer needed, see https://github.com/ruby-concurrency/concurrent-ruby/pull/841." + end + + # Global thread pool optimized for short, fast *operations*. + # + # @return [ThreadPoolExecutor] the thread pool + def self.global_fast_executor + GLOBAL_FAST_EXECUTOR.value! + end + + # Global thread pool optimized for long, blocking (IO) *tasks*. + # + # @return [ThreadPoolExecutor] the thread pool + def self.global_io_executor + GLOBAL_IO_EXECUTOR.value! + end + + def self.global_immediate_executor + GLOBAL_IMMEDIATE_EXECUTOR + end + + # Global thread pool user for global *timers*. + # + # @return [Concurrent::TimerSet] the thread pool + def self.global_timer_set + GLOBAL_TIMER_SET.value! + end + + # General access point to global executors. + # @param [Symbol, Executor] executor_identifier symbols: + # - :fast - {Concurrent.global_fast_executor} + # - :io - {Concurrent.global_io_executor} + # - :immediate - {Concurrent.global_immediate_executor} + # @return [Executor] + def self.executor(executor_identifier) + Options.executor(executor_identifier) + end + + def self.new_fast_executor(opts = {}) + FixedThreadPool.new( + [2, Concurrent.processor_count].max, + auto_terminate: opts.fetch(:auto_terminate, true), + idletime: 60, # 1 minute + max_queue: 0, # unlimited + fallback_policy: :abort, # shouldn't matter -- 0 max queue + name: "fast" + ) + end + + def self.new_io_executor(opts = {}) + CachedThreadPool.new( + auto_terminate: opts.fetch(:auto_terminate, true), + fallback_policy: :abort, # shouldn't matter -- 0 max queue + name: "io" + ) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/constants.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/constants.rb new file mode 100644 index 00000000..676c2afb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/constants.rb @@ -0,0 +1,8 @@ +module Concurrent + + # Various classes within allows for +nil+ values to be stored, + # so a special +NULL+ token is required to indicate the "nil-ness". + # @!visibility private + NULL = ::Object.new + +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/dataflow.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/dataflow.rb new file mode 100644 index 00000000..d55f19d8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/dataflow.rb @@ -0,0 +1,81 @@ +require 'concurrent/future' +require 'concurrent/atomic/atomic_fixnum' + +module Concurrent + + # @!visibility private + class DependencyCounter # :nodoc: + + def initialize(count, &block) + @counter = AtomicFixnum.new(count) + @block = block + end + + def update(time, value, reason) + if @counter.decrement == 0 + @block.call + end + end + end + + # Dataflow allows you to create a task that will be scheduled when all of its data dependencies are available. + # {include:file:docs-source/dataflow.md} + # + # @param [Future] inputs zero or more `Future` operations that this dataflow depends upon + # + # @yield The operation to perform once all the dependencies are met + # @yieldparam [Future] inputs each of the `Future` inputs to the dataflow + # @yieldreturn [Object] the result of the block operation + # + # @return [Object] the result of all the operations + # + # @raise [ArgumentError] if no block is given + # @raise [ArgumentError] if any of the inputs are not `IVar`s + def dataflow(*inputs, &block) + dataflow_with(Concurrent.global_io_executor, *inputs, &block) + end + module_function :dataflow + + def dataflow_with(executor, *inputs, &block) + call_dataflow(:value, executor, *inputs, &block) + end + module_function :dataflow_with + + def dataflow!(*inputs, &block) + dataflow_with!(Concurrent.global_io_executor, *inputs, &block) + end + module_function :dataflow! + + def dataflow_with!(executor, *inputs, &block) + call_dataflow(:value!, executor, *inputs, &block) + end + module_function :dataflow_with! + + private + + def call_dataflow(method, executor, *inputs, &block) + raise ArgumentError.new('an executor must be provided') if executor.nil? + raise ArgumentError.new('no block given') unless block_given? + unless inputs.all? { |input| input.is_a? IVar } + raise ArgumentError.new("Not all dependencies are IVars.\nDependencies: #{ inputs.inspect }") + end + + result = Future.new(executor: executor) do + values = inputs.map { |input| input.send(method) } + block.call(*values) + end + + if inputs.empty? + result.execute + else + counter = DependencyCounter.new(inputs.size) { result.execute } + + inputs.each do |input| + input.add_observer counter + end + end + + result + end + module_function :call_dataflow +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/delay.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/delay.rb new file mode 100644 index 00000000..0d6d91a5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/delay.rb @@ -0,0 +1,199 @@ +require 'thread' +require 'concurrent/concern/obligation' +require 'concurrent/executor/immediate_executor' +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # This file has circular require issues. It must be autoloaded here. + autoload :Options, 'concurrent/options' + + # Lazy evaluation of a block yielding an immutable result. Useful for + # expensive operations that may never be needed. It may be non-blocking, + # supports the `Concern::Obligation` interface, and accepts the injection of + # custom executor upon which to execute the block. Processing of + # block will be deferred until the first time `#value` is called. + # At that time the caller can choose to return immediately and let + # the block execute asynchronously, block indefinitely, or block + # with a timeout. + # + # When a `Delay` is created its state is set to `pending`. The value and + # reason are both `nil`. The first time the `#value` method is called the + # enclosed operation will be run and the calling thread will block. Other + # threads attempting to call `#value` will block as well. Once the operation + # is complete the *value* will be set to the result of the operation or the + # *reason* will be set to the raised exception, as appropriate. All threads + # blocked on `#value` will return. Subsequent calls to `#value` will immediately + # return the cached value. The operation will only be run once. This means that + # any side effects created by the operation will only happen once as well. + # + # `Delay` includes the `Concurrent::Concern::Dereferenceable` mixin to support thread + # safety of the reference returned by `#value`. + # + # @!macro copy_options + # + # @!macro delay_note_regarding_blocking + # @note The default behavior of `Delay` is to block indefinitely when + # calling either `value` or `wait`, executing the delayed operation on + # the current thread. This makes the `timeout` value completely + # irrelevant. To enable non-blocking behavior, use the `executor` + # constructor option. This will cause the delayed operation to be + # execute on the given executor, allowing the call to timeout. + # + # @see Concurrent::Concern::Dereferenceable + class Delay < Synchronization::LockableObject + include Concern::Obligation + + # NOTE: Because the global thread pools are lazy-loaded with these objects + # there is a performance hit every time we post a new task to one of these + # thread pools. Subsequently it is critical that `Delay` perform as fast + # as possible post-completion. This class has been highly optimized using + # the benchmark script `examples/lazy_and_delay.rb`. Do NOT attempt to + # DRY-up this class or perform other refactoring with running the + # benchmarks and ensuring that performance is not negatively impacted. + + # Create a new `Delay` in the `:pending` state. + # + # @!macro executor_and_deref_options + # + # @yield the delayed operation to perform + # + # @raise [ArgumentError] if no block is given + def initialize(opts = {}, &block) + raise ArgumentError.new('no block given') unless block_given? + super(&nil) + synchronize { ns_initialize(opts, &block) } + end + + # Return the value this object represents after applying the options + # specified by the `#set_deref_options` method. If the delayed operation + # raised an exception this method will return nil. The exception object + # can be accessed via the `#reason` method. + # + # @param [Numeric] timeout the maximum number of seconds to wait + # @return [Object] the current value of the object + # + # @!macro delay_note_regarding_blocking + def value(timeout = nil) + if @executor # TODO (pitr 12-Sep-2015): broken unsafe read? + super + else + # this function has been optimized for performance and + # should not be modified without running new benchmarks + synchronize do + execute = @evaluation_started = true unless @evaluation_started + if execute + begin + set_state(true, @task.call, nil) + rescue => ex + set_state(false, nil, ex) + end + elsif incomplete? + raise IllegalOperationError, 'Recursive call to #value during evaluation of the Delay' + end + end + if @do_nothing_on_deref + @value + else + apply_deref_options(@value) + end + end + end + + # Return the value this object represents after applying the options + # specified by the `#set_deref_options` method. If the delayed operation + # raised an exception, this method will raise that exception (even when) + # the operation has already been executed). + # + # @param [Numeric] timeout the maximum number of seconds to wait + # @return [Object] the current value of the object + # @raise [Exception] when `#rejected?` raises `#reason` + # + # @!macro delay_note_regarding_blocking + def value!(timeout = nil) + if @executor + super + else + result = value + raise @reason if @reason + result + end + end + + # Return the value this object represents after applying the options + # specified by the `#set_deref_options` method. + # + # @param [Integer] timeout (nil) the maximum number of seconds to wait for + # the value to be computed. When `nil` the caller will block indefinitely. + # + # @return [Object] self + # + # @!macro delay_note_regarding_blocking + def wait(timeout = nil) + if @executor + execute_task_once + super(timeout) + else + value + end + self + end + + # Reconfigures the block returning the value if still `#incomplete?` + # + # @yield the delayed operation to perform + # @return [true, false] if success + def reconfigure(&block) + synchronize do + raise ArgumentError.new('no block given') unless block_given? + unless @evaluation_started + @task = block + true + else + false + end + end + end + + protected + + def ns_initialize(opts, &block) + init_obligation + set_deref_options(opts) + @executor = opts[:executor] + + @task = block + @state = :pending + @evaluation_started = false + end + + private + + # @!visibility private + def execute_task_once # :nodoc: + # this function has been optimized for performance and + # should not be modified without running new benchmarks + execute = task = nil + synchronize do + execute = @evaluation_started = true unless @evaluation_started + task = @task + end + + if execute + executor = Options.executor_from_options(executor: @executor) + executor.post do + begin + result = task.call + success = true + rescue => ex + reason = ex + end + synchronize do + set_state(success, result, reason) + event.set + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/errors.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/errors.rb new file mode 100644 index 00000000..74f1fc3d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/errors.rb @@ -0,0 +1,74 @@ +module Concurrent + + Error = Class.new(StandardError) + + # Raised when errors occur during configuration. + ConfigurationError = Class.new(Error) + + # Raised when an asynchronous operation is cancelled before execution. + CancelledOperationError = Class.new(Error) + + # Raised when a lifecycle method (such as `stop`) is called in an improper + # sequence or when the object is in an inappropriate state. + LifecycleError = Class.new(Error) + + # Raised when an attempt is made to violate an immutability guarantee. + ImmutabilityError = Class.new(Error) + + # Raised when an operation is attempted which is not legal given the + # receiver's current state + IllegalOperationError = Class.new(Error) + + # Raised when an object's methods are called when it has not been + # properly initialized. + InitializationError = Class.new(Error) + + # Raised when an object with a start/stop lifecycle has been started an + # excessive number of times. Often used in conjunction with a restart + # policy or strategy. + MaxRestartFrequencyError = Class.new(Error) + + # Raised when an attempt is made to modify an immutable object + # (such as an `IVar`) after its final state has been set. + class MultipleAssignmentError < Error + attr_reader :inspection_data + + def initialize(message = nil, inspection_data = nil) + @inspection_data = inspection_data + super message + end + + def inspect + format '%s %s>', super[0..-2], @inspection_data.inspect + end + end + + # Raised by an `Executor` when it is unable to process a given task, + # possibly because of a reject policy or other internal error. + RejectedExecutionError = Class.new(Error) + + # Raised when any finite resource, such as a lock counter, exceeds its + # maximum limit/threshold. + ResourceLimitError = Class.new(Error) + + # Raised when an operation times out. + TimeoutError = Class.new(Error) + + # Aggregates multiple exceptions. + class MultipleErrors < Error + attr_reader :errors + + def initialize(errors, message = "#{errors.size} errors") + @errors = errors + super [*message, + *errors.map { |e| [format('%s (%s)', e.message, e.class), *e.backtrace] }.flatten(1) + ].join("\n") + end + end + + # @!macro internal_implementation_note + class ConcurrentUpdateError < ThreadError + # frozen pre-allocated backtrace to speed ConcurrentUpdateError + CONC_UP_ERR_BACKTRACE = ['backtrace elided; set verbose to enable'].freeze + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/exchanger.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/exchanger.rb new file mode 100644 index 00000000..a5405d25 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/exchanger.rb @@ -0,0 +1,353 @@ +require 'concurrent/constants' +require 'concurrent/errors' +require 'concurrent/maybe' +require 'concurrent/atomic/atomic_reference' +require 'concurrent/atomic/count_down_latch' +require 'concurrent/utility/engine' +require 'concurrent/utility/monotonic_time' + +module Concurrent + + # @!macro exchanger + # + # A synchronization point at which threads can pair and swap elements within + # pairs. Each thread presents some object on entry to the exchange method, + # matches with a partner thread, and receives its partner's object on return. + # + # @!macro thread_safe_variable_comparison + # + # This implementation is very simple, using only a single slot for each + # exchanger (unlike more advanced implementations which use an "arena"). + # This approach will work perfectly fine when there are only a few threads + # accessing a single `Exchanger`. Beyond a handful of threads the performance + # will degrade rapidly due to contention on the single slot, but the algorithm + # will remain correct. + # + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Exchanger.html java.util.concurrent.Exchanger + # @example + # + # exchanger = Concurrent::Exchanger.new + # + # threads = [ + # Thread.new { puts "first: " << exchanger.exchange('foo', 1) }, #=> "first: bar" + # Thread.new { puts "second: " << exchanger.exchange('bar', 1) } #=> "second: foo" + # ] + # threads.each {|t| t.join(2) } + + # @!visibility private + class AbstractExchanger < Synchronization::Object + + # @!visibility private + CANCEL = ::Object.new + private_constant :CANCEL + + def initialize + super + end + + # @!macro exchanger_method_do_exchange + # + # Waits for another thread to arrive at this exchange point (unless the + # current thread is interrupted), and then transfers the given object to + # it, receiving its object in return. The timeout value indicates the + # approximate number of seconds the method should block while waiting + # for the exchange. When the timeout value is `nil` the method will + # block indefinitely. + # + # @param [Object] value the value to exchange with another thread + # @param [Numeric, nil] timeout in seconds, `nil` blocks indefinitely + # + # @!macro exchanger_method_exchange + # + # In some edge cases when a `timeout` is given a return value of `nil` may be + # ambiguous. Specifically, if `nil` is a valid value in the exchange it will + # be impossible to tell whether `nil` is the actual return value or if it + # signifies timeout. When `nil` is a valid value in the exchange consider + # using {#exchange!} or {#try_exchange} instead. + # + # @return [Object] the value exchanged by the other thread or `nil` on timeout + def exchange(value, timeout = nil) + (value = do_exchange(value, timeout)) == CANCEL ? nil : value + end + + # @!macro exchanger_method_do_exchange + # @!macro exchanger_method_exchange_bang + # + # On timeout a {Concurrent::TimeoutError} exception will be raised. + # + # @return [Object] the value exchanged by the other thread + # @raise [Concurrent::TimeoutError] on timeout + def exchange!(value, timeout = nil) + if (value = do_exchange(value, timeout)) == CANCEL + raise Concurrent::TimeoutError + else + value + end + end + + # @!macro exchanger_method_do_exchange + # @!macro exchanger_method_try_exchange + # + # The return value will be a {Concurrent::Maybe} set to `Just` on success or + # `Nothing` on timeout. + # + # @return [Concurrent::Maybe] on success a `Just` maybe will be returned with + # the item exchanged by the other thread as `#value`; on timeout a + # `Nothing` maybe will be returned with {Concurrent::TimeoutError} as `#reason` + # + # @example + # + # exchanger = Concurrent::Exchanger.new + # + # result = exchanger.exchange(:foo, 0.5) + # + # if result.just? + # puts result.value #=> :bar + # else + # puts 'timeout' + # end + def try_exchange(value, timeout = nil) + if (value = do_exchange(value, timeout)) == CANCEL + Concurrent::Maybe.nothing(Concurrent::TimeoutError) + else + Concurrent::Maybe.just(value) + end + end + + private + + # @!macro exchanger_method_do_exchange + # + # @return [Object, CANCEL] the value exchanged by the other thread; {CANCEL} on timeout + def do_exchange(value, timeout) + raise NotImplementedError + end + end + + # @!macro internal_implementation_note + # @!visibility private + class RubyExchanger < AbstractExchanger + # A simplified version of java.util.concurrent.Exchanger written by + # Doug Lea, Bill Scherer, and Michael Scott with assistance from members + # of JCP JSR-166 Expert Group and released to the public domain. It does + # not include the arena or the multi-processor spin loops. + # http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/Exchanger.java + + safe_initialization! + + class Node < Concurrent::Synchronization::Object + attr_atomic :value + safe_initialization! + + def initialize(item) + super() + @Item = item + @Latch = Concurrent::CountDownLatch.new + self.value = nil + end + + def latch + @Latch + end + + def item + @Item + end + end + private_constant :Node + + def initialize + super + end + + private + + attr_atomic(:slot) + + # @!macro exchanger_method_do_exchange + # + # @return [Object, CANCEL] the value exchanged by the other thread; {CANCEL} on timeout + def do_exchange(value, timeout) + + # ALGORITHM + # + # From the original Java version: + # + # > The basic idea is to maintain a "slot", which is a reference to + # > a Node containing both an Item to offer and a "hole" waiting to + # > get filled in. If an incoming "occupying" thread sees that the + # > slot is null, it CAS'es (compareAndSets) a Node there and waits + # > for another to invoke exchange. That second "fulfilling" thread + # > sees that the slot is non-null, and so CASes it back to null, + # > also exchanging items by CASing the hole, plus waking up the + # > occupying thread if it is blocked. In each case CAS'es may + # > fail because a slot at first appears non-null but is null upon + # > CAS, or vice-versa. So threads may need to retry these + # > actions. + # + # This version: + # + # An exchange occurs between an "occupier" thread and a "fulfiller" thread. + # The "slot" is used to setup this interaction. The first thread in the + # exchange puts itself into the slot (occupies) and waits for a fulfiller. + # The second thread removes the occupier from the slot and attempts to + # perform the exchange. Removing the occupier also frees the slot for + # another occupier/fulfiller pair. + # + # Because the occupier and the fulfiller are operating independently and + # because there may be contention with other threads, any failed operation + # indicates contention. Both the occupier and the fulfiller operate within + # spin loops. Any failed actions along the happy path will cause the thread + # to repeat the loop and try again. + # + # When a timeout value is given the thread must be cognizant of time spent + # in the spin loop. The remaining time is checked every loop. When the time + # runs out the thread will exit. + # + # A "node" is the data structure used to perform the exchange. Only the + # occupier's node is necessary. It's the node used for the exchange. + # Each node has an "item," a "hole" (self), and a "latch." The item is the + # node's initial value. It never changes. It's what the fulfiller returns on + # success. The occupier's hole is where the fulfiller put its item. It's the + # item that the occupier returns on success. The latch is used for synchronization. + # Because a thread may act as either an occupier or fulfiller (or possibly + # both in periods of high contention) every thread creates a node when + # the exchange method is first called. + # + # The following steps occur within the spin loop. If any actions fail + # the thread will loop and try again, so long as there is time remaining. + # If time runs out the thread will return CANCEL. + # + # Check the slot for an occupier: + # + # * If the slot is empty try to occupy + # * If the slot is full try to fulfill + # + # Attempt to occupy: + # + # * Attempt to CAS myself into the slot + # * Go to sleep and wait to be woken by a fulfiller + # * If the sleep is successful then the fulfiller completed its happy path + # - Return the value from my hole (the value given by the fulfiller) + # * When the sleep fails (time ran out) attempt to cancel the operation + # - Attempt to CAS myself out of the hole + # - If successful there is no contention + # - Return CANCEL + # - On failure, I am competing with a fulfiller + # - Attempt to CAS my hole to CANCEL + # - On success + # - Let the fulfiller deal with my cancel + # - Return CANCEL + # - On failure the fulfiller has completed its happy path + # - Return th value from my hole (the fulfiller's value) + # + # Attempt to fulfill: + # + # * Attempt to CAS the occupier out of the slot + # - On failure loop again + # * Attempt to CAS my item into the occupier's hole + # - On failure the occupier is trying to cancel + # - Loop again + # - On success we are on the happy path + # - Wake the sleeping occupier + # - Return the occupier's item + + value = NULL if value.nil? # The sentinel allows nil to be a valid value + me = Node.new(value) # create my node in case I need to occupy + end_at = Concurrent.monotonic_time + timeout.to_f # The time to give up + + result = loop do + other = slot + if other && compare_and_set_slot(other, nil) + # try to fulfill + if other.compare_and_set_value(nil, value) + # happy path + other.latch.count_down + break other.item + end + elsif other.nil? && compare_and_set_slot(nil, me) + # try to occupy + timeout = end_at - Concurrent.monotonic_time if timeout + if me.latch.wait(timeout) + # happy path + break me.value + else + # attempt to remove myself from the slot + if compare_and_set_slot(me, nil) + break CANCEL + elsif !me.compare_and_set_value(nil, CANCEL) + # I've failed to block the fulfiller + break me.value + end + end + end + break CANCEL if timeout && Concurrent.monotonic_time >= end_at + end + + result == NULL ? nil : result + end + end + + if Concurrent.on_jruby? + require 'concurrent/utility/native_extension_loader' + + # @!macro internal_implementation_note + # @!visibility private + class JavaExchanger < AbstractExchanger + + def initialize + @exchanger = java.util.concurrent.Exchanger.new + end + + private + + # @!macro exchanger_method_do_exchange + # + # @return [Object, CANCEL] the value exchanged by the other thread; {CANCEL} on timeout + def do_exchange(value, timeout) + result = nil + if timeout.nil? + Synchronization::JRuby.sleep_interruptibly do + result = @exchanger.exchange(value) + end + else + Synchronization::JRuby.sleep_interruptibly do + result = @exchanger.exchange(value, 1000 * timeout, java.util.concurrent.TimeUnit::MILLISECONDS) + end + end + result + rescue java.util.concurrent.TimeoutException + CANCEL + end + end + end + + # @!visibility private + # @!macro internal_implementation_note + ExchangerImplementation = case + when Concurrent.on_jruby? + JavaExchanger + else + RubyExchanger + end + private_constant :ExchangerImplementation + + # @!macro exchanger + class Exchanger < ExchangerImplementation + + # @!method initialize + # Creates exchanger instance + + # @!method exchange(value, timeout = nil) + # @!macro exchanger_method_do_exchange + # @!macro exchanger_method_exchange + + # @!method exchange!(value, timeout = nil) + # @!macro exchanger_method_do_exchange + # @!macro exchanger_method_exchange_bang + + # @!method try_exchange(value, timeout = nil) + # @!macro exchanger_method_do_exchange + # @!macro exchanger_method_try_exchange + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb new file mode 100644 index 00000000..ac429531 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb @@ -0,0 +1,131 @@ +require 'concurrent/errors' +require 'concurrent/concern/deprecation' +require 'concurrent/executor/executor_service' +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # @!macro abstract_executor_service_public_api + # @!visibility private + class AbstractExecutorService < Synchronization::LockableObject + include ExecutorService + include Concern::Deprecation + + # The set of possible fallback policies that may be set at thread pool creation. + FALLBACK_POLICIES = [:abort, :discard, :caller_runs].freeze + + # @!macro executor_service_attr_reader_fallback_policy + attr_reader :fallback_policy + + attr_reader :name + + # Create a new thread pool. + def initialize(opts = {}, &block) + super(&nil) + synchronize do + @auto_terminate = opts.fetch(:auto_terminate, true) + @name = opts.fetch(:name) if opts.key?(:name) + ns_initialize(opts, &block) + end + end + + def to_s + name ? "#{super[0..-2]} name: #{name}>" : super + end + + # @!macro executor_service_method_shutdown + def shutdown + raise NotImplementedError + end + + # @!macro executor_service_method_kill + def kill + raise NotImplementedError + end + + # @!macro executor_service_method_wait_for_termination + def wait_for_termination(timeout = nil) + raise NotImplementedError + end + + # @!macro executor_service_method_running_question + def running? + synchronize { ns_running? } + end + + # @!macro executor_service_method_shuttingdown_question + def shuttingdown? + synchronize { ns_shuttingdown? } + end + + # @!macro executor_service_method_shutdown_question + def shutdown? + synchronize { ns_shutdown? } + end + + # @!macro executor_service_method_auto_terminate_question + def auto_terminate? + synchronize { @auto_terminate } + end + + # @!macro executor_service_method_auto_terminate_setter + def auto_terminate=(value) + deprecated "Method #auto_terminate= has no effect. Set :auto_terminate option when executor is initialized." + end + + private + + # Returns an action which executes the `fallback_policy` once the queue + # size reaches `max_queue`. The reason for the indirection of an action + # is so that the work can be deferred outside of synchronization. + # + # @param [Array] args the arguments to the task which is being handled. + # + # @!visibility private + def fallback_action(*args) + case fallback_policy + when :abort + lambda { raise RejectedExecutionError } + when :discard + lambda { false } + when :caller_runs + lambda { + begin + yield(*args) + rescue => ex + # let it fail + log DEBUG, ex + end + true + } + else + lambda { fail "Unknown fallback policy #{fallback_policy}" } + end + end + + def ns_execute(*args, &task) + raise NotImplementedError + end + + # @!macro executor_service_method_ns_shutdown_execution + # + # Callback method called when an orderly shutdown has completed. + # The default behavior is to signal all waiting threads. + def ns_shutdown_execution + # do nothing + end + + # @!macro executor_service_method_ns_kill_execution + # + # Callback method called when the executor has been killed. + # The default behavior is to do nothing. + def ns_kill_execution + # do nothing + end + + def ns_auto_terminate? + @auto_terminate + end + + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/cached_thread_pool.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/cached_thread_pool.rb new file mode 100644 index 00000000..de50ed17 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/cached_thread_pool.rb @@ -0,0 +1,62 @@ +require 'concurrent/utility/engine' +require 'concurrent/executor/thread_pool_executor' + +module Concurrent + + # A thread pool that dynamically grows and shrinks to fit the current workload. + # New threads are created as needed, existing threads are reused, and threads + # that remain idle for too long are killed and removed from the pool. These + # pools are particularly suited to applications that perform a high volume of + # short-lived tasks. + # + # On creation a `CachedThreadPool` has zero running threads. New threads are + # created on the pool as new operations are `#post`. The size of the pool + # will grow until `#max_length` threads are in the pool or until the number + # of threads exceeds the number of running and pending operations. When a new + # operation is post to the pool the first available idle thread will be tasked + # with the new operation. + # + # Should a thread crash for any reason the thread will immediately be removed + # from the pool. Similarly, threads which remain idle for an extended period + # of time will be killed and reclaimed. Thus these thread pools are very + # efficient at reclaiming unused resources. + # + # The API and behavior of this class are based on Java's `CachedThreadPool` + # + # @!macro thread_pool_options + class CachedThreadPool < ThreadPoolExecutor + + # @!macro cached_thread_pool_method_initialize + # + # Create a new thread pool. + # + # @param [Hash] opts the options defining pool behavior. + # @option opts [Symbol] :fallback_policy (`:abort`) the fallback policy + # + # @raise [ArgumentError] if `fallback_policy` is not a known policy + # + # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newCachedThreadPool-- + def initialize(opts = {}) + defaults = { idletime: DEFAULT_THREAD_IDLETIMEOUT } + overrides = { min_threads: 0, + max_threads: DEFAULT_MAX_POOL_SIZE, + max_queue: DEFAULT_MAX_QUEUE_SIZE } + super(defaults.merge(opts).merge(overrides)) + end + + private + + # @!macro cached_thread_pool_method_initialize + # @!visibility private + def ns_initialize(opts) + super(opts) + if Concurrent.on_jruby? + @max_queue = 0 + @executor = java.util.concurrent.Executors.newCachedThreadPool( + DaemonThreadFactory.new(ns_auto_terminate?)) + @executor.setRejectedExecutionHandler(FALLBACK_POLICY_CLASSES[@fallback_policy].new) + @executor.setKeepAliveTime(opts.fetch(:idletime, DEFAULT_THREAD_IDLETIMEOUT), java.util.concurrent.TimeUnit::SECONDS) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/executor_service.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/executor_service.rb new file mode 100644 index 00000000..7e344919 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/executor_service.rb @@ -0,0 +1,185 @@ +require 'concurrent/concern/logging' + +module Concurrent + + ################################################################### + + # @!macro executor_service_method_post + # + # Submit a task to the executor for asynchronous processing. + # + # @param [Array] args zero or more arguments to be passed to the task + # + # @yield the asynchronous task to perform + # + # @return [Boolean] `true` if the task is queued, `false` if the executor + # is not running + # + # @raise [ArgumentError] if no task is given + + # @!macro executor_service_method_left_shift + # + # Submit a task to the executor for asynchronous processing. + # + # @param [Proc] task the asynchronous task to perform + # + # @return [self] returns itself + + # @!macro executor_service_method_can_overflow_question + # + # Does the task queue have a maximum size? + # + # @return [Boolean] True if the task queue has a maximum size else false. + + # @!macro executor_service_method_serialized_question + # + # Does this executor guarantee serialization of its operations? + # + # @return [Boolean] True if the executor guarantees that all operations + # will be post in the order they are received and no two operations may + # occur simultaneously. Else false. + + ################################################################### + + # @!macro executor_service_public_api + # + # @!method post(*args, &task) + # @!macro executor_service_method_post + # + # @!method <<(task) + # @!macro executor_service_method_left_shift + # + # @!method can_overflow? + # @!macro executor_service_method_can_overflow_question + # + # @!method serialized? + # @!macro executor_service_method_serialized_question + + ################################################################### + + # @!macro executor_service_attr_reader_fallback_policy + # @return [Symbol] The fallback policy in effect. Either `:abort`, `:discard`, or `:caller_runs`. + + # @!macro executor_service_method_shutdown + # + # Begin an orderly shutdown. Tasks already in the queue will be executed, + # but no new tasks will be accepted. Has no additional effect if the + # thread pool is not running. + + # @!macro executor_service_method_kill + # + # Begin an immediate shutdown. In-progress tasks will be allowed to + # complete but enqueued tasks will be dismissed and no new tasks + # will be accepted. Has no additional effect if the thread pool is + # not running. + + # @!macro executor_service_method_wait_for_termination + # + # Block until executor shutdown is complete or until `timeout` seconds have + # passed. + # + # @note Does not initiate shutdown or termination. Either `shutdown` or `kill` + # must be called before this method (or on another thread). + # + # @param [Integer] timeout the maximum number of seconds to wait for shutdown to complete + # + # @return [Boolean] `true` if shutdown complete or false on `timeout` + + # @!macro executor_service_method_running_question + # + # Is the executor running? + # + # @return [Boolean] `true` when running, `false` when shutting down or shutdown + + # @!macro executor_service_method_shuttingdown_question + # + # Is the executor shuttingdown? + # + # @return [Boolean] `true` when not running and not shutdown, else `false` + + # @!macro executor_service_method_shutdown_question + # + # Is the executor shutdown? + # + # @return [Boolean] `true` when shutdown, `false` when shutting down or running + + # @!macro executor_service_method_auto_terminate_question + # + # Is the executor auto-terminate when the application exits? + # + # @return [Boolean] `true` when auto-termination is enabled else `false`. + + # @!macro executor_service_method_auto_terminate_setter + # + # + # Set the auto-terminate behavior for this executor. + # @deprecated Has no effect + # @param [Boolean] value The new auto-terminate value to set for this executor. + # @return [Boolean] `true` when auto-termination is enabled else `false`. + + ################################################################### + + # @!macro abstract_executor_service_public_api + # + # @!macro executor_service_public_api + # + # @!attribute [r] fallback_policy + # @!macro executor_service_attr_reader_fallback_policy + # + # @!method shutdown + # @!macro executor_service_method_shutdown + # + # @!method kill + # @!macro executor_service_method_kill + # + # @!method wait_for_termination(timeout = nil) + # @!macro executor_service_method_wait_for_termination + # + # @!method running? + # @!macro executor_service_method_running_question + # + # @!method shuttingdown? + # @!macro executor_service_method_shuttingdown_question + # + # @!method shutdown? + # @!macro executor_service_method_shutdown_question + # + # @!method auto_terminate? + # @!macro executor_service_method_auto_terminate_question + # + # @!method auto_terminate=(value) + # @!macro executor_service_method_auto_terminate_setter + + ################################################################### + + # @!macro executor_service_public_api + # @!visibility private + module ExecutorService + include Concern::Logging + + # @!macro executor_service_method_post + def post(*args, &task) + raise NotImplementedError + end + + # @!macro executor_service_method_left_shift + def <<(task) + post(&task) + self + end + + # @!macro executor_service_method_can_overflow_question + # + # @note Always returns `false` + def can_overflow? + false + end + + # @!macro executor_service_method_serialized_question + # + # @note Always returns `false` + def serialized? + false + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb new file mode 100644 index 00000000..8324c067 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb @@ -0,0 +1,224 @@ +require 'concurrent/utility/engine' +require 'concurrent/executor/thread_pool_executor' + +module Concurrent + + # @!macro thread_pool_executor_constant_default_max_pool_size + # Default maximum number of threads that will be created in the pool. + + # @!macro thread_pool_executor_constant_default_min_pool_size + # Default minimum number of threads that will be retained in the pool. + + # @!macro thread_pool_executor_constant_default_max_queue_size + # Default maximum number of tasks that may be added to the task queue. + + # @!macro thread_pool_executor_constant_default_thread_timeout + # Default maximum number of seconds a thread in the pool may remain idle + # before being reclaimed. + + # @!macro thread_pool_executor_constant_default_synchronous + # Default value of the :synchronous option. + + # @!macro thread_pool_executor_attr_reader_max_length + # The maximum number of threads that may be created in the pool. + # @return [Integer] The maximum number of threads that may be created in the pool. + + # @!macro thread_pool_executor_attr_reader_min_length + # The minimum number of threads that may be retained in the pool. + # @return [Integer] The minimum number of threads that may be retained in the pool. + + # @!macro thread_pool_executor_attr_reader_largest_length + # The largest number of threads that have been created in the pool since construction. + # @return [Integer] The largest number of threads that have been created in the pool since construction. + + # @!macro thread_pool_executor_attr_reader_scheduled_task_count + # The number of tasks that have been scheduled for execution on the pool since construction. + # @return [Integer] The number of tasks that have been scheduled for execution on the pool since construction. + + # @!macro thread_pool_executor_attr_reader_completed_task_count + # The number of tasks that have been completed by the pool since construction. + # @return [Integer] The number of tasks that have been completed by the pool since construction. + + # @!macro thread_pool_executor_method_active_count + # The number of threads that are actively executing tasks. + # @return [Integer] The number of threads that are actively executing tasks. + + # @!macro thread_pool_executor_attr_reader_idletime + # The number of seconds that a thread may be idle before being reclaimed. + # @return [Integer] The number of seconds that a thread may be idle before being reclaimed. + + # @!macro thread_pool_executor_attr_reader_synchronous + # Whether or not a value of 0 for :max_queue option means the queue must perform direct hand-off or rather unbounded queue. + # @return [true, false] + + # @!macro thread_pool_executor_attr_reader_max_queue + # The maximum number of tasks that may be waiting in the work queue at any one time. + # When the queue size reaches `max_queue` subsequent tasks will be rejected in + # accordance with the configured `fallback_policy`. + # + # @return [Integer] The maximum number of tasks that may be waiting in the work queue at any one time. + # When the queue size reaches `max_queue` subsequent tasks will be rejected in + # accordance with the configured `fallback_policy`. + + # @!macro thread_pool_executor_attr_reader_length + # The number of threads currently in the pool. + # @return [Integer] The number of threads currently in the pool. + + # @!macro thread_pool_executor_attr_reader_queue_length + # The number of tasks in the queue awaiting execution. + # @return [Integer] The number of tasks in the queue awaiting execution. + + # @!macro thread_pool_executor_attr_reader_remaining_capacity + # Number of tasks that may be enqueued before reaching `max_queue` and rejecting + # new tasks. A value of -1 indicates that the queue may grow without bound. + # + # @return [Integer] Number of tasks that may be enqueued before reaching `max_queue` and rejecting + # new tasks. A value of -1 indicates that the queue may grow without bound. + + # @!macro thread_pool_executor_method_prune_pool + # Prune the thread pool of unneeded threads + # + # What is being pruned is controlled by the min_threads and idletime + # parameters passed at pool creation time + # + # This is a no-op on some pool implementation (e.g. the Java one). The Ruby + # pool will auto-prune each time a new job is posted. You will need to call + # this method explicitly in case your application post jobs in bursts (a + # lot of jobs and then nothing for long periods) + + # @!macro thread_pool_executor_public_api + # + # @!macro abstract_executor_service_public_api + # + # @!attribute [r] max_length + # @!macro thread_pool_executor_attr_reader_max_length + # + # @!attribute [r] min_length + # @!macro thread_pool_executor_attr_reader_min_length + # + # @!attribute [r] largest_length + # @!macro thread_pool_executor_attr_reader_largest_length + # + # @!attribute [r] scheduled_task_count + # @!macro thread_pool_executor_attr_reader_scheduled_task_count + # + # @!attribute [r] completed_task_count + # @!macro thread_pool_executor_attr_reader_completed_task_count + # + # @!attribute [r] idletime + # @!macro thread_pool_executor_attr_reader_idletime + # + # @!attribute [r] max_queue + # @!macro thread_pool_executor_attr_reader_max_queue + # + # @!attribute [r] length + # @!macro thread_pool_executor_attr_reader_length + # + # @!attribute [r] queue_length + # @!macro thread_pool_executor_attr_reader_queue_length + # + # @!attribute [r] remaining_capacity + # @!macro thread_pool_executor_attr_reader_remaining_capacity + # + # @!method can_overflow? + # @!macro executor_service_method_can_overflow_question + # + # @!method prune_pool + # @!macro thread_pool_executor_method_prune_pool + + + + + # @!macro thread_pool_options + # + # **Thread Pool Options** + # + # Thread pools support several configuration options: + # + # * `idletime`: The number of seconds that a thread may be idle before being reclaimed. + # * `name`: The name of the executor (optional). Printed in the executor's `#to_s` output and + # a `-worker-` name is given to its threads if supported by used Ruby + # implementation. `` is uniq for each thread. + # * `max_queue`: The maximum number of tasks that may be waiting in the work queue at + # any one time. When the queue size reaches `max_queue` and no new threads can be created, + # subsequent tasks will be rejected in accordance with the configured `fallback_policy`. + # * `auto_terminate`: When true (default), the threads started will be marked as daemon. + # * `fallback_policy`: The policy defining how rejected tasks are handled. + # + # Three fallback policies are supported: + # + # * `:abort`: Raise a `RejectedExecutionError` exception and discard the task. + # * `:discard`: Discard the task and return false. + # * `:caller_runs`: Execute the task on the calling thread. + # + # **Shutting Down Thread Pools** + # + # Killing a thread pool while tasks are still being processed, either by calling + # the `#kill` method or at application exit, will have unpredictable results. There + # is no way for the thread pool to know what resources are being used by the + # in-progress tasks. When those tasks are killed the impact on those resources + # cannot be predicted. The *best* practice is to explicitly shutdown all thread + # pools using the provided methods: + # + # * Call `#shutdown` to initiate an orderly termination of all in-progress tasks + # * Call `#wait_for_termination` with an appropriate timeout interval an allow + # the orderly shutdown to complete + # * Call `#kill` *only when* the thread pool fails to shutdown in the allotted time + # + # On some runtime platforms (most notably the JVM) the application will not + # exit until all thread pools have been shutdown. To prevent applications from + # "hanging" on exit, all threads can be marked as daemon according to the + # `:auto_terminate` option. + # + # ```ruby + # pool1 = Concurrent::FixedThreadPool.new(5) # threads will be marked as daemon + # pool2 = Concurrent::FixedThreadPool.new(5, auto_terminate: false) # mark threads as non-daemon + # ``` + # + # @note Failure to properly shutdown a thread pool can lead to unpredictable results. + # Please read *Shutting Down Thread Pools* for more information. + # + # @see http://docs.oracle.com/javase/tutorial/essential/concurrency/pools.html Java Tutorials: Thread Pools + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html Java Executors class + # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html Java ExecutorService interface + # @see https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#setDaemon-boolean- + + + + + + # @!macro fixed_thread_pool + # + # A thread pool that reuses a fixed number of threads operating off an unbounded queue. + # At any point, at most `num_threads` will be active processing tasks. When all threads are busy new + # tasks `#post` to the thread pool are enqueued until a thread becomes available. + # Should a thread crash for any reason the thread will immediately be removed + # from the pool and replaced. + # + # The API and behavior of this class are based on Java's `FixedThreadPool` + # + # @!macro thread_pool_options + class FixedThreadPool < ThreadPoolExecutor + + # @!macro fixed_thread_pool_method_initialize + # + # Create a new thread pool. + # + # @param [Integer] num_threads the number of threads to allocate + # @param [Hash] opts the options defining pool behavior. + # @option opts [Symbol] :fallback_policy (`:abort`) the fallback policy + # + # @raise [ArgumentError] if `num_threads` is less than or equal to zero + # @raise [ArgumentError] if `fallback_policy` is not a known policy + # + # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool-int- + def initialize(num_threads, opts = {}) + raise ArgumentError.new('number of threads must be greater than zero') if num_threads.to_i < 1 + defaults = { max_queue: DEFAULT_MAX_QUEUE_SIZE, + idletime: DEFAULT_THREAD_IDLETIMEOUT } + overrides = { min_threads: num_threads, + max_threads: num_threads } + super(defaults.merge(opts).merge(overrides)) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/immediate_executor.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/immediate_executor.rb new file mode 100644 index 00000000..282df7a0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/immediate_executor.rb @@ -0,0 +1,66 @@ +require 'concurrent/atomic/event' +require 'concurrent/executor/abstract_executor_service' +require 'concurrent/executor/serial_executor_service' + +module Concurrent + + # An executor service which runs all operations on the current thread, + # blocking as necessary. Operations are performed in the order they are + # received and no two operations can be performed simultaneously. + # + # This executor service exists mainly for testing an debugging. When used + # it immediately runs every `#post` operation on the current thread, blocking + # that thread until the operation is complete. This can be very beneficial + # during testing because it makes all operations deterministic. + # + # @note Intended for use primarily in testing and debugging. + class ImmediateExecutor < AbstractExecutorService + include SerialExecutorService + + # Creates a new executor + def initialize + @stopped = Concurrent::Event.new + end + + # @!macro executor_service_method_post + def post(*args, &task) + raise ArgumentError.new('no block given') unless block_given? + return false unless running? + task.call(*args) + true + end + + # @!macro executor_service_method_left_shift + def <<(task) + post(&task) + self + end + + # @!macro executor_service_method_running_question + def running? + ! shutdown? + end + + # @!macro executor_service_method_shuttingdown_question + def shuttingdown? + false + end + + # @!macro executor_service_method_shutdown_question + def shutdown? + @stopped.set? + end + + # @!macro executor_service_method_shutdown + def shutdown + @stopped.set + true + end + alias_method :kill, :shutdown + + # @!macro executor_service_method_wait_for_termination + def wait_for_termination(timeout = nil) + @stopped.wait(timeout) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/indirect_immediate_executor.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/indirect_immediate_executor.rb new file mode 100644 index 00000000..4f9769fa --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/indirect_immediate_executor.rb @@ -0,0 +1,44 @@ +require 'concurrent/executor/immediate_executor' +require 'concurrent/executor/simple_executor_service' + +module Concurrent + # An executor service which runs all operations on a new thread, blocking + # until it completes. Operations are performed in the order they are received + # and no two operations can be performed simultaneously. + # + # This executor service exists mainly for testing an debugging. When used it + # immediately runs every `#post` operation on a new thread, blocking the + # current thread until the operation is complete. This is similar to how the + # ImmediateExecutor works, but the operation has the full stack of the new + # thread at its disposal. This can be helpful when the operations will spawn + # more operations on the same executor and so on - such a situation might + # overflow the single stack in case of an ImmediateExecutor, which is + # inconsistent with how it would behave for a threaded executor. + # + # @note Intended for use primarily in testing and debugging. + class IndirectImmediateExecutor < ImmediateExecutor + # Creates a new executor + def initialize + super + @internal_executor = SimpleExecutorService.new + end + + # @!macro executor_service_method_post + def post(*args, &task) + raise ArgumentError.new("no block given") unless block_given? + return false unless running? + + event = Concurrent::Event.new + @internal_executor.post do + begin + task.call(*args) + ensure + event.set + end + end + event.wait + + true + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb new file mode 100644 index 00000000..b2bc69a6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb @@ -0,0 +1,100 @@ +require 'concurrent/utility/engine' + +if Concurrent.on_jruby? + require 'concurrent/errors' + require 'concurrent/executor/abstract_executor_service' + + module Concurrent + + # @!macro abstract_executor_service_public_api + # @!visibility private + class JavaExecutorService < AbstractExecutorService + java_import 'java.lang.Runnable' + + FALLBACK_POLICY_CLASSES = { + abort: java.util.concurrent.ThreadPoolExecutor::AbortPolicy, + discard: java.util.concurrent.ThreadPoolExecutor::DiscardPolicy, + caller_runs: java.util.concurrent.ThreadPoolExecutor::CallerRunsPolicy + }.freeze + private_constant :FALLBACK_POLICY_CLASSES + + def post(*args, &task) + raise ArgumentError.new('no block given') unless block_given? + return fallback_action(*args, &task).call unless running? + @executor.submit Job.new(args, task) + true + rescue Java::JavaUtilConcurrent::RejectedExecutionException + raise RejectedExecutionError + end + + def wait_for_termination(timeout = nil) + if timeout.nil? + ok = @executor.awaitTermination(60, java.util.concurrent.TimeUnit::SECONDS) until ok + true + else + @executor.awaitTermination(1000 * timeout, java.util.concurrent.TimeUnit::MILLISECONDS) + end + end + + def shutdown + synchronize do + @executor.shutdown + nil + end + end + + def kill + synchronize do + @executor.shutdownNow + nil + end + end + + private + + def ns_running? + !(ns_shuttingdown? || ns_shutdown?) + end + + def ns_shuttingdown? + @executor.isShutdown && !@executor.isTerminated + end + + def ns_shutdown? + @executor.isTerminated + end + + class Job + include Runnable + def initialize(args, block) + @args = args + @block = block + end + + def run + @block.call(*@args) + end + end + private_constant :Job + end + + class DaemonThreadFactory + # hide include from YARD + send :include, java.util.concurrent.ThreadFactory + + def initialize(daemonize = true) + @daemonize = daemonize + @java_thread_factory = java.util.concurrent.Executors.defaultThreadFactory + end + + def newThread(runnable) + thread = @java_thread_factory.newThread(runnable) + thread.setDaemon(@daemonize) + return thread + end + end + + private_constant :DaemonThreadFactory + + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/java_single_thread_executor.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/java_single_thread_executor.rb new file mode 100644 index 00000000..7aa24f2d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/java_single_thread_executor.rb @@ -0,0 +1,30 @@ +if Concurrent.on_jruby? + + require 'concurrent/executor/java_executor_service' + require 'concurrent/executor/serial_executor_service' + + module Concurrent + + # @!macro single_thread_executor + # @!macro abstract_executor_service_public_api + # @!visibility private + class JavaSingleThreadExecutor < JavaExecutorService + include SerialExecutorService + + # @!macro single_thread_executor_method_initialize + def initialize(opts = {}) + super(opts) + end + + private + + def ns_initialize(opts) + @executor = java.util.concurrent.Executors.newSingleThreadExecutor( + DaemonThreadFactory.new(ns_auto_terminate?) + ) + @fallback_policy = opts.fetch(:fallback_policy, :discard) + raise ArgumentError.new("#{@fallback_policy} is not a valid fallback policy") unless FALLBACK_POLICY_CLASSES.keys.include?(@fallback_policy) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb new file mode 100644 index 00000000..598a5f91 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb @@ -0,0 +1,145 @@ +if Concurrent.on_jruby? + + require 'concurrent/executor/java_executor_service' + + module Concurrent + + # @!macro thread_pool_executor + # @!macro thread_pool_options + # @!visibility private + class JavaThreadPoolExecutor < JavaExecutorService + + # @!macro thread_pool_executor_constant_default_max_pool_size + DEFAULT_MAX_POOL_SIZE = java.lang.Integer::MAX_VALUE # 2147483647 + + # @!macro thread_pool_executor_constant_default_min_pool_size + DEFAULT_MIN_POOL_SIZE = 0 + + # @!macro thread_pool_executor_constant_default_max_queue_size + DEFAULT_MAX_QUEUE_SIZE = 0 + + # @!macro thread_pool_executor_constant_default_thread_timeout + DEFAULT_THREAD_IDLETIMEOUT = 60 + + # @!macro thread_pool_executor_constant_default_synchronous + DEFAULT_SYNCHRONOUS = false + + # @!macro thread_pool_executor_attr_reader_max_length + attr_reader :max_length + + # @!macro thread_pool_executor_attr_reader_max_queue + attr_reader :max_queue + + # @!macro thread_pool_executor_attr_reader_synchronous + attr_reader :synchronous + + # @!macro thread_pool_executor_method_initialize + def initialize(opts = {}) + super(opts) + end + + # @!macro executor_service_method_can_overflow_question + def can_overflow? + @max_queue != 0 + end + + # @!macro thread_pool_executor_attr_reader_min_length + def min_length + @executor.getCorePoolSize + end + + # @!macro thread_pool_executor_attr_reader_max_length + def max_length + @executor.getMaximumPoolSize + end + + # @!macro thread_pool_executor_attr_reader_length + def length + @executor.getPoolSize + end + + # @!macro thread_pool_executor_attr_reader_largest_length + def largest_length + @executor.getLargestPoolSize + end + + # @!macro thread_pool_executor_attr_reader_scheduled_task_count + def scheduled_task_count + @executor.getTaskCount + end + + # @!macro thread_pool_executor_attr_reader_completed_task_count + def completed_task_count + @executor.getCompletedTaskCount + end + + # @!macro thread_pool_executor_method_active_count + def active_count + @executor.getActiveCount + end + + # @!macro thread_pool_executor_attr_reader_idletime + def idletime + @executor.getKeepAliveTime(java.util.concurrent.TimeUnit::SECONDS) + end + + # @!macro thread_pool_executor_attr_reader_queue_length + def queue_length + @executor.getQueue.size + end + + # @!macro thread_pool_executor_attr_reader_remaining_capacity + def remaining_capacity + @max_queue == 0 ? -1 : @executor.getQueue.remainingCapacity + end + + # @!macro executor_service_method_running_question + def running? + super && !@executor.isTerminating + end + + # @!macro thread_pool_executor_method_prune_pool + def prune_pool + end + + private + + def ns_initialize(opts) + min_length = opts.fetch(:min_threads, DEFAULT_MIN_POOL_SIZE).to_i + max_length = opts.fetch(:max_threads, DEFAULT_MAX_POOL_SIZE).to_i + idletime = opts.fetch(:idletime, DEFAULT_THREAD_IDLETIMEOUT).to_i + @max_queue = opts.fetch(:max_queue, DEFAULT_MAX_QUEUE_SIZE).to_i + @synchronous = opts.fetch(:synchronous, DEFAULT_SYNCHRONOUS) + @fallback_policy = opts.fetch(:fallback_policy, :abort) + + raise ArgumentError.new("`synchronous` cannot be set unless `max_queue` is 0") if @synchronous && @max_queue > 0 + raise ArgumentError.new("`max_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}") if max_length < DEFAULT_MIN_POOL_SIZE + raise ArgumentError.new("`max_threads` cannot be greater than #{DEFAULT_MAX_POOL_SIZE}") if max_length > DEFAULT_MAX_POOL_SIZE + raise ArgumentError.new("`min_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}") if min_length < DEFAULT_MIN_POOL_SIZE + raise ArgumentError.new("`min_threads` cannot be more than `max_threads`") if min_length > max_length + raise ArgumentError.new("#{fallback_policy} is not a valid fallback policy") unless FALLBACK_POLICY_CLASSES.include?(@fallback_policy) + + if @max_queue == 0 + if @synchronous + queue = java.util.concurrent.SynchronousQueue.new + else + queue = java.util.concurrent.LinkedBlockingQueue.new + end + else + queue = java.util.concurrent.LinkedBlockingQueue.new(@max_queue) + end + + @executor = java.util.concurrent.ThreadPoolExecutor.new( + min_length, + max_length, + idletime, + java.util.concurrent.TimeUnit::SECONDS, + queue, + DaemonThreadFactory.new(ns_auto_terminate?), + FALLBACK_POLICY_CLASSES[@fallback_policy].new) + + end + end + + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb new file mode 100644 index 00000000..1f7301b9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb @@ -0,0 +1,82 @@ +require 'concurrent/executor/abstract_executor_service' +require 'concurrent/atomic/event' + +module Concurrent + + # @!macro abstract_executor_service_public_api + # @!visibility private + class RubyExecutorService < AbstractExecutorService + safe_initialization! + + def initialize(*args, &block) + super + @StopEvent = Event.new + @StoppedEvent = Event.new + end + + def post(*args, &task) + raise ArgumentError.new('no block given') unless block_given? + deferred_action = synchronize { + if running? + ns_execute(*args, &task) + else + fallback_action(*args, &task) + end + } + if deferred_action + deferred_action.call + else + true + end + end + + def shutdown + synchronize do + break unless running? + stop_event.set + ns_shutdown_execution + end + true + end + + def kill + synchronize do + break if shutdown? + stop_event.set + ns_kill_execution + stopped_event.set + end + true + end + + def wait_for_termination(timeout = nil) + stopped_event.wait(timeout) + end + + private + + def stop_event + @StopEvent + end + + def stopped_event + @StoppedEvent + end + + def ns_shutdown_execution + stopped_event.set + end + + def ns_running? + !stop_event.set? + end + + def ns_shuttingdown? + !(ns_running? || ns_shutdown?) + end + + def ns_shutdown? + stopped_event.set? + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb new file mode 100644 index 00000000..916337d4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb @@ -0,0 +1,21 @@ +require 'concurrent/executor/ruby_thread_pool_executor' + +module Concurrent + + # @!macro single_thread_executor + # @!macro abstract_executor_service_public_api + # @!visibility private + class RubySingleThreadExecutor < RubyThreadPoolExecutor + + # @!macro single_thread_executor_method_initialize + def initialize(opts = {}) + super( + min_threads: 1, + max_threads: 1, + max_queue: 0, + idletime: DEFAULT_THREAD_IDLETIMEOUT, + fallback_policy: opts.fetch(:fallback_policy, :discard), + ) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb new file mode 100644 index 00000000..9375acf3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb @@ -0,0 +1,373 @@ +require 'thread' +require 'concurrent/atomic/event' +require 'concurrent/concern/logging' +require 'concurrent/executor/ruby_executor_service' +require 'concurrent/utility/monotonic_time' + +module Concurrent + + # @!macro thread_pool_executor + # @!macro thread_pool_options + # @!visibility private + class RubyThreadPoolExecutor < RubyExecutorService + + # @!macro thread_pool_executor_constant_default_max_pool_size + DEFAULT_MAX_POOL_SIZE = 2_147_483_647 # java.lang.Integer::MAX_VALUE + + # @!macro thread_pool_executor_constant_default_min_pool_size + DEFAULT_MIN_POOL_SIZE = 0 + + # @!macro thread_pool_executor_constant_default_max_queue_size + DEFAULT_MAX_QUEUE_SIZE = 0 + + # @!macro thread_pool_executor_constant_default_thread_timeout + DEFAULT_THREAD_IDLETIMEOUT = 60 + + # @!macro thread_pool_executor_constant_default_synchronous + DEFAULT_SYNCHRONOUS = false + + # @!macro thread_pool_executor_attr_reader_max_length + attr_reader :max_length + + # @!macro thread_pool_executor_attr_reader_min_length + attr_reader :min_length + + # @!macro thread_pool_executor_attr_reader_idletime + attr_reader :idletime + + # @!macro thread_pool_executor_attr_reader_max_queue + attr_reader :max_queue + + # @!macro thread_pool_executor_attr_reader_synchronous + attr_reader :synchronous + + # @!macro thread_pool_executor_method_initialize + def initialize(opts = {}) + super(opts) + end + + # @!macro thread_pool_executor_attr_reader_largest_length + def largest_length + synchronize { @largest_length } + end + + # @!macro thread_pool_executor_attr_reader_scheduled_task_count + def scheduled_task_count + synchronize { @scheduled_task_count } + end + + # @!macro thread_pool_executor_attr_reader_completed_task_count + def completed_task_count + synchronize { @completed_task_count } + end + + # @!macro thread_pool_executor_method_active_count + def active_count + synchronize do + @pool.length - @ready.length + end + end + + # @!macro executor_service_method_can_overflow_question + def can_overflow? + synchronize { ns_limited_queue? } + end + + # @!macro thread_pool_executor_attr_reader_length + def length + synchronize { @pool.length } + end + + # @!macro thread_pool_executor_attr_reader_queue_length + def queue_length + synchronize { @queue.length } + end + + # @!macro thread_pool_executor_attr_reader_remaining_capacity + def remaining_capacity + synchronize do + if ns_limited_queue? + @max_queue - @queue.length + else + -1 + end + end + end + + # @!visibility private + def remove_busy_worker(worker) + synchronize { ns_remove_busy_worker worker } + end + + # @!visibility private + def ready_worker(worker, last_message) + synchronize { ns_ready_worker worker, last_message } + end + + # @!visibility private + def worker_died(worker) + synchronize { ns_worker_died worker } + end + + # @!visibility private + def worker_task_completed + synchronize { @completed_task_count += 1 } + end + + # @!macro thread_pool_executor_method_prune_pool + def prune_pool + synchronize { ns_prune_pool } + end + + private + + # @!visibility private + def ns_initialize(opts) + @min_length = opts.fetch(:min_threads, DEFAULT_MIN_POOL_SIZE).to_i + @max_length = opts.fetch(:max_threads, DEFAULT_MAX_POOL_SIZE).to_i + @idletime = opts.fetch(:idletime, DEFAULT_THREAD_IDLETIMEOUT).to_i + @max_queue = opts.fetch(:max_queue, DEFAULT_MAX_QUEUE_SIZE).to_i + @synchronous = opts.fetch(:synchronous, DEFAULT_SYNCHRONOUS) + @fallback_policy = opts.fetch(:fallback_policy, :abort) + + raise ArgumentError.new("`synchronous` cannot be set unless `max_queue` is 0") if @synchronous && @max_queue > 0 + raise ArgumentError.new("#{@fallback_policy} is not a valid fallback policy") unless FALLBACK_POLICIES.include?(@fallback_policy) + raise ArgumentError.new("`max_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}") if @max_length < DEFAULT_MIN_POOL_SIZE + raise ArgumentError.new("`max_threads` cannot be greater than #{DEFAULT_MAX_POOL_SIZE}") if @max_length > DEFAULT_MAX_POOL_SIZE + raise ArgumentError.new("`min_threads` cannot be less than #{DEFAULT_MIN_POOL_SIZE}") if @min_length < DEFAULT_MIN_POOL_SIZE + raise ArgumentError.new("`min_threads` cannot be more than `max_threads`") if min_length > max_length + + @pool = [] # all workers + @ready = [] # used as a stash (most idle worker is at the start) + @queue = [] # used as queue + # @ready or @queue is empty at all times + @scheduled_task_count = 0 + @completed_task_count = 0 + @largest_length = 0 + @workers_counter = 0 + @ruby_pid = $$ # detects if Ruby has forked + + @gc_interval = opts.fetch(:gc_interval, @idletime / 2.0).to_i # undocumented + @next_gc_time = Concurrent.monotonic_time + @gc_interval + end + + # @!visibility private + def ns_limited_queue? + @max_queue != 0 + end + + # @!visibility private + def ns_execute(*args, &task) + ns_reset_if_forked + + if ns_assign_worker(*args, &task) || ns_enqueue(*args, &task) + @scheduled_task_count += 1 + else + return fallback_action(*args, &task) + end + + ns_prune_pool if @next_gc_time < Concurrent.monotonic_time + nil + end + + # @!visibility private + def ns_shutdown_execution + ns_reset_if_forked + + if @pool.empty? + # nothing to do + stopped_event.set + end + + if @queue.empty? + # no more tasks will be accepted, just stop all workers + @pool.each(&:stop) + end + end + + # @!visibility private + def ns_kill_execution + # TODO log out unprocessed tasks in queue + # TODO try to shutdown first? + @pool.each(&:kill) + @pool.clear + @ready.clear + end + + # tries to assign task to a worker, tries to get one from @ready or to create new one + # @return [true, false] if task is assigned to a worker + # + # @!visibility private + def ns_assign_worker(*args, &task) + # keep growing if the pool is not at the minimum yet + worker, _ = (@ready.pop if @pool.size >= @min_length) || ns_add_busy_worker + if worker + worker << [task, args] + true + else + false + end + rescue ThreadError + # Raised when the operating system refuses to create the new thread + return false + end + + # tries to enqueue task + # @return [true, false] if enqueued + # + # @!visibility private + def ns_enqueue(*args, &task) + return false if @synchronous + + if !ns_limited_queue? || @queue.size < @max_queue + @queue << [task, args] + true + else + false + end + end + + # @!visibility private + def ns_worker_died(worker) + ns_remove_busy_worker worker + replacement_worker = ns_add_busy_worker + ns_ready_worker replacement_worker, Concurrent.monotonic_time, false if replacement_worker + end + + # creates new worker which has to receive work to do after it's added + # @return [nil, Worker] nil of max capacity is reached + # + # @!visibility private + def ns_add_busy_worker + return if @pool.size >= @max_length + + @workers_counter += 1 + @pool << (worker = Worker.new(self, @workers_counter)) + @largest_length = @pool.length if @pool.length > @largest_length + worker + end + + # handle ready worker, giving it new job or assigning back to @ready + # + # @!visibility private + def ns_ready_worker(worker, last_message, success = true) + task_and_args = @queue.shift + if task_and_args + worker << task_and_args + else + # stop workers when !running?, do not return them to @ready + if running? + raise unless last_message + @ready.push([worker, last_message]) + else + worker.stop + end + end + end + + # removes a worker which is not in not tracked in @ready + # + # @!visibility private + def ns_remove_busy_worker(worker) + @pool.delete(worker) + stopped_event.set if @pool.empty? && !running? + true + end + + # try oldest worker if it is idle for enough time, it's returned back at the start + # + # @!visibility private + def ns_prune_pool + now = Concurrent.monotonic_time + stopped_workers = 0 + while !@ready.empty? && (@pool.size - stopped_workers > @min_length) + worker, last_message = @ready.first + if now - last_message > self.idletime + stopped_workers += 1 + @ready.shift + worker << :stop + else break + end + end + + @next_gc_time = Concurrent.monotonic_time + @gc_interval + end + + def ns_reset_if_forked + if $$ != @ruby_pid + @queue.clear + @ready.clear + @pool.clear + @scheduled_task_count = 0 + @completed_task_count = 0 + @largest_length = 0 + @workers_counter = 0 + @ruby_pid = $$ + end + end + + # @!visibility private + class Worker + include Concern::Logging + + def initialize(pool, id) + # instance variables accessed only under pool's lock so no need to sync here again + @queue = Queue.new + @pool = pool + @thread = create_worker @queue, pool, pool.idletime + + if @thread.respond_to?(:name=) + @thread.name = [pool.name, 'worker', id].compact.join('-') + end + end + + def <<(message) + @queue << message + end + + def stop + @queue << :stop + end + + def kill + @thread.kill + end + + private + + def create_worker(queue, pool, idletime) + Thread.new(queue, pool, idletime) do |my_queue, my_pool, my_idletime| + catch(:stop) do + loop do + + case message = my_queue.pop + when :stop + my_pool.remove_busy_worker(self) + throw :stop + + else + task, args = message + run_task my_pool, task, args + my_pool.ready_worker(self, Concurrent.monotonic_time) + end + end + end + end + end + + def run_task(pool, task, args) + task.call(*args) + pool.worker_task_completed + rescue => ex + # let it fail + log DEBUG, ex + rescue Exception => ex + log ERROR, ex + pool.worker_died(self) + throw :stop + end + end + + private_constant :Worker + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb new file mode 100644 index 00000000..f796b857 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb @@ -0,0 +1,35 @@ +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # A simple utility class that executes a callable and returns and array of three elements: + # success - indicating if the callable has been executed without errors + # value - filled by the callable result if it has been executed without errors, nil otherwise + # reason - the error risen by the callable if it has been executed with errors, nil otherwise + class SafeTaskExecutor < Synchronization::LockableObject + + def initialize(task, opts = {}) + @task = task + @exception_class = opts.fetch(:rescue_exception, false) ? Exception : StandardError + super() # ensures visibility + end + + # @return [Array] + def execute(*args) + success = true + value = reason = nil + + synchronize do + begin + value = @task.call(*args) + success = true + rescue @exception_class => ex + reason = ex + success = false + end + end + + [success, value, reason] + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/serial_executor_service.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/serial_executor_service.rb new file mode 100644 index 00000000..f1c38ecf --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/serial_executor_service.rb @@ -0,0 +1,34 @@ +require 'concurrent/executor/executor_service' + +module Concurrent + + # Indicates that the including `ExecutorService` guarantees + # that all operations will occur in the order they are post and that no + # two operations may occur simultaneously. This module provides no + # functionality and provides no guarantees. That is the responsibility + # of the including class. This module exists solely to allow the including + # object to be interrogated for its serialization status. + # + # @example + # class Foo + # include Concurrent::SerialExecutor + # end + # + # foo = Foo.new + # + # foo.is_a? Concurrent::ExecutorService #=> true + # foo.is_a? Concurrent::SerialExecutor #=> true + # foo.serialized? #=> true + # + # @!visibility private + module SerialExecutorService + include ExecutorService + + # @!macro executor_service_method_serialized_question + # + # @note Always returns `true` + def serialized? + true + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb new file mode 100644 index 00000000..4db7c7f0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb @@ -0,0 +1,107 @@ +require 'concurrent/errors' +require 'concurrent/concern/logging' +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # Ensures passed jobs in a serialized order never running at the same time. + class SerializedExecution < Synchronization::LockableObject + include Concern::Logging + + def initialize() + super() + synchronize { ns_initialize } + end + + Job = Struct.new(:executor, :args, :block) do + def call + block.call(*args) + end + end + + # Submit a task to the executor for asynchronous processing. + # + # @param [Executor] executor to be used for this job + # + # @param [Array] args zero or more arguments to be passed to the task + # + # @yield the asynchronous task to perform + # + # @return [Boolean] `true` if the task is queued, `false` if the executor + # is not running + # + # @raise [ArgumentError] if no task is given + def post(executor, *args, &task) + posts [[executor, args, task]] + true + end + + # As {#post} but allows to submit multiple tasks at once, it's guaranteed that they will not + # be interleaved by other tasks. + # + # @param [Array, Proc)>] posts array of triplets where + # first is a {ExecutorService}, second is array of args for task, third is a task (Proc) + def posts(posts) + # if can_overflow? + # raise ArgumentError, 'SerializedExecution does not support thread-pools which can overflow' + # end + + return nil if posts.empty? + + jobs = posts.map { |executor, args, task| Job.new executor, args, task } + + job_to_post = synchronize do + if @being_executed + @stash.push(*jobs) + nil + else + @being_executed = true + @stash.push(*jobs[1..-1]) + jobs.first + end + end + + call_job job_to_post if job_to_post + true + end + + private + + def ns_initialize + @being_executed = false + @stash = [] + end + + def call_job(job) + did_it_run = begin + job.executor.post { work(job) } + true + rescue RejectedExecutionError => ex + false + end + + # TODO not the best idea to run it myself + unless did_it_run + begin + work job + rescue => ex + # let it fail + log DEBUG, ex + end + end + end + + # ensures next job is executed if any is stashed + def work(job) + job.call + ensure + synchronize do + job = @stash.shift || (@being_executed = false) + end + + # TODO maybe be able to tell caching pool to just enqueue this job, because the current one end at the end + # of this block + call_job job if job + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/serialized_execution_delegator.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/serialized_execution_delegator.rb new file mode 100644 index 00000000..8197781b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/serialized_execution_delegator.rb @@ -0,0 +1,28 @@ +require 'delegate' +require 'concurrent/executor/serial_executor_service' +require 'concurrent/executor/serialized_execution' + +module Concurrent + + # A wrapper/delegator for any `ExecutorService` that + # guarantees serialized execution of tasks. + # + # @see [SimpleDelegator](http://www.ruby-doc.org/stdlib-2.1.2/libdoc/delegate/rdoc/SimpleDelegator.html) + # @see Concurrent::SerializedExecution + class SerializedExecutionDelegator < SimpleDelegator + include SerialExecutorService + + def initialize(executor) + @executor = executor + @serializer = SerializedExecution.new + super(executor) + end + + # @!macro executor_service_method_post + def post(*args, &task) + raise ArgumentError.new('no block given') unless block_given? + return false unless running? + @serializer.post(@executor, *args, &task) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb new file mode 100644 index 00000000..0bc62afd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb @@ -0,0 +1,103 @@ +require 'concurrent/atomic/atomic_boolean' +require 'concurrent/atomic/atomic_fixnum' +require 'concurrent/atomic/event' +require 'concurrent/executor/executor_service' +require 'concurrent/executor/ruby_executor_service' + +module Concurrent + + # An executor service in which every operation spawns a new, + # independently operating thread. + # + # This is perhaps the most inefficient executor service in this + # library. It exists mainly for testing an debugging. Thread creation + # and management is expensive in Ruby and this executor performs no + # resource pooling. This can be very beneficial during testing and + # debugging because it decouples the using code from the underlying + # executor implementation. In production this executor will likely + # lead to suboptimal performance. + # + # @note Intended for use primarily in testing and debugging. + class SimpleExecutorService < RubyExecutorService + + # @!macro executor_service_method_post + def self.post(*args) + raise ArgumentError.new('no block given') unless block_given? + Thread.new(*args) do + Thread.current.abort_on_exception = false + yield(*args) + end + true + end + + # @!macro executor_service_method_left_shift + def self.<<(task) + post(&task) + self + end + + # @!macro executor_service_method_post + def post(*args, &task) + raise ArgumentError.new('no block given') unless block_given? + return false unless running? + @count.increment + Thread.new(*args) do + Thread.current.abort_on_exception = false + begin + yield(*args) + ensure + @count.decrement + @stopped.set if @running.false? && @count.value == 0 + end + end + end + + # @!macro executor_service_method_left_shift + def <<(task) + post(&task) + self + end + + # @!macro executor_service_method_running_question + def running? + @running.true? + end + + # @!macro executor_service_method_shuttingdown_question + def shuttingdown? + @running.false? && ! @stopped.set? + end + + # @!macro executor_service_method_shutdown_question + def shutdown? + @stopped.set? + end + + # @!macro executor_service_method_shutdown + def shutdown + @running.make_false + @stopped.set if @count.value == 0 + true + end + + # @!macro executor_service_method_kill + def kill + @running.make_false + @stopped.set + true + end + + # @!macro executor_service_method_wait_for_termination + def wait_for_termination(timeout = nil) + @stopped.wait(timeout) + end + + private + + def ns_initialize(*args) + @running = Concurrent::AtomicBoolean.new(true) + @stopped = Concurrent::Event.new + @count = Concurrent::AtomicFixnum.new(0) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb new file mode 100644 index 00000000..220eb0ff --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb @@ -0,0 +1,57 @@ +require 'concurrent/utility/engine' +require 'concurrent/executor/ruby_single_thread_executor' + +module Concurrent + + if Concurrent.on_jruby? + require 'concurrent/executor/java_single_thread_executor' + end + + SingleThreadExecutorImplementation = case + when Concurrent.on_jruby? + JavaSingleThreadExecutor + else + RubySingleThreadExecutor + end + private_constant :SingleThreadExecutorImplementation + + # @!macro single_thread_executor + # + # A thread pool with a single thread an unlimited queue. Should the thread + # die for any reason it will be removed and replaced, thus ensuring that + # the executor will always remain viable and available to process jobs. + # + # A common pattern for background processing is to create a single thread + # on which an infinite loop is run. The thread's loop blocks on an input + # source (perhaps blocking I/O or a queue) and processes each input as it + # is received. This pattern has several issues. The thread itself is highly + # susceptible to errors during processing. Also, the thread itself must be + # constantly monitored and restarted should it die. `SingleThreadExecutor` + # encapsulates all these behaviors. The task processor is highly resilient + # to errors from within tasks. Also, should the thread die it will + # automatically be restarted. + # + # The API and behavior of this class are based on Java's `SingleThreadExecutor`. + # + # @!macro abstract_executor_service_public_api + class SingleThreadExecutor < SingleThreadExecutorImplementation + + # @!macro single_thread_executor_method_initialize + # + # Create a new thread pool. + # + # @option opts [Symbol] :fallback_policy (:discard) the policy for handling new + # tasks that are received when the queue size has reached + # `max_queue` or the executor has shut down + # + # @raise [ArgumentError] if `:fallback_policy` is not one of the values specified + # in `FALLBACK_POLICIES` + # + # @see http://docs.oracle.com/javase/tutorial/essential/concurrency/pools.html + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html + # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html + + # @!method initialize(opts = {}) + # @!macro single_thread_executor_method_initialize + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/thread_pool_executor.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/thread_pool_executor.rb new file mode 100644 index 00000000..253d46a9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/thread_pool_executor.rb @@ -0,0 +1,88 @@ +require 'concurrent/utility/engine' +require 'concurrent/executor/ruby_thread_pool_executor' + +module Concurrent + + if Concurrent.on_jruby? + require 'concurrent/executor/java_thread_pool_executor' + end + + ThreadPoolExecutorImplementation = case + when Concurrent.on_jruby? + JavaThreadPoolExecutor + else + RubyThreadPoolExecutor + end + private_constant :ThreadPoolExecutorImplementation + + # @!macro thread_pool_executor + # + # An abstraction composed of one or more threads and a task queue. Tasks + # (blocks or `proc` objects) are submitted to the pool and added to the queue. + # The threads in the pool remove the tasks and execute them in the order + # they were received. + # + # A `ThreadPoolExecutor` will automatically adjust the pool size according + # to the bounds set by `min-threads` and `max-threads`. When a new task is + # submitted and fewer than `min-threads` threads are running, a new thread + # is created to handle the request, even if other worker threads are idle. + # If there are more than `min-threads` but less than `max-threads` threads + # running, a new thread will be created only if the queue is full. + # + # Threads that are idle for too long will be garbage collected, down to the + # configured minimum options. Should a thread crash it, too, will be garbage collected. + # + # `ThreadPoolExecutor` is based on the Java class of the same name. From + # the official Java documentation; + # + # > Thread pools address two different problems: they usually provide + # > improved performance when executing large numbers of asynchronous tasks, + # > due to reduced per-task invocation overhead, and they provide a means + # > of bounding and managing the resources, including threads, consumed + # > when executing a collection of tasks. Each ThreadPoolExecutor also + # > maintains some basic statistics, such as the number of completed tasks. + # > + # > To be useful across a wide range of contexts, this class provides many + # > adjustable parameters and extensibility hooks. However, programmers are + # > urged to use the more convenient Executors factory methods + # > [CachedThreadPool] (unbounded thread pool, with automatic thread reclamation), + # > [FixedThreadPool] (fixed size thread pool) and [SingleThreadExecutor] (single + # > background thread), that preconfigure settings for the most common usage + # > scenarios. + # + # @!macro thread_pool_options + # + # @!macro thread_pool_executor_public_api + class ThreadPoolExecutor < ThreadPoolExecutorImplementation + + # @!macro thread_pool_executor_method_initialize + # + # Create a new thread pool. + # + # @param [Hash] opts the options which configure the thread pool. + # + # @option opts [Integer] :max_threads (DEFAULT_MAX_POOL_SIZE) the maximum + # number of threads to be created + # @option opts [Integer] :min_threads (DEFAULT_MIN_POOL_SIZE) When a new task is submitted + # and fewer than `min_threads` are running, a new thread is created + # @option opts [Integer] :idletime (DEFAULT_THREAD_IDLETIMEOUT) the maximum + # number of seconds a thread may be idle before being reclaimed + # @option opts [Integer] :max_queue (DEFAULT_MAX_QUEUE_SIZE) the maximum + # number of tasks allowed in the work queue at any one time; a value of + # zero means the queue may grow without bound + # @option opts [Symbol] :fallback_policy (:abort) the policy for handling new + # tasks that are received when the queue size has reached + # `max_queue` or the executor has shut down + # @option opts [Boolean] :synchronous (DEFAULT_SYNCHRONOUS) whether or not a value of 0 + # for :max_queue means the queue must perform direct hand-off rather than unbounded. + # @raise [ArgumentError] if `:max_threads` is less than one + # @raise [ArgumentError] if `:min_threads` is less than zero + # @raise [ArgumentError] if `:fallback_policy` is not one of the values specified + # in `FALLBACK_POLICIES` + # + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html + + # @!method initialize(opts = {}) + # @!macro thread_pool_executor_method_initialize + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/timer_set.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/timer_set.rb new file mode 100644 index 00000000..759dce09 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executor/timer_set.rb @@ -0,0 +1,176 @@ +require 'concurrent/scheduled_task' +require 'concurrent/atomic/event' +require 'concurrent/collection/non_concurrent_priority_queue' +require 'concurrent/executor/executor_service' +require 'concurrent/executor/single_thread_executor' +require 'concurrent/errors' +require 'concurrent/options' + +module Concurrent + + # Executes a collection of tasks, each after a given delay. A master task + # monitors the set and schedules each task for execution at the appropriate + # time. Tasks are run on the global thread pool or on the supplied executor. + # Each task is represented as a `ScheduledTask`. + # + # @see Concurrent::ScheduledTask + # + # @!macro monotonic_clock_warning + class TimerSet < RubyExecutorService + + # Create a new set of timed tasks. + # + # @!macro executor_options + # + # @param [Hash] opts the options used to specify the executor on which to perform actions + # @option opts [Executor] :executor when set use the given `Executor` instance. + # Three special values are also supported: `:task` returns the global task pool, + # `:operation` returns the global operation pool, and `:immediate` returns a new + # `ImmediateExecutor` object. + def initialize(opts = {}) + super(opts) + end + + # Post a task to be execute run after a given delay (in seconds). If the + # delay is less than 1/100th of a second the task will be immediately post + # to the executor. + # + # @param [Float] delay the number of seconds to wait for before executing the task. + # @param [Array] args the arguments passed to the task on execution. + # + # @yield the task to be performed. + # + # @return [Concurrent::ScheduledTask, false] IVar representing the task if the post + # is successful; false after shutdown. + # + # @raise [ArgumentError] if the intended execution time is not in the future. + # @raise [ArgumentError] if no block is given. + def post(delay, *args, &task) + raise ArgumentError.new('no block given') unless block_given? + return false unless running? + opts = { executor: @task_executor, + args: args, + timer_set: self } + task = ScheduledTask.execute(delay, opts, &task) # may raise exception + task.unscheduled? ? false : task + end + + # Begin an immediate shutdown. In-progress tasks will be allowed to + # complete but enqueued tasks will be dismissed and no new tasks + # will be accepted. Has no additional effect if the thread pool is + # not running. + def kill + shutdown + end + + private :<< + + private + + # Initialize the object. + # + # @param [Hash] opts the options to create the object with. + # @!visibility private + def ns_initialize(opts) + @queue = Collection::NonConcurrentPriorityQueue.new(order: :min) + @task_executor = Options.executor_from_options(opts) || Concurrent.global_io_executor + @timer_executor = SingleThreadExecutor.new + @condition = Event.new + @ruby_pid = $$ # detects if Ruby has forked + end + + # Post the task to the internal queue. + # + # @note This is intended as a callback method from ScheduledTask + # only. It is not intended to be used directly. Post a task + # by using the `SchedulesTask#execute` method. + # + # @!visibility private + def post_task(task) + synchronize { ns_post_task(task) } + end + + # @!visibility private + def ns_post_task(task) + return false unless ns_running? + ns_reset_if_forked + if (task.initial_delay) <= 0.01 + task.executor.post { task.process_task } + else + @queue.push(task) + # only post the process method when the queue is empty + @timer_executor.post(&method(:process_tasks)) if @queue.length == 1 + @condition.set + end + true + end + + # Remove the given task from the queue. + # + # @note This is intended as a callback method from `ScheduledTask` + # only. It is not intended to be used directly. Cancel a task + # by using the `ScheduledTask#cancel` method. + # + # @!visibility private + def remove_task(task) + synchronize { @queue.delete(task) } + end + + # `ExecutorService` callback called during shutdown. + # + # @!visibility private + def ns_shutdown_execution + ns_reset_if_forked + @queue.clear + @timer_executor.kill + stopped_event.set + end + + def ns_reset_if_forked + if $$ != @ruby_pid + @queue.clear + @condition.reset + @ruby_pid = $$ + end + end + + # Run a loop and execute tasks in the scheduled order and at the approximate + # scheduled time. If no tasks remain the thread will exit gracefully so that + # garbage collection can occur. If there are no ready tasks it will sleep + # for up to 60 seconds waiting for the next scheduled task. + # + # @!visibility private + def process_tasks + loop do + task = synchronize { @condition.reset; @queue.peek } + break unless task + + now = Concurrent.monotonic_time + diff = task.schedule_time - now + + if diff <= 0 + # We need to remove the task from the queue before passing + # it to the executor, to avoid race conditions where we pass + # the peek'ed task to the executor and then pop a different + # one that's been added in the meantime. + # + # Note that there's no race condition between the peek and + # this pop - this pop could retrieve a different task from + # the peek, but that task would be due to fire now anyway + # (because @queue is a priority queue, and this thread is + # the only reader, so whatever timer is at the head of the + # queue now must have the same pop time, or a closer one, as + # when we peeked). + task = synchronize { @queue.pop } + begin + task.executor.post { task.process_task } + rescue RejectedExecutionError + # ignore and continue + end + else + @condition.wait([diff, 60].min) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executors.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executors.rb new file mode 100644 index 00000000..eb1972ce --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/executors.rb @@ -0,0 +1,20 @@ +require 'concurrent/executor/abstract_executor_service' +require 'concurrent/executor/cached_thread_pool' +require 'concurrent/executor/executor_service' +require 'concurrent/executor/fixed_thread_pool' +require 'concurrent/executor/immediate_executor' +require 'concurrent/executor/indirect_immediate_executor' +require 'concurrent/executor/java_executor_service' +require 'concurrent/executor/java_single_thread_executor' +require 'concurrent/executor/java_thread_pool_executor' +require 'concurrent/executor/ruby_executor_service' +require 'concurrent/executor/ruby_single_thread_executor' +require 'concurrent/executor/ruby_thread_pool_executor' +require 'concurrent/executor/cached_thread_pool' +require 'concurrent/executor/safe_task_executor' +require 'concurrent/executor/serial_executor_service' +require 'concurrent/executor/serialized_execution' +require 'concurrent/executor/serialized_execution_delegator' +require 'concurrent/executor/single_thread_executor' +require 'concurrent/executor/thread_pool_executor' +require 'concurrent/executor/timer_set' diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/future.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/future.rb new file mode 100644 index 00000000..1af182ec --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/future.rb @@ -0,0 +1,141 @@ +require 'thread' +require 'concurrent/constants' +require 'concurrent/errors' +require 'concurrent/ivar' +require 'concurrent/executor/safe_task_executor' + +require 'concurrent/options' + +# TODO (pitr-ch 14-Mar-2017): deprecate, Future, Promise, etc. + + +module Concurrent + + # {include:file:docs-source/future.md} + # + # @!macro copy_options + # + # @see http://ruby-doc.org/stdlib-2.1.1/libdoc/observer/rdoc/Observable.html Ruby Observable module + # @see http://clojuredocs.org/clojure_core/clojure.core/future Clojure's future function + # @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html java.util.concurrent.Future + class Future < IVar + + # Create a new `Future` in the `:unscheduled` state. + # + # @yield the asynchronous operation to perform + # + # @!macro executor_and_deref_options + # + # @option opts [object, Array] :args zero or more arguments to be passed the task + # block on execution + # + # @raise [ArgumentError] if no block is given + def initialize(opts = {}, &block) + raise ArgumentError.new('no block given') unless block_given? + super(NULL, opts.merge(__task_from_block__: block), &nil) + end + + # Execute an `:unscheduled` `Future`. Immediately sets the state to `:pending` and + # passes the block to a new thread/thread pool for eventual execution. + # Does nothing if the `Future` is in any state other than `:unscheduled`. + # + # @return [Future] a reference to `self` + # + # @example Instance and execute in separate steps + # future = Concurrent::Future.new{ sleep(1); 42 } + # future.state #=> :unscheduled + # future.execute + # future.state #=> :pending + # + # @example Instance and execute in one line + # future = Concurrent::Future.new{ sleep(1); 42 }.execute + # future.state #=> :pending + def execute + if compare_and_set_state(:pending, :unscheduled) + @executor.post{ safe_execute(@task, @args) } + self + end + end + + # Create a new `Future` object with the given block, execute it, and return the + # `:pending` object. + # + # @yield the asynchronous operation to perform + # + # @!macro executor_and_deref_options + # + # @option opts [object, Array] :args zero or more arguments to be passed the task + # block on execution + # + # @raise [ArgumentError] if no block is given + # + # @return [Future] the newly created `Future` in the `:pending` state + # + # @example + # future = Concurrent::Future.execute{ sleep(1); 42 } + # future.state #=> :pending + def self.execute(opts = {}, &block) + Future.new(opts, &block).execute + end + + # @!macro ivar_set_method + def set(value = NULL, &block) + check_for_block_or_value!(block_given?, value) + synchronize do + if @state != :unscheduled + raise MultipleAssignmentError + else + @task = block || Proc.new { value } + end + end + execute + end + + # Attempt to cancel the operation if it has not already processed. + # The operation can only be cancelled while still `pending`. It cannot + # be cancelled once it has begun processing or has completed. + # + # @return [Boolean] was the operation successfully cancelled. + def cancel + if compare_and_set_state(:cancelled, :pending) + complete(false, nil, CancelledOperationError.new) + true + else + false + end + end + + # Has the operation been successfully cancelled? + # + # @return [Boolean] + def cancelled? + state == :cancelled + end + + # Wait the given number of seconds for the operation to complete. + # On timeout attempt to cancel the operation. + # + # @param [Numeric] timeout the maximum time in seconds to wait. + # @return [Boolean] true if the operation completed before the timeout + # else false + def wait_or_cancel(timeout) + wait(timeout) + if complete? + true + else + cancel + false + end + end + + protected + + def ns_initialize(value, opts) + super + @state = :unscheduled + @task = opts[:__task_from_block__] + @executor = Options.executor_from_options(opts) || Concurrent.global_io_executor + @args = get_arguments_from(opts) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/hash.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/hash.rb new file mode 100644 index 00000000..db0208e0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/hash.rb @@ -0,0 +1,52 @@ +require 'concurrent/utility/engine' +require 'concurrent/thread_safe/util' + +module Concurrent + + # @!macro concurrent_hash + # + # A thread-safe subclass of Hash. This version locks against the object + # itself for every method call, ensuring only one thread can be reading + # or writing at a time. This includes iteration methods like `#each`, + # which takes the lock repeatedly when reading an item. + # + # @see http://ruby-doc.org/core/Hash.html Ruby standard library `Hash` + + # @!macro internal_implementation_note + HashImplementation = case + when Concurrent.on_cruby? + # Hash is not fully thread-safe on CRuby, see + # https://bugs.ruby-lang.org/issues/19237 + # https://github.com/ruby/ruby/commit/ffd52412ab + # https://github.com/ruby-concurrency/concurrent-ruby/issues/929 + # So we will need to add synchronization here (similar to Concurrent::Map). + ::Hash + + when Concurrent.on_jruby? + require 'jruby/synchronized' + + class JRubyHash < ::Hash + include JRuby::Synchronized + end + JRubyHash + + when Concurrent.on_truffleruby? + require 'concurrent/thread_safe/util/data_structures' + + class TruffleRubyHash < ::Hash + end + + ThreadSafe::Util.make_synchronized_on_truffleruby TruffleRubyHash + TruffleRubyHash + + else + warn 'Possibly unsupported Ruby implementation' + ::Hash + end + private_constant :HashImplementation + + # @!macro concurrent_hash + class Hash < HashImplementation + end + +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/immutable_struct.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/immutable_struct.rb new file mode 100644 index 00000000..48462e83 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/immutable_struct.rb @@ -0,0 +1,101 @@ +require 'concurrent/synchronization/abstract_struct' +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # A thread-safe, immutable variation of Ruby's standard `Struct`. + # + # @see http://ruby-doc.org/core/Struct.html Ruby standard library `Struct` + module ImmutableStruct + include Synchronization::AbstractStruct + + def self.included(base) + base.safe_initialization! + end + + # @!macro struct_values + def values + ns_values + end + + alias_method :to_a, :values + + # @!macro struct_values_at + def values_at(*indexes) + ns_values_at(indexes) + end + + # @!macro struct_inspect + def inspect + ns_inspect + end + + alias_method :to_s, :inspect + + # @!macro struct_merge + def merge(other, &block) + ns_merge(other, &block) + end + + # @!macro struct_to_h + def to_h + ns_to_h + end + + # @!macro struct_get + def [](member) + ns_get(member) + end + + # @!macro struct_equality + def ==(other) + ns_equality(other) + end + + # @!macro struct_each + def each(&block) + return enum_for(:each) unless block_given? + ns_each(&block) + end + + # @!macro struct_each_pair + def each_pair(&block) + return enum_for(:each_pair) unless block_given? + ns_each_pair(&block) + end + + # @!macro struct_select + def select(&block) + return enum_for(:select) unless block_given? + ns_select(&block) + end + + private + + # @!visibility private + def initialize_copy(original) + super(original) + ns_initialize_copy + end + + # @!macro struct_new + def self.new(*args, &block) + clazz_name = nil + if args.length == 0 + raise ArgumentError.new('wrong number of arguments (0 for 1+)') + elsif args.length > 0 && args.first.is_a?(String) + clazz_name = args.shift + end + FACTORY.define_struct(clazz_name, args, &block) + end + + FACTORY = Class.new(Synchronization::LockableObject) do + def define_struct(name, members, &block) + synchronize do + Synchronization::AbstractStruct.define_struct_class(ImmutableStruct, Synchronization::Object, name, members, &block) + end + end + end.new + private_constant :FACTORY + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/ivar.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/ivar.rb new file mode 100644 index 00000000..4165038f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/ivar.rb @@ -0,0 +1,208 @@ +require 'concurrent/constants' +require 'concurrent/errors' +require 'concurrent/collection/copy_on_write_observer_set' +require 'concurrent/concern/obligation' +require 'concurrent/concern/observable' +require 'concurrent/executor/safe_task_executor' +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # An `IVar` is like a future that you can assign. As a future is a value that + # is being computed that you can wait on, an `IVar` is a value that is waiting + # to be assigned, that you can wait on. `IVars` are single assignment and + # deterministic. + # + # Then, express futures as an asynchronous computation that assigns an `IVar`. + # The `IVar` becomes the primitive on which [futures](Future) and + # [dataflow](Dataflow) are built. + # + # An `IVar` is a single-element container that is normally created empty, and + # can only be set once. The I in `IVar` stands for immutable. Reading an + # `IVar` normally blocks until it is set. It is safe to set and read an `IVar` + # from different threads. + # + # If you want to have some parallel task set the value in an `IVar`, you want + # a `Future`. If you want to create a graph of parallel tasks all executed + # when the values they depend on are ready you want `dataflow`. `IVar` is + # generally a low-level primitive. + # + # ## Examples + # + # Create, set and get an `IVar` + # + # ```ruby + # ivar = Concurrent::IVar.new + # ivar.set 14 + # ivar.value #=> 14 + # ivar.set 2 # would now be an error + # ``` + # + # ## See Also + # + # 1. For the theory: Arvind, R. Nikhil, and K. Pingali. + # [I-Structures: Data structures for parallel computing](http://dl.acm.org/citation.cfm?id=69562). + # In Proceedings of Workshop on Graph Reduction, 1986. + # 2. For recent application: + # [DataDrivenFuture in Habanero Java from Rice](http://www.cs.rice.edu/~vs3/hjlib/doc/edu/rice/hj/api/HjDataDrivenFuture.html). + class IVar < Synchronization::LockableObject + include Concern::Obligation + include Concern::Observable + + # Create a new `IVar` in the `:pending` state with the (optional) initial value. + # + # @param [Object] value the initial value + # @param [Hash] opts the options to create a message with + # @option opts [String] :dup_on_deref (false) call `#dup` before returning + # the data + # @option opts [String] :freeze_on_deref (false) call `#freeze` before + # returning the data + # @option opts [String] :copy_on_deref (nil) call the given `Proc` passing + # the internal value and returning the value returned from the proc + def initialize(value = NULL, opts = {}, &block) + if value != NULL && block_given? + raise ArgumentError.new('provide only a value or a block') + end + super(&nil) + synchronize { ns_initialize(value, opts, &block) } + end + + # Add an observer on this object that will receive notification on update. + # + # Upon completion the `IVar` will notify all observers in a thread-safe way. + # The `func` method of the observer will be called with three arguments: the + # `Time` at which the `Future` completed the asynchronous operation, the + # final `value` (or `nil` on rejection), and the final `reason` (or `nil` on + # fulfillment). + # + # @param [Object] observer the object that will be notified of changes + # @param [Symbol] func symbol naming the method to call when this + # `Observable` has changes` + def add_observer(observer = nil, func = :update, &block) + raise ArgumentError.new('cannot provide both an observer and a block') if observer && block + direct_notification = false + + if block + observer = block + func = :call + end + + synchronize do + if event.set? + direct_notification = true + else + observers.add_observer(observer, func) + end + end + + observer.send(func, Time.now, self.value, reason) if direct_notification + observer + end + + # @!macro ivar_set_method + # Set the `IVar` to a value and wake or notify all threads waiting on it. + # + # @!macro ivar_set_parameters_and_exceptions + # @param [Object] value the value to store in the `IVar` + # @yield A block operation to use for setting the value + # @raise [ArgumentError] if both a value and a block are given + # @raise [Concurrent::MultipleAssignmentError] if the `IVar` has already + # been set or otherwise completed + # + # @return [IVar] self + def set(value = NULL) + check_for_block_or_value!(block_given?, value) + raise MultipleAssignmentError unless compare_and_set_state(:processing, :pending) + + begin + value = yield if block_given? + complete_without_notification(true, value, nil) + rescue => ex + complete_without_notification(false, nil, ex) + end + + notify_observers(self.value, reason) + self + end + + # @!macro ivar_fail_method + # Set the `IVar` to failed due to some error and wake or notify all threads waiting on it. + # + # @param [Object] reason for the failure + # @raise [Concurrent::MultipleAssignmentError] if the `IVar` has already + # been set or otherwise completed + # @return [IVar] self + def fail(reason = StandardError.new) + complete(false, nil, reason) + end + + # Attempt to set the `IVar` with the given value or block. Return a + # boolean indicating the success or failure of the set operation. + # + # @!macro ivar_set_parameters_and_exceptions + # + # @return [Boolean] true if the value was set else false + def try_set(value = NULL, &block) + set(value, &block) + true + rescue MultipleAssignmentError + false + end + + protected + + # @!visibility private + def ns_initialize(value, opts) + value = yield if block_given? + init_obligation + self.observers = Collection::CopyOnWriteObserverSet.new + set_deref_options(opts) + + @state = :pending + if value != NULL + ns_complete_without_notification(true, value, nil) + end + end + + # @!visibility private + def safe_execute(task, args = []) + if compare_and_set_state(:processing, :pending) + success, val, reason = SafeTaskExecutor.new(task, rescue_exception: true).execute(*@args) + complete(success, val, reason) + yield(success, val, reason) if block_given? + end + end + + # @!visibility private + def complete(success, value, reason) + complete_without_notification(success, value, reason) + notify_observers(self.value, reason) + self + end + + # @!visibility private + def complete_without_notification(success, value, reason) + synchronize { ns_complete_without_notification(success, value, reason) } + self + end + + # @!visibility private + def notify_observers(value, reason) + observers.notify_and_delete_observers{ [Time.now, value, reason] } + end + + # @!visibility private + def ns_complete_without_notification(success, value, reason) + raise MultipleAssignmentError if [:fulfilled, :rejected].include? @state + set_state(success, value, reason) + event.set + end + + # @!visibility private + def check_for_block_or_value!(block_given, value) # :nodoc: + if (block_given && value != NULL) || (! block_given && value == NULL) + raise ArgumentError.new('must set with either a value or a block') + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/map.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/map.rb new file mode 100644 index 00000000..b263f83d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/map.rb @@ -0,0 +1,350 @@ +require 'thread' +require 'concurrent/constants' +require 'concurrent/utility/engine' + +module Concurrent + # @!visibility private + module Collection + + # @!visibility private + MapImplementation = case + when Concurrent.on_jruby? + require 'concurrent/utility/native_extension_loader' + # noinspection RubyResolve + JRubyMapBackend + when Concurrent.on_cruby? + require 'concurrent/collection/map/mri_map_backend' + MriMapBackend + when Concurrent.on_truffleruby? + if defined?(::TruffleRuby::ConcurrentMap) + require 'concurrent/collection/map/truffleruby_map_backend' + TruffleRubyMapBackend + else + require 'concurrent/collection/map/synchronized_map_backend' + SynchronizedMapBackend + end + else + warn 'Concurrent::Map: unsupported Ruby engine, using a fully synchronized Concurrent::Map implementation' + require 'concurrent/collection/map/synchronized_map_backend' + SynchronizedMapBackend + end + end + + # `Concurrent::Map` is a hash-like object and should have much better performance + # characteristics, especially under high concurrency, than `Concurrent::Hash`. + # However, `Concurrent::Map `is not strictly semantically equivalent to a ruby `Hash` + # -- for instance, it does not necessarily retain ordering by insertion time as `Hash` + # does. For most uses it should do fine though, and we recommend you consider + # `Concurrent::Map` instead of `Concurrent::Hash` for your concurrency-safe hash needs. + class Map < Collection::MapImplementation + + # @!macro map.atomic_method + # This method is atomic. + + # @!macro map.atomic_method_with_block + # This method is atomic. + # @note Atomic methods taking a block do not allow the `self` instance + # to be used within the block. Doing so will cause a deadlock. + + # @!method []=(key, value) + # Set a value with key + # @param [Object] key + # @param [Object] value + # @return [Object] the new value + + # @!method compute_if_absent(key) + # Compute and store new value for key if the key is absent. + # @param [Object] key + # @yield new value + # @yieldreturn [Object] new value + # @return [Object] new value or current value + # @!macro map.atomic_method_with_block + + # @!method compute_if_present(key) + # Compute and store new value for key if the key is present. + # @param [Object] key + # @yield new value + # @yieldparam old_value [Object] + # @yieldreturn [Object, nil] new value, when nil the key is removed + # @return [Object, nil] new value or nil + # @!macro map.atomic_method_with_block + + # @!method compute(key) + # Compute and store new value for key. + # @param [Object] key + # @yield compute new value from old one + # @yieldparam old_value [Object, nil] old_value, or nil when key is absent + # @yieldreturn [Object, nil] new value, when nil the key is removed + # @return [Object, nil] new value or nil + # @!macro map.atomic_method_with_block + + # @!method merge_pair(key, value) + # If the key is absent, the value is stored, otherwise new value is + # computed with a block. + # @param [Object] key + # @param [Object] value + # @yield compute new value from old one + # @yieldparam old_value [Object] old value + # @yieldreturn [Object, nil] new value, when nil the key is removed + # @return [Object, nil] new value or nil + # @!macro map.atomic_method_with_block + + # @!method replace_pair(key, old_value, new_value) + # Replaces old_value with new_value if key exists and current value + # matches old_value + # @param [Object] key + # @param [Object] old_value + # @param [Object] new_value + # @return [true, false] true if replaced + # @!macro map.atomic_method + + # @!method replace_if_exists(key, new_value) + # Replaces current value with new_value if key exists + # @param [Object] key + # @param [Object] new_value + # @return [Object, nil] old value or nil + # @!macro map.atomic_method + + # @!method get_and_set(key, value) + # Get the current value under key and set new value. + # @param [Object] key + # @param [Object] value + # @return [Object, nil] old value or nil when the key was absent + # @!macro map.atomic_method + + # @!method delete(key) + # Delete key and its value. + # @param [Object] key + # @return [Object, nil] old value or nil when the key was absent + # @!macro map.atomic_method + + # @!method delete_pair(key, value) + # Delete pair and its value if current value equals the provided value. + # @param [Object] key + # @param [Object] value + # @return [true, false] true if deleted + # @!macro map.atomic_method + + # NonConcurrentMapBackend handles default_proc natively + unless defined?(Collection::NonConcurrentMapBackend) and self < Collection::NonConcurrentMapBackend + + # @param [Hash, nil] options options to set the :initial_capacity or :load_factor. Ignored on some Rubies. + # @param [Proc] default_proc Optional block to compute the default value if the key is not set, like `Hash#default_proc` + def initialize(options = nil, &default_proc) + if options.kind_of?(::Hash) + validate_options_hash!(options) + else + options = nil + end + + super(options) + @default_proc = default_proc + end + + # Get a value with key + # @param [Object] key + # @return [Object] the value + def [](key) + if value = super # non-falsy value is an existing mapping, return it right away + value + # re-check is done with get_or_default(key, NULL) instead of a simple !key?(key) in order to avoid a race condition, whereby by the time the current thread gets to the key?(key) call + # a key => value mapping might have already been created by a different thread (key?(key) would then return true, this elsif branch wouldn't be taken and an incorrect +nil+ value + # would be returned) + # note: nil == value check is not technically necessary + elsif @default_proc && nil == value && NULL == (value = get_or_default(key, NULL)) + @default_proc.call(self, key) + else + value + end + end + end + + alias_method :get, :[] + alias_method :put, :[]= + + # Get a value with key, or default_value when key is absent, + # or fail when no default_value is given. + # @param [Object] key + # @param [Object] default_value + # @yield default value for a key + # @yieldparam key [Object] + # @yieldreturn [Object] default value + # @return [Object] the value or default value + # @raise [KeyError] when key is missing and no default_value is provided + # @!macro map_method_not_atomic + # @note The "fetch-then-act" methods of `Map` are not atomic. `Map` is intended + # to be use as a concurrency primitive with strong happens-before + # guarantees. It is not intended to be used as a high-level abstraction + # supporting complex operations. All read and write operations are + # thread safe, but no guarantees are made regarding race conditions + # between the fetch operation and yielding to the block. Additionally, + # this method does not support recursion. This is due to internal + # constraints that are very unlikely to change in the near future. + def fetch(key, default_value = NULL) + if NULL != (value = get_or_default(key, NULL)) + value + elsif block_given? + yield key + elsif NULL != default_value + default_value + else + raise_fetch_no_key + end + end + + # Fetch value with key, or store default value when key is absent, + # or fail when no default_value is given. This is a two step operation, + # therefore not atomic. The store can overwrite other concurrently + # stored value. + # @param [Object] key + # @param [Object] default_value + # @yield default value for a key + # @yieldparam key [Object] + # @yieldreturn [Object] default value + # @return [Object] the value or default value + def fetch_or_store(key, default_value = NULL) + fetch(key) do + put(key, block_given? ? yield(key) : (NULL == default_value ? raise_fetch_no_key : default_value)) + end + end + + # Insert value into map with key if key is absent in one atomic step. + # @param [Object] key + # @param [Object] value + # @return [Object, nil] the previous value when key was present or nil when there was no key + def put_if_absent(key, value) + computed = false + result = compute_if_absent(key) do + computed = true + value + end + computed ? nil : result + end unless method_defined?(:put_if_absent) + + # Is the value stored in the map. Iterates over all values. + # @param [Object] value + # @return [true, false] + def value?(value) + each_value do |v| + return true if value.equal?(v) + end + false + end + + # All keys + # @return [::Array] keys + def keys + arr = [] + each_pair { |k, v| arr << k } + arr + end unless method_defined?(:keys) + + # All values + # @return [::Array] values + def values + arr = [] + each_pair { |k, v| arr << v } + arr + end unless method_defined?(:values) + + # Iterates over each key. + # @yield for each key in the map + # @yieldparam key [Object] + # @return [self] + # @!macro map.atomic_method_with_block + def each_key + each_pair { |k, v| yield k } + end unless method_defined?(:each_key) + + # Iterates over each value. + # @yield for each value in the map + # @yieldparam value [Object] + # @return [self] + # @!macro map.atomic_method_with_block + def each_value + each_pair { |k, v| yield v } + end unless method_defined?(:each_value) + + # Iterates over each key value pair. + # @yield for each key value pair in the map + # @yieldparam key [Object] + # @yieldparam value [Object] + # @return [self] + # @!macro map.atomic_method_with_block + def each_pair + return enum_for :each_pair unless block_given? + super + end + + alias_method :each, :each_pair unless method_defined?(:each) + + # Find key of a value. + # @param [Object] value + # @return [Object, nil] key or nil when not found + def key(value) + each_pair { |k, v| return k if v == value } + nil + end unless method_defined?(:key) + + # Is map empty? + # @return [true, false] + def empty? + each_pair { |k, v| return false } + true + end unless method_defined?(:empty?) + + # The size of map. + # @return [Integer] size + def size + count = 0 + each_pair { |k, v| count += 1 } + count + end unless method_defined?(:size) + + # @!visibility private + def marshal_dump + raise TypeError, "can't dump hash with default proc" if @default_proc + h = {} + each_pair { |k, v| h[k] = v } + h + end + + # @!visibility private + def marshal_load(hash) + initialize + populate_from(hash) + end + + undef :freeze + + # @!visibility private + def inspect + format '%s entries=%d default_proc=%s>', to_s[0..-2], size.to_s, @default_proc.inspect + end + + private + + def raise_fetch_no_key + raise KeyError, 'key not found' + end + + def initialize_copy(other) + super + populate_from(other) + end + + def populate_from(hash) + hash.each_pair { |k, v| self[k] = v } + self + end + + def validate_options_hash!(options) + if (initial_capacity = options[:initial_capacity]) && (!initial_capacity.kind_of?(Integer) || initial_capacity < 0) + raise ArgumentError, ":initial_capacity must be a positive Integer" + end + if (load_factor = options[:load_factor]) && (!load_factor.kind_of?(Numeric) || load_factor <= 0 || load_factor > 1) + raise ArgumentError, ":load_factor must be a number between 0 and 1" + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/maybe.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/maybe.rb new file mode 100644 index 00000000..317c82b8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/maybe.rb @@ -0,0 +1,229 @@ +require 'concurrent/synchronization/object' + +module Concurrent + + # A `Maybe` encapsulates an optional value. A `Maybe` either contains a value + # of (represented as `Just`), or it is empty (represented as `Nothing`). Using + # `Maybe` is a good way to deal with errors or exceptional cases without + # resorting to drastic measures such as exceptions. + # + # `Maybe` is a replacement for the use of `nil` with better type checking. + # + # For compatibility with {Concurrent::Concern::Obligation} the predicate and + # accessor methods are aliased as `fulfilled?`, `rejected?`, `value`, and + # `reason`. + # + # ## Motivation + # + # A common pattern in languages with pattern matching, such as Erlang and + # Haskell, is to return *either* a value *or* an error from a function + # Consider this Erlang code: + # + # ```erlang + # case file:consult("data.dat") of + # {ok, Terms} -> do_something_useful(Terms); + # {error, Reason} -> lager:error(Reason) + # end. + # ``` + # + # In this example the standard library function `file:consult` returns a + # [tuple](http://erlang.org/doc/reference_manual/data_types.html#id69044) + # with two elements: an [atom](http://erlang.org/doc/reference_manual/data_types.html#id64134) + # (similar to a ruby symbol) and a variable containing ancillary data. On + # success it returns the atom `ok` and the data from the file. On failure it + # returns `error` and a string with an explanation of the problem. With this + # pattern there is no ambiguity regarding success or failure. If the file is + # empty the return value cannot be misinterpreted as an error. And when an + # error occurs the return value provides useful information. + # + # In Ruby we tend to return `nil` when an error occurs or else we raise an + # exception. Both of these idioms are problematic. Returning `nil` is + # ambiguous because `nil` may also be a valid value. It also lacks + # information pertaining to the nature of the error. Raising an exception + # is both expensive and usurps the normal flow of control. All of these + # problems can be solved with the use of a `Maybe`. + # + # A `Maybe` is unambiguous with regard to whether or not it contains a value. + # When `Just` it contains a value, when `Nothing` it does not. When `Just` + # the value it contains may be `nil`, which is perfectly valid. When + # `Nothing` the reason for the lack of a value is contained as well. The + # previous Erlang example can be duplicated in Ruby in a principled way by + # having functions return `Maybe` objects: + # + # ```ruby + # result = MyFileUtils.consult("data.dat") # returns a Maybe + # if result.just? + # do_something_useful(result.value) # or result.just + # else + # logger.error(result.reason) # or result.nothing + # end + # ``` + # + # @example Returning a Maybe from a Function + # module MyFileUtils + # def self.consult(path) + # file = File.open(path, 'r') + # Concurrent::Maybe.just(file.read) + # rescue => ex + # return Concurrent::Maybe.nothing(ex) + # ensure + # file.close if file + # end + # end + # + # maybe = MyFileUtils.consult('bogus.file') + # maybe.just? #=> false + # maybe.nothing? #=> true + # maybe.reason #=> # + # + # maybe = MyFileUtils.consult('README.md') + # maybe.just? #=> true + # maybe.nothing? #=> false + # maybe.value #=> "# Concurrent Ruby\n[![Gem Version..." + # + # @example Using Maybe with a Block + # result = Concurrent::Maybe.from do + # Client.find(10) # Client is an ActiveRecord model + # end + # + # # -- if the record was found + # result.just? #=> true + # result.value #=> # + # + # # -- if the record was not found + # result.just? #=> false + # result.reason #=> ActiveRecord::RecordNotFound + # + # @example Using Maybe with the Null Object Pattern + # # In a Rails controller... + # result = ClientService.new(10).find # returns a Maybe + # render json: result.or(NullClient.new) + # + # @see https://hackage.haskell.org/package/base-4.2.0.1/docs/Data-Maybe.html Haskell Data.Maybe + # @see https://github.com/purescript/purescript-maybe/blob/master/docs/Data.Maybe.md PureScript Data.Maybe + class Maybe < Synchronization::Object + include Comparable + safe_initialization! + + # Indicates that the given attribute has not been set. + # When `Just` the {#nothing} getter will return `NONE`. + # When `Nothing` the {#just} getter will return `NONE`. + NONE = ::Object.new.freeze + + # The value of a `Maybe` when `Just`. Will be `NONE` when `Nothing`. + attr_reader :just + + # The reason for the `Maybe` when `Nothing`. Will be `NONE` when `Just`. + attr_reader :nothing + + private_class_method :new + + # Create a new `Maybe` using the given block. + # + # Runs the given block passing all function arguments to the block as block + # arguments. If the block runs to completion without raising an exception + # a new `Just` is created with the value set to the return value of the + # block. If the block raises an exception a new `Nothing` is created with + # the reason being set to the raised exception. + # + # @param [Array] args Zero or more arguments to pass to the block. + # @yield The block from which to create a new `Maybe`. + # @yieldparam [Array] args Zero or more block arguments passed as + # arguments to the function. + # + # @return [Maybe] The newly created object. + # + # @raise [ArgumentError] when no block given. + def self.from(*args) + raise ArgumentError.new('no block given') unless block_given? + begin + value = yield(*args) + return new(value, NONE) + rescue => ex + return new(NONE, ex) + end + end + + # Create a new `Just` with the given value. + # + # @param [Object] value The value to set for the new `Maybe` object. + # + # @return [Maybe] The newly created object. + def self.just(value) + return new(value, NONE) + end + + # Create a new `Nothing` with the given (optional) reason. + # + # @param [Exception] error The reason to set for the new `Maybe` object. + # When given a string a new `StandardError` will be created with the + # argument as the message. When no argument is given a new + # `StandardError` with an empty message will be created. + # + # @return [Maybe] The newly created object. + def self.nothing(error = '') + if error.is_a?(Exception) + nothing = error + else + nothing = StandardError.new(error.to_s) + end + return new(NONE, nothing) + end + + # Is this `Maybe` a `Just` (successfully fulfilled with a value)? + # + # @return [Boolean] True if `Just` or false if `Nothing`. + def just? + ! nothing? + end + alias :fulfilled? :just? + + # Is this `Maybe` a `nothing` (rejected with an exception upon fulfillment)? + # + # @return [Boolean] True if `Nothing` or false if `Just`. + def nothing? + @nothing != NONE + end + alias :rejected? :nothing? + + alias :value :just + + alias :reason :nothing + + # Comparison operator. + # + # @return [Integer] 0 if self and other are both `Nothing`; + # -1 if self is `Nothing` and other is `Just`; + # 1 if self is `Just` and other is nothing; + # `self.just <=> other.just` if both self and other are `Just`. + def <=>(other) + if nothing? + other.nothing? ? 0 : -1 + else + other.nothing? ? 1 : just <=> other.just + end + end + + # Return either the value of self or the given default value. + # + # @return [Object] The value of self when `Just`; else the given default. + def or(other) + just? ? just : other + end + + private + + # Create a new `Maybe` with the given attributes. + # + # @param [Object] just The value when `Just` else `NONE`. + # @param [Exception, Object] nothing The exception when `Nothing` else `NONE`. + # + # @return [Maybe] The new `Maybe`. + # + # @!visibility private + def initialize(just, nothing) + @just = just + @nothing = nothing + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/mutable_struct.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/mutable_struct.rb new file mode 100644 index 00000000..5d0e9b9a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/mutable_struct.rb @@ -0,0 +1,239 @@ +require 'concurrent/synchronization/abstract_struct' +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # An thread-safe variation of Ruby's standard `Struct`. Values can be set at + # construction or safely changed at any time during the object's lifecycle. + # + # @see http://ruby-doc.org/core/Struct.html Ruby standard library `Struct` + module MutableStruct + include Synchronization::AbstractStruct + + # @!macro struct_new + # + # Factory for creating new struct classes. + # + # ``` + # new([class_name] [, member_name]+>) -> StructClass click to toggle source + # new([class_name] [, member_name]+>) {|StructClass| block } -> StructClass + # new(value, ...) -> obj + # StructClass[value, ...] -> obj + # ``` + # + # The first two forms are used to create a new struct subclass `class_name` + # that can contain a value for each member_name . This subclass can be + # used to create instances of the structure like any other Class . + # + # If the `class_name` is omitted an anonymous struct class will be created. + # Otherwise, the name of this struct will appear as a constant in the struct class, + # so it must be unique for all structs under this base class and must start with a + # capital letter. Assigning a struct class to a constant also gives the class + # the name of the constant. + # + # If a block is given it will be evaluated in the context of `StructClass`, passing + # the created class as a parameter. This is the recommended way to customize a struct. + # Subclassing an anonymous struct creates an extra anonymous class that will never be used. + # + # The last two forms create a new instance of a struct subclass. The number of value + # parameters must be less than or equal to the number of attributes defined for the + # struct. Unset parameters default to nil. Passing more parameters than number of attributes + # will raise an `ArgumentError`. + # + # @see http://ruby-doc.org/core/Struct.html#method-c-new Ruby standard library `Struct#new` + + # @!macro struct_values + # + # Returns the values for this struct as an Array. + # + # @return [Array] the values for this struct + # + def values + synchronize { ns_values } + end + alias_method :to_a, :values + + # @!macro struct_values_at + # + # Returns the struct member values for each selector as an Array. + # + # A selector may be either an Integer offset or a Range of offsets (as in `Array#values_at`). + # + # @param [Fixnum, Range] indexes the index(es) from which to obatin the values (in order) + def values_at(*indexes) + synchronize { ns_values_at(indexes) } + end + + # @!macro struct_inspect + # + # Describe the contents of this struct in a string. + # + # @return [String] the contents of this struct in a string + def inspect + synchronize { ns_inspect } + end + alias_method :to_s, :inspect + + # @!macro struct_merge + # + # Returns a new struct containing the contents of `other` and the contents + # of `self`. If no block is specified, the value for entries with duplicate + # keys will be that of `other`. Otherwise the value for each duplicate key + # is determined by calling the block with the key, its value in `self` and + # its value in `other`. + # + # @param [Hash] other the hash from which to set the new values + # @yield an options block for resolving duplicate keys + # @yieldparam [String, Symbol] member the name of the member which is duplicated + # @yieldparam [Object] selfvalue the value of the member in `self` + # @yieldparam [Object] othervalue the value of the member in `other` + # + # @return [Synchronization::AbstractStruct] a new struct with the new values + # + # @raise [ArgumentError] of given a member that is not defined in the struct + def merge(other, &block) + synchronize { ns_merge(other, &block) } + end + + # @!macro struct_to_h + # + # Returns a hash containing the names and values for the struct’s members. + # + # @return [Hash] the names and values for the struct’s members + def to_h + synchronize { ns_to_h } + end + + # @!macro struct_get + # + # Attribute Reference + # + # @param [Symbol, String, Integer] member the string or symbol name of the member + # for which to obtain the value or the member's index + # + # @return [Object] the value of the given struct member or the member at the given index. + # + # @raise [NameError] if the member does not exist + # @raise [IndexError] if the index is out of range. + def [](member) + synchronize { ns_get(member) } + end + + # @!macro struct_equality + # + # Equality + # + # @return [Boolean] true if other has the same struct subclass and has + # equal member values (according to `Object#==`) + def ==(other) + synchronize { ns_equality(other) } + end + + # @!macro struct_each + # + # Yields the value of each struct member in order. If no block is given + # an enumerator is returned. + # + # @yield the operation to be performed on each struct member + # @yieldparam [Object] value each struct value (in order) + def each(&block) + return enum_for(:each) unless block_given? + synchronize { ns_each(&block) } + end + + # @!macro struct_each_pair + # + # Yields the name and value of each struct member in order. If no block is + # given an enumerator is returned. + # + # @yield the operation to be performed on each struct member/value pair + # @yieldparam [Object] member each struct member (in order) + # @yieldparam [Object] value each struct value (in order) + def each_pair(&block) + return enum_for(:each_pair) unless block_given? + synchronize { ns_each_pair(&block) } + end + + # @!macro struct_select + # + # Yields each member value from the struct to the block and returns an Array + # containing the member values from the struct for which the given block + # returns a true value (equivalent to `Enumerable#select`). + # + # @yield the operation to be performed on each struct member + # @yieldparam [Object] value each struct value (in order) + # + # @return [Array] an array containing each value for which the block returns true + def select(&block) + return enum_for(:select) unless block_given? + synchronize { ns_select(&block) } + end + + # @!macro struct_set + # + # Attribute Assignment + # + # Sets the value of the given struct member or the member at the given index. + # + # @param [Symbol, String, Integer] member the string or symbol name of the member + # for which to obtain the value or the member's index + # + # @return [Object] the value of the given struct member or the member at the given index. + # + # @raise [NameError] if the name does not exist + # @raise [IndexError] if the index is out of range. + def []=(member, value) + if member.is_a? Integer + length = synchronize { @values.length } + if member >= length + raise IndexError.new("offset #{member} too large for struct(size:#{length})") + end + synchronize { @values[member] = value } + else + send("#{member}=", value) + end + rescue NoMethodError + raise NameError.new("no member '#{member}' in struct") + end + + private + + # @!visibility private + def initialize_copy(original) + synchronize do + super(original) + ns_initialize_copy + end + end + + # @!macro struct_new + def self.new(*args, &block) + clazz_name = nil + if args.length == 0 + raise ArgumentError.new('wrong number of arguments (0 for 1+)') + elsif args.length > 0 && args.first.is_a?(String) + clazz_name = args.shift + end + FACTORY.define_struct(clazz_name, args, &block) + end + + FACTORY = Class.new(Synchronization::LockableObject) do + def define_struct(name, members, &block) + synchronize do + clazz = Synchronization::AbstractStruct.define_struct_class(MutableStruct, Synchronization::LockableObject, name, members, &block) + members.each_with_index do |member, index| + clazz.send :remove_method, member + clazz.send(:define_method, member) do + synchronize { @values[index] } + end + clazz.send(:define_method, "#{member}=") do |value| + synchronize { @values[index] = value } + end + end + clazz + end + end + end.new + private_constant :FACTORY + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/mvar.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/mvar.rb new file mode 100644 index 00000000..dfc41950 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/mvar.rb @@ -0,0 +1,242 @@ +require 'concurrent/concern/dereferenceable' +require 'concurrent/synchronization/object' + +module Concurrent + + # An `MVar` is a synchronized single element container. They are empty or + # contain one item. Taking a value from an empty `MVar` blocks, as does + # putting a value into a full one. You can either think of them as blocking + # queue of length one, or a special kind of mutable variable. + # + # On top of the fundamental `#put` and `#take` operations, we also provide a + # `#mutate` that is atomic with respect to operations on the same instance. + # These operations all support timeouts. + # + # We also support non-blocking operations `#try_put!` and `#try_take!`, a + # `#set!` that ignores existing values, a `#value` that returns the value + # without removing it or returns `MVar::EMPTY`, and a `#modify!` that yields + # `MVar::EMPTY` if the `MVar` is empty and can be used to set `MVar::EMPTY`. + # You shouldn't use these operations in the first instance. + # + # `MVar` is a [Dereferenceable](Dereferenceable). + # + # `MVar` is related to M-structures in Id, `MVar` in Haskell and `SyncVar` in Scala. + # + # Note that unlike the original Haskell paper, our `#take` is blocking. This is how + # Haskell and Scala do it today. + # + # @!macro copy_options + # + # ## See Also + # + # 1. P. Barth, R. Nikhil, and Arvind. [M-Structures: Extending a parallel, non- strict, functional language with state](http://dl.acm.org/citation.cfm?id=652538). In Proceedings of the 5th + # ACM Conference on Functional Programming Languages and Computer Architecture (FPCA), 1991. + # + # 2. S. Peyton Jones, A. Gordon, and S. Finne. [Concurrent Haskell](http://dl.acm.org/citation.cfm?id=237794). + # In Proceedings of the 23rd Symposium on Principles of Programming Languages + # (PoPL), 1996. + class MVar < Synchronization::Object + include Concern::Dereferenceable + safe_initialization! + + # Unique value that represents that an `MVar` was empty + EMPTY = ::Object.new + + # Unique value that represents that an `MVar` timed out before it was able + # to produce a value. + TIMEOUT = ::Object.new + + # Create a new `MVar`, either empty or with an initial value. + # + # @param [Hash] opts the options controlling how the future will be processed + # + # @!macro deref_options + def initialize(value = EMPTY, opts = {}) + @value = value + @mutex = Mutex.new + @empty_condition = ConditionVariable.new + @full_condition = ConditionVariable.new + set_deref_options(opts) + end + + # Remove the value from an `MVar`, leaving it empty, and blocking if there + # isn't a value. A timeout can be set to limit the time spent blocked, in + # which case it returns `TIMEOUT` if the time is exceeded. + # @return [Object] the value that was taken, or `TIMEOUT` + def take(timeout = nil) + @mutex.synchronize do + wait_for_full(timeout) + + # If we timed out we'll still be empty + if unlocked_full? + value = @value + @value = EMPTY + @empty_condition.signal + apply_deref_options(value) + else + TIMEOUT + end + end + end + + # acquires lock on the from an `MVAR`, yields the value to provided block, + # and release lock. A timeout can be set to limit the time spent blocked, + # in which case it returns `TIMEOUT` if the time is exceeded. + # @return [Object] the value returned by the block, or `TIMEOUT` + def borrow(timeout = nil) + @mutex.synchronize do + wait_for_full(timeout) + + # if we timeoud out we'll still be empty + if unlocked_full? + yield @value + else + TIMEOUT + end + end + end + + # Put a value into an `MVar`, blocking if there is already a value until + # it is empty. A timeout can be set to limit the time spent blocked, in + # which case it returns `TIMEOUT` if the time is exceeded. + # @return [Object] the value that was put, or `TIMEOUT` + def put(value, timeout = nil) + @mutex.synchronize do + wait_for_empty(timeout) + + # If we timed out we won't be empty + if unlocked_empty? + @value = value + @full_condition.signal + apply_deref_options(value) + else + TIMEOUT + end + end + end + + # Atomically `take`, yield the value to a block for transformation, and then + # `put` the transformed value. Returns the transformed value. A timeout can + # be set to limit the time spent blocked, in which case it returns `TIMEOUT` + # if the time is exceeded. + # @return [Object] the transformed value, or `TIMEOUT` + def modify(timeout = nil) + raise ArgumentError.new('no block given') unless block_given? + + @mutex.synchronize do + wait_for_full(timeout) + + # If we timed out we'll still be empty + if unlocked_full? + value = @value + @value = yield value + @full_condition.signal + apply_deref_options(value) + else + TIMEOUT + end + end + end + + # Non-blocking version of `take`, that returns `EMPTY` instead of blocking. + def try_take! + @mutex.synchronize do + if unlocked_full? + value = @value + @value = EMPTY + @empty_condition.signal + apply_deref_options(value) + else + EMPTY + end + end + end + + # Non-blocking version of `put`, that returns whether or not it was successful. + def try_put!(value) + @mutex.synchronize do + if unlocked_empty? + @value = value + @full_condition.signal + true + else + false + end + end + end + + # Non-blocking version of `put` that will overwrite an existing value. + def set!(value) + @mutex.synchronize do + old_value = @value + @value = value + @full_condition.signal + apply_deref_options(old_value) + end + end + + # Non-blocking version of `modify` that will yield with `EMPTY` if there is no value yet. + def modify! + raise ArgumentError.new('no block given') unless block_given? + + @mutex.synchronize do + value = @value + @value = yield value + if unlocked_empty? + @empty_condition.signal + else + @full_condition.signal + end + apply_deref_options(value) + end + end + + # Returns if the `MVar` is currently empty. + def empty? + @mutex.synchronize { @value == EMPTY } + end + + # Returns if the `MVar` currently contains a value. + def full? + !empty? + end + + protected + + def synchronize(&block) + @mutex.synchronize(&block) + end + + private + + def unlocked_empty? + @value == EMPTY + end + + def unlocked_full? + ! unlocked_empty? + end + + def wait_for_full(timeout) + wait_while(@full_condition, timeout) { unlocked_empty? } + end + + def wait_for_empty(timeout) + wait_while(@empty_condition, timeout) { unlocked_full? } + end + + def wait_while(condition, timeout) + if timeout.nil? + while yield + condition.wait(@mutex) + end + else + stop = Concurrent.monotonic_time + timeout + while yield && timeout > 0.0 + condition.wait(@mutex, timeout) + timeout = stop - Concurrent.monotonic_time + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/options.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/options.rb new file mode 100644 index 00000000..bdd22a9d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/options.rb @@ -0,0 +1,42 @@ +require 'concurrent/configuration' + +module Concurrent + + # @!visibility private + module Options + + # Get the requested `Executor` based on the values set in the options hash. + # + # @param [Hash] opts the options defining the requested executor + # @option opts [Executor] :executor when set use the given `Executor` instance. + # Three special values are also supported: `:fast` returns the global fast executor, + # `:io` returns the global io executor, and `:immediate` returns a new + # `ImmediateExecutor` object. + # + # @return [Executor, nil] the requested thread pool, or nil when no option specified + # + # @!visibility private + def self.executor_from_options(opts = {}) # :nodoc: + if identifier = opts.fetch(:executor, nil) + executor(identifier) + else + nil + end + end + + def self.executor(executor_identifier) + case executor_identifier + when :fast + Concurrent.global_fast_executor + when :io + Concurrent.global_io_executor + when :immediate + Concurrent.global_immediate_executor + when Concurrent::ExecutorService + executor_identifier + else + raise ArgumentError, "executor not recognized by '#{executor_identifier}'" + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/promise.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/promise.rb new file mode 100644 index 00000000..c717f9b0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/promise.rb @@ -0,0 +1,580 @@ +require 'thread' +require 'concurrent/constants' +require 'concurrent/errors' +require 'concurrent/ivar' +require 'concurrent/executor/safe_task_executor' + +require 'concurrent/options' + +module Concurrent + + PromiseExecutionError = Class.new(StandardError) + + # Promises are inspired by the JavaScript [Promises/A](http://wiki.commonjs.org/wiki/Promises/A) + # and [Promises/A+](http://promises-aplus.github.io/promises-spec/) specifications. + # + # > A promise represents the eventual value returned from the single + # > completion of an operation. + # + # Promises are similar to futures and share many of the same behaviours. + # Promises are far more robust, however. Promises can be chained in a tree + # structure where each promise may have zero or more children. Promises are + # chained using the `then` method. The result of a call to `then` is always + # another promise. Promises are resolved asynchronously (with respect to the + # main thread) but in a strict order: parents are guaranteed to be resolved + # before their children, children before their younger siblings. The `then` + # method takes two parameters: an optional block to be executed upon parent + # resolution and an optional callable to be executed upon parent failure. The + # result of each promise is passed to each of its children upon resolution. + # When a promise is rejected all its children will be summarily rejected and + # will receive the reason. + # + # Promises have several possible states: *:unscheduled*, *:pending*, + # *:processing*, *:rejected*, or *:fulfilled*. These are also aggregated as + # `#incomplete?` and `#complete?`. When a Promise is created it is set to + # *:unscheduled*. Once the `#execute` method is called the state becomes + # *:pending*. Once a job is pulled from the thread pool's queue and is given + # to a thread for processing (often immediately upon `#post`) the state + # becomes *:processing*. The future will remain in this state until processing + # is complete. A future that is in the *:unscheduled*, *:pending*, or + # *:processing* is considered `#incomplete?`. A `#complete?` Promise is either + # *:rejected*, indicating that an exception was thrown during processing, or + # *:fulfilled*, indicating success. If a Promise is *:fulfilled* its `#value` + # will be updated to reflect the result of the operation. If *:rejected* the + # `reason` will be updated with a reference to the thrown exception. The + # predicate methods `#unscheduled?`, `#pending?`, `#rejected?`, and + # `#fulfilled?` can be called at any time to obtain the state of the Promise, + # as can the `#state` method, which returns a symbol. + # + # Retrieving the value of a promise is done through the `value` (alias: + # `deref`) method. Obtaining the value of a promise is a potentially blocking + # operation. When a promise is *rejected* a call to `value` will return `nil` + # immediately. When a promise is *fulfilled* a call to `value` will + # immediately return the current value. When a promise is *pending* a call to + # `value` will block until the promise is either *rejected* or *fulfilled*. A + # *timeout* value can be passed to `value` to limit how long the call will + # block. If `nil` the call will block indefinitely. If `0` the call will not + # block. Any other integer or float value will indicate the maximum number of + # seconds to block. + # + # Promises run on the global thread pool. + # + # @!macro copy_options + # + # ### Examples + # + # Start by requiring promises + # + # ```ruby + # require 'concurrent/promise' + # ``` + # + # Then create one + # + # ```ruby + # p = Concurrent::Promise.execute do + # # do something + # 42 + # end + # ``` + # + # Promises can be chained using the `then` method. The `then` method accepts a + # block and an executor, to be executed on fulfillment, and a callable argument to be executed + # on rejection. The result of the each promise is passed as the block argument + # to chained promises. + # + # ```ruby + # p = Concurrent::Promise.new{10}.then{|x| x * 2}.then{|result| result - 10 }.execute + # ``` + # + # And so on, and so on, and so on... + # + # ```ruby + # p = Concurrent::Promise.fulfill(20). + # then{|result| result - 10 }. + # then{|result| result * 3 }. + # then(executor: different_executor){|result| result % 5 }.execute + # ``` + # + # The initial state of a newly created Promise depends on the state of its parent: + # - if parent is *unscheduled* the child will be *unscheduled* + # - if parent is *pending* the child will be *pending* + # - if parent is *fulfilled* the child will be *pending* + # - if parent is *rejected* the child will be *pending* (but will ultimately be *rejected*) + # + # Promises are executed asynchronously from the main thread. By the time a + # child Promise finishes initialization it may be in a different state than its + # parent (by the time a child is created its parent may have completed + # execution and changed state). Despite being asynchronous, however, the order + # of execution of Promise objects in a chain (or tree) is strictly defined. + # + # There are multiple ways to create and execute a new `Promise`. Both ways + # provide identical behavior: + # + # ```ruby + # # create, operate, then execute + # p1 = Concurrent::Promise.new{ "Hello World!" } + # p1.state #=> :unscheduled + # p1.execute + # + # # create and immediately execute + # p2 = Concurrent::Promise.new{ "Hello World!" }.execute + # + # # execute during creation + # p3 = Concurrent::Promise.execute{ "Hello World!" } + # ``` + # + # Once the `execute` method is called a `Promise` becomes `pending`: + # + # ```ruby + # p = Concurrent::Promise.execute{ "Hello, world!" } + # p.state #=> :pending + # p.pending? #=> true + # ``` + # + # Wait a little bit, and the promise will resolve and provide a value: + # + # ```ruby + # p = Concurrent::Promise.execute{ "Hello, world!" } + # sleep(0.1) + # + # p.state #=> :fulfilled + # p.fulfilled? #=> true + # p.value #=> "Hello, world!" + # ``` + # + # If an exception occurs, the promise will be rejected and will provide + # a reason for the rejection: + # + # ```ruby + # p = Concurrent::Promise.execute{ raise StandardError.new("Here comes the Boom!") } + # sleep(0.1) + # + # p.state #=> :rejected + # p.rejected? #=> true + # p.reason #=> "#" + # ``` + # + # #### Rejection + # + # When a promise is rejected all its children will be rejected and will + # receive the rejection `reason` as the rejection callable parameter: + # + # ```ruby + # p = Concurrent::Promise.execute { Thread.pass; raise StandardError } + # + # c1 = p.then(-> reason { 42 }) + # c2 = p.then(-> reason { raise 'Boom!' }) + # + # c1.wait.state #=> :fulfilled + # c1.value #=> 45 + # c2.wait.state #=> :rejected + # c2.reason #=> # + # ``` + # + # Once a promise is rejected it will continue to accept children that will + # receive immediately rejection (they will be executed asynchronously). + # + # #### Aliases + # + # The `then` method is the most generic alias: it accepts a block to be + # executed upon parent fulfillment and a callable to be executed upon parent + # rejection. At least one of them should be passed. The default block is `{ + # |result| result }` that fulfills the child with the parent value. The + # default callable is `{ |reason| raise reason }` that rejects the child with + # the parent reason. + # + # - `on_success { |result| ... }` is the same as `then {|result| ... }` + # - `rescue { |reason| ... }` is the same as `then(Proc.new { |reason| ... } )` + # - `rescue` is aliased by `catch` and `on_error` + class Promise < IVar + + # Initialize a new Promise with the provided options. + # + # @!macro executor_and_deref_options + # + # @!macro promise_init_options + # + # @option opts [Promise] :parent the parent `Promise` when building a chain/tree + # @option opts [Proc] :on_fulfill fulfillment handler + # @option opts [Proc] :on_reject rejection handler + # @option opts [object, Array] :args zero or more arguments to be passed + # the task block on execution + # + # @yield The block operation to be performed asynchronously. + # + # @raise [ArgumentError] if no block is given + # + # @see http://wiki.commonjs.org/wiki/Promises/A + # @see http://promises-aplus.github.io/promises-spec/ + def initialize(opts = {}, &block) + opts.delete_if { |k, v| v.nil? } + super(NULL, opts.merge(__promise_body_from_block__: block), &nil) + end + + # Create a new `Promise` and fulfill it immediately. + # + # @!macro executor_and_deref_options + # + # @!macro promise_init_options + # + # @raise [ArgumentError] if no block is given + # + # @return [Promise] the newly created `Promise` + def self.fulfill(value, opts = {}) + Promise.new(opts).tap { |p| p.send(:synchronized_set_state!, true, value, nil) } + end + + # Create a new `Promise` and reject it immediately. + # + # @!macro executor_and_deref_options + # + # @!macro promise_init_options + # + # @raise [ArgumentError] if no block is given + # + # @return [Promise] the newly created `Promise` + def self.reject(reason, opts = {}) + Promise.new(opts).tap { |p| p.send(:synchronized_set_state!, false, nil, reason) } + end + + # Execute an `:unscheduled` `Promise`. Immediately sets the state to `:pending` and + # passes the block to a new thread/thread pool for eventual execution. + # Does nothing if the `Promise` is in any state other than `:unscheduled`. + # + # @return [Promise] a reference to `self` + def execute + if root? + if compare_and_set_state(:pending, :unscheduled) + set_pending + realize(@promise_body) + end + else + compare_and_set_state(:pending, :unscheduled) + @parent.execute + end + self + end + + # @!macro ivar_set_method + # + # @raise [Concurrent::PromiseExecutionError] if not the root promise + def set(value = NULL, &block) + raise PromiseExecutionError.new('supported only on root promise') unless root? + check_for_block_or_value!(block_given?, value) + synchronize do + if @state != :unscheduled + raise MultipleAssignmentError + else + @promise_body = block || Proc.new { |result| value } + end + end + execute + end + + # @!macro ivar_fail_method + # + # @raise [Concurrent::PromiseExecutionError] if not the root promise + def fail(reason = StandardError.new) + set { raise reason } + end + + # Create a new `Promise` object with the given block, execute it, and return the + # `:pending` object. + # + # @!macro executor_and_deref_options + # + # @!macro promise_init_options + # + # @return [Promise] the newly created `Promise` in the `:pending` state + # + # @raise [ArgumentError] if no block is given + # + # @example + # promise = Concurrent::Promise.execute{ sleep(1); 42 } + # promise.state #=> :pending + def self.execute(opts = {}, &block) + new(opts, &block).execute + end + + # Chain a new promise off the current promise. + # + # @return [Promise] the new promise + # @yield The block operation to be performed asynchronously. + # @overload then(rescuer, executor, &block) + # @param [Proc] rescuer An optional rescue block to be executed if the + # promise is rejected. + # @param [ThreadPool] executor An optional thread pool executor to be used + # in the new Promise + # @overload then(rescuer, executor: executor, &block) + # @param [Proc] rescuer An optional rescue block to be executed if the + # promise is rejected. + # @param [ThreadPool] executor An optional thread pool executor to be used + # in the new Promise + def then(*args, &block) + if args.last.is_a?(::Hash) + executor = args.pop[:executor] + rescuer = args.first + else + rescuer, executor = args + end + + executor ||= @executor + + raise ArgumentError.new('rescuers and block are both missing') if rescuer.nil? && !block_given? + block = Proc.new { |result| result } unless block_given? + child = Promise.new( + parent: self, + executor: executor, + on_fulfill: block, + on_reject: rescuer + ) + + synchronize do + child.state = :pending if @state == :pending + child.on_fulfill(apply_deref_options(@value)) if @state == :fulfilled + child.on_reject(@reason) if @state == :rejected + @children << child + end + + child + end + + # Chain onto this promise an action to be undertaken on success + # (fulfillment). + # + # @yield The block to execute + # + # @return [Promise] self + def on_success(&block) + raise ArgumentError.new('no block given') unless block_given? + self.then(&block) + end + + # Chain onto this promise an action to be undertaken on failure + # (rejection). + # + # @yield The block to execute + # + # @return [Promise] self + def rescue(&block) + self.then(block) + end + + alias_method :catch, :rescue + alias_method :on_error, :rescue + + # Yield the successful result to the block that returns a promise. If that + # promise is also successful the result is the result of the yielded promise. + # If either part fails the whole also fails. + # + # @example + # Promise.execute { 1 }.flat_map { |v| Promise.execute { v + 2 } }.value! #=> 3 + # + # @return [Promise] + def flat_map(&block) + child = Promise.new( + parent: self, + executor: ImmediateExecutor.new, + ) + + on_error { |e| child.on_reject(e) } + on_success do |result1| + begin + inner = block.call(result1) + inner.execute + inner.on_success { |result2| child.on_fulfill(result2) } + inner.on_error { |e| child.on_reject(e) } + rescue => e + child.on_reject(e) + end + end + + child + end + + # Builds a promise that produces the result of promises in an Array + # and fails if any of them fails. + # + # @overload zip(*promises) + # @param [Array] promises + # + # @overload zip(*promises, opts) + # @param [Array] promises + # @param [Hash] opts the configuration options + # @option opts [Executor] :executor (ImmediateExecutor.new) when set use the given `Executor` instance. + # @option opts [Boolean] :execute (true) execute promise before returning + # + # @return [Promise] + def self.zip(*promises) + opts = promises.last.is_a?(::Hash) ? promises.pop.dup : {} + opts[:executor] ||= ImmediateExecutor.new + zero = if !opts.key?(:execute) || opts.delete(:execute) + fulfill([], opts) + else + Promise.new(opts) { [] } + end + + promises.reduce(zero) do |p1, p2| + p1.flat_map do |results| + p2.then do |next_result| + results << next_result + end + end + end + end + + # Builds a promise that produces the result of self and others in an Array + # and fails if any of them fails. + # + # @overload zip(*promises) + # @param [Array] others + # + # @overload zip(*promises, opts) + # @param [Array] others + # @param [Hash] opts the configuration options + # @option opts [Executor] :executor (ImmediateExecutor.new) when set use the given `Executor` instance. + # @option opts [Boolean] :execute (true) execute promise before returning + # + # @return [Promise] + def zip(*others) + self.class.zip(self, *others) + end + + # Aggregates a collection of promises and executes the `then` condition + # if all aggregated promises succeed. Executes the `rescue` handler with + # a `Concurrent::PromiseExecutionError` if any of the aggregated promises + # fail. Upon execution will execute any of the aggregate promises that + # were not already executed. + # + # @!macro promise_self_aggregate + # + # The returned promise will not yet have been executed. Additional `#then` + # and `#rescue` handlers may still be provided. Once the returned promise + # is execute the aggregate promises will be also be executed (if they have + # not been executed already). The results of the aggregate promises will + # be checked upon completion. The necessary `#then` and `#rescue` blocks + # on the aggregating promise will then be executed as appropriate. If the + # `#rescue` handlers are executed the raises exception will be + # `Concurrent::PromiseExecutionError`. + # + # @param [Array] promises Zero or more promises to aggregate + # @return [Promise] an unscheduled (not executed) promise that aggregates + # the promises given as arguments + def self.all?(*promises) + aggregate(:all?, *promises) + end + + # Aggregates a collection of promises and executes the `then` condition + # if any aggregated promises succeed. Executes the `rescue` handler with + # a `Concurrent::PromiseExecutionError` if any of the aggregated promises + # fail. Upon execution will execute any of the aggregate promises that + # were not already executed. + # + # @!macro promise_self_aggregate + def self.any?(*promises) + aggregate(:any?, *promises) + end + + protected + + def ns_initialize(value, opts) + super + + @executor = Options.executor_from_options(opts) || Concurrent.global_io_executor + @args = get_arguments_from(opts) + + @parent = opts.fetch(:parent) { nil } + @on_fulfill = opts.fetch(:on_fulfill) { Proc.new { |result| result } } + @on_reject = opts.fetch(:on_reject) { Proc.new { |reason| raise reason } } + + @promise_body = opts[:__promise_body_from_block__] || Proc.new { |result| result } + @state = :unscheduled + @children = [] + end + + # Aggregate a collection of zero or more promises under a composite promise, + # execute the aggregated promises and collect them into a standard Ruby array, + # call the given Ruby `Ennnumerable` predicate (such as `any?`, `all?`, `none?`, + # or `one?`) on the collection checking for the success or failure of each, + # then executing the composite's `#then` handlers if the predicate returns + # `true` or executing the composite's `#rescue` handlers if the predicate + # returns false. + # + # @!macro promise_self_aggregate + def self.aggregate(method, *promises) + composite = Promise.new do + completed = promises.collect do |promise| + promise.execute if promise.unscheduled? + promise.wait + promise + end + unless completed.empty? || completed.send(method){|promise| promise.fulfilled? } + raise PromiseExecutionError + end + end + composite + end + + # @!visibility private + def set_pending + synchronize do + @state = :pending + @children.each { |c| c.set_pending } + end + end + + # @!visibility private + def root? # :nodoc: + @parent.nil? + end + + # @!visibility private + def on_fulfill(result) + realize Proc.new { @on_fulfill.call(result) } + nil + end + + # @!visibility private + def on_reject(reason) + realize Proc.new { @on_reject.call(reason) } + nil + end + + # @!visibility private + def notify_child(child) + if_state(:fulfilled) { child.on_fulfill(apply_deref_options(@value)) } + if_state(:rejected) { child.on_reject(@reason) } + end + + # @!visibility private + def complete(success, value, reason) + children_to_notify = synchronize do + set_state!(success, value, reason) + @children.dup + end + + children_to_notify.each { |child| notify_child(child) } + observers.notify_and_delete_observers{ [Time.now, self.value, reason] } + end + + # @!visibility private + def realize(task) + @executor.post do + success, value, reason = SafeTaskExecutor.new(task, rescue_exception: true).execute(*@args) + complete(success, value, reason) + end + end + + # @!visibility private + def set_state!(success, value, reason) + set_state(success, value, reason) + event.set + end + + # @!visibility private + def synchronized_set_state!(success, value, reason) + synchronize { set_state!(success, value, reason) } + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/promises.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/promises.rb new file mode 100644 index 00000000..c5df8fe9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/promises.rb @@ -0,0 +1,2178 @@ +require 'concurrent/synchronization/object' +require 'concurrent/atomic/atomic_boolean' +require 'concurrent/atomic/atomic_fixnum' +require 'concurrent/collection/lock_free_stack' +require 'concurrent/configuration' +require 'concurrent/errors' +require 'concurrent/re_include' +require 'concurrent/utility/monotonic_time' + +module Concurrent + + # {include:file:docs-source/promises-main.md} + module Promises + + # @!macro promises.param.default_executor + # @param [Executor, :io, :fast] default_executor Instance of an executor or a name of the + # global executor. Default executor propagates to chained futures unless overridden with + # executor parameter or changed with {AbstractEventFuture#with_default_executor}. + # + # @!macro promises.param.executor + # @param [Executor, :io, :fast] executor Instance of an executor or a name of the + # global executor. The task is executed on it, default executor remains unchanged. + # + # @!macro promises.param.args + # @param [Object] args arguments which are passed to the task when it's executed. + # (It might be prepended with other arguments, see the @yield section). + # + # @!macro promises.shortcut.on + # Shortcut of {#$0_on} with default `:io` executor supplied. + # @see #$0_on + # + # @!macro promises.shortcut.using + # Shortcut of {#$0_using} with default `:io` executor supplied. + # @see #$0_using + # + # @!macro promise.param.task-future + # @yieldreturn will become result of the returned Future. + # Its returned value becomes {Future#value} fulfilling it, + # raised exception becomes {Future#reason} rejecting it. + # + # @!macro promise.param.callback + # @yieldreturn is forgotten. + + # Container of all {Future}, {Event} factory methods. They are never constructed directly with + # new. + module FactoryMethods + extend ReInclude + extend self + + module Configuration + # @return [Executor, :io, :fast] the executor which is used when none is supplied + # to a factory method. The method can be overridden in the receivers of + # `include FactoryMethod` + def default_executor + :io + end + end + + include Configuration + + # @!macro promises.shortcut.on + # @return [ResolvableEvent] + def resolvable_event + resolvable_event_on default_executor + end + + # Creates a resolvable event, user is responsible for resolving the event once + # by calling {Promises::ResolvableEvent#resolve}. + # + # @!macro promises.param.default_executor + # @return [ResolvableEvent] + def resolvable_event_on(default_executor = self.default_executor) + ResolvableEventPromise.new(default_executor).future + end + + # @!macro promises.shortcut.on + # @return [ResolvableFuture] + def resolvable_future + resolvable_future_on default_executor + end + + # Creates resolvable future, user is responsible for resolving the future once by + # {Promises::ResolvableFuture#resolve}, {Promises::ResolvableFuture#fulfill}, + # or {Promises::ResolvableFuture#reject} + # + # @!macro promises.param.default_executor + # @return [ResolvableFuture] + def resolvable_future_on(default_executor = self.default_executor) + ResolvableFuturePromise.new(default_executor).future + end + + # @!macro promises.shortcut.on + # @return [Future] + def future(*args, &task) + future_on(default_executor, *args, &task) + end + + # Constructs a new Future which will be resolved after block is evaluated on default executor. + # Evaluation begins immediately. + # + # @!macro promises.param.default_executor + # @!macro promises.param.args + # @yield [*args] to the task. + # @!macro promise.param.task-future + # @return [Future] + def future_on(default_executor, *args, &task) + ImmediateEventPromise.new(default_executor).future.then(*args, &task) + end + + # Creates a resolved future with will be either fulfilled with the given value or rejected with + # the given reason. + # + # @param [true, false] fulfilled + # @param [Object] value + # @param [Object] reason + # @!macro promises.param.default_executor + # @return [Future] + def resolved_future(fulfilled, value, reason, default_executor = self.default_executor) + ImmediateFuturePromise.new(default_executor, fulfilled, value, reason).future + end + + # Creates a resolved future which will be fulfilled with the given value. + # + # @!macro promises.param.default_executor + # @param [Object] value + # @return [Future] + def fulfilled_future(value, default_executor = self.default_executor) + resolved_future true, value, nil, default_executor + end + + # Creates a resolved future which will be rejected with the given reason. + # + # @!macro promises.param.default_executor + # @param [Object] reason + # @return [Future] + def rejected_future(reason, default_executor = self.default_executor) + resolved_future false, nil, reason, default_executor + end + + # Creates resolved event. + # + # @!macro promises.param.default_executor + # @return [Event] + def resolved_event(default_executor = self.default_executor) + ImmediateEventPromise.new(default_executor).event + end + + # General constructor. Behaves differently based on the argument's type. It's provided for convenience + # but it's better to be explicit. + # + # @see rejected_future, resolved_event, fulfilled_future + # @!macro promises.param.default_executor + # @return [Event, Future] + # + # @overload make_future(nil, default_executor = self.default_executor) + # @param [nil] nil + # @return [Event] resolved event. + # + # @overload make_future(a_future, default_executor = self.default_executor) + # @param [Future] a_future + # @return [Future] a future which will be resolved when a_future is. + # + # @overload make_future(an_event, default_executor = self.default_executor) + # @param [Event] an_event + # @return [Event] an event which will be resolved when an_event is. + # + # @overload make_future(exception, default_executor = self.default_executor) + # @param [Exception] exception + # @return [Future] a rejected future with the exception as its reason. + # + # @overload make_future(value, default_executor = self.default_executor) + # @param [Object] value when none of the above overloads fits + # @return [Future] a fulfilled future with the value. + def make_future(argument = nil, default_executor = self.default_executor) + case argument + when AbstractEventFuture + # returning wrapper would change nothing + argument + when Exception + rejected_future argument, default_executor + when nil + resolved_event default_executor + else + fulfilled_future argument, default_executor + end + end + + # @!macro promises.shortcut.on + # @return [Future, Event] + def delay(*args, &task) + delay_on default_executor, *args, &task + end + + # Creates a new event or future which is resolved only after it is touched, + # see {Concurrent::AbstractEventFuture#touch}. + # + # @!macro promises.param.default_executor + # @overload delay_on(default_executor, *args, &task) + # If task is provided it returns a {Future} representing the result of the task. + # @!macro promises.param.args + # @yield [*args] to the task. + # @!macro promise.param.task-future + # @return [Future] + # @overload delay_on(default_executor) + # If no task is provided, it returns an {Event} + # @return [Event] + def delay_on(default_executor, *args, &task) + event = DelayPromise.new(default_executor).event + task ? event.chain(*args, &task) : event + end + + # @!macro promises.shortcut.on + # @return [Future, Event] + def schedule(intended_time, *args, &task) + schedule_on default_executor, intended_time, *args, &task + end + + # Creates a new event or future which is resolved in intended_time. + # + # @!macro promises.param.default_executor + # @!macro promises.param.intended_time + # @param [Numeric, Time] intended_time `Numeric` means to run in `intended_time` seconds. + # `Time` means to run on `intended_time`. + # @overload schedule_on(default_executor, intended_time, *args, &task) + # If task is provided it returns a {Future} representing the result of the task. + # @!macro promises.param.args + # @yield [*args] to the task. + # @!macro promise.param.task-future + # @return [Future] + # @overload schedule_on(default_executor, intended_time) + # If no task is provided, it returns an {Event} + # @return [Event] + def schedule_on(default_executor, intended_time, *args, &task) + event = ScheduledPromise.new(default_executor, intended_time).event + task ? event.chain(*args, &task) : event + end + + # @!macro promises.shortcut.on + # @return [Future] + def zip_futures(*futures_and_or_events) + zip_futures_on default_executor, *futures_and_or_events + end + + # Creates a new future which is resolved after all futures_and_or_events are resolved. + # Its value is an array of zipped future values. Its reason is an array of reasons for rejection. + # If there is an error it rejects. + # @!macro promises.event-conversion + # If event is supplied, which does not have value and can be only resolved, it's + # represented as `:fulfilled` with value `nil`. + # + # @!macro promises.param.default_executor + # @param [AbstractEventFuture] futures_and_or_events + # @return [Future] + def zip_futures_on(default_executor, *futures_and_or_events) + ZipFuturesPromise.new_blocked_by(futures_and_or_events, default_executor).future + end + + alias_method :zip, :zip_futures + + # @!macro promises.shortcut.on + # @return [Event] + def zip_events(*futures_and_or_events) + zip_events_on default_executor, *futures_and_or_events + end + + # Creates a new event which is resolved after all futures_and_or_events are resolved. + # (Future is resolved when fulfilled or rejected.) + # + # @!macro promises.param.default_executor + # @param [AbstractEventFuture] futures_and_or_events + # @return [Event] + def zip_events_on(default_executor, *futures_and_or_events) + ZipEventsPromise.new_blocked_by(futures_and_or_events, default_executor).event + end + + # @!macro promises.shortcut.on + # @return [Future] + def any_resolved_future(*futures_and_or_events) + any_resolved_future_on default_executor, *futures_and_or_events + end + + alias_method :any, :any_resolved_future + + # Creates a new future which is resolved after the first futures_and_or_events is resolved. + # Its result equals the result of the first resolved future. + # @!macro promises.any-touch + # If resolved it does not propagate {Concurrent::AbstractEventFuture#touch}, leaving delayed + # futures un-executed if they are not required any more. + # @!macro promises.event-conversion + # + # @!macro promises.param.default_executor + # @param [AbstractEventFuture] futures_and_or_events + # @return [Future] + def any_resolved_future_on(default_executor, *futures_and_or_events) + AnyResolvedFuturePromise.new_blocked_by(futures_and_or_events, default_executor).future + end + + # @!macro promises.shortcut.on + # @return [Future] + def any_fulfilled_future(*futures_and_or_events) + any_fulfilled_future_on default_executor, *futures_and_or_events + end + + # Creates a new future which is resolved after the first futures_and_or_events is fulfilled. + # Its result equals the result of the first resolved future or if all futures_and_or_events reject, + # it has reason of the last rejected future. + # @!macro promises.any-touch + # @!macro promises.event-conversion + # + # @!macro promises.param.default_executor + # @param [AbstractEventFuture] futures_and_or_events + # @return [Future] + def any_fulfilled_future_on(default_executor, *futures_and_or_events) + AnyFulfilledFuturePromise.new_blocked_by(futures_and_or_events, default_executor).future + end + + # @!macro promises.shortcut.on + # @return [Event] + def any_event(*futures_and_or_events) + any_event_on default_executor, *futures_and_or_events + end + + # Creates a new event which becomes resolved after the first futures_and_or_events resolves. + # @!macro promises.any-touch + # + # @!macro promises.param.default_executor + # @param [AbstractEventFuture] futures_and_or_events + # @return [Event] + def any_event_on(default_executor, *futures_and_or_events) + AnyResolvedEventPromise.new_blocked_by(futures_and_or_events, default_executor).event + end + + # TODO consider adding first(count, *futures) + # TODO consider adding zip_by(slice, *futures) processing futures in slices + # TODO or rather a generic aggregator taking a function + end + + module InternalStates + # @!visibility private + class State + def resolved? + raise NotImplementedError + end + + def to_sym + raise NotImplementedError + end + end + + # @!visibility private + class Pending < State + def resolved? + false + end + + def to_sym + :pending + end + end + + # @!visibility private + class Reserved < Pending + end + + # @!visibility private + class ResolvedWithResult < State + def resolved? + true + end + + def to_sym + :resolved + end + + def result + [fulfilled?, value, reason] + end + + def fulfilled? + raise NotImplementedError + end + + def value + raise NotImplementedError + end + + def reason + raise NotImplementedError + end + + def apply + raise NotImplementedError + end + end + + # @!visibility private + class Fulfilled < ResolvedWithResult + + def initialize(value) + @Value = value + end + + def fulfilled? + true + end + + def apply(args, block) + block.call value, *args + end + + def value + @Value + end + + def reason + nil + end + + def to_sym + :fulfilled + end + end + + # @!visibility private + class FulfilledArray < Fulfilled + def apply(args, block) + block.call(*value, *args) + end + end + + # @!visibility private + class Rejected < ResolvedWithResult + def initialize(reason) + @Reason = reason + end + + def fulfilled? + false + end + + def value + nil + end + + def reason + @Reason + end + + def to_sym + :rejected + end + + def apply(args, block) + block.call reason, *args + end + end + + # @!visibility private + class PartiallyRejected < ResolvedWithResult + def initialize(value, reason) + super() + @Value = value + @Reason = reason + end + + def fulfilled? + false + end + + def to_sym + :rejected + end + + def value + @Value + end + + def reason + @Reason + end + + def apply(args, block) + block.call(*reason, *args) + end + end + + # @!visibility private + PENDING = Pending.new + # @!visibility private + RESERVED = Reserved.new + # @!visibility private + RESOLVED = Fulfilled.new(nil) + + def RESOLVED.to_sym + :resolved + end + end + + private_constant :InternalStates + + # @!macro promises.shortcut.event-future + # @see Event#$0 + # @see Future#$0 + + # @!macro promises.param.timeout + # @param [Numeric] timeout the maximum time in second to wait. + + # @!macro promises.warn.blocks + # @note This function potentially blocks current thread until the Future is resolved. + # Be careful it can deadlock. Try to chain instead. + + # Common ancestor of {Event} and {Future} classes, many shared methods are defined here. + class AbstractEventFuture < Synchronization::Object + safe_initialization! + attr_atomic(:internal_state) + private :internal_state=, :swap_internal_state, :compare_and_set_internal_state, :update_internal_state + # @!method internal_state + # @!visibility private + + include InternalStates + + def initialize(promise, default_executor) + super() + @Lock = Mutex.new + @Condition = ConditionVariable.new + @Promise = promise + @DefaultExecutor = default_executor + @Callbacks = LockFreeStack.new + @Waiters = AtomicFixnum.new 0 + self.internal_state = PENDING + end + + private :initialize + + # Returns its state. + # @return [Symbol] + # + # @overload an_event.state + # @return [:pending, :resolved] + # @overload a_future.state + # Both :fulfilled, :rejected implies :resolved. + # @return [:pending, :fulfilled, :rejected] + def state + internal_state.to_sym + end + + # Is it in pending state? + # @return [Boolean] + def pending? + !internal_state.resolved? + end + + # Is it in resolved state? + # @return [Boolean] + def resolved? + internal_state.resolved? + end + + # Propagates touch. Requests all the delayed futures, which it depends on, to be + # executed. This method is called by any other method requiring resolved state, like {#wait}. + # @return [self] + def touch + @Promise.touch + self + end + + # @!macro promises.touches + # Calls {Concurrent::AbstractEventFuture#touch}. + + # @!macro promises.method.wait + # Wait (block the Thread) until receiver is {#resolved?}. + # @!macro promises.touches + # + # @!macro promises.warn.blocks + # @!macro promises.param.timeout + # @return [self, true, false] self implies timeout was not used, true implies timeout was used + # and it was resolved, false implies it was not resolved within timeout. + def wait(timeout = nil) + result = wait_until_resolved(timeout) + timeout ? result : self + end + + # Returns default executor. + # @return [Executor] default executor + # @see #with_default_executor + # @see FactoryMethods#future_on + # @see FactoryMethods#resolvable_future + # @see FactoryMethods#any_fulfilled_future_on + # @see similar + def default_executor + @DefaultExecutor + end + + # @!macro promises.shortcut.on + # @return [Future] + def chain(*args, &task) + chain_on @DefaultExecutor, *args, &task + end + + # Chains the task to be executed asynchronously on executor after it is resolved. + # + # @!macro promises.param.executor + # @!macro promises.param.args + # @return [Future] + # @!macro promise.param.task-future + # + # @overload an_event.chain_on(executor, *args, &task) + # @yield [*args] to the task. + # @overload a_future.chain_on(executor, *args, &task) + # @yield [fulfilled, value, reason, *args] to the task. + # @yieldparam [true, false] fulfilled + # @yieldparam [Object] value + # @yieldparam [Object] reason + def chain_on(executor, *args, &task) + ChainPromise.new_blocked_by1(self, executor, executor, args, &task).future + end + + # @return [String] Short string representation. + def to_s + format '%s %s>', super[0..-2], state + end + + alias_method :inspect, :to_s + + # Resolves the resolvable when receiver is resolved. + # + # @param [Resolvable] resolvable + # @return [self] + def chain_resolvable(resolvable) + on_resolution! { resolvable.resolve_with internal_state } + end + + alias_method :tangle, :chain_resolvable + + # @!macro promises.shortcut.using + # @return [self] + def on_resolution(*args, &callback) + on_resolution_using @DefaultExecutor, *args, &callback + end + + # Stores the callback to be executed synchronously on resolving thread after it is + # resolved. + # + # @!macro promises.param.args + # @!macro promise.param.callback + # @return [self] + # + # @overload an_event.on_resolution!(*args, &callback) + # @yield [*args] to the callback. + # @overload a_future.on_resolution!(*args, &callback) + # @yield [fulfilled, value, reason, *args] to the callback. + # @yieldparam [true, false] fulfilled + # @yieldparam [Object] value + # @yieldparam [Object] reason + def on_resolution!(*args, &callback) + add_callback :callback_on_resolution, args, callback + end + + # Stores the callback to be executed asynchronously on executor after it is resolved. + # + # @!macro promises.param.executor + # @!macro promises.param.args + # @!macro promise.param.callback + # @return [self] + # + # @overload an_event.on_resolution_using(executor, *args, &callback) + # @yield [*args] to the callback. + # @overload a_future.on_resolution_using(executor, *args, &callback) + # @yield [fulfilled, value, reason, *args] to the callback. + # @yieldparam [true, false] fulfilled + # @yieldparam [Object] value + # @yieldparam [Object] reason + def on_resolution_using(executor, *args, &callback) + add_callback :async_callback_on_resolution, executor, args, callback + end + + # @!macro promises.method.with_default_executor + # Crates new object with same class with the executor set as its new default executor. + # Any futures depending on it will use the new default executor. + # @!macro promises.shortcut.event-future + # @abstract + # @return [AbstractEventFuture] + def with_default_executor(executor) + raise NotImplementedError + end + + # @!visibility private + def resolve_with(state, raise_on_reassign = true, reserved = false) + if compare_and_set_internal_state(reserved ? RESERVED : PENDING, state) + # go to synchronized block only if there were waiting threads + @Lock.synchronize { @Condition.broadcast } unless @Waiters.value == 0 + call_callbacks state + else + return rejected_resolution(raise_on_reassign, state) + end + self + end + + # For inspection. + # @!visibility private + # @return [Array] + def blocks + @Callbacks.each_with_object([]) do |(method, args), promises| + promises.push(args[0]) if method == :callback_notify_blocked + end + end + + # For inspection. + # @!visibility private + def callbacks + @Callbacks.each.to_a + end + + # For inspection. + # @!visibility private + def promise + @Promise + end + + # For inspection. + # @!visibility private + def touched? + promise.touched? + end + + # For inspection. + # @!visibility private + def waiting_threads + @Waiters.each.to_a + end + + # @!visibility private + def add_callback_notify_blocked(promise, index) + add_callback :callback_notify_blocked, promise, index + end + + # @!visibility private + def add_callback_clear_delayed_node(node) + add_callback(:callback_clear_delayed_node, node) + end + + # @!visibility private + def with_hidden_resolvable + # TODO (pitr-ch 10-Dec-2018): documentation, better name if in edge + self + end + + private + + def add_callback(method, *args) + state = internal_state + if state.resolved? + call_callback method, state, args + else + @Callbacks.push [method, args] + state = internal_state + # take back if it was resolved in the meanwhile + call_callbacks state if state.resolved? + end + self + end + + def callback_clear_delayed_node(state, node) + node.value = nil + end + + # @return [Boolean] + def wait_until_resolved(timeout) + return true if resolved? + + touch + + @Lock.synchronize do + @Waiters.increment + begin + if timeout + start = Concurrent.monotonic_time + until resolved? + break if @Condition.wait(@Lock, timeout) == nil # nil means timeout + timeout -= (Concurrent.monotonic_time - start) + break if timeout <= 0 + end + else + until resolved? + @Condition.wait(@Lock, timeout) + end + end + ensure + # JRuby may raise ConcurrencyError + @Waiters.decrement + end + end + resolved? + end + + def call_callback(method, state, args) + self.send method, state, *args + end + + def call_callbacks(state) + method, args = @Callbacks.pop + while method + call_callback method, state, args + method, args = @Callbacks.pop + end + end + + def with_async(executor, *args, &block) + Concurrent.executor(executor).post(*args, &block) + end + + def async_callback_on_resolution(state, executor, args, callback) + with_async(executor, state, args, callback) do |st, ar, cb| + callback_on_resolution st, ar, cb + end + end + + def callback_notify_blocked(state, promise, index) + promise.on_blocker_resolution self, index + end + end + + # Represents an event which will happen in future (will be resolved). The event is either + # pending or resolved. It should be always resolved. Use {Future} to communicate rejections and + # cancellation. + class Event < AbstractEventFuture + + alias_method :then, :chain + + + # @!macro promises.method.zip + # Creates a new event or a future which will be resolved when receiver and other are. + # Returns an event if receiver and other are events, otherwise returns a future. + # If just one of the parties is Future then the result + # of the returned future is equal to the result of the supplied future. If both are futures + # then the result is as described in {FactoryMethods#zip_futures_on}. + # + # @return [Future, Event] + def zip(other) + if other.is_a?(Future) + ZipFutureEventPromise.new_blocked_by2(other, self, @DefaultExecutor).future + else + ZipEventEventPromise.new_blocked_by2(self, other, @DefaultExecutor).event + end + end + + alias_method :&, :zip + + # Creates a new event which will be resolved when the first of receiver, `event_or_future` + # resolves. + # + # @return [Event] + def any(event_or_future) + AnyResolvedEventPromise.new_blocked_by2(self, event_or_future, @DefaultExecutor).event + end + + alias_method :|, :any + + # Creates new event dependent on receiver which will not evaluate until touched, see {#touch}. + # In other words, it inserts delay into the chain of Futures making rest of it lazy evaluated. + # + # @return [Event] + def delay + event = DelayPromise.new(@DefaultExecutor).event + ZipEventEventPromise.new_blocked_by2(self, event, @DefaultExecutor).event + end + + # @!macro promise.method.schedule + # Creates new event dependent on receiver scheduled to execute on/in intended_time. + # In time is interpreted from the moment the receiver is resolved, therefore it inserts + # delay into the chain. + # + # @!macro promises.param.intended_time + # @return [Event] + def schedule(intended_time) + chain do + event = ScheduledPromise.new(@DefaultExecutor, intended_time).event + ZipEventEventPromise.new_blocked_by2(self, event, @DefaultExecutor).event + end.flat_event + end + + # Converts event to a future. The future is fulfilled when the event is resolved, the future may never fail. + # + # @return [Future] + def to_future + future = Promises.resolvable_future + ensure + chain_resolvable(future) + end + + # Returns self, since this is event + # @return [Event] + def to_event + self + end + + # @!macro promises.method.with_default_executor + # @return [Event] + def with_default_executor(executor) + EventWrapperPromise.new_blocked_by1(self, executor).event + end + + private + + def rejected_resolution(raise_on_reassign, state) + raise Concurrent::MultipleAssignmentError.new('Event can be resolved only once') if raise_on_reassign + return false + end + + def callback_on_resolution(state, args, callback) + callback.call(*args) + end + end + + # Represents a value which will become available in future. May reject with a reason instead, + # e.g. when the tasks raises an exception. + class Future < AbstractEventFuture + + # Is it in fulfilled state? + # @return [Boolean] + def fulfilled? + state = internal_state + state.resolved? && state.fulfilled? + end + + # Is it in rejected state? + # @return [Boolean] + def rejected? + state = internal_state + state.resolved? && !state.fulfilled? + end + + # @!macro promises.warn.nil + # @note Make sure returned `nil` is not confused with timeout, no value when rejected, + # no reason when fulfilled, etc. + # Use more exact methods if needed, like {#wait}, {#value!}, {#result}, etc. + + # @!macro promises.method.value + # Return value of the future. + # @!macro promises.touches + # + # @!macro promises.warn.blocks + # @!macro promises.warn.nil + # @!macro promises.param.timeout + # @!macro promises.param.timeout_value + # @param [Object] timeout_value a value returned by the method when it times out + # @return [Object, nil, timeout_value] the value of the Future when fulfilled, + # timeout_value on timeout, + # nil on rejection. + def value(timeout = nil, timeout_value = nil) + if wait_until_resolved timeout + internal_state.value + else + timeout_value + end + end + + # Returns reason of future's rejection. + # @!macro promises.touches + # + # @!macro promises.warn.blocks + # @!macro promises.warn.nil + # @!macro promises.param.timeout + # @!macro promises.param.timeout_value + # @return [Object, timeout_value] the reason, or timeout_value on timeout, or nil on fulfillment. + def reason(timeout = nil, timeout_value = nil) + if wait_until_resolved timeout + internal_state.reason + else + timeout_value + end + end + + # Returns triplet fulfilled?, value, reason. + # @!macro promises.touches + # + # @!macro promises.warn.blocks + # @!macro promises.param.timeout + # @return [Array(Boolean, Object, Object), nil] triplet of fulfilled?, value, reason, or nil + # on timeout. + def result(timeout = nil) + internal_state.result if wait_until_resolved timeout + end + + # @!macro promises.method.wait + # @raise [Exception] {#reason} on rejection + def wait!(timeout = nil) + result = wait_until_resolved!(timeout) + timeout ? result : self + end + + # @!macro promises.method.value + # @return [Object, nil, timeout_value] the value of the Future when fulfilled, + # or nil on rejection, + # or timeout_value on timeout. + # @raise [Exception] {#reason} on rejection + def value!(timeout = nil, timeout_value = nil) + if wait_until_resolved! timeout + internal_state.value + else + timeout_value + end + end + + # Allows rejected Future to be risen with `raise` method. + # If the reason is not an exception `Runtime.new(reason)` is returned. + # + # @example + # raise Promises.rejected_future(StandardError.new("boom")) + # raise Promises.rejected_future("or just boom") + # @raise [Concurrent::Error] when raising not rejected future + # @return [Exception] + def exception(*args) + raise Concurrent::Error, 'it is not rejected' unless rejected? + raise ArgumentError unless args.size <= 1 + reason = Array(internal_state.reason).flatten.compact + if reason.size > 1 + ex = Concurrent::MultipleErrors.new reason + ex.set_backtrace(caller) + ex + else + ex = if reason[0].respond_to? :exception + reason[0].exception(*args) + else + RuntimeError.new(reason[0]).exception(*args) + end + ex.set_backtrace Array(ex.backtrace) + caller + ex + end + end + + # @!macro promises.shortcut.on + # @return [Future] + def then(*args, &task) + then_on @DefaultExecutor, *args, &task + end + + # Chains the task to be executed asynchronously on executor after it fulfills. Does not run + # the task if it rejects. It will resolve though, triggering any dependent futures. + # + # @!macro promises.param.executor + # @!macro promises.param.args + # @!macro promise.param.task-future + # @return [Future] + # @yield [value, *args] to the task. + def then_on(executor, *args, &task) + ThenPromise.new_blocked_by1(self, executor, executor, args, &task).future + end + + # @!macro promises.shortcut.on + # @return [Future] + def rescue(*args, &task) + rescue_on @DefaultExecutor, *args, &task + end + + # Chains the task to be executed asynchronously on executor after it rejects. Does not run + # the task if it fulfills. It will resolve though, triggering any dependent futures. + # + # @!macro promises.param.executor + # @!macro promises.param.args + # @!macro promise.param.task-future + # @return [Future] + # @yield [reason, *args] to the task. + def rescue_on(executor, *args, &task) + RescuePromise.new_blocked_by1(self, executor, executor, args, &task).future + end + + # @!macro promises.method.zip + # @return [Future] + def zip(other) + if other.is_a?(Future) + ZipFuturesPromise.new_blocked_by2(self, other, @DefaultExecutor).future + else + ZipFutureEventPromise.new_blocked_by2(self, other, @DefaultExecutor).future + end + end + + alias_method :&, :zip + + # Creates a new event which will be resolved when the first of receiver, `event_or_future` + # resolves. Returning future will have value nil if event_or_future is event and resolves + # first. + # + # @return [Future] + def any(event_or_future) + AnyResolvedFuturePromise.new_blocked_by2(self, event_or_future, @DefaultExecutor).future + end + + alias_method :|, :any + + # Creates new future dependent on receiver which will not evaluate until touched, see {#touch}. + # In other words, it inserts delay into the chain of Futures making rest of it lazy evaluated. + # + # @return [Future] + def delay + event = DelayPromise.new(@DefaultExecutor).event + ZipFutureEventPromise.new_blocked_by2(self, event, @DefaultExecutor).future + end + + # @!macro promise.method.schedule + # @return [Future] + def schedule(intended_time) + chain do + event = ScheduledPromise.new(@DefaultExecutor, intended_time).event + ZipFutureEventPromise.new_blocked_by2(self, event, @DefaultExecutor).future + end.flat + end + + # @!macro promises.method.with_default_executor + # @return [Future] + def with_default_executor(executor) + FutureWrapperPromise.new_blocked_by1(self, executor).future + end + + # Creates new future which will have result of the future returned by receiver. If receiver + # rejects it will have its rejection. + # + # @param [Integer] level how many levels of futures should flatten + # @return [Future] + def flat_future(level = 1) + FlatFuturePromise.new_blocked_by1(self, level, @DefaultExecutor).future + end + + alias_method :flat, :flat_future + + # Creates new event which will be resolved when the returned event by receiver is. + # Be careful if the receiver rejects it will just resolve since Event does not hold reason. + # + # @return [Event] + def flat_event + FlatEventPromise.new_blocked_by1(self, @DefaultExecutor).event + end + + # @!macro promises.shortcut.using + # @return [self] + def on_fulfillment(*args, &callback) + on_fulfillment_using @DefaultExecutor, *args, &callback + end + + # Stores the callback to be executed synchronously on resolving thread after it is + # fulfilled. Does nothing on rejection. + # + # @!macro promises.param.args + # @!macro promise.param.callback + # @return [self] + # @yield [value, *args] to the callback. + def on_fulfillment!(*args, &callback) + add_callback :callback_on_fulfillment, args, callback + end + + # Stores the callback to be executed asynchronously on executor after it is + # fulfilled. Does nothing on rejection. + # + # @!macro promises.param.executor + # @!macro promises.param.args + # @!macro promise.param.callback + # @return [self] + # @yield [value, *args] to the callback. + def on_fulfillment_using(executor, *args, &callback) + add_callback :async_callback_on_fulfillment, executor, args, callback + end + + # @!macro promises.shortcut.using + # @return [self] + def on_rejection(*args, &callback) + on_rejection_using @DefaultExecutor, *args, &callback + end + + # Stores the callback to be executed synchronously on resolving thread after it is + # rejected. Does nothing on fulfillment. + # + # @!macro promises.param.args + # @!macro promise.param.callback + # @return [self] + # @yield [reason, *args] to the callback. + def on_rejection!(*args, &callback) + add_callback :callback_on_rejection, args, callback + end + + # Stores the callback to be executed asynchronously on executor after it is + # rejected. Does nothing on fulfillment. + # + # @!macro promises.param.executor + # @!macro promises.param.args + # @!macro promise.param.callback + # @return [self] + # @yield [reason, *args] to the callback. + def on_rejection_using(executor, *args, &callback) + add_callback :async_callback_on_rejection, executor, args, callback + end + + # Allows to use futures as green threads. The receiver has to evaluate to a future which + # represents what should be done next. It basically flattens indefinitely until non Future + # values is returned which becomes result of the returned future. Any encountered exception + # will become reason of the returned future. + # + # @return [Future] + # @param [#call(value)] run_test + # an object which when called returns either Future to keep running with + # or nil, then the run completes with the value. + # The run_test can be used to extract the Future from deeper structure, + # or to distinguish Future which is a resulting value from a future + # which is suppose to continue running. + # @example + # body = lambda do |v| + # v += 1 + # v < 5 ? Promises.future(v, &body) : v + # end + # Promises.future(0, &body).run.value! # => 5 + def run(run_test = method(:run_test)) + RunFuturePromise.new_blocked_by1(self, @DefaultExecutor, run_test).future + end + + # @!visibility private + def apply(args, block) + internal_state.apply args, block + end + + # Converts future to event which is resolved when future is resolved by fulfillment or rejection. + # + # @return [Event] + def to_event + event = Promises.resolvable_event + ensure + chain_resolvable(event) + end + + # Returns self, since this is a future + # @return [Future] + def to_future + self + end + + # @return [String] Short string representation. + def to_s + if resolved? + format '%s with %s>', super[0..-2], (fulfilled? ? value : reason).inspect + else + super + end + end + + alias_method :inspect, :to_s + + private + + def run_test(v) + v if v.is_a?(Future) + end + + def rejected_resolution(raise_on_reassign, state) + if raise_on_reassign + if internal_state == RESERVED + raise Concurrent::MultipleAssignmentError.new( + "Future can be resolved only once. It is already reserved.") + else + raise Concurrent::MultipleAssignmentError.new( + "Future can be resolved only once. It's #{result}, trying to set #{state.result}.", + current_result: result, + new_result: state.result) + end + end + return false + end + + def wait_until_resolved!(timeout = nil) + result = wait_until_resolved(timeout) + raise self if rejected? + result + end + + def async_callback_on_fulfillment(state, executor, args, callback) + with_async(executor, state, args, callback) do |st, ar, cb| + callback_on_fulfillment st, ar, cb + end + end + + def async_callback_on_rejection(state, executor, args, callback) + with_async(executor, state, args, callback) do |st, ar, cb| + callback_on_rejection st, ar, cb + end + end + + def callback_on_fulfillment(state, args, callback) + state.apply args, callback if state.fulfilled? + end + + def callback_on_rejection(state, args, callback) + state.apply args, callback unless state.fulfilled? + end + + def callback_on_resolution(state, args, callback) + callback.call(*state.result, *args) + end + + end + + # Marker module of Future, Event resolved manually. + module Resolvable + include InternalStates + end + + # A Event which can be resolved by user. + class ResolvableEvent < Event + include Resolvable + + # @!macro raise_on_reassign + # @raise [MultipleAssignmentError] when already resolved and raise_on_reassign is true. + + # @!macro promise.param.raise_on_reassign + # @param [Boolean] raise_on_reassign should method raise exception if already resolved + # @return [self, false] false is returned when raise_on_reassign is false and the receiver + # is already resolved. + # + + # Makes the event resolved, which triggers all dependent futures. + # + # @!macro promise.param.raise_on_reassign + # @!macro promise.param.reserved + # @param [true, false] reserved + # Set to true if the resolvable is {#reserve}d by you, + # marks resolution of reserved resolvable events and futures explicitly. + # Advanced feature, ignore unless you use {Resolvable#reserve} from edge. + def resolve(raise_on_reassign = true, reserved = false) + resolve_with RESOLVED, raise_on_reassign, reserved + end + + # Creates new event wrapping receiver, effectively hiding the resolve method. + # + # @return [Event] + def with_hidden_resolvable + @with_hidden_resolvable ||= EventWrapperPromise.new_blocked_by1(self, @DefaultExecutor).event + end + + # Behaves as {AbstractEventFuture#wait} but has one additional optional argument + # resolve_on_timeout. + # + # @param [true, false] resolve_on_timeout + # If it times out and the argument is true it will also resolve the event. + # @return [self, true, false] + # @see AbstractEventFuture#wait + def wait(timeout = nil, resolve_on_timeout = false) + super(timeout) or if resolve_on_timeout + # if it fails to resolve it was resolved in the meantime + # so return true as if there was no timeout + !resolve(false) + else + false + end + end + end + + # A Future which can be resolved by user. + class ResolvableFuture < Future + include Resolvable + + # Makes the future resolved with result of triplet `fulfilled?`, `value`, `reason`, + # which triggers all dependent futures. + # + # @param [true, false] fulfilled + # @param [Object] value + # @param [Object] reason + # @!macro promise.param.raise_on_reassign + # @!macro promise.param.reserved + def resolve(fulfilled = true, value = nil, reason = nil, raise_on_reassign = true, reserved = false) + resolve_with(fulfilled ? Fulfilled.new(value) : Rejected.new(reason), raise_on_reassign, reserved) + end + + # Makes the future fulfilled with `value`, + # which triggers all dependent futures. + # + # @param [Object] value + # @!macro promise.param.raise_on_reassign + # @!macro promise.param.reserved + def fulfill(value, raise_on_reassign = true, reserved = false) + resolve_with Fulfilled.new(value), raise_on_reassign, reserved + end + + # Makes the future rejected with `reason`, + # which triggers all dependent futures. + # + # @param [Object] reason + # @!macro promise.param.raise_on_reassign + # @!macro promise.param.reserved + def reject(reason, raise_on_reassign = true, reserved = false) + resolve_with Rejected.new(reason), raise_on_reassign, reserved + end + + # Evaluates the block and sets its result as future's value fulfilling, if the block raises + # an exception the future rejects with it. + # + # @yield [*args] to the block. + # @yieldreturn [Object] value + # @return [self] + def evaluate_to(*args, &block) + promise.evaluate_to(*args, block) + end + + # Evaluates the block and sets its result as future's value fulfilling, if the block raises + # an exception the future rejects with it. + # + # @yield [*args] to the block. + # @yieldreturn [Object] value + # @return [self] + # @raise [Exception] also raise reason on rejection. + def evaluate_to!(*args, &block) + promise.evaluate_to(*args, block).wait! + end + + # @!macro promises.resolvable.resolve_on_timeout + # @param [::Array(true, Object, nil), ::Array(false, nil, Exception), nil] resolve_on_timeout + # If it times out and the argument is not nil it will also resolve the future + # to the provided resolution. + + # Behaves as {AbstractEventFuture#wait} but has one additional optional argument + # resolve_on_timeout. + # + # @!macro promises.resolvable.resolve_on_timeout + # @return [self, true, false] + # @see AbstractEventFuture#wait + def wait(timeout = nil, resolve_on_timeout = nil) + super(timeout) or if resolve_on_timeout + # if it fails to resolve it was resolved in the meantime + # so return true as if there was no timeout + !resolve(*resolve_on_timeout, false) + else + false + end + end + + # Behaves as {Future#wait!} but has one additional optional argument + # resolve_on_timeout. + # + # @!macro promises.resolvable.resolve_on_timeout + # @return [self, true, false] + # @raise [Exception] {#reason} on rejection + # @see Future#wait! + def wait!(timeout = nil, resolve_on_timeout = nil) + super(timeout) or if resolve_on_timeout + if resolve(*resolve_on_timeout, false) + false + else + # if it fails to resolve it was resolved in the meantime + # so return true as if there was no timeout + raise self if rejected? + true + end + else + false + end + end + + # Behaves as {Future#value} but has one additional optional argument + # resolve_on_timeout. + # + # @!macro promises.resolvable.resolve_on_timeout + # @return [Object, timeout_value, nil] + # @see Future#value + def value(timeout = nil, timeout_value = nil, resolve_on_timeout = nil) + if wait_until_resolved timeout + internal_state.value + else + if resolve_on_timeout + unless resolve(*resolve_on_timeout, false) + # if it fails to resolve it was resolved in the meantime + # so return value as if there was no timeout + return internal_state.value + end + end + timeout_value + end + end + + # Behaves as {Future#value!} but has one additional optional argument + # resolve_on_timeout. + # + # @!macro promises.resolvable.resolve_on_timeout + # @return [Object, timeout_value, nil] + # @raise [Exception] {#reason} on rejection + # @see Future#value! + def value!(timeout = nil, timeout_value = nil, resolve_on_timeout = nil) + if wait_until_resolved! timeout + internal_state.value + else + if resolve_on_timeout + unless resolve(*resolve_on_timeout, false) + # if it fails to resolve it was resolved in the meantime + # so return value as if there was no timeout + raise self if rejected? + return internal_state.value + end + end + timeout_value + end + end + + # Behaves as {Future#reason} but has one additional optional argument + # resolve_on_timeout. + # + # @!macro promises.resolvable.resolve_on_timeout + # @return [Exception, timeout_value, nil] + # @see Future#reason + def reason(timeout = nil, timeout_value = nil, resolve_on_timeout = nil) + if wait_until_resolved timeout + internal_state.reason + else + if resolve_on_timeout + unless resolve(*resolve_on_timeout, false) + # if it fails to resolve it was resolved in the meantime + # so return value as if there was no timeout + return internal_state.reason + end + end + timeout_value + end + end + + # Behaves as {Future#result} but has one additional optional argument + # resolve_on_timeout. + # + # @!macro promises.resolvable.resolve_on_timeout + # @return [::Array(Boolean, Object, Exception), nil] + # @see Future#result + def result(timeout = nil, resolve_on_timeout = nil) + if wait_until_resolved timeout + internal_state.result + else + if resolve_on_timeout + unless resolve(*resolve_on_timeout, false) + # if it fails to resolve it was resolved in the meantime + # so return value as if there was no timeout + internal_state.result + end + end + # otherwise returns nil + end + end + + # Creates new future wrapping receiver, effectively hiding the resolve method and similar. + # + # @return [Future] + def with_hidden_resolvable + @with_hidden_resolvable ||= FutureWrapperPromise.new_blocked_by1(self, @DefaultExecutor).future + end + end + + # @abstract + # @private + class AbstractPromise < Synchronization::Object + safe_initialization! + include InternalStates + + def initialize(future) + super() + @Future = future + end + + def future + @Future + end + + alias_method :event, :future + + def default_executor + future.default_executor + end + + def state + future.state + end + + def touch + end + + def to_s + format '%s %s>', super[0..-2], @Future + end + + alias_method :inspect, :to_s + + def delayed_because + nil + end + + private + + def resolve_with(new_state, raise_on_reassign = true) + @Future.resolve_with(new_state, raise_on_reassign) + end + + # @return [Future] + def evaluate_to(*args, block) + resolve_with Fulfilled.new(block.call(*args)) + rescue Exception => error + resolve_with Rejected.new(error) + raise error unless error.is_a?(StandardError) + end + end + + class ResolvableEventPromise < AbstractPromise + def initialize(default_executor) + super ResolvableEvent.new(self, default_executor) + end + end + + class ResolvableFuturePromise < AbstractPromise + def initialize(default_executor) + super ResolvableFuture.new(self, default_executor) + end + + public :evaluate_to + end + + # @abstract + class InnerPromise < AbstractPromise + end + + # @abstract + class BlockedPromise < InnerPromise + + private_class_method :new + + def self.new_blocked_by1(blocker, *args, &block) + blocker_delayed = blocker.promise.delayed_because + promise = new(blocker_delayed, 1, *args, &block) + blocker.add_callback_notify_blocked promise, 0 + promise + end + + def self.new_blocked_by2(blocker1, blocker2, *args, &block) + blocker_delayed1 = blocker1.promise.delayed_because + blocker_delayed2 = blocker2.promise.delayed_because + delayed = if blocker_delayed1 && blocker_delayed2 + # TODO (pitr-ch 23-Dec-2016): use arrays when we know it will not grow (only flat adds delay) + LockFreeStack.of2(blocker_delayed1, blocker_delayed2) + else + blocker_delayed1 || blocker_delayed2 + end + promise = new(delayed, 2, *args, &block) + blocker1.add_callback_notify_blocked promise, 0 + blocker2.add_callback_notify_blocked promise, 1 + promise + end + + def self.new_blocked_by(blockers, *args, &block) + delayed = blockers.reduce(nil) { |d, f| add_delayed d, f.promise.delayed_because } + promise = new(delayed, blockers.size, *args, &block) + blockers.each_with_index { |f, i| f.add_callback_notify_blocked promise, i } + promise + end + + def self.add_delayed(delayed1, delayed2) + if delayed1 && delayed2 + delayed1.push delayed2 + delayed1 + else + delayed1 || delayed2 + end + end + + def initialize(delayed, blockers_count, future) + super(future) + @Delayed = delayed + @Countdown = AtomicFixnum.new blockers_count + end + + def on_blocker_resolution(future, index) + countdown = process_on_blocker_resolution(future, index) + resolvable = resolvable?(countdown, future, index) + + on_resolvable(future, index) if resolvable + end + + def delayed_because + @Delayed + end + + def touch + clear_and_propagate_touch + end + + # for inspection only + def blocked_by + blocked_by = [] + ObjectSpace.each_object(AbstractEventFuture) { |o| blocked_by.push o if o.blocks.include? self } + blocked_by + end + + private + + def clear_and_propagate_touch(stack_or_element = @Delayed) + return if stack_or_element.nil? + + if stack_or_element.is_a? LockFreeStack + stack_or_element.clear_each { |element| clear_and_propagate_touch element } + else + stack_or_element.touch unless stack_or_element.nil? # if still present + end + end + + # @return [true,false] if resolvable + def resolvable?(countdown, future, index) + countdown.zero? + end + + def process_on_blocker_resolution(future, index) + @Countdown.decrement + end + + def on_resolvable(resolved_future, index) + raise NotImplementedError + end + end + + # @abstract + class BlockedTaskPromise < BlockedPromise + def initialize(delayed, blockers_count, default_executor, executor, args, &task) + raise ArgumentError, 'no block given' unless block_given? + super delayed, 1, Future.new(self, default_executor) + @Executor = executor + @Task = task + @Args = args + end + + def executor + @Executor + end + end + + class ThenPromise < BlockedTaskPromise + private + + def initialize(delayed, blockers_count, default_executor, executor, args, &task) + super delayed, blockers_count, default_executor, executor, args, &task + end + + def on_resolvable(resolved_future, index) + if resolved_future.fulfilled? + Concurrent.executor(@Executor).post(resolved_future, @Args, @Task) do |future, args, task| + evaluate_to lambda { future.apply args, task } + end + else + resolve_with resolved_future.internal_state + end + end + end + + class RescuePromise < BlockedTaskPromise + private + + def initialize(delayed, blockers_count, default_executor, executor, args, &task) + super delayed, blockers_count, default_executor, executor, args, &task + end + + def on_resolvable(resolved_future, index) + if resolved_future.rejected? + Concurrent.executor(@Executor).post(resolved_future, @Args, @Task) do |future, args, task| + evaluate_to lambda { future.apply args, task } + end + else + resolve_with resolved_future.internal_state + end + end + end + + class ChainPromise < BlockedTaskPromise + private + + def on_resolvable(resolved_future, index) + if Future === resolved_future + Concurrent.executor(@Executor).post(resolved_future, @Args, @Task) do |future, args, task| + evaluate_to(*future.result, *args, task) + end + else + Concurrent.executor(@Executor).post(@Args, @Task) do |args, task| + evaluate_to(*args, task) + end + end + end + end + + # will be immediately resolved + class ImmediateEventPromise < InnerPromise + def initialize(default_executor) + super Event.new(self, default_executor).resolve_with(RESOLVED) + end + end + + class ImmediateFuturePromise < InnerPromise + def initialize(default_executor, fulfilled, value, reason) + super Future.new(self, default_executor). + resolve_with(fulfilled ? Fulfilled.new(value) : Rejected.new(reason)) + end + end + + class AbstractFlatPromise < BlockedPromise + + def initialize(delayed_because, blockers_count, event_or_future) + delayed = LockFreeStack.of1(self) + super(delayed, blockers_count, event_or_future) + # noinspection RubyArgCount + @Touched = AtomicBoolean.new false + @DelayedBecause = delayed_because || LockFreeStack.new + + event_or_future.add_callback_clear_delayed_node delayed.peek + end + + def touch + if @Touched.make_true + clear_and_propagate_touch @DelayedBecause + end + end + + private + + def touched? + @Touched.value + end + + def on_resolvable(resolved_future, index) + resolve_with resolved_future.internal_state + end + + def resolvable?(countdown, future, index) + !@Future.internal_state.resolved? && super(countdown, future, index) + end + + def add_delayed_of(future) + delayed = future.promise.delayed_because + if touched? + clear_and_propagate_touch delayed + else + BlockedPromise.add_delayed @DelayedBecause, delayed + clear_and_propagate_touch @DelayedBecause if touched? + end + end + + end + + class FlatEventPromise < AbstractFlatPromise + + private + + def initialize(delayed, blockers_count, default_executor) + super delayed, 2, Event.new(self, default_executor) + end + + def process_on_blocker_resolution(future, index) + countdown = super(future, index) + if countdown.nonzero? + internal_state = future.internal_state + + unless internal_state.fulfilled? + resolve_with RESOLVED + return countdown + end + + value = internal_state.value + case value + when AbstractEventFuture + add_delayed_of value + value.add_callback_notify_blocked self, nil + countdown + else + resolve_with RESOLVED + end + end + countdown + end + + end + + class FlatFuturePromise < AbstractFlatPromise + + private + + def initialize(delayed, blockers_count, levels, default_executor) + raise ArgumentError, 'levels has to be higher than 0' if levels < 1 + # flat promise may result to a future having delayed futures, therefore we have to have empty stack + # to be able to add new delayed futures + super delayed || LockFreeStack.new, 1 + levels, Future.new(self, default_executor) + end + + def process_on_blocker_resolution(future, index) + countdown = super(future, index) + if countdown.nonzero? + internal_state = future.internal_state + + unless internal_state.fulfilled? + resolve_with internal_state + return countdown + end + + value = internal_state.value + case value + when AbstractEventFuture + add_delayed_of value + value.add_callback_notify_blocked self, nil + countdown + else + evaluate_to(lambda { raise TypeError, "returned value #{value.inspect} is not a Future" }) + end + end + countdown + end + + end + + class RunFuturePromise < AbstractFlatPromise + + private + + def initialize(delayed, blockers_count, default_executor, run_test) + super delayed, 1, Future.new(self, default_executor) + @RunTest = run_test + end + + def process_on_blocker_resolution(future, index) + internal_state = future.internal_state + + unless internal_state.fulfilled? + resolve_with internal_state + return 0 + end + + value = internal_state.value + continuation_future = @RunTest.call value + + if continuation_future + add_delayed_of continuation_future + continuation_future.add_callback_notify_blocked self, nil + else + resolve_with internal_state + end + + 1 + end + end + + class ZipEventEventPromise < BlockedPromise + def initialize(delayed, blockers_count, default_executor) + super delayed, 2, Event.new(self, default_executor) + end + + private + + def on_resolvable(resolved_future, index) + resolve_with RESOLVED + end + end + + class ZipFutureEventPromise < BlockedPromise + def initialize(delayed, blockers_count, default_executor) + super delayed, 2, Future.new(self, default_executor) + @result = nil + end + + private + + def process_on_blocker_resolution(future, index) + # first blocking is future, take its result + @result = future.internal_state if index == 0 + # super has to be called after above to piggyback on volatile @Countdown + super future, index + end + + def on_resolvable(resolved_future, index) + resolve_with @result + end + end + + class EventWrapperPromise < BlockedPromise + def initialize(delayed, blockers_count, default_executor) + super delayed, 1, Event.new(self, default_executor) + end + + private + + def on_resolvable(resolved_future, index) + resolve_with RESOLVED + end + end + + class FutureWrapperPromise < BlockedPromise + def initialize(delayed, blockers_count, default_executor) + super delayed, 1, Future.new(self, default_executor) + end + + private + + def on_resolvable(resolved_future, index) + resolve_with resolved_future.internal_state + end + end + + class ZipFuturesPromise < BlockedPromise + + private + + def initialize(delayed, blockers_count, default_executor) + super(delayed, blockers_count, Future.new(self, default_executor)) + @Resolutions = ::Array.new(blockers_count, nil) + + on_resolvable nil, nil if blockers_count == 0 + end + + def process_on_blocker_resolution(future, index) + # TODO (pitr-ch 18-Dec-2016): Can we assume that array will never break under parallel access when never re-sized? + @Resolutions[index] = future.internal_state # has to be set before countdown in super + super future, index + end + + def on_resolvable(resolved_future, index) + all_fulfilled = true + values = ::Array.new(@Resolutions.size) + reasons = ::Array.new(@Resolutions.size) + + @Resolutions.each_with_index do |internal_state, i| + fulfilled, values[i], reasons[i] = internal_state.result + all_fulfilled &&= fulfilled + end + + if all_fulfilled + resolve_with FulfilledArray.new(values) + else + resolve_with PartiallyRejected.new(values, reasons) + end + end + end + + class ZipEventsPromise < BlockedPromise + + private + + def initialize(delayed, blockers_count, default_executor) + super delayed, blockers_count, Event.new(self, default_executor) + + on_resolvable nil, nil if blockers_count == 0 + end + + def on_resolvable(resolved_future, index) + resolve_with RESOLVED + end + end + + # @abstract + class AbstractAnyPromise < BlockedPromise + end + + class AnyResolvedEventPromise < AbstractAnyPromise + + private + + def initialize(delayed, blockers_count, default_executor) + super delayed, blockers_count, Event.new(self, default_executor) + end + + def resolvable?(countdown, future, index) + true + end + + def on_resolvable(resolved_future, index) + resolve_with RESOLVED, false + end + end + + class AnyResolvedFuturePromise < AbstractAnyPromise + + private + + def initialize(delayed, blockers_count, default_executor) + super delayed, blockers_count, Future.new(self, default_executor) + end + + def resolvable?(countdown, future, index) + true + end + + def on_resolvable(resolved_future, index) + resolve_with resolved_future.internal_state, false + end + end + + class AnyFulfilledFuturePromise < AnyResolvedFuturePromise + + private + + def resolvable?(countdown, event_or_future, index) + (event_or_future.is_a?(Event) ? event_or_future.resolved? : event_or_future.fulfilled?) || + # inlined super from BlockedPromise + countdown.zero? + end + end + + class DelayPromise < InnerPromise + + def initialize(default_executor) + event = Event.new(self, default_executor) + @Delayed = LockFreeStack.of1(self) + super event + event.add_callback_clear_delayed_node @Delayed.peek + end + + def touch + @Future.resolve_with RESOLVED + end + + def delayed_because + @Delayed + end + + end + + class ScheduledPromise < InnerPromise + def intended_time + @IntendedTime + end + + def inspect + "#{to_s[0..-2]} intended_time: #{@IntendedTime}>" + end + + private + + def initialize(default_executor, intended_time) + super Event.new(self, default_executor) + + @IntendedTime = intended_time + + in_seconds = begin + now = Time.now + schedule_time = if @IntendedTime.is_a? Time + @IntendedTime + else + now + @IntendedTime + end + [0, schedule_time.to_f - now.to_f].max + end + + Concurrent.global_timer_set.post(in_seconds) do + @Future.resolve_with RESOLVED + end + end + end + + extend FactoryMethods + + private_constant :AbstractPromise, + :ResolvableEventPromise, + :ResolvableFuturePromise, + :InnerPromise, + :BlockedPromise, + :BlockedTaskPromise, + :ThenPromise, + :RescuePromise, + :ChainPromise, + :ImmediateEventPromise, + :ImmediateFuturePromise, + :AbstractFlatPromise, + :FlatFuturePromise, + :FlatEventPromise, + :RunFuturePromise, + :ZipEventEventPromise, + :ZipFutureEventPromise, + :EventWrapperPromise, + :FutureWrapperPromise, + :ZipFuturesPromise, + :ZipEventsPromise, + :AbstractAnyPromise, + :AnyResolvedFuturePromise, + :AnyFulfilledFuturePromise, + :AnyResolvedEventPromise, + :DelayPromise, + :ScheduledPromise + + + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/re_include.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/re_include.rb new file mode 100644 index 00000000..600bc6a5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/re_include.rb @@ -0,0 +1,60 @@ +module Concurrent + + # Methods form module A included to a module B, which is already included into class C, + # will not be visible in the C class. If this module is extended to B then A's methods + # are correctly made visible to C. + # + # @example + # module A + # def a + # :a + # end + # end + # + # module B1 + # end + # + # class C1 + # include B1 + # end + # + # module B2 + # extend Concurrent::ReInclude + # end + # + # class C2 + # include B2 + # end + # + # B1.send :include, A + # B2.send :include, A + # + # C1.new.respond_to? :a # => false + # C2.new.respond_to? :a # => true + # + # @!visibility private + module ReInclude + # @!visibility private + def included(base) + (@re_include_to_bases ||= []) << [:include, base] + super(base) + end + + # @!visibility private + def extended(base) + (@re_include_to_bases ||= []) << [:extend, base] + super(base) + end + + # @!visibility private + def include(*modules) + result = super(*modules) + modules.reverse.each do |module_being_included| + (@re_include_to_bases ||= []).each do |method, mod| + mod.send method, module_being_included + end + end + result + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/scheduled_task.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/scheduled_task.rb new file mode 100644 index 00000000..efe9e193 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/scheduled_task.rb @@ -0,0 +1,331 @@ +require 'concurrent/constants' +require 'concurrent/errors' +require 'concurrent/configuration' +require 'concurrent/ivar' +require 'concurrent/collection/copy_on_notify_observer_set' +require 'concurrent/utility/monotonic_time' + +require 'concurrent/options' + +module Concurrent + + # `ScheduledTask` is a close relative of `Concurrent::Future` but with one + # important difference: A `Future` is set to execute as soon as possible + # whereas a `ScheduledTask` is set to execute after a specified delay. This + # implementation is loosely based on Java's + # [ScheduledExecutorService](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ScheduledExecutorService.html). + # It is a more feature-rich variant of {Concurrent.timer}. + # + # The *intended* schedule time of task execution is set on object construction + # with the `delay` argument. The delay is a numeric (floating point or integer) + # representing a number of seconds in the future. Any other value or a numeric + # equal to or less than zero will result in an exception. The *actual* schedule + # time of task execution is set when the `execute` method is called. + # + # The constructor can also be given zero or more processing options. Currently + # the only supported options are those recognized by the + # [Dereferenceable](Dereferenceable) module. + # + # The final constructor argument is a block representing the task to be performed. + # If no block is given an `ArgumentError` will be raised. + # + # **States** + # + # `ScheduledTask` mixes in the [Obligation](Obligation) module thus giving it + # "future" behavior. This includes the expected lifecycle states. `ScheduledTask` + # has one additional state, however. While the task (block) is being executed the + # state of the object will be `:processing`. This additional state is necessary + # because it has implications for task cancellation. + # + # **Cancellation** + # + # A `:pending` task can be cancelled using the `#cancel` method. A task in any + # other state, including `:processing`, cannot be cancelled. The `#cancel` + # method returns a boolean indicating the success of the cancellation attempt. + # A cancelled `ScheduledTask` cannot be restarted. It is immutable. + # + # **Obligation and Observation** + # + # The result of a `ScheduledTask` can be obtained either synchronously or + # asynchronously. `ScheduledTask` mixes in both the [Obligation](Obligation) + # module and the + # [Observable](http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html) + # module from the Ruby standard library. With one exception `ScheduledTask` + # behaves identically to [Future](Observable) with regard to these modules. + # + # @!macro copy_options + # + # @example Basic usage + # + # require 'concurrent/scheduled_task' + # require 'csv' + # require 'open-uri' + # + # class Ticker + # def get_year_end_closing(symbol, year, api_key) + # uri = "https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=#{symbol}&apikey=#{api_key}&datatype=csv" + # data = [] + # csv = URI.parse(uri).read + # if csv.include?('call frequency') + # return :rate_limit_exceeded + # end + # CSV.parse(csv, headers: true) do |row| + # data << row['close'].to_f if row['timestamp'].include?(year.to_s) + # end + # year_end = data.first + # year_end + # rescue => e + # p e + # end + # end + # + # api_key = ENV['ALPHAVANTAGE_KEY'] + # abort(error_message) unless api_key + # + # # Future + # price = Concurrent::Future.execute{ Ticker.new.get_year_end_closing('TWTR', 2013, api_key) } + # price.state #=> :pending + # price.pending? #=> true + # price.value(0) #=> nil (does not block) + # + # sleep(1) # do other stuff + # + # price.value #=> 63.65 (after blocking if necessary) + # price.state #=> :fulfilled + # price.fulfilled? #=> true + # price.value #=> 63.65 + # + # @example Successful task execution + # + # task = Concurrent::ScheduledTask.new(2){ 'What does the fox say?' } + # task.state #=> :unscheduled + # task.execute + # task.state #=> pending + # + # # wait for it... + # sleep(3) + # + # task.unscheduled? #=> false + # task.pending? #=> false + # task.fulfilled? #=> true + # task.rejected? #=> false + # task.value #=> 'What does the fox say?' + # + # @example One line creation and execution + # + # task = Concurrent::ScheduledTask.new(2){ 'What does the fox say?' }.execute + # task.state #=> pending + # + # task = Concurrent::ScheduledTask.execute(2){ 'What do you get when you multiply 6 by 9?' } + # task.state #=> pending + # + # @example Failed task execution + # + # task = Concurrent::ScheduledTask.execute(2){ raise StandardError.new('Call me maybe?') } + # task.pending? #=> true + # + # # wait for it... + # sleep(3) + # + # task.unscheduled? #=> false + # task.pending? #=> false + # task.fulfilled? #=> false + # task.rejected? #=> true + # task.value #=> nil + # task.reason #=> # + # + # @example Task execution with observation + # + # observer = Class.new{ + # def update(time, value, reason) + # puts "The task completed at #{time} with value '#{value}'" + # end + # }.new + # + # task = Concurrent::ScheduledTask.new(2){ 'What does the fox say?' } + # task.add_observer(observer) + # task.execute + # task.pending? #=> true + # + # # wait for it... + # sleep(3) + # + # #>> The task completed at 2013-11-07 12:26:09 -0500 with value 'What does the fox say?' + # + # @!macro monotonic_clock_warning + # + # @see Concurrent.timer + class ScheduledTask < IVar + include Comparable + + # The executor on which to execute the task. + # @!visibility private + attr_reader :executor + + # Schedule a task for execution at a specified future time. + # + # @param [Float] delay the number of seconds to wait for before executing the task + # + # @yield the task to be performed + # + # @!macro executor_and_deref_options + # + # @option opts [object, Array] :args zero or more arguments to be passed the task + # block on execution + # + # @raise [ArgumentError] When no block is given + # @raise [ArgumentError] When given a time that is in the past + def initialize(delay, opts = {}, &task) + raise ArgumentError.new('no block given') unless block_given? + raise ArgumentError.new('seconds must be greater than zero') if delay.to_f < 0.0 + + super(NULL, opts, &nil) + + synchronize do + ns_set_state(:unscheduled) + @parent = opts.fetch(:timer_set, Concurrent.global_timer_set) + @args = get_arguments_from(opts) + @delay = delay.to_f + @task = task + @time = nil + @executor = Options.executor_from_options(opts) || Concurrent.global_io_executor + self.observers = Collection::CopyOnNotifyObserverSet.new + end + end + + # The `delay` value given at instantiation. + # + # @return [Float] the initial delay. + def initial_delay + synchronize { @delay } + end + + # The monotonic time at which the the task is scheduled to be executed. + # + # @return [Float] the schedule time or nil if `unscheduled` + def schedule_time + synchronize { @time } + end + + # Comparator which orders by schedule time. + # + # @!visibility private + def <=>(other) + schedule_time <=> other.schedule_time + end + + # Has the task been cancelled? + # + # @return [Boolean] true if the task is in the given state else false + def cancelled? + synchronize { ns_check_state?(:cancelled) } + end + + # In the task execution in progress? + # + # @return [Boolean] true if the task is in the given state else false + def processing? + synchronize { ns_check_state?(:processing) } + end + + # Cancel this task and prevent it from executing. A task can only be + # cancelled if it is pending or unscheduled. + # + # @return [Boolean] true if successfully cancelled else false + def cancel + if compare_and_set_state(:cancelled, :pending, :unscheduled) + complete(false, nil, CancelledOperationError.new) + # To avoid deadlocks this call must occur outside of #synchronize + # Changing the state above should prevent redundant calls + @parent.send(:remove_task, self) + else + false + end + end + + # Reschedule the task using the original delay and the current time. + # A task can only be reset while it is `:pending`. + # + # @return [Boolean] true if successfully rescheduled else false + def reset + synchronize{ ns_reschedule(@delay) } + end + + # Reschedule the task using the given delay and the current time. + # A task can only be reset while it is `:pending`. + # + # @param [Float] delay the number of seconds to wait for before executing the task + # + # @return [Boolean] true if successfully rescheduled else false + # + # @raise [ArgumentError] When given a time that is in the past + def reschedule(delay) + delay = delay.to_f + raise ArgumentError.new('seconds must be greater than zero') if delay < 0.0 + synchronize{ ns_reschedule(delay) } + end + + # Execute an `:unscheduled` `ScheduledTask`. Immediately sets the state to `:pending` + # and starts counting down toward execution. Does nothing if the `ScheduledTask` is + # in any state other than `:unscheduled`. + # + # @return [ScheduledTask] a reference to `self` + def execute + if compare_and_set_state(:pending, :unscheduled) + synchronize{ ns_schedule(@delay) } + end + self + end + + # Create a new `ScheduledTask` object with the given block, execute it, and return the + # `:pending` object. + # + # @param [Float] delay the number of seconds to wait for before executing the task + # + # @!macro executor_and_deref_options + # + # @return [ScheduledTask] the newly created `ScheduledTask` in the `:pending` state + # + # @raise [ArgumentError] if no block is given + def self.execute(delay, opts = {}, &task) + new(delay, opts, &task).execute + end + + # Execute the task. + # + # @!visibility private + def process_task + safe_execute(@task, @args) + end + + protected :set, :try_set, :fail, :complete + + protected + + # Schedule the task using the given delay and the current time. + # + # @param [Float] delay the number of seconds to wait for before executing the task + # + # @return [Boolean] true if successfully rescheduled else false + # + # @!visibility private + def ns_schedule(delay) + @delay = delay + @time = Concurrent.monotonic_time + @delay + @parent.send(:post_task, self) + end + + # Reschedule the task using the given delay and the current time. + # A task can only be reset while it is `:pending`. + # + # @param [Float] delay the number of seconds to wait for before executing the task + # + # @return [Boolean] true if successfully rescheduled else false + # + # @!visibility private + def ns_reschedule(delay) + return false unless ns_check_state?(:pending) + @parent.send(:remove_task, self) && ns_schedule(delay) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/set.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/set.rb new file mode 100644 index 00000000..eee4effd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/set.rb @@ -0,0 +1,64 @@ +require 'concurrent/utility/engine' +require 'concurrent/thread_safe/util' +require 'set' + +module Concurrent + + # @!macro concurrent_set + # + # A thread-safe subclass of Set. This version locks against the object + # itself for every method call, ensuring only one thread can be reading + # or writing at a time. This includes iteration methods like `#each`. + # + # @note `a += b` is **not** a **thread-safe** operation on + # `Concurrent::Set`. It reads Set `a`, then it creates new `Concurrent::Set` + # which is union of `a` and `b`, then it writes the union to `a`. + # The read and write are independent operations they do not form a single atomic + # operation therefore when two `+=` operations are executed concurrently updates + # may be lost. Use `#merge` instead. + # + # @see http://ruby-doc.org/stdlib-2.4.0/libdoc/set/rdoc/Set.html Ruby standard library `Set` + + # @!macro internal_implementation_note + SetImplementation = case + when Concurrent.on_cruby? + # The CRuby implementation of Set is written in Ruby itself and is + # not thread safe for certain methods. + require 'monitor' + require 'concurrent/thread_safe/util/data_structures' + + class CRubySet < ::Set + end + + ThreadSafe::Util.make_synchronized_on_cruby CRubySet + CRubySet + + when Concurrent.on_jruby? + require 'jruby/synchronized' + + class JRubySet < ::Set + include JRuby::Synchronized + end + + JRubySet + + when Concurrent.on_truffleruby? + require 'concurrent/thread_safe/util/data_structures' + + class TruffleRubySet < ::Set + end + + ThreadSafe::Util.make_synchronized_on_truffleruby TruffleRubySet + TruffleRubySet + + else + warn 'Possibly unsupported Ruby implementation' + ::Set + end + private_constant :SetImplementation + + # @!macro concurrent_set + class Set < SetImplementation + end +end + diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/settable_struct.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/settable_struct.rb new file mode 100644 index 00000000..99b85619 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/settable_struct.rb @@ -0,0 +1,139 @@ +require 'concurrent/errors' +require 'concurrent/synchronization/abstract_struct' +require 'concurrent/synchronization/lockable_object' + +module Concurrent + + # An thread-safe, write-once variation of Ruby's standard `Struct`. + # Each member can have its value set at most once, either at construction + # or any time thereafter. Attempting to assign a value to a member + # that has already been set will result in a `Concurrent::ImmutabilityError`. + # + # @see http://ruby-doc.org/core/Struct.html Ruby standard library `Struct` + # @see http://en.wikipedia.org/wiki/Final_(Java) Java `final` keyword + module SettableStruct + include Synchronization::AbstractStruct + + # @!macro struct_values + def values + synchronize { ns_values } + end + alias_method :to_a, :values + + # @!macro struct_values_at + def values_at(*indexes) + synchronize { ns_values_at(indexes) } + end + + # @!macro struct_inspect + def inspect + synchronize { ns_inspect } + end + alias_method :to_s, :inspect + + # @!macro struct_merge + def merge(other, &block) + synchronize { ns_merge(other, &block) } + end + + # @!macro struct_to_h + def to_h + synchronize { ns_to_h } + end + + # @!macro struct_get + def [](member) + synchronize { ns_get(member) } + end + + # @!macro struct_equality + def ==(other) + synchronize { ns_equality(other) } + end + + # @!macro struct_each + def each(&block) + return enum_for(:each) unless block_given? + synchronize { ns_each(&block) } + end + + # @!macro struct_each_pair + def each_pair(&block) + return enum_for(:each_pair) unless block_given? + synchronize { ns_each_pair(&block) } + end + + # @!macro struct_select + def select(&block) + return enum_for(:select) unless block_given? + synchronize { ns_select(&block) } + end + + # @!macro struct_set + # + # @raise [Concurrent::ImmutabilityError] if the given member has already been set + def []=(member, value) + if member.is_a? Integer + length = synchronize { @values.length } + if member >= length + raise IndexError.new("offset #{member} too large for struct(size:#{length})") + end + synchronize do + unless @values[member].nil? + raise Concurrent::ImmutabilityError.new('struct member has already been set') + end + @values[member] = value + end + else + send("#{member}=", value) + end + rescue NoMethodError + raise NameError.new("no member '#{member}' in struct") + end + + private + + # @!visibility private + def initialize_copy(original) + synchronize do + super(original) + ns_initialize_copy + end + end + + # @!macro struct_new + def self.new(*args, &block) + clazz_name = nil + if args.length == 0 + raise ArgumentError.new('wrong number of arguments (0 for 1+)') + elsif args.length > 0 && args.first.is_a?(String) + clazz_name = args.shift + end + FACTORY.define_struct(clazz_name, args, &block) + end + + FACTORY = Class.new(Synchronization::LockableObject) do + def define_struct(name, members, &block) + synchronize do + clazz = Synchronization::AbstractStruct.define_struct_class(SettableStruct, Synchronization::LockableObject, name, members, &block) + members.each_with_index do |member, index| + clazz.send :remove_method, member if clazz.instance_methods.include? member + clazz.send(:define_method, member) do + synchronize { @values[index] } + end + clazz.send(:define_method, "#{member}=") do |value| + synchronize do + unless @values[index].nil? + raise Concurrent::ImmutabilityError.new('struct member has already been set') + end + @values[index] = value + end + end + end + clazz + end + end + end.new + private_constant :FACTORY + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization.rb new file mode 100644 index 00000000..6d8cf4bd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization.rb @@ -0,0 +1,13 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first + +require 'concurrent/synchronization/object' +require 'concurrent/synchronization/lockable_object' +require 'concurrent/synchronization/condition' +require 'concurrent/synchronization/lock' + +module Concurrent + # @!visibility private + module Synchronization + end +end + diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb new file mode 100644 index 00000000..d9050b31 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb @@ -0,0 +1,102 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first +require 'concurrent/utility/monotonic_time' +require 'concurrent/synchronization/object' + +module Concurrent + module Synchronization + + # @!visibility private + class AbstractLockableObject < Synchronization::Object + + protected + + # @!macro synchronization_object_method_synchronize + # + # @yield runs the block synchronized against this object, + # equivalent of java's `synchronize(this) {}` + # @note can by made public in descendants if required by `public :synchronize` + def synchronize + raise NotImplementedError + end + + # @!macro synchronization_object_method_ns_wait_until + # + # Wait until condition is met or timeout passes, + # protects against spurious wake-ups. + # @param [Numeric, nil] timeout in seconds, `nil` means no timeout + # @yield condition to be met + # @yieldreturn [true, false] + # @return [true, false] if condition met + # @note only to be used inside synchronized block + # @note to provide direct access to this method in a descendant add method + # ``` + # def wait_until(timeout = nil, &condition) + # synchronize { ns_wait_until(timeout, &condition) } + # end + # ``` + def ns_wait_until(timeout = nil, &condition) + if timeout + wait_until = Concurrent.monotonic_time + timeout + loop do + now = Concurrent.monotonic_time + condition_result = condition.call + return condition_result if now >= wait_until || condition_result + ns_wait wait_until - now + end + else + ns_wait timeout until condition.call + true + end + end + + # @!macro synchronization_object_method_ns_wait + # + # Wait until another thread calls #signal or #broadcast, + # spurious wake-ups can happen. + # + # @param [Numeric, nil] timeout in seconds, `nil` means no timeout + # @return [self] + # @note only to be used inside synchronized block + # @note to provide direct access to this method in a descendant add method + # ``` + # def wait(timeout = nil) + # synchronize { ns_wait(timeout) } + # end + # ``` + def ns_wait(timeout = nil) + raise NotImplementedError + end + + # @!macro synchronization_object_method_ns_signal + # + # Signal one waiting thread. + # @return [self] + # @note only to be used inside synchronized block + # @note to provide direct access to this method in a descendant add method + # ``` + # def signal + # synchronize { ns_signal } + # end + # ``` + def ns_signal + raise NotImplementedError + end + + # @!macro synchronization_object_method_ns_broadcast + # + # Broadcast to all waiting threads. + # @return [self] + # @note only to be used inside synchronized block + # @note to provide direct access to this method in a descendant add method + # ``` + # def broadcast + # synchronize { ns_broadcast } + # end + # ``` + def ns_broadcast + raise NotImplementedError + end + + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb new file mode 100644 index 00000000..7cd2decf --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb @@ -0,0 +1,22 @@ +module Concurrent + module Synchronization + + # @!visibility private + # @!macro internal_implementation_note + class AbstractObject + def initialize + # nothing to do + end + + # @!visibility private + # @abstract + def full_memory_barrier + raise NotImplementedError + end + + def self.attr_volatile(*names) + raise NotImplementedError + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb new file mode 100644 index 00000000..28816c51 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb @@ -0,0 +1,171 @@ +module Concurrent + module Synchronization + + # @!visibility private + # @!macro internal_implementation_note + module AbstractStruct + + # @!visibility private + def initialize(*values) + super() + ns_initialize(*values) + end + + # @!macro struct_length + # + # Returns the number of struct members. + # + # @return [Fixnum] the number of struct members + def length + self.class::MEMBERS.length + end + alias_method :size, :length + + # @!macro struct_members + # + # Returns the struct members as an array of symbols. + # + # @return [Array] the struct members as an array of symbols + def members + self.class::MEMBERS.dup + end + + protected + + # @!macro struct_values + # + # @!visibility private + def ns_values + @values.dup + end + + # @!macro struct_values_at + # + # @!visibility private + def ns_values_at(indexes) + @values.values_at(*indexes) + end + + # @!macro struct_to_h + # + # @!visibility private + def ns_to_h + length.times.reduce({}){|memo, i| memo[self.class::MEMBERS[i]] = @values[i]; memo} + end + + # @!macro struct_get + # + # @!visibility private + def ns_get(member) + if member.is_a? Integer + if member >= @values.length + raise IndexError.new("offset #{member} too large for struct(size:#{@values.length})") + end + @values[member] + else + send(member) + end + rescue NoMethodError + raise NameError.new("no member '#{member}' in struct") + end + + # @!macro struct_equality + # + # @!visibility private + def ns_equality(other) + self.class == other.class && self.values == other.values + end + + # @!macro struct_each + # + # @!visibility private + def ns_each + values.each{|value| yield value } + end + + # @!macro struct_each_pair + # + # @!visibility private + def ns_each_pair + @values.length.times do |index| + yield self.class::MEMBERS[index], @values[index] + end + end + + # @!macro struct_select + # + # @!visibility private + def ns_select + values.select{|value| yield value } + end + + # @!macro struct_inspect + # + # @!visibility private + def ns_inspect + struct = pr_underscore(self.class.ancestors[1]) + clazz = ((self.class.to_s =~ /^#" + end + + # @!macro struct_merge + # + # @!visibility private + def ns_merge(other, &block) + self.class.new(*self.to_h.merge(other, &block).values) + end + + # @!visibility private + def ns_initialize_copy + @values = @values.map do |val| + begin + val.clone + rescue TypeError + val + end + end + end + + # @!visibility private + def pr_underscore(clazz) + word = clazz.to_s.dup # dup string to workaround JRuby 9.2.0.0 bug https://github.com/jruby/jruby/issues/5229 + word.gsub!(/::/, '/') + word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') + word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') + word.tr!("-", "_") + word.downcase! + word + end + + # @!visibility private + def self.define_struct_class(parent, base, name, members, &block) + clazz = Class.new(base || Object) do + include parent + self.const_set(:MEMBERS, members.collect{|member| member.to_s.to_sym}.freeze) + def ns_initialize(*values) + raise ArgumentError.new('struct size differs') if values.length > length + @values = values.fill(nil, values.length..length-1) + end + end + unless name.nil? + begin + parent.send :remove_const, name if parent.const_defined?(name, false) + parent.const_set(name, clazz) + clazz + rescue NameError + raise NameError.new("identifier #{name} needs to be constant") + end + end + members.each_with_index do |member, index| + clazz.send :remove_method, member if clazz.instance_methods(false).include? member + clazz.send(:define_method, member) do + @values[index] + end + end + clazz.class_exec(&block) unless block.nil? + clazz.singleton_class.send :alias_method, :[], :new + clazz + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/condition.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/condition.rb new file mode 100644 index 00000000..5daa68be --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/condition.rb @@ -0,0 +1,62 @@ +require 'concurrent/synchronization/lockable_object' + +module Concurrent + module Synchronization + + # @!visibility private + # TODO (pitr-ch 04-Dec-2016): should be in edge + class Condition < LockableObject + safe_initialization! + + # TODO (pitr 12-Sep-2015): locks two objects, improve + # TODO (pitr 26-Sep-2015): study + # http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8-b132/java/util/concurrent/locks/AbstractQueuedSynchronizer.java#AbstractQueuedSynchronizer.Node + + singleton_class.send :alias_method, :private_new, :new + private_class_method :new + + def initialize(lock) + super() + @Lock = lock + end + + def wait(timeout = nil) + @Lock.synchronize { ns_wait(timeout) } + end + + def ns_wait(timeout = nil) + synchronize { super(timeout) } + end + + def wait_until(timeout = nil, &condition) + @Lock.synchronize { ns_wait_until(timeout, &condition) } + end + + def ns_wait_until(timeout = nil, &condition) + synchronize { super(timeout, &condition) } + end + + def signal + @Lock.synchronize { ns_signal } + end + + def ns_signal + synchronize { super } + end + + def broadcast + @Lock.synchronize { ns_broadcast } + end + + def ns_broadcast + synchronize { super } + end + end + + class LockableObject < LockableObjectImplementation + def new_condition + Condition.private_new(self) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/full_memory_barrier.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/full_memory_barrier.rb new file mode 100644 index 00000000..139e08d8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/full_memory_barrier.rb @@ -0,0 +1,29 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first + +module Concurrent + module Synchronization + case + when Concurrent.on_cruby? + def self.full_memory_barrier + # relying on undocumented behavior of CRuby, GVL acquire has lock which ensures visibility of ivars + # https://github.com/ruby/ruby/blob/ruby_2_2/thread_pthread.c#L204-L211 + end + + when Concurrent.on_jruby? + require 'concurrent/utility/native_extension_loader' + def self.full_memory_barrier + JRubyAttrVolatile.full_memory_barrier + end + + when Concurrent.on_truffleruby? + def self.full_memory_barrier + TruffleRuby.full_memory_barrier + end + + else + warn 'Possibly unsupported Ruby implementation' + def self.full_memory_barrier + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb new file mode 100644 index 00000000..76930461 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb @@ -0,0 +1,15 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first + +module Concurrent + module Synchronization + + if Concurrent.on_jruby? + + # @!visibility private + # @!macro internal_implementation_note + class JRubyLockableObject < AbstractLockableObject + + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/lock.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/lock.rb new file mode 100644 index 00000000..f90e0b5f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/lock.rb @@ -0,0 +1,38 @@ +require 'concurrent/synchronization/lockable_object' + +module Concurrent + module Synchronization + + # @!visibility private + # TODO (pitr-ch 04-Dec-2016): should be in edge + class Lock < LockableObject + # TODO use JavaReentrantLock on JRuby + + public :synchronize + + def wait(timeout = nil) + synchronize { ns_wait(timeout) } + end + + public :ns_wait + + def wait_until(timeout = nil, &condition) + synchronize { ns_wait_until(timeout, &condition) } + end + + public :ns_wait_until + + def signal + synchronize { ns_signal } + end + + public :ns_signal + + def broadcast + synchronize { ns_broadcast } + end + + public :ns_broadcast + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb new file mode 100644 index 00000000..08d2ff66 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb @@ -0,0 +1,75 @@ +require 'concurrent/utility/engine' +require 'concurrent/synchronization/abstract_lockable_object' +require 'concurrent/synchronization/mutex_lockable_object' +require 'concurrent/synchronization/jruby_lockable_object' + +module Concurrent + module Synchronization + + # @!visibility private + # @!macro internal_implementation_note + LockableObjectImplementation = case + when Concurrent.on_cruby? + MutexLockableObject + when Concurrent.on_jruby? + JRubyLockableObject + when Concurrent.on_truffleruby? + MutexLockableObject + else + warn 'Possibly unsupported Ruby implementation' + MonitorLockableObject + end + private_constant :LockableObjectImplementation + + # Safe synchronization under any Ruby implementation. + # It provides methods like {#synchronize}, {#wait}, {#signal} and {#broadcast}. + # Provides a single layer which can improve its implementation over time without changes needed to + # the classes using it. Use {Synchronization::Object} not this abstract class. + # + # @note this object does not support usage together with + # [`Thread#wakeup`](http://ruby-doc.org/core/Thread.html#method-i-wakeup) + # and [`Thread#raise`](http://ruby-doc.org/core/Thread.html#method-i-raise). + # `Thread#sleep` and `Thread#wakeup` will work as expected but mixing `Synchronization::Object#wait` and + # `Thread#wakeup` will not work on all platforms. + # + # @see Event implementation as an example of this class use + # + # @example simple + # class AnClass < Synchronization::Object + # def initialize + # super + # synchronize { @value = 'asd' } + # end + # + # def value + # synchronize { @value } + # end + # end + # + # @!visibility private + class LockableObject < LockableObjectImplementation + + # TODO (pitr 12-Sep-2015): make private for c-r, prohibit subclassing + # TODO (pitr 12-Sep-2015): we inherit too much ourselves :/ + + # @!method initialize(*args, &block) + # @!macro synchronization_object_method_initialize + + # @!method synchronize + # @!macro synchronization_object_method_synchronize + + # @!method wait_until(timeout = nil, &condition) + # @!macro synchronization_object_method_ns_wait_until + + # @!method wait(timeout = nil) + # @!macro synchronization_object_method_ns_wait + + # @!method signal + # @!macro synchronization_object_method_ns_signal + + # @!method broadcast + # @!macro synchronization_object_method_ns_broadcast + + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb new file mode 100644 index 00000000..acc9745a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb @@ -0,0 +1,89 @@ +require 'concurrent/synchronization/abstract_lockable_object' + +module Concurrent + module Synchronization + + # @!visibility private + # @!macro internal_implementation_note + module ConditionSignalling + protected + + def ns_signal + @__Condition__.signal + self + end + + def ns_broadcast + @__Condition__.broadcast + self + end + end + + + # @!visibility private + # @!macro internal_implementation_note + class MutexLockableObject < AbstractLockableObject + include ConditionSignalling + + safe_initialization! + + def initialize + super() + @__Lock__ = ::Mutex.new + @__Condition__ = ::ConditionVariable.new + end + + def initialize_copy(other) + super + @__Lock__ = ::Mutex.new + @__Condition__ = ::ConditionVariable.new + end + + protected + + def synchronize + if @__Lock__.owned? + yield + else + @__Lock__.synchronize { yield } + end + end + + def ns_wait(timeout = nil) + @__Condition__.wait @__Lock__, timeout + self + end + end + + # @!visibility private + # @!macro internal_implementation_note + class MonitorLockableObject < AbstractLockableObject + include ConditionSignalling + + safe_initialization! + + def initialize + super() + @__Lock__ = ::Monitor.new + @__Condition__ = @__Lock__.new_cond + end + + def initialize_copy(other) + super + @__Lock__ = ::Monitor.new + @__Condition__ = @__Lock__.new_cond + end + + protected + + def synchronize # TODO may be a problem with lock.synchronize { lock.wait } + @__Lock__.synchronize { yield } + end + + def ns_wait(timeout = nil) + @__Condition__.wait timeout + self + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/object.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/object.rb new file mode 100644 index 00000000..59219070 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/object.rb @@ -0,0 +1,151 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first + +require 'concurrent/synchronization/safe_initialization' +require 'concurrent/synchronization/volatile' +require 'concurrent/atomic/atomic_reference' + +module Concurrent + module Synchronization + + # Abstract object providing final, volatile, ans CAS extensions to build other concurrent abstractions. + # - final instance variables see {Object.safe_initialization!} + # - volatile instance variables see {Object.attr_volatile} + # - volatile instance variables see {Object.attr_atomic} + # @!visibility private + class Object < AbstractObject + include Volatile + + # TODO make it a module if possible + + # @!method self.attr_volatile(*names) + # Creates methods for reading and writing (as `attr_accessor` does) to a instance variable with + # volatile (Java) semantic. The instance variable should be accessed only through generated methods. + # + # @param [::Array] names of the instance variables to be volatile + # @return [::Array] names of defined method names + + # Has to be called by children. + def initialize + super + __initialize_atomic_fields__ + end + + def self.safe_initialization! + extend SafeInitialization unless safe_initialization? + end + + def self.safe_initialization? + self.singleton_class < SafeInitialization + end + + # For testing purposes, quite slow. Injects assert code to new method which will raise if class instance contains + # any instance variables with CamelCase names and isn't {.safe_initialization?}. + # @raise when offend found + # @return [true] + def self.ensure_safe_initialization_when_final_fields_are_present + Object.class_eval do + def self.new(*args, &block) + object = super(*args, &block) + ensure + has_final_field = object.instance_variables.any? { |v| v.to_s =~ /^@[A-Z]/ } + if has_final_field && !safe_initialization? + raise "there was an instance of #{object.class} with final field but not marked with safe_initialization!" + end + end + end + true + end + + # Creates methods for reading and writing to a instance variable with + # volatile (Java) semantic as {.attr_volatile} does. + # The instance variable should be accessed only through generated methods. + # This method generates following methods: `value`, `value=(new_value) #=> new_value`, + # `swap_value(new_value) #=> old_value`, + # `compare_and_set_value(expected, value) #=> true || false`, `update_value(&block)`. + # @param [::Array] names of the instance variables to be volatile with CAS. + # @return [::Array] names of defined method names. + # @!macro attr_atomic + # @!method $1 + # @return [Object] The $1. + # @!method $1=(new_$1) + # Set the $1. + # @return [Object] new_$1. + # @!method swap_$1(new_$1) + # Set the $1 to new_$1 and return the old $1. + # @return [Object] old $1 + # @!method compare_and_set_$1(expected_$1, new_$1) + # Sets the $1 to new_$1 if the current $1 is expected_$1 + # @return [true, false] + # @!method update_$1(&block) + # Updates the $1 using the block. + # @yield [Object] Calculate a new $1 using given (old) $1 + # @yieldparam [Object] old $1 + # @return [Object] new $1 + def self.attr_atomic(*names) + @__atomic_fields__ ||= [] + @__atomic_fields__ += names + safe_initialization! + define_initialize_atomic_fields + + names.each do |name| + ivar = :"@Atomic#{name.to_s.gsub(/(?:^|_)(.)/) { $1.upcase }}" + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{name} + #{ivar}.get + end + + def #{name}=(value) + #{ivar}.set value + end + + def swap_#{name}(value) + #{ivar}.swap value + end + + def compare_and_set_#{name}(expected, value) + #{ivar}.compare_and_set expected, value + end + + def update_#{name}(&block) + #{ivar}.update(&block) + end + RUBY + end + names.flat_map { |n| [n, :"#{n}=", :"swap_#{n}", :"compare_and_set_#{n}", :"update_#{n}"] } + end + + # @param [true, false] inherited should inherited volatile with CAS fields be returned? + # @return [::Array] Returns defined volatile with CAS fields on this class. + def self.atomic_attributes(inherited = true) + @__atomic_fields__ ||= [] + ((superclass.atomic_attributes if superclass.respond_to?(:atomic_attributes) && inherited) || []) + @__atomic_fields__ + end + + # @return [true, false] is the attribute with name atomic? + def self.atomic_attribute?(name) + atomic_attributes.include? name + end + + private + + def self.define_initialize_atomic_fields + assignments = @__atomic_fields__.map do |name| + "@Atomic#{name.to_s.gsub(/(?:^|_)(.)/) { $1.upcase }} = Concurrent::AtomicReference.new(nil)" + end.join("\n") + + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def __initialize_atomic_fields__ + super + #{assignments} + end + RUBY + end + + private_class_method :define_initialize_atomic_fields + + def __initialize_atomic_fields__ + end + + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/safe_initialization.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/safe_initialization.rb new file mode 100644 index 00000000..f785e352 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/safe_initialization.rb @@ -0,0 +1,36 @@ +require 'concurrent/synchronization/full_memory_barrier' + +module Concurrent + module Synchronization + + # @!visibility private + # @!macro internal_implementation_note + # + # By extending this module, a class and all its children are marked to be constructed safely. Meaning that + # all writes (ivar initializations) are made visible to all readers of newly constructed object. It ensures + # same behaviour as Java's final fields. + # + # Due to using Kernel#extend, the module is not included again if already present in the ancestors, + # which avoids extra overhead. + # + # @example + # class AClass < Concurrent::Synchronization::Object + # extend Concurrent::Synchronization::SafeInitialization + # + # def initialize + # @AFinalValue = 'value' # published safely, #foo will never return nil + # end + # + # def foo + # @AFinalValue + # end + # end + module SafeInitialization + def new(*args, &block) + super(*args, &block) + ensure + Concurrent::Synchronization.full_memory_barrier + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/volatile.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/volatile.rb new file mode 100644 index 00000000..46e8ba6a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/synchronization/volatile.rb @@ -0,0 +1,101 @@ +require 'concurrent/utility/native_extension_loader' # load native parts first +require 'concurrent/utility/engine' +require 'concurrent/synchronization/full_memory_barrier' + +module Concurrent + module Synchronization + + # Volatile adds the attr_volatile class method when included. + # + # @example + # class Foo + # include Concurrent::Synchronization::Volatile + # + # attr_volatile :bar + # + # def initialize + # self.bar = 1 + # end + # end + # + # foo = Foo.new + # foo.bar + # => 1 + # foo.bar = 2 + # => 2 + # + # @!visibility private + module Volatile + def self.included(base) + base.extend(ClassMethods) + end + + def full_memory_barrier + Synchronization.full_memory_barrier + end + + module ClassMethods + if Concurrent.on_cruby? + def attr_volatile(*names) + names.each do |name| + ivar = :"@volatile_#{name}" + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{name} + #{ivar} + end + + def #{name}=(value) + #{ivar} = value + end + RUBY + end + names.map { |n| [n, :"#{n}="] }.flatten + end + + elsif Concurrent.on_jruby? + def attr_volatile(*names) + names.each do |name| + ivar = :"@volatile_#{name}" + + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{name} + ::Concurrent::Synchronization::JRubyAttrVolatile.instance_variable_get_volatile(self, :#{ivar}) + end + + def #{name}=(value) + ::Concurrent::Synchronization::JRubyAttrVolatile.instance_variable_set_volatile(self, :#{ivar}, value) + end + RUBY + + end + names.map { |n| [n, :"#{n}="] }.flatten + end + + else + warn 'Possibly unsupported Ruby implementation' unless Concurrent.on_truffleruby? + + def attr_volatile(*names) + names.each do |name| + ivar = :"@volatile_#{name}" + + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{name} + ::Concurrent::Synchronization.full_memory_barrier + #{ivar} + end + + def #{name}=(value) + #{ivar} = value + ::Concurrent::Synchronization.full_memory_barrier + end + RUBY + end + + names.map { |n| [n, :"#{n}="] }.flatten + end + end + end + + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb new file mode 100644 index 00000000..019d8438 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb @@ -0,0 +1,47 @@ +require 'delegate' +require 'monitor' + +module Concurrent + # This class provides a trivial way to synchronize all calls to a given object + # by wrapping it with a `Delegator` that performs `Monitor#enter/exit` calls + # around the delegated `#send`. Example: + # + # array = [] # not thread-safe on many impls + # array = SynchronizedDelegator.new([]) # thread-safe + # + # A simple `Monitor` provides a very coarse-grained way to synchronize a given + # object, in that it will cause synchronization for methods that have no need + # for it, but this is a trivial way to get thread-safety where none may exist + # currently on some implementations. + # + # This class is currently being considered for inclusion into stdlib, via + # https://bugs.ruby-lang.org/issues/8556 + # + # @!visibility private + class SynchronizedDelegator < SimpleDelegator + def setup + @old_abort = Thread.abort_on_exception + Thread.abort_on_exception = true + end + + def teardown + Thread.abort_on_exception = @old_abort + end + + def initialize(obj) + __setobj__(obj) + @monitor = Monitor.new + end + + def method_missing(method, *args, &block) + monitor = @monitor + begin + monitor.enter + super + ensure + monitor.exit + end + end + + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util.rb new file mode 100644 index 00000000..c67084a2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util.rb @@ -0,0 +1,16 @@ +module Concurrent + + # @!visibility private + module ThreadSafe + + # @!visibility private + module Util + + # TODO (pitr-ch 15-Oct-2016): migrate to Utility::NativeInteger + FIXNUM_BIT_SIZE = (0.size * 8) - 2 + MAX_INT = (2 ** FIXNUM_BIT_SIZE) - 1 + # TODO (pitr-ch 15-Oct-2016): migrate to Utility::ProcessorCounter + CPU_COUNT = 16 # is there a way to determine this? + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb new file mode 100644 index 00000000..852b403b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb @@ -0,0 +1,74 @@ +require 'concurrent/thread_safe/util' +require 'concurrent/thread_safe/util/striped64' + +module Concurrent + + # @!visibility private + module ThreadSafe + + # @!visibility private + module Util + + # A Ruby port of the Doug Lea's jsr166e.LongAdder class version 1.8 + # available in public domain. + # + # Original source code available here: + # http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/LongAdder.java?revision=1.8 + # + # One or more variables that together maintain an initially zero + # sum. When updates (method +add+) are contended across threads, + # the set of variables may grow dynamically to reduce contention. + # Method +sum+ returns the current total combined across the + # variables maintaining the sum. + # + # This class is usually preferable to single +Atomic+ reference when + # multiple threads update a common sum that is used for purposes such + # as collecting statistics, not for fine-grained synchronization + # control. Under low update contention, the two classes have similar + # characteristics. But under high contention, expected throughput of + # this class is significantly higher, at the expense of higher space + # consumption. + # + # @!visibility private + class Adder < Striped64 + # Adds the given value. + def add(x) + if (current_cells = cells) || !cas_base_computed {|current_base| current_base + x} + was_uncontended = true + hash = hash_code + unless current_cells && (cell = current_cells.volatile_get_by_hash(hash)) && (was_uncontended = cell.cas_computed {|current_value| current_value + x}) + retry_update(x, hash, was_uncontended) {|current_value| current_value + x} + end + end + end + + def increment + add(1) + end + + def decrement + add(-1) + end + + # Returns the current sum. The returned value is _NOT_ an + # atomic snapshot: Invocation in the absence of concurrent + # updates returns an accurate result, but concurrent updates that + # occur while the sum is being calculated might not be + # incorporated. + def sum + x = base + if current_cells = cells + current_cells.each do |cell| + x += cell.value if cell + end + end + x + end + + def reset + internal_reset(0) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb new file mode 100644 index 00000000..01eb98f4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb @@ -0,0 +1,52 @@ +require 'concurrent/thread_safe/util' +require 'concurrent/utility/engine' + +# Shim for TruffleRuby.synchronized +if Concurrent.on_truffleruby? && !TruffleRuby.respond_to?(:synchronized) + module TruffleRuby + def self.synchronized(object, &block) + Truffle::System.synchronized(object, &block) + end + end +end + +module Concurrent + module ThreadSafe + module Util + def self.make_synchronized_on_cruby(klass) + klass.class_eval do + def initialize(*args, &block) + @_monitor = Monitor.new + super + end + + def initialize_copy(other) + # make sure a copy is not sharing a monitor with the original object! + @_monitor = Monitor.new + super + end + end + + klass.superclass.instance_methods(false).each do |method| + klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{method}(*args) + monitor = @_monitor + monitor or raise("BUG: Internal monitor was not properly initialized. Please report this to the concurrent-ruby developers.") + monitor.synchronize { super } + end + RUBY + end + end + + def self.make_synchronized_on_truffleruby(klass) + klass.superclass.instance_methods(false).each do |method| + klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{method}(*args, &block) + TruffleRuby.synchronized(self) { super(*args, &block) } + end + RUBY + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util/power_of_two_tuple.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util/power_of_two_tuple.rb new file mode 100644 index 00000000..b54be39c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util/power_of_two_tuple.rb @@ -0,0 +1,38 @@ +require 'concurrent/thread_safe/util' +require 'concurrent/tuple' + +module Concurrent + + # @!visibility private + module ThreadSafe + + # @!visibility private + module Util + + # @!visibility private + class PowerOfTwoTuple < Concurrent::Tuple + + def initialize(size) + raise ArgumentError, "size must be a power of 2 (#{size.inspect} provided)" unless size > 0 && size & (size - 1) == 0 + super(size) + end + + def hash_to_index(hash) + (size - 1) & hash + end + + def volatile_get_by_hash(hash) + volatile_get(hash_to_index(hash)) + end + + def volatile_set_by_hash(hash, value) + volatile_set(hash_to_index(hash), value) + end + + def next_in_size_table + self.class.new(size << 1) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util/striped64.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util/striped64.rb new file mode 100644 index 00000000..4169c3d3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util/striped64.rb @@ -0,0 +1,246 @@ +require 'concurrent/thread_safe/util' +require 'concurrent/thread_safe/util/power_of_two_tuple' +require 'concurrent/thread_safe/util/volatile' +require 'concurrent/thread_safe/util/xor_shift_random' + +module Concurrent + + # @!visibility private + module ThreadSafe + + # @!visibility private + module Util + + # A Ruby port of the Doug Lea's jsr166e.Striped64 class version 1.6 + # available in public domain. + # + # Original source code available here: + # http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/Striped64.java?revision=1.6 + # + # Class holding common representation and mechanics for classes supporting + # dynamic striping on 64bit values. + # + # This class maintains a lazily-initialized table of atomically updated + # variables, plus an extra +base+ field. The table size is a power of two. + # Indexing uses masked per-thread hash codes. Nearly all methods on this + # class are private, accessed directly by subclasses. + # + # Table entries are of class +Cell+; a variant of AtomicLong padded to + # reduce cache contention on most processors. Padding is overkill for most + # Atomics because they are usually irregularly scattered in memory and thus + # don't interfere much with each other. But Atomic objects residing in + # arrays will tend to be placed adjacent to each other, and so will most + # often share cache lines (with a huge negative performance impact) without + # this precaution. + # + # In part because +Cell+s are relatively large, we avoid creating them until + # they are needed. When there is no contention, all updates are made to the + # +base+ field. Upon first contention (a failed CAS on +base+ update), the + # table is initialized to size 2. The table size is doubled upon further + # contention until reaching the nearest power of two greater than or equal + # to the number of CPUS. Table slots remain empty (+nil+) until they are + # needed. + # + # A single spinlock (+busy+) is used for initializing and resizing the + # table, as well as populating slots with new +Cell+s. There is no need for + # a blocking lock: When the lock is not available, threads try other slots + # (or the base). During these retries, there is increased contention and + # reduced locality, which is still better than alternatives. + # + # Per-thread hash codes are initialized to random values. Contention and/or + # table collisions are indicated by failed CASes when performing an update + # operation (see method +retry_update+). Upon a collision, if the table size + # is less than the capacity, it is doubled in size unless some other thread + # holds the lock. If a hashed slot is empty, and lock is available, a new + # +Cell+ is created. Otherwise, if the slot exists, a CAS is tried. Retries + # proceed by "double hashing", using a secondary hash (XorShift) to try to + # find a free slot. + # + # The table size is capped because, when there are more threads than CPUs, + # supposing that each thread were bound to a CPU, there would exist a + # perfect hash function mapping threads to slots that eliminates collisions. + # When we reach capacity, we search for this mapping by randomly varying the + # hash codes of colliding threads. Because search is random, and collisions + # only become known via CAS failures, convergence can be slow, and because + # threads are typically not bound to CPUS forever, may not occur at all. + # However, despite these limitations, observed contention rates are + # typically low in these cases. + # + # It is possible for a +Cell+ to become unused when threads that once hashed + # to it terminate, as well as in the case where doubling the table causes no + # thread to hash to it under expanded mask. We do not try to detect or + # remove such cells, under the assumption that for long-running instances, + # observed contention levels will recur, so the cells will eventually be + # needed again; and for short-lived ones, it does not matter. + # + # @!visibility private + class Striped64 + + # Padded variant of AtomicLong supporting only raw accesses plus CAS. + # The +value+ field is placed between pads, hoping that the JVM doesn't + # reorder them. + # + # Optimisation note: It would be possible to use a release-only + # form of CAS here, if it were provided. + # + # @!visibility private + class Cell < Concurrent::AtomicReference + + alias_method :cas, :compare_and_set + + def cas_computed + cas(current_value = value, yield(current_value)) + end + + # @!visibility private + def self.padding + # TODO: this only adds padding after the :value slot, need to find a way to add padding before the slot + # TODO (pitr-ch 28-Jul-2018): the padding instance vars may not be created + # hide from yardoc in a method + attr_reader :padding_0, :padding_1, :padding_2, :padding_3, :padding_4, :padding_5, :padding_6, :padding_7, :padding_8, :padding_9, :padding_10, :padding_11 + end + padding + end + + extend Volatile + attr_volatile :cells, # Table of cells. When non-null, size is a power of 2. + :base, # Base value, used mainly when there is no contention, but also as a fallback during table initialization races. Updated via CAS. + :busy # Spinlock (locked via CAS) used when resizing and/or creating Cells. + + alias_method :busy?, :busy + + def initialize + super() + self.busy = false + self.base = 0 + end + + # Handles cases of updates involving initialization, resizing, + # creating new Cells, and/or contention. See above for + # explanation. This method suffers the usual non-modularity + # problems of optimistic retry code, relying on rechecked sets of + # reads. + # + # Arguments: + # [+x+] + # the value + # [+hash_code+] + # hash code used + # [+x+] + # false if CAS failed before call + def retry_update(x, hash_code, was_uncontended) # :yields: current_value + hash = hash_code + collided = false # True if last slot nonempty + while true + if current_cells = cells + if !(cell = current_cells.volatile_get_by_hash(hash)) + if busy? + collided = false + else # Try to attach new Cell + if try_to_install_new_cell(Cell.new(x), hash) # Optimistically create and try to insert new cell + break + else + redo # Slot is now non-empty + end + end + elsif !was_uncontended # CAS already known to fail + was_uncontended = true # Continue after rehash + elsif cell.cas_computed {|current_value| yield current_value} + break + elsif current_cells.size >= CPU_COUNT || cells != current_cells # At max size or stale + collided = false + elsif collided && expand_table_unless_stale(current_cells) + collided = false + redo # Retry with expanded table + else + collided = true + end + hash = XorShiftRandom.xorshift(hash) + + elsif try_initialize_cells(x, hash) || cas_base_computed {|current_base| yield current_base} + break + end + end + self.hash_code = hash + end + + private + # Static per-thread hash code key. Shared across all instances to + # reduce Thread locals pollution and because adjustments due to + # collisions in one table are likely to be appropriate for + # others. + THREAD_LOCAL_KEY = "#{name}.hash_code".to_sym + + # A thread-local hash code accessor. The code is initially + # random, but may be set to a different value upon collisions. + def hash_code + Thread.current[THREAD_LOCAL_KEY] ||= XorShiftRandom.get + end + + def hash_code=(hash) + Thread.current[THREAD_LOCAL_KEY] = hash + end + + # Sets base and all +cells+ to the given value. + def internal_reset(initial_value) + current_cells = cells + self.base = initial_value + if current_cells + current_cells.each do |cell| + cell.value = initial_value if cell + end + end + end + + def cas_base_computed + cas_base(current_base = base, yield(current_base)) + end + + def free? + !busy? + end + + def try_initialize_cells(x, hash) + if free? && !cells + try_in_busy do + unless cells # Recheck under lock + new_cells = PowerOfTwoTuple.new(2) + new_cells.volatile_set_by_hash(hash, Cell.new(x)) + self.cells = new_cells + end + end + end + end + + def expand_table_unless_stale(current_cells) + try_in_busy do + if current_cells == cells # Recheck under lock + new_cells = current_cells.next_in_size_table + current_cells.each_with_index {|x, i| new_cells.volatile_set(i, x)} + self.cells = new_cells + end + end + end + + def try_to_install_new_cell(new_cell, hash) + try_in_busy do + # Recheck under lock + if (current_cells = cells) && !current_cells.volatile_get(i = current_cells.hash_to_index(hash)) + current_cells.volatile_set(i, new_cell) + end + end + end + + def try_in_busy + if cas_busy(false, true) + begin + yield + ensure + self.busy = false + end + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util/volatile.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util/volatile.rb new file mode 100644 index 00000000..cdac2a39 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util/volatile.rb @@ -0,0 +1,75 @@ +require 'concurrent/thread_safe/util' + +module Concurrent + + # @!visibility private + module ThreadSafe + + # @!visibility private + module Util + + # @!visibility private + module Volatile + + # Provides +volatile+ (in the JVM's sense) attribute accessors implemented + # atop of +Concurrent::AtomicReference+. + # + # Usage: + # class Foo + # extend Concurrent::ThreadSafe::Util::Volatile + # attr_volatile :foo, :bar + # + # def initialize(bar) + # super() # must super() into parent initializers before using the volatile attribute accessors + # self.bar = bar + # end + # + # def hello + # my_foo = foo # volatile read + # self.foo = 1 # volatile write + # cas_foo(1, 2) # => true | a strong CAS + # end + # end + def attr_volatile(*attr_names) + return if attr_names.empty? + include(Module.new do + atomic_ref_setup = attr_names.map {|attr_name| "@__#{attr_name} = Concurrent::AtomicReference.new"} + initialize_copy_setup = attr_names.zip(atomic_ref_setup).map do |attr_name, ref_setup| + "#{ref_setup}(other.instance_variable_get(:@__#{attr_name}).get)" + end + class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def initialize(*) + super + #{atomic_ref_setup.join('; ')} + end + + def initialize_copy(other) + super + #{initialize_copy_setup.join('; ')} + end + RUBY_EVAL + + attr_names.each do |attr_name| + class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def #{attr_name} + @__#{attr_name}.get + end + + def #{attr_name}=(value) + @__#{attr_name}.set(value) + end + + def compare_and_set_#{attr_name}(old_value, new_value) + @__#{attr_name}.compare_and_set(old_value, new_value) + end + RUBY_EVAL + + alias_method :"cas_#{attr_name}", :"compare_and_set_#{attr_name}" + alias_method :"lazy_set_#{attr_name}", :"#{attr_name}=" + end + end) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb new file mode 100644 index 00000000..c231d182 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb @@ -0,0 +1,50 @@ +require 'concurrent/thread_safe/util' + +module Concurrent + + # @!visibility private + module ThreadSafe + + # @!visibility private + module Util + + # A xorshift random number (positive +Fixnum+s) generator, provides + # reasonably cheap way to generate thread local random numbers without + # contending for the global +Kernel.rand+. + # + # Usage: + # x = XorShiftRandom.get # uses Kernel.rand to generate an initial seed + # while true + # if (x = XorShiftRandom.xorshift).odd? # thread-locally generate a next random number + # do_something_at_random + # end + # end + module XorShiftRandom + extend self + MAX_XOR_SHIFTABLE_INT = MAX_INT - 1 + + # Generates an initial non-zero positive +Fixnum+ via +Kernel.rand+. + def get + Kernel.rand(MAX_XOR_SHIFTABLE_INT) + 1 # 0 can't be xorshifted + end + + # xorshift based on: http://www.jstatsoft.org/v08/i14/paper + if 0.size == 4 + # using the "yˆ=y>>a; yˆ=y<>c;" transform with the (a,b,c) tuple with values (3,1,14) to minimise Bignum overflows + def xorshift(x) + x ^= x >> 3 + x ^= (x << 1) & MAX_INT # cut-off Bignum overflow + x ^= x >> 14 + end + else + # using the "yˆ=y>>a; yˆ=y<>c;" transform with the (a,b,c) tuple with values (1,1,54) to minimise Bignum overflows + def xorshift(x) + x ^= x >> 1 + x ^= (x << 1) & MAX_INT # cut-off Bignum overflow + x ^= x >> 54 + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/timer_task.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/timer_task.rb new file mode 100644 index 00000000..dd2037f6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/timer_task.rb @@ -0,0 +1,361 @@ +require 'concurrent/collection/copy_on_notify_observer_set' +require 'concurrent/concern/dereferenceable' +require 'concurrent/concern/observable' +require 'concurrent/atomic/atomic_boolean' +require 'concurrent/executor/executor_service' +require 'concurrent/executor/ruby_executor_service' +require 'concurrent/executor/safe_task_executor' +require 'concurrent/scheduled_task' + +module Concurrent + + # A very common concurrency pattern is to run a thread that performs a task at + # regular intervals. The thread that performs the task sleeps for the given + # interval then wakes up and performs the task. Lather, rinse, repeat... This + # pattern causes two problems. First, it is difficult to test the business + # logic of the task because the task itself is tightly coupled with the + # concurrency logic. Second, an exception raised while performing the task can + # cause the entire thread to abend. In a long-running application where the + # task thread is intended to run for days/weeks/years a crashed task thread + # can pose a significant problem. `TimerTask` alleviates both problems. + # + # When a `TimerTask` is launched it starts a thread for monitoring the + # execution interval. The `TimerTask` thread does not perform the task, + # however. Instead, the TimerTask launches the task on a separate thread. + # Should the task experience an unrecoverable crash only the task thread will + # crash. This makes the `TimerTask` very fault tolerant. Additionally, the + # `TimerTask` thread can respond to the success or failure of the task, + # performing logging or ancillary operations. + # + # One other advantage of `TimerTask` is that it forces the business logic to + # be completely decoupled from the concurrency logic. The business logic can + # be tested separately then passed to the `TimerTask` for scheduling and + # running. + # + # A `TimerTask` supports two different types of interval calculations. + # A fixed delay will always wait the same amount of time between the + # completion of one task and the start of the next. A fixed rate will + # attempt to maintain a constant rate of execution regardless of the + # duration of the task. For example, if a fixed rate task is scheduled + # to run every 60 seconds but the task itself takes 10 seconds to + # complete, the next task will be scheduled to run 50 seconds after + # the start of the previous task. If the task takes 70 seconds to + # complete, the next task will be start immediately after the previous + # task completes. Tasks will not be executed concurrently. + # + # In some cases it may be necessary for a `TimerTask` to affect its own + # execution cycle. To facilitate this, a reference to the TimerTask instance + # is passed as an argument to the provided block every time the task is + # executed. + # + # The `TimerTask` class includes the `Dereferenceable` mixin module so the + # result of the last execution is always available via the `#value` method. + # Dereferencing options can be passed to the `TimerTask` during construction or + # at any later time using the `#set_deref_options` method. + # + # `TimerTask` supports notification through the Ruby standard library + # {http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html + # Observable} module. On execution the `TimerTask` will notify the observers + # with three arguments: time of execution, the result of the block (or nil on + # failure), and any raised exceptions (or nil on success). + # + # @!macro copy_options + # + # @example Basic usage + # task = Concurrent::TimerTask.new{ puts 'Boom!' } + # task.execute + # + # task.execution_interval #=> 60 (default) + # + # # wait 60 seconds... + # #=> 'Boom!' + # + # task.shutdown #=> true + # + # @example Configuring `:execution_interval` + # task = Concurrent::TimerTask.new(execution_interval: 5) do + # puts 'Boom!' + # end + # + # task.execution_interval #=> 5 + # + # @example Immediate execution with `:run_now` + # task = Concurrent::TimerTask.new(run_now: true){ puts 'Boom!' } + # task.execute + # + # #=> 'Boom!' + # + # @example Configuring `:interval_type` with either :fixed_delay or :fixed_rate, default is :fixed_delay + # task = Concurrent::TimerTask.new(execution_interval: 5, interval_type: :fixed_rate) do + # puts 'Boom!' + # end + # task.interval_type #=> :fixed_rate + # + # @example Last `#value` and `Dereferenceable` mixin + # task = Concurrent::TimerTask.new( + # dup_on_deref: true, + # execution_interval: 5 + # ){ Time.now } + # + # task.execute + # Time.now #=> 2013-11-07 18:06:50 -0500 + # sleep(10) + # task.value #=> 2013-11-07 18:06:55 -0500 + # + # @example Controlling execution from within the block + # timer_task = Concurrent::TimerTask.new(execution_interval: 1) do |task| + # task.execution_interval.to_i.times{ print 'Boom! ' } + # print "\n" + # task.execution_interval += 1 + # if task.execution_interval > 5 + # puts 'Stopping...' + # task.shutdown + # end + # end + # + # timer_task.execute + # #=> Boom! + # #=> Boom! Boom! + # #=> Boom! Boom! Boom! + # #=> Boom! Boom! Boom! Boom! + # #=> Boom! Boom! Boom! Boom! Boom! + # #=> Stopping... + # + # @example Observation + # class TaskObserver + # def update(time, result, ex) + # if result + # print "(#{time}) Execution successfully returned #{result}\n" + # else + # print "(#{time}) Execution failed with error #{ex}\n" + # end + # end + # end + # + # task = Concurrent::TimerTask.new(execution_interval: 1){ 42 } + # task.add_observer(TaskObserver.new) + # task.execute + # sleep 4 + # + # #=> (2013-10-13 19:08:58 -0400) Execution successfully returned 42 + # #=> (2013-10-13 19:08:59 -0400) Execution successfully returned 42 + # #=> (2013-10-13 19:09:00 -0400) Execution successfully returned 42 + # task.shutdown + # + # task = Concurrent::TimerTask.new(execution_interval: 1){ sleep } + # task.add_observer(TaskObserver.new) + # task.execute + # + # #=> (2013-10-13 19:07:25 -0400) Execution timed out + # #=> (2013-10-13 19:07:27 -0400) Execution timed out + # #=> (2013-10-13 19:07:29 -0400) Execution timed out + # task.shutdown + # + # task = Concurrent::TimerTask.new(execution_interval: 1){ raise StandardError } + # task.add_observer(TaskObserver.new) + # task.execute + # + # #=> (2013-10-13 19:09:37 -0400) Execution failed with error StandardError + # #=> (2013-10-13 19:09:38 -0400) Execution failed with error StandardError + # #=> (2013-10-13 19:09:39 -0400) Execution failed with error StandardError + # task.shutdown + # + # @see http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html + # @see http://docs.oracle.com/javase/7/docs/api/java/util/TimerTask.html + class TimerTask < RubyExecutorService + include Concern::Dereferenceable + include Concern::Observable + + # Default `:execution_interval` in seconds. + EXECUTION_INTERVAL = 60 + + # Maintain the interval between the end of one execution and the start of the next execution. + FIXED_DELAY = :fixed_delay + + # Maintain the interval between the start of one execution and the start of the next. + # If execution time exceeds the interval, the next execution will start immediately + # after the previous execution finishes. Executions will not run concurrently. + FIXED_RATE = :fixed_rate + + # Default `:interval_type` + DEFAULT_INTERVAL_TYPE = FIXED_DELAY + + # Create a new TimerTask with the given task and configuration. + # + # @!macro timer_task_initialize + # @param [Hash] opts the options defining task execution. + # @option opts [Float] :execution_interval number of seconds between + # task executions (default: EXECUTION_INTERVAL) + # @option opts [Boolean] :run_now Whether to run the task immediately + # upon instantiation or to wait until the first # execution_interval + # has passed (default: false) + # @options opts [Symbol] :interval_type method to calculate the interval + # between executions, can be either :fixed_rate or :fixed_delay. + # (default: :fixed_delay) + # @option opts [Executor] executor, default is `global_io_executor` + # + # @!macro deref_options + # + # @raise ArgumentError when no block is given. + # + # @yield to the block after :execution_interval seconds have passed since + # the last yield + # @yieldparam task a reference to the `TimerTask` instance so that the + # block can control its own lifecycle. Necessary since `self` will + # refer to the execution context of the block rather than the running + # `TimerTask`. + # + # @return [TimerTask] the new `TimerTask` + def initialize(opts = {}, &task) + raise ArgumentError.new('no block given') unless block_given? + super + set_deref_options opts + end + + # Is the executor running? + # + # @return [Boolean] `true` when running, `false` when shutting down or shutdown + def running? + @running.true? + end + + # Execute a previously created `TimerTask`. + # + # @return [TimerTask] a reference to `self` + # + # @example Instance and execute in separate steps + # task = Concurrent::TimerTask.new(execution_interval: 10){ print "Hello World\n" } + # task.running? #=> false + # task.execute + # task.running? #=> true + # + # @example Instance and execute in one line + # task = Concurrent::TimerTask.new(execution_interval: 10){ print "Hello World\n" }.execute + # task.running? #=> true + def execute + synchronize do + if @running.false? + @running.make_true + schedule_next_task(@run_now ? 0 : @execution_interval) + end + end + self + end + + # Create and execute a new `TimerTask`. + # + # @!macro timer_task_initialize + # + # @example + # task = Concurrent::TimerTask.execute(execution_interval: 10){ print "Hello World\n" } + # task.running? #=> true + def self.execute(opts = {}, &task) + TimerTask.new(opts, &task).execute + end + + # @!attribute [rw] execution_interval + # @return [Fixnum] Number of seconds after the task completes before the + # task is performed again. + def execution_interval + synchronize { @execution_interval } + end + + # @!attribute [rw] execution_interval + # @return [Fixnum] Number of seconds after the task completes before the + # task is performed again. + def execution_interval=(value) + if (value = value.to_f) <= 0.0 + raise ArgumentError.new('must be greater than zero') + else + synchronize { @execution_interval = value } + end + end + + # @!attribute [r] interval_type + # @return [Symbol] method to calculate the interval between executions + attr_reader :interval_type + + # @!attribute [rw] timeout_interval + # @return [Fixnum] Number of seconds the task can run before it is + # considered to have failed. + def timeout_interval + warn 'TimerTask timeouts are now ignored as these were not able to be implemented correctly' + end + + # @!attribute [rw] timeout_interval + # @return [Fixnum] Number of seconds the task can run before it is + # considered to have failed. + def timeout_interval=(value) + warn 'TimerTask timeouts are now ignored as these were not able to be implemented correctly' + end + + private :post, :<< + + private + + def ns_initialize(opts, &task) + set_deref_options(opts) + + self.execution_interval = opts[:execution] || opts[:execution_interval] || EXECUTION_INTERVAL + if opts[:interval_type] && ![FIXED_DELAY, FIXED_RATE].include?(opts[:interval_type]) + raise ArgumentError.new('interval_type must be either :fixed_delay or :fixed_rate') + end + if opts[:timeout] || opts[:timeout_interval] + warn 'TimeTask timeouts are now ignored as these were not able to be implemented correctly' + end + + @run_now = opts[:now] || opts[:run_now] + @interval_type = opts[:interval_type] || DEFAULT_INTERVAL_TYPE + @task = Concurrent::SafeTaskExecutor.new(task) + @executor = opts[:executor] || Concurrent.global_io_executor + @running = Concurrent::AtomicBoolean.new(false) + @value = nil + + self.observers = Collection::CopyOnNotifyObserverSet.new + end + + # @!visibility private + def ns_shutdown_execution + @running.make_false + super + end + + # @!visibility private + def ns_kill_execution + @running.make_false + super + end + + # @!visibility private + def schedule_next_task(interval = execution_interval) + ScheduledTask.execute(interval, executor: @executor, args: [Concurrent::Event.new], &method(:execute_task)) + nil + end + + # @!visibility private + def execute_task(completion) + return nil unless @running.true? + start_time = Concurrent.monotonic_time + _success, value, reason = @task.execute(self) + if completion.try? + self.value = value + schedule_next_task(calculate_next_interval(start_time)) + time = Time.now + observers.notify_observers do + [time, self.value, reason] + end + end + nil + end + + # @!visibility private + def calculate_next_interval(start_time) + if @interval_type == FIXED_RATE + run_time = Concurrent.monotonic_time - start_time + [execution_interval - run_time, 0].max + else # FIXED_DELAY + execution_interval + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/tuple.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/tuple.rb new file mode 100644 index 00000000..56212cfd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/tuple.rb @@ -0,0 +1,82 @@ +require 'concurrent/atomic/atomic_reference' + +module Concurrent + + # A fixed size array with volatile (synchronized, thread safe) getters/setters. + # Mixes in Ruby's `Enumerable` module for enhanced search, sort, and traversal. + # + # @example + # tuple = Concurrent::Tuple.new(16) + # + # tuple.set(0, :foo) #=> :foo | volatile write + # tuple.get(0) #=> :foo | volatile read + # tuple.compare_and_set(0, :foo, :bar) #=> true | strong CAS + # tuple.cas(0, :foo, :baz) #=> false | strong CAS + # tuple.get(0) #=> :bar | volatile read + # + # @see https://en.wikipedia.org/wiki/Tuple Tuple entry at Wikipedia + # @see http://www.erlang.org/doc/reference_manual/data_types.html#id70396 Erlang Tuple + # @see http://ruby-doc.org/core-2.2.2/Enumerable.html Enumerable + class Tuple + include Enumerable + + # The (fixed) size of the tuple. + attr_reader :size + + # Create a new tuple of the given size. + # + # @param [Integer] size the number of elements in the tuple + def initialize(size) + @size = size + @tuple = tuple = ::Array.new(size) + i = 0 + while i < size + tuple[i] = Concurrent::AtomicReference.new + i += 1 + end + end + + # Get the value of the element at the given index. + # + # @param [Integer] i the index from which to retrieve the value + # @return [Object] the value at the given index or nil if the index is out of bounds + def get(i) + return nil if i >= @size || i < 0 + @tuple[i].get + end + alias_method :volatile_get, :get + + # Set the element at the given index to the given value + # + # @param [Integer] i the index for the element to set + # @param [Object] value the value to set at the given index + # + # @return [Object] the new value of the element at the given index or nil if the index is out of bounds + def set(i, value) + return nil if i >= @size || i < 0 + @tuple[i].set(value) + end + alias_method :volatile_set, :set + + # Set the value at the given index to the new value if and only if the current + # value matches the given old value. + # + # @param [Integer] i the index for the element to set + # @param [Object] old_value the value to compare against the current value + # @param [Object] new_value the value to set at the given index + # + # @return [Boolean] true if the value at the given element was set else false + def compare_and_set(i, old_value, new_value) + return false if i >= @size || i < 0 + @tuple[i].compare_and_set(old_value, new_value) + end + alias_method :cas, :compare_and_set + + # Calls the given block once for each element in self, passing that element as a parameter. + # + # @yieldparam [Object] ref the `Concurrent::AtomicReference` object at the current index + def each + @tuple.each {|ref| yield ref.get} + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/tvar.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/tvar.rb new file mode 100644 index 00000000..5d02ef09 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/tvar.rb @@ -0,0 +1,222 @@ +require 'set' +require 'concurrent/synchronization/object' + +module Concurrent + + # A `TVar` is a transactional variable - a single-element container that + # is used as part of a transaction - see `Concurrent::atomically`. + # + # @!macro thread_safe_variable_comparison + # + # {include:file:docs-source/tvar.md} + class TVar < Synchronization::Object + safe_initialization! + + # Create a new `TVar` with an initial value. + def initialize(value) + @value = value + @lock = Mutex.new + end + + # Get the value of a `TVar`. + def value + Concurrent::atomically do + Transaction::current.read(self) + end + end + + # Set the value of a `TVar`. + def value=(value) + Concurrent::atomically do + Transaction::current.write(self, value) + end + end + + # @!visibility private + def unsafe_value # :nodoc: + @value + end + + # @!visibility private + def unsafe_value=(value) # :nodoc: + @value = value + end + + # @!visibility private + def unsafe_lock # :nodoc: + @lock + end + + end + + # Run a block that reads and writes `TVar`s as a single atomic transaction. + # With respect to the value of `TVar` objects, the transaction is atomic, in + # that it either happens or it does not, consistent, in that the `TVar` + # objects involved will never enter an illegal state, and isolated, in that + # transactions never interfere with each other. You may recognise these + # properties from database transactions. + # + # There are some very important and unusual semantics that you must be aware of: + # + # * Most importantly, the block that you pass to atomically may be executed + # more than once. In most cases your code should be free of + # side-effects, except for via TVar. + # + # * If an exception escapes an atomically block it will abort the transaction. + # + # * It is undefined behaviour to use callcc or Fiber with atomically. + # + # * If you create a new thread within an atomically, it will not be part of + # the transaction. Creating a thread counts as a side-effect. + # + # Transactions within transactions are flattened to a single transaction. + # + # @example + # a = new TVar(100_000) + # b = new TVar(100) + # + # Concurrent::atomically do + # a.value -= 10 + # b.value += 10 + # end + def atomically + raise ArgumentError.new('no block given') unless block_given? + + # Get the current transaction + + transaction = Transaction::current + + # Are we not already in a transaction (not nested)? + + if transaction.nil? + # New transaction + + begin + # Retry loop + + loop do + + # Create a new transaction + + transaction = Transaction.new + Transaction::current = transaction + + # Run the block, aborting on exceptions + + begin + result = yield + rescue Transaction::AbortError => e + transaction.abort + result = Transaction::ABORTED + rescue Transaction::LeaveError => e + transaction.abort + break result + rescue => e + transaction.abort + raise e + end + # If we can commit, break out of the loop + + if result != Transaction::ABORTED + if transaction.commit + break result + end + end + end + ensure + # Clear the current transaction + + Transaction::current = nil + end + else + # Nested transaction - flatten it and just run the block + + yield + end + end + + # Abort a currently running transaction - see `Concurrent::atomically`. + def abort_transaction + raise Transaction::AbortError.new + end + + # Leave a transaction without committing or aborting - see `Concurrent::atomically`. + def leave_transaction + raise Transaction::LeaveError.new + end + + module_function :atomically, :abort_transaction, :leave_transaction + + private + + # @!visibility private + class Transaction + + ABORTED = ::Object.new + + OpenEntry = Struct.new(:value, :modified) + + AbortError = Class.new(StandardError) + LeaveError = Class.new(StandardError) + + def initialize + @open_tvars = {} + end + + def read(tvar) + entry = open(tvar) + entry.value + end + + def write(tvar, value) + entry = open(tvar) + entry.modified = true + entry.value = value + end + + def open(tvar) + entry = @open_tvars[tvar] + + unless entry + unless tvar.unsafe_lock.try_lock + Concurrent::abort_transaction + end + + entry = OpenEntry.new(tvar.unsafe_value, false) + @open_tvars[tvar] = entry + end + + entry + end + + def abort + unlock + end + + def commit + @open_tvars.each do |tvar, entry| + if entry.modified + tvar.unsafe_value = entry.value + end + end + + unlock + end + + def unlock + @open_tvars.each_key do |tvar| + tvar.unsafe_lock.unlock + end + end + + def self.current + Thread.current[:current_tvar_transaction] + end + + def self.current=(transaction) + Thread.current[:current_tvar_transaction] = transaction + end + + end + +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/utility/engine.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/utility/engine.rb new file mode 100644 index 00000000..0c574b2a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/utility/engine.rb @@ -0,0 +1,45 @@ +module Concurrent + # @!visibility private + module Utility + + # @!visibility private + module EngineDetector + def on_cruby? + RUBY_ENGINE == 'ruby' + end + + def on_jruby? + RUBY_ENGINE == 'jruby' + end + + def on_truffleruby? + RUBY_ENGINE == 'truffleruby' + end + + def on_windows? + !(RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/).nil? + end + + def on_osx? + !(RbConfig::CONFIG['host_os'] =~ /darwin|mac os/).nil? + end + + def on_linux? + !(RbConfig::CONFIG['host_os'] =~ /linux/).nil? + end + + def ruby_version(version = RUBY_VERSION, comparison, major, minor, patch) + result = (version.split('.').map(&:to_i) <=> [major, minor, patch]) + comparisons = { :== => [0], + :>= => [1, 0], + :<= => [-1, 0], + :> => [1], + :< => [-1] } + comparisons.fetch(comparison).include? result + end + end + end + + # @!visibility private + extend Utility::EngineDetector +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb new file mode 100644 index 00000000..1c987d8a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb @@ -0,0 +1,19 @@ +module Concurrent + + # @!macro monotonic_get_time + # + # Returns the current time as tracked by the application monotonic clock. + # + # @param [Symbol] unit the time unit to be returned, can be either + # :float_second, :float_millisecond, :float_microsecond, :second, + # :millisecond, :microsecond, or :nanosecond default to :float_second. + # + # @return [Float] The current monotonic time since some unspecified + # starting point + # + # @!macro monotonic_clock_warning + def monotonic_time(unit = :float_second) + Process.clock_gettime(Process::CLOCK_MONOTONIC, unit) + end + module_function :monotonic_time +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb new file mode 100644 index 00000000..bf7bab35 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb @@ -0,0 +1,77 @@ +require 'concurrent/utility/engine' +# Synchronization::AbstractObject must be defined before loading the extension +require 'concurrent/synchronization/abstract_object' + +module Concurrent + # @!visibility private + module Utility + # @!visibility private + module NativeExtensionLoader + + def allow_c_extensions? + Concurrent.on_cruby? + end + + def c_extensions_loaded? + defined?(@c_extensions_loaded) && @c_extensions_loaded + end + + def load_native_extensions + if Concurrent.on_cruby? && !c_extensions_loaded? + ['concurrent/concurrent_ruby_ext', + "concurrent/#{RUBY_VERSION[0..2]}/concurrent_ruby_ext" + ].each { |p| try_load_c_extension p } + end + + if Concurrent.on_jruby? && !java_extensions_loaded? + begin + require 'concurrent/concurrent_ruby.jar' + set_java_extensions_loaded + rescue LoadError => e + raise e, "Java extensions are required for JRuby.\n" + e.message, e.backtrace + end + end + end + + private + + def load_error_path(error) + if error.respond_to? :path + error.path + else + error.message.split(' -- ').last + end + end + + def set_c_extensions_loaded + @c_extensions_loaded = true + end + + def java_extensions_loaded? + defined?(@java_extensions_loaded) && @java_extensions_loaded + end + + def set_java_extensions_loaded + @java_extensions_loaded = true + end + + def try_load_c_extension(path) + require path + set_c_extensions_loaded + rescue LoadError => e + if load_error_path(e) == path + # move on with pure-Ruby implementations + # TODO (pitr-ch 12-Jul-2018): warning on verbose? + else + raise e + end + end + + end + end + + # @!visibility private + extend Utility::NativeExtensionLoader +end + +Concurrent.load_native_extensions diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/utility/native_integer.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/utility/native_integer.rb new file mode 100644 index 00000000..de1cdc30 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/utility/native_integer.rb @@ -0,0 +1,54 @@ +module Concurrent + # @!visibility private + module Utility + # @private + module NativeInteger + # http://stackoverflow.com/questions/535721/ruby-max-integer + MIN_VALUE = -(2**(0.size * 8 - 2)) + MAX_VALUE = (2**(0.size * 8 - 2) - 1) + + def ensure_upper_bound(value) + if value > MAX_VALUE + raise RangeError.new("#{value} is greater than the maximum value of #{MAX_VALUE}") + end + value + end + + def ensure_lower_bound(value) + if value < MIN_VALUE + raise RangeError.new("#{value} is less than the maximum value of #{MIN_VALUE}") + end + value + end + + def ensure_integer(value) + unless value.is_a?(Integer) + raise ArgumentError.new("#{value} is not an Integer") + end + value + end + + def ensure_integer_and_bounds(value) + ensure_integer value + ensure_upper_bound value + ensure_lower_bound value + end + + def ensure_positive(value) + if value < 0 + raise ArgumentError.new("#{value} cannot be negative") + end + value + end + + def ensure_positive_and_no_zero(value) + if value < 1 + raise ArgumentError.new("#{value} cannot be negative or zero") + end + value + end + + extend self + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/utility/processor_counter.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/utility/processor_counter.rb new file mode 100644 index 00000000..2489cbd7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/utility/processor_counter.rb @@ -0,0 +1,220 @@ +require 'etc' +require 'rbconfig' +require 'concurrent/delay' + +module Concurrent + # @!visibility private + module Utility + + # @!visibility private + class ProcessorCounter + def initialize + @processor_count = Delay.new { compute_processor_count } + @physical_processor_count = Delay.new { compute_physical_processor_count } + @cpu_quota = Delay.new { compute_cpu_quota } + @cpu_shares = Delay.new { compute_cpu_shares } + end + + def processor_count + @processor_count.value + end + + def physical_processor_count + @physical_processor_count.value + end + + def available_processor_count + cpu_count = processor_count.to_f + quota = cpu_quota + + return cpu_count if quota.nil? + + # cgroup cpus quotas have no limits, so they can be set to higher than the + # real count of cores. + if quota > cpu_count + cpu_count + else + quota + end + end + + def cpu_quota + @cpu_quota.value + end + + def cpu_shares + @cpu_shares.value + end + + private + + def compute_processor_count + if Concurrent.on_jruby? + java.lang.Runtime.getRuntime.availableProcessors + else + Etc.nprocessors + end + end + + def compute_physical_processor_count + ppc = case RbConfig::CONFIG["target_os"] + when /darwin\d\d/ + IO.popen("/usr/sbin/sysctl -n hw.physicalcpu", &:read).to_i + when /linux/ + cores = {} # unique physical ID / core ID combinations + phy = 0 + IO.read("/proc/cpuinfo").scan(/^physical id.*|^core id.*/) do |ln| + if ln.start_with?("physical") + phy = ln[/\d+/] + elsif ln.start_with?("core") + cid = phy + ":" + ln[/\d+/] + cores[cid] = true if not cores[cid] + end + end + cores.count + when /mswin|mingw/ + # Get-CimInstance introduced in PowerShell 3 or earlier: https://learn.microsoft.com/en-us/previous-versions/powershell/module/cimcmdlets/get-ciminstance?view=powershell-3.0 + result = run('powershell -command "Get-CimInstance -ClassName Win32_Processor -Property NumberOfCores | Select-Object -Property NumberOfCores"') + if !result || $?.exitstatus != 0 + # fallback to deprecated wmic for older systems + result = run("wmic cpu get NumberOfCores") + end + if !result || $?.exitstatus != 0 + # Bail out if both commands returned something unexpected + processor_count + else + # powershell: "\nNumberOfCores\n-------------\n 4\n\n\n" + # wmic: "NumberOfCores \n\n4 \n\n\n\n" + result.scan(/\d+/).map(&:to_i).reduce(:+) + end + else + processor_count + end + # fall back to logical count if physical info is invalid + ppc > 0 ? ppc : processor_count + rescue + return 1 + end + + def run(command) + IO.popen(command, &:read) + rescue Errno::ENOENT + end + + def compute_cpu_quota + if RbConfig::CONFIG["target_os"].include?("linux") + if File.exist?("/sys/fs/cgroup/cpu.max") + # cgroups v2: https://docs.kernel.org/admin-guide/cgroup-v2.html#cpu-interface-files + cpu_max = File.read("/sys/fs/cgroup/cpu.max") + return nil if cpu_max.start_with?("max ") # no limit + max, period = cpu_max.split.map(&:to_f) + max / period + elsif File.exist?("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us") + # cgroups v1: https://kernel.googlesource.com/pub/scm/linux/kernel/git/glommer/memcg/+/cpu_stat/Documentation/cgroups/cpu.txt + max = File.read("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us").to_i + # If the cpu.cfs_quota_us is -1, cgroup does not adhere to any CPU time restrictions + # https://docs.kernel.org/scheduler/sched-bwc.html#management + return nil if max <= 0 + period = File.read("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us").to_f + max / period + end + end + end + + def compute_cpu_shares + if RbConfig::CONFIG["target_os"].include?("linux") + if File.exist?("/sys/fs/cgroup/cpu.weight") + # cgroups v2: https://docs.kernel.org/admin-guide/cgroup-v2.html#cpu-interface-files + # Ref: https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/2254-cgroup-v2#phase-1-convert-from-cgroups-v1-settings-to-v2 + weight = File.read("/sys/fs/cgroup/cpu.weight").to_f + ((((weight - 1) * 262142) / 9999) + 2) / 1024 + elsif File.exist?("/sys/fs/cgroup/cpu/cpu.shares") + # cgroups v1: https://kernel.googlesource.com/pub/scm/linux/kernel/git/glommer/memcg/+/cpu_stat/Documentation/cgroups/cpu.txt + File.read("/sys/fs/cgroup/cpu/cpu.shares").to_f / 1024 + end + end + end + end + end + + # create the default ProcessorCounter on load + @processor_counter = Utility::ProcessorCounter.new + singleton_class.send :attr_reader, :processor_counter + + # Number of processors seen by the OS and used for process scheduling. For + # performance reasons the calculated value will be memoized on the first + # call. + # + # When running under JRuby the Java runtime call + # `java.lang.Runtime.getRuntime.availableProcessors` will be used. According + # to the Java documentation this "value may change during a particular + # invocation of the virtual machine... [applications] should therefore + # occasionally poll this property." We still memoize this value once under + # JRuby. + # + # Otherwise Ruby's Etc.nprocessors will be used. + # + # @return [Integer] number of processors seen by the OS or Java runtime + # + # @see http://docs.oracle.com/javase/6/docs/api/java/lang/Runtime.html#availableProcessors() + def self.processor_count + processor_counter.processor_count + end + + # Number of physical processor cores on the current system. For performance + # reasons the calculated value will be memoized on the first call. + # + # On Windows the Win32 API will be queried for the `NumberOfCores from + # Win32_Processor`. This will return the total number "of cores for the + # current instance of the processor." On Unix-like operating systems either + # the `hwprefs` or `sysctl` utility will be called in a subshell and the + # returned value will be used. In the rare case where none of these methods + # work or an exception is raised the function will simply return 1. + # + # @return [Integer] number physical processor cores on the current system + # + # @see https://github.com/grosser/parallel/blob/4fc8b89d08c7091fe0419ca8fba1ec3ce5a8d185/lib/parallel.rb + # + # @see http://msdn.microsoft.com/en-us/library/aa394373(v=vs.85).aspx + # @see http://www.unix.com/man-page/osx/1/HWPREFS/ + # @see http://linux.die.net/man/8/sysctl + def self.physical_processor_count + processor_counter.physical_processor_count + end + + # Number of processors cores available for process scheduling. + # This method takes in account the CPU quota if the process is inside a cgroup with a + # dedicated CPU quota (typically Docker). + # Otherwise it returns the same value as #processor_count but as a Float. + # + # For performance reasons the calculated value will be memoized on the first + # call. + # + # @return [Float] number of available processors + def self.available_processor_count + processor_counter.available_processor_count + end + + # The maximum number of processors cores available for process scheduling. + # Returns `nil` if there is no enforced limit, or a `Float` if the + # process is inside a cgroup with a dedicated CPU quota (typically Docker). + # + # Note that nothing prevents setting a CPU quota higher than the actual number of + # cores on the system. + # + # For performance reasons the calculated value will be memoized on the first + # call. + # + # @return [nil, Float] Maximum number of available processors as set by a cgroup CPU quota, or nil if none set + def self.cpu_quota + processor_counter.cpu_quota + end + + # The CPU shares requested by the process. For performance reasons the calculated + # value will be memoized on the first call. + # + # @return [Float, nil] CPU shares requested by the process, or nil if not set + def self.cpu_shares + processor_counter.cpu_shares + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/version.rb b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/version.rb new file mode 100644 index 00000000..f773e44f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/version.rb @@ -0,0 +1,3 @@ +module Concurrent + VERSION = '1.3.5' +end diff --git a/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/Changes.md b/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/Changes.md new file mode 100644 index 00000000..8f077fc6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/Changes.md @@ -0,0 +1,185 @@ +# connection_pool Changelog + +2.5.3 +------ + +- Fix TruffleRuby/JRuby crash [#201] + +2.5.2 +------ + +- Rollback inadvertant change to `auto_reload_after_fork` default. [#200] + +2.5.1 +------ + +- Pass options to TimedStack in `checkout` [#195] +- Optimize connection lookup [#196] +- Fixes for use with Ractors + +2.5.0 +------ + +- Reap idle connections [#187] +```ruby +idle_timeout = 60 +pool = ConnectionPool.new ... +pool.reap(idle_timeout, &:close) +``` +- `ConnectionPool#idle` returns the count of connections not in use [#187] + +2.4.1 +------ + +- New `auto_reload_after_fork` config option to disable auto-drop [#177, shayonj] + +2.4.0 +------ + +- Automatically drop all connections after fork [#166] + +2.3.0 +------ + +- Minimum Ruby version is now 2.5.0 +- Add pool size to TimeoutError message + +2.2.5 +------ + +- Fix argument forwarding on Ruby 2.7 [#149] + +2.2.4 +------ + +- Add `reload` to close all connections, recreating them afterwards [Andrew Marshall, #140] +- Add `then` as a way to use a pool or a bare connection with the same code path [#138] + +2.2.3 +------ + +- Pool now throws `ConnectionPool::TimeoutError` on timeout. [#130] +- Use monotonic clock present in all modern Rubies [Tero Tasanen, #109] +- Remove code hacks necessary for JRuby 1.7 +- Expose wrapped pool from ConnectionPool::Wrapper [Thomas Lecavelier, #113] + +2.2.2 +------ + +- Add pool `size` and `available` accessors for metrics and monitoring + purposes [#97, robholland] + +2.2.1 +------ + +- Allow CP::Wrapper to use an existing pool [#87, etiennebarrie] +- Use monotonic time for more accurate timeouts [#84, jdantonio] + +2.2.0 +------ + +- Rollback `Timeout` handling introduced in 2.1.1 and 2.1.2. It seems + impossible to safely work around the issue. Please never, ever use + `Timeout.timeout` in your code or you will see rare but mysterious bugs. [#75] + +2.1.3 +------ + +- Don't increment created count until connection is successfully + created. [mylesmegyesi, #73] + +2.1.2 +------ + +- The connection\_pool will now close any connections which respond to + `close` (Dalli) or `disconnect!` (Redis). This ensures discarded connections + from the fix in 2.1.1 are torn down ASAP and don't linger open. + + +2.1.1 +------ + +- Work around a subtle race condition with code which uses `Timeout.timeout` and + checks out a connection within the timeout block. This might cause + connections to get into a bad state and raise very odd errors. [tamird, #67] + + +2.1.0 +------ + +- Refactoring to better support connection pool subclasses [drbrain, + #55] +- `with` should return value of the last expression [#59] + + +2.0.0 +----- + +- The connection pool is now lazy. Connections are created as needed + and retained until the pool is shut down. [drbrain, #52] + +1.2.0 +----- + +- Add `with(options)` and `checkout(options)`. [mattcamuto] + Allows the caller to override the pool timeout. +```ruby +@pool.with(:timeout => 2) do |conn| +end +``` + +1.1.0 +----- + +- New `#shutdown` method (simao) + + This method accepts a block and calls the block for each + connection in the pool. After calling this method, trying to get a + connection from the pool raises `PoolShuttingDownError`. + +1.0.0 +----- + +- `#with_connection` is now gone in favor of `#with`. + +- We no longer pollute the top level namespace with our internal +`TimedStack` class. + +0.9.3 +-------- + +- `#with_connection` is now deprecated in favor of `#with`. + + A warning will be issued in the 0.9 series and the method will be + removed in 1.0. + +- We now reuse objects when possible. + + This means that under no contention, the same object will be checked + out from the pool after subsequent calls to `ConnectionPool#with`. + + This change should have no impact on end user performance. If + anything, it should be an improvement, depending on what objects you + are pooling. + +0.9.2 +-------- + +- Fix reentrant checkout leading to early checkin. + +0.9.1 +-------- + +- Fix invalid superclass in version.rb + +0.9.0 +-------- + +- Move method\_missing magic into ConnectionPool::Wrapper (djanowski) +- Remove BasicObject superclass (djanowski) + +0.1.0 +-------- + +- More precise timeouts and better error message +- ConnectionPool now subclasses BasicObject so `method_missing` is more effective. diff --git a/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/LICENSE b/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/LICENSE new file mode 100644 index 00000000..7673cbfb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2011 Mike Perham + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/README.md b/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/README.md new file mode 100644 index 00000000..804929c9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/README.md @@ -0,0 +1,167 @@ +connection\_pool +================= +[![Build Status](https://github.com/mperham/connection_pool/actions/workflows/ci.yml/badge.svg)](https://github.com/mperham/connection_pool/actions/workflows/ci.yml) + +Generic connection pooling for Ruby. + +MongoDB has its own connection pool. +ActiveRecord has its own connection pool. +This is a generic connection pool that can be used with anything, e.g. Redis, Dalli and other Ruby network clients. + +Usage +----- + +Create a pool of objects to share amongst the fibers or threads in your Ruby application: + +``` ruby +$memcached = ConnectionPool.new(size: 5, timeout: 5) { Dalli::Client.new } +``` + +Then use the pool in your application: + +``` ruby +$memcached.with do |conn| + conn.get('some-count') +end +``` + +If all the objects in the connection pool are in use, `with` will block +until one becomes available. +If no object is available within `:timeout` seconds, +`with` will raise a `ConnectionPool::TimeoutError` (a subclass of `Timeout::Error`). + +You can also use `ConnectionPool#then` to support _both_ a +connection pool and a raw client. + +```ruby +# Compatible with a raw Redis::Client, and ConnectionPool Redis +$redis.then { |r| r.set 'foo' 'bar' } +``` + +Optionally, you can specify a timeout override using the with-block semantics: + +``` ruby +$memcached.with(timeout: 2.0) do |conn| + conn.get('some-count') +end +``` + +This will only modify the resource-get timeout for this particular +invocation. +This is useful if you want to fail-fast on certain non-critical +sections when a resource is not available, or conversely if you are comfortable blocking longer on a particular resource. +This is not implemented in the `ConnectionPool::Wrapper` class. + +## Migrating to a Connection Pool + +You can use `ConnectionPool::Wrapper` to wrap a single global connection, making it easier to migrate existing connection code over time: + +``` ruby +$redis = ConnectionPool::Wrapper.new(size: 5, timeout: 3) { Redis.new } +$redis.sadd('foo', 1) +$redis.smembers('foo') +``` + +The wrapper uses `method_missing` to checkout a connection, run the requested method and then immediately check the connection back into the pool. +It's **not** high-performance so you'll want to port your performance sensitive code to use `with` as soon as possible. + +``` ruby +$redis.with do |conn| + conn.sadd('foo', 1) + conn.smembers('foo') +end +``` + +Once you've ported your entire system to use `with`, you can simply remove `Wrapper` and use the simpler and faster `ConnectionPool`. + + +## Shutdown + +You can shut down a ConnectionPool instance once it should no longer be used. +Further checkout attempts will immediately raise an error but existing checkouts will work. + +```ruby +cp = ConnectionPool.new { Redis.new } +cp.shutdown { |c| c.close } +``` + +Shutting down a connection pool will block until all connections are checked in and closed. +**Note that shutting down is completely optional**; Ruby's garbage collector will reclaim unreferenced pools under normal circumstances. + +## Reload + +You can reload a ConnectionPool instance in the case it is desired to close all connections to the pool and, unlike `shutdown`, afterwards recreate connections so the pool may continue to be used. +Reloading may be useful after forking the process. + +```ruby +cp = ConnectionPool.new { Redis.new } +cp.reload { |conn| conn.quit } +cp.with { |conn| conn.get('some-count') } +``` + +Like `shutdown`, this will block until all connections are checked in and closed. + +## Reap + +You can reap idle connections in the ConnectionPool instance to close connections that were created but have not been used for a certain amount of time. This can be useful to run periodically in a separate thread especially if keeping the connection open is resource intensive. + +You can specify how many seconds the connections have to be idle for them to be reaped. +Defaults to 60 seconds. + +```ruby +cp = ConnectionPool.new { Redis.new } +cp.reap(300) { |conn| conn.close } # Reaps connections that have been idle for 300 seconds (5 minutes). +``` + +### Reaper Thread + +You can start your own reaper thread to reap idle connections in the ConnectionPool instance on a regular interval. + +```ruby +cp = ConnectionPool.new { Redis.new } + +# Start a reaper thread to reap connections that have been idle for 300 seconds (5 minutes). +Thread.new do + loop do + cp.reap(300) { |conn| conn.close } + sleep 300 + end +end +``` + +## Current State + +There are several methods that return information about a pool. + +```ruby +cp = ConnectionPool.new(size: 10) { Redis.new } +cp.size # => 10 +cp.available # => 10 +cp.idle # => 0 + +cp.with do |conn| + cp.size # => 10 + cp.available # => 9 + cp.idle # => 0 +end + +cp.idle # => 1 +``` + +Notes +----- + +- Connections are lazily created as needed. +- There is no provision for repairing or checking the health of a connection; + connections should be self-repairing. This is true of the Dalli and Redis + clients. +- **WARNING**: Don't ever use `Timeout.timeout` in your Ruby code or you will see + occasional silent corruption and mysterious errors. The Timeout API is unsafe + and cannot be used correctly, ever. Use proper socket timeout options as + exposed by Net::HTTP, Redis, Dalli, etc. + + +Author +------ + +Mike Perham, [@getajobmike](https://twitter.com/getajobmike), diff --git a/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/connection_pool.gemspec b/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/connection_pool.gemspec new file mode 100644 index 00000000..32289f25 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/connection_pool.gemspec @@ -0,0 +1,24 @@ +require "./lib/connection_pool/version" + +Gem::Specification.new do |s| + s.name = "connection_pool" + s.version = ConnectionPool::VERSION + s.platform = Gem::Platform::RUBY + s.authors = ["Mike Perham", "Damian Janowski"] + s.email = ["mperham@gmail.com", "damian@educabilia.com"] + s.homepage = "https://github.com/mperham/connection_pool" + s.description = s.summary = "Generic connection pool for Ruby" + + s.files = ["Changes.md", "LICENSE", "README.md", "connection_pool.gemspec", + "lib/connection_pool.rb", "lib/connection_pool/timed_stack.rb", + "lib/connection_pool/version.rb", "lib/connection_pool/wrapper.rb"] + s.executables = [] + s.require_paths = ["lib"] + s.license = "MIT" + s.add_development_dependency "bundler" + s.add_development_dependency "minitest", ">= 5.0.0" + s.add_development_dependency "rake" + s.required_ruby_version = ">= 2.5.0" + + s.metadata = {"changelog_uri" => "https://github.com/mperham/connection_pool/blob/main/Changes.md", "rubygems_mfa_required" => "true"} +end diff --git a/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/lib/connection_pool.rb b/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/lib/connection_pool.rb new file mode 100644 index 00000000..081d8bdf --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/lib/connection_pool.rb @@ -0,0 +1,184 @@ +require "timeout" +require_relative "connection_pool/version" + +class ConnectionPool + class Error < ::RuntimeError; end + + class PoolShuttingDownError < ::ConnectionPool::Error; end + + class TimeoutError < ::Timeout::Error; end +end + +# Generic connection pool class for sharing a limited number of objects or network connections +# among many threads. Note: pool elements are lazily created. +# +# Example usage with block (faster): +# +# @pool = ConnectionPool.new { Redis.new } +# @pool.with do |redis| +# redis.lpop('my-list') if redis.llen('my-list') > 0 +# end +# +# Using optional timeout override (for that single invocation) +# +# @pool.with(timeout: 2.0) do |redis| +# redis.lpop('my-list') if redis.llen('my-list') > 0 +# end +# +# Example usage replacing an existing connection (slower): +# +# $redis = ConnectionPool.wrap { Redis.new } +# +# def do_work +# $redis.lpop('my-list') if $redis.llen('my-list') > 0 +# end +# +# Accepts the following options: +# - :size - number of connections to pool, defaults to 5 +# - :timeout - amount of time to wait for a connection if none currently available, defaults to 5 seconds +# - :auto_reload_after_fork - automatically drop all connections after fork, defaults to true +# +class ConnectionPool + DEFAULTS = {size: 5, timeout: 5, auto_reload_after_fork: true}.freeze + + def self.wrap(options, &block) + Wrapper.new(options, &block) + end + + if Process.respond_to?(:fork) + INSTANCES = ObjectSpace::WeakMap.new + private_constant :INSTANCES + + def self.after_fork + INSTANCES.values.each do |pool| + next unless pool.auto_reload_after_fork + + # We're on after fork, so we know all other threads are dead. + # All we need to do is to ensure the main thread doesn't have a + # checked out connection + pool.checkin(force: true) + pool.reload do |connection| + # Unfortunately we don't know what method to call to close the connection, + # so we try the most common one. + connection.close if connection.respond_to?(:close) + end + end + nil + end + + if ::Process.respond_to?(:_fork) # MRI 3.1+ + module ForkTracker + def _fork + pid = super + if pid == 0 + ConnectionPool.after_fork + end + pid + end + end + Process.singleton_class.prepend(ForkTracker) + end + else + INSTANCES = nil + private_constant :INSTANCES + + def self.after_fork + # noop + end + end + + def initialize(options = {}, &block) + raise ArgumentError, "Connection pool requires a block" unless block + + options = DEFAULTS.merge(options) + + @size = Integer(options.fetch(:size)) + @timeout = options.fetch(:timeout) + @auto_reload_after_fork = options.fetch(:auto_reload_after_fork) + + @available = TimedStack.new(@size, &block) + @key = :"pool-#{@available.object_id}" + @key_count = :"pool-#{@available.object_id}-count" + INSTANCES[self] = self if @auto_reload_after_fork && INSTANCES + end + + def with(options = {}) + Thread.handle_interrupt(Exception => :never) do + conn = checkout(options) + begin + Thread.handle_interrupt(Exception => :immediate) do + yield conn + end + ensure + checkin + end + end + end + alias_method :then, :with + + def checkout(options = {}) + if ::Thread.current[@key] + ::Thread.current[@key_count] += 1 + ::Thread.current[@key] + else + ::Thread.current[@key_count] = 1 + ::Thread.current[@key] = @available.pop(options[:timeout] || @timeout, options) + end + end + + def checkin(force: false) + if ::Thread.current[@key] + if ::Thread.current[@key_count] == 1 || force + @available.push(::Thread.current[@key]) + ::Thread.current[@key] = nil + ::Thread.current[@key_count] = nil + else + ::Thread.current[@key_count] -= 1 + end + elsif !force + raise ConnectionPool::Error, "no connections are checked out" + end + + nil + end + + ## + # Shuts down the ConnectionPool by passing each connection to +block+ and + # then removing it from the pool. Attempting to checkout a connection after + # shutdown will raise +ConnectionPool::PoolShuttingDownError+. + def shutdown(&block) + @available.shutdown(&block) + end + + ## + # Reloads the ConnectionPool by passing each connection to +block+ and then + # removing it the pool. Subsequent checkouts will create new connections as + # needed. + def reload(&block) + @available.shutdown(reload: true, &block) + end + + ## Reaps idle connections that have been idle for over +idle_seconds+. + # +idle_seconds+ defaults to 60. + def reap(idle_seconds = 60, &block) + @available.reap(idle_seconds, &block) + end + + # Size of this connection pool + attr_reader :size + # Automatically drop all connections after fork + attr_reader :auto_reload_after_fork + + # Number of pool entries available for checkout at this instant. + def available + @available.length + end + + # Number of pool entries created and idle in the pool. + def idle + @available.idle + end +end + +require_relative "connection_pool/timed_stack" +require_relative "connection_pool/wrapper" diff --git a/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/lib/connection_pool/timed_stack.rb b/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/lib/connection_pool/timed_stack.rb new file mode 100644 index 00000000..189431ee --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/lib/connection_pool/timed_stack.rb @@ -0,0 +1,221 @@ +## +# The TimedStack manages a pool of homogeneous connections (or any resource +# you wish to manage). Connections are created lazily up to a given maximum +# number. +# +# Examples: +# +# ts = TimedStack.new(1) { MyConnection.new } +# +# # fetch a connection +# conn = ts.pop +# +# # return a connection +# ts.push conn +# +# conn = ts.pop +# ts.pop timeout: 5 +# #=> raises ConnectionPool::TimeoutError after 5 seconds +class ConnectionPool::TimedStack + attr_reader :max + + ## + # Creates a new pool with +size+ connections that are created from the given + # +block+. + def initialize(size = 0, &block) + @create_block = block + @created = 0 + @que = [] + @max = size + @mutex = Thread::Mutex.new + @resource = Thread::ConditionVariable.new + @shutdown_block = nil + end + + ## + # Returns +obj+ to the stack. +options+ is ignored in TimedStack but may be + # used by subclasses that extend TimedStack. + def push(obj, options = {}) + @mutex.synchronize do + if @shutdown_block + @created -= 1 unless @created == 0 + @shutdown_block.call(obj) + else + store_connection obj, options + end + + @resource.broadcast + end + end + alias_method :<<, :push + + ## + # Retrieves a connection from the stack. If a connection is available it is + # immediately returned. If no connection is available within the given + # timeout a ConnectionPool::TimeoutError is raised. + # + # +:timeout+ is the only checked entry in +options+ and is preferred over + # the +timeout+ argument (which will be removed in a future release). Other + # options may be used by subclasses that extend TimedStack. + def pop(timeout = 0.5, options = {}) + options, timeout = timeout, 0.5 if Hash === timeout + timeout = options.fetch :timeout, timeout + + deadline = current_time + timeout + @mutex.synchronize do + loop do + raise ConnectionPool::PoolShuttingDownError if @shutdown_block + if (conn = try_fetch_connection(options)) + return conn + end + + connection = try_create(options) + return connection if connection + + to_wait = deadline - current_time + raise ConnectionPool::TimeoutError, "Waited #{timeout} sec, #{length}/#{@max} available" if to_wait <= 0 + @resource.wait(@mutex, to_wait) + end + end + end + + ## + # Shuts down the TimedStack by passing each connection to +block+ and then + # removing it from the pool. Attempting to checkout a connection after + # shutdown will raise +ConnectionPool::PoolShuttingDownError+ unless + # +:reload+ is +true+. + def shutdown(reload: false, &block) + raise ArgumentError, "shutdown must receive a block" unless block + + @mutex.synchronize do + @shutdown_block = block + @resource.broadcast + + shutdown_connections + @shutdown_block = nil if reload + end + end + + ## + # Reaps connections that were checked in more than +idle_seconds+ ago. + def reap(idle_seconds, &block) + raise ArgumentError, "reap must receive a block" unless block + raise ArgumentError, "idle_seconds must be a number" unless idle_seconds.is_a?(Numeric) + raise ConnectionPool::PoolShuttingDownError if @shutdown_block + + idle.times do + conn = + @mutex.synchronize do + raise ConnectionPool::PoolShuttingDownError if @shutdown_block + + reserve_idle_connection(idle_seconds) + end + break unless conn + + block.call(conn) + end + end + + ## + # Returns +true+ if there are no available connections. + def empty? + (@created - @que.length) >= @max + end + + ## + # The number of connections available on the stack. + def length + @max - @created + @que.length + end + + ## + # The number of connections created and available on the stack. + def idle + @que.length + end + + private + + def current_time + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end + + ## + # This is an extension point for TimedStack and is called with a mutex. + # + # This method must returns a connection from the stack if one exists. Allows + # subclasses with expensive match/search algorithms to avoid double-handling + # their stack. + def try_fetch_connection(options = nil) + connection_stored?(options) && fetch_connection(options) + end + + ## + # This is an extension point for TimedStack and is called with a mutex. + # + # This method must returns true if a connection is available on the stack. + def connection_stored?(options = nil) + !@que.empty? + end + + ## + # This is an extension point for TimedStack and is called with a mutex. + # + # This method must return a connection from the stack. + def fetch_connection(options = nil) + @que.pop&.first + end + + ## + # This is an extension point for TimedStack and is called with a mutex. + # + # This method must shut down all connections on the stack. + def shutdown_connections(options = nil) + while (conn = try_fetch_connection(options)) + @created -= 1 unless @created == 0 + @shutdown_block.call(conn) + end + end + + ## + # This is an extension point for TimedStack and is called with a mutex. + # + # This method returns the oldest idle connection if it has been idle for more than idle_seconds. + # This requires that the stack is kept in order of checked in time (oldest first). + def reserve_idle_connection(idle_seconds) + return unless idle_connections?(idle_seconds) + + @created -= 1 unless @created == 0 + + @que.shift.first + end + + ## + # This is an extension point for TimedStack and is called with a mutex. + # + # Returns true if the first connection in the stack has been idle for more than idle_seconds + def idle_connections?(idle_seconds) + connection_stored? && (current_time - @que.first.last > idle_seconds) + end + + ## + # This is an extension point for TimedStack and is called with a mutex. + # + # This method must return +obj+ to the stack. + def store_connection(obj, options = nil) + @que.push [obj, current_time] + end + + ## + # This is an extension point for TimedStack and is called with a mutex. + # + # This method must create a connection if and only if the total number of + # connections allowed has not been met. + def try_create(options = nil) + unless @created == @max + object = @create_block.call + @created += 1 + object + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/lib/connection_pool/version.rb b/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/lib/connection_pool/version.rb new file mode 100644 index 00000000..02620939 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/lib/connection_pool/version.rb @@ -0,0 +1,3 @@ +class ConnectionPool + VERSION = "2.5.3" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/lib/connection_pool/wrapper.rb b/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/lib/connection_pool/wrapper.rb new file mode 100644 index 00000000..8630bee3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/connection_pool-2.5.3/lib/connection_pool/wrapper.rb @@ -0,0 +1,56 @@ +class ConnectionPool + class Wrapper < ::BasicObject + METHODS = [:with, :pool_shutdown, :wrapped_pool] + + def initialize(options = {}, &block) + @pool = options.fetch(:pool) { ::ConnectionPool.new(options, &block) } + end + + def wrapped_pool + @pool + end + + def with(&block) + @pool.with(&block) + end + + def pool_shutdown(&block) + @pool.shutdown(&block) + end + + def pool_size + @pool.size + end + + def pool_available + @pool.available + end + + def respond_to?(id, *args) + METHODS.include?(id) || with { |c| c.respond_to?(id, *args) } + end + + # rubocop:disable Style/MissingRespondToMissing + if ::RUBY_VERSION >= "3.0.0" + def method_missing(name, *args, **kwargs, &block) + with do |connection| + connection.send(name, *args, **kwargs, &block) + end + end + elsif ::RUBY_VERSION >= "2.7.0" + ruby2_keywords def method_missing(name, *args, &block) + with do |connection| + connection.send(name, *args, &block) + end + end + else + def method_missing(name, *args, &block) + with do |connection| + connection.send(name, *args, &block) + end + end + end + # rubocop:enable Style/MethodMissingSuper + # rubocop:enable Style/MissingRespondToMissing + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/.gitignore b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/.gitignore new file mode 100644 index 00000000..c2757f55 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/.gitignore @@ -0,0 +1,5 @@ +.yardoc/ +doc/ +pkg/ +.DS_Store +Gemfile.lock diff --git a/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/.travis.yml b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/.travis.yml new file mode 100644 index 00000000..0f3af77c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/.travis.yml @@ -0,0 +1,11 @@ +language: ruby +rvm: + - 2.3 + - 2.4 + - 2.5 + - 2.6 + - ruby-head + +matrix: + allow_failures: + - rvm: ruby-head diff --git a/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/.yardopts b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/.yardopts new file mode 100644 index 00000000..29c933bc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/.yardopts @@ -0,0 +1 @@ +--markup markdown diff --git a/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/Gemfile b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/Gemfile new file mode 100644 index 00000000..851fabc2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/Gemfile @@ -0,0 +1,2 @@ +source 'https://rubygems.org' +gemspec diff --git a/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/HISTORY.md b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/HISTORY.md new file mode 100644 index 00000000..d44d160e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/HISTORY.md @@ -0,0 +1,128 @@ +Crass Change History +==================== + +1.0.6 (2020-01-12) +------------------ + +* Number values are now limited to a maximum of `Float::MAX` and a minimum of + negative `Float::MAX`. (#11) + +* Added project metadata to the gemspec. (#9 - @orien) + + +1.0.5 (2019-10-15) +------------------ + +* Removed test files from the gem. (#8 - @t-richards) + + +1.0.4 (2018-04-08) +------------------ + +* Fixed whitespace warnings. (#7 - @yahonda) + + +1.0.3 (2017-11-13) +------------------ + +* Added support for frozen string literals. (#3 - @flavorjones) + + +1.0.2 (2015-04-17) +------------------ + +* Fixed: An at-rule immediately followed by a `{}` simple block would have the + block (and subsequent tokens until a semicolon) incorrectly appended to its + prelude. This was super dumb and made me very sad. + + +1.0.1 (2014-11-16) +------------------ + +* Fixed: Modifications made to the block of an `:at_rule` node in a parse tree + weren't reflected when that node was stringified. This was a regression + introduced in 1.0.0. + + +1.0.0 (2014-11-16) +------------------ + +* Many parsing and tokenization tweaks to bring us into full compliance with the + [14 November 2014 editor's draft][css-syntax-draft] of the CSS syntax spec. + The most significant outwardly visible change is that quoted URLs like + `url("foo")` are now returned as `:function` tokens and not `:url` tokens due + to a change in the tokenization spec. + +* Teensy tiny speed and memory usage improvements that you almost certainly + won't notice. + +* Fixed: A semicolon following a `@charset` rule would be omitted during + serialization. + +* Fixed: A multibyte char at the beginning of an id token could trigger an + encoding error because `StringScanner#peek` is a jerkface. + +[css-syntax-draft]:http://dev.w3.org/csswg/css-syntax-3/ + + +0.2.1 (2014-07-22) +------------------ + +* Fixed: Error when the last property of a rule has no value and no terminating + semicolon. [#2][] + +[#2]:https://github.com/rgrove/crass/issues/2 + + +0.2.0 (2013-10-10) +------------------ + +* Added a `:children` field to `:property` nodes. It's an array containing all + the nodes that make up the property's value. + +* Fixed: Incorrect value was given for `:property` nodes whose values contained + functions. + +* Fixed: When parsing the value of an at-rule's block as a list of rules, a + selector containing a function (such as `#foo:not(.bar)`) would cause that + property and the rest of the token stream to be discarded. + + +0.1.0 (2013-10-04) +------------------ + +* Tokenization is a little over 50% faster. + +* Added tons of unit tests. + +* Added `Crass.parse_properties` and `Crass::Parser.parse_properties`, which can + be used to parse the contents of an HTML element's `style` attribute. + +* Added `Crass::Parser.parse_rules`, which can be used to parse the contents of + an `:at_rule` block like `@media` that may contain style rules. + +* Fixed: `Crass::Parser#consume_at_rule` and `#consume_qualified_rule` didn't + properly handle already-parsed `:simple_block` nodes in the input, which + occurs when parsing rules in the value of an `:at_rule` block. + +* Fixed: On `:property` nodes, `:important` is now set to `true` when the + property is followed by an "!important" declaration. + +* Fixed: "!important" is no longer included in the value of a `:property` node. + +* Fixed: A variety of tokenization bugs uncovered by tests. + +* Fixed: Added a workaround for a possible spec bug when an `:at_keyword` is + encountered while consuming declarations. + + +0.0.2 (2013-09-30) +------------------ + +* Fixed: `:at_rule` nodes now have a `:name` key. + + +0.0.1 (2013-09-27) +------------------ + +* Initial release. diff --git a/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/LICENSE b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/LICENSE new file mode 100644 index 00000000..1a24415d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) 2020 Ryan Grove (ryan@wonko.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the ‘Software’), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/README.md b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/README.md new file mode 100644 index 00000000..e947077f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/README.md @@ -0,0 +1,192 @@ +Crass +===== + +Crass is a Ruby CSS parser that's fully compliant with the +[CSS Syntax Level 3][css] specification. + +* [Home](https://github.com/rgrove/crass/) +* [API Docs](http://rubydoc.info/github/rgrove/crass/master) + +[![Build Status](https://travis-ci.org/rgrove/crass.svg?branch=master)](https://travis-ci.org/rgrove/crass) +[![Gem Version](https://badge.fury.io/rb/crass.svg)](http://badge.fury.io/rb/crass) + +Features +-------- + +* Pure Ruby, with no runtime dependencies other than Ruby 1.9.x or higher. + +* Tokenizes and parses CSS according to the rules defined in the 14 November + 2014 editor's draft of the [CSS Syntax Level 3][css] specification. + +* Extremely tolerant of broken or invalid CSS. If a browser can handle it, Crass + should be able to handle it too. + +* Optionally includes comments in the token stream. + +* Optionally preserves certain CSS hacks, such as the IE "*" hack, which would + otherwise be discarded according to CSS3 tokenizing rules. + +* Capable of serializing the parse tree back to CSS while maintaining all + original whitespace, comments, and indentation. + +[css]: http://dev.w3.org/csswg/css-syntax/ + +Problems +-------- + +* Crass isn't terribly fast. I mean, it's Ruby, and it's not really slow by Ruby + standards. But compared to the CSS parser in your average browser? Yeah, it's + slow. + +* Crass only parses the CSS syntax; it doesn't understand what any of it means, + doesn't coalesce selectors, etc. You can do this yourself by consuming the + parse tree, though. + +* While any node in the parse tree (or the parse tree as a whole) can be + serialized back to CSS with perfect fidelity, changes made to those nodes + (except for wholesale removal of nodes) are not reflected in the serialized + output. + +* Crass only supports UTF-8 input and doesn't respect `@charset` rules. Input in + any other encoding will be converted to UTF-8. + +Installing +---------- + +``` +gem install crass +``` + +Examples +-------- + +Say you have a string containing some CSS: + +```css +/* Comment! */ +a:hover { + color: #0d8bfa; + text-decoration: underline; +} +``` + +Parsing it is simple: + +```ruby +tree = Crass.parse(css, :preserve_comments => true) +``` + +This returns a big fat beautiful parse tree, which looks like this: + +```ruby +[{:node=>:comment, :pos=>0, :raw=>"/* Comment! */", :value=>" Comment! "}, + {:node=>:whitespace, :pos=>14, :raw=>"\n"}, + {:node=>:style_rule, + :selector=> + {:node=>:selector, + :value=>"a:hover", + :tokens=> + [{:node=>:ident, :pos=>15, :raw=>"a", :value=>"a"}, + {:node=>:colon, :pos=>16, :raw=>":"}, + {:node=>:ident, :pos=>17, :raw=>"hover", :value=>"hover"}, + {:node=>:whitespace, :pos=>22, :raw=>" "}]}, + :children=> + [{:node=>:whitespace, :pos=>24, :raw=>"\n "}, + {:node=>:property, + :name=>"color", + :value=>"#0d8bfa", + :children=> + [{:node=>:whitespace, :pos=>33, :raw=>" "}, + {:node=>:hash, + :pos=>34, + :raw=>"#0d8bfa", + :type=>:unrestricted, + :value=>"0d8bfa"}], + :important=>false, + :tokens=> + [{:node=>:ident, :pos=>27, :raw=>"color", :value=>"color"}, + {:node=>:colon, :pos=>32, :raw=>":"}, + {:node=>:whitespace, :pos=>33, :raw=>" "}, + {:node=>:hash, + :pos=>34, + :raw=>"#0d8bfa", + :type=>:unrestricted, + :value=>"0d8bfa"}]}, + {:node=>:semicolon, :pos=>41, :raw=>";"}, + {:node=>:whitespace, :pos=>42, :raw=>"\n "}, + {:node=>:property, + :name=>"text-decoration", + :value=>"underline", + :children=> + [{:node=>:whitespace, :pos=>61, :raw=>" "}, + {:node=>:ident, :pos=>62, :raw=>"underline", :value=>"underline"}], + :important=>false, + :tokens=> + [{:node=>:ident, + :pos=>45, + :raw=>"text-decoration", + :value=>"text-decoration"}, + {:node=>:colon, :pos=>60, :raw=>":"}, + {:node=>:whitespace, :pos=>61, :raw=>" "}, + {:node=>:ident, :pos=>62, :raw=>"underline", :value=>"underline"}]}, + {:node=>:semicolon, :pos=>71, :raw=>";"}, + {:node=>:whitespace, :pos=>72, :raw=>"\n"}]}] +``` + +If you want, you can stringify the parse tree: + +```ruby +css = Crass::Parser.stringify(tree) +``` + +...which gives you back exactly what you put in! + +```css +/* Comment! */ +a:hover { + color: #0d8bfa; + text-decoration: underline; +} +``` + +Wasn't that exciting? + +A Note on Versioning +-------------------- + +As of version 1.0.0, Crass adheres strictly to [SemVer 2.0][semver]. + +[semver]:http://semver.org/spec/v2.0.0.html + +Contributing +------------ + +The best way to contribute is to use Crass and [create issues][issue] when you +run into problems. + +Pull requests that fix bugs are more than welcome as long as they include tests. +Please adhere to the style and format of the surrounding code, or I might ask +you to change things. + +If you want to add a feature or refactor something, please get in touch first to +make sure I'm on board with your idea and approach; I'm pretty picky, and I'd +hate to have to turn down a pull request you spent a lot of time on. + +[issue]: https://github.com/rgrove/crass/issues/new + +Acknowledgments +--------------- + +I'm deeply, deeply grateful to [Simon Sapin][simon] for his wonderfully +comprehensive [CSS parsing tests][css-tests], which I adapted to create many of +Crass's tests. They've been invaluable in helping me fix bugs and handle weird +edge cases, and Crass would be much crappier without them. + +I'm also grateful to [Tab Atkins Jr.][tab] and Simon Sapin (again!) for their +work on the [CSS Syntax Level 3][spec] specification, which defines the +tokenizing and parsing rules that Crass implements. + +[css-tests]:https://github.com/SimonSapin/css-parsing-tests/ +[simon]:http://exyr.org/about/ +[spec]:http://www.w3.org/TR/css-syntax-3/ +[tab]:http://www.xanthir.com/contact/ diff --git a/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/Rakefile b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/Rakefile new file mode 100644 index 00000000..065d0bfe --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/Rakefile @@ -0,0 +1,21 @@ +require 'bundler' +require 'rake/testtask' + +Bundler::GemHelper.install_tasks + +Rake::TestTask.new +task :default => [:test] +task :test => :set_rubyopts + +task :set_rubyopts do + ENV['RUBYOPT'] ||= "" + ENV['RUBYOPT'] += " -w" + + if RUBY_ENGINE == "ruby" && RUBY_VERSION >= "2.3" + ENV['RUBYOPT'] += " --enable-frozen-string-literal --debug=frozen-string-literal" + end +end + +task :'pull-css-tests' do + sh 'git subtree pull -P test/css-parsing-tests https://github.com/SimonSapin/css-parsing-tests.git master --squash' +end diff --git a/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/crass.gemspec b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/crass.gemspec new file mode 100644 index 00000000..ce37e3db --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/crass.gemspec @@ -0,0 +1,31 @@ +# encoding: utf-8 +require './lib/crass/version' + +Gem::Specification.new do |s| + s.name = 'crass' + s.summary = 'CSS parser based on the CSS Syntax Level 3 spec.' + s.description = 'Crass is a pure Ruby CSS parser based on the CSS Syntax Level 3 spec.' + s.version = Crass::VERSION + s.authors = ['Ryan Grove'] + s.email = ['ryan@wonko.com'] + s.homepage = 'https://github.com/rgrove/crass/' + s.license = 'MIT' + + s.metadata = { + 'bug_tracker_uri' => 'https://github.com/rgrove/crass/issues', + 'changelog_uri' => "https://github.com/rgrove/crass/blob/v#{s.version}/HISTORY.md", + 'documentation_uri' => "https://www.rubydoc.info/gems/crass/#{s.version}", + 'source_code_uri' => "https://github.com/rgrove/crass/tree/v#{s.version}", + } + + s.platform = Gem::Platform::RUBY + s.required_ruby_version = Gem::Requirement.new('>= 1.9.2') + + s.require_paths = ['lib'] + + s.files = `git ls-files -z`.split("\x0").grep_v(%r{^test/}) + + # Development dependencies. + s.add_development_dependency 'minitest', '~> 5.0.8' + s.add_development_dependency 'rake', '~> 10.1.0' +end diff --git a/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/lib/crass.rb b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/lib/crass.rb new file mode 100644 index 00000000..5790e3b6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/lib/crass.rb @@ -0,0 +1,22 @@ +# encoding: utf-8 +require_relative 'crass/parser' + +# A CSS parser based on the CSS Syntax Module Level 3 spec. +module Crass + + # Parses _input_ as a CSS stylesheet and returns a parse tree. + # + # See {Tokenizer#initialize} for _options_. + def self.parse(input, options = {}) + Parser.parse_stylesheet(input, options) + end + + # Parses _input_ as a string of CSS properties (such as the contents of an + # HTML element's `style` attribute) and returns a parse tree. + # + # See {Tokenizer#initialize} for _options_. + def self.parse_properties(input, options = {}) + Parser.parse_properties(input, options) + end + +end diff --git a/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/lib/crass/parser.rb b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/lib/crass/parser.rb new file mode 100644 index 00000000..fa133ce9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/lib/crass/parser.rb @@ -0,0 +1,648 @@ +# encoding: utf-8 +require_relative 'token-scanner' +require_relative 'tokenizer' + +module Crass + + # Parses a CSS string or list of tokens. + # + # 5. http://dev.w3.org/csswg/css-syntax/#parsing + class Parser + BLOCK_END_TOKENS = { + :'{' => :'}', + :'[' => :']', + :'(' => :')' + } + + # -- Class Methods --------------------------------------------------------- + + # Parses CSS properties (such as the contents of an HTML element's `style` + # attribute) and returns a parse tree. + # + # See {Tokenizer#initialize} for _options_. + # + # 5.3.6. http://dev.w3.org/csswg/css-syntax/#parse-a-list-of-declarations + def self.parse_properties(input, options = {}) + Parser.new(input, options).parse_properties + end + + # Parses CSS rules (such as the content of a `@media` block) and returns a + # parse tree. The only difference from {parse_stylesheet} is that CDO/CDC + # nodes (``) aren't ignored. + # + # See {Tokenizer#initialize} for _options_. + # + # 5.3.3. http://dev.w3.org/csswg/css-syntax/#parse-a-list-of-rules + def self.parse_rules(input, options = {}) + parser = Parser.new(input, options) + rules = parser.consume_rules + + rules.map do |rule| + if rule[:node] == :qualified_rule + parser.create_style_rule(rule) + else + rule + end + end + end + + # Parses a CSS stylesheet and returns a parse tree. + # + # See {Tokenizer#initialize} for _options_. + # + # 5.3.2. http://dev.w3.org/csswg/css-syntax/#parse-a-stylesheet + def self.parse_stylesheet(input, options = {}) + parser = Parser.new(input, options) + rules = parser.consume_rules(:top_level => true) + + rules.map do |rule| + if rule[:node] == :qualified_rule + parser.create_style_rule(rule) + else + rule + end + end + end + + # Converts a node or array of nodes into a CSS string based on their + # original tokenized input. + # + # Options: + # + # * **:exclude_comments** - When `true`, comments will be excluded. + # + def self.stringify(nodes, options = {}) + nodes = [nodes] unless nodes.is_a?(Array) + string = String.new + + nodes.each do |node| + next if node.nil? + + case node[:node] + when :at_rule + string << '@' + string << node[:name] + string << self.stringify(node[:prelude], options) + + if node[:block] + string << '{' << self.stringify(node[:block], options) << '}' + else + string << ';' + end + + when :comment + string << node[:raw] unless options[:exclude_comments] + + when :simple_block + string << node[:start] + string << self.stringify(node[:value], options) + string << node[:end] + + when :style_rule + string << self.stringify(node[:selector][:tokens], options) + string << '{' << self.stringify(node[:children], options) << '}' + + else + if node.key?(:raw) + string << node[:raw] + elsif node.key?(:tokens) + string << self.stringify(node[:tokens], options) + end + end + end + + string + end + + # -- Instance Methods ------------------------------------------------------ + + # {TokenScanner} wrapping the tokens generated from this parser's input. + attr_reader :tokens + + # Initializes a parser based on the given _input_, which may be a CSS string + # or an array of tokens. + # + # See {Tokenizer#initialize} for _options_. + def initialize(input, options = {}) + unless input.kind_of?(Enumerable) + input = Tokenizer.tokenize(input, options) + end + + @tokens = TokenScanner.new(input) + end + + # Consumes an at-rule and returns it. + # + # 5.4.2. http://dev.w3.org/csswg/css-syntax-3/#consume-at-rule + def consume_at_rule(input = @tokens) + rule = {} + + rule[:tokens] = input.collect do + rule[:name] = input.consume[:value] + rule[:prelude] = [] + + while token = input.consume + node = token[:node] + + if node == :comment # Non-standard. + next + + elsif node == :semicolon + break + + elsif node === :'{' + # Note: The spec says the block should _be_ the consumed simple + # block, but Simon Sapin's CSS parsing tests and tinycss2 expect + # only the _value_ of the consumed simple block here. I assume I'm + # interpreting the spec too literally, so I'm going with the + # tinycss2 behavior. + rule[:block] = consume_simple_block(input)[:value] + break + + elsif node == :simple_block && token[:start] == '{' + # Note: The spec says the block should _be_ the simple block, but + # Simon Sapin's CSS parsing tests and tinycss2 expect only the + # _value_ of the simple block here. I assume I'm interpreting the + # spec too literally, so I'm going with the tinycss2 behavior. + rule[:block] = token[:value] + break + + else + input.reconsume + rule[:prelude] << consume_component_value(input) + end + end + end + + create_node(:at_rule, rule) + end + + # Consumes a component value and returns it, or `nil` if there are no more + # tokens. + # + # 5.4.6. http://dev.w3.org/csswg/css-syntax-3/#consume-a-component-value + def consume_component_value(input = @tokens) + return nil unless token = input.consume + + case token[:node] + when :'{', :'[', :'(' + consume_simple_block(input) + + when :function + if token.key?(:name) + # This is a parsed function, not a function token. This step isn't + # mentioned in the spec, but it's necessary to avoid re-parsing + # functions that have already been parsed. + token + else + consume_function(input) + end + + else + token + end + end + + # Consumes a declaration and returns it, or `nil` on parse error. + # + # 5.4.5. http://dev.w3.org/csswg/css-syntax-3/#consume-a-declaration + def consume_declaration(input = @tokens) + declaration = {} + value = [] + + declaration[:tokens] = input.collect do + declaration[:name] = input.consume[:value] + + next_token = input.peek + + while next_token && next_token[:node] == :whitespace + input.consume + next_token = input.peek + end + + unless next_token && next_token[:node] == :colon + # Parse error. + # + # Note: The spec explicitly says to return nothing here, but Simon + # Sapin's CSS parsing tests expect an error node. + return create_node(:error, :value => 'invalid') + end + + input.consume + + until input.peek.nil? + value << consume_component_value(input) + end + end + + # Look for !important. + important_tokens = value.reject {|token| + node = token[:node] + node == :whitespace || node == :comment || node == :semicolon + }.last(2) + + if important_tokens.size == 2 && + important_tokens[0][:node] == :delim && + important_tokens[0][:value] == '!' && + important_tokens[1][:node] == :ident && + important_tokens[1][:value].downcase == 'important' + + declaration[:important] = true + excl_index = value.index(important_tokens[0]) + + # Technically the spec doesn't require us to trim trailing tokens after + # the !important, but Simon Sapin's CSS parsing tests expect it and + # tinycss2 does it, so we'll go along with the cool kids. + value.slice!(excl_index, value.size - excl_index) + else + declaration[:important] = false + end + + declaration[:value] = value + create_node(:declaration, declaration) + end + + # Consumes a list of declarations and returns them. + # + # By default, the returned list may include `:comment`, `:semicolon`, and + # `:whitespace` nodes, which is non-standard. + # + # Options: + # + # * **:strict** - Set to `true` to exclude non-standard `:comment`, + # `:semicolon`, and `:whitespace` nodes. + # + # 5.4.4. http://dev.w3.org/csswg/css-syntax/#consume-a-list-of-declarations + def consume_declarations(input = @tokens, options = {}) + declarations = [] + + while token = input.consume + case token[:node] + + # Non-standard: Preserve comments, semicolons, and whitespace. + when :comment, :semicolon, :whitespace + declarations << token unless options[:strict] + + when :at_keyword + # When parsing a style rule, this is a parse error. Otherwise it's + # not. + input.reconsume + declarations << consume_at_rule(input) + + when :ident + decl_tokens = [token] + + while next_token = input.peek + break if next_token[:node] == :semicolon + decl_tokens << consume_component_value(input) + end + + if decl = consume_declaration(TokenScanner.new(decl_tokens)) + declarations << decl + end + + else + # Parse error (invalid property name, etc.). + # + # Note: The spec doesn't say we should append anything to the list of + # declarations here, but Simon Sapin's CSS parsing tests expect an + # error node. + declarations << create_node(:error, :value => 'invalid') + input.reconsume + + while next_token = input.peek + break if next_token[:node] == :semicolon + consume_component_value(input) + end + end + end + + declarations + end + + # Consumes a function and returns it. + # + # 5.4.8. http://dev.w3.org/csswg/css-syntax-3/#consume-a-function + def consume_function(input = @tokens) + function = { + :name => input.current[:value], + :value => [], + :tokens => [input.current] # Non-standard, used for serialization. + } + + function[:tokens].concat(input.collect { + while token = input.consume + case token[:node] + when :')' + break + + # Non-standard. + when :comment + next + + else + input.reconsume + function[:value] << consume_component_value(input) + end + end + }) + + create_node(:function, function) + end + + # Consumes a qualified rule and returns it, or `nil` if a parse error + # occurs. + # + # 5.4.3. http://dev.w3.org/csswg/css-syntax-3/#consume-a-qualified-rule + def consume_qualified_rule(input = @tokens) + rule = {:prelude => []} + + rule[:tokens] = input.collect do + while true + unless token = input.consume + # Parse error. + # + # Note: The spec explicitly says to return nothing here, but Simon + # Sapin's CSS parsing tests expect an error node. + return create_node(:error, :value => 'invalid') + end + + if token[:node] == :'{' + # Note: The spec says the block should _be_ the consumed simple + # block, but Simon Sapin's CSS parsing tests and tinycss2 expect + # only the _value_ of the consumed simple block here. I assume I'm + # interpreting the spec too literally, so I'm going with the + # tinycss2 behavior. + rule[:block] = consume_simple_block(input)[:value] + break + elsif token[:node] == :simple_block && token[:start] == '{' + # Note: The spec says the block should _be_ the simple block, but + # Simon Sapin's CSS parsing tests and tinycss2 expect only the + # _value_ of the simple block here. I assume I'm interpreting the + # spec too literally, so I'm going with the tinycss2 behavior. + rule[:block] = token[:value] + break + else + input.reconsume + rule[:prelude] << consume_component_value(input) + end + end + end + + create_node(:qualified_rule, rule) + end + + # Consumes a list of rules and returns them. + # + # 5.4.1. http://dev.w3.org/csswg/css-syntax/#consume-a-list-of-rules + def consume_rules(flags = {}) + rules = [] + + while token = @tokens.consume + case token[:node] + # Non-standard. Spec says to discard comments and whitespace, but we + # keep them so we can serialize faithfully. + when :comment, :whitespace + rules << token + + when :cdc, :cdo + unless flags[:top_level] + @tokens.reconsume + rule = consume_qualified_rule + rules << rule if rule + end + + when :at_keyword + @tokens.reconsume + rule = consume_at_rule + rules << rule if rule + + else + @tokens.reconsume + rule = consume_qualified_rule + rules << rule if rule + end + end + + rules + end + + # Consumes and returns a simple block associated with the current input + # token. + # + # 5.4.7. http://dev.w3.org/csswg/css-syntax/#consume-a-simple-block + def consume_simple_block(input = @tokens) + start_token = input.current[:node] + end_token = BLOCK_END_TOKENS[start_token] + + block = { + :start => start_token.to_s, + :end => end_token.to_s, + :value => [], + :tokens => [input.current] # Non-standard. Used for serialization. + } + + block[:tokens].concat(input.collect do + while token = input.consume + break if token[:node] == end_token + + input.reconsume + block[:value] << consume_component_value(input) + end + end) + + create_node(:simple_block, block) + end + + # Creates and returns a new parse node with the given _properties_. + def create_node(type, properties = {}) + {:node => type}.merge!(properties) + end + + # Parses the given _input_ tokens into a selector node and returns it. + # + # Doesn't bother splitting the selector list into individual selectors or + # validating them. Feel free to do that yourself! It'll be fun! + def create_selector(input) + create_node(:selector, + :value => parse_value(input), + :tokens => input) + end + + # Creates a `:style_rule` node from the given qualified _rule_, and returns + # it. + def create_style_rule(rule) + create_node(:style_rule, + :selector => create_selector(rule[:prelude]), + :children => parse_properties(rule[:block])) + end + + # Parses a single component value and returns it. + # + # 5.3.7. http://dev.w3.org/csswg/css-syntax-3/#parse-a-component-value + def parse_component_value(input = @tokens) + input = TokenScanner.new(input) unless input.is_a?(TokenScanner) + + while input.peek && input.peek[:node] == :whitespace + input.consume + end + + if input.peek.nil? + return create_node(:error, :value => 'empty') + end + + value = consume_component_value(input) + + while input.peek && input.peek[:node] == :whitespace + input.consume + end + + if input.peek.nil? + value + else + create_node(:error, :value => 'extra-input') + end + end + + # Parses a list of component values and returns an array of parsed tokens. + # + # 5.3.8. http://dev.w3.org/csswg/css-syntax/#parse-a-list-of-component-values + def parse_component_values(input = @tokens) + input = TokenScanner.new(input) unless input.is_a?(TokenScanner) + tokens = [] + + while token = consume_component_value(input) + tokens << token + end + + tokens + end + + # Parses a single declaration and returns it. + # + # 5.3.5. http://dev.w3.org/csswg/css-syntax/#parse-a-declaration + def parse_declaration(input = @tokens) + input = TokenScanner.new(input) unless input.is_a?(TokenScanner) + + while input.peek && input.peek[:node] == :whitespace + input.consume + end + + if input.peek.nil? + # Syntax error. + return create_node(:error, :value => 'empty') + elsif input.peek[:node] != :ident + # Syntax error. + return create_node(:error, :value => 'invalid') + end + + if decl = consume_declaration(input) + return decl + end + + # Syntax error. + create_node(:error, :value => 'invalid') + end + + # Parses a list of declarations and returns them. + # + # See {#consume_declarations} for _options_. + # + # 5.3.6. http://dev.w3.org/csswg/css-syntax/#parse-a-list-of-declarations + def parse_declarations(input = @tokens, options = {}) + input = TokenScanner.new(input) unless input.is_a?(TokenScanner) + consume_declarations(input, options) + end + + # Parses a list of declarations and returns an array of `:property` nodes + # (and any non-declaration nodes that were in the input). This is useful for + # parsing the contents of an HTML element's `style` attribute. + def parse_properties(input = @tokens) + properties = [] + + parse_declarations(input).each do |decl| + unless decl[:node] == :declaration + properties << decl + next + end + + children = decl[:value].dup + children.pop if children.last && children.last[:node] == :semicolon + + properties << create_node(:property, + :name => decl[:name], + :value => parse_value(decl[:value]), + :children => children, + :important => decl[:important], + :tokens => decl[:tokens]) + end + + properties + end + + # Parses a single rule and returns it. + # + # 5.3.4. http://dev.w3.org/csswg/css-syntax-3/#parse-a-rule + def parse_rule(input = @tokens) + input = TokenScanner.new(input) unless input.is_a?(TokenScanner) + + while input.peek && input.peek[:node] == :whitespace + input.consume + end + + if input.peek.nil? + # Syntax error. + return create_node(:error, :value => 'empty') + elsif input.peek[:node] == :at_keyword + rule = consume_at_rule(input) + else + rule = consume_qualified_rule(input) + end + + while input.peek && input.peek[:node] == :whitespace + input.consume + end + + if input.peek.nil? + rule + else + # Syntax error. + create_node(:error, :value => 'extra-input') + end + end + + # Returns the unescaped value of a selector name or property declaration. + def parse_value(nodes) + nodes = [nodes] unless nodes.is_a?(Array) + string = String.new + + nodes.each do |node| + case node[:node] + when :comment, :semicolon + next + + when :at_keyword, :ident + string << node[:value] + + when :function + if node[:value].is_a?(String) + string << node[:value] + string << '(' + else + string << parse_value(node[:tokens]) + end + + else + if node.key?(:raw) + string << node[:raw] + elsif node.key?(:tokens) + string << parse_value(node[:tokens]) + end + end + end + + string.strip + end + end + +end diff --git a/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/lib/crass/scanner.rb b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/lib/crass/scanner.rb new file mode 100644 index 00000000..d66bede6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/lib/crass/scanner.rb @@ -0,0 +1,125 @@ +# encoding: utf-8 +require 'strscan' + +module Crass + + # Similar to a StringScanner, but with extra functionality needed to tokenize + # CSS while preserving the original text. + class Scanner + # Current character, or `nil` if the scanner hasn't yet consumed a + # character, or is at the end of the string. + attr_reader :current + + # Current marker position. Use {#marked} to get the substring between + # {#marker} and {#pos}. + attr_accessor :marker + + # Position of the next character that will be consumed. This is a character + # position, not a byte position, so it accounts for multi-byte characters. + attr_accessor :pos + + # String being scanned. + attr_reader :string + + # Creates a Scanner instance for the given _input_ string or IO instance. + def initialize(input) + @string = input.is_a?(IO) ? input.read : input.to_s + @scanner = StringScanner.new(@string) + + reset + end + + # Consumes the next character and returns it, advancing the pointer, or + # an empty string if the end of the string has been reached. + def consume + if @pos < @len + @pos += 1 + @current = @scanner.getch + else + '' + end + end + + # Consumes the rest of the string and returns it, advancing the pointer to + # the end of the string. Returns an empty string is the end of the string + # has already been reached. + def consume_rest + result = @scanner.rest + + @current = result[-1] + @pos = @len + + result + end + + # Returns `true` if the end of the string has been reached, `false` + # otherwise. + def eos? + @pos == @len + end + + # Sets the marker to the position of the next character that will be + # consumed. + def mark + @marker = @pos + end + + # Returns the substring between {#marker} and {#pos}, without altering the + # pointer. + def marked + if result = @string[@marker, @pos - @marker] + result + else + '' + end + end + + # Returns up to _length_ characters starting at the current position, but + # doesn't consume them. The number of characters returned may be less than + # _length_ if the end of the string is reached. + def peek(length = 1) + @string[pos, length] + end + + # Moves the pointer back one character without changing the value of + # {#current}. The next call to {#consume} will re-consume the current + # character. + def reconsume + @scanner.unscan + @pos -= 1 if @pos > 0 + end + + # Resets the pointer to the beginning of the string. + def reset + @current = nil + @len = @string.size + @marker = 0 + @pos = 0 + end + + # Tries to match _pattern_ at the current position. If it matches, the + # matched substring will be returned and the pointer will be advanced. + # Otherwise, `nil` will be returned. + def scan(pattern) + if match = @scanner.scan(pattern) + @pos += match.size + @current = match[-1] + end + + match + end + + # Scans the string until the _pattern_ is matched. Returns the substring up + # to and including the end of the match, and advances the pointer. If there + # is no match, `nil` is returned and the pointer is not advanced. + def scan_until(pattern) + if match = @scanner.scan_until(pattern) + @pos += match.size + @current = match[-1] + end + + match + end + end + +end diff --git a/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/lib/crass/token-scanner.rb b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/lib/crass/token-scanner.rb new file mode 100644 index 00000000..fe5e258c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/lib/crass/token-scanner.rb @@ -0,0 +1,50 @@ +# encoding: utf-8 + +module Crass + + # Like {Scanner}, but for tokens! + class TokenScanner + attr_reader :current, :pos, :tokens + + def initialize(tokens) + @tokens = tokens.to_a + reset + end + + # Executes the given block, collects all tokens that are consumed during its + # execution, and returns them. + def collect + start = @pos + yield + @tokens[start...@pos] || [] + end + + # Consumes the next token and returns it, advancing the pointer. Returns + # `nil` if there is no next token. + def consume + @current = @tokens[@pos] + @pos += 1 if @current + @current + end + + # Returns the next token without consuming it, or `nil` if there is no next + # token. + def peek + @tokens[@pos] + end + + # Reconsumes the current token, moving the pointer back one position. + # + # http://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#reconsume-the-current-input-token + def reconsume + @pos -= 1 if @pos > 0 + end + + # Resets the pointer to the first token in the list. + def reset + @current = nil + @pos = 0 + end + end + +end diff --git a/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/lib/crass/tokenizer.rb b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/lib/crass/tokenizer.rb new file mode 100644 index 00000000..d6ab9a1b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/lib/crass/tokenizer.rb @@ -0,0 +1,708 @@ +# encoding: utf-8 +require_relative 'scanner' + +module Crass + + # Tokenizes a CSS string. + # + # 4. http://dev.w3.org/csswg/css-syntax/#tokenization + class Tokenizer + RE_COMMENT_CLOSE = /\*\// + RE_DIGIT = /[0-9]+/ + RE_ESCAPE = /\\[^\n]/ + RE_HEX = /[0-9A-Fa-f]{1,6}/ + RE_NAME = /[0-9A-Za-z_\u0080-\u{10ffff}-]+/ + RE_NAME_START = /[A-Za-z_\u0080-\u{10ffff}]+/ + RE_NON_PRINTABLE = /[\u0000-\u0008\u000b\u000e-\u001f\u007f]+/ + RE_NUMBER_DECIMAL = /\.[0-9]+/ + RE_NUMBER_EXPONENT = /[Ee][+-]?[0-9]+/ + RE_NUMBER_SIGN = /[+-]/ + + RE_NUMBER_STR = /\A + (? [+-]?) + (? [0-9]*) + (?:\. + (? [0-9]*) + )? + (?:[Ee] + (? [+-]?) + (? [0-9]*) + )? + \z/x + + RE_QUOTED_URL_START = /\A[\n\u0009\u0020]?["']/ + RE_UNICODE_RANGE_START = /\+(?:[0-9A-Fa-f]|\?)/ + RE_UNICODE_RANGE_END = /-[0-9A-Fa-f]/ + RE_WHITESPACE = /[\n\u0009\u0020]+/ + RE_WHITESPACE_ANCHORED = /\A[\n\u0009\u0020]+\z/ + + # -- Class Methods --------------------------------------------------------- + + # Tokenizes the given _input_ as a CSS string and returns an array of + # tokens. + # + # See {#initialize} for _options_. + def self.tokenize(input, options = {}) + Tokenizer.new(input, options).tokenize + end + + # -- Instance Methods ------------------------------------------------------ + + # Initializes a new Tokenizer. + # + # Options: + # + # * **:preserve_comments** - If `true`, comments will be preserved as + # `:comment` tokens. + # + # * **:preserve_hacks** - If `true`, certain non-standard browser hacks + # such as the IE "*" hack will be preserved even though they violate + # CSS 3 syntax rules. + # + def initialize(input, options = {}) + @s = Scanner.new(preprocess(input)) + @options = options + end + + # Consumes a token and returns the token that was consumed. + # + # 4.3.1. http://dev.w3.org/csswg/css-syntax/#consume-a-token + def consume + return nil if @s.eos? + + @s.mark + + # Consume comments. + if comment_token = consume_comments + if @options[:preserve_comments] + return comment_token + else + return consume + end + end + + # Consume whitespace. + return create_token(:whitespace) if @s.scan(RE_WHITESPACE) + + char = @s.consume + + case char.to_sym + when :'"' + consume_string + + when :'#' + if @s.peek =~ RE_NAME || valid_escape?(@s.peek(2)) + create_token(:hash, + :type => start_identifier?(@s.peek(3)) ? :id : :unrestricted, + :value => consume_name) + else + create_token(:delim, :value => char) + end + + when :'$' + if @s.peek == '=' + @s.consume + create_token(:suffix_match) + else + create_token(:delim, :value => char) + end + + when :"'" + consume_string + + when :'(' + create_token(:'(') + + when :')' + create_token(:')') + + when :* + if @s.peek == '=' + @s.consume + create_token(:substring_match) + + # Non-standard: Preserve the IE * hack. + elsif @options[:preserve_hacks] && @s.peek =~ RE_NAME_START + @s.reconsume + consume_ident + + else + create_token(:delim, :value => char) + end + + when :+ + if start_number? + @s.reconsume + consume_numeric + else + create_token(:delim, :value => char) + end + + when :',' + create_token(:comma) + + when :- + nextTwoChars = @s.peek(2) + nextThreeChars = char + nextTwoChars + + if start_number?(nextThreeChars) + @s.reconsume + consume_numeric + elsif nextTwoChars == '->' + @s.consume + @s.consume + create_token(:cdc) + elsif start_identifier?(nextThreeChars) + @s.reconsume + consume_ident + else + create_token(:delim, :value => char) + end + + when :'.' + if start_number? + @s.reconsume + consume_numeric + else + create_token(:delim, :value => char) + end + + when :':' + create_token(:colon) + + when :';' + create_token(:semicolon) + + when :< + if @s.peek(3) == '!--' + @s.consume + @s.consume + @s.consume + + create_token(:cdo) + else + create_token(:delim, :value => char) + end + + when :'@' + if start_identifier?(@s.peek(3)) + create_token(:at_keyword, :value => consume_name) + else + create_token(:delim, :value => char) + end + + when :'[' + create_token(:'[') + + when :'\\' + if valid_escape? + @s.reconsume + consume_ident + else + # Parse error. + create_token(:delim, + :error => true, + :value => char) + end + + when :']' + create_token(:']') + + when :'^' + if @s.peek == '=' + @s.consume + create_token(:prefix_match) + else + create_token(:delim, :value => char) + end + + when :'{' + create_token(:'{') + + when :'}' + create_token(:'}') + + when :U, :u + if @s.peek(2) =~ RE_UNICODE_RANGE_START + @s.consume + consume_unicode_range + else + @s.reconsume + consume_ident + end + + when :| + case @s.peek + when '=' + @s.consume + create_token(:dash_match) + + when '|' + @s.consume + create_token(:column) + + else + create_token(:delim, :value => char) + end + + when :~ + if @s.peek == '=' + @s.consume + create_token(:include_match) + else + create_token(:delim, :value => char) + end + + else + case char + when RE_DIGIT + @s.reconsume + consume_numeric + + when RE_NAME_START + @s.reconsume + consume_ident + + else + create_token(:delim, :value => char) + end + end + end + + # Consumes the remnants of a bad URL and returns the consumed text. + # + # 4.3.15. http://dev.w3.org/csswg/css-syntax/#consume-the-remnants-of-a-bad-url + def consume_bad_url + text = String.new + + until @s.eos? + if valid_escape? + text << consume_escaped + elsif valid_escape?(@s.peek(2)) + @s.consume + text << consume_escaped + else + char = @s.consume + + if char == ')' + break + else + text << char + end + end + end + + text + end + + # Consumes comments and returns them, or `nil` if no comments were consumed. + # + # 4.3.2. http://dev.w3.org/csswg/css-syntax/#consume-comments + def consume_comments + if @s.peek(2) == '/*' + @s.consume + @s.consume + + if text = @s.scan_until(RE_COMMENT_CLOSE) + text.slice!(-2, 2) + else + # Parse error. + text = @s.consume_rest + end + + return create_token(:comment, :value => text) + end + + nil + end + + # Consumes an escaped code point and returns its unescaped value. + # + # This method assumes that the `\` has already been consumed, and that the + # next character in the input has already been verified not to be a newline + # or EOF. + # + # 4.3.8. http://dev.w3.org/csswg/css-syntax/#consume-an-escaped-code-point + def consume_escaped + return "\ufffd" if @s.eos? + + if hex_str = @s.scan(RE_HEX) + @s.consume if @s.peek =~ RE_WHITESPACE + + codepoint = hex_str.hex + + if codepoint == 0 || + codepoint.between?(0xD800, 0xDFFF) || + codepoint > 0x10FFFF + + return "\ufffd" + else + return codepoint.chr(Encoding::UTF_8) + end + end + + @s.consume + end + + # Consumes an ident-like token and returns it. + # + # 4.3.4. http://dev.w3.org/csswg/css-syntax/#consume-an-ident-like-token + def consume_ident + value = consume_name + + if @s.peek == '(' + @s.consume + + if value.downcase == 'url' + @s.consume while @s.peek(2) =~ RE_WHITESPACE_ANCHORED + + if @s.peek(2) =~ RE_QUOTED_URL_START + create_token(:function, :value => value) + else + consume_url + end + else + create_token(:function, :value => value) + end + else + create_token(:ident, :value => value) + end + end + + # Consumes a name and returns it. + # + # 4.3.12. http://dev.w3.org/csswg/css-syntax/#consume-a-name + def consume_name + result = String.new + + until @s.eos? + if match = @s.scan(RE_NAME) + result << match + next + end + + char = @s.consume + + if valid_escape? + result << consume_escaped + + # Non-standard: IE * hack + elsif char == '*' && @options[:preserve_hacks] + result << @s.consume + + else + @s.reconsume + return result + end + end + + result + end + + # Consumes a number and returns a 3-element array containing the number's + # original representation, its numeric value, and its type (either + # `:integer` or `:number`). + # + # 4.3.13. http://dev.w3.org/csswg/css-syntax/#consume-a-number + def consume_number + repr = String.new + type = :integer + + repr << @s.consume if @s.peek =~ RE_NUMBER_SIGN + repr << (@s.scan(RE_DIGIT) || '') + + if match = @s.scan(RE_NUMBER_DECIMAL) + repr << match + type = :number + end + + if match = @s.scan(RE_NUMBER_EXPONENT) + repr << match + type = :number + end + + [repr, convert_string_to_number(repr), type] + end + + # Consumes a numeric token and returns it. + # + # 4.3.3. http://dev.w3.org/csswg/css-syntax/#consume-a-numeric-token + def consume_numeric + number = consume_number + repr = number[0] + value = number[1] + type = number[2] + + if type == :integer + value = value.to_i + else + value = value.to_f + end + + if start_identifier?(@s.peek(3)) + create_token(:dimension, + :repr => repr, + :type => type, + :unit => consume_name, + :value => value) + + elsif @s.peek == '%' + @s.consume + + create_token(:percentage, + :repr => repr, + :type => type, + :value => value) + + else + create_token(:number, + :repr => repr, + :type => type, + :value => value) + end + end + + # Consumes a string token that ends at the given character, and returns the + # token. + # + # 4.3.5. http://dev.w3.org/csswg/css-syntax/#consume-a-string-token + def consume_string(ending = nil) + ending = @s.current if ending.nil? + value = String.new + + until @s.eos? + case char = @s.consume + when ending + break + + when "\n" + # Parse error. + @s.reconsume + return create_token(:bad_string, + :error => true, + :value => value) + + when '\\' + case @s.peek + when '' + # End of the input, so do nothing. + next + + when "\n" + @s.consume + + else + value << consume_escaped + end + + else + value << char + end + end + + create_token(:string, :value => value) + end + + # Consumes a Unicode range token and returns it. Assumes the initial "u+" or + # "U+" has already been consumed. + # + # 4.3.7. http://dev.w3.org/csswg/css-syntax/#consume-a-unicode-range-token + def consume_unicode_range + value = @s.scan(RE_HEX) || String.new + + while value.length < 6 + break unless @s.peek == '?' + value << @s.consume + end + + range = {} + + if value.include?('?') + range[:start] = value.gsub('?', '0').hex + range[:end] = value.gsub('?', 'F').hex + return create_token(:unicode_range, range) + end + + range[:start] = value.hex + + if @s.peek(2) =~ RE_UNICODE_RANGE_END + @s.consume + range[:end] = (@s.scan(RE_HEX) || '').hex + else + range[:end] = range[:start] + end + + create_token(:unicode_range, range) + end + + # Consumes a URL token and returns it. Assumes the original "url(" has + # already been consumed. + # + # 4.3.6. http://dev.w3.org/csswg/css-syntax/#consume-a-url-token + def consume_url + value = String.new + + @s.scan(RE_WHITESPACE) + + until @s.eos? + case char = @s.consume + when ')' + break + + when RE_WHITESPACE + @s.scan(RE_WHITESPACE) + + if @s.eos? || @s.peek == ')' + @s.consume + break + else + return create_token(:bad_url, :value => value + consume_bad_url) + end + + when '"', "'", '(', RE_NON_PRINTABLE + # Parse error. + return create_token(:bad_url, + :error => true, + :value => value + consume_bad_url) + + when '\\' + if valid_escape? + value << consume_escaped + else + # Parse error. + return create_token(:bad_url, + :error => true, + :value => value + consume_bad_url + ) + end + + else + value << char + end + end + + create_token(:url, :value => value) + end + + # Converts a valid CSS number string into a number and returns the number. + # + # 4.3.14. http://dev.w3.org/csswg/css-syntax/#convert-a-string-to-a-number + def convert_string_to_number(str) + matches = RE_NUMBER_STR.match(str) + + s = matches[:sign] == '-' ? -1 : 1 + i = matches[:integer].to_i + f = matches[:fractional].to_i + d = matches[:fractional] ? matches[:fractional].length : 0 + t = matches[:exponent_sign] == '-' ? -1 : 1 + e = matches[:exponent].to_i + + # I know this formula looks nutty, but it's exactly what's defined in the + # spec, and it works. + value = s * (i + f * 10**-d) * 10**(t * e) + + # Maximum and minimum values aren't defined in the spec, but are enforced + # here for sanity. + if value > Float::MAX + value = Float::MAX + elsif value < -Float::MAX + value = -Float::MAX + end + + value + end + + # Creates and returns a new token with the given _properties_. + def create_token(type, properties = {}) + { + :node => type, + :pos => @s.marker, + :raw => @s.marked + }.merge!(properties) + end + + # Preprocesses _input_ to prepare it for the tokenizer. + # + # 3.3. http://dev.w3.org/csswg/css-syntax/#input-preprocessing + def preprocess(input) + input = input.to_s.encode('UTF-8', + :invalid => :replace, + :undef => :replace) + + input.gsub!(/(?:\r\n|[\r\f])/, "\n") + input.gsub!("\u0000", "\ufffd") + input + end + + # Returns `true` if the given three-character _text_ would start an + # identifier. If _text_ is `nil`, the current and next two characters in the + # input stream will be checked, but will not be consumed. + # + # 4.3.10. http://dev.w3.org/csswg/css-syntax/#would-start-an-identifier + def start_identifier?(text = nil) + text = @s.current + @s.peek(2) if text.nil? + + case text[0] + when '-' + nextChar = text[1] + !!(nextChar == '-' || nextChar =~ RE_NAME_START || valid_escape?(text[1, 2])) + + when RE_NAME_START + true + + when '\\' + valid_escape?(text[0, 2]) + + else + false + end + end + + # Returns `true` if the given three-character _text_ would start a number. + # If _text_ is `nil`, the current and next two characters in the input + # stream will be checked, but will not be consumed. + # + # 4.3.11. http://dev.w3.org/csswg/css-syntax/#starts-with-a-number + def start_number?(text = nil) + text = @s.current + @s.peek(2) if text.nil? + + case text[0] + when '+', '-' + !!(text[1] =~ RE_DIGIT || (text[1] == '.' && text[2] =~ RE_DIGIT)) + + when '.' + !!(text[1] =~ RE_DIGIT) + + when RE_DIGIT + true + + else + false + end + end + + # Tokenizes the input stream and returns an array of tokens. + def tokenize + @s.reset + + tokens = [] + + while token = consume + tokens << token + end + + tokens + end + + # Returns `true` if the given two-character _text_ is the beginning of a + # valid escape sequence. If _text_ is `nil`, the current and next character + # in the input stream will be checked, but will not be consumed. + # + # 4.3.9. http://dev.w3.org/csswg/css-syntax/#starts-with-a-valid-escape + def valid_escape?(text = nil) + text = @s.current + @s.peek if text.nil? + !!(text[0] == '\\' && text[1] != "\n") + end + end + +end diff --git a/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/lib/crass/version.rb b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/lib/crass/version.rb new file mode 100644 index 00000000..0dce2d21 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/crass-1.0.6/lib/crass/version.rb @@ -0,0 +1,5 @@ +# encoding: utf-8 + +module Crass + VERSION = '1.0.6' +end diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/LICENSE.txt b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/LICENSE.txt new file mode 100644 index 00000000..07b1b7fe --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/LICENSE.txt @@ -0,0 +1,33 @@ +Copyright (C) 2005-2016 James Edward Gray II. All rights reserved. +Copyright (C) 2007-2017 Yukihiro Matsumoto. All rights reserved. +Copyright (C) 2017 SHIBATA Hiroshi. All rights reserved. +Copyright (C) 2017 Olivier Lacan. All rights reserved. +Copyright (C) 2017 Espartaco Palma. All rights reserved. +Copyright (C) 2017 Marcus Stollsteimer. All rights reserved. +Copyright (C) 2017 pavel. All rights reserved. +Copyright (C) 2017-2018 Steven Daniels. All rights reserved. +Copyright (C) 2018 Tomohiro Ogoke. All rights reserved. +Copyright (C) 2018 Kouhei Sutou. All rights reserved. +Copyright (C) 2018 Mitsutaka Mimura. All rights reserved. +Copyright (C) 2018 Vladislav. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/NEWS.md b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/NEWS.md new file mode 100644 index 00000000..58e44322 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/NEWS.md @@ -0,0 +1,1009 @@ +# News + +## 3.3.5 - 2025-06-01 + +### Improvements + + * docs: Fixed `StringScanner` document URL. + * GH-343 + * Patch by Petrik de Heus + +### Thanks + + * Petrik de Heus + +## 3.3.4 - 2025-04-13 + +### Improvements + + * `csv-filter`: Removed an experimental command line tool. + * GH-341 + +## 3.3.3 - 2025-03-20 + +### Improvements + + * `csv-filter`: Added an experimental command line tool to filter a CSV. + * Patch by Burdette Lamar + +### Fixes + + * Fixed wrong EOF detection for `ARGF` + * GH-328 + * Reported by Takeshi Nishimatsu + + * Fixed a regression bug that `CSV.open` rejects integer mode. + * GH-336 + * Reported by Dave Burgess + +### Thanks + + * Takeshi Nishimatsu + + * Burdette Lamar + + * Dave Burgess + +## 3.3.2 - 2024-12-21 + +### Fixes + + * Fixed a parse bug with a quoted line with `col_sep` and an empty + line. This was introduced in 3.3.1. + * GH-324 + * Reported by stoodfarback + +### Thanks + + * stoodfarback + +## 3.3.1 - 2024-12-15 + +### Improvements + + * `CSV.open`: Changed to detect BOM by default. Note that this isn't + enabled on Windows because Ruby may have a bug. See also: + https://bugs.ruby-lang.org/issues/20526 + * GH-301 + * Reported by Junichi Ito + + * Improved performance. + * GH-311 + * GH-312 + * Patch by Vladimir Kochnev + + * `CSV.open`: Added support for `StringIO` as an input. + * GH-300 + * GH-302 + * Patch by Marcelo + + * Added a built-in time converter. You can use it by `converters: + :time`. + * GH-313 + * Patch by Bart de Water + + * Added `CSV::TSV` for tab-separated values. + * GH-272 + * GH-319 + * Reported by kojix2 + * Patch by Jas + +### Thanks + + * Junichi Ito + + * Vladimir Kochnev + + * Marcelo + + * Bart de Water + + * kojix2 + + * Jas + +## 3.3.0 - 2024-03-22 + +### Fixes + + * Fixed a regression parse bug in 3.2.9 that parsing with + `:skip_lines` may cause wrong result. + +## 3.2.9 - 2024-03-22 + +### Fixes + + * Fixed a parse bug that wrong result may be happen when: + + * `:skip_lines` is used + * `:row_separator` is `"\r\n"` + * There is a line that includes `\n` as a column value + + Reported by Ryo Tsukamoto. + + GH-296 + +### Thanks + + * Ryo Tsukamoto + +## 3.2.8 - 2023-11-08 + +### Improvements + + * Added `CSV::InvalidEncodingError`. + + Patch by Kosuke Shibata. + + GH-287 + +### Thanks + + * Kosuke Shibata + +## 3.2.7 - 2023-06-26 + +### Improvements + + * Removed an unused internal variable. + [GH-273](https://github.com/ruby/csv/issues/273) + [Patch by Mau Magnaguagno] + + * Changed to use `https://` instead of `http://` in documents. + [GH-274](https://github.com/ruby/csv/issues/274) + [Patch by Vivek Bharath Akupatni] + + * Added prefix to a helper module in test. + [GH-278](https://github.com/ruby/csv/issues/278) + [Patch by Luke Gruber] + + * Added a documentation for `liberal_parsing: {backslash_quotes: true}`. + [GH-280](https://github.com/ruby/csv/issues/280) + [Patch by Mark Schneider] + +### Fixes + + * Fixed a wrong execution result in documents. + [GH-276](https://github.com/ruby/csv/issues/276) + [Patch by Yuki Tsujimoto] + + * Fixed a bug that the same line is used multiple times. + [GH-279](https://github.com/ruby/csv/issues/279) + [Reported by Gabriel Nagy] + +### Thanks + + * Mau Magnaguagno + + * Vivek Bharath Akupatni + + * Yuki Tsujimoto + + * Luke Gruber + + * Mark Schneider + + * Gabriel Nagy + +## 3.2.6 - 2022-12-08 + +### Improvements + + * `CSV#read` consumes the same lines with other methods like + `CSV#shift`. + [[GitHub#258](https://github.com/ruby/csv/issues/258)] + [Reported by Lhoussaine Ghallou] + + * All `Enumerable` based methods consume the same lines with other + methods. This may have a performance penalty. + [[GitHub#260](https://github.com/ruby/csv/issues/260)] + [Reported by Lhoussaine Ghallou] + + * Simplify some implementations. + [[GitHub#262](https://github.com/ruby/csv/pull/262)] + [[GitHub#263](https://github.com/ruby/csv/pull/263)] + [Patch by Mau Magnaguagno] + +### Fixes + + * Fixed `CSV.generate_lines` document. + [[GitHub#257](https://github.com/ruby/csv/pull/257)] + [Patch by Sampat Badhe] + +### Thanks + + * Sampat Badhe + + * Lhoussaine Ghallou + + * Mau Magnaguagno + +## 3.2.5 - 2022-08-26 + +### Improvements + + * Added `CSV.generate_lines`. + [[GitHub#255](https://github.com/ruby/csv/issues/255)] + [Reported by OKURA Masafumi] + [[GitHub#256](https://github.com/ruby/csv/pull/256)] + [Patch by Eriko Sugiyama] + +### Thanks + + * OKURA Masafumi + + * Eriko Sugiyama + +## 3.2.4 - 2022-08-22 + +### Improvements + + * Cleaned up internal implementations. + [[GitHub#249](https://github.com/ruby/csv/pull/249)] + [[GitHub#250](https://github.com/ruby/csv/pull/250)] + [[GitHub#251](https://github.com/ruby/csv/pull/251)] + [Patch by Mau Magnaguagno] + + * Added support for RFC 3339 style time. + [[GitHub#248](https://github.com/ruby/csv/pull/248)] + [Patch by Thierry Lambert] + + * Added support for transcoding String CSV. Syntax is + `from-encoding:to-encoding`. + [[GitHub#254](https://github.com/ruby/csv/issues/254)] + [Reported by Richard Stueven] + + * Added quoted information to `CSV::FieldInfo`. + [[GitHub#254](https://github.com/ruby/csv/pull/253)] + [Reported by Hirokazu SUZUKI] + +### Fixes + + * Fixed a link in documents. + [[GitHub#244](https://github.com/ruby/csv/pull/244)] + [Patch by Peter Zhu] + +### Thanks + + * Peter Zhu + + * Mau Magnaguagno + + * Thierry Lambert + + * Richard Stueven + + * Hirokazu SUZUKI + +## 3.2.3 - 2022-04-09 + +### Improvements + + * Added contents summary to `CSV::Table#inspect`. + [GitHub#229][Patch by Eriko Sugiyama] + [GitHub#235][Patch by Sampat Badhe] + + * Suppressed `$INPUT_RECORD_SEPARATOR` deprecation warning by + `Warning.warn`. + [GitHub#233][Reported by Jean byroot Boussier] + + * Improved error message for liberal parsing with quoted values. + [GitHub#231][Patch by Nikolay Rys] + + * Fixed typos in documentation. + [GitHub#236][Patch by Sampat Badhe] + + * Added `:max_field_size` option and deprecated `:field_size_limit` option. + [GitHub#238][Reported by Dan Buettner] + + * Added `:symbol_raw` to built-in header converters. + [GitHub#237][Reported by taki] + [GitHub#239][Patch by Eriko Sugiyama] + +### Fixes + + * Fixed a bug that some texts may be dropped unexpectedly. + [Bug #18245][ruby-core:105587][Reported by Hassan Abdul Rehman] + + * Fixed a bug that `:field_size_limit` doesn't work with not complex row. + [GitHub#238][Reported by Dan Buettner] + +### Thanks + + * Hassan Abdul Rehman + + * Eriko Sugiyama + + * Jean byroot Boussier + + * Nikolay Rys + + * Sampat Badhe + + * Dan Buettner + + * taki + +## 3.2.2 - 2021-12-24 + +### Improvements + + * Added a validation for invalid option combination. + [GitHub#225][Patch by adamroyjones] + + * Improved documentation for developers. + [GitHub#227][Patch by Eriko Sugiyama] + +### Fixes + + * Fixed a bug that all of `ARGF` contents may not be consumed. + [GitHub#228][Reported by Rafael Navaza] + +### Thanks + + * adamroyjones + + * Eriko Sugiyama + + * Rafael Navaza + +## 3.2.1 - 2021-10-23 + +### Improvements + + * doc: Fixed wrong class name. + [GitHub#217][Patch by Vince] + + * Changed to always use `"\n"` for the default row separator on Ruby + 3.0 or later because `$INPUT_RECORD_SEPARATOR` was deprecated + since Ruby 3.0. + + * Added support for Ractor. + [GitHub#218][Patch by rm155] + + * Users who want to use the built-in converters in non-main + Ractors need to call `Ractor.make_shareable(CSV::Converters)` + and/or `Ractor.make_shareable(CSV::HeaderConverters)` before + creating non-main Ractors. + +### Thanks + + * Vince + + * Joakim Antman + + * rm155 + +## 3.2.0 - 2021-06-06 + +### Improvements + + * `CSV.open`: Added support for `:newline` option. + [GitHub#198][Patch by Nobuyoshi Nakada] + + * `CSV::Table#each`: Added support for column mode with duplicated + headers. + [GitHub#206][Reported by Yaroslav Berezovskiy] + + * `Object#CSV`: Added support for Ruby 3.0. + + * `CSV::Row`: Added support for pattern matching. + [GitHub#207][Patch by Kevin Newton] + +### Fixes + + * Fixed typos in documentation. + [GitHub#196][GitHub#205][Patch by Sampat Badhe] + +### Thanks + + * Sampat Badhe + + * Nobuyoshi Nakada + + * Yaroslav Berezovskiy + + * Kevin Newton + +## 3.1.9 - 2020-11-23 + +### Fixes + + * Fixed a compatibility bug that the line to be processed by + `skip_lines:` has a row separator. + [GitHub#194][Reported by Josef Šimánek] + +### Thanks + + * Josef Šimánek + +## 3.1.8 - 2020-11-18 + +### Improvements + + * Improved documentation. + [Patch by Burdette Lamar] + +### Thanks + + * Burdette Lamar + +## 3.1.7 - 2020-08-04 + +### Improvements + + * Improved document. + [GitHub#158][GitHub#160][GitHub#161] + [Patch by Burdette Lamar] + + * Updated required Ruby version to 2.5.0 or later. + [GitHub#159] + [Patch by Gabriel Nagy] + + * Removed stringio 0.1.3 or later dependency. + +### Thanks + + * Burdette Lamar + + * Gabriel Nagy + +## 3.1.6 - 2020-07-20 + +### Improvements + + * Improved document. + [GitHub#127][GitHub#135][GitHub#136][GitHub#137][GitHub#139][GitHub#140] + [GitHub#141][GitHub#142][GitHub#143][GitHub#145][GitHub#146][GitHub#148] + [GitHub#148][GitHub#151][GitHub#152][GitHub#154][GitHub#155][GitHub#157] + [Patch by Burdette Lamar] + + * `CSV.open`: Added support for `undef: :replace`. + [GitHub#129][Patch by Koichi ITO] + + * `CSV.open`: Added support for `invalid: :replace`. + [GitHub#129][Patch by Koichi ITO] + + * Don't run quotable check for invalid encoding field values. + [GitHub#131][Patch by Koichi ITO] + + * Added support for specifying the target indexes and names to + `force_quotes:`. + [GitHub#153][Reported by Aleksandr] + + * `CSV.generate`: Changed to use the encoding of the first non-ASCII + field rather than the encoding of ASCII only field. + + * Changed to require the stringio gem 0.1.3 or later. + +### Thanks + + * Burdette Lamar + + * Koichi ITO + + * Aleksandr + +## 3.1.5 - 2020-05-18 + +### Improvements + + * Improved document. + [GitHub#124][Patch by Burdette Lamar] + +### Fixes + + * Added missing document files. + [GitHub#125][Reported by joast] + +### Thanks + + * Burdette Lamar + + * joast + +## 3.1.4 - 2020-05-17 + +### Improvements + + * Improved document. + [GitHub#122][Patch by Burdette Lamar] + + * Stopped to dropping stack trace for exception caused by + `CSV.parse_line`. + [GitHub#120][Reported by Kyle d'Oliveira] + +### Fixes + + * Fixed a bug that `:write_nil_value` or `:write_empty_value` don't + work with non `String` objects. + [GitHub#123][Reported by asm256] + +### Thanks + + * Burdette Lamar + + * asm256 + + * Kyle d'Oliveira + +## 3.1.3 - 2020-05-09 + +### Improvements + + * `CSV::Row#dup`: Copied deeply. + [GitHub#108][Patch by Jim Kane] + +### Fixes + + * Fixed a infinite loop bug for zero length match `skip_lines`. + [GitHub#110][Patch by Mike MacDonald] + + * `CSV.generate`: Fixed a bug that encoding isn't set correctly. + [GitHub#110][Patch by Seiei Miyagi] + + * Fixed document for the `:strip` option. + [GitHub#114][Patch by TOMITA Masahiro] + + * Fixed a parse bug when split charcter exists in middle of column + value. + [GitHub#115][Reported by TOMITA Masahiro] + +### Thanks + + * Jim Kane + + * Mike MacDonald + + * Seiei Miyagi + + * TOMITA Masahiro + +## 3.1.2 - 2019-10-12 + +### Improvements + + * Added `:col_sep` check. + [GitHub#94][Reported by Florent Beaurain] + + * Suppressed warnings. + [GitHub#96][Patch by Nobuyoshi Nakada] + + * Improved documentation. + [GitHub#101][GitHub#102][Patch by Vitor Oliveira] + +### Fixes + + * Fixed a typo in documentation. + [GitHub#95][Patch by Yuji Yaginuma] + + * Fixed a multibyte character handling bug. + [GitHub#97][Patch by koshigoe] + + * Fixed typos in documentation. + [GitHub#100][Patch by Vitor Oliveira] + + * Fixed a bug that seeked `StringIO` isn't accepted. + [GitHub#98][Patch by MATSUMOTO Katsuyoshi] + + * Fixed a bug that `CSV.generate_line` doesn't work with + `Encoding.default_internal`. + [GitHub#105][Reported by David Rodríguez] + +### Thanks + + * Florent Beaurain + + * Yuji Yaginuma + + * Nobuyoshi Nakada + + * koshigoe + + * Vitor Oliveira + + * MATSUMOTO Katsuyoshi + + * David Rodríguez + +## 3.1.1 - 2019-04-26 + +### Improvements + + * Added documentation for `strip` option. + [GitHub#88][Patch by hayashiyoshino] + + * Added documentation for `write_converters`, `write_nil_value` and + `write_empty_value` options. + [GitHub#87][Patch by Masafumi Koba] + + * Added documentation for `quote_empty` option. + [GitHub#89][Patch by kawa\_tech] + +### Fixes + + * Fixed a bug that `strip; true` removes a newline. + +### Thanks + + * hayashiyoshino + + * Masafumi Koba + + * kawa\_tech + +## 3.1.0 - 2019-04-17 + +### Fixes + + * Fixed a backward incompatibility bug that `CSV#eof?` may raises an + error. + [GitHub#86][Reported by krororo] + +### Thanks + + * krororo + +## 3.0.9 - 2019-04-15 + +### Fixes + + * Fixed a test for Windows. + +## 3.0.8 - 2019-04-11 + +### Fixes + + * Fixed a bug that `strip: String` doesn't work. + +## 3.0.7 - 2019-04-08 + +### Improvements + + * Improve parse performance 1.5x by introducing loose parser. + +### Fixes + + * Fix performance regression in 3.0.5. + + * Fix a bug that `CSV#line` returns wrong value when you + use `quote_char: nil`. + +## 3.0.6 - 2019-03-30 + +### Improvements + + * `CSV.foreach`: Added support for `mode`. + +## 3.0.5 - 2019-03-24 + +### Improvements + + * Added `:liberal_parsing => {backslash_quote: true}` option. + [GitHub#74][Patch by 284km] + + * Added `:write_converters` option. + [GitHub#73][Patch by Danillo Souza] + + * Added `:write_nil_value` option. + + * Added `:write_empty_value` option. + + * Improved invalid byte line number detection. + [GitHub#78][Patch by Alyssa Ross] + + * Added `quote_char: nil` optimization. + [GitHub#79][Patch by 284km] + + * Improved error message. + [GitHub#81][Patch by Andrés Torres] + + * Improved IO-like implementation for `StringIO` data. + [GitHub#80][Patch by Genadi Samokovarov] + + * Added `:strip` option. + [GitHub#58] + +### Fixes + + * Fixed a compatibility bug that `CSV#each` doesn't care `CSV#shift`. + [GitHub#76][Patch by Alyssa Ross] + + * Fixed a compatibility bug that `CSV#eof?` doesn't care `CSV#each` + and `CSV#shift`. + [GitHub#77][Reported by Chi Leung] + + * Fixed a compatibility bug that invalid line isn't ignored. + [GitHub#82][Reported by krororo] + + * Fixed a bug that `:skip_lines` doesn't work with multibyte characters data. + [GitHub#83][Reported by ff2248] + +### Thanks + + * Alyssa Ross + + * 284km + + * Chi Leung + + * Danillo Souza + + * Andrés Torres + + * Genadi Samokovarov + + * krororo + + * ff2248 + +## 3.0.4 - 2019-01-25 + +### Improvements + + * Removed duplicated `CSV::Row#include?` implementations. + [GitHub#69][Patch by Max Schwenk] + + * Removed duplicated `CSV::Row#header?` implementations. + [GitHub#70][Patch by Max Schwenk] + +### Fixes + + * Fixed a typo in document. + [GitHub#72][Patch by Artur Beljajev] + + * Fixed a compatibility bug when row headers are changed. + [GitHub#71][Reported by tomoyuki kosaka] + +### Thanks + + * Max Schwenk + + * Artur Beljajev + + * tomoyuki kosaka + +## 3.0.3 - 2019-01-12 + +### Improvements + + * Migrated benchmark tool to benchmark-driver from benchmark-ips. + [GitHub#57][Patch by 284km] + + * Added `liberal_parsing: {double_quote_outside_quote: true}` parse + option. + [GitHub#66][Reported by Watson] + + * Added `quote_empty:` write option. + [GitHub#35][Reported by Dave Myron] + +### Fixes + + * Fixed a compatibility bug that `CSV.generate` always return + `ASCII-8BIT` encoding string. + [GitHub#63][Patch by Watson] + + * Fixed a compatibility bug that `CSV.parse("", headers: true)` + doesn't return `CSV::Table`. + [GitHub#64][Reported by Watson][Patch by 284km] + + * Fixed a compatibility bug that multiple-characters column + separator doesn't work. + [GitHub#67][Reported by Jesse Reiss] + + * Fixed a compatibility bug that double `#each` parse twice. + [GitHub#68][Reported by Max Schwenk] + +### Thanks + + * Watson + + * 284km + + * Jesse Reiss + + * Dave Myron + + * Max Schwenk + +## 3.0.2 - 2018-12-23 + +### Improvements + + * Changed to use strscan in parser. + [GitHub#52][Patch by 284km] + + * Improves CSV write performance. + 3.0.2 will be about 2 times faster than 3.0.1. + + * Improves CSV parse performance for complex case. + 3.0.2 will be about 2 times faster than 3.0.1. + +### Fixes + + * Fixed a parse error bug for new line only input with `headers` option. + [GitHub#53][Reported by Chris Beer] + + * Fixed some typos in document. + [GitHub#54][Patch by Victor Shepelev] + +### Thanks + + * 284km + + * Chris Beer + + * Victor Shepelev + +## 3.0.1 - 2018-12-07 + +### Improvements + + * Added a test. + [GitHub#38][Patch by 284km] + + * `CSV::Row#dup`: Changed to duplicate internal data. + [GitHub#39][Reported by André Guimarães Sakata] + + * Documented `:nil_value` and `:empty_value` options. + [GitHub#41][Patch by OwlWorks] + + * Added support for separator detection for non-seekable inputs. + [GitHub#45][Patch by Ilmari Karonen] + + * Removed needless code. + [GitHub#48][Patch by Espartaco Palma] + + * Added support for parsing header only CSV with `headers: true`. + [GitHub#47][Patch by Kazuma Shibasaka] + + * Added support for coverage report in CI. + [GitHub#48][Patch by Espartaco Palma] + + * Improved auto CR row separator detection. + [GitHub#51][Reported by Yuki Kurihara] + +### Fixes + + * Fixed a typo in document. + [GitHub#40][Patch by Marcus Stollsteimer] + +### Thanks + + * 284km + + * André Guimarães Sakata + + * Marcus Stollsteimer + + * OwlWorks + + * Ilmari Karonen + + * Espartaco Palma + + * Kazuma Shibasaka + + * Yuki Kurihara + +## 3.0.0 - 2018-06-06 + +### Fixes + + * Fixed a bug that header isn't returned for empty row. + [GitHub#37][Patch by Grace Lee] + +### Thanks + + * Grace Lee + +## 1.0.2 - 2018-05-03 + +### Improvements + + * Split file for CSV::VERSION + + * Code cleanup: Split csv.rb into a more manageable structure + [GitHub#19][Patch by Espartaco Palma] + [GitHub#20][Patch by Steven Daniels] + + * Use CSV::MalformedCSVError for invalid encoding line + [GitHub#26][Reported by deepj] + + * Support implicit Row <-> Array conversion + [Bug #10013][ruby-core:63582][Reported by Dawid Janczak] + + * Update class docs + [GitHub#32][Patch by zverok] + + * Add `Row#each_pair` + [GitHub#33][Patch by zverok] + + * Improve CSV performance + [GitHub#30][Patch by Watson] + + * Add :nil_value and :empty_value option + +### Fixes + + * Fix a bug that "bom|utf-8" doesn't work + [GitHub#23][Reported by Pavel Lobashov] + + * `CSV::Row#to_h`, `#to_hash`: uses the same value as `Row#[]` + [Bug #14482][Reported by tomoya ishida] + + * Make row separator detection more robust + [GitHub#25][Reported by deepj] + + * Fix a bug that too much separator when col_sep is `" "` + [Bug #8784][ruby-core:63582][Reported by Sylvain Laperche] + +### Thanks + + * Espartaco Palma + + * Steven Daniels + + * deepj + + * Dawid Janczak + + * zverok + + * Watson + + * Pavel Lobashov + + * tomoya ishida + + * Sylvain Laperche + + * Ryunosuke Sato + +## 1.0.1 - 2018-02-09 + +### Improvements + + * `CSV::Table#delete`: Added bulk delete support. You can delete + multiple rows and columns at once. + [GitHub#4][Patch by Vladislav] + + * Updated Gem description. + [GitHub#11][Patch by Marcus Stollsteimer] + + * Code cleanup. + [GitHub#12][Patch by Marcus Stollsteimer] + [GitHub#14][Patch by Steven Daniels] + [GitHub#18][Patch by takkanm] + + * `CSV::Table#dig`: Added. + [GitHub#15][Patch by Tomohiro Ogoke] + + * `CSV::Row#dig`: Added. + [GitHub#15][Patch by Tomohiro Ogoke] + + * Added ISO 8601 support to date time converter. + [GitHub#16] + +### Fixes + + * Fixed wrong `CSV::VERSION`. + [GitHub#10][Reported by Marcus Stollsteimer] + + * `CSV.generate`: Fixed a regression bug that `String` argument is + ignored. + [GitHub#13][Patch by pavel] + +### Thanks + + * Vladislav + + * Marcus Stollsteimer + + * Steven Daniels + + * takkanm + + * Tomohiro Ogoke + + * pavel diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/README.md b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/README.md new file mode 100644 index 00000000..cf61a358 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/README.md @@ -0,0 +1,55 @@ +# CSV + +This library provides a complete interface to CSV files and data. It offers tools to enable you to read and write to and from Strings or IO objects, as needed. + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'csv' +``` + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install csv + +## Usage + +```ruby +require "csv" + +CSV.foreach("path/to/file.csv") do |row| + # use row here... +end +``` + +## Documentation + +- [API](https://ruby.github.io/csv/): all classes, methods, and constants. +- [Recipes](https://ruby.github.io/csv/doc/csv/recipes/recipes_rdoc.html): specific code for specific tasks. + +## Development + +After checking out the repo, run `ruby run-test.rb` to check if your changes can pass the test. + +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/csv. + +### NOTE: About RuboCop + +We don't use RuboCop because we can manage our coding style by ourselves. We want to accept small fluctuations in our coding style because we use Ruby. +Please do not submit issues and PRs that aim to introduce RuboCop in this repository. + +## License + +The gem is available as open source under the terms of the [2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause). + +See LICENSE.txt for details. diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/arguments/io.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/arguments/io.rdoc new file mode 100644 index 00000000..f5fe1d19 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/arguments/io.rdoc @@ -0,0 +1,5 @@ +* Argument +io+ should be an IO object that is: + * Open for reading; on return, the IO object will be closed. + * Positioned at the beginning. + To position at the end, for appending, use method CSV.generate. + For any other positioning, pass a preset \StringIO object instead. diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/common/col_sep.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/common/col_sep.rdoc new file mode 100644 index 00000000..b8be8699 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/common/col_sep.rdoc @@ -0,0 +1,57 @@ +====== Option +col_sep+ + +Specifies the \String column separator to be used +for both parsing and generating. +The \String will be transcoded into the data's \Encoding before use. + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:col_sep) # => "," (comma) + +Using the default (comma): + str = CSV.generate do |csv| + csv << [:foo, 0] + csv << [:bar, 1] + csv << [:baz, 2] + end + str # => "foo,0\nbar,1\nbaz,2\n" + ary = CSV.parse(str) + ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + +Using +:+ (colon): + col_sep = ':' + str = CSV.generate(col_sep: col_sep) do |csv| + csv << [:foo, 0] + csv << [:bar, 1] + csv << [:baz, 2] + end + str # => "foo:0\nbar:1\nbaz:2\n" + ary = CSV.parse(str, col_sep: col_sep) + ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + +Using +::+ (two colons): + col_sep = '::' + str = CSV.generate(col_sep: col_sep) do |csv| + csv << [:foo, 0] + csv << [:bar, 1] + csv << [:baz, 2] + end + str # => "foo::0\nbar::1\nbaz::2\n" + ary = CSV.parse(str, col_sep: col_sep) + ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + +Using '' (empty string): + col_sep = '' + str = CSV.generate(col_sep: col_sep) do |csv| + csv << [:foo, 0] + csv << [:bar, 1] + csv << [:baz, 2] + end + str # => "foo0\nbar1\nbaz2\n" + +--- + +Raises an exception if parsing with the empty \String: + col_sep = '' + # Raises ArgumentError (:col_sep must be 1 or more characters: "") + CSV.parse("foo0\nbar1\nbaz2\n", col_sep: col_sep) + diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/common/quote_char.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/common/quote_char.rdoc new file mode 100644 index 00000000..67fd3af6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/common/quote_char.rdoc @@ -0,0 +1,42 @@ +====== Option +quote_char+ + +Specifies the character (\String of length 1) used used to quote fields +in both parsing and generating. +This String will be transcoded into the data's \Encoding before use. + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:quote_char) # => "\"" (double quote) + +This is useful for an application that incorrectly uses ' (single-quote) +to quote fields, instead of the correct " (double-quote). + +Using the default (double quote): + str = CSV.generate do |csv| + csv << ['foo', 0] + csv << ["'bar'", 1] + csv << ['"baz"', 2] + end + str # => "foo,0\n'bar',1\n\"\"\"baz\"\"\",2\n" + ary = CSV.parse(str) + ary # => [["foo", "0"], ["'bar'", "1"], ["\"baz\"", "2"]] + +Using ' (single-quote): + quote_char = "'" + str = CSV.generate(quote_char: quote_char) do |csv| + csv << ['foo', 0] + csv << ["'bar'", 1] + csv << ['"baz"', 2] + end + str # => "foo,0\n'''bar''',1\n\"baz\",2\n" + ary = CSV.parse(str, quote_char: quote_char) + ary # => [["foo", "0"], ["'bar'", "1"], ["\"baz\"", "2"]] + +--- + +Raises an exception if the \String length is greater than 1: + # Raises ArgumentError (:quote_char has to be nil or a single character String) + CSV.new('', quote_char: 'xx') + +Raises an exception if the value is not a \String: + # Raises ArgumentError (:quote_char has to be nil or a single character String) + CSV.new('', quote_char: :foo) diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/common/row_sep.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/common/row_sep.rdoc new file mode 100644 index 00000000..eae15b4a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/common/row_sep.rdoc @@ -0,0 +1,91 @@ +====== Option +row_sep+ + +Specifies the row separator, a \String or the \Symbol :auto (see below), +to be used for both parsing and generating. + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:row_sep) # => :auto + +--- + +When +row_sep+ is a \String, that \String becomes the row separator. +The String will be transcoded into the data's Encoding before use. + +Using "\n": + row_sep = "\n" + str = CSV.generate(row_sep: row_sep) do |csv| + csv << [:foo, 0] + csv << [:bar, 1] + csv << [:baz, 2] + end + str # => "foo,0\nbar,1\nbaz,2\n" + ary = CSV.parse(str) + ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + +Using | (pipe): + row_sep = '|' + str = CSV.generate(row_sep: row_sep) do |csv| + csv << [:foo, 0] + csv << [:bar, 1] + csv << [:baz, 2] + end + str # => "foo,0|bar,1|baz,2|" + ary = CSV.parse(str, row_sep: row_sep) + ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + +Using -- (two hyphens): + row_sep = '--' + str = CSV.generate(row_sep: row_sep) do |csv| + csv << [:foo, 0] + csv << [:bar, 1] + csv << [:baz, 2] + end + str # => "foo,0--bar,1--baz,2--" + ary = CSV.parse(str, row_sep: row_sep) + ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + +Using '' (empty string): + row_sep = '' + str = CSV.generate(row_sep: row_sep) do |csv| + csv << [:foo, 0] + csv << [:bar, 1] + csv << [:baz, 2] + end + str # => "foo,0bar,1baz,2" + ary = CSV.parse(str, row_sep: row_sep) + ary # => [["foo", "0bar", "1baz", "2"]] + +--- + +When +row_sep+ is the \Symbol +:auto+ (the default), +generating uses "\n" as the row separator: + str = CSV.generate do |csv| + csv << [:foo, 0] + csv << [:bar, 1] + csv << [:baz, 2] + end + str # => "foo,0\nbar,1\nbaz,2\n" + +Parsing, on the other hand, invokes auto-discovery of the row separator. + +Auto-discovery reads ahead in the data looking for the next \r\n, +\n+, or +\r+ sequence. +The sequence will be selected even if it occurs in a quoted field, +assuming that you would have the same line endings there. + +Example: + str = CSV.generate do |csv| + csv << [:foo, 0] + csv << [:bar, 1] + csv << [:baz, 2] + end + str # => "foo,0\nbar,1\nbaz,2\n" + ary = CSV.parse(str) + ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + +The default $INPUT_RECORD_SEPARATOR ($/) is used +if any of the following is true: +* None of those sequences is found. +* Data is +ARGF+, +STDIN+, +STDOUT+, or +STDERR+. +* The stream is only available for output. + +Obviously, discovery takes a little time. Set manually if speed is important. Also note that IO objects should be opened in binary mode on Windows if this feature will be used as the line-ending translation can cause problems with resetting the document position to where it was before the read ahead. diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/generating/force_quotes.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/generating/force_quotes.rdoc new file mode 100644 index 00000000..11afd1a1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/generating/force_quotes.rdoc @@ -0,0 +1,17 @@ +====== Option +force_quotes+ + +Specifies the boolean that determines whether each output field is to be double-quoted. + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:force_quotes) # => false + +For examples in this section: + ary = ['foo', 0, nil] + +Using the default, +false+: + str = CSV.generate_line(ary) + str # => "foo,0,\n" + +Using +true+: + str = CSV.generate_line(ary, force_quotes: true) + str # => "\"foo\",\"0\",\"\"\n" diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/generating/quote_empty.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/generating/quote_empty.rdoc new file mode 100644 index 00000000..4c5645c6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/generating/quote_empty.rdoc @@ -0,0 +1,12 @@ +====== Option +quote_empty+ + +Specifies the boolean that determines whether an empty value is to be double-quoted. + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:quote_empty) # => true + +With the default +true+: + CSV.generate_line(['"', ""]) # => "\"\"\"\",\"\"\n" + +With +false+: + CSV.generate_line(['"', ""], quote_empty: false) # => "\"\"\"\",\n" diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/generating/write_converters.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/generating/write_converters.rdoc new file mode 100644 index 00000000..d1a9cc74 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/generating/write_converters.rdoc @@ -0,0 +1,25 @@ +====== Option +write_converters+ + +Specifies converters to be used in generating fields. +See {Write Converters}[#class-CSV-label-Write+Converters] + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:write_converters) # => nil + +With no write converter: + str = CSV.generate_line(["\na\n", "\tb\t", " c "]) + str # => "\"\na\n\",\tb\t, c \n" + +With a write converter: + strip_converter = proc {|field| field.strip } + str = CSV.generate_line(["\na\n", "\tb\t", " c "], write_converters: strip_converter) + str # => "a,b,c\n" + +With two write converters (called in order): + upcase_converter = proc {|field| field.upcase } + downcase_converter = proc {|field| field.downcase } + write_converters = [upcase_converter, downcase_converter] + str = CSV.generate_line(['a', 'b', 'c'], write_converters: write_converters) + str # => "a,b,c\n" + +See also {Write Converters}[#class-CSV-label-Write+Converters] diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/generating/write_empty_value.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/generating/write_empty_value.rdoc new file mode 100644 index 00000000..67be5662 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/generating/write_empty_value.rdoc @@ -0,0 +1,15 @@ +====== Option +write_empty_value+ + +Specifies the object that is to be substituted for each field +that has an empty \String. + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:write_empty_value) # => "" + +Without the option: + str = CSV.generate_line(['a', '', 'c', '']) + str # => "a,\"\",c,\"\"\n" + +With the option: + str = CSV.generate_line(['a', '', 'c', ''], write_empty_value: "x") + str # => "a,x,c,x\n" diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/generating/write_headers.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/generating/write_headers.rdoc new file mode 100644 index 00000000..c56aa48a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/generating/write_headers.rdoc @@ -0,0 +1,29 @@ +====== Option +write_headers+ + +Specifies the boolean that determines whether a header row is included in the output; +ignored if there are no headers. + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:write_headers) # => nil + +Without +write_headers+: + file_path = 't.csv' + CSV.open(file_path,'w', + :headers => ['Name','Value'] + ) do |csv| + csv << ['foo', '0'] + end + CSV.open(file_path) do |csv| + csv.shift + end # => ["foo", "0"] + +With +write_headers+": + CSV.open(file_path,'w', + :write_headers => true, + :headers => ['Name','Value'] + ) do |csv| + csv << ['foo', '0'] + end + CSV.open(file_path) do |csv| + csv.shift + end # => ["Name", "Value"] diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/generating/write_nil_value.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/generating/write_nil_value.rdoc new file mode 100644 index 00000000..65d33ff5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/generating/write_nil_value.rdoc @@ -0,0 +1,14 @@ +====== Option +write_nil_value+ + +Specifies the object that is to be substituted for each +nil+-valued field. + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:write_nil_value) # => nil + +Without the option: + str = CSV.generate_line(['a', nil, 'c', nil]) + str # => "a,,c,\n" + +With the option: + str = CSV.generate_line(['a', nil, 'c', nil], write_nil_value: "x") + str # => "a,x,c,x\n" diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/converters.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/converters.rdoc new file mode 100644 index 00000000..211fa48d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/converters.rdoc @@ -0,0 +1,46 @@ +====== Option +converters+ + +Specifies converters to be used in parsing fields. +See {Field Converters}[#class-CSV-label-Field+Converters] + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:converters) # => nil + +The value may be a field converter name +(see {Stored Converters}[#class-CSV-label-Stored+Converters]): + str = '1,2,3' + # Without a converter + array = CSV.parse_line(str) + array # => ["1", "2", "3"] + # With built-in converter :integer + array = CSV.parse_line(str, converters: :integer) + array # => [1, 2, 3] + +The value may be a converter list +(see {Converter Lists}[#class-CSV-label-Converter+Lists]): + str = '1,3.14159' + # Without converters + array = CSV.parse_line(str) + array # => ["1", "3.14159"] + # With built-in converters + array = CSV.parse_line(str, converters: [:integer, :float]) + array # => [1, 3.14159] + +The value may be a \Proc custom converter: +(see {Custom Field Converters}[#class-CSV-label-Custom+Field+Converters]): + str = ' foo , bar , baz ' + # Without a converter + array = CSV.parse_line(str) + array # => [" foo ", " bar ", " baz "] + # With a custom converter + array = CSV.parse_line(str, converters: proc {|field| field.strip }) + array # => ["foo", "bar", "baz"] + +See also {Custom Field Converters}[#class-CSV-label-Custom+Field+Converters] + +--- + +Raises an exception if the converter is not a converter name or a \Proc: + str = 'foo,0' + # Raises NoMethodError (undefined method `arity' for nil:NilClass) + CSV.parse(str, converters: :foo) diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/empty_value.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/empty_value.rdoc new file mode 100644 index 00000000..7d3bcc07 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/empty_value.rdoc @@ -0,0 +1,13 @@ +====== Option +empty_value+ + +Specifies the object that is to be substituted +for each field that has an empty \String. + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:empty_value) # => "" (empty string) + +With the default, "": + CSV.parse_line('a,"",b,"",c') # => ["a", "", "b", "", "c"] + +With a different object: + CSV.parse_line('a,"",b,"",c', empty_value: 'x') # => ["a", "x", "b", "x", "c"] diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/field_size_limit.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/field_size_limit.rdoc new file mode 100644 index 00000000..797c5776 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/field_size_limit.rdoc @@ -0,0 +1,39 @@ +====== Option +field_size_limit+ + +Specifies the \Integer field size limit. + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:field_size_limit) # => nil + +This is a maximum size CSV will read ahead looking for the closing quote for a field. +(In truth, it reads to the first line ending beyond this size.) +If a quote cannot be found within the limit CSV will raise a MalformedCSVError, +assuming the data is faulty. +You can use this limit to prevent what are effectively DoS attacks on the parser. +However, this limit can cause a legitimate parse to fail; +therefore the default value is +nil+ (no limit). + +For the examples in this section: + str = <<~EOT + "a","b" + " + 2345 + ","" + EOT + str # => "\"a\",\"b\"\n\"\n2345\n\",\"\"\n" + +Using the default +nil+: + ary = CSV.parse(str) + ary # => [["a", "b"], ["\n2345\n", ""]] + +Using 50: + field_size_limit = 50 + ary = CSV.parse(str, field_size_limit: field_size_limit) + ary # => [["a", "b"], ["\n2345\n", ""]] + +--- + +Raises an exception if a field is too long: + big_str = "123456789\n" * 1024 + # Raises CSV::MalformedCSVError (Field size exceeded in line 1.) + CSV.parse('valid,fields,"' + big_str + '"', field_size_limit: 2048) diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/header_converters.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/header_converters.rdoc new file mode 100644 index 00000000..30918080 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/header_converters.rdoc @@ -0,0 +1,43 @@ +====== Option +header_converters+ + +Specifies converters to be used in parsing headers. +See {Header Converters}[#class-CSV-label-Header+Converters] + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:header_converters) # => nil + +Identical in functionality to option {converters}[#class-CSV-label-Option+converters] +except that: +- The converters apply only to the header row. +- The built-in header converters are +:downcase+ and +:symbol+. + +This section assumes prior execution of: + str = <<-EOT + Name,Value + foo,0 + bar,1 + baz,2 + EOT + # With no header converter + table = CSV.parse(str, headers: true) + table.headers # => ["Name", "Value"] + +The value may be a header converter name +(see {Stored Converters}[#class-CSV-label-Stored+Converters]): + table = CSV.parse(str, headers: true, header_converters: :downcase) + table.headers # => ["name", "value"] + +The value may be a converter list +(see {Converter Lists}[#class-CSV-label-Converter+Lists]): + header_converters = [:downcase, :symbol] + table = CSV.parse(str, headers: true, header_converters: header_converters) + table.headers # => [:name, :value] + +The value may be a \Proc custom converter +(see {Custom Header Converters}[#class-CSV-label-Custom+Header+Converters]): + upcase_converter = proc {|field| field.upcase } + table = CSV.parse(str, headers: true, header_converters: upcase_converter) + table.headers # => ["NAME", "VALUE"] + +See also {Custom Header Converters}[#class-CSV-label-Custom+Header+Converters] + diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/headers.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/headers.rdoc new file mode 100644 index 00000000..0ea151f2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/headers.rdoc @@ -0,0 +1,63 @@ +====== Option +headers+ + +Specifies a boolean, \Symbol, \Array, or \String to be used +to define column headers. + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:headers) # => false + +--- + +Without +headers+: + str = <<-EOT + Name,Count + foo,0 + bar,1 + bax,2 + EOT + csv = CSV.new(str) + csv # => # + csv.headers # => nil + csv.shift # => ["Name", "Count"] + +--- + +If set to +true+ or the \Symbol +:first_row+, +the first row of the data is treated as a row of headers: + str = <<-EOT + Name,Count + foo,0 + bar,1 + bax,2 + EOT + csv = CSV.new(str, headers: true) + csv # => # + csv.headers # => ["Name", "Count"] + csv.shift # => # + +--- + +If set to an \Array, the \Array elements are treated as headers: + str = <<-EOT + foo,0 + bar,1 + bax,2 + EOT + csv = CSV.new(str, headers: ['Name', 'Count']) + csv + csv.headers # => ["Name", "Count"] + csv.shift # => # + +--- + +If set to a \String +str+, method CSV::parse_line(str, options) is called +with the current +options+, and the returned \Array is treated as headers: + str = <<-EOT + foo,0 + bar,1 + bax,2 + EOT + csv = CSV.new(str, headers: 'Name,Count') + csv + csv.headers # => ["Name", "Count"] + csv.shift # => # diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/liberal_parsing.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/liberal_parsing.rdoc new file mode 100644 index 00000000..603de286 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/liberal_parsing.rdoc @@ -0,0 +1,38 @@ +====== Option +liberal_parsing+ + +Specifies the boolean or hash value that determines whether +CSV will attempt to parse input not conformant with RFC 4180, +such as double quotes in unquoted fields. + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:liberal_parsing) # => false + +For the next two examples: + str = 'is,this "three, or four",fields' + +Without +liberal_parsing+: + # Raises CSV::MalformedCSVError (Illegal quoting in str 1.) + CSV.parse_line(str) + +With +liberal_parsing+: + ary = CSV.parse_line(str, liberal_parsing: true) + ary # => ["is", "this \"three", " or four\"", "fields"] + +Use the +backslash_quote+ sub-option to parse values that use +a backslash to escape a double-quote character. This +causes the parser to treat \" as if it were +"". + +For the next two examples: + str = 'Show,"Harry \"Handcuff\" Houdini, the one and only","Tampa Theater"' + +With +liberal_parsing+, but without the +backslash_quote+ sub-option: + # Incorrect interpretation of backslash; incorrectly interprets the quoted comma as a field separator. + ary = CSV.parse_line(str, liberal_parsing: true) + ary # => ["Show", "\"Harry \\\"Handcuff\\\" Houdini", " the one and only\"", "Tampa Theater"] + puts ary[1] # => "Harry \"Handcuff\" Houdini + +With +liberal_parsing+ and its +backslash_quote+ sub-option: + ary = CSV.parse_line(str, liberal_parsing: { backslash_quote: true }) + ary # => ["Show", "Harry \"Handcuff\" Houdini, the one and only", "Tampa Theater"] + puts ary[1] # => Harry "Handcuff" Houdini, the one and only diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/nil_value.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/nil_value.rdoc new file mode 100644 index 00000000..412e8795 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/nil_value.rdoc @@ -0,0 +1,12 @@ +====== Option +nil_value+ + +Specifies the object that is to be substituted for each null (no-text) field. + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:nil_value) # => nil + +With the default, +nil+: + CSV.parse_line('a,,b,,c') # => ["a", nil, "b", nil, "c"] + +With a different object: + CSV.parse_line('a,,b,,c', nil_value: 0) # => ["a", 0, "b", 0, "c"] diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/return_headers.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/return_headers.rdoc new file mode 100644 index 00000000..45d2e3f3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/return_headers.rdoc @@ -0,0 +1,22 @@ +====== Option +return_headers+ + +Specifies the boolean that determines whether method #shift +returns or ignores the header row. + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:return_headers) # => false + +Examples: + str = <<-EOT + Name,Count + foo,0 + bar,1 + bax,2 + EOT + # Without return_headers first row is str. + csv = CSV.new(str, headers: true) + csv.shift # => # + # With return_headers first row is headers. + csv = CSV.new(str, headers: true, return_headers: true) + csv.shift # => # + diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/skip_blanks.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/skip_blanks.rdoc new file mode 100644 index 00000000..2c8f7b7b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/skip_blanks.rdoc @@ -0,0 +1,31 @@ +====== Option +skip_blanks+ + +Specifies a boolean that determines whether blank lines in the input will be ignored; +a line that contains a column separator is not considered to be blank. + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:skip_blanks) # => false + +See also option {skiplines}[#class-CSV-label-Option+skip_lines]. + +For examples in this section: + str = <<-EOT + foo,0 + + bar,1 + baz,2 + + , + EOT + +Using the default, +false+: + ary = CSV.parse(str) + ary # => [["foo", "0"], [], ["bar", "1"], ["baz", "2"], [], [nil, nil]] + +Using +true+: + ary = CSV.parse(str, skip_blanks: true) + ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"], [nil, nil]] + +Using a truthy value: + ary = CSV.parse(str, skip_blanks: :foo) + ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"], [nil, nil]] diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/skip_lines.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/skip_lines.rdoc new file mode 100644 index 00000000..1481c40a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/skip_lines.rdoc @@ -0,0 +1,37 @@ +====== Option +skip_lines+ + +Specifies an object to use in identifying comment lines in the input that are to be ignored: +* If a \Regexp, ignores lines that match it. +* If a \String, converts it to a \Regexp, ignores lines that match it. +* If +nil+, no lines are considered to be comments. + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:skip_lines) # => nil + +For examples in this section: + str = <<-EOT + # Comment + foo,0 + bar,1 + baz,2 + # Another comment + EOT + str # => "# Comment\nfoo,0\nbar,1\nbaz,2\n# Another comment\n" + +Using the default, +nil+: + ary = CSV.parse(str) + ary # => [["# Comment"], ["foo", "0"], ["bar", "1"], ["baz", "2"], ["# Another comment"]] + +Using a \Regexp: + ary = CSV.parse(str, skip_lines: /^#/) + ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + +Using a \String: + ary = CSV.parse(str, skip_lines: '#') + ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + +--- + +Raises an exception if given an object that is not a \Regexp, a \String, or +nil+: + # Raises ArgumentError (:skip_lines has to respond to #match: 0) + CSV.parse(str, skip_lines: 0) diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/strip.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/strip.rdoc new file mode 100644 index 00000000..56ae4310 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/strip.rdoc @@ -0,0 +1,15 @@ +====== Option +strip+ + +Specifies the boolean value that determines whether +whitespace is stripped from each input field. + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:strip) # => false + +With default value +false+: + ary = CSV.parse_line(' a , b ') + ary # => [" a ", " b "] + +With value +true+: + ary = CSV.parse_line(' a , b ', strip: true) + ary # => ["a", "b"] diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/unconverted_fields.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/unconverted_fields.rdoc new file mode 100644 index 00000000..3e7f839d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/options/parsing/unconverted_fields.rdoc @@ -0,0 +1,27 @@ +====== Option +unconverted_fields+ + +Specifies the boolean that determines whether unconverted field values are to be available. + +Default value: + CSV::DEFAULT_OPTIONS.fetch(:unconverted_fields) # => nil + +The unconverted field values are those found in the source data, +prior to any conversions performed via option +converters+. + +When option +unconverted_fields+ is +true+, +each returned row (\Array or \CSV::Row) has an added method, ++unconverted_fields+, that returns the unconverted field values: + str = <<-EOT + foo,0 + bar,1 + baz,2 + EOT + # Without unconverted_fields + csv = CSV.parse(str, converters: :integer) + csv # => [["foo", 0], ["bar", 1], ["baz", 2]] + csv.first.respond_to?(:unconverted_fields) # => false + # With unconverted_fields + csv = CSV.parse(str, converters: :integer, unconverted_fields: true) + csv # => [["foo", 0], ["bar", 1], ["baz", 2]] + csv.first.respond_to?(:unconverted_fields) # => true + csv.first.unconverted_fields # => ["foo", "0"] diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/recipes/filtering.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/recipes/filtering.rdoc new file mode 100644 index 00000000..d92afb72 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/recipes/filtering.rdoc @@ -0,0 +1,226 @@ +== Recipes for Filtering \CSV + +These recipes are specific code examples for specific \CSV filtering tasks. + +For other recipes, see {Recipes for CSV}[./recipes_rdoc.html]. + +All code snippets on this page assume that the following has been executed: + require 'csv' + +=== Contents + +- {Source and Output Formats}[#label-Source+and+Output+Formats] + - {Filtering String to String}[#label-Filtering+String+to+String] + - {Recipe: Filter String to String parsing Headers}[#label-Recipe-3A+Filter+String+to+String+parsing+Headers] + - {Recipe: Filter String to String parsing and writing Headers}[#label-Recipe-3A+Filter+String+to+String+parsing+and+writing+Headers] + - {Recipe: Filter String to String Without Headers}[#label-Recipe-3A+Filter+String+to+String+Without+Headers] + - {Filtering String to IO Stream}[#label-Filtering+String+to+IO+Stream] + - {Recipe: Filter String to IO Stream parsing Headers}[#label-Recipe-3A+Filter+String+to+IO+Stream+parsing+Headers] + - {Recipe: Filter String to IO Stream parsing and writing Headers}[#label-Recipe-3A+Filter+String+to+IO+Stream+parsing+and+writing+Headers] + - {Recipe: Filter String to IO Stream Without Headers}[#label-Recipe-3A+Filter+String+to+IO+Stream+Without+Headers] + - {Filtering IO Stream to String}[#label-Filtering+IO+Stream+to+String] + - {Recipe: Filter IO Stream to String parsing Headers}[#label-Recipe-3A+Filter+IO+Stream+to+String+parsing+Headers] + - {Recipe: Filter IO Stream to String parsing and writing Headers}[#label-Recipe-3A+Filter+IO+Stream+to+String+parsing+and+writing+Headers] + - {Recipe: Filter IO Stream to String Without Headers}[#label-Recipe-3A+Filter+IO+Stream+to+String+Without+Headers] + - {Filtering IO Stream to IO Stream}[#label-Filtering+IO+Stream+to+IO+Stream] + - {Recipe: Filter IO Stream to IO Stream parsing Headers}[#label-Recipe-3A+Filter+IO+Stream+to+IO+Stream+parsing+Headers] + - {Recipe: Filter IO Stream to IO Stream parsing and writing Headers}[#label-Recipe-3A+Filter+IO+Stream+to+IO+Stream+parsing+and+writing+Headers] + - {Recipe: Filter IO Stream to IO Stream Without Headers}[#label-Recipe-3A+Filter+IO+Stream+to+IO+Stream+Without+Headers] + +=== Source and Output Formats + +You can use a Unix-style "filter" for \CSV data. +The filter reads source \CSV data and writes output \CSV data as modified by the filter. +The input and output \CSV data may be any mixture of \Strings and \IO streams. + +==== Filtering \String to \String + +You can filter one \String to another, with or without headers. + +===== Recipe: Filter \String to \String parsing Headers + +Use class method CSV.filter with option +headers+ to filter a \String to another \String: + in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + out_string = '' + CSV.filter(in_string, out_string, headers: true) do |row| + row['Name'] = row['Name'].upcase + row['Value'] *= 4 + end + out_string # => "FOO,0000\nBAR,1111\nBAZ,2222\n" + +===== Recipe: Filter \String to \String parsing and writing Headers + +Use class method CSV.filter with option +headers+ and +out_write_headers+ to filter a \String to another \String including header row: + in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + out_string = '' + CSV.filter(in_string, out_string, headers: true, out_write_headers: true) do |row| + unless row.is_a?(Array) + row['Name'] = row['Name'].upcase + row['Value'] *= 4 + end + end + out_string # => "Name,Value\nFOO,0000\nBAR,1111\nBAZ,2222\n" + +===== Recipe: Filter \String to \String Without Headers + +Use class method CSV.filter without option +headers+ to filter a \String to another \String: + in_string = "foo,0\nbar,1\nbaz,2\n" + out_string = '' + CSV.filter(in_string, out_string) do |row| + row[0] = row[0].upcase + row[1] *= 4 + end + out_string # => "FOO,0000\nBAR,1111\nBAZ,2222\n" + +==== Filtering \String to \IO Stream + +You can filter a \String to an \IO stream, with or without headers. + +===== Recipe: Filter \String to \IO Stream parsing Headers + +Use class method CSV.filter with option +headers+ to filter a \String to an \IO stream: + in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + path = 't.csv' + File.open(path, 'w') do |out_io| + CSV.filter(in_string, out_io, headers: true) do |row| + row['Name'] = row['Name'].upcase + row['Value'] *= 4 + end + end + p File.read(path) # => "FOO,0000\nBAR,1111\nBAZ,2222\n" + +===== Recipe: Filter \String to \IO Stream parsing and writing Headers + +Use class method CSV.filter with option +headers+ and +out_write_headers+ to filter a \String to an \IO stream including header row: + in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + path = 't.csv' + File.open(path, 'w') do |out_io| + CSV.filter(in_string, out_io, headers: true, out_write_headers: true ) do |row| + unless row.is_a?(Array) + row['Name'] = row['Name'].upcase + row['Value'] *= 4 + end + end + end + p File.read(path) # => "Name,Value\nFOO,0000\nBAR,1111\nBAZ,2222\n" + +===== Recipe: Filter \String to \IO Stream Without Headers + +Use class method CSV.filter without option +headers+ to filter a \String to an \IO stream: + in_string = "foo,0\nbar,1\nbaz,2\n" + path = 't.csv' + File.open(path, 'w') do |out_io| + CSV.filter(in_string, out_io) do |row| + row[0] = row[0].upcase + row[1] *= 4 + end + end + p File.read(path) # => "FOO,0000\nBAR,1111\nBAZ,2222\n" + +==== Filtering \IO Stream to \String + +You can filter an \IO stream to a \String, with or without headers. + +===== Recipe: Filter \IO Stream to \String parsing Headers + +Use class method CSV.filter with option +headers+ to filter an \IO stream to a \String: + in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + path = 't.csv' + File.write(path, in_string) + out_string = '' + File.open(path) do |in_io| + CSV.filter(in_io, out_string, headers: true) do |row| + row['Name'] = row['Name'].upcase + row['Value'] *= 4 + end + end + out_string # => "FOO,0000\nBAR,1111\nBAZ,2222\n" + +===== Recipe: Filter \IO Stream to \String parsing and writing Headers + +Use class method CSV.filter with option +headers+ and +out_write_headers+ to filter an \IO stream to a \String including header row: + in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + path = 't.csv' + File.write(path, in_string) + out_string = '' + File.open(path) do |in_io| + CSV.filter(in_io, out_string, headers: true, out_write_headers: true) do |row| + unless row.is_a?(Array) + row['Name'] = row['Name'].upcase + row['Value'] *= 4 + end + end + end + out_string # => "Name,Value\nFOO,0000\nBAR,1111\nBAZ,2222\n" + +===== Recipe: Filter \IO Stream to \String Without Headers + +Use class method CSV.filter without option +headers+ to filter an \IO stream to a \String: + in_string = "foo,0\nbar,1\nbaz,2\n" + path = 't.csv' + File.write(path, in_string) + out_string = '' + File.open(path) do |in_io| + CSV.filter(in_io, out_string) do |row| + row[0] = row[0].upcase + row[1] *= 4 + end + end + out_string # => "FOO,0000\nBAR,1111\nBAZ,2222\n" + +==== Filtering \IO Stream to \IO Stream + +You can filter an \IO stream to another \IO stream, with or without headers. + +===== Recipe: Filter \IO Stream to \IO Stream parsing Headers + +Use class method CSV.filter with option +headers+ to filter an \IO stream to another \IO stream: + in_path = 't.csv' + in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + File.write(in_path, in_string) + out_path = 'u.csv' + File.open(in_path) do |in_io| + File.open(out_path, 'w') do |out_io| + CSV.filter(in_io, out_io, headers: true) do |row| + row['Name'] = row['Name'].upcase + row['Value'] *= 4 + end + end + end + p File.read(out_path) # => "FOO,0000\nBAR,1111\nBAZ,2222\n" + +===== Recipe: Filter \IO Stream to \IO Stream parsing and writing Headers + +Use class method CSV.filter with option +headers+ and +out_write_headers+ to filter an \IO stream to another \IO stream including header row: + in_path = 't.csv' + in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + File.write(in_path, in_string) + out_path = 'u.csv' + File.open(in_path) do |in_io| + File.open(out_path, 'w') do |out_io| + CSV.filter(in_io, out_io, headers: true, out_write_headers: true) do |row| + unless row.is_a?(Array) + row['Name'] = row['Name'].upcase + row['Value'] *= 4 + end + end + end + end + p File.read(out_path) # => "Name,Value\nFOO,0000\nBAR,1111\nBAZ,2222\n" + +===== Recipe: Filter \IO Stream to \IO Stream Without Headers + +Use class method CSV.filter without option +headers+ to filter an \IO stream to another \IO stream: + in_path = 't.csv' + in_string = "foo,0\nbar,1\nbaz,2\n" + File.write(in_path, in_string) + out_path = 'u.csv' + File.open(in_path) do |in_io| + File.open(out_path, 'w') do |out_io| + CSV.filter(in_io, out_io) do |row| + row[0] = row[0].upcase + row[1] *= 4 + end + end + end + p File.read(out_path) # => "FOO,0000\nBAR,1111\nBAZ,2222\n" diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/recipes/generating.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/recipes/generating.rdoc new file mode 100644 index 00000000..d96ff85c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/recipes/generating.rdoc @@ -0,0 +1,298 @@ +== Recipes for Generating \CSV + +These recipes are specific code examples for specific \CSV generating tasks. + +For other recipes, see {Recipes for CSV}[./recipes_rdoc.html]. + +All code snippets on this page assume that the following has been executed: + require 'csv' + +=== Contents + +- {Output Formats}[#label-Output+Formats] + - {Generating to a String}[#label-Generating+to+a+String] + - {Recipe: Generate to String with Headers}[#label-Recipe-3A+Generate+to+String+with+Headers] + - {Recipe: Generate to String Without Headers}[#label-Recipe-3A+Generate+to+String+Without+Headers] + - {Generating to a File}[#label-Generating+to+a+File] + - {Recipe: Generate to File with Headers}[#label-Recipe-3A+Generate+to+File+with+Headers] + - {Recipe: Generate to File Without Headers}[#label-Recipe-3A+Generate+to+File+Without+Headers] + - {Generating to IO an Stream}[#label-Generating+to+an+IO+Stream] + - {Recipe: Generate to IO Stream with Headers}[#label-Recipe-3A+Generate+to+IO+Stream+with+Headers] + - {Recipe: Generate to IO Stream Without Headers}[#label-Recipe-3A+Generate+to+IO+Stream+Without+Headers] +- {Converting Fields}[#label-Converting+Fields] + - {Recipe: Filter Generated Field Strings}[#label-Recipe-3A+Filter+Generated+Field+Strings] + - {Recipe: Specify Multiple Write Converters}[#label-Recipe-3A+Specify+Multiple+Write+Converters] +- {RFC 4180 Compliance}[#label-RFC+4180+Compliance] + - {Row Separator}[#label-Row+Separator] + - {Recipe: Generate Compliant Row Separator}[#label-Recipe-3A+Generate+Compliant+Row+Separator] + - {Recipe: Generate Non-Compliant Row Separator}[#label-Recipe-3A+Generate+Non-Compliant+Row+Separator] + - {Column Separator}[#label-Column+Separator] + - {Recipe: Generate Compliant Column Separator}[#label-Recipe-3A+Generate+Compliant+Column+Separator] + - {Recipe: Generate Non-Compliant Column Separator}[#label-Recipe-3A+Generate+Non-Compliant+Column+Separator] + - {Quotes}[#label-Quotes] + - {Recipe: Quote All Fields}[#label-Recipe-3A+Quote+All+Fields] + - {Recipe: Quote Empty Fields}[#label-Recipe-3A+Quote+Empty+Fields] + - {Recipe: Generate Compliant Quote Character}[#label-Recipe-3A+Generate+Compliant+Quote+Character] + - {Recipe: Generate Non-Compliant Quote Character}[#label-Recipe-3A+Generate+Non-Compliant+Quote+Character] + +=== Output Formats + +You can generate \CSV output to a \String, to a \File (via its path), or to an \IO stream. + +==== Generating to a \String + +You can generate \CSV output to a \String, with or without headers. + +===== Recipe: Generate to \String with Headers + +Use class method CSV.generate with option +headers+ to generate to a \String. + +This example uses method CSV#<< to append the rows +that are to be generated: + output_string = CSV.generate('', headers: ['Name', 'Value'], write_headers: true) do |csv| + csv << ['Foo', 0] + csv << ['Bar', 1] + csv << ['Baz', 2] + end + output_string # => "Name,Value\nFoo,0\nBar,1\nBaz,2\n" + +===== Recipe: Generate to \String Without Headers + +Use class method CSV.generate without option +headers+ to generate to a \String. + +This example uses method CSV#<< to append the rows +that are to be generated: + output_string = CSV.generate do |csv| + csv << ['Foo', 0] + csv << ['Bar', 1] + csv << ['Baz', 2] + end + output_string # => "Foo,0\nBar,1\nBaz,2\n" + +==== Generating to a \File + +You can generate /CSV data to a \File, with or without headers. + +===== Recipe: Generate to \File with Headers + +Use class method CSV.open with option +headers+ generate to a \File. + +This example uses method CSV#<< to append the rows +that are to be generated: + path = 't.csv' + CSV.open(path, 'w', headers: ['Name', 'Value'], write_headers: true) do |csv| + csv << ['Foo', 0] + csv << ['Bar', 1] + csv << ['Baz', 2] + end + p File.read(path) # => "Name,Value\nFoo,0\nBar,1\nBaz,2\n" + +===== Recipe: Generate to \File Without Headers + +Use class method CSV.open without option +headers+ to generate to a \File. + +This example uses method CSV#<< to append the rows +that are to be generated: + path = 't.csv' + CSV.open(path, 'w') do |csv| + csv << ['Foo', 0] + csv << ['Bar', 1] + csv << ['Baz', 2] + end + p File.read(path) # => "Foo,0\nBar,1\nBaz,2\n" + +==== Generating to an \IO Stream + +You can generate \CSV data to an \IO stream, with or without headers. + +==== Recipe: Generate to \IO Stream with Headers + +Use class method CSV.new with option +headers+ to generate \CSV data to an \IO stream: + path = 't.csv' + File.open(path, 'w') do |file| + csv = CSV.new(file, headers: ['Name', 'Value'], write_headers: true) + csv << ['Foo', 0] + csv << ['Bar', 1] + csv << ['Baz', 2] + end + p File.read(path) # => "Name,Value\nFoo,0\nBar,1\nBaz,2\n" + +===== Recipe: Generate to \IO Stream Without Headers + +Use class method CSV.new without option +headers+ to generate \CSV data to an \IO stream: + path = 't.csv' + File.open(path, 'w') do |file| + csv = CSV.new(file) + csv << ['Foo', 0] + csv << ['Bar', 1] + csv << ['Baz', 2] + end + p File.read(path) # => "Foo,0\nBar,1\nBaz,2\n" + +=== Converting Fields + +You can use _write_ _converters_ to convert fields when generating \CSV. + +==== Recipe: Filter Generated Field Strings + +Use option :write_converters and a custom converter to convert field values when generating \CSV. + +This example defines and uses a custom write converter to strip whitespace from generated fields: + strip_converter = proc {|field| field.respond_to?(:strip) ? field.strip : field } + output_string = CSV.generate(write_converters: strip_converter) do |csv| + csv << [' foo ', 0] + csv << [' bar ', 1] + csv << [' baz ', 2] + end + output_string # => "foo,0\nbar,1\nbaz,2\n" + +==== Recipe: Specify Multiple Write Converters + +Use option :write_converters and multiple custom converters +to convert field values when generating \CSV. + +This example defines and uses two custom write converters to strip and upcase generated fields: + strip_converter = proc {|field| field.respond_to?(:strip) ? field.strip : field } + upcase_converter = proc {|field| field.respond_to?(:upcase) ? field.upcase : field } + converters = [strip_converter, upcase_converter] + output_string = CSV.generate(write_converters: converters) do |csv| + csv << [' foo ', 0] + csv << [' bar ', 1] + csv << [' baz ', 2] + end + output_string # => "FOO,0\nBAR,1\nBAZ,2\n" + +=== RFC 4180 Compliance + +By default, \CSV generates data that is compliant with +{RFC 4180}[https://www.rfc-editor.org/rfc/rfc4180] +with respect to: +- Column separator. +- Quote character. + +==== Row Separator + +RFC 4180 specifies the row separator CRLF (Ruby "\r\n"). + +===== Recipe: Generate Compliant Row Separator + +For strict compliance, use option +:row_sep+ to specify row separator "\r\n": + output_string = CSV.generate('', row_sep: "\r\n") do |csv| + csv << ['Foo', 0] + csv << ['Bar', 1] + csv << ['Baz', 2] + end + output_string # => "Foo,0\r\nBar,1\r\nBaz,2\r\n" + +===== Recipe: Generate Non-Compliant Row Separator + +For data with non-compliant row separators, use option +:row_sep+ with a different value: +This example source uses semicolon (";') as its row separator: + output_string = CSV.generate('', row_sep: ";") do |csv| + csv << ['Foo', 0] + csv << ['Bar', 1] + csv << ['Baz', 2] + end + output_string # => "Foo,0;Bar,1;Baz,2;" + +==== Column Separator + +RFC 4180 specifies column separator COMMA (Ruby ","). + +===== Recipe: Generate Compliant Column Separator + +Because the \CSV default comma separator is ",", +you need not specify option +:col_sep+ for compliant data: + output_string = CSV.generate('') do |csv| + csv << ['Foo', 0] + csv << ['Bar', 1] + csv << ['Baz', 2] + end + output_string # => "Foo,0\nBar,1\nBaz,2\n" + +===== Recipe: Generate Non-Compliant Column Separator + +For data with non-compliant column separators, use option +:col_sep+. +This example source uses TAB ("\t") as its column separator: + output_string = CSV.generate('', col_sep: "\t") do |csv| + csv << ['Foo', 0] + csv << ['Bar', 1] + csv << ['Baz', 2] + end + output_string # => "Foo\t0\nBar\t1\nBaz\t2\n" + +==== Quotes + +IFC 4180 allows most fields to be quoted or not. +By default, \CSV does not quote most fields. + +However, a field containing the current row separator, column separator, +or quote character is automatically quoted, producing IFC 4180 compliance: + # Field contains row separator. + output_string = CSV.generate('') do |csv| + row_sep = csv.row_sep + csv << ["Foo#{row_sep}Foo", 0] + csv << ['Bar', 1] + csv << ['Baz', 2] + end + output_string # => "\"Foo\nFoo\",0\nBar,1\nBaz,2\n" + # Field contains column separator. + output_string = CSV.generate('') do |csv| + col_sep = csv.col_sep + csv << ["Foo#{col_sep}Foo", 0] + csv << ['Bar', 1] + csv << ['Baz', 2] + end + output_string # => "\"Foo,Foo\",0\nBar,1\nBaz,2\n" + # Field contains quote character. + output_string = CSV.generate('') do |csv| + quote_char = csv.quote_char + csv << ["Foo#{quote_char}Foo", 0] + csv << ['Bar', 1] + csv << ['Baz', 2] + end + output_string # => "\"Foo\"\"Foo\",0\nBar,1\nBaz,2\n" + +===== Recipe: Quote All Fields + +Use option +:force_quotes+ to force quoted fields: + output_string = CSV.generate('', force_quotes: true) do |csv| + csv << ['Foo', 0] + csv << ['Bar', 1] + csv << ['Baz', 2] + end + output_string # => "\"Foo\",\"0\"\n\"Bar\",\"1\"\n\"Baz\",\"2\"\n" + +===== Recipe: Quote Empty Fields + +Use option +:quote_empty+ to force quoting for empty fields: + output_string = CSV.generate('', quote_empty: true) do |csv| + csv << ['Foo', 0] + csv << ['Bar', 1] + csv << ['', 2] + end + output_string # => "Foo,0\nBar,1\n\"\",2\n" + +===== Recipe: Generate Compliant Quote Character + +RFC 4180 specifies quote character DQUOTE (Ruby "\""). + +Because the \CSV default quote character is also "\"", +you need not specify option +:quote_char+ for compliant data: + output_string = CSV.generate('', force_quotes: true) do |csv| + csv << ['Foo', 0] + csv << ['Bar', 1] + csv << ['Baz', 2] + end + output_string # => "\"Foo\",\"0\"\n\"Bar\",\"1\"\n\"Baz\",\"2\"\n" + +===== Recipe: Generate Non-Compliant Quote Character + +For data with non-compliant quote characters, use option +:quote_char+. +This example source uses SQUOTE ("'") as its quote character: + output_string = CSV.generate('', quote_char: "'", force_quotes: true) do |csv| + csv << ['Foo', 0] + csv << ['Bar', 1] + csv << ['Baz', 2] + end + output_string # => "'Foo','0'\n'Bar','1'\n'Baz','2'\n" diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/recipes/parsing.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/recipes/parsing.rdoc new file mode 100644 index 00000000..63673072 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/recipes/parsing.rdoc @@ -0,0 +1,554 @@ +== Recipes for Parsing \CSV + +These recipes are specific code examples for specific \CSV parsing tasks. + +For other recipes, see {Recipes for CSV}[./recipes_rdoc.html]. + +All code snippets on this page assume that the following has been executed: + require 'csv' + +=== Contents + +- {Source Formats}[#label-Source+Formats] + - {Parsing from a String}[#label-Parsing+from+a+String] + - {Recipe: Parse from String with Headers}[#label-Recipe-3A+Parse+from+String+with+Headers] + - {Recipe: Parse from String Without Headers}[#label-Recipe-3A+Parse+from+String+Without+Headers] + - {Parsing from a File}[#label-Parsing+from+a+File] + - {Recipe: Parse from File with Headers}[#label-Recipe-3A+Parse+from+File+with+Headers] + - {Recipe: Parse from File Without Headers}[#label-Recipe-3A+Parse+from+File+Without+Headers] + - {Parsing from an IO Stream}[#label-Parsing+from+an+IO+Stream] + - {Recipe: Parse from IO Stream with Headers}[#label-Recipe-3A+Parse+from+IO+Stream+with+Headers] + - {Recipe: Parse from IO Stream Without Headers}[#label-Recipe-3A+Parse+from+IO+Stream+Without+Headers] +- {RFC 4180 Compliance}[#label-RFC+4180+Compliance] + - {Row Separator}[#label-Row+Separator] + - {Recipe: Handle Compliant Row Separator}[#label-Recipe-3A+Handle+Compliant+Row+Separator] + - {Recipe: Handle Non-Compliant Row Separator}[#label-Recipe-3A+Handle+Non-Compliant+Row+Separator] + - {Column Separator}[#label-Column+Separator] + - {Recipe: Handle Compliant Column Separator}[#label-Recipe-3A+Handle+Compliant+Column+Separator] + - {Recipe: Handle Non-Compliant Column Separator}[#label-Recipe-3A+Handle+Non-Compliant+Column+Separator] + - {Quote Character}[#label-Quote+Character] + - {Recipe: Handle Compliant Quote Character}[#label-Recipe-3A+Handle+Compliant+Quote+Character] + - {Recipe: Handle Non-Compliant Quote Character}[#label-Recipe-3A+Handle+Non-Compliant+Quote+Character] + - {Recipe: Allow Liberal Parsing}[#label-Recipe-3A+Allow+Liberal+Parsing] +- {Special Handling}[#label-Special+Handling] + - {Special Line Handling}[#label-Special+Line+Handling] + - {Recipe: Ignore Blank Lines}[#label-Recipe-3A+Ignore+Blank+Lines] + - {Recipe: Ignore Selected Lines}[#label-Recipe-3A+Ignore+Selected+Lines] + - {Special Field Handling}[#label-Special+Field+Handling] + - {Recipe: Strip Fields}[#label-Recipe-3A+Strip+Fields] + - {Recipe: Handle Null Fields}[#label-Recipe-3A+Handle+Null+Fields] + - {Recipe: Handle Empty Fields}[#label-Recipe-3A+Handle+Empty+Fields] +- {Converting Fields}[#label-Converting+Fields] + - {Converting Fields to Objects}[#label-Converting+Fields+to+Objects] + - {Recipe: Convert Fields to Integers}[#label-Recipe-3A+Convert+Fields+to+Integers] + - {Recipe: Convert Fields to Floats}[#label-Recipe-3A+Convert+Fields+to+Floats] + - {Recipe: Convert Fields to Numerics}[#label-Recipe-3A+Convert+Fields+to+Numerics] + - {Recipe: Convert Fields to Dates}[#label-Recipe-3A+Convert+Fields+to+Dates] + - {Recipe: Convert Fields to DateTimes}[#label-Recipe-3A+Convert+Fields+to+DateTimes] + - {Recipe: Convert Fields to Times}[#label-Recipe-3A+Convert+Fields+to+Times] + - {Recipe: Convert Assorted Fields to Objects}[#label-Recipe-3A+Convert+Assorted+Fields+to+Objects] + - {Recipe: Convert Fields to Other Objects}[#label-Recipe-3A+Convert+Fields+to+Other+Objects] + - {Recipe: Filter Field Strings}[#label-Recipe-3A+Filter+Field+Strings] + - {Recipe: Register Field Converters}[#label-Recipe-3A+Register+Field+Converters] + - {Using Multiple Field Converters}[#label-Using+Multiple+Field+Converters] + - {Recipe: Specify Multiple Field Converters in Option :converters}[#label-Recipe-3A+Specify+Multiple+Field+Converters+in+Option+-3Aconverters] + - {Recipe: Specify Multiple Field Converters in a Custom Converter List}[#label-Recipe-3A+Specify+Multiple+Field+Converters+in+a+Custom+Converter+List] +- {Converting Headers}[#label-Converting+Headers] + - {Recipe: Convert Headers to Lowercase}[#label-Recipe-3A+Convert+Headers+to+Lowercase] + - {Recipe: Convert Headers to Symbols}[#label-Recipe-3A+Convert+Headers+to+Symbols] + - {Recipe: Filter Header Strings}[#label-Recipe-3A+Filter+Header+Strings] + - {Recipe: Register Header Converters}[#label-Recipe-3A+Register+Header+Converters] + - {Using Multiple Header Converters}[#label-Using+Multiple+Header+Converters] + - {Recipe: Specify Multiple Header Converters in Option :header_converters}[#label-Recipe-3A+Specify+Multiple+Header+Converters+in+Option+-3Aheader_converters] + - {Recipe: Specify Multiple Header Converters in a Custom Header Converter List}[#label-Recipe-3A+Specify+Multiple+Header+Converters+in+a+Custom+Header+Converter+List] +- {Diagnostics}[#label-Diagnostics] + - {Recipe: Capture Unconverted Fields}[#label-Recipe-3A+Capture+Unconverted+Fields] + - {Recipe: Capture Field Info}[#label-Recipe-3A+Capture+Field+Info] + +=== Source Formats + +You can parse \CSV data from a \String, from a \File (via its path), or from an \IO stream. + +==== Parsing from a \String + +You can parse \CSV data from a \String, with or without headers. + +===== Recipe: Parse from \String with Headers + +Use class method CSV.parse with option +headers+ to read a source \String all at once +(may have memory resource implications): + string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + CSV.parse(string, headers: true) # => # + +Use instance method CSV#each with option +headers+ to read a source \String one row at a time: + CSV.new(string, headers: true).each do |row| + p row + end +Output: + # + # + # + +===== Recipe: Parse from \String Without Headers + +Use class method CSV.parse without option +headers+ to read a source \String all at once +(may have memory resource implications): + string = "foo,0\nbar,1\nbaz,2\n" + CSV.parse(string) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + +Use instance method CSV#each without option +headers+ to read a source \String one row at a time: + CSV.new(string).each do |row| + p row + end +Output: + ["foo", "0"] + ["bar", "1"] + ["baz", "2"] + +==== Parsing from a \File + +You can parse \CSV data from a \File, with or without headers. + +===== Recipe: Parse from \File with Headers + +Use class method CSV.read with option +headers+ to read a file all at once: + string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + path = 't.csv' + File.write(path, string) + CSV.read(path, headers: true) # => # + +Use class method CSV.foreach with option +headers+ to read one row at a time: + CSV.foreach(path, headers: true) do |row| + p row + end +Output: + # + # + # + +===== Recipe: Parse from \File Without Headers + +Use class method CSV.read without option +headers+ to read a file all at once: + string = "foo,0\nbar,1\nbaz,2\n" + path = 't.csv' + File.write(path, string) + CSV.read(path) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + +Use class method CSV.foreach without option +headers+ to read one row at a time: + CSV.foreach(path) do |row| + p row + end +Output: + ["foo", "0"] + ["bar", "1"] + ["baz", "2"] + +==== Parsing from an \IO Stream + +You can parse \CSV data from an \IO stream, with or without headers. + +===== Recipe: Parse from \IO Stream with Headers + +Use class method CSV.parse with option +headers+ to read an \IO stream all at once: + string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + path = 't.csv' + File.write(path, string) + File.open(path) do |file| + CSV.parse(file, headers: true) + end # => # + +Use class method CSV.foreach with option +headers+ to read one row at a time: + File.open(path) do |file| + CSV.foreach(file, headers: true) do |row| + p row + end + end +Output: + # + # + # + +===== Recipe: Parse from \IO Stream Without Headers + +Use class method CSV.parse without option +headers+ to read an \IO stream all at once: + string = "foo,0\nbar,1\nbaz,2\n" + path = 't.csv' + File.write(path, string) + File.open(path) do |file| + CSV.parse(file) + end # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + +Use class method CSV.foreach without option +headers+ to read one row at a time: + File.open(path) do |file| + CSV.foreach(file) do |row| + p row + end + end +Output: + ["foo", "0"] + ["bar", "1"] + ["baz", "2"] + +=== RFC 4180 Compliance + +By default, \CSV parses data that is compliant with +{RFC 4180}[https://www.rfc-editor.org/rfc/rfc4180] +with respect to: +- Row separator. +- Column separator. +- Quote character. + +==== Row Separator + +RFC 4180 specifies the row separator CRLF (Ruby "\r\n"). + +Although the \CSV default row separator is "\n", +the parser also by default handles row separator "\r" and the RFC-compliant "\r\n". + +===== Recipe: Handle Compliant Row Separator + +For strict compliance, use option +:row_sep+ to specify row separator "\r\n", +which allows the compliant row separator: + source = "foo,1\r\nbar,1\r\nbaz,2\r\n" + CSV.parse(source, row_sep: "\r\n") # => [["foo", "1"], ["bar", "1"], ["baz", "2"]] +But rejects other row separators: + source = "foo,1\nbar,1\nbaz,2\n" + CSV.parse(source, row_sep: "\r\n") # Raised MalformedCSVError + source = "foo,1\rbar,1\rbaz,2\r" + CSV.parse(source, row_sep: "\r\n") # Raised MalformedCSVError + source = "foo,1\n\rbar,1\n\rbaz,2\n\r" + CSV.parse(source, row_sep: "\r\n") # Raised MalformedCSVError + +===== Recipe: Handle Non-Compliant Row Separator + +For data with non-compliant row separators, use option +:row_sep+. +This example source uses semicolon (";") as its row separator: + source = "foo,1;bar,1;baz,2;" + CSV.parse(source, row_sep: ';') # => [["foo", "1"], ["bar", "1"], ["baz", "2"]] + +==== Column Separator + +RFC 4180 specifies column separator COMMA (Ruby ","). + +===== Recipe: Handle Compliant Column Separator + +Because the \CSV default comma separator is ',', +you need not specify option +:col_sep+ for compliant data: + source = "foo,1\nbar,1\nbaz,2\n" + CSV.parse(source) # => [["foo", "1"], ["bar", "1"], ["baz", "2"]] + +===== Recipe: Handle Non-Compliant Column Separator + +For data with non-compliant column separators, use option +:col_sep+. +This example source uses TAB ("\t") as its column separator: + source = "foo,1\tbar,1\tbaz,2" + CSV.parse(source, col_sep: "\t") # => [["foo", "1"], ["bar", "1"], ["baz", "2"]] + +==== Quote Character + +RFC 4180 specifies quote character DQUOTE (Ruby "\""). + +===== Recipe: Handle Compliant Quote Character + +Because the \CSV default quote character is "\"", +you need not specify option +:quote_char+ for compliant data: + source = "\"foo\",\"1\"\n\"bar\",\"1\"\n\"baz\",\"2\"\n" + CSV.parse(source) # => [["foo", "1"], ["bar", "1"], ["baz", "2"]] + +===== Recipe: Handle Non-Compliant Quote Character + +For data with non-compliant quote characters, use option +:quote_char+. +This example source uses SQUOTE ("'") as its quote character: + source = "'foo','1'\n'bar','1'\n'baz','2'\n" + CSV.parse(source, quote_char: "'") # => [["foo", "1"], ["bar", "1"], ["baz", "2"]] + +==== Recipe: Allow Liberal Parsing + +Use option +:liberal_parsing+ to specify that \CSV should +attempt to parse input not conformant with RFC 4180, such as double quotes in unquoted fields: + source = 'is,this "three, or four",fields' + CSV.parse(source) # Raises MalformedCSVError + CSV.parse(source, liberal_parsing: true) # => [["is", "this \"three", " or four\"", "fields"]] + +=== Special Handling + +You can use parsing options to specify special handling for certain lines and fields. + +==== Special Line Handling + +Use parsing options to specify special handling for blank lines, or for other selected lines. + +===== Recipe: Ignore Blank Lines + +Use option +:skip_blanks+ to ignore blank lines: + source = <<-EOT + foo,0 + + bar,1 + baz,2 + + , + EOT + parsed = CSV.parse(source, skip_blanks: true) + parsed # => [["foo", "0"], ["bar", "1"], ["baz", "2"], [nil, nil]] + +===== Recipe: Ignore Selected Lines + +Use option +:skip_lines+ to ignore selected lines. + source = <<-EOT + # Comment + foo,0 + bar,1 + baz,2 + # Another comment + EOT + parsed = CSV.parse(source, skip_lines: /^#/) + parsed # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + +==== Special Field Handling + +Use parsing options to specify special handling for certain field values. + +===== Recipe: Strip Fields + +Use option +:strip+ to strip parsed field values: + CSV.parse_line(' a , b ', strip: true) # => ["a", "b"] + +===== Recipe: Handle Null Fields + +Use option +:nil_value+ to specify a value that will replace each field +that is null (no text): + CSV.parse_line('a,,b,,c', nil_value: 0) # => ["a", 0, "b", 0, "c"] + +===== Recipe: Handle Empty Fields + +Use option +:empty_value+ to specify a value that will replace each field +that is empty (\String of length 0); + CSV.parse_line('a,"",b,"",c', empty_value: 'x') # => ["a", "x", "b", "x", "c"] + +=== Converting Fields + +You can use field converters to change parsed \String fields into other objects, +or to otherwise modify the \String fields. + +==== Converting Fields to Objects + +Use field converters to change parsed \String objects into other, more specific, objects. + +There are built-in field converters for converting to objects of certain classes: +- \Float +- \Integer +- \Date +- \DateTime +- \Time + +Other built-in field converters include: +- +:numeric+: converts to \Integer and \Float. +- +:all+: converts to \DateTime, \Integer, \Float. + +You can also define field converters to convert to objects of other classes. + +===== Recipe: Convert Fields to Integers + +Convert fields to \Integer objects using built-in converter +:integer+: + source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + parsed = CSV.parse(source, headers: true, converters: :integer) + parsed.map {|row| row['Value'].class} # => [Integer, Integer, Integer] + +===== Recipe: Convert Fields to Floats + +Convert fields to \Float objects using built-in converter +:float+: + source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + parsed = CSV.parse(source, headers: true, converters: :float) + parsed.map {|row| row['Value'].class} # => [Float, Float, Float] + +===== Recipe: Convert Fields to Numerics + +Convert fields to \Integer and \Float objects using built-in converter +:numeric+: + source = "Name,Value\nfoo,0\nbar,1.1\nbaz,2.2\n" + parsed = CSV.parse(source, headers: true, converters: :numeric) + parsed.map {|row| row['Value'].class} # => [Integer, Float, Float] + +===== Recipe: Convert Fields to Dates + +Convert fields to \Date objects using built-in converter +:date+: + source = "Name,Date\nfoo,2001-02-03\nbar,2001-02-04\nbaz,2001-02-03\n" + parsed = CSV.parse(source, headers: true, converters: :date) + parsed.map {|row| row['Date'].class} # => [Date, Date, Date] + +===== Recipe: Convert Fields to DateTimes + +Convert fields to \DateTime objects using built-in converter +:date_time+: + source = "Name,DateTime\nfoo,2001-02-03\nbar,2001-02-04\nbaz,2020-05-07T14:59:00-05:00\n" + parsed = CSV.parse(source, headers: true, converters: :date_time) + parsed.map {|row| row['DateTime'].class} # => [DateTime, DateTime, DateTime] + +===== Recipe: Convert Fields to Times + +Convert fields to \Time objects using built-in converter +:time+: + source = "Name,Time\nfoo,2001-02-03\nbar,2001-02-04\nbaz,2020-05-07T14:59:00-05:00\n" + parsed = CSV.parse(source, headers: true, converters: :time) + parsed.map {|row| row['Time'].class} # => [Time, Time, Time] + +===== Recipe: Convert Assorted Fields to Objects + +Convert assorted fields to objects using built-in converter +:all+: + source = "Type,Value\nInteger,0\nFloat,1.0\nDateTime,2001-02-04\n" + parsed = CSV.parse(source, headers: true, converters: :all) + parsed.map {|row| row['Value'].class} # => [Integer, Float, DateTime] + +===== Recipe: Convert Fields to Other Objects + +Define a custom field converter to convert \String fields into other objects. +This example defines and uses a custom field converter +that converts each column-1 value to a \Rational object: + rational_converter = proc do |field, field_context| + field_context.index == 1 ? field.to_r : field + end + source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + parsed = CSV.parse(source, headers: true, converters: rational_converter) + parsed.map {|row| row['Value'].class} # => [Rational, Rational, Rational] + +==== Recipe: Filter Field Strings + +Define a custom field converter to modify \String fields. +This example defines and uses a custom field converter +that strips whitespace from each field value: + strip_converter = proc {|field| field.strip } + source = "Name,Value\n foo , 0 \n bar , 1 \n baz , 2 \n" + parsed = CSV.parse(source, headers: true, converters: strip_converter) + parsed['Name'] # => ["foo", "bar", "baz"] + parsed['Value'] # => ["0", "1", "2"] + +==== Recipe: Register Field Converters + +Register a custom field converter, assigning it a name; +then refer to the converter by its name: + rational_converter = proc do |field, field_context| + field_context.index == 1 ? field.to_r : field + end + CSV::Converters[:rational] = rational_converter + source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + parsed = CSV.parse(source, headers: true, converters: :rational) + parsed['Value'] # => [(0/1), (1/1), (2/1)] + +==== Using Multiple Field Converters + +You can use multiple field converters in either of these ways: +- Specify converters in option +:converters+. +- Specify converters in a custom converter list. + +===== Recipe: Specify Multiple Field Converters in Option +:converters+ + +Apply multiple field converters by specifying them in option +:converters+: + source = "Name,Value\nfoo,0\nbar,1.0\nbaz,2.0\n" + parsed = CSV.parse(source, headers: true, converters: [:integer, :float]) + parsed['Value'] # => [0, 1.0, 2.0] + +===== Recipe: Specify Multiple Field Converters in a Custom Converter List + +Apply multiple field converters by defining and registering a custom converter list: + strip_converter = proc {|field| field.strip } + CSV::Converters[:strip] = strip_converter + CSV::Converters[:my_converters] = [:integer, :float, :strip] + source = "Name,Value\n foo , 0 \n bar , 1.0 \n baz , 2.0 \n" + parsed = CSV.parse(source, headers: true, converters: :my_converters) + parsed['Name'] # => ["foo", "bar", "baz"] + parsed['Value'] # => [0, 1.0, 2.0] + +=== Converting Headers + +You can use header converters to modify parsed \String headers. + +Built-in header converters include: +- +:symbol+: converts \String header to \Symbol. +- +:downcase+: converts \String header to lowercase. + +You can also define header converters to otherwise modify header \Strings. + +==== Recipe: Convert Headers to Lowercase + +Convert headers to lowercase using built-in converter +:downcase+: + source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + parsed = CSV.parse(source, headers: true, header_converters: :downcase) + parsed.headers # => ["name", "value"] + +==== Recipe: Convert Headers to Symbols + +Convert headers to downcased Symbols using built-in converter +:symbol+: + source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + parsed = CSV.parse(source, headers: true, header_converters: :symbol) + parsed.headers # => [:name, :value] + parsed.headers.map {|header| header.class} # => [Symbol, Symbol] + +==== Recipe: Filter Header Strings + +Define a custom header converter to modify \String fields. +This example defines and uses a custom header converter +that capitalizes each header \String: + capitalize_converter = proc {|header| header.capitalize } + source = "NAME,VALUE\nfoo,0\nbar,1\nbaz,2\n" + parsed = CSV.parse(source, headers: true, header_converters: capitalize_converter) + parsed.headers # => ["Name", "Value"] + +==== Recipe: Register Header Converters + +Register a custom header converter, assigning it a name; +then refer to the converter by its name: + capitalize_converter = proc {|header| header.capitalize } + CSV::HeaderConverters[:capitalize] = capitalize_converter + source = "NAME,VALUE\nfoo,0\nbar,1\nbaz,2\n" + parsed = CSV.parse(source, headers: true, header_converters: :capitalize) + parsed.headers # => ["Name", "Value"] + +==== Using Multiple Header Converters + +You can use multiple header converters in either of these ways: +- Specify header converters in option +:header_converters+. +- Specify header converters in a custom header converter list. + +===== Recipe: Specify Multiple Header Converters in Option :header_converters + +Apply multiple header converters by specifying them in option +:header_converters+: + source = "Name,Value\nfoo,0\nbar,1.0\nbaz,2.0\n" + parsed = CSV.parse(source, headers: true, header_converters: [:downcase, :symbol]) + parsed.headers # => [:name, :value] + +===== Recipe: Specify Multiple Header Converters in a Custom Header Converter List + +Apply multiple header converters by defining and registering a custom header converter list: + CSV::HeaderConverters[:my_header_converters] = [:symbol, :downcase] + source = "NAME,VALUE\nfoo,0\nbar,1.0\nbaz,2.0\n" + parsed = CSV.parse(source, headers: true, header_converters: :my_header_converters) + parsed.headers # => [:name, :value] + +=== Diagnostics + +==== Recipe: Capture Unconverted Fields + +To capture unconverted field values, use option +:unconverted_fields+: + source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + parsed = CSV.parse(source, converters: :integer, unconverted_fields: true) + parsed # => [["Name", "Value"], ["foo", 0], ["bar", 1], ["baz", 2]] + parsed.each {|row| p row.unconverted_fields } +Output: + ["Name", "Value"] + ["foo", "0"] + ["bar", "1"] + ["baz", "2"] + +==== Recipe: Capture Field Info + +To capture field info in a custom converter, accept two block arguments. +The first is the field value; the second is a +CSV::FieldInfo+ object: + strip_converter = proc {|field, field_info| p field_info; field.strip } + source = " foo , 0 \n bar , 1 \n baz , 2 \n" + parsed = CSV.parse(source, converters: strip_converter) + parsed # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] +Output: + # + # + # + # + # + # diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/recipes/recipes.rdoc b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/recipes/recipes.rdoc new file mode 100644 index 00000000..9bf7885b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/doc/csv/recipes/recipes.rdoc @@ -0,0 +1,6 @@ +== Recipes for \CSV + +The recipes are specific code examples for specific tasks. See: +- {Recipes for Parsing CSV}[./parsing_rdoc.html] +- {Recipes for Generating CSV}[./generating_rdoc.html] +- {Recipes for Filtering CSV}[./filtering_rdoc.html] diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv.rb b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv.rb new file mode 100644 index 00000000..aef96ac9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv.rb @@ -0,0 +1,3017 @@ +# encoding: US-ASCII +# frozen_string_literal: true +# = csv.rb -- CSV Reading and Writing +# +# Created by James Edward Gray II on 2005-10-31. +# +# See CSV for documentation. +# +# == Description +# +# Welcome to the new and improved CSV. +# +# This version of the CSV library began its life as FasterCSV. FasterCSV was +# intended as a replacement to Ruby's then standard CSV library. It was +# designed to address concerns users of that library had and it had three +# primary goals: +# +# 1. Be significantly faster than CSV while remaining a pure Ruby library. +# 2. Use a smaller and easier to maintain code base. (FasterCSV eventually +# grew larger, was also but considerably richer in features. The parsing +# core remains quite small.) +# 3. Improve on the CSV interface. +# +# Obviously, the last one is subjective. I did try to defer to the original +# interface whenever I didn't have a compelling reason to change it though, so +# hopefully this won't be too radically different. +# +# We must have met our goals because FasterCSV was renamed to CSV and replaced +# the original library as of Ruby 1.9. If you are migrating code from 1.8 or +# earlier, you may have to change your code to comply with the new interface. +# +# == What's the Different From the Old CSV? +# +# I'm sure I'll miss something, but I'll try to mention most of the major +# differences I am aware of, to help others quickly get up to speed: +# +# === \CSV Parsing +# +# * This parser is m17n aware. See CSV for full details. +# * This library has a stricter parser and will throw MalformedCSVErrors on +# problematic data. +# * This library has a less liberal idea of a line ending than CSV. What you +# set as the :row_sep is law. It can auto-detect your line endings +# though. +# * The old library returned empty lines as [nil]. This library calls +# them []. +# * This library has a much faster parser. +# +# === Interface +# +# * CSV now uses keyword parameters to set options. +# * CSV no longer has generate_row() or parse_row(). +# * The old CSV's Reader and Writer classes have been dropped. +# * CSV::open() is now more like Ruby's open(). +# * CSV objects now support most standard IO methods. +# * CSV now has a new() method used to wrap objects like String and IO for +# reading and writing. +# * CSV::generate() is different from the old method. +# * CSV no longer supports partial reads. It works line-by-line. +# * CSV no longer allows the instance methods to override the separators for +# performance reasons. They must be set in the constructor. +# +# If you use this library and find yourself missing any functionality I have +# trimmed, please {let me know}[mailto:james@grayproductions.net]. +# +# == Documentation +# +# See CSV for documentation. +# +# == What is CSV, really? +# +# CSV maintains a pretty strict definition of CSV taken directly from +# {the RFC}[https://www.ietf.org/rfc/rfc4180.txt]. I relax the rules in only one +# place and that is to make using this library easier. CSV will parse all valid +# CSV. +# +# What you don't want to do is to feed CSV invalid data. Because of the way the +# CSV format works, it's common for a parser to need to read until the end of +# the file to be sure a field is invalid. This consumes a lot of time and memory. +# +# Luckily, when working with invalid CSV, Ruby's built-in methods will almost +# always be superior in every way. For example, parsing non-quoted fields is as +# easy as: +# +# data.split(",") +# +# == Questions and/or Comments +# +# Feel free to email {James Edward Gray II}[mailto:james@grayproductions.net] +# with any questions. + +require "forwardable" +require "date" +require "time" +require "stringio" + +require_relative "csv/fields_converter" +require_relative "csv/input_record_separator" +require_relative "csv/parser" +require_relative "csv/row" +require_relative "csv/table" +require_relative "csv/writer" + +# == \CSV +# +# === \CSV Data +# +# \CSV (comma-separated values) data is a text representation of a table: +# - A _row_ _separator_ delimits table rows. +# A common row separator is the newline character "\n". +# - A _column_ _separator_ delimits fields in a row. +# A common column separator is the comma character ",". +# +# This \CSV \String, with row separator "\n" +# and column separator ",", +# has three rows and two columns: +# "foo,0\nbar,1\nbaz,2\n" +# +# Despite the name \CSV, a \CSV representation can use different separators. +# +# For more about tables, see the Wikipedia article +# "{Table (information)}[https://en.wikipedia.org/wiki/Table_(information)]", +# especially its section +# "{Simple table}[https://en.wikipedia.org/wiki/Table_(information)#Simple_table]" +# +# == \Class \CSV +# +# Class \CSV provides methods for: +# - Parsing \CSV data from a \String object, a \File (via its file path), or an \IO object. +# - Generating \CSV data to a \String object. +# +# To make \CSV available: +# require 'csv' +# +# All examples here assume that this has been done. +# +# == Keeping It Simple +# +# A \CSV object has dozens of instance methods that offer fine-grained control +# of parsing and generating \CSV data. +# For many needs, though, simpler approaches will do. +# +# This section summarizes the singleton methods in \CSV +# that allow you to parse and generate without explicitly +# creating \CSV objects. +# For details, follow the links. +# +# === Simple Parsing +# +# Parsing methods commonly return either of: +# - An \Array of Arrays of Strings: +# - The outer \Array is the entire "table". +# - Each inner \Array is a row. +# - Each \String is a field. +# - A CSV::Table object. For details, see +# {\CSV with Headers}[#class-CSV-label-CSV+with+Headers]. +# +# ==== Parsing a \String +# +# The input to be parsed can be a string: +# string = "foo,0\nbar,1\nbaz,2\n" +# +# \Method CSV.parse returns the entire \CSV data: +# CSV.parse(string) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] +# +# \Method CSV.parse_line returns only the first row: +# CSV.parse_line(string) # => ["foo", "0"] +# +# \CSV extends class \String with instance method String#parse_csv, +# which also returns only the first row: +# string.parse_csv # => ["foo", "0"] +# +# ==== Parsing Via a \File Path +# +# The input to be parsed can be in a file: +# string = "foo,0\nbar,1\nbaz,2\n" +# path = 't.csv' +# File.write(path, string) +# +# \Method CSV.read returns the entire \CSV data: +# CSV.read(path) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] +# +# \Method CSV.foreach iterates, passing each row to the given block: +# CSV.foreach(path) do |row| +# p row +# end +# Output: +# ["foo", "0"] +# ["bar", "1"] +# ["baz", "2"] +# +# \Method CSV.table returns the entire \CSV data as a CSV::Table object: +# CSV.table(path) # => # +# +# ==== Parsing from an Open \IO Stream +# +# The input to be parsed can be in an open \IO stream: +# +# \Method CSV.read returns the entire \CSV data: +# File.open(path) do |file| +# CSV.read(file) +# end # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] +# +# As does method CSV.parse: +# File.open(path) do |file| +# CSV.parse(file) +# end # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] +# +# \Method CSV.parse_line returns only the first row: +# File.open(path) do |file| +# CSV.parse_line(file) +# end # => ["foo", "0"] +# +# \Method CSV.foreach iterates, passing each row to the given block: +# File.open(path) do |file| +# CSV.foreach(file) do |row| +# p row +# end +# end +# Output: +# ["foo", "0"] +# ["bar", "1"] +# ["baz", "2"] +# +# \Method CSV.table returns the entire \CSV data as a CSV::Table object: +# File.open(path) do |file| +# CSV.table(file) +# end # => # +# +# === Simple Generating +# +# \Method CSV.generate returns a \String; +# this example uses method CSV#<< to append the rows +# that are to be generated: +# output_string = CSV.generate do |csv| +# csv << ['foo', 0] +# csv << ['bar', 1] +# csv << ['baz', 2] +# end +# output_string # => "foo,0\nbar,1\nbaz,2\n" +# +# \Method CSV.generate_line returns a \String containing the single row +# constructed from an \Array: +# CSV.generate_line(['foo', '0']) # => "foo,0\n" +# +# \CSV extends class \Array with instance method Array#to_csv, +# which forms an \Array into a \String: +# ['foo', '0'].to_csv # => "foo,0\n" +# +# === "Filtering" \CSV +# +# \Method CSV.filter provides a Unix-style filter for \CSV data. +# The input data is processed to form the output data: +# in_string = "foo,0\nbar,1\nbaz,2\n" +# out_string = '' +# CSV.filter(in_string, out_string) do |row| +# row[0] = row[0].upcase +# row[1] *= 4 +# end +# out_string # => "FOO,0000\nBAR,1111\nBAZ,2222\n" +# +# == \CSV Objects +# +# There are three ways to create a \CSV object: +# - \Method CSV.new returns a new \CSV object. +# - \Method CSV.instance returns a new or cached \CSV object. +# - \Method \CSV() also returns a new or cached \CSV object. +# +# === Instance Methods +# +# \CSV has three groups of instance methods: +# - Its own internally defined instance methods. +# - Methods included by module Enumerable. +# - Methods delegated to class IO. See below. +# +# ==== Delegated Methods +# +# For convenience, a CSV object will delegate to many methods in class IO. +# (A few have wrapper "guard code" in \CSV.) You may call: +# * IO#binmode +# * #binmode? +# * IO#close +# * IO#close_read +# * IO#close_write +# * IO#closed? +# * #eof +# * #eof? +# * IO#external_encoding +# * IO#fcntl +# * IO#fileno +# * #flock +# * IO#flush +# * IO#fsync +# * IO#internal_encoding +# * #ioctl +# * IO#isatty +# * #path +# * IO#pid +# * IO#pos +# * IO#pos= +# * IO#reopen +# * #rewind +# * IO#seek +# * #stat +# * IO#string +# * IO#sync +# * IO#sync= +# * IO#tell +# * #to_i +# * #to_io +# * IO#truncate +# * IO#tty? +# +# === Options +# +# The default values for options are: +# DEFAULT_OPTIONS = { +# # For both parsing and generating. +# col_sep: ",", +# row_sep: :auto, +# quote_char: '"', +# # For parsing. +# field_size_limit: nil, +# converters: nil, +# unconverted_fields: nil, +# headers: false, +# return_headers: false, +# header_converters: nil, +# skip_blanks: false, +# skip_lines: nil, +# liberal_parsing: false, +# nil_value: nil, +# empty_value: "", +# strip: false, +# # For generating. +# write_headers: nil, +# quote_empty: true, +# force_quotes: false, +# write_converters: nil, +# write_nil_value: nil, +# write_empty_value: "", +# } +# +# ==== Options for Parsing +# +# Options for parsing, described in detail below, include: +# - +row_sep+: Specifies the row separator; used to delimit rows. +# - +col_sep+: Specifies the column separator; used to delimit fields. +# - +quote_char+: Specifies the quote character; used to quote fields. +# - +field_size_limit+: Specifies the maximum field size + 1 allowed. +# Deprecated since 3.2.3. Use +max_field_size+ instead. +# - +max_field_size+: Specifies the maximum field size allowed. +# - +converters+: Specifies the field converters to be used. +# - +unconverted_fields+: Specifies whether unconverted fields are to be available. +# - +headers+: Specifies whether data contains headers, +# or specifies the headers themselves. +# - +return_headers+: Specifies whether headers are to be returned. +# - +header_converters+: Specifies the header converters to be used. +# - +skip_blanks+: Specifies whether blanks lines are to be ignored. +# - +skip_lines+: Specifies how comments lines are to be recognized. +# - +strip+: Specifies whether leading and trailing whitespace are to be +# stripped from fields. This must be compatible with +col_sep+; if it is not, +# then an +ArgumentError+ exception will be raised. +# - +liberal_parsing+: Specifies whether \CSV should attempt to parse +# non-compliant data. +# - +nil_value+: Specifies the object that is to be substituted for each null (no-text) field. +# - +empty_value+: Specifies the object that is to be substituted for each empty field. +# +# :include: ../doc/csv/options/common/row_sep.rdoc +# +# :include: ../doc/csv/options/common/col_sep.rdoc +# +# :include: ../doc/csv/options/common/quote_char.rdoc +# +# :include: ../doc/csv/options/parsing/field_size_limit.rdoc +# +# :include: ../doc/csv/options/parsing/converters.rdoc +# +# :include: ../doc/csv/options/parsing/unconverted_fields.rdoc +# +# :include: ../doc/csv/options/parsing/headers.rdoc +# +# :include: ../doc/csv/options/parsing/return_headers.rdoc +# +# :include: ../doc/csv/options/parsing/header_converters.rdoc +# +# :include: ../doc/csv/options/parsing/skip_blanks.rdoc +# +# :include: ../doc/csv/options/parsing/skip_lines.rdoc +# +# :include: ../doc/csv/options/parsing/strip.rdoc +# +# :include: ../doc/csv/options/parsing/liberal_parsing.rdoc +# +# :include: ../doc/csv/options/parsing/nil_value.rdoc +# +# :include: ../doc/csv/options/parsing/empty_value.rdoc +# +# ==== Options for Generating +# +# Options for generating, described in detail below, include: +# - +row_sep+: Specifies the row separator; used to delimit rows. +# - +col_sep+: Specifies the column separator; used to delimit fields. +# - +quote_char+: Specifies the quote character; used to quote fields. +# - +write_headers+: Specifies whether headers are to be written. +# - +force_quotes+: Specifies whether each output field is to be quoted. +# - +quote_empty+: Specifies whether each empty output field is to be quoted. +# - +write_converters+: Specifies the field converters to be used in writing. +# - +write_nil_value+: Specifies the object that is to be substituted for each +nil+-valued field. +# - +write_empty_value+: Specifies the object that is to be substituted for each empty field. +# +# :include: ../doc/csv/options/common/row_sep.rdoc +# +# :include: ../doc/csv/options/common/col_sep.rdoc +# +# :include: ../doc/csv/options/common/quote_char.rdoc +# +# :include: ../doc/csv/options/generating/write_headers.rdoc +# +# :include: ../doc/csv/options/generating/force_quotes.rdoc +# +# :include: ../doc/csv/options/generating/quote_empty.rdoc +# +# :include: ../doc/csv/options/generating/write_converters.rdoc +# +# :include: ../doc/csv/options/generating/write_nil_value.rdoc +# +# :include: ../doc/csv/options/generating/write_empty_value.rdoc +# +# === \CSV with Headers +# +# CSV allows to specify column names of CSV file, whether they are in data, or +# provided separately. If headers are specified, reading methods return an instance +# of CSV::Table, consisting of CSV::Row. +# +# # Headers are part of data +# data = CSV.parse(<<~ROWS, headers: true) +# Name,Department,Salary +# Bob,Engineering,1000 +# Jane,Sales,2000 +# John,Management,5000 +# ROWS +# +# data.class #=> CSV::Table +# data.first #=> # +# data.first.to_h #=> {"Name"=>"Bob", "Department"=>"Engineering", "Salary"=>"1000"} +# +# # Headers provided by developer +# data = CSV.parse('Bob,Engineering,1000', headers: %i[name department salary]) +# data.first #=> # +# +# === \Converters +# +# By default, each value (field or header) parsed by \CSV is formed into a \String. +# You can use a _field_ _converter_ or _header_ _converter_ +# to intercept and modify the parsed values: +# - See {Field Converters}[#class-CSV-label-Field+Converters]. +# - See {Header Converters}[#class-CSV-label-Header+Converters]. +# +# Also by default, each value to be written during generation is written 'as-is'. +# You can use a _write_ _converter_ to modify values before writing. +# - See {Write Converters}[#class-CSV-label-Write+Converters]. +# +# ==== Specifying \Converters +# +# You can specify converters for parsing or generating in the +options+ +# argument to various \CSV methods: +# - Option +converters+ for converting parsed field values. +# - Option +header_converters+ for converting parsed header values. +# - Option +write_converters+ for converting values to be written (generated). +# +# There are three forms for specifying converters: +# - A converter proc: executable code to be used for conversion. +# - A converter name: the name of a stored converter. +# - A converter list: an array of converter procs, converter names, and converter lists. +# +# ===== Converter Procs +# +# This converter proc, +strip_converter+, accepts a value +field+ +# and returns field.strip: +# strip_converter = proc {|field| field.strip } +# In this call to CSV.parse, +# the keyword argument converters: string_converter +# specifies that: +# - \Proc +string_converter+ is to be called for each parsed field. +# - The converter's return value is to replace the +field+ value. +# Example: +# string = " foo , 0 \n bar , 1 \n baz , 2 \n" +# array = CSV.parse(string, converters: strip_converter) +# array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] +# +# A converter proc can receive a second argument, +field_info+, +# that contains details about the field. +# This modified +strip_converter+ displays its arguments: +# strip_converter = proc do |field, field_info| +# p [field, field_info] +# field.strip +# end +# string = " foo , 0 \n bar , 1 \n baz , 2 \n" +# array = CSV.parse(string, converters: strip_converter) +# array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] +# Output: +# [" foo ", #] +# [" 0 ", #] +# [" bar ", #] +# [" 1 ", #] +# [" baz ", #] +# [" 2 ", #] +# Each CSV::FieldInfo object shows: +# - The 0-based field index. +# - The 1-based line index. +# - The field header, if any. +# +# ===== Stored \Converters +# +# A converter may be given a name and stored in a structure where +# the parsing methods can find it by name. +# +# The storage structure for field converters is the \Hash CSV::Converters. +# It has several built-in converter procs: +# - :integer: converts each \String-embedded integer into a true \Integer. +# - :float: converts each \String-embedded float into a true \Float. +# - :date: converts each \String-embedded date into a true \Date. +# - :date_time: converts each \String-embedded date-time into a true \DateTime +# - :time: converts each \String-embedded time into a true \Time +# . +# This example creates a converter proc, then stores it: +# strip_converter = proc {|field| field.strip } +# CSV::Converters[:strip] = strip_converter +# Then the parsing method call can refer to the converter +# by its name, :strip: +# string = " foo , 0 \n bar , 1 \n baz , 2 \n" +# array = CSV.parse(string, converters: :strip) +# array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] +# +# The storage structure for header converters is the \Hash CSV::HeaderConverters, +# which works in the same way. +# It also has built-in converter procs: +# - :downcase: Downcases each header. +# - :symbol: Converts each header to a \Symbol. +# +# There is no such storage structure for write headers. +# +# In order for the parsing methods to access stored converters in non-main-Ractors, the +# storage structure must be made shareable first. +# Therefore, Ractor.make_shareable(CSV::Converters) and +# Ractor.make_shareable(CSV::HeaderConverters) must be called before the creation +# of Ractors that use the converters stored in these structures. (Since making the storage +# structures shareable involves freezing them, any custom converters that are to be used +# must be added first.) +# +# ===== Converter Lists +# +# A _converter_ _list_ is an \Array that may include any assortment of: +# - Converter procs. +# - Names of stored converters. +# - Nested converter lists. +# +# Examples: +# numeric_converters = [:integer, :float] +# date_converters = [:date, :date_time] +# [numeric_converters, strip_converter] +# [strip_converter, date_converters, :float] +# +# Like a converter proc, a converter list may be named and stored in either +# \CSV::Converters or CSV::HeaderConverters: +# CSV::Converters[:custom] = [strip_converter, date_converters, :float] +# CSV::HeaderConverters[:custom] = [:downcase, :symbol] +# +# There are two built-in converter lists: +# CSV::Converters[:numeric] # => [:integer, :float] +# CSV::Converters[:all] # => [:date_time, :numeric] +# +# ==== Field \Converters +# +# With no conversion, all parsed fields in all rows become Strings: +# string = "foo,0\nbar,1\nbaz,2\n" +# ary = CSV.parse(string) +# ary # => # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] +# +# When you specify a field converter, each parsed field is passed to the converter; +# its return value becomes the stored value for the field. +# A converter might, for example, convert an integer embedded in a \String +# into a true \Integer. +# (In fact, that's what built-in field converter +:integer+ does.) +# +# There are three ways to use field \converters. +# +# - Using option {converters}[#class-CSV-label-Option+converters] with a parsing method: +# ary = CSV.parse(string, converters: :integer) +# ary # => [0, 1, 2] # => [["foo", 0], ["bar", 1], ["baz", 2]] +# - Using option {converters}[#class-CSV-label-Option+converters] with a new \CSV instance: +# csv = CSV.new(string, converters: :integer) +# # Field converters in effect: +# csv.converters # => [:integer] +# csv.read # => [["foo", 0], ["bar", 1], ["baz", 2]] +# - Using method #convert to add a field converter to a \CSV instance: +# csv = CSV.new(string) +# # Add a converter. +# csv.convert(:integer) +# csv.converters # => [:integer] +# csv.read # => [["foo", 0], ["bar", 1], ["baz", 2]] +# +# Installing a field converter does not affect already-read rows: +# csv = CSV.new(string) +# csv.shift # => ["foo", "0"] +# # Add a converter. +# csv.convert(:integer) +# csv.converters # => [:integer] +# csv.read # => [["bar", 1], ["baz", 2]] +# +# There are additional built-in \converters, and custom \converters are also supported. +# +# ===== Built-In Field \Converters +# +# The built-in field converters are in \Hash CSV::Converters: +# - Each key is a field converter name. +# - Each value is one of: +# - A \Proc field converter. +# - An \Array of field converter names. +# +# Display: +# CSV::Converters.each_pair do |name, value| +# if value.kind_of?(Proc) +# p [name, value.class] +# else +# p [name, value] +# end +# end +# Output: +# [:integer, Proc] +# [:float, Proc] +# [:numeric, [:integer, :float]] +# [:date, Proc] +# [:date_time, Proc] +# [:time, Proc] +# [:all, [:date_time, :numeric]] +# +# Each of these converters transcodes values to UTF-8 before attempting conversion. +# If a value cannot be transcoded to UTF-8 the conversion will +# fail and the value will remain unconverted. +# +# Converter +:integer+ converts each field that Integer() accepts: +# data = '0,1,2,x' +# # Without the converter +# csv = CSV.parse_line(data) +# csv # => ["0", "1", "2", "x"] +# # With the converter +# csv = CSV.parse_line(data, converters: :integer) +# csv # => [0, 1, 2, "x"] +# +# Converter +:float+ converts each field that Float() accepts: +# data = '1.0,3.14159,x' +# # Without the converter +# csv = CSV.parse_line(data) +# csv # => ["1.0", "3.14159", "x"] +# # With the converter +# csv = CSV.parse_line(data, converters: :float) +# csv # => [1.0, 3.14159, "x"] +# +# Converter +:numeric+ converts with both +:integer+ and +:float+.. +# +# Converter +:date+ converts each field that Date::parse accepts: +# data = '2001-02-03,x' +# # Without the converter +# csv = CSV.parse_line(data) +# csv # => ["2001-02-03", "x"] +# # With the converter +# csv = CSV.parse_line(data, converters: :date) +# csv # => [#, "x"] +# +# Converter +:date_time+ converts each field that DateTime::parse accepts: +# data = '2020-05-07T14:59:00-05:00,x' +# # Without the converter +# csv = CSV.parse_line(data) +# csv # => ["2020-05-07T14:59:00-05:00", "x"] +# # With the converter +# csv = CSV.parse_line(data, converters: :date_time) +# csv # => [#, "x"] +# +# Converter +time+ converts each field that Time::parse accepts: +# data = '2020-05-07T14:59:00-05:00,x' +# # Without the converter +# csv = CSV.parse_line(data) +# csv # => ["2020-05-07T14:59:00-05:00", "x"] +# # With the converter +# csv = CSV.parse_line(data, converters: :time) +# csv # => [2020-05-07 14:59:00 -0500, "x"] +# +# Converter +:numeric+ converts with both +:date_time+ and +:numeric+.. +# +# As seen above, method #convert adds \converters to a \CSV instance, +# and method #converters returns an \Array of the \converters in effect: +# csv = CSV.new('0,1,2') +# csv.converters # => [] +# csv.convert(:integer) +# csv.converters # => [:integer] +# csv.convert(:date) +# csv.converters # => [:integer, :date] +# +# ===== Custom Field \Converters +# +# You can define a custom field converter: +# strip_converter = proc {|field| field.strip } +# string = " foo , 0 \n bar , 1 \n baz , 2 \n" +# array = CSV.parse(string, converters: strip_converter) +# array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] +# You can register the converter in \Converters \Hash, +# which allows you to refer to it by name: +# CSV::Converters[:strip] = strip_converter +# string = " foo , 0 \n bar , 1 \n baz , 2 \n" +# array = CSV.parse(string, converters: :strip) +# array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] +# +# ==== Header \Converters +# +# Header converters operate only on headers (and not on other rows). +# +# There are three ways to use header \converters; +# these examples use built-in header converter +:downcase+, +# which downcases each parsed header. +# +# - Option +header_converters+ with a singleton parsing method: +# string = "Name,Count\nFoo,0\n,Bar,1\nBaz,2" +# tbl = CSV.parse(string, headers: true, header_converters: :downcase) +# tbl.class # => CSV::Table +# tbl.headers # => ["name", "count"] +# +# - Option +header_converters+ with a new \CSV instance: +# csv = CSV.new(string, header_converters: :downcase) +# # Header converters in effect: +# csv.header_converters # => [:downcase] +# tbl = CSV.parse(string, headers: true) +# tbl.headers # => ["Name", "Count"] +# +# - Method #header_convert adds a header converter to a \CSV instance: +# csv = CSV.new(string) +# # Add a header converter. +# csv.header_convert(:downcase) +# csv.header_converters # => [:downcase] +# tbl = CSV.parse(string, headers: true) +# tbl.headers # => ["Name", "Count"] +# +# ===== Built-In Header \Converters +# +# The built-in header \converters are in \Hash CSV::HeaderConverters. +# The keys there are the names of the \converters: +# CSV::HeaderConverters.keys # => [:downcase, :symbol] +# +# Converter +:downcase+ converts each header by downcasing it: +# string = "Name,Count\nFoo,0\n,Bar,1\nBaz,2" +# tbl = CSV.parse(string, headers: true, header_converters: :downcase) +# tbl.class # => CSV::Table +# tbl.headers # => ["name", "count"] +# +# Converter +:symbol+ converts each header by making it into a \Symbol: +# string = "Name,Count\nFoo,0\n,Bar,1\nBaz,2" +# tbl = CSV.parse(string, headers: true, header_converters: :symbol) +# tbl.headers # => [:name, :count] +# Details: +# - Strips leading and trailing whitespace. +# - Downcases the header. +# - Replaces embedded spaces with underscores. +# - Removes non-word characters. +# - Makes the string into a \Symbol. +# +# ===== Custom Header \Converters +# +# You can define a custom header converter: +# upcase_converter = proc {|header| header.upcase } +# string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" +# table = CSV.parse(string, headers: true, header_converters: upcase_converter) +# table # => # +# table.headers # => ["NAME", "VALUE"] +# You can register the converter in \HeaderConverters \Hash, +# which allows you to refer to it by name: +# CSV::HeaderConverters[:upcase] = upcase_converter +# table = CSV.parse(string, headers: true, header_converters: :upcase) +# table # => # +# table.headers # => ["NAME", "VALUE"] +# +# ===== Write \Converters +# +# When you specify a write converter for generating \CSV, +# each field to be written is passed to the converter; +# its return value becomes the new value for the field. +# A converter might, for example, strip whitespace from a field. +# +# Using no write converter (all fields unmodified): +# output_string = CSV.generate do |csv| +# csv << [' foo ', 0] +# csv << [' bar ', 1] +# csv << [' baz ', 2] +# end +# output_string # => " foo ,0\n bar ,1\n baz ,2\n" +# Using option +write_converters+ with two custom write converters: +# strip_converter = proc {|field| field.respond_to?(:strip) ? field.strip : field } +# upcase_converter = proc {|field| field.respond_to?(:upcase) ? field.upcase : field } +# write_converters = [strip_converter, upcase_converter] +# output_string = CSV.generate(write_converters: write_converters) do |csv| +# csv << [' foo ', 0] +# csv << [' bar ', 1] +# csv << [' baz ', 2] +# end +# output_string # => "FOO,0\nBAR,1\nBAZ,2\n" +# +# === Character Encodings (M17n or Multilingualization) +# +# This new CSV parser is m17n savvy. The parser works in the Encoding of the IO +# or String object being read from or written to. Your data is never transcoded +# (unless you ask Ruby to transcode it for you) and will literally be parsed in +# the Encoding it is in. Thus CSV will return Arrays or Rows of Strings in the +# Encoding of your data. This is accomplished by transcoding the parser itself +# into your Encoding. +# +# Some transcoding must take place, of course, to accomplish this multiencoding +# support. For example, :col_sep, :row_sep, and +# :quote_char must be transcoded to match your data. Hopefully this +# makes the entire process feel transparent, since CSV's defaults should just +# magically work for your data. However, you can set these values manually in +# the target Encoding to avoid the translation. +# +# It's also important to note that while all of CSV's core parser is now +# Encoding agnostic, some features are not. For example, the built-in +# converters will try to transcode data to UTF-8 before making conversions. +# Again, you can provide custom converters that are aware of your Encodings to +# avoid this translation. It's just too hard for me to support native +# conversions in all of Ruby's Encodings. +# +# Anyway, the practical side of this is simple: make sure IO and String objects +# passed into CSV have the proper Encoding set and everything should just work. +# CSV methods that allow you to open IO objects (CSV::foreach(), CSV::open(), +# CSV::read(), and CSV::readlines()) do allow you to specify the Encoding. +# +# One minor exception comes when generating CSV into a String with an Encoding +# that is not ASCII compatible. There's no existing data for CSV to use to +# prepare itself and thus you will probably need to manually specify the desired +# Encoding for most of those cases. It will try to guess using the fields in a +# row of output though, when using CSV::generate_line() or Array#to_csv(). +# +# I try to point out any other Encoding issues in the documentation of methods +# as they come up. +# +# This has been tested to the best of my ability with all non-"dummy" Encodings +# Ruby ships with. However, it is brave new code and may have some bugs. +# Please feel free to {report}[mailto:james@grayproductions.net] any issues you +# find with it. +# +class CSV + + # The error thrown when the parser encounters illegal CSV formatting. + class MalformedCSVError < RuntimeError + attr_reader :line_number + alias_method :lineno, :line_number + def initialize(message, line_number) + @line_number = line_number + super("#{message} in line #{line_number}.") + end + end + + # The error thrown when the parser encounters invalid encoding in CSV. + class InvalidEncodingError < MalformedCSVError + attr_reader :encoding + def initialize(encoding, line_number) + @encoding = encoding + super("Invalid byte sequence in #{encoding}", line_number) + end + end + + # + # A FieldInfo Struct contains details about a field's position in the data + # source it was read from. CSV will pass this Struct to some blocks that make + # decisions based on field structure. See CSV.convert_fields() for an + # example. + # + # index:: The zero-based index of the field in its row. + # line:: The line of the data source this row is from. + # header:: The header for the column, when available. + # quoted?:: True or false, whether the original value is quoted or not. + # + FieldInfo = Struct.new(:index, :line, :header, :quoted?) + + # A Regexp used to find and convert some common Date formats. + DateMatcher = / \A(?: (\w+,?\s+)?\w+\s+\d{1,2},?\s+\d{2,4} | + \d{4}-\d{2}-\d{2} )\z /x + # A Regexp used to find and convert some common (Date)Time formats. + DateTimeMatcher = + / \A(?: (\w+,?\s+)?\w+\s+\d{1,2}\s+\d{1,2}:\d{1,2}:\d{1,2},?\s+\d{2,4} | + # ISO-8601 and RFC-3339 (space instead of T) recognized by (Date)Time.parse + \d{4}-\d{2}-\d{2} + (?:[T\s]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?(?:[+-]\d{2}(?::\d{2})|Z)?)?)? + )\z /x + + # The encoding used by all converters. + ConverterEncoding = Encoding.find("UTF-8") + + # A \Hash containing the names and \Procs for the built-in field converters. + # See {Built-In Field Converters}[#class-CSV-label-Built-In+Field+Converters]. + # + # This \Hash is intentionally left unfrozen, and may be extended with + # custom field converters. + # See {Custom Field Converters}[#class-CSV-label-Custom+Field+Converters]. + Converters = { + integer: lambda { |f| + Integer(f.encode(ConverterEncoding)) rescue f + }, + float: lambda { |f| + Float(f.encode(ConverterEncoding)) rescue f + }, + numeric: [:integer, :float], + date: lambda { |f| + begin + e = f.encode(ConverterEncoding) + e.match?(DateMatcher) ? Date.parse(e) : f + rescue # encoding conversion or date parse errors + f + end + }, + date_time: lambda { |f| + begin + e = f.encode(ConverterEncoding) + e.match?(DateTimeMatcher) ? DateTime.parse(e) : f + rescue # encoding conversion or date parse errors + f + end + }, + time: lambda { |f| + begin + e = f.encode(ConverterEncoding) + e.match?(DateTimeMatcher) ? Time.parse(e) : f + rescue # encoding conversion or parse errors + f + end + }, + all: [:date_time, :numeric], + } + + # A \Hash containing the names and \Procs for the built-in header converters. + # See {Built-In Header Converters}[#class-CSV-label-Built-In+Header+Converters]. + # + # This \Hash is intentionally left unfrozen, and may be extended with + # custom field converters. + # See {Custom Header Converters}[#class-CSV-label-Custom+Header+Converters]. + HeaderConverters = { + downcase: lambda { |h| h.encode(ConverterEncoding).downcase }, + symbol: lambda { |h| + h.encode(ConverterEncoding).downcase.gsub(/[^\s\w]+/, "").strip. + gsub(/\s+/, "_").to_sym + }, + symbol_raw: lambda { |h| h.encode(ConverterEncoding).to_sym } + } + + # Default values for method options. + DEFAULT_OPTIONS = { + # For both parsing and generating. + col_sep: ",", + row_sep: :auto, + quote_char: '"', + # For parsing. + field_size_limit: nil, + max_field_size: nil, + converters: nil, + unconverted_fields: nil, + headers: false, + return_headers: false, + header_converters: nil, + skip_blanks: false, + skip_lines: nil, + liberal_parsing: false, + nil_value: nil, + empty_value: "", + strip: false, + # For generating. + write_headers: nil, + quote_empty: true, + force_quotes: false, + write_converters: nil, + write_nil_value: nil, + write_empty_value: "", + }.freeze + + class << self + # :call-seq: + # instance(string, **options) + # instance(io = $stdout, **options) + # instance(string, **options) {|csv| ... } + # instance(io = $stdout, **options) {|csv| ... } + # + # Creates or retrieves cached \CSV objects. + # For arguments and options, see CSV.new. + # + # This API is not Ractor-safe. + # + # --- + # + # With no block given, returns a \CSV object. + # + # The first call to +instance+ creates and caches a \CSV object: + # s0 = 's0' + # csv0 = CSV.instance(s0) + # csv0.class # => CSV + # + # Subsequent calls to +instance+ with that _same_ +string+ or +io+ + # retrieve that same cached object: + # csv1 = CSV.instance(s0) + # csv1.class # => CSV + # csv1.equal?(csv0) # => true # Same CSV object + # + # A subsequent call to +instance+ with a _different_ +string+ or +io+ + # creates and caches a _different_ \CSV object. + # s1 = 's1' + # csv2 = CSV.instance(s1) + # csv2.equal?(csv0) # => false # Different CSV object + # + # All the cached objects remains available: + # csv3 = CSV.instance(s0) + # csv3.equal?(csv0) # true # Same CSV object + # csv4 = CSV.instance(s1) + # csv4.equal?(csv2) # true # Same CSV object + # + # --- + # + # When a block is given, calls the block with the created or retrieved + # \CSV object; returns the block's return value: + # CSV.instance(s0) {|csv| :foo } # => :foo + def instance(data = $stdout, **options) + # create a _signature_ for this method call, data object and options + sig = [data.object_id] + + options.values_at(*DEFAULT_OPTIONS.keys) + + # fetch or create the instance for this signature + @@instances ||= Hash.new + instance = (@@instances[sig] ||= new(data, **options)) + + if block_given? + yield instance # run block, if given, returning result + else + instance # or return the instance + end + end + + # :call-seq: + # filter(in_string_or_io, **options) {|row| ... } -> array_of_arrays or csv_table + # filter(in_string_or_io, out_string_or_io, **options) {|row| ... } -> array_of_arrays or csv_table + # filter(**options) {|row| ... } -> array_of_arrays or csv_table + # + # - Parses \CSV from a source (\String, \IO stream, or ARGF). + # - Calls the given block with each parsed row: + # - Without headers, each row is an \Array. + # - With headers, each row is a CSV::Row. + # - Generates \CSV to an output (\String, \IO stream, or STDOUT). + # - Returns the parsed source: + # - Without headers, an \Array of \Arrays. + # - With headers, a CSV::Table. + # + # When +in_string_or_io+ is given, but not +out_string_or_io+, + # parses from the given +in_string_or_io+ + # and generates to STDOUT. + # + # \String input without headers: + # + # in_string = "foo,0\nbar,1\nbaz,2" + # CSV.filter(in_string) do |row| + # row[0].upcase! + # row[1] = - row[1].to_i + # end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]] + # + # Output (to STDOUT): + # + # FOO,0 + # BAR,-1 + # BAZ,-2 + # + # \String input with headers: + # + # in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2" + # CSV.filter(in_string, headers: true) do |row| + # row[0].upcase! + # row[1] = - row[1].to_i + # end # => # + # + # Output (to STDOUT): + # + # Name,Value + # FOO,0 + # BAR,-1 + # BAZ,-2 + # + # \IO stream input without headers: + # + # File.write('t.csv', "foo,0\nbar,1\nbaz,2") + # File.open('t.csv') do |in_io| + # CSV.filter(in_io) do |row| + # row[0].upcase! + # row[1] = - row[1].to_i + # end + # end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]] + # + # Output (to STDOUT): + # + # FOO,0 + # BAR,-1 + # BAZ,-2 + # + # \IO stream input with headers: + # + # File.write('t.csv', "Name,Value\nfoo,0\nbar,1\nbaz,2") + # File.open('t.csv') do |in_io| + # CSV.filter(in_io, headers: true) do |row| + # row[0].upcase! + # row[1] = - row[1].to_i + # end + # end # => # + # + # Output (to STDOUT): + # + # Name,Value + # FOO,0 + # BAR,-1 + # BAZ,-2 + # + # When both +in_string_or_io+ and +out_string_or_io+ are given, + # parses from +in_string_or_io+ and generates to +out_string_or_io+. + # + # \String output without headers: + # + # in_string = "foo,0\nbar,1\nbaz,2" + # out_string = '' + # CSV.filter(in_string, out_string) do |row| + # row[0].upcase! + # row[1] = - row[1].to_i + # end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]] + # out_string # => "FOO,0\nBAR,-1\nBAZ,-2\n" + # + # \String output with headers: + # + # in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2" + # out_string = '' + # CSV.filter(in_string, out_string, headers: true) do |row| + # row[0].upcase! + # row[1] = - row[1].to_i + # end # => # + # out_string # => "Name,Value\nFOO,0\nBAR,-1\nBAZ,-2\n" + # + # \IO stream output without headers: + # + # in_string = "foo,0\nbar,1\nbaz,2" + # File.open('t.csv', 'w') do |out_io| + # CSV.filter(in_string, out_io) do |row| + # row[0].upcase! + # row[1] = - row[1].to_i + # end + # end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]] + # File.read('t.csv') # => "FOO,0\nBAR,-1\nBAZ,-2\n" + # + # \IO stream output with headers: + # + # in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2" + # File.open('t.csv', 'w') do |out_io| + # CSV.filter(in_string, out_io, headers: true) do |row| + # row[0].upcase! + # row[1] = - row[1].to_i + # end + # end # => # + # File.read('t.csv') # => "Name,Value\nFOO,0\nBAR,-1\nBAZ,-2\n" + # + # When neither +in_string_or_io+ nor +out_string_or_io+ given, + # parses from {ARGF}[rdoc-ref:ARGF] + # and generates to STDOUT. + # + # Without headers: + # + # # Put Ruby code into a file. + # ruby = <<-EOT + # require 'csv' + # CSV.filter do |row| + # row[0].upcase! + # row[1] = - row[1].to_i + # end + # EOT + # File.write('t.rb', ruby) + # # Put some CSV into a file. + # File.write('t.csv', "foo,0\nbar,1\nbaz,2") + # # Run the Ruby code with CSV filename as argument. + # system(Gem.ruby, "t.rb", "t.csv") + # + # Output (to STDOUT): + # + # FOO,0 + # BAR,-1 + # BAZ,-2 + # + # With headers: + # + # # Put Ruby code into a file. + # ruby = <<-EOT + # require 'csv' + # CSV.filter(headers: true) do |row| + # row[0].upcase! + # row[1] = - row[1].to_i + # end + # EOT + # File.write('t.rb', ruby) + # # Put some CSV into a file. + # File.write('t.csv', "Name,Value\nfoo,0\nbar,1\nbaz,2") + # # Run the Ruby code with CSV filename as argument. + # system(Gem.ruby, "t.rb", "t.csv") + # + # Output (to STDOUT): + # + # Name,Value + # FOO,0 + # BAR,-1 + # BAZ,-2 + # + # Arguments: + # + # * Argument +in_string_or_io+ must be a \String or an \IO stream. + # * Argument +out_string_or_io+ must be a \String or an \IO stream. + # * Arguments **options must be keyword options. + # + # - Each option defined as an {option for parsing}[#class-CSV-label-Options+for+Parsing] + # is used for parsing the filter input. + # - Each option defined as an {option for generating}[#class-CSV-label-Options+for+Generating] + # is used for generator the filter input. + # + # However, there are three options that may be used for both parsing and generating: + # +col_sep+, +quote_char+, and +row_sep+. + # + # Therefore for method +filter+ (and method +filter+ only), + # there are special options that allow these parsing and generating options + # to be specified separately: + # + # - Options +input_col_sep+ and +output_col_sep+ + # (and their aliases +in_col_sep+ and +out_col_sep+) + # specify the column separators for parsing and generating. + # - Options +input_quote_char+ and +output_quote_char+ + # (and their aliases +in_quote_char+ and +out_quote_char+) + # specify the quote characters for parsing and generting. + # - Options +input_row_sep+ and +output_row_sep+ + # (and their aliases +in_row_sep+ and +out_row_sep+) + # specify the row separators for parsing and generating. + # + # Example options (for column separators): + # + # CSV.filter # Default for both parsing and generating. + # CSV.filter(in_col_sep: ';') # ';' for parsing, default for generating. + # CSV.filter(out_col_sep: '|') # Default for parsing, '|' for generating. + # CSV.filter(in_col_sep: ';', out_col_sep: '|') # ';' for parsing, '|' for generating. + # + # Note that for a special option (e.g., +input_col_sep+) + # and its corresponding "regular" option (e.g., +col_sep+), + # the two are mutually overriding. + # + # Another example (possibly surprising): + # + # CSV.filter(in_col_sep: ';', col_sep: '|') # '|' for both parsing(!) and generating. + # + def filter(input=nil, output=nil, **options) + # parse options for input, output, or both + in_options, out_options = Hash.new, {row_sep: InputRecordSeparator.value} + options.each do |key, value| + case key + when /\Ain(?:put)?_(.+)\Z/ + in_options[$1.to_sym] = value + when /\Aout(?:put)?_(.+)\Z/ + out_options[$1.to_sym] = value + else + in_options[key] = value + out_options[key] = value + end + end + + # build input and output wrappers + input = new(input || ARGF, **in_options) + output = new(output || $stdout, **out_options) + + # process headers + need_manual_header_output = + (in_options[:headers] and + out_options[:headers] == true and + out_options[:write_headers]) + if need_manual_header_output + first_row = input.shift + if first_row + if first_row.is_a?(Row) + headers = first_row.headers + yield headers + output << headers + end + yield first_row + output << first_row + end + end + + # read, yield, write + input.each do |row| + yield row + output << row + end + end + + # + # :call-seq: + # foreach(path_or_io, mode='r', **options) {|row| ... ) + # foreach(path_or_io, mode='r', **options) -> new_enumerator + # + # Calls the block with each row read from source +path_or_io+. + # + # \Path input without headers: + # + # string = "foo,0\nbar,1\nbaz,2\n" + # in_path = 't.csv' + # File.write(in_path, string) + # CSV.foreach(in_path) {|row| p row } + # + # Output: + # + # ["foo", "0"] + # ["bar", "1"] + # ["baz", "2"] + # + # \Path input with headers: + # + # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # in_path = 't.csv' + # File.write(in_path, string) + # CSV.foreach(in_path, headers: true) {|row| p row } + # + # Output: + # + # + # + # + # + # \IO stream input without headers: + # + # string = "foo,0\nbar,1\nbaz,2\n" + # path = 't.csv' + # File.write(path, string) + # File.open('t.csv') do |in_io| + # CSV.foreach(in_io) {|row| p row } + # end + # + # Output: + # + # ["foo", "0"] + # ["bar", "1"] + # ["baz", "2"] + # + # \IO stream input with headers: + # + # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # path = 't.csv' + # File.write(path, string) + # File.open('t.csv') do |in_io| + # CSV.foreach(in_io, headers: true) {|row| p row } + # end + # + # Output: + # + # + # + # + # + # With no block given, returns an \Enumerator: + # + # string = "foo,0\nbar,1\nbaz,2\n" + # path = 't.csv' + # File.write(path, string) + # CSV.foreach(path) # => # + # + # Arguments: + # * Argument +path_or_io+ must be a file path or an \IO stream. + # * Argument +mode+, if given, must be a \File mode. + # See {Access Modes}[https://docs.ruby-lang.org/en/master/File.html#class-File-label-Access+Modes]. + # * Arguments **options must be keyword options. + # See {Options for Parsing}[#class-CSV-label-Options+for+Parsing]. + # * This method optionally accepts an additional :encoding option + # that you can use to specify the Encoding of the data read from +path+ or +io+. + # You must provide this unless your data is in the encoding + # given by Encoding::default_external. + # Parsing will use this to determine how to parse the data. + # You may provide a second Encoding to + # have the data transcoded as it is read. For example, + # encoding: 'UTF-32BE:UTF-8' + # would read +UTF-32BE+ data from the file + # but transcode it to +UTF-8+ before parsing. + def foreach(path, mode="r", **options, &block) + return to_enum(__method__, path, mode, **options) unless block_given? + open(path, mode, **options) do |csv| + csv.each(&block) + end + end + + # + # :call-seq: + # generate(csv_string, **options) {|csv| ... } + # generate(**options) {|csv| ... } + # + # * Argument +csv_string+, if given, must be a \String object; + # defaults to a new empty \String. + # * Arguments +options+, if given, should be generating options. + # See {Options for Generating}[#class-CSV-label-Options+for+Generating]. + # + # --- + # + # Creates a new \CSV object via CSV.new(csv_string, **options); + # calls the block with the \CSV object, which the block may modify; + # returns the \String generated from the \CSV object. + # + # Note that a passed \String *is* modified by this method. + # Pass csv_string.dup if the \String must be preserved. + # + # This method has one additional option: :encoding, + # which sets the base Encoding for the output if no no +str+ is specified. + # CSV needs this hint if you plan to output non-ASCII compatible data. + # + # --- + # + # Add lines: + # input_string = "foo,0\nbar,1\nbaz,2\n" + # output_string = CSV.generate(input_string) do |csv| + # csv << ['bat', 3] + # csv << ['bam', 4] + # end + # output_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n" + # input_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n" + # output_string.equal?(input_string) # => true # Same string, modified + # + # Add lines into new string, preserving old string: + # input_string = "foo,0\nbar,1\nbaz,2\n" + # output_string = CSV.generate(input_string.dup) do |csv| + # csv << ['bat', 3] + # csv << ['bam', 4] + # end + # output_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n" + # input_string # => "foo,0\nbar,1\nbaz,2\n" + # output_string.equal?(input_string) # => false # Different strings + # + # Create lines from nothing: + # output_string = CSV.generate do |csv| + # csv << ['foo', 0] + # csv << ['bar', 1] + # csv << ['baz', 2] + # end + # output_string # => "foo,0\nbar,1\nbaz,2\n" + # + # --- + # + # Raises an exception if +csv_string+ is not a \String object: + # # Raises TypeError (no implicit conversion of Integer into String) + # CSV.generate(0) + # + def generate(str=nil, **options) + encoding = options[:encoding] + # add a default empty String, if none was given + if str + str = StringIO.new(str) + str.seek(0, IO::SEEK_END) + str.set_encoding(encoding) if encoding + else + str = +"" + str.force_encoding(encoding) if encoding + end + csv = new(str, **options) # wrap + yield csv # yield for appending + csv.string # return final String + end + + # :call-seq: + # CSV.generate_line(ary) + # CSV.generate_line(ary, **options) + # + # Returns the \String created by generating \CSV from +ary+ + # using the specified +options+. + # + # Argument +ary+ must be an \Array. + # + # Special options: + # * Option :row_sep defaults to "\n"> on Ruby 3.0 or later + # and $INPUT_RECORD_SEPARATOR ($/) otherwise.: + # $INPUT_RECORD_SEPARATOR # => "\n" + # * This method accepts an additional option, :encoding, which sets the base + # Encoding for the output. This method will try to guess your Encoding from + # the first non-+nil+ field in +row+, if possible, but you may need to use + # this parameter as a backup plan. + # + # For other +options+, + # see {Options for Generating}[#class-CSV-label-Options+for+Generating]. + # + # --- + # + # Returns the \String generated from an \Array: + # CSV.generate_line(['foo', '0']) # => "foo,0\n" + # + # --- + # + # Raises an exception if +ary+ is not an \Array: + # # Raises NoMethodError (undefined method `find' for :foo:Symbol) + # CSV.generate_line(:foo) + # + def generate_line(row, **options) + options = {row_sep: InputRecordSeparator.value}.merge(options) + str = +"" + if options[:encoding] + str.force_encoding(options[:encoding]) + else + fallback_encoding = nil + output_encoding = nil + row.each do |field| + next unless field.is_a?(String) + fallback_encoding ||= field.encoding + next if field.ascii_only? + output_encoding = field.encoding + break + end + output_encoding ||= fallback_encoding + if output_encoding + str.force_encoding(output_encoding) + end + end + (new(str, **options) << row).string + end + + # :call-seq: + # CSV.generate_lines(rows) + # CSV.generate_lines(rows, **options) + # + # Returns the \String created by generating \CSV from + # using the specified +options+. + # + # Argument +rows+ must be an \Array of row. Row is \Array of \String or \CSV::Row. + # + # Special options: + # * Option :row_sep defaults to "\n" on Ruby 3.0 or later + # and $INPUT_RECORD_SEPARATOR ($/) otherwise.: + # $INPUT_RECORD_SEPARATOR # => "\n" + # * This method accepts an additional option, :encoding, which sets the base + # Encoding for the output. This method will try to guess your Encoding from + # the first non-+nil+ field in +row+, if possible, but you may need to use + # this parameter as a backup plan. + # + # For other +options+, + # see {Options for Generating}[#class-CSV-label-Options+for+Generating]. + # + # --- + # + # Returns the \String generated from an + # CSV.generate_lines([['foo', '0'], ['bar', '1'], ['baz', '2']]) # => "foo,0\nbar,1\nbaz,2\n" + # + # --- + # + # Raises an exception + # # Raises NoMethodError (undefined method `each' for :foo:Symbol) + # CSV.generate_lines(:foo) + # + def generate_lines(rows, **options) + self.generate(**options) do |csv| + rows.each do |row| + csv << row + end + end + end + + # + # :call-seq: + # open(path_or_io, mode = "rb", **options ) -> new_csv + # open(path_or_io, mode = "rb", **options ) { |csv| ... } -> object + # + # possible options elements: + # keyword form: + # :invalid => nil # raise error on invalid byte sequence (default) + # :invalid => :replace # replace invalid byte sequence + # :undef => :replace # replace undefined conversion + # :replace => string # replacement string ("?" or "\uFFFD" if not specified) + # + # * Argument +path_or_io+, must be a file path or an \IO stream. + # :include: ../doc/csv/arguments/io.rdoc + # * Argument +mode+, if given, must be a \File mode. + # See {Access Modes}[https://docs.ruby-lang.org/en/master/File.html#class-File-label-Access+Modes]. + # * Arguments **options must be keyword options. + # See {Options for Generating}[#class-CSV-label-Options+for+Generating]. + # * This method optionally accepts an additional :encoding option + # that you can use to specify the Encoding of the data read from +path+ or +io+. + # You must provide this unless your data is in the encoding + # given by Encoding::default_external. + # Parsing will use this to determine how to parse the data. + # You may provide a second Encoding to + # have the data transcoded as it is read. For example, + # encoding: 'UTF-32BE:UTF-8' + # would read +UTF-32BE+ data from the file + # but transcode it to +UTF-8+ before parsing. + # + # --- + # + # These examples assume prior execution of: + # string = "foo,0\nbar,1\nbaz,2\n" + # path = 't.csv' + # File.write(path, string) + # + # string_io = StringIO.new + # string_io << "foo,0\nbar,1\nbaz,2\n" + # + # --- + # + # With no block given, returns a new \CSV object. + # + # Create a \CSV object using a file path: + # csv = CSV.open(path) + # csv # => # + # + # Create a \CSV object using an open \File: + # csv = CSV.open(File.open(path)) + # csv # => # + # + # Create a \CSV object using a \StringIO: + # csv = CSV.open(string_io) + # csv # => # + # --- + # + # With a block given, calls the block with the created \CSV object; + # returns the block's return value: + # + # Using a file path: + # csv = CSV.open(path) {|csv| p csv} + # csv # => # + # Output: + # # + # + # Using an open \File: + # csv = CSV.open(File.open(path)) {|csv| p csv} + # csv # => # + # Output: + # # + # + # Using a \StringIO: + # csv = CSV.open(string_io) {|csv| p csv} + # csv # => # + # Output: + # # + # --- + # + # Raises an exception if the argument is not a \String object or \IO object: + # # Raises TypeError (no implicit conversion of Symbol into String) + # CSV.open(:foo) + def open(filename_or_io, mode="r", **options) + # wrap a File opened with the remaining +args+ with no newline + # decorator + file_opts = {} + may_enable_bom_detection_automatically(filename_or_io, + mode, + options, + file_opts) + file_opts.merge!(options) + unless file_opts.key?(:newline) + file_opts[:universal_newline] ||= false + end + options.delete(:invalid) + options.delete(:undef) + options.delete(:replace) + options.delete_if {|k, _| /newline\z/.match?(k)} + + if filename_or_io.is_a?(StringIO) + f = create_stringio(filename_or_io.string, mode, **file_opts) + else + begin + f = File.open(filename_or_io, mode, **file_opts) + rescue ArgumentError => e + raise unless /needs binmode/.match?(e.message) and mode == "r" + mode = "rb" + file_opts = {encoding: Encoding.default_external}.merge(file_opts) + retry + end + end + + begin + csv = new(f, **options) + rescue Exception + f.close + raise + end + + # handle blocks like Ruby's open(), not like the CSV library + if block_given? + begin + yield csv + ensure + csv.close + end + else + csv + end + end + + # + # :call-seq: + # parse(string) -> array_of_arrays + # parse(io) -> array_of_arrays + # parse(string, headers: ..., **options) -> csv_table + # parse(io, headers: ..., **options) -> csv_table + # parse(string, **options) {|row| ... } + # parse(io, **options) {|row| ... } + # + # Parses +string+ or +io+ using the specified +options+. + # + # - Argument +string+ should be a \String object; + # it will be put into a new StringIO object positioned at the beginning. + # :include: ../doc/csv/arguments/io.rdoc + # - Argument +options+: see {Options for Parsing}[#class-CSV-label-Options+for+Parsing] + # + # ====== Without Option +headers+ + # + # Without {option +headers+}[#class-CSV-label-Option+headers] case. + # + # These examples assume prior execution of: + # string = "foo,0\nbar,1\nbaz,2\n" + # path = 't.csv' + # File.write(path, string) + # + # --- + # + # With no block given, returns an \Array of Arrays formed from the source. + # + # Parse a \String: + # a_of_a = CSV.parse(string) + # a_of_a # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + # + # Parse an open \File: + # a_of_a = File.open(path) do |file| + # CSV.parse(file) + # end + # a_of_a # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + # + # --- + # + # With a block given, calls the block with each parsed row: + # + # Parse a \String: + # CSV.parse(string) {|row| p row } + # + # Output: + # ["foo", "0"] + # ["bar", "1"] + # ["baz", "2"] + # + # Parse an open \File: + # File.open(path) do |file| + # CSV.parse(file) {|row| p row } + # end + # + # Output: + # ["foo", "0"] + # ["bar", "1"] + # ["baz", "2"] + # + # ====== With Option +headers+ + # + # With {option +headers+}[#class-CSV-label-Option+headers] case. + # + # These examples assume prior execution of: + # string = "Name,Count\nfoo,0\nbar,1\nbaz,2\n" + # path = 't.csv' + # File.write(path, string) + # + # --- + # + # With no block given, returns a CSV::Table object formed from the source. + # + # Parse a \String: + # csv_table = CSV.parse(string, headers: ['Name', 'Count']) + # csv_table # => # + # + # Parse an open \File: + # csv_table = File.open(path) do |file| + # CSV.parse(file, headers: ['Name', 'Count']) + # end + # csv_table # => # + # + # --- + # + # With a block given, calls the block with each parsed row, + # which has been formed into a CSV::Row object: + # + # Parse a \String: + # CSV.parse(string, headers: ['Name', 'Count']) {|row| p row } + # + # Output: + # # + # # + # # + # + # Parse an open \File: + # File.open(path) do |file| + # CSV.parse(file, headers: ['Name', 'Count']) {|row| p row } + # end + # + # Output: + # # + # # + # # + # + # --- + # + # Raises an exception if the argument is not a \String object or \IO object: + # # Raises NoMethodError (undefined method `close' for :foo:Symbol) + # CSV.parse(:foo) + # + # --- + # + # Please make sure if your text contains \BOM or not. CSV.parse will not remove + # \BOM automatically. You might want to remove \BOM before calling CSV.parse : + # # remove BOM on calling File.open + # File.open(path, encoding: 'bom|utf-8') do |file| + # CSV.parse(file, headers: true) do |row| + # # you can get value by column name because BOM is removed + # p row['Name'] + # end + # end + # + # Output: + # # "foo" + # # "bar" + # # "baz" + def parse(str, **options, &block) + csv = new(str, **options) + + return csv.each(&block) if block_given? + + # slurp contents, if no block is given + begin + csv.read + ensure + csv.close + end + end + + # :call-seq: + # CSV.parse_line(string) -> new_array or nil + # CSV.parse_line(io) -> new_array or nil + # CSV.parse_line(string, **options) -> new_array or nil + # CSV.parse_line(io, **options) -> new_array or nil + # CSV.parse_line(string, headers: true, **options) -> csv_row or nil + # CSV.parse_line(io, headers: true, **options) -> csv_row or nil + # + # Returns the data created by parsing the first line of +string+ or +io+ + # using the specified +options+. + # + # - Argument +string+ should be a \String object; + # it will be put into a new StringIO object positioned at the beginning. + # :include: ../doc/csv/arguments/io.rdoc + # - Argument +options+: see {Options for Parsing}[#class-CSV-label-Options+for+Parsing] + # + # ====== Without Option +headers+ + # + # Without option +headers+, returns the first row as a new \Array. + # + # These examples assume prior execution of: + # string = "foo,0\nbar,1\nbaz,2\n" + # path = 't.csv' + # File.write(path, string) + # + # Parse the first line from a \String object: + # CSV.parse_line(string) # => ["foo", "0"] + # + # Parse the first line from a File object: + # File.open(path) do |file| + # CSV.parse_line(file) # => ["foo", "0"] + # end # => ["foo", "0"] + # + # Returns +nil+ if the argument is an empty \String: + # CSV.parse_line('') # => nil + # + # ====== With Option +headers+ + # + # With {option +headers+}[#class-CSV-label-Option+headers], + # returns the first row as a CSV::Row object. + # + # These examples assume prior execution of: + # string = "Name,Count\nfoo,0\nbar,1\nbaz,2\n" + # path = 't.csv' + # File.write(path, string) + # + # Parse the first line from a \String object: + # CSV.parse_line(string, headers: true) # => # + # + # Parse the first line from a File object: + # File.open(path) do |file| + # CSV.parse_line(file, headers: true) + # end # => # + # + # --- + # + # Raises an exception if the argument is +nil+: + # # Raises ArgumentError (Cannot parse nil as CSV): + # CSV.parse_line(nil) + # + def parse_line(line, **options) + new(line, **options).each.first + end + + # + # :call-seq: + # read(source, **options) -> array_of_arrays + # read(source, headers: true, **options) -> csv_table + # + # Opens the given +source+ with the given +options+ (see CSV.open), + # reads the source (see CSV#read), and returns the result, + # which will be either an \Array of Arrays or a CSV::Table. + # + # Without headers: + # string = "foo,0\nbar,1\nbaz,2\n" + # path = 't.csv' + # File.write(path, string) + # CSV.read(path) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + # + # With headers: + # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # path = 't.csv' + # File.write(path, string) + # CSV.read(path, headers: true) # => # + def read(path, **options) + open(path, **options) { |csv| csv.read } + end + + # :call-seq: + # CSV.readlines(source, **options) + # + # Alias for CSV.read. + def readlines(path, **options) + read(path, **options) + end + + # :call-seq: + # CSV.table(source, **options) + # + # Calls CSV.read with +source+, +options+, and certain default options: + # - +headers+: +true+ + # - +converters+: +:numeric+ + # - +header_converters+: +:symbol+ + # + # Returns a CSV::Table object. + # + # Example: + # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # path = 't.csv' + # File.write(path, string) + # CSV.table(path) # => # + def table(path, **options) + default_options = { + headers: true, + converters: :numeric, + header_converters: :symbol, + } + options = default_options.merge(options) + read(path, **options) + end + + ON_WINDOWS = /mingw|mswin/.match?(RUBY_PLATFORM) + private_constant :ON_WINDOWS + + private + def may_enable_bom_detection_automatically(filename_or_io, + mode, + options, + file_opts) + if filename_or_io.is_a?(StringIO) + # Support to StringIO was dropped for Ruby 2.6 and earlier without BOM support: + # https://github.com/ruby/stringio/pull/47 + return if RUBY_VERSION < "2.7" + else + # "bom|utf-8" may be buggy on Windows: + # https://bugs.ruby-lang.org/issues/20526 + return if ON_WINDOWS + end + return unless Encoding.default_external == Encoding::UTF_8 + return if options.key?(:encoding) + return if options.key?(:external_encoding) + return if mode.is_a?(String) and mode.include?(":") + file_opts[:encoding] = "bom|utf-8" + end + + if RUBY_VERSION < "2.7" + def create_stringio(str, mode, opts) + opts.delete_if {|k, _| k == :universal_newline or DEFAULT_OPTIONS.key?(k)} + raise ArgumentError, "Unsupported options parsing StringIO: #{opts.keys}" unless opts.empty? + StringIO.new(str, mode) + end + else + def create_stringio(str, mode, opts) + StringIO.new(str, mode, **opts) + end + end + end + + # :call-seq: + # CSV.new(string) + # CSV.new(io) + # CSV.new(string, **options) + # CSV.new(io, **options) + # + # Returns the new \CSV object created using +string+ or +io+ + # and the specified +options+. + # + # - Argument +string+ should be a \String object; + # it will be put into a new StringIO object positioned at the beginning. + # :include: ../doc/csv/arguments/io.rdoc + # - Argument +options+: See: + # * {Options for Parsing}[#class-CSV-label-Options+for+Parsing] + # * {Options for Generating}[#class-CSV-label-Options+for+Generating] + # For performance reasons, the options cannot be overridden + # in a \CSV object, so those specified here will endure. + # + # In addition to the \CSV instance methods, several \IO methods are delegated. + # See {Delegated Methods}[#class-CSV-label-Delegated+Methods]. + # + # --- + # + # Create a \CSV object from a \String object: + # csv = CSV.new('foo,0') + # csv # => # + # + # Create a \CSV object from a \File object: + # File.write('t.csv', 'foo,0') + # csv = CSV.new(File.open('t.csv')) + # csv # => # + # + # --- + # + # Raises an exception if the argument is +nil+: + # # Raises ArgumentError (Cannot parse nil as CSV): + # CSV.new(nil) + # + def initialize(data, + col_sep: ",", + row_sep: :auto, + quote_char: '"', + field_size_limit: nil, + max_field_size: nil, + converters: nil, + unconverted_fields: nil, + headers: false, + return_headers: false, + write_headers: nil, + header_converters: nil, + skip_blanks: false, + force_quotes: false, + skip_lines: nil, + liberal_parsing: false, + internal_encoding: nil, + external_encoding: nil, + encoding: nil, + nil_value: nil, + empty_value: "", + strip: false, + quote_empty: true, + write_converters: nil, + write_nil_value: nil, + write_empty_value: "") + raise ArgumentError.new("Cannot parse nil as CSV") if data.nil? + + if data.is_a?(String) + if encoding + if encoding.is_a?(String) + data_external_encoding, data_internal_encoding = encoding.split(":", 2) + if data_internal_encoding + data = data.encode(data_internal_encoding, data_external_encoding) + else + data = data.dup.force_encoding(data_external_encoding) + end + else + data = data.dup.force_encoding(encoding) + end + end + @io = StringIO.new(data) + else + @io = data + end + @encoding = determine_encoding(encoding, internal_encoding) + + @base_fields_converter_options = { + nil_value: nil_value, + empty_value: empty_value, + } + @write_fields_converter_options = { + nil_value: write_nil_value, + empty_value: write_empty_value, + } + @initial_converters = converters + @initial_header_converters = header_converters + @initial_write_converters = write_converters + + if max_field_size.nil? and field_size_limit + max_field_size = field_size_limit - 1 + end + @parser_options = { + column_separator: col_sep, + row_separator: row_sep, + quote_character: quote_char, + max_field_size: max_field_size, + unconverted_fields: unconverted_fields, + headers: headers, + return_headers: return_headers, + skip_blanks: skip_blanks, + skip_lines: skip_lines, + liberal_parsing: liberal_parsing, + encoding: @encoding, + nil_value: nil_value, + empty_value: empty_value, + strip: strip, + } + @parser = nil + @parser_enumerator = nil + @eof_error = nil + + @writer_options = { + encoding: @encoding, + force_encoding: (not encoding.nil?), + force_quotes: force_quotes, + headers: headers, + write_headers: write_headers, + column_separator: col_sep, + row_separator: row_sep, + quote_character: quote_char, + quote_empty: quote_empty, + } + + @writer = nil + writer if @writer_options[:write_headers] + end + + class TSV < CSV + def initialize(data, **options) + super(data, **({col_sep: "\t"}.merge(options))) + end + end + + # :call-seq: + # csv.col_sep -> string + # + # Returns the encoded column separator; used for parsing and writing; + # see {Option +col_sep+}[#class-CSV-label-Option+col_sep]: + # CSV.new('').col_sep # => "," + def col_sep + parser.column_separator + end + + # :call-seq: + # csv.row_sep -> string + # + # Returns the encoded row separator; used for parsing and writing; + # see {Option +row_sep+}[#class-CSV-label-Option+row_sep]: + # CSV.new('').row_sep # => "\n" + def row_sep + parser.row_separator + end + + # :call-seq: + # csv.quote_char -> character + # + # Returns the encoded quote character; used for parsing and writing; + # see {Option +quote_char+}[#class-CSV-label-Option+quote_char]: + # CSV.new('').quote_char # => "\"" + def quote_char + parser.quote_character + end + + # :call-seq: + # csv.field_size_limit -> integer or nil + # + # Returns the limit for field size; used for parsing; + # see {Option +field_size_limit+}[#class-CSV-label-Option+field_size_limit]: + # CSV.new('').field_size_limit # => nil + # + # Deprecated since 3.2.3. Use +max_field_size+ instead. + def field_size_limit + parser.field_size_limit + end + + # :call-seq: + # csv.max_field_size -> integer or nil + # + # Returns the limit for field size; used for parsing; + # see {Option +max_field_size+}[#class-CSV-label-Option+max_field_size]: + # CSV.new('').max_field_size # => nil + # + # Since 3.2.3. + def max_field_size + parser.max_field_size + end + + # :call-seq: + # csv.skip_lines -> regexp or nil + # + # Returns the \Regexp used to identify comment lines; used for parsing; + # see {Option +skip_lines+}[#class-CSV-label-Option+skip_lines]: + # CSV.new('').skip_lines # => nil + def skip_lines + parser.skip_lines + end + + # :call-seq: + # csv.converters -> array + # + # Returns an \Array containing field converters; + # see {Field Converters}[#class-CSV-label-Field+Converters]: + # csv = CSV.new('') + # csv.converters # => [] + # csv.convert(:integer) + # csv.converters # => [:integer] + # csv.convert(proc {|x| x.to_s }) + # csv.converters + # + # Notes that you need to call + # +Ractor.make_shareable(CSV::Converters)+ on the main Ractor to use + # this method. + def converters + parser_fields_converter.map do |converter| + name = Converters.rassoc(converter) + name ? name.first : converter + end + end + + # :call-seq: + # csv.unconverted_fields? -> object + # + # Returns the value that determines whether unconverted fields are to be + # available; used for parsing; + # see {Option +unconverted_fields+}[#class-CSV-label-Option+unconverted_fields]: + # CSV.new('').unconverted_fields? # => nil + def unconverted_fields? + parser.unconverted_fields? + end + + # :call-seq: + # csv.headers -> object + # + # Returns the value that determines whether headers are used; used for parsing; + # see {Option +headers+}[#class-CSV-label-Option+headers]: + # CSV.new('').headers # => nil + def headers + if @writer + @writer.headers + else + parsed_headers = parser.headers + return parsed_headers if parsed_headers + raw_headers = @parser_options[:headers] + raw_headers = nil if raw_headers == false + raw_headers + end + end + + # :call-seq: + # csv.return_headers? -> true or false + # + # Returns the value that determines whether headers are to be returned; used for parsing; + # see {Option +return_headers+}[#class-CSV-label-Option+return_headers]: + # CSV.new('').return_headers? # => false + def return_headers? + parser.return_headers? + end + + # :call-seq: + # csv.write_headers? -> true or false + # + # Returns the value that determines whether headers are to be written; used for generating; + # see {Option +write_headers+}[#class-CSV-label-Option+write_headers]: + # CSV.new('').write_headers? # => nil + def write_headers? + @writer_options[:write_headers] + end + + # :call-seq: + # csv.header_converters -> array + # + # Returns an \Array containing header converters; used for parsing; + # see {Header Converters}[#class-CSV-label-Header+Converters]: + # CSV.new('').header_converters # => [] + # + # Notes that you need to call + # +Ractor.make_shareable(CSV::HeaderConverters)+ on the main Ractor + # to use this method. + def header_converters + header_fields_converter.map do |converter| + name = HeaderConverters.rassoc(converter) + name ? name.first : converter + end + end + + # :call-seq: + # csv.skip_blanks? -> true or false + # + # Returns the value that determines whether blank lines are to be ignored; used for parsing; + # see {Option +skip_blanks+}[#class-CSV-label-Option+skip_blanks]: + # CSV.new('').skip_blanks? # => false + def skip_blanks? + parser.skip_blanks? + end + + # :call-seq: + # csv.force_quotes? -> true or false + # + # Returns the value that determines whether all output fields are to be quoted; + # used for generating; + # see {Option +force_quotes+}[#class-CSV-label-Option+force_quotes]: + # CSV.new('').force_quotes? # => false + def force_quotes? + @writer_options[:force_quotes] + end + + # :call-seq: + # csv.liberal_parsing? -> true or false + # + # Returns the value that determines whether illegal input is to be handled; used for parsing; + # see {Option +liberal_parsing+}[#class-CSV-label-Option+liberal_parsing]: + # CSV.new('').liberal_parsing? # => false + def liberal_parsing? + parser.liberal_parsing? + end + + # :call-seq: + # csv.encoding -> encoding + # + # Returns the encoding used for parsing and generating; + # see {Character Encodings (M17n or Multilingualization)}[#class-CSV-label-Character+Encodings+-28M17n+or+Multilingualization-29]: + # CSV.new('').encoding # => # + attr_reader :encoding + + # :call-seq: + # csv.line_no -> integer + # + # Returns the count of the rows parsed or generated. + # + # Parsing: + # string = "foo,0\nbar,1\nbaz,2\n" + # path = 't.csv' + # File.write(path, string) + # CSV.open(path) do |csv| + # csv.each do |row| + # p [csv.lineno, row] + # end + # end + # Output: + # [1, ["foo", "0"]] + # [2, ["bar", "1"]] + # [3, ["baz", "2"]] + # + # Generating: + # CSV.generate do |csv| + # p csv.lineno; csv << ['foo', 0] + # p csv.lineno; csv << ['bar', 1] + # p csv.lineno; csv << ['baz', 2] + # end + # Output: + # 0 + # 1 + # 2 + def lineno + if @writer + @writer.lineno + else + parser.lineno + end + end + + # :call-seq: + # csv.line -> array + # + # Returns the line most recently read: + # string = "foo,0\nbar,1\nbaz,2\n" + # path = 't.csv' + # File.write(path, string) + # CSV.open(path) do |csv| + # csv.each do |row| + # p [csv.lineno, csv.line] + # end + # end + # Output: + # [1, "foo,0\n"] + # [2, "bar,1\n"] + # [3, "baz,2\n"] + def line + parser.line + end + + ### IO and StringIO Delegation ### + + extend Forwardable + def_delegators :@io, :binmode, :close, :close_read, :close_write, + :closed?, :external_encoding, :fcntl, + :fileno, :flush, :fsync, :internal_encoding, + :isatty, :pid, :pos, :pos=, :reopen, + :seek, :string, :sync, :sync=, :tell, + :truncate, :tty? + + def binmode? + if @io.respond_to?(:binmode?) + @io.binmode? + else + false + end + end + + def flock(*args) + raise NotImplementedError unless @io.respond_to?(:flock) + @io.flock(*args) + end + + def ioctl(*args) + raise NotImplementedError unless @io.respond_to?(:ioctl) + @io.ioctl(*args) + end + + def path + @io.path if @io.respond_to?(:path) + end + + def stat(*args) + raise NotImplementedError unless @io.respond_to?(:stat) + @io.stat(*args) + end + + def to_i + raise NotImplementedError unless @io.respond_to?(:to_i) + @io.to_i + end + + def to_io + @io.respond_to?(:to_io) ? @io.to_io : @io + end + + def eof? + return false if @eof_error + begin + parser_enumerator.peek + false + rescue MalformedCSVError => error + @eof_error = error + false + rescue StopIteration + true + end + end + alias_method :eof, :eof? + + # Rewinds the underlying IO object and resets CSV's lineno() counter. + def rewind + @parser = nil + @parser_enumerator = nil + @eof_error = nil + @writer.rewind if @writer + @io.rewind + end + + ### End Delegation ### + + # :call-seq: + # csv << row -> self + # + # Appends a row to +self+. + # + # - Argument +row+ must be an \Array object or a CSV::Row object. + # - The output stream must be open for writing. + # + # --- + # + # Append Arrays: + # CSV.generate do |csv| + # csv << ['foo', 0] + # csv << ['bar', 1] + # csv << ['baz', 2] + # end # => "foo,0\nbar,1\nbaz,2\n" + # + # Append CSV::Rows: + # headers = [] + # CSV.generate do |csv| + # csv << CSV::Row.new(headers, ['foo', 0]) + # csv << CSV::Row.new(headers, ['bar', 1]) + # csv << CSV::Row.new(headers, ['baz', 2]) + # end # => "foo,0\nbar,1\nbaz,2\n" + # + # Headers in CSV::Row objects are not appended: + # headers = ['Name', 'Count'] + # CSV.generate do |csv| + # csv << CSV::Row.new(headers, ['foo', 0]) + # csv << CSV::Row.new(headers, ['bar', 1]) + # csv << CSV::Row.new(headers, ['baz', 2]) + # end # => "foo,0\nbar,1\nbaz,2\n" + # + # --- + # + # Raises an exception if +row+ is not an \Array or \CSV::Row: + # CSV.generate do |csv| + # # Raises NoMethodError (undefined method `collect' for :foo:Symbol) + # csv << :foo + # end + # + # Raises an exception if the output stream is not opened for writing: + # path = 't.csv' + # File.write(path, '') + # File.open(path) do |file| + # CSV.open(file) do |csv| + # # Raises IOError (not opened for writing) + # csv << ['foo', 0] + # end + # end + def <<(row) + writer << row + self + end + alias_method :add_row, :<< + alias_method :puts, :<< + + # :call-seq: + # convert(converter_name) -> array_of_procs + # convert {|field, field_info| ... } -> array_of_procs + # + # - With no block, installs a field converter (a \Proc). + # - With a block, defines and installs a custom field converter. + # - Returns the \Array of installed field converters. + # + # - Argument +converter_name+, if given, should be the name + # of an existing field converter. + # + # See {Field Converters}[#class-CSV-label-Field+Converters]. + # --- + # + # With no block, installs a field converter: + # csv = CSV.new('') + # csv.convert(:integer) + # csv.convert(:float) + # csv.convert(:date) + # csv.converters # => [:integer, :float, :date] + # + # --- + # + # The block, if given, is called for each field: + # - Argument +field+ is the field value. + # - Argument +field_info+ is a CSV::FieldInfo object + # containing details about the field. + # + # The examples here assume the prior execution of: + # string = "foo,0\nbar,1\nbaz,2\n" + # path = 't.csv' + # File.write(path, string) + # + # Example giving a block: + # csv = CSV.open(path) + # csv.convert {|field, field_info| p [field, field_info]; field.upcase } + # csv.read # => [["FOO", "0"], ["BAR", "1"], ["BAZ", "2"]] + # + # Output: + # ["foo", #] + # ["0", #] + # ["bar", #] + # ["1", #] + # ["baz", #] + # ["2", #] + # + # The block need not return a \String object: + # csv = CSV.open(path) + # csv.convert {|field, field_info| field.to_sym } + # csv.read # => [[:foo, :"0"], [:bar, :"1"], [:baz, :"2"]] + # + # If +converter_name+ is given, the block is not called: + # csv = CSV.open(path) + # csv.convert(:integer) {|field, field_info| fail 'Cannot happen' } + # csv.read # => [["foo", 0], ["bar", 1], ["baz", 2]] + # + # --- + # + # Raises a parse-time exception if +converter_name+ is not the name of a built-in + # field converter: + # csv = CSV.open(path) + # csv.convert(:nosuch) => [nil] + # # Raises NoMethodError (undefined method `arity' for nil:NilClass) + # csv.read + def convert(name = nil, &converter) + parser_fields_converter.add_converter(name, &converter) + end + + # :call-seq: + # header_convert(converter_name) -> array_of_procs + # header_convert {|header, field_info| ... } -> array_of_procs + # + # - With no block, installs a header converter (a \Proc). + # - With a block, defines and installs a custom header converter. + # - Returns the \Array of installed header converters. + # + # - Argument +converter_name+, if given, should be the name + # of an existing header converter. + # + # See {Header Converters}[#class-CSV-label-Header+Converters]. + # --- + # + # With no block, installs a header converter: + # csv = CSV.new('') + # csv.header_convert(:symbol) + # csv.header_convert(:downcase) + # csv.header_converters # => [:symbol, :downcase] + # + # --- + # + # The block, if given, is called for each header: + # - Argument +header+ is the header value. + # - Argument +field_info+ is a CSV::FieldInfo object + # containing details about the header. + # + # The examples here assume the prior execution of: + # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # path = 't.csv' + # File.write(path, string) + # + # Example giving a block: + # csv = CSV.open(path, headers: true) + # csv.header_convert {|header, field_info| p [header, field_info]; header.upcase } + # table = csv.read + # table # => # + # table.headers # => ["NAME", "VALUE"] + # + # Output: + # ["Name", #] + # ["Value", #] + + # The block need not return a \String object: + # csv = CSV.open(path, headers: true) + # csv.header_convert {|header, field_info| header.to_sym } + # table = csv.read + # table.headers # => [:Name, :Value] + # + # If +converter_name+ is given, the block is not called: + # csv = CSV.open(path, headers: true) + # csv.header_convert(:downcase) {|header, field_info| fail 'Cannot happen' } + # table = csv.read + # table.headers # => ["name", "value"] + # --- + # + # Raises a parse-time exception if +converter_name+ is not the name of a built-in + # field converter: + # csv = CSV.open(path, headers: true) + # csv.header_convert(:nosuch) + # # Raises NoMethodError (undefined method `arity' for nil:NilClass) + # csv.read + def header_convert(name = nil, &converter) + header_fields_converter.add_converter(name, &converter) + end + + include Enumerable + + # :call-seq: + # csv.each -> enumerator + # csv.each {|row| ...} + # + # Calls the block with each successive row. + # The data source must be opened for reading. + # + # Without headers: + # string = "foo,0\nbar,1\nbaz,2\n" + # csv = CSV.new(string) + # csv.each do |row| + # p row + # end + # Output: + # ["foo", "0"] + # ["bar", "1"] + # ["baz", "2"] + # + # With headers: + # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # csv = CSV.new(string, headers: true) + # csv.each do |row| + # p row + # end + # Output: + # + # + # + # + # --- + # + # Raises an exception if the source is not opened for reading: + # string = "foo,0\nbar,1\nbaz,2\n" + # csv = CSV.new(string) + # csv.close + # # Raises IOError (not opened for reading) + # csv.each do |row| + # p row + # end + def each(&block) + return to_enum(__method__) unless block_given? + begin + while true + yield(parser_enumerator.next) + end + rescue StopIteration + end + end + + # :call-seq: + # csv.read -> array or csv_table + # + # Forms the remaining rows from +self+ into: + # - A CSV::Table object, if headers are in use. + # - An \Array of Arrays, otherwise. + # + # The data source must be opened for reading. + # + # Without headers: + # string = "foo,0\nbar,1\nbaz,2\n" + # path = 't.csv' + # File.write(path, string) + # csv = CSV.open(path) + # csv.read # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + # + # With headers: + # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # path = 't.csv' + # File.write(path, string) + # csv = CSV.open(path, headers: true) + # csv.read # => # + # + # --- + # + # Raises an exception if the source is not opened for reading: + # string = "foo,0\nbar,1\nbaz,2\n" + # csv = CSV.new(string) + # csv.close + # # Raises IOError (not opened for reading) + # csv.read + def read + rows = to_a + if parser.use_headers? + Table.new(rows, headers: parser.headers) + else + rows + end + end + alias_method :readlines, :read + + # :call-seq: + # csv.header_row? -> true or false + # + # Returns +true+ if the next row to be read is a header row\; + # +false+ otherwise. + # + # Without headers: + # string = "foo,0\nbar,1\nbaz,2\n" + # csv = CSV.new(string) + # csv.header_row? # => false + # + # With headers: + # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # csv = CSV.new(string, headers: true) + # csv.header_row? # => true + # csv.shift # => # + # csv.header_row? # => false + # + # --- + # + # Raises an exception if the source is not opened for reading: + # string = "foo,0\nbar,1\nbaz,2\n" + # csv = CSV.new(string) + # csv.close + # # Raises IOError (not opened for reading) + # csv.header_row? + def header_row? + parser.header_row? + end + + # :call-seq: + # csv.shift -> array, csv_row, or nil + # + # Returns the next row of data as: + # - An \Array if no headers are used. + # - A CSV::Row object if headers are used. + # + # The data source must be opened for reading. + # + # Without headers: + # string = "foo,0\nbar,1\nbaz,2\n" + # csv = CSV.new(string) + # csv.shift # => ["foo", "0"] + # csv.shift # => ["bar", "1"] + # csv.shift # => ["baz", "2"] + # csv.shift # => nil + # + # With headers: + # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # csv = CSV.new(string, headers: true) + # csv.shift # => # + # csv.shift # => # + # csv.shift # => # + # csv.shift # => nil + # + # --- + # + # Raises an exception if the source is not opened for reading: + # string = "foo,0\nbar,1\nbaz,2\n" + # csv = CSV.new(string) + # csv.close + # # Raises IOError (not opened for reading) + # csv.shift + def shift + if @eof_error + eof_error, @eof_error = @eof_error, nil + raise eof_error + end + begin + parser_enumerator.next + rescue StopIteration + nil + end + end + alias_method :gets, :shift + alias_method :readline, :shift + + # :call-seq: + # csv.inspect -> string + # + # Returns a \String showing certain properties of +self+: + # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # csv = CSV.new(string, headers: true) + # s = csv.inspect + # s # => "#" + def inspect + str = ["#<", self.class.to_s, " io_type:"] + # show type of wrapped IO + if @io == $stdout then str << "$stdout" + elsif @io == $stdin then str << "$stdin" + elsif @io == $stderr then str << "$stderr" + else str << @io.class.to_s + end + # show IO.path(), if available + if @io.respond_to?(:path) and (p = @io.path) + str << " io_path:" << p.inspect + end + # show encoding + str << " encoding:" << @encoding.name + # show other attributes + ["lineno", "col_sep", "row_sep", "quote_char"].each do |attr_name| + if a = __send__(attr_name) + str << " " << attr_name << ":" << a.inspect + end + end + ["skip_blanks", "liberal_parsing"].each do |attr_name| + if a = __send__("#{attr_name}?") + str << " " << attr_name << ":" << a.inspect + end + end + _headers = headers + str << " headers:" << _headers.inspect if _headers + str << ">" + begin + str.join('') + rescue # any encoding error + str.map do |s| + e = Encoding::Converter.asciicompat_encoding(s.encoding) + e ? s.encode(e) : s.force_encoding("ASCII-8BIT") + end.join('') + end + end + + private + + def determine_encoding(encoding, internal_encoding) + # honor the IO encoding if we can, otherwise default to ASCII-8BIT + io_encoding = raw_encoding + return io_encoding if io_encoding + + return Encoding.find(internal_encoding) if internal_encoding + + if encoding + encoding, = encoding.split(":", 2) if encoding.is_a?(String) + return Encoding.find(encoding) + end + + Encoding.default_internal || Encoding.default_external + end + + def normalize_converters(converters) + converters ||= [] + unless converters.is_a?(Array) + converters = [converters] + end + converters.collect do |converter| + case converter + when Proc # custom code block + [nil, converter] + else # by name + [converter, nil] + end + end + end + + # + # Processes +fields+ with @converters, or @header_converters + # if +headers+ is passed as +true+, returning the converted field set. Any + # converter that changes the field into something other than a String halts + # the pipeline of conversion for that field. This is primarily an efficiency + # shortcut. + # + def convert_fields(fields, headers = false) + if headers + header_fields_converter.convert(fields, nil, 0) + else + parser_fields_converter.convert(fields, @headers, lineno) + end + end + + # + # Returns the encoding of the internal IO object. + # + def raw_encoding + if @io.respond_to? :internal_encoding + @io.internal_encoding || @io.external_encoding + elsif @io.respond_to? :encoding + @io.encoding + else + nil + end + end + + def parser_fields_converter + @parser_fields_converter ||= build_parser_fields_converter + end + + def build_parser_fields_converter + specific_options = { + builtin_converters_name: :Converters, + } + options = @base_fields_converter_options.merge(specific_options) + build_fields_converter(@initial_converters, options) + end + + def header_fields_converter + @header_fields_converter ||= build_header_fields_converter + end + + def build_header_fields_converter + specific_options = { + builtin_converters_name: :HeaderConverters, + accept_nil: true, + } + options = @base_fields_converter_options.merge(specific_options) + build_fields_converter(@initial_header_converters, options) + end + + def writer_fields_converter + @writer_fields_converter ||= build_writer_fields_converter + end + + def build_writer_fields_converter + build_fields_converter(@initial_write_converters, + @write_fields_converter_options) + end + + def build_fields_converter(initial_converters, options) + fields_converter = FieldsConverter.new(options) + normalize_converters(initial_converters).each do |name, converter| + fields_converter.add_converter(name, &converter) + end + fields_converter + end + + def parser + @parser ||= Parser.new(@io, parser_options) + end + + def parser_options + @parser_options.merge(header_fields_converter: header_fields_converter, + fields_converter: parser_fields_converter) + end + + def parser_enumerator + @parser_enumerator ||= parser.parse + end + + def writer + @writer ||= Writer.new(@io, writer_options) + end + + def writer_options + @writer_options.merge(header_fields_converter: header_fields_converter, + fields_converter: writer_fields_converter) + end +end + +# Passes +args+ to CSV::instance. +# +# CSV("CSV,data").read +# #=> [["CSV", "data"]] +# +# If a block is given, the instance is passed the block and the return value +# becomes the return value of the block. +# +# CSV("CSV,data") { |c| +# c.read.any? { |a| a.include?("data") } +# } #=> true +# +# CSV("CSV,data") { |c| +# c.read.any? { |a| a.include?("zombies") } +# } #=> false +# +# CSV options may also be given. +# +# io = StringIO.new +# CSV(io, col_sep: ";") { |csv| csv << ["a", "b", "c"] } +# +# This API is not Ractor-safe. +# +def CSV(*args, **options, &block) + CSV.instance(*args, **options, &block) +end + +require_relative "csv/version" +require_relative "csv/core_ext/array" +require_relative "csv/core_ext/string" diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/core_ext/array.rb b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/core_ext/array.rb new file mode 100644 index 00000000..ad1b881b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/core_ext/array.rb @@ -0,0 +1,9 @@ +class Array + # Equivalent to CSV::generate_line(self, options) + # + # ["CSV", "data"].to_csv + # #=> "CSV,data\n" + def to_csv(**options) + CSV.generate_line(self, **options) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/core_ext/string.rb b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/core_ext/string.rb new file mode 100644 index 00000000..61286d0f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/core_ext/string.rb @@ -0,0 +1,9 @@ +class String + # Equivalent to CSV::parse_line(self, options) + # + # "CSV,data".parse_csv + # #=> ["CSV", "data"] + def parse_csv(**options) + CSV.parse_line(self, **options) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/fields_converter.rb b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/fields_converter.rb new file mode 100644 index 00000000..8ea13408 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/fields_converter.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +class CSV + # Note: Don't use this class directly. This is an internal class. + class FieldsConverter + include Enumerable + + NO_QUOTED_FIELDS = [] # :nodoc: + def NO_QUOTED_FIELDS.[](_index) + false + end + NO_QUOTED_FIELDS.freeze + + # + # A CSV::FieldsConverter is a data structure for storing the + # fields converter properties to be passed as a parameter + # when parsing a new file (e.g. CSV::Parser.new(@io, parser_options)) + # + + def initialize(options={}) + @converters = [] + @nil_value = options[:nil_value] + @empty_value = options[:empty_value] + @empty_value_is_empty_string = (@empty_value == "") + @accept_nil = options[:accept_nil] + @builtin_converters_name = options[:builtin_converters_name] + @need_static_convert = need_static_convert? + end + + def add_converter(name=nil, &converter) + if name.nil? # custom converter + @converters << converter + else # named converter + combo = builtin_converters[name] + case combo + when Array # combo converter + combo.each do |sub_name| + add_converter(sub_name) + end + else # individual named converter + @converters << combo + end + end + end + + def each(&block) + @converters.each(&block) + end + + def empty? + @converters.empty? + end + + def convert(fields, headers, lineno, quoted_fields=NO_QUOTED_FIELDS) + return fields unless need_convert? + + fields.collect.with_index do |field, index| + if field.nil? + field = @nil_value + elsif field.is_a?(String) and field.empty? + field = @empty_value unless @empty_value_is_empty_string + end + @converters.each do |converter| + break if field.nil? and @accept_nil + if converter.arity == 1 # straight field converter + field = converter[field] + else # FieldInfo converter + if headers + header = headers[index] + else + header = nil + end + quoted = quoted_fields[index] + field = converter[field, FieldInfo.new(index, lineno, header, quoted)] + end + break unless field.is_a?(String) # short-circuit pipeline for speed + end + field # final state of each field, converted or original + end + end + + private + def need_static_convert? + not (@nil_value.nil? and @empty_value_is_empty_string) + end + + def need_convert? + @need_static_convert or + (not @converters.empty?) + end + + def builtin_converters + @builtin_converters ||= ::CSV.const_get(@builtin_converters_name) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/input_record_separator.rb b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/input_record_separator.rb new file mode 100644 index 00000000..7a99343c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/input_record_separator.rb @@ -0,0 +1,18 @@ +require "English" +require "stringio" + +class CSV + module InputRecordSeparator + class << self + if RUBY_VERSION >= "3.0.0" + def value + "\n" + end + else + def value + $INPUT_RECORD_SEPARATOR + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/parser.rb b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/parser.rb new file mode 100644 index 00000000..4a74e40d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/parser.rb @@ -0,0 +1,1302 @@ +# frozen_string_literal: true + +require "strscan" + +require_relative "input_record_separator" +require_relative "row" +require_relative "table" + +class CSV + # Note: Don't use this class directly. This is an internal class. + class Parser + # + # A CSV::Parser is m17n aware. The parser works in the Encoding of the IO + # or String object being read from or written to. Your data is never transcoded + # (unless you ask Ruby to transcode it for you) and will literally be parsed in + # the Encoding it is in. Thus CSV will return Arrays or Rows of Strings in the + # Encoding of your data. This is accomplished by transcoding the parser itself + # into your Encoding. + # + + class << self + ARGF_OBJECT_ID = ARGF.object_id + # Convenient method to check whether the give input reached EOF + # or not. + def eof?(input) + # We can't use input != ARGF in Ractor. Because ARGF isn't a + # shareable object. + input.object_id != ARGF_OBJECT_ID and + input.respond_to?(:eof) and + input.eof? + end + end + + # Raised when encoding is invalid. + class InvalidEncoding < StandardError + end + + # Raised when unexpected case is happen. + class UnexpectedError < StandardError + end + + # + # CSV::Scanner receives a CSV output, scans it and return the content. + # It also controls the life cycle of the object with its methods +keep_start+, + # +keep_end+, +keep_back+, +keep_drop+. + # + # Uses StringScanner (the official strscan gem). Strscan provides lexical + # scanning operations on a String. We inherit its object and take advantage + # on the methods. For more information, please visit: + # https://docs.ruby-lang.org/en/master/StringScanner.html + # + class Scanner < StringScanner + alias_method :scan_all, :scan + + def initialize(*args) + super + @keeps = [] + end + + def each_line(row_separator) + position = pos + rest.each_line(row_separator) do |line| + position += line.bytesize + self.pos = position + yield(line) + end + end + + def keep_start + @keeps.push(pos) + end + + def keep_end + start = @keeps.pop + string.byteslice(start, pos - start) + end + + def keep_back + self.pos = @keeps.pop + end + + def keep_drop + @keeps.pop + end + end + + # + # CSV::InputsScanner receives IO inputs, encoding and the chunk_size. + # It also controls the life cycle of the object with its methods +keep_start+, + # +keep_end+, +keep_back+, +keep_drop+. + # + # CSV::InputsScanner.scan() tries to match with pattern at the current position. + # If there's a match, the scanner advances the "scan pointer" and returns the matched string. + # Otherwise, the scanner returns nil. + # + # CSV::InputsScanner.rest() returns the "rest" of the string (i.e. everything after the scan pointer). + # If there is no more data (eos? = true), it returns "". + # + class InputsScanner + def initialize(inputs, encoding, row_separator, chunk_size: 8192) + @inputs = inputs.dup + @encoding = encoding + @row_separator = row_separator + @chunk_size = chunk_size + @last_scanner = @inputs.empty? + @keeps = [] + read_chunk + end + + def each_line(row_separator) + return enum_for(__method__, row_separator) unless block_given? + buffer = nil + input = @scanner.rest + position = @scanner.pos + offset = 0 + n_row_separator_chars = row_separator.size + # trace(__method__, :start, input) + while true + input.each_line(row_separator) do |line| + @scanner.pos += line.bytesize + if buffer + if n_row_separator_chars == 2 and + buffer.end_with?(row_separator[0]) and + line.start_with?(row_separator[1]) + buffer << line[0] + line = line[1..-1] + position += buffer.bytesize + offset + @scanner.pos = position + offset = 0 + yield(buffer) + buffer = nil + next if line.empty? + else + buffer << line + line = buffer + buffer = nil + end + end + if line.end_with?(row_separator) + position += line.bytesize + offset + @scanner.pos = position + offset = 0 + yield(line) + else + buffer = line + end + end + break unless read_chunk + input = @scanner.rest + position = @scanner.pos + offset = -buffer.bytesize if buffer + end + yield(buffer) if buffer + end + + def scan(pattern) + # trace(__method__, pattern, :start) + value = @scanner.scan(pattern) + # trace(__method__, pattern, :done, :last, value) if @last_scanner + return value if @last_scanner + + read_chunk if value and @scanner.eos? + # trace(__method__, pattern, :done, value) + value + end + + def scan_all(pattern) + # trace(__method__, pattern, :start) + value = @scanner.scan(pattern) + # trace(__method__, pattern, :done, :last, value) if @last_scanner + return value if @last_scanner + + # trace(__method__, pattern, :done, :nil) if value.nil? + return nil if value.nil? + while @scanner.eos? and read_chunk and (sub_value = @scanner.scan(pattern)) + # trace(__method__, pattern, :sub, sub_value) + value << sub_value + end + # trace(__method__, pattern, :done, value) + value + end + + def eos? + @scanner.eos? + end + + def keep_start + # trace(__method__, :start) + adjust_last_keep + @keeps.push([@scanner, @scanner.pos, nil]) + # trace(__method__, :done) + end + + def keep_end + # trace(__method__, :start) + scanner, start, buffer = @keeps.pop + if scanner == @scanner + keep = @scanner.string.byteslice(start, @scanner.pos - start) + else + keep = @scanner.string.byteslice(0, @scanner.pos) + end + if buffer + buffer << keep + keep = buffer + end + # trace(__method__, :done, keep) + keep + end + + def keep_back + # trace(__method__, :start) + scanner, start, buffer = @keeps.pop + if buffer + # trace(__method__, :rescan, start, buffer) + string = @scanner.string + if scanner == @scanner + keep = string.byteslice(start, + string.bytesize - @scanner.pos - start) + else + keep = string + end + if keep and not keep.empty? + @inputs.unshift(StringIO.new(keep)) + @last_scanner = false + end + @scanner = StringScanner.new(buffer) + else + if @scanner != scanner + message = "scanners are different but no buffer: " + message += "#{@scanner.inspect}(#{@scanner.object_id}): " + message += "#{scanner.inspect}(#{scanner.object_id})" + raise UnexpectedError, message + end + # trace(__method__, :repos, start, buffer) + @scanner.pos = start + last_scanner, last_start, last_buffer = @keeps.last + # Drop the last buffer when the last buffer is the same data + # in the last keep. If we keep it, we have duplicated data + # by the next keep_back. + if last_scanner == @scanner and + last_buffer and + last_buffer == last_scanner.string.byteslice(last_start, start) + @keeps.last[2] = nil + end + end + read_chunk if @scanner.eos? + end + + def keep_drop + _, _, buffer = @keeps.pop + # trace(__method__, :done, :empty) unless buffer + return unless buffer + + last_keep = @keeps.last + # trace(__method__, :done, :no_last_keep) unless last_keep + return unless last_keep + + if last_keep[2] + last_keep[2] << buffer + else + last_keep[2] = buffer + end + # trace(__method__, :done) + end + + def rest + @scanner.rest + end + + def check(pattern) + @scanner.check(pattern) + end + + private + def trace(*args) + pp([*args, @scanner, @scanner&.string, @scanner&.pos, @keeps]) + end + + def adjust_last_keep + # trace(__method__, :start) + + keep = @keeps.last + # trace(__method__, :done, :empty) if keep.nil? + return if keep.nil? + + scanner, start, buffer = keep + string = @scanner.string + if @scanner != scanner + start = 0 + end + if start == 0 and @scanner.eos? + keep_data = string + else + keep_data = string.byteslice(start, @scanner.pos - start) + end + if keep_data + if buffer + buffer << keep_data + else + keep[2] = keep_data.dup + end + end + + # trace(__method__, :done) + end + + def read_chunk + return false if @last_scanner + + adjust_last_keep + + input = @inputs.first + case input + when StringIO + string = input.read + raise InvalidEncoding unless string.valid_encoding? + # trace(__method__, :stringio, string) + @scanner = StringScanner.new(string) + @inputs.shift + @last_scanner = @inputs.empty? + true + else + chunk = input.gets(@row_separator, @chunk_size) + if chunk + raise InvalidEncoding unless chunk.valid_encoding? + # trace(__method__, :chunk, chunk) + @scanner = StringScanner.new(chunk) + if Parser.eof?(input) + @inputs.shift + @last_scanner = @inputs.empty? + end + true + else + # trace(__method__, :no_chunk) + @scanner = StringScanner.new("".encode(@encoding)) + @inputs.shift + @last_scanner = @inputs.empty? + if @last_scanner + false + else + read_chunk + end + end + end + end + end + + def initialize(input, options) + @input = input + @options = options + @samples = [] + + prepare + end + + def column_separator + @column_separator + end + + def row_separator + @row_separator + end + + def quote_character + @quote_character + end + + def field_size_limit + @max_field_size&.succ + end + + def max_field_size + @max_field_size + end + + def skip_lines + @skip_lines + end + + def unconverted_fields? + @unconverted_fields + end + + def headers + @headers + end + + def header_row? + @use_headers and @headers.nil? + end + + def return_headers? + @return_headers + end + + def skip_blanks? + @skip_blanks + end + + def liberal_parsing? + @liberal_parsing + end + + def lineno + @lineno + end + + def line + last_line + end + + def parse(&block) + return to_enum(__method__) unless block_given? + + if @return_headers and @headers and @raw_headers + headers = Row.new(@headers, @raw_headers, true) + if @unconverted_fields + headers = add_unconverted_fields(headers, []) + end + yield headers + end + + begin + @scanner ||= build_scanner + __send__(@parse_method, &block) + rescue InvalidEncoding + if @scanner + ignore_broken_line + lineno = @lineno + else + lineno = @lineno + 1 + end + raise InvalidEncodingError.new(@encoding, lineno) + rescue UnexpectedError => error + if @scanner + ignore_broken_line + lineno = @lineno + else + lineno = @lineno + 1 + end + message = "This should not be happen: #{error.message}: " + message += "Please report this to https://github.com/ruby/csv/issues" + raise MalformedCSVError.new(message, lineno) + end + end + + def use_headers? + @use_headers + end + + private + # A set of tasks to prepare the file in order to parse it + def prepare + prepare_variable + prepare_quote_character + prepare_backslash + prepare_skip_lines + prepare_strip + prepare_separators + validate_strip_and_col_sep_options + prepare_quoted + prepare_unquoted + prepare_line + prepare_header + prepare_parser + end + + def prepare_variable + @encoding = @options[:encoding] + liberal_parsing = @options[:liberal_parsing] + if liberal_parsing + @liberal_parsing = true + if liberal_parsing.is_a?(Hash) + @double_quote_outside_quote = + liberal_parsing[:double_quote_outside_quote] + @backslash_quote = liberal_parsing[:backslash_quote] + else + @double_quote_outside_quote = false + @backslash_quote = false + end + else + @liberal_parsing = false + @backslash_quote = false + end + @unconverted_fields = @options[:unconverted_fields] + @max_field_size = @options[:max_field_size] + @skip_blanks = @options[:skip_blanks] + @fields_converter = @options[:fields_converter] + @header_fields_converter = @options[:header_fields_converter] + end + + def prepare_quote_character + @quote_character = @options[:quote_character] + if @quote_character.nil? + @escaped_quote_character = nil + @escaped_quote = nil + else + @quote_character = @quote_character.to_s.encode(@encoding) + if @quote_character.length != 1 + message = ":quote_char has to be nil or a single character String" + raise ArgumentError, message + end + @escaped_quote_character = Regexp.escape(@quote_character) + @escaped_quote = Regexp.new(@escaped_quote_character) + end + end + + def prepare_backslash + return unless @backslash_quote + + @backslash_character = "\\".encode(@encoding) + + @escaped_backslash_character = Regexp.escape(@backslash_character) + @escaped_backslash = Regexp.new(@escaped_backslash_character) + if @quote_character.nil? + @backslash_quote_character = nil + else + @backslash_quote_character = + @backslash_character + @escaped_quote_character + end + end + + def prepare_skip_lines + skip_lines = @options[:skip_lines] + case skip_lines + when String + @skip_lines = skip_lines.encode(@encoding) + when Regexp, nil + @skip_lines = skip_lines + else + unless skip_lines.respond_to?(:match) + message = + ":skip_lines has to respond to \#match: #{skip_lines.inspect}" + raise ArgumentError, message + end + @skip_lines = skip_lines + end + end + + def prepare_strip + @strip = @options[:strip] + @escaped_strip = nil + @strip_value = nil + @rstrip_value = nil + if @strip.is_a?(String) + case @strip.length + when 0 + raise ArgumentError, ":strip must not be an empty String" + when 1 + # ok + else + raise ArgumentError, ":strip doesn't support 2 or more characters yet" + end + @strip = @strip.encode(@encoding) + @escaped_strip = Regexp.escape(@strip) + if @quote_character + @strip_value = Regexp.new(@escaped_strip + + "+".encode(@encoding)) + @rstrip_value = Regexp.new(@escaped_strip + + "+\\z".encode(@encoding)) + end + elsif @strip + strip_values = " \t\f\v" + @escaped_strip = strip_values.encode(@encoding) + if @quote_character + @strip_value = Regexp.new("[#{strip_values}]+".encode(@encoding)) + @rstrip_value = Regexp.new("[#{strip_values}]+\\z".encode(@encoding)) + end + end + end + + begin + StringScanner.new("x").scan("x") + rescue TypeError + STRING_SCANNER_SCAN_ACCEPT_STRING = false + else + STRING_SCANNER_SCAN_ACCEPT_STRING = true + end + + def prepare_separators + column_separator = @options[:column_separator] + @column_separator = column_separator.to_s.encode(@encoding) + if @column_separator.size < 1 + message = ":col_sep must be 1 or more characters: " + message += column_separator.inspect + raise ArgumentError, message + end + @row_separator = + resolve_row_separator(@options[:row_separator]).encode(@encoding) + + @escaped_column_separator = Regexp.escape(@column_separator) + @escaped_first_column_separator = Regexp.escape(@column_separator[0]) + if @column_separator.size > 1 + @column_end = Regexp.new(@escaped_column_separator) + @column_ends = @column_separator.each_char.collect do |char| + Regexp.new(Regexp.escape(char)) + end + @first_column_separators = Regexp.new(@escaped_first_column_separator + + "+".encode(@encoding)) + else + if STRING_SCANNER_SCAN_ACCEPT_STRING + @column_end = @column_separator + else + @column_end = Regexp.new(@escaped_column_separator) + end + @column_ends = nil + @first_column_separators = nil + end + + escaped_row_separator = Regexp.escape(@row_separator) + @row_end = Regexp.new(escaped_row_separator) + if @row_separator.size > 1 + @row_ends = @row_separator.each_char.collect do |char| + Regexp.new(Regexp.escape(char)) + end + else + @row_ends = nil + end + + @cr = "\r".encode(@encoding) + @lf = "\n".encode(@encoding) + @line_end = Regexp.new("\r\n|\n|\r".encode(@encoding)) + @not_line_end = Regexp.new("[^\r\n]+".encode(@encoding)) + end + + # This method verifies that there are no (obvious) ambiguities with the + # provided +col_sep+ and +strip+ parsing options. For example, if +col_sep+ + # and +strip+ were both equal to +\t+, then there would be no clear way to + # parse the input. + def validate_strip_and_col_sep_options + return unless @strip + + if @strip.is_a?(String) + if @column_separator.start_with?(@strip) || @column_separator.end_with?(@strip) + raise ArgumentError, + "The provided strip (#{@escaped_strip}) and " \ + "col_sep (#{@escaped_column_separator}) options are incompatible." + end + else + if Regexp.new("\\A[#{@escaped_strip}]|[#{@escaped_strip}]\\z").match?(@column_separator) + raise ArgumentError, + "The provided strip (true) and " \ + "col_sep (#{@escaped_column_separator}) options are incompatible." + end + end + end + + def prepare_quoted + if @quote_character + @quotes = Regexp.new(@escaped_quote_character + + "+".encode(@encoding)) + no_quoted_values = @escaped_quote_character.dup + if @backslash_quote + no_quoted_values << @escaped_backslash_character + end + @quoted_value = Regexp.new("[^".encode(@encoding) + + no_quoted_values + + "]+".encode(@encoding)) + end + if @escaped_strip + @split_column_separator = Regexp.new(@escaped_strip + + "*".encode(@encoding) + + @escaped_column_separator + + @escaped_strip + + "*".encode(@encoding)) + else + if @column_separator == " ".encode(@encoding) + @split_column_separator = Regexp.new(@escaped_column_separator) + else + @split_column_separator = @column_separator + end + end + end + + def prepare_unquoted + return if @quote_character.nil? + + no_unquoted_values = "\r\n".encode(@encoding) + no_unquoted_values << @escaped_first_column_separator + unless @liberal_parsing + no_unquoted_values << @escaped_quote_character + end + @unquoted_value = Regexp.new("[^".encode(@encoding) + + no_unquoted_values + + "]+".encode(@encoding)) + end + + def resolve_row_separator(separator) + if separator == :auto + cr = "\r".encode(@encoding) + lf = "\n".encode(@encoding) + if @input.is_a?(StringIO) + pos = @input.pos + separator = detect_row_separator(@input.read, cr, lf) + @input.seek(pos) + elsif @input.respond_to?(:gets) + if @input.is_a?(File) + chunk_size = 32 * 1024 + else + chunk_size = 1024 + end + begin + while separator == :auto + # + # if we run out of data, it's probably a single line + # (ensure will set default value) + # + break unless sample = @input.gets(nil, chunk_size) + + # extend sample if we're unsure of the line ending + if sample.end_with?(cr) + sample << (@input.gets(nil, 1) || "") + end + + @samples << sample + + separator = detect_row_separator(sample, cr, lf) + end + rescue IOError + # do nothing: ensure will set default + end + end + separator = InputRecordSeparator.value if separator == :auto + end + separator.to_s.encode(@encoding) + end + + def detect_row_separator(sample, cr, lf) + lf_index = sample.index(lf) + if lf_index + cr_index = sample[0, lf_index].index(cr) + else + cr_index = sample.index(cr) + end + if cr_index and lf_index + if cr_index + 1 == lf_index + cr + lf + elsif cr_index < lf_index + cr + else + lf + end + elsif cr_index + cr + elsif lf_index + lf + else + :auto + end + end + + def prepare_line + @lineno = 0 + @last_line = nil + @scanner = nil + end + + def last_line + if @scanner + @last_line ||= @scanner.keep_end + else + @last_line + end + end + + def prepare_header + @return_headers = @options[:return_headers] + + headers = @options[:headers] + case headers + when Array + @raw_headers = headers + quoted_fields = FieldsConverter::NO_QUOTED_FIELDS + @use_headers = true + when String + @raw_headers, quoted_fields = parse_headers(headers) + @use_headers = true + when nil, false + @raw_headers = nil + @use_headers = false + else + @raw_headers = nil + @use_headers = true + end + if @raw_headers + @headers = adjust_headers(@raw_headers, quoted_fields) + else + @headers = nil + end + end + + def parse_headers(row) + quoted_fields = [] + converter = lambda do |field, info| + quoted_fields << info.quoted? + field + end + headers = CSV.parse_line(row, + col_sep: @column_separator, + row_sep: @row_separator, + quote_char: @quote_character, + converters: [converter]) + [headers, quoted_fields] + end + + def adjust_headers(headers, quoted_fields) + adjusted_headers = @header_fields_converter.convert(headers, nil, @lineno, quoted_fields) + adjusted_headers.each {|h| h.freeze if h.is_a? String} + adjusted_headers + end + + def prepare_parser + @may_quoted = may_quoted? + if @quote_character.nil? + @parse_method = :parse_no_quote + elsif @liberal_parsing or @strip + @parse_method = :parse_quotable_robust + else + @parse_method = :parse_quotable_loose + end + end + + def may_quoted? + return false if @quote_character.nil? + + if @input.is_a?(StringIO) + pos = @input.pos + sample = @input.read + @input.seek(pos) + else + return false if @samples.empty? + sample = @samples.first + end + sample[0, 128].index(@quote_character) + end + + class UnoptimizedStringIO # :nodoc: + def initialize(string) + @io = StringIO.new(string, "rb:#{string.encoding}") + end + + def gets(*args) + @io.gets(*args) + end + + def each_line(*args, &block) + @io.each_line(*args, &block) + end + + def eof? + @io.eof? + end + end + + SCANNER_TEST = (ENV["CSV_PARSER_SCANNER_TEST"] == "yes") + if SCANNER_TEST + SCANNER_TEST_CHUNK_SIZE_NAME = "CSV_PARSER_SCANNER_TEST_CHUNK_SIZE" + SCANNER_TEST_CHUNK_SIZE_VALUE = ENV[SCANNER_TEST_CHUNK_SIZE_NAME] + def build_scanner + inputs = @samples.collect do |sample| + UnoptimizedStringIO.new(sample) + end + if @input.is_a?(StringIO) + inputs << UnoptimizedStringIO.new(@input.read) + else + inputs << @input + end + begin + chunk_size_value = ENV[SCANNER_TEST_CHUNK_SIZE_NAME] + rescue # Ractor::IsolationError + # Ractor on Ruby 3.0 can't read ENV value. + chunk_size_value = SCANNER_TEST_CHUNK_SIZE_VALUE + end + chunk_size = Integer((chunk_size_value || "1"), 10) + InputsScanner.new(inputs, + @encoding, + @row_separator, + chunk_size: chunk_size) + end + else + def build_scanner + string = nil + if @samples.empty? and @input.is_a?(StringIO) + string = @input.read + elsif @samples.size == 1 and Parser.eof?(@input) + string = @samples[0] + end + if string + unless string.valid_encoding? + index = string.lines(@row_separator).index do |line| + !line.valid_encoding? + end + if index + raise InvalidEncodingError.new(@encoding, @lineno + index + 1) + end + end + Scanner.new(string) + else + inputs = @samples.collect do |sample| + StringIO.new(sample) + end + inputs << @input + InputsScanner.new(inputs, @encoding, @row_separator) + end + end + end + + def skip_needless_lines + return unless @skip_lines + + until @scanner.eos? + @scanner.keep_start + line = @scanner.scan_all(@not_line_end) || "".encode(@encoding) + line << @row_separator if parse_row_end + if skip_line?(line) + @lineno += 1 + @scanner.keep_drop + else + @scanner.keep_back + return + end + end + end + + def skip_line?(line) + line = line.delete_suffix(@row_separator) + case @skip_lines + when String + line.include?(@skip_lines) + when Regexp + @skip_lines.match?(line) + else + @skip_lines.match(line) + end + end + + def validate_field_size(field) + return unless @max_field_size + return if field.size <= @max_field_size + ignore_broken_line + message = "Field size exceeded: #{field.size} > #{@max_field_size}" + raise MalformedCSVError.new(message, @lineno) + end + + def parse_no_quote(&block) + @scanner.each_line(@row_separator) do |line| + next if @skip_lines and skip_line?(line) + original_line = line + line = line.delete_suffix(@row_separator) + + if line.empty? + next if @skip_blanks + row = [] + else + line = strip_value(line) + row = line.split(@split_column_separator, -1) + if @max_field_size + row.each do |column| + validate_field_size(column) + end + end + n_columns = row.size + i = 0 + while i < n_columns + row[i] = nil if row[i].empty? + i += 1 + end + end + @last_line = original_line + emit_row(row, &block) + end + end + + def parse_quotable_loose(&block) + @scanner.keep_start + @scanner.each_line(@row_separator) do |line| + if @skip_lines and skip_line?(line) + @scanner.keep_drop + @scanner.keep_start + next + end + original_line = line + line = line.delete_suffix(@row_separator) + + if line.empty? + if @skip_blanks + @scanner.keep_drop + @scanner.keep_start + next + end + row = [] + quoted_fields = FieldsConverter::NO_QUOTED_FIELDS + elsif line.include?(@cr) or line.include?(@lf) + @scanner.keep_back + @parse_method = :parse_quotable_robust + return parse_quotable_robust(&block) + else + row = line.split(@split_column_separator, -1) + quoted_fields = [] + n_columns = row.size + i = 0 + while i < n_columns + column = row[i] + if column.empty? + quoted_fields << false + row[i] = nil + else + n_quotes = column.count(@quote_character) + if n_quotes.zero? + quoted_fields << false + # no quote + elsif n_quotes == 2 and + column.start_with?(@quote_character) and + column.end_with?(@quote_character) + quoted_fields << true + row[i] = column[1..-2] + else + @scanner.keep_back + @parse_method = :parse_quotable_robust + return parse_quotable_robust(&block) + end + validate_field_size(row[i]) + end + i += 1 + end + end + @scanner.keep_drop + @scanner.keep_start + @last_line = original_line + emit_row(row, quoted_fields, &block) + end + @scanner.keep_drop + end + + def parse_quotable_robust(&block) + row = [] + quoted_fields = [] + skip_needless_lines + start_row + while true + @quoted_column_value = false + @unquoted_column_value = false + @scanner.scan_all(@strip_value) if @strip_value + value = parse_column_value + if value + @scanner.scan_all(@strip_value) if @strip_value + validate_field_size(value) + end + if parse_column_end + row << value + quoted_fields << @quoted_column_value + elsif parse_row_end + if row.empty? and value.nil? + emit_row([], &block) unless @skip_blanks + else + row << value + quoted_fields << @quoted_column_value + emit_row(row, quoted_fields, &block) + row = [] + quoted_fields.clear + end + skip_needless_lines + start_row + elsif @scanner.eos? + break if row.empty? and value.nil? + row << value + quoted_fields << @quoted_column_value + emit_row(row, quoted_fields, &block) + break + else + if @quoted_column_value + if liberal_parsing? and (new_line = @scanner.check(@line_end)) + message = + "Illegal end-of-line sequence outside of a quoted field " + + "<#{new_line.inspect}>" + else + message = "Any value after quoted field isn't allowed" + end + ignore_broken_line + raise MalformedCSVError.new(message, @lineno) + elsif @unquoted_column_value and + (new_line = @scanner.scan(@line_end)) + ignore_broken_line + message = "Unquoted fields do not allow new line " + + "<#{new_line.inspect}>" + raise MalformedCSVError.new(message, @lineno) + elsif @scanner.rest.start_with?(@quote_character) + ignore_broken_line + message = "Illegal quoting" + raise MalformedCSVError.new(message, @lineno) + elsif (new_line = @scanner.scan(@line_end)) + ignore_broken_line + message = "New line must be <#{@row_separator.inspect}> " + + "not <#{new_line.inspect}>" + raise MalformedCSVError.new(message, @lineno) + else + ignore_broken_line + raise MalformedCSVError.new("TODO: Meaningful message", + @lineno) + end + end + end + end + + def parse_column_value + if @liberal_parsing + quoted_value = parse_quoted_column_value + if quoted_value + @scanner.scan_all(@strip_value) if @strip_value + unquoted_value = parse_unquoted_column_value + if unquoted_value + if @double_quote_outside_quote + unquoted_value = unquoted_value.gsub(@quote_character * 2, + @quote_character) + if quoted_value.empty? # %Q{""...} case + return @quote_character + unquoted_value + end + end + @quote_character + quoted_value + @quote_character + unquoted_value + else + quoted_value + end + else + parse_unquoted_column_value + end + elsif @may_quoted + parse_quoted_column_value || + parse_unquoted_column_value + else + parse_unquoted_column_value || + parse_quoted_column_value + end + end + + def parse_unquoted_column_value + value = @scanner.scan_all(@unquoted_value) + return nil unless value + + @unquoted_column_value = true + if @first_column_separators + while true + @scanner.keep_start + is_column_end = @column_ends.all? do |column_end| + @scanner.scan(column_end) + end + @scanner.keep_back + break if is_column_end + sub_separator = @scanner.scan_all(@first_column_separators) + break if sub_separator.nil? + value << sub_separator + sub_value = @scanner.scan_all(@unquoted_value) + break if sub_value.nil? + value << sub_value + end + end + value.gsub!(@backslash_quote_character, @quote_character) if @backslash_quote + if @rstrip_value + value.gsub!(@rstrip_value, "") + end + value + end + + def parse_quoted_column_value + quotes = @scanner.scan_all(@quotes) + return nil unless quotes + + @quoted_column_value = true + n_quotes = quotes.size + if (n_quotes % 2).zero? + quotes[0, (n_quotes - 2) / 2] + else + value = quotes[0, n_quotes / 2] + while true + quoted_value = @scanner.scan_all(@quoted_value) + value << quoted_value if quoted_value + if @backslash_quote + if @scanner.scan(@escaped_backslash) + if @scanner.scan(@escaped_quote) + value << @quote_character + else + value << @backslash_character + end + next + end + end + + quotes = @scanner.scan_all(@quotes) + unless quotes + ignore_broken_line + message = "Unclosed quoted field" + raise MalformedCSVError.new(message, @lineno) + end + n_quotes = quotes.size + if n_quotes == 1 + break + else + value << quotes[0, n_quotes / 2] + break if (n_quotes % 2) == 1 + end + end + value + end + end + + def parse_column_end + return true if @scanner.scan(@column_end) + return false unless @column_ends + + @scanner.keep_start + if @column_ends.all? {|column_end| @scanner.scan(column_end)} + @scanner.keep_drop + true + else + @scanner.keep_back + false + end + end + + def parse_row_end + return true if @scanner.scan(@row_end) + return false unless @row_ends + @scanner.keep_start + if @row_ends.all? {|row_end| @scanner.scan(row_end)} + @scanner.keep_drop + true + else + @scanner.keep_back + false + end + end + + def strip_value(value) + return value unless @strip + return value if value.nil? + + case @strip + when String + while value.delete_prefix!(@strip) + # do nothing + end + while value.delete_suffix!(@strip) + # do nothing + end + else + value.strip! + end + value + end + + def ignore_broken_line + @scanner.scan_all(@not_line_end) + @scanner.scan_all(@line_end) + @lineno += 1 + end + + def start_row + if @last_line + @last_line = nil + else + @scanner.keep_drop + end + @scanner.keep_start + end + + def emit_row(row, quoted_fields=FieldsConverter::NO_QUOTED_FIELDS, &block) + @lineno += 1 + + raw_row = row + if @use_headers + if @headers.nil? + @headers = adjust_headers(row, quoted_fields) + return unless @return_headers + row = Row.new(@headers, row, true) + else + row = Row.new(@headers, + @fields_converter.convert(raw_row, @headers, @lineno, quoted_fields)) + end + else + # convert fields, if needed... + row = @fields_converter.convert(raw_row, nil, @lineno, quoted_fields) + end + + # inject unconverted fields and accessor, if requested... + if @unconverted_fields and not row.respond_to?(:unconverted_fields) + add_unconverted_fields(row, raw_row) + end + + yield(row) + end + + # This method injects an instance variable unconverted_fields into + # +row+ and an accessor method for +row+ called unconverted_fields(). The + # variable is set to the contents of +fields+. + def add_unconverted_fields(row, fields) + class << row + attr_reader :unconverted_fields + end + row.instance_variable_set(:@unconverted_fields, fields) + row + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/row.rb b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/row.rb new file mode 100644 index 00000000..86323f7d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/row.rb @@ -0,0 +1,757 @@ +# frozen_string_literal: true + +require "forwardable" + +class CSV + # = \CSV::Row + # A \CSV::Row instance represents a \CSV table row. + # (see {class CSV}[../CSV.html]). + # + # The instance may have: + # - Fields: each is an object, not necessarily a \String. + # - Headers: each serves a key, and also need not be a \String. + # + # === Instance Methods + # + # \CSV::Row has three groups of instance methods: + # - Its own internally defined instance methods. + # - Methods included by module Enumerable. + # - Methods delegated to class Array.: + # * Array#empty? + # * Array#length + # * Array#size + # + # == Creating a \CSV::Row Instance + # + # Commonly, a new \CSV::Row instance is created by parsing \CSV source + # that has headers: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.each {|row| p row } + # Output: + # # + # # + # # + # + # You can also create a row directly. See ::new. + # + # == Headers + # + # Like a \CSV::Table, a \CSV::Row has headers. + # + # A \CSV::Row that was created by parsing \CSV source + # inherits its headers from the table: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # row = table.first + # row.headers # => ["Name", "Value"] + # + # You can also create a new row with headers; + # like the keys in a \Hash, the headers need not be Strings: + # row = CSV::Row.new([:name, :value], ['foo', 0]) + # row.headers # => [:name, :value] + # + # The new row retains its headers even if added to a table + # that has headers: + # table << row # => # + # row.headers # => [:name, :value] + # row[:name] # => "foo" + # row['Name'] # => nil + # + # + # + # == Accessing Fields + # + # You may access a field in a \CSV::Row with either its \Integer index + # (\Array-style) or its header (\Hash-style). + # + # Fetch a field using method #[]: + # row = CSV::Row.new(['Name', 'Value'], ['foo', 0]) + # row[1] # => 0 + # row['Value'] # => 0 + # + # Set a field using method #[]=: + # row = CSV::Row.new(['Name', 'Value'], ['foo', 0]) + # row # => # + # row[0] = 'bar' + # row['Value'] = 1 + # row # => # + # + class Row + # :call-seq: + # CSV::Row.new(headers, fields, header_row = false) -> csv_row + # + # Returns the new \CSV::Row instance constructed from + # arguments +headers+ and +fields+; both should be Arrays; + # note that the fields need not be Strings: + # row = CSV::Row.new(['Name', 'Value'], ['foo', 0]) + # row # => # + # + # If the \Array lengths are different, the shorter is +nil+-filled: + # row = CSV::Row.new(['Name', 'Value', 'Date', 'Size'], ['foo', 0]) + # row # => # + # + # Each \CSV::Row object is either a field row or a header row; + # by default, a new row is a field row; for the row created above: + # row.field_row? # => true + # row.header_row? # => false + # + # If the optional argument +header_row+ is given as +true+, + # the created row is a header row: + # row = CSV::Row.new(['Name', 'Value'], ['foo', 0], header_row = true) + # row # => # + # row.field_row? # => false + # row.header_row? # => true + def initialize(headers, fields, header_row = false) + @header_row = header_row + headers.each { |h| h.freeze if h.is_a? String } + + # handle extra headers or fields + @row = if headers.size >= fields.size + headers.zip(fields) + else + fields.zip(headers).each(&:reverse!) + end + end + + # Internal data format used to compare equality. + attr_reader :row + protected :row + + ### Array Delegation ### + + extend Forwardable + def_delegators :@row, :empty?, :length, :size + + # :call-seq: + # row.initialize_copy(other_row) -> self + # + # Calls superclass method. + def initialize_copy(other) + super_return_value = super + @row = @row.collect(&:dup) + super_return_value + end + + # :call-seq: + # row.header_row? -> true or false + # + # Returns +true+ if this is a header row, +false+ otherwise. + def header_row? + @header_row + end + + # :call-seq: + # row.field_row? -> true or false + # + # Returns +true+ if this is a field row, +false+ otherwise. + def field_row? + not header_row? + end + + # :call-seq: + # row.headers -> array_of_headers + # + # Returns the headers for this row: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # row = table.first + # row.headers # => ["Name", "Value"] + def headers + @row.map(&:first) + end + + # :call-seq: + # field(index) -> value + # field(header) -> value + # field(header, offset) -> value + # + # Returns the field value for the given +index+ or +header+. + # + # --- + # + # Fetch field value by \Integer index: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.field(0) # => "foo" + # row.field(1) # => "bar" + # + # Counts backward from the last column if +index+ is negative: + # row.field(-1) # => "0" + # row.field(-2) # => "foo" + # + # Returns +nil+ if +index+ is out of range: + # row.field(2) # => nil + # row.field(-3) # => nil + # + # --- + # + # Fetch field value by header (first found): + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.field('Name') # => "Foo" + # + # Fetch field value by header, ignoring +offset+ leading fields: + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.field('Name', 2) # => "Baz" + # + # Returns +nil+ if the header does not exist. + def field(header_or_index, minimum_index = 0) + # locate the pair + finder = (header_or_index.is_a?(Integer) || header_or_index.is_a?(Range)) ? :[] : :assoc + pair = @row[minimum_index..-1].public_send(finder, header_or_index) + + # return the field if we have a pair + if pair.nil? + nil + else + header_or_index.is_a?(Range) ? pair.map(&:last) : pair.last + end + end + alias_method :[], :field + + # + # :call-seq: + # fetch(header) -> value + # fetch(header, default) -> value + # fetch(header) {|row| ... } -> value + # + # Returns the field value as specified by +header+. + # + # --- + # + # With the single argument +header+, returns the field value + # for that header (first found): + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.fetch('Name') # => "Foo" + # + # Raises exception +KeyError+ if the header does not exist. + # + # --- + # + # With arguments +header+ and +default+ given, + # returns the field value for the header (first found) + # if the header exists, otherwise returns +default+: + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.fetch('Name', '') # => "Foo" + # row.fetch(:nosuch, '') # => "" + # + # --- + # + # With argument +header+ and a block given, + # returns the field value for the header (first found) + # if the header exists; otherwise calls the block + # and returns its return value: + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.fetch('Name') {|header| fail 'Cannot happen' } # => "Foo" + # row.fetch(:nosuch) {|header| "Header '#{header} not found'" } # => "Header 'nosuch not found'" + def fetch(header, *varargs) + raise ArgumentError, "Too many arguments" if varargs.length > 1 + pair = @row.assoc(header) + if pair + pair.last + else + if block_given? + yield header + elsif varargs.empty? + raise KeyError, "key not found: #{header}" + else + varargs.first + end + end + end + + # :call-seq: + # row.has_key?(header) -> true or false + # + # Returns +true+ if there is a field with the given +header+, + # +false+ otherwise. + def has_key?(header) + !!@row.assoc(header) + end + alias_method :include?, :has_key? + alias_method :key?, :has_key? + alias_method :member?, :has_key? + alias_method :header?, :has_key? + + # + # :call-seq: + # row[index] = value -> value + # row[header, offset] = value -> value + # row[header] = value -> value + # + # Assigns the field value for the given +index+ or +header+; + # returns +value+. + # + # --- + # + # Assign field value by \Integer index: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row[0] = 'Bat' + # row[1] = 3 + # row # => # + # + # Counts backward from the last column if +index+ is negative: + # row[-1] = 4 + # row[-2] = 'Bam' + # row # => # + # + # Extends the row with nil:nil if positive +index+ is not in the row: + # row[4] = 5 + # row # => # + # + # Raises IndexError if negative +index+ is too small (too far from zero). + # + # --- + # + # Assign field value by header (first found): + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row['Name'] = 'Bat' + # row # => # + # + # Assign field value by header, ignoring +offset+ leading fields: + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row['Name', 2] = 4 + # row # => # + # + # Append new field by (new) header: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row['New'] = 6 + # row# => # + def []=(*args) + value = args.pop + + if args.first.is_a? Integer + if @row[args.first].nil? # extending past the end with index + @row[args.first] = [nil, value] + @row.map! { |pair| pair.nil? ? [nil, nil] : pair } + else # normal index assignment + @row[args.first][1] = value + end + else + index = index(*args) + if index.nil? # appending a field + self << [args.first, value] + else # normal header assignment + @row[index][1] = value + end + end + end + + # + # :call-seq: + # row << [header, value] -> self + # row << hash -> self + # row << value -> self + # + # Adds a field to +self+; returns +self+: + # + # If the argument is a 2-element \Array [header, value], + # a field is added with the given +header+ and +value+: + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row << ['NAME', 'Bat'] + # row # => # + # + # If the argument is a \Hash, each key-value pair is added + # as a field with header +key+ and value +value+. + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row << {NAME: 'Bat', name: 'Bam'} + # row # => # + # + # Otherwise, the given +value+ is added as a field with no header. + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row << 'Bag' + # row # => # + def <<(arg) + if arg.is_a?(Array) and arg.size == 2 # appending a header and name + @row << arg + elsif arg.is_a?(Hash) # append header and name pairs + arg.each { |pair| @row << pair } + else # append field value + @row << [nil, arg] + end + + self # for chaining + end + + # :call-seq: + # row.push(*values) -> self + # + # Appends each of the given +values+ to +self+ as a field; returns +self+: + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.push('Bat', 'Bam') + # row # => # + def push(*args) + args.each { |arg| self << arg } + + self # for chaining + end + + # + # :call-seq: + # delete(index) -> [header, value] or nil + # delete(header) -> [header, value] or empty_array + # delete(header, offset) -> [header, value] or empty_array + # + # Removes a specified field from +self+; returns the 2-element \Array + # [header, value] if the field exists. + # + # If an \Integer argument +index+ is given, + # removes and returns the field at offset +index+, + # or returns +nil+ if the field does not exist: + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.delete(1) # => ["Name", "Bar"] + # row.delete(50) # => nil + # + # Otherwise, if the single argument +header+ is given, + # removes and returns the first-found field with the given header, + # of returns a new empty \Array if the field does not exist: + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.delete('Name') # => ["Name", "Foo"] + # row.delete('NAME') # => [] + # + # If argument +header+ and \Integer argument +offset+ are given, + # removes and returns the first-found field with the given header + # whose +index+ is at least as large as +offset+: + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.delete('Name', 1) # => ["Name", "Bar"] + # row.delete('NAME', 1) # => [] + def delete(header_or_index, minimum_index = 0) + if header_or_index.is_a? Integer # by index + @row.delete_at(header_or_index) + elsif i = index(header_or_index, minimum_index) # by header + @row.delete_at(i) + else + [ ] + end + end + + # :call-seq: + # row.delete_if {|header, value| ... } -> self + # + # Removes fields from +self+ as selected by the block; returns +self+. + # + # Removes each field for which the block returns a truthy value: + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.delete_if {|header, value| value.start_with?('B') } # => true + # row # => # + # row.delete_if {|header, value| header.start_with?('B') } # => false + # + # If no block is given, returns a new Enumerator: + # row.delete_if # => #:delete_if> + def delete_if(&block) + return enum_for(__method__) { size } unless block_given? + + @row.delete_if(&block) + + self # for chaining + end + + # :call-seq: + # self.fields(*specifiers) -> array_of_fields + # + # Returns field values per the given +specifiers+, which may be any mixture of: + # - \Integer index. + # - \Range of \Integer indexes. + # - 2-element \Array containing a header and offset. + # - Header. + # - \Range of headers. + # + # For +specifier+ in one of the first four cases above, + # returns the result of self.field(specifier); see #field. + # + # Although there may be any number of +specifiers+, + # the examples here will illustrate one at a time. + # + # When the specifier is an \Integer +index+, + # returns self.field(index)L + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.fields(1) # => ["Bar"] + # + # When the specifier is a \Range of \Integers +range+, + # returns self.field(range): + # row.fields(1..2) # => ["Bar", "Baz"] + # + # When the specifier is a 2-element \Array +array+, + # returns self.field(array)L + # row.fields('Name', 1) # => ["Foo", "Bar"] + # + # When the specifier is a header +header+, + # returns self.field(header)L + # row.fields('Name') # => ["Foo"] + # + # When the specifier is a \Range of headers +range+, + # forms a new \Range +new_range+ from the indexes of + # range.start and range.end, + # and returns self.field(new_range): + # source = "Name,NAME,name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.fields('Name'..'NAME') # => ["Foo", "Bar"] + # + # Returns all fields if no argument given: + # row.fields # => ["Foo", "Bar", "Baz"] + def fields(*headers_and_or_indices) + if headers_and_or_indices.empty? # return all fields--no arguments + @row.map(&:last) + else # or work like values_at() + all = [] + headers_and_or_indices.each do |h_or_i| + if h_or_i.is_a? Range + index_begin = h_or_i.begin.is_a?(Integer) ? h_or_i.begin : + index(h_or_i.begin) + index_end = h_or_i.end.is_a?(Integer) ? h_or_i.end : + index(h_or_i.end) + new_range = h_or_i.exclude_end? ? (index_begin...index_end) : + (index_begin..index_end) + all.concat(fields.values_at(new_range)) + else + all << field(*Array(h_or_i)) + end + end + return all + end + end + alias_method :values_at, :fields + + # :call-seq: + # index(header) -> index + # index(header, offset) -> index + # + # Returns the index for the given header, if it exists; + # otherwise returns +nil+. + # + # With the single argument +header+, returns the index + # of the first-found field with the given +header+: + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.index('Name') # => 0 + # row.index('NAME') # => nil + # + # With arguments +header+ and +offset+, + # returns the index of the first-found field with given +header+, + # but ignoring the first +offset+ fields: + # row.index('Name', 1) # => 1 + # row.index('Name', 3) # => nil + def index(header, minimum_index = 0) + # find the pair + index = headers[minimum_index..-1].index(header) + # return the index at the right offset, if we found one + index.nil? ? nil : index + minimum_index + end + + # :call-seq: + # row.field?(value) -> true or false + # + # Returns +true+ if +value+ is a field in this row, +false+ otherwise: + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.field?('Bar') # => true + # row.field?('BAR') # => false + def field?(data) + fields.include? data + end + + include Enumerable + + # :call-seq: + # row.each {|header, value| ... } -> self + # + # Calls the block with each header-value pair; returns +self+: + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.each {|header, value| p [header, value] } + # Output: + # ["Name", "Foo"] + # ["Name", "Bar"] + # ["Name", "Baz"] + # + # If no block is given, returns a new Enumerator: + # row.each # => #:each> + def each(&block) + return enum_for(__method__) { size } unless block_given? + + @row.each(&block) + + self # for chaining + end + + alias_method :each_pair, :each + + # :call-seq: + # row == other -> true or false + # + # Returns +true+ if +other+ is a /CSV::Row that has the same + # fields (headers and values) in the same order as +self+; + # otherwise returns +false+: + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # other_row = table[0] + # row == other_row # => true + # other_row = table[1] + # row == other_row # => false + def ==(other) + return @row == other.row if other.is_a? CSV::Row + @row == other + end + + # :call-seq: + # row.to_h -> hash + # + # Returns the new \Hash formed by adding each header-value pair in +self+ + # as a key-value pair in the \Hash. + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.to_h # => {"Name"=>"foo", "Value"=>"0"} + # + # Header order is preserved, but repeated headers are ignored: + # source = "Name,Name,Name\nFoo,Bar,Baz\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.to_h # => {"Name"=>"Foo"} + def to_h + hash = {} + each do |key, _value| + hash[key] = self[key] unless hash.key?(key) + end + hash + end + alias_method :to_hash, :to_h + + # :call-seq: + # row.deconstruct_keys(keys) -> hash + # + # Returns the new \Hash suitable for pattern matching containing only the + # keys specified as an argument. + def deconstruct_keys(keys) + if keys.nil? + to_h + else + keys.to_h { |key| [key, self[key]] } + end + end + + alias_method :to_ary, :to_a + + # :call-seq: + # row.deconstruct -> array + # + # Returns the new \Array suitable for pattern matching containing the values + # of the row. + def deconstruct + fields + end + + # :call-seq: + # row.to_csv -> csv_string + # + # Returns the row as a \CSV String. Headers are not included: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.to_csv # => "foo,0\n" + def to_csv(**options) + fields.to_csv(**options) + end + alias_method :to_s, :to_csv + + # :call-seq: + # row.dig(index_or_header, *identifiers) -> object + # + # Finds and returns the object in nested object that is specified + # by +index_or_header+ and +specifiers+. + # + # The nested objects may be instances of various classes. + # See {Dig Methods}[rdoc-ref:dig_methods.rdoc]. + # + # Examples: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.dig(1) # => "0" + # row.dig('Value') # => "0" + # row.dig(5) # => nil + def dig(index_or_header, *indexes) + value = field(index_or_header) + if value.nil? + nil + elsif indexes.empty? + value + else + unless value.respond_to?(:dig) + raise TypeError, "#{value.class} does not have \#dig method" + end + value.dig(*indexes) + end + end + + # :call-seq: + # row.inspect -> string + # + # Returns an ASCII-compatible \String showing: + # - Class \CSV::Row. + # - Header-value pairs. + # Example: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # row = table[0] + # row.inspect # => "#" + def inspect + str = ["#<", self.class.to_s] + each do |header, field| + str << " " << (header.is_a?(Symbol) ? header.to_s : header.inspect) << + ":" << field.inspect + end + str << ">" + begin + str.join('') + rescue # any encoding error + str.map do |s| + e = Encoding::Converter.asciicompat_encoding(s.encoding) + e ? s.encode(e) : s.force_encoding("ASCII-8BIT") + end.join('') + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/table.rb b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/table.rb new file mode 100644 index 00000000..fb19f545 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/table.rb @@ -0,0 +1,1055 @@ +# frozen_string_literal: true + +require "forwardable" + +class CSV + # = \CSV::Table + # A \CSV::Table instance represents \CSV data. + # (see {class CSV}[../CSV.html]). + # + # The instance may have: + # - Rows: each is a Table::Row object. + # - Headers: names for the columns. + # + # === Instance Methods + # + # \CSV::Table has three groups of instance methods: + # - Its own internally defined instance methods. + # - Methods included by module Enumerable. + # - Methods delegated to class Array.: + # * Array#empty? + # * Array#length + # * Array#size + # + # == Creating a \CSV::Table Instance + # + # Commonly, a new \CSV::Table instance is created by parsing \CSV source + # using headers: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.class # => CSV::Table + # + # You can also create an instance directly. See ::new. + # + # == Headers + # + # If a table has headers, the headers serve as labels for the columns of data. + # Each header serves as the label for its column. + # + # The headers for a \CSV::Table object are stored as an \Array of Strings. + # + # Commonly, headers are defined in the first row of \CSV source: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.headers # => ["Name", "Value"] + # + # If no headers are defined, the \Array is empty: + # table = CSV::Table.new([]) + # table.headers # => [] + # + # == Access Modes + # + # \CSV::Table provides three modes for accessing table data: + # - \Row mode. + # - Column mode. + # - Mixed mode (the default for a new table). + # + # The access mode for a\CSV::Table instance affects the behavior + # of some of its instance methods: + # - #[] + # - #[]= + # - #delete + # - #delete_if + # - #each + # - #values_at + # + # === \Row Mode + # + # Set a table to row mode with method #by_row!: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.by_row! # => # + # + # Specify a single row by an \Integer index: + # # Get a row. + # table[1] # => # + # # Set a row, then get it. + # table[1] = CSV::Row.new(['Name', 'Value'], ['bam', 3]) + # table[1] # => # + # + # Specify a sequence of rows by a \Range: + # # Get rows. + # table[1..2] # => [#, #] + # # Set rows, then get them. + # table[1..2] = [ + # CSV::Row.new(['Name', 'Value'], ['bat', 4]), + # CSV::Row.new(['Name', 'Value'], ['bad', 5]), + # ] + # table[1..2] # => [["Name", #], ["Value", #]] + # + # === Column Mode + # + # Set a table to column mode with method #by_col!: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.by_col! # => # + # + # Specify a column by an \Integer index: + # # Get a column. + # table[0] + # # Set a column, then get it. + # table[0] = ['FOO', 'BAR', 'BAZ'] + # table[0] # => ["FOO", "BAR", "BAZ"] + # + # Specify a column by its \String header: + # # Get a column. + # table['Name'] # => ["FOO", "BAR", "BAZ"] + # # Set a column, then get it. + # table['Name'] = ['Foo', 'Bar', 'Baz'] + # table['Name'] # => ["Foo", "Bar", "Baz"] + # + # === Mixed Mode + # + # In mixed mode, you can refer to either rows or columns: + # - An \Integer index refers to a row. + # - A \Range index refers to multiple rows. + # - A \String index refers to a column. + # + # Set a table to mixed mode with method #by_col_or_row!: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.by_col_or_row! # => # + # + # Specify a single row by an \Integer index: + # # Get a row. + # table[1] # => # + # # Set a row, then get it. + # table[1] = CSV::Row.new(['Name', 'Value'], ['bam', 3]) + # table[1] # => # + # + # Specify a sequence of rows by a \Range: + # # Get rows. + # table[1..2] # => [#, #] + # # Set rows, then get them. + # table[1] = CSV::Row.new(['Name', 'Value'], ['bat', 4]) + # table[2] = CSV::Row.new(['Name', 'Value'], ['bad', 5]) + # table[1..2] # => [["Name", #], ["Value", #]] + # + # Specify a column by its \String header: + # # Get a column. + # table['Name'] # => ["foo", "bat", "bad"] + # # Set a column, then get it. + # table['Name'] = ['Foo', 'Bar', 'Baz'] + # table['Name'] # => ["Foo", "Bar", "Baz"] + class Table + # :call-seq: + # CSV::Table.new(array_of_rows, headers = nil) -> csv_table + # + # Returns a new \CSV::Table object. + # + # - Argument +array_of_rows+ must be an \Array of CSV::Row objects. + # - Argument +headers+, if given, may be an \Array of Strings. + # + # --- + # + # Create an empty \CSV::Table object: + # table = CSV::Table.new([]) + # table # => # + # + # Create a non-empty \CSV::Table object: + # rows = [ + # CSV::Row.new([], []), + # CSV::Row.new([], []), + # CSV::Row.new([], []), + # ] + # table = CSV::Table.new(rows) + # table # => # + # + # --- + # + # If argument +headers+ is an \Array of Strings, + # those Strings become the table's headers: + # table = CSV::Table.new([], headers: ['Name', 'Age']) + # table.headers # => ["Name", "Age"] + # + # If argument +headers+ is not given and the table has rows, + # the headers are taken from the first row: + # rows = [ + # CSV::Row.new(['Foo', 'Bar'], []), + # CSV::Row.new(['foo', 'bar'], []), + # CSV::Row.new(['FOO', 'BAR'], []), + # ] + # table = CSV::Table.new(rows) + # table.headers # => ["Foo", "Bar"] + # + # If argument +headers+ is not given and the table is empty (has no rows), + # the headers are also empty: + # table = CSV::Table.new([]) + # table.headers # => [] + # + # --- + # + # Raises an exception if argument +array_of_rows+ is not an \Array object: + # # Raises NoMethodError (undefined method `first' for :foo:Symbol): + # CSV::Table.new(:foo) + # + # Raises an exception if an element of +array_of_rows+ is not a \CSV::Table object: + # # Raises NoMethodError (undefined method `headers' for :foo:Symbol): + # CSV::Table.new([:foo]) + def initialize(array_of_rows, headers: nil) + @table = array_of_rows + @headers = headers + unless @headers + if @table.empty? + @headers = [] + else + @headers = @table.first.headers + end + end + + @mode = :col_or_row + end + + # The current access mode for indexing and iteration. + attr_reader :mode + + # Internal data format used to compare equality. + attr_reader :table + protected :table + + ### Array Delegation ### + + extend Forwardable + def_delegators :@table, :empty?, :length, :size + + # :call-seq: + # table.by_col -> table_dup + # + # Returns a duplicate of +self+, in column mode + # (see {Column Mode}[#class-CSV::Table-label-Column+Mode]): + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.mode # => :col_or_row + # dup_table = table.by_col + # dup_table.mode # => :col + # dup_table.equal?(table) # => false # It's a dup + # + # This may be used to chain method calls without changing the mode + # (but also will affect performance and memory usage): + # dup_table.by_col['Name'] + # + # Also note that changes to the duplicate table will not affect the original. + def by_col + self.class.new(@table.dup).by_col! + end + + # :call-seq: + # table.by_col! -> self + # + # Sets the mode for +self+ to column mode + # (see {Column Mode}[#class-CSV::Table-label-Column+Mode]); returns +self+: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.mode # => :col_or_row + # table1 = table.by_col! + # table.mode # => :col + # table1.equal?(table) # => true # Returned self + def by_col! + @mode = :col + + self + end + + # :call-seq: + # table.by_col_or_row -> table_dup + # + # Returns a duplicate of +self+, in mixed mode + # (see {Mixed Mode}[#class-CSV::Table-label-Mixed+Mode]): + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true).by_col! + # table.mode # => :col + # dup_table = table.by_col_or_row + # dup_table.mode # => :col_or_row + # dup_table.equal?(table) # => false # It's a dup + # + # This may be used to chain method calls without changing the mode + # (but also will affect performance and memory usage): + # dup_table.by_col_or_row['Name'] + # + # Also note that changes to the duplicate table will not affect the original. + def by_col_or_row + self.class.new(@table.dup).by_col_or_row! + end + + # :call-seq: + # table.by_col_or_row! -> self + # + # Sets the mode for +self+ to mixed mode + # (see {Mixed Mode}[#class-CSV::Table-label-Mixed+Mode]); returns +self+: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true).by_col! + # table.mode # => :col + # table1 = table.by_col_or_row! + # table.mode # => :col_or_row + # table1.equal?(table) # => true # Returned self + def by_col_or_row! + @mode = :col_or_row + + self + end + + # :call-seq: + # table.by_row -> table_dup + # + # Returns a duplicate of +self+, in row mode + # (see {Row Mode}[#class-CSV::Table-label-Row+Mode]): + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.mode # => :col_or_row + # dup_table = table.by_row + # dup_table.mode # => :row + # dup_table.equal?(table) # => false # It's a dup + # + # This may be used to chain method calls without changing the mode + # (but also will affect performance and memory usage): + # dup_table.by_row[1] + # + # Also note that changes to the duplicate table will not affect the original. + def by_row + self.class.new(@table.dup).by_row! + end + + # :call-seq: + # table.by_row! -> self + # + # Sets the mode for +self+ to row mode + # (see {Row Mode}[#class-CSV::Table-label-Row+Mode]); returns +self+: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.mode # => :col_or_row + # table1 = table.by_row! + # table.mode # => :row + # table1.equal?(table) # => true # Returned self + def by_row! + @mode = :row + + self + end + + # :call-seq: + # table.headers -> array_of_headers + # + # Returns a new \Array containing the \String headers for the table. + # + # If the table is not empty, returns the headers from the first row: + # rows = [ + # CSV::Row.new(['Foo', 'Bar'], []), + # CSV::Row.new(['FOO', 'BAR'], []), + # CSV::Row.new(['foo', 'bar'], []), + # ] + # table = CSV::Table.new(rows) + # table.headers # => ["Foo", "Bar"] + # table.delete(0) + # table.headers # => ["FOO", "BAR"] + # table.delete(0) + # table.headers # => ["foo", "bar"] + # + # If the table is empty, returns a copy of the headers in the table itself: + # table.delete(0) + # table.headers # => ["Foo", "Bar"] + def headers + if @table.empty? + @headers.dup + else + @table.first.headers + end + end + + # :call-seq: + # table[n] -> row or column_data + # table[range] -> array_of_rows or array_of_column_data + # table[header] -> array_of_column_data + # + # Returns data from the table; does not modify the table. + # + # --- + # + # Fetch a \Row by Its \Integer Index:: + # - Form: table[n], +n+ an integer. + # - Access mode: :row or :col_or_row. + # - Return value: _nth_ row of the table, if that row exists; + # otherwise +nil+. + # + # Returns the _nth_ row of the table if that row exists: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.by_row! # => # + # table[1] # => # + # table.by_col_or_row! # => # + # table[1] # => # + # + # Counts backward from the last row if +n+ is negative: + # table[-1] # => # + # + # Returns +nil+ if +n+ is too large or too small: + # table[4] # => nil + # table[-4] # => nil + # + # Raises an exception if the access mode is :row + # and +n+ is not an \Integer: + # table.by_row! # => # + # # Raises TypeError (no implicit conversion of String into Integer): + # table['Name'] + # + # --- + # + # Fetch a Column by Its \Integer Index:: + # - Form: table[n], +n+ an \Integer. + # - Access mode: :col. + # - Return value: _nth_ column of the table, if that column exists; + # otherwise an \Array of +nil+ fields of length self.size. + # + # Returns the _nth_ column of the table if that column exists: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.by_col! # => # + # table[1] # => ["0", "1", "2"] + # + # Counts backward from the last column if +n+ is negative: + # table[-2] # => ["foo", "bar", "baz"] + # + # Returns an \Array of +nil+ fields if +n+ is too large or too small: + # table[4] # => [nil, nil, nil] + # table[-4] # => [nil, nil, nil] + # + # --- + # + # Fetch Rows by \Range:: + # - Form: table[range], +range+ a \Range object. + # - Access mode: :row or :col_or_row. + # - Return value: rows from the table, beginning at row range.start, + # if those rows exists. + # + # Returns rows from the table, beginning at row range.first, + # if those rows exist: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.by_row! # => # + # rows = table[1..2] # => # + # rows # => [#, #] + # table.by_col_or_row! # => # + # rows = table[1..2] # => # + # rows # => [#, #] + # + # If there are too few rows, returns all from range.start to the end: + # rows = table[1..50] # => # + # rows # => [#, #] + # + # Special case: if range.start == table.size, returns an empty \Array: + # table[table.size..50] # => [] + # + # If range.end is negative, calculates the ending index from the end: + # rows = table[0..-1] + # rows # => [#, #, #] + # + # If range.start is negative, calculates the starting index from the end: + # rows = table[-1..2] + # rows # => [#] + # + # If range.start is larger than table.size, returns +nil+: + # table[4..4] # => nil + # + # --- + # + # Fetch Columns by \Range:: + # - Form: table[range], +range+ a \Range object. + # - Access mode: :col. + # - Return value: column data from the table, beginning at column range.start, + # if those columns exist. + # + # Returns column values from the table, if the column exists; + # the values are arranged by row: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.by_col! + # table[0..1] # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + # + # Special case: if range.start == headers.size, + # returns an \Array (size: table.size) of empty \Arrays: + # table[table.headers.size..50] # => [[], [], []] + # + # If range.end is negative, calculates the ending index from the end: + # table[0..-1] # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + # + # If range.start is negative, calculates the starting index from the end: + # table[-2..2] # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] + # + # If range.start is larger than table.size, + # returns an \Array of +nil+ values: + # table[4..4] # => [nil, nil, nil] + # + # --- + # + # Fetch a Column by Its \String Header:: + # - Form: table[header], +header+ a \String header. + # - Access mode: :col or :col_or_row + # - Return value: column data from the table, if that +header+ exists. + # + # Returns column values from the table, if the column exists: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.by_col! # => # + # table['Name'] # => ["foo", "bar", "baz"] + # table.by_col_or_row! # => # + # col = table['Name'] + # col # => ["foo", "bar", "baz"] + # + # Modifying the returned column values does not modify the table: + # col[0] = 'bat' + # col # => ["bat", "bar", "baz"] + # table['Name'] # => ["foo", "bar", "baz"] + # + # Returns an \Array of +nil+ values if there is no such column: + # table['Nosuch'] # => [nil, nil, nil] + def [](index_or_header) + if @mode == :row or # by index + (@mode == :col_or_row and (index_or_header.is_a?(Integer) or index_or_header.is_a?(Range))) + @table[index_or_header] + else # by header + @table.map { |row| row[index_or_header] } + end + end + + # :call-seq: + # table[n] = row -> row + # table[n] = field_or_array_of_fields -> field_or_array_of_fields + # table[header] = field_or_array_of_fields -> field_or_array_of_fields + # + # Puts data onto the table. + # + # --- + # + # Set a \Row by Its \Integer Index:: + # - Form: table[n] = row, +n+ an \Integer, + # +row+ a \CSV::Row instance or an \Array of fields. + # - Access mode: :row or :col_or_row. + # - Return value: +row+. + # + # If the row exists, it is replaced: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # new_row = CSV::Row.new(['Name', 'Value'], ['bat', 3]) + # table.by_row! # => # + # return_value = table[0] = new_row + # return_value.equal?(new_row) # => true # Returned the row + # table[0].to_h # => {"Name"=>"bat", "Value"=>3} + # + # With access mode :col_or_row: + # table.by_col_or_row! # => # + # table[0] = CSV::Row.new(['Name', 'Value'], ['bam', 4]) + # table[0].to_h # => {"Name"=>"bam", "Value"=>4} + # + # With an \Array instead of a \CSV::Row, inherits headers from the table: + # array = ['bad', 5] + # return_value = table[0] = array + # return_value.equal?(array) # => true # Returned the array + # table[0].to_h # => {"Name"=>"bad", "Value"=>5} + # + # If the row does not exist, extends the table by adding rows: + # assigns rows with +nil+ as needed: + # table.size # => 3 + # table[5] = ['bag', 6] + # table.size # => 6 + # table[3] # => nil + # table[4]# => nil + # table[5].to_h # => {"Name"=>"bag", "Value"=>6} + # + # Note that the +nil+ rows are actually +nil+, not a row of +nil+ fields. + # + # --- + # + # Set a Column by Its \Integer Index:: + # - Form: table[n] = array_of_fields, +n+ an \Integer, + # +array_of_fields+ an \Array of \String fields. + # - Access mode: :col. + # - Return value: +array_of_fields+. + # + # If the column exists, it is replaced: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # new_col = [3, 4, 5] + # table.by_col! # => # + # return_value = table[1] = new_col + # return_value.equal?(new_col) # => true # Returned the column + # table[1] # => [3, 4, 5] + # # The rows, as revised: + # table.by_row! # => # + # table[0].to_h # => {"Name"=>"foo", "Value"=>3} + # table[1].to_h # => {"Name"=>"bar", "Value"=>4} + # table[2].to_h # => {"Name"=>"baz", "Value"=>5} + # table.by_col! # => # + # + # If there are too few values, fills with +nil+ values: + # table[1] = [0] + # table[1] # => [0, nil, nil] + # + # If there are too many values, ignores the extra values: + # table[1] = [0, 1, 2, 3, 4] + # table[1] # => [0, 1, 2] + # + # If a single value is given, replaces all fields in the column with that value: + # table[1] = 'bat' + # table[1] # => ["bat", "bat", "bat"] + # + # --- + # + # Set a Column by Its \String Header:: + # - Form: table[header] = field_or_array_of_fields, + # +header+ a \String header, +field_or_array_of_fields+ a field value + # or an \Array of \String fields. + # - Access mode: :col or :col_or_row. + # - Return value: +field_or_array_of_fields+. + # + # If the column exists, it is replaced: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # new_col = [3, 4, 5] + # table.by_col! # => # + # return_value = table['Value'] = new_col + # return_value.equal?(new_col) # => true # Returned the column + # table['Value'] # => [3, 4, 5] + # # The rows, as revised: + # table.by_row! # => # + # table[0].to_h # => {"Name"=>"foo", "Value"=>3} + # table[1].to_h # => {"Name"=>"bar", "Value"=>4} + # table[2].to_h # => {"Name"=>"baz", "Value"=>5} + # table.by_col! # => # + # + # If there are too few values, fills with +nil+ values: + # table['Value'] = [0] + # table['Value'] # => [0, nil, nil] + # + # If there are too many values, ignores the extra values: + # table['Value'] = [0, 1, 2, 3, 4] + # table['Value'] # => [0, 1, 2] + # + # If the column does not exist, extends the table by adding columns: + # table['Note'] = ['x', 'y', 'z'] + # table['Note'] # => ["x", "y", "z"] + # # The rows, as revised: + # table.by_row! + # table[0].to_h # => {"Name"=>"foo", "Value"=>0, "Note"=>"x"} + # table[1].to_h # => {"Name"=>"bar", "Value"=>1, "Note"=>"y"} + # table[2].to_h # => {"Name"=>"baz", "Value"=>2, "Note"=>"z"} + # table.by_col! + # + # If a single value is given, replaces all fields in the column with that value: + # table['Value'] = 'bat' + # table['Value'] # => ["bat", "bat", "bat"] + def []=(index_or_header, value) + if @mode == :row or # by index + (@mode == :col_or_row and index_or_header.is_a? Integer) + if value.is_a? Array + @table[index_or_header] = Row.new(headers, value) + else + @table[index_or_header] = value + end + else # set column + unless index_or_header.is_a? Integer + index = @headers.index(index_or_header) || @headers.size + @headers[index] = index_or_header + end + if value.is_a? Array # multiple values + @table.each_with_index do |row, i| + if row.header_row? + row[index_or_header] = index_or_header + else + row[index_or_header] = value[i] + end + end + else # repeated value + @table.each do |row| + if row.header_row? + row[index_or_header] = index_or_header + else + row[index_or_header] = value + end + end + end + end + end + + # :call-seq: + # table.values_at(*indexes) -> array_of_rows + # table.values_at(*headers) -> array_of_columns_data + # + # If the access mode is :row or :col_or_row, + # and each argument is either an \Integer or a \Range, + # returns rows. + # Otherwise, returns columns data. + # + # In either case, the returned values are in the order + # specified by the arguments. Arguments may be repeated. + # + # --- + # + # Returns rows as an \Array of \CSV::Row objects. + # + # No argument: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.values_at # => [] + # + # One index: + # values = table.values_at(0) + # values # => [#] + # + # Two indexes: + # values = table.values_at(2, 0) + # values # => [#, #] + # + # One \Range: + # values = table.values_at(1..2) + # values # => [#, #] + # + # \Ranges and indexes: + # values = table.values_at(0..1, 1..2, 0, 2) + # pp values + # Output: + # [#, + # #, + # #, + # #, + # #, + # #] + # + # --- + # + # Returns columns data as row Arrays, + # each consisting of the specified columns data for that row: + # values = table.values_at('Name') + # values # => [["foo"], ["bar"], ["baz"]] + # values = table.values_at('Value', 'Name') + # values # => [["0", "foo"], ["1", "bar"], ["2", "baz"]] + def values_at(*indices_or_headers) + if @mode == :row or # by indices + ( @mode == :col_or_row and indices_or_headers.all? do |index| + index.is_a?(Integer) or + ( index.is_a?(Range) and + index.first.is_a?(Integer) and + index.last.is_a?(Integer) ) + end ) + @table.values_at(*indices_or_headers) + else # by headers + @table.map { |row| row.values_at(*indices_or_headers) } + end + end + + # :call-seq: + # table << row_or_array -> self + # + # If +row_or_array+ is a \CSV::Row object, + # it is appended to the table: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table << CSV::Row.new(table.headers, ['bat', 3]) + # table[3] # => # + # + # If +row_or_array+ is an \Array, it is used to create a new + # \CSV::Row object which is then appended to the table: + # table << ['bam', 4] + # table[4] # => # + def <<(row_or_array) + if row_or_array.is_a? Array # append Array + @table << Row.new(headers, row_or_array) + else # append Row + @table << row_or_array + end + + self # for chaining + end + + # + # :call-seq: + # table.push(*rows_or_arrays) -> self + # + # A shortcut for appending multiple rows. Equivalent to: + # rows.each {|row| self << row } + # + # Each argument may be either a \CSV::Row object or an \Array: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # rows = [ + # CSV::Row.new(table.headers, ['bat', 3]), + # ['bam', 4] + # ] + # table.push(*rows) + # table[3..4] # => [#, #] + def push(*rows) + rows.each { |row| self << row } + + self # for chaining + end + + # :call-seq: + # table.delete(*indexes) -> deleted_values + # table.delete(*headers) -> deleted_values + # + # If the access mode is :row or :col_or_row, + # and each argument is either an \Integer or a \Range, + # returns deleted rows. + # Otherwise, returns deleted columns data. + # + # In either case, the returned values are in the order + # specified by the arguments. Arguments may be repeated. + # + # --- + # + # Returns rows as an \Array of \CSV::Row objects. + # + # One index: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # deleted_values = table.delete(0) + # deleted_values # => [#] + # + # Two indexes: + # table = CSV.parse(source, headers: true) + # deleted_values = table.delete(2, 0) + # deleted_values # => [#, #] + # + # --- + # + # Returns columns data as column Arrays. + # + # One header: + # table = CSV.parse(source, headers: true) + # deleted_values = table.delete('Name') + # deleted_values # => ["foo", "bar", "baz"] + # + # Two headers: + # table = CSV.parse(source, headers: true) + # deleted_values = table.delete('Value', 'Name') + # deleted_values # => [["0", "1", "2"], ["foo", "bar", "baz"]] + def delete(*indexes_or_headers) + if indexes_or_headers.empty? + raise ArgumentError, "wrong number of arguments (given 0, expected 1+)" + end + deleted_values = indexes_or_headers.map do |index_or_header| + if @mode == :row or # by index + (@mode == :col_or_row and index_or_header.is_a? Integer) + @table.delete_at(index_or_header) + else # by header + if index_or_header.is_a? Integer + @headers.delete_at(index_or_header) + else + @headers.delete(index_or_header) + end + @table.map { |row| row.delete(index_or_header).last } + end + end + if indexes_or_headers.size == 1 + deleted_values[0] + else + deleted_values + end + end + + # :call-seq: + # table.delete_if {|row_or_column| ... } -> self + # + # Removes rows or columns for which the block returns a truthy value; + # returns +self+. + # + # Removes rows when the access mode is :row or :col_or_row; + # calls the block with each \CSV::Row object: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.by_row! # => # + # table.size # => 3 + # table.delete_if {|row| row['Name'].start_with?('b') } + # table.size # => 1 + # + # Removes columns when the access mode is :col; + # calls the block with each column as a 2-element array + # containing the header and an \Array of column fields: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.by_col! # => # + # table.headers.size # => 2 + # table.delete_if {|column_data| column_data[1].include?('2') } + # table.headers.size # => 1 + # + # Returns a new \Enumerator if no block is given: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.delete_if # => #:delete_if> + def delete_if(&block) + return enum_for(__method__) { @mode == :row or @mode == :col_or_row ? size : headers.size } unless block_given? + + if @mode == :row or @mode == :col_or_row # by index + @table.delete_if(&block) + else # by header + headers.each do |header| + delete(header) if yield([header, self[header]]) + end + end + + self # for chaining + end + + include Enumerable + + # :call-seq: + # table.each {|row_or_column| ... ) -> self + # + # Calls the block with each row or column; returns +self+. + # + # When the access mode is :row or :col_or_row, + # calls the block with each \CSV::Row object: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.by_row! # => # + # table.each {|row| p row } + # Output: + # # + # # + # # + # + # When the access mode is :col, + # calls the block with each column as a 2-element array + # containing the header and an \Array of column fields: + # table.by_col! # => # + # table.each {|column_data| p column_data } + # Output: + # ["Name", ["foo", "bar", "baz"]] + # ["Value", ["0", "1", "2"]] + # + # Returns a new \Enumerator if no block is given: + # table.each # => #:each> + def each(&block) + return enum_for(__method__) { @mode == :col ? headers.size : size } unless block_given? + + if @mode == :col + headers.each.with_index do |header, i| + yield([header, @table.map {|row| row[header, i]}]) + end + else + @table.each(&block) + end + + self # for chaining + end + + # :call-seq: + # table == other_table -> true or false + # + # Returns +true+ if all each row of +self+ == + # the corresponding row of +other_table+, otherwise, +false+. + # + # The access mode does no affect the result. + # + # Equal tables: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # other_table = CSV.parse(source, headers: true) + # table == other_table # => true + # + # Different row count: + # other_table.delete(2) + # table == other_table # => false + # + # Different last row: + # other_table << ['bat', 3] + # table == other_table # => false + def ==(other) + return @table == other.table if other.is_a? CSV::Table + @table == other + end + + # :call-seq: + # table.to_a -> array_of_arrays + # + # Returns the table as an \Array of \Arrays; + # the headers are in the first row: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.to_a # => [["Name", "Value"], ["foo", "0"], ["bar", "1"], ["baz", "2"]] + def to_a + array = [headers] + @table.each do |row| + array.push(row.fields) unless row.header_row? + end + + array + end + + # :call-seq: + # table.to_csv(**options) -> csv_string + # + # Returns the table as \CSV string. + # See {Options for Generating}[../CSV.html#class-CSV-label-Options+for+Generating]. + # + # Defaults option +write_headers+ to +true+: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.to_csv # => "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # + # Omits the headers if option +write_headers+ is given as +false+ + # (see {Option +write_headers+}[../CSV.html#class-CSV-label-Option+write_headers]): + # table.to_csv(write_headers: false) # => "foo,0\nbar,1\nbaz,2\n" + # + # Limit rows if option +limit+ is given like +2+: + # table.to_csv(limit: 2) # => "Name,Value\nfoo,0\nbar,1\n" + def to_csv(write_headers: true, limit: nil, **options) + array = write_headers ? [headers.to_csv(**options)] : [] + limit ||= @table.size + limit = @table.size + 1 + limit if limit < 0 + limit = 0 if limit < 0 + @table.first(limit).each do |row| + array.push(row.fields.to_csv(**options)) unless row.header_row? + end + + array.join("") + end + alias_method :to_s, :to_csv + + # + # Extracts the nested value specified by the sequence of +index+ or +header+ objects by calling dig at each step, + # returning nil if any intermediate step is nil. + # + def dig(index_or_header, *index_or_headers) + value = self[index_or_header] + if value.nil? + nil + elsif index_or_headers.empty? + value + else + unless value.respond_to?(:dig) + raise TypeError, "#{value.class} does not have \#dig method" + end + value.dig(*index_or_headers) + end + end + + # :call-seq: + # table.inspect => string + # + # Returns a US-ASCII-encoded \String showing table: + # - Class: CSV::Table. + # - Access mode: :row, :col, or :col_or_row. + # - Size: Row count, including the header row. + # + # Example: + # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" + # table = CSV.parse(source, headers: true) + # table.inspect # => "#\nName,Value\nfoo,0\nbar,1\nbaz,2\n" + # + def inspect + inspected = +"#<#{self.class} mode:#{@mode} row_count:#{to_a.size}>" + summary = to_csv(limit: 5) + inspected << "\n" << summary if summary.encoding.ascii_compatible? + inspected + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/version.rb b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/version.rb new file mode 100644 index 00000000..227f7f75 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/version.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class CSV + # The version of the installed library. + VERSION = "3.3.5" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/writer.rb b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/writer.rb new file mode 100644 index 00000000..b59f111e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/csv-3.3.5/lib/csv/writer.rb @@ -0,0 +1,209 @@ +# frozen_string_literal: true + +require_relative "input_record_separator" +require_relative "row" + +class CSV + # Note: Don't use this class directly. This is an internal class. + class Writer + # + # A CSV::Writer receives an output, prepares the header, format and output. + # It allows us to write new rows in the object and rewind it. + # + attr_reader :lineno + attr_reader :headers + + def initialize(output, options) + @output = output + @options = options + @lineno = 0 + @fields_converter = nil + prepare + if @options[:write_headers] and @headers + self << @headers + end + @fields_converter = @options[:fields_converter] + end + + # + # Adds a new row + # + def <<(row) + case row + when Row + row = row.fields + when Hash + row = @headers.collect {|header| row[header]} + end + + @headers ||= row if @use_headers + @lineno += 1 + + if @fields_converter + row = @fields_converter.convert(row, nil, lineno) + end + + i = -1 + converted_row = row.collect do |field| + i += 1 + quote(field, i) + end + line = converted_row.join(@column_separator) + @row_separator + if @output_encoding + line = line.encode(@output_encoding) + end + @output << line + + self + end + + # + # Winds back to the beginning + # + def rewind + @lineno = 0 + @headers = nil if @options[:headers].nil? + end + + private + def prepare + @encoding = @options[:encoding] + + prepare_header + prepare_format + prepare_output + end + + def prepare_header + headers = @options[:headers] + case headers + when Array + @headers = headers + @use_headers = true + when String + @headers = CSV.parse_line(headers, + col_sep: @options[:column_separator], + row_sep: @options[:row_separator], + quote_char: @options[:quote_character]) + @use_headers = true + when true + @headers = nil + @use_headers = true + else + @headers = nil + @use_headers = false + end + return unless @headers + + converter = @options[:header_fields_converter] + @headers = converter.convert(@headers, nil, 0, []) + @headers.each do |header| + header.freeze if header.is_a?(String) + end + end + + def prepare_force_quotes_fields(force_quotes) + @force_quotes_fields = {} + force_quotes.each do |name_or_index| + case name_or_index + when Integer + index = name_or_index + @force_quotes_fields[index] = true + when String, Symbol + name = name_or_index.to_s + if @headers.nil? + message = ":headers is required when you use field name " + + "in :force_quotes: " + + "#{name_or_index.inspect}: #{force_quotes.inspect}" + raise ArgumentError, message + end + index = @headers.index(name) + next if index.nil? + @force_quotes_fields[index] = true + else + message = ":force_quotes element must be " + + "field index or field name: " + + "#{name_or_index.inspect}: #{force_quotes.inspect}" + raise ArgumentError, message + end + end + end + + def prepare_format + @column_separator = @options[:column_separator].to_s.encode(@encoding) + row_separator = @options[:row_separator] + if row_separator == :auto + @row_separator = InputRecordSeparator.value.encode(@encoding) + else + @row_separator = row_separator.to_s.encode(@encoding) + end + @quote_character = @options[:quote_character] + force_quotes = @options[:force_quotes] + if force_quotes.is_a?(Array) + prepare_force_quotes_fields(force_quotes) + @force_quotes = false + elsif force_quotes + @force_quotes_fields = nil + @force_quotes = true + else + @force_quotes_fields = nil + @force_quotes = false + end + unless @force_quotes + @quotable_pattern = + Regexp.new("[\r\n".encode(@encoding) + + Regexp.escape(@column_separator) + + Regexp.escape(@quote_character.encode(@encoding)) + + "]".encode(@encoding)) + end + @quote_empty = @options.fetch(:quote_empty, true) + end + + def prepare_output + @output_encoding = nil + return unless @output.is_a?(StringIO) + + output_encoding = @output.internal_encoding || @output.external_encoding + if @encoding != output_encoding + if @options[:force_encoding] + @output_encoding = output_encoding + else + compatible_encoding = Encoding.compatible?(@encoding, output_encoding) + if compatible_encoding + @output.set_encoding(compatible_encoding) + @output.seek(0, IO::SEEK_END) + end + end + end + end + + def quote_field(field) + field = String(field) + encoded_quote_character = @quote_character.encode(field.encoding) + encoded_quote_character + + field.gsub(encoded_quote_character, + encoded_quote_character * 2) + + encoded_quote_character + end + + def quote(field, i) + if @force_quotes + quote_field(field) + elsif @force_quotes_fields and @force_quotes_fields[i] + quote_field(field) + else + if field.nil? # represent +nil+ fields as empty unquoted fields + "" + else + field = String(field) # Stringify fields + # represent empty fields as empty quoted fields + if (@quote_empty and field.empty?) or (field.valid_encoding? and @quotable_pattern.match?(field)) + quote_field(field) + else + field # unquoted field + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/README.md b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/README.md new file mode 100644 index 00000000..0a1c39f1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/README.md @@ -0,0 +1,102 @@ +# `Date` + +A subclass of `Object` that includes the `Comparable` module and easily handles date. + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'date' +``` + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install date + +## Usage + +```ruby +require 'date' +``` + +A `Date` object is created with `Date::new`, `Date::jd`, `Date::ordinal`, `Date::commercial`, `Date::parse`, `Date::strptime`, `Date::today`, `Time#to_date`, etc. + +```ruby +require 'date' + +Date.new(2001,2,3) + #=> # +Date.jd(2451944) + #=> # +Date.ordinal(2001,34) + #=> # +Date.commercial(2001,5,6) + #=> # +Date.parse('2001-02-03') + #=> # +Date.strptime('03-02-2001', '%d-%m-%Y') + #=> # +Time.new(2001,2,3).to_date + #=> # +``` + +All `Date` objects are immutable; hence cannot modify themselves. + +The concept of a date object can be represented as a tuple of the day count, the offset and the day of calendar reform. + +The day count denotes the absolute position of a temporal dimension. The offset is relative adjustment, which determines decoded local time with the day count. The day of calendar reform denotes the start day of the new style. The old style of the West is the Julian calendar which was adopted by Caesar. The new style is the Gregorian calendar, which is the current civil calendar of many countries. + +The day count is virtually the astronomical Julian day number. The offset in this class is usually zero, and cannot be specified directly. + +A `Date` object can be created with an optional argument, the day of calendar reform as a Julian day number, which should be 2298874 to 2426355 or negative/positive infinity. The default value is `Date::ITALY` (2299161=1582-10-15). See also sample/cal.rb. + +``` +$ ruby sample/cal.rb -c it 10 1582 +October 1582 +S M Tu W Th F S +1 2 3 4 15 16 +17 18 19 20 21 22 23 +24 25 26 27 28 29 30 +31 +``` + +``` +$ ruby sample/cal.rb -c gb 9 1752 +September 1752 +S M Tu W Th F S +1 2 14 15 16 +17 18 19 20 21 22 23 +24 25 26 27 28 29 30 +``` + +A `Date` object has various methods. See each reference. + +```ruby +d = Date.parse('3rd Feb 2001') + #=> # +d.year #=> 2001 +d.mon #=> 2 +d.mday #=> 3 +d.wday #=> 6 +d += 1 #=> # +d.strftime('%a %d %b %Y') #=> "Sun 04 Feb 2001" +``` + +## Development + +After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. + +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/date. + +## License + +The gem is available as open source under the terms of the [2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause). diff --git a/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/Makefile b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/Makefile new file mode 100644 index 00000000..62ec5b00 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/Makefile @@ -0,0 +1,269 @@ + +SHELL = /bin/sh + +# V=0 quiet, V=1 verbose. other values don't work. +V = 0 +V0 = $(V:0=) +Q1 = $(V:1=) +Q = $(Q1:0=@) +ECHO1 = $(V:1=@ :) +ECHO = $(ECHO1:0=@ echo) +NULLCMD = : + +#### Start of system configuration section. #### + +srcdir = . +topdir = /usr/include/ruby-3.2.0 +hdrdir = $(topdir) +arch_hdrdir = /usr/include/x86_64-linux-gnu/ruby-3.2.0 +PATH_SEPARATOR = : +VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby +prefix = $(DESTDIR)/usr +rubysitearchprefix = $(sitearchlibdir)/$(RUBY_BASE_NAME) +rubyarchprefix = $(archlibdir)/$(RUBY_BASE_NAME) +rubylibprefix = $(libdir)/$(RUBY_BASE_NAME) +exec_prefix = $(prefix) +vendorarchhdrdir = $(sitearchincludedir)/$(RUBY_VERSION_NAME)/vendor_ruby +sitearchhdrdir = $(sitearchincludedir)/$(RUBY_VERSION_NAME)/site_ruby +rubyarchhdrdir = $(archincludedir)/$(RUBY_VERSION_NAME) +vendorhdrdir = $(rubyhdrdir)/vendor_ruby +sitehdrdir = $(rubyhdrdir)/site_ruby +rubyhdrdir = $(includedir)/$(RUBY_VERSION_NAME) +vendorarchdir = $(rubysitearchprefix)/vendor_ruby/$(ruby_version) +vendorlibdir = $(vendordir)/$(ruby_version) +vendordir = $(rubylibprefix)/vendor_ruby +sitearchdir = $(DESTDIR)/usr/local/lib/x86_64-linux-gnu/site_ruby +sitelibdir = $(sitedir)/$(ruby_version) +sitedir = $(DESTDIR)/usr/local/lib/site_ruby +rubyarchdir = $(rubyarchprefix)/$(ruby_version) +rubylibdir = $(rubylibprefix)/$(ruby_version) +sitearchincludedir = $(includedir)/$(sitearch) +archincludedir = $(includedir)/$(arch) +sitearchlibdir = $(libdir)/$(sitearch) +archlibdir = $(libdir)/$(arch) +ridir = $(datarootdir)/$(RI_BASE_NAME) +mandir = $(datarootdir)/man +localedir = $(datarootdir)/locale +libdir = $(exec_prefix)/lib +psdir = $(docdir) +pdfdir = $(docdir) +dvidir = $(docdir) +htmldir = $(docdir) +infodir = $(datarootdir)/info +docdir = $(datarootdir)/doc/$(PACKAGE) +oldincludedir = $(DESTDIR)/usr/include +includedir = $(prefix)/include +runstatedir = $(DESTDIR)/var/run +localstatedir = $(DESTDIR)/var +sharedstatedir = $(prefix)/com +sysconfdir = $(DESTDIR)/etc +datadir = $(datarootdir) +datarootdir = $(prefix)/share +libexecdir = $(exec_prefix)/libexec +sbindir = $(exec_prefix)/sbin +bindir = $(exec_prefix)/bin +archdir = $(rubyarchdir) + + +CC_WRAPPER = +CC = x86_64-linux-gnu-gcc +CXX = x86_64-linux-gnu-g++ +LIBRUBY = $(LIBRUBY_SO) +LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a +LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME) +LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static $(MAINLIBS) +empty = +OUTFLAG = -o $(empty) +COUTFLAG = -o $(empty) +CSRCFLAG = $(empty) + +RUBY_EXTCONF_H = +cflags = $(optflags) $(debugflags) $(warnflags) +cxxflags = +optflags = -O3 -fno-fast-math +debugflags = -ggdb3 +warnflags = -Wall -Wextra -Wdeprecated-declarations -Wdiv-by-zero -Wduplicated-cond -Wimplicit-function-declaration -Wimplicit-int -Wmisleading-indentation -Wpointer-arith -Wwrite-strings -Wold-style-definition -Wimplicit-fallthrough=0 -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-packed-bitfield-compat -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wsuggest-attribute=format -Wsuggest-attribute=noreturn -Wunused-variable -Wundef +cppflags = +CCDLFLAGS = -fPIC +CFLAGS = $(CCDLFLAGS) -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC $(ARCH_FLAG) +INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir) +DEFS = +CPPFLAGS = -DHAVE_RB_CATEGORY_WARN -DHAVE_TIMEZONE -Wdate-time -D_FORTIFY_SOURCE=3 $(DEFS) $(cppflags) +CXXFLAGS = $(CCDLFLAGS) -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 $(ARCH_FLAG) +ldflags = -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed +dldflags = -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now +ARCH_FLAG = +DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG) +LDSHARED = $(CC) -shared +LDSHAREDXX = $(CXX) -shared +AR = x86_64-linux-gnu-gcc-ar +EXEEXT = + +RUBY_INSTALL_NAME = $(RUBY_BASE_NAME)3.2 +RUBY_SO_NAME = ruby-3.2 +RUBYW_INSTALL_NAME = +RUBY_VERSION_NAME = $(RUBY_BASE_NAME)-$(ruby_version) +RUBYW_BASE_NAME = rubyw +RUBY_BASE_NAME = ruby + +arch = x86_64-linux-gnu +sitearch = $(arch) +ruby_version = 3.2.0 +ruby = $(bindir)/$(RUBY_BASE_NAME)3.2 +RUBY = $(ruby) +BUILTRUBY = $(bindir)/$(RUBY_BASE_NAME)3.2 +ruby_headers = $(hdrdir)/ruby.h $(hdrdir)/ruby/backward.h $(hdrdir)/ruby/ruby.h $(hdrdir)/ruby/defines.h $(hdrdir)/ruby/missing.h $(hdrdir)/ruby/intern.h $(hdrdir)/ruby/st.h $(hdrdir)/ruby/subst.h $(arch_hdrdir)/ruby/config.h + +RM = rm -f +RM_RF = rm -fr +RMDIRS = rmdir --ignore-fail-on-non-empty -p +MAKEDIRS = /bin/mkdir -p +INSTALL = /usr/bin/install -c +INSTALL_PROG = $(INSTALL) -m 0755 +INSTALL_DATA = $(INSTALL) -m 644 +COPY = cp +TOUCH = exit > + +#### End of system configuration section. #### + +preload = +libpath = . $(archlibdir) +LIBPATH = -L. -L$(archlibdir) +DEFFILE = + +CLEANFILES = mkmf.log +DISTCLEANFILES = +DISTCLEANDIRS = + +extout = +extout_prefix = +target_prefix = +LOCAL_LIBS = +LIBS = $(LIBRUBYARG_SHARED) -lm -lpthread -lc +ORIG_SRCS = date_core.c date_parse.c date_strftime.c date_strptime.c +SRCS = $(ORIG_SRCS) +OBJS = date_core.o date_parse.o date_strftime.o date_strptime.o +HDRS = $(srcdir)/date_tmx.h $(srcdir)/zonetab.h +LOCAL_HDRS = +TARGET = date_core +TARGET_NAME = date_core +TARGET_ENTRY = Init_$(TARGET_NAME) +DLLIB = $(TARGET).so +EXTSTATIC = +STATIC_LIB = + +TIMESTAMP_DIR = . +BINDIR = $(bindir) +RUBYCOMMONDIR = $(sitedir)$(target_prefix) +RUBYLIBDIR = $(sitelibdir)$(target_prefix) +RUBYARCHDIR = $(sitearchdir)$(target_prefix) +HDRDIR = $(sitehdrdir)$(target_prefix) +ARCHHDRDIR = $(sitearchhdrdir)$(target_prefix) +TARGET_SO_DIR = +TARGET_SO = $(TARGET_SO_DIR)$(DLLIB) +CLEANLIBS = $(TARGET_SO) false +CLEANOBJS = $(OBJS) *.bak +TARGET_SO_DIR_TIMESTAMP = $(TIMESTAMP_DIR)/.sitearchdir.time + +all: $(DLLIB) +static: $(STATIC_LIB) +.PHONY: all install static install-so install-rb +.PHONY: clean clean-so clean-static clean-rb + +clean-static:: +clean-rb-default:: +clean-rb:: +clean-so:: +clean: clean-so clean-static clean-rb-default clean-rb + -$(Q)$(RM_RF) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) .*.time + +distclean-rb-default:: +distclean-rb:: +distclean-so:: +distclean-static:: +distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb + -$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log + -$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES) + -$(Q)$(RMDIRS) $(DISTCLEANDIRS) 2> /dev/null || true + +realclean: distclean +install: install-so install-rb + +install-so: $(DLLIB) $(TARGET_SO_DIR_TIMESTAMP) + $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR) +clean-static:: + -$(Q)$(RM) $(STATIC_LIB) +install-rb: pre-install-rb do-install-rb install-rb-default +install-rb-default: pre-install-rb-default do-install-rb-default +pre-install-rb: Makefile +pre-install-rb-default: Makefile +do-install-rb: +do-install-rb-default: +pre-install-rb-default: + @$(NULLCMD) +$(TARGET_SO_DIR_TIMESTAMP): + $(Q) $(MAKEDIRS) $(@D) $(RUBYARCHDIR) + $(Q) $(TOUCH) $@ + +site-install: site-install-so site-install-rb +site-install-so: install-so +site-install-rb: install-rb + +.SUFFIXES: .c .m .cc .mm .cxx .cpp .o .S + +.cc.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cc.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.mm.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.mm.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.cxx.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cxx.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.cpp.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cpp.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.c.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.c.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.m.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.m.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +$(TARGET_SO): $(OBJS) Makefile + $(ECHO) linking shared-object $(DLLIB) + -$(Q)$(RM) $(@) + $(Q) $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS) + + + +$(OBJS): $(HDRS) $(ruby_headers) diff --git a/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/date_core.c b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/date_core.c new file mode 100644 index 00000000..2e92539b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/date_core.c @@ -0,0 +1,10064 @@ +/* + date_core.c: Coded by Tadayoshi Funaba 2010-2014 +*/ + +#include "ruby.h" +#include "ruby/encoding.h" +#include "ruby/util.h" +#include +#include +#if defined(HAVE_SYS_TIME_H) +#include +#endif + +#undef NDEBUG +#define NDEBUG +#include + +#ifdef RUBY_EXTCONF_H +#include RUBY_EXTCONF_H +#endif + +#define USE_PACK + +static ID id_cmp, id_le_p, id_ge_p, id_eqeq_p; +static VALUE cDate, cDateTime; +static VALUE eDateError; +static VALUE half_days_in_day, day_in_nanoseconds; +static double positive_inf, negative_inf; + +// used by deconstruct_keys +static VALUE sym_year, sym_month, sym_day, sym_yday, sym_wday; +static VALUE sym_hour, sym_min, sym_sec, sym_sec_fraction, sym_zone; + +#define f_boolcast(x) ((x) ? Qtrue : Qfalse) + +#define f_abs(x) rb_funcall(x, rb_intern("abs"), 0) +#define f_negate(x) rb_funcall(x, rb_intern("-@"), 0) +#define f_add(x,y) rb_funcall(x, '+', 1, y) +#define f_sub(x,y) rb_funcall(x, '-', 1, y) +#define f_mul(x,y) rb_funcall(x, '*', 1, y) +#define f_div(x,y) rb_funcall(x, '/', 1, y) +#define f_quo(x,y) rb_funcall(x, rb_intern("quo"), 1, y) +#define f_idiv(x,y) rb_funcall(x, rb_intern("div"), 1, y) +#define f_mod(x,y) rb_funcall(x, '%', 1, y) +#define f_remainder(x,y) rb_funcall(x, rb_intern("remainder"), 1, y) +#define f_expt(x,y) rb_funcall(x, rb_intern("**"), 1, y) +#define f_floor(x) rb_funcall(x, rb_intern("floor"), 0) +#define f_ceil(x) rb_funcall(x, rb_intern("ceil"), 0) +#define f_truncate(x) rb_funcall(x, rb_intern("truncate"), 0) +#define f_round(x) rb_funcall(x, rb_intern("round"), 0) + +#define f_to_i(x) rb_funcall(x, rb_intern("to_i"), 0) +#define f_to_r(x) rb_funcall(x, rb_intern("to_r"), 0) +#define f_to_s(x) rb_funcall(x, rb_intern("to_s"), 0) +#define f_inspect(x) rb_funcall(x, rb_intern("inspect"), 0) + +#define f_add3(x,y,z) f_add(f_add(x, y), z) +#define f_sub3(x,y,z) f_sub(f_sub(x, y), z) + +#define f_frozen_ary(...) rb_ary_freeze(rb_ary_new3(__VA_ARGS__)) + +static VALUE date_initialize(int argc, VALUE *argv, VALUE self); +static VALUE datetime_initialize(int argc, VALUE *argv, VALUE self); + +#define RETURN_FALSE_UNLESS_NUMERIC(obj) if(!RTEST(rb_obj_is_kind_of((obj), rb_cNumeric))) return Qfalse +inline static void +check_numeric(VALUE obj, const char* field) +{ + if(!RTEST(rb_obj_is_kind_of(obj, rb_cNumeric))) { + rb_raise(rb_eTypeError, "invalid %s (not numeric)", field); + } +} + +inline static int +f_cmp(VALUE x, VALUE y) +{ + if (FIXNUM_P(x) && FIXNUM_P(y)) { + long c = FIX2LONG(x) - FIX2LONG(y); + if (c > 0) + return 1; + else if (c < 0) + return -1; + return 0; + } + return rb_cmpint(rb_funcallv(x, id_cmp, 1, &y), x, y); +} + +inline static VALUE +f_lt_p(VALUE x, VALUE y) +{ + if (FIXNUM_P(x) && FIXNUM_P(y)) + return f_boolcast(FIX2LONG(x) < FIX2LONG(y)); + return rb_funcall(x, '<', 1, y); +} + +inline static VALUE +f_gt_p(VALUE x, VALUE y) +{ + if (FIXNUM_P(x) && FIXNUM_P(y)) + return f_boolcast(FIX2LONG(x) > FIX2LONG(y)); + return rb_funcall(x, '>', 1, y); +} + +inline static VALUE +f_le_p(VALUE x, VALUE y) +{ + if (FIXNUM_P(x) && FIXNUM_P(y)) + return f_boolcast(FIX2LONG(x) <= FIX2LONG(y)); + return rb_funcall(x, id_le_p, 1, y); +} + +inline static VALUE +f_ge_p(VALUE x, VALUE y) +{ + if (FIXNUM_P(x) && FIXNUM_P(y)) + return f_boolcast(FIX2LONG(x) >= FIX2LONG(y)); + return rb_funcall(x, id_ge_p, 1, y); +} + +inline static VALUE +f_eqeq_p(VALUE x, VALUE y) +{ + if (FIXNUM_P(x) && FIXNUM_P(y)) + return f_boolcast(FIX2LONG(x) == FIX2LONG(y)); + return rb_funcall(x, id_eqeq_p, 1, y); +} + +inline static VALUE +f_zero_p(VALUE x) +{ + switch (TYPE(x)) { + case T_FIXNUM: + return f_boolcast(FIX2LONG(x) == 0); + case T_BIGNUM: + return Qfalse; + case T_RATIONAL: + { + VALUE num = rb_rational_num(x); + return f_boolcast(FIXNUM_P(num) && FIX2LONG(num) == 0); + } + } + return rb_funcall(x, id_eqeq_p, 1, INT2FIX(0)); +} + +#define f_nonzero_p(x) (!f_zero_p(x)) + +inline static VALUE +f_negative_p(VALUE x) +{ + if (FIXNUM_P(x)) + return f_boolcast(FIX2LONG(x) < 0); + return rb_funcall(x, '<', 1, INT2FIX(0)); +} + +#define f_positive_p(x) (!f_negative_p(x)) + +#define f_ajd(x) rb_funcall(x, rb_intern("ajd"), 0) +#define f_jd(x) rb_funcall(x, rb_intern("jd"), 0) +#define f_year(x) rb_funcall(x, rb_intern("year"), 0) +#define f_mon(x) rb_funcall(x, rb_intern("mon"), 0) +#define f_mday(x) rb_funcall(x, rb_intern("mday"), 0) +#define f_wday(x) rb_funcall(x, rb_intern("wday"), 0) +#define f_hour(x) rb_funcall(x, rb_intern("hour"), 0) +#define f_min(x) rb_funcall(x, rb_intern("min"), 0) +#define f_sec(x) rb_funcall(x, rb_intern("sec"), 0) + +/* copied from time.c */ +#define NDIV(x,y) (-(-((x)+1)/(y))-1) +#define NMOD(x,y) ((y)-(-((x)+1)%(y))-1) +#define DIV(n,d) ((n)<0 ? NDIV((n),(d)) : (n)/(d)) +#define MOD(n,d) ((n)<0 ? NMOD((n),(d)) : (n)%(d)) + +#define HAVE_JD (1 << 0) +#define HAVE_DF (1 << 1) +#define HAVE_CIVIL (1 << 2) +#define HAVE_TIME (1 << 3) +#define COMPLEX_DAT (1 << 7) + +#define have_jd_p(x) ((x)->flags & HAVE_JD) +#define have_df_p(x) ((x)->flags & HAVE_DF) +#define have_civil_p(x) ((x)->flags & HAVE_CIVIL) +#define have_time_p(x) ((x)->flags & HAVE_TIME) +#define complex_dat_p(x) ((x)->flags & COMPLEX_DAT) +#define simple_dat_p(x) (!complex_dat_p(x)) + +#define ITALY 2299161 /* 1582-10-15 */ +#define ENGLAND 2361222 /* 1752-09-14 */ +#define JULIAN positive_inf +#define GREGORIAN negative_inf +#define DEFAULT_SG ITALY + +#define UNIX_EPOCH_IN_CJD INT2FIX(2440588) /* 1970-01-01 */ + +#define MINUTE_IN_SECONDS 60 +#define HOUR_IN_SECONDS 3600 +#define DAY_IN_SECONDS 86400 +#define SECOND_IN_MILLISECONDS 1000 +#define SECOND_IN_NANOSECONDS 1000000000 + +#define JC_PERIOD0 1461 /* 365.25 * 4 */ +#define GC_PERIOD0 146097 /* 365.2425 * 400 */ +#define CM_PERIOD0 71149239 /* (lcm 7 1461 146097) */ +#define CM_PERIOD (0xfffffff / CM_PERIOD0 * CM_PERIOD0) +#define CM_PERIOD_JCY (CM_PERIOD / JC_PERIOD0 * 4) +#define CM_PERIOD_GCY (CM_PERIOD / GC_PERIOD0 * 400) + +#define REFORM_BEGIN_YEAR 1582 +#define REFORM_END_YEAR 1930 +#define REFORM_BEGIN_JD 2298874 /* ns 1582-01-01 */ +#define REFORM_END_JD 2426355 /* os 1930-12-31 */ + +#ifdef USE_PACK +#define SEC_WIDTH 6 +#define MIN_WIDTH 6 +#define HOUR_WIDTH 5 +#define MDAY_WIDTH 5 +#define MON_WIDTH 4 + +#define SEC_SHIFT 0 +#define MIN_SHIFT SEC_WIDTH +#define HOUR_SHIFT (MIN_WIDTH + SEC_WIDTH) +#define MDAY_SHIFT (HOUR_WIDTH + MIN_WIDTH + SEC_WIDTH) +#define MON_SHIFT (MDAY_WIDTH + HOUR_WIDTH + MIN_WIDTH + SEC_WIDTH) + +#define PK_MASK(x) ((1 << (x)) - 1) + +#define EX_SEC(x) (((x) >> SEC_SHIFT) & PK_MASK(SEC_WIDTH)) +#define EX_MIN(x) (((x) >> MIN_SHIFT) & PK_MASK(MIN_WIDTH)) +#define EX_HOUR(x) (((x) >> HOUR_SHIFT) & PK_MASK(HOUR_WIDTH)) +#define EX_MDAY(x) (((x) >> MDAY_SHIFT) & PK_MASK(MDAY_WIDTH)) +#define EX_MON(x) (((x) >> MON_SHIFT) & PK_MASK(MON_WIDTH)) + +#define PACK5(m,d,h,min,s) \ + (((m) << MON_SHIFT) | ((d) << MDAY_SHIFT) |\ + ((h) << HOUR_SHIFT) | ((min) << MIN_SHIFT) | ((s) << SEC_SHIFT)) + +#define PACK2(m,d) \ + (((m) << MON_SHIFT) | ((d) << MDAY_SHIFT)) +#endif + +#ifdef HAVE_FLOAT_H +#include +#endif + +#if defined(FLT_RADIX) && defined(FLT_MANT_DIG) && FLT_RADIX == 2 && FLT_MANT_DIG > 22 +#define date_sg_t float +#else +#define date_sg_t double +#endif + +#define JULIAN_EPOCH_DATE "-4712-01-01" +#define JULIAN_EPOCH_DATETIME JULIAN_EPOCH_DATE "T00:00:00+00:00" +#define JULIAN_EPOCH_DATETIME_RFC3339 "Mon, 1 Jan -4712 00:00:00 +0000" +#define JULIAN_EPOCH_DATETIME_HTTPDATE "Mon, 01 Jan -4712 00:00:00 GMT" + +/* A set of nth, jd, df and sf denote ajd + 1/2. Each ajd begin at + * noon of GMT (assume equal to UTC). However, this begins at + * midnight. + */ + +struct SimpleDateData +{ + unsigned flags; + int jd; /* as utc */ + VALUE nth; /* not always canonicalized */ + date_sg_t sg; /* 2298874..2426355 or -/+oo -- at most 22 bits */ + /* decoded as utc=local */ + int year; /* truncated */ +#ifndef USE_PACK + int mon; + int mday; + /* hour is zero */ + /* min is zero */ + /* sec is zero */ +#else + /* packed civil */ + unsigned pc; +#endif +}; + +struct ComplexDateData +{ + unsigned flags; + int jd; /* as utc */ + VALUE nth; /* not always canonicalized */ + date_sg_t sg; /* 2298874..2426355 or -/+oo -- at most 22 bits */ + /* decoded as local */ + int year; /* truncated */ +#ifndef USE_PACK + int mon; + int mday; + int hour; + int min; + int sec; +#else + /* packed civil */ + unsigned pc; +#endif + int df; /* as utc, in secs */ + int of; /* in secs */ + VALUE sf; /* in nano secs */ +}; + +union DateData { + unsigned flags; + struct SimpleDateData s; + struct ComplexDateData c; +}; + +#define get_d1(x)\ + union DateData *dat;\ + TypedData_Get_Struct(x, union DateData, &d_lite_type, dat); + +#define get_d1a(x)\ + union DateData *adat;\ + TypedData_Get_Struct(x, union DateData, &d_lite_type, adat); + +#define get_d1b(x)\ + union DateData *bdat;\ + TypedData_Get_Struct(x, union DateData, &d_lite_type, bdat); + +#define get_d2(x,y)\ + union DateData *adat, *bdat;\ + TypedData_Get_Struct(x, union DateData, &d_lite_type, adat);\ + TypedData_Get_Struct(y, union DateData, &d_lite_type, bdat); + +inline static VALUE +canon(VALUE x) +{ + if (RB_TYPE_P(x, T_RATIONAL)) { + VALUE den = rb_rational_den(x); + if (FIXNUM_P(den) && FIX2LONG(den) == 1) + return rb_rational_num(x); + } + return x; +} + +#ifndef USE_PACK +#define set_to_simple(obj, x, _nth, _jd ,_sg, _year, _mon, _mday, _flags) \ +do {\ + RB_OBJ_WRITE((obj), &(x)->nth, canon(_nth)); \ + (x)->jd = _jd;\ + (x)->sg = (date_sg_t)(_sg);\ + (x)->year = _year;\ + (x)->mon = _mon;\ + (x)->mday = _mday;\ + (x)->flags = (_flags) & ~COMPLEX_DAT;\ +} while (0) +#else +#define set_to_simple(obj, x, _nth, _jd ,_sg, _year, _mon, _mday, _flags) \ +do {\ + RB_OBJ_WRITE((obj), &(x)->nth, canon(_nth)); \ + (x)->jd = _jd;\ + (x)->sg = (date_sg_t)(_sg);\ + (x)->year = _year;\ + (x)->pc = PACK2(_mon, _mday);\ + (x)->flags = (_flags) & ~COMPLEX_DAT;\ +} while (0) +#endif + +#ifndef USE_PACK +#define set_to_complex(obj, x, _nth, _jd ,_df, _sf, _of, _sg,\ +_year, _mon, _mday, _hour, _min, _sec, _flags) \ +do {\ + RB_OBJ_WRITE((obj), &(x)->nth, canon(_nth));\ + (x)->jd = _jd;\ + (x)->df = _df;\ + RB_OBJ_WRITE((obj), &(x)->sf, canon(_sf));\ + (x)->of = _of;\ + (x)->sg = (date_sg_t)(_sg);\ + (x)->year = _year;\ + (x)->mon = _mon;\ + (x)->mday = _mday;\ + (x)->hour = _hour;\ + (x)->min = _min;\ + (x)->sec = _sec;\ + (x)->flags = (_flags) | COMPLEX_DAT;\ +} while (0) +#else +#define set_to_complex(obj, x, _nth, _jd ,_df, _sf, _of, _sg,\ +_year, _mon, _mday, _hour, _min, _sec, _flags) \ +do {\ + RB_OBJ_WRITE((obj), &(x)->nth, canon(_nth));\ + (x)->jd = _jd;\ + (x)->df = _df;\ + RB_OBJ_WRITE((obj), &(x)->sf, canon(_sf));\ + (x)->of = _of;\ + (x)->sg = (date_sg_t)(_sg);\ + (x)->year = _year;\ + (x)->pc = PACK5(_mon, _mday, _hour, _min, _sec);\ + (x)->flags = (_flags) | COMPLEX_DAT;\ +} while (0) +#endif + +#ifndef USE_PACK +#define copy_simple_to_complex(obj, x, y) \ +do {\ + RB_OBJ_WRITE((obj), &(x)->nth, (y)->nth);\ + (x)->jd = (y)->jd;\ + (x)->df = 0;\ + (x)->sf = INT2FIX(0);\ + (x)->of = 0;\ + (x)->sg = (date_sg_t)((y)->sg);\ + (x)->year = (y)->year;\ + (x)->mon = (y)->mon;\ + (x)->mday = (y)->mday;\ + (x)->hour = 0;\ + (x)->min = 0;\ + (x)->sec = 0;\ + (x)->flags = (y)->flags;\ +} while (0) +#else +#define copy_simple_to_complex(obj, x, y) \ +do {\ + RB_OBJ_WRITE((obj), &(x)->nth, (y)->nth);\ + (x)->jd = (y)->jd;\ + (x)->df = 0;\ + RB_OBJ_WRITE((obj), &(x)->sf, INT2FIX(0));\ + (x)->of = 0;\ + (x)->sg = (date_sg_t)((y)->sg);\ + (x)->year = (y)->year;\ + (x)->pc = PACK5(EX_MON((y)->pc), EX_MDAY((y)->pc), 0, 0, 0);\ + (x)->flags = (y)->flags;\ +} while (0) +#endif + +#ifndef USE_PACK +#define copy_complex_to_simple(obj, x, y) \ +do {\ + RB_OBJ_WRITE((obj), &(x)->nth, (y)->nth);\ + (x)->jd = (y)->jd;\ + (x)->sg = (date_sg_t)((y)->sg);\ + (x)->year = (y)->year;\ + (x)->mon = (y)->mon;\ + (x)->mday = (y)->mday;\ + (x)->flags = (y)->flags;\ +} while (0) +#else +#define copy_complex_to_simple(obj, x, y) \ +do {\ + RB_OBJ_WRITE((obj), &(x)->nth, (y)->nth);\ + (x)->jd = (y)->jd;\ + (x)->sg = (date_sg_t)((y)->sg);\ + (x)->year = (y)->year;\ + (x)->pc = PACK2(EX_MON((y)->pc), EX_MDAY((y)->pc));\ + (x)->flags = (y)->flags;\ +} while (0) +#endif + +/* base */ + +static int c_valid_civil_p(int, int, int, double, + int *, int *, int *, int *); + +static int +c_find_fdoy(int y, double sg, int *rjd, int *ns) +{ + int d, rm, rd; + + for (d = 1; d < 31; d++) + if (c_valid_civil_p(y, 1, d, sg, &rm, &rd, rjd, ns)) + return 1; + return 0; +} + +static int +c_find_ldoy(int y, double sg, int *rjd, int *ns) +{ + int i, rm, rd; + + for (i = 0; i < 30; i++) + if (c_valid_civil_p(y, 12, 31 - i, sg, &rm, &rd, rjd, ns)) + return 1; + return 0; +} + +#ifndef NDEBUG +/* :nodoc: */ +static int +c_find_fdom(int y, int m, double sg, int *rjd, int *ns) +{ + int d, rm, rd; + + for (d = 1; d < 31; d++) + if (c_valid_civil_p(y, m, d, sg, &rm, &rd, rjd, ns)) + return 1; + return 0; +} +#endif + +static int +c_find_ldom(int y, int m, double sg, int *rjd, int *ns) +{ + int i, rm, rd; + + for (i = 0; i < 30; i++) + if (c_valid_civil_p(y, m, 31 - i, sg, &rm, &rd, rjd, ns)) + return 1; + return 0; +} + +static void +c_civil_to_jd(int y, int m, int d, double sg, int *rjd, int *ns) +{ + double a, b, jd; + + if (m <= 2) { + y -= 1; + m += 12; + } + a = floor(y / 100.0); + b = 2 - a + floor(a / 4.0); + jd = floor(365.25 * (y + 4716)) + + floor(30.6001 * (m + 1)) + + d + b - 1524; + if (jd < sg) { + jd -= b; + *ns = 0; + } + else + *ns = 1; + + *rjd = (int)jd; +} + +static void +c_jd_to_civil(int jd, double sg, int *ry, int *rm, int *rdom) +{ + double x, a, b, c, d, e, y, m, dom; + + if (jd < sg) + a = jd; + else { + x = floor((jd - 1867216.25) / 36524.25); + a = jd + 1 + x - floor(x / 4.0); + } + b = a + 1524; + c = floor((b - 122.1) / 365.25); + d = floor(365.25 * c); + e = floor((b - d) / 30.6001); + dom = b - d - floor(30.6001 * e); + if (e <= 13) { + m = e - 1; + y = c - 4716; + } + else { + m = e - 13; + y = c - 4715; + } + + *ry = (int)y; + *rm = (int)m; + *rdom = (int)dom; +} + +static void +c_ordinal_to_jd(int y, int d, double sg, int *rjd, int *ns) +{ + int ns2; + + c_find_fdoy(y, sg, rjd, &ns2); + *rjd += d - 1; + *ns = (*rjd < sg) ? 0 : 1; +} + +static void +c_jd_to_ordinal(int jd, double sg, int *ry, int *rd) +{ + int rm2, rd2, rjd, ns; + + c_jd_to_civil(jd, sg, ry, &rm2, &rd2); + c_find_fdoy(*ry, sg, &rjd, &ns); + *rd = (jd - rjd) + 1; +} + +static void +c_commercial_to_jd(int y, int w, int d, double sg, int *rjd, int *ns) +{ + int rjd2, ns2; + + c_find_fdoy(y, sg, &rjd2, &ns2); + rjd2 += 3; + *rjd = + (rjd2 - MOD((rjd2 - 1) + 1, 7)) + + 7 * (w - 1) + + (d - 1); + *ns = (*rjd < sg) ? 0 : 1; +} + +static void +c_jd_to_commercial(int jd, double sg, int *ry, int *rw, int *rd) +{ + int ry2, rm2, rd2, a, rjd2, ns2; + + c_jd_to_civil(jd - 3, sg, &ry2, &rm2, &rd2); + a = ry2; + c_commercial_to_jd(a + 1, 1, 1, sg, &rjd2, &ns2); + if (jd >= rjd2) + *ry = a + 1; + else { + c_commercial_to_jd(a, 1, 1, sg, &rjd2, &ns2); + *ry = a; + } + *rw = 1 + DIV(jd - rjd2, 7); + *rd = MOD(jd + 1, 7); + if (*rd == 0) + *rd = 7; +} + +static void +c_weeknum_to_jd(int y, int w, int d, int f, double sg, int *rjd, int *ns) +{ + int rjd2, ns2; + + c_find_fdoy(y, sg, &rjd2, &ns2); + rjd2 += 6; + *rjd = (rjd2 - MOD(((rjd2 - f) + 1), 7) - 7) + 7 * w + d; + *ns = (*rjd < sg) ? 0 : 1; +} + +static void +c_jd_to_weeknum(int jd, int f, double sg, int *ry, int *rw, int *rd) +{ + int rm, rd2, rjd, ns, j; + + c_jd_to_civil(jd, sg, ry, &rm, &rd2); + c_find_fdoy(*ry, sg, &rjd, &ns); + rjd += 6; + j = jd - (rjd - MOD((rjd - f) + 1, 7)) + 7; + *rw = (int)DIV(j, 7); + *rd = (int)MOD(j, 7); +} + +#ifndef NDEBUG +/* :nodoc: */ +static void +c_nth_kday_to_jd(int y, int m, int n, int k, double sg, int *rjd, int *ns) +{ + int rjd2, ns2; + + if (n > 0) { + c_find_fdom(y, m, sg, &rjd2, &ns2); + rjd2 -= 1; + } + else { + c_find_ldom(y, m, sg, &rjd2, &ns2); + rjd2 += 7; + } + *rjd = (rjd2 - MOD((rjd2 - k) + 1, 7)) + 7 * n; + *ns = (*rjd < sg) ? 0 : 1; +} +#endif + +inline static int +c_jd_to_wday(int jd) +{ + return MOD(jd + 1, 7); +} + +#ifndef NDEBUG +/* :nodoc: */ +static void +c_jd_to_nth_kday(int jd, double sg, int *ry, int *rm, int *rn, int *rk) +{ + int rd, rjd, ns2; + + c_jd_to_civil(jd, sg, ry, rm, &rd); + c_find_fdom(*ry, *rm, sg, &rjd, &ns2); + *rn = DIV(jd - rjd, 7) + 1; + *rk = c_jd_to_wday(jd); +} +#endif + +static int +c_valid_ordinal_p(int y, int d, double sg, + int *rd, int *rjd, int *ns) +{ + int ry2, rd2; + + if (d < 0) { + int rjd2, ns2; + + if (!c_find_ldoy(y, sg, &rjd2, &ns2)) + return 0; + c_jd_to_ordinal(rjd2 + d + 1, sg, &ry2, &rd2); + if (ry2 != y) + return 0; + d = rd2; + } + c_ordinal_to_jd(y, d, sg, rjd, ns); + c_jd_to_ordinal(*rjd, sg, &ry2, &rd2); + if (ry2 != y || rd2 != d) + return 0; + return 1; +} + +static const int monthtab[2][13] = { + { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } +}; + +inline static int +c_julian_leap_p(int y) +{ + return MOD(y, 4) == 0; +} + +inline static int +c_gregorian_leap_p(int y) +{ + return (MOD(y, 4) == 0 && y % 100 != 0) || MOD(y, 400) == 0; +} + +static int +c_julian_last_day_of_month(int y, int m) +{ + assert(m >= 1 && m <= 12); + return monthtab[c_julian_leap_p(y) ? 1 : 0][m]; +} + +static int +c_gregorian_last_day_of_month(int y, int m) +{ + assert(m >= 1 && m <= 12); + return monthtab[c_gregorian_leap_p(y) ? 1 : 0][m]; +} + +static int +c_valid_julian_p(int y, int m, int d, int *rm, int *rd) +{ + int last; + + if (m < 0) + m += 13; + if (m < 1 || m > 12) + return 0; + last = c_julian_last_day_of_month(y, m); + if (d < 0) + d = last + d + 1; + if (d < 1 || d > last) + return 0; + *rm = m; + *rd = d; + return 1; +} + +static int +c_valid_gregorian_p(int y, int m, int d, int *rm, int *rd) +{ + int last; + + if (m < 0) + m += 13; + if (m < 1 || m > 12) + return 0; + last = c_gregorian_last_day_of_month(y, m); + if (d < 0) + d = last + d + 1; + if (d < 1 || d > last) + return 0; + *rm = m; + *rd = d; + return 1; +} + +static int +c_valid_civil_p(int y, int m, int d, double sg, + int *rm, int *rd, int *rjd, int *ns) +{ + int ry; + + if (m < 0) + m += 13; + if (m < 1 || m > 12) + return 0; + if (d < 0) { + if (!c_find_ldom(y, m, sg, rjd, ns)) + return 0; + c_jd_to_civil(*rjd + d + 1, sg, &ry, rm, rd); + if (ry != y || *rm != m) + return 0; + d = *rd; + } + c_civil_to_jd(y, m, d, sg, rjd, ns); + c_jd_to_civil(*rjd, sg, &ry, rm, rd); + if (ry != y || *rm != m || *rd != d) + return 0; + return 1; +} + +static int +c_valid_commercial_p(int y, int w, int d, double sg, + int *rw, int *rd, int *rjd, int *ns) +{ + int ns2, ry2, rw2, rd2; + + if (d < 0) + d += 8; + if (w < 0) { + int rjd2; + + c_commercial_to_jd(y + 1, 1, 1, sg, &rjd2, &ns2); + c_jd_to_commercial(rjd2 + w * 7, sg, &ry2, &rw2, &rd2); + if (ry2 != y) + return 0; + w = rw2; + } + c_commercial_to_jd(y, w, d, sg, rjd, ns); + c_jd_to_commercial(*rjd, sg, &ry2, rw, rd); + if (y != ry2 || w != *rw || d != *rd) + return 0; + return 1; +} + +static int +c_valid_weeknum_p(int y, int w, int d, int f, double sg, + int *rw, int *rd, int *rjd, int *ns) +{ + int ns2, ry2, rw2, rd2; + + if (d < 0) + d += 7; + if (w < 0) { + int rjd2; + + c_weeknum_to_jd(y + 1, 1, f, f, sg, &rjd2, &ns2); + c_jd_to_weeknum(rjd2 + w * 7, f, sg, &ry2, &rw2, &rd2); + if (ry2 != y) + return 0; + w = rw2; + } + c_weeknum_to_jd(y, w, d, f, sg, rjd, ns); + c_jd_to_weeknum(*rjd, f, sg, &ry2, rw, rd); + if (y != ry2 || w != *rw || d != *rd) + return 0; + return 1; +} + +#ifndef NDEBUG +/* :nodoc: */ +static int +c_valid_nth_kday_p(int y, int m, int n, int k, double sg, + int *rm, int *rn, int *rk, int *rjd, int *ns) +{ + int ns2, ry2, rm2, rn2, rk2; + + if (k < 0) + k += 7; + if (n < 0) { + int t, ny, nm, rjd2; + + t = y * 12 + m; + ny = DIV(t, 12); + nm = MOD(t, 12) + 1; + + c_nth_kday_to_jd(ny, nm, 1, k, sg, &rjd2, &ns2); + c_jd_to_nth_kday(rjd2 + n * 7, sg, &ry2, &rm2, &rn2, &rk2); + if (ry2 != y || rm2 != m) + return 0; + n = rn2; + } + c_nth_kday_to_jd(y, m, n, k, sg, rjd, ns); + c_jd_to_nth_kday(*rjd, sg, &ry2, rm, rn, rk); + if (y != ry2 || m != *rm || n != *rn || k != *rk) + return 0; + return 1; +} +#endif + +static int +c_valid_time_p(int h, int min, int s, int *rh, int *rmin, int *rs) +{ + if (h < 0) + h += 24; + if (min < 0) + min += 60; + if (s < 0) + s += 60; + *rh = h; + *rmin = min; + *rs = s; + return !(h < 0 || h > 24 || + min < 0 || min > 59 || + s < 0 || s > 59 || + (h == 24 && (min > 0 || s > 0))); +} + +inline static int +c_valid_start_p(double sg) +{ + if (isnan(sg)) + return 0; + if (isinf(sg)) + return 1; + if (sg < REFORM_BEGIN_JD || sg > REFORM_END_JD) + return 0; + return 1; +} + +inline static int +df_local_to_utc(int df, int of) +{ + df -= of; + if (df < 0) + df += DAY_IN_SECONDS; + else if (df >= DAY_IN_SECONDS) + df -= DAY_IN_SECONDS; + return df; +} + +inline static int +df_utc_to_local(int df, int of) +{ + df += of; + if (df < 0) + df += DAY_IN_SECONDS; + else if (df >= DAY_IN_SECONDS) + df -= DAY_IN_SECONDS; + return df; +} + +inline static int +jd_local_to_utc(int jd, int df, int of) +{ + df -= of; + if (df < 0) + jd -= 1; + else if (df >= DAY_IN_SECONDS) + jd += 1; + return jd; +} + +inline static int +jd_utc_to_local(int jd, int df, int of) +{ + df += of; + if (df < 0) + jd -= 1; + else if (df >= DAY_IN_SECONDS) + jd += 1; + return jd; +} + +inline static int +time_to_df(int h, int min, int s) +{ + return h * HOUR_IN_SECONDS + min * MINUTE_IN_SECONDS + s; +} + +inline static void +df_to_time(int df, int *h, int *min, int *s) +{ + *h = df / HOUR_IN_SECONDS; + df %= HOUR_IN_SECONDS; + *min = df / MINUTE_IN_SECONDS; + *s = df % MINUTE_IN_SECONDS; +} + +static VALUE +sec_to_day(VALUE s) +{ + if (FIXNUM_P(s)) + return rb_rational_new2(s, INT2FIX(DAY_IN_SECONDS)); + return f_quo(s, INT2FIX(DAY_IN_SECONDS)); +} + +inline static VALUE +isec_to_day(int s) +{ + return sec_to_day(INT2FIX(s)); +} + +static VALUE +ns_to_day(VALUE n) +{ + if (FIXNUM_P(n)) + return rb_rational_new2(n, day_in_nanoseconds); + return f_quo(n, day_in_nanoseconds); +} + +#ifndef NDEBUG +/* :nodoc: */ +static VALUE +ms_to_sec(VALUE m) +{ + if (FIXNUM_P(m)) + return rb_rational_new2(m, INT2FIX(SECOND_IN_MILLISECONDS)); + return f_quo(m, INT2FIX(SECOND_IN_MILLISECONDS)); +} +#endif + +static VALUE +ns_to_sec(VALUE n) +{ + if (FIXNUM_P(n)) + return rb_rational_new2(n, INT2FIX(SECOND_IN_NANOSECONDS)); + return f_quo(n, INT2FIX(SECOND_IN_NANOSECONDS)); +} + +#ifndef NDEBUG +/* :nodoc: */ +inline static VALUE +ins_to_day(int n) +{ + return ns_to_day(INT2FIX(n)); +} +#endif + +static int +safe_mul_p(VALUE x, long m) +{ + long ix; + + if (!FIXNUM_P(x)) + return 0; + ix = FIX2LONG(x); + if (ix < 0) { + if (ix <= (FIXNUM_MIN / m)) + return 0; + } + else { + if (ix >= (FIXNUM_MAX / m)) + return 0; + } + return 1; +} + +static VALUE +day_to_sec(VALUE d) +{ + if (safe_mul_p(d, DAY_IN_SECONDS)) + return LONG2FIX(FIX2LONG(d) * DAY_IN_SECONDS); + return f_mul(d, INT2FIX(DAY_IN_SECONDS)); +} + +#ifndef NDEBUG +/* :nodoc: */ +static VALUE +day_to_ns(VALUE d) +{ + return f_mul(d, day_in_nanoseconds); +} +#endif + +static VALUE +sec_to_ms(VALUE s) +{ + if (safe_mul_p(s, SECOND_IN_MILLISECONDS)) + return LONG2FIX(FIX2LONG(s) * SECOND_IN_MILLISECONDS); + return f_mul(s, INT2FIX(SECOND_IN_MILLISECONDS)); +} + +static VALUE +sec_to_ns(VALUE s) +{ + if (safe_mul_p(s, SECOND_IN_NANOSECONDS)) + return LONG2FIX(FIX2LONG(s) * SECOND_IN_NANOSECONDS); + return f_mul(s, INT2FIX(SECOND_IN_NANOSECONDS)); +} + +#ifndef NDEBUG +/* :nodoc: */ +static VALUE +isec_to_ns(int s) +{ + return sec_to_ns(INT2FIX(s)); +} +#endif + +static VALUE +div_day(VALUE d, VALUE *f) +{ + if (f) + *f = f_mod(d, INT2FIX(1)); + return f_floor(d); +} + +static VALUE +div_df(VALUE d, VALUE *f) +{ + VALUE s = day_to_sec(d); + + if (f) + *f = f_mod(s, INT2FIX(1)); + return f_floor(s); +} + +#ifndef NDEBUG +/* :nodoc: */ +static VALUE +div_sf(VALUE s, VALUE *f) +{ + VALUE n = sec_to_ns(s); + + if (f) + *f = f_mod(n, INT2FIX(1)); + return f_floor(n); +} +#endif + +static void +decode_day(VALUE d, VALUE *jd, VALUE *df, VALUE *sf) +{ + VALUE f; + + *jd = div_day(d, &f); + *df = div_df(f, &f); + *sf = sec_to_ns(f); +} + +inline static double +s_virtual_sg(union DateData *x) +{ + if (isinf(x->s.sg)) + return x->s.sg; + if (f_zero_p(x->s.nth)) + return x->s.sg; + else if (f_negative_p(x->s.nth)) + return positive_inf; + return negative_inf; +} + +inline static double +c_virtual_sg(union DateData *x) +{ + if (isinf(x->c.sg)) + return x->c.sg; + if (f_zero_p(x->c.nth)) + return x->c.sg; + else if (f_negative_p(x->c.nth)) + return positive_inf; + return negative_inf; +} + +inline static double +m_virtual_sg(union DateData *x) +{ + if (simple_dat_p(x)) + return s_virtual_sg(x); + else + return c_virtual_sg(x); +} + +#define canonicalize_jd(_nth, _jd) \ +do {\ + if (_jd < 0) {\ + _nth = f_sub(_nth, INT2FIX(1));\ + _jd += CM_PERIOD;\ + }\ + if (_jd >= CM_PERIOD) {\ + _nth = f_add(_nth, INT2FIX(1));\ + _jd -= CM_PERIOD;\ + }\ +} while (0) + +inline static void +canonicalize_s_jd(VALUE obj, union DateData *x) +{ + int j = x->s.jd; + VALUE nth = x->s.nth; + assert(have_jd_p(x)); + canonicalize_jd(nth, x->s.jd); + RB_OBJ_WRITE(obj, &x->s.nth, nth); + if (x->s.jd != j) + x->flags &= ~HAVE_CIVIL; +} + +inline static void +get_s_jd(union DateData *x) +{ + assert(simple_dat_p(x)); + if (!have_jd_p(x)) { + int jd, ns; + + assert(have_civil_p(x)); +#ifndef USE_PACK + c_civil_to_jd(x->s.year, x->s.mon, x->s.mday, + s_virtual_sg(x), &jd, &ns); +#else + c_civil_to_jd(x->s.year, EX_MON(x->s.pc), EX_MDAY(x->s.pc), + s_virtual_sg(x), &jd, &ns); +#endif + x->s.jd = jd; + x->s.flags |= HAVE_JD; + } +} + +inline static void +get_s_civil(union DateData *x) +{ + assert(simple_dat_p(x)); + if (!have_civil_p(x)) { + int y, m, d; + + assert(have_jd_p(x)); + c_jd_to_civil(x->s.jd, s_virtual_sg(x), &y, &m, &d); + x->s.year = y; +#ifndef USE_PACK + x->s.mon = m; + x->s.mday = d; +#else + x->s.pc = PACK2(m, d); +#endif + x->s.flags |= HAVE_CIVIL; + } +} + +inline static void +get_c_df(union DateData *x) +{ + assert(complex_dat_p(x)); + if (!have_df_p(x)) { + assert(have_time_p(x)); +#ifndef USE_PACK + x->c.df = df_local_to_utc(time_to_df(x->c.hour, x->c.min, x->c.sec), + x->c.of); +#else + x->c.df = df_local_to_utc(time_to_df(EX_HOUR(x->c.pc), + EX_MIN(x->c.pc), + EX_SEC(x->c.pc)), + x->c.of); +#endif + x->c.flags |= HAVE_DF; + } +} + +inline static void +get_c_time(union DateData *x) +{ + assert(complex_dat_p(x)); + if (!have_time_p(x)) { +#ifndef USE_PACK + int r; + assert(have_df_p(x)); + r = df_utc_to_local(x->c.df, x->c.of); + df_to_time(r, &x->c.hour, &x->c.min, &x->c.sec); + x->c.flags |= HAVE_TIME; +#else + int r, m, d, h, min, s; + + assert(have_df_p(x)); + m = EX_MON(x->c.pc); + d = EX_MDAY(x->c.pc); + r = df_utc_to_local(x->c.df, x->c.of); + df_to_time(r, &h, &min, &s); + x->c.pc = PACK5(m, d, h, min, s); + x->c.flags |= HAVE_TIME; +#endif + } +} + +inline static void +canonicalize_c_jd(VALUE obj, union DateData *x) +{ + int j = x->c.jd; + VALUE nth = x->c.nth; + assert(have_jd_p(x)); + canonicalize_jd(nth, x->c.jd); + RB_OBJ_WRITE(obj, &x->c.nth, nth); + if (x->c.jd != j) + x->flags &= ~HAVE_CIVIL; +} + +inline static void +get_c_jd(union DateData *x) +{ + assert(complex_dat_p(x)); + if (!have_jd_p(x)) { + int jd, ns; + + assert(have_civil_p(x)); +#ifndef USE_PACK + c_civil_to_jd(x->c.year, x->c.mon, x->c.mday, + c_virtual_sg(x), &jd, &ns); +#else + c_civil_to_jd(x->c.year, EX_MON(x->c.pc), EX_MDAY(x->c.pc), + c_virtual_sg(x), &jd, &ns); +#endif + + get_c_time(x); +#ifndef USE_PACK + x->c.jd = jd_local_to_utc(jd, + time_to_df(x->c.hour, x->c.min, x->c.sec), + x->c.of); +#else + x->c.jd = jd_local_to_utc(jd, + time_to_df(EX_HOUR(x->c.pc), + EX_MIN(x->c.pc), + EX_SEC(x->c.pc)), + x->c.of); +#endif + x->c.flags |= HAVE_JD; + } +} + +inline static void +get_c_civil(union DateData *x) +{ + assert(complex_dat_p(x)); + if (!have_civil_p(x)) { +#ifndef USE_PACK + int jd, y, m, d; +#else + int jd, y, m, d, h, min, s; +#endif + + assert(have_jd_p(x)); + get_c_df(x); + jd = jd_utc_to_local(x->c.jd, x->c.df, x->c.of); + c_jd_to_civil(jd, c_virtual_sg(x), &y, &m, &d); + x->c.year = y; +#ifndef USE_PACK + x->c.mon = m; + x->c.mday = d; +#else + h = EX_HOUR(x->c.pc); + min = EX_MIN(x->c.pc); + s = EX_SEC(x->c.pc); + x->c.pc = PACK5(m, d, h, min, s); +#endif + x->c.flags |= HAVE_CIVIL; + } +} + +inline static int +local_jd(union DateData *x) +{ + assert(complex_dat_p(x)); + assert(have_jd_p(x)); + assert(have_df_p(x)); + return jd_utc_to_local(x->c.jd, x->c.df, x->c.of); +} + +inline static int +local_df(union DateData *x) +{ + assert(complex_dat_p(x)); + assert(have_df_p(x)); + return df_utc_to_local(x->c.df, x->c.of); +} + +static void +decode_year(VALUE y, double style, + VALUE *nth, int *ry) +{ + int period; + VALUE t; + + period = (style < 0) ? + CM_PERIOD_GCY : + CM_PERIOD_JCY; + if (FIXNUM_P(y)) { + long iy, it, inth; + + iy = FIX2LONG(y); + if (iy >= (FIXNUM_MAX - 4712)) + goto big; + it = iy + 4712; /* shift */ + inth = DIV(it, ((long)period)); + *nth = LONG2FIX(inth); + if (inth) + it = MOD(it, ((long)period)); + *ry = (int)it - 4712; /* unshift */ + return; + } + big: + t = f_add(y, INT2FIX(4712)); /* shift */ + *nth = f_idiv(t, INT2FIX(period)); + if (f_nonzero_p(*nth)) + t = f_mod(t, INT2FIX(period)); + *ry = FIX2INT(t) - 4712; /* unshift */ +} + +static void +encode_year(VALUE nth, int y, double style, + VALUE *ry) +{ + int period; + VALUE t; + + period = (style < 0) ? + CM_PERIOD_GCY : + CM_PERIOD_JCY; + if (f_zero_p(nth)) + *ry = INT2FIX(y); + else { + t = f_mul(INT2FIX(period), nth); + t = f_add(t, INT2FIX(y)); + *ry = t; + } +} + +static void +decode_jd(VALUE jd, VALUE *nth, int *rjd) +{ + *nth = f_idiv(jd, INT2FIX(CM_PERIOD)); + if (f_zero_p(*nth)) { + *rjd = FIX2INT(jd); + return; + } + *rjd = FIX2INT(f_mod(jd, INT2FIX(CM_PERIOD))); +} + +static void +encode_jd(VALUE nth, int jd, VALUE *rjd) +{ + if (f_zero_p(nth)) { + *rjd = INT2FIX(jd); + return; + } + *rjd = f_add(f_mul(INT2FIX(CM_PERIOD), nth), INT2FIX(jd)); +} + +inline static double +guess_style(VALUE y, double sg) /* -/+oo or zero */ +{ + double style = 0; + + if (isinf(sg)) + style = sg; + else if (!FIXNUM_P(y)) + style = f_positive_p(y) ? negative_inf : positive_inf; + else { + long iy = FIX2LONG(y); + + assert(FIXNUM_P(y)); + if (iy < REFORM_BEGIN_YEAR) + style = positive_inf; + else if (iy > REFORM_END_YEAR) + style = negative_inf; + } + return style; +} + +inline static void +m_canonicalize_jd(VALUE obj, union DateData *x) +{ + if (simple_dat_p(x)) { + get_s_jd(x); + canonicalize_s_jd(obj, x); + } + else { + get_c_jd(x); + canonicalize_c_jd(obj, x); + } +} + +inline static VALUE +m_nth(union DateData *x) +{ + if (simple_dat_p(x)) + return x->s.nth; + else { + get_c_civil(x); + return x->c.nth; + } +} + +inline static int +m_jd(union DateData *x) +{ + if (simple_dat_p(x)) { + get_s_jd(x); + return x->s.jd; + } + else { + get_c_jd(x); + return x->c.jd; + } +} + +static VALUE +m_real_jd(union DateData *x) +{ + VALUE nth, rjd; + int jd; + + nth = m_nth(x); + jd = m_jd(x); + + encode_jd(nth, jd, &rjd); + return rjd; +} + +static int +m_local_jd(union DateData *x) +{ + if (simple_dat_p(x)) { + get_s_jd(x); + return x->s.jd; + } + else { + get_c_jd(x); + get_c_df(x); + return local_jd(x); + } +} + +static VALUE +m_real_local_jd(union DateData *x) +{ + VALUE nth, rjd; + int jd; + + nth = m_nth(x); + jd = m_local_jd(x); + + encode_jd(nth, jd, &rjd); + return rjd; +} + +inline static int +m_df(union DateData *x) +{ + if (simple_dat_p(x)) + return 0; + else { + get_c_df(x); + return x->c.df; + } +} + +#ifndef NDEBUG +/* :nodoc: */ +static VALUE +m_df_in_day(union DateData *x) +{ + return isec_to_day(m_df(x)); +} +#endif + +static int +m_local_df(union DateData *x) +{ + if (simple_dat_p(x)) + return 0; + else { + get_c_df(x); + return local_df(x); + } +} + +#ifndef NDEBUG +static VALUE +m_local_df_in_day(union DateData *x) +{ + return isec_to_day(m_local_df(x)); +} +#endif + +inline static VALUE +m_sf(union DateData *x) +{ + if (simple_dat_p(x)) + return INT2FIX(0); + else + return x->c.sf; +} + +#ifndef NDEBUG +static VALUE +m_sf_in_day(union DateData *x) +{ + return ns_to_day(m_sf(x)); +} +#endif + +static VALUE +m_sf_in_sec(union DateData *x) +{ + return ns_to_sec(m_sf(x)); +} + +static VALUE +m_fr(union DateData *x) +{ + if (simple_dat_p(x)) + return INT2FIX(0); + else { + int df; + VALUE sf, fr; + + df = m_local_df(x); + sf = m_sf(x); + fr = isec_to_day(df); + if (f_nonzero_p(sf)) + fr = f_add(fr, ns_to_day(sf)); + return fr; + } +} + +#define HALF_DAYS_IN_SECONDS (DAY_IN_SECONDS / 2) + +static VALUE +m_ajd(union DateData *x) +{ + VALUE r, sf; + int df; + + if (simple_dat_p(x)) { + r = m_real_jd(x); + if (FIXNUM_P(r) && FIX2LONG(r) <= (FIXNUM_MAX / 2)) { + long ir = FIX2LONG(r); + ir = ir * 2 - 1; + return rb_rational_new2(LONG2FIX(ir), INT2FIX(2)); + } + else + return rb_rational_new2(f_sub(f_mul(r, + INT2FIX(2)), + INT2FIX(1)), + INT2FIX(2)); + } + + r = m_real_jd(x); + df = m_df(x); + df -= HALF_DAYS_IN_SECONDS; + if (df) + r = f_add(r, isec_to_day(df)); + sf = m_sf(x); + if (f_nonzero_p(sf)) + r = f_add(r, ns_to_day(sf)); + + return r; +} + +static VALUE +m_amjd(union DateData *x) +{ + VALUE r, sf; + int df; + + r = m_real_jd(x); + if (FIXNUM_P(r) && FIX2LONG(r) >= (FIXNUM_MIN + 2400001)) { + long ir = FIX2LONG(r); + ir -= 2400001; + r = rb_rational_new1(LONG2FIX(ir)); + } + else + r = rb_rational_new1(f_sub(m_real_jd(x), + INT2FIX(2400001))); + + if (simple_dat_p(x)) + return r; + + df = m_df(x); + if (df) + r = f_add(r, isec_to_day(df)); + sf = m_sf(x); + if (f_nonzero_p(sf)) + r = f_add(r, ns_to_day(sf)); + + return r; +} + +inline static int +m_of(union DateData *x) +{ + if (simple_dat_p(x)) + return 0; + else { + get_c_jd(x); + return x->c.of; + } +} + +static VALUE +m_of_in_day(union DateData *x) +{ + return isec_to_day(m_of(x)); +} + +inline static double +m_sg(union DateData *x) +{ + if (simple_dat_p(x)) + return x->s.sg; + else { + get_c_jd(x); + return x->c.sg; + } +} + +static int +m_julian_p(union DateData *x) +{ + int jd; + double sg; + + if (simple_dat_p(x)) { + get_s_jd(x); + jd = x->s.jd; + sg = s_virtual_sg(x); + } + else { + get_c_jd(x); + jd = x->c.jd; + sg = c_virtual_sg(x); + } + if (isinf(sg)) + return sg == positive_inf; + return jd < sg; +} + +inline static int +m_gregorian_p(union DateData *x) +{ + return !m_julian_p(x); +} + +inline static int +m_proleptic_julian_p(union DateData *x) +{ + double sg; + + sg = m_sg(x); + if (isinf(sg) && sg > 0) + return 1; + return 0; +} + +inline static int +m_proleptic_gregorian_p(union DateData *x) +{ + double sg; + + sg = m_sg(x); + if (isinf(sg) && sg < 0) + return 1; + return 0; +} + +inline static int +m_year(union DateData *x) +{ + if (simple_dat_p(x)) { + get_s_civil(x); + return x->s.year; + } + else { + get_c_civil(x); + return x->c.year; + } +} + +static VALUE +m_real_year(union DateData *x) +{ + VALUE nth, ry; + int year; + + nth = m_nth(x); + year = m_year(x); + + if (f_zero_p(nth)) + return INT2FIX(year); + + encode_year(nth, year, + m_gregorian_p(x) ? -1 : +1, + &ry); + return ry; +} + +inline static int +m_mon(union DateData *x) +{ + if (simple_dat_p(x)) { + get_s_civil(x); +#ifndef USE_PACK + return x->s.mon; +#else + return EX_MON(x->s.pc); +#endif + } + else { + get_c_civil(x); +#ifndef USE_PACK + return x->c.mon; +#else + return EX_MON(x->c.pc); +#endif + } +} + +inline static int +m_mday(union DateData *x) +{ + if (simple_dat_p(x)) { + get_s_civil(x); +#ifndef USE_PACK + return x->s.mday; +#else + return EX_MDAY(x->s.pc); +#endif + } + else { + get_c_civil(x); +#ifndef USE_PACK + return x->c.mday; +#else + return EX_MDAY(x->c.pc); +#endif + } +} + +static const int yeartab[2][13] = { + { 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }, + { 0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 } +}; + +static int +c_julian_to_yday(int y, int m, int d) +{ + assert(m >= 1 && m <= 12); + return yeartab[c_julian_leap_p(y) ? 1 : 0][m] + d; +} + +static int +c_gregorian_to_yday(int y, int m, int d) +{ + assert(m >= 1 && m <= 12); + return yeartab[c_gregorian_leap_p(y) ? 1 : 0][m] + d; +} + +static int +m_yday(union DateData *x) +{ + int jd, ry, rd; + double sg; + + jd = m_local_jd(x); + sg = m_virtual_sg(x); /* !=m_sg() */ + + if (m_proleptic_gregorian_p(x) || + (jd - sg) > 366) + return c_gregorian_to_yday(m_year(x), m_mon(x), m_mday(x)); + if (m_proleptic_julian_p(x)) + return c_julian_to_yday(m_year(x), m_mon(x), m_mday(x)); + c_jd_to_ordinal(jd, sg, &ry, &rd); + return rd; +} + +static int +m_wday(union DateData *x) +{ + return c_jd_to_wday(m_local_jd(x)); +} + +static int +m_cwyear(union DateData *x) +{ + int ry, rw, rd; + + c_jd_to_commercial(m_local_jd(x), m_virtual_sg(x), /* !=m_sg() */ + &ry, &rw, &rd); + return ry; +} + +static VALUE +m_real_cwyear(union DateData *x) +{ + VALUE nth, ry; + int year; + + nth = m_nth(x); + year = m_cwyear(x); + + if (f_zero_p(nth)) + return INT2FIX(year); + + encode_year(nth, year, + m_gregorian_p(x) ? -1 : +1, + &ry); + return ry; +} + +static int +m_cweek(union DateData *x) +{ + int ry, rw, rd; + + c_jd_to_commercial(m_local_jd(x), m_virtual_sg(x), /* !=m_sg() */ + &ry, &rw, &rd); + return rw; +} + +static int +m_cwday(union DateData *x) +{ + int w; + + w = m_wday(x); + if (w == 0) + w = 7; + return w; +} + +static int +m_wnumx(union DateData *x, int f) +{ + int ry, rw, rd; + + c_jd_to_weeknum(m_local_jd(x), f, m_virtual_sg(x), /* !=m_sg() */ + &ry, &rw, &rd); + return rw; +} + +static int +m_wnum0(union DateData *x) +{ + return m_wnumx(x, 0); +} + +static int +m_wnum1(union DateData *x) +{ + return m_wnumx(x, 1); +} + +inline static int +m_hour(union DateData *x) +{ + if (simple_dat_p(x)) + return 0; + else { + get_c_time(x); +#ifndef USE_PACK + return x->c.hour; +#else + return EX_HOUR(x->c.pc); +#endif + } +} + +inline static int +m_min(union DateData *x) +{ + if (simple_dat_p(x)) + return 0; + else { + get_c_time(x); +#ifndef USE_PACK + return x->c.min; +#else + return EX_MIN(x->c.pc); +#endif + } +} + +inline static int +m_sec(union DateData *x) +{ + if (simple_dat_p(x)) + return 0; + else { + get_c_time(x); +#ifndef USE_PACK + return x->c.sec; +#else + return EX_SEC(x->c.pc); +#endif + } +} + +#define decode_offset(of,s,h,m)\ +do {\ + int a;\ + s = (of < 0) ? '-' : '+';\ + a = (of < 0) ? -of : of;\ + h = a / HOUR_IN_SECONDS;\ + m = a % HOUR_IN_SECONDS / MINUTE_IN_SECONDS;\ +} while (0) + +static VALUE +of2str(int of) +{ + int s, h, m; + + decode_offset(of, s, h, m); + return rb_enc_sprintf(rb_usascii_encoding(), "%c%02d:%02d", s, h, m); +} + +static VALUE +m_zone(union DateData *x) +{ + if (simple_dat_p(x)) + return rb_usascii_str_new2("+00:00"); + return of2str(m_of(x)); +} + +inline static VALUE +f_kind_of_p(VALUE x, VALUE c) +{ + return rb_obj_is_kind_of(x, c); +} + +inline static VALUE +k_date_p(VALUE x) +{ + return f_kind_of_p(x, cDate); +} + +inline static VALUE +k_numeric_p(VALUE x) +{ + return f_kind_of_p(x, rb_cNumeric); +} + +inline static VALUE +k_rational_p(VALUE x) +{ + return f_kind_of_p(x, rb_cRational); +} + +static inline void +expect_numeric(VALUE x) +{ + if (!k_numeric_p(x)) + rb_raise(rb_eTypeError, "expected numeric"); +} + +#ifndef NDEBUG +/* :nodoc: */ +static void +civil_to_jd(VALUE y, int m, int d, double sg, + VALUE *nth, int *ry, + int *rjd, + int *ns) +{ + double style = guess_style(y, sg); + + if (style == 0) { + int jd; + + c_civil_to_jd(FIX2INT(y), m, d, sg, &jd, ns); + decode_jd(INT2FIX(jd), nth, rjd); + if (f_zero_p(*nth)) + *ry = FIX2INT(y); + else { + VALUE nth2; + decode_year(y, *ns ? -1 : +1, &nth2, ry); + } + } + else { + decode_year(y, style, nth, ry); + c_civil_to_jd(*ry, m, d, style, rjd, ns); + } +} + +static void +jd_to_civil(VALUE jd, double sg, + VALUE *nth, int *rjd, + int *ry, int *rm, int *rd) +{ + decode_jd(jd, nth, rjd); + c_jd_to_civil(*rjd, sg, ry, rm, rd); +} + +static void +ordinal_to_jd(VALUE y, int d, double sg, + VALUE *nth, int *ry, + int *rjd, + int *ns) +{ + double style = guess_style(y, sg); + + if (style == 0) { + int jd; + + c_ordinal_to_jd(FIX2INT(y), d, sg, &jd, ns); + decode_jd(INT2FIX(jd), nth, rjd); + if (f_zero_p(*nth)) + *ry = FIX2INT(y); + else { + VALUE nth2; + decode_year(y, *ns ? -1 : +1, &nth2, ry); + } + } + else { + decode_year(y, style, nth, ry); + c_ordinal_to_jd(*ry, d, style, rjd, ns); + } +} + +static void +jd_to_ordinal(VALUE jd, double sg, + VALUE *nth, int *rjd, + int *ry, int *rd) +{ + decode_jd(jd, nth, rjd); + c_jd_to_ordinal(*rjd, sg, ry, rd); +} + +static void +commercial_to_jd(VALUE y, int w, int d, double sg, + VALUE *nth, int *ry, + int *rjd, + int *ns) +{ + double style = guess_style(y, sg); + + if (style == 0) { + int jd; + + c_commercial_to_jd(FIX2INT(y), w, d, sg, &jd, ns); + decode_jd(INT2FIX(jd), nth, rjd); + if (f_zero_p(*nth)) + *ry = FIX2INT(y); + else { + VALUE nth2; + decode_year(y, *ns ? -1 : +1, &nth2, ry); + } + } + else { + decode_year(y, style, nth, ry); + c_commercial_to_jd(*ry, w, d, style, rjd, ns); + } +} + +static void +jd_to_commercial(VALUE jd, double sg, + VALUE *nth, int *rjd, + int *ry, int *rw, int *rd) +{ + decode_jd(jd, nth, rjd); + c_jd_to_commercial(*rjd, sg, ry, rw, rd); +} + +static void +weeknum_to_jd(VALUE y, int w, int d, int f, double sg, + VALUE *nth, int *ry, + int *rjd, + int *ns) +{ + double style = guess_style(y, sg); + + if (style == 0) { + int jd; + + c_weeknum_to_jd(FIX2INT(y), w, d, f, sg, &jd, ns); + decode_jd(INT2FIX(jd), nth, rjd); + if (f_zero_p(*nth)) + *ry = FIX2INT(y); + else { + VALUE nth2; + decode_year(y, *ns ? -1 : +1, &nth2, ry); + } + } + else { + decode_year(y, style, nth, ry); + c_weeknum_to_jd(*ry, w, d, f, style, rjd, ns); + } +} + +static void +jd_to_weeknum(VALUE jd, int f, double sg, + VALUE *nth, int *rjd, + int *ry, int *rw, int *rd) +{ + decode_jd(jd, nth, rjd); + c_jd_to_weeknum(*rjd, f, sg, ry, rw, rd); +} + +static void +nth_kday_to_jd(VALUE y, int m, int n, int k, double sg, + VALUE *nth, int *ry, + int *rjd, + int *ns) +{ + double style = guess_style(y, sg); + + if (style == 0) { + int jd; + + c_nth_kday_to_jd(FIX2INT(y), m, n, k, sg, &jd, ns); + decode_jd(INT2FIX(jd), nth, rjd); + if (f_zero_p(*nth)) + *ry = FIX2INT(y); + else { + VALUE nth2; + decode_year(y, *ns ? -1 : +1, &nth2, ry); + } + } + else { + decode_year(y, style, nth, ry); + c_nth_kday_to_jd(*ry, m, n, k, style, rjd, ns); + } +} + +static void +jd_to_nth_kday(VALUE jd, double sg, + VALUE *nth, int *rjd, + int *ry, int *rm, int *rn, int *rk) +{ + decode_jd(jd, nth, rjd); + c_jd_to_nth_kday(*rjd, sg, ry, rm, rn, rk); +} +#endif + +static int +valid_ordinal_p(VALUE y, int d, double sg, + VALUE *nth, int *ry, + int *rd, int *rjd, + int *ns) +{ + double style = guess_style(y, sg); + int r; + + if (style == 0) { + int jd; + + r = c_valid_ordinal_p(FIX2INT(y), d, sg, rd, &jd, ns); + if (!r) + return 0; + decode_jd(INT2FIX(jd), nth, rjd); + if (f_zero_p(*nth)) + *ry = FIX2INT(y); + else { + VALUE nth2; + decode_year(y, *ns ? -1 : +1, &nth2, ry); + } + } + else { + decode_year(y, style, nth, ry); + r = c_valid_ordinal_p(*ry, d, style, rd, rjd, ns); + } + return r; +} + +static int +valid_gregorian_p(VALUE y, int m, int d, + VALUE *nth, int *ry, + int *rm, int *rd) +{ + decode_year(y, -1, nth, ry); + return c_valid_gregorian_p(*ry, m, d, rm, rd); +} + +static int +valid_civil_p(VALUE y, int m, int d, double sg, + VALUE *nth, int *ry, + int *rm, int *rd, int *rjd, + int *ns) +{ + double style = guess_style(y, sg); + int r; + + if (style == 0) { + int jd; + + r = c_valid_civil_p(FIX2INT(y), m, d, sg, rm, rd, &jd, ns); + if (!r) + return 0; + decode_jd(INT2FIX(jd), nth, rjd); + if (f_zero_p(*nth)) + *ry = FIX2INT(y); + else { + VALUE nth2; + decode_year(y, *ns ? -1 : +1, &nth2, ry); + } + } + else { + decode_year(y, style, nth, ry); + if (style < 0) + r = c_valid_gregorian_p(*ry, m, d, rm, rd); + else + r = c_valid_julian_p(*ry, m, d, rm, rd); + if (!r) + return 0; + c_civil_to_jd(*ry, *rm, *rd, style, rjd, ns); + } + return r; +} + +static int +valid_commercial_p(VALUE y, int w, int d, double sg, + VALUE *nth, int *ry, + int *rw, int *rd, int *rjd, + int *ns) +{ + double style = guess_style(y, sg); + int r; + + if (style == 0) { + int jd; + + r = c_valid_commercial_p(FIX2INT(y), w, d, sg, rw, rd, &jd, ns); + if (!r) + return 0; + decode_jd(INT2FIX(jd), nth, rjd); + if (f_zero_p(*nth)) + *ry = FIX2INT(y); + else { + VALUE nth2; + decode_year(y, *ns ? -1 : +1, &nth2, ry); + } + } + else { + decode_year(y, style, nth, ry); + r = c_valid_commercial_p(*ry, w, d, style, rw, rd, rjd, ns); + } + return r; +} + +static int +valid_weeknum_p(VALUE y, int w, int d, int f, double sg, + VALUE *nth, int *ry, + int *rw, int *rd, int *rjd, + int *ns) +{ + double style = guess_style(y, sg); + int r; + + if (style == 0) { + int jd; + + r = c_valid_weeknum_p(FIX2INT(y), w, d, f, sg, rw, rd, &jd, ns); + if (!r) + return 0; + decode_jd(INT2FIX(jd), nth, rjd); + if (f_zero_p(*nth)) + *ry = FIX2INT(y); + else { + VALUE nth2; + decode_year(y, *ns ? -1 : +1, &nth2, ry); + } + } + else { + decode_year(y, style, nth, ry); + r = c_valid_weeknum_p(*ry, w, d, f, style, rw, rd, rjd, ns); + } + return r; +} + +#ifndef NDEBUG +/* :nodoc: */ +static int +valid_nth_kday_p(VALUE y, int m, int n, int k, double sg, + VALUE *nth, int *ry, + int *rm, int *rn, int *rk, int *rjd, + int *ns) +{ + double style = guess_style(y, sg); + int r; + + if (style == 0) { + int jd; + + r = c_valid_nth_kday_p(FIX2INT(y), m, n, k, sg, rm, rn, rk, &jd, ns); + if (!r) + return 0; + decode_jd(INT2FIX(jd), nth, rjd); + if (f_zero_p(*nth)) + *ry = FIX2INT(y); + else { + VALUE nth2; + decode_year(y, *ns ? -1 : +1, &nth2, ry); + } + } + else { + decode_year(y, style, nth, ry); + r = c_valid_nth_kday_p(*ry, m, n, k, style, rm, rn, rk, rjd, ns); + } + return r; +} +#endif + +VALUE date_zone_to_diff(VALUE); + +static int +offset_to_sec(VALUE vof, int *rof) +{ + int try_rational = 1; + + again: + switch (TYPE(vof)) { + case T_FIXNUM: + { + long n; + + n = FIX2LONG(vof); + if (n != -1 && n != 0 && n != 1) + return 0; + *rof = (int)n * DAY_IN_SECONDS; + return 1; + } + case T_FLOAT: + { + double n; + + n = RFLOAT_VALUE(vof) * DAY_IN_SECONDS; + if (n < -DAY_IN_SECONDS || n > DAY_IN_SECONDS) + return 0; + *rof = (int)round(n); + if (*rof != n) + rb_warning("fraction of offset is ignored"); + return 1; + } + default: + expect_numeric(vof); + vof = f_to_r(vof); + if (!k_rational_p(vof)) { + if (!try_rational) Check_Type(vof, T_RATIONAL); + try_rational = 0; + goto again; + } + /* fall through */ + case T_RATIONAL: + { + VALUE vs, vn, vd; + long n; + + vs = day_to_sec(vof); + + if (!k_rational_p(vs)) { + vn = vs; + goto rounded; + } + vn = rb_rational_num(vs); + vd = rb_rational_den(vs); + + if (FIXNUM_P(vn) && FIXNUM_P(vd) && (FIX2LONG(vd) == 1)) + n = FIX2LONG(vn); + else { + vn = f_round(vs); + if (!f_eqeq_p(vn, vs)) + rb_warning("fraction of offset is ignored"); + rounded: + if (!FIXNUM_P(vn)) + return 0; + n = FIX2LONG(vn); + if (n < -DAY_IN_SECONDS || n > DAY_IN_SECONDS) + return 0; + } + *rof = (int)n; + return 1; + } + case T_STRING: + { + VALUE vs = date_zone_to_diff(vof); + long n; + + if (!FIXNUM_P(vs)) + return 0; + n = FIX2LONG(vs); + if (n < -DAY_IN_SECONDS || n > DAY_IN_SECONDS) + return 0; + *rof = (int)n; + return 1; + } + } + return 0; +} + +/* date */ + +#define valid_sg(sg) \ +do {\ + if (!c_valid_start_p(sg)) {\ + sg = 0;\ + rb_warning("invalid start is ignored");\ + }\ +} while (0) + +static VALUE +valid_jd_sub(int argc, VALUE *argv, VALUE klass, int need_jd) +{ + double sg = NUM2DBL(argv[1]); + valid_sg(sg); + return argv[0]; +} + +#ifndef NDEBUG +/* :nodoc: */ +static VALUE +date_s__valid_jd_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vjd, vsg; + VALUE argv2[2]; + + rb_scan_args(argc, argv, "11", &vjd, &vsg); + + argv2[0] = vjd; + if (argc < 2) + argv2[1] = DBL2NUM(GREGORIAN); + else + argv2[1] = vsg; + + return valid_jd_sub(2, argv2, klass, 1); +} +#endif + +/* + * call-seq: + * Date.valid_jd?(jd, start = Date::ITALY) -> true + * + * Implemented for compatibility; + * returns +true+ unless +jd+ is invalid (i.e., not a Numeric). + * + * Date.valid_jd?(2451944) # => true + * + * See argument {start}[rdoc-ref:calendars.rdoc@Argument+start]. + * + * Related: Date.jd. + */ +static VALUE +date_s_valid_jd_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vjd, vsg; + VALUE argv2[2]; + + rb_scan_args(argc, argv, "11", &vjd, &vsg); + + RETURN_FALSE_UNLESS_NUMERIC(vjd); + argv2[0] = vjd; + if (argc < 2) + argv2[1] = INT2FIX(DEFAULT_SG); + else + argv2[1] = vsg; + + if (NIL_P(valid_jd_sub(2, argv2, klass, 0))) + return Qfalse; + return Qtrue; +} + +static VALUE +valid_civil_sub(int argc, VALUE *argv, VALUE klass, int need_jd) +{ + VALUE nth, y; + int m, d, ry, rm, rd; + double sg; + + y = argv[0]; + m = NUM2INT(argv[1]); + d = NUM2INT(argv[2]); + sg = NUM2DBL(argv[3]); + + valid_sg(sg); + + if (!need_jd && (guess_style(y, sg) < 0)) { + if (!valid_gregorian_p(y, m, d, + &nth, &ry, + &rm, &rd)) + return Qnil; + return INT2FIX(0); /* dummy */ + } + else { + int rjd, ns; + VALUE rjd2; + + if (!valid_civil_p(y, m, d, sg, + &nth, &ry, + &rm, &rd, &rjd, + &ns)) + return Qnil; + if (!need_jd) + return INT2FIX(0); /* dummy */ + encode_jd(nth, rjd, &rjd2); + return rjd2; + } +} + +#ifndef NDEBUG +/* :nodoc: */ +static VALUE +date_s__valid_civil_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vm, vd, vsg; + VALUE argv2[4]; + + rb_scan_args(argc, argv, "31", &vy, &vm, &vd, &vsg); + + argv2[0] = vy; + argv2[1] = vm; + argv2[2] = vd; + if (argc < 4) + argv2[3] = DBL2NUM(GREGORIAN); + else + argv2[3] = vsg; + + return valid_civil_sub(4, argv2, klass, 1); +} +#endif + +/* + * call-seq: + * Date.valid_civil?(year, month, mday, start = Date::ITALY) -> true or false + * + * Returns +true+ if the arguments define a valid ordinal date, + * +false+ otherwise: + * + * Date.valid_date?(2001, 2, 3) # => true + * Date.valid_date?(2001, 2, 29) # => false + * Date.valid_date?(2001, 2, -1) # => true + * + * See argument {start}[rdoc-ref:calendars.rdoc@Argument+start]. + * + * Related: Date.jd, Date.new. + */ +static VALUE +date_s_valid_civil_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vm, vd, vsg; + VALUE argv2[4]; + + rb_scan_args(argc, argv, "31", &vy, &vm, &vd, &vsg); + + RETURN_FALSE_UNLESS_NUMERIC(vy); + RETURN_FALSE_UNLESS_NUMERIC(vm); + RETURN_FALSE_UNLESS_NUMERIC(vd); + argv2[0] = vy; + argv2[1] = vm; + argv2[2] = vd; + if (argc < 4) + argv2[3] = INT2FIX(DEFAULT_SG); + else + argv2[3] = vsg; + + if (NIL_P(valid_civil_sub(4, argv2, klass, 0))) + return Qfalse; + return Qtrue; +} + +static VALUE +valid_ordinal_sub(int argc, VALUE *argv, VALUE klass, int need_jd) +{ + VALUE nth, y; + int d, ry, rd; + double sg; + + y = argv[0]; + d = NUM2INT(argv[1]); + sg = NUM2DBL(argv[2]); + + valid_sg(sg); + + { + int rjd, ns; + VALUE rjd2; + + if (!valid_ordinal_p(y, d, sg, + &nth, &ry, + &rd, &rjd, + &ns)) + return Qnil; + if (!need_jd) + return INT2FIX(0); /* dummy */ + encode_jd(nth, rjd, &rjd2); + return rjd2; + } +} + +#ifndef NDEBUG +/* :nodoc: */ +static VALUE +date_s__valid_ordinal_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vd, vsg; + VALUE argv2[3]; + + rb_scan_args(argc, argv, "21", &vy, &vd, &vsg); + + argv2[0] = vy; + argv2[1] = vd; + if (argc < 3) + argv2[2] = DBL2NUM(GREGORIAN); + else + argv2[2] = vsg; + + return valid_ordinal_sub(3, argv2, klass, 1); +} +#endif + +/* + * call-seq: + * Date.valid_ordinal?(year, yday, start = Date::ITALY) -> true or false + * + * Returns +true+ if the arguments define a valid ordinal date, + * +false+ otherwise: + * + * Date.valid_ordinal?(2001, 34) # => true + * Date.valid_ordinal?(2001, 366) # => false + * + * See argument {start}[rdoc-ref:calendars.rdoc@Argument+start]. + * + * Related: Date.jd, Date.ordinal. + */ +static VALUE +date_s_valid_ordinal_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vd, vsg; + VALUE argv2[3]; + + rb_scan_args(argc, argv, "21", &vy, &vd, &vsg); + + RETURN_FALSE_UNLESS_NUMERIC(vy); + RETURN_FALSE_UNLESS_NUMERIC(vd); + argv2[0] = vy; + argv2[1] = vd; + if (argc < 3) + argv2[2] = INT2FIX(DEFAULT_SG); + else + argv2[2] = vsg; + + if (NIL_P(valid_ordinal_sub(3, argv2, klass, 0))) + return Qfalse; + return Qtrue; +} + +static VALUE +valid_commercial_sub(int argc, VALUE *argv, VALUE klass, int need_jd) +{ + VALUE nth, y; + int w, d, ry, rw, rd; + double sg; + + y = argv[0]; + w = NUM2INT(argv[1]); + d = NUM2INT(argv[2]); + sg = NUM2DBL(argv[3]); + + valid_sg(sg); + + { + int rjd, ns; + VALUE rjd2; + + if (!valid_commercial_p(y, w, d, sg, + &nth, &ry, + &rw, &rd, &rjd, + &ns)) + return Qnil; + if (!need_jd) + return INT2FIX(0); /* dummy */ + encode_jd(nth, rjd, &rjd2); + return rjd2; + } +} + +#ifndef NDEBUG +/* :nodoc: */ +static VALUE +date_s__valid_commercial_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vw, vd, vsg; + VALUE argv2[4]; + + rb_scan_args(argc, argv, "31", &vy, &vw, &vd, &vsg); + + argv2[0] = vy; + argv2[1] = vw; + argv2[2] = vd; + if (argc < 4) + argv2[3] = DBL2NUM(GREGORIAN); + else + argv2[3] = vsg; + + return valid_commercial_sub(4, argv2, klass, 1); +} +#endif + +/* + * call-seq: + * Date.valid_commercial?(cwyear, cweek, cwday, start = Date::ITALY) -> true or false + * + * Returns +true+ if the arguments define a valid commercial date, + * +false+ otherwise: + * + * Date.valid_commercial?(2001, 5, 6) # => true + * Date.valid_commercial?(2001, 5, 8) # => false + * + * See Date.commercial. + * + * See argument {start}[rdoc-ref:calendars.rdoc@Argument+start]. + * + * Related: Date.jd, Date.commercial. + */ +static VALUE +date_s_valid_commercial_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vw, vd, vsg; + VALUE argv2[4]; + + rb_scan_args(argc, argv, "31", &vy, &vw, &vd, &vsg); + + RETURN_FALSE_UNLESS_NUMERIC(vy); + RETURN_FALSE_UNLESS_NUMERIC(vw); + RETURN_FALSE_UNLESS_NUMERIC(vd); + argv2[0] = vy; + argv2[1] = vw; + argv2[2] = vd; + if (argc < 4) + argv2[3] = INT2FIX(DEFAULT_SG); + else + argv2[3] = vsg; + + if (NIL_P(valid_commercial_sub(4, argv2, klass, 0))) + return Qfalse; + return Qtrue; +} + +#ifndef NDEBUG +/* :nodoc: */ +static VALUE +valid_weeknum_sub(int argc, VALUE *argv, VALUE klass, int need_jd) +{ + VALUE nth, y; + int w, d, f, ry, rw, rd; + double sg; + + y = argv[0]; + w = NUM2INT(argv[1]); + d = NUM2INT(argv[2]); + f = NUM2INT(argv[3]); + sg = NUM2DBL(argv[4]); + + valid_sg(sg); + + { + int rjd, ns; + VALUE rjd2; + + if (!valid_weeknum_p(y, w, d, f, sg, + &nth, &ry, + &rw, &rd, &rjd, + &ns)) + return Qnil; + if (!need_jd) + return INT2FIX(0); /* dummy */ + encode_jd(nth, rjd, &rjd2); + return rjd2; + } +} + +/* :nodoc: */ +static VALUE +date_s__valid_weeknum_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vw, vd, vf, vsg; + VALUE argv2[5]; + + rb_scan_args(argc, argv, "41", &vy, &vw, &vd, &vf, &vsg); + + argv2[0] = vy; + argv2[1] = vw; + argv2[2] = vd; + argv2[3] = vf; + if (argc < 5) + argv2[4] = DBL2NUM(GREGORIAN); + else + argv2[4] = vsg; + + return valid_weeknum_sub(5, argv2, klass, 1); +} + +/* :nodoc: */ +static VALUE +date_s_valid_weeknum_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vw, vd, vf, vsg; + VALUE argv2[5]; + + rb_scan_args(argc, argv, "41", &vy, &vw, &vd, &vf, &vsg); + + argv2[0] = vy; + argv2[1] = vw; + argv2[2] = vd; + argv2[3] = vf; + if (argc < 5) + argv2[4] = INT2FIX(DEFAULT_SG); + else + argv2[4] = vsg; + + if (NIL_P(valid_weeknum_sub(5, argv2, klass, 0))) + return Qfalse; + return Qtrue; +} + +static VALUE +valid_nth_kday_sub(int argc, VALUE *argv, VALUE klass, int need_jd) +{ + VALUE nth, y; + int m, n, k, ry, rm, rn, rk; + double sg; + + y = argv[0]; + m = NUM2INT(argv[1]); + n = NUM2INT(argv[2]); + k = NUM2INT(argv[3]); + sg = NUM2DBL(argv[4]); + + { + int rjd, ns; + VALUE rjd2; + + if (!valid_nth_kday_p(y, m, n, k, sg, + &nth, &ry, + &rm, &rn, &rk, &rjd, + &ns)) + return Qnil; + if (!need_jd) + return INT2FIX(0); /* dummy */ + encode_jd(nth, rjd, &rjd2); + return rjd2; + } +} + +/* :nodoc: */ +static VALUE +date_s__valid_nth_kday_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vm, vn, vk, vsg; + VALUE argv2[5]; + + rb_scan_args(argc, argv, "41", &vy, &vm, &vn, &vk, &vsg); + + argv2[0] = vy; + argv2[1] = vm; + argv2[2] = vn; + argv2[3] = vk; + if (argc < 5) + argv2[4] = DBL2NUM(GREGORIAN); + else + argv2[4] = vsg; + + return valid_nth_kday_sub(5, argv2, klass, 1); +} + +/* :nodoc: */ +static VALUE +date_s_valid_nth_kday_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vm, vn, vk, vsg; + VALUE argv2[5]; + + rb_scan_args(argc, argv, "41", &vy, &vm, &vn, &vk, &vsg); + + argv2[0] = vy; + argv2[1] = vm; + argv2[2] = vn; + argv2[3] = vk; + if (argc < 5) + argv2[4] = INT2FIX(DEFAULT_SG); + else + argv2[4] = vsg; + + if (NIL_P(valid_nth_kday_sub(5, argv2, klass, 0))) + return Qfalse; + return Qtrue; +} + +/* :nodoc: */ +static VALUE +date_s_zone_to_diff(VALUE klass, VALUE str) +{ + return date_zone_to_diff(str); +} +#endif + +/* + * call-seq: + * Date.julian_leap?(year) -> true or false + * + * Returns +true+ if the given year is a leap year + * in the {proleptic Julian calendar}[https://en.wikipedia.org/wiki/Proleptic_Julian_calendar], +false+ otherwise: + * + * Date.julian_leap?(1900) # => true + * Date.julian_leap?(1901) # => false + * + * Related: Date.gregorian_leap?. + */ +static VALUE +date_s_julian_leap_p(VALUE klass, VALUE y) +{ + VALUE nth; + int ry; + + check_numeric(y, "year"); + decode_year(y, +1, &nth, &ry); + return f_boolcast(c_julian_leap_p(ry)); +} + +/* + * call-seq: + * Date.gregorian_leap?(year) -> true or false + * + * Returns +true+ if the given year is a leap year + * in the {proleptic Gregorian calendar}[https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar], +false+ otherwise: + * + * Date.gregorian_leap?(2000) # => true + * Date.gregorian_leap?(2001) # => false + * + * Related: Date.julian_leap?. + */ +static VALUE +date_s_gregorian_leap_p(VALUE klass, VALUE y) +{ + VALUE nth; + int ry; + + check_numeric(y, "year"); + decode_year(y, -1, &nth, &ry); + return f_boolcast(c_gregorian_leap_p(ry)); +} + +static void +d_lite_gc_mark(void *ptr) +{ + union DateData *dat = ptr; + if (simple_dat_p(dat)) + rb_gc_mark(dat->s.nth); + else { + rb_gc_mark(dat->c.nth); + rb_gc_mark(dat->c.sf); + } +} + +static size_t +d_lite_memsize(const void *ptr) +{ + const union DateData *dat = ptr; + return complex_dat_p(dat) ? sizeof(struct ComplexDateData) : sizeof(struct SimpleDateData); +} + +#ifndef HAVE_RB_EXT_RACTOR_SAFE +# define RUBY_TYPED_FROZEN_SHAREABLE 0 +#endif + +static const rb_data_type_t d_lite_type = { + "Date", + {d_lite_gc_mark, RUBY_TYPED_DEFAULT_FREE, d_lite_memsize,}, + 0, 0, + RUBY_TYPED_FREE_IMMEDIATELY|RUBY_TYPED_WB_PROTECTED|RUBY_TYPED_FROZEN_SHAREABLE, +}; + +inline static VALUE +d_simple_new_internal(VALUE klass, + VALUE nth, int jd, + double sg, + int y, int m, int d, + unsigned flags) +{ + struct SimpleDateData *dat; + VALUE obj; + + obj = TypedData_Make_Struct(klass, struct SimpleDateData, + &d_lite_type, dat); + set_to_simple(obj, dat, nth, jd, sg, y, m, d, flags); + + assert(have_jd_p(dat) || have_civil_p(dat)); + + return obj; +} + +inline static VALUE +d_complex_new_internal(VALUE klass, + VALUE nth, int jd, + int df, VALUE sf, + int of, double sg, + int y, int m, int d, + int h, int min, int s, + unsigned flags) +{ + struct ComplexDateData *dat; + VALUE obj; + + obj = TypedData_Make_Struct(klass, struct ComplexDateData, + &d_lite_type, dat); + set_to_complex(obj, dat, nth, jd, df, sf, of, sg, + y, m, d, h, min, s, flags); + + assert(have_jd_p(dat) || have_civil_p(dat)); + assert(have_df_p(dat) || have_time_p(dat)); + + return obj; +} + +static VALUE +d_lite_s_alloc_simple(VALUE klass) +{ + return d_simple_new_internal(klass, + INT2FIX(0), 0, + DEFAULT_SG, + 0, 0, 0, + HAVE_JD); +} + +static VALUE +d_lite_s_alloc_complex(VALUE klass) +{ + return d_complex_new_internal(klass, + INT2FIX(0), 0, + 0, INT2FIX(0), + 0, DEFAULT_SG, + 0, 0, 0, + 0, 0, 0, + HAVE_JD | HAVE_DF); +} + +static VALUE +d_lite_s_alloc(VALUE klass) +{ + return d_lite_s_alloc_complex(klass); +} + +static void +old_to_new(VALUE ajd, VALUE of, VALUE sg, + VALUE *rnth, int *rjd, int *rdf, VALUE *rsf, + int *rof, double *rsg) +{ + VALUE jd, df, sf, of2, t; + + decode_day(f_add(ajd, half_days_in_day), + &jd, &df, &sf); + t = day_to_sec(of); + of2 = f_round(t); + + if (!f_eqeq_p(of2, t)) + rb_warning("fraction of offset is ignored"); + + decode_jd(jd, rnth, rjd); + + *rdf = NUM2INT(df); + *rsf = sf; + *rof = NUM2INT(of2); + *rsg = NUM2DBL(sg); + + if (*rdf < 0 || *rdf >= DAY_IN_SECONDS) + rb_raise(eDateError, "invalid day fraction"); + + if (f_lt_p(*rsf, INT2FIX(0)) || + f_ge_p(*rsf, INT2FIX(SECOND_IN_NANOSECONDS))) + + if (*rof < -DAY_IN_SECONDS || *rof > DAY_IN_SECONDS) { + *rof = 0; + rb_warning("invalid offset is ignored"); + } + + if (!c_valid_start_p(*rsg)) { + *rsg = DEFAULT_SG; + rb_warning("invalid start is ignored"); + } +} + +#ifndef NDEBUG +/* :nodoc: */ +static VALUE +date_s_new_bang(int argc, VALUE *argv, VALUE klass) +{ + VALUE ajd, of, sg, nth, sf; + int jd, df, rof; + double rsg; + + rb_scan_args(argc, argv, "03", &ajd, &of, &sg); + + switch (argc) { + case 0: + ajd = INT2FIX(0); + case 1: + of = INT2FIX(0); + case 2: + sg = INT2FIX(DEFAULT_SG); + } + + old_to_new(ajd, of, sg, + &nth, &jd, &df, &sf, &rof, &rsg); + + if (!df && f_zero_p(sf) && !rof) + return d_simple_new_internal(klass, + nth, jd, + rsg, + 0, 0, 0, + HAVE_JD); + else + return d_complex_new_internal(klass, + nth, jd, + df, sf, + rof, rsg, + 0, 0, 0, + 0, 0, 0, + HAVE_JD | HAVE_DF); +} +#endif + +inline static int +wholenum_p(VALUE x) +{ + if (FIXNUM_P(x)) + return 1; + switch (TYPE(x)) { + case T_BIGNUM: + return 1; + case T_FLOAT: + { + double d = RFLOAT_VALUE(x); + return round(d) == d; + } + break; + case T_RATIONAL: + { + VALUE den = rb_rational_den(x); + return FIXNUM_P(den) && FIX2LONG(den) == 1; + } + break; + } + return 0; +} + +inline static VALUE +to_integer(VALUE x) +{ + if (RB_INTEGER_TYPE_P(x)) + return x; + return f_to_i(x); +} + +inline static VALUE +d_trunc(VALUE d, VALUE *fr) +{ + VALUE rd; + + if (wholenum_p(d)) { + rd = to_integer(d); + *fr = INT2FIX(0); + } + else { + rd = f_idiv(d, INT2FIX(1)); + *fr = f_mod(d, INT2FIX(1)); + } + return rd; +} + +#define jd_trunc d_trunc +#define k_trunc d_trunc + +inline static VALUE +h_trunc(VALUE h, VALUE *fr) +{ + VALUE rh; + + if (wholenum_p(h)) { + rh = to_integer(h); + *fr = INT2FIX(0); + } + else { + rh = f_idiv(h, INT2FIX(1)); + *fr = f_mod(h, INT2FIX(1)); + *fr = f_quo(*fr, INT2FIX(24)); + } + return rh; +} + +inline static VALUE +min_trunc(VALUE min, VALUE *fr) +{ + VALUE rmin; + + if (wholenum_p(min)) { + rmin = to_integer(min); + *fr = INT2FIX(0); + } + else { + rmin = f_idiv(min, INT2FIX(1)); + *fr = f_mod(min, INT2FIX(1)); + *fr = f_quo(*fr, INT2FIX(1440)); + } + return rmin; +} + +inline static VALUE +s_trunc(VALUE s, VALUE *fr) +{ + VALUE rs; + + if (wholenum_p(s)) { + rs = to_integer(s); + *fr = INT2FIX(0); + } + else { + rs = f_idiv(s, INT2FIX(1)); + *fr = f_mod(s, INT2FIX(1)); + *fr = f_quo(*fr, INT2FIX(86400)); + } + return rs; +} + +#define num2num_with_frac(s,n) \ +do {\ + s = s##_trunc(v##s, &fr);\ + if (f_nonzero_p(fr)) {\ + if (argc > n)\ + rb_raise(eDateError, "invalid fraction");\ + fr2 = fr;\ + }\ +} while (0) + +#define num2int_with_frac(s,n) \ +do {\ + s = NUM2INT(s##_trunc(v##s, &fr));\ + if (f_nonzero_p(fr)) {\ + if (argc > n)\ + rb_raise(eDateError, "invalid fraction");\ + fr2 = fr;\ + }\ +} while (0) + +#define canon24oc() \ +do {\ + if (rh == 24) {\ + rh = 0;\ + fr2 = f_add(fr2, INT2FIX(1));\ + }\ +} while (0) + +#define add_frac() \ +do {\ + if (f_nonzero_p(fr2))\ + ret = d_lite_plus(ret, fr2);\ +} while (0) + +#define val2sg(vsg,dsg) \ +do {\ + dsg = NUM2DBL(vsg);\ + if (!c_valid_start_p(dsg)) {\ + dsg = DEFAULT_SG;\ + rb_warning("invalid start is ignored");\ + }\ +} while (0) + +static VALUE d_lite_plus(VALUE, VALUE); + +/* + * call-seq: + * Date.jd(jd = 0, start = Date::ITALY) -> date + * + * Returns a new \Date object formed from the arguments: + * + * Date.jd(2451944).to_s # => "2001-02-03" + * Date.jd(2451945).to_s # => "2001-02-04" + * Date.jd(0).to_s # => "-4712-01-01" + * + * The returned date is: + * + * - Gregorian, if the argument is greater than or equal to +start+: + * + * Date::ITALY # => 2299161 + * Date.jd(Date::ITALY).gregorian? # => true + * Date.jd(Date::ITALY + 1).gregorian? # => true + * + * - Julian, otherwise + * + * Date.jd(Date::ITALY - 1).julian? # => true + * + * See argument {start}[rdoc-ref:calendars.rdoc@Argument+start]. + * + * Related: Date.new. + */ +static VALUE +date_s_jd(int argc, VALUE *argv, VALUE klass) +{ + VALUE vjd, vsg, jd, fr, fr2, ret; + double sg; + + rb_scan_args(argc, argv, "02", &vjd, &vsg); + + jd = INT2FIX(0); + fr2 = INT2FIX(0); + sg = DEFAULT_SG; + + switch (argc) { + case 2: + val2sg(vsg, sg); + case 1: + check_numeric(vjd, "jd"); + num2num_with_frac(jd, positive_inf); + } + + { + VALUE nth; + int rjd; + + decode_jd(jd, &nth, &rjd); + ret = d_simple_new_internal(klass, + nth, rjd, + sg, + 0, 0, 0, + HAVE_JD); + } + add_frac(); + return ret; +} + +/* + * call-seq: + * Date.ordinal(year = -4712, yday = 1, start = Date::ITALY) -> date + * + * Returns a new \Date object formed fom the arguments. + * + * With no arguments, returns the date for January 1, -4712: + * + * Date.ordinal.to_s # => "-4712-01-01" + * + * With argument +year+, returns the date for January 1 of that year: + * + * Date.ordinal(2001).to_s # => "2001-01-01" + * Date.ordinal(-2001).to_s # => "-2001-01-01" + * + * With positive argument +yday+ == +n+, + * returns the date for the +nth+ day of the given year: + * + * Date.ordinal(2001, 14).to_s # => "2001-01-14" + * + * With negative argument +yday+, counts backward from the end of the year: + * + * Date.ordinal(2001, -14).to_s # => "2001-12-18" + * + * Raises an exception if +yday+ is zero or out of range. + * + * See argument {start}[rdoc-ref:calendars.rdoc@Argument+start]. + * + * Related: Date.jd, Date.new. + */ +static VALUE +date_s_ordinal(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vd, vsg, y, fr, fr2, ret; + int d; + double sg; + + rb_scan_args(argc, argv, "03", &vy, &vd, &vsg); + + y = INT2FIX(-4712); + d = 1; + fr2 = INT2FIX(0); + sg = DEFAULT_SG; + + switch (argc) { + case 3: + val2sg(vsg, sg); + case 2: + check_numeric(vd, "yday"); + num2int_with_frac(d, positive_inf); + case 1: + check_numeric(vy, "year"); + y = vy; + } + + { + VALUE nth; + int ry, rd, rjd, ns; + + if (!valid_ordinal_p(y, d, sg, + &nth, &ry, + &rd, &rjd, + &ns)) + rb_raise(eDateError, "invalid date"); + + ret = d_simple_new_internal(klass, + nth, rjd, + sg, + 0, 0, 0, + HAVE_JD); + } + add_frac(); + return ret; +} + +/* + * Same as Date.new. + */ +static VALUE +date_s_civil(int argc, VALUE *argv, VALUE klass) +{ + return date_initialize(argc, argv, d_lite_s_alloc_simple(klass)); +} + +/* + * call-seq: + * Date.new(year = -4712, month = 1, mday = 1, start = Date::ITALY) -> date + * + * Returns a new \Date object constructed from the given arguments: + * + * Date.new(2022).to_s # => "2022-01-01" + * Date.new(2022, 2).to_s # => "2022-02-01" + * Date.new(2022, 2, 4).to_s # => "2022-02-04" + * + * Argument +month+ should be in range (1..12) or range (-12..-1); + * when the argument is negative, counts backward from the end of the year: + * + * Date.new(2022, -11, 4).to_s # => "2022-02-04" + * + * Argument +mday+ should be in range (1..n) or range (-n..-1) + * where +n+ is the number of days in the month; + * when the argument is negative, counts backward from the end of the month. + * + * See argument {start}[rdoc-ref:calendars.rdoc@Argument+start]. + * + * Related: Date.jd. + */ +static VALUE +date_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE vy, vm, vd, vsg, y, fr, fr2, ret; + int m, d; + double sg; + struct SimpleDateData *dat = rb_check_typeddata(self, &d_lite_type); + + if (!simple_dat_p(dat)) { + rb_raise(rb_eTypeError, "Date expected"); + } + + rb_scan_args(argc, argv, "04", &vy, &vm, &vd, &vsg); + + y = INT2FIX(-4712); + m = 1; + d = 1; + fr2 = INT2FIX(0); + sg = DEFAULT_SG; + + switch (argc) { + case 4: + val2sg(vsg, sg); + case 3: + check_numeric(vd, "day"); + num2int_with_frac(d, positive_inf); + case 2: + check_numeric(vm, "month"); + m = NUM2INT(vm); + case 1: + check_numeric(vy, "year"); + y = vy; + } + + if (guess_style(y, sg) < 0) { + VALUE nth; + int ry, rm, rd; + + if (!valid_gregorian_p(y, m, d, + &nth, &ry, + &rm, &rd)) + rb_raise(eDateError, "invalid date"); + + set_to_simple(self, dat, nth, 0, sg, ry, rm, rd, HAVE_CIVIL); + } + else { + VALUE nth; + int ry, rm, rd, rjd, ns; + + if (!valid_civil_p(y, m, d, sg, + &nth, &ry, + &rm, &rd, &rjd, + &ns)) + rb_raise(eDateError, "invalid date"); + + set_to_simple(self, dat, nth, rjd, sg, ry, rm, rd, HAVE_JD | HAVE_CIVIL); + } + ret = self; + add_frac(); + return ret; +} + +/* + * call-seq: + * Date.commercial(cwyear = -4712, cweek = 1, cwday = 1, start = Date::ITALY) -> date + * + * Returns a new \Date object constructed from the arguments. + * + * Argument +cwyear+ gives the year, and should be an integer. + * + * Argument +cweek+ gives the index of the week within the year, + * and should be in range (1..53) or (-53..-1); + * in some years, 53 or -53 will be out-of-range; + * if negative, counts backward from the end of the year: + * + * Date.commercial(2022, 1, 1).to_s # => "2022-01-03" + * Date.commercial(2022, 52, 1).to_s # => "2022-12-26" + * + * Argument +cwday+ gives the indes of the weekday within the week, + * and should be in range (1..7) or (-7..-1); + * 1 or -7 is Monday; + * if negative, counts backward from the end of the week: + * + * Date.commercial(2022, 1, 1).to_s # => "2022-01-03" + * Date.commercial(2022, 1, -7).to_s # => "2022-01-03" + * + * When +cweek+ is 1: + * + * - If January 1 is a Friday, Saturday, or Sunday, + * the first week begins in the week after: + * + * Date::ABBR_DAYNAMES[Date.new(2023, 1, 1).wday] # => "Sun" + * Date.commercial(2023, 1, 1).to_s # => "2023-01-02" + Date.commercial(2023, 1, 7).to_s # => "2023-01-08" + * + * - Otherwise, the first week is the week of January 1, + * which may mean some of the days fall on the year before: + * + * Date::ABBR_DAYNAMES[Date.new(2020, 1, 1).wday] # => "Wed" + * Date.commercial(2020, 1, 1).to_s # => "2019-12-30" + Date.commercial(2020, 1, 7).to_s # => "2020-01-05" + * + * See argument {start}[rdoc-ref:calendars.rdoc@Argument+start]. + * + * Related: Date.jd, Date.new, Date.ordinal. + */ +static VALUE +date_s_commercial(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vw, vd, vsg, y, fr, fr2, ret; + int w, d; + double sg; + + rb_scan_args(argc, argv, "04", &vy, &vw, &vd, &vsg); + + y = INT2FIX(-4712); + w = 1; + d = 1; + fr2 = INT2FIX(0); + sg = DEFAULT_SG; + + switch (argc) { + case 4: + val2sg(vsg, sg); + case 3: + check_numeric(vd, "cwday"); + num2int_with_frac(d, positive_inf); + case 2: + check_numeric(vw, "cweek"); + w = NUM2INT(vw); + case 1: + check_numeric(vy, "year"); + y = vy; + } + + { + VALUE nth; + int ry, rw, rd, rjd, ns; + + if (!valid_commercial_p(y, w, d, sg, + &nth, &ry, + &rw, &rd, &rjd, + &ns)) + rb_raise(eDateError, "invalid date"); + + ret = d_simple_new_internal(klass, + nth, rjd, + sg, + 0, 0, 0, + HAVE_JD); + } + add_frac(); + return ret; +} + +#ifndef NDEBUG +/* :nodoc: */ +static VALUE +date_s_weeknum(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vw, vd, vf, vsg, y, fr, fr2, ret; + int w, d, f; + double sg; + + rb_scan_args(argc, argv, "05", &vy, &vw, &vd, &vf, &vsg); + + y = INT2FIX(-4712); + w = 0; + d = 1; + f = 0; + fr2 = INT2FIX(0); + sg = DEFAULT_SG; + + switch (argc) { + case 5: + val2sg(vsg, sg); + case 4: + f = NUM2INT(vf); + case 3: + num2int_with_frac(d, positive_inf); + case 2: + w = NUM2INT(vw); + case 1: + y = vy; + } + + { + VALUE nth; + int ry, rw, rd, rjd, ns; + + if (!valid_weeknum_p(y, w, d, f, sg, + &nth, &ry, + &rw, &rd, &rjd, + &ns)) + rb_raise(eDateError, "invalid date"); + + ret = d_simple_new_internal(klass, + nth, rjd, + sg, + 0, 0, 0, + HAVE_JD); + } + add_frac(); + return ret; +} + +/* :nodoc: */ +static VALUE +date_s_nth_kday(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vm, vn, vk, vsg, y, fr, fr2, ret; + int m, n, k; + double sg; + + rb_scan_args(argc, argv, "05", &vy, &vm, &vn, &vk, &vsg); + + y = INT2FIX(-4712); + m = 1; + n = 1; + k = 1; + fr2 = INT2FIX(0); + sg = DEFAULT_SG; + + switch (argc) { + case 5: + val2sg(vsg, sg); + case 4: + num2int_with_frac(k, positive_inf); + case 3: + n = NUM2INT(vn); + case 2: + m = NUM2INT(vm); + case 1: + y = vy; + } + + { + VALUE nth; + int ry, rm, rn, rk, rjd, ns; + + if (!valid_nth_kday_p(y, m, n, k, sg, + &nth, &ry, + &rm, &rn, &rk, &rjd, + &ns)) + rb_raise(eDateError, "invalid date"); + + ret = d_simple_new_internal(klass, + nth, rjd, + sg, + 0, 0, 0, + HAVE_JD); + } + add_frac(); + return ret; +} +#endif + +#if !defined(HAVE_GMTIME_R) +static struct tm* +gmtime_r(const time_t *t, struct tm *tm) +{ + auto struct tm *tmp = gmtime(t); + if (tmp) + *tm = *tmp; + return tmp; +} + +static struct tm* +localtime_r(const time_t *t, struct tm *tm) +{ + auto struct tm *tmp = localtime(t); + if (tmp) + *tm = *tmp; + return tmp; +} +#endif + +static void set_sg(union DateData *, double); + +/* + * call-seq: + * Date.today(start = Date::ITALY) -> date + * + * Returns a new \Date object constructed from the present date: + * + * Date.today.to_s # => "2022-07-06" + * + * See argument {start}[rdoc-ref:calendars.rdoc@Argument+start]. + * + */ +static VALUE +date_s_today(int argc, VALUE *argv, VALUE klass) +{ + VALUE vsg, nth, ret; + double sg; + time_t t; + struct tm tm; + int y, ry, m, d; + + rb_scan_args(argc, argv, "01", &vsg); + + if (argc < 1) + sg = DEFAULT_SG; + else + val2sg(vsg, sg); + + if (time(&t) == -1) + rb_sys_fail("time"); + tzset(); + if (!localtime_r(&t, &tm)) + rb_sys_fail("localtime"); + + y = tm.tm_year + 1900; + m = tm.tm_mon + 1; + d = tm.tm_mday; + + decode_year(INT2FIX(y), -1, &nth, &ry); + + ret = d_simple_new_internal(klass, + nth, 0, + GREGORIAN, + ry, m, d, + HAVE_CIVIL); + { + get_d1(ret); + set_sg(dat, sg); + } + return ret; +} + +#define set_hash0(k,v) rb_hash_aset(hash, k, v) +#define ref_hash0(k) rb_hash_aref(hash, k) +#define del_hash0(k) rb_hash_delete(hash, k) + +#define sym(x) ID2SYM(rb_intern(x"")) + +#define set_hash(k,v) set_hash0(sym(k), v) +#define ref_hash(k) ref_hash0(sym(k)) +#define del_hash(k) del_hash0(sym(k)) + +static VALUE +rt_rewrite_frags(VALUE hash) +{ + VALUE seconds; + + seconds = del_hash("seconds"); + if (!NIL_P(seconds)) { + VALUE offset, d, h, min, s, fr; + + offset = ref_hash("offset"); + if (!NIL_P(offset)) + seconds = f_add(seconds, offset); + + d = f_idiv(seconds, INT2FIX(DAY_IN_SECONDS)); + fr = f_mod(seconds, INT2FIX(DAY_IN_SECONDS)); + + h = f_idiv(fr, INT2FIX(HOUR_IN_SECONDS)); + fr = f_mod(fr, INT2FIX(HOUR_IN_SECONDS)); + + min = f_idiv(fr, INT2FIX(MINUTE_IN_SECONDS)); + fr = f_mod(fr, INT2FIX(MINUTE_IN_SECONDS)); + + s = f_idiv(fr, INT2FIX(1)); + fr = f_mod(fr, INT2FIX(1)); + + set_hash("jd", f_add(UNIX_EPOCH_IN_CJD, d)); + set_hash("hour", h); + set_hash("min", min); + set_hash("sec", s); + set_hash("sec_fraction", fr); + } + return hash; +} + +static VALUE d_lite_year(VALUE); +static VALUE d_lite_wday(VALUE); +static VALUE d_lite_jd(VALUE); + +static VALUE +rt_complete_frags(VALUE klass, VALUE hash) +{ + static VALUE tab = Qnil; + int g; + long e; + VALUE k, a, d; + + if (NIL_P(tab)) { + tab = f_frozen_ary(11, + f_frozen_ary(2, + sym("time"), + f_frozen_ary(3, + sym("hour"), + sym("min"), + sym("sec"))), + f_frozen_ary(2, + Qnil, + f_frozen_ary(1, + sym("jd"))), + f_frozen_ary(2, + sym("ordinal"), + f_frozen_ary(5, + sym("year"), + sym("yday"), + sym("hour"), + sym("min"), + sym("sec"))), + f_frozen_ary(2, + sym("civil"), + f_frozen_ary(6, + sym("year"), + sym("mon"), + sym("mday"), + sym("hour"), + sym("min"), + sym("sec"))), + f_frozen_ary(2, + sym("commercial"), + f_frozen_ary(6, + sym("cwyear"), + sym("cweek"), + sym("cwday"), + sym("hour"), + sym("min"), + sym("sec"))), + f_frozen_ary(2, + sym("wday"), + f_frozen_ary(4, + sym("wday"), + sym("hour"), + sym("min"), + sym("sec"))), + f_frozen_ary(2, + sym("wnum0"), + f_frozen_ary(6, + sym("year"), + sym("wnum0"), + sym("wday"), + sym("hour"), + sym("min"), + sym("sec"))), + f_frozen_ary(2, + sym("wnum1"), + f_frozen_ary(6, + sym("year"), + sym("wnum1"), + sym("wday"), + sym("hour"), + sym("min"), + sym("sec"))), + f_frozen_ary(2, + Qnil, + f_frozen_ary(6, + sym("cwyear"), + sym("cweek"), + sym("wday"), + sym("hour"), + sym("min"), + sym("sec"))), + f_frozen_ary(2, + Qnil, + f_frozen_ary(6, + sym("year"), + sym("wnum0"), + sym("cwday"), + sym("hour"), + sym("min"), + sym("sec"))), + f_frozen_ary(2, + Qnil, + f_frozen_ary(6, + sym("year"), + sym("wnum1"), + sym("cwday"), + sym("hour"), + sym("min"), + sym("sec")))); + rb_gc_register_mark_object(tab); + } + + { + long i, eno = 0, idx = 0; + + for (i = 0; i < RARRAY_LEN(tab); i++) { + VALUE x, a; + + x = RARRAY_AREF(tab, i); + a = RARRAY_AREF(x, 1); + + { + long j, n = 0; + + for (j = 0; j < RARRAY_LEN(a); j++) + if (!NIL_P(ref_hash0(RARRAY_AREF(a, j)))) + n++; + if (n > eno) { + eno = n; + idx = i; + } + } + } + if (eno == 0) + g = 0; + else { + g = 1; + k = RARRAY_AREF(RARRAY_AREF(tab, idx), 0); + a = RARRAY_AREF(RARRAY_AREF(tab, idx), 1); + e = eno; + } + } + + d = Qnil; + + if (g && !NIL_P(k) && (RARRAY_LEN(a) - e)) { + if (k == sym("ordinal")) { + if (NIL_P(ref_hash("year"))) { + if (NIL_P(d)) + d = date_s_today(0, (VALUE *)0, cDate); + set_hash("year", d_lite_year(d)); + } + if (NIL_P(ref_hash("yday"))) + set_hash("yday", INT2FIX(1)); + } + else if (k == sym("civil")) { + long i; + + for (i = 0; i < RARRAY_LEN(a); i++) { + VALUE e = RARRAY_AREF(a, i); + + if (!NIL_P(ref_hash0(e))) + break; + if (NIL_P(d)) + d = date_s_today(0, (VALUE *)0, cDate); + set_hash0(e, rb_funcall(d, SYM2ID(e), 0)); + } + if (NIL_P(ref_hash("mon"))) + set_hash("mon", INT2FIX(1)); + if (NIL_P(ref_hash("mday"))) + set_hash("mday", INT2FIX(1)); + } + else if (k == sym("commercial")) { + long i; + + for (i = 0; i < RARRAY_LEN(a); i++) { + VALUE e = RARRAY_AREF(a, i); + + if (!NIL_P(ref_hash0(e))) + break; + if (NIL_P(d)) + d = date_s_today(0, (VALUE *)0, cDate); + set_hash0(e, rb_funcall(d, SYM2ID(e), 0)); + } + if (NIL_P(ref_hash("cweek"))) + set_hash("cweek", INT2FIX(1)); + if (NIL_P(ref_hash("cwday"))) + set_hash("cwday", INT2FIX(1)); + } + else if (k == sym("wday")) { + if (NIL_P(d)) + d = date_s_today(0, (VALUE *)0, cDate); + set_hash("jd", d_lite_jd(f_add(f_sub(d, + d_lite_wday(d)), + ref_hash("wday")))); + } + else if (k == sym("wnum0")) { + long i; + + for (i = 0; i < RARRAY_LEN(a); i++) { + VALUE e = RARRAY_AREF(a, i); + + if (!NIL_P(ref_hash0(e))) + break; + if (NIL_P(d)) + d = date_s_today(0, (VALUE *)0, cDate); + set_hash0(e, rb_funcall(d, SYM2ID(e), 0)); + } + if (NIL_P(ref_hash("wnum0"))) + set_hash("wnum0", INT2FIX(0)); + if (NIL_P(ref_hash("wday"))) + set_hash("wday", INT2FIX(0)); + } + else if (k == sym("wnum1")) { + long i; + + for (i = 0; i < RARRAY_LEN(a); i++) { + VALUE e = RARRAY_AREF(a, i); + + if (!NIL_P(ref_hash0(e))) + break; + if (NIL_P(d)) + d = date_s_today(0, (VALUE *)0, cDate); + set_hash0(e, rb_funcall(d, SYM2ID(e), 0)); + } + if (NIL_P(ref_hash("wnum1"))) + set_hash("wnum1", INT2FIX(0)); + if (NIL_P(ref_hash("wday"))) + set_hash("wday", INT2FIX(1)); + } + } + + if (g && k == sym("time")) { + if (f_le_p(klass, cDateTime)) { + if (NIL_P(d)) + d = date_s_today(0, (VALUE *)0, cDate); + if (NIL_P(ref_hash("jd"))) + set_hash("jd", d_lite_jd(d)); + } + } + + if (NIL_P(ref_hash("hour"))) + set_hash("hour", INT2FIX(0)); + if (NIL_P(ref_hash("min"))) + set_hash("min", INT2FIX(0)); + if (NIL_P(ref_hash("sec"))) + set_hash("sec", INT2FIX(0)); + else if (f_gt_p(ref_hash("sec"), INT2FIX(59))) + set_hash("sec", INT2FIX(59)); + + return hash; +} + +static VALUE +rt__valid_jd_p(VALUE jd, VALUE sg) +{ + return jd; +} + +static VALUE +rt__valid_ordinal_p(VALUE y, VALUE d, VALUE sg) +{ + VALUE nth, rjd2; + int ry, rd, rjd, ns; + + if (!valid_ordinal_p(y, NUM2INT(d), NUM2DBL(sg), + &nth, &ry, + &rd, &rjd, + &ns)) + return Qnil; + encode_jd(nth, rjd, &rjd2); + return rjd2; +} + +static VALUE +rt__valid_civil_p(VALUE y, VALUE m, VALUE d, VALUE sg) +{ + VALUE nth, rjd2; + int ry, rm, rd, rjd, ns; + + if (!valid_civil_p(y, NUM2INT(m), NUM2INT(d), NUM2DBL(sg), + &nth, &ry, + &rm, &rd, &rjd, + &ns)) + return Qnil; + encode_jd(nth, rjd, &rjd2); + return rjd2; +} + +static VALUE +rt__valid_commercial_p(VALUE y, VALUE w, VALUE d, VALUE sg) +{ + VALUE nth, rjd2; + int ry, rw, rd, rjd, ns; + + if (!valid_commercial_p(y, NUM2INT(w), NUM2INT(d), NUM2DBL(sg), + &nth, &ry, + &rw, &rd, &rjd, + &ns)) + return Qnil; + encode_jd(nth, rjd, &rjd2); + return rjd2; +} + +static VALUE +rt__valid_weeknum_p(VALUE y, VALUE w, VALUE d, VALUE f, VALUE sg) +{ + VALUE nth, rjd2; + int ry, rw, rd, rjd, ns; + + if (!valid_weeknum_p(y, NUM2INT(w), NUM2INT(d), NUM2INT(f), NUM2DBL(sg), + &nth, &ry, + &rw, &rd, &rjd, + &ns)) + return Qnil; + encode_jd(nth, rjd, &rjd2); + return rjd2; +} + +static VALUE +rt__valid_date_frags_p(VALUE hash, VALUE sg) +{ + { + VALUE vjd; + + if (!NIL_P(vjd = ref_hash("jd"))) { + VALUE jd = rt__valid_jd_p(vjd, sg); + if (!NIL_P(jd)) + return jd; + } + } + + { + VALUE year, yday; + + if (!NIL_P(yday = ref_hash("yday")) && + !NIL_P(year = ref_hash("year"))) { + VALUE jd = rt__valid_ordinal_p(year, yday, sg); + if (!NIL_P(jd)) + return jd; + } + } + + { + VALUE year, mon, mday; + + if (!NIL_P(mday = ref_hash("mday")) && + !NIL_P(mon = ref_hash("mon")) && + !NIL_P(year = ref_hash("year"))) { + VALUE jd = rt__valid_civil_p(year, mon, mday, sg); + if (!NIL_P(jd)) + return jd; + } + } + + { + VALUE year, week, wday; + + wday = ref_hash("cwday"); + if (NIL_P(wday)) { + wday = ref_hash("wday"); + if (!NIL_P(wday)) + if (f_zero_p(wday)) + wday = INT2FIX(7); + } + + if (!NIL_P(wday) && + !NIL_P(week = ref_hash("cweek")) && + !NIL_P(year = ref_hash("cwyear"))) { + VALUE jd = rt__valid_commercial_p(year, week, wday, sg); + if (!NIL_P(jd)) + return jd; + } + } + + { + VALUE year, week, wday; + + wday = ref_hash("wday"); + if (NIL_P(wday)) { + wday = ref_hash("cwday"); + if (!NIL_P(wday)) + if (f_eqeq_p(wday, INT2FIX(7))) + wday = INT2FIX(0); + } + + if (!NIL_P(wday) && + !NIL_P(week = ref_hash("wnum0")) && + !NIL_P(year = ref_hash("year"))) { + VALUE jd = rt__valid_weeknum_p(year, week, wday, INT2FIX(0), sg); + if (!NIL_P(jd)) + return jd; + } + } + + { + VALUE year, week, wday; + + wday = ref_hash("wday"); + if (NIL_P(wday)) + wday = ref_hash("cwday"); + if (!NIL_P(wday)) + wday = f_mod(f_sub(wday, INT2FIX(1)), + INT2FIX(7)); + + if (!NIL_P(wday) && + !NIL_P(week = ref_hash("wnum1")) && + !NIL_P(year = ref_hash("year"))) { + VALUE jd = rt__valid_weeknum_p(year, week, wday, INT2FIX(1), sg); + if (!NIL_P(jd)) + return jd; + } + } + return Qnil; +} + +static VALUE +d_new_by_frags(VALUE klass, VALUE hash, VALUE sg) +{ + VALUE jd; + + if (!c_valid_start_p(NUM2DBL(sg))) { + sg = INT2FIX(DEFAULT_SG); + rb_warning("invalid start is ignored"); + } + + if (NIL_P(hash)) + rb_raise(eDateError, "invalid date"); + + if (NIL_P(ref_hash("jd")) && + NIL_P(ref_hash("yday")) && + !NIL_P(ref_hash("year")) && + !NIL_P(ref_hash("mon")) && + !NIL_P(ref_hash("mday"))) + jd = rt__valid_civil_p(ref_hash("year"), + ref_hash("mon"), + ref_hash("mday"), sg); + else { + hash = rt_rewrite_frags(hash); + hash = rt_complete_frags(klass, hash); + jd = rt__valid_date_frags_p(hash, sg); + } + + if (NIL_P(jd)) + rb_raise(eDateError, "invalid date"); + { + VALUE nth; + int rjd; + + decode_jd(jd, &nth, &rjd); + return d_simple_new_internal(klass, + nth, rjd, + NUM2DBL(sg), + 0, 0, 0, + HAVE_JD); + } +} + +VALUE date__strptime(const char *str, size_t slen, + const char *fmt, size_t flen, VALUE hash); + +static VALUE +date_s__strptime_internal(int argc, VALUE *argv, VALUE klass, + const char *default_fmt) +{ + VALUE vstr, vfmt, hash; + const char *str, *fmt; + size_t slen, flen; + + rb_scan_args(argc, argv, "11", &vstr, &vfmt); + + StringValue(vstr); + if (!rb_enc_str_asciicompat_p(vstr)) + rb_raise(rb_eArgError, + "string should have ASCII compatible encoding"); + str = RSTRING_PTR(vstr); + slen = RSTRING_LEN(vstr); + if (argc < 2) { + fmt = default_fmt; + flen = strlen(default_fmt); + } + else { + StringValue(vfmt); + if (!rb_enc_str_asciicompat_p(vfmt)) + rb_raise(rb_eArgError, + "format should have ASCII compatible encoding"); + fmt = RSTRING_PTR(vfmt); + flen = RSTRING_LEN(vfmt); + } + hash = rb_hash_new(); + if (NIL_P(date__strptime(str, slen, fmt, flen, hash))) + return Qnil; + + { + VALUE zone = ref_hash("zone"); + VALUE left = ref_hash("leftover"); + + if (!NIL_P(zone)) { + rb_enc_copy(zone, vstr); + set_hash("zone", zone); + } + if (!NIL_P(left)) { + rb_enc_copy(left, vstr); + set_hash("leftover", left); + } + } + + return hash; +} + +/* + * call-seq: + * Date._strptime(string, format = '%F') -> hash + * + * Returns a hash of values parsed from +string+ + * according to the given +format+: + * + * Date._strptime('2001-02-03', '%Y-%m-%d') # => {:year=>2001, :mon=>2, :mday=>3} + * + * For other formats, see + * {Formats for Dates and Times}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html]. + * (Unlike Date.strftime, does not support flags and width.) + * + * See also {strptime(3)}[https://man7.org/linux/man-pages/man3/strptime.3.html]. + * + * Related: Date.strptime (returns a \Date object). + */ +static VALUE +date_s__strptime(int argc, VALUE *argv, VALUE klass) +{ + return date_s__strptime_internal(argc, argv, klass, "%F"); +} + +/* + * call-seq: + * Date.strptime(string = '-4712-01-01', format = '%F', start = Date::ITALY) -> date + * + * Returns a new \Date object with values parsed from +string+, + * according to the given +format+: + * + * Date.strptime('2001-02-03', '%Y-%m-%d') # => # + * Date.strptime('03-02-2001', '%d-%m-%Y') # => # + * Date.strptime('2001-034', '%Y-%j') # => # + * Date.strptime('2001-W05-6', '%G-W%V-%u') # => # + * Date.strptime('2001 04 6', '%Y %U %w') # => # + * Date.strptime('2001 05 6', '%Y %W %u') # => # + * Date.strptime('sat3feb01', '%a%d%b%y') # => # + * + * For other formats, see + * {Formats for Dates and Times}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html]. + * (Unlike Date.strftime, does not support flags and width.) + * + * See argument {start}[rdoc-ref:calendars.rdoc@Argument+start]. + * + * See also {strptime(3)}[https://man7.org/linux/man-pages/man3/strptime.3.html]. + * + * Related: Date._strptime (returns a hash). + */ +static VALUE +date_s_strptime(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, fmt, sg; + + rb_scan_args(argc, argv, "03", &str, &fmt, &sg); + + switch (argc) { + case 0: + str = rb_str_new2(JULIAN_EPOCH_DATE); + case 1: + fmt = rb_str_new2("%F"); + case 2: + sg = INT2FIX(DEFAULT_SG); + } + + { + VALUE argv2[2], hash; + + argv2[0] = str; + argv2[1] = fmt; + hash = date_s__strptime(2, argv2, klass); + return d_new_by_frags(klass, hash, sg); + } +} + +VALUE date__parse(VALUE str, VALUE comp); + +static size_t +get_limit(VALUE opt) +{ + if (!NIL_P(opt)) { + VALUE limit = rb_hash_aref(opt, ID2SYM(rb_intern("limit"))); + if (NIL_P(limit)) return SIZE_MAX; + return NUM2SIZET(limit); + } + return 128; +} + +#ifndef HAVE_RB_CATEGORY_WARN +#define rb_category_warn(category, fmt) rb_warn(fmt) +#endif + +static void +check_limit(VALUE str, VALUE opt) +{ + size_t slen, limit; + if (NIL_P(str)) return; + StringValue(str); + slen = RSTRING_LEN(str); + limit = get_limit(opt); + if (slen > limit) { + rb_raise(rb_eArgError, + "string length (%"PRI_SIZE_PREFIX"u) exceeds the limit %"PRI_SIZE_PREFIX"u", slen, limit); + } +} + +static VALUE +date_s__parse_internal(int argc, VALUE *argv, VALUE klass) +{ + VALUE vstr, vcomp, hash, opt; + + argc = rb_scan_args(argc, argv, "11:", &vstr, &vcomp, &opt); + check_limit(vstr, opt); + StringValue(vstr); + if (!rb_enc_str_asciicompat_p(vstr)) + rb_raise(rb_eArgError, + "string should have ASCII compatible encoding"); + if (argc < 2) + vcomp = Qtrue; + + hash = date__parse(vstr, vcomp); + + return hash; +} + +/* + * call-seq: + * Date._parse(string, comp = true, limit: 128) -> hash + * + * Note: + * This method recognizes many forms in +string+, + * but it is not a validator. + * For formats, see + * {"Specialized Format Strings" in Formats for Dates and Times}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-Specialized+Format+Strings] + * + * If +string+ does not specify a valid date, + * the result is unpredictable; + * consider using Date._strptime instead. + * + * Returns a hash of values parsed from +string+: + * + * Date._parse('2001-02-03') # => {:year=>2001, :mon=>2, :mday=>3} + * + * If +comp+ is +true+ and the given year is in the range (0..99), + * the current century is supplied; + * otherwise, the year is taken as given: + * + * Date._parse('01-02-03', true) # => {:year=>2001, :mon=>2, :mday=>3} + * Date._parse('01-02-03', false) # => {:year=>1, :mon=>2, :mday=>3} + * + * See argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date.parse(returns a \Date object). + */ +static VALUE +date_s__parse(int argc, VALUE *argv, VALUE klass) +{ + return date_s__parse_internal(argc, argv, klass); +} + +/* + * call-seq: + * Date.parse(string = '-4712-01-01', comp = true, start = Date::ITALY, limit: 128) -> date + * + * Note: + * This method recognizes many forms in +string+, + * but it is not a validator. + * For formats, see + * {"Specialized Format Strings" in Formats for Dates and Times}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-Specialized+Format+Strings] + * If +string+ does not specify a valid date, + * the result is unpredictable; + * consider using Date._strptime instead. + * + * Returns a new \Date object with values parsed from +string+: + * + * Date.parse('2001-02-03') # => # + * Date.parse('20010203') # => # + * Date.parse('3rd Feb 2001') # => # + * + * If +comp+ is +true+ and the given year is in the range (0..99), + * the current century is supplied; + * otherwise, the year is taken as given: + * + * Date.parse('01-02-03', true) # => # + * Date.parse('01-02-03', false) # => # + * + * See: + * + * - Argument {start}[rdoc-ref:calendars.rdoc@Argument+start]. + * - Argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date._parse (returns a hash). + */ +static VALUE +date_s_parse(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, comp, sg, opt; + + argc = rb_scan_args(argc, argv, "03:", &str, &comp, &sg, &opt); + + switch (argc) { + case 0: + str = rb_str_new2(JULIAN_EPOCH_DATE); + case 1: + comp = Qtrue; + case 2: + sg = INT2FIX(DEFAULT_SG); + } + + { + int argc2 = 2; + VALUE argv2[3], hash; + argv2[0] = str; + argv2[1] = comp; + if (!NIL_P(opt)) argv2[argc2++] = opt; + hash = date_s__parse(argc2, argv2, klass); + return d_new_by_frags(klass, hash, sg); + } +} + +VALUE date__iso8601(VALUE); +VALUE date__rfc3339(VALUE); +VALUE date__xmlschema(VALUE); +VALUE date__rfc2822(VALUE); +VALUE date__httpdate(VALUE); +VALUE date__jisx0301(VALUE); + +/* + * call-seq: + * Date._iso8601(string, limit: 128) -> hash + * + * Returns a hash of values parsed from +string+, which should contain + * an {ISO 8601 formatted date}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-ISO+8601+Format+Specifications]: + * + * d = Date.new(2001, 2, 3) + * s = d.iso8601 # => "2001-02-03" + * Date._iso8601(s) # => {:mday=>3, :year=>2001, :mon=>2} + * + * See argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date.iso8601 (returns a \Date object). + */ +static VALUE +date_s__iso8601(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + check_limit(str, opt); + + return date__iso8601(str); +} + +/* + * call-seq: + * Date.iso8601(string = '-4712-01-01', start = Date::ITALY, limit: 128) -> date + * + * Returns a new \Date object with values parsed from +string+, + * which should contain + * an {ISO 8601 formatted date}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-ISO+8601+Format+Specifications]: + * + * d = Date.new(2001, 2, 3) + * s = d.iso8601 # => "2001-02-03" + * Date.iso8601(s) # => # + * + * See: + * + * - Argument {start}[rdoc-ref:calendars.rdoc@Argument+start]. + * - Argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date._iso8601 (returns a hash). + */ +static VALUE +date_s_iso8601(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg, opt; + + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + + switch (argc) { + case 0: + str = rb_str_new2(JULIAN_EPOCH_DATE); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + hash = date_s__iso8601(argc2, argv2, klass); + return d_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * Date._rfc3339(string, limit: 128) -> hash + * + * Returns a hash of values parsed from +string+, which should be a valid + * {RFC 3339 format}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-RFC+3339+Format]: + * + * d = Date.new(2001, 2, 3) + * s = d.rfc3339 # => "2001-02-03T00:00:00+00:00" + * Date._rfc3339(s) + * # => {:year=>2001, :mon=>2, :mday=>3, :hour=>0, :min=>0, :sec=>0, :zone=>"+00:00", :offset=>0} + * + * See argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date.rfc3339 (returns a \Date object). + */ +static VALUE +date_s__rfc3339(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + check_limit(str, opt); + + return date__rfc3339(str); +} + +/* + * call-seq: + * Date.rfc3339(string = '-4712-01-01T00:00:00+00:00', start = Date::ITALY, limit: 128) -> date + * + * Returns a new \Date object with values parsed from +string+, + * which should be a valid + * {RFC 3339 format}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-RFC+3339+Format]: + * + * d = Date.new(2001, 2, 3) + * s = d.rfc3339 # => "2001-02-03T00:00:00+00:00" + * Date.rfc3339(s) # => # + * + * See: + * + * - Argument {start}[rdoc-ref:calendars.rdoc@Argument+start]. + * - Argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date._rfc3339 (returns a hash). + */ +static VALUE +date_s_rfc3339(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg, opt; + + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + + switch (argc) { + case 0: + str = rb_str_new2(JULIAN_EPOCH_DATETIME); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + hash = date_s__rfc3339(argc2, argv2, klass); + return d_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * Date._xmlschema(string, limit: 128) -> hash + * + * Returns a hash of values parsed from +string+, which should be a valid + * XML date format: + * + * d = Date.new(2001, 2, 3) + * s = d.xmlschema # => "2001-02-03" + * Date._xmlschema(s) # => {:year=>2001, :mon=>2, :mday=>3} + * + * See argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date.xmlschema (returns a \Date object). + */ +static VALUE +date_s__xmlschema(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + check_limit(str, opt); + + return date__xmlschema(str); +} + +/* + * call-seq: + * Date.xmlschema(string = '-4712-01-01', start = Date::ITALY, limit: 128) -> date + * + * Returns a new \Date object with values parsed from +string+, + * which should be a valid XML date format: + * + * d = Date.new(2001, 2, 3) + * s = d.xmlschema # => "2001-02-03" + * Date.xmlschema(s) # => # + * + * See: + * + * - Argument {start}[rdoc-ref:calendars.rdoc@Argument+start]. + * - Argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date._xmlschema (returns a hash). + */ +static VALUE +date_s_xmlschema(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg, opt; + + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + + switch (argc) { + case 0: + str = rb_str_new2(JULIAN_EPOCH_DATE); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + hash = date_s__xmlschema(argc2, argv2, klass); + return d_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * Date._rfc2822(string, limit: 128) -> hash + * + * Returns a hash of values parsed from +string+, which should be a valid + * {RFC 2822 date format}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-RFC+2822+Format]: + * + * d = Date.new(2001, 2, 3) + * s = d.rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" + * Date._rfc2822(s) + * # => {:wday=>6, :mday=>3, :mon=>2, :year=>2001, :hour=>0, :min=>0, :sec=>0, :zone=>"+0000", :offset=>0} + * + * See argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date.rfc2822 (returns a \Date object). + */ +static VALUE +date_s__rfc2822(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + check_limit(str, opt); + + return date__rfc2822(str); +} + +/* + * call-seq: + * Date.rfc2822(string = 'Mon, 1 Jan -4712 00:00:00 +0000', start = Date::ITALY, limit: 128) -> date + * + * Returns a new \Date object with values parsed from +string+, + * which should be a valid + * {RFC 2822 date format}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-RFC+2822+Format]: + * + * d = Date.new(2001, 2, 3) + * s = d.rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" + * Date.rfc2822(s) # => # + * + * See: + * + * - Argument {start}[rdoc-ref:calendars.rdoc@Argument+start]. + * - Argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date._rfc2822 (returns a hash). + */ +static VALUE +date_s_rfc2822(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg, opt; + + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + + switch (argc) { + case 0: + str = rb_str_new2(JULIAN_EPOCH_DATETIME_RFC3339); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + hash = date_s__rfc2822(argc2, argv2, klass); + return d_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * Date._httpdate(string, limit: 128) -> hash + * + * Returns a hash of values parsed from +string+, which should be a valid + * {HTTP date format}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-HTTP+Format]: + * + * d = Date.new(2001, 2, 3) + * s = d.httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" + * Date._httpdate(s) + * # => {:wday=>6, :mday=>3, :mon=>2, :year=>2001, :hour=>0, :min=>0, :sec=>0, :zone=>"GMT", :offset=>0} + * + * Related: Date.httpdate (returns a \Date object). + */ +static VALUE +date_s__httpdate(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + check_limit(str, opt); + + return date__httpdate(str); +} + +/* + * call-seq: + * Date.httpdate(string = 'Mon, 01 Jan -4712 00:00:00 GMT', start = Date::ITALY, limit: 128) -> date + * + * Returns a new \Date object with values parsed from +string+, + * which should be a valid + * {HTTP date format}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-HTTP+Format]: + * + * d = Date.new(2001, 2, 3) + s = d.httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" + Date.httpdate(s) # => # + * + * See: + * + * - Argument {start}[rdoc-ref:calendars.rdoc@Argument+start]. + * - Argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date._httpdate (returns a hash). + */ +static VALUE +date_s_httpdate(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg, opt; + + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + + switch (argc) { + case 0: + str = rb_str_new2(JULIAN_EPOCH_DATETIME_HTTPDATE); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + hash = date_s__httpdate(argc2, argv2, klass); + return d_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * Date._jisx0301(string, limit: 128) -> hash + * + * Returns a hash of values parsed from +string+, which should be a valid + * {JIS X 0301 date format}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-JIS+X+0301+Format]: + * + * d = Date.new(2001, 2, 3) + * s = d.jisx0301 # => "H13.02.03" + * Date._jisx0301(s) # => {:year=>2001, :mon=>2, :mday=>3} + * + * See argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date.jisx0301 (returns a \Date object). + */ +static VALUE +date_s__jisx0301(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + check_limit(str, opt); + + return date__jisx0301(str); +} + +/* + * call-seq: + * Date.jisx0301(string = '-4712-01-01', start = Date::ITALY, limit: 128) -> date + * + * Returns a new \Date object with values parsed from +string+, + * which should be a valid {JIS X 0301 format}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-JIS+X+0301+Format]: + * + * d = Date.new(2001, 2, 3) + * s = d.jisx0301 # => "H13.02.03" + * Date.jisx0301(s) # => # + * + * For no-era year, legacy format, Heisei is assumed. + * + * Date.jisx0301('13.02.03') # => # + * + * See: + * + * - Argument {start}[rdoc-ref:calendars.rdoc@Argument+start]. + * - Argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date._jisx0301 (returns a hash). + */ +static VALUE +date_s_jisx0301(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg, opt; + + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + + switch (argc) { + case 0: + str = rb_str_new2(JULIAN_EPOCH_DATE); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + hash = date_s__jisx0301(argc2, argv2, klass); + return d_new_by_frags(klass, hash, sg); + } +} + +static VALUE +dup_obj(VALUE self) +{ + get_d1a(self); + + if (simple_dat_p(adat)) { + VALUE new = d_lite_s_alloc_simple(rb_obj_class(self)); + { + get_d1b(new); + bdat->s = adat->s; + RB_OBJ_WRITTEN(new, Qundef, bdat->s.nth); + return new; + } + } + else { + VALUE new = d_lite_s_alloc_complex(rb_obj_class(self)); + { + get_d1b(new); + bdat->c = adat->c; + RB_OBJ_WRITTEN(new, Qundef, bdat->c.nth); + RB_OBJ_WRITTEN(new, Qundef, bdat->c.sf); + return new; + } + } +} + +static VALUE +dup_obj_as_complex(VALUE self) +{ + get_d1a(self); + + if (simple_dat_p(adat)) { + VALUE new = d_lite_s_alloc_complex(rb_obj_class(self)); + { + get_d1b(new); + copy_simple_to_complex(new, &bdat->c, &adat->s); + bdat->c.flags |= HAVE_DF | COMPLEX_DAT; + return new; + } + } + else { + VALUE new = d_lite_s_alloc_complex(rb_obj_class(self)); + { + get_d1b(new); + bdat->c = adat->c; + RB_OBJ_WRITTEN(new, Qundef, bdat->c.nth); + RB_OBJ_WRITTEN(new, Qundef, bdat->c.sf); + return new; + } + } +} + +#define val2off(vof,iof) \ +do {\ + if (!offset_to_sec(vof, &iof)) {\ + iof = 0;\ + rb_warning("invalid offset is ignored");\ + }\ +} while (0) + +#if 0 +static VALUE +d_lite_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE jd, vjd, vdf, sf, vsf, vof, vsg; + int df, of; + double sg; + + rb_check_frozen(self); + + rb_scan_args(argc, argv, "05", &vjd, &vdf, &vsf, &vof, &vsg); + + jd = INT2FIX(0); + df = 0; + sf = INT2FIX(0); + of = 0; + sg = DEFAULT_SG; + + switch (argc) { + case 5: + val2sg(vsg, sg); + case 4: + val2off(vof, of); + case 3: + sf = vsf; + if (f_lt_p(sf, INT2FIX(0)) || + f_ge_p(sf, INT2FIX(SECOND_IN_NANOSECONDS))) + rb_raise(eDateError, "invalid second fraction"); + case 2: + df = NUM2INT(vdf); + if (df < 0 || df >= DAY_IN_SECONDS) + rb_raise(eDateError, "invalid day fraction"); + case 1: + jd = vjd; + } + + { + VALUE nth; + int rjd; + + get_d1(self); + + decode_jd(jd, &nth, &rjd); + if (!df && f_zero_p(sf) && !of) { + set_to_simple(self, &dat->s, nth, rjd, sg, 0, 0, 0, HAVE_JD); + } + else { + if (!complex_dat_p(dat)) + rb_raise(rb_eArgError, + "cannot load complex into simple"); + + set_to_complex(self, &dat->c, nth, rjd, df, sf, of, sg, + 0, 0, 0, 0, 0, 0, HAVE_JD | HAVE_DF); + } + } + return self; +} +#endif + +/* :nodoc: */ +static VALUE +d_lite_initialize_copy(VALUE copy, VALUE date) +{ + rb_check_frozen(copy); + + if (copy == date) + return copy; + { + get_d2(copy, date); + if (simple_dat_p(bdat)) { + if (simple_dat_p(adat)) { + adat->s = bdat->s; + } + else { + adat->c.flags = bdat->s.flags | COMPLEX_DAT; + adat->c.nth = bdat->s.nth; + adat->c.jd = bdat->s.jd; + adat->c.df = 0; + adat->c.sf = INT2FIX(0); + adat->c.of = 0; + adat->c.sg = bdat->s.sg; + adat->c.year = bdat->s.year; +#ifndef USE_PACK + adat->c.mon = bdat->s.mon; + adat->c.mday = bdat->s.mday; + adat->c.hour = bdat->s.hour; + adat->c.min = bdat->s.min; + adat->c.sec = bdat->s.sec; +#else + adat->c.pc = bdat->s.pc; +#endif + } + } + else { + if (!complex_dat_p(adat)) + rb_raise(rb_eArgError, + "cannot load complex into simple"); + + adat->c = bdat->c; + } + } + return copy; +} + +#ifndef NDEBUG +/* :nodoc: */ +static VALUE +d_lite_fill(VALUE self) +{ + get_d1(self); + + if (simple_dat_p(dat)) { + get_s_jd(dat); + get_s_civil(dat); + } + else { + get_c_jd(dat); + get_c_civil(dat); + get_c_df(dat); + get_c_time(dat); + } + return self; +} +#endif + +/* + * call-seq: + * d.ajd -> rational + * + * Returns the astronomical Julian day number. This is a fractional + * number, which is not adjusted by the offset. + * + * DateTime.new(2001,2,3,4,5,6,'+7').ajd #=> (11769328217/4800) + * DateTime.new(2001,2,2,14,5,6,'-7').ajd #=> (11769328217/4800) + */ +static VALUE +d_lite_ajd(VALUE self) +{ + get_d1(self); + return m_ajd(dat); +} + +/* + * call-seq: + * d.amjd -> rational + * + * Returns the astronomical modified Julian day number. This is + * a fractional number, which is not adjusted by the offset. + * + * DateTime.new(2001,2,3,4,5,6,'+7').amjd #=> (249325817/4800) + * DateTime.new(2001,2,2,14,5,6,'-7').amjd #=> (249325817/4800) + */ +static VALUE +d_lite_amjd(VALUE self) +{ + get_d1(self); + return m_amjd(dat); +} + +/* + * call-seq: + * d.jd -> integer + * + * Returns the Julian day number. This is a whole number, which is + * adjusted by the offset as the local time. + * + * DateTime.new(2001,2,3,4,5,6,'+7').jd #=> 2451944 + * DateTime.new(2001,2,3,4,5,6,'-7').jd #=> 2451944 + */ +static VALUE +d_lite_jd(VALUE self) +{ + get_d1(self); + return m_real_local_jd(dat); +} + +/* + * call-seq: + * d.mjd -> integer + * + * Returns the modified Julian day number. This is a whole number, + * which is adjusted by the offset as the local time. + * + * DateTime.new(2001,2,3,4,5,6,'+7').mjd #=> 51943 + * DateTime.new(2001,2,3,4,5,6,'-7').mjd #=> 51943 + */ +static VALUE +d_lite_mjd(VALUE self) +{ + get_d1(self); + return f_sub(m_real_local_jd(dat), INT2FIX(2400001)); +} + +/* + * call-seq: + * ld -> integer + * + * Returns the + * {Lilian day number}[https://en.wikipedia.org/wiki/Lilian_date], + * which is the number of days since the beginning of the Gregorian + * calendar, October 15, 1582. + * + * Date.new(2001, 2, 3).ld # => 152784 + * + */ +static VALUE +d_lite_ld(VALUE self) +{ + get_d1(self); + return f_sub(m_real_local_jd(dat), INT2FIX(2299160)); +} + +/* + * call-seq: + * year -> integer + * + * Returns the year: + * + * Date.new(2001, 2, 3).year # => 2001 + * (Date.new(1, 1, 1) - 1).year # => 0 + * + */ +static VALUE +d_lite_year(VALUE self) +{ + get_d1(self); + return m_real_year(dat); +} + +/* + * call-seq: + * yday -> integer + * + * Returns the day of the year, in range (1..366): + * + * Date.new(2001, 2, 3).yday # => 34 + * + */ +static VALUE +d_lite_yday(VALUE self) +{ + get_d1(self); + return INT2FIX(m_yday(dat)); +} + +/* + * call-seq: + * mon -> integer + * + * Returns the month in range (1..12): + * + * Date.new(2001, 2, 3).mon # => 2 + * + */ +static VALUE +d_lite_mon(VALUE self) +{ + get_d1(self); + return INT2FIX(m_mon(dat)); +} + +/* + * call-seq: + * mday -> integer + * + * Returns the day of the month in range (1..31): + * + * Date.new(2001, 2, 3).mday # => 3 + * + */ +static VALUE +d_lite_mday(VALUE self) +{ + get_d1(self); + return INT2FIX(m_mday(dat)); +} + +/* + * call-seq: + * day_fraction -> rational + * + * Returns the fractional part of the day in range (Rational(0, 1)...Rational(1, 1)): + * + * DateTime.new(2001,2,3,12).day_fraction # => (1/2) + * + */ +static VALUE +d_lite_day_fraction(VALUE self) +{ + get_d1(self); + if (simple_dat_p(dat)) + return INT2FIX(0); + return m_fr(dat); +} + +/* + * call-seq: + * cwyear -> integer + * + * Returns commercial-date year for +self+ + * (see Date.commercial): + * + * Date.new(2001, 2, 3).cwyear # => 2001 + * Date.new(2000, 1, 1).cwyear # => 1999 + * + */ +static VALUE +d_lite_cwyear(VALUE self) +{ + get_d1(self); + return m_real_cwyear(dat); +} + +/* + * call-seq: + * cweek -> integer + * + * Returns commercial-date week index for +self+ + * (see Date.commercial): + * + * Date.new(2001, 2, 3).cweek # => 5 + * + */ +static VALUE +d_lite_cweek(VALUE self) +{ + get_d1(self); + return INT2FIX(m_cweek(dat)); +} + +/* + * call-seq: + * cwday -> integer + * + * Returns the commercial-date weekday index for +self+ + * (see Date.commercial); + * 1 is Monday: + * + * Date.new(2001, 2, 3).cwday # => 6 + * + */ +static VALUE +d_lite_cwday(VALUE self) +{ + get_d1(self); + return INT2FIX(m_cwday(dat)); +} + +#ifndef NDEBUG +/* :nodoc: */ +static VALUE +d_lite_wnum0(VALUE self) +{ + get_d1(self); + return INT2FIX(m_wnum0(dat)); +} + +/* :nodoc: */ +static VALUE +d_lite_wnum1(VALUE self) +{ + get_d1(self); + return INT2FIX(m_wnum1(dat)); +} +#endif + +/* + * call-seq: + * wday -> integer + * + * Returns the day of week in range (0..6); Sunday is 0: + * + * Date.new(2001, 2, 3).wday # => 6 + * + */ +static VALUE +d_lite_wday(VALUE self) +{ + get_d1(self); + return INT2FIX(m_wday(dat)); +} + +/* + * call-seq: + * sunday? -> true or false + * + * Returns +true+ if +self+ is a Sunday, +false+ otherwise. + */ +static VALUE +d_lite_sunday_p(VALUE self) +{ + get_d1(self); + return f_boolcast(m_wday(dat) == 0); +} + +/* + * call-seq: + * monday? -> true or false + * + * Returns +true+ if +self+ is a Monday, +false+ otherwise. + */ +static VALUE +d_lite_monday_p(VALUE self) +{ + get_d1(self); + return f_boolcast(m_wday(dat) == 1); +} + +/* + * call-seq: + * tuesday? -> true or false + * + * Returns +true+ if +self+ is a Tuesday, +false+ otherwise. + */ +static VALUE +d_lite_tuesday_p(VALUE self) +{ + get_d1(self); + return f_boolcast(m_wday(dat) == 2); +} + +/* + * call-seq: + * wednesday? -> true or false + * + * Returns +true+ if +self+ is a Wednesday, +false+ otherwise. + */ +static VALUE +d_lite_wednesday_p(VALUE self) +{ + get_d1(self); + return f_boolcast(m_wday(dat) == 3); +} + +/* + * call-seq: + * thursday? -> true or false + * + * Returns +true+ if +self+ is a Thursday, +false+ otherwise. + */ +static VALUE +d_lite_thursday_p(VALUE self) +{ + get_d1(self); + return f_boolcast(m_wday(dat) == 4); +} + +/* + * call-seq: + * friday? -> true or false + * + * Returns +true+ if +self+ is a Friday, +false+ otherwise. + */ +static VALUE +d_lite_friday_p(VALUE self) +{ + get_d1(self); + return f_boolcast(m_wday(dat) == 5); +} + +/* + * call-seq: + * saturday? -> true or false + * + * Returns +true+ if +self+ is a Saturday, +false+ otherwise. + */ +static VALUE +d_lite_saturday_p(VALUE self) +{ + get_d1(self); + return f_boolcast(m_wday(dat) == 6); +} + +#ifndef NDEBUG +/* :nodoc: */ +static VALUE +d_lite_nth_kday_p(VALUE self, VALUE n, VALUE k) +{ + int rjd, ns; + + get_d1(self); + + if (NUM2INT(k) != m_wday(dat)) + return Qfalse; + + c_nth_kday_to_jd(m_year(dat), m_mon(dat), + NUM2INT(n), NUM2INT(k), m_virtual_sg(dat), /* !=m_sg() */ + &rjd, &ns); + if (m_local_jd(dat) != rjd) + return Qfalse; + return Qtrue; +} +#endif + +/* + * call-seq: + * hour -> integer + * + * Returns the hour in range (0..23): + * + * DateTime.new(2001, 2, 3, 4, 5, 6).hour # => 4 + * + */ +static VALUE +d_lite_hour(VALUE self) +{ + get_d1(self); + return INT2FIX(m_hour(dat)); +} + +/* + * call-seq: + * min -> integer + * + * Returns the minute in range (0..59): + * + * DateTime.new(2001, 2, 3, 4, 5, 6).min # => 5 + * + */ +static VALUE +d_lite_min(VALUE self) +{ + get_d1(self); + return INT2FIX(m_min(dat)); +} + +/* + * call-seq: + * sec -> integer + * + * Returns the second in range (0..59): + * + * DateTime.new(2001, 2, 3, 4, 5, 6).sec # => 6 + * + */ +static VALUE +d_lite_sec(VALUE self) +{ + get_d1(self); + return INT2FIX(m_sec(dat)); +} + +/* + * call-seq: + * sec_fraction -> rational + * + * Returns the fractional part of the second in range + * (Rational(0, 1)...Rational(1, 1)): + * + * DateTime.new(2001, 2, 3, 4, 5, 6.5).sec_fraction # => (1/2) + * + */ +static VALUE +d_lite_sec_fraction(VALUE self) +{ + get_d1(self); + return m_sf_in_sec(dat); +} + +/* + * call-seq: + * d.offset -> rational + * + * Returns the offset. + * + * DateTime.parse('04pm+0730').offset #=> (5/16) + */ +static VALUE +d_lite_offset(VALUE self) +{ + get_d1(self); + return m_of_in_day(dat); +} + +/* + * call-seq: + * d.zone -> string + * + * Returns the timezone. + * + * DateTime.parse('04pm+0730').zone #=> "+07:30" + */ +static VALUE +d_lite_zone(VALUE self) +{ + get_d1(self); + return m_zone(dat); +} + +/* + * call-seq: + * d.julian? -> true or false + * + * Returns +true+ if the date is before the date of calendar reform, + * +false+ otherwise: + * + * (Date.new(1582, 10, 15) - 1).julian? # => true + * Date.new(1582, 10, 15).julian? # => false + * + */ +static VALUE +d_lite_julian_p(VALUE self) +{ + get_d1(self); + return f_boolcast(m_julian_p(dat)); +} + +/* + * call-seq: + * gregorian? -> true or false + * + * Returns +true+ if the date is on or after + * the date of calendar reform, +false+ otherwise: + * + * Date.new(1582, 10, 15).gregorian? # => true + * (Date.new(1582, 10, 15) - 1).gregorian? # => false + * + */ +static VALUE +d_lite_gregorian_p(VALUE self) +{ + get_d1(self); + return f_boolcast(m_gregorian_p(dat)); +} + +/* + * call-seq: + * leap? -> true or false + * + * Returns +true+ if the year is a leap year, +false+ otherwise: + * + * Date.new(2000).leap? # => true + * Date.new(2001).leap? # => false + * + */ +static VALUE +d_lite_leap_p(VALUE self) +{ + int rjd, ns, ry, rm, rd; + + get_d1(self); + if (m_gregorian_p(dat)) + return f_boolcast(c_gregorian_leap_p(m_year(dat))); + + c_civil_to_jd(m_year(dat), 3, 1, m_virtual_sg(dat), + &rjd, &ns); + c_jd_to_civil(rjd - 1, m_virtual_sg(dat), &ry, &rm, &rd); + return f_boolcast(rd == 29); +} + +/* + * call-seq: + * start -> float + * + * Returns the Julian start date for calendar reform; + * if not an infinity, the returned value is suitable + * for passing to Date#jd: + * + * d = Date.new(2001, 2, 3, Date::ITALY) + * s = d.start # => 2299161.0 + * Date.jd(s).to_s # => "1582-10-15" + * + * d = Date.new(2001, 2, 3, Date::ENGLAND) + * s = d.start # => 2361222.0 + * Date.jd(s).to_s # => "1752-09-14" + * + * Date.new(2001, 2, 3, Date::GREGORIAN).start # => -Infinity + * Date.new(2001, 2, 3, Date::JULIAN).start # => Infinity + * + * See argument {start}[rdoc-ref:calendars.rdoc@Argument+start]. + * + */ +static VALUE +d_lite_start(VALUE self) +{ + get_d1(self); + return DBL2NUM(m_sg(dat)); +} + +static void +clear_civil(union DateData *x) +{ + if (simple_dat_p(x)) { + x->s.year = 0; +#ifndef USE_PACK + x->s.mon = 0; + x->s.mday = 0; +#else + x->s.pc = 0; +#endif + x->s.flags &= ~HAVE_CIVIL; + } + else { + x->c.year = 0; +#ifndef USE_PACK + x->c.mon = 0; + x->c.mday = 0; + x->c.hour = 0; + x->c.min = 0; + x->c.sec = 0; +#else + x->c.pc = 0; +#endif + x->c.flags &= ~(HAVE_CIVIL | HAVE_TIME); + } +} + +static void +set_sg(union DateData *x, double sg) +{ + if (simple_dat_p(x)) { + get_s_jd(x); + clear_civil(x); + x->s.sg = (date_sg_t)sg; + } else { + get_c_jd(x); + get_c_df(x); + clear_civil(x); + x->c.sg = (date_sg_t)sg; + } +} + +static VALUE +dup_obj_with_new_start(VALUE obj, double sg) +{ + volatile VALUE dup = dup_obj(obj); + { + get_d1(dup); + set_sg(dat, sg); + } + return dup; +} + +/* + * call-seq: + * new_start(start = Date::ITALY]) -> new_date + * + * Returns a copy of +self+ with the given +start+ value: + * + * d0 = Date.new(2000, 2, 3) + * d0.julian? # => false + * d1 = d0.new_start(Date::JULIAN) + * d1.julian? # => true + * + * See argument {start}[rdoc-ref:calendars.rdoc@Argument+start]. + * + */ +static VALUE +d_lite_new_start(int argc, VALUE *argv, VALUE self) +{ + VALUE vsg; + double sg; + + rb_scan_args(argc, argv, "01", &vsg); + + sg = DEFAULT_SG; + if (argc >= 1) + val2sg(vsg, sg); + + return dup_obj_with_new_start(self, sg); +} + +/* + * call-seq: + * italy -> new_date + * + * Equivalent to Date#new_start with argument Date::ITALY. + * + */ +static VALUE +d_lite_italy(VALUE self) +{ + return dup_obj_with_new_start(self, ITALY); +} + +/* + * call-seq: + * england -> new_date + * + * Equivalent to Date#new_start with argument Date::ENGLAND. + */ +static VALUE +d_lite_england(VALUE self) +{ + return dup_obj_with_new_start(self, ENGLAND); +} + +/* + * call-seq: + * julian -> new_date + * + * Equivalent to Date#new_start with argument Date::JULIAN. + */ +static VALUE +d_lite_julian(VALUE self) +{ + return dup_obj_with_new_start(self, JULIAN); +} + +/* + * call-seq: + * gregorian -> new_date + * + * Equivalent to Date#new_start with argument Date::GREGORIAN. + */ +static VALUE +d_lite_gregorian(VALUE self) +{ + return dup_obj_with_new_start(self, GREGORIAN); +} + +static void +set_of(union DateData *x, int of) +{ + assert(complex_dat_p(x)); + get_c_jd(x); + get_c_df(x); + clear_civil(x); + x->c.of = of; +} + +static VALUE +dup_obj_with_new_offset(VALUE obj, int of) +{ + volatile VALUE dup = dup_obj_as_complex(obj); + { + get_d1(dup); + set_of(dat, of); + } + return dup; +} + +/* + * call-seq: + * d.new_offset([offset=0]) -> date + * + * Duplicates self and resets its offset. + * + * d = DateTime.new(2001,2,3,4,5,6,'-02:00') + * #=> # + * d.new_offset('+09:00') #=> # + */ +static VALUE +d_lite_new_offset(int argc, VALUE *argv, VALUE self) +{ + VALUE vof; + int rof; + + rb_scan_args(argc, argv, "01", &vof); + + rof = 0; + if (argc >= 1) + val2off(vof, rof); + + return dup_obj_with_new_offset(self, rof); +} + +/* + * call-seq: + * d + other -> date + * + * Returns a date object pointing +other+ days after self. The other + * should be a numeric value. If the other is a fractional number, + * assumes its precision is at most nanosecond. + * + * Date.new(2001,2,3) + 1 #=> # + * DateTime.new(2001,2,3) + Rational(1,2) + * #=> # + * DateTime.new(2001,2,3) + Rational(-1,2) + * #=> # + * DateTime.jd(0,12) + DateTime.new(2001,2,3).ajd + * #=> # + */ +static VALUE +d_lite_plus(VALUE self, VALUE other) +{ + int try_rational = 1; + get_d1(self); + + again: + switch (TYPE(other)) { + case T_FIXNUM: + { + VALUE nth; + long t; + int jd; + + nth = m_nth(dat); + t = FIX2LONG(other); + if (DIV(t, CM_PERIOD)) { + nth = f_add(nth, INT2FIX(DIV(t, CM_PERIOD))); + t = MOD(t, CM_PERIOD); + } + + if (!t) + jd = m_jd(dat); + else { + jd = m_jd(dat) + (int)t; + canonicalize_jd(nth, jd); + } + + if (simple_dat_p(dat)) + return d_simple_new_internal(rb_obj_class(self), + nth, jd, + dat->s.sg, + 0, 0, 0, + (dat->s.flags | HAVE_JD) & + ~HAVE_CIVIL); + else + return d_complex_new_internal(rb_obj_class(self), + nth, jd, + dat->c.df, dat->c.sf, + dat->c.of, dat->c.sg, + 0, 0, 0, +#ifndef USE_PACK + dat->c.hour, + dat->c.min, + dat->c.sec, +#else + EX_HOUR(dat->c.pc), + EX_MIN(dat->c.pc), + EX_SEC(dat->c.pc), +#endif + (dat->c.flags | HAVE_JD) & + ~HAVE_CIVIL); + } + break; + case T_BIGNUM: + { + VALUE nth; + int jd, s; + + if (f_positive_p(other)) + s = +1; + else { + s = -1; + other = f_negate(other); + } + + nth = f_idiv(other, INT2FIX(CM_PERIOD)); + jd = FIX2INT(f_mod(other, INT2FIX(CM_PERIOD))); + + if (s < 0) { + nth = f_negate(nth); + jd = -jd; + } + + if (!jd) + jd = m_jd(dat); + else { + jd = m_jd(dat) + jd; + canonicalize_jd(nth, jd); + } + + if (f_zero_p(nth)) + nth = m_nth(dat); + else + nth = f_add(m_nth(dat), nth); + + if (simple_dat_p(dat)) + return d_simple_new_internal(rb_obj_class(self), + nth, jd, + dat->s.sg, + 0, 0, 0, + (dat->s.flags | HAVE_JD) & + ~HAVE_CIVIL); + else + return d_complex_new_internal(rb_obj_class(self), + nth, jd, + dat->c.df, dat->c.sf, + dat->c.of, dat->c.sg, + 0, 0, 0, +#ifndef USE_PACK + dat->c.hour, + dat->c.min, + dat->c.sec, +#else + EX_HOUR(dat->c.pc), + EX_MIN(dat->c.pc), + EX_SEC(dat->c.pc), +#endif + (dat->c.flags | HAVE_JD) & + ~HAVE_CIVIL); + } + break; + case T_FLOAT: + { + double jd, o, tmp; + int s, df; + VALUE nth, sf; + + o = RFLOAT_VALUE(other); + + if (o > 0) + s = +1; + else { + s = -1; + o = -o; + } + + o = modf(o, &tmp); + + if (!floor(tmp / CM_PERIOD)) { + nth = INT2FIX(0); + jd = (int)tmp; + } + else { + double i, f; + + f = modf(tmp / CM_PERIOD, &i); + nth = f_floor(DBL2NUM(i)); + jd = (int)(f * CM_PERIOD); + } + + o *= DAY_IN_SECONDS; + o = modf(o, &tmp); + df = (int)tmp; + o *= SECOND_IN_NANOSECONDS; + sf = INT2FIX((int)round(o)); + + if (s < 0) { + jd = -jd; + df = -df; + sf = f_negate(sf); + } + + if (f_zero_p(sf)) + sf = m_sf(dat); + else { + sf = f_add(m_sf(dat), sf); + if (f_lt_p(sf, INT2FIX(0))) { + df -= 1; + sf = f_add(sf, INT2FIX(SECOND_IN_NANOSECONDS)); + } + else if (f_ge_p(sf, INT2FIX(SECOND_IN_NANOSECONDS))) { + df += 1; + sf = f_sub(sf, INT2FIX(SECOND_IN_NANOSECONDS)); + } + } + + if (!df) + df = m_df(dat); + else { + df = m_df(dat) + df; + if (df < 0) { + jd -= 1; + df += DAY_IN_SECONDS; + } + else if (df >= DAY_IN_SECONDS) { + jd += 1; + df -= DAY_IN_SECONDS; + } + } + + if (!jd) + jd = m_jd(dat); + else { + jd = m_jd(dat) + jd; + canonicalize_jd(nth, jd); + } + + if (f_zero_p(nth)) + nth = m_nth(dat); + else + nth = f_add(m_nth(dat), nth); + + if (!df && f_zero_p(sf) && !m_of(dat)) + return d_simple_new_internal(rb_obj_class(self), + nth, (int)jd, + m_sg(dat), + 0, 0, 0, + (dat->s.flags | HAVE_JD) & + ~(HAVE_CIVIL | HAVE_TIME | + COMPLEX_DAT)); + else + return d_complex_new_internal(rb_obj_class(self), + nth, (int)jd, + df, sf, + m_of(dat), m_sg(dat), + 0, 0, 0, + 0, 0, 0, + (dat->c.flags | + HAVE_JD | HAVE_DF) & + ~(HAVE_CIVIL | HAVE_TIME)); + } + break; + default: + expect_numeric(other); + other = f_to_r(other); + if (!k_rational_p(other)) { + if (!try_rational) Check_Type(other, T_RATIONAL); + try_rational = 0; + goto again; + } + /* fall through */ + case T_RATIONAL: + { + VALUE nth, sf, t; + int jd, df, s; + + if (wholenum_p(other)) { + other = rb_rational_num(other); + goto again; + } + + if (f_positive_p(other)) + s = +1; + else { + s = -1; + other = f_negate(other); + } + + nth = f_idiv(other, INT2FIX(CM_PERIOD)); + t = f_mod(other, INT2FIX(CM_PERIOD)); + + jd = FIX2INT(f_idiv(t, INT2FIX(1))); + t = f_mod(t, INT2FIX(1)); + + t = f_mul(t, INT2FIX(DAY_IN_SECONDS)); + df = FIX2INT(f_idiv(t, INT2FIX(1))); + t = f_mod(t, INT2FIX(1)); + + sf = f_mul(t, INT2FIX(SECOND_IN_NANOSECONDS)); + + if (s < 0) { + nth = f_negate(nth); + jd = -jd; + df = -df; + sf = f_negate(sf); + } + + if (f_zero_p(sf)) + sf = m_sf(dat); + else { + sf = f_add(m_sf(dat), sf); + if (f_lt_p(sf, INT2FIX(0))) { + df -= 1; + sf = f_add(sf, INT2FIX(SECOND_IN_NANOSECONDS)); + } + else if (f_ge_p(sf, INT2FIX(SECOND_IN_NANOSECONDS))) { + df += 1; + sf = f_sub(sf, INT2FIX(SECOND_IN_NANOSECONDS)); + } + } + + if (!df) + df = m_df(dat); + else { + df = m_df(dat) + df; + if (df < 0) { + jd -= 1; + df += DAY_IN_SECONDS; + } + else if (df >= DAY_IN_SECONDS) { + jd += 1; + df -= DAY_IN_SECONDS; + } + } + + if (!jd) + jd = m_jd(dat); + else { + jd = m_jd(dat) + jd; + canonicalize_jd(nth, jd); + } + + if (f_zero_p(nth)) + nth = m_nth(dat); + else + nth = f_add(m_nth(dat), nth); + + if (!df && f_zero_p(sf) && !m_of(dat)) + return d_simple_new_internal(rb_obj_class(self), + nth, jd, + m_sg(dat), + 0, 0, 0, + (dat->s.flags | HAVE_JD) & + ~(HAVE_CIVIL | HAVE_TIME | + COMPLEX_DAT)); + else + return d_complex_new_internal(rb_obj_class(self), + nth, jd, + df, sf, + m_of(dat), m_sg(dat), + 0, 0, 0, + 0, 0, 0, + (dat->c.flags | + HAVE_JD | HAVE_DF) & + ~(HAVE_CIVIL | HAVE_TIME)); + } + break; + } +} + +static VALUE +minus_dd(VALUE self, VALUE other) +{ + get_d2(self, other); + + { + int d, df; + VALUE n, sf, r; + + n = f_sub(m_nth(adat), m_nth(bdat)); + d = m_jd(adat) - m_jd(bdat); + df = m_df(adat) - m_df(bdat); + sf = f_sub(m_sf(adat), m_sf(bdat)); + canonicalize_jd(n, d); + + if (df < 0) { + d -= 1; + df += DAY_IN_SECONDS; + } + else if (df >= DAY_IN_SECONDS) { + d += 1; + df -= DAY_IN_SECONDS; + } + + if (f_lt_p(sf, INT2FIX(0))) { + df -= 1; + sf = f_add(sf, INT2FIX(SECOND_IN_NANOSECONDS)); + } + else if (f_ge_p(sf, INT2FIX(SECOND_IN_NANOSECONDS))) { + df += 1; + sf = f_sub(sf, INT2FIX(SECOND_IN_NANOSECONDS)); + } + + if (f_zero_p(n)) + r = INT2FIX(0); + else + r = f_mul(n, INT2FIX(CM_PERIOD)); + + if (d) + r = f_add(r, rb_rational_new1(INT2FIX(d))); + if (df) + r = f_add(r, isec_to_day(df)); + if (f_nonzero_p(sf)) + r = f_add(r, ns_to_day(sf)); + + if (RB_TYPE_P(r, T_RATIONAL)) + return r; + return rb_rational_new1(r); + } +} + +/* + * call-seq: + * d - other -> date or rational + * + * If the other is a date object, returns a Rational + * whose value is the difference between the two dates in days. + * If the other is a numeric value, returns a date object + * pointing +other+ days before self. + * If the other is a fractional number, + * assumes its precision is at most nanosecond. + * + * Date.new(2001,2,3) - 1 #=> # + * DateTime.new(2001,2,3) - Rational(1,2) + * #=> # + * Date.new(2001,2,3) - Date.new(2001) + * #=> (33/1) + * DateTime.new(2001,2,3) - DateTime.new(2001,2,2,12) + * #=> (1/2) + */ +static VALUE +d_lite_minus(VALUE self, VALUE other) +{ + if (k_date_p(other)) + return minus_dd(self, other); + + switch (TYPE(other)) { + case T_FIXNUM: + return d_lite_plus(self, LONG2NUM(-FIX2LONG(other))); + case T_FLOAT: + return d_lite_plus(self, DBL2NUM(-RFLOAT_VALUE(other))); + default: + expect_numeric(other); + /* fall through */ + case T_BIGNUM: + case T_RATIONAL: + return d_lite_plus(self, f_negate(other)); + } +} + +/* + * call-seq: + * next_day(n = 1) -> new_date + * + * Equivalent to Date#+ with argument +n+. + */ +static VALUE +d_lite_next_day(int argc, VALUE *argv, VALUE self) +{ + VALUE n; + + rb_scan_args(argc, argv, "01", &n); + if (argc < 1) + n = INT2FIX(1); + return d_lite_plus(self, n); +} + +/* + * call-seq: + * prev_day(n = 1) -> new_date + * + * Equivalent to Date#- with argument +n+. + */ +static VALUE +d_lite_prev_day(int argc, VALUE *argv, VALUE self) +{ + VALUE n; + + rb_scan_args(argc, argv, "01", &n); + if (argc < 1) + n = INT2FIX(1); + return d_lite_minus(self, n); +} + +/* + * call-seq: + * d.next -> new_date + * + * Returns a new \Date object representing the following day: + * + * d = Date.new(2001, 2, 3) + * d.to_s # => "2001-02-03" + * d.next.to_s # => "2001-02-04" + * + */ +static VALUE +d_lite_next(VALUE self) +{ + return d_lite_next_day(0, (VALUE *)NULL, self); +} + +/* + * call-seq: + * d >> n -> new_date + * + * Returns a new \Date object representing the date + * +n+ months later; +n+ should be a numeric: + * + * (Date.new(2001, 2, 3) >> 1).to_s # => "2001-03-03" + * (Date.new(2001, 2, 3) >> -2).to_s # => "2000-12-03" + * + * When the same day does not exist for the new month, + * the last day of that month is used instead: + * + * (Date.new(2001, 1, 31) >> 1).to_s # => "2001-02-28" + * (Date.new(2001, 1, 31) >> -4).to_s # => "2000-09-30" + * + * This results in the following, possibly unexpected, behaviors: + * + * d0 = Date.new(2001, 1, 31) + * d1 = d0 >> 1 # => # + * d2 = d1 >> 1 # => # + * + * d0 = Date.new(2001, 1, 31) + * d1 = d0 >> 1 # => # + * d2 = d1 >> -1 # => # + * + */ +static VALUE +d_lite_rshift(VALUE self, VALUE other) +{ + VALUE t, y, nth, rjd2; + int m, d, rjd; + double sg; + + get_d1(self); + t = f_add3(f_mul(m_real_year(dat), INT2FIX(12)), + INT2FIX(m_mon(dat) - 1), + other); + if (FIXNUM_P(t)) { + long it = FIX2LONG(t); + y = LONG2NUM(DIV(it, 12)); + it = MOD(it, 12); + m = (int)it + 1; + } + else { + y = f_idiv(t, INT2FIX(12)); + t = f_mod(t, INT2FIX(12)); + m = FIX2INT(t) + 1; + } + d = m_mday(dat); + sg = m_sg(dat); + + while (1) { + int ry, rm, rd, ns; + + if (valid_civil_p(y, m, d, sg, + &nth, &ry, + &rm, &rd, &rjd, &ns)) + break; + if (--d < 1) + rb_raise(eDateError, "invalid date"); + } + encode_jd(nth, rjd, &rjd2); + return d_lite_plus(self, f_sub(rjd2, m_real_local_jd(dat))); +} + +/* + * call-seq: + * d << n -> date + * + * Returns a new \Date object representing the date + * +n+ months earlier; +n+ should be a numeric: + * + * (Date.new(2001, 2, 3) << 1).to_s # => "2001-01-03" + * (Date.new(2001, 2, 3) << -2).to_s # => "2001-04-03" + * + * When the same day does not exist for the new month, + * the last day of that month is used instead: + * + * (Date.new(2001, 3, 31) << 1).to_s # => "2001-02-28" + * (Date.new(2001, 3, 31) << -6).to_s # => "2001-09-30" + * + * This results in the following, possibly unexpected, behaviors: + * + * d0 = Date.new(2001, 3, 31) + * d0 << 2 # => # + * d0 << 1 << 1 # => # + * + * d0 = Date.new(2001, 3, 31) + * d1 = d0 << 1 # => # + * d2 = d1 << -1 # => # + * + */ +static VALUE +d_lite_lshift(VALUE self, VALUE other) +{ + expect_numeric(other); + return d_lite_rshift(self, f_negate(other)); +} + +/* + * call-seq: + * next_month(n = 1) -> new_date + * + * Equivalent to #>> with argument +n+. + */ +static VALUE +d_lite_next_month(int argc, VALUE *argv, VALUE self) +{ + VALUE n; + + rb_scan_args(argc, argv, "01", &n); + if (argc < 1) + n = INT2FIX(1); + return d_lite_rshift(self, n); +} + +/* + * call-seq: + * prev_month(n = 1) -> new_date + * + * Equivalent to #<< with argument +n+. + */ +static VALUE +d_lite_prev_month(int argc, VALUE *argv, VALUE self) +{ + VALUE n; + + rb_scan_args(argc, argv, "01", &n); + if (argc < 1) + n = INT2FIX(1); + return d_lite_lshift(self, n); +} + +/* + * call-seq: + * next_year(n = 1) -> new_date + * + * Equivalent to #>> with argument n * 12. + */ +static VALUE +d_lite_next_year(int argc, VALUE *argv, VALUE self) +{ + VALUE n; + + rb_scan_args(argc, argv, "01", &n); + if (argc < 1) + n = INT2FIX(1); + return d_lite_rshift(self, f_mul(n, INT2FIX(12))); +} + +/* + * call-seq: + * prev_year(n = 1) -> new_date + * + * Equivalent to #<< with argument n * 12. + */ +static VALUE +d_lite_prev_year(int argc, VALUE *argv, VALUE self) +{ + VALUE n; + + rb_scan_args(argc, argv, "01", &n); + if (argc < 1) + n = INT2FIX(1); + return d_lite_lshift(self, f_mul(n, INT2FIX(12))); +} + +static VALUE d_lite_cmp(VALUE, VALUE); + +/* + * call-seq: + * step(limit, step = 1){|date| ... } -> self + * + * Calls the block with specified dates; + * returns +self+. + * + * - The first +date+ is +self+. + * - Each successive +date+ is date + step, + * where +step+ is the numeric step size in days. + * - The last date is the last one that is before or equal to +limit+, + * which should be a \Date object. + * + * Example: + * + * limit = Date.new(2001, 12, 31) + * Date.new(2001).step(limit){|date| p date.to_s if date.mday == 31 } + * + * Output: + * + * "2001-01-31" + * "2001-03-31" + * "2001-05-31" + * "2001-07-31" + * "2001-08-31" + * "2001-10-31" + * "2001-12-31" + * + * Returns an Enumerator if no block is given. + */ +static VALUE +d_lite_step(int argc, VALUE *argv, VALUE self) +{ + VALUE limit, step, date; + int c; + + rb_scan_args(argc, argv, "11", &limit, &step); + + if (argc < 2) + step = INT2FIX(1); + +#if 0 + if (f_zero_p(step)) + rb_raise(rb_eArgError, "step can't be 0"); +#endif + + RETURN_ENUMERATOR(self, argc, argv); + + date = self; + c = f_cmp(step, INT2FIX(0)); + if (c < 0) { + while (FIX2INT(d_lite_cmp(date, limit)) >= 0) { + rb_yield(date); + date = d_lite_plus(date, step); + } + } + else if (c == 0) { + while (1) + rb_yield(date); + } + else /* if (c > 0) */ { + while (FIX2INT(d_lite_cmp(date, limit)) <= 0) { + rb_yield(date); + date = d_lite_plus(date, step); + } + } + return self; +} + +/* + * call-seq: + * upto(max){|date| ... } -> self + * + * Equivalent to #step with arguments +max+ and +1+. + */ +static VALUE +d_lite_upto(VALUE self, VALUE max) +{ + VALUE date; + + RETURN_ENUMERATOR(self, 1, &max); + + date = self; + while (FIX2INT(d_lite_cmp(date, max)) <= 0) { + rb_yield(date); + date = d_lite_plus(date, INT2FIX(1)); + } + return self; +} + +/* + * call-seq: + * downto(min){|date| ... } -> self + * + * Equivalent to #step with arguments +min+ and -1. + */ +static VALUE +d_lite_downto(VALUE self, VALUE min) +{ + VALUE date; + + RETURN_ENUMERATOR(self, 1, &min); + + date = self; + while (FIX2INT(d_lite_cmp(date, min)) >= 0) { + rb_yield(date); + date = d_lite_plus(date, INT2FIX(-1)); + } + return self; +} + +static VALUE +cmp_gen(VALUE self, VALUE other) +{ + get_d1(self); + + if (k_numeric_p(other)) + return INT2FIX(f_cmp(m_ajd(dat), other)); + else if (k_date_p(other)) + return INT2FIX(f_cmp(m_ajd(dat), f_ajd(other))); + return rb_num_coerce_cmp(self, other, id_cmp); +} + +static VALUE +cmp_dd(VALUE self, VALUE other) +{ + get_d2(self, other); + + { + VALUE a_nth, b_nth, + a_sf, b_sf; + int a_jd, b_jd, + a_df, b_df; + + m_canonicalize_jd(self, adat); + m_canonicalize_jd(other, bdat); + a_nth = m_nth(adat); + b_nth = m_nth(bdat); + if (f_eqeq_p(a_nth, b_nth)) { + a_jd = m_jd(adat); + b_jd = m_jd(bdat); + if (a_jd == b_jd) { + a_df = m_df(adat); + b_df = m_df(bdat); + if (a_df == b_df) { + a_sf = m_sf(adat); + b_sf = m_sf(bdat); + if (f_eqeq_p(a_sf, b_sf)) { + return INT2FIX(0); + } + else if (f_lt_p(a_sf, b_sf)) { + return INT2FIX(-1); + } + else { + return INT2FIX(1); + } + } + else if (a_df < b_df) { + return INT2FIX(-1); + } + else { + return INT2FIX(1); + } + } + else if (a_jd < b_jd) { + return INT2FIX(-1); + } + else { + return INT2FIX(1); + } + } + else if (f_lt_p(a_nth, b_nth)) { + return INT2FIX(-1); + } + else { + return INT2FIX(1); + } + } +} + +/* + * call-seq: + * self <=> other -> -1, 0, 1 or nil + * + * Compares +self+ and +other+, returning: + * + * - -1 if +other+ is larger. + * - 0 if the two are equal. + * - 1 if +other+ is smaller. + * - +nil+ if the two are incomparable. + * + * Argument +other+ may be: + * + * - Another \Date object: + * + * d = Date.new(2022, 7, 27) # => # + * prev_date = d.prev_day # => # + * next_date = d.next_day # => # + * d <=> next_date # => -1 + * d <=> d # => 0 + * d <=> prev_date # => 1 + * + * - A DateTime object: + * + * d <=> DateTime.new(2022, 7, 26) # => 1 + * d <=> DateTime.new(2022, 7, 27) # => 0 + * d <=> DateTime.new(2022, 7, 28) # => -1 + * + * - A numeric (compares self.ajd to +other+): + * + * d <=> 2459788 # => -1 + * d <=> 2459787 # => 1 + * d <=> 2459786 # => 1 + * d <=> d.ajd # => 0 + * + * - Any other object: + * + * d <=> Object.new # => nil + * + */ +static VALUE +d_lite_cmp(VALUE self, VALUE other) +{ + if (!k_date_p(other)) + return cmp_gen(self, other); + + { + get_d2(self, other); + + if (!(simple_dat_p(adat) && simple_dat_p(bdat) && + m_gregorian_p(adat) == m_gregorian_p(bdat))) + return cmp_dd(self, other); + + { + VALUE a_nth, b_nth; + int a_jd, b_jd; + + m_canonicalize_jd(self, adat); + m_canonicalize_jd(other, bdat); + a_nth = m_nth(adat); + b_nth = m_nth(bdat); + if (f_eqeq_p(a_nth, b_nth)) { + a_jd = m_jd(adat); + b_jd = m_jd(bdat); + if (a_jd == b_jd) { + return INT2FIX(0); + } + else if (a_jd < b_jd) { + return INT2FIX(-1); + } + else { + return INT2FIX(1); + } + } + else if (f_lt_p(a_nth, b_nth)) { + return INT2FIX(-1); + } + else { + return INT2FIX(1); + } + } + } +} + +static VALUE +equal_gen(VALUE self, VALUE other) +{ + get_d1(self); + + if (k_numeric_p(other)) + return f_eqeq_p(m_real_local_jd(dat), other); + else if (k_date_p(other)) + return f_eqeq_p(m_real_local_jd(dat), f_jd(other)); + return rb_num_coerce_cmp(self, other, id_eqeq_p); +} + +/* + * call-seq: + * self === other -> true, false, or nil. + * + * Returns +true+ if +self+ and +other+ represent the same date, + * +false+ if not, +nil+ if the two are not comparable. + * + * Argument +other+ may be: + * + * - Another \Date object: + * + * d = Date.new(2022, 7, 27) # => # + * prev_date = d.prev_day # => # + * next_date = d.next_day # => # + * d === prev_date # => false + * d === d # => true + * d === next_date # => false + * + * - A DateTime object: + * + * d === DateTime.new(2022, 7, 26) # => false + * d === DateTime.new(2022, 7, 27) # => true + * d === DateTime.new(2022, 7, 28) # => false + * + * - A numeric (compares self.jd to +other+): + * + * d === 2459788 # => true + * d === 2459787 # => false + * d === 2459786 # => false + * d === d.jd # => true + * + * - An object not comparable: + * + * d === Object.new # => nil + * + */ +static VALUE +d_lite_equal(VALUE self, VALUE other) +{ + if (!k_date_p(other)) + return equal_gen(self, other); + + { + get_d2(self, other); + + if (!(m_gregorian_p(adat) == m_gregorian_p(bdat))) + return equal_gen(self, other); + + { + VALUE a_nth, b_nth; + int a_jd, b_jd; + + m_canonicalize_jd(self, adat); + m_canonicalize_jd(other, bdat); + a_nth = m_nth(adat); + b_nth = m_nth(bdat); + a_jd = m_local_jd(adat); + b_jd = m_local_jd(bdat); + if (f_eqeq_p(a_nth, b_nth) && + a_jd == b_jd) + return Qtrue; + return Qfalse; + } + } +} + +/* :nodoc: */ +static VALUE +d_lite_eql_p(VALUE self, VALUE other) +{ + if (!k_date_p(other)) + return Qfalse; + return f_zero_p(d_lite_cmp(self, other)); +} + +/* :nodoc: */ +static VALUE +d_lite_hash(VALUE self) +{ + st_index_t v, h[4]; + + get_d1(self); + h[0] = m_nth(dat); + h[1] = m_jd(dat); + h[2] = m_df(dat); + h[3] = m_sf(dat); + v = rb_memhash(h, sizeof(h)); + return ST2FIX(v); +} + +#include "date_tmx.h" +static void set_tmx(VALUE, struct tmx *); +static VALUE strftimev(const char *, VALUE, + void (*)(VALUE, struct tmx *)); + +/* + * call-seq: + * to_s -> string + * + * Returns a string representation of the date in +self+ + * in {ISO 8601 extended date format}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-ISO+8601+Format+Specifications] + * ('%Y-%m-%d'): + * + * Date.new(2001, 2, 3).to_s # => "2001-02-03" + * + */ +static VALUE +d_lite_to_s(VALUE self) +{ + return strftimev("%Y-%m-%d", self, set_tmx); +} + +#ifndef NDEBUG +/* :nodoc: */ +static VALUE +mk_inspect_raw(union DateData *x, VALUE klass) +{ + char flags[6]; + + flags[0] = (x->flags & COMPLEX_DAT) ? 'C' : 'S'; + flags[1] = (x->flags & HAVE_JD) ? 'j' : '-'; + flags[2] = (x->flags & HAVE_DF) ? 'd' : '-'; + flags[3] = (x->flags & HAVE_CIVIL) ? 'c' : '-'; + flags[4] = (x->flags & HAVE_TIME) ? 't' : '-'; + flags[5] = '\0'; + + if (simple_dat_p(x)) { + return rb_enc_sprintf(rb_usascii_encoding(), + "#<%"PRIsVALUE": " + "(%+"PRIsVALUE"th,%dj),+0s,%.0fj; " + "%dy%dm%dd; %s>", + klass, + x->s.nth, x->s.jd, x->s.sg, +#ifndef USE_PACK + x->s.year, x->s.mon, x->s.mday, +#else + x->s.year, + EX_MON(x->s.pc), EX_MDAY(x->s.pc), +#endif + flags); + } + else { + return rb_enc_sprintf(rb_usascii_encoding(), + "#<%"PRIsVALUE": " + "(%+"PRIsVALUE"th,%dj,%ds,%+"PRIsVALUE"n)," + "%+ds,%.0fj; " + "%dy%dm%dd %dh%dm%ds; %s>", + klass, + x->c.nth, x->c.jd, x->c.df, x->c.sf, + x->c.of, x->c.sg, +#ifndef USE_PACK + x->c.year, x->c.mon, x->c.mday, + x->c.hour, x->c.min, x->c.sec, +#else + x->c.year, + EX_MON(x->c.pc), EX_MDAY(x->c.pc), + EX_HOUR(x->c.pc), EX_MIN(x->c.pc), + EX_SEC(x->c.pc), +#endif + flags); + } +} + +/* :nodoc: */ +static VALUE +d_lite_inspect_raw(VALUE self) +{ + get_d1(self); + return mk_inspect_raw(dat, rb_obj_class(self)); +} +#endif + +static VALUE +mk_inspect(union DateData *x, VALUE klass, VALUE to_s) +{ + return rb_enc_sprintf(rb_usascii_encoding(), + "#<%"PRIsVALUE": %"PRIsVALUE" " + "((%+"PRIsVALUE"j,%ds,%+"PRIsVALUE"n),%+ds,%.0fj)>", + klass, to_s, + m_real_jd(x), m_df(x), m_sf(x), + m_of(x), m_sg(x)); +} + +/* + * call-seq: + * inspect -> string + * + * Returns a string representation of +self+: + * + * Date.new(2001, 2, 3).inspect + * # => "#" + * + */ +static VALUE +d_lite_inspect(VALUE self) +{ + get_d1(self); + return mk_inspect(dat, rb_obj_class(self), self); +} + +#include +#include "date_tmx.h" + +size_t date_strftime(char *s, size_t maxsize, const char *format, + const struct tmx *tmx); + +#define SMALLBUF 100 +static size_t +date_strftime_alloc(char **buf, const char *format, + struct tmx *tmx) +{ + size_t size, len, flen; + + (*buf)[0] = '\0'; + flen = strlen(format); + if (flen == 0) { + return 0; + } + errno = 0; + len = date_strftime(*buf, SMALLBUF, format, tmx); + if (len != 0 || (**buf == '\0' && errno != ERANGE)) return len; + for (size=1024; ; size*=2) { + *buf = xmalloc(size); + (*buf)[0] = '\0'; + len = date_strftime(*buf, size, format, tmx); + /* + * buflen can be zero EITHER because there's not enough + * room in the string, or because the control command + * goes to the empty string. Make a reasonable guess that + * if the buffer is 1024 times bigger than the length of the + * format string, it's not failing for lack of room. + */ + if (len > 0) break; + xfree(*buf); + if (size >= 1024 * flen) { + rb_sys_fail(format); + break; + } + } + return len; +} + +static VALUE +tmx_m_secs(union DateData *x) +{ + VALUE s; + int df; + + s = day_to_sec(f_sub(m_real_jd(x), + UNIX_EPOCH_IN_CJD)); + if (simple_dat_p(x)) + return s; + df = m_df(x); + if (df) + s = f_add(s, INT2FIX(df)); + return s; +} + +#define MILLISECOND_IN_NANOSECONDS 1000000 + +static VALUE +tmx_m_msecs(union DateData *x) +{ + VALUE s, sf; + + s = sec_to_ms(tmx_m_secs(x)); + if (simple_dat_p(x)) + return s; + sf = m_sf(x); + if (f_nonzero_p(sf)) + s = f_add(s, f_div(sf, INT2FIX(MILLISECOND_IN_NANOSECONDS))); + return s; +} + +static int +tmx_m_of(union DateData *x) +{ + return m_of(x); +} + +static char * +tmx_m_zone(union DateData *x) +{ + VALUE zone = m_zone(x); + /* TODO: fix potential dangling pointer */ + return RSTRING_PTR(zone); +} + +static const struct tmx_funcs tmx_funcs = { + (VALUE (*)(void *))m_real_year, + (int (*)(void *))m_yday, + (int (*)(void *))m_mon, + (int (*)(void *))m_mday, + (VALUE (*)(void *))m_real_cwyear, + (int (*)(void *))m_cweek, + (int (*)(void *))m_cwday, + (int (*)(void *))m_wnum0, + (int (*)(void *))m_wnum1, + (int (*)(void *))m_wday, + (int (*)(void *))m_hour, + (int (*)(void *))m_min, + (int (*)(void *))m_sec, + (VALUE (*)(void *))m_sf_in_sec, + (VALUE (*)(void *))tmx_m_secs, + (VALUE (*)(void *))tmx_m_msecs, + (int (*)(void *))tmx_m_of, + (char *(*)(void *))tmx_m_zone +}; + +static void +set_tmx(VALUE self, struct tmx *tmx) +{ + get_d1(self); + tmx->dat = (void *)dat; + tmx->funcs = &tmx_funcs; +} + +static VALUE +date_strftime_internal(int argc, VALUE *argv, VALUE self, + const char *default_fmt, + void (*func)(VALUE, struct tmx *)) +{ + VALUE vfmt; + const char *fmt; + long len; + char buffer[SMALLBUF], *buf = buffer; + struct tmx tmx; + VALUE str; + + rb_scan_args(argc, argv, "01", &vfmt); + + if (argc < 1) + vfmt = rb_usascii_str_new2(default_fmt); + else { + StringValue(vfmt); + if (!rb_enc_str_asciicompat_p(vfmt)) { + rb_raise(rb_eArgError, + "format should have ASCII compatible encoding"); + } + } + fmt = RSTRING_PTR(vfmt); + len = RSTRING_LEN(vfmt); + (*func)(self, &tmx); + if (memchr(fmt, '\0', len)) { + /* Ruby string may contain \0's. */ + const char *p = fmt, *pe = fmt + len; + + str = rb_str_new(0, 0); + while (p < pe) { + len = date_strftime_alloc(&buf, p, &tmx); + rb_str_cat(str, buf, len); + p += strlen(p); + if (buf != buffer) { + xfree(buf); + buf = buffer; + } + for (fmt = p; p < pe && !*p; ++p); + if (p > fmt) rb_str_cat(str, fmt, p - fmt); + } + rb_enc_copy(str, vfmt); + return str; + } + else + len = date_strftime_alloc(&buf, fmt, &tmx); + + str = rb_str_new(buf, len); + if (buf != buffer) xfree(buf); + rb_enc_copy(str, vfmt); + return str; +} + +/* + * call-seq: + * strftime(format = '%F') -> string + * + * Returns a string representation of the date in +self+, + * formatted according the given +format+: + * + * Date.new(2001, 2, 3).strftime # => "2001-02-03" + * + * For other formats, see + * {Formats for Dates and Times}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html]. + * + */ +static VALUE +d_lite_strftime(int argc, VALUE *argv, VALUE self) +{ + return date_strftime_internal(argc, argv, self, + "%Y-%m-%d", set_tmx); +} + +static VALUE +strftimev(const char *fmt, VALUE self, + void (*func)(VALUE, struct tmx *)) +{ + char buffer[SMALLBUF], *buf = buffer; + struct tmx tmx; + long len; + VALUE str; + + (*func)(self, &tmx); + len = date_strftime_alloc(&buf, fmt, &tmx); + RB_GC_GUARD(self); + str = rb_usascii_str_new(buf, len); + if (buf != buffer) xfree(buf); + return str; +} + +/* + * call-seq: + * asctime -> string + * + * Equivalent to #strftime with argument '%a %b %e %T %Y' + * (or its {shorthand form}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-Shorthand+Conversion+Specifiers] + * '%c'): + * + * Date.new(2001, 2, 3).asctime # => "Sat Feb 3 00:00:00 2001" + * + * See {asctime}[https://linux.die.net/man/3/asctime]. + * + */ +static VALUE +d_lite_asctime(VALUE self) +{ + return strftimev("%a %b %e %H:%M:%S %Y", self, set_tmx); +} + +/* + * call-seq: + * iso8601 -> string + * + * Equivalent to #strftime with argument '%Y-%m-%d' + * (or its {shorthand form}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-Shorthand+Conversion+Specifiers] + * '%F'); + * + * Date.new(2001, 2, 3).iso8601 # => "2001-02-03" + * + */ +static VALUE +d_lite_iso8601(VALUE self) +{ + return strftimev("%Y-%m-%d", self, set_tmx); +} + +/* + * call-seq: + * rfc3339 -> string + * + * Equivalent to #strftime with argument '%FT%T%:z'; + * see {Formats for Dates and Times}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html]: + * + * Date.new(2001, 2, 3).rfc3339 # => "2001-02-03T00:00:00+00:00" + * + */ +static VALUE +d_lite_rfc3339(VALUE self) +{ + return strftimev("%Y-%m-%dT%H:%M:%S%:z", self, set_tmx); +} + +/* + * call-seq: + * rfc2822 -> string + * + * Equivalent to #strftime with argument '%a, %-d %b %Y %T %z'; + * see {Formats for Dates and Times}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html]: + * + * Date.new(2001, 2, 3).rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" + * + */ +static VALUE +d_lite_rfc2822(VALUE self) +{ + return strftimev("%a, %-d %b %Y %T %z", self, set_tmx); +} + +/* + * call-seq: + * httpdate -> string + * + * Equivalent to #strftime with argument '%a, %d %b %Y %T GMT'; + * see {Formats for Dates and Times}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html]: + * + * Date.new(2001, 2, 3).httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" + * + */ +static VALUE +d_lite_httpdate(VALUE self) +{ + volatile VALUE dup = dup_obj_with_new_offset(self, 0); + return strftimev("%a, %d %b %Y %T GMT", dup, set_tmx); +} + +enum { + DECIMAL_SIZE_OF_LONG = DECIMAL_SIZE_OF_BITS(CHAR_BIT*sizeof(long)), + JISX0301_DATE_SIZE = DECIMAL_SIZE_OF_LONG+8 +}; + +static const char * +jisx0301_date_format(char *fmt, size_t size, VALUE jd, VALUE y) +{ + if (FIXNUM_P(jd)) { + long d = FIX2INT(jd); + long s; + char c; + if (d < 2405160) + return "%Y-%m-%d"; + if (d < 2419614) { + c = 'M'; + s = 1867; + } + else if (d < 2424875) { + c = 'T'; + s = 1911; + } + else if (d < 2447535) { + c = 'S'; + s = 1925; + } + else if (d < 2458605) { + c = 'H'; + s = 1988; + } + else { + c = 'R'; + s = 2018; + } + snprintf(fmt, size, "%c%02ld" ".%%m.%%d", c, FIX2INT(y) - s); + return fmt; + } + return "%Y-%m-%d"; +} + +/* + * call-seq: + * jisx0301 -> string + * + * Returns a string representation of the date in +self+ + * in JIS X 0301 format. + * + * Date.new(2001, 2, 3).jisx0301 # => "H13.02.03" + * + */ +static VALUE +d_lite_jisx0301(VALUE self) +{ + char fmtbuf[JISX0301_DATE_SIZE]; + const char *fmt; + + get_d1(self); + fmt = jisx0301_date_format(fmtbuf, sizeof(fmtbuf), + m_real_local_jd(dat), + m_real_year(dat)); + return strftimev(fmt, self, set_tmx); +} + +static VALUE +deconstruct_keys(VALUE self, VALUE keys, int is_datetime) +{ + VALUE h = rb_hash_new(); + long i; + + get_d1(self); + + if (NIL_P(keys)) { + rb_hash_aset(h, sym_year, m_real_year(dat)); + rb_hash_aset(h, sym_month, INT2FIX(m_mon(dat))); + rb_hash_aset(h, sym_day, INT2FIX(m_mday(dat))); + rb_hash_aset(h, sym_yday, INT2FIX(m_yday(dat))); + rb_hash_aset(h, sym_wday, INT2FIX(m_wday(dat))); + if (is_datetime) { + rb_hash_aset(h, sym_hour, INT2FIX(m_hour(dat))); + rb_hash_aset(h, sym_min, INT2FIX(m_min(dat))); + rb_hash_aset(h, sym_sec, INT2FIX(m_sec(dat))); + rb_hash_aset(h, sym_sec_fraction, m_sf_in_sec(dat)); + rb_hash_aset(h, sym_zone, m_zone(dat)); + } + + return h; + } + if (!RB_TYPE_P(keys, T_ARRAY)) { + rb_raise(rb_eTypeError, + "wrong argument type %"PRIsVALUE" (expected Array or nil)", + rb_obj_class(keys)); + + } + + for (i=0; i hash + * + * Returns a hash of the name/value pairs, to use in pattern matching. + * Possible keys are: :year, :month, :day, + * :wday, :yday. + * + * Possible usages: + * + * d = Date.new(2022, 10, 5) + * + * if d in wday: 3, day: ..7 # uses deconstruct_keys underneath + * puts "first Wednesday of the month" + * end + * #=> prints "first Wednesday of the month" + * + * case d + * in year: ...2022 + * puts "too old" + * in month: ..9 + * puts "quarter 1-3" + * in wday: 1..5, month: + * puts "working day in month #{month}" + * end + * #=> prints "working day in month 10" + * + * Note that deconstruction by pattern can also be combined with class check: + * + * if d in Date(wday: 3, day: ..7) + * puts "first Wednesday of the month" + * end + * + */ +static VALUE +d_lite_deconstruct_keys(VALUE self, VALUE keys) +{ + return deconstruct_keys(self, keys, /* is_datetime=false */ 0); +} + +#ifndef NDEBUG +/* :nodoc: */ +static VALUE +d_lite_marshal_dump_old(VALUE self) +{ + VALUE a; + + get_d1(self); + + a = rb_ary_new3(3, + m_ajd(dat), + m_of_in_day(dat), + DBL2NUM(m_sg(dat))); + + if (FL_TEST(self, FL_EXIVAR)) { + rb_copy_generic_ivar(a, self); + FL_SET(a, FL_EXIVAR); + } + + return a; +} +#endif + +/* :nodoc: */ +static VALUE +d_lite_marshal_dump(VALUE self) +{ + VALUE a; + + get_d1(self); + + a = rb_ary_new3(6, + m_nth(dat), + INT2FIX(m_jd(dat)), + INT2FIX(m_df(dat)), + m_sf(dat), + INT2FIX(m_of(dat)), + DBL2NUM(m_sg(dat))); + + if (FL_TEST(self, FL_EXIVAR)) { + rb_copy_generic_ivar(a, self); + FL_SET(a, FL_EXIVAR); + } + + return a; +} + +/* :nodoc: */ +static VALUE +d_lite_marshal_load(VALUE self, VALUE a) +{ + VALUE nth, sf; + int jd, df, of; + double sg; + + get_d1(self); + + rb_check_frozen(self); + + if (!RB_TYPE_P(a, T_ARRAY)) + rb_raise(rb_eTypeError, "expected an array"); + + switch (RARRAY_LEN(a)) { + case 2: /* 1.6.x */ + case 3: /* 1.8.x, 1.9.2 */ + { + VALUE ajd, vof, vsg; + + if (RARRAY_LEN(a) == 2) { + ajd = f_sub(RARRAY_AREF(a, 0), half_days_in_day); + vof = INT2FIX(0); + vsg = RARRAY_AREF(a, 1); + if (!k_numeric_p(vsg)) + vsg = DBL2NUM(RTEST(vsg) ? GREGORIAN : JULIAN); + } + else { + ajd = RARRAY_AREF(a, 0); + vof = RARRAY_AREF(a, 1); + vsg = RARRAY_AREF(a, 2); + } + + old_to_new(ajd, vof, vsg, + &nth, &jd, &df, &sf, &of, &sg); + } + break; + case 6: + { + nth = RARRAY_AREF(a, 0); + jd = NUM2INT(RARRAY_AREF(a, 1)); + df = NUM2INT(RARRAY_AREF(a, 2)); + sf = RARRAY_AREF(a, 3); + of = NUM2INT(RARRAY_AREF(a, 4)); + sg = NUM2DBL(RARRAY_AREF(a, 5)); + } + break; + default: + rb_raise(rb_eTypeError, "invalid size"); + break; + } + + if (simple_dat_p(dat)) { + if (df || !f_zero_p(sf) || of) { + /* loading a fractional date; promote to complex */ + dat = ruby_xrealloc(dat, sizeof(struct ComplexDateData)); + RTYPEDDATA(self)->data = dat; + goto complex_data; + } + set_to_simple(self, &dat->s, nth, jd, sg, 0, 0, 0, HAVE_JD); + } else { + complex_data: + set_to_complex(self, &dat->c, nth, jd, df, sf, of, sg, + 0, 0, 0, 0, 0, 0, + HAVE_JD | HAVE_DF); + } + + if (FL_TEST(a, FL_EXIVAR)) { + rb_copy_generic_ivar(self, a); + FL_SET(self, FL_EXIVAR); + } + + return self; +} + +/* :nodoc: */ +static VALUE +date_s__load(VALUE klass, VALUE s) +{ + VALUE a, obj; + + a = rb_marshal_load(s); + obj = d_lite_s_alloc(klass); + return d_lite_marshal_load(obj, a); +} + +/* datetime */ + +/* + * call-seq: + * DateTime.jd([jd=0[, hour=0[, minute=0[, second=0[, offset=0[, start=Date::ITALY]]]]]]) -> datetime + * + * Creates a DateTime object denoting the given chronological Julian + * day number. + * + * DateTime.jd(2451944) #=> # + * DateTime.jd(2451945) #=> # + * DateTime.jd(Rational('0.5')) + * #=> # + */ +static VALUE +datetime_s_jd(int argc, VALUE *argv, VALUE klass) +{ + VALUE vjd, vh, vmin, vs, vof, vsg, jd, fr, fr2, ret; + int h, min, s, rof; + double sg; + + rb_scan_args(argc, argv, "06", &vjd, &vh, &vmin, &vs, &vof, &vsg); + + jd = INT2FIX(0); + + h = min = s = 0; + fr2 = INT2FIX(0); + rof = 0; + sg = DEFAULT_SG; + + switch (argc) { + case 6: + val2sg(vsg, sg); + case 5: + val2off(vof, rof); + case 4: + check_numeric(vs, "second"); + num2int_with_frac(s, positive_inf); + case 3: + check_numeric(vmin, "minute"); + num2int_with_frac(min, 3); + case 2: + check_numeric(vh, "hour"); + num2int_with_frac(h, 2); + case 1: + check_numeric(vjd, "jd"); + num2num_with_frac(jd, 1); + } + + { + VALUE nth; + int rh, rmin, rs, rjd, rjd2; + + if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) + rb_raise(eDateError, "invalid date"); + canon24oc(); + + decode_jd(jd, &nth, &rjd); + rjd2 = jd_local_to_utc(rjd, + time_to_df(rh, rmin, rs), + rof); + + ret = d_complex_new_internal(klass, + nth, rjd2, + 0, INT2FIX(0), + rof, sg, + 0, 0, 0, + rh, rmin, rs, + HAVE_JD | HAVE_TIME); + } + add_frac(); + return ret; +} + +/* + * call-seq: + * DateTime.ordinal([year=-4712[, yday=1[, hour=0[, minute=0[, second=0[, offset=0[, start=Date::ITALY]]]]]]]) -> datetime + * + * Creates a DateTime object denoting the given ordinal date. + * + * DateTime.ordinal(2001,34) #=> # + * DateTime.ordinal(2001,34,4,5,6,'+7') + * #=> # + * DateTime.ordinal(2001,-332,-20,-55,-54,'+7') + * #=> # + */ +static VALUE +datetime_s_ordinal(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vd, vh, vmin, vs, vof, vsg, y, fr, fr2, ret; + int d, h, min, s, rof; + double sg; + + rb_scan_args(argc, argv, "07", &vy, &vd, &vh, &vmin, &vs, &vof, &vsg); + + y = INT2FIX(-4712); + d = 1; + + h = min = s = 0; + fr2 = INT2FIX(0); + rof = 0; + sg = DEFAULT_SG; + + switch (argc) { + case 7: + val2sg(vsg, sg); + case 6: + val2off(vof, rof); + case 5: + check_numeric(vs, "second"); + num2int_with_frac(s, positive_inf); + case 4: + check_numeric(vmin, "minute"); + num2int_with_frac(min, 4); + case 3: + check_numeric(vh, "hour"); + num2int_with_frac(h, 3); + case 2: + check_numeric(vd, "yday"); + num2int_with_frac(d, 2); + case 1: + check_numeric(vy, "year"); + y = vy; + } + + { + VALUE nth; + int ry, rd, rh, rmin, rs, rjd, rjd2, ns; + + if (!valid_ordinal_p(y, d, sg, + &nth, &ry, + &rd, &rjd, + &ns)) + rb_raise(eDateError, "invalid date"); + if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) + rb_raise(eDateError, "invalid date"); + canon24oc(); + + rjd2 = jd_local_to_utc(rjd, + time_to_df(rh, rmin, rs), + rof); + + ret = d_complex_new_internal(klass, + nth, rjd2, + 0, INT2FIX(0), + rof, sg, + 0, 0, 0, + rh, rmin, rs, + HAVE_JD | HAVE_TIME); + } + add_frac(); + return ret; +} + +/* + * Same as DateTime.new. + */ +static VALUE +datetime_s_civil(int argc, VALUE *argv, VALUE klass) +{ + return datetime_initialize(argc, argv, d_lite_s_alloc_complex(klass)); +} + +static VALUE +datetime_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE vy, vm, vd, vh, vmin, vs, vof, vsg, y, fr, fr2, ret; + int m, d, h, min, s, rof; + double sg; + struct ComplexDateData *dat = rb_check_typeddata(self, &d_lite_type); + + if (!complex_dat_p(dat)) { + rb_raise(rb_eTypeError, "DateTime expected"); + } + + rb_scan_args(argc, argv, "08", &vy, &vm, &vd, &vh, &vmin, &vs, &vof, &vsg); + + y = INT2FIX(-4712); + m = 1; + d = 1; + + h = min = s = 0; + fr2 = INT2FIX(0); + rof = 0; + sg = DEFAULT_SG; + + switch (argc) { + case 8: + val2sg(vsg, sg); + case 7: + val2off(vof, rof); + case 6: + check_numeric(vs, "second"); + num2int_with_frac(s, positive_inf); + case 5: + check_numeric(vmin, "minute"); + num2int_with_frac(min, 5); + case 4: + check_numeric(vh, "hour"); + num2int_with_frac(h, 4); + case 3: + check_numeric(vd, "day"); + num2int_with_frac(d, 3); + case 2: + check_numeric(vm, "month"); + m = NUM2INT(vm); + case 1: + check_numeric(vy, "year"); + y = vy; + } + + if (guess_style(y, sg) < 0) { + VALUE nth; + int ry, rm, rd, rh, rmin, rs; + + if (!valid_gregorian_p(y, m, d, + &nth, &ry, + &rm, &rd)) + rb_raise(eDateError, "invalid date"); + if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) + rb_raise(eDateError, "invalid date"); + canon24oc(); + + set_to_complex(self, dat, + nth, 0, + 0, INT2FIX(0), + rof, sg, + ry, rm, rd, + rh, rmin, rs, + HAVE_CIVIL | HAVE_TIME); + } + else { + VALUE nth; + int ry, rm, rd, rh, rmin, rs, rjd, rjd2, ns; + + if (!valid_civil_p(y, m, d, sg, + &nth, &ry, + &rm, &rd, &rjd, + &ns)) + rb_raise(eDateError, "invalid date"); + if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) + rb_raise(eDateError, "invalid date"); + canon24oc(); + + rjd2 = jd_local_to_utc(rjd, + time_to_df(rh, rmin, rs), + rof); + + set_to_complex(self, dat, + nth, rjd2, + 0, INT2FIX(0), + rof, sg, + ry, rm, rd, + rh, rmin, rs, + HAVE_JD | HAVE_CIVIL | HAVE_TIME); + } + ret = self; + add_frac(); + return ret; +} + +/* + * call-seq: + * DateTime.commercial([cwyear=-4712[, cweek=1[, cwday=1[, hour=0[, minute=0[, second=0[, offset=0[, start=Date::ITALY]]]]]]]]) -> datetime + * + * Creates a DateTime object denoting the given week date. + * + * DateTime.commercial(2001) #=> # + * DateTime.commercial(2002) #=> # + * DateTime.commercial(2001,5,6,4,5,6,'+7') + * #=> # + */ +static VALUE +datetime_s_commercial(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vw, vd, vh, vmin, vs, vof, vsg, y, fr, fr2, ret; + int w, d, h, min, s, rof; + double sg; + + rb_scan_args(argc, argv, "08", &vy, &vw, &vd, &vh, &vmin, &vs, &vof, &vsg); + + y = INT2FIX(-4712); + w = 1; + d = 1; + + h = min = s = 0; + fr2 = INT2FIX(0); + rof = 0; + sg = DEFAULT_SG; + + switch (argc) { + case 8: + val2sg(vsg, sg); + case 7: + val2off(vof, rof); + case 6: + check_numeric(vs, "second"); + num2int_with_frac(s, positive_inf); + case 5: + check_numeric(vmin, "minute"); + num2int_with_frac(min, 5); + case 4: + check_numeric(vh, "hour"); + num2int_with_frac(h, 4); + case 3: + check_numeric(vd, "cwday"); + num2int_with_frac(d, 3); + case 2: + check_numeric(vw, "cweek"); + w = NUM2INT(vw); + case 1: + check_numeric(vy, "year"); + y = vy; + } + + { + VALUE nth; + int ry, rw, rd, rh, rmin, rs, rjd, rjd2, ns; + + if (!valid_commercial_p(y, w, d, sg, + &nth, &ry, + &rw, &rd, &rjd, + &ns)) + rb_raise(eDateError, "invalid date"); + if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) + rb_raise(eDateError, "invalid date"); + canon24oc(); + + rjd2 = jd_local_to_utc(rjd, + time_to_df(rh, rmin, rs), + rof); + + ret = d_complex_new_internal(klass, + nth, rjd2, + 0, INT2FIX(0), + rof, sg, + 0, 0, 0, + rh, rmin, rs, + HAVE_JD | HAVE_TIME); + } + add_frac(); + return ret; +} + +#ifndef NDEBUG +/* :nodoc: */ +static VALUE +datetime_s_weeknum(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vw, vd, vf, vh, vmin, vs, vof, vsg, y, fr, fr2, ret; + int w, d, f, h, min, s, rof; + double sg; + + rb_scan_args(argc, argv, "09", &vy, &vw, &vd, &vf, + &vh, &vmin, &vs, &vof, &vsg); + + y = INT2FIX(-4712); + w = 0; + d = 1; + f = 0; + + h = min = s = 0; + fr2 = INT2FIX(0); + rof = 0; + sg = DEFAULT_SG; + + switch (argc) { + case 9: + val2sg(vsg, sg); + case 8: + val2off(vof, rof); + case 7: + num2int_with_frac(s, positive_inf); + case 6: + num2int_with_frac(min, 6); + case 5: + num2int_with_frac(h, 5); + case 4: + f = NUM2INT(vf); + case 3: + num2int_with_frac(d, 4); + case 2: + w = NUM2INT(vw); + case 1: + y = vy; + } + + { + VALUE nth; + int ry, rw, rd, rh, rmin, rs, rjd, rjd2, ns; + + if (!valid_weeknum_p(y, w, d, f, sg, + &nth, &ry, + &rw, &rd, &rjd, + &ns)) + rb_raise(eDateError, "invalid date"); + if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) + rb_raise(eDateError, "invalid date"); + canon24oc(); + + rjd2 = jd_local_to_utc(rjd, + time_to_df(rh, rmin, rs), + rof); + ret = d_complex_new_internal(klass, + nth, rjd2, + 0, INT2FIX(0), + rof, sg, + 0, 0, 0, + rh, rmin, rs, + HAVE_JD | HAVE_TIME); + } + add_frac(); + return ret; +} + +/* :nodoc: */ +static VALUE +datetime_s_nth_kday(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vm, vn, vk, vh, vmin, vs, vof, vsg, y, fr, fr2, ret; + int m, n, k, h, min, s, rof; + double sg; + + rb_scan_args(argc, argv, "09", &vy, &vm, &vn, &vk, + &vh, &vmin, &vs, &vof, &vsg); + + y = INT2FIX(-4712); + m = 1; + n = 1; + k = 1; + + h = min = s = 0; + fr2 = INT2FIX(0); + rof = 0; + sg = DEFAULT_SG; + + switch (argc) { + case 9: + val2sg(vsg, sg); + case 8: + val2off(vof, rof); + case 7: + num2int_with_frac(s, positive_inf); + case 6: + num2int_with_frac(min, 6); + case 5: + num2int_with_frac(h, 5); + case 4: + num2int_with_frac(k, 4); + case 3: + n = NUM2INT(vn); + case 2: + m = NUM2INT(vm); + case 1: + y = vy; + } + + { + VALUE nth; + int ry, rm, rn, rk, rh, rmin, rs, rjd, rjd2, ns; + + if (!valid_nth_kday_p(y, m, n, k, sg, + &nth, &ry, + &rm, &rn, &rk, &rjd, + &ns)) + rb_raise(eDateError, "invalid date"); + if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) + rb_raise(eDateError, "invalid date"); + canon24oc(); + + rjd2 = jd_local_to_utc(rjd, + time_to_df(rh, rmin, rs), + rof); + ret = d_complex_new_internal(klass, + nth, rjd2, + 0, INT2FIX(0), + rof, sg, + 0, 0, 0, + rh, rmin, rs, + HAVE_JD | HAVE_TIME); + } + add_frac(); + return ret; +} +#endif + +/* + * call-seq: + * DateTime.now([start=Date::ITALY]) -> datetime + * + * Creates a DateTime object denoting the present time. + * + * DateTime.now #=> # + */ +static VALUE +datetime_s_now(int argc, VALUE *argv, VALUE klass) +{ + VALUE vsg, nth, ret; + double sg; +#ifdef HAVE_CLOCK_GETTIME + struct timespec ts; +#else + struct timeval tv; +#endif + time_t sec; + struct tm tm; + long sf, of; + int y, ry, m, d, h, min, s; + + rb_scan_args(argc, argv, "01", &vsg); + + if (argc < 1) + sg = DEFAULT_SG; + else + sg = NUM2DBL(vsg); + +#ifdef HAVE_CLOCK_GETTIME + if (clock_gettime(CLOCK_REALTIME, &ts) == -1) + rb_sys_fail("clock_gettime"); + sec = ts.tv_sec; +#else + if (gettimeofday(&tv, NULL) == -1) + rb_sys_fail("gettimeofday"); + sec = tv.tv_sec; +#endif + tzset(); + if (!localtime_r(&sec, &tm)) + rb_sys_fail("localtime"); + + y = tm.tm_year + 1900; + m = tm.tm_mon + 1; + d = tm.tm_mday; + h = tm.tm_hour; + min = tm.tm_min; + s = tm.tm_sec; + if (s == 60) + s = 59; +#ifdef HAVE_STRUCT_TM_TM_GMTOFF + of = tm.tm_gmtoff; +#elif defined(HAVE_TIMEZONE) +#if defined(HAVE_ALTZONE) && !defined(_AIX) + of = (long)-((tm.tm_isdst > 0) ? altzone : timezone); +#else + of = (long)-timezone; + if (tm.tm_isdst) { + time_t sec2; + + tm.tm_isdst = 0; + sec2 = mktime(&tm); + of += (long)difftime(sec2, sec); + } +#endif +#elif defined(HAVE_TIMEGM) + { + time_t sec2; + + sec2 = timegm(&tm); + of = (long)difftime(sec2, sec); + } +#else + { + struct tm tm2; + time_t sec2; + + if (!gmtime_r(&sec, &tm2)) + rb_sys_fail("gmtime"); + tm2.tm_isdst = tm.tm_isdst; + sec2 = mktime(&tm2); + of = (long)difftime(sec, sec2); + } +#endif +#ifdef HAVE_CLOCK_GETTIME + sf = ts.tv_nsec; +#else + sf = tv.tv_usec * 1000; +#endif + + if (of < -DAY_IN_SECONDS || of > DAY_IN_SECONDS) { + of = 0; + rb_warning("invalid offset is ignored"); + } + + decode_year(INT2FIX(y), -1, &nth, &ry); + + ret = d_complex_new_internal(klass, + nth, 0, + 0, LONG2NUM(sf), + (int)of, GREGORIAN, + ry, m, d, + h, min, s, + HAVE_CIVIL | HAVE_TIME); + { + get_d1(ret); + set_sg(dat, sg); + } + return ret; +} + +static VALUE +dt_new_by_frags(VALUE klass, VALUE hash, VALUE sg) +{ + VALUE jd, sf, t; + int df, of; + + if (!c_valid_start_p(NUM2DBL(sg))) { + sg = INT2FIX(DEFAULT_SG); + rb_warning("invalid start is ignored"); + } + + if (NIL_P(hash)) + rb_raise(eDateError, "invalid date"); + + if (NIL_P(ref_hash("jd")) && + NIL_P(ref_hash("yday")) && + !NIL_P(ref_hash("year")) && + !NIL_P(ref_hash("mon")) && + !NIL_P(ref_hash("mday"))) { + jd = rt__valid_civil_p(ref_hash("year"), + ref_hash("mon"), + ref_hash("mday"), sg); + + if (NIL_P(ref_hash("hour"))) + set_hash("hour", INT2FIX(0)); + if (NIL_P(ref_hash("min"))) + set_hash("min", INT2FIX(0)); + if (NIL_P(ref_hash("sec"))) + set_hash("sec", INT2FIX(0)); + else if (f_eqeq_p(ref_hash("sec"), INT2FIX(60))) + set_hash("sec", INT2FIX(59)); + } + else { + hash = rt_rewrite_frags(hash); + hash = rt_complete_frags(klass, hash); + jd = rt__valid_date_frags_p(hash, sg); + } + + if (NIL_P(jd)) + rb_raise(eDateError, "invalid date"); + + { + int rh, rmin, rs; + + if (!c_valid_time_p(NUM2INT(ref_hash("hour")), + NUM2INT(ref_hash("min")), + NUM2INT(ref_hash("sec")), + &rh, &rmin, &rs)) + rb_raise(eDateError, "invalid date"); + + df = time_to_df(rh, rmin, rs); + } + + t = ref_hash("sec_fraction"); + if (NIL_P(t)) + sf = INT2FIX(0); + else + sf = sec_to_ns(t); + + t = ref_hash("offset"); + if (NIL_P(t)) + of = 0; + else { + of = NUM2INT(t); + if (of < -DAY_IN_SECONDS || of > DAY_IN_SECONDS) { + of = 0; + rb_warning("invalid offset is ignored"); + } + } + { + VALUE nth; + int rjd, rjd2; + + decode_jd(jd, &nth, &rjd); + rjd2 = jd_local_to_utc(rjd, df, of); + df = df_local_to_utc(df, of); + + return d_complex_new_internal(klass, + nth, rjd2, + df, sf, + of, NUM2DBL(sg), + 0, 0, 0, + 0, 0, 0, + HAVE_JD | HAVE_DF); + } +} + +/* + * call-seq: + * DateTime._strptime(string[, format='%FT%T%z']) -> hash + * + * Parses the given representation of date and time with the given + * template, and returns a hash of parsed elements. _strptime does + * not support specification of flags and width unlike strftime. + * + * See also strptime(3) and #strftime. + */ +static VALUE +datetime_s__strptime(int argc, VALUE *argv, VALUE klass) +{ + return date_s__strptime_internal(argc, argv, klass, "%FT%T%z"); +} + +/* + * call-seq: + * DateTime.strptime([string='-4712-01-01T00:00:00+00:00'[, format='%FT%T%z'[ ,start=Date::ITALY]]]) -> datetime + * + * Parses the given representation of date and time with the given + * template, and creates a DateTime object. strptime does not support + * specification of flags and width unlike strftime. + * + * DateTime.strptime('2001-02-03T04:05:06+07:00', '%Y-%m-%dT%H:%M:%S%z') + * #=> # + * DateTime.strptime('03-02-2001 04:05:06 PM', '%d-%m-%Y %I:%M:%S %p') + * #=> # + * DateTime.strptime('2001-W05-6T04:05:06+07:00', '%G-W%V-%uT%H:%M:%S%z') + * #=> # + * DateTime.strptime('2001 04 6 04 05 06 +7', '%Y %U %w %H %M %S %z') + * #=> # + * DateTime.strptime('2001 05 6 04 05 06 +7', '%Y %W %u %H %M %S %z') + * #=> # + * DateTime.strptime('-1', '%s') + * #=> # + * DateTime.strptime('-1000', '%Q') + * #=> # + * DateTime.strptime('sat3feb014pm+7', '%a%d%b%y%H%p%z') + * #=> # + * + * See also strptime(3) and #strftime. + */ +static VALUE +datetime_s_strptime(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, fmt, sg; + + rb_scan_args(argc, argv, "03", &str, &fmt, &sg); + + switch (argc) { + case 0: + str = rb_str_new2(JULIAN_EPOCH_DATETIME); + case 1: + fmt = rb_str_new2("%FT%T%z"); + case 2: + sg = INT2FIX(DEFAULT_SG); + } + + { + VALUE argv2[2], hash; + + argv2[0] = str; + argv2[1] = fmt; + hash = date_s__strptime(2, argv2, klass); + return dt_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * DateTime.parse(string='-4712-01-01T00:00:00+00:00'[, comp=true[, start=Date::ITALY]], limit: 128) -> datetime + * + * Parses the given representation of date and time, and creates a + * DateTime object. + * + * This method *does* *not* function as a validator. If the input + * string does not match valid formats strictly, you may get a cryptic + * result. Should consider to use DateTime.strptime instead of this + * method as possible. + * + * If the optional second argument is true and the detected year is in + * the range "00" to "99", makes it full. + * + * DateTime.parse('2001-02-03T04:05:06+07:00') + * #=> # + * DateTime.parse('20010203T040506+0700') + * #=> # + * DateTime.parse('3rd Feb 2001 04:05:06 PM') + * #=> # + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing limit: nil, but note + * that it may take a long time to parse. + */ +static VALUE +datetime_s_parse(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, comp, sg, opt; + + argc = rb_scan_args(argc, argv, "03:", &str, &comp, &sg, &opt); + + switch (argc) { + case 0: + str = rb_str_new2(JULIAN_EPOCH_DATETIME); + case 1: + comp = Qtrue; + case 2: + sg = INT2FIX(DEFAULT_SG); + } + + { + int argc2 = 2; + VALUE argv2[3], hash; + argv2[0] = str; + argv2[1] = comp; + argv2[2] = opt; + if (!NIL_P(opt)) argc2++; + hash = date_s__parse(argc2, argv2, klass); + return dt_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * DateTime.iso8601(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime + * + * Creates a new DateTime object by parsing from a string according to + * some typical ISO 8601 formats. + * + * DateTime.iso8601('2001-02-03T04:05:06+07:00') + * #=> # + * DateTime.iso8601('20010203T040506+0700') + * #=> # + * DateTime.iso8601('2001-W05-6T04:05:06+07:00') + * #=> # + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing limit: nil, but note + * that it may take a long time to parse. + */ +static VALUE +datetime_s_iso8601(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg, opt; + + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + + switch (argc) { + case 0: + str = rb_str_new2(JULIAN_EPOCH_DATETIME); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + hash = date_s__iso8601(argc2, argv2, klass); + return dt_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * DateTime.rfc3339(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime + * + * Creates a new DateTime object by parsing from a string according to + * some typical RFC 3339 formats. + * + * DateTime.rfc3339('2001-02-03T04:05:06+07:00') + * #=> # + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing limit: nil, but note + * that it may take a long time to parse. + */ +static VALUE +datetime_s_rfc3339(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg, opt; + + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + + switch (argc) { + case 0: + str = rb_str_new2(JULIAN_EPOCH_DATETIME); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + hash = date_s__rfc3339(argc2, argv2, klass); + return dt_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * DateTime.xmlschema(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime + * + * Creates a new DateTime object by parsing from a string according to + * some typical XML Schema formats. + * + * DateTime.xmlschema('2001-02-03T04:05:06+07:00') + * #=> # + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing limit: nil, but note + * that it may take a long time to parse. + */ +static VALUE +datetime_s_xmlschema(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg, opt; + + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + + switch (argc) { + case 0: + str = rb_str_new2(JULIAN_EPOCH_DATETIME); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + hash = date_s__xmlschema(argc2, argv2, klass); + return dt_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * DateTime.rfc2822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY], limit: 128) -> datetime + * DateTime.rfc822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY], limit: 128) -> datetime + * + * Creates a new DateTime object by parsing from a string according to + * some typical RFC 2822 formats. + * + * DateTime.rfc2822('Sat, 3 Feb 2001 04:05:06 +0700') + * #=> # + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing limit: nil, but note + * that it may take a long time to parse. + */ +static VALUE +datetime_s_rfc2822(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg, opt; + + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + + switch (argc) { + case 0: + str = rb_str_new2(JULIAN_EPOCH_DATETIME_RFC3339); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + hash = date_s__rfc2822(argc2, argv2, klass); + return dt_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * DateTime.httpdate(string='Mon, 01 Jan -4712 00:00:00 GMT'[, start=Date::ITALY]) -> datetime + * + * Creates a new DateTime object by parsing from a string according to + * some RFC 2616 format. + * + * DateTime.httpdate('Sat, 03 Feb 2001 04:05:06 GMT') + * #=> # + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing limit: nil, but note + * that it may take a long time to parse. + */ +static VALUE +datetime_s_httpdate(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg, opt; + + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + + switch (argc) { + case 0: + str = rb_str_new2(JULIAN_EPOCH_DATETIME_HTTPDATE); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + hash = date_s__httpdate(argc2, argv2, klass); + return dt_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * DateTime.jisx0301(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime + * + * Creates a new DateTime object by parsing from a string according to + * some typical JIS X 0301 formats. + * + * DateTime.jisx0301('H13.02.03T04:05:06+07:00') + * #=> # + * + * For no-era year, legacy format, Heisei is assumed. + * + * DateTime.jisx0301('13.02.03T04:05:06+07:00') + * #=> # + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing limit: nil, but note + * that it may take a long time to parse. + */ +static VALUE +datetime_s_jisx0301(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg, opt; + + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); + + switch (argc) { + case 0: + str = rb_str_new2(JULIAN_EPOCH_DATETIME); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + hash = date_s__jisx0301(argc2, argv2, klass); + return dt_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * dt.to_s -> string + * + * Returns a string in an ISO 8601 format. (This method doesn't use the + * expanded representations.) + * + * DateTime.new(2001,2,3,4,5,6,'-7').to_s + * #=> "2001-02-03T04:05:06-07:00" + */ +static VALUE +dt_lite_to_s(VALUE self) +{ + return strftimev("%Y-%m-%dT%H:%M:%S%:z", self, set_tmx); +} + +/* + * call-seq: + * strftime(format = '%FT%T%:z') -> string + * + * Returns a string representation of +self+, + * formatted according the given +format: + * + * DateTime.now.strftime # => "2022-07-01T11:03:19-05:00" + * + * For other formats, + * see {Formats for Dates and Times}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html]: + * + */ +static VALUE +dt_lite_strftime(int argc, VALUE *argv, VALUE self) +{ + return date_strftime_internal(argc, argv, self, + "%Y-%m-%dT%H:%M:%S%:z", set_tmx); +} + +static VALUE +iso8601_timediv(VALUE self, long n) +{ + static const char timefmt[] = "T%H:%M:%S"; + static const char zone[] = "%:z"; + char fmt[sizeof(timefmt) + sizeof(zone) + rb_strlen_lit(".%N") + + DECIMAL_SIZE_OF_LONG]; + char *p = fmt; + + memcpy(p, timefmt, sizeof(timefmt)-1); + p += sizeof(timefmt)-1; + if (n > 0) p += snprintf(p, fmt+sizeof(fmt)-p, ".%%%ldN", n); + memcpy(p, zone, sizeof(zone)); + return strftimev(fmt, self, set_tmx); +} + +/* + * call-seq: + * dt.iso8601([n=0]) -> string + * dt.xmlschema([n=0]) -> string + * + * This method is equivalent to strftime('%FT%T%:z'). + * The optional argument +n+ is the number of digits for fractional seconds. + * + * DateTime.parse('2001-02-03T04:05:06.123456789+07:00').iso8601(9) + * #=> "2001-02-03T04:05:06.123456789+07:00" + */ +static VALUE +dt_lite_iso8601(int argc, VALUE *argv, VALUE self) +{ + long n = 0; + + rb_check_arity(argc, 0, 1); + if (argc >= 1) + n = NUM2LONG(argv[0]); + + return rb_str_append(strftimev("%Y-%m-%d", self, set_tmx), + iso8601_timediv(self, n)); +} + +/* + * call-seq: + * dt.rfc3339([n=0]) -> string + * + * This method is equivalent to strftime('%FT%T%:z'). + * The optional argument +n+ is the number of digits for fractional seconds. + * + * DateTime.parse('2001-02-03T04:05:06.123456789+07:00').rfc3339(9) + * #=> "2001-02-03T04:05:06.123456789+07:00" + */ +static VALUE +dt_lite_rfc3339(int argc, VALUE *argv, VALUE self) +{ + return dt_lite_iso8601(argc, argv, self); +} + +/* + * call-seq: + * dt.jisx0301([n=0]) -> string + * + * Returns a string in a JIS X 0301 format. + * The optional argument +n+ is the number of digits for fractional seconds. + * + * DateTime.parse('2001-02-03T04:05:06.123456789+07:00').jisx0301(9) + * #=> "H13.02.03T04:05:06.123456789+07:00" + */ +static VALUE +dt_lite_jisx0301(int argc, VALUE *argv, VALUE self) +{ + long n = 0; + + rb_check_arity(argc, 0, 1); + if (argc >= 1) + n = NUM2LONG(argv[0]); + + return rb_str_append(d_lite_jisx0301(self), + iso8601_timediv(self, n)); +} + +/* + * call-seq: + * deconstruct_keys(array_of_names_or_nil) -> hash + * + * Returns a hash of the name/value pairs, to use in pattern matching. + * Possible keys are: :year, :month, :day, + * :wday, :yday, :hour, :min, + * :sec, :sec_fraction, :zone. + * + * Possible usages: + * + * dt = DateTime.new(2022, 10, 5, 13, 30) + * + * if d in wday: 1..5, hour: 10..18 # uses deconstruct_keys underneath + * puts "Working time" + * end + * #=> prints "Working time" + * + * case dt + * in year: ...2022 + * puts "too old" + * in month: ..9 + * puts "quarter 1-3" + * in wday: 1..5, month: + * puts "working day in month #{month}" + * end + * #=> prints "working day in month 10" + * + * Note that deconstruction by pattern can also be combined with class check: + * + * if d in DateTime(wday: 1..5, hour: 10..18, day: ..7) + * puts "Working time, first week of the month" + * end + * + */ +static VALUE +dt_lite_deconstruct_keys(VALUE self, VALUE keys) +{ + return deconstruct_keys(self, keys, /* is_datetime=true */ 1); +} + +/* conversions */ + +#define f_subsec(x) rb_funcall(x, rb_intern("subsec"), 0) +#define f_utc_offset(x) rb_funcall(x, rb_intern("utc_offset"), 0) +#define f_local3(x,y,m,d) rb_funcall(x, rb_intern("local"), 3, y, m, d) + +/* + * call-seq: + * t.to_time -> time + * + * Returns self. + */ +static VALUE +time_to_time(VALUE self) +{ + return self; +} + +/* + * call-seq: + * t.to_date -> date + * + * Returns a Date object which denotes self. + */ +static VALUE +time_to_date(VALUE self) +{ + VALUE y, nth, ret; + int ry, m, d; + + y = f_year(self); + m = FIX2INT(f_mon(self)); + d = FIX2INT(f_mday(self)); + + decode_year(y, -1, &nth, &ry); + + ret = d_simple_new_internal(cDate, + nth, 0, + GREGORIAN, + ry, m, d, + HAVE_CIVIL); + { + get_d1(ret); + set_sg(dat, DEFAULT_SG); + } + return ret; +} + +/* + * call-seq: + * t.to_datetime -> datetime + * + * Returns a DateTime object which denotes self. + */ +static VALUE +time_to_datetime(VALUE self) +{ + VALUE y, sf, nth, ret; + int ry, m, d, h, min, s, of; + + y = f_year(self); + m = FIX2INT(f_mon(self)); + d = FIX2INT(f_mday(self)); + + h = FIX2INT(f_hour(self)); + min = FIX2INT(f_min(self)); + s = FIX2INT(f_sec(self)); + if (s == 60) + s = 59; + + sf = sec_to_ns(f_subsec(self)); + of = FIX2INT(f_utc_offset(self)); + + decode_year(y, -1, &nth, &ry); + + ret = d_complex_new_internal(cDateTime, + nth, 0, + 0, sf, + of, GREGORIAN, + ry, m, d, + h, min, s, + HAVE_CIVIL | HAVE_TIME); + { + get_d1(ret); + set_sg(dat, DEFAULT_SG); + } + return ret; +} + +/* + * call-seq: + * to_time -> time + * + * Returns a new Time object with the same value as +self+; + * if +self+ is a Julian date, derives its Gregorian date + * for conversion to the \Time object: + * + * Date.new(2001, 2, 3).to_time # => 2001-02-03 00:00:00 -0600 + * Date.new(2001, 2, 3, Date::JULIAN).to_time # => 2001-02-16 00:00:00 -0600 + * + */ +static VALUE +date_to_time(VALUE self) +{ + VALUE t; + + get_d1a(self); + + if (m_julian_p(adat)) { + VALUE g = d_lite_gregorian(self); + get_d1b(g); + adat = bdat; + self = g; + } + + t = f_local3(rb_cTime, + m_real_year(adat), + INT2FIX(m_mon(adat)), + INT2FIX(m_mday(adat))); + RB_GC_GUARD(self); /* may be the converted gregorian */ + return t; +} + +/* + * call-seq: + * to_date -> self + * + * Returns +self+. + */ +static VALUE +date_to_date(VALUE self) +{ + return self; +} + +/* + * call-seq: + * d.to_datetime -> datetime + * + * Returns a DateTime whose value is the same as +self+: + * + * Date.new(2001, 2, 3).to_datetime # => # + * + */ +static VALUE +date_to_datetime(VALUE self) +{ + get_d1a(self); + + if (simple_dat_p(adat)) { + VALUE new = d_lite_s_alloc_simple(cDateTime); + { + get_d1b(new); + bdat->s = adat->s; + return new; + } + } + else { + VALUE new = d_lite_s_alloc_complex(cDateTime); + { + get_d1b(new); + bdat->c = adat->c; + bdat->c.df = 0; + RB_OBJ_WRITE(new, &bdat->c.sf, INT2FIX(0)); +#ifndef USE_PACK + bdat->c.hour = 0; + bdat->c.min = 0; + bdat->c.sec = 0; +#else + bdat->c.pc = PACK5(EX_MON(adat->c.pc), EX_MDAY(adat->c.pc), + 0, 0, 0); + bdat->c.flags |= HAVE_DF | HAVE_TIME; +#endif + return new; + } + } +} + +/* + * call-seq: + * dt.to_time -> time + * + * Returns a Time object which denotes self. + */ +static VALUE +datetime_to_time(VALUE self) +{ + get_d1(self); + + if (m_julian_p(dat)) { + VALUE g = d_lite_gregorian(self); + get_d1a(g); + dat = adat; + self = g; + } + + { + VALUE t; + + t = rb_funcall(rb_cTime, + rb_intern("new"), + 7, + m_real_year(dat), + INT2FIX(m_mon(dat)), + INT2FIX(m_mday(dat)), + INT2FIX(m_hour(dat)), + INT2FIX(m_min(dat)), + f_add(INT2FIX(m_sec(dat)), + m_sf_in_sec(dat)), + INT2FIX(m_of(dat))); + RB_GC_GUARD(self); /* may be the converted gregorian */ + return t; + } +} + +/* + * call-seq: + * dt.to_date -> date + * + * Returns a Date object which denotes self. + */ +static VALUE +datetime_to_date(VALUE self) +{ + get_d1a(self); + + if (simple_dat_p(adat)) { + VALUE new = d_lite_s_alloc_simple(cDate); + { + get_d1b(new); + bdat->s = adat->s; + bdat->s.jd = m_local_jd(adat); + return new; + } + } + else { + VALUE new = d_lite_s_alloc_simple(cDate); + { + get_d1b(new); + copy_complex_to_simple(new, &bdat->s, &adat->c); + bdat->s.jd = m_local_jd(adat); + bdat->s.flags &= ~(HAVE_DF | HAVE_TIME | COMPLEX_DAT); + return new; + } + } +} + +/* + * call-seq: + * dt.to_datetime -> self + * + * Returns self. + */ +static VALUE +datetime_to_datetime(VALUE self) +{ + return self; +} + +#ifndef NDEBUG +/* tests */ + +#define MIN_YEAR -4713 +#define MAX_YEAR 1000000 +#define MIN_JD -327 +#define MAX_JD 366963925 + +/* :nodoc: */ +static int +test_civil(int from, int to, double sg) +{ + int j; + + fprintf(stderr, "test_civil: %d...%d (%d) - %.0f\n", + from, to, to - from, sg); + for (j = from; j <= to; j++) { + int y, m, d, rj, ns; + + c_jd_to_civil(j, sg, &y, &m, &d); + c_civil_to_jd(y, m, d, sg, &rj, &ns); + if (j != rj) { + fprintf(stderr, "%d != %d\n", j, rj); + return 0; + } + } + return 1; +} + +/* :nodoc: */ +static VALUE +date_s_test_civil(VALUE klass) +{ + if (!test_civil(MIN_JD, MIN_JD + 366, GREGORIAN)) + return Qfalse; + if (!test_civil(2305814, 2598007, GREGORIAN)) + return Qfalse; + if (!test_civil(MAX_JD - 366, MAX_JD, GREGORIAN)) + return Qfalse; + + if (!test_civil(MIN_JD, MIN_JD + 366, ITALY)) + return Qfalse; + if (!test_civil(2305814, 2598007, ITALY)) + return Qfalse; + if (!test_civil(MAX_JD - 366, MAX_JD, ITALY)) + return Qfalse; + + return Qtrue; +} + +/* :nodoc: */ +static int +test_ordinal(int from, int to, double sg) +{ + int j; + + fprintf(stderr, "test_ordinal: %d...%d (%d) - %.0f\n", + from, to, to - from, sg); + for (j = from; j <= to; j++) { + int y, d, rj, ns; + + c_jd_to_ordinal(j, sg, &y, &d); + c_ordinal_to_jd(y, d, sg, &rj, &ns); + if (j != rj) { + fprintf(stderr, "%d != %d\n", j, rj); + return 0; + } + } + return 1; +} + +/* :nodoc: */ +static VALUE +date_s_test_ordinal(VALUE klass) +{ + if (!test_ordinal(MIN_JD, MIN_JD + 366, GREGORIAN)) + return Qfalse; + if (!test_ordinal(2305814, 2598007, GREGORIAN)) + return Qfalse; + if (!test_ordinal(MAX_JD - 366, MAX_JD, GREGORIAN)) + return Qfalse; + + if (!test_ordinal(MIN_JD, MIN_JD + 366, ITALY)) + return Qfalse; + if (!test_ordinal(2305814, 2598007, ITALY)) + return Qfalse; + if (!test_ordinal(MAX_JD - 366, MAX_JD, ITALY)) + return Qfalse; + + return Qtrue; +} + +/* :nodoc: */ +static int +test_commercial(int from, int to, double sg) +{ + int j; + + fprintf(stderr, "test_commercial: %d...%d (%d) - %.0f\n", + from, to, to - from, sg); + for (j = from; j <= to; j++) { + int y, w, d, rj, ns; + + c_jd_to_commercial(j, sg, &y, &w, &d); + c_commercial_to_jd(y, w, d, sg, &rj, &ns); + if (j != rj) { + fprintf(stderr, "%d != %d\n", j, rj); + return 0; + } + } + return 1; +} + +/* :nodoc: */ +static VALUE +date_s_test_commercial(VALUE klass) +{ + if (!test_commercial(MIN_JD, MIN_JD + 366, GREGORIAN)) + return Qfalse; + if (!test_commercial(2305814, 2598007, GREGORIAN)) + return Qfalse; + if (!test_commercial(MAX_JD - 366, MAX_JD, GREGORIAN)) + return Qfalse; + + if (!test_commercial(MIN_JD, MIN_JD + 366, ITALY)) + return Qfalse; + if (!test_commercial(2305814, 2598007, ITALY)) + return Qfalse; + if (!test_commercial(MAX_JD - 366, MAX_JD, ITALY)) + return Qfalse; + + return Qtrue; +} + +/* :nodoc: */ +static int +test_weeknum(int from, int to, int f, double sg) +{ + int j; + + fprintf(stderr, "test_weeknum: %d...%d (%d) - %.0f\n", + from, to, to - from, sg); + for (j = from; j <= to; j++) { + int y, w, d, rj, ns; + + c_jd_to_weeknum(j, f, sg, &y, &w, &d); + c_weeknum_to_jd(y, w, d, f, sg, &rj, &ns); + if (j != rj) { + fprintf(stderr, "%d != %d\n", j, rj); + return 0; + } + } + return 1; +} + +/* :nodoc: */ +static VALUE +date_s_test_weeknum(VALUE klass) +{ + int f; + + for (f = 0; f <= 1; f++) { + if (!test_weeknum(MIN_JD, MIN_JD + 366, f, GREGORIAN)) + return Qfalse; + if (!test_weeknum(2305814, 2598007, f, GREGORIAN)) + return Qfalse; + if (!test_weeknum(MAX_JD - 366, MAX_JD, f, GREGORIAN)) + return Qfalse; + + if (!test_weeknum(MIN_JD, MIN_JD + 366, f, ITALY)) + return Qfalse; + if (!test_weeknum(2305814, 2598007, f, ITALY)) + return Qfalse; + if (!test_weeknum(MAX_JD - 366, MAX_JD, f, ITALY)) + return Qfalse; + } + + return Qtrue; +} + +/* :nodoc: */ +static int +test_nth_kday(int from, int to, double sg) +{ + int j; + + fprintf(stderr, "test_nth_kday: %d...%d (%d) - %.0f\n", + from, to, to - from, sg); + for (j = from; j <= to; j++) { + int y, m, n, k, rj, ns; + + c_jd_to_nth_kday(j, sg, &y, &m, &n, &k); + c_nth_kday_to_jd(y, m, n, k, sg, &rj, &ns); + if (j != rj) { + fprintf(stderr, "%d != %d\n", j, rj); + return 0; + } + } + return 1; +} + +/* :nodoc: */ +static VALUE +date_s_test_nth_kday(VALUE klass) +{ + if (!test_nth_kday(MIN_JD, MIN_JD + 366, GREGORIAN)) + return Qfalse; + if (!test_nth_kday(2305814, 2598007, GREGORIAN)) + return Qfalse; + if (!test_nth_kday(MAX_JD - 366, MAX_JD, GREGORIAN)) + return Qfalse; + + if (!test_nth_kday(MIN_JD, MIN_JD + 366, ITALY)) + return Qfalse; + if (!test_nth_kday(2305814, 2598007, ITALY)) + return Qfalse; + if (!test_nth_kday(MAX_JD - 366, MAX_JD, ITALY)) + return Qfalse; + + return Qtrue; +} + +/* :nodoc: */ +static int +test_unit_v2v(VALUE i, + VALUE (* conv1)(VALUE), + VALUE (* conv2)(VALUE)) +{ + VALUE c, o; + c = (*conv1)(i); + o = (*conv2)(c); + return f_eqeq_p(o, i); +} + +/* :nodoc: */ +static int +test_unit_v2v_iter2(VALUE (* conv1)(VALUE), + VALUE (* conv2)(VALUE)) +{ + if (!test_unit_v2v(INT2FIX(0), conv1, conv2)) + return 0; + if (!test_unit_v2v(INT2FIX(1), conv1, conv2)) + return 0; + if (!test_unit_v2v(INT2FIX(2), conv1, conv2)) + return 0; + if (!test_unit_v2v(INT2FIX(3), conv1, conv2)) + return 0; + if (!test_unit_v2v(INT2FIX(11), conv1, conv2)) + return 0; + if (!test_unit_v2v(INT2FIX(65535), conv1, conv2)) + return 0; + if (!test_unit_v2v(INT2FIX(1073741823), conv1, conv2)) + return 0; + if (!test_unit_v2v(INT2NUM(1073741824), conv1, conv2)) + return 0; + if (!test_unit_v2v(rb_rational_new2(INT2FIX(0), INT2FIX(1)), conv1, conv2)) + return 0; + if (!test_unit_v2v(rb_rational_new2(INT2FIX(1), INT2FIX(1)), conv1, conv2)) + return 0; + if (!test_unit_v2v(rb_rational_new2(INT2FIX(1), INT2FIX(2)), conv1, conv2)) + return 0; + if (!test_unit_v2v(rb_rational_new2(INT2FIX(2), INT2FIX(3)), conv1, conv2)) + return 0; + return 1; +} + +/* :nodoc: */ +static int +test_unit_v2v_iter(VALUE (* conv1)(VALUE), + VALUE (* conv2)(VALUE)) +{ + if (!test_unit_v2v_iter2(conv1, conv2)) + return 0; + if (!test_unit_v2v_iter2(conv2, conv1)) + return 0; + return 1; +} + +/* :nodoc: */ +static VALUE +date_s_test_unit_conv(VALUE klass) +{ + if (!test_unit_v2v_iter(sec_to_day, day_to_sec)) + return Qfalse; + if (!test_unit_v2v_iter(ms_to_sec, sec_to_ms)) + return Qfalse; + if (!test_unit_v2v_iter(ns_to_day, day_to_ns)) + return Qfalse; + if (!test_unit_v2v_iter(ns_to_sec, sec_to_ns)) + return Qfalse; + return Qtrue; +} + +/* :nodoc: */ +static VALUE +date_s_test_all(VALUE klass) +{ + if (date_s_test_civil(klass) == Qfalse) + return Qfalse; + if (date_s_test_ordinal(klass) == Qfalse) + return Qfalse; + if (date_s_test_commercial(klass) == Qfalse) + return Qfalse; + if (date_s_test_weeknum(klass) == Qfalse) + return Qfalse; + if (date_s_test_nth_kday(klass) == Qfalse) + return Qfalse; + if (date_s_test_unit_conv(klass) == Qfalse) + return Qfalse; + return Qtrue; +} +#endif + +static const char *monthnames[] = { + NULL, + "January", "February", "March", + "April", "May", "June", + "July", "August", "September", + "October", "November", "December" +}; + +static const char *abbr_monthnames[] = { + NULL, + "Jan", "Feb", "Mar", "Apr", + "May", "Jun", "Jul", "Aug", + "Sep", "Oct", "Nov", "Dec" +}; + +static const char *daynames[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday" +}; + +static const char *abbr_daynames[] = { + "Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat" +}; + +static VALUE +mk_ary_of_str(long len, const char *a[]) +{ + VALUE o; + long i; + + o = rb_ary_new2(len); + for (i = 0; i < len; i++) { + VALUE e; + + if (!a[i]) + e = Qnil; + else { + e = rb_usascii_str_new2(a[i]); + rb_obj_freeze(e); + } + rb_ary_push(o, e); + } + rb_ary_freeze(o); + return o; +} + +/* :nodoc: */ +static VALUE +d_lite_zero(VALUE x) +{ + return INT2FIX(0); +} + +void +Init_date_core(void) +{ + #ifdef HAVE_RB_EXT_RACTOR_SAFE + RB_EXT_RACTOR_SAFE(true); + #endif + id_cmp = rb_intern_const("<=>"); + id_le_p = rb_intern_const("<="); + id_ge_p = rb_intern_const(">="); + id_eqeq_p = rb_intern_const("=="); + + sym_year = ID2SYM(rb_intern_const("year")); + sym_month = ID2SYM(rb_intern_const("month")); + sym_yday = ID2SYM(rb_intern_const("yday")); + sym_wday = ID2SYM(rb_intern_const("wday")); + sym_day = ID2SYM(rb_intern_const("day")); + sym_hour = ID2SYM(rb_intern_const("hour")); + sym_min = ID2SYM(rb_intern_const("min")); + sym_sec = ID2SYM(rb_intern_const("sec")); + sym_sec_fraction = ID2SYM(rb_intern_const("sec_fraction")); + sym_zone = ID2SYM(rb_intern_const("zone")); + + half_days_in_day = rb_rational_new2(INT2FIX(1), INT2FIX(2)); + +#if (LONG_MAX / DAY_IN_SECONDS) > SECOND_IN_NANOSECONDS + day_in_nanoseconds = LONG2NUM((long)DAY_IN_SECONDS * + SECOND_IN_NANOSECONDS); +#elif defined HAVE_LONG_LONG + day_in_nanoseconds = LL2NUM((LONG_LONG)DAY_IN_SECONDS * + SECOND_IN_NANOSECONDS); +#else + day_in_nanoseconds = f_mul(INT2FIX(DAY_IN_SECONDS), + INT2FIX(SECOND_IN_NANOSECONDS)); +#endif + + rb_gc_register_mark_object(half_days_in_day); + rb_gc_register_mark_object(day_in_nanoseconds); + + positive_inf = +INFINITY; + negative_inf = -INFINITY; + + /* + * \Class \Date provides methods for storing and manipulating + * calendar dates. + * + * Consider using + * {class Time}[https://docs.ruby-lang.org/en/master/Time.html] + * instead of class \Date if: + * + * - You need both dates and times; \Date handles only dates. + * - You need only Gregorian dates (and not Julian dates); + * see {Julian and Gregorian Calendars}[rdoc-ref:calendars.rdoc]. + * + * A \Date object, once created, is immutable, and cannot be modified. + * + * == Creating a \Date + * + * You can create a date for the current date, using Date.today: + * + * Date.today # => # + * + * You can create a specific date from various combinations of arguments: + * + * - Date.new takes integer year, month, and day-of-month: + * + * Date.new(1999, 12, 31) # => # + * + * - Date.ordinal takes integer year and day-of-year: + * + * Date.ordinal(1999, 365) # => # + * + * - Date.jd takes integer Julian day: + * + * Date.jd(2451544) # => # + * + * - Date.commercial takes integer commercial data (year, week, day-of-week): + * + * Date.commercial(1999, 52, 5) # => # + * + * - Date.parse takes a string, which it parses heuristically: + * + * Date.parse('1999-12-31') # => # + * Date.parse('31-12-1999') # => # + * Date.parse('1999-365') # => # + * Date.parse('1999-W52-5') # => # + * + * - Date.strptime takes a date string and a format string, + * then parses the date string according to the format string: + * + * Date.strptime('1999-12-31', '%Y-%m-%d') # => # + * Date.strptime('31-12-1999', '%d-%m-%Y') # => # + * Date.strptime('1999-365', '%Y-%j') # => # + * Date.strptime('1999-W52-5', '%G-W%V-%u') # => # + * Date.strptime('1999 52 5', '%Y %U %w') # => # + * Date.strptime('1999 52 5', '%Y %W %u') # => # + * Date.strptime('fri31dec99', '%a%d%b%y') # => # + * + * See also the specialized methods in + * {"Specialized Format Strings" in Formats for Dates and Times}[https://docs.ruby-lang.org/en/master/strftime_formatting_rdoc.html#label-Specialized+Format+Strings] + * + * == Argument +limit+ + * + * Certain singleton methods in \Date that parse string arguments + * also take optional keyword argument +limit+, + * which can limit the length of the string argument. + * + * When +limit+ is: + * + * - Non-negative: + * raises ArgumentError if the string length is greater than _limit_. + * - Other numeric or +nil+: ignores +limit+. + * - Other non-numeric: raises TypeError. + * + */ + cDate = rb_define_class("Date", rb_cObject); + + /* Exception for invalid date/time */ + eDateError = rb_define_class_under(cDate, "Error", rb_eArgError); + + rb_include_module(cDate, rb_mComparable); + + /* An array of strings of full month names in English. The first + * element is nil. + */ + rb_define_const(cDate, "MONTHNAMES", mk_ary_of_str(13, monthnames)); + + /* An array of strings of abbreviated month names in English. The + * first element is nil. + */ + rb_define_const(cDate, "ABBR_MONTHNAMES", + mk_ary_of_str(13, abbr_monthnames)); + + /* An array of strings of the full names of days of the week in English. + * The first is "Sunday". + */ + rb_define_const(cDate, "DAYNAMES", mk_ary_of_str(7, daynames)); + + /* An array of strings of abbreviated day names in English. The + * first is "Sun". + */ + rb_define_const(cDate, "ABBR_DAYNAMES", mk_ary_of_str(7, abbr_daynames)); + + /* The Julian day number of the day of calendar reform for Italy + * and some catholic countries. + */ + rb_define_const(cDate, "ITALY", INT2FIX(ITALY)); + + /* The Julian day number of the day of calendar reform for England + * and her colonies. + */ + rb_define_const(cDate, "ENGLAND", INT2FIX(ENGLAND)); + + /* The Julian day number of the day of calendar reform for the + * proleptic Julian calendar. + */ + rb_define_const(cDate, "JULIAN", DBL2NUM(JULIAN)); + + /* The Julian day number of the day of calendar reform for the + * proleptic Gregorian calendar. + */ + rb_define_const(cDate, "GREGORIAN", DBL2NUM(GREGORIAN)); + + rb_define_alloc_func(cDate, d_lite_s_alloc_simple); + +#ifndef NDEBUG + rb_define_private_method(CLASS_OF(cDate), "_valid_jd?", + date_s__valid_jd_p, -1); + rb_define_private_method(CLASS_OF(cDate), "_valid_ordinal?", + date_s__valid_ordinal_p, -1); + rb_define_private_method(CLASS_OF(cDate), "_valid_civil?", + date_s__valid_civil_p, -1); + rb_define_private_method(CLASS_OF(cDate), "_valid_date?", + date_s__valid_civil_p, -1); + rb_define_private_method(CLASS_OF(cDate), "_valid_commercial?", + date_s__valid_commercial_p, -1); + rb_define_private_method(CLASS_OF(cDate), "_valid_weeknum?", + date_s__valid_weeknum_p, -1); + rb_define_private_method(CLASS_OF(cDate), "_valid_nth_kday?", + date_s__valid_nth_kday_p, -1); +#endif + + rb_define_singleton_method(cDate, "valid_jd?", date_s_valid_jd_p, -1); + rb_define_singleton_method(cDate, "valid_ordinal?", + date_s_valid_ordinal_p, -1); + rb_define_singleton_method(cDate, "valid_civil?", date_s_valid_civil_p, -1); + rb_define_singleton_method(cDate, "valid_date?", date_s_valid_civil_p, -1); + rb_define_singleton_method(cDate, "valid_commercial?", + date_s_valid_commercial_p, -1); + +#ifndef NDEBUG + rb_define_private_method(CLASS_OF(cDate), "valid_weeknum?", + date_s_valid_weeknum_p, -1); + rb_define_private_method(CLASS_OF(cDate), "valid_nth_kday?", + date_s_valid_nth_kday_p, -1); + rb_define_private_method(CLASS_OF(cDate), "zone_to_diff", + date_s_zone_to_diff, 1); +#endif + + rb_define_singleton_method(cDate, "julian_leap?", date_s_julian_leap_p, 1); + rb_define_singleton_method(cDate, "gregorian_leap?", + date_s_gregorian_leap_p, 1); + rb_define_singleton_method(cDate, "leap?", + date_s_gregorian_leap_p, 1); + +#ifndef NDEBUG + rb_define_singleton_method(cDate, "new!", date_s_new_bang, -1); + rb_define_alias(rb_singleton_class(cDate), "new_l!", "new"); +#endif + + rb_define_singleton_method(cDate, "jd", date_s_jd, -1); + rb_define_singleton_method(cDate, "ordinal", date_s_ordinal, -1); + rb_define_singleton_method(cDate, "civil", date_s_civil, -1); + rb_define_singleton_method(cDate, "commercial", date_s_commercial, -1); + +#ifndef NDEBUG + rb_define_singleton_method(cDate, "weeknum", date_s_weeknum, -1); + rb_define_singleton_method(cDate, "nth_kday", date_s_nth_kday, -1); +#endif + + rb_define_singleton_method(cDate, "today", date_s_today, -1); + rb_define_singleton_method(cDate, "_strptime", date_s__strptime, -1); + rb_define_singleton_method(cDate, "strptime", date_s_strptime, -1); + rb_define_singleton_method(cDate, "_parse", date_s__parse, -1); + rb_define_singleton_method(cDate, "parse", date_s_parse, -1); + rb_define_singleton_method(cDate, "_iso8601", date_s__iso8601, -1); + rb_define_singleton_method(cDate, "iso8601", date_s_iso8601, -1); + rb_define_singleton_method(cDate, "_rfc3339", date_s__rfc3339, -1); + rb_define_singleton_method(cDate, "rfc3339", date_s_rfc3339, -1); + rb_define_singleton_method(cDate, "_xmlschema", date_s__xmlschema, -1); + rb_define_singleton_method(cDate, "xmlschema", date_s_xmlschema, -1); + rb_define_singleton_method(cDate, "_rfc2822", date_s__rfc2822, -1); + rb_define_singleton_method(cDate, "_rfc822", date_s__rfc2822, -1); + rb_define_singleton_method(cDate, "rfc2822", date_s_rfc2822, -1); + rb_define_singleton_method(cDate, "rfc822", date_s_rfc2822, -1); + rb_define_singleton_method(cDate, "_httpdate", date_s__httpdate, -1); + rb_define_singleton_method(cDate, "httpdate", date_s_httpdate, -1); + rb_define_singleton_method(cDate, "_jisx0301", date_s__jisx0301, -1); + rb_define_singleton_method(cDate, "jisx0301", date_s_jisx0301, -1); + + rb_define_method(cDate, "initialize", date_initialize, -1); + rb_define_method(cDate, "initialize_copy", d_lite_initialize_copy, 1); + +#ifndef NDEBUG + rb_define_method(cDate, "fill", d_lite_fill, 0); +#endif + + rb_define_method(cDate, "ajd", d_lite_ajd, 0); + rb_define_method(cDate, "amjd", d_lite_amjd, 0); + rb_define_method(cDate, "jd", d_lite_jd, 0); + rb_define_method(cDate, "mjd", d_lite_mjd, 0); + rb_define_method(cDate, "ld", d_lite_ld, 0); + + rb_define_method(cDate, "year", d_lite_year, 0); + rb_define_method(cDate, "yday", d_lite_yday, 0); + rb_define_method(cDate, "mon", d_lite_mon, 0); + rb_define_method(cDate, "month", d_lite_mon, 0); + rb_define_method(cDate, "mday", d_lite_mday, 0); + rb_define_method(cDate, "day", d_lite_mday, 0); + rb_define_method(cDate, "day_fraction", d_lite_day_fraction, 0); + + rb_define_method(cDate, "cwyear", d_lite_cwyear, 0); + rb_define_method(cDate, "cweek", d_lite_cweek, 0); + rb_define_method(cDate, "cwday", d_lite_cwday, 0); + +#ifndef NDEBUG + rb_define_private_method(cDate, "wnum0", d_lite_wnum0, 0); + rb_define_private_method(cDate, "wnum1", d_lite_wnum1, 0); +#endif + + rb_define_method(cDate, "wday", d_lite_wday, 0); + + rb_define_method(cDate, "sunday?", d_lite_sunday_p, 0); + rb_define_method(cDate, "monday?", d_lite_monday_p, 0); + rb_define_method(cDate, "tuesday?", d_lite_tuesday_p, 0); + rb_define_method(cDate, "wednesday?", d_lite_wednesday_p, 0); + rb_define_method(cDate, "thursday?", d_lite_thursday_p, 0); + rb_define_method(cDate, "friday?", d_lite_friday_p, 0); + rb_define_method(cDate, "saturday?", d_lite_saturday_p, 0); + +#ifndef NDEBUG + rb_define_method(cDate, "nth_kday?", d_lite_nth_kday_p, 2); +#endif + + rb_define_private_method(cDate, "hour", d_lite_zero, 0); + rb_define_private_method(cDate, "min", d_lite_zero, 0); + rb_define_private_method(cDate, "minute", d_lite_zero, 0); + rb_define_private_method(cDate, "sec", d_lite_zero, 0); + rb_define_private_method(cDate, "second", d_lite_zero, 0); + + rb_define_method(cDate, "julian?", d_lite_julian_p, 0); + rb_define_method(cDate, "gregorian?", d_lite_gregorian_p, 0); + rb_define_method(cDate, "leap?", d_lite_leap_p, 0); + + rb_define_method(cDate, "start", d_lite_start, 0); + rb_define_method(cDate, "new_start", d_lite_new_start, -1); + rb_define_method(cDate, "italy", d_lite_italy, 0); + rb_define_method(cDate, "england", d_lite_england, 0); + rb_define_method(cDate, "julian", d_lite_julian, 0); + rb_define_method(cDate, "gregorian", d_lite_gregorian, 0); + + rb_define_method(cDate, "+", d_lite_plus, 1); + rb_define_method(cDate, "-", d_lite_minus, 1); + + rb_define_method(cDate, "next_day", d_lite_next_day, -1); + rb_define_method(cDate, "prev_day", d_lite_prev_day, -1); + rb_define_method(cDate, "next", d_lite_next, 0); + rb_define_method(cDate, "succ", d_lite_next, 0); + + rb_define_method(cDate, ">>", d_lite_rshift, 1); + rb_define_method(cDate, "<<", d_lite_lshift, 1); + + rb_define_method(cDate, "next_month", d_lite_next_month, -1); + rb_define_method(cDate, "prev_month", d_lite_prev_month, -1); + rb_define_method(cDate, "next_year", d_lite_next_year, -1); + rb_define_method(cDate, "prev_year", d_lite_prev_year, -1); + + rb_define_method(cDate, "step", d_lite_step, -1); + rb_define_method(cDate, "upto", d_lite_upto, 1); + rb_define_method(cDate, "downto", d_lite_downto, 1); + + rb_define_method(cDate, "<=>", d_lite_cmp, 1); + rb_define_method(cDate, "===", d_lite_equal, 1); + rb_define_method(cDate, "eql?", d_lite_eql_p, 1); + rb_define_method(cDate, "hash", d_lite_hash, 0); + + rb_define_method(cDate, "to_s", d_lite_to_s, 0); +#ifndef NDEBUG + rb_define_method(cDate, "inspect_raw", d_lite_inspect_raw, 0); +#endif + rb_define_method(cDate, "inspect", d_lite_inspect, 0); + + rb_define_method(cDate, "strftime", d_lite_strftime, -1); + + rb_define_method(cDate, "asctime", d_lite_asctime, 0); + rb_define_method(cDate, "ctime", d_lite_asctime, 0); + rb_define_method(cDate, "iso8601", d_lite_iso8601, 0); + rb_define_method(cDate, "xmlschema", d_lite_iso8601, 0); + rb_define_method(cDate, "rfc3339", d_lite_rfc3339, 0); + rb_define_method(cDate, "rfc2822", d_lite_rfc2822, 0); + rb_define_method(cDate, "rfc822", d_lite_rfc2822, 0); + rb_define_method(cDate, "httpdate", d_lite_httpdate, 0); + rb_define_method(cDate, "jisx0301", d_lite_jisx0301, 0); + + rb_define_method(cDate, "deconstruct_keys", d_lite_deconstruct_keys, 1); + +#ifndef NDEBUG + rb_define_method(cDate, "marshal_dump_old", d_lite_marshal_dump_old, 0); +#endif + rb_define_method(cDate, "marshal_dump", d_lite_marshal_dump, 0); + rb_define_method(cDate, "marshal_load", d_lite_marshal_load, 1); + rb_define_singleton_method(cDate, "_load", date_s__load, 1); + + /* + * == DateTime + * + * A subclass of Date that easily handles date, hour, minute, second, + * and offset. + * + * DateTime class is considered deprecated. Use Time class. + * + * DateTime does not consider any leap seconds, does not track + * any summer time rules. + * + * A DateTime object is created with DateTime::new, DateTime::jd, + * DateTime::ordinal, DateTime::commercial, DateTime::parse, + * DateTime::strptime, DateTime::now, Time#to_datetime, etc. + * + * require 'date' + * + * DateTime.new(2001,2,3,4,5,6) + * #=> # + * + * The last element of day, hour, minute, or second can be a + * fractional number. The fractional number's precision is assumed + * at most nanosecond. + * + * DateTime.new(2001,2,3.5) + * #=> # + * + * An optional argument, the offset, indicates the difference + * between the local time and UTC. For example, Rational(3,24) + * represents ahead of 3 hours of UTC, Rational(-5,24) represents + * behind of 5 hours of UTC. The offset should be -1 to +1, and + * its precision is assumed at most second. The default value is + * zero (equals to UTC). + * + * DateTime.new(2001,2,3,4,5,6,Rational(3,24)) + * #=> # + * + * The offset also accepts string form: + * + * DateTime.new(2001,2,3,4,5,6,'+03:00') + * #=> # + * + * An optional argument, the day of calendar reform (+start+), denotes + * a Julian day number, which should be 2298874 to 2426355 or + * negative/positive infinity. + * The default value is +Date::ITALY+ (2299161=1582-10-15). + * + * A DateTime object has various methods. See each reference. + * + * d = DateTime.parse('3rd Feb 2001 04:05:06+03:30') + * #=> # + * d.hour #=> 4 + * d.min #=> 5 + * d.sec #=> 6 + * d.offset #=> (7/48) + * d.zone #=> "+03:30" + * d += Rational('1.5') + * #=> # + * d = d.new_offset('+09:00') + * #=> # + * d.strftime('%I:%M:%S %p') + * #=> "09:35:06 PM" + * d > DateTime.new(1999) + * #=> true + * + * === When should you use DateTime and when should you use Time? + * + * It's a common misconception that + * {William Shakespeare}[https://en.wikipedia.org/wiki/William_Shakespeare] + * and + * {Miguel de Cervantes}[https://en.wikipedia.org/wiki/Miguel_de_Cervantes] + * died on the same day in history - + * so much so that UNESCO named April 23 as + * {World Book Day because of this fact}[https://en.wikipedia.org/wiki/World_Book_Day]. + * However, because England hadn't yet adopted the + * {Gregorian Calendar Reform}[https://en.wikipedia.org/wiki/Gregorian_calendar#Gregorian_reform] + * (and wouldn't until {1752}[https://en.wikipedia.org/wiki/Calendar_(New_Style)_Act_1750]) + * their deaths are actually 10 days apart. + * Since Ruby's Time class implements a + * {proleptic Gregorian calendar}[https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar] + * and has no concept of calendar reform there's no way + * to express this with Time objects. This is where DateTime steps in: + * + * shakespeare = DateTime.iso8601('1616-04-23', Date::ENGLAND) + * #=> Tue, 23 Apr 1616 00:00:00 +0000 + * cervantes = DateTime.iso8601('1616-04-23', Date::ITALY) + * #=> Sat, 23 Apr 1616 00:00:00 +0000 + * + * Already you can see something is weird - the days of the week + * are different. Taking this further: + * + * cervantes == shakespeare + * #=> false + * (shakespeare - cervantes).to_i + * #=> 10 + * + * This shows that in fact they died 10 days apart (in reality + * 11 days since Cervantes died a day earlier but was buried on + * the 23rd). We can see the actual date of Shakespeare's death by + * using the #gregorian method to convert it: + * + * shakespeare.gregorian + * #=> Tue, 03 May 1616 00:00:00 +0000 + * + * So there's an argument that all the celebrations that take + * place on the 23rd April in Stratford-upon-Avon are actually + * the wrong date since England is now using the Gregorian calendar. + * You can see why when we transition across the reform + * date boundary: + * + * # start off with the anniversary of Shakespeare's birth in 1751 + * shakespeare = DateTime.iso8601('1751-04-23', Date::ENGLAND) + * #=> Tue, 23 Apr 1751 00:00:00 +0000 + * + * # add 366 days since 1752 is a leap year and April 23 is after February 29 + * shakespeare + 366 + * #=> Thu, 23 Apr 1752 00:00:00 +0000 + * + * # add another 365 days to take us to the anniversary in 1753 + * shakespeare + 366 + 365 + * #=> Fri, 04 May 1753 00:00:00 +0000 + * + * As you can see, if we're accurately tracking the number of + * {solar years}[https://en.wikipedia.org/wiki/Tropical_year] + * since Shakespeare's birthday then the correct anniversary date + * would be the 4th May and not the 23rd April. + * + * So when should you use DateTime in Ruby and when should + * you use Time? Almost certainly you'll want to use Time + * since your app is probably dealing with current dates and + * times. However, if you need to deal with dates and times in a + * historical context you'll want to use DateTime to avoid + * making the same mistakes as UNESCO. If you also have to deal + * with timezones then best of luck - just bear in mind that + * you'll probably be dealing with + * {local solar times}[https://en.wikipedia.org/wiki/Solar_time], + * since it wasn't until the 19th century that the introduction + * of the railways necessitated the need for + * {Standard Time}[https://en.wikipedia.org/wiki/Standard_time#Great_Britain] + * and eventually timezones. + */ + + cDateTime = rb_define_class("DateTime", cDate); + rb_define_alloc_func(cDateTime, d_lite_s_alloc_complex); + + rb_define_singleton_method(cDateTime, "jd", datetime_s_jd, -1); + rb_define_singleton_method(cDateTime, "ordinal", datetime_s_ordinal, -1); + rb_define_singleton_method(cDateTime, "civil", datetime_s_civil, -1); + rb_define_singleton_method(cDateTime, "new", datetime_s_civil, -1); + rb_define_singleton_method(cDateTime, "commercial", + datetime_s_commercial, -1); + +#ifndef NDEBUG + rb_define_singleton_method(cDateTime, "weeknum", + datetime_s_weeknum, -1); + rb_define_singleton_method(cDateTime, "nth_kday", + datetime_s_nth_kday, -1); +#endif + + rb_undef_method(CLASS_OF(cDateTime), "today"); + + rb_define_singleton_method(cDateTime, "now", datetime_s_now, -1); + rb_define_singleton_method(cDateTime, "_strptime", + datetime_s__strptime, -1); + rb_define_singleton_method(cDateTime, "strptime", + datetime_s_strptime, -1); + rb_define_singleton_method(cDateTime, "parse", + datetime_s_parse, -1); + rb_define_singleton_method(cDateTime, "iso8601", + datetime_s_iso8601, -1); + rb_define_singleton_method(cDateTime, "rfc3339", + datetime_s_rfc3339, -1); + rb_define_singleton_method(cDateTime, "xmlschema", + datetime_s_xmlschema, -1); + rb_define_singleton_method(cDateTime, "rfc2822", + datetime_s_rfc2822, -1); + rb_define_singleton_method(cDateTime, "rfc822", + datetime_s_rfc2822, -1); + rb_define_singleton_method(cDateTime, "httpdate", + datetime_s_httpdate, -1); + rb_define_singleton_method(cDateTime, "jisx0301", + datetime_s_jisx0301, -1); + + rb_define_method(cDateTime, "hour", d_lite_hour, 0); + rb_define_method(cDateTime, "min", d_lite_min, 0); + rb_define_method(cDateTime, "minute", d_lite_min, 0); + rb_define_method(cDateTime, "sec", d_lite_sec, 0); + rb_define_method(cDateTime, "second", d_lite_sec, 0); + rb_define_method(cDateTime, "sec_fraction", d_lite_sec_fraction, 0); + rb_define_method(cDateTime, "second_fraction", d_lite_sec_fraction, 0); + rb_define_method(cDateTime, "offset", d_lite_offset, 0); + rb_define_method(cDateTime, "zone", d_lite_zone, 0); + rb_define_method(cDateTime, "new_offset", d_lite_new_offset, -1); + + rb_define_method(cDateTime, "to_s", dt_lite_to_s, 0); + + rb_define_method(cDateTime, "strftime", dt_lite_strftime, -1); + + rb_define_method(cDateTime, "iso8601", dt_lite_iso8601, -1); + rb_define_method(cDateTime, "xmlschema", dt_lite_iso8601, -1); + rb_define_method(cDateTime, "rfc3339", dt_lite_rfc3339, -1); + rb_define_method(cDateTime, "jisx0301", dt_lite_jisx0301, -1); + + rb_define_method(cDateTime, "deconstruct_keys", dt_lite_deconstruct_keys, 1); + + /* conversions */ + + rb_define_method(rb_cTime, "to_time", time_to_time, 0); + rb_define_method(rb_cTime, "to_date", time_to_date, 0); + rb_define_method(rb_cTime, "to_datetime", time_to_datetime, 0); + + rb_define_method(cDate, "to_time", date_to_time, 0); + rb_define_method(cDate, "to_date", date_to_date, 0); + rb_define_method(cDate, "to_datetime", date_to_datetime, 0); + + rb_define_method(cDateTime, "to_time", datetime_to_time, 0); + rb_define_method(cDateTime, "to_date", datetime_to_date, 0); + rb_define_method(cDateTime, "to_datetime", datetime_to_datetime, 0); + +#ifndef NDEBUG + /* tests */ + + rb_define_singleton_method(cDate, "test_civil", date_s_test_civil, 0); + rb_define_singleton_method(cDate, "test_ordinal", date_s_test_ordinal, 0); + rb_define_singleton_method(cDate, "test_commercial", + date_s_test_commercial, 0); + rb_define_singleton_method(cDate, "test_weeknum", date_s_test_weeknum, 0); + rb_define_singleton_method(cDate, "test_nth_kday", date_s_test_nth_kday, 0); + rb_define_singleton_method(cDate, "test_unit_conv", + date_s_test_unit_conv, 0); + rb_define_singleton_method(cDate, "test_all", date_s_test_all, 0); +#endif +} + +/* +Local variables: +c-file-style: "ruby" +End: +*/ diff --git a/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/date_parse.c b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/date_parse.c new file mode 100644 index 00000000..a1600e47 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/date_parse.c @@ -0,0 +1,3086 @@ +/* + date_parse.c: Coded by Tadayoshi Funaba 2011,2012 +*/ + +#include "ruby.h" +#include "ruby/encoding.h" +#include "ruby/re.h" +#include + +#undef strncasecmp +#define strncasecmp STRNCASECMP + +RUBY_EXTERN VALUE rb_int_positive_pow(long x, unsigned long y); +RUBY_EXTERN unsigned long ruby_scan_digits(const char *str, ssize_t len, int base, size_t *retlen, int *overflow); + +/* #define TIGHT_PARSER */ + +#define sizeof_array(o) (sizeof o / sizeof o[0]) + +#define f_negate(x) rb_funcall(x, rb_intern("-@"), 0) +#define f_add(x,y) rb_funcall(x, '+', 1, y) +#define f_sub(x,y) rb_funcall(x, '-', 1, y) +#define f_mul(x,y) rb_funcall(x, '*', 1, y) +#define f_div(x,y) rb_funcall(x, '/', 1, y) +#define f_idiv(x,y) rb_funcall(x, rb_intern("div"), 1, y) +#define f_mod(x,y) rb_funcall(x, '%', 1, y) +#define f_expt(x,y) rb_funcall(x, rb_intern("**"), 1, y) + +#define f_lt_p(x,y) rb_funcall(x, '<', 1, y) +#define f_gt_p(x,y) rb_funcall(x, '>', 1, y) +#define f_le_p(x,y) rb_funcall(x, rb_intern("<="), 1, y) +#define f_ge_p(x,y) rb_funcall(x, rb_intern(">="), 1, y) + +#define f_to_s(x) rb_funcall(x, rb_intern("to_s"), 0) + +#define f_match(r,s) rb_funcall(r, rb_intern("match"), 1, s) +#define f_aref(o,i) rb_funcall(o, rb_intern("[]"), 1, i) +#define f_aref2(o,i,j) rb_funcall(o, rb_intern("[]"), 2, i, j) +#define f_begin(o,i) rb_funcall(o, rb_intern("begin"), 1, i) +#define f_end(o,i) rb_funcall(o, rb_intern("end"), 1, i) +#define f_aset(o,i,v) rb_funcall(o, rb_intern("[]="), 2, i, v) +#define f_aset2(o,i,j,v) rb_funcall(o, rb_intern("[]="), 3, i, j, v) +#define f_sub_bang(s,r,x) rb_funcall(s, rb_intern("sub!"), 2, r, x) +#define f_gsub_bang(s,r,x) rb_funcall(s, rb_intern("gsub!"), 2, r, x) + +#define set_hash(k,v) rb_hash_aset(hash, ID2SYM(rb_intern(k"")), v) +#define ref_hash(k) rb_hash_aref(hash, ID2SYM(rb_intern(k""))) +#define del_hash(k) rb_hash_delete(hash, ID2SYM(rb_intern(k""))) + +#define cstr2num(s) rb_cstr_to_inum(s, 10, 0) +#define str2num(s) rb_str_to_inum(s, 10, 0) + +static const char abbr_days[][4] = { + "sun", "mon", "tue", "wed", + "thu", "fri", "sat" +}; + +static const char abbr_months[][4] = { + "jan", "feb", "mar", "apr", "may", "jun", + "jul", "aug", "sep", "oct", "nov", "dec" +}; + +#define issign(c) ((c) == '-' || (c) == '+') +#define asp_string() rb_str_new(" ", 1) +#ifdef TIGHT_PARSER +#define asuba_string() rb_str_new("\001", 1) +#define asubb_string() rb_str_new("\002", 1) +#define asubw_string() rb_str_new("\027", 1) +#define asubt_string() rb_str_new("\024", 1) +#endif + +static size_t +digit_span(const char *s, const char *e) +{ + size_t i = 0; + while (s + i < e && isdigit((unsigned char)s[i])) i++; + return i; +} + +static void +s3e(VALUE hash, VALUE y, VALUE m, VALUE d, int bc) +{ + VALUE vbuf = 0; + VALUE c = Qnil; + + if (!RB_TYPE_P(m, T_STRING)) + m = f_to_s(m); + + if (!NIL_P(y) && !NIL_P(m) && NIL_P(d)) { + VALUE oy = y; + VALUE om = m; + VALUE od = d; + + y = od; + m = oy; + d = om; + } + + if (NIL_P(y)) { + if (!NIL_P(d) && RSTRING_LEN(d) > 2) { + y = d; + d = Qnil; + } + if (!NIL_P(d) && RSTRING_LEN(d) > 0 && *RSTRING_PTR(d) == '\'') { + y = d; + d = Qnil; + } + } + + if (!NIL_P(y)) { + const char *s, *bp, *ep; + size_t l; + + s = RSTRING_PTR(y); + ep = RSTRING_END(y); + while (s < ep && !issign(*s) && !isdigit((unsigned char)*s)) + s++; + if (s >= ep) goto no_date; + bp = s; + if (issign((unsigned char)*s)) + s++; + l = digit_span(s, ep); + ep = s + l; + if (*ep) { + y = d; + d = rb_str_new(bp, ep - bp); + } + no_date:; + } + + if (!NIL_P(m)) { + const char *s; + + s = RSTRING_PTR(m); + if (*s == '\'' || RSTRING_LEN(m) > 2) { + /* us -> be */ + VALUE oy = y; + VALUE om = m; + VALUE od = d; + + y = om; + m = od; + d = oy; + } + } + + if (!NIL_P(d)) { + const char *s; + + s = RSTRING_PTR(d); + if (*s == '\'' || RSTRING_LEN(d) > 2) { + VALUE oy = y; + VALUE od = d; + + y = od; + d = oy; + } + } + + if (!NIL_P(y)) { + const char *s, *bp, *ep; + int sign = 0; + size_t l; + VALUE iy; + + s = RSTRING_PTR(y); + ep = RSTRING_END(y); + while (s < ep && !issign(*s) && !isdigit((unsigned char)*s)) + s++; + if (s >= ep) goto no_year; + bp = s; + if (issign(*s)) { + s++; + sign = 1; + } + if (sign) + c = Qfalse; + l = digit_span(s, ep); + ep = s + l; + if (l > 2) + c = Qfalse; + { + char *buf; + + buf = ALLOCV_N(char, vbuf, ep - bp + 1); + memcpy(buf, bp, ep - bp); + buf[ep - bp] = '\0'; + iy = cstr2num(buf); + ALLOCV_END(vbuf); + } + set_hash("year", iy); + no_year:; + } + + if (bc) + set_hash("_bc", Qtrue); + + if (!NIL_P(m)) { + const char *s, *bp, *ep; + size_t l; + VALUE im; + + s = RSTRING_PTR(m); + ep = RSTRING_END(m); + while (s < ep && !isdigit((unsigned char)*s)) + s++; + if (s >= ep) goto no_month; + bp = s; + l = digit_span(s, ep); + ep = s + l; + { + char *buf; + + buf = ALLOCV_N(char, vbuf, ep - bp + 1); + memcpy(buf, bp, ep - bp); + buf[ep - bp] = '\0'; + im = cstr2num(buf); + ALLOCV_END(vbuf); + } + set_hash("mon", im); + no_month:; + } + + if (!NIL_P(d)) { + const char *s, *bp, *ep; + size_t l; + VALUE id; + + s = RSTRING_PTR(d); + ep = RSTRING_END(d); + while (s < ep && !isdigit((unsigned char)*s)) + s++; + if (s >= ep) goto no_mday; + bp = s; + l = digit_span(s, ep); + ep = s + l; + { + char *buf; + + buf = ALLOCV_N(char, vbuf, ep - bp + 1); + memcpy(buf, bp, ep - bp); + buf[ep - bp] = '\0'; + id = cstr2num(buf); + ALLOCV_END(vbuf); + } + set_hash("mday", id); + no_mday:; + } + + if (!NIL_P(c)) + set_hash("_comp", c); +} + +#define DAYS "sunday|monday|tuesday|wednesday|thursday|friday|saturday" +#define MONTHS "january|february|march|april|may|june|july|august|september|october|november|december" +#define ABBR_DAYS "sun|mon|tue|wed|thu|fri|sat" +#define ABBR_MONTHS "jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec" + +#define NUMBER "(? n && isspace((unsigned char)s[l - n - 1])); + return n; +} + +static long +shrunk_size(const char *s, long l) +{ + long i, ni; + int sp = 0; + for (i = ni = 0; i < l; ++i) { + if (!isspace((unsigned char)s[i])) { + if (sp) ni++; + sp = 0; + ni++; + } + else { + sp = 1; + } + } + return ni < l ? ni : 0; +} + +static long +shrink_space(char *d, const char *s, long l) +{ + long i, ni; + int sp = 0; + for (i = ni = 0; i < l; ++i) { + if (!isspace((unsigned char)s[i])) { + if (sp) d[ni++] = ' '; + sp = 0; + d[ni++] = s[i]; + } + else { + sp = 1; + } + } + return ni; +} + +VALUE +date_zone_to_diff(VALUE str) +{ + VALUE offset = Qnil; + long l = RSTRING_LEN(str); + const char *s = RSTRING_PTR(str); + + { + int dst = 0; + int w; + + if ((w = str_end_with_word(s, l, "time")) > 0) { + int wtime = w; + l -= w; + if ((w = str_end_with_word(s, l, "standard")) > 0) { + l -= w; + } + else if ((w = str_end_with_word(s, l, "daylight")) > 0) { + l -= w; + dst = 1; + } + else { + l += wtime; + } + } + else if ((w = str_end_with_word(s, l, "dst")) > 0) { + l -= w; + dst = 1; + } + + { + const char *zn = s; + long sl = shrunk_size(s, l); + char shrunk_buff[MAX_WORD_LENGTH]; /* no terminator to be added */ + const struct zone *z = 0; + + if (sl <= 0) { + sl = l; + } + else if (sl <= MAX_WORD_LENGTH) { + char *d = shrunk_buff; + sl = shrink_space(d, s, l); + zn = d; + } + + if (sl > 0 && sl <= MAX_WORD_LENGTH) { + z = zonetab(zn, (unsigned int)sl); + } + + if (z) { + int d = z->offset; + if (dst) + d += 3600; + offset = INT2FIX(d); + goto ok; + } + } + + { + char *p; + int sign = 0; + long hour = 0, min = 0, sec = 0; + + if (l > 3 && + (strncasecmp(s, "gmt", 3) == 0 || + strncasecmp(s, "utc", 3) == 0)) { + s += 3; + l -= 3; + } + if (issign(*s)) { + sign = *s == '-'; + s++; + l--; + +#define out_of_range(v, min, max) ((v) < (min) || (max) < (v)) + hour = STRTOUL(s, &p, 10); + if (*p == ':') { + if (out_of_range(hour, 0, 23)) return Qnil; + s = ++p; + min = STRTOUL(s, &p, 10); + if (out_of_range(min, 0, 59)) return Qnil; + if (*p == ':') { + s = ++p; + sec = STRTOUL(s, &p, 10); + if (out_of_range(sec, 0, 59)) return Qnil; + } + } + else if (*p == ',' || *p == '.') { + /* fractional hour */ + size_t n; + int ov; + /* no over precision for offset; 10**-7 hour = 0.36 + * milliseconds should be enough. */ + const size_t max_digits = 7; /* 36 * 10**7 < 32-bit FIXNUM_MAX */ + + if (out_of_range(hour, 0, 23)) return Qnil; + + n = (s + l) - ++p; + if (n > max_digits) n = max_digits; + sec = ruby_scan_digits(p, n, 10, &n, &ov); + if ((p += n) < s + l && *p >= ('5' + !(sec & 1)) && *p <= '9') { + /* round half to even */ + sec++; + } + sec *= 36; + if (sign) { + hour = -hour; + sec = -sec; + } + if (n <= 2) { + /* HH.nn or HH.n */ + if (n == 1) sec *= 10; + offset = INT2FIX(sec + hour * 3600); + } + else { + VALUE denom = rb_int_positive_pow(10, (int)(n - 2)); + offset = f_add(rb_rational_new(INT2FIX(sec), denom), INT2FIX(hour * 3600)); + if (rb_rational_den(offset) == INT2FIX(1)) { + offset = rb_rational_num(offset); + } + } + goto ok; + } + else if (l > 2) { + size_t n; + int ov; + + if (l >= 1) + hour = ruby_scan_digits(&s[0], 2 - l % 2, 10, &n, &ov); + if (l >= 3) + min = ruby_scan_digits(&s[2 - l % 2], 2, 10, &n, &ov); + if (l >= 5) + sec = ruby_scan_digits(&s[4 - l % 2], 2, 10, &n, &ov); + } + sec += min * 60 + hour * 3600; + if (sign) sec = -sec; + offset = INT2FIX(sec); +#undef out_of_range + } + } + } + RB_GC_GUARD(str); + ok: + return offset; +} + +static int +day_num(VALUE s) +{ + int i; + + for (i = 0; i < (int)sizeof_array(abbr_days); i++) + if (strncasecmp(abbr_days[i], RSTRING_PTR(s), 3) == 0) + break; + return i; +} + +static int +mon_num(VALUE s) +{ + int i; + + for (i = 0; i < (int)sizeof_array(abbr_months); i++) + if (strncasecmp(abbr_months[i], RSTRING_PTR(s), 3) == 0) + break; + return i + 1; +} + +static int +parse_day_cb(VALUE m, VALUE hash) +{ + VALUE s; + + s = rb_reg_nth_match(1, m); + set_hash("wday", INT2FIX(day_num(s))); + return 1; +} + +static int +parse_day(VALUE str, VALUE hash) +{ + static const char pat_source[] = +#ifndef TIGHT_PARSER + "\\b(" ABBR_DAYS ")[^-/\\d\\s]*" +#else + "(" VALID_DAYS ")" +#endif + ; + static VALUE pat = Qnil; + + REGCOMP_I(pat); +#ifndef TIGHT_PARSER + SUBS(str, pat, parse_day_cb); +#else + SUBW(str, pat, parse_day_cb); +#endif +} + +static int +parse_time2_cb(VALUE m, VALUE hash) +{ + VALUE h, min, s, f, p; + + h = rb_reg_nth_match(1, m); + h = str2num(h); + + min = rb_reg_nth_match(2, m); + if (!NIL_P(min)) + min = str2num(min); + + s = rb_reg_nth_match(3, m); + if (!NIL_P(s)) + s = str2num(s); + + f = rb_reg_nth_match(4, m); + + if (!NIL_P(f)) + f = rb_rational_new2(str2num(f), + f_expt(INT2FIX(10), LONG2NUM(RSTRING_LEN(f)))); + + p = rb_reg_nth_match(5, m); + + if (!NIL_P(p)) { + int ih = NUM2INT(h); + ih %= 12; + if (*RSTRING_PTR(p) == 'P' || *RSTRING_PTR(p) == 'p') + ih += 12; + h = INT2FIX(ih); + } + + set_hash("hour", h); + if (!NIL_P(min)) + set_hash("min", min); + if (!NIL_P(s)) + set_hash("sec", s); + if (!NIL_P(f)) + set_hash("sec_fraction", f); + + return 1; +} + +static int +parse_time_cb(VALUE m, VALUE hash) +{ + static const char pat_source[] = + "\\A(\\d+)h?" + "(?:\\s*:?\\s*(\\d+)m?" + "(?:" + "\\s*:?\\s*(\\d+)(?:[,.](\\d+))?s?" + ")?" + ")?" + "(?:\\s*([ap])(?:m\\b|\\.m\\.))?"; + static VALUE pat = Qnil; + VALUE s1, s2; + + s1 = rb_reg_nth_match(1, m); + s2 = rb_reg_nth_match(2, m); + + if (!NIL_P(s2)) + set_hash("zone", s2); + + REGCOMP_I(pat); + + { + VALUE m = f_match(pat, s1); + + if (NIL_P(m)) + return 0; + parse_time2_cb(m, hash); + } + + return 1; +} + +static int +parse_time(VALUE str, VALUE hash) +{ + static const char pat_source[] = + "(" + "" NUMBER "+\\s*" + "(?:" + "(?:" + ":\\s*\\d+" + "(?:" +#ifndef TIGHT_PARSER + "\\s*:\\s*\\d+(?:[,.]\\d*)?" +#else + "\\s*:\\s*\\d+(?:[,.]\\d+)?" +#endif + ")?" + "|" + "h(?:\\s*\\d+m?(?:\\s*\\d+s?)?)?" + ")" + "(?:" + "\\s*" + "[ap](?:m\\b|\\.m\\.)" + ")?" + "|" + "[ap](?:m\\b|\\.m\\.)" + ")" + ")" + "(?:" + "\\s*" + "(" + "(?:gmt|utc?)?[-+]\\d+(?:[,.:]\\d+(?::\\d+)?)?" + "|" + "(?-i:[[:alpha:].\\s]+)(?:standard|daylight)\\stime\\b" + "|" + "(?-i:[[:alpha:]]+)(?:\\sdst)?\\b" + ")" + ")?"; + static VALUE pat = Qnil; + + REGCOMP_I(pat); +#ifndef TIGHT_PARSER + SUBS(str, pat, parse_time_cb); +#else + SUBT(str, pat, parse_time_cb); +#endif +} + +#define BEGIN_ERA "\\b" +#define END_ERA "(?!(? 1) + return 0; + return 1; +} +#endif + +static int +parse_eu_cb(VALUE m, VALUE hash) +{ +#ifndef TIGHT_PARSER + VALUE y, mon, d, b; + + d = rb_reg_nth_match(1, m); + mon = rb_reg_nth_match(2, m); + b = rb_reg_nth_match(3, m); + y = rb_reg_nth_match(4, m); + + mon = INT2FIX(mon_num(mon)); + + s3e(hash, y, mon, d, !NIL_P(b) && + (*RSTRING_PTR(b) == 'B' || + *RSTRING_PTR(b) == 'b')); +#else + VALUE y, mon, d; + + d = rb_reg_nth_match(1, m); + mon = rb_reg_nth_match(2, m); + y = rb_reg_nth_match(3, m); + + if (!check_apost(d, mon, y)) + return 0; + + mon = INT2FIX(mon_num(mon)); + + s3e(hash, y, mon, d, 0); +#endif + return 1; +} + +static int +parse_eu(VALUE str, VALUE hash) +{ + static const char pat_source[] = +#ifdef TIGHT_PARSER + BOS + FPW_COM FPT_COM +#endif +#ifndef TIGHT_PARSER + "('?" NUMBER "+)[^-\\d\\s]*" +#else + "(\\d+)(?:(?:st|nd|rd|th)\\b)?" +#endif + "\\s*" +#ifndef TIGHT_PARSER + "(" ABBR_MONTHS ")[^-\\d\\s']*" +#else + "(" VALID_MONTHS ")" +#endif + "(?:" + "\\s*" +#ifndef TIGHT_PARSER + "(?:" + BEGIN_ERA + "(c(?:e|\\.e\\.)|b(?:ce|\\.c\\.e\\.)|a(?:d|\\.d\\.)|b(?:c|\\.c\\.))" + END_ERA + ")?" + "\\s*" + "('?-?\\d+(?:(?:st|nd|rd|th)\\b)?)" +#else + "(?:" FPA ")?" + "\\s*" + "([-']?\\d+)" + "\\s*" + "(?:" FPA "|" FPB ")?" +#endif + ")?" +#ifdef TIGHT_PARSER + COM_FPT COM_FPW + EOS +#endif + ; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + SUBS(str, pat, parse_eu_cb); +} + +static int +parse_us_cb(VALUE m, VALUE hash) +{ +#ifndef TIGHT_PARSER + VALUE y, mon, d, b; + + mon = rb_reg_nth_match(1, m); + d = rb_reg_nth_match(2, m); + + b = rb_reg_nth_match(3, m); + y = rb_reg_nth_match(4, m); + + mon = INT2FIX(mon_num(mon)); + + s3e(hash, y, mon, d, !NIL_P(b) && + (*RSTRING_PTR(b) == 'B' || + *RSTRING_PTR(b) == 'b')); +#else + VALUE y, mon, d; + + mon = rb_reg_nth_match(1, m); + d = rb_reg_nth_match(2, m); + y = rb_reg_nth_match(3, m); + + if (!check_apost(mon, d, y)) + return 0; + + mon = INT2FIX(mon_num(mon)); + + s3e(hash, y, mon, d, 0); +#endif + return 1; +} + +static int +parse_us(VALUE str, VALUE hash) +{ + static const char pat_source[] = +#ifdef TIGHT_PARSER + BOS + FPW_COM FPT_COM +#endif +#ifndef TIGHT_PARSER + "\\b(" ABBR_MONTHS ")[^-\\d\\s']*" +#else + "\\b(" VALID_MONTHS ")" +#endif + "\\s*" +#ifndef TIGHT_PARSER + "('?\\d+)[^-\\d\\s']*" +#else + "('?\\d+)(?:(?:st|nd|rd|th)\\b)?" + COM_FPT +#endif + "(?:" + "\\s*+,?" + "\\s*+" +#ifndef TIGHT_PARSER + "(c(?:e|\\.e\\.)|b(?:ce|\\.c\\.e\\.)|a(?:d|\\.d\\.)|b(?:c|\\.c\\.))?" + "\\s*" + "('?-?\\d+)" +#else + "(?:" FPA ")?" + "\\s*" + "([-']?\\d+)" + "\\s*" + "(?:" FPA "|" FPB ")?" +#endif + ")?" +#ifdef TIGHT_PARSER + COM_FPT COM_FPW + EOS +#endif + ; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + SUBS(str, pat, parse_us_cb); +} + +static int +parse_iso_cb(VALUE m, VALUE hash) +{ + VALUE y, mon, d; + + y = rb_reg_nth_match(1, m); + mon = rb_reg_nth_match(2, m); + d = rb_reg_nth_match(3, m); + +#ifdef TIGHT_PARSER + if (!check_apost(y, mon, d)) + return 0; +#endif + + s3e(hash, y, mon, d, 0); + return 1; +} + +static int +parse_iso(VALUE str, VALUE hash) +{ + static const char pat_source[] = +#ifndef TIGHT_PARSER + "('?[-+]?" NUMBER "+)-(\\d+)-('?-?\\d+)" +#else + BOS + FPW_COM FPT_COM + "([-+']?\\d+)-(\\d+)-([-']?\\d+)" + TEE_FPT COM_FPW + EOS +#endif + ; + static VALUE pat = Qnil; + + REGCOMP_0(pat); + SUBS(str, pat, parse_iso_cb); +} + +static int +parse_iso21_cb(VALUE m, VALUE hash) +{ + VALUE y, w, d; + + y = rb_reg_nth_match(1, m); + w = rb_reg_nth_match(2, m); + d = rb_reg_nth_match(3, m); + + if (!NIL_P(y)) + set_hash("cwyear", str2num(y)); + set_hash("cweek", str2num(w)); + if (!NIL_P(d)) + set_hash("cwday", str2num(d)); + + return 1; +} + +static int +parse_iso21(VALUE str, VALUE hash) +{ + static const char pat_source[] = +#ifndef TIGHT_PARSER + "\\b(\\d{2}|\\d{4})?-?w(\\d{2})(?:-?(\\d))?\\b" +#else + BOS + FPW_COM FPT_COM + "(\\d{2}|\\d{4})?-?w(\\d{2})(?:-?(\\d))?" + TEE_FPT COM_FPW + EOS +#endif + ; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + SUBS(str, pat, parse_iso21_cb); +} + +static int +parse_iso22_cb(VALUE m, VALUE hash) +{ + VALUE d; + + d = rb_reg_nth_match(1, m); + set_hash("cwday", str2num(d)); + return 1; +} + +static int +parse_iso22(VALUE str, VALUE hash) +{ + static const char pat_source[] = +#ifndef TIGHT_PARSER + "-w-(\\d)\\b" +#else + BOS + FPW_COM FPT_COM + "-w-(\\d)" + TEE_FPT COM_FPW + EOS +#endif + ; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + SUBS(str, pat, parse_iso22_cb); +} + +static int +parse_iso23_cb(VALUE m, VALUE hash) +{ + VALUE mon, d; + + mon = rb_reg_nth_match(1, m); + d = rb_reg_nth_match(2, m); + + if (!NIL_P(mon)) + set_hash("mon", str2num(mon)); + set_hash("mday", str2num(d)); + + return 1; +} + +static int +parse_iso23(VALUE str, VALUE hash) +{ + static const char pat_source[] = +#ifndef TIGHT_PARSER + "--(\\d{2})?-(\\d{2})\\b" +#else + BOS + FPW_COM FPT_COM + "--(\\d{2})?-(\\d{2})" + TEE_FPT COM_FPW + EOS +#endif + ; + static VALUE pat = Qnil; + + REGCOMP_0(pat); + SUBS(str, pat, parse_iso23_cb); +} + +static int +parse_iso24_cb(VALUE m, VALUE hash) +{ + VALUE mon, d; + + mon = rb_reg_nth_match(1, m); + d = rb_reg_nth_match(2, m); + + set_hash("mon", str2num(mon)); + if (!NIL_P(d)) + set_hash("mday", str2num(d)); + + return 1; +} + +static int +parse_iso24(VALUE str, VALUE hash) +{ + static const char pat_source[] = +#ifndef TIGHT_PARSER + "--(\\d{2})(\\d{2})?\\b" +#else + BOS + FPW_COM FPT_COM + "--(\\d{2})(\\d{2})?" + TEE_FPT COM_FPW + EOS +#endif + ; + static VALUE pat = Qnil; + + REGCOMP_0(pat); + SUBS(str, pat, parse_iso24_cb); +} + +static int +parse_iso25_cb(VALUE m, VALUE hash) +{ + VALUE y, d; + + y = rb_reg_nth_match(1, m); + d = rb_reg_nth_match(2, m); + + set_hash("year", str2num(y)); + set_hash("yday", str2num(d)); + + return 1; +} + +static int +parse_iso25(VALUE str, VALUE hash) +{ + static const char pat0_source[] = +#ifndef TIGHT_PARSER + "[,.](\\d{2}|\\d{4})-\\d{3}\\b" +#else + BOS + FPW_COM FPT_COM + "[,.](\\d{2}|\\d{4})-\\d{3}" + TEE_FPT COM_FPW + EOS +#endif + ; + static VALUE pat0 = Qnil; + static const char pat_source[] = +#ifndef TIGHT_PARSER + "\\b(\\d{2}|\\d{4})-(\\d{3})\\b" +#else + BOS + FPW_COM FPT_COM + "(\\d{2}|\\d{4})-(\\d{3})" + TEE_FPT COM_FPW + EOS +#endif + ; + static VALUE pat = Qnil; + + REGCOMP_0(pat0); + REGCOMP_0(pat); + + if (!NIL_P(f_match(pat0, str))) + return 0; + SUBS(str, pat, parse_iso25_cb); +} + +static int +parse_iso26_cb(VALUE m, VALUE hash) +{ + VALUE d; + + d = rb_reg_nth_match(1, m); + set_hash("yday", str2num(d)); + + return 1; +} +static int +parse_iso26(VALUE str, VALUE hash) +{ + static const char pat0_source[] = +#ifndef TIGHT_PARSER + "\\d-\\d{3}\\b" +#else + BOS + FPW_COM FPT_COM + "\\d-\\d{3}" + TEE_FPT COM_FPW + EOS +#endif + ; + static VALUE pat0 = Qnil; + static const char pat_source[] = +#ifndef TIGHT_PARSER + "\\b-(\\d{3})\\b" +#else + BOS + FPW_COM FPT_COM + "-(\\d{3})" + TEE_FPT COM_FPW + EOS +#endif + ; + static VALUE pat = Qnil; + + REGCOMP_0(pat0); + REGCOMP_0(pat); + + if (!NIL_P(f_match(pat0, str))) + return 0; + SUBS(str, pat, parse_iso26_cb); +} + +static int +parse_iso2(VALUE str, VALUE hash) +{ + if (parse_iso21(str, hash)) + goto ok; + if (parse_iso22(str, hash)) + goto ok; + if (parse_iso23(str, hash)) + goto ok; + if (parse_iso24(str, hash)) + goto ok; + if (parse_iso25(str, hash)) + goto ok; + if (parse_iso26(str, hash)) + goto ok; + return 0; + + ok: + return 1; +} + +#define JISX0301_ERA_INITIALS "mtshr" +#define JISX0301_DEFAULT_ERA 'H' /* obsolete */ + +static int +gengo(int c) +{ + int e; + + switch (c) { + case 'M': case 'm': e = 1867; break; + case 'T': case 't': e = 1911; break; + case 'S': case 's': e = 1925; break; + case 'H': case 'h': e = 1988; break; + case 'R': case 'r': e = 2018; break; + default: e = 0; break; + } + return e; +} + +static int +parse_jis_cb(VALUE m, VALUE hash) +{ + VALUE e, y, mon, d; + int ep; + + e = rb_reg_nth_match(1, m); + y = rb_reg_nth_match(2, m); + mon = rb_reg_nth_match(3, m); + d = rb_reg_nth_match(4, m); + + ep = gengo(*RSTRING_PTR(e)); + + set_hash("year", f_add(str2num(y), INT2FIX(ep))); + set_hash("mon", str2num(mon)); + set_hash("mday", str2num(d)); + + return 1; +} + +static int +parse_jis(VALUE str, VALUE hash) +{ + static const char pat_source[] = +#ifndef TIGHT_PARSER + "\\b([" JISX0301_ERA_INITIALS "])(\\d+)\\.(\\d+)\\.(\\d+)" +#else + BOS + FPW_COM FPT_COM + "([" JISX0301_ERA_INITIALS "])(\\d+)\\.(\\d+)\\.(\\d+)" + TEE_FPT COM_FPW + EOS +#endif + ; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + SUBS(str, pat, parse_jis_cb); +} + +static int +parse_vms11_cb(VALUE m, VALUE hash) +{ + VALUE y, mon, d; + + d = rb_reg_nth_match(1, m); + mon = rb_reg_nth_match(2, m); + y = rb_reg_nth_match(3, m); + +#ifdef TIGHT_PARSER + if (!check_apost(d, mon, y)) + return 0; +#endif + + mon = INT2FIX(mon_num(mon)); + + s3e(hash, y, mon, d, 0); + return 1; +} + +static int +parse_vms11(VALUE str, VALUE hash) +{ + static const char pat_source[] = +#ifndef TIGHT_PARSER + "('?-?" NUMBER "+)-(" ABBR_MONTHS ")[^-/.]*" + "-('?-?\\d+)" +#else + BOS + FPW_COM FPT_COM + "([-']?\\d+)-(" DOTLESS_VALID_MONTHS ")" + "-([-']?\\d+)" + COM_FPT COM_FPW + EOS +#endif + ; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + SUBS(str, pat, parse_vms11_cb); +} + +static int +parse_vms12_cb(VALUE m, VALUE hash) +{ + VALUE y, mon, d; + + mon = rb_reg_nth_match(1, m); + d = rb_reg_nth_match(2, m); + y = rb_reg_nth_match(3, m); + +#ifdef TIGHT_PARSER + if (!check_apost(mon, d, y)) + return 0; +#endif + + mon = INT2FIX(mon_num(mon)); + + s3e(hash, y, mon, d, 0); + return 1; +} + +static int +parse_vms12(VALUE str, VALUE hash) +{ + static const char pat_source[] = +#ifndef TIGHT_PARSER + "\\b(" ABBR_MONTHS ")[^-/.]*" + "-('?-?\\d+)(?:-('?-?\\d+))?" +#else + BOS + FPW_COM FPT_COM + "(" DOTLESS_VALID_MONTHS ")" + "-([-']?\\d+)(?:-([-']?\\d+))?" + COM_FPT COM_FPW + EOS +#endif + ; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + SUBS(str, pat, parse_vms12_cb); +} + +static int +parse_vms(VALUE str, VALUE hash) +{ + if (parse_vms11(str, hash)) + goto ok; + if (parse_vms12(str, hash)) + goto ok; + return 0; + + ok: + return 1; +} + +static int +parse_sla_cb(VALUE m, VALUE hash) +{ + VALUE y, mon, d; + + y = rb_reg_nth_match(1, m); + mon = rb_reg_nth_match(2, m); + d = rb_reg_nth_match(3, m); + +#ifdef TIGHT_PARSER + if (!check_apost(y, mon, d)) + return 0; +#endif + + s3e(hash, y, mon, d, 0); + return 1; +} + +static int +parse_sla(VALUE str, VALUE hash) +{ + static const char pat_source[] = +#ifndef TIGHT_PARSER + "('?-?" NUMBER "+)/\\s*('?\\d+)(?:\\D\\s*('?-?\\d+))?" +#else + BOS + FPW_COM FPT_COM + "([-']?\\d+)/\\s*('?\\d+)(?:(?:[-/]|\\s+)\\s*([-']?\\d+))?" + COM_FPT COM_FPW + EOS +#endif + ; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + SUBS(str, pat, parse_sla_cb); +} + +#ifdef TIGHT_PARSER +static int +parse_sla2_cb(VALUE m, VALUE hash) +{ + VALUE y, mon, d; + + d = rb_reg_nth_match(1, m); + mon = rb_reg_nth_match(2, m); + y = rb_reg_nth_match(3, m); + + if (!check_apost(d, mon, y)) + return 0; + + mon = INT2FIX(mon_num(mon)); + + s3e(hash, y, mon, d, 0); + return 1; +} + +static int +parse_sla2(VALUE str, VALUE hash) +{ + static const char pat_source[] = + BOS + FPW_COM FPT_COM + "([-']?\\d+)/\\s*(" DOTLESS_VALID_MONTHS ")(?:(?:[-/]|\\s+)\\s*([-']?\\d+))?" + COM_FPT COM_FPW + EOS + ; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + SUBS(str, pat, parse_sla2_cb); +} + +static int +parse_sla3_cb(VALUE m, VALUE hash) +{ + VALUE y, mon, d; + + mon = rb_reg_nth_match(1, m); + d = rb_reg_nth_match(2, m); + y = rb_reg_nth_match(3, m); + + if (!check_apost(mon, d, y)) + return 0; + + mon = INT2FIX(mon_num(mon)); + + s3e(hash, y, mon, d, 0); + return 1; +} + +static int +parse_sla3(VALUE str, VALUE hash) +{ + static const char pat_source[] = + BOS + FPW_COM FPT_COM + "(" DOTLESS_VALID_MONTHS ")/\\s*([-']?\\d+)(?:(?:[-/]|\\s+)\\s*([-']?\\d+))?" + COM_FPT COM_FPW + EOS + ; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + SUBS(str, pat, parse_sla3_cb); +} +#endif + +static int +parse_dot_cb(VALUE m, VALUE hash) +{ + VALUE y, mon, d; + + y = rb_reg_nth_match(1, m); + mon = rb_reg_nth_match(2, m); + d = rb_reg_nth_match(3, m); + +#ifdef TIGHT_PARSER + if (!check_apost(y, mon, d)) + return 0; +#endif + + s3e(hash, y, mon, d, 0); + return 1; +} + +static int +parse_dot(VALUE str, VALUE hash) +{ + static const char pat_source[] = +#ifndef TIGHT_PARSER + "('?-?" NUMBER "+)\\.\\s*('?\\d+)\\.\\s*('?-?\\d+)" +#else + BOS + FPW_COM FPT_COM + "([-']?\\d+)\\.\\s*(\\d+)\\.\\s*([-']?\\d+)" + COM_FPT COM_FPW + EOS +#endif + ; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + SUBS(str, pat, parse_dot_cb); +} + +#ifdef TIGHT_PARSER +static int +parse_dot2_cb(VALUE m, VALUE hash) +{ + VALUE y, mon, d; + + d = rb_reg_nth_match(1, m); + mon = rb_reg_nth_match(2, m); + y = rb_reg_nth_match(3, m); + + if (!check_apost(d, mon, y)) + return 0; + + mon = INT2FIX(mon_num(mon)); + + s3e(hash, y, mon, d, 0); + return 1; +} + +static int +parse_dot2(VALUE str, VALUE hash) +{ + static const char pat_source[] = + BOS + FPW_COM FPT_COM + "([-']?\\d+)\\.\\s*(" DOTLESS_VALID_MONTHS ")(?:(?:[./])\\s*([-']?\\d+))?" + COM_FPT COM_FPW + EOS + ; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + SUBS(str, pat, parse_dot2_cb); +} + +static int +parse_dot3_cb(VALUE m, VALUE hash) +{ + VALUE y, mon, d; + + mon = rb_reg_nth_match(1, m); + d = rb_reg_nth_match(2, m); + y = rb_reg_nth_match(3, m); + + if (!check_apost(mon, d, y)) + return 0; + + mon = INT2FIX(mon_num(mon)); + + s3e(hash, y, mon, d, 0); + return 1; +} + +static int +parse_dot3(VALUE str, VALUE hash) +{ + static const char pat_source[] = + BOS + FPW_COM FPT_COM + "(" DOTLESS_VALID_MONTHS ")\\.\\s*([-']?\\d+)(?:(?:[./])\\s*([-']?\\d+))?" + COM_FPT COM_FPW + EOS + ; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + SUBS(str, pat, parse_dot3_cb); +} +#endif + +static int +parse_year_cb(VALUE m, VALUE hash) +{ + VALUE y; + + y = rb_reg_nth_match(1, m); + set_hash("year", str2num(y)); + return 1; +} + +static int +parse_year(VALUE str, VALUE hash) +{ + static const char pat_source[] = +#ifndef TIGHT_PARSER + "'(\\d+)\\b" +#else + BOS + FPW_COM FPT_COM + "'(\\d+)" + COM_FPT COM_FPW + EOS +#endif + ; + static VALUE pat = Qnil; + + REGCOMP_0(pat); + SUBS(str, pat, parse_year_cb); +} + +static int +parse_mon_cb(VALUE m, VALUE hash) +{ + VALUE mon; + + mon = rb_reg_nth_match(1, m); + set_hash("mon", INT2FIX(mon_num(mon))); + return 1; +} + +static int +parse_mon(VALUE str, VALUE hash) +{ + static const char pat_source[] = +#ifndef TIGHT_PARSER + "\\b(" ABBR_MONTHS ")\\S*" +#else + BOS + FPW_COM FPT_COM + "(" VALID_MONTHS ")" + COM_FPT COM_FPW + EOS +#endif + ; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + SUBS(str, pat, parse_mon_cb); +} + +static int +parse_mday_cb(VALUE m, VALUE hash) +{ + VALUE d; + + d = rb_reg_nth_match(1, m); + set_hash("mday", str2num(d)); + return 1; +} + +static int +parse_mday(VALUE str, VALUE hash) +{ + static const char pat_source[] = +#ifndef TIGHT_PARSER + "(" NUMBER "+)(st|nd|rd|th)\\b" +#else + BOS + FPW_COM FPT_COM + "(\\d+)(st|nd|rd|th)" + COM_FPT COM_FPW + EOS +#endif + ; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + SUBS(str, pat, parse_mday_cb); +} + +static int +n2i(const char *s, long f, long w) +{ + long e, i; + int v; + + e = f + w; + v = 0; + for (i = f; i < e; i++) { + v *= 10; + v += s[i] - '0'; + } + return v; +} + +static int +parse_ddd_cb(VALUE m, VALUE hash) +{ + VALUE s1, s2, s3, s4, s5; + const char *cs2, *cs3, *cs5; + long l2, l3, l4, l5; + + s1 = rb_reg_nth_match(1, m); + s2 = rb_reg_nth_match(2, m); + s3 = rb_reg_nth_match(3, m); + s4 = rb_reg_nth_match(4, m); + s5 = rb_reg_nth_match(5, m); + + cs2 = RSTRING_PTR(s2); + l2 = RSTRING_LEN(s2); + + switch (l2) { + case 2: + if (NIL_P(s3) && !NIL_P(s4)) + set_hash("sec", INT2FIX(n2i(cs2, l2-2, 2))); + else + set_hash("mday", INT2FIX(n2i(cs2, 0, 2))); + break; + case 4: + if (NIL_P(s3) && !NIL_P(s4)) { + set_hash("sec", INT2FIX(n2i(cs2, l2-2, 2))); + set_hash("min", INT2FIX(n2i(cs2, l2-4, 2))); + } + else { + set_hash("mon", INT2FIX(n2i(cs2, 0, 2))); + set_hash("mday", INT2FIX(n2i(cs2, 2, 2))); + } + break; + case 6: + if (NIL_P(s3) && !NIL_P(s4)) { + set_hash("sec", INT2FIX(n2i(cs2, l2-2, 2))); + set_hash("min", INT2FIX(n2i(cs2, l2-4, 2))); + set_hash("hour", INT2FIX(n2i(cs2, l2-6, 2))); + } + else { + int y = n2i(cs2, 0, 2); + if (!NIL_P(s1) && *RSTRING_PTR(s1) == '-') + y = -y; + set_hash("year", INT2FIX(y)); + set_hash("mon", INT2FIX(n2i(cs2, 2, 2))); + set_hash("mday", INT2FIX(n2i(cs2, 4, 2))); + } + break; + case 8: + case 10: + case 12: + case 14: + if (NIL_P(s3) && !NIL_P(s4)) { + set_hash("sec", INT2FIX(n2i(cs2, l2-2, 2))); + set_hash("min", INT2FIX(n2i(cs2, l2-4, 2))); + set_hash("hour", INT2FIX(n2i(cs2, l2-6, 2))); + set_hash("mday", INT2FIX(n2i(cs2, l2-8, 2))); + if (l2 >= 10) + set_hash("mon", INT2FIX(n2i(cs2, l2-10, 2))); + if (l2 == 12) { + int y = n2i(cs2, l2-12, 2); + if (!NIL_P(s1) && *RSTRING_PTR(s1) == '-') + y = -y; + set_hash("year", INT2FIX(y)); + } + if (l2 == 14) { + int y = n2i(cs2, l2-14, 4); + if (!NIL_P(s1) && *RSTRING_PTR(s1) == '-') + y = -y; + set_hash("year", INT2FIX(y)); + set_hash("_comp", Qfalse); + } + } + else { + int y = n2i(cs2, 0, 4); + if (!NIL_P(s1) && *RSTRING_PTR(s1) == '-') + y = -y; + set_hash("year", INT2FIX(y)); + set_hash("mon", INT2FIX(n2i(cs2, 4, 2))); + set_hash("mday", INT2FIX(n2i(cs2, 6, 2))); + if (l2 >= 10) + set_hash("hour", INT2FIX(n2i(cs2, 8, 2))); + if (l2 >= 12) + set_hash("min", INT2FIX(n2i(cs2, 10, 2))); + if (l2 >= 14) + set_hash("sec", INT2FIX(n2i(cs2, 12, 2))); + set_hash("_comp", Qfalse); + } + break; + case 3: + if (NIL_P(s3) && !NIL_P(s4)) { + set_hash("sec", INT2FIX(n2i(cs2, l2-2, 2))); + set_hash("min", INT2FIX(n2i(cs2, l2-3, 1))); + } + else + set_hash("yday", INT2FIX(n2i(cs2, 0, 3))); + break; + case 5: + if (NIL_P(s3) && !NIL_P(s4)) { + set_hash("sec", INT2FIX(n2i(cs2, l2-2, 2))); + set_hash("min", INT2FIX(n2i(cs2, l2-4, 2))); + set_hash("hour", INT2FIX(n2i(cs2, l2-5, 1))); + } + else { + int y = n2i(cs2, 0, 2); + if (!NIL_P(s1) && *RSTRING_PTR(s1) == '-') + y = -y; + set_hash("year", INT2FIX(y)); + set_hash("yday", INT2FIX(n2i(cs2, 2, 3))); + } + break; + case 7: + if (NIL_P(s3) && !NIL_P(s4)) { + set_hash("sec", INT2FIX(n2i(cs2, l2-2, 2))); + set_hash("min", INT2FIX(n2i(cs2, l2-4, 2))); + set_hash("hour", INT2FIX(n2i(cs2, l2-6, 2))); + set_hash("mday", INT2FIX(n2i(cs2, l2-7, 1))); + } + else { + int y = n2i(cs2, 0, 4); + if (!NIL_P(s1) && *RSTRING_PTR(s1) == '-') + y = -y; + set_hash("year", INT2FIX(y)); + set_hash("yday", INT2FIX(n2i(cs2, 4, 3))); + } + break; + } + RB_GC_GUARD(s2); + if (!NIL_P(s3)) { + cs3 = RSTRING_PTR(s3); + l3 = RSTRING_LEN(s3); + + if (!NIL_P(s4)) { + switch (l3) { + case 2: + case 4: + case 6: + set_hash("sec", INT2FIX(n2i(cs3, l3-2, 2))); + if (l3 >= 4) + set_hash("min", INT2FIX(n2i(cs3, l3-4, 2))); + if (l3 >= 6) + set_hash("hour", INT2FIX(n2i(cs3, l3-6, 2))); + break; + } + } + else { + switch (l3) { + case 2: + case 4: + case 6: + set_hash("hour", INT2FIX(n2i(cs3, 0, 2))); + if (l3 >= 4) + set_hash("min", INT2FIX(n2i(cs3, 2, 2))); + if (l3 >= 6) + set_hash("sec", INT2FIX(n2i(cs3, 4, 2))); + break; + } + } + RB_GC_GUARD(s3); + } + if (!NIL_P(s4)) { + l4 = RSTRING_LEN(s4); + + set_hash("sec_fraction", + rb_rational_new2(str2num(s4), + f_expt(INT2FIX(10), LONG2NUM(l4)))); + } + if (!NIL_P(s5)) { + cs5 = RSTRING_PTR(s5); + l5 = RSTRING_LEN(s5); + + set_hash("zone", s5); + + if (*cs5 == '[') { + const char *s1, *s2; + VALUE zone; + + l5 -= 2; + s1 = cs5 + 1; + s2 = memchr(s1, ':', l5); + if (s2) { + s2++; + zone = rb_str_subseq(s5, s2 - cs5, l5 - (s2 - s1)); + s5 = rb_str_subseq(s5, 1, s2 - s1); + } + else { + zone = rb_str_subseq(s5, 1, l5); + if (isdigit((unsigned char)*s1)) + s5 = rb_str_append(rb_str_new_cstr("+"), zone); + else + s5 = zone; + } + set_hash("zone", zone); + set_hash("offset", date_zone_to_diff(s5)); + } + RB_GC_GUARD(s5); + } + + return 1; +} + +static int +parse_ddd(VALUE str, VALUE hash) +{ + static const char pat_source[] = +#ifdef TIGHT_PARSER + BOS +#endif + "([-+]?)(" NUMBER "{2,14})" + "(?:" + "\\s*" + "t?" + "\\s*" + "(\\d{2,6})?(?:[,.](\\d*))?" + ")?" + "(?:" + "\\s*" + "(" + "z\\b" + "|" + "[-+]\\d{1,4}\\b" + "|" + "\\[[-+]?\\d[^\\]]*\\]" + ")" + ")?" +#ifdef TIGHT_PARSER + EOS +#endif + ; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + SUBS(str, pat, parse_ddd_cb); +} + +#ifndef TIGHT_PARSER +static int +parse_bc_cb(VALUE m, VALUE hash) +{ + set_hash("_bc", Qtrue); + return 1; +} + +static int +parse_bc(VALUE str, VALUE hash) +{ + static const char pat_source[] = + "\\b(bc\\b|bce\\b|b\\.c\\.|b\\.c\\.e\\.)"; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + SUBS(str, pat, parse_bc_cb); +} + +static int +parse_frag_cb(VALUE m, VALUE hash) +{ + VALUE s, n; + + s = rb_reg_nth_match(1, m); + + if (!NIL_P(ref_hash("hour")) && NIL_P(ref_hash("mday"))) { + n = str2num(s); + if (f_ge_p(n, INT2FIX(1)) && + f_le_p(n, INT2FIX(31))) + set_hash("mday", n); + } + if (!NIL_P(ref_hash("mday")) && NIL_P(ref_hash("hour"))) { + n = str2num(s); + if (f_ge_p(n, INT2FIX(0)) && + f_le_p(n, INT2FIX(24))) + set_hash("hour", n); + } + + return 1; +} + +static int +parse_frag(VALUE str, VALUE hash) +{ + static const char pat_source[] = "\\A\\s*(\\d{1,2})\\s*\\z"; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + SUBS(str, pat, parse_frag_cb); +} +#endif + +#ifdef TIGHT_PARSER +static int +parse_dummy_cb(VALUE m, VALUE hash) +{ + return 1; +} + +static int +parse_wday_only(VALUE str, VALUE hash) +{ + static const char pat_source[] = "\\A\\s*" FPW "\\s*\\z"; + static VALUE pat = Qnil; + + REGCOMP_0(pat); + SUBS(str, pat, parse_dummy_cb); +} + +static int +parse_time_only(VALUE str, VALUE hash) +{ + static const char pat_source[] = "\\A\\s*" FPT "\\s*\\z"; + static VALUE pat = Qnil; + + REGCOMP_0(pat); + SUBS(str, pat, parse_dummy_cb); +} + +static int +parse_wday_and_time(VALUE str, VALUE hash) +{ + static const char pat_source[] = "\\A\\s*(" FPW "\\s+" FPT "|" FPT "\\s+" FPW ")\\s*\\z"; + static VALUE pat = Qnil; + + REGCOMP_0(pat); + SUBS(str, pat, parse_dummy_cb); +} + +static unsigned +have_invalid_char_p(VALUE s) +{ + long i; + + for (i = 0; i < RSTRING_LEN(s); i++) + if (iscntrl((unsigned char)RSTRING_PTR(s)[i]) && + !isspace((unsigned char)RSTRING_PTR(s)[i])) + return 1; + return 0; +} +#endif + +#define HAVE_ALPHA (1<<0) +#define HAVE_DIGIT (1<<1) +#define HAVE_DASH (1<<2) +#define HAVE_DOT (1<<3) +#define HAVE_SLASH (1<<4) + +static unsigned +check_class(VALUE s) +{ + unsigned flags; + long i; + + flags = 0; + for (i = 0; i < RSTRING_LEN(s); i++) { + if (isalpha((unsigned char)RSTRING_PTR(s)[i])) + flags |= HAVE_ALPHA; + if (isdigit((unsigned char)RSTRING_PTR(s)[i])) + flags |= HAVE_DIGIT; + if (RSTRING_PTR(s)[i] == '-') + flags |= HAVE_DASH; + if (RSTRING_PTR(s)[i] == '.') + flags |= HAVE_DOT; + if (RSTRING_PTR(s)[i] == '/') + flags |= HAVE_SLASH; + } + return flags; +} + +#define HAVE_ELEM_P(x) ((check_class(str) & (x)) == (x)) + +#ifdef TIGHT_PARSER +#define PARSER_ERROR return rb_hash_new() +#endif + +VALUE +date__parse(VALUE str, VALUE comp) +{ + VALUE backref, hash; + +#ifdef TIGHT_PARSER + if (have_invalid_char_p(str)) + PARSER_ERROR; +#endif + + backref = rb_backref_get(); + rb_match_busy(backref); + + { + static const char pat_source[] = +#ifndef TIGHT_PARSER + "[^-+',./:@[:alnum:]\\[\\]]+" +#else + "[^[:graph:]]+" +#endif + ; + static VALUE pat = Qnil; + + REGCOMP_0(pat); + str = rb_str_dup(str); + f_gsub_bang(str, pat, asp_string()); + } + + hash = rb_hash_new(); + set_hash("_comp", comp); + + if (HAVE_ELEM_P(HAVE_ALPHA)) + parse_day(str, hash); + if (HAVE_ELEM_P(HAVE_DIGIT)) + parse_time(str, hash); + +#ifdef TIGHT_PARSER + if (HAVE_ELEM_P(HAVE_ALPHA)) + parse_era(str, hash); +#endif + + if (HAVE_ELEM_P(HAVE_ALPHA|HAVE_DIGIT)) { + if (parse_eu(str, hash)) + goto ok; + if (parse_us(str, hash)) + goto ok; + } + if (HAVE_ELEM_P(HAVE_DIGIT|HAVE_DASH)) + if (parse_iso(str, hash)) + goto ok; + if (HAVE_ELEM_P(HAVE_DIGIT|HAVE_DOT)) + if (parse_jis(str, hash)) + goto ok; + if (HAVE_ELEM_P(HAVE_ALPHA|HAVE_DIGIT|HAVE_DASH)) + if (parse_vms(str, hash)) + goto ok; + if (HAVE_ELEM_P(HAVE_DIGIT|HAVE_SLASH)) + if (parse_sla(str, hash)) + goto ok; +#ifdef TIGHT_PARSER + if (HAVE_ELEM_P(HAVE_ALPHA|HAVE_DIGIT|HAVE_SLASH)) { + if (parse_sla2(str, hash)) + goto ok; + if (parse_sla3(str, hash)) + goto ok; + } +#endif + if (HAVE_ELEM_P(HAVE_DIGIT|HAVE_DOT)) + if (parse_dot(str, hash)) + goto ok; +#ifdef TIGHT_PARSER + if (HAVE_ELEM_P(HAVE_ALPHA|HAVE_DIGIT|HAVE_DOT)) { + if (parse_dot2(str, hash)) + goto ok; + if (parse_dot3(str, hash)) + goto ok; + } +#endif + if (HAVE_ELEM_P(HAVE_DIGIT)) + if (parse_iso2(str, hash)) + goto ok; + if (HAVE_ELEM_P(HAVE_DIGIT)) + if (parse_year(str, hash)) + goto ok; + if (HAVE_ELEM_P(HAVE_ALPHA)) + if (parse_mon(str, hash)) + goto ok; + if (HAVE_ELEM_P(HAVE_DIGIT)) + if (parse_mday(str, hash)) + goto ok; + if (HAVE_ELEM_P(HAVE_DIGIT)) + if (parse_ddd(str, hash)) + goto ok; + +#ifdef TIGHT_PARSER + if (parse_wday_only(str, hash)) + goto ok; + if (parse_time_only(str, hash)) + goto ok; + if (parse_wday_and_time(str, hash)) + goto ok; + + PARSER_ERROR; /* not found */ +#endif + + ok: +#ifndef TIGHT_PARSER + if (HAVE_ELEM_P(HAVE_ALPHA)) + parse_bc(str, hash); + if (HAVE_ELEM_P(HAVE_DIGIT)) + parse_frag(str, hash); +#endif + + { + if (RTEST(del_hash("_bc"))) { + VALUE y; + + y = ref_hash("cwyear"); + if (!NIL_P(y)) { + y = f_add(f_negate(y), INT2FIX(1)); + set_hash("cwyear", y); + } + y = ref_hash("year"); + if (!NIL_P(y)) { + y = f_add(f_negate(y), INT2FIX(1)); + set_hash("year", y); + } + } + + if (RTEST(del_hash("_comp"))) { + VALUE y; + + y = ref_hash("cwyear"); + if (!NIL_P(y)) + if (f_ge_p(y, INT2FIX(0)) && f_le_p(y, INT2FIX(99))) { + if (f_ge_p(y, INT2FIX(69))) + set_hash("cwyear", f_add(y, INT2FIX(1900))); + else + set_hash("cwyear", f_add(y, INT2FIX(2000))); + } + y = ref_hash("year"); + if (!NIL_P(y)) + if (f_ge_p(y, INT2FIX(0)) && f_le_p(y, INT2FIX(99))) { + if (f_ge_p(y, INT2FIX(69))) + set_hash("year", f_add(y, INT2FIX(1900))); + else + set_hash("year", f_add(y, INT2FIX(2000))); + } + } + + } + + { + VALUE zone = ref_hash("zone"); + if (!NIL_P(zone) && NIL_P(ref_hash("offset"))) + set_hash("offset", date_zone_to_diff(zone)); + } + + rb_backref_set(backref); + + return hash; +} + +static VALUE +comp_year69(VALUE y) +{ + if (f_ge_p(y, INT2FIX(69))) + return f_add(y, INT2FIX(1900)); + return f_add(y, INT2FIX(2000)); +} + +static VALUE +comp_year50(VALUE y) +{ + if (f_ge_p(y, INT2FIX(50))) + return f_add(y, INT2FIX(1900)); + return f_add(y, INT2FIX(2000)); +} + +static VALUE +sec_fraction(VALUE f) +{ + return rb_rational_new2(str2num(f), + f_expt(INT2FIX(10), + LONG2NUM(RSTRING_LEN(f)))); +} + +#define SNUM 14 + +static int +iso8601_ext_datetime_cb(VALUE m, VALUE hash) +{ + VALUE s[SNUM + 1], y; + + { + int i; + s[0] = Qnil; + for (i = 1; i <= SNUM; i++) + s[i] = rb_reg_nth_match(i, m); + } + + if (!NIL_P(s[1])) { + if (!NIL_P(s[3])) set_hash("mday", str2num(s[3])); + if (strcmp(RSTRING_PTR(s[1]), "-") != 0) { + y = str2num(s[1]); + if (RSTRING_LEN(s[1]) < 4) + y = comp_year69(y); + set_hash("year", y); + } + if (NIL_P(s[2])) { + if (strcmp(RSTRING_PTR(s[1]), "-") != 0) + return 0; + } + else + set_hash("mon", str2num(s[2])); + } + else if (!NIL_P(s[5])) { + set_hash("yday", str2num(s[5])); + if (!NIL_P(s[4])) { + y = str2num(s[4]); + if (RSTRING_LEN(s[4]) < 4) + y = comp_year69(y); + set_hash("year", y); + } + } + else if (!NIL_P(s[8])) { + set_hash("cweek", str2num(s[7])); + set_hash("cwday", str2num(s[8])); + if (!NIL_P(s[6])) { + y = str2num(s[6]); + if (RSTRING_LEN(s[6]) < 4) + y = comp_year69(y); + set_hash("cwyear", y); + } + } + else if (!NIL_P(s[9])) { + set_hash("cwday", str2num(s[9])); + } + if (!NIL_P(s[10])) { + set_hash("hour", str2num(s[10])); + set_hash("min", str2num(s[11])); + if (!NIL_P(s[12])) + set_hash("sec", str2num(s[12])); + } + if (!NIL_P(s[13])) { + set_hash("sec_fraction", sec_fraction(s[13])); + } + if (!NIL_P(s[14])) { + set_hash("zone", s[14]); + set_hash("offset", date_zone_to_diff(s[14])); + } + + return 1; +} + +static int +iso8601_ext_datetime(VALUE str, VALUE hash) +{ + static const char pat_source[] = + "\\A\\s*(?:([-+]?\\d{2,}|-)-(\\d{2})?(?:-(\\d{2}))?|" + "([-+]?\\d{2,})?-(\\d{3})|" + "(\\d{4}|\\d{2})?-w(\\d{2})-(\\d)|" + "-w-(\\d))" + "(?:t" + "(\\d{2}):(\\d{2})(?::(\\d{2})(?:[,.](\\d+))?)?" + "(z|[-+]\\d{2}(?::?\\d{2})?)?)?\\s*\\z"; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + MATCH(str, pat, iso8601_ext_datetime_cb); +} + +#undef SNUM +#define SNUM 17 + +static int +iso8601_bas_datetime_cb(VALUE m, VALUE hash) +{ + VALUE s[SNUM + 1], y; + + { + int i; + s[0] = Qnil; + for (i = 1; i <= SNUM; i++) + s[i] = rb_reg_nth_match(i, m); + } + + if (!NIL_P(s[3])) { + set_hash("mday", str2num(s[3])); + if (strcmp(RSTRING_PTR(s[1]), "--") != 0) { + y = str2num(s[1]); + if (RSTRING_LEN(s[1]) < 4) + y = comp_year69(y); + set_hash("year", y); + } + if (*RSTRING_PTR(s[2]) == '-') { + if (strcmp(RSTRING_PTR(s[1]), "--") != 0) + return 0; + } + else + set_hash("mon", str2num(s[2])); + } + else if (!NIL_P(s[5])) { + set_hash("yday", str2num(s[5])); + y = str2num(s[4]); + if (RSTRING_LEN(s[4]) < 4) + y = comp_year69(y); + set_hash("year", y); + } + else if (!NIL_P(s[6])) { + set_hash("yday", str2num(s[6])); + } + else if (!NIL_P(s[9])) { + set_hash("cweek", str2num(s[8])); + set_hash("cwday", str2num(s[9])); + y = str2num(s[7]); + if (RSTRING_LEN(s[7]) < 4) + y = comp_year69(y); + set_hash("cwyear", y); + } + else if (!NIL_P(s[11])) { + set_hash("cweek", str2num(s[10])); + set_hash("cwday", str2num(s[11])); + } + else if (!NIL_P(s[12])) { + set_hash("cwday", str2num(s[12])); + } + if (!NIL_P(s[13])) { + set_hash("hour", str2num(s[13])); + set_hash("min", str2num(s[14])); + if (!NIL_P(s[15])) + set_hash("sec", str2num(s[15])); + } + if (!NIL_P(s[16])) { + set_hash("sec_fraction", sec_fraction(s[16])); + } + if (!NIL_P(s[17])) { + set_hash("zone", s[17]); + set_hash("offset", date_zone_to_diff(s[17])); + } + + return 1; +} + +static int +iso8601_bas_datetime(VALUE str, VALUE hash) +{ + static const char pat_source[] = + "\\A\\s*(?:([-+]?(?:\\d{4}|\\d{2})|--)(\\d{2}|-)(\\d{2})|" + "([-+]?(?:\\d{4}|\\d{2}))(\\d{3})|" + "-(\\d{3})|" + "(\\d{4}|\\d{2})w(\\d{2})(\\d)|" + "-w(\\d{2})(\\d)|" + "-w-(\\d))" + "(?:t?" + "(\\d{2})(\\d{2})(?:(\\d{2})(?:[,.](\\d+))?)?" + "(z|[-+]\\d{2}(?:\\d{2})?)?)?\\s*\\z"; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + MATCH(str, pat, iso8601_bas_datetime_cb); +} + +#undef SNUM +#define SNUM 5 + +static int +iso8601_ext_time_cb(VALUE m, VALUE hash) +{ + VALUE s[SNUM + 1]; + + { + int i; + s[0] = Qnil; + for (i = 1; i <= SNUM; i++) + s[i] = rb_reg_nth_match(i, m); + } + + set_hash("hour", str2num(s[1])); + set_hash("min", str2num(s[2])); + if (!NIL_P(s[3])) + set_hash("sec", str2num(s[3])); + if (!NIL_P(s[4])) + set_hash("sec_fraction", sec_fraction(s[4])); + if (!NIL_P(s[5])) { + set_hash("zone", s[5]); + set_hash("offset", date_zone_to_diff(s[5])); + } + + return 1; +} + +#define iso8601_bas_time_cb iso8601_ext_time_cb + +static int +iso8601_ext_time(VALUE str, VALUE hash) +{ + static const char pat_source[] = + "\\A\\s*(\\d{2}):(\\d{2})(?::(\\d{2})(?:[,.](\\d+))?" + "(z|[-+]\\d{2}(:?\\d{2})?)?)?\\s*\\z"; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + MATCH(str, pat, iso8601_ext_time_cb); +} + +static int +iso8601_bas_time(VALUE str, VALUE hash) +{ + static const char pat_source[] = + "\\A\\s*(\\d{2})(\\d{2})(?:(\\d{2})(?:[,.](\\d+))?" + "(z|[-+]\\d{2}(\\d{2})?)?)?\\s*\\z"; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + MATCH(str, pat, iso8601_bas_time_cb); +} + +VALUE +date__iso8601(VALUE str) +{ + VALUE backref, hash; + + backref = rb_backref_get(); + rb_match_busy(backref); + + hash = rb_hash_new(); + + if (iso8601_ext_datetime(str, hash)) + goto ok; + if (iso8601_bas_datetime(str, hash)) + goto ok; + if (iso8601_ext_time(str, hash)) + goto ok; + if (iso8601_bas_time(str, hash)) + goto ok; + + ok: + rb_backref_set(backref); + + return hash; +} + +#undef SNUM +#define SNUM 8 + +static int +rfc3339_cb(VALUE m, VALUE hash) +{ + VALUE s[SNUM + 1]; + + { + int i; + s[0] = Qnil; + for (i = 1; i <= SNUM; i++) + s[i] = rb_reg_nth_match(i, m); + } + + set_hash("year", str2num(s[1])); + set_hash("mon", str2num(s[2])); + set_hash("mday", str2num(s[3])); + set_hash("hour", str2num(s[4])); + set_hash("min", str2num(s[5])); + set_hash("sec", str2num(s[6])); + set_hash("zone", s[8]); + set_hash("offset", date_zone_to_diff(s[8])); + if (!NIL_P(s[7])) + set_hash("sec_fraction", sec_fraction(s[7])); + + return 1; +} + +static int +rfc3339(VALUE str, VALUE hash) +{ + static const char pat_source[] = + "\\A\\s*(-?\\d{4})-(\\d{2})-(\\d{2})" + "(?:t|\\s)" + "(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d+))?" + "(z|[-+]\\d{2}:\\d{2})\\s*\\z"; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + MATCH(str, pat, rfc3339_cb); +} + +VALUE +date__rfc3339(VALUE str) +{ + VALUE backref, hash; + + backref = rb_backref_get(); + rb_match_busy(backref); + + hash = rb_hash_new(); + rfc3339(str, hash); + rb_backref_set(backref); + return hash; +} + +#undef SNUM +#define SNUM 8 + +static int +xmlschema_datetime_cb(VALUE m, VALUE hash) +{ + VALUE s[SNUM + 1]; + + { + int i; + s[0] = Qnil; + for (i = 1; i <= SNUM; i++) + s[i] = rb_reg_nth_match(i, m); + } + + set_hash("year", str2num(s[1])); + if (!NIL_P(s[2])) + set_hash("mon", str2num(s[2])); + if (!NIL_P(s[3])) + set_hash("mday", str2num(s[3])); + if (!NIL_P(s[4])) + set_hash("hour", str2num(s[4])); + if (!NIL_P(s[5])) + set_hash("min", str2num(s[5])); + if (!NIL_P(s[6])) + set_hash("sec", str2num(s[6])); + if (!NIL_P(s[7])) + set_hash("sec_fraction", sec_fraction(s[7])); + if (!NIL_P(s[8])) { + set_hash("zone", s[8]); + set_hash("offset", date_zone_to_diff(s[8])); + } + + return 1; +} + +static int +xmlschema_datetime(VALUE str, VALUE hash) +{ + static const char pat_source[] = + "\\A\\s*(-?\\d{4,})(?:-(\\d{2})(?:-(\\d{2}))?)?" + "(?:t" + "(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d+))?)?" + "(z|[-+]\\d{2}:\\d{2})?\\s*\\z"; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + MATCH(str, pat, xmlschema_datetime_cb); +} + +#undef SNUM +#define SNUM 5 + +static int +xmlschema_time_cb(VALUE m, VALUE hash) +{ + VALUE s[SNUM + 1]; + + { + int i; + s[0] = Qnil; + for (i = 1; i <= SNUM; i++) + s[i] = rb_reg_nth_match(i, m); + } + + set_hash("hour", str2num(s[1])); + set_hash("min", str2num(s[2])); + if (!NIL_P(s[3])) + set_hash("sec", str2num(s[3])); + if (!NIL_P(s[4])) + set_hash("sec_fraction", sec_fraction(s[4])); + if (!NIL_P(s[5])) { + set_hash("zone", s[5]); + set_hash("offset", date_zone_to_diff(s[5])); + } + + return 1; +} + +static int +xmlschema_time(VALUE str, VALUE hash) +{ + static const char pat_source[] = + "\\A\\s*(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d+))?" + "(z|[-+]\\d{2}:\\d{2})?\\s*\\z"; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + MATCH(str, pat, xmlschema_time_cb); +} + +#undef SNUM +#define SNUM 4 + +static int +xmlschema_trunc_cb(VALUE m, VALUE hash) +{ + VALUE s[SNUM + 1]; + + { + int i; + s[0] = Qnil; + for (i = 1; i <= SNUM; i++) + s[i] = rb_reg_nth_match(i, m); + } + + if (!NIL_P(s[1])) + set_hash("mon", str2num(s[1])); + if (!NIL_P(s[2])) + set_hash("mday", str2num(s[2])); + if (!NIL_P(s[3])) + set_hash("mday", str2num(s[3])); + if (!NIL_P(s[4])) { + set_hash("zone", s[4]); + set_hash("offset", date_zone_to_diff(s[4])); + } + + return 1; +} + +static int +xmlschema_trunc(VALUE str, VALUE hash) +{ + static const char pat_source[] = + "\\A\\s*(?:--(\\d{2})(?:-(\\d{2}))?|---(\\d{2}))" + "(z|[-+]\\d{2}:\\d{2})?\\s*\\z"; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + MATCH(str, pat, xmlschema_trunc_cb); +} + +VALUE +date__xmlschema(VALUE str) +{ + VALUE backref, hash; + + backref = rb_backref_get(); + rb_match_busy(backref); + + hash = rb_hash_new(); + + if (xmlschema_datetime(str, hash)) + goto ok; + if (xmlschema_time(str, hash)) + goto ok; + if (xmlschema_trunc(str, hash)) + goto ok; + + ok: + rb_backref_set(backref); + + return hash; +} + +#undef SNUM +#define SNUM 8 + +static int +rfc2822_cb(VALUE m, VALUE hash) +{ + VALUE s[SNUM + 1], y; + + { + int i; + s[0] = Qnil; + for (i = 1; i <= SNUM; i++) + s[i] = rb_reg_nth_match(i, m); + } + + if (!NIL_P(s[1])) { + set_hash("wday", INT2FIX(day_num(s[1]))); + } + set_hash("mday", str2num(s[2])); + set_hash("mon", INT2FIX(mon_num(s[3]))); + y = str2num(s[4]); + if (RSTRING_LEN(s[4]) < 4) + y = comp_year50(y); + set_hash("year", y); + set_hash("hour", str2num(s[5])); + set_hash("min", str2num(s[6])); + if (!NIL_P(s[7])) + set_hash("sec", str2num(s[7])); + set_hash("zone", s[8]); + set_hash("offset", date_zone_to_diff(s[8])); + + return 1; +} + +static int +rfc2822(VALUE str, VALUE hash) +{ + static const char pat_source[] = + "\\A\\s*(?:(" ABBR_DAYS ")\\s*,\\s+)?" + "(\\d{1,2})\\s+" + "(" ABBR_MONTHS ")\\s+" + "(-?\\d{2,})\\s+" + "(\\d{2}):(\\d{2})(?::(\\d{2}))?\\s*" + "([-+]\\d{4}|ut|gmt|e[sd]t|c[sd]t|m[sd]t|p[sd]t|[a-ik-z])\\s*\\z"; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + MATCH(str, pat, rfc2822_cb); +} + +VALUE +date__rfc2822(VALUE str) +{ + VALUE backref, hash; + + backref = rb_backref_get(); + rb_match_busy(backref); + + hash = rb_hash_new(); + rfc2822(str, hash); + rb_backref_set(backref); + return hash; +} + +#undef SNUM +#define SNUM 8 + +static int +httpdate_type1_cb(VALUE m, VALUE hash) +{ + VALUE s[SNUM + 1]; + + { + int i; + s[0] = Qnil; + for (i = 1; i <= SNUM; i++) + s[i] = rb_reg_nth_match(i, m); + } + + set_hash("wday", INT2FIX(day_num(s[1]))); + set_hash("mday", str2num(s[2])); + set_hash("mon", INT2FIX(mon_num(s[3]))); + set_hash("year", str2num(s[4])); + set_hash("hour", str2num(s[5])); + set_hash("min", str2num(s[6])); + set_hash("sec", str2num(s[7])); + set_hash("zone", s[8]); + set_hash("offset", INT2FIX(0)); + + return 1; +} + +static int +httpdate_type1(VALUE str, VALUE hash) +{ + static const char pat_source[] = + "\\A\\s*(" ABBR_DAYS ")\\s*,\\s+" + "(\\d{2})\\s+" + "(" ABBR_MONTHS ")\\s+" + "(-?\\d{4})\\s+" + "(\\d{2}):(\\d{2}):(\\d{2})\\s+" + "(gmt)\\s*\\z"; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + MATCH(str, pat, httpdate_type1_cb); +} + +#undef SNUM +#define SNUM 8 + +static int +httpdate_type2_cb(VALUE m, VALUE hash) +{ + VALUE s[SNUM + 1], y; + + { + int i; + s[0] = Qnil; + for (i = 1; i <= SNUM; i++) + s[i] = rb_reg_nth_match(i, m); + } + + set_hash("wday", INT2FIX(day_num(s[1]))); + set_hash("mday", str2num(s[2])); + set_hash("mon", INT2FIX(mon_num(s[3]))); + y = str2num(s[4]); + if (f_ge_p(y, INT2FIX(0)) && f_le_p(y, INT2FIX(99))) + y = comp_year69(y); + set_hash("year", y); + set_hash("hour", str2num(s[5])); + set_hash("min", str2num(s[6])); + set_hash("sec", str2num(s[7])); + set_hash("zone", s[8]); + set_hash("offset", INT2FIX(0)); + + return 1; +} + +static int +httpdate_type2(VALUE str, VALUE hash) +{ + static const char pat_source[] = + "\\A\\s*(" DAYS ")\\s*,\\s+" + "(\\d{2})\\s*-\\s*" + "(" ABBR_MONTHS ")\\s*-\\s*" + "(\\d{2})\\s+" + "(\\d{2}):(\\d{2}):(\\d{2})\\s+" + "(gmt)\\s*\\z"; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + MATCH(str, pat, httpdate_type2_cb); +} + +#undef SNUM +#define SNUM 7 + +static int +httpdate_type3_cb(VALUE m, VALUE hash) +{ + VALUE s[SNUM + 1]; + + { + int i; + s[0] = Qnil; + for (i = 1; i <= SNUM; i++) + s[i] = rb_reg_nth_match(i, m); + } + + set_hash("wday", INT2FIX(day_num(s[1]))); + set_hash("mon", INT2FIX(mon_num(s[2]))); + set_hash("mday", str2num(s[3])); + set_hash("hour", str2num(s[4])); + set_hash("min", str2num(s[5])); + set_hash("sec", str2num(s[6])); + set_hash("year", str2num(s[7])); + + return 1; +} + +static int +httpdate_type3(VALUE str, VALUE hash) +{ + static const char pat_source[] = + "\\A\\s*(" ABBR_DAYS ")\\s+" + "(" ABBR_MONTHS ")\\s+" + "(\\d{1,2})\\s+" + "(\\d{2}):(\\d{2}):(\\d{2})\\s+" + "(\\d{4})\\s*\\z"; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + MATCH(str, pat, httpdate_type3_cb); +} + +VALUE +date__httpdate(VALUE str) +{ + VALUE backref, hash; + + backref = rb_backref_get(); + rb_match_busy(backref); + + hash = rb_hash_new(); + + if (httpdate_type1(str, hash)) + goto ok; + if (httpdate_type2(str, hash)) + goto ok; + if (httpdate_type3(str, hash)) + goto ok; + + ok: + rb_backref_set(backref); + + return hash; +} + +#undef SNUM +#define SNUM 9 + +static int +jisx0301_cb(VALUE m, VALUE hash) +{ + VALUE s[SNUM + 1]; + int ep; + + { + int i; + s[0] = Qnil; + for (i = 1; i <= SNUM; i++) + s[i] = rb_reg_nth_match(i, m); + } + + ep = gengo(NIL_P(s[1]) ? JISX0301_DEFAULT_ERA : *RSTRING_PTR(s[1])); + set_hash("year", f_add(str2num(s[2]), INT2FIX(ep))); + set_hash("mon", str2num(s[3])); + set_hash("mday", str2num(s[4])); + if (!NIL_P(s[5])) { + set_hash("hour", str2num(s[5])); + if (!NIL_P(s[6])) + set_hash("min", str2num(s[6])); + if (!NIL_P(s[7])) + set_hash("sec", str2num(s[7])); + } + if (!NIL_P(s[8])) + set_hash("sec_fraction", sec_fraction(s[8])); + if (!NIL_P(s[9])) { + set_hash("zone", s[9]); + set_hash("offset", date_zone_to_diff(s[9])); + } + + return 1; +} + +static int +jisx0301(VALUE str, VALUE hash) +{ + static const char pat_source[] = + "\\A\\s*([" JISX0301_ERA_INITIALS "])?(\\d{2})\\.(\\d{2})\\.(\\d{2})" + "(?:t" + "(?:(\\d{2}):(\\d{2})(?::(\\d{2})(?:[,.](\\d*))?)?" + "(z|[-+]\\d{2}(?::?\\d{2})?)?)?)?\\s*\\z"; + static VALUE pat = Qnil; + + REGCOMP_I(pat); + MATCH(str, pat, jisx0301_cb); +} + +VALUE +date__jisx0301(VALUE str) +{ + VALUE backref, hash; + + backref = rb_backref_get(); + rb_match_busy(backref); + + hash = rb_hash_new(); + if (jisx0301(str, hash)) + goto ok; + hash = date__iso8601(str); + + ok: + rb_backref_set(backref); + return hash; +} + +/* +Local variables: +c-file-style: "ruby" +End: +*/ diff --git a/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/date_strftime.c b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/date_strftime.c new file mode 100644 index 00000000..d7f28989 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/date_strftime.c @@ -0,0 +1,638 @@ +/* + date_strftime.c: based on a public-domain implementation of ANSI C + library routine strftime, which is originally written by Arnold + Robbins. + */ + +#include "ruby/ruby.h" +#include "date_tmx.h" + +#include +#include +#include +#include + +#if defined(HAVE_SYS_TIME_H) +#include +#endif + +#undef strchr /* avoid AIX weirdness */ + +#define range(low, item, hi) (item) + +#define add(x,y) (rb_funcall((x), '+', 1, (y))) +#define sub(x,y) (rb_funcall((x), '-', 1, (y))) +#define mul(x,y) (rb_funcall((x), '*', 1, (y))) +#define quo(x,y) (rb_funcall((x), rb_intern("quo"), 1, (y))) +#define div(x,y) (rb_funcall((x), rb_intern("div"), 1, (y))) +#define mod(x,y) (rb_funcall((x), '%', 1, (y))) + +static void +upcase(char *s, size_t i) +{ + do { + if (ISLOWER(*s)) + *s = TOUPPER(*s); + } while (s++, --i); +} + +static void +downcase(char *s, size_t i) +{ + do { + if (ISUPPER(*s)) + *s = TOLOWER(*s); + } while (s++, --i); +} + +/* strftime --- produce formatted time */ + +static size_t +date_strftime_with_tmx(char *s, const size_t maxsize, const char *format, + const struct tmx *tmx) +{ + char *endp = s + maxsize; + char *start = s; + const char *sp, *tp; + auto char tbuf[100]; + ptrdiff_t i; + int v, w; + size_t colons; + int precision, flags; + char padding; + /* LOCALE_[OE] and COLONS are actually modifiers, not flags */ + enum {LEFT, CHCASE, LOWER, UPPER, LOCALE_O, LOCALE_E, COLONS}; +#define BIT_OF(n) (1U<<(n)) + + /* various tables for locale C */ + static const char days_l[][10] = { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday", + }; + static const char months_l[][10] = { + "January", "February", "March", "April", + "May", "June", "July", "August", "September", + "October", "November", "December", + }; + static const char ampm[][3] = { "AM", "PM", }; + + if (s == NULL || format == NULL || tmx == NULL || maxsize == 0) + return 0; + + /* quick check if we even need to bother */ + if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize) { + err: + errno = ERANGE; + return 0; + } + + for (; *format && s < endp - 1; format++) { +#define FLAG_FOUND() do { \ + if (precision > 0 || flags & (BIT_OF(LOCALE_E) | BIT_OF(LOCALE_O) | BIT_OF(COLONS))) \ + goto unknown; \ + } while (0) +#define NEEDS(n) do if (s >= endp || (n) >= endp - s - 1) goto err; while (0) +#define FILL_PADDING(i) do { \ + if (!(flags & BIT_OF(LEFT)) && precision > (i)) { \ + NEEDS(precision); \ + memset(s, padding ? padding : ' ', precision - (i)); \ + s += precision - (i); \ + } \ + else { \ + NEEDS(i); \ + } \ + } while (0); +#define FMT(def_pad, def_prec, fmt, val) \ + do { \ + int l; \ + if (precision <= 0) precision = (def_prec); \ + if (flags & BIT_OF(LEFT)) precision = 1; \ + l = snprintf(s, endp - s, \ + ((padding == '0' || (!padding && (def_pad) == '0')) ? \ + "%0*"fmt : "%*"fmt), \ + precision, (val)); \ + if (l < 0) goto err; \ + s += l; \ + } while (0) +#define STRFTIME(fmt) \ + do { \ + i = date_strftime_with_tmx(s, endp - s, (fmt), tmx); \ + if (!i) return 0; \ + if (flags & BIT_OF(UPPER)) \ + upcase(s, i); \ + if (!(flags & BIT_OF(LEFT)) && precision > i) { \ + if (start + maxsize < s + precision) { \ + errno = ERANGE; \ + return 0; \ + } \ + memmove(s + precision - i, s, i); \ + memset(s, padding ? padding : ' ', precision - i); \ + s += precision; \ + } \ + else s += i; \ + } while (0) +#define FMTV(def_pad, def_prec, fmt, val) \ + do { \ + VALUE tmp = (val); \ + if (FIXNUM_P(tmp)) { \ + FMT((def_pad), (def_prec), "l"fmt, FIX2LONG(tmp)); \ + } \ + else { \ + VALUE args[2], result; \ + size_t l; \ + if (precision <= 0) precision = (def_prec); \ + if (flags & BIT_OF(LEFT)) precision = 1; \ + args[0] = INT2FIX(precision); \ + args[1] = (val); \ + if (padding == '0' || (!padding && (def_pad) == '0')) \ + result = rb_str_format(2, args, rb_str_new2("%0*"fmt)); \ + else \ + result = rb_str_format(2, args, rb_str_new2("%*"fmt)); \ + l = strlcpy(s, StringValueCStr(result), endp - s); \ + if ((size_t)(endp - s) <= l) \ + goto err; \ + s += l; \ + } \ + } while (0) + + if (*format != '%') { + *s++ = *format; + continue; + } + tp = tbuf; + sp = format; + precision = -1; + flags = 0; + padding = 0; + colons = 0; + again: + switch (*++format) { + case '\0': + format--; + goto unknown; + + case 'A': /* full weekday name */ + case 'a': /* abbreviated weekday name */ + if (flags & BIT_OF(CHCASE)) { + flags &= ~(BIT_OF(LOWER) | BIT_OF(CHCASE)); + flags |= BIT_OF(UPPER); + } + { + int wday = tmx_wday; + if (wday < 0 || wday > 6) + i = 1, tp = "?"; + else { + if (*format == 'A') + i = strlen(tp = days_l[wday]); + else + i = 3, tp = days_l[wday]; + } + } + break; + + case 'B': /* full month name */ + case 'b': /* abbreviated month name */ + case 'h': /* same as %b */ + if (flags & BIT_OF(CHCASE)) { + flags &= ~(BIT_OF(LOWER) | BIT_OF(CHCASE)); + flags |= BIT_OF(UPPER); + } + { + int mon = tmx_mon; + if (mon < 1 || mon > 12) + i = 1, tp = "?"; + else { + if (*format == 'B') + i = strlen(tp = months_l[mon - 1]); + else + i = 3, tp = months_l[mon - 1]; + } + } + break; + + case 'C': /* century (year/100) */ + FMTV('0', 2, "d", div(tmx_year, INT2FIX(100))); + continue; + + case 'c': /* appropriate date and time representation */ + STRFTIME("%a %b %e %H:%M:%S %Y"); + continue; + + case 'D': + STRFTIME("%m/%d/%y"); + continue; + + case 'd': /* day of the month, 01 - 31 */ + case 'e': /* day of month, blank padded */ + v = range(1, tmx_mday, 31); + FMT((*format == 'd') ? '0' : ' ', 2, "d", v); + continue; + + case 'F': + STRFTIME("%Y-%m-%d"); + continue; + + case 'G': /* year of ISO week with century */ + case 'Y': /* year with century */ + { + VALUE year = (*format == 'G') ? tmx_cwyear : tmx_year; + if (FIXNUM_P(year)) { + long y = FIX2LONG(year); + FMT('0', 0 <= y ? 4 : 5, "ld", y); + } + else { + FMTV('0', 4, "d", year); + } + } + continue; + + case 'g': /* year of ISO week without a century */ + case 'y': /* year without a century */ + v = NUM2INT(mod((*format == 'g') ? tmx_cwyear : tmx_year, INT2FIX(100))); + FMT('0', 2, "d", v); + continue; + + case 'H': /* hour, 24-hour clock, 00 - 23 */ + case 'k': /* hour, 24-hour clock, blank pad */ + v = range(0, tmx_hour, 23); + FMT((*format == 'H') ? '0' : ' ', 2, "d", v); + continue; + + case 'I': /* hour, 12-hour clock, 01 - 12 */ + case 'l': /* hour, 12-hour clock, 1 - 12, blank pad */ + v = range(0, tmx_hour, 23); + if (v == 0) + v = 12; + else if (v > 12) + v -= 12; + FMT((*format == 'I') ? '0' : ' ', 2, "d", v); + continue; + + case 'j': /* day of the year, 001 - 366 */ + v = range(1, tmx_yday, 366); + FMT('0', 3, "d", v); + continue; + + case 'L': /* millisecond */ + case 'N': /* nanosecond */ + if (*format == 'L') + w = 3; + else + w = 9; + if (precision <= 0) + precision = w; + NEEDS(precision); + + { + VALUE subsec = tmx_sec_fraction; + int ww; + long n; + + ww = precision; + while (9 <= ww) { + subsec = mul(subsec, INT2FIX(1000000000)); + ww -= 9; + } + n = 1; + for (; 0 < ww; ww--) + n *= 10; + if (n != 1) + subsec = mul(subsec, INT2FIX(n)); + subsec = div(subsec, INT2FIX(1)); + + if (FIXNUM_P(subsec)) { + (void)snprintf(s, endp - s, "%0*ld", + precision, FIX2LONG(subsec)); + s += precision; + } + else { + VALUE args[2], result; + args[0] = INT2FIX(precision); + args[1] = subsec; + result = rb_str_format(2, args, rb_str_new2("%0*d")); + (void)strlcpy(s, StringValueCStr(result), endp - s); + s += precision; + } + } + continue; + + case 'M': /* minute, 00 - 59 */ + v = range(0, tmx_min, 59); + FMT('0', 2, "d", v); + continue; + + case 'm': /* month, 01 - 12 */ + v = range(1, tmx_mon, 12); + FMT('0', 2, "d", v); + continue; + + case 'n': /* same as \n */ + FILL_PADDING(1); + *s++ = '\n'; + continue; + + case 't': /* same as \t */ + FILL_PADDING(1); + *s++ = '\t'; + continue; + + case 'P': /* am or pm based on 12-hour clock */ + case 'p': /* AM or PM based on 12-hour clock */ + if ((*format == 'p' && (flags & BIT_OF(CHCASE))) || + (*format == 'P' && !(flags & (BIT_OF(CHCASE) | BIT_OF(UPPER))))) { + flags &= ~(BIT_OF(UPPER) | BIT_OF(CHCASE)); + flags |= BIT_OF(LOWER); + } + v = range(0, tmx_hour, 23); + if (v < 12) + tp = ampm[0]; + else + tp = ampm[1]; + i = 2; + break; + + case 'Q': /* milliseconds since Unix epoch */ + FMTV('0', 1, "d", tmx_msecs); + continue; + + case 'R': + STRFTIME("%H:%M"); + continue; + + case 'r': + STRFTIME("%I:%M:%S %p"); + continue; + + case 'S': /* second, 00 - 59 */ + v = range(0, tmx_sec, 59); + FMT('0', 2, "d", v); + continue; + + case 's': /* seconds since Unix epoch */ + FMTV('0', 1, "d", tmx_secs); + continue; + + case 'T': + STRFTIME("%H:%M:%S"); + continue; + + case 'U': /* week of year, Sunday is first day of week */ + case 'W': /* week of year, Monday is first day of week */ + v = range(0, (*format == 'U') ? tmx_wnum0 : tmx_wnum1, 53); + FMT('0', 2, "d", v); + continue; + + case 'u': /* weekday, Monday == 1, 1 - 7 */ + v = range(1, tmx_cwday, 7); + FMT('0', 1, "d", v); + continue; + + case 'V': /* week of year according ISO 8601 */ + v = range(1, tmx_cweek, 53); + FMT('0', 2, "d", v); + continue; + + case 'v': + STRFTIME("%e-%^b-%Y"); + continue; + + case 'w': /* weekday, Sunday == 0, 0 - 6 */ + v = range(0, tmx_wday, 6); + FMT('0', 1, "d", v); + continue; + + case 'X': /* appropriate time representation */ + STRFTIME("%H:%M:%S"); + continue; + + case 'x': /* appropriate date representation */ + STRFTIME("%m/%d/%y"); + continue; + + case 'Z': /* time zone name or abbreviation */ + if (flags & BIT_OF(CHCASE)) { + flags &= ~(BIT_OF(UPPER) | BIT_OF(CHCASE)); + flags |= BIT_OF(LOWER); + } + { + char *zone = tmx_zone; + if (zone == NULL) + tp = ""; + else + tp = zone; + i = strlen(tp); + } + break; + + case 'z': /* offset from UTC */ + { + long off, aoff; + int hl, hw; + + off = tmx_offset; + aoff = off; + if (aoff < 0) + aoff = -off; + + if ((aoff / 3600) < 10) + hl = 1; + else + hl = 2; + hw = 2; + if (flags & BIT_OF(LEFT) && hl == 1) + hw = 1; + + switch (colons) { + case 0: /* %z -> +hhmm */ + precision = precision <= (3 + hw) ? hw : precision - 3; + NEEDS(precision + 3); + break; + + case 1: /* %:z -> +hh:mm */ + precision = precision <= (4 + hw) ? hw : precision - 4; + NEEDS(precision + 4); + break; + + case 2: /* %::z -> +hh:mm:ss */ + precision = precision <= (7 + hw) ? hw : precision - 7; + NEEDS(precision + 7); + break; + + case 3: /* %:::z -> +hh[:mm[:ss]] */ + { + if (aoff % 3600 == 0) { + precision = precision <= (1 + hw) ? + hw : precision - 1; + NEEDS(precision + 3); + } + else if (aoff % 60 == 0) { + precision = precision <= (4 + hw) ? + hw : precision - 4; + NEEDS(precision + 4); + } + else { + precision = precision <= (7 + hw) ? + hw : precision - 7; + NEEDS(precision + 7); + } + } + break; + + default: + format--; + goto unknown; + } + if (padding == ' ' && precision > hl) { + i = snprintf(s, endp - s, "%*s", precision - hl, ""); + precision = hl; + if (i < 0) goto err; + s += i; + } + if (off < 0) { + off = -off; + *s++ = '-'; + } else { + *s++ = '+'; + } + i = snprintf(s, endp - s, "%.*ld", precision, off / 3600); + if (i < 0) goto err; + s += i; + off = off % 3600; + if (colons == 3 && off == 0) + continue; + if (1 <= colons) + *s++ = ':'; + i = snprintf(s, endp - s, "%02d", (int)(off / 60)); + if (i < 0) goto err; + s += i; + off = off % 60; + if (colons == 3 && off == 0) + continue; + if (2 <= colons) { + *s++ = ':'; + i = snprintf(s, endp - s, "%02d", (int)off); + if (i < 0) goto err; + s += i; + } + } + continue; + + case '+': + STRFTIME("%a %b %e %H:%M:%S %Z %Y"); + continue; + + case 'E': + /* POSIX locale extensions, ignored for now */ + flags |= BIT_OF(LOCALE_E); + if (*(format + 1) && strchr("cCxXyY", *(format + 1))) + goto again; + goto unknown; + case 'O': + /* POSIX locale extensions, ignored for now */ + flags |= BIT_OF(LOCALE_O); + if (*(format + 1) && strchr("deHkIlmMSuUVwWy", *(format + 1))) + goto again; + goto unknown; + + case ':': + flags |= BIT_OF(COLONS); + { + size_t l = strspn(format, ":"); + format += l; + if (*format == 'z') { + colons = l; + format--; + goto again; + } + format -= l; + } + goto unknown; + + case '_': + FLAG_FOUND(); + padding = ' '; + goto again; + + case '-': + FLAG_FOUND(); + flags |= BIT_OF(LEFT); + goto again; + + case '^': + FLAG_FOUND(); + flags |= BIT_OF(UPPER); + goto again; + + case '#': + FLAG_FOUND(); + flags |= BIT_OF(CHCASE); + goto again; + + case '0': + FLAG_FOUND(); + padding = '0'; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + { + char *e; + unsigned long prec = strtoul(format, &e, 10); + if (prec > INT_MAX || prec > maxsize) { + errno = ERANGE; + return 0; + } + precision = (int)prec; + format = e - 1; + goto again; + } + + case '%': + FILL_PADDING(1); + *s++ = '%'; + continue; + + default: + unknown: + i = format - sp + 1; + tp = sp; + precision = -1; + flags = 0; + padding = 0; + colons = 0; + break; + } + if (i) { + FILL_PADDING(i); + memcpy(s, tp, i); + switch (flags & (BIT_OF(UPPER) | BIT_OF(LOWER))) { + case BIT_OF(UPPER): + upcase(s, i); + break; + case BIT_OF(LOWER): + downcase(s, i); + break; + } + s += i; + } + } + if (s >= endp) { + goto err; + } + if (*format == '\0') { + *s = '\0'; + return (s - start); + } + return 0; +} + +size_t +date_strftime(char *s, size_t maxsize, const char *format, + const struct tmx *tmx) +{ + return date_strftime_with_tmx(s, maxsize, format, tmx); +} + +/* +Local variables: +c-file-style: "ruby" +End: +*/ diff --git a/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/date_strptime.c b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/date_strptime.c new file mode 100644 index 00000000..da58c21a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/date_strptime.c @@ -0,0 +1,703 @@ +/* + date_strptime.c: Coded by Tadayoshi Funaba 2011,2012 +*/ + +#include "ruby.h" +#include "ruby/encoding.h" +#include "ruby/re.h" +#include + +#undef strncasecmp +#define strncasecmp STRNCASECMP + +static const char *day_names[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday", +}; +static const int ABBREVIATED_DAY_NAME_LENGTH = 3; + +static const char *month_names[] = { + "January", "February", "March", "April", + "May", "June", "July", "August", "September", + "October", "November", "December", +}; +static const int ABBREVIATED_MONTH_NAME_LENGTH = 3; + +#define sizeof_array(o) (sizeof o / sizeof o[0]) + +#define f_negate(x) rb_funcall(x, rb_intern("-@"), 0) +#define f_add(x,y) rb_funcall(x, '+', 1, y) +#define f_sub(x,y) rb_funcall(x, '-', 1, y) +#define f_mul(x,y) rb_funcall(x, '*', 1, y) +#define f_div(x,y) rb_funcall(x, '/', 1, y) +#define f_idiv(x,y) rb_funcall(x, rb_intern("div"), 1, y) +#define f_mod(x,y) rb_funcall(x, '%', 1, y) +#define f_expt(x,y) rb_funcall(x, rb_intern("**"), 1, y) + +#define f_lt_p(x,y) rb_funcall(x, '<', 1, y) +#define f_gt_p(x,y) rb_funcall(x, '>', 1, y) +#define f_le_p(x,y) rb_funcall(x, rb_intern("<="), 1, y) +#define f_ge_p(x,y) rb_funcall(x, rb_intern(">="), 1, y) + +#define f_match(r,s) rb_funcall(r, rb_intern("match"), 1, s) +#define f_aref(o,i) rb_funcall(o, rb_intern("[]"), 1, i) +#define f_end(o,i) rb_funcall(o, rb_intern("end"), 1, i) + +#define issign(c) ((c) == '-' || (c) == '+') + +static int +num_pattern_p(const char *s) +{ + if (isdigit((unsigned char)*s)) + return 1; + if (*s == '%') { + s++; + if (*s == 'E' || *s == 'O') + s++; + if (*s && + (strchr("CDdeFGgHIjkLlMmNQRrSsTUuVvWwXxYy", *s) || + isdigit((unsigned char)*s))) + return 1; + } + return 0; +} + +#define NUM_PATTERN_P() num_pattern_p(&fmt[fi + 1]) + +static long +read_digits(const char *s, size_t slen, VALUE *n, size_t width) +{ + size_t l; + + if (!width) + return 0; + + l = 0; + while (l < slen && ISDIGIT(s[l])) { + if (++l == width) break; + } + + if (l == 0) + return 0; + + if ((4 * l * sizeof(char)) <= (sizeof(long)*CHAR_BIT)) { + const char *os = s; + long v; + + v = 0; + while ((size_t)(s - os) < l) { + v *= 10; + v += *s - '0'; + s++; + } + if (os == s) + return 0; + *n = LONG2NUM(v); + return l; + } + else { + VALUE vbuf = 0; + char *s2 = ALLOCV_N(char, vbuf, l + 1); + memcpy(s2, s, l); + s2[l] = '\0'; + *n = rb_cstr_to_inum(s2, 10, 0); + ALLOCV_END(vbuf); + return l; + } +} + +#define set_hash(k,v) rb_hash_aset(hash, ID2SYM(rb_intern(k"")), v) +#define ref_hash(k) rb_hash_aref(hash, ID2SYM(rb_intern(k""))) +#define del_hash(k) rb_hash_delete(hash, ID2SYM(rb_intern(k""))) + +#define fail() \ +do { \ + set_hash("_fail", Qtrue); \ + return 0; \ +} while (0) + +#define fail_p() (!NIL_P(ref_hash("_fail"))) + +#define READ_DIGITS(n,w) \ +do { \ + size_t l; \ + l = read_digits(&str[si], slen - si, &n, w); \ + if (l == 0) \ + fail(); \ + si += l; \ +} while (0) + +#define READ_DIGITS_MAX(n) READ_DIGITS(n, LONG_MAX) + +static int +valid_range_p(VALUE v, int a, int b) +{ + if (FIXNUM_P(v)) { + int vi = FIX2INT(v); + return !(vi < a || vi > b); + } + return !(f_lt_p(v, INT2NUM(a)) || f_gt_p(v, INT2NUM(b))); +} + +#define recur(fmt) \ +do { \ + size_t l; \ + l = date__strptime_internal(&str[si], slen - si, \ + fmt, sizeof fmt - 1, hash); \ + if (fail_p()) \ + return 0; \ + si += l; \ +} while (0) + +VALUE date_zone_to_diff(VALUE); + +static inline int +head_match_p(size_t len, const char *name, const char *str, size_t slen, size_t si) +{ + return slen - si >= len && strncasecmp(name, &str[si], len) == 0; +} + +static size_t +date__strptime_internal(const char *str, size_t slen, + const char *fmt, size_t flen, VALUE hash) +{ + size_t si, fi; + int c; + +#define HEAD_MATCH_P(len, name) head_match_p(len, name, str, slen, si) + si = fi = 0; + + while (fi < flen) { + if (isspace((unsigned char)fmt[fi])) { + while (si < slen && isspace((unsigned char)str[si])) + si++; + while (++fi < flen && isspace((unsigned char)fmt[fi])); + continue; + } + + if (si >= slen) fail(); + + switch (fmt[fi]) { + case '%': + + again: + fi++; + c = fmt[fi]; + + switch (c) { + case 'E': + if (fmt[fi + 1] && strchr("cCxXyY", fmt[fi + 1])) + goto again; + fi--; + goto ordinal; + case 'O': + if (fmt[fi + 1] && strchr("deHImMSuUVwWy", fmt[fi + 1])) + goto again; + fi--; + goto ordinal; + case ':': + { + int i; + + for (i = 1; i < 3 && fi + i < flen && fmt[fi+i] == ':'; ++i); + if (fmt[fi+i] == 'z') { + fi += i - 1; + goto again; + } + fail(); + } + + case 'A': + case 'a': + { + int i; + + for (i = 0; i < (int)sizeof_array(day_names); i++) { + const char *day_name = day_names[i]; + size_t l = strlen(day_name); + if (HEAD_MATCH_P(l, day_name) || + HEAD_MATCH_P(l = ABBREVIATED_DAY_NAME_LENGTH, day_name)) { + si += l; + set_hash("wday", INT2FIX(i)); + goto matched; + } + } + fail(); + } + case 'B': + case 'b': + case 'h': + { + int i; + + for (i = 0; i < (int)sizeof_array(month_names); i++) { + const char *month_name = month_names[i]; + size_t l = strlen(month_name); + if (HEAD_MATCH_P(l, month_name) || + HEAD_MATCH_P(l = ABBREVIATED_MONTH_NAME_LENGTH, month_name)) { + si += l; + set_hash("mon", INT2FIX(i + 1)); + goto matched; + } + } + fail(); + } + + case 'C': + { + VALUE n; + + if (NUM_PATTERN_P()) + READ_DIGITS(n, 2); + else + READ_DIGITS_MAX(n); + set_hash("_cent", n); + goto matched; + } + + case 'c': + recur("%a %b %e %H:%M:%S %Y"); + goto matched; + + case 'D': + recur("%m/%d/%y"); + goto matched; + + case 'd': + case 'e': + { + VALUE n; + + if (str[si] == ' ') { + si++; + READ_DIGITS(n, 1); + } else { + READ_DIGITS(n, 2); + } + if (!valid_range_p(n, 1, 31)) + fail(); + set_hash("mday", n); + goto matched; + } + + case 'F': + recur("%Y-%m-%d"); + goto matched; + + case 'G': + { + VALUE n; + + if (NUM_PATTERN_P()) + READ_DIGITS(n, 4); + else + READ_DIGITS_MAX(n); + set_hash("cwyear", n); + goto matched; + } + + case 'g': + { + VALUE n; + + READ_DIGITS(n, 2); + if (!valid_range_p(n, 0, 99)) + fail(); + set_hash("cwyear",n); + if (NIL_P(ref_hash("_cent"))) + set_hash("_cent", + INT2FIX(f_ge_p(n, INT2FIX(69)) ? 19 : 20)); + goto matched; + } + + case 'H': + case 'k': + { + VALUE n; + + if (str[si] == ' ') { + si++; + READ_DIGITS(n, 1); + } else { + READ_DIGITS(n, 2); + } + if (!valid_range_p(n, 0, 24)) + fail(); + set_hash("hour", n); + goto matched; + } + + case 'I': + case 'l': + { + VALUE n; + + if (str[si] == ' ') { + si++; + READ_DIGITS(n, 1); + } else { + READ_DIGITS(n, 2); + } + if (!valid_range_p(n, 1, 12)) + fail(); + set_hash("hour", n); + goto matched; + } + + case 'j': + { + VALUE n; + + READ_DIGITS(n, 3); + if (!valid_range_p(n, 1, 366)) + fail(); + set_hash("yday", n); + goto matched; + } + + case 'L': + case 'N': + { + VALUE n; + int sign = 1; + size_t osi; + + if (issign(str[si])) { + if (str[si] == '-') + sign = -1; + si++; + } + osi = si; + if (NUM_PATTERN_P()) + READ_DIGITS(n, c == 'L' ? 3 : 9); + else + READ_DIGITS_MAX(n); + if (sign == -1) + n = f_negate(n); + set_hash("sec_fraction", + rb_rational_new2(n, + f_expt(INT2FIX(10), + ULONG2NUM(si - osi)))); + goto matched; + } + + case 'M': + { + VALUE n; + + READ_DIGITS(n, 2); + if (!valid_range_p(n, 0, 59)) + fail(); + set_hash("min", n); + goto matched; + } + + case 'm': + { + VALUE n; + + READ_DIGITS(n, 2); + if (!valid_range_p(n, 1, 12)) + fail(); + set_hash("mon", n); + goto matched; + } + + case 'n': + case 't': + recur(" "); + goto matched; + + case 'P': + case 'p': + if (slen - si < 2) fail(); + { + char c = str[si]; + const int hour = (c == 'P' || c == 'p') ? 12 : 0; + if (!hour && !(c == 'A' || c == 'a')) fail(); + if ((c = str[si+1]) == '.') { + if (slen - si < 4 || str[si+3] != '.') fail(); + c = str[si += 2]; + } + if (!(c == 'M' || c == 'm')) fail(); + si += 2; + set_hash("_merid", INT2FIX(hour)); + goto matched; + } + + case 'Q': + { + VALUE n; + int sign = 1; + + if (str[si] == '-') { + sign = -1; + si++; + } + READ_DIGITS_MAX(n); + if (sign == -1) + n = f_negate(n); + set_hash("seconds", + rb_rational_new2(n, INT2FIX(1000))); + goto matched; + } + + case 'R': + recur("%H:%M"); + goto matched; + + case 'r': + recur("%I:%M:%S %p"); + goto matched; + + case 'S': + { + VALUE n; + + READ_DIGITS(n, 2); + if (!valid_range_p(n, 0, 60)) + fail(); + set_hash("sec", n); + goto matched; + } + + case 's': + { + VALUE n; + int sign = 1; + + if (str[si] == '-') { + sign = -1; + si++; + } + READ_DIGITS_MAX(n); + if (sign == -1) + n = f_negate(n); + set_hash("seconds", n); + goto matched; + } + + case 'T': + recur("%H:%M:%S"); + goto matched; + + case 'U': + case 'W': + { + VALUE n; + + READ_DIGITS(n, 2); + if (!valid_range_p(n, 0, 53)) + fail(); + set_hash(c == 'U' ? "wnum0" : "wnum1", n); + goto matched; + } + + case 'u': + { + VALUE n; + + READ_DIGITS(n, 1); + if (!valid_range_p(n, 1, 7)) + fail(); + set_hash("cwday", n); + goto matched; + } + + case 'V': + { + VALUE n; + + READ_DIGITS(n, 2); + if (!valid_range_p(n, 1, 53)) + fail(); + set_hash("cweek", n); + goto matched; + } + + case 'v': + recur("%e-%b-%Y"); + goto matched; + + case 'w': + { + VALUE n; + + READ_DIGITS(n, 1); + if (!valid_range_p(n, 0, 6)) + fail(); + set_hash("wday", n); + goto matched; + } + + case 'X': + recur("%H:%M:%S"); + goto matched; + + case 'x': + recur("%m/%d/%y"); + goto matched; + + case 'Y': + { + VALUE n; + int sign = 1; + + if (issign(str[si])) { + if (str[si] == '-') + sign = -1; + si++; + } + if (NUM_PATTERN_P()) + READ_DIGITS(n, 4); + else + READ_DIGITS_MAX(n); + if (sign == -1) + n = f_negate(n); + set_hash("year", n); + goto matched; + } + + case 'y': + { + VALUE n; + int sign = 1; + + READ_DIGITS(n, 2); + if (!valid_range_p(n, 0, 99)) + fail(); + if (sign == -1) + n = f_negate(n); + set_hash("year", n); + if (NIL_P(ref_hash("_cent"))) + set_hash("_cent", + INT2FIX(f_ge_p(n, INT2FIX(69)) ? 19 : 20)); + goto matched; + } + + case 'Z': + case 'z': + { + static const char pat_source[] = + "\\A(" + "(?:gmt|utc?)?[-+]\\d+(?:[,.:]\\d+(?::\\d+)?)?" + "|(?-i:[[:alpha:].\\s]+)(?:standard|daylight)\\s+time\\b" + "|(?-i:[[:alpha:]]+)(?:\\s+dst)?\\b" + ")"; + static VALUE pat = Qnil; + VALUE m, b; + + if (NIL_P(pat)) { + pat = rb_reg_new(pat_source, sizeof pat_source - 1, + ONIG_OPTION_IGNORECASE); + rb_obj_freeze(pat); + rb_gc_register_mark_object(pat); + } + + b = rb_backref_get(); + rb_match_busy(b); + m = f_match(pat, rb_usascii_str_new(&str[si], slen - si)); + + if (!NIL_P(m)) { + VALUE s, l, o; + + s = rb_reg_nth_match(1, m); + l = f_end(m, INT2FIX(0)); + o = date_zone_to_diff(s); + si += NUM2LONG(l); + set_hash("zone", s); + set_hash("offset", o); + rb_backref_set(b); + goto matched; + } + rb_backref_set(b); + fail(); + } + + case '%': + if (str[si] != '%') + fail(); + si++; + goto matched; + + case '+': + recur("%a %b %e %H:%M:%S %Z %Y"); + goto matched; + + default: + if (str[si] != '%') + fail(); + si++; + if (fi < flen) { + if (si >= slen || str[si] != fmt[fi]) + fail(); + si++; + } + goto matched; + } + default: + ordinal: + if (str[si] != fmt[fi]) + fail(); + si++; + fi++; + break; + matched: + fi++; + break; + } + } + + return si; +} + +VALUE +date__strptime(const char *str, size_t slen, + const char *fmt, size_t flen, VALUE hash) +{ + size_t si; + VALUE cent, merid; + + si = date__strptime_internal(str, slen, fmt, flen, hash); + + if (slen > si) { + VALUE s; + + s = rb_usascii_str_new(&str[si], slen - si); + set_hash("leftover", s); + } + + if (fail_p()) + return Qnil; + + cent = del_hash("_cent"); + if (!NIL_P(cent)) { + VALUE year; + + year = ref_hash("cwyear"); + if (!NIL_P(year)) + set_hash("cwyear", f_add(year, f_mul(cent, INT2FIX(100)))); + year = ref_hash("year"); + if (!NIL_P(year)) + set_hash("year", f_add(year, f_mul(cent, INT2FIX(100)))); + } + + merid = del_hash("_merid"); + if (!NIL_P(merid)) { + VALUE hour; + + hour = ref_hash("hour"); + if (!NIL_P(hour)) { + hour = f_mod(hour, INT2FIX(12)); + set_hash("hour", f_add(hour, merid)); + } + } + + return hash; +} + +/* +Local variables: +c-file-style: "ruby" +End: +*/ diff --git a/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/date_tmx.h b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/date_tmx.h new file mode 100644 index 00000000..993a1532 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/date_tmx.h @@ -0,0 +1,56 @@ +#ifndef DATE_TMX_H +#define DATE_TMX_H + +struct tmx_funcs { + VALUE (*year)(void *dat); + int (*yday)(void *dat); + int (*mon)(void *dat); + int (*mday)(void *dat); + VALUE (*cwyear)(void *dat); + int (*cweek)(void *dat); + int (*cwday)(void *dat); + int (*wnum0)(void *dat); + int (*wnum1)(void *dat); + int (*wday)(void *dat); + int (*hour)(void *dat); + int (*min)(void *dat); + int (*sec)(void *dat); + VALUE (*sec_fraction)(void *dat); + VALUE (*secs)(void *dat); + VALUE (*msecs)(void *dat); + int (*offset)(void *dat); + char *(*zone)(void *dat); +}; +struct tmx { + void *dat; + const struct tmx_funcs *funcs; +}; + +#define tmx_attr(x) (tmx->funcs->x)(tmx->dat) + +#define tmx_year tmx_attr(year) +#define tmx_yday tmx_attr(yday) +#define tmx_mon tmx_attr(mon) +#define tmx_mday tmx_attr(mday) +#define tmx_cwyear tmx_attr(cwyear) +#define tmx_cweek tmx_attr(cweek) +#define tmx_cwday tmx_attr(cwday) +#define tmx_wnum0 tmx_attr(wnum0) +#define tmx_wnum1 tmx_attr(wnum1) +#define tmx_wday tmx_attr(wday) +#define tmx_hour tmx_attr(hour) +#define tmx_min tmx_attr(min) +#define tmx_sec tmx_attr(sec) +#define tmx_sec_fraction tmx_attr(sec_fraction) +#define tmx_secs tmx_attr(secs) +#define tmx_msecs tmx_attr(msecs) +#define tmx_offset tmx_attr(offset) +#define tmx_zone tmx_attr(zone) + +#endif + +/* +Local variables: +c-file-style: "ruby" +End: +*/ diff --git a/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/extconf.rb b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/extconf.rb new file mode 100644 index 00000000..8a1467df --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/extconf.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true +require 'mkmf' + +config_string("strict_warnflags") {|w| $warnflags += " #{w}"} + +append_cflags("-Wno-compound-token-split-by-macro") if RUBY_VERSION < "2.7." +have_func("rb_category_warn") +with_werror("", {:werror => true}) do |opt, | + have_var("timezone", "time.h", opt) + have_var("altzone", "time.h", opt) +end + +create_makefile('date_core') diff --git a/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/prereq.mk b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/prereq.mk new file mode 100644 index 00000000..b5d271a3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/prereq.mk @@ -0,0 +1,19 @@ +.SUFFIXES: .list + +.list.h: + gperf --ignore-case -L ANSI-C -C -c -P -p -j1 -i 1 -g -o -t -N $(*F) $< \ + | sed -f $(top_srcdir)/tool/gperf.sed \ + > $(@F) + +zonetab.h: zonetab.list + +.PHONY: update-zonetab +update-zonetab: + $(RUBY) -C $(srcdir) update-abbr + +.PHONY: update-nothing +update-nothing: + +update = nothing + +zonetab.list: update-$(update) diff --git a/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/zonetab.h b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/zonetab.h new file mode 100644 index 00000000..2a2e8910 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/zonetab.h @@ -0,0 +1,1564 @@ +/* ANSI-C code produced by gperf version 3.1 */ +/* Command-line: gperf --ignore-case -L ANSI-C -C -c -P -p -j1 -i 1 -g -o -t -N zonetab zonetab.list */ +/* Computed positions: -k'1-4,9' */ + +#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ + && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ + && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ + && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ + && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ + && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ + && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ + && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ + && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ + && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ + && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ + && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ + && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ + && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ + && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \ + && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ + && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ + && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ + && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ + && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ + && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ + && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ + && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)) +/* The character set is not based on ISO-646. */ +#error "gperf generated tables don't work with this execution character set. Please report a bug to ." +#endif + +#line 1 "zonetab.list" + +#define GPERF_DOWNCASE 1 +#define GPERF_CASE_STRNCMP 1 +#define gperf_case_strncmp strncasecmp +struct zone { + int name; + int offset; +}; +static const struct zone *zonetab(register const char *str, register size_t len); +#line 12 "zonetab.list" +struct zone; + +#define TOTAL_KEYWORDS 316 +#define MIN_WORD_LENGTH 1 +#define MAX_WORD_LENGTH 17 +#define MIN_HASH_VALUE 2 +#define MAX_HASH_VALUE 619 +/* maximum key range = 618, duplicates = 0 */ + +#ifndef GPERF_DOWNCASE +#define GPERF_DOWNCASE 1 +static unsigned char gperf_downcase[256] = + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, + 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, + 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, + 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, + 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, + 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, + 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, + 255 + }; +#endif + +#ifndef GPERF_CASE_STRNCMP +#define GPERF_CASE_STRNCMP 1 +static int +gperf_case_strncmp (register const char *s1, register const char *s2, register size_t n) +{ + for (; n > 0;) + { + unsigned char c1 = gperf_downcase[(unsigned char)*s1++]; + unsigned char c2 = gperf_downcase[(unsigned char)*s2++]; + if (c1 != 0 && c1 == c2) + { + n--; + continue; + } + return (int)c1 - (int)c2; + } + return 0; +} +#endif + +#ifdef __GNUC__ +__inline +#else +#ifdef __cplusplus +inline +#endif +#endif +static unsigned int +hash (register const char *str, register size_t len) +{ + static const unsigned short asso_values[] = + { + 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, + 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, + 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, + 620, 620, 17, 620, 620, 620, 620, 620, 620, 620, + 620, 620, 620, 620, 620, 3, 2, 620, 620, 620, + 620, 620, 70, 8, 3, 620, 620, 620, 620, 620, + 620, 620, 620, 620, 620, 39, 176, 207, 70, 168, + 1, 5, 18, 74, 218, 2, 117, 130, 48, 88, + 125, 225, 92, 1, 1, 12, 54, 30, 36, 13, + 48, 168, 263, 59, 114, 166, 109, 39, 176, 207, + 70, 168, 1, 5, 18, 74, 218, 2, 117, 130, + 48, 88, 125, 225, 92, 1, 1, 12, 54, 30, + 36, 13, 48, 168, 263, 59, 114, 166, 109, 27, + 104, 1, 9, 4, 309, 190, 188, 177, 255, 108, + 2, 341, 3, 620, 620, 620, 620, 620, 620, 12, + 54, 30, 36, 13, 48, 168, 263, 59, 114, 166, + 109, 27, 104, 1, 9, 4, 309, 190, 188, 177, + 255, 108, 2, 341, 3, 620, 620, 620, 620, 620, + 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, + 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, + 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, + 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, + 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, + 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, + 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, + 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, + 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, + 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, + 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, + 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, + 620, 620, 620, 620, 620, 620, 620, 620 + }; + register unsigned int hval = (unsigned int)len; + + switch (hval) + { + default: + hval += asso_values[(unsigned char)str[8]]; + /*FALLTHROUGH*/ + case 8: + case 7: + case 6: + case 5: + case 4: + hval += asso_values[(unsigned char)str[3]]; + /*FALLTHROUGH*/ + case 3: + hval += asso_values[(unsigned char)str[2]]; + /*FALLTHROUGH*/ + case 2: + hval += asso_values[(unsigned char)str[1]+6]; + /*FALLTHROUGH*/ + case 1: + hval += asso_values[(unsigned char)str[0]+52]; + break; + } + return (unsigned int)hval; +} + +struct stringpool_t + { + char stringpool_str2[sizeof("o")]; + char stringpool_str3[sizeof("x")]; + char stringpool_str4[sizeof("z")]; + char stringpool_str5[sizeof("q")]; + char stringpool_str8[sizeof("omst")]; + char stringpool_str9[sizeof("omsst")]; + char stringpool_str10[sizeof("p")]; + char stringpool_str13[sizeof("a")]; + char stringpool_str14[sizeof("e")]; + char stringpool_str15[sizeof("pet")]; + char stringpool_str16[sizeof("pmst")]; + char stringpool_str17[sizeof("pett")]; + char stringpool_str18[sizeof("petst")]; + char stringpool_str19[sizeof("eet")]; + char stringpool_str20[sizeof("aest")]; + char stringpool_str21[sizeof("eest")]; + char stringpool_str22[sizeof("eat")]; + char stringpool_str24[sizeof("east")]; + char stringpool_str25[sizeof("easst")]; + char stringpool_str26[sizeof("pst")]; + char stringpool_str27[sizeof("eastern")]; + char stringpool_str28[sizeof("m")]; + char stringpool_str29[sizeof("ast")]; + char stringpool_str30[sizeof("est")]; + char stringpool_str31[sizeof("c")]; + char stringpool_str32[sizeof("mmt")]; + char stringpool_str33[sizeof("met")]; + char stringpool_str35[sizeof("mest")]; + char stringpool_str36[sizeof("cet")]; + char stringpool_str37[sizeof("d")]; + char stringpool_str38[sizeof("cest")]; + char stringpool_str39[sizeof("cat")]; + char stringpool_str41[sizeof("cast")]; + char stringpool_str42[sizeof("magt")]; + char stringpool_str43[sizeof("magst")]; + char stringpool_str44[sizeof("mst")]; + char stringpool_str45[sizeof("msk")]; + char stringpool_str46[sizeof("cot")]; + char stringpool_str47[sizeof("cst")]; + char stringpool_str48[sizeof("aqtt")]; + char stringpool_str49[sizeof("f")]; + char stringpool_str52[sizeof("art")]; + char stringpool_str53[sizeof("fnt")]; + char stringpool_str54[sizeof("fet")]; + char stringpool_str55[sizeof("b")]; + char stringpool_str57[sizeof("anat")]; + char stringpool_str58[sizeof("anast")]; + char stringpool_str59[sizeof("bnt")]; + char stringpool_str60[sizeof("i")]; + char stringpool_str61[sizeof("pht")]; + char stringpool_str62[sizeof("at")]; + char stringpool_str63[sizeof("zp6")]; + char stringpool_str64[sizeof("mewt")]; + char stringpool_str65[sizeof("fst")]; + char stringpool_str66[sizeof("ahst")]; + char stringpool_str67[sizeof("mawt")]; + char stringpool_str68[sizeof("zp5")]; + char stringpool_str70[sizeof("bot")]; + char stringpool_str71[sizeof("bst")]; + char stringpool_str72[sizeof("pwt")]; + char stringpool_str74[sizeof("pont")]; + char stringpool_str75[sizeof("iot")]; + char stringpool_str76[sizeof("ist")]; + char stringpool_str77[sizeof("awst")]; + char stringpool_str79[sizeof("mht")]; + char stringpool_str80[sizeof("mez")]; + char stringpool_str81[sizeof("orat")]; + char stringpool_str82[sizeof("mesz")]; + char stringpool_str84[sizeof("chst")]; + char stringpool_str85[sizeof("pmdt")]; + char stringpool_str88[sizeof("central")]; + char stringpool_str89[sizeof("aedt")]; + char stringpool_str90[sizeof("act")]; + char stringpool_str91[sizeof("ect")]; + char stringpool_str92[sizeof("acst")]; + char stringpool_str93[sizeof("eadt")]; + char stringpool_str94[sizeof("brt")]; + char stringpool_str95[sizeof("chut")]; + char stringpool_str96[sizeof("brst")]; + char stringpool_str97[sizeof("cen. australia")]; + char stringpool_str100[sizeof("davt")]; + char stringpool_str101[sizeof("irst")]; + char stringpool_str102[sizeof("irkt")]; + char stringpool_str103[sizeof("irkst")]; + char stringpool_str104[sizeof("bt")]; + char stringpool_str105[sizeof("n")]; + char stringpool_str106[sizeof("btt")]; + char stringpool_str107[sizeof("mountain")]; + char stringpool_str108[sizeof("cct")]; + char stringpool_str109[sizeof("w")]; + char stringpool_str110[sizeof("l")]; + char stringpool_str111[sizeof("fwt")]; + char stringpool_str113[sizeof("msd")]; + char stringpool_str114[sizeof("wet")]; + char stringpool_str116[sizeof("west")]; + char stringpool_str117[sizeof("wat")]; + char stringpool_str119[sizeof("wast")]; + char stringpool_str120[sizeof("wakt")]; + char stringpool_str121[sizeof("nst")]; + char stringpool_str122[sizeof("acwst")]; + char stringpool_str123[sizeof("chast")]; + char stringpool_str124[sizeof("cist")]; + char stringpool_str125[sizeof("azt")]; + char stringpool_str126[sizeof("clt")]; + char stringpool_str127[sizeof("azst")]; + char stringpool_str128[sizeof("clst")]; + char stringpool_str129[sizeof("mart")]; + char stringpool_str130[sizeof("zp4")]; + char stringpool_str131[sizeof("jst")]; + char stringpool_str132[sizeof("central asia")]; + char stringpool_str133[sizeof("aft")]; + char stringpool_str134[sizeof("e. south america")]; + char stringpool_str135[sizeof("central america")]; + char stringpool_str137[sizeof("ict")]; + char stringpool_str143[sizeof("pgt")]; + char stringpool_str144[sizeof("nrt")]; + char stringpool_str145[sizeof("mexico")]; + char stringpool_str146[sizeof("awdt")]; + char stringpool_str147[sizeof("egt")]; + char stringpool_str148[sizeof("cxt")]; + char stringpool_str149[sizeof("egst")]; + char stringpool_str150[sizeof("phot")]; + char stringpool_str151[sizeof("alaskan")]; + char stringpool_str154[sizeof("nt")]; + char stringpool_str158[sizeof("wt")]; + char stringpool_str160[sizeof("west asia")]; + char stringpool_str161[sizeof("acdt")]; + char stringpool_str162[sizeof("npt")]; + char stringpool_str163[sizeof("lhst")]; + char stringpool_str164[sizeof("afghanistan")]; + char stringpool_str167[sizeof("k")]; + char stringpool_str169[sizeof("g")]; + char stringpool_str170[sizeof("irdt")]; + char stringpool_str171[sizeof("chot")]; + char stringpool_str172[sizeof("chost")]; + char stringpool_str173[sizeof("gmt")]; + char stringpool_str174[sizeof("get")]; + char stringpool_str175[sizeof("novt")]; + char stringpool_str176[sizeof("novst")]; + char stringpool_str177[sizeof("fjt")]; + char stringpool_str178[sizeof("u")]; + char stringpool_str179[sizeof("fjst")]; + char stringpool_str181[sizeof("pyst")]; + char stringpool_str182[sizeof("nct")]; + char stringpool_str183[sizeof("kst")]; + char stringpool_str184[sizeof("kost")]; + char stringpool_str185[sizeof("gst")]; + char stringpool_str186[sizeof("iran")]; + char stringpool_str187[sizeof("e. africa")]; + char stringpool_str188[sizeof("wadt")]; + char stringpool_str189[sizeof("t")]; + char stringpool_str190[sizeof("e. australia")]; + char stringpool_str191[sizeof("s")]; + char stringpool_str192[sizeof("chadt")]; + char stringpool_str193[sizeof("tmt")]; + char stringpool_str194[sizeof("cidst")]; + char stringpool_str195[sizeof("aoe")]; + char stringpool_str197[sizeof("myt")]; + char stringpool_str198[sizeof("west pacific")]; + char stringpool_str199[sizeof("mut")]; + char stringpool_str200[sizeof("wit")]; + char stringpool_str201[sizeof("sast")]; + char stringpool_str202[sizeof("sakt")]; + char stringpool_str203[sizeof("new zealand")]; + char stringpool_str204[sizeof("tot")]; + char stringpool_str205[sizeof("china")]; + char stringpool_str206[sizeof("tost")]; + char stringpool_str207[sizeof("sst")]; + char stringpool_str209[sizeof("india")]; + char stringpool_str211[sizeof("warst")]; + char stringpool_str212[sizeof("sbt")]; + char stringpool_str214[sizeof("azot")]; + char stringpool_str215[sizeof("azost")]; + char stringpool_str216[sizeof("taht")]; + char stringpool_str217[sizeof("nzt")]; + char stringpool_str218[sizeof("dateline")]; + char stringpool_str219[sizeof("nzst")]; + char stringpool_str220[sizeof("tokyo")]; + char stringpool_str221[sizeof("central pacific")]; + char stringpool_str223[sizeof("qyzt")]; + char stringpool_str224[sizeof("atlantic")]; + char stringpool_str225[sizeof("nft")]; + char stringpool_str227[sizeof("ut")]; + char stringpool_str228[sizeof("trt")]; + char stringpool_str229[sizeof("wft")]; + char stringpool_str230[sizeof("srt")]; + char stringpool_str231[sizeof("pdt")]; + char stringpool_str232[sizeof("lhdt")]; + char stringpool_str234[sizeof("adt")]; + char stringpool_str235[sizeof("edt")]; + char stringpool_str238[sizeof("pkt")]; + char stringpool_str239[sizeof("almt")]; + char stringpool_str240[sizeof("wita")]; + char stringpool_str242[sizeof("wgt")]; + char stringpool_str243[sizeof("akst")]; + char stringpool_str244[sizeof("wgst")]; + char stringpool_str246[sizeof("krat")]; + char stringpool_str247[sizeof("krast")]; + char stringpool_str248[sizeof("mid-atlantic")]; + char stringpool_str249[sizeof("mdt")]; + char stringpool_str250[sizeof("lint")]; + char stringpool_str251[sizeof("malay peninsula")]; + char stringpool_str252[sizeof("cdt")]; + char stringpool_str253[sizeof("swt")]; + char stringpool_str255[sizeof("se asia")]; + char stringpool_str256[sizeof("v")]; + char stringpool_str258[sizeof("tonga")]; + char stringpool_str259[sizeof("ckt")]; + char stringpool_str261[sizeof("vet")]; + char stringpool_str262[sizeof("caucasus")]; + char stringpool_str263[sizeof("central europe")]; + char stringpool_str264[sizeof("h")]; + char stringpool_str265[sizeof("central european")]; + char stringpool_str266[sizeof("newfoundland")]; + char stringpool_str267[sizeof("arab")]; + char stringpool_str268[sizeof("sct")]; + char stringpool_str269[sizeof("arabic")]; + char stringpool_str270[sizeof("arabian")]; + char stringpool_str271[sizeof("ddut")]; + char stringpool_str273[sizeof("vost")]; + char stringpool_str274[sizeof("hast")]; + char stringpool_str275[sizeof("nepal")]; + char stringpool_str276[sizeof("nut")]; + char stringpool_str277[sizeof("fkt")]; + char stringpool_str279[sizeof("fkst")]; + char stringpool_str280[sizeof("hst")]; + char stringpool_str281[sizeof("idt")]; + char stringpool_str284[sizeof("tlt")]; + char stringpool_str285[sizeof("w. australia")]; + char stringpool_str286[sizeof("egypt")]; + char stringpool_str287[sizeof("myanmar")]; + char stringpool_str288[sizeof("nzdt")]; + char stringpool_str289[sizeof("gft")]; + char stringpool_str290[sizeof("uzt")]; + char stringpool_str293[sizeof("north asia")]; + char stringpool_str294[sizeof("mvt")]; + char stringpool_str295[sizeof("galt")]; + char stringpool_str296[sizeof("nfdt")]; + char stringpool_str297[sizeof("cvt")]; + char stringpool_str298[sizeof("north asia east")]; + char stringpool_str300[sizeof("kgt")]; + char stringpool_str301[sizeof("aus central")]; + char stringpool_str302[sizeof("pacific")]; + char stringpool_str304[sizeof("canada central")]; + char stringpool_str306[sizeof("pacific sa")]; + char stringpool_str307[sizeof("azores")]; + char stringpool_str308[sizeof("gamt")]; + char stringpool_str309[sizeof("tft")]; + char stringpool_str310[sizeof("r")]; + char stringpool_str311[sizeof("fle")]; + char stringpool_str312[sizeof("akdt")]; + char stringpool_str313[sizeof("ulat")]; + char stringpool_str314[sizeof("ulast")]; + char stringpool_str315[sizeof("ret")]; + char stringpool_str317[sizeof("tjt")]; + char stringpool_str319[sizeof("south africa")]; + char stringpool_str324[sizeof("sgt")]; + char stringpool_str326[sizeof("ndt")]; + char stringpool_str327[sizeof("rott")]; + char stringpool_str330[sizeof("samt")]; + char stringpool_str332[sizeof("tasmania")]; + char stringpool_str334[sizeof("hovt")]; + char stringpool_str335[sizeof("hovst")]; + char stringpool_str338[sizeof("gyt")]; + char stringpool_str342[sizeof("y")]; + char stringpool_str343[sizeof("hadt")]; + char stringpool_str344[sizeof("sa western")]; + char stringpool_str345[sizeof("hawaiian")]; + char stringpool_str347[sizeof("uyt")]; + char stringpool_str349[sizeof("uyst")]; + char stringpool_str350[sizeof("yekt")]; + char stringpool_str351[sizeof("yekst")]; + char stringpool_str352[sizeof("kuyt")]; + char stringpool_str353[sizeof("yakt")]; + char stringpool_str354[sizeof("yakst")]; + char stringpool_str358[sizeof("yst")]; + char stringpool_str359[sizeof("jerusalem")]; + char stringpool_str365[sizeof("sri lanka")]; + char stringpool_str367[sizeof("yakutsk")]; + char stringpool_str375[sizeof("wib")]; + char stringpool_str377[sizeof("aus eastern")]; + char stringpool_str378[sizeof("gilt")]; + char stringpool_str387[sizeof("us mountain")]; + char stringpool_str391[sizeof("vlat")]; + char stringpool_str392[sizeof("vlast")]; + char stringpool_str395[sizeof("gtb")]; + char stringpool_str398[sizeof("taipei")]; + char stringpool_str399[sizeof("sret")]; + char stringpool_str408[sizeof("cape verde")]; + char stringpool_str417[sizeof("tkt")]; + char stringpool_str418[sizeof("samoa")]; + char stringpool_str421[sizeof("sa pacific")]; + char stringpool_str427[sizeof("vut")]; + char stringpool_str428[sizeof("idlw")]; + char stringpool_str432[sizeof("fiji")]; + char stringpool_str435[sizeof("utc")]; + char stringpool_str443[sizeof("korea")]; + char stringpool_str445[sizeof("e. europe")]; + char stringpool_str449[sizeof("syot")]; + char stringpool_str452[sizeof("n. central asia")]; + char stringpool_str455[sizeof("tvt")]; + char stringpool_str458[sizeof("w. central africa")]; + char stringpool_str466[sizeof("ekaterinburg")]; + char stringpool_str468[sizeof("vladivostok")]; + char stringpool_str476[sizeof("yapt")]; + char stringpool_str477[sizeof("us eastern")]; + char stringpool_str482[sizeof("sa eastern")]; + char stringpool_str485[sizeof("hdt")]; + char stringpool_str486[sizeof("russian")]; + char stringpool_str492[sizeof("hkt")]; + char stringpool_str497[sizeof("romance")]; + char stringpool_str540[sizeof("w. europe")]; + char stringpool_str563[sizeof("ydt")]; + char stringpool_str566[sizeof("idle")]; + char stringpool_str567[sizeof("greenwich")]; + char stringpool_str619[sizeof("greenland")]; + }; +static const struct stringpool_t stringpool_contents = + { + "o", + "x", + "z", + "q", + "omst", + "omsst", + "p", + "a", + "e", + "pet", + "pmst", + "pett", + "petst", + "eet", + "aest", + "eest", + "eat", + "east", + "easst", + "pst", + "eastern", + "m", + "ast", + "est", + "c", + "mmt", + "met", + "mest", + "cet", + "d", + "cest", + "cat", + "cast", + "magt", + "magst", + "mst", + "msk", + "cot", + "cst", + "aqtt", + "f", + "art", + "fnt", + "fet", + "b", + "anat", + "anast", + "bnt", + "i", + "pht", + "at", + "zp6", + "mewt", + "fst", + "ahst", + "mawt", + "zp5", + "bot", + "bst", + "pwt", + "pont", + "iot", + "ist", + "awst", + "mht", + "mez", + "orat", + "mesz", + "chst", + "pmdt", + "central", + "aedt", + "act", + "ect", + "acst", + "eadt", + "brt", + "chut", + "brst", + "cen. australia", + "davt", + "irst", + "irkt", + "irkst", + "bt", + "n", + "btt", + "mountain", + "cct", + "w", + "l", + "fwt", + "msd", + "wet", + "west", + "wat", + "wast", + "wakt", + "nst", + "acwst", + "chast", + "cist", + "azt", + "clt", + "azst", + "clst", + "mart", + "zp4", + "jst", + "central asia", + "aft", + "e. south america", + "central america", + "ict", + "pgt", + "nrt", + "mexico", + "awdt", + "egt", + "cxt", + "egst", + "phot", + "alaskan", + "nt", + "wt", + "west asia", + "acdt", + "npt", + "lhst", + "afghanistan", + "k", + "g", + "irdt", + "chot", + "chost", + "gmt", + "get", + "novt", + "novst", + "fjt", + "u", + "fjst", + "pyst", + "nct", + "kst", + "kost", + "gst", + "iran", + "e. africa", + "wadt", + "t", + "e. australia", + "s", + "chadt", + "tmt", + "cidst", + "aoe", + "myt", + "west pacific", + "mut", + "wit", + "sast", + "sakt", + "new zealand", + "tot", + "china", + "tost", + "sst", + "india", + "warst", + "sbt", + "azot", + "azost", + "taht", + "nzt", + "dateline", + "nzst", + "tokyo", + "central pacific", + "qyzt", + "atlantic", + "nft", + "ut", + "trt", + "wft", + "srt", + "pdt", + "lhdt", + "adt", + "edt", + "pkt", + "almt", + "wita", + "wgt", + "akst", + "wgst", + "krat", + "krast", + "mid-atlantic", + "mdt", + "lint", + "malay peninsula", + "cdt", + "swt", + "se asia", + "v", + "tonga", + "ckt", + "vet", + "caucasus", + "central europe", + "h", + "central european", + "newfoundland", + "arab", + "sct", + "arabic", + "arabian", + "ddut", + "vost", + "hast", + "nepal", + "nut", + "fkt", + "fkst", + "hst", + "idt", + "tlt", + "w. australia", + "egypt", + "myanmar", + "nzdt", + "gft", + "uzt", + "north asia", + "mvt", + "galt", + "nfdt", + "cvt", + "north asia east", + "kgt", + "aus central", + "pacific", + "canada central", + "pacific sa", + "azores", + "gamt", + "tft", + "r", + "fle", + "akdt", + "ulat", + "ulast", + "ret", + "tjt", + "south africa", + "sgt", + "ndt", + "rott", + "samt", + "tasmania", + "hovt", + "hovst", + "gyt", + "y", + "hadt", + "sa western", + "hawaiian", + "uyt", + "uyst", + "yekt", + "yekst", + "kuyt", + "yakt", + "yakst", + "yst", + "jerusalem", + "sri lanka", + "yakutsk", + "wib", + "aus eastern", + "gilt", + "us mountain", + "vlat", + "vlast", + "gtb", + "taipei", + "sret", + "cape verde", + "tkt", + "samoa", + "sa pacific", + "vut", + "idlw", + "fiji", + "utc", + "korea", + "e. europe", + "syot", + "n. central asia", + "tvt", + "w. central africa", + "ekaterinburg", + "vladivostok", + "yapt", + "us eastern", + "sa eastern", + "hdt", + "russian", + "hkt", + "romance", + "w. europe", + "ydt", + "idle", + "greenwich", + "greenland" + }; +#define stringpool ((const char *) &stringpool_contents) +const struct zone * +zonetab (register const char *str, register size_t len) +{ + static const struct zone wordlist[] = + { + {-1}, {-1}, +#line 37 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2, -2*3600}, +#line 46 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str3, -11*3600}, +#line 48 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str4, 0*3600}, +#line 39 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str5, -4*3600}, + {-1}, {-1}, +#line 272 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str8,21600}, +#line 271 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str9,25200}, +#line 38 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str10, -3*3600}, + {-1}, {-1}, +#line 24 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str13, 1*3600}, +#line 28 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str14, 5*3600}, +#line 274 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str15,-18000}, +#line 282 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str16,-10800}, +#line 276 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str17,43200}, +#line 275 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str18,43200}, +#line 83 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str19, 2*3600}, +#line 189 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str20,36000}, +#line 91 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str21, 3*3600}, +#line 90 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str22, 3*3600}, + {-1}, +#line 104 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str24,-6*3600}, +#line 220 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str25,-18000}, +#line 22 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str26, -8*3600}, +#line 136 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str27, -18000}, +#line 35 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str28, 12*3600}, +#line 59 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str29, -4*3600}, +#line 16 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str30, -5*3600}, +#line 26 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str31, 3*3600}, +#line 259 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str32,23400}, +#line 76 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str33, 1*3600}, + {-1}, +#line 85 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str35, 2*3600}, +#line 74 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str36, 1*3600}, +#line 27 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str37, 4*3600}, +#line 82 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str38, 2*3600}, +#line 68 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str39,2*3600}, + {-1}, +#line 205 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str41,28800}, +#line 255 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str42,39600}, +#line 254 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str43,43200}, +#line 20 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str44, -7*3600}, +#line 92 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str45, 3*3600}, +#line 215 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str46,-18000}, +#line 18 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str47, -6*3600}, +#line 195 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str48,18000}, +#line 29 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str49, 6*3600}, + {-1}, {-1}, +#line 54 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str52, -3*3600}, +#line 229 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str53,-7200}, +#line 224 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str54,10800}, +#line 25 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str55, 2*3600}, + {-1}, +#line 193 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str57,43200}, +#line 192 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str58,43200}, +#line 202 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str59,28800}, +#line 32 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str60, 9*3600}, +#line 279 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str61,28800}, +#line 51 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str62, -2*3600}, +#line 97 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str63, 6*3600}, +#line 77 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str64, 1*3600}, +#line 84 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str65, 2*3600}, +#line 67 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str66,-10*3600}, +#line 257 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str67,18000}, +#line 95 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str68, 5*3600}, + {-1}, +#line 203 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str70,-14400}, +#line 73 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str71, 1*3600}, +#line 284 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str72,32400}, + {-1}, +#line 283 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str74,39600}, +#line 241 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str75,21600}, +#line 96 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str76, (5*3600+1800)}, +#line 197 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str77,28800}, + {-1}, +#line 258 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str79,43200}, +#line 78 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str80, 1*3600}, +#line 273 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str81,18000}, +#line 86 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str82, 2*3600}, + {-1}, +#line 210 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str84,36000}, +#line 281 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str85,-7200}, + {-1}, {-1}, +#line 129 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str88, -21600}, +#line 188 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str89,39600}, +#line 186 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str90,-18000}, +#line 221 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str91,-18000}, +#line 185 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str92,34200}, +#line 106 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str93,11*3600}, +#line 56 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str94, -3*3600}, +#line 211 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str95,36000}, +#line 52 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str96,-2*3600}, +#line 123 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str97, 34200}, + {-1}, {-1}, +#line 218 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str100,25200}, +#line 245 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str101,12600}, +#line 244 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str102,28800}, +#line 243 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str103,32400}, +#line 89 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str104, 3*3600}, +#line 36 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str105, -1*3600}, +#line 204 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str106,21600}, +#line 151 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str107, -25200}, +#line 99 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str108, (6*3600+1800)}, +#line 45 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str109, -10*3600}, +#line 34 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str110, 11*3600}, +#line 75 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str111, 1*3600}, + {-1}, +#line 93 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str113, 4*3600}, +#line 50 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str114, 0*3600}, + {-1}, +#line 81 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str116, 1*3600}, +#line 80 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str117, 1*3600}, + {-1}, +#line 98 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str119, 2*3600}, +#line 316 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str120,43200}, +#line 58 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str121, -(2*3600+1800)}, +#line 187 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str122,31500}, +#line 207 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str123,45900}, +#line 213 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str124,-18000}, +#line 201 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str125,14400}, +#line 60 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str126, -4*3600}, +#line 200 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str127,18000}, +#line 57 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str128,-3*3600}, +#line 256 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str129,-30600}, +#line 94 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str130, 4*3600}, +#line 102 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str131, 9*3600}, +#line 125 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str132, 21600}, +#line 190 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str133,16200}, +#line 135 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str134, -10800}, +#line 124 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str135, -21600}, + {-1}, +#line 239 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str137,25200}, + {-1}, {-1}, {-1}, {-1}, {-1}, +#line 277 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str143,36000}, +#line 269 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str144,43200}, +#line 149 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str145, -21600}, +#line 196 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str146,32400}, +#line 223 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str147,-3600}, +#line 217 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str148,25200}, +#line 222 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str149,0}, +#line 278 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str150,46800}, +#line 112 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str151, -32400}, + {-1}, {-1}, +#line 71 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str154, -11*3600}, + {-1}, {-1}, {-1}, +#line 324 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str158,0}, + {-1}, +#line 181 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str160, 18000}, +#line 184 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str161,37800}, +#line 268 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str162,20700}, +#line 252 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str163,37800}, +#line 111 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str164, 16200}, + {-1}, {-1}, +#line 33 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str167, 10*3600}, + {-1}, +#line 30 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str169, 7*3600}, +#line 242 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str170,16200}, +#line 209 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str171,28800}, +#line 208 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str172,32400}, +#line 15 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str173, 0*3600}, +#line 232 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str174,14400}, +#line 267 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str175,25200}, +#line 266 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str176,25200}, +#line 226 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str177,43200}, +#line 43 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str178, -8*3600}, +#line 225 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str179,46800}, + {-1}, +#line 285 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str181,-10800}, +#line 263 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str182,39600}, +#line 103 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str183, 9*3600}, +#line 247 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str184,39600}, +#line 105 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str185, 10*3600}, +#line 146 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str186, 12600}, +#line 132 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str187, 10800}, +#line 101 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str188, 8*3600}, +#line 42 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str189, -7*3600}, +#line 133 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str190, 36000}, +#line 41 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str191, -6*3600}, +#line 206 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str192,49500}, +#line 301 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str193,18000}, +#line 212 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str194,-14400}, +#line 194 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str195,-43200}, + {-1}, +#line 262 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str197,28800}, +#line 182 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str198, 36000}, +#line 260 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str199,14400}, +#line 322 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str200,32400}, +#line 87 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str201, 2*3600}, +#line 289 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str202,39600}, +#line 155 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str203, 43200}, +#line 303 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str204,46800}, +#line 130 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str205, 28800}, +#line 302 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str206,50400}, +#line 88 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str207, -11*3600}, + {-1}, +#line 145 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str209, 19800}, + {-1}, +#line 317 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str211,-10800}, +#line 291 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str212,39600}, + {-1}, +#line 199 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str214,-3600}, +#line 198 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str215,0}, +#line 296 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str216,-36000}, +#line 109 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str217, 12*3600}, +#line 131 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str218, -43200}, +#line 108 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str219,12*3600}, +#line 173 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str220, 32400}, +#line 128 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str221, 39600}, + {-1}, +#line 286 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str223,21600}, +#line 116 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str224, -14400}, +#line 265 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str225,39600}, + {-1}, +#line 14 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str227, 0*3600}, +#line 304 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str228,10800}, +#line 318 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str229,43200}, +#line 294 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str230,-10800}, +#line 23 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str231, -7*3600}, +#line 251 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str232,39600}, + {-1}, +#line 55 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str234, -3*3600}, +#line 17 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str235, -4*3600}, + {-1}, {-1}, +#line 280 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str238,18000}, +#line 191 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str239,21600}, +#line 323 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str240,28800}, + {-1}, +#line 320 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str242,-7200}, +#line 63 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str243,-9*3600}, +#line 319 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str244,-3600}, + {-1}, +#line 249 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str246,25200}, +#line 248 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str247,28800}, +#line 150 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str248, -7200}, +#line 21 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str249, -6*3600}, +#line 253 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str250,50400}, +#line 168 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str251, 28800}, +#line 19 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str252, -5*3600}, +#line 79 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str253, 1*3600}, + {-1}, +#line 167 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str255, 25200}, +#line 44 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str256, -9*3600}, + {-1}, +#line 174 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str258, 46800}, +#line 214 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str259,-36000}, + {-1}, +#line 311 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str261,-14400}, +#line 122 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str262, 14400}, +#line 126 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str263, 3600}, +#line 31 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str264, 8*3600}, +#line 127 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str265, 3600}, +#line 156 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str266, -12600}, +#line 113 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str267, 10800}, +#line 292 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str268,14400}, +#line 115 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str269, 10800}, +#line 114 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str270, 14400}, +#line 219 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str271,36000}, + {-1}, +#line 314 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str273,21600}, +#line 69 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str274,-10*3600}, +#line 154 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str275, 20700}, +#line 270 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str276,-39600}, +#line 228 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str277,-14400}, + {-1}, +#line 227 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str279,-10800}, +#line 70 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str280,-10*3600}, +#line 240 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str281,10800}, + {-1}, {-1}, +#line 300 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str284,32400}, +#line 178 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str285, 28800}, +#line 137 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str286, 7200}, +#line 152 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str287, 23400}, +#line 110 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str288,13*3600}, +#line 233 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str289,-10800}, +#line 310 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str290,18000}, + {-1}, {-1}, +#line 158 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str293, 25200}, +#line 261 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str294,18000}, +#line 230 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str295,-21600}, +#line 264 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str296,43200}, +#line 216 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str297,-3600}, +#line 157 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str298, 28800}, + {-1}, +#line 246 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str300,21600}, +#line 117 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str301, 34200}, +#line 160 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str302, -28800}, + {-1}, +#line 120 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str304, -21600}, + {-1}, +#line 159 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str306, -14400}, +#line 119 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str307, -3600}, +#line 231 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str308,-32400}, +#line 297 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str309,18000}, +#line 40 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str310, -5*3600}, +#line 140 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str311, 7200}, +#line 61 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str312,-8*3600}, +#line 307 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str313,28800}, +#line 306 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str314,32400}, +#line 287 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str315,14400}, + {-1}, +#line 298 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str317,18000}, + {-1}, +#line 169 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str319, 7200}, + {-1}, {-1}, {-1}, {-1}, +#line 100 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str324, 8*3600}, + {-1}, +#line 53 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str326, -(1*3600+1800)}, +#line 288 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str327,-10800}, + {-1}, {-1}, +#line 290 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str330,14400}, + {-1}, +#line 172 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str332, 36000}, + {-1}, +#line 238 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str334,25200}, +#line 237 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str335,28800}, + {-1}, {-1}, +#line 235 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str338,-14400}, + {-1}, {-1}, {-1}, +#line 47 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str342, -12*3600}, +#line 64 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str343,-9*3600}, +#line 165 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str344, -14400}, +#line 144 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str345, -36000}, + {-1}, +#line 309 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str347,-10800}, + {-1}, +#line 308 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str349,-7200}, +#line 329 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str350,18000}, +#line 328 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str351,21600}, +#line 250 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str352,14400}, +#line 326 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str353,32400}, +#line 325 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str354,36000}, + {-1}, {-1}, {-1}, +#line 66 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str358, -9*3600}, +#line 147 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str359, 7200}, + {-1}, {-1}, {-1}, {-1}, {-1}, +#line 170 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str365, 21600}, + {-1}, +#line 183 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str367, 32400}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, +#line 321 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str375,25200}, + {-1}, +#line 118 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str377, 36000}, +#line 234 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str378,43200}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, +#line 176 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str387, -25200}, + {-1}, {-1}, {-1}, +#line 313 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str391,36000}, +#line 312 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str392,39600}, + {-1}, {-1}, +#line 143 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str395, 7200}, + {-1}, {-1}, +#line 171 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str398, 28800}, +#line 293 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str399,39600}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, +#line 121 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str408, -3600}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, +#line 299 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str417,46800}, +#line 166 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str418, -39600}, + {-1}, {-1}, +#line 164 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str421, -18000}, + {-1}, {-1}, {-1}, {-1}, {-1}, +#line 315 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str427,39600}, +#line 72 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str428,-12*3600}, + {-1}, {-1}, {-1}, +#line 139 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str432, 43200}, + {-1}, {-1}, +#line 49 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str435, 0*3600}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, +#line 148 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str443, 32400}, + {-1}, +#line 134 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str445, 7200}, + {-1}, {-1}, {-1}, +#line 295 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str449,10800}, + {-1}, {-1}, +#line 153 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str452, 21600}, + {-1}, {-1}, +#line 305 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str455,43200}, + {-1}, {-1}, +#line 179 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str458, 3600}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, +#line 138 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str466, 18000}, + {-1}, +#line 177 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str468, 36000}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, +#line 327 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str476,36000}, +#line 175 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str477, -18000}, + {-1}, {-1}, {-1}, {-1}, +#line 163 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str482, -10800}, + {-1}, {-1}, +#line 65 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str485, -9*3600}, +#line 162 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str486, 10800}, + {-1}, {-1}, {-1}, {-1}, {-1}, +#line 236 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str492,28800}, + {-1}, {-1}, {-1}, {-1}, +#line 161 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str497, 3600}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, +#line 180 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str540, 3600}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, +#line 62 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str563, -8*3600}, + {-1}, {-1}, +#line 107 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str566,12*3600}, +#line 142 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str567, 0}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, +#line 141 "zonetab.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str619, -10800} + }; + + if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) + { + register unsigned int key = hash (str, len); + + if (key <= MAX_HASH_VALUE) + { + register int o = wordlist[key].name; + if (o >= 0) + { + register const char *s = o + stringpool; + + if ((((unsigned char)*str ^ (unsigned char)*s) & ~32) == 0 && !gperf_case_strncmp (str, s, len) && s[len] == '\0') + return &wordlist[key]; + } + } + } + return 0; +} +#line 330 "zonetab.list" + diff --git a/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/zonetab.list b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/zonetab.list new file mode 100644 index 00000000..63b68734 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/ext/date/zonetab.list @@ -0,0 +1,330 @@ +%{ +#define GPERF_DOWNCASE 1 +#define GPERF_CASE_STRNCMP 1 +#define gperf_case_strncmp strncasecmp +struct zone { + int name; + int offset; +}; +static const struct zone *zonetab(register const char *str, register size_t len); +%} + +struct zone; +%% +ut, 0*3600 +gmt, 0*3600 +est, -5*3600 +edt, -4*3600 +cst, -6*3600 +cdt, -5*3600 +mst, -7*3600 +mdt, -6*3600 +pst, -8*3600 +pdt, -7*3600 +a, 1*3600 +b, 2*3600 +c, 3*3600 +d, 4*3600 +e, 5*3600 +f, 6*3600 +g, 7*3600 +h, 8*3600 +i, 9*3600 +k, 10*3600 +l, 11*3600 +m, 12*3600 +n, -1*3600 +o, -2*3600 +p, -3*3600 +q, -4*3600 +r, -5*3600 +s, -6*3600 +t, -7*3600 +u, -8*3600 +v, -9*3600 +w, -10*3600 +x, -11*3600 +y, -12*3600 +z, 0*3600 +utc, 0*3600 +wet, 0*3600 +at, -2*3600 +brst,-2*3600 +ndt, -(1*3600+1800) +art, -3*3600 +adt, -3*3600 +brt, -3*3600 +clst,-3*3600 +nst, -(2*3600+1800) +ast, -4*3600 +clt, -4*3600 +akdt,-8*3600 +ydt, -8*3600 +akst,-9*3600 +hadt,-9*3600 +hdt, -9*3600 +yst, -9*3600 +ahst,-10*3600 +cat,2*3600 +hast,-10*3600 +hst,-10*3600 +nt, -11*3600 +idlw,-12*3600 +bst, 1*3600 +cet, 1*3600 +fwt, 1*3600 +met, 1*3600 +mewt, 1*3600 +mez, 1*3600 +swt, 1*3600 +wat, 1*3600 +west, 1*3600 +cest, 2*3600 +eet, 2*3600 +fst, 2*3600 +mest, 2*3600 +mesz, 2*3600 +sast, 2*3600 +sst, -11*3600 +bt, 3*3600 +eat, 3*3600 +eest, 3*3600 +msk, 3*3600 +msd, 4*3600 +zp4, 4*3600 +zp5, 5*3600 +ist, (5*3600+1800) +zp6, 6*3600 +wast, 2*3600 +cct, (6*3600+1800) +sgt, 8*3600 +wadt, 8*3600 +jst, 9*3600 +kst, 9*3600 +east,-6*3600 +gst, 10*3600 +eadt,11*3600 +idle,12*3600 +nzst,12*3600 +nzt, 12*3600 +nzdt,13*3600 +afghanistan, 16200 +alaskan, -32400 +arab, 10800 +arabian, 14400 +arabic, 10800 +atlantic, -14400 +aus central, 34200 +aus eastern, 36000 +azores, -3600 +canada central, -21600 +cape verde, -3600 +caucasus, 14400 +cen. australia, 34200 +central america, -21600 +central asia, 21600 +central europe, 3600 +central european, 3600 +central pacific, 39600 +central, -21600 +china, 28800 +dateline, -43200 +e. africa, 10800 +e. australia, 36000 +e. europe, 7200 +e. south america, -10800 +eastern, -18000 +egypt, 7200 +ekaterinburg, 18000 +fiji, 43200 +fle, 7200 +greenland, -10800 +greenwich, 0 +gtb, 7200 +hawaiian, -36000 +india, 19800 +iran, 12600 +jerusalem, 7200 +korea, 32400 +mexico, -21600 +mid-atlantic, -7200 +mountain, -25200 +myanmar, 23400 +n. central asia, 21600 +nepal, 20700 +new zealand, 43200 +newfoundland, -12600 +north asia east, 28800 +north asia, 25200 +pacific sa, -14400 +pacific, -28800 +romance, 3600 +russian, 10800 +sa eastern, -10800 +sa pacific, -18000 +sa western, -14400 +samoa, -39600 +se asia, 25200 +malay peninsula, 28800 +south africa, 7200 +sri lanka, 21600 +taipei, 28800 +tasmania, 36000 +tokyo, 32400 +tonga, 46800 +us eastern, -18000 +us mountain, -25200 +vladivostok, 36000 +w. australia, 28800 +w. central africa, 3600 +w. europe, 3600 +west asia, 18000 +west pacific, 36000 +yakutsk, 32400 +acdt,37800 +acst,34200 +act,-18000 +acwst,31500 +aedt,39600 +aest,36000 +aft,16200 +almt,21600 +anast,43200 +anat,43200 +aoe,-43200 +aqtt,18000 +awdt,32400 +awst,28800 +azost,0 +azot,-3600 +azst,18000 +azt,14400 +bnt,28800 +bot,-14400 +btt,21600 +cast,28800 +chadt,49500 +chast,45900 +chost,32400 +chot,28800 +chst,36000 +chut,36000 +cidst,-14400 +cist,-18000 +ckt,-36000 +cot,-18000 +cvt,-3600 +cxt,25200 +davt,25200 +ddut,36000 +easst,-18000 +ect,-18000 +egst,0 +egt,-3600 +fet,10800 +fjst,46800 +fjt,43200 +fkst,-10800 +fkt,-14400 +fnt,-7200 +galt,-21600 +gamt,-32400 +get,14400 +gft,-10800 +gilt,43200 +gyt,-14400 +hkt,28800 +hovst,28800 +hovt,25200 +ict,25200 +idt,10800 +iot,21600 +irdt,16200 +irkst,32400 +irkt,28800 +irst,12600 +kgt,21600 +kost,39600 +krast,28800 +krat,25200 +kuyt,14400 +lhdt,39600 +lhst,37800 +lint,50400 +magst,43200 +magt,39600 +mart,-30600 +mawt,18000 +mht,43200 +mmt,23400 +mut,14400 +mvt,18000 +myt,28800 +nct,39600 +nfdt,43200 +nft,39600 +novst,25200 +novt,25200 +npt,20700 +nrt,43200 +nut,-39600 +omsst,25200 +omst,21600 +orat,18000 +pet,-18000 +petst,43200 +pett,43200 +pgt,36000 +phot,46800 +pht,28800 +pkt,18000 +pmdt,-7200 +pmst,-10800 +pont,39600 +pwt,32400 +pyst,-10800 +qyzt,21600 +ret,14400 +rott,-10800 +sakt,39600 +samt,14400 +sbt,39600 +sct,14400 +sret,39600 +srt,-10800 +syot,10800 +taht,-36000 +tft,18000 +tjt,18000 +tkt,46800 +tlt,32400 +tmt,18000 +tost,50400 +tot,46800 +trt,10800 +tvt,43200 +ulast,32400 +ulat,28800 +uyst,-7200 +uyt,-10800 +uzt,18000 +vet,-14400 +vlast,39600 +vlat,36000 +vost,21600 +vut,39600 +wakt,43200 +warst,-10800 +wft,43200 +wgst,-3600 +wgt,-7200 +wib,25200 +wit,32400 +wita,28800 +wt,0 +yakst,36000 +yakt,32400 +yapt,36000 +yekst,21600 +yekt,18000 +%% diff --git a/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/lib/date.rb b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/lib/date.rb new file mode 100644 index 00000000..aa630eb6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/lib/date.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true +# date.rb: Written by Tadayoshi Funaba 1998-2011 + +require 'date_core' + +class Date + VERSION = "3.4.1" # :nodoc: + + # call-seq: + # infinite? -> false + # + # Returns +false+ + def infinite? + false + end + + class Infinity < Numeric # :nodoc: + + def initialize(d=1) @d = d <=> 0 end + + def d() @d end + + protected :d + + def zero?() false end + def finite?() false end + def infinite?() d.nonzero? end + def nan?() d.zero? end + + def abs() self.class.new end + + def -@() self.class.new(-d) end + def +@() self.class.new(+d) end + + def <=>(other) + case other + when Infinity; return d <=> other.d + when Float::INFINITY; return d <=> 1 + when -Float::INFINITY; return d <=> -1 + when Numeric; return d + else + begin + l, r = other.coerce(self) + return l <=> r + rescue NoMethodError + end + end + nil + end + + def coerce(other) + case other + when Numeric; return -d, d + else + super + end + end + + def to_f + return 0 if @d == 0 + if @d > 0 + Float::INFINITY + else + -Float::INFINITY + end + end + + end + +end diff --git a/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/lib/date_core.so b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/lib/date_core.so new file mode 100755 index 00000000..ee653573 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/gems/date-3.4.1/lib/date_core.so differ diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/.rspec b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/.rspec new file mode 100644 index 00000000..53607ea5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/.rspec @@ -0,0 +1 @@ +--colour diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/CHANGELOG.md b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/CHANGELOG.md new file mode 100644 index 00000000..c2881230 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/CHANGELOG.md @@ -0,0 +1,518 @@ +# Changelog + +## 1.6.2 / 2025-05-12 + +- Handle upcoming changes to the `cgi` gem in Ruby 3.5 ([#147][pull-147]) + +- Fix issues found with `htmldiff` in Ruby 1.8 (which is used approximately + never, since the code change which broke Ruby 1.8 was made 6 years ago). + [#148][pull-148] + +- Fixed some standardrb formatting and configuration issues. + +## 1.6.1 / 2025-03-25 + +- Performed further work on `Diff::LCS::Ldiff` improvements ([#46][issue-46]) + and resolve several thread safety issues cleanly by making it a class. + [#129][pull-129] + +- Restructure the project to be more consistent with the rest of the projects + that I manage. + +- Increased GitHub action security. + +- Added [trusted publishing][tp] for fully automated releases. + +## 1.6.0 / 2025-02-13 + +- Baptiste Courtois (@annih) has done significant work on making `bin/ldiff` + work better, contributing a number of issues and pull requests. These include: + + - Separation of command parsing from diff-generation in `Diff::LCS::Ldiff` + code extraction making it easier to use separately from the `bin/ldiff` + command in [#103][pull-103]. This partially resolves [#46][issue-46]. + + - Improvement of binary and empty file detection and tests in [#104][pull-104] + and [#105][pull-105]. This resolves issues [#100][issue-100], + [#102][issue-102]. + + - Various ldiff fixes for output [#101][pull-101] resolves issues + [#106][issue-106] (ldiff ed scripts are inverted), [#107][issue-107] (ldiff + hunk ranges are incorrect; regression or incorrect fix for [#60][issue-60]), + and [#95][issue-95]. + +- Patrick Linnane fixed various minor typos. [#93][pull-93] + +- Mark Young added a Changelog link to the RubyGems metadata. [#92][pull-92] + This has been modified to incorporate it into the README. + +- Updated the documentation on `Diff::LCS#lcs` to be clearer about the + requirements for object equality comparison. This resolves [#70][issue-70]. + +- Governance: + + Changes described here are effective 2024-12-31. + + - Update gem management details to use markdown files for everything, enabled + in part by [flavorjones/hoe-markdown][hoe-markdown]. Several files were + renamed to be more consistent with standard practices. + + - Updated security notes with an [age][age] public key rather than pointing to + Keybase.io and a PGP public key which I no longer use. The use of the + [Tidelift security contact][tidelift] is recommended over direct disclosure. + +## 1.5.1 / 2024-01-31 + +- Peter Goldstein updated CI configuration to add Ruby 3.1 and Masato Nakamura + added Ruby 3.2 and 3.3. [#82][pull-82], [#89][pull-89] + +- Switched to [standard ruby][standard ruby] formatting. + +- Justin Steele converted the licence file to Markdown. [#84][pull-84] + +- Updated the gem SPDX identifier for GPL 2.0 or later, resolving [#86][pull-86] + by Vit Ondruch. + +- Resolve a potential security issue with `ldiff` in its use of `IO.read` + instead of `File.read`. [#91][issue-91] + +- Added MFA authentication requirement for release to RubyGems. [#90][pull-90] + +- Added Dependabot management for actions and gems. [#90][pull-90] + +- Updated CodeQL configuration. [#90][pull-90] + +## 1.5.0 / 2021-12-23 + +- Updated the CI configuration and monkey-patch Hoe. + +- Kenichi Kamiya fixed a test configuration deprecation in SimpleCov. + [#69][pull-69] + +- Tien introduced several corrections and code improvements: + + - Removed an off-by-one error when calculating an index value by embracing + Ruby iteration properly. This had a side-effect of fixing a long-standing + bug in `#traverse_sequences` where the traversal would not be transitive. + That is, `LCS(s2, s1)` should produce a sequence that is transitive with + `LCS(s1, s2)` on traversal, and applying the diff computed from those + results would result in equivalent changes that could be played forward or + backward as appropriate. [#71][pull-71], [#75][pull-75] + + - The above fix resulted in a changed order of the longest common subsequence + when callbacks were applied. After analysis, it was determined that the + computed subsequence was _equivalent_ to the prior version, so the test was + updated. This also resulted in the clarification of documentation when + traversing the sub-sequences. [#79][pull-79] + + - An infinite loop case in the case where `Diff::LCS` would be included into + an enumerable class has been fixed. [#73][pull-73] + + - Clarified the purpose of a threshold test in calculation of LCS. + [#72][pull-72], [#80][pull-80] + +- Removed autotest directory + +## 1.4.4 / 2020-07-01 + +- Fixed an issue reported by Jun Aruga in the `Diff::LCS::Ldiff` binary text + detection. [#44][issue-44] + +- Fixed a theoretical issue reported by Jun Aruga in `Diff::LCS::Hunk` to raise + a more useful exception. [#43][issue-43] + +- Added documentation that should address custom object issues as reported in + [#35][issue-35]. + +- Fixed more diff errors, in part reported in [#65][issue-65]. + + - The use of `Numeric#abs` is incorrect in `Diff::LCS::Block#diff_size`. The + diff size _must_ be accurate for correct change placement. + + - When selecting `@max_diff_size` in `Diff::LCS::Hunk`, choose it based on + `block.diff_size.abs`. + + - Made a number of changes that will, unfortunately, increase allocations at + the cost of being safe with frozen strings. + + - Add some knowledge that when `Diff::LCS::Hunk#diff` is called, that we are + processing the _last_ hunk, so some changes will be made to how the output + is generated. + + - `old`, `ed`, and `reverse_ed` formats have no differences. + + - `unified` format will report `\ No newline at end of file` given the + correct conditions, at most once. Unified range reporting also differs for + the last hunk such that the `length` of the range is reduced by one. + + - `context` format will report `\No newline at end of file` given the + correct conditions, up to once per "file". Context range reporting also + differs for the last hunk such that the `end` part of the range is reduced + by one to a minimum of one. + +- Added a bunch more tests for the cases above, and fixed `hunk_spec.rb` so that + the phrase being compared isn't nonsense French. + +- Updated formatting. + +- Added a Rake task to assist with manual testing on Ruby 1.8. + +## 1.4.3 / 2020-06-29 + +- Fixed several issues with 1.4 on Rubies older than 2.0. Some of this was + providing useful shim functions to Hoe 3.x (which dropped these older Rubies a + while ago). Specifically: + + - Removed Array#lazy from a method in `Diff::LCS::Hunk`. + + - Changed some unit tests to use old-style Symbol-keyed hashes. + + - Changed some unit test helper functions to no longer use keyword parameters, + but only a trailing options hash. + + - Made the use of `psych` dependent on `RUBY_VERSION >= 1.9`. + + Resolves [#63][issue-63]. + +## 1.4.2 / 2020-06-23 + +- Camille Drapier fixed a small issue with RuboCop configuration. [#59][pull-59] + +- Applied another fix (and unit test) to fix an issue for the Chef team. + [#60][issue-60], [#61][pull-61] + +## 1.4.1 / 2020-06-23 + +- Fix an issue where diff sizes could be negative, and they should be. + [#57][issue-57], [#58][pull-58] + +## 1.4 / 2020-06-23 + +- Ruby versions lower than 2.4 are soft-deprecated and will not be run as part + of the CI process any longer. + +- Akinora MUSHA (knu) added the ability for `Diff::LCS::Change` objects to be + implicitly treated arrays. Originally provided as pull request [#47][pull-47], + but it introduced a number of test failures as documented in [#48][issue-48], + and remediation of `Diff::LCS` itself was introduced in [#49][pull-49]. + +- Resolved [#5][issue-05] with some tests comparing output from `system` calls + to `bin/ldiff` with some pre-generated output. Resolved [#6][issue-06] with + these tests. + +- Resolved a previously undetected `bin/ldiff` issue with `--context` output not + matching `diff --context` output. + +- Resolved an issue with later versions of Ruby not working with an `OptParse` + specification of `Numeric`; this has been changed to `Integer`. + +- Brandon Fish added TruffleRuby in [#52][pull-52]. + +- Fixed two missing classes as reported in [#53][issue-53]. + +## 1.3 / 2017-01-18 + +- Bugs fixed: + + - Fixed an error for `bin/ldiff --version`. Fixes issue [#21][issue-21]. + + - Force `Diff::LCS::Change` and `Diff::LCS::ContextChange` to only perform + equality comparisons against themselves. Provided by Kevin Mook in pull + request [#29][pull-29]. + + - Fix tab expansion in `htmldiff`, provided by Mark Friedgan in pull request + [#25][pull-25]. + + - Silence Ruby 2.4 `Fixnum` deprecation warnings. Fixes issue [#38][issue-38] + and pull request [#36][pull-36]. + + - Ensure that test dependencies are loaded properly. Fixes issue + [#33][issue-33] and pull request [#34][pull-34]. + + - Fix issue [#1][issue-01] with incorrect intuition of patch direction. + Tentative fix, but the previous failure cases pass now. + +- Tooling changes: + + - Added SimpleCov and Coveralls support. + + - Change the homepage (temporarily) to the GitHub repo. + + - Updated testing and gem infrastructure. + + - Modernized the specs. + +- Cleaned up documentation. + +- Added a Code of Conduct. + +## 1.2.5 / 2013-11-08 + +- Bugs fixed: + + - Comparing arrays flattened them too far, especially with `Diff::LCS.sdiff`. + Fixed by Josh Bronson in pull request [#23][pull-23]. + +## 1.2.4 / 2013-04-20 + +- Bugs fixed: + + - A bug was introduced after 1.1.3 when pruning common sequences at the start + of comparison. Paul Kunysch (@pck) fixed this in pull request + [#18][pull-18]. Thanks! + + - The Rubinius (1.9 mode) bug in [rubinius/rubinius#2268][rubinius#2268] has + been fixed by the Rubinius team two days after it was filed. Thanks for + fixing this so quickly! + +- Switching to Raggi's hoe-gemspec2 for gemspec generation. + +## 1.2.3 / 2013-04-11 + +- Bugs Fixed: + + - The new encoding detection for diff output generation (added in 1.2.2) + introduced a bug if the left side of the comparison was the empty set. + Originally found in [rspec/rspec-expectations#238][rspec-expectations#238] + and [rspec/rspec-expectations#239][rspec-expectations#239]. Jon Rowe + developed a reasonable heuristic (left side, right side, empty string + literal) to avoid this bug. + + - There is a known issue with Rubinius in 1.9 mode reported in + [rubinius/rubinius#2268][rubinius#2268] and demonstrated in the Travis CI + builds. For all other tested platforms, diff-lcs is considered stable. As + soon as a suitably small test-case can be created for the Rubinius team to + examine, this will be added to the Rubinius issue around this. + +## 1.2.2 / 2013-03-30 + +- Bugs Fixed: + + - `Diff::LCS::Hunk` could not properly generate a difference for comparison + sets that are not US-ASCII-compatible because of the use of literal regular + expressions and strings. Jon Rowe found this in + [rspec/rspec-expectations#219][rspec-expectations#219] and provided a first + pass implementation in pull request [#15][pull-15]. I've reworked it because + of test failures in Rubinius when running in Ruby 1.9 mode. This coerces the + added values to the encoding of the old dataset (as determined by the first + piece of the old dataset). + + - Adding Travis CI testing for Ruby 2.0. + +## 1.2.1 / 2013-02-09 + +- Bugs Fixed: + + - As seen in [rspec/rspec-expectations#200][rspec-expectations#200], the + release of `Diff::LCS` 1.2 introduced an unnecessary public API change to + `Diff::LCS::Hunk` (see the change at + [rspec/rspec-expectations@3d6fc82c][rspec-expectations@3d6fc82c] for + details). The new method name (and behaviour) is more correct, but I should + not have renamed the function or should have at least provided an alias. + This release restores `Diff::LCS::Hunk#unshift` as an alias to #merge. Note + that the old `#unshift` behaviour was incorrect and will not be restored. + +## 1.2.0 / 2013-01-21 + +- Minor Enhancements: + + - Added special case handling for `Diff::LCS.patch` so that it handles patches + that are empty or contain no changes. + + - Added two new methods (`#patch_me` and `#unpatch_me`) to the include-able + module. + +- Bugs Fixed: + + - Fixed issue [#1][issue-01] patch direction detection. + + - Resolved issue [#2][issue-02] by handling `string[string.size, 1]` properly + (it returns `""` not `nil`). + + - Michael Granger (ged) fixed an implementation error in `Diff::LCS::Change` + and added specs in pull request [#8][pull-08]. Thanks! + + - Made the code auto-testable. + + - Vít Ondruch (voxik) provided the latest version of the GPL2 license file in + pull request [#10][pull-10]. Thanks! + + - Fixed a documentation issue with the include-able versions of `#patch!` and + `#unpatch!` where they implied that they would replace the original value. + Given that `Diff::LCS.patch` always returns a copy, the documentation was + incorrect and has been corrected. To provide the behaviour that was + originally documented, two new methods were added to provide this behaviour. + Found by scooter-dangle in issue [#12][issue-12]. Thanks! + +- Code Style Changes: + + - Removed trailing spaces. + + - Calling class methods using `.` instead of `::`. + + - Vít Ondruch (voxik) removed unnecessary shebangs in pull request + [#9][pull-09]. Thanks! + + - Kenichi Kamiya (kachick) removed some warnings of an unused variable in + lucky pull request [#13][pull-13]. Thanks! + + - Embarked on a major refactoring to make the files a little more manageable + and understand the code on a deeper level. + + - Adding CI via Travis CI. + +## 1.1.3 / 2011-08-27 + +- Converted to 'hoe' for release. + +- Converted tests to RSpec 2. + +- Extracted the body of `htmldiff` into a class available from + `diff/lcs/htmldiff`. + +- Migrated development and issue tracking to GitHub. + +- Bugs fixed: + + - Eliminated the explicit use of RubyGems in both `bin/htmldiff` and + `bin/ldiff`. Resolves issue [#4][issue-04]. + + - Eliminated Ruby warnings. Resolves issue [#3][issue-03]. + +## 1.1.2 / 2004-10-20 + +- Fixed a problem reported by Mauricio Fernandez in `htmldiff`. + +## 1.1.1 / 2004-09-25 + +- Fixed bug #891 (Set returned from patch command does not contain last equal + part). + +- Fixed a problem with callback initialisation code (it assumed that all + callbacks passed as classes can be initialised; now, it rescues NoMethodError + in the event of private :new being called). + +- Modified the non-initialisable callbacks to have a private `#new` method. + +- Moved `ldiff` core code to `Diff::LCS::Ldiff` (`diff/lcs/ldiff.rb`). + +## 1.1.0 + +- Eliminated the need for `Diff::LCS::Event` and removed it. + +- Added a contextual diff callback, `Diff::LCS::ContextDiffCallback`. + +- Implemented (un-)patching for standard diff callback output formats with both + `#diff` and `#sdiff`. + +- Extensive documentation changes. + +## 1.0.4 + +- Fixed a problem with `bin/ldiff` output, especially for unified format. + Newlines that should have been present weren't. + +- Changed the `.tar.gz` installer to generate Windows batch files if ones do not + exist already. Removed the existing batch files as they didn't work. + +## 1.0.3 + +- Fixed a problem with `#traverse_sequences` where the first difference from the + left sequence might not be appropriately captured. + +## 1.0.2 + +- Fixed an issue with `ldiff` not working because actions were changed from + symbols to strings. + +## 1.0.1 + +- Minor modifications to the `gemspec`, the `README`. + +- Renamed the diff program to `ldiff` (as well as the companion batch file) so + as to not collide with the standard diff program. + +- Fixed issues with RubyGems. Requires RubyGems > 0.6.1 or >= 0.6.1 with the + latest CVS version. + +## 1.0 + +- Initial release based mostly on Perl's Algorithm::Diff. + +[age]: https://github.com/FiloSottile/age +[hoe-halostatue]: https://github.com/halostatue/hoe-halostatue +[hoe-markdown]: https://github.com/flavorjones/hoe-markdown +[issue-01]: https://github.com/halostatue/diff-lcs/issues/1 +[issue-02]: https://github.com/halostatue/diff-lcs/issues/2 +[issue-03]: https://github.com/halostatue/diff-lcs/issues/3 +[issue-04]: https://github.com/halostatue/diff-lcs/issues/4 +[issue-05]: https://github.com/halostatue/diff-lcs/issues/5 +[issue-06]: https://github.com/halostatue/diff-lcs/issues/6 +[issue-12]: https://github.com/halostatue/diff-lcs/issues/12 +[issue-21]: https://github.com/halostatue/diff-lcs/issues/21 +[issue-33]: https://github.com/halostatue/diff-lcs/issues/33 +[issue-35]: https://github.com/halostatue/diff-lcs/issues/35 +[issue-38]: https://github.com/halostatue/diff-lcs/issues/38 +[issue-43]: https://github.com/halostatue/diff-lcs/issues/43 +[issue-44]: https://github.com/halostatue/diff-lcs/issues/44 +[issue-46]: https://github.com/halostatue/diff-lcs/issues/46 +[issue-48]: https://github.com/halostatue/diff-lcs/issues/48 +[issue-53]: https://github.com/halostatue/diff-lcs/issues/53 +[issue-57]: https://github.com/halostatue/diff-lcs/issues/57 +[issue-60]: https://github.com/halostatue/diff-lcs/issues/60 +[issue-63]: https://github.com/halostatue/diff-lcs/issues/63 +[issue-65]: https://github.com/halostatue/diff-lcs/issues/65 +[issue-70]: https://github.com/halostatue/diff-lcs/issues/70 +[issue-91]: https://github.com/halostatue/diff-lcs/issues/91 +[issue-95]: https://github.com/halostatue/diff-lcs/issues/95 +[issue-100]: https://github.com/halostatue/diff-lcs/issues/100 +[issue-102]: https://github.com/halostatue/diff-lcs/issues/102 +[issue-106]: https://github.com/halostatue/diff-lcs/issues/106 +[issue-107]: https://github.com/halostatue/diff-lcs/issues/107 +[pull-08]: https://github.com/halostatue/diff-lcs/pull/8 +[pull-09]: https://github.com/halostatue/diff-lcs/pull/9 +[pull-10]: https://github.com/halostatue/diff-lcs/pull/10 +[pull-13]: https://github.com/halostatue/diff-lcs/pull/13 +[pull-15]: https://github.com/halostatue/diff-lcs/pull/15 +[pull-18]: https://github.com/halostatue/diff-lcs/pull/18 +[pull-23]: https://github.com/halostatue/diff-lcs/pull/23 +[pull-25]: https://github.com/halostatue/diff-lcs/pull/25 +[pull-29]: https://github.com/halostatue/diff-lcs/pull/29 +[pull-34]: https://github.com/halostatue/diff-lcs/pull/34 +[pull-36]: https://github.com/halostatue/diff-lcs/pull/36 +[pull-47]: https://github.com/halostatue/diff-lcs/pull/47 +[pull-49]: https://github.com/halostatue/diff-lcs/pull/49 +[pull-52]: https://github.com/halostatue/diff-lcs/pull/52 +[pull-58]: https://github.com/halostatue/diff-lcs/pull/58 +[pull-59]: https://github.com/halostatue/diff-lcs/pull/59 +[pull-61]: https://github.com/halostatue/diff-lcs/pull/61 +[pull-69]: https://github.com/halostatue/diff-lcs/pull/69 +[pull-71]: https://github.com/halostatue/diff-lcs/pull/71 +[pull-72]: https://github.com/halostatue/diff-lcs/pull/72 +[pull-73]: https://github.com/halostatue/diff-lcs/pull/73 +[pull-75]: https://github.com/halostatue/diff-lcs/pull/75 +[pull-79]: https://github.com/halostatue/diff-lcs/pull/79 +[pull-80]: https://github.com/halostatue/diff-lcs/pull/80 +[pull-82]: https://github.com/halostatue/diff-lcs/pull/82 +[pull-84]: https://github.com/halostatue/diff-lcs/pull/84 +[pull-86]: https://github.com/halostatue/diff-lcs/pull/86 +[pull-89]: https://github.com/halostatue/diff-lcs/pull/89 +[pull-90]: https://github.com/halostatue/diff-lcs/pull/90 +[pull-92]: https://github.com/halostatue/diff-lcs/pull/92 +[pull-93]: https://github.com/halostatue/diff-lcs/pull/93 +[pull-101]: https://github.com/halostatue/diff-lcs/pull/101 +[pull-103]: https://github.com/halostatue/diff-lcs/pull/103 +[pull-104]: https://github.com/halostatue/diff-lcs/pull/104 +[pull-105]: https://github.com/halostatue/diff-lcs/pull/105 +[pull-129]: https://github.com/halostatue/diff-lcs/pull/129 +[pull-147]: https://github.com/halostatue/diff-lcs/pull/147 +[pull-148]: https://github.com/halostatue/diff-lcs/pull/148 +[rspec-expectations#200]: https://github.com/rspec/rspec-expectations/pull/200 +[rspec-expectations#219]: https://github.com/rspec/rspec-expectations/issues/219 +[rspec-expectations#238]: https://github.com/rspec/rspec-expectations/issues/238 +[rspec-expectations#239]: https://github.com/rspec/rspec-expectations/issues/239 +[rspec-expectations@3d6fc82c]: https://github.com/rspec/rspec-expectations/commit/3d6fc82c +[rubinius#2268]: https://github.com/rubinius/rubinius/issues/2268 +[standard ruby]: https://github.com/standardrb/standard +[tidelift]: https://tidelift.com/security +[tp]: https://guides.rubygems.org/trusted-publishing/ diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/CODE_OF_CONDUCT.md b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..184b5fb3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances of + any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, + without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at [INSERT CONTACT +METHOD]. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +. Translations are available at +. + +[homepage]: https://www.contributor-covenant.org +[Mozilla CoC]: https://github.com/mozilla/diversity diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/CONTRIBUTING.md b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/CONTRIBUTING.md new file mode 100644 index 00000000..4bcde4bd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/CONTRIBUTING.md @@ -0,0 +1,71 @@ +# Contributing + +Contribution to diff-lcs is encouraged in any form: a bug report, a feature +request, or code contributions. There are a few DOs and DON'Ts for +contributions. + +- DO: + + - Keep the coding style that already exists for any updated Ruby code (support + or otherwise). I use [Standard Ruby][standardrb] for linting and formatting. + + - Use thoughtfully-named topic branches for contributions. Rebase your commits + into logical chunks as necessary. + + - Use [quality commit messages][qcm]. + + - Add your name or GitHub handle to `CONTRIBUTORS.md` and a record in the + `CHANGELOG.md` as a separate commit from your main change. (Follow the style + in the `CHANGELOG.md` and provide a link to your PR.) + + - Add or update tests as appropriate for your change. The test suite is + written in [RSpec][rspec]. + + - Add or update documentation as appropriate for your change. The + documentation is RDoc; diff-lcs does not use extensions that may be present + in alternative documentation generators. + +- DO NOT: + + - Modify `VERSION` in `lib/diff/lcs/version.rb`. When your patch is accepted + and a release is made, the version will be updated at that point. + + - Modify `diff-lcs.gemspec`; it is a generated file. (You _may_ use + `rake gemspec` to regenerate it if your change involves metadata related to + gem itself). + + - Modify the `Gemfile`. + +## Test Dependencies + +diff-lcs uses Ryan Davis's [Hoe][Hoe] to manage the release process, and it adds +a number of rake tasks. You will mostly be interested in `rake`, which runs +tests in the same way that `rake spec` does. + +To assist with the installation of the development dependencies for diff-lcs, I +have provided a Gemfile pointing to the (generated) `diff-lcs.gemspec` file. +`minitar.gemspec` file. This will permit you to use `bundle install` to install +the dependencies. + +You can run tests with code coverage analysis by running `rake spec:coverage`. + +## Workflow + +Here's the most direct way to get your work merged into the project: + +- Fork the project. +- Clone your fork (`git clone git://github.com//diff-lcs.git`). +- Create a topic branch to contain your change + (`git checkout -b my_awesome_feature`). +- Hack away, add tests. Not necessarily in that order. +- Make sure everything still passes by running `rake`. +- If necessary, rebase your commits into logical chunks, without errors. +- Push the branch up (`git push origin my_awesome_feature`). +- Create a pull request against halostatue/diff-lcs and describe what your + change does and the why you think it should be merged. + +[hoe]: https://github.com/seattlerb/hoe +[qcm]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html +[release-gem]: https://github.com/rubygems/release-gem +[rspec]: http://rspec.info/documentation/ +[standardrb]: https://github.com/standardrb/standard diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/CONTRIBUTORS.md b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/CONTRIBUTORS.md new file mode 100644 index 00000000..9053019c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/CONTRIBUTORS.md @@ -0,0 +1,49 @@ +# Contributors + +- Austin Ziegler (@halostatue) created diff-lcs. + +Thanks to everyone else who has contributed to diff-lcs over the years: + +- @ginriki +- @joshbronson +- @kevinmook +- @mckaz +- Akinori Musha +- Artem Ignatyev +- Brandon Fish +- Baptiste Courtois (@annih) +- Camille Drapier +- Cédric Boutillier +- @earlopain +- Gregg Kellogg +- Jagdeep Singh +- Jason Gladish +- Jon Rowe +- Josef Strzibny +- Josep (@apuratepp) +- Josh Bronson +- Jun Aruga +- Justin Steele +- Kenichi Kamiya +- Kensuke Nagae +- Kevin Ansfield +- Koichi Ito +- Mark Friedgan +- Masato Nakamura +- Mark Young +- Michael Granger +- Myron Marston +- Nicolas Leger +- Oleg Orlov +- Patrick Linnane +- Paul Kunysch +- Pete Higgins +- Peter Goldstein +- Peter Wagenet +- Philippe Lafoucrière +- Ryan Lovelett +- Scott Steele +- Simon Courtois +- Tien (@tiendo1011) +- Tomas Jura +- Vít Ondruch diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/LICENCE.md b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/LICENCE.md new file mode 100644 index 00000000..c57c3f16 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/LICENCE.md @@ -0,0 +1,40 @@ +# Licence + +This software is available under three licenses: the GNU GPL version 2 (or at +your option, a later version), the Perl Artistic license, or the MIT license. +Note that my preference for licensing is the MIT license, but Algorithm::Diff +was dually originally licensed with the Perl Artistic and the GNU GPL ("the same +terms as Perl itself") and given that the Ruby implementation originally hewed +pretty closely to the Perl version, I must maintain the additional licensing +terms. + +- Copyright 2004–2025 Austin Ziegler and contributors. +- Adapted from Algorithm::Diff (Perl) by Ned Konz and a Smalltalk version by + Mario I. Wolczko. + +## MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## Perl Artistic License + +See the file docs/artistic.txt in the main distribution. + +## GNU GPL version 2 + +See the file docs/COPYING.txt in the main distribution. diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/Manifest.txt b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/Manifest.txt new file mode 100644 index 00000000..fe58c86c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/Manifest.txt @@ -0,0 +1,115 @@ +.rspec +CHANGELOG.md +CODE_OF_CONDUCT.md +CONTRIBUTING.md +CONTRIBUTORS.md +LICENCE.md +Manifest.txt +README.md +Rakefile +SECURITY.md +bin/htmldiff +bin/ldiff +docs/COPYING.txt +docs/artistic.txt +lib/diff-lcs.rb +lib/diff/lcs.rb +lib/diff/lcs/array.rb +lib/diff/lcs/backports.rb +lib/diff/lcs/block.rb +lib/diff/lcs/callbacks.rb +lib/diff/lcs/change.rb +lib/diff/lcs/htmldiff.rb +lib/diff/lcs/hunk.rb +lib/diff/lcs/internals.rb +lib/diff/lcs/ldiff.rb +lib/diff/lcs/string.rb +lib/diff/lcs/version.rb +mise.toml +spec/change_spec.rb +spec/diff_spec.rb +spec/fixtures/123_x +spec/fixtures/456_x +spec/fixtures/aX +spec/fixtures/bXaX +spec/fixtures/ds1.csv +spec/fixtures/ds2.csv +spec/fixtures/empty +spec/fixtures/file1.bin +spec/fixtures/file2.bin +spec/fixtures/four_lines +spec/fixtures/four_lines_with_missing_new_line +spec/fixtures/ldiff/diff.missing_new_line1-e +spec/fixtures/ldiff/diff.missing_new_line1-f +spec/fixtures/ldiff/diff.missing_new_line2-e +spec/fixtures/ldiff/diff.missing_new_line2-f +spec/fixtures/ldiff/error.diff.chef-e +spec/fixtures/ldiff/error.diff.chef-f +spec/fixtures/ldiff/error.diff.missing_new_line1-e +spec/fixtures/ldiff/error.diff.missing_new_line1-f +spec/fixtures/ldiff/error.diff.missing_new_line2-e +spec/fixtures/ldiff/error.diff.missing_new_line2-f +spec/fixtures/ldiff/output.diff +spec/fixtures/ldiff/output.diff-c +spec/fixtures/ldiff/output.diff-e +spec/fixtures/ldiff/output.diff-f +spec/fixtures/ldiff/output.diff-u +spec/fixtures/ldiff/output.diff.bin1 +spec/fixtures/ldiff/output.diff.bin1-c +spec/fixtures/ldiff/output.diff.bin1-e +spec/fixtures/ldiff/output.diff.bin1-f +spec/fixtures/ldiff/output.diff.bin1-u +spec/fixtures/ldiff/output.diff.bin2 +spec/fixtures/ldiff/output.diff.bin2-c +spec/fixtures/ldiff/output.diff.bin2-e +spec/fixtures/ldiff/output.diff.bin2-f +spec/fixtures/ldiff/output.diff.bin2-u +spec/fixtures/ldiff/output.diff.chef +spec/fixtures/ldiff/output.diff.chef-c +spec/fixtures/ldiff/output.diff.chef-e +spec/fixtures/ldiff/output.diff.chef-f +spec/fixtures/ldiff/output.diff.chef-u +spec/fixtures/ldiff/output.diff.chef2 +spec/fixtures/ldiff/output.diff.chef2-c +spec/fixtures/ldiff/output.diff.chef2-d +spec/fixtures/ldiff/output.diff.chef2-e +spec/fixtures/ldiff/output.diff.chef2-f +spec/fixtures/ldiff/output.diff.chef2-u +spec/fixtures/ldiff/output.diff.empty.vs.four_lines +spec/fixtures/ldiff/output.diff.empty.vs.four_lines-c +spec/fixtures/ldiff/output.diff.empty.vs.four_lines-e +spec/fixtures/ldiff/output.diff.empty.vs.four_lines-f +spec/fixtures/ldiff/output.diff.empty.vs.four_lines-u +spec/fixtures/ldiff/output.diff.four_lines.vs.empty +spec/fixtures/ldiff/output.diff.four_lines.vs.empty-c +spec/fixtures/ldiff/output.diff.four_lines.vs.empty-e +spec/fixtures/ldiff/output.diff.four_lines.vs.empty-f +spec/fixtures/ldiff/output.diff.four_lines.vs.empty-u +spec/fixtures/ldiff/output.diff.issue95_trailing_context +spec/fixtures/ldiff/output.diff.issue95_trailing_context-c +spec/fixtures/ldiff/output.diff.issue95_trailing_context-e +spec/fixtures/ldiff/output.diff.issue95_trailing_context-f +spec/fixtures/ldiff/output.diff.issue95_trailing_context-u +spec/fixtures/ldiff/output.diff.missing_new_line1 +spec/fixtures/ldiff/output.diff.missing_new_line1-c +spec/fixtures/ldiff/output.diff.missing_new_line1-e +spec/fixtures/ldiff/output.diff.missing_new_line1-f +spec/fixtures/ldiff/output.diff.missing_new_line1-u +spec/fixtures/ldiff/output.diff.missing_new_line2 +spec/fixtures/ldiff/output.diff.missing_new_line2-c +spec/fixtures/ldiff/output.diff.missing_new_line2-e +spec/fixtures/ldiff/output.diff.missing_new_line2-f +spec/fixtures/ldiff/output.diff.missing_new_line2-u +spec/fixtures/new-chef +spec/fixtures/new-chef2 +spec/fixtures/old-chef +spec/fixtures/old-chef2 +spec/hunk_spec.rb +spec/issues_spec.rb +spec/lcs_spec.rb +spec/ldiff_spec.rb +spec/patch_spec.rb +spec/sdiff_spec.rb +spec/spec_helper.rb +spec/traverse_balanced_spec.rb +spec/traverse_sequences_spec.rb diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/README.md b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/README.md new file mode 100644 index 00000000..65838036 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/README.md @@ -0,0 +1,92 @@ +# Diff::LCS + +- home :: https://github.com/halostatue/diff-lcs +- changelog :: https://github.com/halostatue/diff-lcs/blob/main/CHANGELOG.md +- code :: https://github.com/halostatue/diff-lcs +- bugs :: https://github.com/halostatue/diff-lcs/issues +- rdoc :: http://rubydoc.info/github/halostatue/diff-lcs + + + + + +## Description + +Diff::LCS computes the difference between two Enumerable sequences using the +McIlroy-Hunt longest common subsequence (LCS) algorithm. It includes utilities +to create a simple HTML diff output format and a standard diff-like tool. + +This is release 1.6.1, providing a simple extension that allows for +Diff::LCS::Change objects to be treated implicitly as arrays and fixes a number +of formatting issues. + +Ruby versions below 2.5 are soft-deprecated, which means that older versions are +no longer part of the CI test suite. If any changes have been introduced that +break those versions, bug reports and patches will be accepted, but it will be +up to the reporter to verify any fixes prior to release. The next major release +will completely break compatibility. + +## Synopsis + +Using this module is quite simple. By default, Diff::LCS does not extend objects +with the Diff::LCS interface, but will be called as if it were a function: + +```ruby +require 'diff/lcs' + +seq1 = %w(a b c e h j l m n p) +seq2 = %w(b c d e f j k l m r s t) + +lcs = Diff::LCS.LCS(seq1, seq2) +diffs = Diff::LCS.diff(seq1, seq2) +sdiff = Diff::LCS.sdiff(seq1, seq2) +seq = Diff::LCS.traverse_sequences(seq1, seq2, callback_obj) +bal = Diff::LCS.traverse_balanced(seq1, seq2, callback_obj) +seq2 == Diff::LCS.patch!(seq1, diffs) +seq1 == Diff::LCS.unpatch!(seq2, diffs) +seq2 == Diff::LCS.patch!(seq1, sdiff) +seq1 == Diff::LCS.unpatch!(seq2, sdiff) +``` + +Objects can be extended with Diff::LCS: + +```ruby +seq1.extend(Diff::LCS) +lcs = seq1.lcs(seq2) +diffs = seq1.diff(seq2) +sdiff = seq1.sdiff(seq2) +seq = seq1.traverse_sequences(seq2, callback_obj) +bal = seq1.traverse_balanced(seq2, callback_obj) +seq2 == seq1.patch!(diffs) +seq1 == seq2.unpatch!(diffs) +seq2 == seq1.patch!(sdiff) +seq1 == seq2.unpatch!(sdiff) +``` + +By requiring 'diff/lcs/array' or 'diff/lcs/string', Array or String will be +extended for use this way. + +Note that Diff::LCS requires a sequenced enumerable container, which means that +the order of enumeration is both predictable and consistent for the same set of +data. While it is theoretically possible to generate a diff for an unordered +hash, it will only be meaningful if the enumeration of the hashes is consistent. +In general, this will mean that containers that behave like String or Array will +perform best. + +## History + +Diff::LCS is a port of Perl's Algorithm::Diff that uses the McIlroy-Hunt longest +common subsequence (LCS) algorithm to compute intelligent differences between +two sequenced enumerable containers. The implementation is based on Mario I. +Wolczko's [Smalltalk version 1.2][smalltalk] (1993) and Ned Konz's Perl version +[Algorithm::Diff 1.15][perl]. `Diff::LCS#sdiff` and +`Diff::LCS#traverse_balanced` were originally written for the Perl version by +Mike Schilli. + +The algorithm is described in A Fast Algorithm for Computing Longest Common +Subsequences, CACM, vol.20, no.5, pp.350-353, May 1977, with a few minor +improvements to improve the speed. A simplified description of the algorithm, +originally written for the Perl version, was written by Mark-Jason Dominus. + +[smalltalk]: ftp://st.cs.uiuc.edu/pub/Smalltalk/MANCHESTER/manchester/4.0/diff.st +[perl]: http://search.cpan.org/~nedkonz/Algorithm-Diff-1.15/ diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/Rakefile b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/Rakefile new file mode 100644 index 00000000..0bfe927b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/Rakefile @@ -0,0 +1,115 @@ +require "rubygems" +require "rspec" +require "rspec/core/rake_task" +require "hoe" +require "rake/clean" + +MAINTENANCE = ENV["MAINTENANCE"] == "true" +BUILD_DOCS = MAINTENANCE || ENV["DOCS"] == "true" +TRUSTED_RELEASE = ENV["rubygems_release_gem"] == "true" + +Hoe.plugin :halostatue +Hoe.plugin :rubygems + +Hoe.plugins.delete :debug +Hoe.plugins.delete :newb +Hoe.plugins.delete :signing +Hoe.plugins.delete :publish unless BUILD_DOCS + +if RUBY_VERSION < "1.9" + class Array # :nodoc: + def to_h + Hash[*flatten(1)] + end + end + + class Gem::Specification # :nodoc: + def metadata=(*) + end + + def default_value(*) + end + end + + class Object # :nodoc: + def caller_locations(*) + [] + end + end +end + +_spec = Hoe.spec "diff-lcs" do + developer("Austin Ziegler", "halostatue@gmail.com") + + self.trusted_release = TRUSTED_RELEASE + + require_ruby_version ">= 1.8" + + self.history_file = "CHANGELOG.md" + self.readme_file = "README.md" + self.licenses = ["MIT", "Artistic-1.0-Perl", "GPL-2.0-or-later"] + + spec_extras[:metadata] = ->(val) { + val["rubygems_mfa_required"] = "true" + } + + extra_dev_deps << ["hoe", "~> 4.0"] + extra_dev_deps << ["hoe-halostatue", "~> 2.0"] + extra_dev_deps << ["hoe-rubygems", "~> 1.0"] + extra_dev_deps << ["rspec", ">= 2.0", "< 4"] + extra_dev_deps << ["rake", ">= 10.0", "< 14"] + extra_dev_deps << ["rdoc", ">= 6.3.1", "< 7"] +end + +if BUILD_DOCS + rake_tasks = Rake.application.instance_variable_get(:@tasks) + tasks = ["publish_docs", "publish_on_announce", "debug_email", "post_blog", "announce"] + tasks.each do |task| + rake_tasks.delete(task) + end +end + +desc "Run all specifications" +RSpec::Core::RakeTask.new(:spec) do |t| + rspec_dirs = %w[spec lib].join(":") + t.rspec_opts = ["-I#{rspec_dirs}"] +end + +task :version do + require "diff/lcs/version" + puts Diff::LCS::VERSION +end + +Rake::Task["spec"].actions.uniq! { |a| a.source_location } + +# standard:disable Style/HashSyntax +task :default => :spec unless Rake::Task["default"].prereqs.include?("spec") +task :test => :spec unless Rake::Task["test"].prereqs.include?("spec") +# standard:enable Style/HashSyntax + +if RUBY_VERSION >= "3.0" && RUBY_ENGINE == "ruby" + namespace :spec do + desc "Runs test coverage. Only works Ruby 2.0+ and assumes 'simplecov' is installed." + task :coverage do + ENV["COVERAGE"] = "true" + Rake::Task["spec"].execute + end + end +end + +if MAINTENANCE + task ruby18: :package do + require "diff/lcs/version" + # standard:disable Layout/HeredocIndentation + puts <<-MESSAGE +You are starting a barebones Ruby 1.8 docker environment for testing. +A snapshot package has been built, so install it with: + + cd diff-lcs + gem install pkg/diff-lcs-#{Diff::LCS::VERSION} + + MESSAGE + # standard:enable Layout/HeredocIndentation + sh "docker run -it --rm -v #{Dir.pwd}:/root/diff-lcs bellbind/docker-ruby18-rails2 bash -l" + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/SECURITY.md b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/SECURITY.md new file mode 100644 index 00000000..16854f66 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/SECURITY.md @@ -0,0 +1,41 @@ +# diff-lcs Security + +## Supported Versions + +Security reports are accepted for the most recent major release and the previous +version for a limited time after the initial major release version. After a +major release, the previous version will receive full support for six months and +security support for an additional six months (for a total of twelve months). + +Because diff-lcs 1.x supports a wide range of Ruby versions, security reports +will only be accepted when they can be demonstrated on Ruby 3.1 or higher. + +> [!information] +> +> There will be a diff-lcs 2.0 released in 2025 which narrows support to modern +> versions of Ruby only. +> +> | Release Date | Support Ends | Security Support Ends | +> | ------------ | ------------ | --------------------- | +> | 2025 | +6 months | +12 months | +> +> If the 2.0.0 release happens on 2025-07-01, regular support for diff-lcs 1.x +> will end on 2026-12-31 and security support for diff-lcs 1.x will end on +> 2026-06-30. + +## Reporting a Vulnerability + +By preference, use the [Tidelift security contact][tidelift]. Tidelift will +coordinate the fix and disclosure. + +Alternatively, Send an email to [diff-lcs@halostatue.ca][email] with the text +`Diff::LCS` in the subject. Emails sent to this address should be encrypted +using [age][age] with the following public key: + +``` +age1fc6ngxmn02m62fej5cl30lrvwmxn4k3q2atqu53aatekmnqfwumqj4g93w +``` + +[tidelift]: https://tidelift.com/security +[email]: mailto:diff-lcs@halostatue.ca +[age]: https://github.com/FiloSottile/age diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/bin/htmldiff b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/bin/htmldiff new file mode 100755 index 00000000..bcd89d21 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/bin/htmldiff @@ -0,0 +1,35 @@ +#! /usr/bin/env ruby -w +# frozen_string_literal: true + +require "diff/lcs" +require "diff/lcs/htmldiff" + +begin + require "text/format" +rescue LoadError + Diff::LCS::HTMLDiff.can_expand_tabs = false +end + +if ARGV.size < 2 or ARGV.size > 3 + warn "usage: #{File.basename($0)} old new [output.html]" + warn " #{File.basename($0)} old new > output.html" + exit 127 +end + +left = IO.read(ARGV[0]).split($/) +right = IO.read(ARGV[1]).split($/) + +options = { :title => "diff #{ARGV[0]} #{ARGV[1]}" } + +htmldiff = Diff::LCS::HTMLDiff.new(left, right, options) + +if ARGV[2] + File.open(ARGV[2], "w") do |f| + htmldiff.options[:output] = f + htmldiff.run + end +else + htmldiff.run +end + +# vim: ft=ruby diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/bin/ldiff b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/bin/ldiff new file mode 100755 index 00000000..f4734f58 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/bin/ldiff @@ -0,0 +1,9 @@ +#! /usr/bin/env ruby -w +# frozen_string_literal: true + +require 'diff/lcs' +require 'diff/lcs/ldiff' + +exit Diff::LCS::Ldiff.run(ARGV) + +# vim: ft=ruby diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/docs/COPYING.txt b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/docs/COPYING.txt new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/docs/COPYING.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/docs/artistic.txt b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/docs/artistic.txt new file mode 100644 index 00000000..763e17a9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/docs/artistic.txt @@ -0,0 +1,127 @@ +The "Artistic License" + + Preamble + +The intent of this document is to state the conditions under which a +Package may be copied, such that the Copyright Holder maintains some +semblance of artistic control over the development of the package, +while giving the users of the package the right to use and distribute +the Package in a more-or-less customary fashion, plus the right to make +reasonable modifications. + +Definitions: + + "Package" refers to the collection of files distributed by the + Copyright Holder, and derivatives of that collection of files + created through textual modification. + + "Standard Version" refers to such a Package if it has not been + modified, or has been modified in accordance with the wishes + of the Copyright Holder as specified below. + + "Copyright Holder" is whoever is named in the copyright or + copyrights for the package. + + "You" is you, if you're thinking about copying or distributing + this Package. + + "Reasonable copying fee" is whatever you can justify on the + basis of media cost, duplication charges, time of people involved, + and so on. (You will not be required to justify it to the + Copyright Holder, but only to the computing community at large + as a market that must bear the fee.) + + "Freely Available" means that no fee is charged for the item + itself, though there may be fees involved in handling the item. + It also means that recipients of the item may redistribute it + under the same conditions they received it. + +1. You may make and give away verbatim copies of the source form of the +Standard Version of this Package without restriction, provided that you +duplicate all of the original copyright notices and associated disclaimers. + +2. You may apply bug fixes, portability fixes and other modifications +derived from the Public Domain or from the Copyright Holder. A Package +modified in such a way shall still be considered the Standard Version. + +3. You may otherwise modify your copy of this Package in any way, provided +that you insert a prominent notice in each changed file stating how and +when you changed that file, and provided that you do at least ONE of the +following: + + a) place your modifications in the Public Domain or otherwise make them + Freely Available, such as by posting said modifications to Usenet or + an equivalent medium, or placing the modifications on a major archive + site such as uunet.uu.net, or by allowing the Copyright Holder to include + your modifications in the Standard Version of the Package. + + b) use the modified Package only within your corporation or organization. + + c) rename any non-standard executables so the names do not conflict + with standard executables, which must also be provided, and provide + a separate manual page for each non-standard executable that clearly + documents how it differs from the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +4. You may distribute the programs of this Package in object code or +executable form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and library files, + together with instructions (in the manual page or equivalent) on where + to get the Standard Version. + + b) accompany the distribution with the machine-readable source of + the Package with your modifications. + + c) give non-standard executables non-standard names, and clearly + document the differences in manual pages (or equivalent), together + with instructions on where to get the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +5. You may charge a reasonable copying fee for any distribution of this +Package. You may charge any fee you choose for support of this +Package. You may not charge a fee for this Package itself. However, +you may distribute this Package in aggregate with other (possibly +commercial) programs as part of a larger (possibly commercial) software +distribution provided that you do not advertise this Package as a +product of your own. You may embed this Package's interpreter within +an executable of yours (by linking); this shall be construed as a mere +form of aggregation, provided that the complete Standard Version of the +interpreter is so embedded. + +6. The scripts and library files supplied as input to or produced as +output from the programs of this Package do not automatically fall +under the copyright of this Package, but belong to whoever generated +them, and may be sold commercially, and may be aggregated with this +Package. If such scripts or library files are aggregated with this +Package via the so-called "undump" or "unexec" methods of producing a +binary executable image, then distribution of such an image shall +neither be construed as a distribution of this Package nor shall it +fall under the restrictions of Paragraphs 3 and 4, provided that you do +not represent such an executable image as a Standard Version of this +Package. + +7. C subroutines (or comparably compiled subroutines in other +languages) supplied by you and linked into this Package in order to +emulate subroutines and variables of the language defined by this +Package shall not be considered part of this Package, but are the +equivalent of input as in Paragraph 6, provided these subroutines do +not change the language in any way that would cause it to fail the +regression tests for the language. + +8. Aggregation of this Package with a commercial distribution is always +permitted provided that the use of this Package is embedded; that is, +when no overt attempt is made to make this Package's interfaces visible +to the end user of the commercial distribution. Such use shall not be +construed as a distribution of this Package. + +9. The name of the Copyright Holder may not be used to endorse or promote +products derived from this software without specific prior written permission. + +10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + + The End diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff-lcs.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff-lcs.rb new file mode 100644 index 00000000..bc07bf99 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff-lcs.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "diff/lcs" diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs.rb new file mode 100644 index 00000000..5ee89377 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs.rb @@ -0,0 +1,742 @@ +# frozen_string_literal: true + +module Diff; end unless defined? Diff + +# == How Diff Works (by Mark-Jason Dominus) +# +# I once read an article written by the authors of +diff+; they said that they +# hard worked very hard on the algorithm until they found the right one. +# +# I think what they ended up using (and I hope someone will correct me, because +# I am not very confident about this) was the `longest common subsequence' +# method. In the LCS problem, you have two sequences of items: +# +# a b c d f g h j q z +# a b c d e f g i j k r x y z +# +# and you want to find the longest sequence of items that is present in both +# original sequences in the same order. That is, you want to find a new +# sequence *S* which can be obtained from the first sequence by deleting some +# items, and from the second sequence by deleting other items. You also want +# *S* to be as long as possible. In this case *S* is: +# +# a b c d f g j z +# +# From there it's only a small step to get diff-like output: +# +# e h i k q r x y +# + - + + - + + + +# +# This module solves the LCS problem. It also includes a canned function to +# generate +diff+-like output. +# +# It might seem from the example above that the LCS of two sequences is always +# pretty obvious, but that's not always the case, especially when the two +# sequences have many repeated elements. For example, consider +# +# a x b y c z p d q +# a b c a x b y c z +# +# A naive approach might start by matching up the +a+ and +b+ that appear at +# the beginning of each sequence, like this: +# +# a x b y c z p d q +# a b c a b y c z +# +# This finds the common subsequence +a b c z+. But actually, the LCS is +a x b +# y c z+: +# +# a x b y c z p d q +# a b c a x b y c z +module Diff::LCS +end + +require "diff/lcs/version" +require "diff/lcs/callbacks" +require "diff/lcs/internals" + +module Diff::LCS + # Returns an Array containing the longest common subsequence(s) between + # +self+ and +other+. See Diff::LCS#lcs. + # + # lcs = seq1.lcs(seq2) + # + # A note when using objects: Diff::LCS only works properly when each object + # can be used as a key in a Hash. This means that those objects must implement + # the methods +#hash+ and +#eql?+ such that two objects containing identical values + # compare identically for key purposes. That is: + # + # O.new('a').eql?(O.new('a')) == true && + # O.new('a').hash == O.new('a').hash + def lcs(other, &block) # :yields: self[i] if there are matched subsequences + Diff::LCS.lcs(self, other, &block) + end + + # Returns the difference set between +self+ and +other+. See Diff::LCS#diff. + def diff(other, callbacks = nil, &block) + Diff::LCS.diff(self, other, callbacks, &block) + end + + # Returns the balanced ("side-by-side") difference set between +self+ and + # +other+. See Diff::LCS#sdiff. + def sdiff(other, callbacks = nil, &block) + Diff::LCS.sdiff(self, other, callbacks, &block) + end + + # Traverses the discovered longest common subsequences between +self+ and + # +other+. See Diff::LCS#traverse_sequences. + def traverse_sequences(other, callbacks = nil, &block) + Diff::LCS.traverse_sequences(self, other, callbacks || Diff::LCS::SequenceCallbacks, &block) + end + + # Traverses the discovered longest common subsequences between +self+ and + # +other+ using the alternate, balanced algorithm. See + # Diff::LCS#traverse_balanced. + def traverse_balanced(other, callbacks = nil, &block) + Diff::LCS.traverse_balanced(self, other, callbacks || Diff::LCS::BalancedCallbacks, &block) + end + + # Attempts to patch +self+ with the provided +patchset+. A new sequence based + # on +self+ and the +patchset+ will be created. See Diff::LCS#patch. Attempts + # to autodiscover the direction of the patch. + def patch(patchset) + Diff::LCS.patch(self, patchset) + end + alias_method :unpatch, :patch + + # Attempts to patch +self+ with the provided +patchset+. A new sequence based + # on +self+ and the +patchset+ will be created. See Diff::LCS#patch. Does no + # patch direction autodiscovery. + def patch!(patchset) + Diff::LCS.patch!(self, patchset) + end + + # Attempts to unpatch +self+ with the provided +patchset+. A new sequence + # based on +self+ and the +patchset+ will be created. See Diff::LCS#unpatch. + # Does no patch direction autodiscovery. + def unpatch!(patchset) + Diff::LCS.unpatch!(self, patchset) + end + + # Attempts to patch +self+ with the provided +patchset+, using #patch!. If + # the sequence this is used on supports #replace, the value of +self+ will be + # replaced. See Diff::LCS#patch. Does no patch direction autodiscovery. + def patch_me(patchset) + if respond_to? :replace + replace(patch!(patchset)) + else + patch!(patchset) + end + end + + # Attempts to unpatch +self+ with the provided +patchset+, using #unpatch!. + # If the sequence this is used on supports #replace, the value of +self+ will + # be replaced. See Diff::LCS#unpatch. Does no patch direction autodiscovery. + def unpatch_me(patchset) + if respond_to? :replace + replace(unpatch!(patchset)) + else + unpatch!(patchset) + end + end +end + +class << Diff::LCS + def lcs(seq1, seq2, &block) # :yields: seq1[i] for each matched + matches = Diff::LCS::Internals.lcs(seq1, seq2) + ret = [] + string = seq1.is_a? String + matches.each_index do |i| + next if matches[i].nil? + + v = string ? seq1[i, 1] : seq1[i] + v = block[v] if block + ret << v + end + ret + end + alias_method :LCS, :lcs + + # #diff computes the smallest set of additions and deletions necessary to + # turn the first sequence into the second, and returns a description of these + # changes. + # + # See Diff::LCS::DiffCallbacks for the default behaviour. An alternate + # behaviour may be implemented with Diff::LCS::ContextDiffCallbacks. If a + # Class argument is provided for +callbacks+, #diff will attempt to + # initialise it. If the +callbacks+ object (possibly initialised) responds to + # #finish, it will be called. + def diff(seq1, seq2, callbacks = nil, &block) # :yields: diff changes + diff_traversal(:diff, seq1, seq2, callbacks || Diff::LCS::DiffCallbacks, &block) + end + + # #sdiff computes all necessary components to show two sequences and their + # minimized differences side by side, just like the Unix utility + # sdiff does: + # + # old < - + # same same + # before | after + # - > new + # + # See Diff::LCS::SDiffCallbacks for the default behaviour. An alternate + # behaviour may be implemented with Diff::LCS::ContextDiffCallbacks. If a + # Class argument is provided for +callbacks+, #diff will attempt to + # initialise it. If the +callbacks+ object (possibly initialised) responds to + # #finish, it will be called. + # + # Each element of a returned array is a Diff::LCS::ContextChange object, + # which can be implicitly converted to an array. + # + # Diff::LCS.sdiff(a, b).each do |action, (old_pos, old_element), (new_pos, new_element)| + # case action + # when '!' + # # replace + # when '-' + # # delete + # when '+' + # # insert + # end + # end + def sdiff(seq1, seq2, callbacks = nil, &block) # :yields: diff changes + diff_traversal(:sdiff, seq1, seq2, callbacks || Diff::LCS::SDiffCallbacks, &block) + end + + # #traverse_sequences is the most general facility provided by this module; + # #diff and #lcs are implemented as calls to it. + # + # The arguments to #traverse_sequences are the two sequences to traverse, and + # a callback object, like this: + # + # traverse_sequences(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new) + # + # == Callback Methods + # + # Optional callback methods are emphasized. + # + # callbacks#match:: Called when +a+ and +b+ are pointing to + # common elements in +A+ and +B+. + # callbacks#discard_a:: Called when +a+ is pointing to an + # element not in +B+. + # callbacks#discard_b:: Called when +b+ is pointing to an + # element not in +A+. + # callbacks#finished_a:: Called when +a+ has reached the end of + # sequence +A+. + # callbacks#finished_b:: Called when +b+ has reached the end of + # sequence +B+. + # + # == Algorithm + # + # a---+ + # v + # A = a b c e h j l m n p + # B = b c d e f j k l m r s t + # ^ + # b---+ + # + # If there are two arrows (+a+ and +b+) pointing to elements of sequences +A+ + # and +B+, the arrows will initially point to the first elements of their + # respective sequences. #traverse_sequences will advance the arrows through + # the sequences one element at a time, calling a method on the user-specified + # callback object before each advance. It will advance the arrows in such a + # way that if there are elements A[i] and B[j] which are + # both equal and part of the longest common subsequence, there will be some + # moment during the execution of #traverse_sequences when arrow +a+ is + # pointing to A[i] and arrow +b+ is pointing to B[j]. When + # this happens, #traverse_sequences will call callbacks#match and + # then it will advance both arrows. + # + # Otherwise, one of the arrows is pointing to an element of its sequence that + # is not part of the longest common subsequence. #traverse_sequences will + # advance that arrow and will call callbacks#discard_a or + # callbacks#discard_b, depending on which arrow it advanced. If both + # arrows point to elements that are not part of the longest common + # subsequence, then #traverse_sequences will advance arrow +a+ and call the + # appropriate callback, then it will advance arrow +b+ and call the appropriate + # callback. + # + # The methods for callbacks#match, callbacks#discard_a, and + # callbacks#discard_b are invoked with an event comprising the + # action ("=", "+", or "-", respectively), the indexes +i+ and +j+, and the + # elements A[i] and B[j]. Return values are discarded by + # #traverse_sequences. + # + # === End of Sequences + # + # If arrow +a+ reaches the end of its sequence before arrow +b+ does, + # #traverse_sequence will try to call callbacks#finished_a with the + # last index and element of +A+ (A[-1]) and the current index and + # element of +B+ (B[j]). If callbacks#finished_a does not + # exist, then callbacks#discard_b will be called on each element of + # +B+ until the end of the sequence is reached (the call will be done with + # A[-1] and B[j] for each element). + # + # If +b+ reaches the end of +B+ before +a+ reaches the end of +A+, + # callbacks#finished_b will be called with the current index and + # element of +A+ (A[i]) and the last index and element of +B+ + # (A[-1]). Again, if callbacks#finished_b does not exist on + # the callback object, then callbacks#discard_a will be called on + # each element of +A+ until the end of the sequence is reached (A[i] + # and B[-1]). + # + # There is a chance that one additional callbacks#discard_a or + # callbacks#discard_b will be called after the end of the sequence + # is reached, if +a+ has not yet reached the end of +A+ or +b+ has not yet + # reached the end of +B+. + def traverse_sequences(seq1, seq2, callbacks = Diff::LCS::SequenceCallbacks) # :yields: change events + callbacks ||= Diff::LCS::SequenceCallbacks + matches = Diff::LCS::Internals.lcs(seq1, seq2) + + run_finished_a = run_finished_b = false + string = seq1.is_a?(String) + + a_size = seq1.size + b_size = seq2.size + ai = bj = 0 + + matches.each do |b_line| + if b_line.nil? + unless seq1[ai].nil? + ax = string ? seq1[ai, 1] : seq1[ai] + bx = string ? seq2[bj, 1] : seq2[bj] + + event = Diff::LCS::ContextChange.new("-", ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_a(event) + end + else + ax = string ? seq1[ai, 1] : seq1[ai] + + loop do + break unless bj < b_line + + bx = string ? seq2[bj, 1] : seq2[bj] + event = Diff::LCS::ContextChange.new("+", ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_b(event) + bj += 1 + end + bx = string ? seq2[bj, 1] : seq2[bj] + event = Diff::LCS::ContextChange.new("=", ai, ax, bj, bx) + event = yield event if block_given? + callbacks.match(event) + bj += 1 + end + ai += 1 + end + + # The last entry (if any) processed was a match. +ai+ and +bj+ point just + # past the last matching lines in their sequences. + while (ai < a_size) || (bj < b_size) + # last A? + if ai == a_size && bj < b_size + if callbacks.respond_to?(:finished_a) && !run_finished_a + ax = string ? seq1[-1, 1] : seq1[-1] + bx = string ? seq2[bj, 1] : seq2[bj] + event = Diff::LCS::ContextChange.new(">", a_size - 1, ax, bj, bx) + event = yield event if block_given? + callbacks.finished_a(event) + run_finished_a = true + else + ax = string ? seq1[ai, 1] : seq1[ai] + loop do + bx = string ? seq2[bj, 1] : seq2[bj] + event = Diff::LCS::ContextChange.new("+", ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_b(event) + bj += 1 + break unless bj < b_size + end + end + end + + # last B? + if bj == b_size && ai < a_size + if callbacks.respond_to?(:finished_b) && !run_finished_b + ax = string ? seq1[ai, 1] : seq1[ai] + bx = string ? seq2[-1, 1] : seq2[-1] + event = Diff::LCS::ContextChange.new("<", ai, ax, b_size - 1, bx) + event = yield event if block_given? + callbacks.finished_b(event) + run_finished_b = true + else + bx = string ? seq2[bj, 1] : seq2[bj] + loop do + ax = string ? seq1[ai, 1] : seq1[ai] + event = Diff::LCS::ContextChange.new("-", ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_a(event) + ai += 1 + break unless bj < b_size + end + end + end + + if ai < a_size + ax = string ? seq1[ai, 1] : seq1[ai] + bx = string ? seq2[bj, 1] : seq2[bj] + event = Diff::LCS::ContextChange.new("-", ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_a(event) + ai += 1 + end + + if bj < b_size + ax = string ? seq1[ai, 1] : seq1[ai] + bx = string ? seq2[bj, 1] : seq2[bj] + event = Diff::LCS::ContextChange.new("+", ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_b(event) + bj += 1 + end + end + end + + # #traverse_balanced is an alternative to #traverse_sequences. It uses a + # different algorithm to iterate through the entries in the computed longest + # common subsequence. Instead of viewing the changes as insertions or + # deletions from one of the sequences, #traverse_balanced will report + # changes between the sequences. + # + # The arguments to #traverse_balanced are the two sequences to traverse and a + # callback object, like this: + # + # traverse_balanced(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new) + # + # #sdiff is implemented with #traverse_balanced. + # + # == Callback Methods + # + # Optional callback methods are emphasized. + # + # callbacks#match:: Called when +a+ and +b+ are pointing to + # common elements in +A+ and +B+. + # callbacks#discard_a:: Called when +a+ is pointing to an + # element not in +B+. + # callbacks#discard_b:: Called when +b+ is pointing to an + # element not in +A+. + # callbacks#change:: Called when +a+ and +b+ are pointing to + # the same relative position, but + # A[a] and B[b] are not + # the same; a change has + # occurred. + # + # #traverse_balanced might be a bit slower than #traverse_sequences, + # noticeable only while processing huge amounts of data. + # + # == Algorithm + # + # a---+ + # v + # A = a b c e h j l m n p + # B = b c d e f j k l m r s t + # ^ + # b---+ + # + # === Matches + # + # If there are two arrows (+a+ and +b+) pointing to elements of sequences +A+ + # and +B+, the arrows will initially point to the first elements of their + # respective sequences. #traverse_sequences will advance the arrows through + # the sequences one element at a time, calling a method on the user-specified + # callback object before each advance. It will advance the arrows in such a + # way that if there are elements A[i] and B[j] which are + # both equal and part of the longest common subsequence, there will be some + # moment during the execution of #traverse_sequences when arrow +a+ is + # pointing to A[i] and arrow +b+ is pointing to B[j]. When + # this happens, #traverse_sequences will call callbacks#match and + # then it will advance both arrows. + # + # === Discards + # + # Otherwise, one of the arrows is pointing to an element of its sequence that + # is not part of the longest common subsequence. #traverse_sequences will + # advance that arrow and will call callbacks#discard_a or + # callbacks#discard_b, depending on which arrow it advanced. + # + # === Changes + # + # If both +a+ and +b+ point to elements that are not part of the longest + # common subsequence, then #traverse_sequences will try to call + # callbacks#change and advance both arrows. If + # callbacks#change is not implemented, then + # callbacks#discard_a and callbacks#discard_b will be + # called in turn. + # + # The methods for callbacks#match, callbacks#discard_a, + # callbacks#discard_b, and callbacks#change are invoked + # with an event comprising the action ("=", "+", "-", or "!", respectively), + # the indexes +i+ and +j+, and the elements A[i] and B[j]. + # Return values are discarded by #traverse_balanced. + # + # === Context + # + # Note that +i+ and +j+ may not be the same index position, even if +a+ and + # +b+ are considered to be pointing to matching or changed elements. + def traverse_balanced(seq1, seq2, callbacks = Diff::LCS::BalancedCallbacks) + matches = Diff::LCS::Internals.lcs(seq1, seq2) + a_size = seq1.size + b_size = seq2.size + ai = bj = mb = 0 + ma = -1 + string = seq1.is_a?(String) + + # Process all the lines in the match vector. + loop do + # Find next match indexes +ma+ and +mb+ + loop do + ma += 1 + break unless ma < matches.size && matches[ma].nil? + end + + break if ma >= matches.size # end of matches? + + mb = matches[ma] + + # Change(seq2) + while (ai < ma) || (bj < mb) + ax = string ? seq1[ai, 1] : seq1[ai] + bx = string ? seq2[bj, 1] : seq2[bj] + + case [(ai < ma), (bj < mb)] + when [true, true] + if callbacks.respond_to?(:change) + event = Diff::LCS::ContextChange.new("!", ai, ax, bj, bx) + event = yield event if block_given? + callbacks.change(event) + ai += 1 + else + event = Diff::LCS::ContextChange.new("-", ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_a(event) + ai += 1 + ax = string ? seq1[ai, 1] : seq1[ai] + event = Diff::LCS::ContextChange.new("+", ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_b(event) + end + + bj += 1 + when [true, false] + event = Diff::LCS::ContextChange.new("-", ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_a(event) + ai += 1 + when [false, true] + event = Diff::LCS::ContextChange.new("+", ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_b(event) + bj += 1 + end + end + + # Match + ax = string ? seq1[ai, 1] : seq1[ai] + bx = string ? seq2[bj, 1] : seq2[bj] + event = Diff::LCS::ContextChange.new("=", ai, ax, bj, bx) + event = yield event if block_given? + callbacks.match(event) + ai += 1 + bj += 1 + end + + while (ai < a_size) || (bj < b_size) + ax = string ? seq1[ai, 1] : seq1[ai] + bx = string ? seq2[bj, 1] : seq2[bj] + + case [(ai < a_size), (bj < b_size)] + when [true, true] + if callbacks.respond_to?(:change) + event = Diff::LCS::ContextChange.new("!", ai, ax, bj, bx) + event = yield event if block_given? + callbacks.change(event) + ai += 1 + else + event = Diff::LCS::ContextChange.new("-", ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_a(event) + ai += 1 + ax = string ? seq1[ai, 1] : seq1[ai] + event = Diff::LCS::ContextChange.new("+", ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_b(event) + end + + bj += 1 + when [true, false] + event = Diff::LCS::ContextChange.new("-", ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_a(event) + ai += 1 + when [false, true] + event = Diff::LCS::ContextChange.new("+", ai, ax, bj, bx) + event = yield event if block_given? + callbacks.discard_b(event) + bj += 1 + end + end + end + + # standard:disable Style/HashSyntax + PATCH_MAP = { # :nodoc: + :patch => {"+" => "+", "-" => "-", "!" => "!", "=" => "="}.freeze, + :unpatch => {"+" => "-", "-" => "+", "!" => "!", "=" => "="}.freeze + }.freeze + # standard:enable Style/HashSyntax + + # Applies a +patchset+ to the sequence +src+ according to the +direction+ + # (:patch or :unpatch), producing a new sequence. + # + # If the +direction+ is not specified, Diff::LCS::patch will attempt to + # discover the direction of the +patchset+. + # + # A +patchset+ can be considered to apply forward (:patch) if the + # following expression is true: + # + # patch(s1, diff(s1, s2)) -> s2 + # + # A +patchset+ can be considered to apply backward (:unpatch) if the + # following expression is true: + # + # patch(s2, diff(s1, s2)) -> s1 + # + # If the +patchset+ contains no changes, the +src+ value will be returned as + # either src.dup or +src+. A +patchset+ can be deemed as having no + # changes if the following predicate returns true: + # + # patchset.empty? or + # patchset.flatten(1).all? { |change| change.unchanged? } + # + # === Patchsets + # + # A +patchset+ is always an enumerable sequence of changes, hunks of changes, + # or a mix of the two. A hunk of changes is an enumerable sequence of + # changes: + # + # [ # patchset + # # change + # [ # hunk + # # change + # ] + # ] + # + # The +patch+ method accepts patchsets that are enumerable sequences + # containing either Diff::LCS::Change objects (or a subclass) or the array + # representations of those objects. Prior to application, array + # representations of Diff::LCS::Change objects will be reified. + def patch(src, patchset, direction = nil) + # Normalize the patchset. + has_changes, patchset = Diff::LCS::Internals.analyze_patchset(patchset) + + return src.respond_to?(:dup) ? src.dup : src unless has_changes + + string = src.is_a?(String) + # Start with a new empty type of the source's class + res = src.class.new + + direction ||= Diff::LCS::Internals.intuit_diff_direction(src, patchset) + + ai = bj = 0 + + patch_map = PATCH_MAP[direction] + + patchset.each do |change| + # Both Change and ContextChange support #action + action = patch_map[change.action] + + case change + when Diff::LCS::ContextChange + case direction + when :patch + el = change.new_element + op = change.old_position + np = change.new_position + when :unpatch + el = change.old_element + op = change.new_position + np = change.old_position + end + + case action + when "-" # Remove details from the old string + while ai < op + res << (string ? src[ai, 1] : src[ai]) + ai += 1 + bj += 1 + end + ai += 1 + when "+" + while bj < np + res << (string ? src[ai, 1] : src[ai]) + ai += 1 + bj += 1 + end + + res << el + bj += 1 + when "=" + # This only appears in sdiff output with the SDiff callback. + # Therefore, we only need to worry about dealing with a single + # element. + res << el + + ai += 1 + bj += 1 + when "!" + while ai < op + res << (string ? src[ai, 1] : src[ai]) + ai += 1 + bj += 1 + end + + bj += 1 + ai += 1 + + res << el + end + when Diff::LCS::Change + case action + when "-" + while ai < change.position + res << (string ? src[ai, 1] : src[ai]) + ai += 1 + bj += 1 + end + ai += 1 + when "+" + while bj < change.position + res << (string ? src[ai, 1] : src[ai]) + ai += 1 + bj += 1 + end + + bj += 1 + + res << change.element + end + end + end + + while ai < src.size + res << (string ? src[ai, 1] : src[ai]) + ai += 1 + bj += 1 + end + + res + end + + # Given a set of patchset, convert the current version to the prior version. + # Does no auto-discovery. + def unpatch!(src, patchset) + patch(src, patchset, :unpatch) + end + + # Given a set of patchset, convert the current version to the next version. + # Does no auto-discovery. + def patch!(src, patchset) + patch(src, patchset, :patch) + end +end + +require "diff/lcs/backports" diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/array.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/array.rb new file mode 100644 index 00000000..663918a2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/array.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "diff/lcs" + +class Array + include Diff::LCS +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/backports.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/backports.rb new file mode 100644 index 00000000..6543c8a4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/backports.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +unless 0.respond_to?(:positive?) + class Fixnum # standard:disable Lint/UnifiedInteger + def positive? + self > 0 + end + + def negative? + self < 0 + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/block.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/block.rb new file mode 100644 index 00000000..226ed6fa --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/block.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# A block is an operation removing, adding, or changing a group of items. +# Basically, this is just a list of changes, where each change adds or +# deletes a single item. Used by bin/ldiff. +class Diff::LCS::Block + attr_reader :changes, :insert, :remove + + def initialize(chunk) + @changes = [] + @insert = [] + @remove = [] + + chunk.each do |item| + @changes << item + @remove << item if item.deleting? + @insert << item if item.adding? + end + end + + def diff_size + @insert.size - @remove.size + end + + def op + case [@remove.empty?, @insert.empty?] + when [false, false] + "!" + when [false, true] + "-" + when [true, false] + "+" + else # [true, true] + "^" + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/callbacks.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/callbacks.rb new file mode 100644 index 00000000..2c5a7791 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/callbacks.rb @@ -0,0 +1,327 @@ +# frozen_string_literal: true + +require "diff/lcs/change" + +module Diff::LCS + # This callback object implements the default set of callback events, + # which only returns the event itself. Note that #finished_a and + # #finished_b are not implemented -- I haven't yet figured out where they + # would be useful. + # + # Note that this is intended to be called as is, e.g., + # + # Diff::LCS.LCS(seq1, seq2, Diff::LCS::DefaultCallbacks) + class DefaultCallbacks + class << self + # Called when two items match. + def match(event) + event + end + + # Called when the old value is discarded in favour of the new value. + def discard_a(event) + event + end + + # Called when the new value is discarded in favour of the old value. + def discard_b(event) + event + end + + # Called when both the old and new values have changed. + def change(event) + event + end + + private :new + end + end + + # An alias for DefaultCallbacks that is used in + # Diff::LCS#traverse_sequences. + # + # Diff::LCS.LCS(seq1, seq2, Diff::LCS::SequenceCallbacks) + SequenceCallbacks = DefaultCallbacks + + # An alias for DefaultCallbacks that is used in + # Diff::LCS#traverse_balanced. + # + # Diff::LCS.LCS(seq1, seq2, Diff::LCS::BalancedCallbacks) + BalancedCallbacks = DefaultCallbacks + + def self.callbacks_for(callbacks) + callbacks.new + rescue + callbacks + end +end + +# This will produce a compound array of simple diff change objects. Each +# element in the #diffs array is a +hunk+ or +hunk+ array, where each +# element in each +hunk+ array is a single Change object representing the +# addition or removal of a single element from one of the two tested +# sequences. The +hunk+ provides the full context for the changes. +# +# diffs = Diff::LCS.diff(seq1, seq2) +# # This example shows a simplified array format. +# # [ [ [ '-', 0, 'a' ] ], # 1 +# # [ [ '+', 2, 'd' ] ], # 2 +# # [ [ '-', 4, 'h' ], # 3 +# # [ '+', 4, 'f' ] ], +# # [ [ '+', 6, 'k' ] ], # 4 +# # [ [ '-', 8, 'n' ], # 5 +# # [ '-', 9, 'p' ], +# # [ '+', 9, 'r' ], +# # [ '+', 10, 's' ], +# # [ '+', 11, 't' ] ] ] +# +# There are five hunks here. The first hunk says that the +a+ at position 0 +# of the first sequence should be deleted ('-'). The second hunk +# says that the +d+ at position 2 of the second sequence should be inserted +# ('+'). The third hunk says that the +h+ at position 4 of the +# first sequence should be removed and replaced with the +f+ from position 4 +# of the second sequence. The other two hunks are described similarly. +# +# === Use +# +# This callback object must be initialised and is used by the Diff::LCS#diff +# method. +# +# cbo = Diff::LCS::DiffCallbacks.new +# Diff::LCS.LCS(seq1, seq2, cbo) +# cbo.finish +# +# Note that the call to #finish is absolutely necessary, or the last set of +# changes will not be visible. Alternatively, can be used as: +# +# cbo = Diff::LCS::DiffCallbacks.new { |tcbo| Diff::LCS.LCS(seq1, seq2, tcbo) } +# +# The necessary #finish call will be made. +# +# === Simplified Array Format +# +# The simplified array format used in the example above can be obtained +# with: +# +# require 'pp' +# pp diffs.map { |e| e.map { |f| f.to_a } } +class Diff::LCS::DiffCallbacks + # Returns the difference set collected during the diff process. + attr_reader :diffs + + def initialize # :yields: self + @hunk = [] + @diffs = [] + + return unless block_given? + + begin + yield self + ensure + finish + end + end + + # Finalizes the diff process. If an unprocessed hunk still exists, then it + # is appended to the diff list. + def finish + finish_hunk + end + + def match(_event) + finish_hunk + end + + def discard_a(event) + @hunk << Diff::LCS::Change.new("-", event.old_position, event.old_element) + end + + def discard_b(event) + @hunk << Diff::LCS::Change.new("+", event.new_position, event.new_element) + end + + def finish_hunk + @diffs << @hunk unless @hunk.empty? + @hunk = [] + end + private :finish_hunk +end + +# This will produce a compound array of contextual diff change objects. Each +# element in the #diffs array is a "hunk" array, where each element in each +# "hunk" array is a single change. Each change is a Diff::LCS::ContextChange +# that contains both the old index and new index values for the change. The +# "hunk" provides the full context for the changes. Both old and new objects +# will be presented for changed objects. +nil+ will be substituted for a +# discarded object. +# +# seq1 = %w(a b c e h j l m n p) +# seq2 = %w(b c d e f j k l m r s t) +# +# diffs = Diff::LCS.diff(seq1, seq2, Diff::LCS::ContextDiffCallbacks) +# # This example shows a simplified array format. +# # [ [ [ '-', [ 0, 'a' ], [ 0, nil ] ] ], # 1 +# # [ [ '+', [ 3, nil ], [ 2, 'd' ] ] ], # 2 +# # [ [ '-', [ 4, 'h' ], [ 4, nil ] ], # 3 +# # [ '+', [ 5, nil ], [ 4, 'f' ] ] ], +# # [ [ '+', [ 6, nil ], [ 6, 'k' ] ] ], # 4 +# # [ [ '-', [ 8, 'n' ], [ 9, nil ] ], # 5 +# # [ '+', [ 9, nil ], [ 9, 'r' ] ], +# # [ '-', [ 9, 'p' ], [ 10, nil ] ], +# # [ '+', [ 10, nil ], [ 10, 's' ] ], +# # [ '+', [ 10, nil ], [ 11, 't' ] ] ] ] +# +# The five hunks shown are comprised of individual changes; if there is a +# related set of changes, they are still shown individually. +# +# This callback can also be used with Diff::LCS#sdiff, which will produce +# results like: +# +# diffs = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextCallbacks) +# # This example shows a simplified array format. +# # [ [ [ "-", [ 0, "a" ], [ 0, nil ] ] ], # 1 +# # [ [ "+", [ 3, nil ], [ 2, "d" ] ] ], # 2 +# # [ [ "!", [ 4, "h" ], [ 4, "f" ] ] ], # 3 +# # [ [ "+", [ 6, nil ], [ 6, "k" ] ] ], # 4 +# # [ [ "!", [ 8, "n" ], [ 9, "r" ] ], # 5 +# # [ "!", [ 9, "p" ], [ 10, "s" ] ], +# # [ "+", [ 10, nil ], [ 11, "t" ] ] ] ] +# +# The five hunks are still present, but are significantly shorter in total +# presentation, because changed items are shown as changes ("!") instead of +# potentially "mismatched" pairs of additions and deletions. +# +# The result of this operation is similar to that of +# Diff::LCS::SDiffCallbacks. They may be compared as: +# +# s = Diff::LCS.sdiff(seq1, seq2).reject { |e| e.action == "=" } +# c = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextDiffCallbacks).flatten(1) +# +# s == c # -> true +# +# === Use +# +# This callback object must be initialised and can be used by the +# Diff::LCS#diff or Diff::LCS#sdiff methods. +# +# cbo = Diff::LCS::ContextDiffCallbacks.new +# Diff::LCS.LCS(seq1, seq2, cbo) +# cbo.finish +# +# Note that the call to #finish is absolutely necessary, or the last set of +# changes will not be visible. Alternatively, can be used as: +# +# cbo = Diff::LCS::ContextDiffCallbacks.new { |tcbo| Diff::LCS.LCS(seq1, seq2, tcbo) } +# +# The necessary #finish call will be made. +# +# === Simplified Array Format +# +# The simplified array format used in the example above can be obtained +# with: +# +# require 'pp' +# pp diffs.map { |e| e.map { |f| f.to_a } } +class Diff::LCS::ContextDiffCallbacks < Diff::LCS::DiffCallbacks + def discard_a(event) + @hunk << Diff::LCS::ContextChange.simplify(event) + end + + def discard_b(event) + @hunk << Diff::LCS::ContextChange.simplify(event) + end + + def change(event) + @hunk << Diff::LCS::ContextChange.simplify(event) + end +end + +# This will produce a simple array of diff change objects. Each element in +# the #diffs array is a single ContextChange. In the set of #diffs provided +# by SDiffCallbacks, both old and new objects will be presented for both +# changed and unchanged objects. +nil+ will be substituted +# for a discarded object. +# +# The diffset produced by this callback, when provided to Diff::LCS#sdiff, +# will compute and display the necessary components to show two sequences +# and their minimized differences side by side, just like the Unix utility +# +sdiff+. +# +# same same +# before | after +# old < - +# - > new +# +# seq1 = %w(a b c e h j l m n p) +# seq2 = %w(b c d e f j k l m r s t) +# +# diffs = Diff::LCS.sdiff(seq1, seq2) +# # This example shows a simplified array format. +# # [ [ "-", [ 0, "a"], [ 0, nil ] ], +# # [ "=", [ 1, "b"], [ 0, "b" ] ], +# # [ "=", [ 2, "c"], [ 1, "c" ] ], +# # [ "+", [ 3, nil], [ 2, "d" ] ], +# # [ "=", [ 3, "e"], [ 3, "e" ] ], +# # [ "!", [ 4, "h"], [ 4, "f" ] ], +# # [ "=", [ 5, "j"], [ 5, "j" ] ], +# # [ "+", [ 6, nil], [ 6, "k" ] ], +# # [ "=", [ 6, "l"], [ 7, "l" ] ], +# # [ "=", [ 7, "m"], [ 8, "m" ] ], +# # [ "!", [ 8, "n"], [ 9, "r" ] ], +# # [ "!", [ 9, "p"], [ 10, "s" ] ], +# # [ "+", [ 10, nil], [ 11, "t" ] ] ] +# +# The result of this operation is similar to that of +# Diff::LCS::ContextDiffCallbacks. They may be compared as: +# +# s = Diff::LCS.sdiff(seq1, seq2).reject { |e| e.action == "=" } +# c = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextDiffCallbacks).flatten(1) +# +# s == c # -> true +# +# === Use +# +# This callback object must be initialised and is used by the Diff::LCS#sdiff +# method. +# +# cbo = Diff::LCS::SDiffCallbacks.new +# Diff::LCS.LCS(seq1, seq2, cbo) +# +# As with the other initialisable callback objects, +# Diff::LCS::SDiffCallbacks can be initialised with a block. As there is no +# "fininishing" to be done, this has no effect on the state of the object. +# +# cbo = Diff::LCS::SDiffCallbacks.new { |tcbo| Diff::LCS.LCS(seq1, seq2, tcbo) } +# +# === Simplified Array Format +# +# The simplified array format used in the example above can be obtained +# with: +# +# require 'pp' +# pp diffs.map { |e| e.to_a } +class Diff::LCS::SDiffCallbacks + # Returns the difference set collected during the diff process. + attr_reader :diffs + + def initialize # :yields: self + @diffs = [] + yield self if block_given? + end + + def match(event) + @diffs << Diff::LCS::ContextChange.simplify(event) + end + + def discard_a(event) + @diffs << Diff::LCS::ContextChange.simplify(event) + end + + def discard_b(event) + @diffs << Diff::LCS::ContextChange.simplify(event) + end + + def change(event) + @diffs << Diff::LCS::ContextChange.simplify(event) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/change.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/change.rb new file mode 100644 index 00000000..714d78c8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/change.rb @@ -0,0 +1,174 @@ +# frozen_string_literal: true + +# Represents a simplistic (non-contextual) change. Represents the removal or +# addition of an element from either the old or the new sequenced +# enumerable. +class Diff::LCS::Change + IntClass = 1.class # Fixnum is deprecated in Ruby 2.4 # standard:disable Naming/ConstantName + + # The only actions valid for changes are '+' (add), '-' (delete), '=' + # (no change), '!' (changed), '<' (tail changes from first sequence), or + # '>' (tail changes from second sequence). The last two ('<>') are only + # found with Diff::LCS::diff and Diff::LCS::sdiff. + VALID_ACTIONS = %w[+ - = ! > <].freeze + + def self.valid_action?(action) + VALID_ACTIONS.include? action + end + + # Returns the action this Change represents. + attr_reader :action + + # Returns the position of the Change. + attr_reader :position + # Returns the sequence element of the Change. + attr_reader :element + + def initialize(*args) + @action, @position, @element = *args + + fail "Invalid Change Action '#{@action}'" unless Diff::LCS::Change.valid_action?(@action) + fail "Invalid Position Type" unless @position.is_a? IntClass + end + + def inspect(*_args) + "#<#{self.class}: #{to_a.inspect}>" + end + + def to_a + [@action, @position, @element] + end + + alias_method :to_ary, :to_a + + def self.from_a(arr) + arr = arr.flatten(1) + case arr.size + when 5 + Diff::LCS::ContextChange.new(*arr[0...5]) + when 3 + Diff::LCS::Change.new(*arr[0...3]) + else + fail "Invalid change array format provided." + end + end + + include Comparable + + def ==(other) + (self.class == other.class) and + (action == other.action) and + (position == other.position) and + (element == other.element) + end + + def <=>(other) + r = action <=> other.action + r = position <=> other.position if r.zero? + r = element <=> other.element if r.zero? + r + end + + def adding? + @action == "+" + end + + def deleting? + @action == "-" + end + + def unchanged? + @action == "=" + end + + def changed? + @action == "!" + end + + def finished_a? + @action == ">" + end + + def finished_b? + @action == "<" + end +end + +# Represents a contextual change. Contains the position and values of the +# elements in the old and the new sequenced enumerables as well as the action +# taken. +class Diff::LCS::ContextChange < Diff::LCS::Change + # We don't need these two values. + undef :position + undef :element + + # Returns the old position being changed. + attr_reader :old_position + # Returns the new position being changed. + attr_reader :new_position + # Returns the old element being changed. + attr_reader :old_element + # Returns the new element being changed. + attr_reader :new_element + + def initialize(*args) + @action, @old_position, @old_element, @new_position, @new_element = *args + + fail "Invalid Change Action '#{@action}'" unless Diff::LCS::Change.valid_action?(@action) + fail "Invalid (Old) Position Type" unless @old_position.nil? || @old_position.is_a?(IntClass) + fail "Invalid (New) Position Type" unless @new_position.nil? || @new_position.is_a?(IntClass) + end + + def to_a + [ + @action, + [@old_position, @old_element], + [@new_position, @new_element] + ] + end + + alias_method :to_ary, :to_a + + def self.from_a(arr) + Diff::LCS::Change.from_a(arr) + end + + # Simplifies a context change for use in some diff callbacks. '<' actions + # are converted to '-' and '>' actions are converted to '+'. + def self.simplify(event) + ea = event.to_a + + case ea[0] + when "-" + ea[2][1] = nil + when "<" + ea[0] = "-" + ea[2][1] = nil + when "+" + ea[1][1] = nil + when ">" + ea[0] = "+" + ea[1][1] = nil + end + + Diff::LCS::ContextChange.from_a(ea) + end + + def ==(other) + (self.class == other.class) and + (@action == other.action) and + (@old_position == other.old_position) and + (@new_position == other.new_position) and + (@old_element == other.old_element) and + (@new_element == other.new_element) + end + + def <=>(other) + r = @action <=> other.action + r = @old_position <=> other.old_position if r.zero? + r = @new_position <=> other.new_position if r.zero? + r = @old_element <=> other.old_element if r.zero? + r = @new_element <=> other.new_element if r.zero? + r + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/htmldiff.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/htmldiff.rb new file mode 100644 index 00000000..90732438 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/htmldiff.rb @@ -0,0 +1,160 @@ +# frozen_string_literal: true + +require "erb" + +# Produce a simple HTML diff view. +class Diff::LCS::HTMLDiff + class << self + # standard:disable ThreadSafety/ClassAndModuleAttributes + attr_accessor :can_expand_tabs # :nodoc: + # standard:enable ThreadSafety/ClassAndModuleAttributes + end + self.can_expand_tabs = true + + class Callbacks # :nodoc: + attr_accessor :output + attr_accessor :match_class + attr_accessor :only_a_class + attr_accessor :only_b_class + + def initialize(output, options = {}) + @output = output + options ||= {} + + @match_class = options[:match_class] || "match" + @only_a_class = options[:only_a_class] || "only_a" + @only_b_class = options[:only_b_class] || "only_b" + end + + def htmlize(element, css_class) + element = " " if element.empty? + %(
    #{element}
    \n) + end + private :htmlize + + # This will be called with both lines are the same + def match(event) + @output << htmlize(event.old_element, :match_class) + end + + # This will be called when there is a line in A that isn't in B + def discard_a(event) + @output << htmlize(event.old_element, :only_a_class) + end + + # This will be called when there is a line in B that isn't in A + def discard_b(event) + @output << htmlize(event.new_element, :only_b_class) + end + end + + # standard:disable Style/HashSyntax + DEFAULT_OPTIONS = { + :expand_tabs => nil, + :output => nil, + :css => nil, + :title => nil + }.freeze + # standard:enable Style/HashSyntax + + # standard:disable Layout/HeredocIndentation + DEFAULT_CSS = <<-CSS +body { margin: 0; } +.diff +{ + border: 1px solid black; + margin: 1em 2em; +} +p +{ + margin-left: 2em; +} +pre +{ + padding-left: 1em; + margin: 0; + font-family: Inconsolata, Consolas, Lucida, Courier, monospaced; + white-space: pre; +} +.match { } +.only_a +{ + background-color: #fdd; + color: red; + text-decoration: line-through; +} +.only_b +{ + background-color: #ddf; + color: blue; + border-left: 3px solid blue +} +h1 { margin-left: 2em; } + CSS + # standard:enable Layout/HeredocIndentation + + def initialize(left, right, options = nil) + @left = left + @right = right + @options = options + + @options = DEFAULT_OPTIONS.dup if @options.nil? + end + + def verify_options + @options[:expand_tabs] ||= 4 + @options[:expand_tabs] = 4 if @options[:expand_tabs].negative? + + @options[:output] ||= $stdout + + @options[:css] ||= DEFAULT_CSS.dup + + @options[:title] ||= "diff" + end + private :verify_options + + attr_reader :options + + def run + verify_options + + if @options[:expand_tabs].positive? && self.class.can_expand_tabs + formatter = Text::Format.new + formatter.tabstop = @options[:expand_tabs] + + @left.map! { |line| formatter.expand(line.chomp) } + @right.map! { |line| formatter.expand(line.chomp) } + end + + @left.map! { |line| ERB::Util.html_escape(line.chomp) } + @right.map! { |line| ERB::Util.html_escape(line.chomp) } + + # standard:disable Layout/HeredocIndentation + @options[:output] << <<-OUTPUT + + + #{@options[:title]} + + + +

    #{@options[:title]}

    +

    Legend: Only in Old  + Only in New

    +
    + OUTPUT + # standard:enable Layout/HeredocIndentation + + callbacks = Callbacks.new(@options[:output]) + Diff::LCS.traverse_sequences(@left, @right, callbacks) + + # standard:disable Layout/HeredocIndentation + @options[:output] << <<-OUTPUT +
    + + + OUTPUT + # standard:enable Layout/HeredocIndentation + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/hunk.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/hunk.rb new file mode 100644 index 00000000..24b33bca --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/hunk.rb @@ -0,0 +1,379 @@ +# frozen_string_literal: true + +require "diff/lcs/block" + +# A Hunk is a group of Blocks which overlap because of the context surrounding +# each block. (So if we're not using context, every hunk will contain one +# block.) Used in the diff program (bin/ldiff). +class Diff::LCS::Hunk + OLD_DIFF_OP_ACTION = {"+" => "a", "-" => "d", "!" => "c"}.freeze # :nodoc: + ED_DIFF_OP_ACTION = {"+" => "a", "-" => "d", "!" => "c"}.freeze # :nodoc: + + private_constant :OLD_DIFF_OP_ACTION, :ED_DIFF_OP_ACTION if respond_to?(:private_constant) + + # Create a hunk using references to both the old and new data, as well as the + # piece of data. + def initialize(data_old, data_new, piece, flag_context, file_length_difference) + # At first, a hunk will have just one Block in it + @blocks = [Diff::LCS::Block.new(piece)] + + if @blocks[0].remove.empty? && @blocks[0].insert.empty? + fail "Cannot build a hunk from #{piece.inspect}; has no add or remove actions" + end + + if String.method_defined?(:encoding) + @preferred_data_encoding = data_old.fetch(0) { data_new.fetch(0) { "" } }.encoding + end + + @data_old = data_old + @data_new = data_new + @old_empty = data_old.empty? || (data_old.size == 1 && data_old[0].empty?) + @new_empty = data_new.empty? || (data_new.size == 1 && data_new[0].empty?) + + before = after = file_length_difference + after += @blocks[0].diff_size + @file_length_difference = after # The caller must get this manually + @max_diff_size = @blocks.map { |e| e.diff_size.abs }.max + + # Save the start & end of each array. If the array doesn't exist (e.g., + # we're only adding items in this block), then figure out the line number + # based on the line number of the other file and the current difference in + # file lengths. + if @blocks[0].remove.empty? + a1 = a2 = nil + else + a1 = @blocks[0].remove[0].position + a2 = @blocks[0].remove[-1].position + end + + if @blocks[0].insert.empty? + b1 = b2 = nil + else + b1 = @blocks[0].insert[0].position + b2 = @blocks[0].insert[-1].position + end + + @start_old = a1 || (b1 - before) + @start_new = b1 || (a1 + before) + @end_old = a2 || (b2 - after) + @end_new = b2 || (a2 + after) + + self.flag_context = flag_context + end + + attr_reader :blocks + attr_reader :start_old, :start_new + attr_reader :end_old, :end_new + attr_reader :file_length_difference + + # Change the "start" and "end" fields to note that context should be added + # to this hunk. + attr_accessor :flag_context + undef :flag_context= + def flag_context=(context) # :nodoc: # standard:disable Lint/DuplicateMethods + return if context.nil? || context.zero? + + add_start = (context > @start_old) ? @start_old : context + + @start_old -= add_start + @start_new -= add_start + + old_size = @data_old.size + + add_end = + if (@end_old + context) >= old_size + old_size - @end_old - 1 + else + context + end + + @end_old += add_end + @end_new += add_end + end + + # Merges this hunk and the provided hunk together if they overlap. Returns + # a truthy value so that if there is no overlap, you can know the merge + # was skipped. + def merge(hunk) + return unless overlaps?(hunk) + + @start_old = hunk.start_old + @start_new = hunk.start_new + blocks.unshift(*hunk.blocks) + end + alias_method :unshift, :merge + + # Determines whether there is an overlap between this hunk and the + # provided hunk. This will be true if the difference between the two hunks + # start or end positions is within one position of each other. + def overlaps?(hunk) + hunk and (((@start_old - hunk.end_old) <= 1) or + ((@start_new - hunk.end_new) <= 1)) + end + + # Returns a diff string based on a format. + def diff(format, last = false) + case format + when :old + old_diff(last) + when :unified + unified_diff(last) + when :context + context_diff(last) + when :ed + self + when :reverse_ed, :ed_finish + ed_diff(format, last) + else + fail "Unknown diff format #{format}." + end + end + + # Note that an old diff can't have any context. Therefore, we know that + # there's only one block in the hunk. + def old_diff(last = false) + warn "Expecting only one block in an old diff hunk!" if @blocks.size > 1 + + block = @blocks[0] + + if last + old_missing_newline = !@old_empty && missing_last_newline?(@data_old) + new_missing_newline = !@new_empty && missing_last_newline?(@data_new) + end + + # Calculate item number range. Old diff range is just like a context + # diff range, except the ranges are on one line with the action between + # them. + s = encode("#{context_range(:old, ",")}#{OLD_DIFF_OP_ACTION[block.op]}#{context_range(:new, ",")}\n") + # If removing anything, just print out all the remove lines in the hunk + # which is just all the remove lines in the block. + unless block.remove.empty? + @data_old[@start_old..@end_old].each { |e| s << encode("< ") + e.chomp + encode("\n") } + end + + s << encode("\\ No newline at end of file\n") if old_missing_newline && !new_missing_newline + s << encode("---\n") if block.op == "!" + + unless block.insert.empty? + @data_new[@start_new..@end_new].each { |e| s << encode("> ") + e.chomp + encode("\n") } + end + + s << encode("\\ No newline at end of file\n") if new_missing_newline && !old_missing_newline + + s + end + private :old_diff + + def unified_diff(last = false) + # Calculate item number range. + s = encode("@@ -#{unified_range(:old)} +#{unified_range(:new)} @@\n") + + # Outlist starts containing the hunk of the old file. Removing an item + # just means putting a '-' in front of it. Inserting an item requires + # getting it from the new file and splicing it in. We splice in + # +num_added+ items. Remove blocks use +num_added+ because splicing + # changed the length of outlist. + # + # We remove +num_removed+ items. Insert blocks use +num_removed+ + # because their item numbers -- corresponding to positions in the NEW + # file -- don't take removed items into account. + lo, hi, num_added, num_removed = @start_old, @end_old, 0, 0 + + # standard:disable Performance/UnfreezeString + outlist = @data_old[lo..hi].map { |e| String.new("#{encode(" ")}#{e.chomp}") } + # standard:enable Performance/UnfreezeString + + last_block = blocks[-1] + + if last + old_missing_newline = !@old_empty && missing_last_newline?(@data_old) + new_missing_newline = !@new_empty && missing_last_newline?(@data_new) + end + + @blocks.each do |block| + block.remove.each do |item| + op = item.action.to_s # - + offset = item.position - lo + num_added + outlist[offset][0, 1] = encode(op) + num_removed += 1 + end + + if last && block == last_block && old_missing_newline && !new_missing_newline + outlist << encode('\\ No newline at end of file') + num_removed += 1 + end + + block.insert.each do |item| + op = item.action.to_s # + + offset = item.position - @start_new + num_removed + outlist[offset, 0] = encode(op) + @data_new[item.position].chomp + num_added += 1 + end + end + + outlist << encode('\\ No newline at end of file') if last && new_missing_newline + + s << outlist.join(encode("\n")) + + s + end + private :unified_diff + + def context_diff(last = false) + s = encode("***************\n") + s << encode("*** #{context_range(:old, ",")} ****\n") + r = context_range(:new, ",") + + if last + old_missing_newline = missing_last_newline?(@data_old) + new_missing_newline = missing_last_newline?(@data_new) + end + + # Print out file 1 part for each block in context diff format if there + # are any blocks that remove items + lo, hi = @start_old, @end_old + removes = @blocks.reject { |e| e.remove.empty? } + + unless removes.empty? + # standard:disable Performance/UnfreezeString + outlist = @data_old[lo..hi].map { |e| String.new("#{encode(" ")}#{e.chomp}") } + # standard:enable Performance/UnfreezeString + + last_block = removes[-1] + + removes.each do |block| + block.remove.each do |item| + outlist[item.position - lo][0, 1] = encode(block.op) # - or ! + end + + if last && block == last_block && old_missing_newline + outlist << encode('\\ No newline at end of file') + end + end + + s << outlist.join(encode("\n")) << encode("\n") + end + + s << encode("--- #{r} ----\n") + lo, hi = @start_new, @end_new + inserts = @blocks.reject { |e| e.insert.empty? } + + unless inserts.empty? + # standard:disable Performance/UnfreezeString + outlist = @data_new[lo..hi].map { |e| String.new("#{encode(" ")}#{e.chomp}") } + # standard:enable Performance/UnfreezeString + + last_block = inserts[-1] + + inserts.each do |block| + block.insert.each do |item| + outlist[item.position - lo][0, 1] = encode(block.op) # + or ! + end + + if last && block == last_block && new_missing_newline + outlist << encode('\\ No newline at end of file') + end + end + s << outlist.join(encode("\n")) + end + + s + end + private :context_diff + + def ed_diff(format, last) + warn "Expecting only one block in an old diff hunk!" if @blocks.size > 1 + if last + # ed script doesn't support well incomplete lines + warn ": No newline at end of file\n" if !@old_empty && missing_last_newline?(@data_old) + warn ": No newline at end of file\n" if !@new_empty && missing_last_newline?(@data_new) + + if @blocks[0].op == "!" + return +"" if @blocks[0].changes[0].element == @blocks[0].changes[1].element + "\n" + return +"" if @blocks[0].changes[0].element + "\n" == @blocks[0].changes[1].element + end + end + + s = + if format == :reverse_ed + encode("#{ED_DIFF_OP_ACTION[@blocks[0].op]}#{context_range(:old, " ")}\n") + else + encode("#{context_range(:old, ",")}#{ED_DIFF_OP_ACTION[@blocks[0].op]}\n") + end + + unless @blocks[0].insert.empty? + @data_new[@start_new..@end_new].each do |e| + s << e.chomp + encode("\n") + end + s << encode(".\n") + end + s + end + private :ed_diff + + # Generate a range of item numbers to print. Only print 1 number if the + # range has only one item in it. Otherwise, it's 'start,end' + def context_range(mode, op) + case mode + when :old + s, e = (@start_old + 1), (@end_old + 1) + when :new + s, e = (@start_new + 1), (@end_new + 1) + end + + (s < e) ? "#{s}#{op}#{e}" : e.to_s + end + private :context_range + + # Generate a range of item numbers to print for unified diff. Print number + # where block starts, followed by number of lines in the block + # (don't print number of lines if it's 1) + def unified_range(mode) + case mode + when :old + return "0,0" if @old_empty + s, e = (@start_old + 1), (@end_old + 1) + when :new + return "0,0" if @new_empty + s, e = (@start_new + 1), (@end_new + 1) + end + + length = e - s + 1 + + (length <= 1) ? e.to_s : "#{s},#{length}" + end + private :unified_range + + def missing_last_newline?(data) + newline = encode("\n") + + if data[-2] + data[-2].end_with?(newline) && !data[-1].end_with?(newline) + elsif data[-1] + !data[-1].end_with?(newline) + else + true + end + end + + if String.method_defined?(:encoding) + def encode(literal, target_encoding = @preferred_data_encoding) + literal.encode target_encoding + end + + def encode_as(string, *args) + args.map { |arg| arg.encode(string.encoding) } + end + else + def encode(literal, _target_encoding = nil) + literal + end + + def encode_as(_string, *args) + args + end + end + + private :encode + private :encode_as +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/internals.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/internals.rb new file mode 100644 index 00000000..8a9160a6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/internals.rb @@ -0,0 +1,308 @@ +# frozen_string_literal: true + +class << Diff::LCS + def diff_traversal(method, seq1, seq2, callbacks, &block) + callbacks = callbacks_for(callbacks) + case method + when :diff + traverse_sequences(seq1, seq2, callbacks) + when :sdiff + traverse_balanced(seq1, seq2, callbacks) + end + callbacks.finish if callbacks.respond_to? :finish + + if block + callbacks.diffs.map do |hunk| + if hunk.is_a? Array + hunk.map { |hunk_block| block[hunk_block] } + else + block[hunk] + end + end + else + callbacks.diffs + end + end + private :diff_traversal +end + +module Diff::LCS::Internals # :nodoc: +end + +class << Diff::LCS::Internals + # Compute the longest common subsequence between the sequenced + # Enumerables +a+ and +b+. The result is an array whose contents is such + # that + # + # result = Diff::LCS::Internals.lcs(a, b) + # result.each_with_index do |e, i| + # assert_equal(a[i], b[e]) unless e.nil? + # end + def lcs(a, b) + a_start = b_start = 0 + a_finish = a.size - 1 + b_finish = b.size - 1 + vector = [] + + # Collect any common elements at the beginning... + while (a_start <= a_finish) && (b_start <= b_finish) && (a[a_start] == b[b_start]) + vector[a_start] = b_start + a_start += 1 + b_start += 1 + end + + # Now the end... + while (a_start <= a_finish) && (b_start <= b_finish) && (a[a_finish] == b[b_finish]) + vector[a_finish] = b_finish + a_finish -= 1 + b_finish -= 1 + end + + # Now, compute the equivalence classes of positions of elements. + # An explanation for how this works: https://codeforces.com/topic/92191 + b_matches = position_hash(b, b_start..b_finish) + + thresh = [] + links = [] + string = a.is_a?(String) + + (a_start..a_finish).each do |i| + ai = string ? a[i, 1] : a[i] + bm = b_matches[ai] + k = nil + bm.reverse_each do |j| + # Although the threshold check is not mandatory for this to work, + # it may have an optimization purpose + # An attempt to remove it: https://github.com/halostatue/diff-lcs/pull/72 + # Why it is reintroduced: https://github.com/halostatue/diff-lcs/issues/78 + if k && (thresh[k] > j) && (thresh[k - 1] < j) + thresh[k] = j + else + k = replace_next_larger(thresh, j, k) + end + links[k] = [k.positive? ? links[k - 1] : nil, i, j] unless k.nil? + end + end + + unless thresh.empty? + link = links[thresh.size - 1] + until link.nil? + vector[link[1]] = link[2] + link = link[0] + end + end + + vector + end + + # This method will analyze the provided patchset to provide a single-pass + # normalization (conversion of the array form of Diff::LCS::Change objects to + # the object form of same) and detection of whether the patchset represents + # changes to be made. + def analyze_patchset(patchset, depth = 0) + fail "Patchset too complex" if depth > 1 + + has_changes = false + new_patchset = [] + + # Format: + # [ # patchset + # # hunk (change) + # [ # hunk + # # change + # ] + # ] + + patchset.each do |hunk| + case hunk + when Diff::LCS::Change + has_changes ||= !hunk.unchanged? + new_patchset << hunk + when Array + # Detect if the 'hunk' is actually an array-format change object. + if Diff::LCS::Change.valid_action? hunk[0] + hunk = Diff::LCS::Change.from_a(hunk) + has_changes ||= !hunk.unchanged? + new_patchset << hunk + else + with_changes, hunk = analyze_patchset(hunk, depth + 1) + has_changes ||= with_changes + new_patchset.concat(hunk) + end + else + fail ArgumentError, "Cannot normalise a hunk of class #{hunk.class}." + end + end + + [has_changes, new_patchset] + end + + # Examine the patchset and the source to see in which direction the + # patch should be applied. + # + # WARNING: By default, this examines the whole patch, so this could take + # some time. This also works better with Diff::LCS::ContextChange or + # Diff::LCS::Change as its source, as an array will cause the creation + # of one of the above. + def intuit_diff_direction(src, patchset, limit = nil) + string = src.is_a?(String) + count = left_match = left_miss = right_match = right_miss = 0 + + patchset.each do |change| + count += 1 + + case change + when Diff::LCS::ContextChange + le = string ? src[change.old_position, 1] : src[change.old_position] + re = string ? src[change.new_position, 1] : src[change.new_position] + + case change.action + when "-" # Remove details from the old string + if le == change.old_element + left_match += 1 + else + left_miss += 1 + end + when "+" + if re == change.new_element + right_match += 1 + else + right_miss += 1 + end + when "=" + left_miss += 1 if le != change.old_element + right_miss += 1 if re != change.new_element + when "!" + if le == change.old_element + left_match += 1 + elsif re == change.new_element + right_match += 1 + else + left_miss += 1 + right_miss += 1 + end + end + when Diff::LCS::Change + # With a simplistic change, we can't tell the difference between + # the left and right on '!' actions, so we ignore those. On '=' + # actions, if there's a miss, we miss both left and right. + element = string ? src[change.position, 1] : src[change.position] + + case change.action + when "-" + if element == change.element + left_match += 1 + else + left_miss += 1 + end + when "+" + if element == change.element + right_match += 1 + else + right_miss += 1 + end + when "=" + if element != change.element + left_miss += 1 + right_miss += 1 + end + end + end + + break if !limit.nil? && (count > limit) + end + + no_left = left_match.zero? && left_miss.positive? + no_right = right_match.zero? && right_miss.positive? + + case [no_left, no_right] + when [false, true] + :patch + when [true, false] + :unpatch + else + case left_match <=> right_match + when 1 + if left_miss.zero? + :patch + else + :unpatch + end + when -1 + if right_miss.zero? + :unpatch + else + :patch + end + else + fail "The provided patchset does not appear to apply to the provided \ +enumerable as either source or destination value." + end + end + end + + # Find the place at which +value+ would normally be inserted into the + # Enumerable. If that place is already occupied by +value+, do nothing + # and return +nil+. If the place does not exist (i.e., it is off the end + # of the Enumerable), add it to the end. Otherwise, replace the element + # at that point with +value+. It is assumed that the Enumerable's values + # are numeric. + # + # This operation preserves the sort order. + def replace_next_larger(enum, value, last_index = nil) + # Off the end? + if enum.empty? || (value > enum[-1]) + enum << value + return enum.size - 1 + end + + # Binary search for the insertion point + last_index ||= enum.size - 1 + first_index = 0 + while first_index <= last_index + i = (first_index + last_index) >> 1 + + found = enum[i] + + return nil if value == found + + if value > found + first_index = i + 1 + else + last_index = i - 1 + end + end + + # The insertion point is in first_index; overwrite the next larger + # value. + enum[first_index] = value + first_index + end + private :replace_next_larger + + # If +vector+ maps the matching elements of another collection onto this + # Enumerable, compute the inverse of +vector+ that maps this Enumerable + # onto the collection. (Currently unused.) + def inverse_vector(a, vector) + inverse = a.dup + (0...vector.size).each do |i| + inverse[vector[i]] = i unless vector[i].nil? + end + inverse + end + private :inverse_vector + + # Returns a hash mapping each element of an Enumerable to the set of + # positions it occupies in the Enumerable, optionally restricted to the + # elements specified in the range of indexes specified by +interval+. + def position_hash(enum, interval) + string = enum.is_a?(String) + hash = Hash.new { |h, k| h[k] = [] } + interval.each do |i| + k = string ? enum[i, 1] : enum[i] + hash[k] << i + end + hash + end + private :position_hash +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/ldiff.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/ldiff.rb new file mode 100644 index 00000000..6442c9bf --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/ldiff.rb @@ -0,0 +1,189 @@ +# frozen_string_literal: true + +require "optparse" +require "diff/lcs/hunk" + +class Diff::LCS::Ldiff # :nodoc: + # standard:disable Layout/HeredocIndentation + BANNER = <<-COPYRIGHT +ldiff #{Diff::LCS::VERSION} + Copyright 2004-2025 Austin Ziegler + + Part of Diff::LCS. + https://github.com/halostatue/diff-lcs + + This program is free software. It may be redistributed and/or modified under + the terms of the GPL version 2 (or later), the Perl Artistic licence, or the + MIT licence. + COPYRIGHT + # standard:enable Layout/HeredocIndentation + + InputInfo = Struct.new(:filename, :data, :stat) do + def initialize(filename) + super(filename, ::File.read(filename), ::File.stat(filename)) + end + end + + attr_reader :format, :lines # :nodoc: + attr_reader :file_old, :file_new # :nodoc: + attr_reader :data_old, :data_new # :nodoc: + + def self.run(args, input = $stdin, output = $stdout, error = $stderr) # :nodoc: + new.run(args, input, output, error) + end + + def initialize + @binary = nil + @format = :old + @lines = 0 + end + + def run(args, _input = $stdin, output = $stdout, error = $stderr) # :nodoc: + args.options do |o| + o.banner = "Usage: #{File.basename($0)} [options] oldfile newfile" + o.separator "" + o.on( + "-c", "-C", "--context [LINES]", Integer, + "Displays a context diff with LINES lines", "of context. Default 3 lines." + ) do |ctx| + @format = :context + @lines = ctx || 3 + end + o.on( + "-u", "-U", "--unified [LINES]", Integer, + "Displays a unified diff with LINES lines", "of context. Default 3 lines." + ) do |ctx| + @format = :unified + @lines = ctx || 3 + end + o.on("-e", "Creates an 'ed' script to change", "oldfile to newfile.") do |_ctx| + @format = :ed + end + o.on("-f", "Creates an 'ed' script to change", "oldfile to newfile in reverse order.") do |_ctx| + @format = :reverse_ed + end + o.on( + "-a", "--text", + "Treat the files as text and compare them", "line-by-line, even if they do not seem", "to be text." + ) do |_txt| + @binary = false + end + o.on("--binary", "Treats the files as binary.") do |_bin| + @binary = true + end + o.on("-q", "--brief", "Report only whether or not the files", "differ, not the details.") do |_ctx| + @format = :report + end + o.on_tail("--help", "Shows this text.") do + error << o + return 0 + end + o.on_tail("--version", "Shows the version of Diff::LCS.") do + error << Diff::LCS::Ldiff::BANNER + return 0 + end + o.on_tail "" + o.on_tail 'By default, runs produces an "old-style" diff, with output like UNIX diff.' + o.parse! + end + + unless args.size == 2 + error << args.options + return 127 + end + + # Defaults are for old-style diff + @format ||= :old + @lines ||= 0 + + file_old, file_new = *ARGV + diff?( + InputInfo.new(file_old), + InputInfo.new(file_new), + @format, + output, + binary: @binary, + lines: @lines + ) ? 1 : 0 + end + + def diff?(info_old, info_new, format, output, binary: nil, lines: 0) + case format + when :context + char_old = "*" * 3 + char_new = "-" * 3 + when :unified + char_old = "-" * 3 + char_new = "+" * 3 + end + + # After we've read up to a certain point in each file, the number of + # items we've read from each file will differ by FLD (could be 0). + file_length_difference = 0 + + # Test binary status + if binary.nil? + old_bin = info_old.data[0, 4096].include?("\0") + new_bin = info_new.data[0, 4096].include?("\0") + binary = old_bin || new_bin + end + + # diff yields lots of pieces, each of which is basically a Block object + if binary + has_diffs = (info_old.data != info_new.data) + if format != :report + if has_diffs + output << "Binary files #{info_old.filename} and #{info_new.filename} differ\n" + return true + end + return false + end + else + data_old = info_old.data.lines.to_a + data_new = info_new.data.lines.to_a + diffs = Diff::LCS.diff(data_old, data_new) + return false if diffs.empty? + end + + case format + when :report + output << "Files #{info_old.filename} and #{info_new.filename} differ\n" + return true + when :unified, :context + ft = info_old.stat.mtime.localtime.strftime("%Y-%m-%d %H:%M:%S.000000000 %z") + output << "#{char_old} #{info_old.filename}\t#{ft}\n" + ft = info_new.stat.mtime.localtime.strftime("%Y-%m-%d %H:%M:%S.000000000 %z") + output << "#{char_new} #{info_new.filename}\t#{ft}\n" + when :ed + real_output = output + output = [] + end + + # Loop over hunks. If a hunk overlaps with the last hunk, join them. + # Otherwise, print out the old one. + oldhunk = hunk = nil + diffs.each do |piece| + begin + hunk = Diff::LCS::Hunk.new(data_old, data_new, piece, lines, file_length_difference) + file_length_difference = hunk.file_length_difference + + next unless oldhunk + next if lines.positive? && hunk.merge(oldhunk) + + output << oldhunk.diff(format) + output << "\n" if format == :unified + ensure + oldhunk = hunk + end + end + + last = oldhunk.diff(format, true) + last << "\n" unless last.is_a?(Diff::LCS::Hunk) || last.empty? || last.end_with?("\n") + + output << last + + output.reverse_each { |e| real_output << e.diff(:ed_finish, e == output[0]) } if format == :ed + + true + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/string.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/string.rb new file mode 100644 index 00000000..9ab32e92 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/string.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class String + include Diff::LCS +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/version.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/version.rb new file mode 100644 index 00000000..82830e3c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/lib/diff/lcs/version.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Diff + module LCS + VERSION = "1.6.2" + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/mise.toml b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/mise.toml new file mode 100644 index 00000000..22418cf1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/mise.toml @@ -0,0 +1,5 @@ +[tools] +ruby = "3.4" + +[env] +MAINTENANCE = "true" diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/change_spec.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/change_spec.rb new file mode 100644 index 00000000..42533ae0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/change_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Diff::LCS::Change do + describe "an add" do + subject { described_class.new("+", 0, "element") } + it { should_not be_deleting } + it { should be_adding } + it { should_not be_unchanged } + it { should_not be_changed } + it { should_not be_finished_a } + it { should_not be_finished_b } + end + + describe "a delete" do + subject { described_class.new("-", 0, "element") } + it { should be_deleting } + it { should_not be_adding } + it { should_not be_unchanged } + it { should_not be_changed } + it { should_not be_finished_a } + it { should_not be_finished_b } + end + + describe "an unchanged" do + subject { described_class.new("=", 0, "element") } + it { should_not be_deleting } + it { should_not be_adding } + it { should be_unchanged } + it { should_not be_changed } + it { should_not be_finished_a } + it { should_not be_finished_b } + end + + describe "a changed" do + subject { described_class.new("!", 0, "element") } + it { should_not be_deleting } + it { should_not be_adding } + it { should_not be_unchanged } + it { should be_changed } + it { should_not be_finished_a } + it { should_not be_finished_b } + end + + describe "a finished_a" do + subject { described_class.new(">", 0, "element") } + it { should_not be_deleting } + it { should_not be_adding } + it { should_not be_unchanged } + it { should_not be_changed } + it { should be_finished_a } + it { should_not be_finished_b } + end + + describe "a finished_b" do + subject { described_class.new("<", 0, "element") } + it { should_not be_deleting } + it { should_not be_adding } + it { should_not be_unchanged } + it { should_not be_changed } + it { should_not be_finished_a } + it { should be_finished_b } + end + + describe "as array" do + it "should be converted" do + action, position, element = described_class.new("!", 0, "element") + expect(action).to eq "!" + expect(position).to eq 0 + expect(element).to eq "element" + end + end +end + +describe Diff::LCS::ContextChange do + describe "as array" do + it "should be converted" do + action, (old_position, old_element), (new_position, new_element) = + described_class.new("!", 1, "old_element", 2, "new_element") + + expect(action).to eq "!" + expect(old_position).to eq 1 + expect(old_element).to eq "old_element" + expect(new_position).to eq 2 + expect(new_element).to eq "new_element" + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/diff_spec.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/diff_spec.rb new file mode 100644 index 00000000..869f0986 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/diff_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Diff::LCS, ".diff" do + include Diff::LCS::SpecHelper::Matchers + + it "correctly diffs seq1 to seq2" do + diff_s1_s2 = Diff::LCS.diff(seq1, seq2) + expect(change_diff(correct_forward_diff)).to eq(diff_s1_s2) + end + + it "correctly diffs seq2 to seq1" do + diff_s2_s1 = Diff::LCS.diff(seq2, seq1) + expect(change_diff(correct_backward_diff)).to eq(diff_s2_s1) + end + + it "correctly diffs against an empty sequence" do + diff = Diff::LCS.diff(word_sequence, []) + correct_diff = [ + [ + ["-", 0, "abcd"], + ["-", 1, "efgh"], + ["-", 2, "ijkl"], + ["-", 3, "mnopqrstuvwxyz"] + ] + ] + + expect(change_diff(correct_diff)).to eq(diff) + + diff = Diff::LCS.diff([], word_sequence) + correct_diff.each do |hunk| + hunk.each { |change| change[0] = "+" } + end + expect(change_diff(correct_diff)).to eq(diff) + end + + it "correctly diffs 'xx' and 'xaxb'" do + left = "xx" + right = "xaxb" + expect(Diff::LCS.patch(left, Diff::LCS.diff(left, right))).to eq(right) + end + + it "returns an empty diff with (hello, hello)" do + expect(Diff::LCS.diff(hello, hello)).to be_empty + end + + it "returns an empty diff with (hello_ary, hello_ary)" do + expect(Diff::LCS.diff(hello_ary, hello_ary)).to be_empty + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/123_x b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/123_x new file mode 100644 index 00000000..cd34c235 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/123_x @@ -0,0 +1,2 @@ +123 +x diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/456_x b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/456_x new file mode 100644 index 00000000..9a823ac3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/456_x @@ -0,0 +1,2 @@ +456 +x diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/aX b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/aX new file mode 100644 index 00000000..5765d6a7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/aX @@ -0,0 +1 @@ +aX diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/bXaX b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/bXaX new file mode 100644 index 00000000..a1c813db --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/bXaX @@ -0,0 +1 @@ +bXaX diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ds1.csv b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ds1.csv new file mode 100644 index 00000000..9ac84281 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ds1.csv @@ -0,0 +1,50 @@ +1,3 +2,7 +3,13 +4,21 +5,31 +6,43 +7,57 +8,73 +9,91 +10,111 +11,133 +12,157 +13,183 +14,211 +15,241 +16,273 +17,307 +18,343 +19,381 +20,421 +21,463 +22,507 +23,553 +24,601 +25,651 +26,703 +27,757 +28,813 +29,871 +30,931 +31,993 +32,1057 +33,1123 +34,1191 +35,1261 +36,1333 +37,1407 +38,1483 +39,1561 +40,1641 +41,1723 +42,1807 +43,1893 +44,1981 +45,2071 +46,2163 +47,2257 +48,2353 +49,2451 +50,2500 \ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ds2.csv b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ds2.csv new file mode 100644 index 00000000..797de761 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ds2.csv @@ -0,0 +1,51 @@ + 1,3 +2,7 +3,13 +4,21 +5,31 +6,42 +7,57 +8,73 +9,91 +10,111 +11,133 +12,157 +13,183 +14,211 +15,241 +16,273 +17,307 +18,343 +19,200 +20,421 +21,463 +22,507 +23,553 +24,601 +25,651 +26,703 +27,757 +28,813 +29,871 +30,931 +31,123 +32,1057 +33,1123 +34,1000 +35,1261 +36,1333 +37,1407 +38,1483 +39,1561 +40,1641 +41,1723 +42,1807 +43,1893 +44,1981 +45,2071 +46,2163 +47,1524 +48,2353 +49,2451 +50,2500 +51,2520 diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/empty b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/empty new file mode 100644 index 00000000..e69de29b diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/file1.bin b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/file1.bin new file mode 100644 index 00000000..f76dd238 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/file1.bin differ diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/file2.bin b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/file2.bin new file mode 100644 index 00000000..ba18e3dc Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/file2.bin differ diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/four_lines b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/four_lines new file mode 100644 index 00000000..f384549c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/four_lines @@ -0,0 +1,4 @@ +one +two +three +four diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/four_lines_with_missing_new_line b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/four_lines_with_missing_new_line new file mode 100644 index 00000000..c40a3bdd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/four_lines_with_missing_new_line @@ -0,0 +1,4 @@ +one +two +three +four \ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/diff.missing_new_line1-e b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/diff.missing_new_line1-e new file mode 100644 index 00000000..1e8a89cd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/diff.missing_new_line1-e @@ -0,0 +1 @@ +No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/diff.missing_new_line1-f b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/diff.missing_new_line1-f new file mode 100644 index 00000000..1e8a89cd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/diff.missing_new_line1-f @@ -0,0 +1 @@ +No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/diff.missing_new_line2-e b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/diff.missing_new_line2-e new file mode 100644 index 00000000..1e8a89cd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/diff.missing_new_line2-e @@ -0,0 +1 @@ +No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/diff.missing_new_line2-f b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/diff.missing_new_line2-f new file mode 100644 index 00000000..1e8a89cd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/diff.missing_new_line2-f @@ -0,0 +1 @@ +No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/error.diff.chef-e b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/error.diff.chef-e new file mode 100644 index 00000000..8ed03195 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/error.diff.chef-e @@ -0,0 +1,2 @@ +: No newline at end of file +: No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/error.diff.chef-f b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/error.diff.chef-f new file mode 100644 index 00000000..8ed03195 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/error.diff.chef-f @@ -0,0 +1,2 @@ +: No newline at end of file +: No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/error.diff.missing_new_line1-e b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/error.diff.missing_new_line1-e new file mode 100644 index 00000000..397dd5b9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/error.diff.missing_new_line1-e @@ -0,0 +1 @@ +: No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/error.diff.missing_new_line1-f b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/error.diff.missing_new_line1-f new file mode 100644 index 00000000..397dd5b9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/error.diff.missing_new_line1-f @@ -0,0 +1 @@ +: No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/error.diff.missing_new_line2-e b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/error.diff.missing_new_line2-e new file mode 100644 index 00000000..f9493ef9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/error.diff.missing_new_line2-e @@ -0,0 +1 @@ +: No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/error.diff.missing_new_line2-f b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/error.diff.missing_new_line2-f new file mode 100644 index 00000000..f9493ef9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/error.diff.missing_new_line2-f @@ -0,0 +1 @@ +: No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff new file mode 100644 index 00000000..fa1a3479 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff @@ -0,0 +1,4 @@ +1c1 +< aX +--- +> bXaX diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff-c b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff-c new file mode 100644 index 00000000..0e1ad998 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff-c @@ -0,0 +1,7 @@ +*** spec/fixtures/aX 2020-06-23 11:15:32.000000000 -0400 +--- spec/fixtures/bXaX 2020-06-23 11:15:32.000000000 -0400 +*************** +*** 1 **** +! aX +--- 1 ---- +! bXaX diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff-e b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff-e new file mode 100644 index 00000000..13e0f7f0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff-e @@ -0,0 +1,3 @@ +1c +bXaX +. diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff-f b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff-f new file mode 100644 index 00000000..77710c76 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff-f @@ -0,0 +1,3 @@ +c1 +bXaX +. diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff-u b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff-u new file mode 100644 index 00000000..b84f7180 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff-u @@ -0,0 +1,5 @@ +--- spec/fixtures/aX 2020-06-23 11:15:32.000000000 -0400 ++++ spec/fixtures/bXaX 2020-06-23 11:15:32.000000000 -0400 +@@ -1 +1 @@ +-aX ++bXaX diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin1 b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin1 new file mode 100644 index 00000000..e69de29b diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin1-c b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin1-c new file mode 100644 index 00000000..e69de29b diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin1-e b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin1-e new file mode 100644 index 00000000..e69de29b diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin1-f b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin1-f new file mode 100644 index 00000000..e69de29b diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin1-u b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin1-u new file mode 100644 index 00000000..e69de29b diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin2 b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin2 new file mode 100644 index 00000000..41b625cd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin2 @@ -0,0 +1 @@ +Binary files spec/fixtures/file1.bin and spec/fixtures/file2.bin differ diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin2-c b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin2-c new file mode 100644 index 00000000..41b625cd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin2-c @@ -0,0 +1 @@ +Binary files spec/fixtures/file1.bin and spec/fixtures/file2.bin differ diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin2-e b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin2-e new file mode 100644 index 00000000..41b625cd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin2-e @@ -0,0 +1 @@ +Binary files spec/fixtures/file1.bin and spec/fixtures/file2.bin differ diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin2-f b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin2-f new file mode 100644 index 00000000..41b625cd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin2-f @@ -0,0 +1 @@ +Binary files spec/fixtures/file1.bin and spec/fixtures/file2.bin differ diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin2-u b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin2-u new file mode 100644 index 00000000..41b625cd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.bin2-u @@ -0,0 +1 @@ +Binary files spec/fixtures/file1.bin and spec/fixtures/file2.bin differ diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef new file mode 100644 index 00000000..8b98efb1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef @@ -0,0 +1,4 @@ +3c3 +< "description": "hi" +--- +> "description": "lo" diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef-c b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef-c new file mode 100644 index 00000000..efbfa195 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef-c @@ -0,0 +1,15 @@ +*** spec/fixtures/old-chef 2020-06-23 23:18:20.000000000 -0400 +--- spec/fixtures/new-chef 2020-06-23 23:18:20.000000000 -0400 +*************** +*** 1,4 **** + { + "name": "x", +! "description": "hi" + } +\ No newline at end of file +--- 1,4 ---- + { + "name": "x", +! "description": "lo" + } +\ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef-e b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef-e new file mode 100644 index 00000000..775d881c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef-e @@ -0,0 +1,3 @@ +3c + "description": "lo" +. diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef-f b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef-f new file mode 100644 index 00000000..9bf1e67f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef-f @@ -0,0 +1,3 @@ +c3 + "description": "lo" +. diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef-u b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef-u new file mode 100644 index 00000000..dbacd889 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef-u @@ -0,0 +1,9 @@ +--- spec/fixtures/old-chef 2020-06-23 23:18:20.000000000 -0400 ++++ spec/fixtures/new-chef 2020-06-23 23:18:20.000000000 -0400 +@@ -1,4 +1,4 @@ + { + "name": "x", +- "description": "hi" ++ "description": "lo" + } +\ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef2 b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef2 new file mode 100644 index 00000000..496b3dc8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef2 @@ -0,0 +1,7 @@ +2d1 +< recipe[b::default] +14a14,17 +> recipe[o::new] +> recipe[p::new] +> recipe[q::new] +> recipe[r::new] diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef2-c b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef2-c new file mode 100644 index 00000000..8349a7a8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef2-c @@ -0,0 +1,20 @@ +*** spec/fixtures/old-chef2 2020-06-30 09:43:35.000000000 -0400 +--- spec/fixtures/new-chef2 2020-06-30 09:44:32.000000000 -0400 +*************** +*** 1,5 **** + recipe[a::default] +- recipe[b::default] + recipe[c::default] + recipe[d::default] + recipe[e::default] +--- 1,4 ---- +*************** +*** 12,14 **** +--- 11,17 ---- + recipe[l::default] + recipe[m::default] + recipe[n::default] ++ recipe[o::new] ++ recipe[p::new] ++ recipe[q::new] ++ recipe[r::new] diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef2-d b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef2-d new file mode 100644 index 00000000..ca32a490 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef2-d @@ -0,0 +1,7 @@ +d2 +a14 +recipe[o::new] +recipe[p::new] +recipe[q::new] +recipe[r::new] +. diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef2-e b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef2-e new file mode 100644 index 00000000..89f3fa07 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef2-e @@ -0,0 +1,7 @@ +14a +recipe[o::new] +recipe[p::new] +recipe[q::new] +recipe[r::new] +. +2d diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef2-f b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef2-f new file mode 100644 index 00000000..ca32a490 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef2-f @@ -0,0 +1,7 @@ +d2 +a14 +recipe[o::new] +recipe[p::new] +recipe[q::new] +recipe[r::new] +. diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef2-u b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef2-u new file mode 100644 index 00000000..ef025c7e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.chef2-u @@ -0,0 +1,16 @@ +--- spec/fixtures/old-chef2 2020-06-30 09:43:35.000000000 -0400 ++++ spec/fixtures/new-chef2 2020-06-30 09:44:32.000000000 -0400 +@@ -1,5 +1,4 @@ + recipe[a::default] +-recipe[b::default] + recipe[c::default] + recipe[d::default] + recipe[e::default] +@@ -12,3 +11,7 @@ + recipe[l::default] + recipe[m::default] + recipe[n::default] ++recipe[o::new] ++recipe[p::new] ++recipe[q::new] ++recipe[r::new] diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.empty.vs.four_lines b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.empty.vs.four_lines new file mode 100644 index 00000000..e2afc316 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.empty.vs.four_lines @@ -0,0 +1,5 @@ +0a1,4 +> one +> two +> three +> four diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-c b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-c new file mode 100644 index 00000000..be0e8274 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-c @@ -0,0 +1,9 @@ +*** spec/fixtures/empty 2025-01-31 12:14:52.856031635 +0100 +--- spec/fixtures/four_lines 2025-01-31 12:13:45.476036544 +0100 +*************** +*** 0 **** +--- 1,4 ---- ++ one ++ two ++ three ++ four diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-e b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-e new file mode 100644 index 00000000..f8f92feb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-e @@ -0,0 +1,6 @@ +0a +one +two +three +four +. diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-f b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-f new file mode 100644 index 00000000..f02e5a03 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-f @@ -0,0 +1,6 @@ +a0 +one +two +three +four +. diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-u b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-u new file mode 100644 index 00000000..60bd55cc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-u @@ -0,0 +1,7 @@ +--- spec/fixtures/empty 2025-01-31 12:14:52.856031635 +0100 ++++ spec/fixtures/four_lines 2025-01-31 12:13:45.476036544 +0100 +@@ -0,0 +1,4 @@ ++one ++two ++three ++four diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.four_lines.vs.empty b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.four_lines.vs.empty new file mode 100644 index 00000000..67d0a584 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.four_lines.vs.empty @@ -0,0 +1,5 @@ +1,4d0 +< one +< two +< three +< four diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-c b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-c new file mode 100644 index 00000000..b216344d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-c @@ -0,0 +1,9 @@ +*** spec/fixtures/four_lines 2025-01-31 12:13:45.476036544 +0100 +--- spec/fixtures/empty 2025-01-31 12:14:52.856031635 +0100 +*************** +*** 1,4 **** +- one +- two +- three +- four +--- 0 ---- diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-e b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-e new file mode 100644 index 00000000..c821d7c9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-e @@ -0,0 +1 @@ +1,4d diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-f b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-f new file mode 100644 index 00000000..442bd5ab --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-f @@ -0,0 +1 @@ +d1 4 diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-u b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-u new file mode 100644 index 00000000..79e6d752 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-u @@ -0,0 +1,7 @@ +--- spec/fixtures/four_lines 2025-01-31 12:13:45.476036544 +0100 ++++ spec/fixtures/empty 2025-01-31 12:14:52.856031635 +0100 +@@ -1,4 +0,0 @@ +-one +-two +-three +-four diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.issue95_trailing_context b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.issue95_trailing_context new file mode 100644 index 00000000..4335560d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.issue95_trailing_context @@ -0,0 +1,4 @@ +1c1 +< 123 +--- +> 456 diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.issue95_trailing_context-c b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.issue95_trailing_context-c new file mode 100644 index 00000000..4b759fa6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.issue95_trailing_context-c @@ -0,0 +1,9 @@ +*** spec/fixtures/123_x 2025-01-31 17:00:17.070615716 +0100 +--- spec/fixtures/456_x 2025-01-31 16:58:26.380624827 +0100 +*************** +*** 1,2 **** +! 123 + x +--- 1,2 ---- +! 456 + x diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.issue95_trailing_context-e b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.issue95_trailing_context-e new file mode 100644 index 00000000..7a8334b3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.issue95_trailing_context-e @@ -0,0 +1,3 @@ +1c +456 +. diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.issue95_trailing_context-f b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.issue95_trailing_context-f new file mode 100644 index 00000000..97223a8c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.issue95_trailing_context-f @@ -0,0 +1,3 @@ +c1 +456 +. diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.issue95_trailing_context-u b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.issue95_trailing_context-u new file mode 100644 index 00000000..7fbf0e2e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.issue95_trailing_context-u @@ -0,0 +1,6 @@ +--- spec/fixtures/123_x 2025-01-31 17:00:17.070615716 +0100 ++++ spec/fixtures/456_x 2025-01-31 16:58:26.380624827 +0100 +@@ -1,2 +1,2 @@ +-123 ++456 + x diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line1 b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line1 new file mode 100644 index 00000000..c5cb113b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line1 @@ -0,0 +1,5 @@ +4c4 +< four +--- +> four +\ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line1-c b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line1-c new file mode 100644 index 00000000..55d1ade5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line1-c @@ -0,0 +1,14 @@ +*** spec/fixtures/four_lines 2025-01-31 12:17:43.926013315 +0100 +--- spec/fixtures/four_lines_with_missing_new_line 2025-01-31 12:17:43.926013315 +0100 +*************** +*** 1,4 **** + one + two + three +! four +--- 1,4 ---- + one + two + three +! four +\ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line1-e b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line1-e new file mode 100644 index 00000000..e69de29b diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line1-f b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line1-f new file mode 100644 index 00000000..e69de29b diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line1-u b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line1-u new file mode 100644 index 00000000..010518bf --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line1-u @@ -0,0 +1,9 @@ +--- spec/fixtures/four_lines 2025-01-31 12:17:43.926013315 +0100 ++++ spec/fixtures/four_lines_with_missing_new_line 2025-01-31 12:17:43.926013315 +0100 +@@ -1,4 +1,4 @@ + one + two + three +-four ++four +\ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line2 b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line2 new file mode 100644 index 00000000..10e43267 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line2 @@ -0,0 +1,5 @@ +4c4 +< four +\ No newline at end of file +--- +> four diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line2-c b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line2-c new file mode 100644 index 00000000..b4310305 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line2-c @@ -0,0 +1,14 @@ +*** spec/fixtures/four_lines_with_missing_new_line 2025-01-31 12:17:43.926013315 +0100 +--- spec/fixtures/four_lines 2025-01-31 12:17:43.926013315 +0100 +*************** +*** 1,4 **** + one + two + three +! four +\ No newline at end of file +--- 1,4 ---- + one + two + three +! four diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line2-e b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line2-e new file mode 100644 index 00000000..e69de29b diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line2-f b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line2-f new file mode 100644 index 00000000..e69de29b diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line2-u b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line2-u new file mode 100644 index 00000000..2481a9e0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/ldiff/output.diff.missing_new_line2-u @@ -0,0 +1,9 @@ +--- spec/fixtures/four_lines_with_missing_new_line 2025-01-31 12:17:43.926013315 +0100 ++++ spec/fixtures/four_lines 2025-01-31 12:17:43.926013315 +0100 +@@ -1,4 +1,4 @@ + one + two + three +-four +\ No newline at end of file ++four diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/new-chef b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/new-chef new file mode 100644 index 00000000..d7babfec --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/new-chef @@ -0,0 +1,4 @@ +{ + "name": "x", + "description": "lo" +} \ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/new-chef2 b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/new-chef2 new file mode 100644 index 00000000..8213c73c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/new-chef2 @@ -0,0 +1,17 @@ +recipe[a::default] +recipe[c::default] +recipe[d::default] +recipe[e::default] +recipe[f::default] +recipe[g::default] +recipe[h::default] +recipe[i::default] +recipe[j::default] +recipe[k::default] +recipe[l::default] +recipe[m::default] +recipe[n::default] +recipe[o::new] +recipe[p::new] +recipe[q::new] +recipe[r::new] diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/old-chef b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/old-chef new file mode 100644 index 00000000..5f9e38b8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/old-chef @@ -0,0 +1,4 @@ +{ + "name": "x", + "description": "hi" +} \ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/old-chef2 b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/old-chef2 new file mode 100644 index 00000000..4a23407f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/fixtures/old-chef2 @@ -0,0 +1,14 @@ +recipe[a::default] +recipe[b::default] +recipe[c::default] +recipe[d::default] +recipe[e::default] +recipe[f::default] +recipe[g::default] +recipe[h::default] +recipe[i::default] +recipe[j::default] +recipe[k::default] +recipe[l::default] +recipe[m::default] +recipe[n::default] diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/hunk_spec.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/hunk_spec.rb new file mode 100644 index 00000000..7d910399 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/hunk_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require "spec_helper" + +if String.method_defined?(:encoding) + require "diff/lcs/hunk" + + describe Diff::LCS::Hunk do + let(:old_data) { ["Tu a un carté avec {count} itéms".encode("UTF-16LE")] } + let(:new_data) { ["Tu a un carte avec {count} items".encode("UTF-16LE")] } + let(:pieces) { Diff::LCS.diff old_data, new_data } + let(:hunk) { Diff::LCS::Hunk.new(old_data, new_data, pieces[0], 3, 0) } + + it "produces a unified diff from the two pieces" do + expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp + @@ -1 +1 @@ + -Tu a un carté avec {count} itéms + +Tu a un carte avec {count} items + EXPECTED + + expect(hunk.diff(:unified)).to eq(expected) + end + + it "produces a unified diff from the two pieces (last entry)" do + expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp + @@ -1 +1 @@ + -Tu a un carté avec {count} itéms + +Tu a un carte avec {count} items + \\ No newline at end of file + EXPECTED + + expect(hunk.diff(:unified, true)).to eq(expected) + end + + it "produces a context diff from the two pieces" do + expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp + *************** + *** 1 **** + ! Tu a un carté avec {count} itéms + --- 1 ---- + ! Tu a un carte avec {count} items + EXPECTED + + expect(hunk.diff(:context)).to eq(expected) + end + + it "produces an old diff from the two pieces" do + expected = <<-EXPECTED.gsub(/^ +/, "").encode("UTF-16LE").chomp + 1c1 + < Tu a un carté avec {count} itéms + --- + > Tu a un carte avec {count} items + + EXPECTED + + expect(hunk.diff(:old)).to eq(expected) + end + + it "produces a reverse ed diff from the two pieces" do + expected = <<-EXPECTED.gsub(/^ +/, "").encode("UTF-16LE").chomp + c1 + Tu a un carte avec {count} items + . + + EXPECTED + + expect(hunk.diff(:reverse_ed)).to eq(expected) + end + + context "with empty first data set" do + let(:old_data) { [] } + + it "produces a unified diff" do + expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp + @@ -0,0 +1 @@ + +Tu a un carte avec {count} items + EXPECTED + + expect(hunk.diff(:unified)).to eq(expected) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/issues_spec.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/issues_spec.rb new file mode 100644 index 00000000..5b0fb2a1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/issues_spec.rb @@ -0,0 +1,160 @@ +# frozen_string_literal: true + +require "spec_helper" +require "diff/lcs/hunk" + +describe "Diff::LCS Issues" do + include Diff::LCS::SpecHelper::Matchers + + describe "issue #1" do + shared_examples "handles simple diffs" do |s1, s2, forward_diff| + before do + @diff_s1_s2 = Diff::LCS.diff(s1, s2) + end + + it "creates the correct diff" do + expect(change_diff(forward_diff)).to eq(@diff_s1_s2) + end + + it "creates the correct patch s1->s2" do + expect(Diff::LCS.patch(s1, @diff_s1_s2)).to eq(s2) + end + + it "creates the correct patch s2->s1" do + expect(Diff::LCS.patch(s2, @diff_s1_s2)).to eq(s1) + end + end + + describe "string" do + it_has_behavior "handles simple diffs", "aX", "bXaX", [ + [ + ["+", 0, "b"], + ["+", 1, "X"] + ] + ] + it_has_behavior "handles simple diffs", "bXaX", "aX", [ + [ + ["-", 0, "b"], + ["-", 1, "X"] + ] + ] + end + + describe "array" do + it_has_behavior "handles simple diffs", %w[a X], %w[b X a X], [ + [ + ["+", 0, "b"], + ["+", 1, "X"] + ] + ] + it_has_behavior "handles simple diffs", %w[b X a X], %w[a X], [ + [ + ["-", 0, "b"], + ["-", 1, "X"] + ] + ] + end + end + + describe "issue #57" do + it "should fail with a correct error" do + # standard:disable Style/HashSyntax + expect { + actual = {:category => "app.rack.request"} + expected = {:category => "rack.middleware", :title => "Anonymous Middleware"} + expect(actual).to eq(expected) + }.to raise_error(RSpec::Expectations::ExpectationNotMetError) + # standard:enable Style/HashSyntax + end + end + + describe "issue #65" do + def diff_lines(old_lines, new_lines) + file_length_difference = 0 + previous_hunk = nil + output = [] + + Diff::LCS.diff(old_lines, new_lines).each do |piece| + hunk = Diff::LCS::Hunk.new(old_lines, new_lines, piece, 3, file_length_difference) + file_length_difference = hunk.file_length_difference + maybe_contiguous_hunks = previous_hunk.nil? || hunk.merge(previous_hunk) + + output << "#{previous_hunk.diff(:unified)}\n" unless maybe_contiguous_hunks + + previous_hunk = hunk + end + output << "#{previous_hunk.diff(:unified, true)}\n" unless previous_hunk.nil? + output.join + end + + it "should not misplace the new chunk" do + old_data = [ + "recipe[a::default]", "recipe[b::default]", "recipe[c::default]", + "recipe[d::default]", "recipe[e::default]", "recipe[f::default]", + "recipe[g::default]", "recipe[h::default]", "recipe[i::default]", + "recipe[j::default]", "recipe[k::default]", "recipe[l::default]", + "recipe[m::default]", "recipe[n::default]" + ] + + new_data = [ + "recipe[a::default]", "recipe[c::default]", "recipe[d::default]", + "recipe[e::default]", "recipe[f::default]", "recipe[g::default]", + "recipe[h::default]", "recipe[i::default]", "recipe[j::default]", + "recipe[k::default]", "recipe[l::default]", "recipe[m::default]", + "recipe[n::default]", "recipe[o::new]", "recipe[p::new]", + "recipe[q::new]", "recipe[r::new]" + ] + + # standard:disable Layout/HeredocIndentation + expect(diff_lines(old_data, new_data)).to eq(<<-EODIFF) +@@ -1,5 +1,4 @@ + recipe[a::default] +-recipe[b::default] + recipe[c::default] + recipe[d::default] + recipe[e::default] +@@ -12,3 +11,7 @@ + recipe[l::default] + recipe[m::default] + recipe[n::default] ++recipe[o::new] ++recipe[p::new] ++recipe[q::new] ++recipe[r::new] + EODIFF + # standard:enable Layout/HeredocIndentation + end + end + + describe "issue #107 (replaces issue #60)" do + it "should produce unified output with correct context" do + # standard:disable Layout/HeredocIndentation + old_data = <<-DATA_OLD.strip.split("\n").map(&:chomp) +{ + "name": "x", + "description": "hi" +} + DATA_OLD + + new_data = <<-DATA_NEW.strip.split("\n").map(&:chomp) +{ + "name": "x", + "description": "lo" +} + DATA_NEW + + diff = ::Diff::LCS.diff(old_data, new_data) + hunk = ::Diff::LCS::Hunk.new(old_data, new_data, diff.first, 3, 0) + + expect(hunk.diff(:unified)).to eq(<<-EXPECTED.chomp) +@@ -1,4 +1,4 @@ + { + "name": "x", +- "description": "hi" ++ "description": "lo" + } + EXPECTED + # standard:enable Layout/HeredocIndentation + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/lcs_spec.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/lcs_spec.rb new file mode 100644 index 00000000..c17f22f6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/lcs_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Diff::LCS::Internals, ".lcs" do + include Diff::LCS::SpecHelper::Matchers + + it "returns a meaningful LCS array with (seq1, seq2)" do + res = Diff::LCS::Internals.lcs(seq1, seq2) + # The result of the LCS (less the +nil+ values) must be as long as the + # correct result. + expect(res.compact.size).to eq(correct_lcs.size) + expect(res).to correctly_map_sequence(seq1).to_other_sequence(seq2) + + # Compact these transformations and they should be the correct LCS. + x_seq1 = (0...res.size).map { |ix| res[ix] ? seq1[ix] : nil }.compact + x_seq2 = (0...res.size).map { |ix| res[ix] ? seq2[res[ix]] : nil }.compact + + expect(x_seq1).to eq(correct_lcs) + expect(x_seq2).to eq(correct_lcs) + end + + it "returns all indexes with (hello, hello)" do + expect(Diff::LCS::Internals.lcs(hello, hello)).to \ + eq((0...hello.size).to_a) + end + + it "returns all indexes with (hello_ary, hello_ary)" do + expect(Diff::LCS::Internals.lcs(hello_ary, hello_ary)).to \ + eq((0...hello_ary.size).to_a) + end +end + +describe Diff::LCS, ".LCS" do + include Diff::LCS::SpecHelper::Matchers + + it "returns the correct compacted values from Diff::LCS.LCS" do + res = Diff::LCS.LCS(seq1, seq2) + expect(res).to eq(correct_lcs) + expect(res.compact).to eq(res) + end + + it "is transitive" do + res = Diff::LCS.LCS(seq2, seq1) + expect(res).to eq(correct_lcs) + expect(res.compact).to eq(res) + end + + it "returns %W(h e l l o) with (hello, hello)" do + expect(Diff::LCS.LCS(hello, hello)).to eq(hello.chars) + end + + it "returns hello_ary with (hello_ary, hello_ary)" do + expect(Diff::LCS.LCS(hello_ary, hello_ary)).to eq(hello_ary) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/ldiff_spec.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/ldiff_spec.rb new file mode 100644 index 00000000..e13b5614 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/ldiff_spec.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "bin/ldiff" do + include CaptureSubprocessIO + + # standard:disable Style/HashSyntax + fixtures = [ + {:name => "diff", :left => "aX", :right => "bXaX", :diff => 1}, + {:name => "diff.missing_new_line1", :left => "four_lines", :right => "four_lines_with_missing_new_line", :diff => 1}, + {:name => "diff.missing_new_line2", :left => "four_lines_with_missing_new_line", :right => "four_lines", :diff => 1}, + {:name => "diff.issue95_trailing_context", :left => "123_x", :right => "456_x", :diff => 1}, + {:name => "diff.four_lines.vs.empty", :left => "four_lines", :right => "empty", :diff => 1}, + {:name => "diff.empty.vs.four_lines", :left => "empty", :right => "four_lines", :diff => 1}, + {:name => "diff.bin1", :left => "file1.bin", :right => "file1.bin", :diff => 0}, + {:name => "diff.bin2", :left => "file1.bin", :right => "file2.bin", :diff => 1}, + {:name => "diff.chef", :left => "old-chef", :right => "new-chef", :diff => 1}, + {:name => "diff.chef2", :left => "old-chef2", :right => "new-chef2", :diff => 1} + ].product([nil, "-e", "-f", "-c", "-u"]).map { |(fixture, flag)| + fixture = fixture.dup + fixture[:flag] = flag + fixture + } + # standard:enable Style/HashSyntax + + def self.test_ldiff(fixture) + desc = [ + fixture[:flag], + "spec/fixtures/#{fixture[:left]}", + "spec/fixtures/#{fixture[:right]}", + "#", + "=>", + "spec/fixtures/ldiff/output.#{fixture[:name]}#{fixture[:flag]}" + ].join(" ") + + it desc do + stdout, stderr, status = run_ldiff(fixture) + expect(status).to eq(fixture[:diff]) + expect(stderr).to eq(read_fixture(fixture, mode: "error", allow_missing: true)) + expect(stdout).to eq(read_fixture(fixture, mode: "output", allow_missing: false)) + end + end + + fixtures.each do |fixture| + test_ldiff(fixture) + end + + def read_fixture(options, mode: "output", allow_missing: false) + fixture = options.fetch(:name) + flag = options.fetch(:flag) + name = "spec/fixtures/ldiff/#{mode}.#{fixture}#{flag}" + + return "" if !::File.exist?(name) && allow_missing + + data = IO.__send__(IO.respond_to?(:binread) ? :binread : :read, name) + clean_data(data, flag) + end + + def clean_data(data, flag) + data = + case flag + when "-c", "-u" + clean_output_timestamp(data) + else + data + end + data.gsub(/\r\n?/, "\n") + end + + def clean_output_timestamp(data) + data.gsub( + %r{ + ^ + [-+*]{3} + \s* + spec/fixtures/(\S+) + \s* + \d{4}-\d\d-\d\d + \s* + \d\d:\d\d:\d\d(?:\.\d+) + \s* + (?:[-+]\d{4}|Z) + }x, + '*** spec/fixtures/\1 0000-00-00 :00 =>:00 =>00.000000000 -0000' + ) + end + + def run_ldiff(options) + flag = options.fetch(:flag) + left = options.fetch(:left) + right = options.fetch(:right) + + stdout, stderr = capture_subprocess_io do + system("ruby -Ilib bin/ldiff #{flag} spec/fixtures/#{left} spec/fixtures/#{right}") + end + + [clean_data(stdout, flag), stderr, $?.exitstatus] + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/patch_spec.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/patch_spec.rb new file mode 100644 index 00000000..8fc3ee25 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/patch_spec.rb @@ -0,0 +1,416 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Diff::LCS.patch" do + include Diff::LCS::SpecHelper::Matchers + + shared_examples "patch sequences correctly" do + it "correctly patches left-to-right (patch autodiscovery)" do + expect(Diff::LCS.patch(s1, patch_set)).to eq(s2) + end + + it "correctly patches left-to-right (explicit patch)" do + expect(Diff::LCS.patch(s1, patch_set, :patch)).to eq(s2) + expect(Diff::LCS.patch!(s1, patch_set)).to eq(s2) + end + + it "correctly patches right-to-left (unpatch autodiscovery)" do + expect(Diff::LCS.patch(s2, patch_set)).to eq(s1) + end + + it "correctly patches right-to-left (explicit unpatch)" do + expect(Diff::LCS.patch(s2, patch_set, :unpatch)).to eq(s1) + expect(Diff::LCS.unpatch!(s2, patch_set)).to eq(s1) + end + end + + describe "using a Diff::LCS.diff patchset" do + describe "an empty patchset returns the source" do + it "works on a string (hello)" do + diff = Diff::LCS.diff(hello, hello) + expect(Diff::LCS.patch(hello, diff)).to eq(hello) + end + + it "works on an array %W(h e l l o)" do + diff = Diff::LCS.diff(hello_ary, hello_ary) + expect(Diff::LCS.patch(hello_ary, diff)).to eq(hello_ary) + end + end + + describe "with default diff callbacks (DiffCallbacks)" do + describe "forward (s1 -> s2)" do + it_has_behavior "patch sequences correctly" do + let(:s1) { seq1 } + let(:s2) { seq2 } + let(:patch_set) { Diff::LCS.diff(seq1, seq2) } + end + end + + describe "reverse (s2 -> s1)" do + it_has_behavior "patch sequences correctly" do + let(:s1) { seq2 } + let(:s2) { seq1 } + let(:patch_set) { Diff::LCS.diff(seq2, seq1) } + end + end + end + + describe "with context diff callbacks (ContextDiffCallbacks)" do + describe "forward (s1 -> s2)" do + it_has_behavior "patch sequences correctly" do + let(:s1) { seq1 } + let(:s2) { seq2 } + let(:patch_set) { + Diff::LCS.diff(seq1, seq2, Diff::LCS::ContextDiffCallbacks) + } + end + end + + describe "reverse (s2 -> s1)" do + it_has_behavior "patch sequences correctly" do + let(:s1) { seq2 } + let(:s2) { seq1 } + let(:patch_set) { + Diff::LCS.diff(seq2, seq1, Diff::LCS::ContextDiffCallbacks) + } + end + end + end + + describe "with sdiff callbacks (SDiffCallbacks)" do + describe "forward (s1 -> s2)" do + it_has_behavior "patch sequences correctly" do + let(:s1) { seq1 } + let(:s2) { seq2 } + let(:patch_set) { + Diff::LCS.diff(seq1, seq2, Diff::LCS::SDiffCallbacks) + } + end + end + + describe "reverse (s2 -> s1)" do + it_has_behavior "patch sequences correctly" do + let(:s1) { seq2 } + let(:s2) { seq1 } + let(:patch_set) { + Diff::LCS.diff(seq2, seq1, Diff::LCS::SDiffCallbacks) + } + end + end + end + end + + describe "using a Diff::LCS.sdiff patchset" do + describe "an empty patchset returns the source" do + it "works on a string (hello)" do + expect(Diff::LCS.patch(hello, Diff::LCS.sdiff(hello, hello))).to eq(hello) + end + + it "works on an array %W(h e l l o)" do + expect(Diff::LCS.patch(hello_ary, Diff::LCS.sdiff(hello_ary, hello_ary))).to eq(hello_ary) + end + end + + describe "with default diff callbacks (DiffCallbacks)" do + describe "forward (s1 -> s2)" do + it_has_behavior "patch sequences correctly" do + let(:s1) { seq1 } + let(:s2) { seq2 } + let(:patch_set) { + Diff::LCS.sdiff(seq1, seq2, Diff::LCS::DiffCallbacks) + } + end + end + + describe "reverse (s2 -> s1)" do + it_has_behavior "patch sequences correctly" do + let(:s1) { seq2 } + let(:s2) { seq1 } + let(:patch_set) { + Diff::LCS.sdiff(seq2, seq1, Diff::LCS::DiffCallbacks) + } + end + end + end + + describe "with context diff callbacks (DiffCallbacks)" do + describe "forward (s1 -> s2)" do + it_has_behavior "patch sequences correctly" do + let(:s1) { seq1 } + let(:s2) { seq2 } + let(:patch_set) { + Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextDiffCallbacks) + } + end + end + + describe "reverse (s2 -> s1)" do + it_has_behavior "patch sequences correctly" do + let(:s1) { seq2 } + let(:s2) { seq1 } + let(:patch_set) { + Diff::LCS.sdiff(seq2, seq1, Diff::LCS::ContextDiffCallbacks) + } + end + end + end + + describe "with sdiff callbacks (SDiffCallbacks)" do + describe "forward (s1 -> s2)" do + it_has_behavior "patch sequences correctly" do + let(:s1) { seq1 } + let(:s2) { seq2 } + let(:patch_set) { Diff::LCS.sdiff(seq1, seq2) } + end + end + + describe "reverse (s2 -> s1)" do + it_has_behavior "patch sequences correctly" do + let(:s1) { seq2 } + let(:s2) { seq1 } + let(:patch_set) { Diff::LCS.sdiff(seq2, seq1) } + end + end + end + end + + # Note: because of the error in autodiscovery ("does not autodiscover s1 + # to s2 patches"), this cannot use the "patch sequences correctly" shared + # set. Once the bug in autodiscovery is fixed, this can be converted as + # above. + describe "fix bug 891: patchsets do not contain the last equal part" do + before :each do + @s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + @s2 = %w[a b c d D e f g h i j k] + end + + describe "using Diff::LCS.diff with default diff callbacks" do + before :each do + @patch_set_s1_s2 = Diff::LCS.diff(@s1, @s2) + @patch_set_s2_s1 = Diff::LCS.diff(@s2, @s1) + end + + it "autodiscovers s1 to s2 patches" do + expect do + expect(Diff::LCS.patch(@s1, @patch_set_s1_s2)).to eq(@s2) + end.to_not raise_error + end + + it "autodiscovers s2 to s1 patches" do + expect do + expect(Diff::LCS.patch(@s1, @patch_set_s2_s1)).to eq(@s2) + end.to_not raise_error + end + + it "autodiscovers s2 to s1 the left-to-right patches" do + expect(Diff::LCS.patch(@s2, @patch_set_s2_s1)).to eq(@s1) + expect(Diff::LCS.patch(@s2, @patch_set_s1_s2)).to eq(@s1) + end + + it "correctly patches left-to-right (explicit patch)" do + expect(Diff::LCS.patch(@s1, @patch_set_s1_s2, :patch)).to eq(@s2) + expect(Diff::LCS.patch(@s2, @patch_set_s2_s1, :patch)).to eq(@s1) + expect(Diff::LCS.patch!(@s1, @patch_set_s1_s2)).to eq(@s2) + expect(Diff::LCS.patch!(@s2, @patch_set_s2_s1)).to eq(@s1) + end + + it "correctly patches right-to-left (explicit unpatch)" do + expect(Diff::LCS.patch(@s2, @patch_set_s1_s2, :unpatch)).to eq(@s1) + expect(Diff::LCS.patch(@s1, @patch_set_s2_s1, :unpatch)).to eq(@s2) + expect(Diff::LCS.unpatch!(@s2, @patch_set_s1_s2)).to eq(@s1) + expect(Diff::LCS.unpatch!(@s1, @patch_set_s2_s1)).to eq(@s2) + end + end + + describe "using Diff::LCS.diff with context diff callbacks" do + before :each do + @patch_set_s1_s2 = Diff::LCS.diff(@s1, @s2, Diff::LCS::ContextDiffCallbacks) + @patch_set_s2_s1 = Diff::LCS.diff(@s2, @s1, Diff::LCS::ContextDiffCallbacks) + end + + it "autodiscovers s1 to s2 patches" do + expect do + expect(Diff::LCS.patch(@s1, @patch_set_s1_s2)).to eq(@s2) + end.to_not raise_error + end + + it "autodiscovers s2 to s1 patches" do + expect do + expect(Diff::LCS.patch(@s1, @patch_set_s2_s1)).to eq(@s2) + end.to_not raise_error + end + + it "autodiscovers s2 to s1 the left-to-right patches" do + expect(Diff::LCS.patch(@s2, @patch_set_s2_s1)).to eq(@s1) + expect(Diff::LCS.patch(@s2, @patch_set_s1_s2)).to eq(@s1) + end + + it "correctly patches left-to-right (explicit patch)" do + expect(Diff::LCS.patch(@s1, @patch_set_s1_s2, :patch)).to eq(@s2) + expect(Diff::LCS.patch(@s2, @patch_set_s2_s1, :patch)).to eq(@s1) + expect(Diff::LCS.patch!(@s1, @patch_set_s1_s2)).to eq(@s2) + expect(Diff::LCS.patch!(@s2, @patch_set_s2_s1)).to eq(@s1) + end + + it "correctly patches right-to-left (explicit unpatch)" do + expect(Diff::LCS.patch(@s2, @patch_set_s1_s2, :unpatch)).to eq(@s1) + expect(Diff::LCS.patch(@s1, @patch_set_s2_s1, :unpatch)).to eq(@s2) + expect(Diff::LCS.unpatch!(@s2, @patch_set_s1_s2)).to eq(@s1) + expect(Diff::LCS.unpatch!(@s1, @patch_set_s2_s1)).to eq(@s2) + end + end + + describe "using Diff::LCS.diff with sdiff callbacks" do + before(:each) do + @patch_set_s1_s2 = Diff::LCS.diff(@s1, @s2, Diff::LCS::SDiffCallbacks) + @patch_set_s2_s1 = Diff::LCS.diff(@s2, @s1, Diff::LCS::SDiffCallbacks) + end + + it "autodiscovers s1 to s2 patches" do + expect do + expect(Diff::LCS.patch(@s1, @patch_set_s1_s2)).to eq(@s2) + end.to_not raise_error + end + + it "autodiscovers s2 to s1 patches" do + expect do + expect(Diff::LCS.patch(@s1, @patch_set_s2_s1)).to eq(@s2) + end.to_not raise_error + end + + it "autodiscovers s2 to s1 the left-to-right patches" do + expect(Diff::LCS.patch(@s2, @patch_set_s2_s1)).to eq(@s1) + expect(Diff::LCS.patch(@s2, @patch_set_s1_s2)).to eq(@s1) + end + + it "correctly patches left-to-right (explicit patch)" do + expect(Diff::LCS.patch(@s1, @patch_set_s1_s2, :patch)).to eq(@s2) + expect(Diff::LCS.patch(@s2, @patch_set_s2_s1, :patch)).to eq(@s1) + expect(Diff::LCS.patch!(@s1, @patch_set_s1_s2)).to eq(@s2) + expect(Diff::LCS.patch!(@s2, @patch_set_s2_s1)).to eq(@s1) + end + + it "correctly patches right-to-left (explicit unpatch)" do + expect(Diff::LCS.patch(@s2, @patch_set_s1_s2, :unpatch)).to eq(@s1) + expect(Diff::LCS.patch(@s1, @patch_set_s2_s1, :unpatch)).to eq(@s2) + expect(Diff::LCS.unpatch!(@s2, @patch_set_s1_s2)).to eq(@s1) + expect(Diff::LCS.unpatch!(@s1, @patch_set_s2_s1)).to eq(@s2) + end + end + + describe "using Diff::LCS.sdiff with default sdiff callbacks" do + before(:each) do + @patch_set_s1_s2 = Diff::LCS.sdiff(@s1, @s2) + @patch_set_s2_s1 = Diff::LCS.sdiff(@s2, @s1) + end + + it "autodiscovers s1 to s2 patches" do + expect do + expect(Diff::LCS.patch(@s1, @patch_set_s1_s2)).to eq(@s2) + end.to_not raise_error + end + + it "autodiscovers s2 to s1 patches" do + expect do + expect(Diff::LCS.patch(@s1, @patch_set_s2_s1)).to eq(@s2) + end.to_not raise_error + end + + it "autodiscovers s2 to s1 the left-to-right patches" do + expect(Diff::LCS.patch(@s2, @patch_set_s2_s1)).to eq(@s1) + expect(Diff::LCS.patch(@s2, @patch_set_s1_s2)).to eq(@s1) + end + + it "correctly patches left-to-right (explicit patch)" do + expect(Diff::LCS.patch(@s1, @patch_set_s1_s2, :patch)).to eq(@s2) + expect(Diff::LCS.patch(@s2, @patch_set_s2_s1, :patch)).to eq(@s1) + expect(Diff::LCS.patch!(@s1, @patch_set_s1_s2)).to eq(@s2) + expect(Diff::LCS.patch!(@s2, @patch_set_s2_s1)).to eq(@s1) + end + + it "correctly patches right-to-left (explicit unpatch)" do + expect(Diff::LCS.patch(@s2, @patch_set_s1_s2, :unpatch)).to eq(@s1) + expect(Diff::LCS.patch(@s1, @patch_set_s2_s1, :unpatch)).to eq(@s2) + expect(Diff::LCS.unpatch!(@s2, @patch_set_s1_s2)).to eq(@s1) + expect(Diff::LCS.unpatch!(@s1, @patch_set_s2_s1)).to eq(@s2) + end + end + + describe "using Diff::LCS.sdiff with context diff callbacks" do + before(:each) do + @patch_set_s1_s2 = Diff::LCS.sdiff(@s1, @s2, Diff::LCS::ContextDiffCallbacks) + @patch_set_s2_s1 = Diff::LCS.sdiff(@s2, @s1, Diff::LCS::ContextDiffCallbacks) + end + + it "autodiscovers s1 to s2 patches" do + expect do + expect(Diff::LCS.patch(@s1, @patch_set_s1_s2)).to eq(@s2) + end.to_not raise_error + end + + it "autodiscovers s2 to s1 patches" do + expect do + expect(Diff::LCS.patch(@s1, @patch_set_s2_s1)).to eq(@s2) + end.to_not raise_error + end + + it "autodiscovers s2 to s1 the left-to-right patches" do + expect(Diff::LCS.patch(@s2, @patch_set_s2_s1)).to eq(@s1) + expect(Diff::LCS.patch(@s2, @patch_set_s1_s2)).to eq(@s1) + end + + it "correctly patches left-to-right (explicit patch)" do + expect(Diff::LCS.patch(@s1, @patch_set_s1_s2, :patch)).to eq(@s2) + expect(Diff::LCS.patch(@s2, @patch_set_s2_s1, :patch)).to eq(@s1) + expect(Diff::LCS.patch!(@s1, @patch_set_s1_s2)).to eq(@s2) + expect(Diff::LCS.patch!(@s2, @patch_set_s2_s1)).to eq(@s1) + end + + it "correctly patches right-to-left (explicit unpatch)" do + expect(Diff::LCS.patch(@s2, @patch_set_s1_s2, :unpatch)).to eq(@s1) + expect(Diff::LCS.patch(@s1, @patch_set_s2_s1, :unpatch)).to eq(@s2) + expect(Diff::LCS.unpatch!(@s2, @patch_set_s1_s2)).to eq(@s1) + expect(Diff::LCS.unpatch!(@s1, @patch_set_s2_s1)).to eq(@s2) + end + end + + describe "using Diff::LCS.sdiff with default diff callbacks" do + before(:each) do + @patch_set_s1_s2 = Diff::LCS.sdiff(@s1, @s2, Diff::LCS::DiffCallbacks) + @patch_set_s2_s1 = Diff::LCS.sdiff(@s2, @s1, Diff::LCS::DiffCallbacks) + end + + it "autodiscovers s1 to s2 patches" do + expect do + expect(Diff::LCS.patch(@s1, @patch_set_s1_s2)).to eq(@s2) + end.to_not raise_error + end + + it "autodiscovers s2 to s1 patches" do + expect do + expect(Diff::LCS.patch(@s1, @patch_set_s2_s1)).to eq(@s2) + end.to_not raise_error + end + + it "autodiscovers s2 to s1 the left-to-right patches" do + expect(Diff::LCS.patch(@s2, @patch_set_s2_s1)).to eq(@s1) + expect(Diff::LCS.patch(@s2, @patch_set_s1_s2)).to eq(@s1) + end + + it "correctly patches left-to-right (explicit patch)" do + expect(Diff::LCS.patch(@s1, @patch_set_s1_s2, :patch)).to eq(@s2) + expect(Diff::LCS.patch(@s2, @patch_set_s2_s1, :patch)).to eq(@s1) + expect(Diff::LCS.patch!(@s1, @patch_set_s1_s2)).to eq(@s2) + expect(Diff::LCS.patch!(@s2, @patch_set_s2_s1)).to eq(@s1) + end + + it "correctly patches right-to-left (explicit unpatch)" do + expect(Diff::LCS.patch(@s2, @patch_set_s1_s2, :unpatch)).to eq(@s1) + expect(Diff::LCS.patch(@s1, @patch_set_s2_s1, :unpatch)).to eq(@s2) + expect(Diff::LCS.unpatch!(@s2, @patch_set_s1_s2)).to eq(@s1) + expect(Diff::LCS.unpatch!(@s1, @patch_set_s2_s1)).to eq(@s2) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/sdiff_spec.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/sdiff_spec.rb new file mode 100644 index 00000000..aded3017 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/sdiff_spec.rb @@ -0,0 +1,216 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Diff::LCS.sdiff" do + include Diff::LCS::SpecHelper::Matchers + + shared_examples "compare sequences correctly" do + it "compares s1 -> s2 correctly" do + expect(Diff::LCS.sdiff(s1, s2)).to eq(context_diff(result)) + end + + it "compares s2 -> s1 correctly" do + expect(Diff::LCS.sdiff(s2, s1)).to eq(context_diff(reverse_sdiff(result))) + end + end + + describe "using seq1 & seq2" do + let(:s1) { seq1 } + let(:s2) { seq2 } + let(:result) { correct_forward_sdiff } + + it_has_behavior "compare sequences correctly" + end + + describe "using %w(abc def yyy xxx ghi jkl) & %w(abc dxf xxx ghi jkl)" do + let(:s1) { %w[abc def yyy xxx ghi jkl] } + let(:s2) { %w[abc dxf xxx ghi jkl] } + let(:result) { + # standard:disable Layout/ExtraSpacing + [ + ["=", [0, "abc"], [0, "abc"]], + ["!", [1, "def"], [1, "dxf"]], + ["-", [2, "yyy"], [2, nil]], + ["=", [3, "xxx"], [2, "xxx"]], + ["=", [4, "ghi"], [3, "ghi"]], + ["=", [5, "jkl"], [4, "jkl"]] + ] + # standard:enable Layout/ExtraSpacing + } + + it_has_behavior "compare sequences correctly" + end + + describe "using %w(a b c d e) & %w(a e)" do + let(:s1) { %w[a b c d e] } + let(:s2) { %w[a e] } + let(:result) { + [ + ["=", [0, "a"], [0, "a"]], + ["-", [1, "b"], [1, nil]], + ["-", [2, "c"], [1, nil]], + ["-", [3, "d"], [1, nil]], + ["=", [4, "e"], [1, "e"]] + ] + } + + it_has_behavior "compare sequences correctly" + end + + describe "using %w(a e) & %w(a b c d e)" do + let(:s1) { %w[a e] } + let(:s2) { %w[a b c d e] } + let(:result) { + [ + ["=", [0, "a"], [0, "a"]], + ["+", [1, nil], [1, "b"]], + ["+", [1, nil], [2, "c"]], + ["+", [1, nil], [3, "d"]], + ["=", [1, "e"], [4, "e"]] + ] + } + + it_has_behavior "compare sequences correctly" + end + + describe "using %w(v x a e) & %w(w y a b c d e)" do + let(:s1) { %w[v x a e] } + let(:s2) { %w[w y a b c d e] } + let(:result) { + [ + ["!", [0, "v"], [0, "w"]], + ["!", [1, "x"], [1, "y"]], + ["=", [2, "a"], [2, "a"]], + ["+", [3, nil], [3, "b"]], + ["+", [3, nil], [4, "c"]], + ["+", [3, nil], [5, "d"]], + ["=", [3, "e"], [6, "e"]] + ] + } + + it_has_behavior "compare sequences correctly" + end + + describe "using %w(x a e) & %w(a b c d e)" do + let(:s1) { %w[x a e] } + let(:s2) { %w[a b c d e] } + let(:result) { + [ + ["-", [0, "x"], [0, nil]], + ["=", [1, "a"], [0, "a"]], + ["+", [2, nil], [1, "b"]], + ["+", [2, nil], [2, "c"]], + ["+", [2, nil], [3, "d"]], + ["=", [2, "e"], [4, "e"]] + ] + } + + it_has_behavior "compare sequences correctly" + end + + describe "using %w(a e) & %w(x a b c d e)" do + let(:s1) { %w[a e] } + let(:s2) { %w[x a b c d e] } + let(:result) { + [ + ["+", [0, nil], [0, "x"]], + ["=", [0, "a"], [1, "a"]], + ["+", [1, nil], [2, "b"]], + ["+", [1, nil], [3, "c"]], + ["+", [1, nil], [4, "d"]], + ["=", [1, "e"], [5, "e"]] + ] + } + + it_has_behavior "compare sequences correctly" + end + + describe "using %w(a e v) & %w(x a b c d e w x)" do + let(:s1) { %w[a e v] } + let(:s2) { %w[x a b c d e w x] } + let(:result) { + [ + ["+", [0, nil], [0, "x"]], + ["=", [0, "a"], [1, "a"]], + ["+", [1, nil], [2, "b"]], + ["+", [1, nil], [3, "c"]], + ["+", [1, nil], [4, "d"]], + ["=", [1, "e"], [5, "e"]], + ["!", [2, "v"], [6, "w"]], + ["+", [3, nil], [7, "x"]] + ] + } + + it_has_behavior "compare sequences correctly" + end + + describe "using %w() & %w(a b c)" do + let(:s1) { %w[] } + let(:s2) { %w[a b c] } + let(:result) { + [ + ["+", [0, nil], [0, "a"]], + ["+", [0, nil], [1, "b"]], + ["+", [0, nil], [2, "c"]] + ] + } + + it_has_behavior "compare sequences correctly" + end + + describe "using %w(a b c) & %w(1)" do + let(:s1) { %w[a b c] } + let(:s2) { %w[1] } + let(:result) { + [ + ["!", [0, "a"], [0, "1"]], + ["-", [1, "b"], [1, nil]], + ["-", [2, "c"], [1, nil]] + ] + } + + it_has_behavior "compare sequences correctly" + end + + describe "using %w(a b c) & %w(c)" do + let(:s1) { %w[a b c] } + let(:s2) { %w[c] } + let(:result) { + [ + ["-", [0, "a"], [0, nil]], + ["-", [1, "b"], [0, nil]], + ["=", [2, "c"], [0, "c"]] + ] + } + + it_has_behavior "compare sequences correctly" + end + + describe "using %w(abcd efgh ijkl mnop) & []" do + let(:s1) { %w[abcd efgh ijkl mnop] } + let(:s2) { [] } + let(:result) { + [ + ["-", [0, "abcd"], [0, nil]], + ["-", [1, "efgh"], [0, nil]], + ["-", [2, "ijkl"], [0, nil]], + ["-", [3, "mnop"], [0, nil]] + ] + } + + it_has_behavior "compare sequences correctly" + end + + describe "using [[1,2]] & []" do + let(:s1) { [[1, 2]] } + let(:s2) { [] } + let(:result) { + [ + ["-", [0, [1, 2]], [0, nil]] + ] + } + + it_has_behavior "compare sequences correctly" + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/spec_helper.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/spec_helper.rb new file mode 100644 index 00000000..baaa3d03 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/spec_helper.rb @@ -0,0 +1,376 @@ +# frozen_string_literal: true + +require "rubygems" +require "pathname" + +require "psych" if RUBY_VERSION >= "1.9" + +if ENV["COVERAGE"] + require "simplecov" + require "simplecov-lcov" + + SimpleCov::Formatter::LcovFormatter.config do |config| + config.report_with_single_file = true + config.lcov_file_name = "lcov.info" + end + + SimpleCov.start "test_frameworks" do + enable_coverage :branch + primary_coverage :branch + formatter SimpleCov::Formatter::MultiFormatter.new([ + SimpleCov::Formatter::HTMLFormatter, + SimpleCov::Formatter::LcovFormatter, + SimpleCov::Formatter::SimpleFormatter + ]) + end +end + +file = Pathname.new(__FILE__).expand_path +path = file.parent +parent = path.parent + +$:.unshift parent.join("lib") + +module CaptureSubprocessIO + def _synchronize + yield + end + + def capture_subprocess_io + _synchronize { _capture_subprocess_io { yield } } + end + + def _capture_subprocess_io + require "tempfile" + + captured_stdout, captured_stderr = Tempfile.new("out"), Tempfile.new("err") + + orig_stdout, orig_stderr = $stdout.dup, $stderr.dup + $stdout.reopen captured_stdout + $stderr.reopen captured_stderr + + yield + + $stdout.rewind + $stderr.rewind + + [captured_stdout.read, captured_stderr.read] + ensure + captured_stdout.unlink + captured_stderr.unlink + $stdout.reopen orig_stdout + $stderr.reopen orig_stderr + end + private :_capture_subprocess_io +end + +require "diff-lcs" + +module Diff::LCS::SpecHelper + def hello + "hello" + end + + def hello_ary + %w[h e l l o] + end + + def seq1 + %w[a b c e h j l m n p] + end + + def skipped_seq1 + %w[a h n p] + end + + def seq2 + %w[b c d e f j k l m r s t] + end + + def skipped_seq2 + %w[d f k r s t] + end + + def word_sequence + %w[abcd efgh ijkl mnopqrstuvwxyz] + end + + def correct_lcs + %w[b c e j l m] + end + + # standard:disable Layout/ExtraSpacing + def correct_forward_diff + [ + [ + ["-", 0, "a"] + ], + [ + ["+", 2, "d"] + ], + [ + ["-", 4, "h"], + ["+", 4, "f"] + ], + [ + ["+", 6, "k"] + ], + [ + ["-", 8, "n"], + ["+", 9, "r"], + ["-", 9, "p"], + ["+", 10, "s"], + ["+", 11, "t"] + ] + ] + end + + def correct_backward_diff + [ + [ + ["+", 0, "a"] + ], + [ + ["-", 2, "d"] + ], + [ + ["-", 4, "f"], + ["+", 4, "h"] + ], + [ + ["-", 6, "k"] + ], + [ + ["-", 9, "r"], + ["+", 8, "n"], + ["-", 10, "s"], + ["+", 9, "p"], + ["-", 11, "t"] + ] + ] + end + + def correct_forward_sdiff + [ + ["-", [0, "a"], [0, nil]], + ["=", [1, "b"], [0, "b"]], + ["=", [2, "c"], [1, "c"]], + ["+", [3, nil], [2, "d"]], + ["=", [3, "e"], [3, "e"]], + ["!", [4, "h"], [4, "f"]], + ["=", [5, "j"], [5, "j"]], + ["+", [6, nil], [6, "k"]], + ["=", [6, "l"], [7, "l"]], + ["=", [7, "m"], [8, "m"]], + ["!", [8, "n"], [9, "r"]], + ["!", [9, "p"], [10, "s"]], + ["+", [10, nil], [11, "t"]] + ] + end + # standard:enable Layout/ExtraSpacing + + def reverse_sdiff(forward_sdiff) + forward_sdiff.map { |line| + line[1], line[2] = line[2], line[1] + case line[0] + when "-" then line[0] = "+" + when "+" then line[0] = "-" + end + line + } + end + + def change_diff(diff) + map_diffs(diff, Diff::LCS::Change) + end + + def context_diff(diff) + map_diffs(diff, Diff::LCS::ContextChange) + end + + def format_diffs(diffs) + diffs.map { |e| + if e.is_a?(Array) + e.map { |f| f.to_a.join }.join(", ") + else + e.to_a.join + end + }.join("\n") + end + + def map_diffs(diffs, klass = Diff::LCS::ContextChange) + diffs.map do |chunks| + if klass == Diff::LCS::ContextChange + klass.from_a(chunks) + else + chunks.map { |changes| klass.from_a(changes) } + end + end + end + + def balanced_traversal(s1, s2, callback_type) + callback = __send__(callback_type) + Diff::LCS.traverse_balanced(s1, s2, callback) + callback + end + + def balanced_reverse(change_result) + new_result = [] + change_result.each do |line| + line = [line[0], line[2], line[1]] + case line[0] + when "<" + line[0] = ">" + when ">" + line[0] = "<" + end + new_result << line + end + new_result.sort_by { |line| [line[1], line[2]] } + end + + def map_to_no_change(change_result) + new_result = [] + change_result.each do |line| + case line[0] + when "!" + new_result << ["<", line[1], line[2]] + new_result << [">", line[1] + 1, line[2]] + else + new_result << line + end + end + new_result + end + + class SimpleCallback + def initialize + reset + end + + attr_reader :matched_a + attr_reader :matched_b + attr_reader :discards_a + attr_reader :discards_b + attr_reader :done_a + attr_reader :done_b + + def reset + @matched_a = [] + @matched_b = [] + @discards_a = [] + @discards_b = [] + @done_a = [] + @done_b = [] + self + end + + def match(event) + @matched_a << event.old_element + @matched_b << event.new_element + end + + def discard_b(event) + @discards_b << event.new_element + end + + def discard_a(event) + @discards_a << event.old_element + end + + def finished_a(event) + @done_a << [ + event.old_element, event.old_position, + event.new_element, event.new_position + ] + end + + def finished_b(event) + @done_b << [ + event.old_element, event.old_position, + event.new_element, event.new_position + ] + end + end + + def simple_callback + SimpleCallback.new + end + + class SimpleCallbackNoFinishers < SimpleCallback + undef :finished_a + undef :finished_b + end + + def simple_callback_no_finishers + SimpleCallbackNoFinishers.new + end + + class BalancedCallback + def initialize + reset + end + + attr_reader :result + + def reset + @result = [] + end + + def match(event) + @result << ["=", event.old_position, event.new_position] + end + + def discard_a(event) + @result << ["<", event.old_position, event.new_position] + end + + def discard_b(event) + @result << [">", event.old_position, event.new_position] + end + + def change(event) + @result << ["!", event.old_position, event.new_position] + end + end + + def balanced_callback + BalancedCallback.new + end + + class BalancedCallbackNoChange < BalancedCallback + undef :change + end + + def balanced_callback_no_change + BalancedCallbackNoChange.new + end + + module Matchers + extend RSpec::Matchers::DSL + + matcher :be_nil_or_match_values do |ii, s1, s2| + match do |ee| + expect(ee).to(satisfy { |vee| vee.nil? || s1[ii] == s2[ee] }) + end + end + + matcher :correctly_map_sequence do |s1| + match do |actual| + actual.each_index { |ii| expect(actual[ii]).to be_nil_or_match_values(ii, s1, @s2) } + end + + chain :to_other_sequence do |s2| + @s2 = s2 + end + end + end +end + +RSpec.configure do |conf| + conf.include Diff::LCS::SpecHelper + conf.alias_it_should_behave_like_to :it_has_behavior, "has behavior:" + # standard:disable Style/HashSyntax + conf.filter_run_excluding :broken => true + # standard:enable Style/HashSyntax +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/traverse_balanced_spec.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/traverse_balanced_spec.rb new file mode 100644 index 00000000..3a3f6779 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/traverse_balanced_spec.rb @@ -0,0 +1,312 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Diff::LCS.traverse_balanced" do + include Diff::LCS::SpecHelper::Matchers + + shared_examples "with a #change callback" do |s1, s2, result| + it "traverses s1 -> s2 correctly" do + traversal = balanced_traversal(s1, s2, :balanced_callback) + expect(traversal.result).to eq(result) + end + + it "traverses s2 -> s1 correctly" do + traversal = balanced_traversal(s2, s1, :balanced_callback) + expect(traversal.result).to eq(balanced_reverse(result)) + end + end + + shared_examples "without a #change callback" do |s1, s2, result| + it "traverses s1 -> s2 correctly" do + traversal = balanced_traversal(s1, s2, :balanced_callback_no_change) + expect(traversal.result).to eq(map_to_no_change(result)) + end + + it "traverses s2 -> s1 correctly" do + traversal = balanced_traversal(s2, s1, :balanced_callback_no_change) + expect(traversal.result).to eq(map_to_no_change(balanced_reverse(result))) + end + end + + describe "identical string sequences ('abc')" do + s1 = s2 = "abc" + + result = [ + ["=", 0, 0], + ["=", 1, 1], + ["=", 2, 2] + ] + + it_has_behavior "with a #change callback", s1, s2, result + it_has_behavior "without a #change callback", s1, s2, result + end + + describe "identical array sequences %w(a b c)" do + s1 = s2 = %w[a b c] + + result = [ + ["=", 0, 0], + ["=", 1, 1], + ["=", 2, 2] + ] + + it_has_behavior "with a #change callback", s1, s2, result + it_has_behavior "without a #change callback", s1, s2, result + end + + describe "sequences %w(a b c) & %w(a x c)" do + s1 = %w[a b c] + s2 = %w[a x c] + + result = [ + ["=", 0, 0], + ["!", 1, 1], + ["=", 2, 2] + ] + + it_has_behavior "with a #change callback", s1, s2, result + it_has_behavior "without a #change callback", s1, s2, result + end + + describe "sequences %w(a x y c) & %w(a v w c)" do + s1 = %w[a x y c] + s2 = %w[a v w c] + + result = [ + ["=", 0, 0], + ["!", 1, 1], + ["!", 2, 2], + ["=", 3, 3] + ] + + it_has_behavior "with a #change callback", s1, s2, result + it_has_behavior "without a #change callback", s1, s2, result + end + + describe "sequences %w(x y c) & %w(v w c)" do + s1 = %w[x y c] + s2 = %w[v w c] + result = [ + ["!", 0, 0], + ["!", 1, 1], + ["=", 2, 2] + ] + + it_has_behavior "with a #change callback", s1, s2, result + it_has_behavior "without a #change callback", s1, s2, result + end + + describe "sequences %w(a x y z) & %w(b v w)" do + s1 = %w[a x y z] + s2 = %w[b v w] + result = [ + ["!", 0, 0], + ["!", 1, 1], + ["!", 2, 2], + ["<", 3, 3] + ] + + it_has_behavior "with a #change callback", s1, s2, result + it_has_behavior "without a #change callback", s1, s2, result + end + + describe "sequences %w(a z) & %w(a)" do + s1 = %w[a z] + s2 = %w[a] + result = [ + ["=", 0, 0], + ["<", 1, 1] + ] + + it_has_behavior "with a #change callback", s1, s2, result + it_has_behavior "without a #change callback", s1, s2, result + end + + describe "sequences %w(z a) & %w(a)" do + s1 = %w[z a] + s2 = %w[a] + result = [ + ["<", 0, 0], + ["=", 1, 0] + ] + + it_has_behavior "with a #change callback", s1, s2, result + it_has_behavior "without a #change callback", s1, s2, result + end + + describe "sequences %w(a b c) & %w(x y z)" do + s1 = %w[a b c] + s2 = %w[x y z] + result = [ + ["!", 0, 0], + ["!", 1, 1], + ["!", 2, 2] + ] + + it_has_behavior "with a #change callback", s1, s2, result + it_has_behavior "without a #change callback", s1, s2, result + end + + describe "sequences %w(abcd efgh ijkl mnoopqrstuvwxyz) & []" do + s1 = %w[abcd efgh ijkl mnopqrstuvwxyz] + s2 = [] + result = [ + ["<", 0, 0], + ["<", 1, 0], + ["<", 2, 0], + ["<", 3, 0] + ] + + it_has_behavior "with a #change callback", s1, s2, result + it_has_behavior "without a #change callback", s1, s2, result + end + + describe "strings %q(a b c) & %q(a x c)" do + s1 = "a b c" + s2 = "a x c" + + result = [ + ["=", 0, 0], + ["=", 1, 1], + ["!", 2, 2], + ["=", 3, 3], + ["=", 4, 4] + ] + + it_has_behavior "with a #change callback", s1, s2, result + it_has_behavior "without a #change callback", s1, s2, result + end + + describe "strings %q(a x y c) & %q(a v w c)" do + s1 = "a x y c" + s2 = "a v w c" + + result = [ + ["=", 0, 0], + ["=", 1, 1], + ["!", 2, 2], + ["=", 3, 3], + ["!", 4, 4], + ["=", 5, 5], + ["=", 6, 6] + ] + + it_has_behavior "with a #change callback", s1, s2, result + it_has_behavior "without a #change callback", s1, s2, result + end + + describe "strings %q(x y c) & %q(v w c)" do + s1 = "x y c" + s2 = "v w c" + result = [ + ["!", 0, 0], + ["=", 1, 1], + ["!", 2, 2], + ["=", 3, 3], + ["=", 4, 4] + ] + + it_has_behavior "with a #change callback", s1, s2, result + it_has_behavior "without a #change callback", s1, s2, result + end + + describe "strings %q(a x y z) & %q(b v w)" do + s1 = "a x y z" + s2 = "b v w" + result = [ + ["!", 0, 0], + ["=", 1, 1], + ["!", 2, 2], + ["=", 3, 3], + ["!", 4, 4], + ["<", 5, 5], + ["<", 6, 5] + ] + + it_has_behavior "with a #change callback", s1, s2, result + it_has_behavior "without a #change callback", s1, s2, result + end + + describe "strings %q(a z) & %q(a)" do + s1 = "a z" + s2 = "a" + result = [ + ["=", 0, 0], + ["<", 1, 1], + ["<", 2, 1] + ] + + it_has_behavior "with a #change callback", s1, s2, result + it_has_behavior "without a #change callback", s1, s2, result + end + + describe "strings %q(z a) & %q(a)" do + s1 = "z a" + s2 = "a" + result = [ + ["<", 0, 0], + ["<", 1, 0], + ["=", 2, 0] + ] + + it_has_behavior "with a #change callback", s1, s2, result + it_has_behavior "without a #change callback", s1, s2, result + end + + describe "strings %q(a b c) & %q(x y z)" do + s1 = "a b c" + s2 = "x y z" + result = [ + ["!", 0, 0], + ["=", 1, 1], + ["!", 2, 2], + ["=", 3, 3], + ["!", 4, 4] + ] + + it_has_behavior "with a #change callback", s1, s2, result + it_has_behavior "without a #change callback", s1, s2, result + end + + describe "strings %q(abcd efgh ijkl mnopqrstuvwxyz) & %q()" do + s1 = "abcd efgh ijkl mnopqrstuvwxyz" + s2 = "" + # standard:disable Layout/ExtraSpacing + result = [ + ["<", 0, 0], + ["<", 1, 0], + ["<", 2, 0], + ["<", 3, 0], + ["<", 4, 0], + ["<", 5, 0], + ["<", 6, 0], + ["<", 7, 0], + ["<", 8, 0], + ["<", 9, 0], + ["<", 10, 0], + ["<", 11, 0], + ["<", 12, 0], + ["<", 13, 0], + ["<", 14, 0], + ["<", 15, 0], + ["<", 16, 0], + ["<", 17, 0], + ["<", 18, 0], + ["<", 19, 0], + ["<", 20, 0], + ["<", 21, 0], + ["<", 22, 0], + ["<", 23, 0], + ["<", 24, 0], + ["<", 25, 0], + ["<", 26, 0], + ["<", 27, 0], + ["<", 28, 0] + ] + # standard:enable Layout/ExtraSpacing + + it_has_behavior "with a #change callback", s1, s2, result + it_has_behavior "without a #change callback", s1, s2, result + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/traverse_sequences_spec.rb b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/traverse_sequences_spec.rb new file mode 100644 index 00000000..8e9928f1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/diff-lcs-1.6.2/spec/traverse_sequences_spec.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Diff::LCS.traverse_sequences" do + describe "callback with no finishers" do + describe "over (seq1, seq2)" do + before(:each) do + @callback_s1_s2 = simple_callback_no_finishers + Diff::LCS.traverse_sequences(seq1, seq2, @callback_s1_s2) + + @callback_s2_s1 = simple_callback_no_finishers + Diff::LCS.traverse_sequences(seq2, seq1, @callback_s2_s1) + end + + it "has the correct LCS result on left-matches" do + expect(@callback_s1_s2.matched_a).to eq(correct_lcs) + expect(@callback_s2_s1.matched_a).to eq(correct_lcs) + end + + it "has the correct LCS result on right-matches" do + expect(@callback_s1_s2.matched_b).to eq(correct_lcs) + expect(@callback_s2_s1.matched_b).to eq(correct_lcs) + end + + it "has the correct skipped sequences with the left sequence" do + expect(@callback_s1_s2.discards_a).to eq(skipped_seq1) + expect(@callback_s2_s1.discards_a).to eq(skipped_seq2) + end + + it "has the correct skipped sequences with the right sequence" do + expect(@callback_s1_s2.discards_b).to eq(skipped_seq2) + expect(@callback_s2_s1.discards_b).to eq(skipped_seq1) + end + + it "does not have anything done markers from the left or right sequences" do + expect(@callback_s1_s2.done_a).to be_empty + expect(@callback_s1_s2.done_b).to be_empty + expect(@callback_s2_s1.done_a).to be_empty + expect(@callback_s2_s1.done_b).to be_empty + end + end + + describe "over (hello, hello)" do + before(:each) do + @callback = simple_callback_no_finishers + Diff::LCS.traverse_sequences(hello, hello, @callback) + end + + it "has the correct LCS result on left-matches" do + expect(@callback.matched_a).to eq(hello.chars) + end + + it "has the correct LCS result on right-matches" do + expect(@callback.matched_b).to eq(hello.chars) + end + + it "has the correct skipped sequences with the left sequence" do + expect(@callback.discards_a).to be_empty + end + + it "has the correct skipped sequences with the right sequence" do + expect(@callback.discards_b).to be_empty + end + + it "does not have anything done markers from the left or right sequences" do + expect(@callback.done_a).to be_empty + expect(@callback.done_b).to be_empty + end + end + + describe "over (hello_ary, hello_ary)" do + before(:each) do + @callback = simple_callback_no_finishers + Diff::LCS.traverse_sequences(hello_ary, hello_ary, @callback) + end + + it "has the correct LCS result on left-matches" do + expect(@callback.matched_a).to eq(hello_ary) + end + + it "has the correct LCS result on right-matches" do + expect(@callback.matched_b).to eq(hello_ary) + end + + it "has the correct skipped sequences with the left sequence" do + expect(@callback.discards_a).to be_empty + end + + it "has the correct skipped sequences with the right sequence" do + expect(@callback.discards_b).to be_empty + end + + it "does not have anything done markers from the left or right sequences" do + expect(@callback.done_a).to be_empty + expect(@callback.done_b).to be_empty + end + end + end + + describe "callback with finisher" do + before(:each) do + @callback_s1_s2 = simple_callback + Diff::LCS.traverse_sequences(seq1, seq2, @callback_s1_s2) + @callback_s2_s1 = simple_callback + Diff::LCS.traverse_sequences(seq2, seq1, @callback_s2_s1) + end + + it "has the correct LCS result on left-matches" do + expect(@callback_s1_s2.matched_a).to eq(correct_lcs) + expect(@callback_s2_s1.matched_a).to eq(correct_lcs) + end + + it "has the correct LCS result on right-matches" do + expect(@callback_s1_s2.matched_b).to eq(correct_lcs) + expect(@callback_s2_s1.matched_b).to eq(correct_lcs) + end + + it "has the correct skipped sequences for the left sequence" do + expect(@callback_s1_s2.discards_a).to eq(skipped_seq1) + expect(@callback_s2_s1.discards_a).to eq(skipped_seq2) + end + + it "has the correct skipped sequences for the right sequence" do + expect(@callback_s1_s2.discards_b).to eq(skipped_seq2) + expect(@callback_s2_s1.discards_b).to eq(skipped_seq1) + end + + it "has done markers differently-sized sequences" do + expect(@callback_s1_s2.done_a).to eq([["p", 9, "t", 11]]) + expect(@callback_s1_s2.done_b).to be_empty + + expect(@callback_s2_s1.done_a).to be_empty + expect(@callback_s2_s1.done_b).to eq([["t", 11, "p", 9]]) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/LICENSE.txt b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/LICENSE.txt new file mode 100644 index 00000000..a009caef --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/drb.gemspec b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/drb.gemspec new file mode 100644 index 00000000..40f16697 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/drb.gemspec @@ -0,0 +1,42 @@ +begin + require_relative "lib/drb/version" +rescue LoadError # Fallback to load version file in ruby core repository + require_relative "version" +end + +Gem::Specification.new do |spec| + spec.name = "drb" + spec.version = DRb::VERSION + spec.authors = ["Masatoshi SEKI"] + spec.email = ["seki@ruby-lang.org"] + + spec.summary = %q{Distributed object system for Ruby} + spec.description = %q{Distributed object system for Ruby} + spec.homepage = "https://github.com/ruby/drb" + spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0") + spec.licenses = ["Ruby", "BSD-2-Clause"] + + spec.metadata["homepage_uri"] = spec.homepage + spec.metadata["source_code_uri"] = spec.homepage + spec.metadata["changelog_uri"] = + "#{spec.homepage}/releases/tag/v#{spec.version}" + + spec.files = %w[ + LICENSE.txt + drb.gemspec + lib/drb.rb + lib/drb/acl.rb + lib/drb/drb.rb + lib/drb/eq.rb + lib/drb/extserv.rb + lib/drb/extservm.rb + lib/drb/gw.rb + lib/drb/observer.rb + lib/drb/ssl.rb + lib/drb/timeridconv.rb + lib/drb/unix.rb + lib/drb/version.rb + lib/drb/weakidconv.rb + ] + spec.require_paths = ["lib"] +end diff --git a/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb.rb b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb.rb new file mode 100644 index 00000000..2bb4716f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: false +require 'drb/drb' + diff --git a/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/acl.rb b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/acl.rb new file mode 100644 index 00000000..b004656f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/acl.rb @@ -0,0 +1,239 @@ +# frozen_string_literal: false +# Copyright (c) 2000,2002,2003 Masatoshi SEKI +# +# acl.rb is copyrighted free software by Masatoshi SEKI. +# You can redistribute it and/or modify it under the same terms as Ruby. + +require 'ipaddr' + +## +# Simple Access Control Lists. +# +# Access control lists are composed of "allow" and "deny" halves to control +# access. Use "all" or "*" to match any address. To match a specific address +# use any address or address mask that IPAddr can understand. +# +# Example: +# +# list = %w[ +# deny all +# allow 192.168.1.1 +# allow ::ffff:192.168.1.2 +# allow 192.168.1.3 +# ] +# +# # From Socket#peeraddr, see also ACL#allow_socket? +# addr = ["AF_INET", 10, "lc630", "192.168.1.3"] +# +# acl = ACL.new +# p acl.allow_addr?(addr) # => true +# +# acl = ACL.new(list, ACL::DENY_ALLOW) +# p acl.allow_addr?(addr) # => true + +class ACL + + ## + # The current version of ACL + + VERSION=["2.0.0"] + + ## + # An entry in an ACL + + class ACLEntry + + ## + # Creates a new entry using +str+. + # + # +str+ may be "*" or "all" to match any address, an IP address string + # to match a specific address, an IP address mask per IPAddr, or one + # containing "*" to match part of an IPv4 address. + # + # IPAddr::InvalidPrefixError may be raised when an IP network + # address with an invalid netmask/prefix is given. + + def initialize(str) + if str == '*' or str == 'all' + @pat = [:all] + elsif str.include?('*') + @pat = [:name, dot_pat(str)] + else + begin + @pat = [:ip, IPAddr.new(str)] + rescue IPAddr::InvalidPrefixError + # In this case, `str` shouldn't be a host name pattern + # because it contains a slash. + raise + rescue ArgumentError + @pat = [:name, dot_pat(str)] + end + end + end + + private + + ## + # Creates a regular expression to match IPv4 addresses + + def dot_pat_str(str) + list = str.split('.').collect { |s| + (s == '*') ? '.+' : s + } + list.join("\\.") + end + + private + + ## + # Creates a Regexp to match an address. + + def dot_pat(str) + /\A#{dot_pat_str(str)}\z/ + end + + public + + ## + # Matches +addr+ against this entry. + + def match(addr) + case @pat[0] + when :all + true + when :ip + begin + ipaddr = IPAddr.new(addr[3]) + ipaddr = ipaddr.ipv4_mapped if @pat[1].ipv6? && ipaddr.ipv4? + rescue ArgumentError + return false + end + (@pat[1].include?(ipaddr)) ? true : false + when :name + (@pat[1] =~ addr[2]) ? true : false + else + false + end + end + end + + ## + # A list of ACLEntry objects. Used to implement the allow and deny halves + # of an ACL + + class ACLList + + ## + # Creates an empty ACLList + + def initialize + @list = [] + end + + public + + ## + # Matches +addr+ against each ACLEntry in this list. + + def match(addr) + @list.each do |e| + return true if e.match(addr) + end + false + end + + public + + ## + # Adds +str+ as an ACLEntry in this list + + def add(str) + @list.push(ACLEntry.new(str)) + end + + end + + ## + # Default to deny + + DENY_ALLOW = 0 + + ## + # Default to allow + + ALLOW_DENY = 1 + + ## + # Creates a new ACL from +list+ with an evaluation +order+ of DENY_ALLOW or + # ALLOW_DENY. + # + # An ACL +list+ is an Array of "allow" or "deny" and an address or address + # mask or "all" or "*" to match any address: + # + # %w[ + # deny all + # allow 192.0.2.2 + # allow 192.0.2.128/26 + # ] + + def initialize(list=nil, order = DENY_ALLOW) + @order = order + @deny = ACLList.new + @allow = ACLList.new + install_list(list) if list + end + + public + + ## + # Allow connections from Socket +soc+? + + def allow_socket?(soc) + allow_addr?(soc.peeraddr) + end + + public + + ## + # Allow connections from addrinfo +addr+? It must be formatted like + # Socket#peeraddr: + # + # ["AF_INET", 10, "lc630", "192.0.2.1"] + + def allow_addr?(addr) + case @order + when DENY_ALLOW + return true if @allow.match(addr) + return false if @deny.match(addr) + return true + when ALLOW_DENY + return false if @deny.match(addr) + return true if @allow.match(addr) + return false + else + false + end + end + + public + + ## + # Adds +list+ of ACL entries to this ACL. + + def install_list(list) + i = 0 + while i < list.size + permission, domain = list.slice(i,2) + case permission.downcase + when 'allow' + @allow.add(domain) + when 'deny' + @deny.add(domain) + else + raise "Invalid ACL entry #{list}" + end + i += 2 + end + end + +end diff --git a/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/drb.rb b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/drb.rb new file mode 100644 index 00000000..8073ac09 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/drb.rb @@ -0,0 +1,1993 @@ +# frozen_string_literal: false +# +# = drb/drb.rb +# +# Distributed Ruby: _dRuby_ +# +# Copyright (c) 1999-2003 Masatoshi SEKI. You can redistribute it and/or +# modify it under the same terms as Ruby. +# +# Author:: Masatoshi SEKI +# +# Documentation:: William Webber (william@williamwebber.com) +# +# == Overview +# +# dRuby is a distributed object system for Ruby. It allows an object in one +# Ruby process to invoke methods on an object in another Ruby process on the +# same or a different machine. +# +# The Ruby standard library contains the core classes of the dRuby package. +# However, the full package also includes access control lists and the +# Rinda tuple-space distributed task management system, as well as a +# large number of samples. The full dRuby package can be downloaded from +# the dRuby home page (see *References*). +# +# For an introduction and examples of usage see the documentation to the +# DRb module. +# +# == References +# +# [http://www2a.biglobe.ne.jp/~seki/ruby/druby.html] +# The dRuby home page, in Japanese. Contains the full dRuby package +# and links to other Japanese-language sources. +# +# [http://www2a.biglobe.ne.jp/~seki/ruby/druby.en.html] +# The English version of the dRuby home page. +# +# [https://web.archive.org/web/20160611151955/https://pragprog.com/book/sidruby/the-druby-book] +# The dRuby Book: Distributed and Parallel Computing with Ruby +# by Masatoshi Seki and Makoto Inoue +# +# [http://ruby-doc.com/docs/ProgrammingRuby/html/ospace.html] +# The chapter from *Programming* *Ruby* by Dave Thomas and Andy Hunt +# which discusses dRuby. +# +# [http://www.clio.ne.jp/home/web-i31s/Flotuard/Ruby/PRC2K_seki/dRuby.en.html] +# Translation of presentation on Ruby by Masatoshi Seki. + +require 'socket' +require 'io/wait' +require 'monitor' +require_relative 'eq' +require_relative 'version' + +# +# == Overview +# +# dRuby is a distributed object system for Ruby. It is written in +# pure Ruby and uses its own protocol. No add-in services are needed +# beyond those provided by the Ruby runtime, such as TCP sockets. It +# does not rely on or interoperate with other distributed object +# systems such as CORBA, RMI, or .NET. +# +# dRuby allows methods to be called in one Ruby process upon a Ruby +# object located in another Ruby process, even on another machine. +# References to objects can be passed between processes. Method +# arguments and return values are dumped and loaded in marshalled +# format. All of this is done transparently to both the caller of the +# remote method and the object that it is called upon. +# +# An object in a remote process is locally represented by a +# DRb::DRbObject instance. This acts as a sort of proxy for the +# remote object. Methods called upon this DRbObject instance are +# forwarded to its remote object. This is arranged dynamically at run +# time. There are no statically declared interfaces for remote +# objects, such as CORBA's IDL. +# +# dRuby calls made into a process are handled by a DRb::DRbServer +# instance within that process. This reconstitutes the method call, +# invokes it upon the specified local object, and returns the value to +# the remote caller. Any object can receive calls over dRuby. There +# is no need to implement a special interface, or mixin special +# functionality. Nor, in the general case, does an object need to +# explicitly register itself with a DRbServer in order to receive +# dRuby calls. +# +# One process wishing to make dRuby calls upon another process must +# somehow obtain an initial reference to an object in the remote +# process by some means other than as the return value of a remote +# method call, as there is initially no remote object reference it can +# invoke a method upon. This is done by attaching to the server by +# URI. Each DRbServer binds itself to a URI such as +# 'druby://example.com:8787'. A DRbServer can have an object attached +# to it that acts as the server's *front* *object*. A DRbObject can +# be explicitly created from the server's URI. This DRbObject's +# remote object will be the server's front object. This front object +# can then return references to other Ruby objects in the DRbServer's +# process. +# +# Method calls made over dRuby behave largely the same as normal Ruby +# method calls made within a process. Method calls with blocks are +# supported, as are raising exceptions. In addition to a method's +# standard errors, a dRuby call may also raise one of the +# dRuby-specific errors, all of which are subclasses of DRb::DRbError. +# +# Any type of object can be passed as an argument to a dRuby call or +# returned as its return value. By default, such objects are dumped +# or marshalled at the local end, then loaded or unmarshalled at the +# remote end. The remote end therefore receives a copy of the local +# object, not a distributed reference to it; methods invoked upon this +# copy are executed entirely in the remote process, not passed on to +# the local original. This has semantics similar to pass-by-value. +# +# However, if an object cannot be marshalled, a dRuby reference to it +# is passed or returned instead. This will turn up at the remote end +# as a DRbObject instance. All methods invoked upon this remote proxy +# are forwarded to the local object, as described in the discussion of +# DRbObjects. This has semantics similar to the normal Ruby +# pass-by-reference. +# +# The easiest way to signal that we want an otherwise marshallable +# object to be passed or returned as a DRbObject reference, rather +# than marshalled and sent as a copy, is to include the +# DRb::DRbUndumped mixin module. +# +# dRuby supports calling remote methods with blocks. As blocks (or +# rather the Proc objects that represent them) are not marshallable, +# the block executes in the local, not the remote, context. Each +# value yielded to the block is passed from the remote object to the +# local block, then the value returned by each block invocation is +# passed back to the remote execution context to be collected, before +# the collected values are finally returned to the local context as +# the return value of the method invocation. +# +# == Examples of usage +# +# For more dRuby samples, see the +samples+ directory in the full +# dRuby distribution. +# +# === dRuby in client/server mode +# +# This illustrates setting up a simple client-server drb +# system. Run the server and client code in different terminals, +# starting the server code first. +# +# ==== Server code +# +# require 'drb/drb' +# +# # The URI for the server to connect to +# URI="druby://localhost:8787" +# +# class TimeServer +# +# def get_current_time +# return Time.now +# end +# +# end +# +# # The object that handles requests on the server +# FRONT_OBJECT=TimeServer.new +# +# DRb.start_service(URI, FRONT_OBJECT) +# # Wait for the drb server thread to finish before exiting. +# DRb.thread.join +# +# ==== Client code +# +# require 'drb/drb' +# +# # The URI to connect to +# SERVER_URI="druby://localhost:8787" +# +# # Start a local DRbServer to handle callbacks. +# # +# # Not necessary for this small example, but will be required +# # as soon as we pass a non-marshallable object as an argument +# # to a dRuby call. +# # +# # Note: this must be called at least once per process to take any effect. +# # This is particularly important if your application forks. +# DRb.start_service +# +# timeserver = DRbObject.new_with_uri(SERVER_URI) +# puts timeserver.get_current_time +# +# === Remote objects under dRuby +# +# This example illustrates returning a reference to an object +# from a dRuby call. The Logger instances live in the server +# process. References to them are returned to the client process, +# where methods can be invoked upon them. These methods are +# executed in the server process. +# +# ==== Server code +# +# require 'drb/drb' +# +# URI="druby://localhost:8787" +# +# class Logger +# +# # Make dRuby send Logger instances as dRuby references, +# # not copies. +# include DRb::DRbUndumped +# +# def initialize(n, fname) +# @name = n +# @filename = fname +# end +# +# def log(message) +# File.open(@filename, "a") do |f| +# f.puts("#{Time.now}: #{@name}: #{message}") +# end +# end +# +# end +# +# # We have a central object for creating and retrieving loggers. +# # This retains a local reference to all loggers created. This +# # is so an existing logger can be looked up by name, but also +# # to prevent loggers from being garbage collected. A dRuby +# # reference to an object is not sufficient to prevent it being +# # garbage collected! +# class LoggerFactory +# +# def initialize(bdir) +# @basedir = bdir +# @loggers = {} +# end +# +# def get_logger(name) +# if !@loggers.has_key? name +# # make the filename safe, then declare it to be so +# fname = name.gsub(/[.\/\\\:]/, "_") +# @loggers[name] = Logger.new(name, @basedir + "/" + fname) +# end +# return @loggers[name] +# end +# +# end +# +# FRONT_OBJECT=LoggerFactory.new("/tmp/dlog") +# +# DRb.start_service(URI, FRONT_OBJECT) +# DRb.thread.join +# +# ==== Client code +# +# require 'drb/drb' +# +# SERVER_URI="druby://localhost:8787" +# +# DRb.start_service +# +# log_service=DRbObject.new_with_uri(SERVER_URI) +# +# ["loga", "logb", "logc"].each do |logname| +# +# logger=log_service.get_logger(logname) +# +# logger.log("Hello, world!") +# logger.log("Goodbye, world!") +# logger.log("=== EOT ===") +# +# end +# +# == Security +# +# As with all network services, security needs to be considered when +# using dRuby. By allowing external access to a Ruby object, you are +# not only allowing outside clients to call the methods you have +# defined for that object, but by default to execute arbitrary Ruby +# code on your server. Consider the following: +# +# # !!! UNSAFE CODE !!! +# ro = DRbObject::new_with_uri("druby://your.server.com:8989") +# class << ro +# undef :instance_eval # force call to be passed to remote object +# end +# ro.instance_eval("`rm -rf *`") +# +# The dangers posed by instance_eval and friends are such that a +# DRbServer should only be used when clients are trusted. +# +# A DRbServer can be configured with an access control list to +# selectively allow or deny access from specified IP addresses. The +# main druby distribution provides the ACL class for this purpose. In +# general, this mechanism should only be used alongside, rather than +# as a replacement for, a good firewall. +# +# == dRuby internals +# +# dRuby is implemented using three main components: a remote method +# call marshaller/unmarshaller; a transport protocol; and an +# ID-to-object mapper. The latter two can be directly, and the first +# indirectly, replaced, in order to provide different behaviour and +# capabilities. +# +# Marshalling and unmarshalling of remote method calls is performed by +# a DRb::DRbMessage instance. This uses the Marshal module to dump +# the method call before sending it over the transport layer, then +# reconstitute it at the other end. There is normally no need to +# replace this component, and no direct way is provided to do so. +# However, it is possible to implement an alternative marshalling +# scheme as part of an implementation of the transport layer. +# +# The transport layer is responsible for opening client and server +# network connections and forwarding dRuby request across them. +# Normally, it uses DRb::DRbMessage internally to manage marshalling +# and unmarshalling. The transport layer is managed by +# DRb::DRbProtocol. Multiple protocols can be installed in +# DRbProtocol at the one time; selection between them is determined by +# the scheme of a dRuby URI. The default transport protocol is +# selected by the scheme 'druby:', and implemented by +# DRb::DRbTCPSocket. This uses plain TCP/IP sockets for +# communication. An alternative protocol, using UNIX domain sockets, +# is implemented by DRb::DRbUNIXSocket in the file drb/unix.rb, and +# selected by the scheme 'drbunix:'. A sample implementation over +# HTTP can be found in the samples accompanying the main dRuby +# distribution. +# +# The ID-to-object mapping component maps dRuby object ids to the +# objects they refer to, and vice versa. The implementation to use +# can be specified as part of a DRb::DRbServer's configuration. The +# default implementation is provided by DRb::DRbIdConv. It uses an +# object's ObjectSpace id as its dRuby id. This means that the dRuby +# reference to that object only remains meaningful for the lifetime of +# the object's process and the lifetime of the object within that +# process. A modified implementation is provided by DRb::TimerIdConv +# in the file drb/timeridconv.rb. This implementation retains a local +# reference to all objects exported over dRuby for a configurable +# period of time (defaulting to ten minutes), to prevent them being +# garbage-collected within this time. Another sample implementation +# is provided in sample/name.rb in the main dRuby distribution. This +# allows objects to specify their own id or "name". A dRuby reference +# can be made persistent across processes by having each process +# register an object using the same dRuby name. +# +module DRb + + # Superclass of all errors raised in the DRb module. + class DRbError < RuntimeError; end + + # Error raised when an error occurs on the underlying communication + # protocol. + class DRbConnError < DRbError; end + + class DRbObjectSpace # :nodoc: + # This is an internal class for DRbIdConv. This must not be used + # by users. + + include MonitorMixin + + def initialize + super() + @map = ObjectSpace::WeakMap.new + end + + def to_id(obj) + synchronize do + @map[obj.__id__] = obj + obj.__id__ + end + end + + def to_obj(ref) + synchronize do + obj = @map[ref] + raise RangeError.new("invalid reference") unless obj.__id__ == ref + obj + end + end + end + + # :nodoc: + # + # This is an internal singleton instance. This must not be used + # by users. + DRB_OBJECT_SPACE = DRbObjectSpace.new + + # Class responsible for converting between an object and its id. + # + # This, the default implementation, uses an object's local ObjectSpace + # __id__ as its id. This means that an object's identification over + # drb remains valid only while that object instance remains alive + # within the server runtime. + # + # For alternative mechanisms, see DRb::TimerIdConv in drb/timeridconv.rb + # and DRbNameIdConv in sample/name.rb in the full drb distribution. + class DRbIdConv + + # Convert an object reference id to an object. + # + # This implementation looks up the reference id in the local object + # space and returns the object it refers to. + def to_obj(ref) + DRB_OBJECT_SPACE.to_obj(ref) + end + + # Convert an object into a reference id. + # + # This implementation returns the object's __id__ in the local + # object space. + def to_id(obj) + (nil == obj) ? nil : DRB_OBJECT_SPACE.to_id(obj) + end + end + + # Mixin module making an object undumpable or unmarshallable. + # + # If an object which includes this module is returned by method + # called over drb, then the object remains in the server space + # and a reference to the object is returned, rather than the + # object being marshalled and moved into the client space. + module DRbUndumped + def _dump(dummy) # :nodoc: + raise TypeError, 'can\'t dump' + end + end + + # Error raised by the DRb module when an attempt is made to refer to + # the context's current drb server but the context does not have one. + # See #current_server. + class DRbServerNotFound < DRbError; end + + # Error raised by the DRbProtocol module when it cannot find any + # protocol implementation support the scheme specified in a URI. + class DRbBadURI < DRbError; end + + # Error raised by a dRuby protocol when it doesn't support the + # scheme specified in a URI. See DRb::DRbProtocol. + class DRbBadScheme < DRbError; end + + # An exception wrapping a DRb::DRbUnknown object + class DRbUnknownError < DRbError + + # Create a new DRbUnknownError for the DRb::DRbUnknown object +unknown+ + def initialize(unknown) + @unknown = unknown + super(unknown.name) + end + + # Get the wrapped DRb::DRbUnknown object. + attr_reader :unknown + + def self._load(s) # :nodoc: + Marshal::load(s) + end + + def _dump(lv) # :nodoc: + Marshal::dump(@unknown) + end + end + + # An exception wrapping an error object + class DRbRemoteError < DRbError + + # Creates a new remote error that wraps the Exception +error+ + def initialize(error) + @reason = error.class.to_s + super("#{error.message} (#{error.class})") + set_backtrace(error.backtrace) + end + + # the class of the error, as a string. + attr_reader :reason + end + + # Class wrapping a marshalled object whose type is unknown locally. + # + # If an object is returned by a method invoked over drb, but the + # class of the object is unknown in the client namespace, or + # the object is a constant unknown in the client namespace, then + # the still-marshalled object is returned wrapped in a DRbUnknown instance. + # + # If this object is passed as an argument to a method invoked over + # drb, then the wrapped object is passed instead. + # + # The class or constant name of the object can be read from the + # +name+ attribute. The marshalled object is held in the +buf+ + # attribute. + class DRbUnknown + + # Create a new DRbUnknown object. + # + # +buf+ is a string containing a marshalled object that could not + # be unmarshalled. +err+ is the error message that was raised + # when the unmarshalling failed. It is used to determine the + # name of the unmarshalled object. + def initialize(err, buf) + case err.to_s + when /uninitialized constant (\S+)/ + @name = $1 + when /undefined class\/module (\S+)/ + @name = $1 + else + @name = nil + end + @buf = buf + end + + # The name of the unknown thing. + # + # Class name for unknown objects; variable name for unknown + # constants. + attr_reader :name + + # Buffer contained the marshalled, unknown object. + attr_reader :buf + + def self._load(s) # :nodoc: + begin + Marshal::load(s) + rescue NameError, ArgumentError + DRbUnknown.new($!, s) + end + end + + def _dump(lv) # :nodoc: + @buf + end + + # Attempt to load the wrapped marshalled object again. + # + # If the class of the object is now known locally, the object + # will be unmarshalled and returned. Otherwise, a new + # but identical DRbUnknown object will be returned. + def reload + self.class._load(@buf) + end + + # Create a DRbUnknownError exception containing this object. + def exception + DRbUnknownError.new(self) + end + end + + # An Array wrapper that can be sent to another server via DRb. + # + # All entries in the array will be dumped or be references that point to + # the local server. + + class DRbArray + + # Creates a new DRbArray that either dumps or wraps all the items in the + # Array +ary+ so they can be loaded by a remote DRb server. + + def initialize(ary) + @ary = ary.collect { |obj| + if obj.kind_of? DRbUndumped + DRbObject.new(obj) + else + begin + Marshal.dump(obj) + obj + rescue + DRbObject.new(obj) + end + end + } + end + + def self._load(s) # :nodoc: + Marshal::load(s) + end + + def _dump(lv) # :nodoc: + Marshal.dump(@ary) + end + end + + # Handler for sending and receiving drb messages. + # + # This takes care of the low-level marshalling and unmarshalling + # of drb requests and responses sent over the wire between server + # and client. This relieves the implementor of a new drb + # protocol layer with having to deal with these details. + # + # The user does not have to directly deal with this object in + # normal use. + class DRbMessage + def initialize(config) # :nodoc: + @load_limit = config[:load_limit] + @argc_limit = config[:argc_limit] + end + + def dump(obj, error=false) # :nodoc: + case obj + when DRbUndumped + obj = make_proxy(obj, error) + when Object + # nothing + else + obj = make_proxy(obj, error) + end + begin + str = Marshal::dump(obj) + rescue + str = Marshal::dump(make_proxy(obj, error)) + end + [str.size].pack('N') + str + end + + def load(soc) # :nodoc: + begin + sz = soc.read(4) # sizeof (N) + rescue + raise(DRbConnError, $!.message, $!.backtrace) + end + raise(DRbConnError, 'connection closed') if sz.nil? + raise(DRbConnError, 'premature header') if sz.size < 4 + sz = sz.unpack('N')[0] + raise(DRbConnError, "too large packet #{sz}") if @load_limit < sz + begin + str = soc.read(sz) + rescue + raise(DRbConnError, $!.message, $!.backtrace) + end + raise(DRbConnError, 'connection closed') if str.nil? + raise(DRbConnError, 'premature marshal format(can\'t read)') if str.size < sz + DRb.mutex.synchronize do + begin + Marshal::load(str) + rescue NameError, ArgumentError + DRbUnknown.new($!, str) + end + end + end + + def send_request(stream, ref, msg_id, arg, b) # :nodoc: + ary = [] + ary.push(dump(ref.__drbref)) + ary.push(dump(msg_id.id2name)) + ary.push(dump(arg.length)) + arg.each do |e| + ary.push(dump(e)) + end + ary.push(dump(b)) + stream.write(ary.join('')) + rescue + raise(DRbConnError, $!.message, $!.backtrace) + end + + def recv_request(stream) # :nodoc: + ref = load(stream) + ro = DRb.to_obj(ref) + msg = load(stream) + argc = load(stream) + raise(DRbConnError, "too many arguments") if @argc_limit < argc + argv = Array.new(argc, nil) + argc.times do |n| + argv[n] = load(stream) + end + block = load(stream) + return ro, msg, argv, block + end + + def send_reply(stream, succ, result) # :nodoc: + stream.write(dump(succ) + dump(result, !succ)) + rescue + raise(DRbConnError, $!.message, $!.backtrace) + end + + def recv_reply(stream) # :nodoc: + succ = load(stream) + result = load(stream) + [succ, result] + end + + private + def make_proxy(obj, error=false) # :nodoc: + if error + DRbRemoteError.new(obj) + else + DRbObject.new(obj) + end + end + end + + # Module managing the underlying network protocol(s) used by drb. + # + # By default, drb uses the DRbTCPSocket protocol. Other protocols + # can be defined. A protocol must define the following class methods: + # + # [open(uri, config)] Open a client connection to the server at +uri+, + # using configuration +config+. Return a protocol + # instance for this connection. + # [open_server(uri, config)] Open a server listening at +uri+, + # using configuration +config+. Return a + # protocol instance for this listener. + # [uri_option(uri, config)] Take a URI, possibly containing an option + # component (e.g. a trailing '?param=val'), + # and return a [uri, option] tuple. + # + # All of these methods should raise a DRbBadScheme error if the URI + # does not identify the protocol they support (e.g. "druby:" for + # the standard Ruby protocol). This is how the DRbProtocol module, + # given a URI, determines which protocol implementation serves that + # protocol. + # + # The protocol instance returned by #open_server must have the + # following methods: + # + # [accept] Accept a new connection to the server. Returns a protocol + # instance capable of communicating with the client. + # [close] Close the server connection. + # [uri] Get the URI for this server. + # + # The protocol instance returned by #open must have the following methods: + # + # [send_request (ref, msg_id, arg, b)] + # Send a request to +ref+ with the given message id and arguments. + # This is most easily implemented by calling DRbMessage.send_request, + # providing a stream that sits on top of the current protocol. + # [recv_reply] + # Receive a reply from the server and return it as a [success-boolean, + # reply-value] pair. This is most easily implemented by calling + # DRb.recv_reply, providing a stream that sits on top of the + # current protocol. + # [alive?] + # Is this connection still alive? + # [close] + # Close this connection. + # + # The protocol instance returned by #open_server().accept() must have + # the following methods: + # + # [recv_request] + # Receive a request from the client and return a [object, message, + # args, block] tuple. This is most easily implemented by calling + # DRbMessage.recv_request, providing a stream that sits on top of + # the current protocol. + # [send_reply(succ, result)] + # Send a reply to the client. This is most easily implemented + # by calling DRbMessage.send_reply, providing a stream that sits + # on top of the current protocol. + # [close] + # Close this connection. + # + # A new protocol is registered with the DRbProtocol module using + # the add_protocol method. + # + # For examples of other protocols, see DRbUNIXSocket in drb/unix.rb, + # and HTTP0 in sample/http0.rb and sample/http0serv.rb in the full + # drb distribution. + module DRbProtocol + + # Add a new protocol to the DRbProtocol module. + def add_protocol(prot) + @protocol.push(prot) + end + module_function :add_protocol + + # Open a client connection to +uri+ with the configuration +config+. + # + # The DRbProtocol module asks each registered protocol in turn to + # try to open the URI. Each protocol signals that it does not handle that + # URI by raising a DRbBadScheme error. If no protocol recognises the + # URI, then a DRbBadURI error is raised. If a protocol accepts the + # URI, but an error occurs in opening it, a DRbConnError is raised. + def open(uri, config, first=true) + @protocol.each do |prot| + begin + return prot.open(uri, config) + rescue DRbBadScheme + rescue DRbConnError + raise($!) + rescue + raise(DRbConnError, "#{uri} - #{$!.inspect}") + end + end + if first && (config[:auto_load] != false) + auto_load(uri) + return open(uri, config, false) + end + raise DRbBadURI, 'can\'t parse uri:' + uri + end + module_function :open + + # Open a server listening for connections at +uri+ with + # configuration +config+. + # + # The DRbProtocol module asks each registered protocol in turn to + # try to open a server at the URI. Each protocol signals that it does + # not handle that URI by raising a DRbBadScheme error. If no protocol + # recognises the URI, then a DRbBadURI error is raised. If a protocol + # accepts the URI, but an error occurs in opening it, the underlying + # error is passed on to the caller. + def open_server(uri, config, first=true) + @protocol.each do |prot| + begin + return prot.open_server(uri, config) + rescue DRbBadScheme + end + end + if first && (config[:auto_load] != false) + auto_load(uri) + return open_server(uri, config, false) + end + raise DRbBadURI, 'can\'t parse uri:' + uri + end + module_function :open_server + + # Parse +uri+ into a [uri, option] pair. + # + # The DRbProtocol module asks each registered protocol in turn to + # try to parse the URI. Each protocol signals that it does not handle that + # URI by raising a DRbBadScheme error. If no protocol recognises the + # URI, then a DRbBadURI error is raised. + def uri_option(uri, config, first=true) + @protocol.each do |prot| + begin + uri, opt = prot.uri_option(uri, config) + # opt = nil if opt == '' + return uri, opt + rescue DRbBadScheme + end + end + if first && (config[:auto_load] != false) + auto_load(uri) + return uri_option(uri, config, false) + end + raise DRbBadURI, 'can\'t parse uri:' + uri + end + module_function :uri_option + + def auto_load(uri) # :nodoc: + if /\Adrb([a-z0-9]+):/ =~ uri + require("drb/#{$1}") rescue nil + end + end + module_function :auto_load + end + + # The default drb protocol which communicates over a TCP socket. + # + # The DRb TCP protocol URI looks like: + # druby://:?. The option is optional. + + class DRbTCPSocket + # :stopdoc: + private + def self.parse_uri(uri) + if /\Adruby:\/\/(.*?):(\d+)(\?(.*))?\z/ =~ uri + host = $1 + port = $2.to_i + option = $4 + [host, port, option] + else + raise(DRbBadScheme, uri) unless uri.start_with?('druby:') + raise(DRbBadURI, 'can\'t parse uri:' + uri) + end + end + + public + + # Open a client connection to +uri+ (DRb URI string) using configuration + # +config+. + # + # This can raise DRb::DRbBadScheme or DRb::DRbBadURI if +uri+ is not for a + # recognized protocol. See DRb::DRbServer.new for information on built-in + # URI protocols. + def self.open(uri, config) + host, port, = parse_uri(uri) + soc = TCPSocket.open(host, port) + self.new(uri, soc, config) + end + + # Returns the hostname of this server + def self.getservername + host = Socket::gethostname + begin + Socket::getaddrinfo(host, nil, + Socket::AF_UNSPEC, + Socket::SOCK_STREAM, + 0, + Socket::AI_PASSIVE)[0][3] + rescue + 'localhost' + end + end + + # For the families available for +host+, returns a TCPServer on +port+. + # If +port+ is 0 the first available port is used. IPv4 servers are + # preferred over IPv6 servers. + def self.open_server_inaddr_any(host, port) + infos = Socket::getaddrinfo(host, nil, + Socket::AF_UNSPEC, + Socket::SOCK_STREAM, + 0, + Socket::AI_PASSIVE) + families = Hash[*infos.collect { |af, *_| af }.uniq.zip([]).flatten] + return TCPServer.open('0.0.0.0', port) if families.has_key?('AF_INET') + return TCPServer.open('::', port) if families.has_key?('AF_INET6') + return TCPServer.open(port) + # :stopdoc: + end + + # Open a server listening for connections at +uri+ using + # configuration +config+. + def self.open_server(uri, config) + uri = 'druby://:0' unless uri + host, port, _ = parse_uri(uri) + config = {:tcp_original_host => host}.update(config) + if host.size == 0 + host = getservername + soc = open_server_inaddr_any(host, port) + else + soc = TCPServer.open(host, port) + end + port = soc.addr[1] if port == 0 + config[:tcp_port] = port + uri = "druby://#{host}:#{port}" + self.new(uri, soc, config) + end + + # Parse +uri+ into a [uri, option] pair. + def self.uri_option(uri, config) + host, port, option = parse_uri(uri) + return "druby://#{host}:#{port}", option + end + + # Create a new DRbTCPSocket instance. + # + # +uri+ is the URI we are connected to. + # +soc+ is the tcp socket we are bound to. +config+ is our + # configuration. + def initialize(uri, soc, config={}) + @uri = uri + @socket = soc + @config = config + @acl = config[:tcp_acl] + @msg = DRbMessage.new(config) + set_sockopt(@socket) + @shutdown_pipe_r, @shutdown_pipe_w = IO.pipe + end + + # Get the URI that we are connected to. + attr_reader :uri + + # Get the address of our TCP peer (the other end of the socket + # we are bound to. + def peeraddr + @socket.peeraddr + end + + # Get the socket. + def stream; @socket; end + + # On the client side, send a request to the server. + def send_request(ref, msg_id, arg, b) + @msg.send_request(stream, ref, msg_id, arg, b) + end + + # On the server side, receive a request from the client. + def recv_request + @msg.recv_request(stream) + end + + # On the server side, send a reply to the client. + def send_reply(succ, result) + @msg.send_reply(stream, succ, result) + end + + # On the client side, receive a reply from the server. + def recv_reply + @msg.recv_reply(stream) + end + + public + + # Close the connection. + # + # If this is an instance returned by #open_server, then this stops + # listening for new connections altogether. If this is an instance + # returned by #open or by #accept, then it closes this particular + # client-server session. + def close + shutdown + if @socket + @socket.close + @socket = nil + end + close_shutdown_pipe + end + + def close_shutdown_pipe + @shutdown_pipe_w.close + @shutdown_pipe_r.close + end + private :close_shutdown_pipe + + # On the server side, for an instance returned by #open_server, + # accept a client connection and return a new instance to handle + # the server's side of this client-server session. + def accept + while true + s = accept_or_shutdown + return nil unless s + break if (@acl ? @acl.allow_socket?(s) : true) + s.close + end + if @config[:tcp_original_host].to_s.size == 0 + uri = "druby://#{s.addr[3]}:#{@config[:tcp_port]}" + else + uri = @uri + end + self.class.new(uri, s, @config) + end + + def accept_or_shutdown + readables, = IO.select([@socket, @shutdown_pipe_r]) + if readables.include? @shutdown_pipe_r + return nil + end + @socket.accept + end + private :accept_or_shutdown + + # Graceful shutdown + def shutdown + @shutdown_pipe_w.close + end + + # Check to see if this connection is alive. + def alive? + return false unless @socket + if @socket.to_io.wait_readable(0) + close + return false + end + true + end + + def set_sockopt(soc) # :nodoc: + soc.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) + rescue IOError, Errno::ECONNRESET, Errno::EINVAL + # closed/shutdown socket, ignore error + end + end + + module DRbProtocol + @protocol = [DRbTCPSocket] # default + end + + class DRbURIOption # :nodoc: I don't understand the purpose of this class... + def initialize(option) + @option = option.to_s + end + attr_reader :option + def to_s; @option; end + + def ==(other) + return false unless DRbURIOption === other + @option == other.option + end + + def hash + @option.hash + end + + alias eql? == + end + + # Object wrapping a reference to a remote drb object. + # + # Method calls on this object are relayed to the remote + # object that this object is a stub for. + class DRbObject + + # Unmarshall a marshalled DRbObject. + # + # If the referenced object is located within the local server, then + # the object itself is returned. Otherwise, a new DRbObject is + # created to act as a stub for the remote referenced object. + def self._load(s) + uri, ref = Marshal.load(s) + + if DRb.here?(uri) + obj = DRb.to_obj(ref) + return obj + end + + self.new_with(uri, ref) + end + + # Creates a DRb::DRbObject given the reference information to the remote + # host +uri+ and object +ref+. + + def self.new_with(uri, ref) + it = self.allocate + it.instance_variable_set(:@uri, uri) + it.instance_variable_set(:@ref, ref) + it + end + + # Create a new DRbObject from a URI alone. + def self.new_with_uri(uri) + self.new(nil, uri) + end + + # Marshall this object. + # + # The URI and ref of the object are marshalled. + def _dump(lv) + Marshal.dump([@uri, @ref]) + end + + # Create a new remote object stub. + # + # +obj+ is the (local) object we want to create a stub for. Normally + # this is +nil+. +uri+ is the URI of the remote object that this + # will be a stub for. + def initialize(obj, uri=nil) + @uri = nil + @ref = nil + case obj + when Object + is_nil = obj.nil? + when BasicObject + is_nil = false + end + + if is_nil + return if uri.nil? + @uri, option = DRbProtocol.uri_option(uri, DRb.config) + @ref = DRbURIOption.new(option) unless option.nil? + else + @uri = uri ? uri : (DRb.uri rescue nil) + @ref = obj ? DRb.to_id(obj) : nil + end + end + + # Get the URI of the remote object. + def __drburi + @uri + end + + # Get the reference of the object, if local. + def __drbref + @ref + end + + undef :to_s + undef :to_a if respond_to?(:to_a) + + # Routes respond_to? to the referenced remote object. + def respond_to?(msg_id, priv=false) + case msg_id + when :_dump + true + when :marshal_dump + false + else + method_missing(:respond_to?, msg_id, priv) + end + end + + # Routes method calls to the referenced remote object. + ruby2_keywords def method_missing(msg_id, *a, &b) + if DRb.here?(@uri) + obj = DRb.to_obj(@ref) + DRb.current_server.check_insecure_method(obj, msg_id) + return obj.__send__(msg_id, *a, &b) + end + + succ, result = self.class.with_friend(@uri) do + DRbConn.open(@uri) do |conn| + conn.send_message(self, msg_id, a, b) + end + end + + if succ + return result + elsif DRbUnknown === result + raise result + else + bt = self.class.prepare_backtrace(@uri, result) + result.set_backtrace(bt + caller) + raise result + end + end + + # Given the +uri+ of another host executes the block provided. + def self.with_friend(uri) # :nodoc: + friend = DRb.fetch_server(uri) + return yield() unless friend + + save = Thread.current['DRb'] + Thread.current['DRb'] = { 'server' => friend } + return yield + ensure + Thread.current['DRb'] = save if friend + end + + # Returns a modified backtrace from +result+ with the +uri+ where each call + # in the backtrace came from. + def self.prepare_backtrace(uri, result) # :nodoc: + prefix = "(#{uri}) " + bt = [] + result.backtrace.each do |x| + break if /[`']__send__'$/ =~ x + if /\A\(druby:\/\// =~ x + bt.push(x) + else + bt.push(prefix + x) + end + end + bt + end + + def pretty_print(q) # :nodoc: + q.pp_object(self) + end + + def pretty_print_cycle(q) # :nodoc: + q.object_address_group(self) { + q.breakable + q.text '...' + } + end + end + + class ThreadObject + include MonitorMixin + + def initialize(&blk) + super() + @wait_ev = new_cond + @req_ev = new_cond + @res_ev = new_cond + @status = :wait + @req = nil + @res = nil + @thread = Thread.new(self, &blk) + end + + def alive? + @thread.alive? + end + + def kill + @thread.kill + @thread.join + end + + def method_missing(msg, *arg, &blk) + synchronize do + @wait_ev.wait_until { @status == :wait } + @req = [msg] + arg + @status = :req + @req_ev.broadcast + @res_ev.wait_until { @status == :res } + value = @res + @req = @res = nil + @status = :wait + @wait_ev.broadcast + return value + end + end + + def _execute() + synchronize do + @req_ev.wait_until { @status == :req } + @res = yield(@req) + @status = :res + @res_ev.signal + end + end + end + + # Class handling the connection between a DRbObject and the + # server the real object lives on. + # + # This class maintains a pool of connections, to reduce the + # overhead of starting and closing down connections for each + # method call. + # + # This class is used internally by DRbObject. The user does + # not normally need to deal with it directly. + class DRbConn + POOL_SIZE = 16 # :nodoc: + + def self.make_pool + ThreadObject.new do |queue| + pool = [] + while true + queue._execute do |message| + case(message[0]) + when :take then + remote_uri = message[1] + conn = nil + new_pool = [] + pool.each do |c| + if conn.nil? and c.uri == remote_uri + conn = c if c.alive? + else + new_pool.push c + end + end + pool = new_pool + conn + when :store then + conn = message[1] + pool.unshift(conn) + pool.pop.close while pool.size > POOL_SIZE + conn + else + nil + end + end + end + end + end + @pool_proxy = nil + + def self.stop_pool + @pool_proxy&.kill + @pool_proxy = nil + end + + def self.open(remote_uri) # :nodoc: + begin + @pool_proxy = make_pool unless @pool_proxy&.alive? + + conn = @pool_proxy.take(remote_uri) + conn = self.new(remote_uri) unless conn + succ, result = yield(conn) + return succ, result + + ensure + if conn + if succ + @pool_proxy.store(conn) + else + conn.close + end + end + end + end + + def initialize(remote_uri) # :nodoc: + @uri = remote_uri + @protocol = DRbProtocol.open(remote_uri, DRb.config) + end + attr_reader :uri # :nodoc: + + def send_message(ref, msg_id, arg, block) # :nodoc: + @protocol.send_request(ref, msg_id, arg, block) + @protocol.recv_reply + end + + def close # :nodoc: + @protocol.close + @protocol = nil + end + + def alive? # :nodoc: + return false unless @protocol + @protocol.alive? + end + end + + # Class representing a drb server instance. + # + # A DRbServer must be running in the local process before any incoming + # dRuby calls can be accepted, or any local objects can be passed as + # dRuby references to remote processes, even if those local objects are + # never actually called remotely. You do not need to start a DRbServer + # in the local process if you are only making outgoing dRuby calls + # passing marshalled parameters. + # + # Unless multiple servers are being used, the local DRbServer is normally + # started by calling DRb.start_service. + class DRbServer + @@acl = nil + @@idconv = DRbIdConv.new + @@secondary_server = nil + @@argc_limit = 256 + @@load_limit = 0xffffffff + @@verbose = false + + # Set the default value for the :argc_limit option. + # + # See #new(). The initial default value is 256. + def self.default_argc_limit(argc) + @@argc_limit = argc + end + + # Set the default value for the :load_limit option. + # + # See #new(). The initial default value is 25 MB. + def self.default_load_limit(sz) + @@load_limit = sz + end + + # Set the default access control list to +acl+. The default ACL is +nil+. + # + # See also DRb::ACL and #new() + def self.default_acl(acl) + @@acl = acl + end + + # Set the default value for the :id_conv option. + # + # See #new(). The initial default value is a DRbIdConv instance. + def self.default_id_conv(idconv) + @@idconv = idconv + end + + # Set the default value of the :verbose option. + # + # See #new(). The initial default value is false. + def self.verbose=(on) + @@verbose = on + end + + # Get the default value of the :verbose option. + def self.verbose + @@verbose + end + + def self.make_config(hash={}) # :nodoc: + default_config = { + :idconv => @@idconv, + :verbose => @@verbose, + :tcp_acl => @@acl, + :load_limit => @@load_limit, + :argc_limit => @@argc_limit, + } + default_config.update(hash) + end + + # Create a new DRbServer instance. + # + # +uri+ is the URI to bind to. This is normally of the form + # 'druby://:' where is a hostname of + # the local machine. If nil, then the system's default hostname + # will be bound to, on a port selected by the system; these value + # can be retrieved from the +uri+ attribute. 'druby:' specifies + # the default dRuby transport protocol: another protocol, such + # as 'drbunix:', can be specified instead. + # + # +front+ is the front object for the server, that is, the object + # to which remote method calls on the server will be passed. If + # nil, then the server will not accept remote method calls. + # + # If +config_or_acl+ is a hash, it is the configuration to + # use for this server. The following options are recognised: + # + # :idconv :: an id-to-object conversion object. This defaults + # to an instance of the class DRb::DRbIdConv. + # :verbose :: if true, all unsuccessful remote calls on objects + # in the server will be logged to $stdout. false + # by default. + # :tcp_acl :: the access control list for this server. See + # the ACL class from the main dRuby distribution. + # :load_limit :: the maximum message size in bytes accepted by + # the server. Defaults to 25 MB (26214400). + # :argc_limit :: the maximum number of arguments to a remote + # method accepted by the server. Defaults to + # 256. + # The default values of these options can be modified on + # a class-wide basis by the class methods #default_argc_limit, + # #default_load_limit, #default_acl, #default_id_conv, + # and #verbose= + # + # If +config_or_acl+ is not a hash, but is not nil, it is + # assumed to be the access control list for this server. + # See the :tcp_acl option for more details. + # + # If no other server is currently set as the primary server, + # this will become the primary server. + # + # The server will immediately start running in its own thread. + def initialize(uri=nil, front=nil, config_or_acl=nil) + if Hash === config_or_acl + config = config_or_acl.dup + else + acl = config_or_acl || @@acl + config = { + :tcp_acl => acl + } + end + + @config = self.class.make_config(config) + + @protocol = DRbProtocol.open_server(uri, @config) + @uri = @protocol.uri + @exported_uri = [@uri] + + @front = front + @idconv = @config[:idconv] + + @grp = ThreadGroup.new + @thread = run + + DRb.regist_server(self) + end + + # The URI of this DRbServer. + attr_reader :uri + + # The main thread of this DRbServer. + # + # This is the thread that listens for and accepts connections + # from clients, not that handles each client's request-response + # session. + attr_reader :thread + + # The front object of the DRbServer. + # + # This object receives remote method calls made on the server's + # URI alone, with an object id. + attr_reader :front + + # The configuration of this DRbServer + attr_reader :config + + # Set whether to operate in verbose mode. + # + # In verbose mode, failed calls are logged to stdout. + def verbose=(v); @config[:verbose]=v; end + + # Get whether the server is in verbose mode. + # + # In verbose mode, failed calls are logged to stdout. + def verbose; @config[:verbose]; end + + # Is this server alive? + def alive? + @thread.alive? + end + + # Is +uri+ the URI for this server? + def here?(uri) + @exported_uri.include?(uri) + end + + # Stop this server. + def stop_service + DRb.remove_server(self) + if Thread.current['DRb'] && Thread.current['DRb']['server'] == self + Thread.current['DRb']['stop_service'] = true + else + shutdown + end + end + + # Convert a dRuby reference to the local object it refers to. + def to_obj(ref) + return front if ref.nil? + return front[ref.to_s] if DRbURIOption === ref + @idconv.to_obj(ref) + end + + # Convert a local object to a dRuby reference. + def to_id(obj) + return nil if obj.__id__ == front.__id__ + @idconv.to_id(obj) + end + + private + + def shutdown + current = Thread.current + if @protocol.respond_to? :shutdown + @protocol.shutdown + else + [@thread, *@grp.list].each { |thread| + thread.kill unless thread == current # xxx: Thread#kill + } + end + @thread.join unless @thread == current + end + + ## + # Starts the DRb main loop in a new thread. + + def run + Thread.start do + begin + while main_loop + end + ensure + @protocol.close if @protocol + end + end + end + + # List of insecure methods. + # + # These methods are not callable via dRuby. + INSECURE_METHOD = [ + :__send__ + ] + + # Has a method been included in the list of insecure methods? + def insecure_method?(msg_id) + INSECURE_METHOD.include?(msg_id) + end + + # Coerce an object to a string, providing our own representation if + # to_s is not defined for the object. + def any_to_s(obj) + "#{obj}:#{obj.class}" + rescue + Kernel.instance_method(:to_s).bind_call(obj) + end + + # Check that a method is callable via dRuby. + # + # +obj+ is the object we want to invoke the method on. +msg_id+ is the + # method name, as a Symbol. + # + # If the method is an insecure method (see #insecure_method?) a + # SecurityError is thrown. If the method is private or undefined, + # a NameError is thrown. + def check_insecure_method(obj, msg_id) + return true if Proc === obj && msg_id == :__drb_yield + raise(ArgumentError, "#{any_to_s(msg_id)} is not a symbol") unless Symbol == msg_id.class + raise(SecurityError, "insecure method '#{msg_id}'") if insecure_method?(msg_id) + + case obj + when Object + if obj.private_methods.include?(msg_id) + desc = any_to_s(obj) + raise NoMethodError, "private method '#{msg_id}' called for #{desc}" + elsif obj.protected_methods.include?(msg_id) + desc = any_to_s(obj) + raise NoMethodError, "protected method '#{msg_id}' called for #{desc}" + else + true + end + else + if Kernel.instance_method(:private_methods).bind(obj).call.include?(msg_id) + desc = any_to_s(obj) + raise NoMethodError, "private method '#{msg_id}' called for #{desc}" + elsif Kernel.instance_method(:protected_methods).bind(obj).call.include?(msg_id) + desc = any_to_s(obj) + raise NoMethodError, "protected method '#{msg_id}' called for #{desc}" + else + true + end + end + end + public :check_insecure_method + + class InvokeMethod # :nodoc: + def initialize(drb_server, client) + @drb_server = drb_server + @client = client + end + + def perform + begin + setup_message + ensure + @result = nil + @succ = false + end + + if @block + @result = perform_with_block + else + @result = perform_without_block + end + @succ = true + case @result + when Array + if @msg_id == :to_ary + @result = DRbArray.new(@result) + end + end + return @succ, @result + rescue NoMemoryError, SystemExit, SystemStackError, SecurityError + raise + rescue Exception + @result = $! + return @succ, @result + end + + private + def init_with_client + obj, msg, argv, block = @client.recv_request + @obj = obj + @msg_id = msg.intern + @argv = argv + @block = block + end + + def check_insecure_method + @drb_server.check_insecure_method(@obj, @msg_id) + end + + def setup_message + init_with_client + check_insecure_method + end + + def block_yield(x) + if x.size == 1 && x[0].class == Array + x[0] = DRbArray.new(x[0]) + end + @block.call(*x) + end + + def perform_with_block + @obj.__send__(@msg_id, *@argv) do |*x| + jump_error = nil + begin + block_value = block_yield(x) + rescue LocalJumpError + jump_error = $! + end + if jump_error + case jump_error.reason + when :break + break(jump_error.exit_value) + else + raise jump_error + end + end + block_value + end + end + + def perform_without_block + if Proc === @obj && @msg_id == :__drb_yield + if @argv.size == 1 + ary = @argv + else + ary = [@argv] + end + ary.collect(&@obj)[0] + else + @obj.__send__(@msg_id, *@argv) + end + end + + end + + def error_print(exception) + exception.backtrace.inject(true) do |first, x| + if first + $stderr.puts "#{x}: #{exception} (#{exception.class})" + else + $stderr.puts "\tfrom #{x}" + end + false + end + end + + # The main loop performed by a DRbServer's internal thread. + # + # Accepts a connection from a client, and starts up its own + # thread to handle it. This thread loops, receiving requests + # from the client, invoking them on a local object, and + # returning responses, until the client closes the connection + # or a local method call fails. + def main_loop + client0 = @protocol.accept + return nil if !client0 + Thread.start(client0) do |client| + @grp.add Thread.current + Thread.current['DRb'] = { 'client' => client , + 'server' => self } + DRb.mutex.synchronize do + client_uri = client.uri + @exported_uri << client_uri unless @exported_uri.include?(client_uri) + end + _last_invoke_method = nil + loop do + begin + succ = false + invoke_method = InvokeMethod.new(self, client) + succ, result = invoke_method.perform + error_print(result) if !succ && verbose + unless DRbConnError === result && result.message == 'connection closed' + client.send_reply(succ, result) + end + rescue Exception => e + error_print(e) if verbose + ensure + _last_invoke_method = invoke_method + client.close unless succ + if Thread.current['DRb']['stop_service'] + shutdown + break + end + break unless succ + end + end + end + end + end + + @primary_server = nil + + # Start a dRuby server locally. + # + # The new dRuby server will become the primary server, even + # if another server is currently the primary server. + # + # +uri+ is the URI for the server to bind to. If nil, + # the server will bind to random port on the default local host + # name and use the default dRuby protocol. + # + # +front+ is the server's front object. This may be nil. + # + # +config+ is the configuration for the new server. This may + # be nil. + # + # See DRbServer::new. + def start_service(uri=nil, front=nil, config=nil) + @primary_server = DRbServer.new(uri, front, config) + end + module_function :start_service + + # The primary local dRuby server. + # + # This is the server created by the #start_service call. + attr_accessor :primary_server + module_function :primary_server=, :primary_server + + # Get the 'current' server. + # + # In the context of execution taking place within the main + # thread of a dRuby server (typically, as a result of a remote + # call on the server or one of its objects), the current + # server is that server. Otherwise, the current server is + # the primary server. + # + # If the above rule fails to find a server, a DRbServerNotFound + # error is raised. + def current_server + drb = Thread.current['DRb'] + server = (drb && drb['server']) ? drb['server'] : @primary_server + raise DRbServerNotFound unless server + return server + end + module_function :current_server + + # Stop the local dRuby server. + # + # This operates on the primary server. If there is no primary + # server currently running, it is a noop. + def stop_service + @primary_server.stop_service if @primary_server + @primary_server = nil + end + module_function :stop_service + + # Get the URI defining the local dRuby space. + # + # This is the URI of the current server. See #current_server. + def uri + drb = Thread.current['DRb'] + client = (drb && drb['client']) + if client + uri = client.uri + return uri if uri + end + current_server.uri + end + module_function :uri + + # Is +uri+ the URI for the current local server? + def here?(uri) + current_server.here?(uri) rescue false + # (current_server.uri rescue nil) == uri + end + module_function :here? + + # Get the configuration of the current server. + # + # If there is no current server, this returns the default configuration. + # See #current_server and DRbServer::make_config. + def config + current_server.config + rescue + DRbServer.make_config + end + module_function :config + + # Get the front object of the current server. + # + # This raises a DRbServerNotFound error if there is no current server. + # See #current_server. + def front + current_server.front + end + module_function :front + + # Convert a reference into an object using the current server. + # + # This raises a DRbServerNotFound error if there is no current server. + # See #current_server. + def to_obj(ref) + current_server.to_obj(ref) + end + + # Get a reference id for an object using the current server. + # + # This raises a DRbServerNotFound error if there is no current server. + # See #current_server. + def to_id(obj) + current_server.to_id(obj) + end + module_function :to_id + module_function :to_obj + + # Get the thread of the primary server. + # + # This returns nil if there is no primary server. See #primary_server. + def thread + @primary_server ? @primary_server.thread : nil + end + module_function :thread + + # Set the default id conversion object. + # + # This is expected to be an instance such as DRb::DRbIdConv that responds to + # #to_id and #to_obj that can convert objects to and from DRb references. + # + # See DRbServer#default_id_conv. + def install_id_conv(idconv) + DRbServer.default_id_conv(idconv) + end + module_function :install_id_conv + + # Set the default ACL to +acl+. + # + # See DRb::DRbServer.default_acl. + def install_acl(acl) + DRbServer.default_acl(acl) + end + module_function :install_acl + + @mutex = Thread::Mutex.new + def mutex # :nodoc: + @mutex + end + module_function :mutex + + @server = {} + # Registers +server+ with DRb. + # + # This is called when a new DRb::DRbServer is created. + # + # If there is no primary server then +server+ becomes the primary server. + # + # Example: + # + # require 'drb' + # + # s = DRb::DRbServer.new # automatically calls regist_server + # DRb.fetch_server s.uri #=> # + def regist_server(server) + @server[server.uri] = server + mutex.synchronize do + @primary_server = server unless @primary_server + end + end + module_function :regist_server + + # Removes +server+ from the list of registered servers. + def remove_server(server) + @server.delete(server.uri) + mutex.synchronize do + if @primary_server == server + @primary_server = nil + end + end + end + module_function :remove_server + + # Retrieves the server with the given +uri+. + # + # See also regist_server and remove_server. + def fetch_server(uri) + @server[uri] + end + module_function :fetch_server +end + +# :stopdoc: +DRbObject = DRb::DRbObject +DRbUndumped = DRb::DRbUndumped +DRbIdConv = DRb::DRbIdConv diff --git a/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/eq.rb b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/eq.rb new file mode 100644 index 00000000..15ca5cae --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/eq.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: false +module DRb + class DRbObject # :nodoc: + def ==(other) + return false unless DRbObject === other + (@ref == other.__drbref) && (@uri == other.__drburi) + end + + def hash + [@uri, @ref].hash + end + + alias eql? == + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/extserv.rb b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/extserv.rb new file mode 100644 index 00000000..9523fe84 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/extserv.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: false +=begin + external service + Copyright (c) 2000,2002 Masatoshi SEKI +=end + +require_relative 'drb' +require 'monitor' + +module DRb + class ExtServ + include MonitorMixin + include DRbUndumped + + def initialize(there, name, server=nil) + super() + @server = server || DRb::primary_server + @name = name + ro = DRbObject.new(nil, there) + synchronize do + @invoker = ro.register(name, DRbObject.new(self, @server.uri)) + end + end + attr_reader :server + + def front + DRbObject.new(nil, @server.uri) + end + + def stop_service + synchronize do + @invoker.unregister(@name) + server = @server + @server = nil + server.stop_service + true + end + end + + def alive? + @server ? @server.alive? : false + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/extservm.rb b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/extservm.rb new file mode 100644 index 00000000..9333a108 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/extservm.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: false +=begin + external service manager + Copyright (c) 2000 Masatoshi SEKI +=end + +require_relative 'drb' +require 'monitor' + +module DRb + class ExtServManager + include DRbUndumped + include MonitorMixin + + @@command = {} + + def self.command + @@command + end + + def self.command=(cmd) + @@command = cmd + end + + def initialize + super() + @cond = new_cond + @servers = {} + @waiting = [] + @queue = Thread::Queue.new + @thread = invoke_thread + @uri = nil + end + attr_accessor :uri + + def service(name) + synchronize do + while true + server = @servers[name] + return server if server && server.alive? # server may be `false' + invoke_service(name) + @cond.wait + end + end + end + + def register(name, ro) + synchronize do + @servers[name] = ro + @cond.signal + end + self + end + alias regist register + + def unregister(name) + synchronize do + @servers.delete(name) + end + end + alias unregist unregister + + private + def invoke_thread + Thread.new do + while name = @queue.pop + invoke_service_command(name, @@command[name]) + end + end + end + + def invoke_service(name) + @queue.push(name) + end + + def invoke_service_command(name, command) + raise "invalid command. name: #{name}" unless command + synchronize do + return if @servers.include?(name) + @servers[name] = false + end + uri = @uri || DRb.uri + if command.respond_to? :to_ary + command = command.to_ary + [uri, name] + pid = spawn(*command) + else + pid = spawn("#{command} #{uri} #{name}") + end + th = Process.detach(pid) + th[:drb_service] = name + th + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/gw.rb b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/gw.rb new file mode 100644 index 00000000..65a52547 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/gw.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: false +require_relative 'drb' +require 'monitor' + +module DRb + + # Gateway id conversion forms a gateway between different DRb protocols or + # networks. + # + # The gateway needs to install this id conversion and create servers for + # each of the protocols or networks it will be a gateway between. It then + # needs to create a server that attaches to each of these networks. For + # example: + # + # require 'drb/drb' + # require 'drb/unix' + # require 'drb/gw' + # + # DRb.install_id_conv DRb::GWIdConv.new + # gw = DRb::GW.new + # s1 = DRb::DRbServer.new 'drbunix:/path/to/gateway', gw + # s2 = DRb::DRbServer.new 'druby://example:10000', gw + # + # s1.thread.join + # s2.thread.join + # + # Each client must register services with the gateway, for example: + # + # DRb.start_service 'drbunix:', nil # an anonymous server + # gw = DRbObject.new nil, 'drbunix:/path/to/gateway' + # gw[:unix] = some_service + # DRb.thread.join + + class GWIdConv < DRbIdConv + def to_obj(ref) # :nodoc: + if Array === ref && ref[0] == :DRbObject + return DRbObject.new_with(ref[1], ref[2]) + end + super(ref) + end + end + + # The GW provides a synchronized store for participants in the gateway to + # communicate. + + class GW + include MonitorMixin + + # Creates a new GW + + def initialize + super() + @hash = {} + end + + # Retrieves +key+ from the GW + + def [](key) + synchronize do + @hash[key] + end + end + + # Stores value +v+ at +key+ in the GW + + def []=(key, v) + synchronize do + @hash[key] = v + end + end + end + + class DRbObject # :nodoc: + def self._load(s) + uri, ref = Marshal.load(s) + if DRb.uri == uri + return ref ? DRb.to_obj(ref) : DRb.front + end + + self.new_with(DRb.uri, [:DRbObject, uri, ref]) + end + + def _dump(lv) + if DRb.uri == @uri + if Array === @ref && @ref[0] == :DRbObject + Marshal.dump([@ref[1], @ref[2]]) + else + Marshal.dump([@uri, @ref]) # ?? + end + else + Marshal.dump([DRb.uri, [:DRbObject, @uri, @ref]]) + end + end + end +end + +=begin +DRb.install_id_conv(DRb::GWIdConv.new) + +front = DRb::GW.new + +s1 = DRb::DRbServer.new('drbunix:/tmp/gw_b_a', front) +s2 = DRb::DRbServer.new('drbunix:/tmp/gw_b_c', front) + +s1.thread.join +s2.thread.join +=end + +=begin +# foo.rb + +require 'drb/drb' + +class Foo + include DRbUndumped + def initialize(name, peer=nil) + @name = name + @peer = peer + end + + def ping(obj) + puts "#{@name}: ping: #{obj.inspect}" + @peer.ping(self) if @peer + end +end +=end + +=begin +# gw_a.rb +require 'drb/unix' +require 'foo' + +obj = Foo.new('a') +DRb.start_service("drbunix:/tmp/gw_a", obj) + +robj = DRbObject.new_with_uri('drbunix:/tmp/gw_b_a') +robj[:a] = obj + +DRb.thread.join +=end + +=begin +# gw_c.rb +require 'drb/unix' +require 'foo' + +foo = Foo.new('c', nil) + +DRb.start_service("drbunix:/tmp/gw_c", nil) + +robj = DRbObject.new_with_uri("drbunix:/tmp/gw_b_c") + +puts "c->b" +a = robj[:a] +sleep 2 + +a.ping(foo) + +DRb.thread.join +=end + diff --git a/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/observer.rb b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/observer.rb new file mode 100644 index 00000000..0fb7301e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/observer.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: false +require 'observer' + +module DRb + # The Observable module extended to DRb. See Observable for details. + module DRbObservable + include Observable + + # Notifies observers of a change in state. See also + # Observable#notify_observers + def notify_observers(*arg) + if defined? @observer_state and @observer_state + if defined? @observer_peers + @observer_peers.each do |observer, method| + begin + observer.__send__(method, *arg) + rescue + delete_observer(observer) + end + end + end + @observer_state = false + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/ssl.rb b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/ssl.rb new file mode 100644 index 00000000..9ec55d36 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/ssl.rb @@ -0,0 +1,354 @@ +# frozen_string_literal: false +require 'socket' +require 'openssl' +require_relative 'drb' +require 'singleton' + +module DRb + + # The protocol for DRb over an SSL socket + # + # The URI for a DRb socket over SSL is: + # drbssl://:?. The option is optional + class DRbSSLSocket < DRbTCPSocket + + # SSLConfig handles the needed SSL information for establishing a + # DRbSSLSocket connection, including generating the X509 / RSA pair. + # + # An instance of this config can be passed to DRbSSLSocket.new, + # DRbSSLSocket.open and DRbSSLSocket.open_server + # + # See DRb::DRbSSLSocket::SSLConfig.new for more details + class SSLConfig + + # Default values for a SSLConfig instance. + # + # See DRb::DRbSSLSocket::SSLConfig.new for more details + DEFAULT = { + :SSLCertificate => nil, + :SSLPrivateKey => nil, + :SSLClientCA => nil, + :SSLCACertificatePath => nil, + :SSLCACertificateFile => nil, + :SSLTmpDhCallback => nil, + :SSLVerifyMode => ::OpenSSL::SSL::VERIFY_NONE, + :SSLVerifyDepth => nil, + :SSLVerifyCallback => nil, # custom verification + :SSLCertificateStore => nil, + # Must specify if you use auto generated certificate. + :SSLCertName => nil, # e.g. [["CN","fqdn.example.com"]] + :SSLCertComment => "Generated by Ruby/OpenSSL" + } + + # Create a new DRb::DRbSSLSocket::SSLConfig instance + # + # The DRb::DRbSSLSocket will take either a +config+ Hash or an instance + # of SSLConfig, and will setup the certificate for its session for the + # configuration. If want it to generate a generic certificate, the bare + # minimum is to provide the :SSLCertName + # + # === Config options + # + # From +config+ Hash: + # + # :SSLCertificate :: + # An instance of OpenSSL::X509::Certificate. If this is not provided, + # then a generic X509 is generated, with a correspond :SSLPrivateKey + # + # :SSLPrivateKey :: + # A private key instance, like OpenSSL::PKey::RSA. This key must be + # the key that signed the :SSLCertificate + # + # :SSLClientCA :: + # An OpenSSL::X509::Certificate, or Array of certificates that will + # used as ClientCAs in the SSL Context + # + # :SSLCACertificatePath :: + # A path to the directory of CA certificates. The certificates must + # be in PEM format. + # + # :SSLCACertificateFile :: + # A path to a CA certificate file, in PEM format. + # + # :SSLTmpDhCallback :: + # A DH callback. See OpenSSL::SSL::SSLContext.tmp_dh_callback + # + # :SSLMinVersion :: + # This is the minimum SSL version to allow. See + # OpenSSL::SSL::SSLContext#min_version=. + # + # :SSLMaxVersion :: + # This is the maximum SSL version to allow. See + # OpenSSL::SSL::SSLContext#max_version=. + # + # :SSLVerifyMode :: + # This is the SSL verification mode. See OpenSSL::SSL::VERIFY_* for + # available modes. The default is OpenSSL::SSL::VERIFY_NONE + # + # :SSLVerifyDepth :: + # Number of CA certificates to walk, when verifying a certificate + # chain. + # + # :SSLVerifyCallback :: + # A callback to be used for additional verification. See + # OpenSSL::SSL::SSLContext.verify_callback + # + # :SSLCertificateStore :: + # A OpenSSL::X509::Store used for verification of certificates + # + # :SSLCertName :: + # Issuer name for the certificate. This is required when generating + # the certificate (if :SSLCertificate and :SSLPrivateKey were not + # given). The value of this is to be an Array of pairs: + # + # [["C", "Raleigh"], ["ST","North Carolina"], + # ["CN","fqdn.example.com"]] + # + # See also OpenSSL::X509::Name + # + # :SSLCertComment :: + # A comment to be used for generating the certificate. The default is + # "Generated by Ruby/OpenSSL" + # + # + # === Example + # + # These values can be added after the fact, like a Hash. + # + # require 'drb/ssl' + # c = DRb::DRbSSLSocket::SSLConfig.new {} + # c[:SSLCertificate] = + # OpenSSL::X509::Certificate.new(File.read('mycert.crt')) + # c[:SSLPrivateKey] = OpenSSL::PKey::RSA.new(File.read('mycert.key')) + # c[:SSLVerifyMode] = OpenSSL::SSL::VERIFY_PEER + # c[:SSLCACertificatePath] = "/etc/ssl/certs/" + # c.setup_certificate + # + # or + # + # require 'drb/ssl' + # c = DRb::DRbSSLSocket::SSLConfig.new({ + # :SSLCertName => [["CN" => DRb::DRbSSLSocket.getservername]] + # }) + # c.setup_certificate + # + def initialize(config) + @config = config + @cert = config[:SSLCertificate] + @pkey = config[:SSLPrivateKey] + @ssl_ctx = nil + end + + # A convenience method to access the values like a Hash + def [](key); + @config[key] || DEFAULT[key] + end + + # Connect to IO +tcp+, with context of the current certificate + # configuration + def connect(tcp) + ssl = ::OpenSSL::SSL::SSLSocket.new(tcp, @ssl_ctx) + ssl.sync = true + ssl.connect + ssl + end + + # Accept connection to IO +tcp+, with context of the current certificate + # configuration + def accept(tcp) + ssl = OpenSSL::SSL::SSLSocket.new(tcp, @ssl_ctx) + ssl.sync = true + ssl.accept + ssl + end + + # Ensures that :SSLCertificate and :SSLPrivateKey have been provided + # or that a new certificate is generated with the other parameters + # provided. + def setup_certificate + if @cert && @pkey + return + end + + rsa = OpenSSL::PKey::RSA.new(2048){|p, n| + next unless self[:verbose] + case p + when 0; $stderr.putc "." # BN_generate_prime + when 1; $stderr.putc "+" # BN_generate_prime + when 2; $stderr.putc "*" # searching good prime, + # n = #of try, + # but also data from BN_generate_prime + when 3; $stderr.putc "\n" # found good prime, n==0 - p, n==1 - q, + # but also data from BN_generate_prime + else; $stderr.putc "*" # BN_generate_prime + end + } + + cert = OpenSSL::X509::Certificate.new + cert.version = 2 # This means v3 + cert.serial = 0 + name = OpenSSL::X509::Name.new(self[:SSLCertName]) + cert.subject = name + cert.issuer = name + cert.not_before = Time.now + cert.not_after = Time.now + (365*24*60*60) + cert.public_key = rsa.public_key + + ef = OpenSSL::X509::ExtensionFactory.new(nil,cert) + cert.extensions = [ + ef.create_extension("basicConstraints","CA:FALSE"), + ef.create_extension("subjectKeyIdentifier", "hash") ] + ef.issuer_certificate = cert + cert.add_extension(ef.create_extension("authorityKeyIdentifier", + "keyid:always,issuer:always")) + if comment = self[:SSLCertComment] + cert.add_extension(ef.create_extension("nsComment", comment)) + end + cert.sign(rsa, "SHA256") + + @cert = cert + @pkey = rsa + end + + # Establish the OpenSSL::SSL::SSLContext with the configuration + # parameters provided. + def setup_ssl_context + ctx = ::OpenSSL::SSL::SSLContext.new + ctx.cert = @cert + ctx.key = @pkey + ctx.min_version = self[:SSLMinVersion] + ctx.max_version = self[:SSLMaxVersion] + ctx.client_ca = self[:SSLClientCA] + ctx.ca_path = self[:SSLCACertificatePath] + ctx.ca_file = self[:SSLCACertificateFile] + ctx.tmp_dh_callback = self[:SSLTmpDhCallback] + ctx.verify_mode = self[:SSLVerifyMode] + ctx.verify_depth = self[:SSLVerifyDepth] + ctx.verify_callback = self[:SSLVerifyCallback] + ctx.cert_store = self[:SSLCertificateStore] + @ssl_ctx = ctx + end + end + + # Parse the dRuby +uri+ for an SSL connection. + # + # Expects drbssl://... + # + # Raises DRbBadScheme or DRbBadURI if +uri+ is not matching or malformed + def self.parse_uri(uri) # :nodoc: + if /\Adrbssl:\/\/(.*?):(\d+)(\?(.*))?\z/ =~ uri + host = $1 + port = $2.to_i + option = $4 + [host, port, option] + else + raise(DRbBadScheme, uri) unless uri.start_with?('drbssl:') + raise(DRbBadURI, 'can\'t parse uri:' + uri) + end + end + + # Return an DRb::DRbSSLSocket instance as a client-side connection, + # with the SSL connected. This is called from DRb::start_service or while + # connecting to a remote object: + # + # DRb.start_service 'drbssl://localhost:0', front, config + # + # +uri+ is the URI we are connected to, + # 'drbssl://localhost:0' above, +config+ is our + # configuration. Either a Hash or DRb::DRbSSLSocket::SSLConfig + def self.open(uri, config) + host, port, = parse_uri(uri) + soc = TCPSocket.open(host, port) + ssl_conf = SSLConfig::new(config) + ssl_conf.setup_ssl_context + ssl = ssl_conf.connect(soc) + self.new(uri, ssl, ssl_conf, true) + end + + # Returns a DRb::DRbSSLSocket instance as a server-side connection, with + # the SSL connected. This is called from DRb::start_service or while + # connecting to a remote object: + # + # DRb.start_service 'drbssl://localhost:0', front, config + # + # +uri+ is the URI we are connected to, + # 'drbssl://localhost:0' above, +config+ is our + # configuration. Either a Hash or DRb::DRbSSLSocket::SSLConfig + def self.open_server(uri, config) + uri = 'drbssl://:0' unless uri + host, port, = parse_uri(uri) + if host.size == 0 + host = getservername + soc = open_server_inaddr_any(host, port) + else + soc = TCPServer.open(host, port) + end + port = soc.addr[1] if port == 0 + @uri = "drbssl://#{host}:#{port}" + + ssl_conf = SSLConfig.new(config) + ssl_conf.setup_certificate + ssl_conf.setup_ssl_context + self.new(@uri, soc, ssl_conf, false) + end + + # This is a convenience method to parse +uri+ and separate out any + # additional options appended in the +uri+. + # + # Returns an option-less uri and the option => [uri,option] + # + # The +config+ is completely unused, so passing nil is sufficient. + def self.uri_option(uri, config) # :nodoc: + host, port, option = parse_uri(uri) + return "drbssl://#{host}:#{port}", option + end + + # Create a DRb::DRbSSLSocket instance. + # + # +uri+ is the URI we are connected to. + # +soc+ is the tcp socket we are bound to. + # +config+ is our configuration. Either a Hash or SSLConfig + # +is_established+ is a boolean of whether +soc+ is currently established + # + # This is called automatically based on the DRb protocol. + def initialize(uri, soc, config, is_established) + @ssl = is_established ? soc : nil + super(uri, soc.to_io, config) + end + + # Returns the SSL stream + def stream; @ssl; end # :nodoc: + + # Closes the SSL stream before closing the dRuby connection. + def close # :nodoc: + if @ssl + @ssl.close + @ssl = nil + end + super + end + + def accept # :nodoc: + begin + while true + soc = accept_or_shutdown + return nil unless soc + break if (@acl ? @acl.allow_socket?(soc) : true) + soc.close + end + begin + ssl = @config.accept(soc) + rescue Exception + soc.close + raise + end + self.class.new(uri, ssl, @config, true) + rescue OpenSSL::SSL::SSLError + warn("#{$!.message} (#{$!.class})", uplevel: 0) if @config[:verbose] + retry + end + end + end + + DRbProtocol.add_protocol(DRbSSLSocket) +end diff --git a/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/timeridconv.rb b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/timeridconv.rb new file mode 100644 index 00000000..3ead98a7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/timeridconv.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: false +require_relative 'drb' +require 'monitor' + +module DRb + + # Timer id conversion keeps objects alive for a certain amount of time after + # their last access. The default time period is 600 seconds and can be + # changed upon initialization. + # + # To use TimerIdConv: + # + # DRb.install_id_conv TimerIdConv.new 60 # one minute + + class TimerIdConv < DRbIdConv + class TimerHolder2 # :nodoc: + include MonitorMixin + + class InvalidIndexError < RuntimeError; end + + def initialize(keeping=600) + super() + @sentinel = Object.new + @gc = {} + @renew = {} + @keeping = keeping + @expires = nil + end + + def add(obj) + synchronize do + rotate + key = obj.__id__ + @renew[key] = obj + invoke_keeper + return key + end + end + + def fetch(key) + synchronize do + rotate + obj = peek(key) + raise InvalidIndexError if obj == @sentinel + @renew[key] = obj # KeepIt + return obj + end + end + + private + def peek(key) + return @renew.fetch(key) { @gc.fetch(key, @sentinel) } + end + + def invoke_keeper + return if @expires + @expires = Time.now + @keeping + on_gc + end + + def on_gc + return unless Thread.main.alive? + return if @expires.nil? + Thread.new { rotate } if @expires < Time.now + ObjectSpace.define_finalizer(Object.new) {on_gc} + end + + def rotate + synchronize do + if @expires &.< Time.now + @gc = @renew # GCed + @renew = {} + @expires = @gc.empty? ? nil : Time.now + @keeping + end + end + end + end + + # Creates a new TimerIdConv which will hold objects for +keeping+ seconds. + def initialize(keeping=600) + @holder = TimerHolder2.new(keeping) + end + + def to_obj(ref) # :nodoc: + return super if ref.nil? + @holder.fetch(ref) + rescue TimerHolder2::InvalidIndexError + raise "invalid reference" + end + + def to_id(obj) # :nodoc: + return @holder.add(obj) + end + end +end + +# DRb.install_id_conv(TimerIdConv.new) diff --git a/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/unix.rb b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/unix.rb new file mode 100644 index 00000000..1e371ccf --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/unix.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: false +require 'socket' +require_relative 'drb' +require 'tmpdir' + +raise(LoadError, "UNIXServer is required") unless defined?(UNIXServer) + +module DRb + + # Implements DRb over a UNIX socket + # + # DRb UNIX socket URIs look like drbunix:?. The + # option is optional. + + class DRbUNIXSocket < DRbTCPSocket + # :stopdoc: + def self.parse_uri(uri) + if /\Adrbunix:(.*?)(\?(.*))?\z/ =~ uri + filename = $1 + option = $3 + [filename, option] + else + raise(DRbBadScheme, uri) unless uri.start_with?('drbunix:') + raise(DRbBadURI, 'can\'t parse uri:' + uri) + end + end + + def self.open(uri, config) + filename, = parse_uri(uri) + soc = UNIXSocket.open(filename) + self.new(uri, soc, config) + end + + def self.open_server(uri, config) + filename, = parse_uri(uri) + if filename.size == 0 + soc = temp_server + filename = soc.path + uri = 'drbunix:' + soc.path + else + soc = UNIXServer.open(filename) + end + owner = config[:UNIXFileOwner] + group = config[:UNIXFileGroup] + if owner || group + require 'etc' + owner = Etc.getpwnam( owner ).uid if owner + group = Etc.getgrnam( group ).gid if group + File.chown owner, group, filename + end + mode = config[:UNIXFileMode] + File.chmod(mode, filename) if mode + + self.new(uri, soc, config, true) + end + + def self.uri_option(uri, config) + filename, option = parse_uri(uri) + return "drbunix:#{filename}", option + end + + def initialize(uri, soc, config={}, server_mode = false) + super(uri, soc, config) + set_sockopt(@socket) + @server_mode = server_mode + @acl = nil + end + + # import from tempfile.rb + Max_try = 10 + private + def self.temp_server + tmpdir = Dir::tmpdir + n = 0 + while true + begin + tmpname = sprintf('%s/druby%d.%d', tmpdir, $$, n) + lock = tmpname + '.lock' + unless File.exist?(tmpname) or File.exist?(lock) + Dir.mkdir(lock) + break + end + rescue + raise "cannot generate tempfile '%s'" % tmpname if n >= Max_try + #sleep(1) + end + n += 1 + end + soc = UNIXServer.new(tmpname) + Dir.rmdir(lock) + soc + end + + public + def close + return unless @socket + shutdown # DRbProtocol#shutdown + path = @socket.path if @server_mode + @socket.close + File.unlink(path) if @server_mode + @socket = nil + close_shutdown_pipe + end + + def accept + s = accept_or_shutdown + return nil unless s + self.class.new(nil, s, @config) + end + + def set_sockopt(soc) + # no-op for now + end + end + + DRbProtocol.add_protocol(DRbUNIXSocket) + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/version.rb b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/version.rb new file mode 100644 index 00000000..176ea4c4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/version.rb @@ -0,0 +1,3 @@ +module DRb + VERSION = "2.2.3" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/weakidconv.rb b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/weakidconv.rb new file mode 100644 index 00000000..951af8a8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/drb-2.2.3/lib/drb/weakidconv.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: false +require_relative 'drb' + +module DRb + + # DRb::WeakIdConv is deprecated since 2.2.1. You don't need to use + # DRb::WeakIdConv instead of DRb::DRbIdConv. It's the same class. + # + # This file still exists for backward compatibility. + # + # To use WeakIdConv: + # + # DRb.start_service(nil, nil, {:idconv => DRb::WeakIdConv.new}) + + def self.const_missing(name) # :nodoc: + case name + when :WeakIdConv + warn("DRb::WeakIdConv is deprecated. " + + "You can use the DRb::DRbIdConv. " + + "You don't need to use this.", + uplevel: 1) + const_set(:WeakIdConv, DRbIdConv) + singleton_class.remove_method(:const_missing) + DRbIdConv + else + super + end + end +end + +# DRb.install_id_conv(WeakIdConv.new) diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/.document b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/.document new file mode 100644 index 00000000..70bb6266 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/.document @@ -0,0 +1,6 @@ +LICENSE.txt +NEWS.md +README.md +ext/ +lib/ +_doc/ diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/.github/dependabot.yml b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/.github/dependabot.yml new file mode 100644 index 00000000..b18fd293 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'weekly' diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/.github/workflows/test.yml b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/.github/workflows/test.yml new file mode 100644 index 00000000..02e96716 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/.github/workflows/test.yml @@ -0,0 +1,33 @@ +name: test + +on: + push: + branches: [master] + pull_request: + workflow_dispatch: + +jobs: + ruby-versions: + uses: ruby/actions/.github/workflows/ruby_versions.yml@master + with: + engine: cruby + versions: '["jruby", "truffleruby-head"]' + + test: + needs: ruby-versions + name: build (${{ matrix.ruby }} / ${{ matrix.os }}) + strategy: + matrix: + ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} + os: [ubuntu-latest] + fail-fast: false + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + - name: Run test + run: bundle exec rake test diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/.gitignore b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/.gitignore new file mode 100644 index 00000000..f53c35c0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/.gitignore @@ -0,0 +1,12 @@ +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ +Gemfile.lock +*.so +*.bundle +*.gem diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/.rdoc_options b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/.rdoc_options new file mode 100644 index 00000000..62d6c583 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/.rdoc_options @@ -0,0 +1 @@ +main_page: README.md diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/BDSL b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/BDSL new file mode 100644 index 00000000..66d93598 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/BDSL @@ -0,0 +1,22 @@ +Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/COPYING b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/COPYING new file mode 100644 index 00000000..48e5a96d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/COPYING @@ -0,0 +1,56 @@ +Ruby is copyrighted free software by Yukihiro Matsumoto . +You can redistribute it and/or modify it under either the terms of the +2-clause BSDL (see the file BSDL), or the conditions below: + +1. You may make and give away verbatim copies of the source form of the + software without restriction, provided that you duplicate all of the + original copyright notices and associated disclaimers. + +2. You may modify your copy of the software in any way, provided that + you do at least ONE of the following: + + a. place your modifications in the Public Domain or otherwise + make them Freely Available, such as by posting said + modifications to Usenet or an equivalent medium, or by allowing + the author to include your modifications in the software. + + b. use the modified software only within your corporation or + organization. + + c. give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d. make other distribution arrangements with the author. + +3. You may distribute the software in object code or binary form, + provided that you do at least ONE of the following: + + a. distribute the binaries and library files of the software, + together with instructions (in the manual page or equivalent) + on where to get the original distribution. + + b. accompany the distribution with the machine-readable source of + the software. + + c. give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d. make other distribution arrangements with the author. + +4. You may modify and include the part of the software into any other + software (possibly commercial). But some files in the distribution + are not written by the author, so that they are not under these terms. + + For the list of those files and their copying conditions, see the + file LEGAL. + +5. The scripts and library files supplied as input to or produced as + output from the software do not automatically fall under the + copyright of the software, but belong to whomever generated them, + and may be sold commercially, and may be aggregated with this + software. + +6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/Gemfile b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/Gemfile new file mode 100644 index 00000000..eded3b08 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/Gemfile @@ -0,0 +1,10 @@ +source 'https://rubygems.org' + +gemspec + +group :development do + gem 'rake' + gem 'rake-compiler' + gem 'test-unit' + gem "test-unit-ruby-core" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/LICENSE.txt b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/LICENSE.txt new file mode 100644 index 00000000..ca9fe595 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/LICENSE.txt @@ -0,0 +1,2 @@ +All the files in this distribution are covered under either the Ruby's +license (see the file COPYING) or BSD-2-Clause license (see the file BSDL). diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/NEWS.md b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/NEWS.md new file mode 100644 index 00000000..ff5ffa99 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/NEWS.md @@ -0,0 +1,61 @@ +# Change Log + +## 5.0.1 + +* Rescue `LoadError` when failing to load `erb/escape` + +## 5.0.0 + +* Bump `required_ruby_version` to Ruby 3.2+ [#60](https://github.com/ruby/erb/pull/60) +* Drop `cgi` from runtime dependencies [#59](https://github.com/ruby/erb/pull/59) +* Make `ERB::VERSION` public + +## 4.0.4 + +* Skip building the C extension for JRuby [#52](https://github.com/ruby/erb/pull/52) + +## 4.0.3 + +* Enable `frozen_string_literal` in all files [#49](https://github.com/ruby/erb/pull/49) + +## 4.0.2 + +* Fix line numbers after multi-line `<%#` [#42](https://github.com/ruby/erb/pull/42) + +## 4.0.1 + +* Stop building the C extension for TruffleRuby [#39](https://github.com/ruby/erb/pull/39) + +## 4.0.0 + +* Optimize `ERB::Util.html_escape` [#27](https://github.com/ruby/erb/pull/27) + * No longer duplicate an argument string when nothing is escaped. + * This makes `ERB::Util.html_escape` faster than `CGI.escapeHTML` in no-escape cases. + * It skips calling `#to_s` when an argument is already a String. +* Define `ERB::Escape.html_escape` as an alias to `ERB::Util.html_escape` [#38](https://github.com/ruby/erb/pull/38) + * `ERB::Util.html_escape` is known to be monkey-patched by Rails. + `ERB::Escape.html_escape` is useful when you want a non-monkey-patched version. +* Drop deprecated `-S` option from `erb` command + +## 3.0.0 + +* Bump `required_ruby_version` to Ruby 2.7+ [#23](https://github.com/ruby/erb/pull/23) +* `ERB::Util.url_encode` uses a native implementation [#23](https://github.com/ruby/erb/pull/23) +* Fix a bug that a magic comment with a wrong format could be detected [#6](https://github.com/ruby/erb/pull/6) + +## 2.2.3 + +* Bump `required_ruby_version` from 2.3 to 2.5 as it has never been supported [#3](https://github.com/ruby/erb/pull/3) + +## 2.2.2 + +* `ERB.version` returns just a version number +* `ERB::Revision` is deprecated + +## 2.2.1 + +* `ERB#initialize` warns `safe_level` and later arguments even without -w + +## 2.2.0 + +* Ruby 3.0 promoted ERB to a default gem diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/README.md b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/README.md new file mode 100644 index 00000000..908fdb7e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/README.md @@ -0,0 +1,255 @@ +# ERB + +An easy to use but powerful templating system for Ruby. + +## Introduction + +ERB provides an easy to use but powerful templating system for Ruby. Using +ERB, actual Ruby code can be added to any plain text document for the +purposes of generating document information details and/or flow control. + +A very simple example is this: + +```rb +require 'erb' + +x = 42 +template = ERB.new <<-EOF + The value of x is: <%= x %> +EOF +puts template.result(binding) +``` + +Prints: `The value of x is: 42` + +More complex examples are given below. + +## Recognized Tags + +ERB recognizes certain tags in the provided template and converts them based +on the rules below: + +```erb +<% Ruby code -- inline with output %> +<%= Ruby expression -- replace with result %> +<%# comment -- ignored -- useful in testing %> (`<% #` doesn't work. Don't use Ruby comments.) +% a line of Ruby code -- treated as <% line %> (optional -- see ERB.new) +%% replaced with % if first thing on a line and % processing is used +<%% or %%> -- replace with <% or %> respectively +``` + +All other text is passed through ERB filtering unchanged. + +## Options + +There are several settings you can change when you use ERB: +* the nature of the tags that are recognized; +* the binding used to resolve local variables in the template. + +See the ERB.new and ERB#result methods for more detail. + +## Character encodings + +ERB (or Ruby code generated by ERB) returns a string in the same +character encoding as the input string. When the input string has +a magic comment, however, it returns a string in the encoding specified +by the magic comment. + +```rb +# -*- coding: utf-8 -*- +require 'erb' + +template = ERB.new < + __ENCODING__ is <%= __ENCODING__ %>. +EOF +puts template.result +``` + +Prints: `__ENCODING__ is Big5.` + +## Examples + +### Plain Text + +ERB is useful for any generic templating situation. Note that in this example, we use the +convenient "% at start of line" tag, and we quote the template literally with +`%q{...}` to avoid trouble with the backslash. + +```rb +require "erb" + +# Create template. +template = %q{ + From: James Edward Gray II + To: <%= to %> + Subject: Addressing Needs + + <%= to[/\w+/] %>: + + Just wanted to send a quick note assuring that your needs are being + addressed. + + I want you to know that my team will keep working on the issues, + especially: + + <%# ignore numerous minor requests -- focus on priorities %> + % priorities.each do |priority| + * <%= priority %> + % end + + Thanks for your patience. + + James Edward Gray II +}.gsub(/^ /, '') + +message = ERB.new(template, trim_mode: "%<>") + +# Set up template data. +to = "Community Spokesman " +priorities = [ "Run Ruby Quiz", + "Document Modules", + "Answer Questions on Ruby Talk" ] + +# Produce result. +email = message.result +puts email +``` + +Generates: + +``` +From: James Edward Gray II +To: Community Spokesman +Subject: Addressing Needs + +Community: + +Just wanted to send a quick note assuring that your needs are being addressed. + +I want you to know that my team will keep working on the issues, especially: + + * Run Ruby Quiz + * Document Modules + * Answer Questions on Ruby Talk + +Thanks for your patience. + +James Edward Gray II +``` + +### Ruby in HTML + +ERB is often used in .rhtml files (HTML with embedded Ruby). Notice the need in +this example to provide a special binding when the template is run, so that the instance +variables in the Product object can be resolved. + +```rb +require "erb" + +# Build template data class. +class Product + def initialize( code, name, desc, cost ) + @code = code + @name = name + @desc = desc + @cost = cost + + @features = [ ] + end + + def add_feature( feature ) + @features << feature + end + + # Support templating of member data. + def get_binding + binding + end + + # ... +end + +# Create template. +template = %{ + + Ruby Toys -- <%= @name %> + + +

    <%= @name %> (<%= @code %>)

    +

    <%= @desc %>

    + +
      + <% @features.each do |f| %> +
    • <%= f %>
    • + <% end %> +
    + +

    + <% if @cost < 10 %> + Only <%= @cost %>!!! + <% else %> + Call for a price, today! + <% end %> +

    + + + +}.gsub(/^ /, '') + +rhtml = ERB.new(template) + +# Set up template data. +toy = Product.new( "TZ-1002", + "Rubysapien", + "Geek's Best Friend! Responds to Ruby commands...", + 999.95 ) +toy.add_feature("Listens for verbal commands in the Ruby language!") +toy.add_feature("Ignores Perl, Java, and all C variants.") +toy.add_feature("Karate-Chop Action!!!") +toy.add_feature("Matz signature on left leg.") +toy.add_feature("Gem studded eyes... Rubies, of course!") + +# Produce result. +rhtml.run(toy.get_binding) +``` + +Generates (some blank lines removed): + +```html + + Ruby Toys -- Rubysapien + + +

    Rubysapien (TZ-1002)

    +

    Geek's Best Friend! Responds to Ruby commands...

    + +
      +
    • Listens for verbal commands in the Ruby language!
    • +
    • Ignores Perl, Java, and all C variants.
    • +
    • Karate-Chop Action!!!
    • +
    • Matz signature on left leg.
    • +
    • Gem studded eyes... Rubies, of course!
    • +
    + +

    + Call for a price, today! +

    + + + +``` + +## Notes + +There are a variety of templating solutions available in various Ruby projects. +For example, RDoc, distributed with Ruby, uses its own template engine, which +can be reused elsewhere. + +Other popular engines could be found in the corresponding +[Category](https://www.ruby-toolbox.com/categories/template_engines) of +The Ruby Toolbox. + +## License + +The gem is available as open source under the terms of the [2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause). diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/Rakefile b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/Rakefile new file mode 100644 index 00000000..8e83d7eb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/Rakefile @@ -0,0 +1,19 @@ +require 'bundler/gem_tasks' +require 'rake/testtask' + +Rake::TestTask.new(:test) do |t| + t.libs << 'test/lib' + t.ruby_opts << '-rhelper' + t.test_files = FileList['test/**/test_*.rb'] +end + +case RUBY_ENGINE +when 'jruby', 'truffleruby' + # not using C extension +else + require 'rake/extensiontask' + Rake::ExtensionTask.new('erb/escape') + task test: :compile +end + +task default: :test diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/_doc/cgi.rb b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/_doc/cgi.rb new file mode 100644 index 00000000..0902b73d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/_doc/cgi.rb @@ -0,0 +1,3 @@ +# See {CGI document}[https://docs.ruby-lang.org/en/master/CGI.html] +module CGI +end diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/bin/console b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/bin/console new file mode 100755 index 00000000..6355e0f6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/bin/console @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +require "erb" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require "irb" +IRB.start(__FILE__) diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/bin/setup b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/bin/setup new file mode 100755 index 00000000..dce67d86 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/erb.gemspec b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/erb.gemspec new file mode 100644 index 00000000..0a59abad --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/erb.gemspec @@ -0,0 +1,36 @@ +begin + require_relative 'lib/erb/version' +rescue LoadError + # for Ruby core repository + require_relative 'erb/version' +end + +Gem::Specification.new do |spec| + spec.name = 'erb' + spec.version = ERB::VERSION + spec.authors = ['Masatoshi SEKI', 'Takashi Kokubun'] + spec.email = ['seki@ruby-lang.org', 'k0kubun@ruby-lang.org'] + + spec.summary = %q{An easy to use but powerful templating system for Ruby.} + spec.description = %q{An easy to use but powerful templating system for Ruby.} + spec.homepage = 'https://github.com/ruby/erb' + spec.licenses = ['Ruby', 'BSD-2-Clause'] + + spec.metadata['homepage_uri'] = spec.homepage + spec.metadata['source_code_uri'] = spec.homepage + + spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do + `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + end + spec.bindir = 'libexec' + spec.executables = ['erb'] + spec.require_paths = ['lib'] + + spec.required_ruby_version = '>= 3.2.0' + + if RUBY_ENGINE == 'jruby' + spec.platform = 'java' + else + spec.extensions = ['ext/erb/escape/extconf.rb'] + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/ext/erb/escape/Makefile b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/ext/erb/escape/Makefile new file mode 100644 index 00000000..6a8a46eb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/ext/erb/escape/Makefile @@ -0,0 +1,269 @@ + +SHELL = /bin/sh + +# V=0 quiet, V=1 verbose. other values don't work. +V = 0 +V0 = $(V:0=) +Q1 = $(V:1=) +Q = $(Q1:0=@) +ECHO1 = $(V:1=@ :) +ECHO = $(ECHO1:0=@ echo) +NULLCMD = : + +#### Start of system configuration section. #### + +srcdir = . +topdir = /usr/include/ruby-3.2.0 +hdrdir = $(topdir) +arch_hdrdir = /usr/include/x86_64-linux-gnu/ruby-3.2.0 +PATH_SEPARATOR = : +VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby +prefix = $(DESTDIR)/usr +rubysitearchprefix = $(sitearchlibdir)/$(RUBY_BASE_NAME) +rubyarchprefix = $(archlibdir)/$(RUBY_BASE_NAME) +rubylibprefix = $(libdir)/$(RUBY_BASE_NAME) +exec_prefix = $(prefix) +vendorarchhdrdir = $(sitearchincludedir)/$(RUBY_VERSION_NAME)/vendor_ruby +sitearchhdrdir = $(sitearchincludedir)/$(RUBY_VERSION_NAME)/site_ruby +rubyarchhdrdir = $(archincludedir)/$(RUBY_VERSION_NAME) +vendorhdrdir = $(rubyhdrdir)/vendor_ruby +sitehdrdir = $(rubyhdrdir)/site_ruby +rubyhdrdir = $(includedir)/$(RUBY_VERSION_NAME) +vendorarchdir = $(rubysitearchprefix)/vendor_ruby/$(ruby_version) +vendorlibdir = $(vendordir)/$(ruby_version) +vendordir = $(rubylibprefix)/vendor_ruby +sitearchdir = $(DESTDIR)/usr/local/lib/x86_64-linux-gnu/site_ruby +sitelibdir = $(sitedir)/$(ruby_version) +sitedir = $(DESTDIR)/usr/local/lib/site_ruby +rubyarchdir = $(rubyarchprefix)/$(ruby_version) +rubylibdir = $(rubylibprefix)/$(ruby_version) +sitearchincludedir = $(includedir)/$(sitearch) +archincludedir = $(includedir)/$(arch) +sitearchlibdir = $(libdir)/$(sitearch) +archlibdir = $(libdir)/$(arch) +ridir = $(datarootdir)/$(RI_BASE_NAME) +mandir = $(datarootdir)/man +localedir = $(datarootdir)/locale +libdir = $(exec_prefix)/lib +psdir = $(docdir) +pdfdir = $(docdir) +dvidir = $(docdir) +htmldir = $(docdir) +infodir = $(datarootdir)/info +docdir = $(datarootdir)/doc/$(PACKAGE) +oldincludedir = $(DESTDIR)/usr/include +includedir = $(prefix)/include +runstatedir = $(DESTDIR)/var/run +localstatedir = $(DESTDIR)/var +sharedstatedir = $(prefix)/com +sysconfdir = $(DESTDIR)/etc +datadir = $(datarootdir) +datarootdir = $(prefix)/share +libexecdir = $(exec_prefix)/libexec +sbindir = $(exec_prefix)/sbin +bindir = $(exec_prefix)/bin +archdir = $(rubyarchdir) + + +CC_WRAPPER = +CC = x86_64-linux-gnu-gcc +CXX = x86_64-linux-gnu-g++ +LIBRUBY = $(LIBRUBY_SO) +LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a +LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME) +LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static $(MAINLIBS) +empty = +OUTFLAG = -o $(empty) +COUTFLAG = -o $(empty) +CSRCFLAG = $(empty) + +RUBY_EXTCONF_H = +cflags = $(optflags) $(debugflags) $(warnflags) +cxxflags = +optflags = -O3 -fno-fast-math +debugflags = -ggdb3 +warnflags = -Wall -Wextra -Wdeprecated-declarations -Wdiv-by-zero -Wduplicated-cond -Wimplicit-function-declaration -Wimplicit-int -Wmisleading-indentation -Wpointer-arith -Wwrite-strings -Wold-style-definition -Wimplicit-fallthrough=0 -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-packed-bitfield-compat -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wsuggest-attribute=format -Wsuggest-attribute=noreturn -Wunused-variable -Wundef +cppflags = +CCDLFLAGS = -fPIC +CFLAGS = $(CCDLFLAGS) -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC $(ARCH_FLAG) +INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir) +DEFS = +CPPFLAGS = -Wdate-time -D_FORTIFY_SOURCE=3 $(DEFS) $(cppflags) +CXXFLAGS = $(CCDLFLAGS) -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 $(ARCH_FLAG) +ldflags = -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed +dldflags = -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now +ARCH_FLAG = +DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG) +LDSHARED = $(CC) -shared +LDSHAREDXX = $(CXX) -shared +AR = x86_64-linux-gnu-gcc-ar +EXEEXT = + +RUBY_INSTALL_NAME = $(RUBY_BASE_NAME)3.2 +RUBY_SO_NAME = ruby-3.2 +RUBYW_INSTALL_NAME = +RUBY_VERSION_NAME = $(RUBY_BASE_NAME)-$(ruby_version) +RUBYW_BASE_NAME = rubyw +RUBY_BASE_NAME = ruby + +arch = x86_64-linux-gnu +sitearch = $(arch) +ruby_version = 3.2.0 +ruby = $(bindir)/$(RUBY_BASE_NAME)3.2 +RUBY = $(ruby) +BUILTRUBY = $(bindir)/$(RUBY_BASE_NAME)3.2 +ruby_headers = $(hdrdir)/ruby.h $(hdrdir)/ruby/backward.h $(hdrdir)/ruby/ruby.h $(hdrdir)/ruby/defines.h $(hdrdir)/ruby/missing.h $(hdrdir)/ruby/intern.h $(hdrdir)/ruby/st.h $(hdrdir)/ruby/subst.h $(arch_hdrdir)/ruby/config.h + +RM = rm -f +RM_RF = rm -fr +RMDIRS = rmdir --ignore-fail-on-non-empty -p +MAKEDIRS = /bin/mkdir -p +INSTALL = /usr/bin/install -c +INSTALL_PROG = $(INSTALL) -m 0755 +INSTALL_DATA = $(INSTALL) -m 644 +COPY = cp +TOUCH = exit > + +#### End of system configuration section. #### + +preload = +libpath = . $(archlibdir) +LIBPATH = -L. -L$(archlibdir) +DEFFILE = + +CLEANFILES = mkmf.log +DISTCLEANFILES = +DISTCLEANDIRS = + +extout = +extout_prefix = +target_prefix = /erb +LOCAL_LIBS = +LIBS = $(LIBRUBYARG_SHARED) -lm -lpthread -lc +ORIG_SRCS = escape.c +SRCS = $(ORIG_SRCS) +OBJS = escape.o +HDRS = +LOCAL_HDRS = +TARGET = escape +TARGET_NAME = escape +TARGET_ENTRY = Init_$(TARGET_NAME) +DLLIB = $(TARGET).so +EXTSTATIC = +STATIC_LIB = + +TIMESTAMP_DIR = . +BINDIR = $(bindir) +RUBYCOMMONDIR = $(sitedir)$(target_prefix) +RUBYLIBDIR = $(sitelibdir)$(target_prefix) +RUBYARCHDIR = $(sitearchdir)$(target_prefix) +HDRDIR = $(sitehdrdir)$(target_prefix) +ARCHHDRDIR = $(sitearchhdrdir)$(target_prefix) +TARGET_SO_DIR = +TARGET_SO = $(TARGET_SO_DIR)$(DLLIB) +CLEANLIBS = $(TARGET_SO) false +CLEANOBJS = $(OBJS) *.bak +TARGET_SO_DIR_TIMESTAMP = $(TIMESTAMP_DIR)/.sitearchdir.-.erb.time + +all: $(DLLIB) +static: $(STATIC_LIB) +.PHONY: all install static install-so install-rb +.PHONY: clean clean-so clean-static clean-rb + +clean-static:: +clean-rb-default:: +clean-rb:: +clean-so:: +clean: clean-so clean-static clean-rb-default clean-rb + -$(Q)$(RM_RF) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) .*.time + +distclean-rb-default:: +distclean-rb:: +distclean-so:: +distclean-static:: +distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb + -$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log + -$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES) + -$(Q)$(RMDIRS) $(DISTCLEANDIRS) 2> /dev/null || true + +realclean: distclean +install: install-so install-rb + +install-so: $(DLLIB) $(TARGET_SO_DIR_TIMESTAMP) + $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR) +clean-static:: + -$(Q)$(RM) $(STATIC_LIB) +install-rb: pre-install-rb do-install-rb install-rb-default +install-rb-default: pre-install-rb-default do-install-rb-default +pre-install-rb: Makefile +pre-install-rb-default: Makefile +do-install-rb: +do-install-rb-default: +pre-install-rb-default: + @$(NULLCMD) +$(TARGET_SO_DIR_TIMESTAMP): + $(Q) $(MAKEDIRS) $(@D) $(RUBYARCHDIR) + $(Q) $(TOUCH) $@ + +site-install: site-install-so site-install-rb +site-install-so: install-so +site-install-rb: install-rb + +.SUFFIXES: .c .m .cc .mm .cxx .cpp .o .S + +.cc.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cc.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.mm.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.mm.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.cxx.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cxx.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.cpp.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cpp.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.c.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.c.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.m.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.m.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +$(TARGET_SO): $(OBJS) Makefile + $(ECHO) linking shared-object erb/$(DLLIB) + -$(Q)$(RM) $(@) + $(Q) $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS) + + + +$(OBJS): $(HDRS) $(ruby_headers) diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/ext/erb/escape/escape.c b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/ext/erb/escape/escape.c new file mode 100644 index 00000000..db2abd97 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/ext/erb/escape/escape.c @@ -0,0 +1,98 @@ +#include "ruby.h" +#include "ruby/encoding.h" + +static VALUE rb_cERB, rb_mEscape, rb_cCGI; +static ID id_escapeHTML; + +#define HTML_ESCAPE_MAX_LEN 6 + +static const struct { + uint8_t len; + char str[HTML_ESCAPE_MAX_LEN+1]; +} html_escape_table[UCHAR_MAX+1] = { +#define HTML_ESCAPE(c, str) [c] = {rb_strlen_lit(str), str} + HTML_ESCAPE('\'', "'"), + HTML_ESCAPE('&', "&"), + HTML_ESCAPE('"', """), + HTML_ESCAPE('<', "<"), + HTML_ESCAPE('>', ">"), +#undef HTML_ESCAPE +}; + +static inline void +preserve_original_state(VALUE orig, VALUE dest) +{ + rb_enc_associate(dest, rb_enc_get(orig)); +} + +static inline long +escaped_length(VALUE str) +{ + const long len = RSTRING_LEN(str); + if (len >= LONG_MAX / HTML_ESCAPE_MAX_LEN) { + ruby_malloc_size_overflow(len, HTML_ESCAPE_MAX_LEN); + } + return len * HTML_ESCAPE_MAX_LEN; +} + +static VALUE +optimized_escape_html(VALUE str) +{ + VALUE vbuf; + char *buf = ALLOCV_N(char, vbuf, escaped_length(str)); + const char *cstr = RSTRING_PTR(str); + const char *end = cstr + RSTRING_LEN(str); + + char *dest = buf; + while (cstr < end) { + const unsigned char c = *cstr++; + uint8_t len = html_escape_table[c].len; + if (len) { + memcpy(dest, html_escape_table[c].str, len); + dest += len; + } + else { + *dest++ = c; + } + } + + VALUE escaped = str; + if (RSTRING_LEN(str) < (dest - buf)) { + escaped = rb_str_new(buf, dest - buf); + preserve_original_state(str, escaped); + } + ALLOCV_END(vbuf); + return escaped; +} + +/* + * ERB::Util.html_escape is similar to CGI.escapeHTML but different in the following two parts: + * + * * ERB::Util.html_escape converts an argument with #to_s first (only if it's not T_STRING) + * * ERB::Util.html_escape does not allocate a new string when nothing needs to be escaped + */ +static VALUE +erb_escape_html(VALUE self, VALUE str) +{ + if (!RB_TYPE_P(str, T_STRING)) { + str = rb_convert_type(str, T_STRING, "String", "to_s"); + } + + if (rb_enc_str_asciicompat_p(str)) { + return optimized_escape_html(str); + } + else { + return rb_funcall(rb_cCGI, id_escapeHTML, 1, str); + } +} + +void +Init_escape(void) +{ + rb_cERB = rb_define_class("ERB", rb_cObject); + rb_mEscape = rb_define_module_under(rb_cERB, "Escape"); + rb_define_module_function(rb_mEscape, "html_escape", erb_escape_html, 1); + + rb_cCGI = rb_define_class("CGI", rb_cObject); + id_escapeHTML = rb_intern("escapeHTML"); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/ext/erb/escape/extconf.rb b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/ext/erb/escape/extconf.rb new file mode 100644 index 00000000..783e8c1f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/ext/erb/escape/extconf.rb @@ -0,0 +1,8 @@ +require 'mkmf' + +case RUBY_ENGINE +when 'jruby', 'truffleruby' + File.write('Makefile', dummy_makefile($srcdir).join) +else + create_makefile 'erb/escape' +end diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/lib/erb.rb b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/lib/erb.rb new file mode 100644 index 00000000..ebf91e47 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/lib/erb.rb @@ -0,0 +1,504 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: true +# = ERB -- Ruby Templating +# +# Author:: Masatoshi SEKI +# Documentation:: James Edward Gray II, Gavin Sinclair, and Simon Chiang +# +# See ERB for primary documentation and ERB::Util for a couple of utility +# routines. +# +# Copyright (c) 1999-2000,2002,2003 Masatoshi SEKI +# +# You can redistribute it and/or modify it under the same terms as Ruby. + +require 'erb/version' +require 'erb/compiler' +require 'erb/def_method' +require 'erb/util' + +# +# = ERB -- Ruby Templating +# +# == Introduction +# +# ERB provides an easy to use but powerful templating system for Ruby. Using +# ERB, actual Ruby code can be added to any plain text document for the +# purposes of generating document information details and/or flow control. +# +# A very simple example is this: +# +# require 'erb' +# +# x = 42 +# template = ERB.new <<-EOF +# The value of x is: <%= x %> +# EOF +# puts template.result(binding) +# +# Prints: The value of x is: 42 +# +# More complex examples are given below. +# +# +# == Recognized Tags +# +# ERB recognizes certain tags in the provided template and converts them based +# on the rules below: +# +# <% Ruby code -- inline with output %> +# <%= Ruby expression -- replace with result %> +# <%# comment -- ignored -- useful in testing %> (`<% #` doesn't work. Don't use Ruby comments.) +# % a line of Ruby code -- treated as <% line %> (optional -- see ERB.new) +# %% replaced with % if first thing on a line and % processing is used +# <%% or %%> -- replace with <% or %> respectively +# +# All other text is passed through ERB filtering unchanged. +# +# +# == Options +# +# There are several settings you can change when you use ERB: +# * the nature of the tags that are recognized; +# * the binding used to resolve local variables in the template. +# +# See the ERB.new and ERB#result methods for more detail. +# +# == Character encodings +# +# ERB (or Ruby code generated by ERB) returns a string in the same +# character encoding as the input string. When the input string has +# a magic comment, however, it returns a string in the encoding specified +# by the magic comment. +# +# # -*- coding: utf-8 -*- +# require 'erb' +# +# template = ERB.new < +# \_\_ENCODING\_\_ is <%= \_\_ENCODING\_\_ %>. +# EOF +# puts template.result +# +# Prints: \_\_ENCODING\_\_ is Big5. +# +# +# == Examples +# +# === Plain Text +# +# ERB is useful for any generic templating situation. Note that in this example, we use the +# convenient "% at start of line" tag, and we quote the template literally with +# %q{...} to avoid trouble with the backslash. +# +# require "erb" +# +# # Create template. +# template = %q{ +# From: James Edward Gray II +# To: <%= to %> +# Subject: Addressing Needs +# +# <%= to[/\w+/] %>: +# +# Just wanted to send a quick note assuring that your needs are being +# addressed. +# +# I want you to know that my team will keep working on the issues, +# especially: +# +# <%# ignore numerous minor requests -- focus on priorities %> +# % priorities.each do |priority| +# * <%= priority %> +# % end +# +# Thanks for your patience. +# +# James Edward Gray II +# }.gsub(/^ /, '') +# +# message = ERB.new(template, trim_mode: "%<>") +# +# # Set up template data. +# to = "Community Spokesman " +# priorities = [ "Run Ruby Quiz", +# "Document Modules", +# "Answer Questions on Ruby Talk" ] +# +# # Produce result. +# email = message.result +# puts email +# +# Generates: +# +# From: James Edward Gray II +# To: Community Spokesman +# Subject: Addressing Needs +# +# Community: +# +# Just wanted to send a quick note assuring that your needs are being addressed. +# +# I want you to know that my team will keep working on the issues, especially: +# +# * Run Ruby Quiz +# * Document Modules +# * Answer Questions on Ruby Talk +# +# Thanks for your patience. +# +# James Edward Gray II +# +# === Ruby in HTML +# +# ERB is often used in .rhtml files (HTML with embedded Ruby). Notice the need in +# this example to provide a special binding when the template is run, so that the instance +# variables in the Product object can be resolved. +# +# require "erb" +# +# # Build template data class. +# class Product +# def initialize( code, name, desc, cost ) +# @code = code +# @name = name +# @desc = desc +# @cost = cost +# +# @features = [ ] +# end +# +# def add_feature( feature ) +# @features << feature +# end +# +# # Support templating of member data. +# def get_binding +# binding +# end +# +# # ... +# end +# +# # Create template. +# template = %{ +# +# Ruby Toys -- <%= @name %> +# +# +#

    <%= @name %> (<%= @code %>)

    +#

    <%= @desc %>

    +# +#
      +# <% @features.each do |f| %> +#
    • <%= f %>
    • +# <% end %> +#
    +# +#

    +# <% if @cost < 10 %> +# Only <%= @cost %>!!! +# <% else %> +# Call for a price, today! +# <% end %> +#

    +# +# +# +# }.gsub(/^ /, '') +# +# rhtml = ERB.new(template) +# +# # Set up template data. +# toy = Product.new( "TZ-1002", +# "Rubysapien", +# "Geek's Best Friend! Responds to Ruby commands...", +# 999.95 ) +# toy.add_feature("Listens for verbal commands in the Ruby language!") +# toy.add_feature("Ignores Perl, Java, and all C variants.") +# toy.add_feature("Karate-Chop Action!!!") +# toy.add_feature("Matz signature on left leg.") +# toy.add_feature("Gem studded eyes... Rubies, of course!") +# +# # Produce result. +# rhtml.run(toy.get_binding) +# +# Generates (some blank lines removed): +# +# +# Ruby Toys -- Rubysapien +# +# +#

    Rubysapien (TZ-1002)

    +#

    Geek's Best Friend! Responds to Ruby commands...

    +# +#
      +#
    • Listens for verbal commands in the Ruby language!
    • +#
    • Ignores Perl, Java, and all C variants.
    • +#
    • Karate-Chop Action!!!
    • +#
    • Matz signature on left leg.
    • +#
    • Gem studded eyes... Rubies, of course!
    • +#
    +# +#

    +# Call for a price, today! +#

    +# +# +# +# +# +# == Notes +# +# There are a variety of templating solutions available in various Ruby projects. +# For example, RDoc, distributed with Ruby, uses its own template engine, which +# can be reused elsewhere. +# +# Other popular engines could be found in the corresponding +# {Category}[https://www.ruby-toolbox.com/categories/template_engines] of +# The Ruby Toolbox. +# +class ERB + Revision = '$Date:: $' # :nodoc: #' + deprecate_constant :Revision + + # Returns revision information for the erb.rb module. + def self.version + VERSION + end + + # + # Constructs a new ERB object with the template specified in _str_. + # + # An ERB object works by building a chunk of Ruby code that will output + # the completed template when run. + # + # If _trim_mode_ is passed a String containing one or more of the following + # modifiers, ERB will adjust its code generation as listed: + # + # % enables Ruby code processing for lines beginning with % + # <> omit newline for lines starting with <% and ending in %> + # > omit newline for lines ending in %> + # - omit blank lines ending in -%> + # + # _eoutvar_ can be used to set the name of the variable ERB will build up + # its output in. This is useful when you need to run multiple ERB + # templates through the same binding and/or when you want to control where + # output ends up. Pass the name of the variable to be used inside a String. + # + # === Example + # + # require "erb" + # + # # build data class + # class Listings + # PRODUCT = { :name => "Chicken Fried Steak", + # :desc => "A well messaged pattie, breaded and fried.", + # :cost => 9.95 } + # + # attr_reader :product, :price + # + # def initialize( product = "", price = "" ) + # @product = product + # @price = price + # end + # + # def build + # b = binding + # # create and run templates, filling member data variables + # ERB.new(<<~'END_PRODUCT', trim_mode: "", eoutvar: "@product").result b + # <%= PRODUCT[:name] %> + # <%= PRODUCT[:desc] %> + # END_PRODUCT + # ERB.new(<<~'END_PRICE', trim_mode: "", eoutvar: "@price").result b + # <%= PRODUCT[:name] %> -- <%= PRODUCT[:cost] %> + # <%= PRODUCT[:desc] %> + # END_PRICE + # end + # end + # + # # setup template data + # listings = Listings.new + # listings.build + # + # puts listings.product + "\n" + listings.price + # + # _Generates_ + # + # Chicken Fried Steak + # A well massaged pattie, breaded and fried. + # + # Chicken Fried Steak -- 9.95 + # A well massaged pattie, breaded and fried. + # + def initialize(str, safe_level=NOT_GIVEN, legacy_trim_mode=NOT_GIVEN, legacy_eoutvar=NOT_GIVEN, trim_mode: nil, eoutvar: '_erbout') + # Complex initializer for $SAFE deprecation at [Feature #14256]. Use keyword arguments to pass trim_mode or eoutvar. + if safe_level != NOT_GIVEN + warn 'Passing safe_level with the 2nd argument of ERB.new is deprecated. Do not use it, and specify other arguments as keyword arguments.', uplevel: 1 + end + if legacy_trim_mode != NOT_GIVEN + warn 'Passing trim_mode with the 3rd argument of ERB.new is deprecated. Use keyword argument like ERB.new(str, trim_mode: ...) instead.', uplevel: 1 + trim_mode = legacy_trim_mode + end + if legacy_eoutvar != NOT_GIVEN + warn 'Passing eoutvar with the 4th argument of ERB.new is deprecated. Use keyword argument like ERB.new(str, eoutvar: ...) instead.', uplevel: 1 + eoutvar = legacy_eoutvar + end + + compiler = make_compiler(trim_mode) + set_eoutvar(compiler, eoutvar) + @src, @encoding, @frozen_string = *compiler.compile(str) + @filename = nil + @lineno = 0 + @_init = self.class.singleton_class + end + NOT_GIVEN = defined?(Ractor) ? Ractor.make_shareable(Object.new) : Object.new + private_constant :NOT_GIVEN + + ## + # Creates a new compiler for ERB. See ERB::Compiler.new for details + + def make_compiler(trim_mode) + ERB::Compiler.new(trim_mode) + end + + # The Ruby code generated by ERB + attr_reader :src + + # The encoding to eval + attr_reader :encoding + + # The optional _filename_ argument passed to Kernel#eval when the ERB code + # is run + attr_accessor :filename + + # The optional _lineno_ argument passed to Kernel#eval when the ERB code + # is run + attr_accessor :lineno + + # + # Sets optional filename and line number that will be used in ERB code + # evaluation and error reporting. See also #filename= and #lineno= + # + # erb = ERB.new('<%= some_x %>') + # erb.render + # # undefined local variable or method `some_x' + # # from (erb):1 + # + # erb.location = ['file.erb', 3] + # # All subsequent error reporting would use new location + # erb.render + # # undefined local variable or method `some_x' + # # from file.erb:4 + # + def location=((filename, lineno)) + @filename = filename + @lineno = lineno if lineno + end + + # + # Can be used to set _eoutvar_ as described in ERB::new. It's probably + # easier to just use the constructor though, since calling this method + # requires the setup of an ERB _compiler_ object. + # + def set_eoutvar(compiler, eoutvar = '_erbout') + compiler.put_cmd = "#{eoutvar}.<<" + compiler.insert_cmd = "#{eoutvar}.<<" + compiler.pre_cmd = ["#{eoutvar} = +''"] + compiler.post_cmd = [eoutvar] + end + + # Generate results and print them. (see ERB#result) + def run(b=new_toplevel) + print self.result(b) + end + + # + # Executes the generated ERB code to produce a completed template, returning + # the results of that code. + # + # _b_ accepts a Binding object which is used to set the context of + # code evaluation. + # + def result(b=new_toplevel) + unless @_init.equal?(self.class.singleton_class) + raise ArgumentError, "not initialized" + end + eval(@src, b, (@filename || '(erb)'), @lineno) + end + + # Render a template on a new toplevel binding with local variables specified + # by a Hash object. + def result_with_hash(hash) + b = new_toplevel(hash.keys) + hash.each_pair do |key, value| + b.local_variable_set(key, value) + end + result(b) + end + + ## + # Returns a new binding each time *near* TOPLEVEL_BINDING for runs that do + # not specify a binding. + + def new_toplevel(vars = nil) + b = TOPLEVEL_BINDING + if vars + vars = vars.select {|v| b.local_variable_defined?(v)} + unless vars.empty? + return b.eval("tap {|;#{vars.join(',')}| break binding}") + end + end + b.dup + end + private :new_toplevel + + # Define _methodname_ as instance method of _mod_ from compiled Ruby source. + # + # example: + # filename = 'example.rhtml' # 'arg1' and 'arg2' are used in example.rhtml + # erb = ERB.new(File.read(filename)) + # erb.def_method(MyClass, 'render(arg1, arg2)', filename) + # print MyClass.new.render('foo', 123) + def def_method(mod, methodname, fname='(ERB)') + src = self.src.sub(/^(?!#|$)/) {"def #{methodname}\n"} << "\nend\n" + mod.module_eval do + eval(src, binding, fname, -1) + end + end + + # Create unnamed module, define _methodname_ as instance method of it, and return it. + # + # example: + # filename = 'example.rhtml' # 'arg1' and 'arg2' are used in example.rhtml + # erb = ERB.new(File.read(filename)) + # erb.filename = filename + # MyModule = erb.def_module('render(arg1, arg2)') + # class MyClass + # include MyModule + # end + def def_module(methodname='erb') + mod = Module.new + def_method(mod, methodname, @filename || '(ERB)') + mod + end + + # Define unnamed class which has _methodname_ as instance method, and return it. + # + # example: + # class MyClass_ + # def initialize(arg1, arg2) + # @arg1 = arg1; @arg2 = arg2 + # end + # end + # filename = 'example.rhtml' # @arg1 and @arg2 are used in example.rhtml + # erb = ERB.new(File.read(filename)) + # erb.filename = filename + # MyClass = erb.def_class(MyClass_, 'render()') + # print MyClass.new('foo', 123).render() + def def_class(superklass=Object, methodname='result') + cls = Class.new(superklass) + def_method(cls, methodname, @filename || '(ERB)') + cls + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/lib/erb/compiler.rb b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/lib/erb/compiler.rb new file mode 100644 index 00000000..08b5eb4e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/lib/erb/compiler.rb @@ -0,0 +1,488 @@ +# frozen_string_literal: true +#-- +# ERB::Compiler +# +# Compiles ERB templates into Ruby code; the compiled code produces the +# template result when evaluated. ERB::Compiler provides hooks to define how +# generated output is handled. +# +# Internally ERB does something like this to generate the code returned by +# ERB#src: +# +# compiler = ERB::Compiler.new('<>') +# compiler.pre_cmd = ["_erbout=+''"] +# compiler.put_cmd = "_erbout.<<" +# compiler.insert_cmd = "_erbout.<<" +# compiler.post_cmd = ["_erbout"] +# +# code, enc = compiler.compile("Got <%= obj %>!\n") +# puts code +# +# Generates: +# +# #coding:UTF-8 +# _erbout=+''; _erbout.<< "Got ".freeze; _erbout.<<(( obj ).to_s); _erbout.<< "!\n".freeze; _erbout +# +# By default the output is sent to the print method. For example: +# +# compiler = ERB::Compiler.new('<>') +# code, enc = compiler.compile("Got <%= obj %>!\n") +# puts code +# +# Generates: +# +# #coding:UTF-8 +# print "Got ".freeze; print(( obj ).to_s); print "!\n".freeze +# +# == Evaluation +# +# The compiled code can be used in any context where the names in the code +# correctly resolve. Using the last example, each of these print 'Got It!' +# +# Evaluate using a variable: +# +# obj = 'It' +# eval code +# +# Evaluate using an input: +# +# mod = Module.new +# mod.module_eval %{ +# def get(obj) +# #{code} +# end +# } +# extend mod +# get('It') +# +# Evaluate using an accessor: +# +# klass = Class.new Object +# klass.class_eval %{ +# attr_accessor :obj +# def initialize(obj) +# @obj = obj +# end +# def get_it +# #{code} +# end +# } +# klass.new('It').get_it +# +# Good! See also ERB#def_method, ERB#def_module, and ERB#def_class. +class ERB::Compiler # :nodoc: + class PercentLine # :nodoc: + def initialize(str) + @value = str + end + attr_reader :value + alias :to_s :value + end + + class Scanner # :nodoc: + @scanner_map = defined?(Ractor) ? Ractor.make_shareable({}) : {} + class << self + if defined?(Ractor) + def register_scanner(klass, trim_mode, percent) + @scanner_map = Ractor.make_shareable({ **@scanner_map, [trim_mode, percent] => klass }) + end + else + def register_scanner(klass, trim_mode, percent) + @scanner_map[[trim_mode, percent]] = klass + end + end + alias :regist_scanner :register_scanner + end + + def self.default_scanner=(klass) + @default_scanner = klass + end + + def self.make_scanner(src, trim_mode, percent) + klass = @scanner_map.fetch([trim_mode, percent], @default_scanner) + klass.new(src, trim_mode, percent) + end + + DEFAULT_STAGS = %w(<%% <%= <%# <%).freeze + DEFAULT_ETAGS = %w(%%> %>).freeze + def initialize(src, trim_mode, percent) + @src = src + @stag = nil + @stags = DEFAULT_STAGS + @etags = DEFAULT_ETAGS + end + attr_accessor :stag + attr_reader :stags, :etags + + def scan; end + end + + class TrimScanner < Scanner # :nodoc: + def initialize(src, trim_mode, percent) + super + @trim_mode = trim_mode + @percent = percent + if @trim_mode == '>' + @scan_reg = /(.*?)(%>\r?\n|#{(stags + etags).join('|')}|\n|\z)/m + @scan_line = self.method(:trim_line1) + elsif @trim_mode == '<>' + @scan_reg = /(.*?)(%>\r?\n|#{(stags + etags).join('|')}|\n|\z)/m + @scan_line = self.method(:trim_line2) + elsif @trim_mode == '-' + @scan_reg = /(.*?)(^[ \t]*<%\-|<%\-|-%>\r?\n|-%>|#{(stags + etags).join('|')}|\z)/m + @scan_line = self.method(:explicit_trim_line) + else + @scan_reg = /(.*?)(#{(stags + etags).join('|')}|\n|\z)/m + @scan_line = self.method(:scan_line) + end + end + + def scan(&block) + @stag = nil + if @percent + @src.each_line do |line| + percent_line(line, &block) + end + else + @scan_line.call(@src, &block) + end + nil + end + + def percent_line(line, &block) + if @stag || line[0] != ?% + return @scan_line.call(line, &block) + end + + line[0] = '' + if line[0] == ?% + @scan_line.call(line, &block) + else + yield(PercentLine.new(line.chomp)) + end + end + + def scan_line(line) + line.scan(@scan_reg) do |tokens| + tokens.each do |token| + next if token.empty? + yield(token) + end + end + end + + def trim_line1(line) + line.scan(@scan_reg) do |tokens| + tokens.each do |token| + next if token.empty? + if token == "%>\n" || token == "%>\r\n" + yield('%>') + yield(:cr) + else + yield(token) + end + end + end + end + + def trim_line2(line) + head = nil + line.scan(@scan_reg) do |tokens| + tokens.each do |token| + next if token.empty? + head = token unless head + if token == "%>\n" || token == "%>\r\n" + yield('%>') + if is_erb_stag?(head) + yield(:cr) + else + yield("\n") + end + head = nil + else + yield(token) + head = nil if token == "\n" + end + end + end + end + + def explicit_trim_line(line) + line.scan(@scan_reg) do |tokens| + tokens.each do |token| + next if token.empty? + if @stag.nil? && /[ \t]*<%-/ =~ token + yield('<%') + elsif @stag && (token == "-%>\n" || token == "-%>\r\n") + yield('%>') + yield(:cr) + elsif @stag && token == '-%>' + yield('%>') + else + yield(token) + end + end + end + end + + ERB_STAG = %w(<%= <%# <%) + def is_erb_stag?(s) + ERB_STAG.member?(s) + end + end + + Scanner.default_scanner = TrimScanner + + begin + require 'strscan' + rescue LoadError + else + class SimpleScanner < Scanner # :nodoc: + def scan + stag_reg = (stags == DEFAULT_STAGS) ? /(.*?)(<%[%=#]?|\z)/m : /(.*?)(#{stags.join('|')}|\z)/m + etag_reg = (etags == DEFAULT_ETAGS) ? /(.*?)(%%?>|\z)/m : /(.*?)(#{etags.join('|')}|\z)/m + scanner = StringScanner.new(@src) + while ! scanner.eos? + scanner.scan(@stag ? etag_reg : stag_reg) + yield(scanner[1]) + yield(scanner[2]) + end + end + end + Scanner.register_scanner(SimpleScanner, nil, false) + + class ExplicitScanner < Scanner # :nodoc: + def scan + stag_reg = /(.*?)(^[ \t]*<%-|<%-|#{stags.join('|')}|\z)/m + etag_reg = /(.*?)(-%>|#{etags.join('|')}|\z)/m + scanner = StringScanner.new(@src) + while ! scanner.eos? + scanner.scan(@stag ? etag_reg : stag_reg) + yield(scanner[1]) + + elem = scanner[2] + if /[ \t]*<%-/ =~ elem + yield('<%') + elsif elem == '-%>' + yield('%>') + yield(:cr) if scanner.scan(/(\r?\n|\z)/) + else + yield(elem) + end + end + end + end + Scanner.register_scanner(ExplicitScanner, '-', false) + end + + class Buffer # :nodoc: + def initialize(compiler, enc=nil, frozen=nil) + @compiler = compiler + @line = [] + @script = +'' + @script << "#coding:#{enc}\n" if enc + @script << "#frozen-string-literal:#{frozen}\n" unless frozen.nil? + @compiler.pre_cmd.each do |x| + push(x) + end + end + attr_reader :script + + def push(cmd) + @line << cmd + end + + def cr + @script << (@line.join('; ')) + @line = [] + @script << "\n" + end + + def close + return unless @line + @compiler.post_cmd.each do |x| + push(x) + end + @script << (@line.join('; ')) + @line = nil + end + end + + def add_put_cmd(out, content) + out.push("#{@put_cmd} #{content.dump}.freeze#{"\n" * content.count("\n")}") + end + + def add_insert_cmd(out, content) + out.push("#{@insert_cmd}((#{content}).to_s)") + end + + # Compiles an ERB template into Ruby code. Returns an array of the code + # and encoding like ["code", Encoding]. + def compile(s) + enc = s.encoding + raise ArgumentError, "#{enc} is not ASCII compatible" if enc.dummy? + s = s.b # see String#b + magic_comment = detect_magic_comment(s, enc) + out = Buffer.new(self, *magic_comment) + + self.content = +'' + scanner = make_scanner(s) + scanner.scan do |token| + next if token.nil? + next if token == '' + if scanner.stag.nil? + compile_stag(token, out, scanner) + else + compile_etag(token, out, scanner) + end + end + add_put_cmd(out, content) if content.size > 0 + out.close + return out.script, *magic_comment + end + + def compile_stag(stag, out, scanner) + case stag + when PercentLine + add_put_cmd(out, content) if content.size > 0 + self.content = +'' + out.push(stag.to_s) + out.cr + when :cr + out.cr + when '<%', '<%=', '<%#' + scanner.stag = stag + add_put_cmd(out, content) if content.size > 0 + self.content = +'' + when "\n" + content << "\n" + add_put_cmd(out, content) + self.content = +'' + when '<%%' + content << '<%' + else + content << stag + end + end + + def compile_etag(etag, out, scanner) + case etag + when '%>' + compile_content(scanner.stag, out) + scanner.stag = nil + self.content = +'' + when '%%>' + content << '%>' + else + content << etag + end + end + + def compile_content(stag, out) + case stag + when '<%' + if content[-1] == ?\n + content.chop! + out.push(content) + out.cr + else + out.push(content) + end + when '<%=' + add_insert_cmd(out, content) + when '<%#' + out.push("\n" * content.count("\n")) # only adjust lineno + end + end + + def prepare_trim_mode(mode) # :nodoc: + case mode + when 1 + return [false, '>'] + when 2 + return [false, '<>'] + when 0, nil + return [false, nil] + when String + unless mode.match?(/\A(%|-|>|<>){1,2}\z/) + warn_invalid_trim_mode(mode, uplevel: 5) + end + + perc = mode.include?('%') + if mode.include?('-') + return [perc, '-'] + elsif mode.include?('<>') + return [perc, '<>'] + elsif mode.include?('>') + return [perc, '>'] + else + [perc, nil] + end + else + warn_invalid_trim_mode(mode, uplevel: 5) + return [false, nil] + end + end + + def make_scanner(src) # :nodoc: + Scanner.make_scanner(src, @trim_mode, @percent) + end + + # Construct a new compiler using the trim_mode. See ERB::new for available + # trim modes. + def initialize(trim_mode) + @percent, @trim_mode = prepare_trim_mode(trim_mode) + @put_cmd = 'print' + @insert_cmd = @put_cmd + @pre_cmd = [] + @post_cmd = [] + end + attr_reader :percent, :trim_mode + + # The command to handle text that ends with a newline + attr_accessor :put_cmd + + # The command to handle text that is inserted prior to a newline + attr_accessor :insert_cmd + + # An array of commands prepended to compiled code + attr_accessor :pre_cmd + + # An array of commands appended to compiled code + attr_accessor :post_cmd + + private + + # A buffered text in #compile + attr_accessor :content + + def detect_magic_comment(s, enc = nil) + re = @percent ? /\G(?:<%#(.*)%>|%#(.*)\n)/ : /\G<%#(.*)%>/ + frozen = nil + s.scan(re) do + comment = $+ + comment = $1 if comment[/-\*-\s*([^\s].*?)\s*-\*-$/] + case comment + when %r"coding\s*[=:]\s*([[:alnum:]\-_]+)" + enc = Encoding.find($1.sub(/-(?:mac|dos|unix)/i, '')) + when %r"frozen[-_]string[-_]literal\s*:\s*([[:alnum:]]+)" + frozen = $1 + end + end + return enc, frozen + end + + # :stopdoc: + WARNING_UPLEVEL = Class.new { + attr_reader :c + def initialize from + @c = caller.length - from.length + end + }.new(caller(0)).c + private_constant :WARNING_UPLEVEL + # :startdoc: + + def warn_invalid_trim_mode(mode, uplevel:) + warn "Invalid ERB trim mode: #{mode.inspect} (trim_mode: nil, 0, 1, 2, or String composed of '%' and/or '-', '>', '<>')", uplevel: uplevel + WARNING_UPLEVEL + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/lib/erb/def_method.rb b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/lib/erb/def_method.rb new file mode 100644 index 00000000..e503b371 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/lib/erb/def_method.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +# ERB::DefMethod +# +# Utility module to define eRuby script as instance method. +# +# === Example +# +# example.rhtml: +# <% for item in @items %> +# <%= item %> +# <% end %> +# +# example.rb: +# require 'erb' +# class MyClass +# extend ERB::DefMethod +# def_erb_method('render()', 'example.rhtml') +# def initialize(items) +# @items = items +# end +# end +# print MyClass.new([10,20,30]).render() +# +# result: +# +# 10 +# +# 20 +# +# 30 +# +module ERB::DefMethod + # define _methodname_ as instance method of current module, using ERB + # object or eRuby file + def def_erb_method(methodname, erb_or_fname) + if erb_or_fname.kind_of? String + fname = erb_or_fname + erb = ERB.new(File.read(fname)) + erb.def_method(self, methodname, fname) + else + erb = erb_or_fname + erb.def_method(self, methodname, erb.filename || '(ERB)') + end + end + module_function :def_erb_method +end diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/lib/erb/util.rb b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/lib/erb/util.rb new file mode 100644 index 00000000..42c7a576 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/lib/erb/util.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +# Load CGI.escapeHTML and CGI.escapeURIComponent. +# CRuby: +# cgi.gem v0.1.0+ (Ruby 2.7-3.4) and Ruby 3.5+ stdlib have 'cgi/escape' and CGI.escapeHTML. +# cgi.gem v0.3.3+ (Ruby 3.2-3.4) and Ruby 3.5+ stdlib have CGI.escapeURIComponent. +# JRuby: cgi.gem has a Java extension 'cgi/escape'. +# TruffleRuby: lib/truffle/cgi/escape.rb requires 'cgi/util'. +require 'cgi/escape' + +# Load or define ERB::Escape#html_escape. +# We don't build the C extention 'cgi/escape' for JRuby, TruffleRuby, and WASM. +# miniruby (used by CRuby build scripts) also fails to load erb/escape.so. +begin + require 'erb/escape' +rescue LoadError + # ERB::Escape + # + # A subset of ERB::Util. Unlike ERB::Util#html_escape, we expect/hope + # Rails will not monkey-patch ERB::Escape#html_escape. + module ERB::Escape + def html_escape(s) + CGI.escapeHTML(s.to_s) + end + module_function :html_escape + end +end + +# ERB::Util +# +# A utility module for conversion routines, often handy in HTML generation. +module ERB::Util + # + # A utility method for escaping HTML tag characters in _s_. + # + # require "erb" + # include ERB::Util + # + # puts html_escape("is a > 0 & a < 10?") + # + # _Generates_ + # + # is a > 0 & a < 10? + # + include ERB::Escape # html_escape + module_function :html_escape + alias h html_escape + module_function :h + + # + # A utility method for encoding the String _s_ as a URL. + # + # require "erb" + # include ERB::Util + # + # puts url_encode("Programming Ruby: The Pragmatic Programmer's Guide") + # + # _Generates_ + # + # Programming%20Ruby%3A%20%20The%20Pragmatic%20Programmer%27s%20Guide + # + if CGI.respond_to?(:escapeURIComponent) + def url_encode(s) + CGI.escapeURIComponent(s.to_s) + end + else # cgi.gem <= v0.3.2 + def url_encode(s) + s.to_s.b.gsub(/[^a-zA-Z0-9_\-.~]/n) do |m| + sprintf("%%%02X", m.unpack1("C")) + end + end + end + alias u url_encode + module_function :u + module_function :url_encode +end diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/lib/erb/version.rb b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/lib/erb/version.rb new file mode 100644 index 00000000..3d0cedcd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/lib/erb/version.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true +class ERB + VERSION = '5.0.1' +end diff --git a/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/libexec/erb b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/libexec/erb new file mode 100755 index 00000000..4381671f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erb-5.0.1/libexec/erb @@ -0,0 +1,164 @@ +#!/usr/bin/env ruby +# Tiny eRuby --- ERB2 +# Copyright (c) 1999-2000,2002 Masatoshi SEKI +# You can redistribute it and/or modify it under the same terms as Ruby. + +require 'erb' + +class ERB + module Main + def ARGV.switch + return nil if self.empty? + arg = self.shift + return nil if arg == '--' + case arg + when /\A-(.)(.*)/ + if $1 == '-' + arg, @maybe_arg = arg.split(/=/, 2) + return arg + end + raise 'unknown switch "-"' if $2[0] == ?- and $1 != 'T' + if $2.size > 0 + self.unshift "-#{$2}" + @maybe_arg = $2 + else + @maybe_arg = nil + end + "-#{$1}" + when /\A(\w+)=/ + arg + else + self.unshift arg + nil + end + end + + def ARGV.req_arg + (@maybe_arg || self.shift || raise('missing argument')).tap { + @maybe_arg = nil + } + end + + def trim_mode_opt(trim_mode, disable_percent) + return trim_mode if disable_percent + case trim_mode + when 0 + return '%' + when 1 + return '%>' + when 2 + return '%<>' + when '-' + return '%-' + end + end + module_function :trim_mode_opt + + def run(factory=ERB) + trim_mode = 0 + disable_percent = false + variables = {} + begin + while switch = ARGV.switch + case switch + when '-x' # ruby source + output = true + when '-n' # line number + number = true + when '-v' # verbose + $VERBOSE = true + when '--version' # version + STDERR.puts factory.version + exit + when '-d', '--debug' # debug + $DEBUG = true + when '-r' # require + require ARGV.req_arg + when '-T' # trim mode + arg = ARGV.req_arg + if arg == '-' + trim_mode = arg + next + end + raise "invalid trim mode #{arg.dump}" unless arg =~ /\A[0-2]\z/ + trim_mode = arg.to_i + when '-E', '--encoding' + arg = ARGV.req_arg + set_encoding(*arg.split(/:/, 2)) + when '-U' + set_encoding(Encoding::UTF_8, Encoding::UTF_8) + when '-P' + disable_percent = true + when '--help' + raise "print this help" + when /\A-/ + raise "unknown switch #{switch.dump}" + else + var, val = *switch.split('=', 2) + (variables ||= {})[var] = val + end + end + rescue # usage + STDERR.puts $!.to_s + STDERR.puts File.basename($0) + + " [switches] [var=value...] [inputfile]" + STDERR.puts <) (jaredcwhite) (#26, #27) + +=== 1.9.0 (2019-09-25) + +* Change default :bufvar from 'String.new' to '::String.new' to work with BasicObject (jeremyevans) + +=== 1.8.0 (2018-12-18) + +* Support :yield_returns_buffer option in capture_end for always returning the (potentially modified) buffer in <%|= tags (evanleck) (#15) + +=== 1.7.1 (2018-03-05) + +* Make whitespace handling for <%# %> tags more compatible with Erubis (jeremyevans) (#14) + +=== 1.7.0 (2017-10-09) + +* Fix escaping in erubi/capture_end, the setting was previously inverted (jeremyevans) (#10) + +=== 1.6.1 (2017-06-27) + +* Fix usage on newer versions of JRuby 9.1 (jeremyevans) + +=== 1.6.0 (2017-02-27) + +* Use cgi/escape if available for 6x faster HTML escaping (k0kubun, jeremyevans) (#4) + +=== 1.5.0 (2017-01-26) + +* Drop tilt/erubi file, as tilt now ships with Erubi support (jeremyevans) + +* Drop erubi/capture file, Erubi::CaptureEngine support (jeremyevans) + +=== 1.4.0 (2017-01-20) + +* Allow postambles to depend on internal state of engine (jeremyevans) + +* Allow overriding of behavior for <%= and <%== tags to depend on which indicator was used (jeremyevans) + +* Make whitespace handling for <% %> tags more compatible with Erubis for subclasses overriding add_text (jeremyevans) + +=== 1.3.0 (2016-12-29) + +* Support :capture=>:explicit option in tilt support to use Erubi::CaptureEndEngine (jeremyevans) + +* Add erubi/capture_end containing Erubi::CaptureEndEngine, allowing <%|= and <%|== for opening capture tags, and <%| for closing capture tags (jeremyevans) + +=== 1.2.1 (2016-11-21) + +* Don't automatically freeze template text strings on ruby 1.9 or 2.0 (jeremyevans) + +=== 1.2.0 (2016-11-21) + +* Engine#src now returns a frozen string (jeremyevans) + +* Automatically freeze template text strings on ruby 2.1+, reducing garbage generated (jeremyevans) + +* Allow overriding of behavior for <%= and <%== tags (ujifgc) (#1) + +=== 1.1.0 (2016-11-14) + +* Add :ensure option to supporting restoring bufvar to original value (jeremyevans) + +* Don't have tilt support require erb (jeremyevans) + +* Support :engine_class option in tilt support to override engine class used (jeremyevans) + +* Support :capture option in tilt support to use Erubi::CaptureEngine (jeremyevans) + +* Add erubi/capture file containing Erubi::CaptureEngine, allowing <%|= and <%|== for capture (and escaping) blocks in templates (jeremyevans) + +* Raise ArgumentError if template source code contains indicators matched by regexp but not handled (jeremyevans) + +* Add :bufval option to support arbitrary buffer values (jeremyevans) + +* Add :regexp option to specify regexp used for scanning (jeremyevans) + +* Add :src option to specify initial template source (jeremyevans) + +=== 1.0.0 (2016-11-10) + +* Initial Public Release diff --git a/vendor/bundle/ruby/3.2.0/gems/erubi-1.13.1/MIT-LICENSE b/vendor/bundle/ruby/3.2.0/gems/erubi-1.13.1/MIT-LICENSE new file mode 100644 index 00000000..a8950e2a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erubi-1.13.1/MIT-LICENSE @@ -0,0 +1,21 @@ +copyright(c) 2006-2011 kuwata-lab.com all rights reserved. +copyright(c) 2016-2021 Jeremy Evans + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/bundle/ruby/3.2.0/gems/erubi-1.13.1/README.rdoc b/vendor/bundle/ruby/3.2.0/gems/erubi-1.13.1/README.rdoc new file mode 100644 index 00000000..cc2af14d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erubi-1.13.1/README.rdoc @@ -0,0 +1,151 @@ += Erubi + +Erubi is a ERB template engine for ruby. It is a simplified fork of Erubis, using +the same basic algorithm, with the following differences: + +* Handles postfix conditionals when using escaping (e.g. <%= foo if bar %>) +* Supports frozen_string_literal: true in templates via :freeze option +* Works with ruby's --enable-frozen-string-literal option +* Automatically freezes strings for template text when ruby optimizes it (on ruby 2.1+) +* Escapes ' (apostrophe) when escaping for better XSS protection +* Has 15x-6x faster escaping by using erb/escape or cgi/escape +* Has 81% smaller memory footprint (calculated using +ObjectSpace.memsize_of_all+) +* Does no monkey patching (Erubis adds a method to Kernel) +* Uses an immutable design (all options passed to the constructor, which returns a frozen object) +* Has simpler internals (1 file, <150 lines of code) +* Is not dead (Erubis hasn't been updated since 2011) + +It is not designed with Erubis API compatibility in mind, though most Erubis +ERB syntax works, with the following exceptions: + +* No support for <%=== for debug output + += Installation + + gem install erubi + += Source Code + +Source code is available on GitHub at https://github.com/jeremyevans/erubi + += Usage + +Erubi only has built in support for retrieving the generated source for a +file: + + require 'erubi' + eval(Erubi::Engine.new(File.read('filename.erb')).src) + +Most users will probably use Erubi via Rails or Tilt. Erubi is the default +erb template handler in Tilt 2.0.6+ and Rails 5.1+. + +== Capturing + +Erubi does not support capturing block output into the template by default. +It currently ships with two implementations that allow it. + +=== Erubi::CaptureBlockEngine + +The recommended implementation can be required via +erubi/capture_block+, +which allows capturing to work with normal <%= and <%== +tags. + + <%= form do %> + + <% end %> + +When using the capture_block support, capture methods should just return +the text it emit into the template, and call +capture+ on the buffer value. +Since the buffer variable is a local variable and not an instance variable +by default, you'll probably want to set the +:bufvar+ variable when using +the capture_block support to an instance variable, and have any methods +used call capture on that instance variable. Example: + + def form(&block) + "
    #{@_buf.capture(&block)}
    " + end + + puts eval(Erubi::CaptureBlockEngine.new(<<-END, bufvar: '@_buf', trim: false).src) + before + <%= form do %> + inside + <% end %> + after + END + + # Output: + # before + #
    + # inside + #
    + # after + +To use the capture_block support with tilt: + + require 'tilt' + require 'erubi/capture_block' + Tilt.new("filename.erb", :engine_class=>Erubi::CaptureBlockEngine).render + +Note that the capture_block support, while very compatible with the default +support, is not 100% compatible. One area where behavior differs is when +using multiple statements inside <%= and <%== tags: + + <%= 1; 2 %> + +The default support will output 2, but the capture_block support will output +1. + +=== Erubi::CaptureEndEngine + +An alternative capture implementation can be required via +erubi/capture_end+, +which supports it via <%|= and <%|== tags which are +closed with a <%| tag: + + <%|= form do %> + + <%| end %> + +It is only recommended to use +erubi/capture_end+ for backwards +compatibilty. + +When using the capture_end support, capture methods (such as +form+ in the example +above) should return the (potentially modified) buffer. Similar to the +capture_block support, using an instance variable is recommended. Example: + + def form + @_buf << "
    " + yield + @_buf << "
    " + @_buf + end + + puts eval(Erubi::CaptureEndEngine.new(<<-END, bufvar: '@_buf').src) + before + <%|= form do %> + inside + <%| end %> + after + END + + # Output: + # before + #
    + # inside + #
    + # after + +Alternatively, passing the option :yield_returns_buffer => true will return the +buffer captured by the block instead of the last expression in the block. + += Reporting Bugs + +The bug tracker is located at https://github.com/jeremyevans/erubi/issues + += License + +MIT + += Authors + +Jeremy Evans +kuwata-lab.com diff --git a/vendor/bundle/ruby/3.2.0/gems/erubi-1.13.1/Rakefile b/vendor/bundle/ruby/3.2.0/gems/erubi-1.13.1/Rakefile new file mode 100644 index 00000000..d9221a78 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erubi-1.13.1/Rakefile @@ -0,0 +1,71 @@ +require "rake" +require "rake/clean" + +NAME = 'erubi' +CLEAN.include ["#{NAME}-*.gem", "rdoc", "coverage"] + +# Gem Packaging and Release + +desc "Packages #{NAME}" +task :package=>[:clean] do |p| + sh %{gem build #{NAME}.gemspec} +end + +### RDoc + +RDOC_DEFAULT_OPTS = ["--line-numbers", "--inline-source", '--title', 'Erubi: Small ERB Implementation'] + +begin + gem 'hanna' + RDOC_DEFAULT_OPTS.concat(['-f', 'hanna']) +rescue Gem::LoadError +end + +rdoc_task_class = begin + require "rdoc/task" + RDoc::Task +rescue LoadError + require "rake/rdoctask" + Rake::RDocTask +end + +RDOC_OPTS = RDOC_DEFAULT_OPTS + ['--main', 'README.rdoc'] +RDOC_FILES = %w"README.rdoc CHANGELOG MIT-LICENSE lib/**/*.rb" + +rdoc_task_class.new do |rdoc| + rdoc.rdoc_dir = "rdoc" + rdoc.options += RDOC_OPTS + rdoc.rdoc_files.add RDOC_FILES +end + +### Specs + +spec = proc do |env| + env.each{|k,v| ENV[k] = v} + sh "#{FileUtils::RUBY} #{'-w' if RUBY_VERSION >= '3'} #{'-W:strict_unused_block' if RUBY_VERSION >= '3.4'} test/test.rb" + env.each{|k,v| ENV.delete(k)} +end + +desc "Run specs" +task "spec" do + spec.call({}) +end + +task :default=>:spec + +desc "Run specs with coverage" +task "spec_cov" do + spec.call('COVERAGE'=>'1') +end + +### Other + +desc "Start an IRB shell using the extension" +task :irb do + require 'rbconfig' + ruby = ENV['RUBY'] || File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']) + irb = ENV['IRB'] || File.join(RbConfig::CONFIG['bindir'], File.basename(ruby).sub('ruby', 'irb')) + sh %{#{irb} -I lib -r #{NAME}} +end + + diff --git a/vendor/bundle/ruby/3.2.0/gems/erubi-1.13.1/lib/erubi.rb b/vendor/bundle/ruby/3.2.0/gems/erubi-1.13.1/lib/erubi.rb new file mode 100644 index 00000000..af0bc438 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erubi-1.13.1/lib/erubi.rb @@ -0,0 +1,299 @@ +# frozen_string_literal: true + +module Erubi + VERSION = '1.13.1' + + # :nocov: + if RUBY_VERSION >= '1.9' + RANGE_FIRST = 0 + RANGE_LAST = -1 + else + RANGE_FIRST = 0..0 + RANGE_LAST = -1..-1 + end + + MATCH_METHOD = RUBY_VERSION >= '2.4' ? :match? : :match + SKIP_DEFINED_FOR_INSTANCE_VARIABLE = RUBY_VERSION > '3' + FREEZE_TEMPLATE_LITERALS = !eval("''").frozen? && RUBY_VERSION >= '2.1' + # :nocov: + + begin + require 'erb/escape' + define_method(:h, ERB::Escape.instance_method(:html_escape)) + # :nocov: + rescue LoadError + begin + require 'cgi/escape' + unless CGI.respond_to?(:escapeHTML) # work around for JRuby 9.1 + CGI = Object.new + CGI.extend(defined?(::CGI::Escape) ? ::CGI::Escape : ::CGI::Util) + end + # Escape characters with their HTML/XML equivalents. + def h(value) + CGI.escapeHTML(value.to_s) + end + rescue LoadError + ESCAPE_TABLE = {'&' => '&'.freeze, '<' => '<'.freeze, '>' => '>'.freeze, '"' => '"'.freeze, "'" => '''.freeze}.freeze + if RUBY_VERSION >= '1.9' + def h(value) + value.to_s.gsub(/[&<>"']/, ESCAPE_TABLE) + end + else + def h(value) + value.to_s.gsub(/[&<>"']/){|s| ESCAPE_TABLE[s]} + end + end + end + end + # :nocov: + module_function :h + + class Engine + # The default regular expression used for scanning. + DEFAULT_REGEXP = /<%(={1,2}|-|\#|%)?(.*?)([-=])?%>([ \t]*\r?\n)?/m + + # The frozen ruby source code generated from the template, which can be evaled. + attr_reader :src + + # The filename of the template, if one was given. + attr_reader :filename + + # The variable name used for the buffer variable. + attr_reader :bufvar + + # Initialize a new Erubi::Engine. Options: + # +:bufval+ :: The value to use for the buffer variable, as a string (default '::String.new'). + # +:bufvar+ :: The variable name to use for the buffer variable, as a string. + # +:chain_appends+ :: Whether to chain << calls to the buffer variable. Offers better + # performance, but can cause issues when the buffer variable is reassigned during + # template rendering (default +false+). + # +:ensure+ :: Wrap the template in a begin/ensure block restoring the previous value of bufvar. + # +:escapefunc+ :: The function to use for escaping, as a string (default: '::Erubi.h'). + # +:escape+ :: Whether to make <%= escape by default, and <%== not escape by default. + # +:escape_html+ :: Same as +:escape+, with lower priority. + # +:filename+ :: The filename for the template. + # +:freeze+ :: Whether to enable add a frozen_string_literal: true magic comment at the top of + # the resulting source code. Note this may cause problems if you are wrapping the resulting + # source code in other code, because the magic comment only has an effect at the beginning of + # the file, and having the magic comment later in the file can trigger warnings. + # +:freeze_template_literals+ :: Whether to suffix all literal strings for template code with .freeze + # (default: +true+ on Ruby 2.1+, +false+ on Ruby 2.0 and older). + # Can be set to +false+ on Ruby 2.3+ when frozen string literals are enabled + # in order to improve performance. + # +:literal_prefix+ :: The prefix to output when using escaped tag delimiters (default '<%'). + # +:literal_postfix+ :: The postfix to output when using escaped tag delimiters (default '%>'). + # +:outvar+ :: Same as +:bufvar+, with lower priority. + # +:postamble+ :: The postamble for the template, by default returns the resulting source code. + # +:preamble+ :: The preamble for the template, by default initializes the buffer variable. + # +:regexp+ :: The regexp to use for scanning. + # +:src+ :: The initial value to use for the source code, an empty string by default. + # +:trim+ :: Whether to trim leading and trailing whitespace, true by default. + def initialize(input, properties={}) + @escape = escape = properties.fetch(:escape){properties.fetch(:escape_html, false)} + trim = properties[:trim] != false + @filename = properties[:filename] + @bufvar = bufvar = properties[:bufvar] || properties[:outvar] || "_buf" + bufval = properties[:bufval] || '::String.new' + regexp = properties[:regexp] || DEFAULT_REGEXP + literal_prefix = properties[:literal_prefix] || '<%' + literal_postfix = properties[:literal_postfix] || '%>' + preamble = properties[:preamble] || "#{bufvar} = #{bufval};" + postamble = properties[:postamble] || "#{bufvar}.to_s\n" + @chain_appends = properties[:chain_appends] + @text_end = if properties.fetch(:freeze_template_literals, FREEZE_TEMPLATE_LITERALS) + "'.freeze" + else + "'" + end + + @buffer_on_stack = false + @src = src = properties[:src] || String.new + src << "# frozen_string_literal: true\n" if properties[:freeze] + if properties[:ensure] + src << "begin; __original_outvar = #{bufvar}" + if SKIP_DEFINED_FOR_INSTANCE_VARIABLE && /\A@[^@]/ =~ bufvar + src << "; " + else + src << " if defined?(#{bufvar}); " + end + end + + unless @escapefunc = properties[:escapefunc] + if escape + @escapefunc = '__erubi.h' + src << "__erubi = ::Erubi; " + else + @escapefunc = '::Erubi.h' + end + end + + src << preamble + + pos = 0 + is_bol = true + input.scan(regexp) do |indicator, code, tailch, rspace| + match = Regexp.last_match + len = match.begin(0) - pos + text = input[pos, len] + pos = match.end(0) + ch = indicator ? indicator[RANGE_FIRST] : nil + + lspace = nil + + unless ch == '=' + if text.empty? + lspace = "" if is_bol + elsif text[RANGE_LAST] == "\n" + lspace = "" + else + rindex = text.rindex("\n") + if rindex + range = rindex+1..-1 + s = text[range] + if /\A[ \t]*\z/.send(MATCH_METHOD, s) + lspace = s + text[range] = '' + end + else + if is_bol && /\A[ \t]*\z/.send(MATCH_METHOD, text) + lspace = text + text = '' + end + end + end + end + + is_bol = rspace + add_text(text) + case ch + when '=' + rspace = nil if tailch && !tailch.empty? + add_expression(indicator, code) + add_text(rspace) if rspace + when nil, '-' + if trim && lspace && rspace + add_code("#{lspace}#{code}#{rspace}") + else + add_text(lspace) if lspace + add_code(code) + add_text(rspace) if rspace + end + when '#' + n = code.count("\n") + (rspace ? 1 : 0) + if trim && lspace && rspace + add_code("\n" * n) + else + add_text(lspace) if lspace + add_code("\n" * n) + add_text(rspace) if rspace + end + when '%' + add_text("#{lspace}#{literal_prefix}#{code}#{tailch}#{literal_postfix}#{rspace}") + else + handle(indicator, code, tailch, rspace, lspace) + end + end + rest = pos == 0 ? input : input[pos..-1] + add_text(rest) + + src << "\n" unless src[RANGE_LAST] == "\n" + add_postamble(postamble) + src << "; ensure\n " << bufvar << " = __original_outvar\nend\n" if properties[:ensure] + src.freeze + freeze + end + + private + + if RUBY_VERSION >= '2.3' + def _dup_string_if_frozen(string) + +string + end + # :nocov: + else + def _dup_string_if_frozen(string) + string.frozen? ? string.dup : string + end + end + # :nocov: + + # Add raw text to the template. Modifies argument if argument is mutable as a memory optimization. + # Must be called with a string, cannot be called with nil (Rails's subclass depends on it). + def add_text(text) + return if text.empty? + + text = _dup_string_if_frozen(text) + text.gsub!(/['\\]/, '\\\\\&') + + with_buffer{@src << " << '" << text << @text_end} + end + + # Add ruby code to the template + def add_code(code) + terminate_expression + @src << code + @src << ';' unless code[RANGE_LAST] == "\n" + @buffer_on_stack = false + end + + # Add the given ruby expression result to the template, + # escaping it based on the indicator given and escape flag. + def add_expression(indicator, code) + if ((indicator == '=') ^ @escape) + add_expression_result(code) + else + add_expression_result_escaped(code) + end + end + + # Add the result of Ruby expression to the template + def add_expression_result(code) + with_buffer{@src << ' << (' << code << ').to_s'} + end + + # Add the escaped result of Ruby expression to the template + def add_expression_result_escaped(code) + with_buffer{@src << ' << ' << @escapefunc << '((' << code << '))'} + end + + # Add the given postamble to the src. Can be overridden in subclasses + # to make additional changes to src that depend on the current state. + def add_postamble(postamble) + terminate_expression + @src << postamble + end + + # Raise an exception, as the base engine class does not support handling other indicators. + def handle(indicator, code, tailch, rspace, lspace) + raise ArgumentError, "Invalid indicator: #{indicator}" + end + + # Make sure the buffer variable is the target of the next append + # before yielding to the block. Mark that the buffer is the target + # of the next append after the block executes. + # + # This method should only be called if the block will result in + # code where << will append to the bufvar. + def with_buffer + if @chain_appends + unless @buffer_on_stack + @src << '; ' << @bufvar + end + yield + @buffer_on_stack = true + else + @src << ' ' << @bufvar + yield + @src << ';' + end + end + + # Make sure that any current expression has been terminated. + # The default is to terminate all expressions, but when + # the chain_appends option is used, expressions may not be + # terminated. + def terminate_expression + @src << '; ' if @chain_appends + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/erubi-1.13.1/lib/erubi/capture_block.rb b/vendor/bundle/ruby/3.2.0/gems/erubi-1.13.1/lib/erubi/capture_block.rb new file mode 100644 index 00000000..9995b65d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erubi-1.13.1/lib/erubi/capture_block.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'erubi' + +module Erubi + # An engine class that supports capturing blocks via the <%= and <%== tags: + # + # <%= upcase_form do %> + # <%= 'foo' %> + # <% end %> + # + # Where +upcase_form+ is defined like: + # + # def upcase_form(&block) + # "
    #{@bufvar.capture(&block).upcase}
    " + # end + # + # With output being: + # + #
    + # FOO + #
    + # + # This requires using a string subclass as the buffer value, provided by the + # CaptureBlockEngine::Buffer class. + # + # This engine does not support the :escapefunc option. To change the escaping function, + # use a subclass of CaptureBlockEngine::Buffer and override the #| method. + # + # This engine does not support the :chain_appends option, and ignores it if present. + class CaptureBlockEngine < Engine + class Buffer < ::String + + # Convert argument to string when concatening + def <<(v) + concat(v.to_s) + end + + # Escape argument using Erubi.h then then concatenate it to the receiver. + def |(v) + concat(h(v)) + end + + # Temporarily clear the receiver before yielding to the block, yield the + # given args to the block, return any data captured by the receiver, and + # restore the original data the receiver contained before returning. + def capture(*args) + prev = dup + replace("") # 1.8 support! + yield(*args) + dup + ensure + replace(prev) + end + + private + + if RUBY_VERSION >= '2' + define_method(:h, ::Erubi.instance_method(:h)) + # :nocov: + else + def h(v) + ::Erubi.h(v) + end + end + # :nocov: + end + + def initialize(input, properties={}) + properties = Hash[properties] + properties[:bufval] ||= '::Erubi::CaptureBlockEngine::Buffer.new' + properties[:chain_appends] = false + super + end + + private + + def add_expression_result(code) + add_expression_op(' <<= ', code) + end + + def add_expression_result_escaped(code) + add_expression_op(' |= ', code) + end + + def add_expression_op(op, code) + check = /\A\s*\z/.send(MATCH_METHOD, code) ? "''" : '' + with_buffer{@src << op << check << code} + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/erubi-1.13.1/lib/erubi/capture_end.rb b/vendor/bundle/ruby/3.2.0/gems/erubi-1.13.1/lib/erubi/capture_end.rb new file mode 100644 index 00000000..e381dfbb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/erubi-1.13.1/lib/erubi/capture_end.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'erubi' + +module Erubi + # An engine class that supports capturing blocks via the <%|= and <%|== tags, + # explicitly ending the captures using <%| end %> blocks. + class CaptureEndEngine < Engine + # Initializes the engine. Accepts the same arguments as ::Erubi::Engine, and these + # additional options: + # :escape_capture :: Whether to make <%|= escape by default, and <%|== not escape by default, + # defaults to the same value as :escape. + # :yield_returns_buffer :: Whether to have <%| tags insert the buffer as an expression, so that + # <%| end %> tags will have the buffer be the last expression inside + # the block, and therefore have the buffer be returned by the yield + # expression. Normally the buffer will be returned anyway, but there + # are cases where the last expression will not be the buffer, + # and therefore a different object will be returned. + def initialize(input, properties={}) + properties = Hash[properties] + escape = properties.fetch(:escape){properties.fetch(:escape_html, false)} + @escape_capture = properties.fetch(:escape_capture, escape) + @yield_returns_buffer = properties.fetch(:yield_returns_buffer, false) + @bufval = properties[:bufval] ||= '::String.new' + @bufstack = '__erubi_stack' + properties[:regexp] ||= /<%(\|?={1,2}|-|\#|%|\|)?(.*?)([-=])?%>([ \t]*\r?\n)?/m + super + end + + private + + # Handle the <%|= and <%|== tags + def handle(indicator, code, tailch, rspace, lspace) + case indicator + when '|=', '|==' + rspace = nil if tailch && !tailch.empty? + add_text(lspace) if lspace + escape_capture = !((indicator == '|=') ^ @escape_capture) + terminate_expression + @src << "begin; (#{@bufstack} ||= []) << #{@bufvar}; #{@bufvar} = #{@bufval}; #{@bufstack}.last << #{@escapefunc if escape_capture}((" << code + @buffer_on_stack = false + add_text(rspace) if rspace + when '|' + rspace = nil if tailch && !tailch.empty? + add_text(lspace) if lspace + if @yield_returns_buffer + terminate_expression + @src << " #{@bufvar}; " + end + @src << code << ")).to_s; ensure; #{@bufvar} = #{@bufstack}.pop; end;" + @buffer_on_stack = false + add_text(rspace) if rspace + else + super + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/.github/dependabot.yml b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/.github/dependabot.yml new file mode 100644 index 00000000..870617c5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/.github/dependabot.yml @@ -0,0 +1,12 @@ +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +version: 2 +updates: + - package-ecosystem: "bundler" + directory: "/" # Location of package manifests + schedule: + interval: "monthly" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" \ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/.github/workflows/codeql.yml b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/.github/workflows/codeql.yml new file mode 100644 index 00000000..1a732181 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/.github/workflows/codeql.yml @@ -0,0 +1,59 @@ +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '39 15 * * 4' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'ruby' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 \ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/.github/workflows/ruby.yml b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/.github/workflows/ruby.yml new file mode 100644 index 00000000..77cc550b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/.github/workflows/ruby.yml @@ -0,0 +1,25 @@ +name: Ruby + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + test: + + runs-on: ubuntu-latest + strategy: + matrix: + ruby-version: ['1.9', '2.0', '2.1', '2.2', '2.4', '2.5', '2.6', '2.7', '3.0', '3.1'] + + steps: + - uses: actions/checkout@v3 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + bundler-cache: true + - name: Run tests + run: bundle exec rake diff --git a/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/.gitignore b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/.gitignore new file mode 100644 index 00000000..d85caa32 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/.gitignore @@ -0,0 +1,18 @@ +*.gem +*.rbc +.bundle +.config +.yardoc +Gemfile.lock +InstalledFiles +_yardoc +coverage +doc/ +lib/bundler/man +pkg +rdoc +spec/reports +test/tmp +test/version_tmp +tmp +vendor/ \ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/.ruby-version b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/.ruby-version new file mode 100644 index 00000000..e2bdf6e4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/.ruby-version @@ -0,0 +1 @@ +2.7.3 \ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/Gemfile b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/Gemfile new file mode 100644 index 00000000..ca7b9a1c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in hash_diff.gemspec +gemspec \ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/LICENSE.txt b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/LICENSE.txt new file mode 100644 index 00000000..3173288f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2013 Zeal, LLC (dba. Coding Zeal) + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/README.md b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/README.md new file mode 100644 index 00000000..a0da86d9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/README.md @@ -0,0 +1,111 @@ +# HashDiff + +[![Ruby](https://github.com/CodingZeal/hash_diff/actions/workflows/ruby.yml/badge.svg)](https://github.com/CodingZeal/hash_diff/actions/workflows/ruby.yml) [![Code Climate](https://codeclimate.com/github/CodingZeal/hash_diff.png)](https://codeclimate.com/github/CodingZeal/hash_diff) [![Gem Version](https://badge.fury.io/rb/hash_diff.png)](http://badge.fury.io/rb/hash_diff) + +Deep comparison of Ruby Hash objects + +## Installation + +Add this line to your application's Gemfile: + + gem 'hash_diff' + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install hash_diff + +## Usage + +Easily find the differences between two Ruby hashes. + +```ruby + left = { + foo: 'bar', + bar: 'foo', + nested: { + foo: 'bar', + bar: { + one: 'foo1' + } + }, + num: 1, + favorite_restaurant: "Shoney's" + } + + right = { + foo: 'bar2', + bar: 'foo2', + nested: { + foo: 'bar2', + bar: { + two: 'foo2' + } + }, + word: 'monkey', + favorite_restaurant: nil + } + + hash_diff = HashDiff::Comparison.new( left, right ) +``` + +Comparison#diff returns the left and right side differences + +```ruby + hash_diff.diff # => { foo: ["bar", "bar2"], bar: ["foo", "foo2"], nested: { foo: ["bar", "bar2"], bar: { one: ["foo1", HashDiff::NO_VALUE], two: [HashDiff::NO_VALUE, "foo2"] } }, num: [1, HashDiff::NO_VALUE], word: [HashDiff::NO_VALUE, "monkey"], favorite_restaurant: ["Shoney's", nil] } +``` + +You can also compare two arrays. The comparison is sensitive to the order of the elements in the array. + +#### Missing keys + +When there is a key that exists on one side it will return `HashDiff::NO_VALUE` to represent a missing key. + +Comparison#left_diff returns only the left side differences + +```ruby + hash_diff.left_diff # => {:foo=>"bar2", :bar=>"foo2", :nested=>{:foo=>"bar2", :bar=>{:one=>HashDiff::NO_VALUE, :two=>"foo2"}}, :num=>HashDiff::NO_VALUE, :favorite_restaurant=>nil, :word=>"monkey"} +``` + +Comparison#right_diff returns only the right side differences + +```ruby + hash_diff.right_diff # => {:foo=>"bar", :bar=>"foo", :nested=>{:foo=>"bar", :bar=>{:one=>"foo1", :two=>HashDiff::NO_VALUE}}, :num=>1, :favorite_restaurant=>"Shoney's", :word=>HashDiff::NO_VALUE} +``` + +You can also use these shorthand methods + +```ruby + HashDiff.diff(left, right) + HashDiff.left_diff(left, right) + HashDiff.right_diff(left, right) +``` + +Hash#diff is not provided by default, and monkey patching is frowned upon by some, but to provide a one way shorthand call `HashDiff.patch!` + +```ruby + # run prior to implementation + HashDiff.patch! + + left = { foo: 'bar', num: 1 } + right = { foo: 'baz', num: 1 } + + left.diff(right) # => { foo: 'baz' } +``` + +## License + +Authored by the Engineering Team of [Coding ZEAL](https://codingzeal.com?utm_source=github) + +Copyright (c) 2017 Zeal, LLC. Licensed under the [MIT license](https://opensource.org/licenses/MIT). + +## Contributing + +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create new Pull Request diff --git a/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/Rakefile b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/Rakefile new file mode 100644 index 00000000..93ec5668 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/Rakefile @@ -0,0 +1,8 @@ +require "bundler/gem_tasks" + +begin + require 'rspec/core/rake_task' + RSpec::Core::RakeTask.new(:spec) + task default: :spec +rescue LoadError +end diff --git a/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/hash_diff.gemspec b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/hash_diff.gemspec new file mode 100644 index 00000000..2f1915ff --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/hash_diff.gemspec @@ -0,0 +1,24 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'hash_diff/version' + +Gem::Specification.new do |spec| + spec.name = "hash_diff" + spec.version = HashDiff::VERSION + spec.authors = ["Coding Zeal", "Adam Cuppy", "Mike Bianco"] + spec.email = ["info@codingzeal.com", "mike@mikebian.co"] + spec.description = %q{Diff tool for deep Ruby hash comparison} + spec.summary = %q{Deep Ruby Hash comparison} + spec.homepage = "https://github.com/CodingZeal/hash_diff" + spec.license = "MIT" + + spec.files = `git ls-files`.split($/) + spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } + spec.test_files = spec.files.grep(%r{^(spec|features)/}) + spec.require_paths = ["lib"] + + spec.add_development_dependency "bundler" + spec.add_development_dependency "rake" + spec.add_development_dependency "rspec", "~> 3.1" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/lib/hash_diff.rb b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/lib/hash_diff.rb new file mode 100644 index 00000000..47d5e935 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/lib/hash_diff.rb @@ -0,0 +1,28 @@ +require "hash_diff/version" +require "hash_diff/comparison" + +module HashDiff + class NO_VALUE; end + + def self.patch! + Hash.class_eval do + def diff(right) + HashDiff.left_diff(self, right) + end + end unless Hash.new.respond_to?(:diff) + end + + module_function + + def diff(*args) + Comparison.new(*args).diff + end + + def left_diff(*args) + Comparison.new(*args).left_diff + end + + def right_diff(*args) + Comparison.new(*args).right_diff + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/lib/hash_diff/comparison.rb b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/lib/hash_diff/comparison.rb new file mode 100644 index 00000000..cfe35090 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/lib/hash_diff/comparison.rb @@ -0,0 +1,84 @@ +module HashDiff + class Comparison + def initialize(left, right) + @left = left + @right = right + end + + attr_reader :left, :right + + def diff + @diff ||= find_differences { |l, r| [l, r] } + end + + def left_diff + @left_diff ||= find_differences { |_, r| r } + end + + def right_diff + @right_diff ||= find_differences { |l, _| l } + end + + protected + + def find_differences(&reporter) + combined_keys.each_with_object({ }, &comparison_strategy(reporter)) + end + + private + + def comparison_strategy(reporter) + lambda do |key, diff| + diff[key] = report_difference(key, reporter) unless equal?(key) + end + end + + def combined_keys + if hash?(left) && hash?(right) then + (left.keys + right.keys).uniq + elsif array?(left) && array?(right) then + (0..[left.size, right.size].max).to_a + else + raise ArgumentError, "Don't know how to extract keys. Neither arrays nor hashes given" + end + end + + def equal?(key) + value_with_default(left, key) == value_with_default(right, key) + end + + def hash?(value) + value.is_a?(Hash) + end + + def array?(value) + value.is_a?(Array) + end + + def comparable_hash?(key) + hash?(left[key]) && hash?(right[key]) + end + + def comparable_array?(key) + array?(left[key]) && array?(right[key]) + end + + def report_difference(key, reporter) + if comparable_hash?(key) + self.class.new(left[key], right[key]).find_differences(&reporter) + elsif comparable_array?(key) + self.class.new(left[key], right[key]).find_differences(&reporter) + else + reporter.call( + value_with_default(left, key), + value_with_default(right, key) + ) + end + end + + def value_with_default(obj, key) + obj.fetch(key, NO_VALUE) + end + end +end + diff --git a/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/lib/hash_diff/version.rb b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/lib/hash_diff/version.rb new file mode 100644 index 00000000..4658704b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/lib/hash_diff/version.rb @@ -0,0 +1,3 @@ +module HashDiff + VERSION = "1.1.1" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/spec/hash_diff/array_comparison_spec.rb b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/spec/hash_diff/array_comparison_spec.rb new file mode 100644 index 00000000..a9130f4e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/spec/hash_diff/array_comparison_spec.rb @@ -0,0 +1,78 @@ +require "spec_helper" + +describe HashDiff::Comparison do + let(:left) { + [ + { + foo: 'bar', + bar: 'foo', + }, + { + nested: { + foo: 'bar', + bar: { + one: 'foo1' + } + }, + }, + { + num: 1, + word: nil + } + ] + } + + def comparison(to_compare) + HashDiff::Comparison.new(left, to_compare) + end + + def right + [ + { + foo: 'bar', + bar: 'foo', + }, + { + nested: { + foo: 'bar', + bar: { + one: 'foo1' + } + }, + }, + { + num: 1, + word: nil + } + ] + end + + describe 'when arrays are the same' do + it 'properly determines equality' do + expect(comparison(right).diff).to be_empty + end + + it 'handles empty arrays' do + expect(HashDiff::Comparison.new([], []).diff).to be_empty + end + end + + describe 'when arrays are different' do + it 'reports arrays as not equal with a different order' do + # move an item from the end to the beginning + right_shuffled = right + popped = right_shuffled.pop + right_shuffled.unshift(popped) + + expect(comparison(right_shuffled).diff).to_not be_empty + end + + it 'should a deep comparison' do + right_with_extra_nested_element = right + right_with_extra_nested_element[1][:nested][:bar][:two] = 'two' + + expect(comparison(right_with_extra_nested_element).diff).to_not be_empty + end + end + +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/spec/hash_diff/comparison_spec.rb b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/spec/hash_diff/comparison_spec.rb new file mode 100644 index 00000000..572969a3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/spec/hash_diff/comparison_spec.rb @@ -0,0 +1,133 @@ +require "spec_helper" + +describe HashDiff::Comparison do + + let(:app_v1_properties) { + { + foo: 'bar', + bar: 'foo', + nested: { + foo: 'bar', + bar: { + one: 'foo1' + } + }, + num: 1, + word: nil + } + } + let(:app_v2_properties) { + { + foo: 'bar2', + bar: 'foo2', + nested: { + foo: 'bar2', + bar: { + two: 'foo2' + } + }, + word: 'monkey' + } + } + + subject(:comparison) { + HashDiff::Comparison.new(app_v1_properties, app_v2_properties) + } + + describe "#diff" do + subject { comparison.diff } + + context "when different" do + let(:diff) { + { + foo: ["bar", "bar2"], + bar: ["foo", "foo2"], + nested: { + foo: ["bar", "bar2"], + bar: { + one: ["foo1", HashDiff::NO_VALUE], + two: [HashDiff::NO_VALUE, "foo2"] + } + }, + num: [1, HashDiff::NO_VALUE], + word: [nil, "monkey"] + } + } + + it { expect(subject).to eq diff } + end + + context "when similar" do + let(:app_v1_properties) { { foo: 'bar', bar: 'foo' } } + + context "in the same order" do + let(:app_v2_properties) { app_v1_properties } + + it { expect(subject).to be_empty } + end + + context "in a different order" do + let(:app_v2_properties) { { bar: 'foo', foo: 'bar' } } + + it { expect(subject).to be_empty } + end + end + + context "when hashes have both symbol and string keys" do + let(:app_v1_properties) { { foo: "bar" } } + let(:app_v2_properties) { { "foo" => "bar" } } + let(:diff) do + { + foo: ["bar", HashDiff::NO_VALUE], + "foo" => [HashDiff::NO_VALUE, "bar"] + } + end + + it { expect(subject).to eq(diff) } + end + end + + describe "#left_diff" do + subject { comparison.left_diff } + + let(:diff) { + { + foo: "bar2", + bar: "foo2", + nested: { + foo: "bar2", + bar: { + one: HashDiff::NO_VALUE, + two: "foo2" + } + }, + num: HashDiff::NO_VALUE, + word: "monkey" + } + } + + it { expect(subject).to eq diff } + end + + describe "#right_diff" do + subject { comparison.right_diff } + + let(:diff) { + { + foo: "bar", + bar: "foo", + nested: { + foo: "bar", + bar: { + one: "foo1", + two: HashDiff::NO_VALUE + } + }, + num: 1, + word: nil + } + } + + it { expect(subject).to eq diff } + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/spec/hash_diff_spec.rb b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/spec/hash_diff_spec.rb new file mode 100644 index 00000000..55461a0f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/spec/hash_diff_spec.rb @@ -0,0 +1,54 @@ +require "spec_helper" + +describe HashDiff do + describe ".diff" do + subject { described_class.diff left, right } + + let(:left) { + { foo: "bar" } + } + let(:right) { + { foo: "bar2" } + } + + it { expect(subject).to eq({ foo: ['bar', 'bar2']}) } + end + + describe ".left_diff" do + subject { described_class.left_diff left, right } + + let(:left) { + { foo: "bar" } + } + let(:right) { + { foo: "bar2" } + } + + it { expect(subject).to eq({ foo: 'bar2' }) } + end + + describe ".right_diff" do + subject { described_class.right_diff left, right } + + let(:left) { + { foo: "bar" } + } + let(:right) { + { foo: "bar2" } + } + + it { expect(subject).to eq({ foo: 'bar' }) } + end + + describe ".patch!" do + before { described_class.patch! } + + it "patches #diff to Hash" do + expect({}).to respond_to(:diff) + end + + it "leaves Object alone" do + expect(Object.new).not_to respond_to(:diff) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/spec/spec_helper.rb b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/spec/spec_helper.rb new file mode 100644 index 00000000..2fe92e77 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/hash_diff-1.1.1/spec/spec_helper.rb @@ -0,0 +1,11 @@ +require 'rspec' + +PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')).freeze +$LOAD_PATH << File.join(PROJECT_ROOT, 'lib') +Dir[File.join(PROJECT_ROOT, 'spec/support/**/*.rb')].each { |file| require(file) } + +require 'hash_diff' + +RSpec.configure do |c| + c.filter_run_excluding :skip_on_windows => !(RbConfig::CONFIG['host_os'] =~ /mingw32/).nil? +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/.editorconfig b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/.editorconfig new file mode 100644 index 00000000..13c69667 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/.editorconfig @@ -0,0 +1,18 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://EditorConfig.org + +root = true +[*] +end_of_line = lf +trim_trailing_whitespace = true + +[**.rb] +indent_size = 2 +indent_style = spaces +insert_final_newline = true + +[**.xml] +trim_trailing_whitespace = false + +[**.html] +trim_trailing_whitespace = false diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/.github/dependabot.yml b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/.github/dependabot.yml new file mode 100644 index 00000000..5ace4600 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/.github/workflows/ci.yml b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/.github/workflows/ci.yml new file mode 100644 index 00000000..875c76af --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: CI +on: [push, pull_request] +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + ruby: + - 2.7 + - "3.0" # Quoted, to avoid YAML float 3.0 interplated to "3" + - 3.1 + - 3.2 + - 3.3 + - 3.4 + steps: + - name: Check out repository code + uses: actions/checkout@v4 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true # Run "bundle install", and cache the result automatically. + - name: Run Rake + run: bundle exec rake diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/.gitignore b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/.gitignore new file mode 100644 index 00000000..e6d2c64f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/.gitignore @@ -0,0 +1,14 @@ +Gemfile.lock +.DS_Store +.yardoc/ +doc/ +tmp/ +log/ +pkg/ +*.swp +/.bundle +.rvmrc +coverage +*.gem +.idea +.tool-versions diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/.rubocop.yml b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/.rubocop.yml new file mode 100644 index 00000000..d06156e7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/.rubocop.yml @@ -0,0 +1,92 @@ +inherit_from: .rubocop_todo.yml + +# Offense count: 963 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +Style/StringLiterals: + Enabled: false + +# Offense count: 327 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SupportedStyles. +Style/SpaceInsideHashLiteralBraces: + Enabled: false + +# Offense count: 33 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. +Style/SpaceInsideBlockBraces: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +Style/SpaceBeforeSemicolon: + Enabled: false + +# Offense count: 20 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +Style/SignalException: + Enabled: false + +# Offense count: 1 +# Configuration parameters: Methods. +Style/SingleLineBlockParams: + Enabled: false + +# Offense count: 6 +# Cop supports --auto-correct. +Style/PerlBackrefs: + Enabled: false + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: AllowAsExpressionSeparator. +Style/Semicolon: + Enabled: false + +# Offense count: 77 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +Style/BracesAroundHashParameters: + Enabled: false + +# Offense count: 36 +Style/Documentation: + Enabled: false + +# Offense count: 6 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes. +Style/RegexpLiteral: + Enabled: false + +# Offense count: 5 +# Cop supports --auto-correct. +Style/NumericLiterals: + MinDigits: 6 + +# Offense count: 4 +# Cop supports --auto-correct. +Lint/UnusedMethodArgument: + Enabled: false + +# Offense count: 11 +# Cop supports --auto-correct. +Lint/UnusedBlockArgument: + Enabled: false + +# Offense count: 1 +Lint/Void: + Enabled: false + +# Offense count: 22 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +Style/IndentHash: + Enabled: false + +# Offense count: 7 +# Configuration parameters: MinBodyLength. +Style/GuardClause: + Enabled: false diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/.rubocop_todo.yml b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/.rubocop_todo.yml new file mode 100644 index 00000000..be9903f2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/.rubocop_todo.yml @@ -0,0 +1,124 @@ +# This configuration was generated by `rubocop --auto-gen-config` +# on 2015-04-24 07:22:28 +0200 using RuboCop version 0.30.0. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 33 +Lint/AmbiguousRegexpLiteral: + Enabled: false + +# Offense count: 1 +# Configuration parameters: AlignWith, SupportedStyles. +Lint/EndAlignment: + Enabled: false + +# Offense count: 1 +Lint/SuppressedException: + Enabled: false + +# Offense count: 5 +Lint/UselessAssignment: + Enabled: false + +# Offense count: 23 +Metrics/AbcSize: + Max: 86 + +# Offense count: 1 +# Configuration parameters: CountComments. +Metrics/ClassLength: + Max: 285 + +# Offense count: 8 +Metrics/CyclomaticComplexity: + Max: 17 + +# Offense count: 332 +# Configuration parameters: AllowURI, URISchemes. +Metrics/LineLength: + Max: 266 + +# Offense count: 17 +# Configuration parameters: CountComments. +Metrics/MethodLength: + Max: 39 + +# Offense count: 8 +Metrics/PerceivedComplexity: + Max: 20 + +# Offense count: 1 +Style/AccessorMethodName: + Enabled: false + +# Offense count: 1 +Style/AsciiComments: + Enabled: false + +# Offense count: 14 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods. +Style/BlockDelimiters: + Enabled: false + +# Offense count: 2 +Style/CaseEquality: + Enabled: false + +# Offense count: 3 +# Configuration parameters: IndentWhenRelativeTo, SupportedStyles, IndentOneStep. +Style/CaseIndentation: + Enabled: false + +# Offense count: 4 +# Configuration parameters: EnforcedStyle, SupportedStyles. +Style/ClassAndModuleChildren: + Enabled: false + +# Offense count: 7 +Style/ConstantName: + Enabled: false + +# Offense count: 2 +Style/EachWithObject: + Enabled: false + +# Offense count: 2 +# Cop supports --auto-correct. +Style/ElseAlignment: + Enabled: false + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +Style/FirstParameterIndentation: + Enabled: false + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, UseHashRocketsWithSymbolValues. +Style/HashSyntax: + Enabled: false + +# Offense count: 7 +# Cop supports --auto-correct. +# Configuration parameters: MaxLineLength. +Style/IfUnlessModifier: + Enabled: false + +# Offense count: 11 +# Cop supports --auto-correct. +Style/Lambda: + Enabled: false + +# Offense count: 1 +# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles. +Style/Next: + Enabled: false + +# Offense count: 2 +# Configuration parameters: EnforcedStyle, SupportedStyles. +Style/RaiseArgs: + Enabled: false diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/CONTRIBUTING.md b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/CONTRIBUTING.md new file mode 100644 index 00000000..3bac5b15 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/CONTRIBUTING.md @@ -0,0 +1,23 @@ +# Contributing + +* Contributions will not be accepted without tests. +* Please post unconfirmed bugs to the mailing list first: https://groups.google.com/forum/#!forum/httparty-gem +* Don't change the version. The maintainers will handle that when they release. +* Always provide as much information and reproducibility as possible when filing an issue or submitting a pull request. + +## Workflow + +* Fork the project. +* Run `bundle` +* Run `bundle exec rake` +* Make your feature addition or bug fix. +* Add tests for it. This is important so I don't break it in a future version unintentionally. +* Run `bundle exec rake` (No, REALLY :)) +* Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself in another branch so I can ignore when I pull) +* Send me a pull request. Bonus points for topic branches. + +## Help and Docs + +* https://groups.google.com/forum/#!forum/httparty-gem +* http://stackoverflow.com/questions/tagged/httparty +* http://rdoc.info/projects/jnunemaker/httparty diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/Changelog.md b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/Changelog.md new file mode 100644 index 00000000..e0fc332e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/Changelog.md @@ -0,0 +1,624 @@ +# Changelog + +All notable [changes since 0.22 are documented in GitHub Releases](https://github.com/jnunemaker/httparty/releases). + +## 0.21.0 + +- [escape filename in the multipart/form-data Content-Disposition header](https://github.com/jnunemaker/httparty/commit/cdb45a678c43e44570b4e73f84b1abeb5ec22b8e) +- [Fix request marshaling](https://github.com/jnunemaker/httparty/pull/767) +- [Replace `mime-types` with `mini_mime`](https://github.com/jnunemaker/httparty/pull/769) + +## 0.20.0 + +Breaking changes + +- Require Ruby >= 2.3.0 + +Fixes + +- [`Marshal.dump` fails on response objects when request option `:logger` is set or `:parser` is a proc](https://github.com/jnunemaker/httparty/pull/714) +- [Switch `:pem` option to to `OpenSSL::PKey.read` to support other algorithms](https://github.com/jnunemaker/httparty/pull/720) + +## 0.19.1 + +- [Remove use of unary + method for creating non-frozen string to increase compatibility with older versions of ruby](https://github.com/jnunemaker/httparty/commit/4416141d37fd71bdba4f37589ec265f55aa446ce) + +## 0.19.0 + +- [Multipart/Form-Data: rewind files after read](https://github.com/jnunemaker/httparty/pull/709) +- [add frozen_string_literal pragma to all files](https://github.com/jnunemaker/httparty/pull/711) +- [Better handling of Accept-Encoding / Content-Encoding decompression (fixes #562)](https://github.com/jnunemaker/httparty/pull/729) + +## 0.18.1 + +- [Rename cop Lint/HandleExceptions to Lint/SuppressedException](https://github.com/jnunemaker/httparty/pull/699). +- [Encode keys in query params](https://github.com/jnunemaker/httparty/pull/698). +- [Fixed SSL doc example](https://github.com/jnunemaker/httparty/pull/692). +- [Add a build status badge](https://github.com/jnunemaker/httparty/pull/701). + +## 0.18.0 + +- [Support gzip/deflate transfer encoding when explicit headers are set](https://github.com/jnunemaker/httparty/pull/678). +- [Support edge case cookie format with a blank attribute](https://github.com/jnunemaker/httparty/pull/685). + +## 0.17.3 + +0.17.2 is broken https://github.com/jnunemaker/httparty/issues/681 + +## 0.17.2 + +- [Add Response#nil? deprecetion warning](https://github.com/jnunemaker/httparty/pull/680) + +## 0.17.1 + +- [Pass options to dynamic block headers](https://github.com/jnunemaker/httparty/pull/661) +- [Normalize urls with URI adapter to allow International Domain Names support](https://github.com/jnunemaker/httparty/pull/668) +- [Add max_retries support](https://github.com/jnunemaker/httparty/pull/660) +- [Minize gem size by removing test files](https://github.com/jnunemaker/httparty/pull/658) + +## 0.17.0 + +- [Fix encoding of streamed chunk](https://github.com/jnunemaker/httparty/pull/644) +- [Avoid modifying frozen strings](https://github.com/jnunemaker/httparty/pull/649) +- [Expose .connection on fragment block param](https://github.com/jnunemaker/httparty/pull/648) +- [Add support for `Net::HTTP#write_timeout` method (Ruby 2.6.0)](https://github.com/jnunemaker/httparty/pull/647) + +## 0.16.4 + +- [Add support for Ruby 2.6](https://github.com/jnunemaker/httparty/pull/636) +- [Fix a few multipart issues](https://github.com/jnunemaker/httparty/pull/626) +- [Improve a memory usage for https requests](https://github.com/jnunemaker/httparty/pull/625) +- [Add response code to streamed body](https://github.com/jnunemaker/httparty/pull/588) + +## 0.16.3 + +- [Add Logstash-compatible formatter](https://github.com/jnunemaker/httparty/pull/612) +- [Add support for headers specified with symbols](https://github.com/jnunemaker/httparty/pull/622) +- [Fix response object marshalling](https://github.com/jnunemaker/httparty/pull/618) +- [Add ability to send multipart, without passing file](https://github.com/jnunemaker/httparty/pull/615) +- [Fix detection of content_type for multipart payload](https://github.com/jnunemaker/httparty/pull/616) +- [Process dynamic headers before making actual request](https://github.com/jnunemaker/httparty/pull/606) +- [Fix multipart uploads with ActionDispatch::Http::UploadedFile TempFile by using original_filename](https://github.com/jnunemaker/httparty/pull/598) +- [Added support for lock and unlock http requests](https://github.com/jnunemaker/httparty/pull/596) + +## 0.16.2 + +- [Support ActionDispatch::Http::UploadedFile again](https://github.com/jnunemaker/httparty/pull/585) + +## 0.16.1 + +- [Parse content with application/hal+json content type as JSON](https://github.com/jnunemaker/httparty/pull/573) +- [Convert objects to string when concatenating in multipart stuff](https://github.com/jnunemaker/httparty/pull/575) +- [Fix multipart to set its header even when other headers are provided](https://github.com/jnunemaker/httparty/pull/576) + +## 0.16.0 + +- [Add multipart support](https://github.com/jnunemaker/httparty/pull/569) + +## 0.15.7 + +Fixed + +- [Add Response#pretty_print | Restore documented behavior](https://github.com/jnunemaker/httparty/pull/570) +- [Add ability to parse response from JSONAPI ](https://github.com/jnunemaker/httparty/pull/553) + +## 0.15.6 + +Fixed + +- [Encoding and content type stuff](https://github.com/jnunemaker/httparty/pull/543) + +## 0.15.5 + +Fixed + +- [Use non-destructive gsub](https://github.com/jnunemaker/httparty/pull/540) + +## 0.15.4 + +Fixed + +- Prevent gsub errors with different encodings. +- Prevent passing nil to encode_body. + +## 0.15.3 + +Fixed + +- [Fix processing nil body for HEAD requests](https://github.com/jnunemaker/httparty/pull/530). +- Add missing require to headers.rb (33439a8). + +## 0.15.2 + +Fixed + +- Remove symlink from specs. It was reportedly still getting bundled with gem. + +## 0.15.1 + +Fixed + +- Stop including test files in gem. Fixes installation issues on windows due to symlink in spec dir. + +## 0.15.0 + +Breaking Changes + +- require Ruby >= 2.0.0 + +Fixed + +- [fix numerous bugs](https://github.com/jnunemaker/httparty/pull/513) +- [handle utf-8 bom for json parsing](https://github.com/jnunemaker/httparty/pull/520) +- [do not overwrite default headers unless specified](https://github.com/jnunemaker/httparty/pull/518) + +## 0.14.0 + +Breaking Changes + +- None + +Added + +- [added status predicate methods to Response#respond_to?](https://github.com/jnunemaker/httparty/pull/482) +- [support for MKCOL method](https://github.com/jnunemaker/httparty/pull/465) +- one fewer dependency: [remove json gem from gemspec](https://github.com/jnunemaker/httparty/pull/464) +- [optional raising exception on certain status codes](https://github.com/jnunemaker/httparty/pull/455) + +Fixed + +- [allow empty array to be used as param](https://github.com/jnunemaker/httparty/pull/477) +- [stop mutating cookie hash](https://github.com/jnunemaker/httparty/pull/460) + +## 0.13.7 aka "party not as hard" + +- remove post install emoji as it caused installation issues for some people + +## 0.13.6 + +- avoid calling String#strip on invalid Strings +- preserve request method on 307 and 308 redirects +- output version with --version for command line bin +- maintain head request method across redirects by default +- add support for RFC2617 MD5-sess algorithm type +- add party popper emoji to post install message + +## 0.13.5 + +- allow setting a custom URI adapter + +## 0.13.4 + +- correct redirect url for redirect paths without leading slash +- remove core_extensions.rb as backwards compat for ruby 1.8 not needed +- replace URI.encode with ERB::Util.url_encode +- allow the response to be tapped + +## 0.13.3 + +- minor improvement + - added option to allow for streaming large files without loading them into memory (672cdae) + +## 0.13.2 + +- minor improvement + - [Set correct path on redirect to filename](https://github.com/jnunemaker/httparty/pull/337) + - ensure logger works with curl format + +## 0.13.1 2014-04-08 + +- new + - [Added ability to specify a body_stream in HttpRequest](https://github.com/jnunemaker/httparty/pull/275) + - [Added read_timeout and open_timeout options](https://github.com/jnunemaker/httparty/pull/278) +- change + - [Initialize HTTParty requests with an URI object and a String](https://github.com/jnunemaker/httparty/pull/274) +- minor improvement + - [Add stackexchange API example](https://github.com/jnunemaker/httparty/pull/280) + +## 0.13.0 2014-02-14 + +- new + - [Add CSV support](https://github.com/jnunemaker/httparty/pull/269) + - [Allows PKCS12 client certificates](https://github.com/jnunemaker/httparty/pull/246) +- bug fix + - [Digest auth no longer fails when multiple headers are sent by the server](https://github.com/jnunemaker/httparty/pull/272) + - [Use 'Basement.copy' when calling 'HTTParty.copy'](https://github.com/jnunemaker/httparty/pull/268) + - [No longer appends ampersand when queries are embedded in paths](https://github.com/jnunemaker/httparty/pull/252) +- change + - [Merge - instead of overwrite - default headers with request provided headers](https://github.com/jnunemaker/httparty/pull/270) + - [Modernize respond_to implementations to support second param](https://github.com/jnunemaker/httparty/pull/264) + - [Sort query parameters by key before processing](https://github.com/jnunemaker/httparty/pull/245) +- minor improvement + - [Add HTTParty::Error base class](https://github.com/jnunemaker/httparty/pull/260) + +## 0.12.0 2013-10-10 + +- new + - [Added initial logging support](https://github.com/jnunemaker/httparty/pull/243) + - [Add support for local host and port binding](https://github.com/jnunemaker/httparty/pull/238) + - [content_type_charset_support](https://github.com/jnunemaker/httparty/commit/82e351f0904e8ecc856015ff2854698a2ca47fbc) +- bug fix + - [No longer attempt to decompress the body on HEAD requests](https://github.com/jnunemaker/httparty/commit/f2b8cc3d49e0e9363d7054b14f30c340d7b8e7f1) + - [Adding java check in aliasing of multiple choices](https://github.com/jnunemaker/httparty/pull/204/commits) +- change + - [MIME-type files of javascript are returned as a string instead of JSON](https://github.com/jnunemaker/httparty/pull/239) + - [Made SSL connections use the system certificate store by default](https://github.com/jnunemaker/httparty/pull/226) + - [Do not pass proxy options to Net::HTTP connection if not specified](https://github.com/jnunemaker/httparty/pull/222) + - [Replace multi_json with stdlib json](https://github.com/jnunemaker/httparty/pull/214) + - [Require Ruby >= 1.9.3] + - [Response returns array of returned cookie strings](https://github.com/jnunemaker/httparty/pull/218) + - [Allow '=' within value of a cookie] +- minor improvements + - [Improve documentation of ssl_ca_file, ssl_ca_path](https://github.com/jnunemaker/httparty/pull/223) + - [Fix example URLs](https://github.com/jnunemaker/httparty/pull/232) + +## 0.11.0 2013-04-10 + +- new + - [Add COPY http request handling](https://github.com/jnunemaker/httparty/pull/190) + - [Ruby 2.0 tests](https://github.com/jnunemaker/httparty/pull/194) + - [Ruby >= 2.0.0 support both multiple_choice? and multiple_choices?] +- bug fix + - [Maintain blocks passed to 'perform' in redirects](https://github.com/jnunemaker/httparty/pull/191) + - [Fixed nc value being quoted, this was against spec](https://github.com/jnunemaker/httparty/pull/196) + - [Request#uri no longer duplicates non-relative-path params](https://github.com/jnunemaker/httparty/pull/189) +- change + - [Client-side-only cookie attributes are removed: case-insensitive](https://github.com/jnunemaker/httparty/pull/188) + +## 0.10.2 2013-01-26 + +- bug fix + - [hash_conversions misnamed variable](https://github.com/jnunemaker/httparty/pull/187) + +## 0.10.1 2013-01-26 + +- new + - [Added support for MOVE requests](https://github.com/jnunemaker/httparty/pull/183) + - [Bump multi xml version](https://github.com/jnunemaker/httparty/pull/181) + +## 0.10.0 2013-01-10 + +- changes + - removed yaml support because of security risk (see rails yaml issues) + +## 0.9.0 2012-09-07 + +- new + - [support for connection adapters](https://github.com/jnunemaker/httparty/pull/157) + - [allow ssl_version on ruby 1.9](https://github.com/jnunemaker/httparty/pull/159) +- bug fixes + - [don't treat port 4430 as ssl](https://github.com/jnunemaker/httparty/commit/a296b1c97f83d7dcc6ef85720a43664c265685ac) + - [deep clone default options](https://github.com/jnunemaker/httparty/commit/f74227d30f9389b4b23a888c9af49fb9b8248e1f) + - a few net digest auth fixes + +## 0.8.3 2012-04-21 + +- new + - [lazy parsing of responses](https://github.com/jnunemaker/httparty/commit/9fd5259c8dab00e426082b66af44ede2c9068f45) + - [add support for PATCH requests](https://github.com/jnunemaker/httparty/commit/7ab6641e37a9e31517e46f6124f38c615395d38a) +- bug fixes + - [subclasses no longer override superclass options](https://github.com/jnunemaker/httparty/commit/682af8fbf672e7b3009e650da776c85cdfe78d39) + +## 0.8.2 2012-04-12 + +- new + - add -r to make CLI return failure code if status >= 400 + - allow blank username from CLI +- bug fixes + - return nil for null body + - automatically deflate responses with a Content-Encoding: x-gzip header + - Do not HEAD on POST request with digest authentication + - add support for proxy authentication + - fix posting data with CLI + - require rexml/document if xml format from CLI + - support for fragmented responses + +## 0.8.1 2011-10-05 + +- bug fixes + - content-encoding header should be removed when automatically inflating the body + +## 0.8.0 2011-09-13 + +- new + - switch to multi json/xml for parsing by default +- bug fixes + - fix redirects to relative uri's + +## 0.7.8 2011-06-06 + +- bug fix + - Make response honor respond to + - net http timeout can also be a float + +## 0.7.7 2011-04-16 + +- bug fix + - Fix NoMethodError when using the NON_RAILS_QUERY_STRING_NORMALIZER with a hash whose key is a symbol and value is nil + +## 0.7.5 2011-04-16 + +- bug fix + - caused issue with latest rubygems + +## 0.7.4 2011-02-13 + +- bug fixes + - Set VERIFY_NONE when using https. Ruby 1.9.2 no longer sets this for us. gh-67 + +## 0.7.3 2011-01-20 + +- bug fixes + - Fix digest auth for unspecified quality of protection (bjoernalbers, mtrudel, dwo) + +## 0.7.2 2011-01-20 + +- bug fixes + - Fix gem dependencies + +## 0.7.1 2011-01-19 + +- bug fixes + - Fix uninitialized constant HTTParty::Response::Net in 1.9.2 (cap10morgan) + - Other fixes for 1.9.2, full suite still fails (cap10morgan) + +## 0.7.0 2011-01-18 + +- minor enhancements + - Added query methods for HTTP status codes, i.e. response.success? + response.created? (thanks citizenparker) + - Added support for ssl_ca_file and ssl_ca_path (dlitz) + - Allow custom query string normalization. gh-8 + - Unlock private keys with password (freerange) + - Added high level request documentation (phildarnowsky) + - Added basic post example (pbuckley) + - Response object has access to its corresponding request object + - Added example of siginin into tripit.com + - Added option to follow redirects (rkj). gh-56 +- bug fixes + - Fixed superclass mismatch exception while running tests + (thanks dlitz http://github.com/dlitz/httparty/commit/48224f0615b32133afcff4718ad426df7a4b401b) + +## 0.6.1 2010-07-07 + +- minor enhancements + - updated to crack 0.1.8 +- bug fixes + - subclasses always merge into the parent's default_options and + default_cookies (l4rk). + - subclasses play nicely with grand parents. gh-49 + +## 0.6.0 2010-06-13 + +- major enhancements + + - Digest Auth (bartiaco, sbecker, gilles, and aaronrussell) + - Maintain HTTP method across redirects (bartiaco and sbecker) + - HTTParty::Response#response returns the Net::HTTPResponse object + - HTTParty::Response#headers returns a HTTParty::Response::Headers object + which quacks like a Hash + Net::HTTPHeader. The #headers method continues + to be backwards-compatible with the old Hash return value but may become + deprecated in the future. + +- minor enhancements + - Update crack requirement to version 0.1.7 + You may still get a warning because Crack's version constant is out of date + - Timeout option can be set for all requests using HTTParty.default_timeout (taazza) + - Closed #38 "headers hash should downcase keys so canonical header name can be used" + - Closed #40 "Gzip response" wherein gziped and deflated responses are + automatically inflated. (carsonmcdonald) + +## 0.5.2 2010-01-31 + +- minor enhancements + - Update crack requirement to version 0.1.6 + +## 0.5.1 2010-01-30 + +- bug fixes + + - Handle 304 response correctly by returning the HTTParty::Response object instead of redirecting (seth and hellvinz) + - Only redirect 300 responses if the header contains a Location + - Don't append empty query strings to the uri. Closes #31 + - When no_follow is enabled, only raise the RedirectionTooDeep exception when a response tries redirecting. Closes #28 + +- major enhancements + + - Removed rubygems dependency. I suggest adding rubygems to RUBYOPT if this causes problems for you. + $ export RUBYOPT='rubygems' + - HTTParty#debug_output prints debugging information for the current request (iwarshak) + - HTTParty#no_follow now available as a class-level option. Sets whether or not to follow redirects. + +- minor enhancements + - HTTParty::VERSION now available + - Update crack requirement to version 0.1.5 + +## 0.5.0 2009-12-07 + +- bug fixes + + - inheritable attributes no longer mutable by subclasses (yyyc514) + - namespace BasicObject within HTTParty to avoid class name collisions (eric) + +- major enhancements + + - Custom Parsers via class or proc + - Deprecation warning on HTTParty::AllowedFormats + moved to HTTParty::Parser::SupportedFormats + +- minor enhancements + - Curl inspired output when using the binary in verbose mode (alexvollmer) + - raise UnsupportedURIScheme when scheme is not HTTP or HTTPS (djspinmonkey) + - Allow SSL for ports other than 443 when scheme is HTTPS (stefankroes) + - Accept PEM certificates via HTTParty#pem (chrislo) + - Support HEAD and OPTION verbs (grempe) + - Verify SSL certificates when providing a PEM file (collectiveidea/danielmorrison) + +## 0.4.5 2009-09-12 + +- bug fixes + + - Fixed class-level headers overwritten by cookie management code. Closes #19 + - Fixed "superclass mismatch for class BlankSlate" error. Closes #20 + - Fixed reading files as post data from the command line (vesan) + +- minor enhancements + - Timeout option added; will raise a Timeout::Error after the timeout has elapsed (attack). Closes #17 + HTTParty.get "http://github.com", timeout: 1 + - Building gem with Jeweler + +## 0.4.4 2009-07-19 + +- 2 minor update + - :query no longer sets form data. Use body and set content type to application/x-www-form-urlencoded if you need it. :query was wrong for that. + - Fixed a bug in the cookies class method that caused cookies to be forgotten after the first request. + - Also, some general cleanup of tests and such. + +## 0.4.3 2009-04-23 + +- 1 minor update + - added message to the response object + +## 0.4.2 2009-03-30 + +- 2 minor changes + - response code now returns an integer instead of a string (jqr) + - rubyforge project setup for crack so i'm now depending on that instead of jnunemaker-crack + +## 0.4.1 2009-03-29 + +- 1 minor fix + - gem 'jnunemaker-crack' instead of gem 'crack' + +## 0.4.0 2009-03-29 + +- 1 minor change + - Switched xml and json parsing to crack (same code as before just moved to gem for easier reuse in other projects) + +## 0.3.1 2009-02-10 + +- 1 minor fix, 1 minor enhancement + - Fixed unescaping umlauts (siebertm) + - Added yaml response parsing (Miha Filej) + +## 0.3.0 2009-01-31 + +- 1 major enhancement, 1 bug fix + - JSON gem no longer a requirement. It was conflicting with rails json stuff so I just stole ActiveSupport's json decoding and bundled it with HTTParty. + - Fixed bug where query strings were being duplicated on redirects + - Added a bunch of specs and moved some code around. + +## 0.2.10 2009-01-29 + +- 1 minor enhancement + - Made encoding on query parameters treat everything except URI::PATTERN::UNRESERVED as UNSAFE to force encoding of '+' character (Julian Russell) + +## 0.2.9 2009-01-29 + +- 3 minor enhancements + - Added a 'headers' accessor to the response with a hash of any HTTP headers. (Don Peterson) + - Add support for a ":cookies" option to be used at the class level, or as an option on any individual call. It should be passed a hash, which will be converted to the proper format and added to the request headers when the call is made. (Don Peterson) + - Refactored several specs and added a full suite of cucumber features (Don Peterson) + +## 0.2.8 2009-01-28 + +- 1 major fix + - fixed major bug with response where it wouldn't iterate or really work at all with parsed responses + +## 0.2.7 2009-01-28 + +- 2 minor fixes, 2 minor enhancements, 2 major enhancements + - fixed undefined method add_node for nil class error that occasionally happened (juliocesar) + - Handle nil or unexpected values better when typecasting. (Brian Landau) + - More robust handling of mime types (Alex Vollmer) + - Fixed support for specifying headers and added support for basic auth to CLI. (Alex Vollmer) + - Added first class response object that includes original body and status code (Alex Vollmer) + - Now parsing all response types as some non-200 responses provide important information, this means no more exception raising (Alex Vollmer) + +## 0.2.6 2009-01-05 + +- 1 minor bug fix + - added explicit require of time as Time#parse failed outside of rails (willcodeforfoo) + +## 0.2.5 2009-01-05 + +- 1 major enhancement + - Add command line interface to HTTParty (Alex Vollmer) + +## 0.2.4 2008-12-23 + +- 1 bug fix + - Fixed that mimetype detection was failing if no mimetype was returned from service (skippy) + +## 0.2.3 2008-12-23 + +- 1 bug fix + - Fixed typecasting class variable naming issue + +## 0.2.2 2008-12-08 + +- 1 bug fix + - Added the missing core extension hash method to_xml_attributes + +## 0.2.1 2008-12-08 + +- 1 bug fix + - Fixed that HTTParty was borking ActiveSupport and as such Rails (thanks to Rob Sanheim) + +## 0.2.0 2008-12-07 + +- 1 major enhancement + - Removed ActiveSupport as a dependency. Now requires json gem for json deserialization and uses an included class to do the xml parsing. + +## 0.1.8 2008-11-30 + +- 3 major enhancements + - Moved base_uri normalization into request class and out of httparty module, fixing + the problem where base_uri was not always being normalized. + - Stupid simple support for HTTParty.get/post/put/delete. (jqr) + - Switched gem management to Echoe from newgem. + +## 0.1.7 2008-11-30 + +- 1 major enhancement + - fixed multiple class definitions overriding each others options + +## 0.1.6 2008-11-26 + +- 1 major enhancement + - now passing :query to set_form_data if post request to avoid content length errors + +## 0.1.5 2008-11-14 + +- 2 major enhancements + - Refactored send request method out into its own object. + - Added :html format if you just want to do that. + +## 0.1.4 2008-11-08 + +- 3 major enhancements: + - Removed some cruft + - Added ability to follow redirects automatically and turn that off (Alex Vollmer) + +## 0.1.3 2008-08-22 + +- 3 major enhancements: + - Added http_proxy key for setting proxy server and port (francxk@gmail.com) + - Now raises exception when http error occurs (francxk@gmail.com) + - Changed auto format detection from file extension to response content type (Jay Pignata) + +## 0.1.2 2008-08-09 + +- 1 major enhancement: + - default_params were not being appended to query string if option[:query] was blank + +## 0.1.1 2008-07-30 + +- 2 major enhancement: + - Added :basic_auth key for options when making a request + - :query and :body both now work with query string or hash + +## 0.1.0 2008-07-27 + +- 1 major enhancement: + - Initial release diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/Gemfile b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/Gemfile new file mode 100644 index 00000000..ae8ef233 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/Gemfile @@ -0,0 +1,27 @@ +source 'https://rubygems.org' +gemspec + +gem 'base64' +gem 'rake' +gem 'mongrel', '1.2.0.pre2' +gem 'json' + +group :development do + gem 'guard' + gem 'guard-rspec' + gem 'guard-bundler' +end + +group :test do + gem 'rexml' + gem 'rspec', '~> 3.4' + gem 'simplecov', require: false + gem 'aruba' + gem 'cucumber', '~> 2.3' + gem 'webmock' + gem 'addressable' +end + +group :development, :test do + gem 'pry' +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/Guardfile b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/Guardfile new file mode 100644 index 00000000..4075dc50 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/Guardfile @@ -0,0 +1,17 @@ +rspec_options = { + all_after_pass: false, + all_on_start: false, + failed_mode: :keep, + cmd: 'bundle exec rspec', +} + +guard 'rspec', rspec_options do + watch(%r{^spec/.+_spec\.rb$}) + watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { "spec" } +end + +guard 'bundler' do + watch('Gemfile') + watch(/^.+\.gemspec/) +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/MIT-LICENSE b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/MIT-LICENSE new file mode 100644 index 00000000..ea4d3405 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2008 John Nunemaker + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/README.md b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/README.md new file mode 100644 index 00000000..9aa163d6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/README.md @@ -0,0 +1,79 @@ +# httparty + +[![CI](https://github.com/jnunemaker/httparty/actions/workflows/ci.yml/badge.svg)](https://github.com/jnunemaker/httparty/actions/workflows/ci.yml) + +Makes http fun again! Ain't no party like a httparty, because a httparty don't stop. + +## Install + +``` +gem install httparty +``` + +## Requirements + +- Ruby 2.7.0 or higher +- You like to party! + +## Examples + +```ruby +# Use the class methods to get down to business quickly +response = HTTParty.get('http://api.stackexchange.com/2.2/questions?site=stackoverflow') + +puts response.body, response.code, response.message, response.headers.inspect + +# Or wrap things up in your own class +class StackExchange + include HTTParty + base_uri 'api.stackexchange.com' + + def initialize(service, page) + @options = { query: { site: service, page: page } } + end + + def questions + self.class.get("/2.2/questions", @options) + end + + def users + self.class.get("/2.2/users", @options) + end +end + +stack_exchange = StackExchange.new("stackoverflow", 1) +puts stack_exchange.questions +puts stack_exchange.users +``` + +See the [examples directory](http://github.com/jnunemaker/httparty/tree/main/examples) for even more goodies. + +## Command Line Interface + +httparty also includes the executable `httparty` which can be +used to query web services and examine the resulting output. By default +it will output the response as a pretty-printed Ruby object (useful for +grokking the structure of output). This can also be overridden to output +formatted XML or JSON. Execute `httparty --help` for all the +options. Below is an example of how easy it is. + +``` +httparty "https://api.stackexchange.com/2.2/questions?site=stackoverflow" +``` + +## Help and Docs + +- [Docs](https://github.com/jnunemaker/httparty/tree/main/docs) +- https://github.com/jnunemaker/httparty/discussions +- https://www.rubydoc.info/github/jnunemaker/httparty + +## Contributing + +- Fork the project. +- Run `bundle` +- Run `bundle exec rake` +- Make your feature addition or bug fix. +- Add tests for it. This is important so I don't break it in a future version unintentionally. +- Run `bundle exec rake` (No, REALLY :)) +- Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself in another branch so I can ignore when I pull) +- Send me a pull request. Bonus points for topic branches. diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/Rakefile b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/Rakefile new file mode 100644 index 00000000..d994fb66 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/Rakefile @@ -0,0 +1,10 @@ +begin + require 'rspec/core/rake_task' + RSpec::Core::RakeTask.new(:spec) +rescue LoadError +end + +require 'cucumber/rake/task' +Cucumber::Rake::Task.new(:features) + +task default: [:spec, :features] diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/bin/httparty b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/bin/httparty new file mode 100755 index 00000000..3bdb86fc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/bin/httparty @@ -0,0 +1,123 @@ +#!/usr/bin/env ruby + +require "optparse" +require "pp" + +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "/../lib")) +require "httparty" + +opts = { + action: :get, + headers: {}, + verbose: false +} + +OptionParser.new do |o| + o.banner = "USAGE: #{$PROGRAM_NAME} [options] [url]" + + o.on("-f", + "--format [FORMAT]", + "Output format to use instead of pretty-print ruby: " \ + "plain, csv, json or xml") do |f| + opts[:output_format] = f.downcase.to_sym + end + + o.on("-a", + "--action [ACTION]", + "HTTP action: get (default), post, put, delete, head, or options") do |a| + opts[:action] = a.downcase.to_sym + end + + o.on("-d", + "--data [BODY]", + "Data to put in request body (prefix with '@' for file)") do |d| + if d =~ /^@/ + opts[:body] = open(d[1..-1]).read + else + opts[:body] = d + end + end + + o.on("-H", "--header [NAME:VALUE]", "Additional HTTP headers in NAME:VALUE form") do |h| + abort "Invalid header specification, should be Name:Value" unless h =~ /.+:.+/ + name, value = h.split(':') + opts[:headers][name.strip] = value.strip + end + + o.on("-v", "--verbose", "If set, print verbose output") do |v| + opts[:verbose] = true + end + + o.on("-u", "--user [CREDS]", "Use basic authentication. Value should be user:password") do |u| + abort "Invalid credentials format. Must be user:password" unless u =~ /.*:.+/ + user, password = u.split(':') + opts[:basic_auth] = { username: user, password: password } + end + + o.on("-r", "--response-code", "Command fails if response code >= 400") do + opts[:response_code] = true + end + + o.on("-h", "--help", "Show help documentation") do |h| + puts o + exit + end + + o.on("--version", "Show HTTParty version") do |ver| + puts "Version: #{HTTParty::VERSION}" + exit + end +end.parse! + +if ARGV.empty? + STDERR.puts "You need to provide a URL" + STDERR.puts "USAGE: #{$PROGRAM_NAME} [options] [url]" +end + +def dump_headers(response) + resp_type = Net::HTTPResponse::CODE_TO_OBJ[response.code.to_s] + puts "#{response.code} #{resp_type.to_s.sub(/^Net::HTTP/, '')}" + response.headers.each do |n, v| + puts "#{n}: #{v}" + end + puts +end + +if opts[:verbose] + puts "#{opts[:action].to_s.upcase} #{ARGV.first}" + opts[:headers].each do |n, v| + puts "#{n}: #{v}" + end + puts +end + +response = HTTParty.send(opts[:action], ARGV.first, opts) +if opts[:output_format].nil? + dump_headers(response) if opts[:verbose] + pp response +else + print_format = opts[:output_format] + dump_headers(response) if opts[:verbose] + + case opts[:output_format] + when :json + begin + require 'json' + puts JSON.pretty_generate(response.parsed_response) + rescue LoadError + puts YAML.dump(response) + rescue JSON::JSONError + puts response.inspect + end + when :xml + require 'rexml/document' + REXML::Document.new(response.body).write(STDOUT, 2) + puts + when :csv + require 'csv' + puts CSV.parse(response.body).map(&:to_s) + else + puts response + end +end +exit false if opts[:response_code] && response.code >= 400 diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/cucumber.yml b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/cucumber.yml new file mode 100644 index 00000000..80df8ac8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/cucumber.yml @@ -0,0 +1 @@ +default: features --format progress diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/docs/README.md b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/docs/README.md new file mode 100644 index 00000000..3806d485 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/docs/README.md @@ -0,0 +1,193 @@ +# httparty + +Makes http fun again! + +## Table of contents +- [Parsing JSON](#parsing-json) +- [Working with SSL](#working-with-ssl) + +## Parsing JSON +If the response Content Type is `application/json`, HTTParty will parse the response and return Ruby objects such as a hash or array. The default behavior for parsing JSON will return keys as strings. This can be supressed with the `format` option. To get hash keys as symbols: + +```ruby +response = HTTParty.get('http://example.com', format: :plain) +JSON.parse response, symbolize_names: true +``` + +## Posting JSON +When using Content Type `application/json` with `POST`, `PUT` or `PATCH` requests, the body should be a string of valid JSON: + +```ruby +# With written JSON +HTTParty.post('http://example.com', body: "{\"foo\":\"bar\"}", headers: { 'Content-Type' => 'application/json' }) + +# Using JSON.generate +HTTParty.post('http://example.com', body: JSON.generate({ foo: 'bar' }), headers: { 'Content-Type' => 'application/json' }) + +# Using object.to_json +HTTParty.post('http://example.com', body: { foo: 'bar' }.to_json, headers: { 'Content-Type' => 'application/json' }) +``` + +## Working with SSL + +You can use this guide to work with SSL certificates. + +#### Using `pem` option + +```ruby +# Use this example if you are using a pem file +# - cert.pem must contain the content of a PEM file having the private key appended (separated from the cert by a newline \n) +# - Use an empty string for the password if the cert is not password protected + +class Client + include HTTParty + + base_uri "https://example.com" + pem File.read("#{File.expand_path('.')}/path/to/certs/cert.pem"), "123456" +end +``` + +#### Using `pkcs12` option + +```ruby +# Use this example if you are using a pkcs12 file + +class Client + include HTTParty + + base_uri "https://example.com" + pkcs12 File.read("#{File.expand_path('.')}/path/to/certs/cert.p12"), "123456" +end +``` + +#### Using `ssl_ca_file` option + +```ruby +# Use this example if you are using a pkcs12 file + +class Client + include HTTParty + + base_uri "https://example.com" + ssl_ca_file "#{File.expand_path('.')}/path/to/certs/cert.pem" +end +``` + +#### Using `ssl_ca_path` option + +```ruby +# Use this example if you are using a pkcs12 file + +class Client + include HTTParty + + base_uri "https://example.com" + ssl_ca_path '/path/to/certs' +end +``` + +You can also include all of these options with the call: + +```ruby +class Client + include HTTParty + + base_uri "https://example.com" + + def self.fetch + get("/resources", pem: File.read("#{File.expand_path('.')}/path/to/certs/cert.pem"), pem_password: "123456") + end +end +``` + +### Avoid SSL verification + +In some cases you may want to skip SSL verification, because the entity that issued the certificate is not a valid one, but you still want to work with it. You can achieve this through: + +```ruby +# Skips SSL certificate verification + +class Client + include HTTParty + + base_uri "https://example.com" + pem File.read("#{File.expand_path('.')}/path/to/certs/cert.pem"), "123456" + + def self.fetch + get("/resources", verify: false) + # You can also use something like: + # get("resources", verify_peer: false) + end +end +``` + +### HTTP Compression + +The `Accept-Encoding` request header and `Content-Encoding` response header +are used to control compression (gzip, etc.) over the wire. Refer to +[RFC-2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html) for details. +(For clarity: these headers are **not** used for character encoding i.e. `utf-8` +which is specified in the `Accept` and `Content-Type` headers.) + +Unless you have specific requirements otherwise, we recommend to **not** set +set the `Accept-Encoding` header on HTTParty requests. In this case, `Net::HTTP` +will set a sensible default compression scheme and automatically decompress the response. + +If you explicitly set `Accept-Encoding`, there be dragons: + +* If the HTTP response `Content-Encoding` received on the wire is `gzip` or `deflate`, + `Net::HTTP` will automatically decompress it, and will omit `Content-Encoding` + from your `HTTParty::Response` headers. + +* For the following encodings, HTTParty will automatically decompress them if you include + the required gem into your project. Similar to above, if decompression succeeds, + `Content-Encoding` will be omitted from your `HTTParty::Response` headers. + **Warning:** Support for these encodings is experimental and not fully battle-tested. + + | Content-Encoding | Required Gem | + | --- | --- | + | `br` (Brotli) | [brotli](https://rubygems.org/gems/brotli) | + | `compress` (LZW) | [ruby-lzws](https://rubygems.org/gems/ruby-lzws) | + | `zstd` (Zstandard) | [zstd-ruby](https://rubygems.org/gems/zstd-ruby) | + +* For other encodings, `HTTParty::Response#body` will return the raw uncompressed byte string, + and you'll need to inspect the `Content-Encoding` response header and decompress it yourself. + In this case, `HTTParty::Response#parsed_response` will be `nil`. + +* Lastly, you may use the `skip_decompression` option to disable all automatic decompression + and always get `HTTParty::Response#body` in its raw form along with the `Content-Encoding` header. + +```ruby +# Accept-Encoding=gzip,deflate can be safely assumed to be auto-decompressed + +res = HTTParty.get('https://example.com/test.json', headers: { 'Accept-Encoding' => 'gzip,deflate,identity' }) +JSON.parse(res.body) # safe + + +# Accept-Encoding=br,compress requires third-party gems + +require 'brotli' +require 'lzws' +require 'zstd-ruby' +res = HTTParty.get('https://example.com/test.json', headers: { 'Accept-Encoding' => 'br,compress,zstd' }) +JSON.parse(res.body) + + +# Accept-Encoding=* may return unhandled Content-Encoding + +res = HTTParty.get('https://example.com/test.json', headers: { 'Accept-Encoding' => '*' }) +encoding = res.headers['Content-Encoding'] +if encoding +JSON.parse(your_decompression_handling(res.body, encoding)) +else +# Content-Encoding not present implies decompressed +JSON.parse(res.body) +end + + +# Gimme the raw data! + +res = HTTParty.get('https://example.com/test.json', skip_decompression: true) +encoding = res.headers['Content-Encoding'] +JSON.parse(your_decompression_handling(res.body, encoding)) +``` diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/README.md b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/README.md new file mode 100644 index 00000000..e79ddc46 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/README.md @@ -0,0 +1,89 @@ +## Examples + +* [Amazon Book Search](aaws.rb) + * Httparty included into poro class + * Uses `get` requests + * Transforms query params to uppercased params + +* [Google Search](google.rb) + * Httparty included into poro class + * Uses `get` requests + +* [Crack Custom Parser](crack.rb) + * Creates a custom parser for XML using crack gem + * Uses `get` request + +* [Create HTML Nokogiri parser](nokogiri_html_parser.rb) + * Adds Html as a format + * passed the body of request to Nokogiri + +* [More Custom Parsers](custom_parsers.rb) + * Create an additional parser for atom or make it the ONLY parser + +* [Basic Auth, Delicious](delicious.rb) + * Basic Auth, shows how to merge those into options + * Uses `get` requests + +* [Passing Headers, User Agent](headers_and_user_agents.rb) + * Use the class method of Httparty + * Pass the User-Agent in the headers + * Uses `get` requests + +* [Basic Post Request](basic.rb) + * Httparty included into poro class + * Uses `post` requests + +* [Access Rubyurl Shortener](rubyurl.rb) + * Httparty included into poro class + * Uses `post` requests + +* [Add a custom log file](logging.rb) + * create a log file and have httparty log requests + +* [Accessing StackExchange](stackexchange.rb) + * Httparty included into poro class + * Creates methods for different endpoints + * Uses `get` requests + +* [Accessing Tripit](tripit_sign_in.rb) + * Httparty included into poro class + * Example of using `debug_output` to see headers/urls passed + * Getting and using Cookies + * Uses `get` requests + +* [Accessing Twitter](twitter.rb) + * Httparty included into poro class + * Basic Auth + * Loads settings from a config file + * Uses `get` requests + * Uses `post` requests + +* [Accessing WhoIsMyRep](whoismyrep.rb) + * Httparty included into poro class + * Uses `get` requests + * Two ways to pass params to get, inline on the url or in query hash + +* [Rescue Json Error](rescue_json.rb) + * Rescue errors due to parsing response + +* [Download file using stream mode](stream_download.rb) + * Uses `get` requests + * Uses `stream_body` mode + * Download file without using the memory + +* [Microsoft graph](microsoft_graph.rb) + * Basic Auth + * Uses `post` requests + * Uses multipart + +* [Multipart](multipart.rb) + * Multipart data upload _(with and without file)_ + +* [Uploading File](body_stream.rb) + * Uses `body_stream` to upload file + +* [Accessing x509 Peer Certificate](peer_cert.rb) + * Provides access to the server's TLS certificate + +* [Accessing IDNs](idn.rb) + * Uses a `get` request with an International domain names, which are Urls with emojis and non-ASCII characters such as accented letters. \ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/aaws.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/aaws.rb new file mode 100644 index 00000000..1fb9a623 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/aaws.rb @@ -0,0 +1,36 @@ +require 'rubygems' +require 'active_support' +require 'active_support/core_ext/hash' +require 'active_support/core_ext/string' + +dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +require File.join(dir, 'httparty') +require 'pp' +config = YAML.load(File.read(File.join(ENV['HOME'], '.aaws'))) + +module AAWS + class Book + include HTTParty + base_uri 'http://ecs.amazonaws.com' + default_params Service: 'AWSECommerceService', Operation: 'ItemSearch', SearchIndex: 'Books' + + def initialize(key) + @auth = { AWSAccessKeyId: key } + end + + def search(options = {}) + raise ArgumentError, 'You must search for something' if options[:query].blank? + + # amazon uses nasty camelized query params + options[:query] = options[:query] + .reverse_merge(@auth) + .transform_keys { |k| k.to_s.camelize } + + # make a request and return the items (NOTE: this doesn't handle errors at this point) + self.class.get('/onca/xml', options)['ItemSearchResponse']['Items'] + end + end +end + +aaws = AAWS::Book.new(config[:access_key]) +pp aaws.search(query: { title: 'Ruby On Rails' }) diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/basic.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/basic.rb new file mode 100644 index 00000000..6f784be2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/basic.rb @@ -0,0 +1,28 @@ +dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +require File.join(dir, 'httparty') +require 'pp' + +# You can also use post, put, delete, head, options in the same fashion +response = HTTParty.get('https://api.stackexchange.com/2.2/questions?site=stackoverflow') +puts response.body, response.code, response.message, response.headers.inspect + +# An example post to a minimal rails app in the development environment +# Note that "skip_before_filter :verify_authenticity_token" must be set in the +# "pears" controller for this example + +class Partay + include HTTParty + base_uri 'http://localhost:3000' +end + +options = { + body: { + pear: { # your resource + foo: '123', # your columns/data + bar: 'second', + baz: 'last thing' + } + } +} + +pp Partay.post('/pears.xml', options) diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/body_stream.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/body_stream.rb new file mode 100644 index 00000000..8ca405e9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/body_stream.rb @@ -0,0 +1,14 @@ +# To upload file to a server use :body_stream + +HTTParty.put( + 'http://localhost:3000/train', + body_stream: File.open('sample_configs/config_train_server_md.yml', 'r') +) + + +# Actually, it works with any IO object + +HTTParty.put( + 'http://localhost:3000/train', + body_stream: StringIO.new('foo') +) diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/crack.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/crack.rb new file mode 100644 index 00000000..bd5eb585 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/crack.rb @@ -0,0 +1,19 @@ +require 'rubygems' +require 'crack' + +dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +require File.join(dir, 'httparty') +require 'pp' + +class Rep + include HTTParty + + parser( + proc do |body, format| + Crack::XML.parse(body) + end + ) +end + +pp Rep.get('http://whoismyrepresentative.com/getall_mems.php?zip=46544') +pp Rep.get('http://whoismyrepresentative.com/getall_mems.php', query: { zip: 46544 }) diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/custom_parsers.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/custom_parsers.rb new file mode 100644 index 00000000..1c88f0f6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/custom_parsers.rb @@ -0,0 +1,68 @@ +dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +require File.join(dir, 'httparty') +require 'pp' + +class ParseAtom + include HTTParty + + # Support Atom along with the default parsers: xml, json, etc. + class Parser::Atom < HTTParty::Parser + SupportedFormats.merge!({"application/atom+xml" => :atom}) + + protected + + # perform atom parsing on body + def atom + body.to_atom + end + end + + parser Parser::Atom +end + +class OnlyParseAtom + include HTTParty + + # Only support Atom + class Parser::OnlyAtom < HTTParty::Parser + SupportedFormats = { "application/atom+xml" => :atom } + + protected + + # perform atom parsing on body + def atom + body.to_atom + end + end + + parser Parser::OnlyAtom +end + +class SkipParsing + include HTTParty + + # Parse the response body however you like + class Parser::Simple < HTTParty::Parser + def parse + body + end + end + + parser Parser::Simple +end + +class AdHocParsing + include HTTParty + parser( + proc do |body, format| + case format + when :json + body.to_json + when :xml + body.to_xml + else + body + end + end + ) +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/delicious.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/delicious.rb new file mode 100644 index 00000000..86a7ad6c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/delicious.rb @@ -0,0 +1,37 @@ +dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +require File.join(dir, 'httparty') +require 'pp' +config = YAML.load(File.read(File.join(ENV['HOME'], '.delicious'))) + +class Delicious + include HTTParty + base_uri 'https://api.del.icio.us/v1' + + def initialize(u, p) + @auth = { username: u, password: p } + end + + # query params that filter the posts are: + # tag (optional). Filter by this tag. + # dt (optional). Filter by this date (CCYY-MM-DDThh:mm:ssZ). + # url (optional). Filter by this url. + # ie: posts(query: {tag: 'ruby'}) + def posts(options = {}) + options.merge!({ basic_auth: @auth }) + self.class.get('/posts/get', options) + end + + # query params that filter the posts are: + # tag (optional). Filter by this tag. + # count (optional). Number of items to retrieve (Default:15, Maximum:100). + def recent(options = {}) + options.merge!({ basic_auth: @auth }) + self.class.get('/posts/recent', options) + end +end + +delicious = Delicious.new(config['username'], config['password']) +pp delicious.posts(query: { tag: 'ruby' }) +pp delicious.recent + +delicious.recent['posts']['post'].each { |post| puts post['href'] } diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/google.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/google.rb new file mode 100644 index 00000000..e91d7931 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/google.rb @@ -0,0 +1,16 @@ +dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +require File.join(dir, 'httparty') +require 'pp' + +class Google + include HTTParty + format :html +end + +# google.com redirects to www.google.com so this is live test for redirection +pp Google.get('http://google.com') + +puts '', '*' * 70, '' + +# check that ssl is requesting right +pp Google.get('https://www.google.com') diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/headers_and_user_agents.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/headers_and_user_agents.rb new file mode 100644 index 00000000..da8e7c61 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/headers_and_user_agents.rb @@ -0,0 +1,10 @@ +# To send custom user agents to identify your application to a web service (or mask as a specific browser for testing), send "User-Agent" as a hash to headers as shown below. + +dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +require File.join(dir, 'httparty') +require 'pp' + +response = HTTParty.get('http://example.com', { + headers: {"User-Agent" => "Httparty"}, + debug_output: STDOUT, # To show that User-Agent is Httparty +}) diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/idn.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/idn.rb new file mode 100644 index 00000000..4c3d9bab --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/idn.rb @@ -0,0 +1,10 @@ +dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +require File.join(dir, 'httparty') +require 'pp' + +class Idn + include HTTParty + uri_adapter Addressable::URI +end + +pp Idn.get("https://i❤️.ws/emojidomain/💎?format=json") \ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/logging.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/logging.rb new file mode 100644 index 00000000..d0dbae5a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/logging.rb @@ -0,0 +1,36 @@ +dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +require File.join(dir, 'httparty') +require 'logger' +require 'pp' + +my_logger = Logger.new STDOUT + +my_logger.info "Logging can be used on the main HTTParty class. It logs redirects too." +HTTParty.get "http://google.com", logger: my_logger + +my_logger.info '*' * 70 + +my_logger.info "It can be used also on a custom class." + +class Google + include HTTParty + logger ::Logger.new STDOUT +end + +Google.get "http://google.com" + +my_logger.info '*' * 70 + +my_logger.info "The default formatter is :apache. The :curl formatter can also be used." +my_logger.info "You can tell which method to call on the logger too. It is info by default." +HTTParty.get "http://google.com", logger: my_logger, log_level: :debug, log_format: :curl + +my_logger.info '*' * 70 + +my_logger.info "These configs are also available on custom classes." +class Google + include HTTParty + logger ::Logger.new(STDOUT), :debug, :curl +end + +Google.get "http://google.com" diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/microsoft_graph.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/microsoft_graph.rb new file mode 100644 index 00000000..0e44b6c3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/microsoft_graph.rb @@ -0,0 +1,52 @@ +require 'httparty' + +class MicrosoftGraph + MS_BASE_URL = "https://login.microsoftonline.com".freeze + TOKEN_REQUEST_PATH = "oauth2/v2.0/token".freeze + + def initialize(tenant_id) + @tenant_id = tenant_id + end + + # Make a request to the Microsoft Graph API, for instance https://graph.microsoft.com/v1.0/users + def request(url) + return false unless (token = bearer_token) + + response = HTTParty.get( + url, + headers: { + Authorization: "Bearer #{token}" + } + ) + + return false unless response.code == 200 + + return JSON.parse(response.body) + end + + private + + # A post to the Microsoft Graph to get a bearer token for the specified tenant. In this example + # our Rails application has already been given permission to request these tokens by the admin of + # the specified tenant_id. + # + # See here for more information https://developer.microsoft.com/en-us/graph/docs/concepts/auth_v2_service + # + # This request also makes use of the multipart/form-data post body. + def bearer_token + response = HTTParty.post( + "#{MS_BASE_URL}/#{@tenant_id}/#{TOKEN_REQUEST_PATH}", + multipart: true, + body: { + client_id: Rails.application.credentials[Rails.env.to_sym][:microsoft_client_id], + client_secret: Rails.application.credentials[Rails.env.to_sym][:microsoft_client_secret], + scope: 'https://graph.microsoft.com/.default', + grant_type: 'client_credentials' + } + ) + + return false unless response.code == 200 + + JSON.parse(response.body)['access_token'] + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/multipart.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/multipart.rb new file mode 100644 index 00000000..24c69167 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/multipart.rb @@ -0,0 +1,22 @@ +# If you are uploading file in params, multipart will used as content-type automatically + +HTTParty.post( + 'http://localhost:3000/user', + body: { + name: 'Foo Bar', + email: 'example@email.com', + avatar: File.open('/full/path/to/avatar.jpg') + } +) + + +# However, you can force it yourself + +HTTParty.post( + 'http://localhost:3000/user', + multipart: true, + body: { + name: 'Foo Bar', + email: 'example@email.com' + } +) diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/nokogiri_html_parser.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/nokogiri_html_parser.rb new file mode 100644 index 00000000..94c3ae10 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/nokogiri_html_parser.rb @@ -0,0 +1,19 @@ +require 'rubygems' +require 'nokogiri' + +dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +require File.join(dir, 'httparty') +require 'pp' + +class HtmlParserIncluded < HTTParty::Parser + def html + Nokogiri::HTML(body) + end +end + +class Page + include HTTParty + parser HtmlParserIncluded +end + +pp Page.get('http://www.google.com') diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/party_foul_mode.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/party_foul_mode.rb new file mode 100644 index 00000000..e11dcfcd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/party_foul_mode.rb @@ -0,0 +1,90 @@ +require 'httparty' + +class APIClient + include HTTParty + base_uri 'api.example.com' + + def self.fetch_user(id) + begin + get("/users/#{id}", foul: true) + rescue HTTParty::NetworkError => e + handle_network_error(e) + rescue HTTParty::ResponseError => e + handle_api_error(e) + end + end + + private + + def self.handle_network_error(error) + case error.cause + when Errno::ECONNREFUSED + { + error: :server_down, + message: "The API server appears to be down", + details: error.message + } + when Net::OpenTimeout, Timeout::Error + { + error: :timeout, + message: "The request timed out", + details: error.message + } + when SocketError + { + error: :network_error, + message: "Could not connect to the API server", + details: error.message + } + when OpenSSL::SSL::SSLError + { + error: :ssl_error, + message: "SSL certificate verification failed", + details: error.message + } + else + { + error: :unknown_network_error, + message: "An unexpected network error occurred", + details: error.message + } + end + end + + def self.handle_api_error(error) + { + error: :api_error, + message: "API returned error #{error.response.code}", + details: error.response.body + } + end +end + +# Example usage: + +# 1. When server is down +result = APIClient.fetch_user(123) +puts "Server down example:" +puts result.inspect +puts + +# 2. When request times out +result = APIClient.fetch_user(456) +puts "Timeout example:" +puts result.inspect +puts + +# 3. When SSL error occurs +result = APIClient.fetch_user(789) +puts "SSL error example:" +puts result.inspect +puts + +# 4. Simple example without a wrapper class +begin + HTTParty.get('https://api.example.com/users', foul: true) +rescue HTTParty::Foul => e + puts "Direct usage example:" + puts "Error type: #{e.cause.class}" + puts "Error message: #{e.message}" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/peer_cert.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/peer_cert.rb new file mode 100644 index 00000000..a16de689 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/peer_cert.rb @@ -0,0 +1,9 @@ +dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +require File.join(dir, 'httparty') + +peer_cert = nil +HTTParty.get("https://www.example.com") do |fragment| + peer_cert ||= fragment.connection.peer_cert +end + +puts "The server's certificate expires #{peer_cert.not_after}" diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/rescue_json.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/rescue_json.rb new file mode 100644 index 00000000..a4b66213 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/rescue_json.rb @@ -0,0 +1,17 @@ +dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +require File.join(dir, 'httparty') + +# Take note of the "; 1" at the end of the following line. It's required only if +# running this in IRB, because IRB will try to inspect the variable named +# "request", triggering the exception. +request = HTTParty.get 'https://rubygems.org/api/v1/versions/doesnotexist.json' ; 1 + +# Check an exception due to parsing the response +# because HTTParty evaluate the response lazily +begin + request.inspect + # This would also suffice by forcing the request to be parsed: + # request.parsed_response +rescue => e + puts "Rescued #{e.inspect}" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/rubyurl.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/rubyurl.rb new file mode 100644 index 00000000..54458e05 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/rubyurl.rb @@ -0,0 +1,14 @@ +dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +require File.join(dir, 'httparty') +require 'pp' + +class Rubyurl + include HTTParty + base_uri 'rubyurl.com' + + def self.shorten(website_url) + post('/api/links.json', query: { link: { website_url: website_url } }) + end +end + +pp Rubyurl.shorten('http://istwitterdown.com/') diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/stackexchange.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/stackexchange.rb new file mode 100644 index 00000000..f262dc15 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/stackexchange.rb @@ -0,0 +1,24 @@ +dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +require File.join(dir, 'httparty') +require 'pp' + +class StackExchange + include HTTParty + base_uri 'api.stackexchange.com' + + def initialize(service, page) + @options = { query: { site: service, page: page } } + end + + def questions + self.class.get("/2.2/questions", @options) + end + + def users + self.class.get("/2.2/users", @options) + end +end + +stack_exchange = StackExchange.new("stackoverflow", 1) +pp stack_exchange.questions +pp stack_exchange.users diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/stream_download.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/stream_download.rb new file mode 100644 index 00000000..4dfcf984 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/stream_download.rb @@ -0,0 +1,26 @@ +dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +require File.join(dir, 'httparty') +require 'pp' + +# download file linux-4.6.4.tar.xz without using the memory +response = nil +filename = "linux-4.6.4.tar.xz" +url = "https://cdn.kernel.org/pub/linux/kernel/v4.x/#{filename}" + +File.open(filename, "w") do |file| + response = HTTParty.get(url, stream_body: true) do |fragment| + if [301, 302].include?(fragment.code) + print "skip writing for redirect" + elsif fragment.code == 200 + print "." + file.write(fragment) + else + raise StandardError, "Non-success status code while streaming #{fragment.code}" + end + end +end +puts + +pp "Success: #{response.success?}" +pp File.stat(filename).inspect +File.unlink(filename) diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/tripit_sign_in.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/tripit_sign_in.rb new file mode 100644 index 00000000..e0fc0cfd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/tripit_sign_in.rb @@ -0,0 +1,44 @@ +dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +require File.join(dir, 'httparty') + +class TripIt + include HTTParty + base_uri 'https://www.tripit.com' + debug_output + + def initialize(email, password) + @email = email + get_response = self.class.get('/account/login') + get_response_cookie = parse_cookie(get_response.headers['Set-Cookie']) + + post_response = self.class.post( + '/account/login', + body: { + login_email_address: email, + login_password: password + }, + headers: {'Cookie' => get_response_cookie.to_cookie_string } + ) + + @cookie = parse_cookie(post_response.headers['Set-Cookie']) + end + + def account_settings + self.class.get('/account/edit', headers: { 'Cookie' => @cookie.to_cookie_string }) + end + + def logged_in? + account_settings.include? "You're logged in as #{@email}" + end + + private + + def parse_cookie(resp) + cookie_hash = CookieHash.new + resp.get_fields('Set-Cookie').each { |c| cookie_hash.add_cookies(c) } + cookie_hash + end +end + +tripit = TripIt.new('email', 'password') +puts "Logged in: #{tripit.logged_in?}" diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/twitter.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/twitter.rb new file mode 100644 index 00000000..812c1f1f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/twitter.rb @@ -0,0 +1,31 @@ +dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +require File.join(dir, 'httparty') +require 'pp' +config = YAML.load(File.read(File.join(ENV['HOME'], '.twitter'))) + +class Twitter + include HTTParty + base_uri 'twitter.com' + + def initialize(u, p) + @auth = {username: u, password: p} + end + + # which can be :friends, :user or :public + # options[:query] can be things like since, since_id, count, etc. + def timeline(which = :friends, options = {}) + options.merge!({ basic_auth: @auth }) + self.class.get("/statuses/#{which}_timeline.json", options) + end + + def post(text) + options = { query: { status: text }, basic_auth: @auth } + self.class.post('/statuses/update.json', options) + end +end + +twitter = Twitter.new(config['email'], config['password']) +pp twitter.timeline +# pp twitter.timeline(:friends, query: {since_id: 868482746}) +# pp twitter.timeline(:friends, query: 'since_id=868482746') +# pp twitter.post('this is a test of 0.2.0') diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/whoismyrep.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/whoismyrep.rb new file mode 100644 index 00000000..c991918d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/examples/whoismyrep.rb @@ -0,0 +1,10 @@ +dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +require File.join(dir, 'httparty') +require 'pp' + +class Rep + include HTTParty +end + +pp Rep.get('http://whoismyrepresentative.com/getall_mems.php?zip=46544') +pp Rep.get('http://whoismyrepresentative.com/getall_mems.php', query: { zip: 46544 }) diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/httparty.gemspec b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/httparty.gemspec new file mode 100644 index 00000000..b0d5fded --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/httparty.gemspec @@ -0,0 +1,31 @@ +# -*- encoding: utf-8 -*- +$LOAD_PATH.push File.expand_path("../lib", __FILE__) +require "httparty/version" + +Gem::Specification.new do |s| + s.name = "httparty" + s.version = HTTParty::VERSION + s.platform = Gem::Platform::RUBY + s.licenses = ['MIT'] + s.authors = ["John Nunemaker", "Sandro Turriate"] + s.email = ["nunemaker@gmail.com"] + s.homepage = "https://github.com/jnunemaker/httparty" + s.summary = 'Makes http fun! Also, makes consuming restful web services dead easy.' + s.description = 'Makes http fun! Also, makes consuming restful web services dead easy.' + + s.required_ruby_version = '>= 2.7.0' + + s.add_dependency 'csv' + s.add_dependency 'multi_xml', ">= 0.5.2" + s.add_dependency 'mini_mime', ">= 1.0.0" + + # If this line is removed, all hard partying will cease. + s.post_install_message = "When you HTTParty, you must party hard!" + + all_files = `git ls-files`.split("\n") + test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + + s.files = all_files - test_files + s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } + s.require_paths = ["lib"] +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty.rb new file mode 100644 index 00000000..f04f8d0e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty.rb @@ -0,0 +1,699 @@ +# frozen_string_literal: true + +require 'pathname' +require 'net/http' +require 'uri' + +require 'httparty/module_inheritable_attributes' +require 'httparty/cookie_hash' +require 'httparty/net_digest_auth' +require 'httparty/version' +require 'httparty/connection_adapter' +require 'httparty/logger/logger' +require 'httparty/request/body' +require 'httparty/response_fragment' +require 'httparty/decompressor' +require 'httparty/text_encoder' +require 'httparty/headers_processor' + +# @see HTTParty::ClassMethods +module HTTParty + def self.included(base) + base.extend ClassMethods + base.send :include, ModuleInheritableAttributes + base.send(:mattr_inheritable, :default_options) + base.send(:mattr_inheritable, :default_cookies) + base.instance_variable_set(:@default_options, {}) + base.instance_variable_set(:@default_cookies, CookieHash.new) + end + + # == Common Request Options + # Request methods (get, post, patch, put, delete, head, options) all take a common set of options. These are: + # + # [:+body+:] Body of the request. If passed an object that responds to #to_hash, will try to normalize it first, by default passing it to ActiveSupport::to_params. Any other kind of object will get used as-is. + # [:+http_proxyaddr+:] Address of proxy server to use. + # [:+http_proxyport+:] Port of proxy server to use. + # [:+http_proxyuser+:] User for proxy server authentication. + # [:+http_proxypass+:] Password for proxy server authentication. + # [:+limit+:] Maximum number of redirects to follow. Takes precedences over :+no_follow+. + # [:+query+:] Query string, or an object that responds to #to_hash representing it. Normalized according to the same rules as :+body+. If you specify this on a POST, you must use an object which responds to #to_hash. See also HTTParty::ClassMethods.default_params. + # [:+timeout+:] Timeout for opening connection and reading data. + # [:+local_host+:] Local address to bind to before connecting. + # [:+local_port+:] Local port to bind to before connecting. + # [:+body_stream+:] Allow streaming to a REST server to specify a body_stream. + # [:+stream_body+:] Allow for streaming large files without loading them into memory. + # [:+multipart+:] Force content-type to be multipart + # + # There are also another set of options with names corresponding to various class methods. The methods in question are those that let you set a class-wide default, and the options override the defaults on a request-by-request basis. Those options are: + # * :+base_uri+: see HTTParty::ClassMethods.base_uri. + # * :+basic_auth+: see HTTParty::ClassMethods.basic_auth. Only one of :+basic_auth+ and :+digest_auth+ can be used at a time; if you try using both, you'll get an ArgumentError. + # * :+debug_output+: see HTTParty::ClassMethods.debug_output. + # * :+digest_auth+: see HTTParty::ClassMethods.digest_auth. Only one of :+basic_auth+ and :+digest_auth+ can be used at a time; if you try using both, you'll get an ArgumentError. + # * :+format+: see HTTParty::ClassMethods.format. + # * :+headers+: see HTTParty::ClassMethods.headers. Must be a an object which responds to #to_hash. + # * :+maintain_method_across_redirects+: see HTTParty::ClassMethods.maintain_method_across_redirects. + # * :+no_follow+: see HTTParty::ClassMethods.no_follow. + # * :+parser+: see HTTParty::ClassMethods.parser. + # * :+uri_adapter+: see HTTParty::ClassMethods.uri_adapter + # * :+connection_adapter+: see HTTParty::ClassMethods.connection_adapter. + # * :+pem+: see HTTParty::ClassMethods.pem. + # * :+query_string_normalizer+: see HTTParty::ClassMethods.query_string_normalizer + # * :+ssl_ca_file+: see HTTParty::ClassMethods.ssl_ca_file. + # * :+ssl_ca_path+: see HTTParty::ClassMethods.ssl_ca_path. + + module ClassMethods + # Turns on or off the foul option. + # + # class Foo + # include HTTParty + # foul true + # end + def foul(bool) + default_options[:foul] = bool + end + + # Turns on logging + # + # class Foo + # include HTTParty + # logger Logger.new('http_logger'), :info, :apache + # end + def logger(logger, level = :info, format = :apache) + default_options[:logger] = logger + default_options[:log_level] = level + default_options[:log_format] = format + end + + # Raises HTTParty::ResponseError if response's code matches this statuses + # + # class Foo + # include HTTParty + # raise_on [404, 500, '5[0-9]*'] + # end + def raise_on(codes = []) + default_options[:raise_on] = *codes + end + + # Allows setting http proxy information to be used + # + # class Foo + # include HTTParty + # http_proxy 'http://foo.com', 80, 'user', 'pass' + # end + def http_proxy(addr = nil, port = nil, user = nil, pass = nil) + default_options[:http_proxyaddr] = addr + default_options[:http_proxyport] = port + default_options[:http_proxyuser] = user + default_options[:http_proxypass] = pass + end + + # Allows setting a base uri to be used for each request. + # Will normalize uri to include http, etc. + # + # class Foo + # include HTTParty + # base_uri 'twitter.com' + # end + def base_uri(uri = nil) + return default_options[:base_uri] unless uri + default_options[:base_uri] = HTTParty.normalize_base_uri(uri) + end + + # Allows setting basic authentication username and password. + # + # class Foo + # include HTTParty + # basic_auth 'username', 'password' + # end + def basic_auth(u, p) + default_options[:basic_auth] = {username: u, password: p} + end + + # Allows setting digest authentication username and password. + # + # class Foo + # include HTTParty + # digest_auth 'username', 'password' + # end + def digest_auth(u, p) + default_options[:digest_auth] = {username: u, password: p} + end + + # Do not send rails style query strings. + # Specifically, don't use bracket notation when sending an array + # + # For a query: + # get '/', query: {selected_ids: [1,2,3]} + # + # The default query string looks like this: + # /?selected_ids[]=1&selected_ids[]=2&selected_ids[]=3 + # + # Call `disable_rails_query_string_format` to transform the query string + # into: + # /?selected_ids=1&selected_ids=2&selected_ids=3 + # + # @example + # class Foo + # include HTTParty + # disable_rails_query_string_format + # end + def disable_rails_query_string_format + query_string_normalizer Request::NON_RAILS_QUERY_STRING_NORMALIZER + end + + # Allows setting default parameters to be appended to each request. + # Great for api keys and such. + # + # class Foo + # include HTTParty + # default_params api_key: 'secret', another: 'foo' + # end + def default_params(h = {}) + raise ArgumentError, 'Default params must be an object which responds to #to_hash' unless h.respond_to?(:to_hash) + default_options[:default_params] ||= {} + default_options[:default_params].merge!(h) + end + + # Allows setting a default timeout for all HTTP calls + # Timeout is specified in seconds. + # + # class Foo + # include HTTParty + # default_timeout 10 + # end + def default_timeout(value) + validate_timeout_argument(__method__, value) + default_options[:timeout] = value + end + + # Allows setting a default open_timeout for all HTTP calls in seconds + # + # class Foo + # include HTTParty + # open_timeout 10 + # end + def open_timeout(value) + validate_timeout_argument(__method__, value) + default_options[:open_timeout] = value + end + + # Allows setting a default read_timeout for all HTTP calls in seconds + # + # class Foo + # include HTTParty + # read_timeout 10 + # end + def read_timeout(value) + validate_timeout_argument(__method__, value) + default_options[:read_timeout] = value + end + + # Allows setting a default write_timeout for all HTTP calls in seconds + # Supported by Ruby > 2.6.0 + # + # class Foo + # include HTTParty + # write_timeout 10 + # end + def write_timeout(value) + validate_timeout_argument(__method__, value) + default_options[:write_timeout] = value + end + + + # Set an output stream for debugging, defaults to $stderr. + # The output stream is passed on to Net::HTTP#set_debug_output. + # + # class Foo + # include HTTParty + # debug_output $stderr + # end + def debug_output(stream = $stderr) + default_options[:debug_output] = stream + end + + # Allows setting HTTP headers to be used for each request. + # + # class Foo + # include HTTParty + # headers 'Accept' => 'text/html' + # end + def headers(h = nil) + if h + raise ArgumentError, 'Headers must be an object which responds to #to_hash' unless h.respond_to?(:to_hash) + default_options[:headers] ||= {} + default_options[:headers].merge!(h.to_hash) + else + default_options[:headers] || {} + end + end + + def cookies(h = {}) + raise ArgumentError, 'Cookies must be an object which responds to #to_hash' unless h.respond_to?(:to_hash) + default_cookies.add_cookies(h) + end + + # Proceed to the location header when an HTTP response dictates a redirect. + # Redirects are always followed by default. + # + # @example + # class Foo + # include HTTParty + # base_uri 'http://google.com' + # follow_redirects true + # end + def follow_redirects(value = true) + default_options[:follow_redirects] = value + end + + # Allows setting the format with which to parse. + # Must be one of the allowed formats ie: json, xml + # + # class Foo + # include HTTParty + # format :json + # end + def format(f = nil) + if f.nil? + default_options[:format] + else + parser(Parser) if parser.nil? + default_options[:format] = f + validate_format + end + end + + # Declare whether or not to follow redirects. When true, an + # {HTTParty::RedirectionTooDeep} error will raise upon encountering a + # redirect. You can then gain access to the response object via + # HTTParty::RedirectionTooDeep#response. + # + # @see HTTParty::ResponseError#response + # + # @example + # class Foo + # include HTTParty + # base_uri 'http://google.com' + # no_follow true + # end + # + # begin + # Foo.get('/') + # rescue HTTParty::RedirectionTooDeep => e + # puts e.response.body + # end + def no_follow(value = false) + default_options[:no_follow] = value + end + + # Declare that you wish to maintain the chosen HTTP method across redirects. + # The default behavior is to follow redirects via the GET method, except + # if you are making a HEAD request, in which case the default is to + # follow all redirects with HEAD requests. + # If you wish to maintain the original method, you can set this option to true. + # + # @example + # class Foo + # include HTTParty + # base_uri 'http://google.com' + # maintain_method_across_redirects true + # end + + def maintain_method_across_redirects(value = true) + default_options[:maintain_method_across_redirects] = value + end + + # Declare that you wish to resend the full HTTP request across redirects, + # even on redirects that should logically become GET requests. + # A 303 redirect in HTTP signifies that the redirected url should normally + # retrieved using a GET request, for instance, it is the output of a previous + # POST. maintain_method_across_redirects respects this behavior, but you + # can force HTTParty to resend_on_redirect even on 303 responses. + # + # @example + # class Foo + # include HTTParty + # base_uri 'http://google.com' + # resend_on_redirect + # end + + def resend_on_redirect(value = true) + default_options[:resend_on_redirect] = value + end + + # Allows setting a PEM file to be used + # + # class Foo + # include HTTParty + # pem File.read('/home/user/my.pem'), "optional password" + # end + def pem(pem_contents, password = nil) + default_options[:pem] = pem_contents + default_options[:pem_password] = password + end + + # Allows setting a PKCS12 file to be used + # + # class Foo + # include HTTParty + # pkcs12 File.read('/home/user/my.p12'), "password" + # end + def pkcs12(p12_contents, password) + default_options[:p12] = p12_contents + default_options[:p12_password] = password + end + + # Override the way query strings are normalized. + # Helpful for overriding the default rails normalization of Array queries. + # + # For a query: + # get '/', query: {selected_ids: [1,2,3]} + # + # The default query string normalizer returns: + # /?selected_ids[]=1&selected_ids[]=2&selected_ids[]=3 + # + # Let's change it to this: + # /?selected_ids=1&selected_ids=2&selected_ids=3 + # + # Pass a Proc to the query normalizer which accepts the yielded query. + # + # @example Modifying Array query strings + # class ServiceWrapper + # include HTTParty + # + # query_string_normalizer proc { |query| + # query.map do |key, value| + # value.map {|v| "#{key}=#{v}"} + # end.join('&') + # } + # end + # + # @param [Proc] normalizer custom query string normalizer. + # @yield [Hash, String] query string + # @yieldreturn [Array] an array that will later be joined with '&' + def query_string_normalizer(normalizer) + default_options[:query_string_normalizer] = normalizer + end + + # Allows setting of SSL version to use. This only works in Ruby 1.9+. + # You can get a list of valid versions from OpenSSL::SSL::SSLContext::METHODS. + # + # class Foo + # include HTTParty + # ssl_version :SSLv3 + # end + def ssl_version(version) + default_options[:ssl_version] = version + end + + # Deactivate automatic decompression of the response body. + # This will require you to explicitly handle body decompression + # by inspecting the Content-Encoding response header. + # + # Refer to docs/README.md "HTTP Compression" section for + # further details. + # + # @example + # class Foo + # include HTTParty + # skip_decompression + # end + def skip_decompression(value = true) + default_options[:skip_decompression] = !!value + end + + # Allows setting of SSL ciphers to use. This only works in Ruby 1.9+. + # You can get a list of valid specific ciphers from OpenSSL::Cipher.ciphers. + # You also can specify a cipher suite here, listed here at openssl.org: + # http://www.openssl.org/docs/apps/ciphers.html#CIPHER_SUITE_NAMES + # + # class Foo + # include HTTParty + # ciphers "RC4-SHA" + # end + def ciphers(cipher_names) + default_options[:ciphers] = cipher_names + end + + # Allows setting an OpenSSL certificate authority file. The file + # should contain one or more certificates in PEM format. + # + # Setting this option enables certificate verification. All + # certificates along a chain must be available in ssl_ca_file or + # ssl_ca_path for verification to succeed. + # + # + # class Foo + # include HTTParty + # ssl_ca_file '/etc/ssl/certs/ca-certificates.crt' + # end + def ssl_ca_file(path) + default_options[:ssl_ca_file] = path + end + + # Allows setting an OpenSSL certificate authority path (directory). + # + # Setting this option enables certificate verification. All + # certificates along a chain must be available in ssl_ca_file or + # ssl_ca_path for verification to succeed. + # + # class Foo + # include HTTParty + # ssl_ca_path '/etc/ssl/certs/' + # end + def ssl_ca_path(path) + default_options[:ssl_ca_path] = path + end + + # Allows setting a custom parser for the response. + # + # class Foo + # include HTTParty + # parser Proc.new {|data| ...} + # end + def parser(custom_parser = nil) + if custom_parser.nil? + default_options[:parser] + else + default_options[:parser] = custom_parser + validate_format + end + end + + # Allows setting a custom URI adapter. + # + # class Foo + # include HTTParty + # uri_adapter Addressable::URI + # end + def uri_adapter(uri_adapter) + raise ArgumentError, 'The URI adapter should respond to #parse' unless uri_adapter.respond_to?(:parse) + default_options[:uri_adapter] = uri_adapter + end + + # Allows setting a custom connection_adapter for the http connections + # + # @example + # class Foo + # include HTTParty + # connection_adapter Proc.new {|uri, options| ... } + # end + # + # @example provide optional configuration for your connection_adapter + # class Foo + # include HTTParty + # connection_adapter Proc.new {|uri, options| ... }, {foo: :bar} + # end + # + # @see HTTParty::ConnectionAdapter + def connection_adapter(custom_adapter = nil, options = nil) + if custom_adapter.nil? + default_options[:connection_adapter] + else + default_options[:connection_adapter] = custom_adapter + default_options[:connection_adapter_options] = options + end + end + + # Allows making a get request to a url. + # + # class Foo + # include HTTParty + # end + # + # # Simple get with full url + # Foo.get('http://foo.com/resource.json') + # + # # Simple get with full url and query parameters + # # ie: http://foo.com/resource.json?limit=10 + # Foo.get('http://foo.com/resource.json', query: {limit: 10}) + def get(path, options = {}, &block) + perform_request Net::HTTP::Get, path, options, &block + end + + # Allows making a post request to a url. + # + # class Foo + # include HTTParty + # end + # + # # Simple post with full url and setting the body + # Foo.post('http://foo.com/resources', body: {bar: 'baz'}) + # + # # Simple post with full url using :query option, + # # which appends the parameters to the URI. + # Foo.post('http://foo.com/resources', query: {bar: 'baz'}) + def post(path, options = {}, &block) + perform_request Net::HTTP::Post, path, options, &block + end + + # Perform a PATCH request to a path + def patch(path, options = {}, &block) + perform_request Net::HTTP::Patch, path, options, &block + end + + # Perform a PUT request to a path + def put(path, options = {}, &block) + perform_request Net::HTTP::Put, path, options, &block + end + + # Perform a DELETE request to a path + def delete(path, options = {}, &block) + perform_request Net::HTTP::Delete, path, options, &block + end + + # Perform a MOVE request to a path + def move(path, options = {}, &block) + perform_request Net::HTTP::Move, path, options, &block + end + + # Perform a COPY request to a path + def copy(path, options = {}, &block) + perform_request Net::HTTP::Copy, path, options, &block + end + + # Perform a HEAD request to a path + def head(path, options = {}, &block) + ensure_method_maintained_across_redirects options + perform_request Net::HTTP::Head, path, options, &block + end + + # Perform an OPTIONS request to a path + def options(path, options = {}, &block) + perform_request Net::HTTP::Options, path, options, &block + end + + # Perform a MKCOL request to a path + def mkcol(path, options = {}, &block) + perform_request Net::HTTP::Mkcol, path, options, &block + end + + def lock(path, options = {}, &block) + perform_request Net::HTTP::Lock, path, options, &block + end + + def unlock(path, options = {}, &block) + perform_request Net::HTTP::Unlock, path, options, &block + end + + def build_request(http_method, path, options = {}) + options = ModuleInheritableAttributes.hash_deep_dup(default_options).merge(options) + HeadersProcessor.new(headers, options).call + process_cookies(options) + Request.new(http_method, path, options) + end + + attr_reader :default_options + + private + + def validate_timeout_argument(timeout_type, value) + raise ArgumentError, "#{ timeout_type } must be an integer or float" unless value && (value.is_a?(Integer) || value.is_a?(Float)) + end + + def ensure_method_maintained_across_redirects(options) + unless options.key?(:maintain_method_across_redirects) + options[:maintain_method_across_redirects] = true + end + end + + def perform_request(http_method, path, options, &block) #:nodoc: + build_request(http_method, path, options).perform(&block) + end + + def process_cookies(options) #:nodoc: + return unless options[:cookies] || default_cookies.any? + options[:headers] ||= headers.dup + options[:headers]['cookie'] = cookies.merge(options.delete(:cookies) || {}).to_cookie_string + end + + def validate_format + if format && parser.respond_to?(:supports_format?) && !parser.supports_format?(format) + supported_format_names = parser.supported_formats.map(&:to_s).sort.join(', ') + raise UnsupportedFormat, "'#{format.inspect}' Must be one of: #{supported_format_names}" + end + end + end + + def self.normalize_base_uri(url) #:nodoc: + normalized_url = url.dup + use_ssl = (normalized_url =~ /^https/) || (normalized_url =~ /:443\b/) + ends_with_slash = normalized_url =~ /\/$/ + + normalized_url.chop! if ends_with_slash + normalized_url.gsub!(/^https?:\/\//i, '') + + "http#{'s' if use_ssl}://#{normalized_url}" + end + + class Basement #:nodoc: + include HTTParty + end + + def self.get(*args, &block) + Basement.get(*args, &block) + end + + def self.post(*args, &block) + Basement.post(*args, &block) + end + + def self.patch(*args, &block) + Basement.patch(*args, &block) + end + + def self.put(*args, &block) + Basement.put(*args, &block) + end + + def self.delete(*args, &block) + Basement.delete(*args, &block) + end + + def self.move(*args, &block) + Basement.move(*args, &block) + end + + def self.copy(*args, &block) + Basement.copy(*args, &block) + end + + def self.head(*args, &block) + Basement.head(*args, &block) + end + + def self.options(*args, &block) + Basement.options(*args, &block) + end + + def self.build_request(*args, &block) + Basement.build_request(*args, &block) + end +end + +require 'httparty/hash_conversions' +require 'httparty/utils' +require 'httparty/exceptions' +require 'httparty/parser' +require 'httparty/request' +require 'httparty/response' diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/connection_adapter.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/connection_adapter.rb new file mode 100644 index 00000000..262016fb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/connection_adapter.rb @@ -0,0 +1,237 @@ +# frozen_string_literal: true + +module HTTParty + # Default connection adapter that returns a new Net::HTTP each time + # + # == Custom Connection Factories + # + # If you like to implement your own connection adapter, subclassing + # HTTParty::ConnectionAdapter will make it easier. Just override + # the #connection method. The uri and options attributes will have + # all the info you need to construct your http connection. Whatever + # you return from your connection method needs to adhere to the + # Net::HTTP interface as this is what HTTParty expects. + # + # @example log the uri and options + # class LoggingConnectionAdapter < HTTParty::ConnectionAdapter + # def connection + # puts uri + # puts options + # Net::HTTP.new(uri) + # end + # end + # + # @example count number of http calls + # class CountingConnectionAdapter < HTTParty::ConnectionAdapter + # @@count = 0 + # + # self.count + # @@count + # end + # + # def connection + # self.count += 1 + # super + # end + # end + # + # === Configuration + # There is lots of configuration data available for your connection adapter + # in the #options attribute. It is up to you to interpret them within your + # connection adapter. Take a look at the implementation of + # HTTParty::ConnectionAdapter#connection for examples of how they are used. + # The keys used in options are + # * :+timeout+: timeout in seconds + # * :+open_timeout+: http connection open_timeout in seconds, overrides timeout if set + # * :+read_timeout+: http connection read_timeout in seconds, overrides timeout if set + # * :+write_timeout+: http connection write_timeout in seconds, overrides timeout if set (Ruby >= 2.6.0 required) + # * :+debug_output+: see HTTParty::ClassMethods.debug_output. + # * :+cert_store+: contains certificate data. see method 'attach_ssl_certificates' + # * :+pem+: contains pem client certificate data. see method 'attach_ssl_certificates' + # * :+p12+: contains PKCS12 client client certificate data. see method 'attach_ssl_certificates' + # * :+verify+: verify the server’s certificate against the ca certificate. + # * :+verify_peer+: set to false to turn off server verification but still send client certificate + # * :+ssl_ca_file+: see HTTParty::ClassMethods.ssl_ca_file. + # * :+ssl_ca_path+: see HTTParty::ClassMethods.ssl_ca_path. + # * :+ssl_version+: SSL versions to allow. see method 'attach_ssl_certificates' + # * :+ciphers+: The list of SSL ciphers to support + # * :+connection_adapter_options+: contains the hash you passed to HTTParty.connection_adapter when you configured your connection adapter + # * :+local_host+: The local address to bind to + # * :+local_port+: The local port to bind to + # * :+http_proxyaddr+: HTTP Proxy address + # * :+http_proxyport+: HTTP Proxy port + # * :+http_proxyuser+: HTTP Proxy user + # * :+http_proxypass+: HTTP Proxy password + # + # === Inherited methods + # * :+clean_host+: Method used to sanitize host names + + class ConnectionAdapter + # Private: Regex used to strip brackets from IPv6 URIs. + StripIpv6BracketsRegex = /\A\[(.*)\]\z/ + + OPTION_DEFAULTS = { + verify: true, + verify_peer: true + } + + # Public + def self.call(uri, options) + new(uri, options).connection + end + + def self.default_cert_store + @default_cert_store ||= OpenSSL::X509::Store.new.tap do |cert_store| + cert_store.set_default_paths + end + end + + attr_reader :uri, :options + + def initialize(uri, options = {}) + uri_adapter = options[:uri_adapter] || URI + raise ArgumentError, "uri must be a #{uri_adapter}, not a #{uri.class}" unless uri.is_a? uri_adapter + + @uri = uri + @options = OPTION_DEFAULTS.merge(options) + end + + def connection + host = clean_host(uri.host) + port = uri.port || (uri.scheme == 'https' ? 443 : 80) + if options.key?(:http_proxyaddr) + http = Net::HTTP.new( + host, + port, + options[:http_proxyaddr], + options[:http_proxyport], + options[:http_proxyuser], + options[:http_proxypass] + ) + else + http = Net::HTTP.new(host, port) + end + + http.use_ssl = ssl_implied?(uri) + + attach_ssl_certificates(http, options) + + if add_timeout?(options[:timeout]) + http.open_timeout = options[:timeout] + http.read_timeout = options[:timeout] + http.write_timeout = options[:timeout] + end + + if add_timeout?(options[:read_timeout]) + http.read_timeout = options[:read_timeout] + end + + if add_timeout?(options[:open_timeout]) + http.open_timeout = options[:open_timeout] + end + + if add_timeout?(options[:write_timeout]) + http.write_timeout = options[:write_timeout] + end + + if add_max_retries?(options[:max_retries]) + http.max_retries = options[:max_retries] + end + + if options[:debug_output] + http.set_debug_output(options[:debug_output]) + end + + if options[:ciphers] + http.ciphers = options[:ciphers] + end + + # Bind to a specific local address or port + # + # @see https://bugs.ruby-lang.org/issues/6617 + if options[:local_host] + http.local_host = options[:local_host] + end + + if options[:local_port] + http.local_port = options[:local_port] + end + + http + end + + private + + def add_timeout?(timeout) + timeout && (timeout.is_a?(Integer) || timeout.is_a?(Float)) + end + + def add_max_retries?(max_retries) + max_retries && max_retries.is_a?(Integer) && max_retries >= 0 + end + + def clean_host(host) + strip_ipv6_brackets(host) + end + + def strip_ipv6_brackets(host) + StripIpv6BracketsRegex =~ host ? $1 : host + end + + def ssl_implied?(uri) + uri.port == 443 || uri.scheme == 'https' + end + + def verify_ssl_certificate? + !(options[:verify] == false || options[:verify_peer] == false) + end + + def attach_ssl_certificates(http, options) + if http.use_ssl? + if options.fetch(:verify, true) + http.verify_mode = OpenSSL::SSL::VERIFY_PEER + if options[:cert_store] + http.cert_store = options[:cert_store] + else + # Use the default cert store by default, i.e. system ca certs + http.cert_store = self.class.default_cert_store + end + else + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + end + + # Client certificate authentication + # Note: options[:pem] must contain the content of a PEM file having the private key appended + if options[:pem] + http.cert = OpenSSL::X509::Certificate.new(options[:pem]) + http.key = OpenSSL::PKey.read(options[:pem], options[:pem_password]) + http.verify_mode = verify_ssl_certificate? ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE + end + + # PKCS12 client certificate authentication + if options[:p12] + p12 = OpenSSL::PKCS12.new(options[:p12], options[:p12_password]) + http.cert = p12.certificate + http.key = p12.key + http.verify_mode = verify_ssl_certificate? ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE + end + + # SSL certificate authority file and/or directory + if options[:ssl_ca_file] + http.ca_file = options[:ssl_ca_file] + http.verify_mode = OpenSSL::SSL::VERIFY_PEER + end + + if options[:ssl_ca_path] + http.ca_path = options[:ssl_ca_path] + http.verify_mode = OpenSSL::SSL::VERIFY_PEER + end + + # This is only Ruby 1.9+ + if options[:ssl_version] && http.respond_to?(:ssl_version=) + http.ssl_version = options[:ssl_version] + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/cookie_hash.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/cookie_hash.rb new file mode 100644 index 00000000..7d33d17b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/cookie_hash.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class HTTParty::CookieHash < Hash #:nodoc: + CLIENT_COOKIES = %w(path expires domain path secure httponly samesite) + + def add_cookies(data) + case data + when Hash + merge!(data) + when String + data.split('; ').each do |cookie| + key, value = cookie.split('=', 2) + self[key.to_sym] = value if key + end + else + raise "add_cookies only takes a Hash or a String" + end + end + + def to_cookie_string + select { |k, v| !CLIENT_COOKIES.include?(k.to_s.downcase) }.collect { |k, v| "#{k}=#{v}" }.join('; ') + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/decompressor.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/decompressor.rb new file mode 100644 index 00000000..8557c475 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/decompressor.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +module HTTParty + # Decompresses the response body based on the Content-Encoding header. + # + # Net::HTTP automatically decompresses Content-Encoding values "gzip" and "deflate". + # This class will handle "br" (Brotli) and "compress" (LZW) if the requisite + # gems are installed. Otherwise, it returns nil if the body data cannot be + # decompressed. + # + # @abstract Read the HTTP Compression section for more information. + class Decompressor + + # "gzip" and "deflate" are handled by Net::HTTP + # hence they do not need to be handled by HTTParty + SupportedEncodings = { + 'none' => :none, + 'identity' => :none, + 'br' => :brotli, + 'compress' => :lzw, + 'zstd' => :zstd + }.freeze + + # The response body of the request + # @return [String] + attr_reader :body + + # The Content-Encoding algorithm used to encode the body + # @return [Symbol] e.g. :gzip + attr_reader :encoding + + # @param [String] body - the response body of the request + # @param [Symbol] encoding - the Content-Encoding algorithm used to encode the body + def initialize(body, encoding) + @body = body + @encoding = encoding + end + + # Perform decompression on the response body + # @return [String] the decompressed body + # @return [nil] when the response body is nil or cannot decompressed + def decompress + return nil if body.nil? + return body if encoding.nil? || encoding.strip.empty? + + if supports_encoding? + decompress_supported_encoding + else + nil + end + end + + protected + + def supports_encoding? + SupportedEncodings.keys.include?(encoding) + end + + def decompress_supported_encoding + method = SupportedEncodings[encoding] + if respond_to?(method, true) + send(method) + else + raise NotImplementedError, "#{self.class.name} has not implemented a decompression method for #{encoding.inspect} encoding." + end + end + + def none + body + end + + def brotli + return nil unless defined?(::Brotli) + begin + ::Brotli.inflate(body) + rescue StandardError + nil + end + end + + def lzw + begin + if defined?(::LZWS::String) + ::LZWS::String.decompress(body) + elsif defined?(::LZW::Simple) + ::LZW::Simple.new.decompress(body) + end + rescue StandardError + nil + end + end + + def zstd + return nil unless defined?(::Zstd) + begin + ::Zstd.decompress(body) + rescue StandardError + nil + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/exceptions.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/exceptions.rb new file mode 100644 index 00000000..ddc93fc3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/exceptions.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module HTTParty + COMMON_NETWORK_ERRORS = [ + EOFError, + Errno::ECONNABORTED, + Errno::ECONNREFUSED, + Errno::ECONNRESET, + Errno::EHOSTUNREACH, + Errno::EINVAL, + Errno::ENETUNREACH, + Errno::ENOTSOCK, + Errno::EPIPE, + Errno::ETIMEDOUT, + Net::HTTPBadResponse, + Net::HTTPHeaderSyntaxError, + Net::ProtocolError, + Net::ReadTimeout, + OpenSSL::SSL::SSLError, + SocketError, + Timeout::Error # Also covers subclasses like Net::OpenTimeout + ].freeze + + # @abstract Exceptions raised by HTTParty inherit from Error + class Error < StandardError; end + + # @abstract Exceptions raised by HTTParty inherit from this because it is funny + # and if you don't like fun you should be using a different library. + class Foul < Error; end + + # Exception raised when you attempt to set a non-existent format + class UnsupportedFormat < Foul; end + + # Exception raised when using a URI scheme other than HTTP or HTTPS + class UnsupportedURIScheme < Foul; end + + # @abstract Exceptions which inherit from ResponseError contain the Net::HTTP + # response object accessible via the {#response} method. + class ResponseError < Foul + # Returns the response of the last request + # @return [Net::HTTPResponse] A subclass of Net::HTTPResponse, e.g. + # Net::HTTPOK + attr_reader :response + + # Instantiate an instance of ResponseError with a Net::HTTPResponse object + # @param [Net::HTTPResponse] + def initialize(response) + @response = response + super(response) + end + end + + # Exception that is raised when request has redirected too many times. + # Calling {#response} returns the Net:HTTP response object. + class RedirectionTooDeep < ResponseError; end + + # Exception that is raised when request redirects and location header is present more than once + class DuplicateLocationHeader < ResponseError; end + + # Exception that is raised when common network errors occur. + class NetworkError < Foul; end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/hash_conversions.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/hash_conversions.rb new file mode 100644 index 00000000..270effe3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/hash_conversions.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'erb' + +module HTTParty + module HashConversions + # @return This hash as a query string + # + # @example + # { name: "Bob", + # address: { + # street: '111 Ruby Ave.', + # city: 'Ruby Central', + # phones: ['111-111-1111', '222-222-2222'] + # } + # }.to_params + # #=> "name=Bob&address[city]=Ruby Central&address[phones][]=111-111-1111&address[phones][]=222-222-2222&address[street]=111 Ruby Ave." + def self.to_params(hash) + hash.to_hash.map { |k, v| normalize_param(k, v) }.join.chop + end + + # @param key The key for the param. + # @param value The value for the param. + # + # @return This key value pair as a param + # + # @example normalize_param(:name, "Bob Jones") #=> "name=Bob%20Jones&" + def self.normalize_param(key, value) + normalized_keys = normalize_keys(key, value) + + normalized_keys.flatten.each_slice(2).inject(''.dup) do |string, (k, v)| + string << "#{ERB::Util.url_encode(k)}=#{ERB::Util.url_encode(v.to_s)}&" + end + end + + def self.normalize_keys(key, value) + stack = [] + normalized_keys = [] + + if value.respond_to?(:to_ary) + if value.empty? + normalized_keys << ["#{key}[]", ''] + else + normalized_keys = value.to_ary.flat_map do |element| + normalize_keys("#{key}[]", element) + end + end + elsif value.respond_to?(:to_hash) + stack << [key, value.to_hash] + else + normalized_keys << [key.to_s, value] + end + + stack.each do |parent, hash| + hash.each do |child_key, child_value| + if child_value.respond_to?(:to_hash) + stack << ["#{parent}[#{child_key}]", child_value.to_hash] + elsif child_value.respond_to?(:to_ary) + child_value.to_ary.each do |v| + normalized_keys << normalize_keys("#{parent}[#{child_key}][]", v).flatten + end + else + normalized_keys << normalize_keys("#{parent}[#{child_key}]", child_value).flatten + end + end + end + + normalized_keys + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/headers_processor.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/headers_processor.rb new file mode 100644 index 00000000..05b82c6a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/headers_processor.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module HTTParty + class HeadersProcessor + attr_reader :headers, :options + + def initialize(headers, options) + @headers = headers + @options = options + end + + def call + return unless options[:headers] + + options[:headers] = headers.merge(options[:headers]) if headers.any? + options[:headers] = Utils.stringify_keys(process_dynamic_headers) + end + + private + + def process_dynamic_headers + options[:headers].each_with_object({}) do |header, processed_headers| + key, value = header + processed_headers[key] = if value.respond_to?(:call) + value.arity == 0 ? value.call : value.call(options) + else + value + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/logger/apache_formatter.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/logger/apache_formatter.rb new file mode 100644 index 00000000..1e3fe97f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/logger/apache_formatter.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module HTTParty + module Logger + class ApacheFormatter #:nodoc: + TAG_NAME = HTTParty.name + + attr_accessor :level, :logger + + def initialize(logger, level) + @logger = logger + @level = level.to_sym + end + + def format(request, response) + @request = request + @response = response + + logger.public_send level, message + end + + private + + attr_reader :request, :response + + def message + "[#{TAG_NAME}] [#{current_time}] #{response.code} \"#{http_method} #{path}\" #{content_length || '-'} " + end + + def current_time + Time.now.strftime('%Y-%m-%d %H:%M:%S %z') + end + + def http_method + request.http_method.name.split('::').last.upcase + end + + def path + request.path.to_s + end + + def content_length + response.respond_to?(:headers) ? response.headers['Content-Length'] : response['Content-Length'] + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/logger/curl_formatter.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/logger/curl_formatter.rb new file mode 100644 index 00000000..eee2aad0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/logger/curl_formatter.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module HTTParty + module Logger + class CurlFormatter #:nodoc: + TAG_NAME = HTTParty.name + OUT = '>' + IN = '<' + + attr_accessor :level, :logger + + def initialize(logger, level) + @logger = logger + @level = level.to_sym + @messages = [] + end + + def format(request, response) + @request = request + @response = response + + log_request + log_response + + logger.public_send level, messages.join("\n") + end + + private + + attr_reader :request, :response + attr_accessor :messages + + def log_request + log_url + log_headers + log_query + log OUT, request.raw_body if request.raw_body + log OUT + end + + def log_response + log IN, "HTTP/#{response.http_version} #{response.code}" + log_response_headers + log IN, "\n#{response.body}" + log IN + end + + def log_url + http_method = request.http_method.name.split('::').last.upcase + uri = if request.options[:base_uri] + request.options[:base_uri] + request.path.path + else + request.path.to_s + end + + log OUT, "#{http_method} #{uri}" + end + + def log_headers + return unless request.options[:headers] && request.options[:headers].size > 0 + + log OUT, 'Headers: ' + log_hash request.options[:headers] + end + + def log_query + return unless request.options[:query] + + log OUT, 'Query: ' + log_hash request.options[:query] + end + + def log_response_headers + headers = response.respond_to?(:headers) ? response.headers : response + response.each_header do |response_header| + log IN, "#{response_header.capitalize}: #{headers[response_header]}" + end + end + + def log_hash(hash) + hash.each { |k, v| log(OUT, "#{k}: #{v}") } + end + + def log(direction, line = '') + messages << "[#{TAG_NAME}] [#{current_time}] #{direction} #{line}" + end + + def current_time + Time.now.strftime("%Y-%m-%d %H:%M:%S %z") + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/logger/logger.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/logger/logger.rb new file mode 100644 index 00000000..b1db7826 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/logger/logger.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'httparty/logger/apache_formatter' +require 'httparty/logger/curl_formatter' +require 'httparty/logger/logstash_formatter' + +module HTTParty + module Logger + def self.formatters + @formatters ||= { + :curl => Logger::CurlFormatter, + :apache => Logger::ApacheFormatter, + :logstash => Logger::LogstashFormatter, + } + end + + def self.add_formatter(name, formatter) + raise HTTParty::Error.new("Log Formatter with name #{name} already exists") if formatters.include?(name) + formatters.merge!(name.to_sym => formatter) + end + + def self.build(logger, level, formatter) + level ||= :info + formatter ||= :apache + + logger_klass = formatters[formatter] || Logger::ApacheFormatter + logger_klass.new(logger, level) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/logger/logstash_formatter.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/logger/logstash_formatter.rb new file mode 100644 index 00000000..dd129ec9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/logger/logstash_formatter.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module HTTParty + module Logger + class LogstashFormatter #:nodoc: + TAG_NAME = HTTParty.name + + attr_accessor :level, :logger + + def initialize(logger, level) + @logger = logger + @level = level.to_sym + end + + def format(request, response) + @request = request + @response = response + + logger.public_send level, logstash_message + end + + private + + attr_reader :request, :response + + def logstash_message + require 'json' + { + '@timestamp' => current_time, + '@version' => 1, + 'content_length' => content_length || '-', + 'http_method' => http_method, + 'message' => message, + 'path' => path, + 'response_code' => response.code, + 'severity' => level, + 'tags' => [TAG_NAME], + }.to_json + end + + def message + "[#{TAG_NAME}] #{response.code} \"#{http_method} #{path}\" #{content_length || '-'} " + end + + def current_time + Time.now.strftime('%Y-%m-%d %H:%M:%S %z') + end + + def http_method + @http_method ||= request.http_method.name.split('::').last.upcase + end + + def path + @path ||= request.path.to_s + end + + def content_length + @content_length ||= response.respond_to?(:headers) ? response.headers['Content-Length'] : response['Content-Length'] + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/module_inheritable_attributes.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/module_inheritable_attributes.rb new file mode 100644 index 00000000..eb841fae --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/module_inheritable_attributes.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module HTTParty + module ModuleInheritableAttributes #:nodoc: + def self.included(base) + base.extend(ClassMethods) + end + + # borrowed from Rails 3.2 ActiveSupport + def self.hash_deep_dup(hash) + duplicate = hash.dup + + duplicate.each_pair do |key, value| + if value.is_a?(Hash) + duplicate[key] = hash_deep_dup(value) + elsif value.is_a?(Proc) + duplicate[key] = value.dup + else + duplicate[key] = value + end + end + + duplicate + end + + module ClassMethods #:nodoc: + def mattr_inheritable(*args) + @mattr_inheritable_attrs ||= [:mattr_inheritable_attrs] + @mattr_inheritable_attrs += args + + args.each do |arg| + singleton_class.attr_accessor(arg) + end + + @mattr_inheritable_attrs + end + + def inherited(subclass) + super + @mattr_inheritable_attrs.each do |inheritable_attribute| + ivar = :"@#{inheritable_attribute}" + subclass.instance_variable_set(ivar, instance_variable_get(ivar).clone) + + if instance_variable_get(ivar).respond_to?(:merge) + subclass.class_eval <<~RUBY, __FILE__, __LINE__ + 1 + def self.#{inheritable_attribute} + duplicate = ModuleInheritableAttributes.hash_deep_dup(#{ivar}) + #{ivar} = superclass.#{inheritable_attribute}.merge(duplicate) + end + RUBY + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/net_digest_auth.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/net_digest_auth.rb new file mode 100644 index 00000000..a6b6cc21 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/net_digest_auth.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +require 'digest/md5' +require 'net/http' + +module Net + module HTTPHeader + def digest_auth(username, password, response) + authenticator = DigestAuthenticator.new( + username, + password, + @method, + @path, + response + ) + + authenticator.authorization_header.each do |v| + add_field('Authorization', v) + end + + authenticator.cookie_header.each do |v| + add_field('Cookie', v) + end + end + + class DigestAuthenticator + def initialize(username, password, method, path, response_header) + @username = username + @password = password + @method = method + @path = path + @response = parse(response_header) + @cookies = parse_cookies(response_header) + end + + def authorization_header + @cnonce = md5(random) + header = [ + %(Digest username="#{@username}"), + %(realm="#{@response['realm']}"), + %(nonce="#{@response['nonce']}"), + %(uri="#{@path}"), + %(response="#{request_digest}") + ] + + header << %(algorithm="#{@response['algorithm']}") if algorithm_present? + + if qop_present? + header << %(cnonce="#{@cnonce}") + header << %(qop="#{@response['qop']}") + header << 'nc=00000001' + end + + header << %(opaque="#{@response['opaque']}") if opaque_present? + header + end + + def cookie_header + @cookies + end + + private + + def parse(response_header) + header = response_header['www-authenticate'] + + header = header.gsub(/qop=(auth(?:-int)?)/, 'qop="\\1"') + + header =~ /Digest (.*)/ + params = {} + if $1 + non_quoted = $1.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 } + non_quoted.gsub(/(\w+)=([^,]*)/) { params[$1] = $2 } + end + params + end + + def parse_cookies(response_header) + return [] unless response_header['Set-Cookie'] + + cookies = response_header['Set-Cookie'].split('; ') + + cookies.reduce([]) do |ret, cookie| + ret << cookie + ret + end + + cookies + end + + def opaque_present? + @response.key?('opaque') && !@response['opaque'].empty? + end + + def qop_present? + @response.key?('qop') && !@response['qop'].empty? + end + + def random + format '%x', (Time.now.to_i + rand(65535)) + end + + def request_digest + a = [md5(a1), @response['nonce'], md5(a2)] + a.insert(2, '00000001', @cnonce, @response['qop']) if qop_present? + md5(a.join(':')) + end + + def md5(str) + Digest::MD5.hexdigest(str) + end + + def algorithm_present? + @response.key?('algorithm') && !@response['algorithm'].empty? + end + + def use_md5_sess? + algorithm_present? && @response['algorithm'] == 'MD5-sess' + end + + def a1 + a1_user_realm_pwd = [@username, @response['realm'], @password].join(':') + if use_md5_sess? + [ md5(a1_user_realm_pwd), @response['nonce'], @cnonce ].join(':') + else + a1_user_realm_pwd + end + end + + def a2 + [@method, @path].join(':') + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/parser.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/parser.rb new file mode 100644 index 00000000..44451ce4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/parser.rb @@ -0,0 +1,157 @@ +# frozen_string_literal: true + +module HTTParty + # The default parser used by HTTParty, supports xml, json, html, csv and + # plain text. + # + # == Custom Parsers + # + # If you'd like to do your own custom parsing, subclassing HTTParty::Parser + # will make that process much easier. There are a few different ways you can + # utilize HTTParty::Parser as a superclass. + # + # @example Intercept the parsing for all formats + # class SimpleParser < HTTParty::Parser + # def parse + # perform_parsing + # end + # end + # + # @example Add the atom format and parsing method to the default parser + # class AtomParsingIncluded < HTTParty::Parser + # SupportedFormats.merge!( + # {"application/atom+xml" => :atom} + # ) + # + # def atom + # perform_atom_parsing + # end + # end + # + # @example Only support the atom format + # class ParseOnlyAtom < HTTParty::Parser + # SupportedFormats = {"application/atom+xml" => :atom} + # + # def atom + # perform_atom_parsing + # end + # end + # + # @abstract Read the Custom Parsers section for more information. + class Parser + SupportedFormats = { + 'text/xml' => :xml, + 'application/xml' => :xml, + 'application/json' => :json, + 'application/vnd.api+json' => :json, + 'application/hal+json' => :json, + 'text/json' => :json, + 'application/javascript' => :plain, + 'text/javascript' => :plain, + 'text/html' => :html, + 'text/plain' => :plain, + 'text/csv' => :csv, + 'application/csv' => :csv, + 'text/comma-separated-values' => :csv + } + + # The response body of the request + # @return [String] + attr_reader :body + + # The intended parsing format for the request + # @return [Symbol] e.g. :json + attr_reader :format + + # Instantiate the parser and call {#parse}. + # @param [String] body the response body + # @param [Symbol] format the response format + # @return parsed response + def self.call(body, format) + new(body, format).parse + end + + # @return [Hash] the SupportedFormats hash + def self.formats + const_get(:SupportedFormats) + end + + # @param [String] mimetype response MIME type + # @return [Symbol] + # @return [nil] mime type not supported + def self.format_from_mimetype(mimetype) + formats[formats.keys.detect {|k| mimetype.include?(k)}] + end + + # @return [Array] list of supported formats + def self.supported_formats + formats.values.uniq + end + + # @param [Symbol] format e.g. :json, :xml + # @return [Boolean] + def self.supports_format?(format) + supported_formats.include?(format) + end + + def initialize(body, format) + @body = body + @format = format + end + + # @return [Object] the parsed body + # @return [nil] when the response body is nil, an empty string, spaces only or "null" + def parse + return nil if body.nil? + return nil if body == 'null' + return nil if body.valid_encoding? && body.strip.empty? + if body.valid_encoding? && body.encoding == Encoding::UTF_8 + @body = body.gsub(/\A#{UTF8_BOM}/, '') + end + if supports_format? + parse_supported_format + else + body + end + end + + protected + + def xml + require 'multi_xml' + MultiXml.parse(body) + end + + UTF8_BOM = "\xEF\xBB\xBF" + + def json + require 'json' + JSON.parse(body, :quirks_mode => true, :allow_nan => true) + end + + def csv + require 'csv' + CSV.parse(body) + end + + def html + body + end + + def plain + body + end + + def supports_format? + self.class.supports_format?(format) + end + + def parse_supported_format + if respond_to?(format, true) + send(format) + else + raise NotImplementedError, "#{self.class.name} has not implemented a parsing method for the #{format.inspect} format." + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/request.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/request.rb new file mode 100644 index 00000000..36b7d357 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/request.rb @@ -0,0 +1,437 @@ +# frozen_string_literal: true + +require 'erb' + +module HTTParty + class Request #:nodoc: + SupportedHTTPMethods = [ + Net::HTTP::Get, + Net::HTTP::Post, + Net::HTTP::Patch, + Net::HTTP::Put, + Net::HTTP::Delete, + Net::HTTP::Head, + Net::HTTP::Options, + Net::HTTP::Move, + Net::HTTP::Copy, + Net::HTTP::Mkcol, + Net::HTTP::Lock, + Net::HTTP::Unlock, + ] + + SupportedURISchemes = ['http', 'https', 'webcal', nil] + + NON_RAILS_QUERY_STRING_NORMALIZER = proc do |query| + Array(query).sort_by { |a| a[0].to_s }.map do |key, value| + if value.nil? + key.to_s + elsif value.respond_to?(:to_ary) + value.to_ary.map {|v| "#{key}=#{ERB::Util.url_encode(v.to_s)}"} + else + HashConversions.to_params(key => value) + end + end.flatten.join('&') + end + + JSON_API_QUERY_STRING_NORMALIZER = proc do |query| + Array(query).sort_by { |a| a[0].to_s }.map do |key, value| + if value.nil? + key.to_s + elsif value.respond_to?(:to_ary) + values = value.to_ary.map{|v| ERB::Util.url_encode(v.to_s)} + "#{key}=#{values.join(',')}" + else + HashConversions.to_params(key => value) + end + end.flatten.join('&') + end + + def self._load(data) + http_method, path, options, last_response, last_uri, raw_request = Marshal.load(data) + instance = new(http_method, path, options) + instance.last_response = last_response + instance.last_uri = last_uri + instance.instance_variable_set("@raw_request", raw_request) + instance + end + + attr_accessor :http_method, :options, :last_response, :redirect, :last_uri + attr_reader :path + + def initialize(http_method, path, o = {}) + @changed_hosts = false + @credentials_sent = false + + self.http_method = http_method + self.options = { + limit: o.delete(:no_follow) ? 1 : 5, + assume_utf16_is_big_endian: true, + default_params: {}, + follow_redirects: true, + parser: Parser, + uri_adapter: URI, + connection_adapter: ConnectionAdapter + }.merge(o) + self.path = path + set_basic_auth_from_uri + end + + def path=(uri) + uri_adapter = options[:uri_adapter] + + @path = if uri.is_a?(uri_adapter) + uri + elsif String.try_convert(uri) + uri_adapter.parse(uri).normalize + else + raise ArgumentError, + "bad argument (expected #{uri_adapter} object or URI string)" + end + end + + def request_uri(uri) + if uri.respond_to? :request_uri + uri.request_uri + else + uri.path + end + end + + def uri + if redirect && path.relative? && path.path[0] != '/' + last_uri_host = @last_uri.path.gsub(/[^\/]+$/, '') + + path.path = "/#{path.path}" if last_uri_host[-1] != '/' + path.path = "#{last_uri_host}#{path.path}" + end + + if path.relative? && path.host + new_uri = options[:uri_adapter].parse("#{@last_uri.scheme}:#{path}").normalize + elsif path.relative? + new_uri = options[:uri_adapter].parse("#{base_uri}#{path}").normalize + else + new_uri = path.clone + end + + # avoid double query string on redirects [#12] + unless redirect + new_uri.query = query_string(new_uri) + end + + unless SupportedURISchemes.include? new_uri.scheme + raise UnsupportedURIScheme, "'#{new_uri}' Must be HTTP, HTTPS or Generic" + end + + @last_uri = new_uri + end + + def base_uri + if redirect + base_uri = "#{@last_uri.scheme}://#{@last_uri.host}" + base_uri = "#{base_uri}:#{@last_uri.port}" if @last_uri.port != 80 + base_uri + else + options[:base_uri] && HTTParty.normalize_base_uri(options[:base_uri]) + end + end + + def format + options[:format] || (format_from_mimetype(last_response['content-type']) if last_response) + end + + def parser + options[:parser] + end + + def connection_adapter + options[:connection_adapter] + end + + def perform(&block) + validate + setup_raw_request + chunked_body = nil + current_http = http + + begin + self.last_response = current_http.request(@raw_request) do |http_response| + if block + chunks = [] + + http_response.read_body do |fragment| + encoded_fragment = encode_text(fragment, http_response['content-type']) + chunks << encoded_fragment if !options[:stream_body] + block.call ResponseFragment.new(encoded_fragment, http_response, current_http) + end + + chunked_body = chunks.join + end + end + + handle_host_redirection if response_redirects? + result = handle_unauthorized + result ||= handle_response(chunked_body, &block) + result + rescue *COMMON_NETWORK_ERRORS => e + raise options[:foul] ? HTTParty::NetworkError.new("#{e.class}: #{e.message}") : e + end + end + + def handle_unauthorized(&block) + return unless digest_auth? && response_unauthorized? && response_has_digest_auth_challenge? + return if @credentials_sent + @credentials_sent = true + perform(&block) + end + + def raw_body + @raw_request.body + end + + def _dump(_level) + opts = options.dup + opts.delete(:logger) + opts.delete(:parser) if opts[:parser] && opts[:parser].is_a?(Proc) + Marshal.dump([http_method, path, opts, last_response, @last_uri, @raw_request]) + end + + private + + def http + connection_adapter.call(uri, options) + end + + def credentials + (options[:basic_auth] || options[:digest_auth]).to_hash + end + + def username + credentials[:username] + end + + def password + credentials[:password] + end + + def normalize_query(query) + if query_string_normalizer + query_string_normalizer.call(query) + else + HashConversions.to_params(query) + end + end + + def query_string_normalizer + options[:query_string_normalizer] + end + + def setup_raw_request + if options[:headers].respond_to?(:to_hash) + headers_hash = options[:headers].to_hash + else + headers_hash = nil + end + + @raw_request = http_method.new(request_uri(uri), headers_hash) + @raw_request.body_stream = options[:body_stream] if options[:body_stream] + + if options[:body] + body = Body.new( + options[:body], + query_string_normalizer: query_string_normalizer, + force_multipart: options[:multipart] + ) + + if body.multipart? + content_type = "multipart/form-data; boundary=#{body.boundary}" + @raw_request['Content-Type'] = content_type + end + @raw_request.body = body.call + end + + @raw_request.instance_variable_set(:@decode_content, decompress_content?) + + if options[:basic_auth] && send_authorization_header? + @raw_request.basic_auth(username, password) + @credentials_sent = true + end + setup_digest_auth if digest_auth? && response_unauthorized? && response_has_digest_auth_challenge? + end + + def digest_auth? + !!options[:digest_auth] + end + + def decompress_content? + !options[:skip_decompression] + end + + def response_unauthorized? + !!last_response && last_response.code == '401' + end + + def response_has_digest_auth_challenge? + !last_response['www-authenticate'].nil? && last_response['www-authenticate'].length > 0 + end + + def setup_digest_auth + @raw_request.digest_auth(username, password, last_response) + end + + def query_string(uri) + query_string_parts = [] + query_string_parts << uri.query unless uri.query.nil? + + if options[:query].respond_to?(:to_hash) + query_string_parts << normalize_query(options[:default_params].merge(options[:query].to_hash)) + else + query_string_parts << normalize_query(options[:default_params]) unless options[:default_params].empty? + query_string_parts << options[:query] unless options[:query].nil? + end + + query_string_parts.reject!(&:empty?) unless query_string_parts == [''] + query_string_parts.size > 0 ? query_string_parts.join('&') : nil + end + + def assume_utf16_is_big_endian + options[:assume_utf16_is_big_endian] + end + + def handle_response(raw_body, &block) + if response_redirects? + handle_redirection(&block) + else + raw_body ||= last_response.body + + body = decompress(raw_body, last_response['content-encoding']) unless raw_body.nil? + + unless body.nil? + body = encode_text(body, last_response['content-type']) + + if decompress_content? + last_response.delete('content-encoding') + raw_body = body + end + end + + Response.new(self, last_response, lambda { parse_response(body) }, body: raw_body) + end + end + + def handle_redirection(&block) + options[:limit] -= 1 + if options[:logger] + logger = HTTParty::Logger.build(options[:logger], options[:log_level], options[:log_format]) + logger.format(self, last_response) + end + self.path = last_response['location'] + self.redirect = true + if last_response.class == Net::HTTPSeeOther + unless options[:maintain_method_across_redirects] && options[:resend_on_redirect] + self.http_method = Net::HTTP::Get + end + elsif last_response.code != '307' && last_response.code != '308' + unless options[:maintain_method_across_redirects] + self.http_method = Net::HTTP::Get + end + end + if http_method == Net::HTTP::Get + clear_body + end + capture_cookies(last_response) + perform(&block) + end + + def handle_host_redirection + check_duplicate_location_header + redirect_path = options[:uri_adapter].parse(last_response['location']).normalize + return if redirect_path.relative? || path.host == redirect_path.host || uri.host == redirect_path.host + @changed_hosts = true + end + + def check_duplicate_location_header + location = last_response.get_fields('location') + if location.is_a?(Array) && location.count > 1 + raise DuplicateLocationHeader.new(last_response) + end + end + + def send_authorization_header? + !@changed_hosts + end + + def response_redirects? + case last_response + when Net::HTTPNotModified # 304 + false + when Net::HTTPRedirection + options[:follow_redirects] && last_response.key?('location') + end + end + + def parse_response(body) + parser.call(body, format) + end + + # Some Web Application Firewalls reject incoming GET requests that have a body + # if we redirect, and the resulting verb is GET then we will clear the body that + # may be left behind from the initiating request + def clear_body + options[:body] = nil + @raw_request.body = nil + end + + def capture_cookies(response) + return unless response['Set-Cookie'] + cookies_hash = HTTParty::CookieHash.new + cookies_hash.add_cookies(options[:headers].to_hash['Cookie']) if options[:headers] && options[:headers].to_hash['Cookie'] + response.get_fields('Set-Cookie').each { |cookie| cookies_hash.add_cookies(cookie) } + + options[:headers] ||= {} + options[:headers]['Cookie'] = cookies_hash.to_cookie_string + end + + # Uses the HTTP Content-Type header to determine the format of the + # response It compares the MIME type returned to the types stored in the + # SupportedFormats hash + def format_from_mimetype(mimetype) + if mimetype && parser.respond_to?(:format_from_mimetype) + parser.format_from_mimetype(mimetype) + end + end + + def validate + raise HTTParty::RedirectionTooDeep.new(last_response), 'HTTP redirects too deep' if options[:limit].to_i <= 0 + raise ArgumentError, 'only get, post, patch, put, delete, head, and options methods are supported' unless SupportedHTTPMethods.include?(http_method) + raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].respond_to?(:to_hash) + raise ArgumentError, 'only one authentication method, :basic_auth or :digest_auth may be used at a time' if options[:basic_auth] && options[:digest_auth] + raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].respond_to?(:to_hash) + raise ArgumentError, ':digest_auth must be a hash' if options[:digest_auth] && !options[:digest_auth].respond_to?(:to_hash) + raise ArgumentError, ':query must be hash if using HTTP Post' if post? && !options[:query].nil? && !options[:query].respond_to?(:to_hash) + end + + def post? + Net::HTTP::Post == http_method + end + + def set_basic_auth_from_uri + if path.userinfo + username, password = path.userinfo.split(':') + options[:basic_auth] = {username: username, password: password} + @credentials_sent = true + end + end + + def decompress(body, encoding) + Decompressor.new(body, encoding).decompress + end + + def encode_text(text, content_type) + TextEncoder.new( + text, + content_type: content_type, + assume_utf16_is_big_endian: assume_utf16_is_big_endian + ).call + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/request/body.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/request/body.rb new file mode 100644 index 00000000..3b0e6a0f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/request/body.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require_relative 'multipart_boundary' + +module HTTParty + class Request + class Body + NEWLINE = "\r\n" + private_constant :NEWLINE + + def initialize(params, query_string_normalizer: nil, force_multipart: false) + @params = params + @query_string_normalizer = query_string_normalizer + @force_multipart = force_multipart + end + + def call + if params.respond_to?(:to_hash) + multipart? ? generate_multipart : normalize_query(params) + else + params + end + end + + def boundary + @boundary ||= MultipartBoundary.generate + end + + def multipart? + params.respond_to?(:to_hash) && (force_multipart || has_file?(params)) + end + + private + + # https://html.spec.whatwg.org/#multipart-form-data + MULTIPART_FORM_DATA_REPLACEMENT_TABLE = { + '"' => '%22', + "\r" => '%0D', + "\n" => '%0A' + }.freeze + + def generate_multipart + normalized_params = params.flat_map { |key, value| HashConversions.normalize_keys(key, value) } + + multipart = normalized_params.inject(''.dup) do |memo, (key, value)| + memo << "--#{boundary}#{NEWLINE}" + memo << %(Content-Disposition: form-data; name="#{key}") + # value.path is used to support ActionDispatch::Http::UploadedFile + # https://github.com/jnunemaker/httparty/pull/585 + memo << %(; filename="#{file_name(value).gsub(/["\r\n]/, MULTIPART_FORM_DATA_REPLACEMENT_TABLE)}") if file?(value) + memo << NEWLINE + memo << "Content-Type: #{content_type(value)}#{NEWLINE}" if file?(value) + memo << NEWLINE + memo << content_body(value) + memo << NEWLINE + end + + multipart << "--#{boundary}--#{NEWLINE}" + end + + def has_file?(value) + if value.respond_to?(:to_hash) + value.to_hash.any? { |_, v| has_file?(v) } + elsif value.respond_to?(:to_ary) + value.to_ary.any? { |v| has_file?(v) } + else + file?(value) + end + end + + def file?(object) + object.respond_to?(:path) && object.respond_to?(:read) + end + + def normalize_query(query) + if query_string_normalizer + query_string_normalizer.call(query) + else + HashConversions.to_params(query) + end + end + + def content_body(object) + if file?(object) + object = (file = object).read + file.rewind if file.respond_to?(:rewind) + end + + object.to_s + end + + def content_type(object) + return object.content_type if object.respond_to?(:content_type) + require 'mini_mime' + mime = MiniMime.lookup_by_filename(object.path) + mime ? mime.content_type : 'application/octet-stream' + end + + def file_name(object) + object.respond_to?(:original_filename) ? object.original_filename : File.basename(object.path) + end + + attr_reader :params, :query_string_normalizer, :force_multipart + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/request/multipart_boundary.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/request/multipart_boundary.rb new file mode 100644 index 00000000..f9e2ac14 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/request/multipart_boundary.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'securerandom' + +module HTTParty + class Request + class MultipartBoundary + def self.generate + "------------------------#{SecureRandom.urlsafe_base64(12)}" + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/response.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/response.rb new file mode 100644 index 00000000..2a6fa42b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/response.rb @@ -0,0 +1,156 @@ +# frozen_string_literal: true + +module HTTParty + class Response < Object + def self.underscore(string) + string.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z])([A-Z])/, '\1_\2').downcase + end + + def self._load(data) + req, resp, parsed_resp, resp_body = Marshal.load(data) + + new(req, resp, -> { parsed_resp }, body: resp_body) + end + + attr_reader :request, :response, :body, :headers + + def initialize(request, response, parsed_block, options = {}) + @request = request + @response = response + @body = options[:body] || response.body + @parsed_block = parsed_block + @headers = Headers.new(response.to_hash) + + if request.options[:logger] + logger = ::HTTParty::Logger.build( + request.options[:logger], + request.options[:log_level], + request.options[:log_format] + ) + logger.format(request, self) + end + + throw_exception + end + + def parsed_response + @parsed_response ||= @parsed_block.call + end + + def code + response.code.to_i + end + + def http_version + response.http_version + end + + def tap + yield self + self + end + + def inspect + inspect_id = ::Kernel::format '%x', (object_id * 2) + %(#<#{self.class}:0x#{inspect_id} parsed_response=#{parsed_response.inspect}, @response=#{response.inspect}, @headers=#{headers.inspect}>) + end + + CODES_TO_OBJ = ::Net::HTTPResponse::CODE_CLASS_TO_OBJ.merge ::Net::HTTPResponse::CODE_TO_OBJ + + CODES_TO_OBJ.each do |response_code, klass| + name = klass.name.sub('Net::HTTP', '') + name = "#{underscore(name)}?".to_sym + + define_method(name) do + klass === response + end + end + + # Support old multiple_choice? method from pre 2.0.0 era. + if ::RUBY_PLATFORM != 'java' + alias_method :multiple_choice?, :multiple_choices? + end + + # Support old status codes method from pre 2.6.0 era. + if ::RUBY_PLATFORM != 'java' + alias_method :gateway_time_out?, :gateway_timeout? + alias_method :request_entity_too_large?, :payload_too_large? + alias_method :request_time_out?, :request_timeout? + alias_method :request_uri_too_long?, :uri_too_long? + alias_method :requested_range_not_satisfiable?, :range_not_satisfiable? + end + + def nil? + warn_about_nil_deprecation + response.nil? || response.body.nil? || response.body.empty? + end + + def to_s + if !response.nil? && !response.body.nil? && response.body.respond_to?(:to_s) + response.body.to_s + else + inspect + end + end + + def pretty_print(pp) + if !parsed_response.nil? && parsed_response.respond_to?(:pretty_print) + parsed_response.pretty_print(pp) + else + super + end + end + + def display(port=$>) + if !parsed_response.nil? && parsed_response.respond_to?(:display) + parsed_response.display(port) + elsif !response.nil? && !response.body.nil? && response.body.respond_to?(:display) + response.body.display(port) + else + port.write(inspect) + end + end + + def respond_to_missing?(name, *args) + return true if super + parsed_response.respond_to?(name) || response.respond_to?(name) + end + + def _dump(_level) + Marshal.dump([request, response, parsed_response, body]) + end + + protected + + def method_missing(name, *args, &block) + if parsed_response.respond_to?(name) + parsed_response.send(name, *args, &block) + elsif response.respond_to?(name) + response.send(name, *args, &block) + else + super + end + end + + def throw_exception + if @request.options[:raise_on].to_a.detect { |c| code.to_s.match(/#{c.to_s}/) } + ::Kernel.raise ::HTTParty::ResponseError.new(@response), "Code #{code} - #{body}" + end + end + + private + + def warn_about_nil_deprecation + trace_line = caller.reject { |line| line.include?('httparty') }.first + warning = "[DEPRECATION] HTTParty will no longer override `response#nil?`. " \ + "This functionality will be removed in future versions. " \ + "Please, add explicit check `response.body.nil? || response.body.empty?`. " \ + "For more info refer to: https://github.com/jnunemaker/httparty/issues/568\n" \ + "#{trace_line}" + + warn(warning) + end + end +end + +require 'httparty/response/headers' diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/response/headers.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/response/headers.rb new file mode 100644 index 00000000..cc5fc7b5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/response/headers.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'delegate' + +module HTTParty + class Response #:nodoc: + class Headers < ::SimpleDelegator + include ::Net::HTTPHeader + + def initialize(header_values = nil) + @header = {} + if header_values + header_values.each_pair do |k,v| + if v.is_a?(Array) + v.each do |sub_v| + add_field(k, sub_v) + end + else + add_field(k, v) + end + end + end + super(@header) + end + + def ==(other) + if other.is_a?(::Net::HTTPHeader) + @header == other.instance_variable_get(:@header) + elsif other.is_a?(Hash) + @header == other || @header == Headers.new(other).instance_variable_get(:@header) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/response_fragment.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/response_fragment.rb new file mode 100644 index 00000000..1fb15a0f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/response_fragment.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'delegate' + +module HTTParty + # Allow access to http_response and code by delegation on fragment + class ResponseFragment < SimpleDelegator + attr_reader :http_response, :connection + + def code + @http_response.code.to_i + end + + def initialize(fragment, http_response, connection) + @fragment = fragment + @http_response = http_response + @connection = connection + super fragment + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/text_encoder.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/text_encoder.rb new file mode 100644 index 00000000..006893ee --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/text_encoder.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +module HTTParty + class TextEncoder + attr_reader :text, :content_type, :assume_utf16_is_big_endian + + def initialize(text, assume_utf16_is_big_endian: true, content_type: nil) + @text = +text + @content_type = content_type + @assume_utf16_is_big_endian = assume_utf16_is_big_endian + end + + def call + if can_encode? + encoded_text + else + text + end + end + + private + + def can_encode? + ''.respond_to?(:encoding) && charset + end + + def encoded_text + if 'utf-16'.casecmp(charset) == 0 + encode_utf_16 + else + encode_with_ruby_encoding + end + end + + def encode_utf_16 + if text.bytesize >= 2 + if text.getbyte(0) == 0xFF && text.getbyte(1) == 0xFE + return text.force_encoding('UTF-16LE') + elsif text.getbyte(0) == 0xFE && text.getbyte(1) == 0xFF + return text.force_encoding('UTF-16BE') + end + end + + if assume_utf16_is_big_endian # option + text.force_encoding('UTF-16BE') + else + text.force_encoding('UTF-16LE') + end + end + + def encode_with_ruby_encoding + # NOTE: This will raise an argument error if the + # charset does not exist + encoding = Encoding.find(charset) + text.force_encoding(encoding.to_s) + rescue ArgumentError + text + end + + def charset + return nil if content_type.nil? + + if (matchdata = content_type.match(/;\s*charset\s*=\s*([^=,;"\s]+)/i)) + return matchdata.captures.first + end + + if (matchdata = content_type.match(/;\s*charset\s*=\s*"((\\.|[^\\"])+)"/i)) + return matchdata.captures.first.gsub(/\\(.)/, '\1') + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/utils.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/utils.rb new file mode 100644 index 00000000..8393e920 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/utils.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module HTTParty + module Utils + def self.stringify_keys(hash) + return hash.transform_keys(&:to_s) if hash.respond_to?(:transform_keys) + + hash.each_with_object({}) do |(key, value), new_hash| + new_hash[key.to_s] = value + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/version.rb b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/version.rb new file mode 100644 index 00000000..614f5c44 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/lib/httparty/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module HTTParty + VERSION = '0.23.1' +end diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/script/release b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/script/release new file mode 100755 index 00000000..72bd6b49 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/script/release @@ -0,0 +1,42 @@ +#!/bin/sh +#/ Usage: release +#/ +#/ Tag the version in the repo and push the gem. +#/ + +set -e +cd $(dirname "$0")/.. + +[ "$1" = "--help" -o "$1" = "-h" -o "$1" = "help" ] && { + grep '^#/' <"$0"| cut -c4- + exit 0 +} + +gem_name=httparty + +# Build a new gem archive. +rm -rf $gem_name-*.gem +gem build -q $gem_name.gemspec + +# Make sure we're on the main branch. +(git branch | grep -q '* main') || { + echo "Only release from the main branch." + exit 1 +} + +# Figure out what version we're releasing. +tag=v`ls $gem_name-*.gem | sed "s/^$gem_name-\(.*\)\.gem$/\1/"` + +echo "Releasing $tag" + +# Make sure we haven't released this version before. +git fetch -t origin + +(git tag -l | grep -q "$tag") && { + echo "Whoops, there's already a '${tag}' tag." + exit 1 +} + +# Tag it and bag it. +gem push $gem_name-*.gem && git tag "$tag" && + git push origin main && git push origin "$tag" diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/website/css/common.css b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/website/css/common.css new file mode 100644 index 00000000..1467d234 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/website/css/common.css @@ -0,0 +1,47 @@ +@media screen, projection { + /* + Copyright (c) 2007, Yahoo! Inc. All rights reserved. + Code licensed under the BSD License: + http://developer.yahoo.net/yui/license.txt + version: 2.2.0 + */ + body {font:13px arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}table {font-size:inherit;font:100%;}select, input, textarea {font:99% arial,helvetica,clean,sans-serif;}pre, code {font:115% monospace;*font-size:100%;}body * {line-height:1.22em;} + body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}/*ol,ul {list-style:none;}*/caption,th {text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;} + /* end of yahoo reset and fonts */ + + body {color:#333; background:#4b1a1a; line-height:1.3;} + p {margin:0 0 20px;} + a {color:#4b1a1a;} + a:hover {text-decoration:none;} + strong {font-weight:bold;} + em {font-style:italics;} + h1,h2,h3,h4,h5,h6 {font-weight:bold;} + h1 {font-size:197%; margin:30px 0; color:#4b1a1a;} + h2 {font-size:174%; margin:20px 0; color:#b8111a;} + h3 {font-size:152%; margin:10px 0;} + h4 {font-size:129%; margin:10px 0;} + pre {background:#eee; margin:0 0 20px; padding:20px; border:1px solid #ccc; font-size:100%; overflow:auto;} + code {font-size:100%; margin:0; padding:0;} + ul, ol {margin:10px 0 10px 25px;} + ol li {margin:0 0 10px;} + + + + + + div#wrapper {background:#fff; width:560px; margin:0 auto; padding:20px; border:10px solid #bc8c46; border-width:0 10px;} + div#header {position:relative; border-bottom:1px dotted; margin:0 0 10px; padding:0 0 10px;} + div#header p {margin:0; padding:0;} + div#header h1 {margin:0; padding:0;} + ul#nav {position:absolute; top:0; right:0; list-style:none; margin:0; padding:0;} + ul#nav li {display:inline; padding:0 0 0 5px;} + ul#nav li a {} + div#content {} + div#footer {margin:40px 0 0; border-top:1px dotted; padding:10px 0 0;} + + + + + + +} \ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/website/index.html b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/website/index.html new file mode 100644 index 00000000..86916b3c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/httparty-0.23.1/website/index.html @@ -0,0 +1,73 @@ + + + + + HTTParty by John Nunemaker + + + + +
    + + +
    +

    Install

    +
    $ sudo gem install httparty
    + +

    Some Quick Examples

    + +

    The following is a simple example of wrapping Twitter's API for posting updates.

    + +
    class Twitter
    +  include HTTParty
    +  base_uri 'twitter.com'
    +  basic_auth 'username', 'password'
    +end
    +
    +Twitter.post('/statuses/update.json', query: {status: "It's an HTTParty and everyone is invited!"})
    + +

    That is really it! The object returned is a ruby hash that is decoded from Twitter's json response. JSON parsing is used because of the .json extension in the path of the request. You can also explicitly set a format (see the examples).

    + +

    That works and all but what if you don't want to embed your username and password in the class? Below is an example to fix that:

    + +
    class Twitter
    +  include HTTParty
    +  base_uri 'twitter.com'
    +
    +  def initialize(u, p)
    +    @auth = {username: u, password: p}
    +  end
    +
    +  def post(text)
    +    options = { query: {status: text}, basic_auth: @auth }
    +    self.class.post('/statuses/update.json', options)
    +  end
    +end
    +
    +Twitter.new('username', 'password').post("It's an HTTParty and everyone is invited!")
    + +

    More Examples: There are several examples in the gem itself.

    + +

    Support

    +

    Conversations welcome in the google group and bugs/features over at Github.

    + + +
    + + +
    + + + \ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/MIT-LICENSE b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/MIT-LICENSE new file mode 100644 index 00000000..ed8e9ee6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2008 The Ruby I18n team + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/README.md b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/README.md new file mode 100644 index 00000000..7b7a6bca --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/README.md @@ -0,0 +1,127 @@ +# Ruby I18n + +[![Gem Version](https://badge.fury.io/rb/i18n.svg)](https://badge.fury.io/rb/i18n) +[![Build Status](https://github.com/ruby-i18n/i18n/workflows/Ruby/badge.svg)](https://github.com/ruby-i18n/i18n/actions?query=workflow%3ARuby) + +Ruby internationalization and localization (i18n) solution. + +Currently maintained by @radar. + +## Usage + +### Rails + +You will most commonly use this library within a Rails app. + +We support Rails versions from 6.0 and up. + +[See the Rails Guide](https://guides.rubyonrails.org/i18n.html) for an example of its usage. + +### Ruby (without Rails) + +We support Ruby versions from 3.0 and up. + +If you want to use this library without Rails, you can simply add `i18n` to your `Gemfile`: + +```ruby +gem 'i18n' +``` + +Then configure I18n with some translations, and a default locale: + +```ruby +I18n.load_path += Dir[File.expand_path("config/locales") + "/*.yml"] +I18n.default_locale = :en # (note that `en` is already the default!) +``` + +A simple translation file in your project might live at `config/locales/en.yml` and look like: + +```yml +en: + test: "This is a test" +``` + +You can then access this translation by doing: + +```ruby +I18n.t(:test) +``` + +You can switch locales in your project by setting `I18n.locale` to a different value: + +```ruby +I18n.locale = :de +I18n.t(:test) # => "Dies ist ein Test" +``` + +## Features + +* Translation and localization +* Interpolation of values to translations +* Pluralization (CLDR compatible) +* Customizable transliteration to ASCII +* Flexible defaults +* Bulk lookup +* Lambdas as translation data +* Custom key/scope separator +* Custom exception handlers +* Extensible architecture with a swappable backend + +## Pluggable Features + +* Cache +* Pluralization: lambda pluralizers stored as translation data +* Locale fallbacks, RFC4647 compliant (optionally: RFC4646 locale validation) +* [Gettext support](https://github.com/ruby-i18n/i18n/wiki/Gettext) +* Translation metadata + +## Alternative Backend + +* Chain +* ActiveRecord (optionally: ActiveRecord::Missing and ActiveRecord::StoreProcs) +* KeyValue (uses active_support/json and cannot store procs) + +For more information and lots of resources see [the 'Resources' page on the wiki](https://github.com/ruby-i18n/i18n/wiki/Resources). + +## Tests + +You can run tests both with + +* `rake test` or just `rake` +* run any test file directly, e.g. `ruby -Ilib:test test/api/simple_test.rb` + +You can run all tests against all Gemfiles with + +* `ruby test/run_all.rb` + +The structure of the test suite is a bit unusual as it uses modules to reuse +particular tests in different test cases. + +The reason for this is that we need to enforce the I18n API across various +combinations of extensions. E.g. the Simple backend alone needs to support +the same API as any combination of feature and/or optimization modules included +to the Simple backend. We test this by reusing the same API definition (implemented +as test methods) in test cases with different setups. + +You can find the test cases that enforce the API in test/api. And you can find +the API definition test methods in test/api/tests. + +All other test cases (e.g. as defined in test/backend, test/core_ext) etc. +follow the usual test setup and should be easy to grok. + +## More Documentation + +Additional documentation can be found here: https://github.com/ruby-i18n/i18n/wiki + +## Contributors + +* @radar +* @carlosantoniodasilva +* @josevalim +* @knapo +* @tigrish +* [and many more](https://github.com/ruby-i18n/i18n/graphs/contributors) + +## License + +MIT License. See the included MIT-LICENSE file. diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n.rb new file mode 100644 index 00000000..9a6a535d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n.rb @@ -0,0 +1,477 @@ +# frozen_string_literal: true + +require 'concurrent/map' +require 'concurrent/hash' + +require 'i18n/version' +require 'i18n/utils' +require 'i18n/exceptions' +require 'i18n/interpolate/ruby' + +module I18n + autoload :Backend, 'i18n/backend' + autoload :Config, 'i18n/config' + autoload :Gettext, 'i18n/gettext' + autoload :Locale, 'i18n/locale' + autoload :Tests, 'i18n/tests' + autoload :Middleware, 'i18n/middleware' + + RESERVED_KEYS = %i[ + cascade + deep_interpolation + skip_interpolation + default + exception_handler + fallback + fallback_in_progress + fallback_original_locale + format + object + raise + resolve + scope + separator + throw + ] + EMPTY_HASH = {}.freeze + + def self.new_double_nested_cache # :nodoc: + Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new } + end + + # Marks a key as reserved. Reserved keys are used internally, + # and can't also be used for interpolation. If you are using any + # extra keys as I18n options, you should call I18n.reserve_key + # before any I18n.translate (etc) calls are made. + def self.reserve_key(key) + RESERVED_KEYS << key.to_sym + @reserved_keys_pattern = nil + end + + def self.reserved_keys_pattern # :nodoc: + @reserved_keys_pattern ||= /(?E.g., ActionView ships with the translation: + # :date => {:formats => {:short => "%b %d"}}. + # + # Translations can be looked up at any level of this hash using the key argument + # and the scope option. E.g., in this example I18n.t :date + # returns the whole translations hash {:formats => {:short => "%b %d"}}. + # + # Key can be either a single key or a dot-separated key (both Strings and Symbols + # work). E.g., the short format can be looked up using both: + # I18n.t 'date.formats.short' + # I18n.t :'date.formats.short' + # + # Scope can be either a single key, a dot-separated key or an array of keys + # or dot-separated keys. Keys and scopes can be combined freely. So these + # examples will all look up the same short date format: + # I18n.t 'date.formats.short' + # I18n.t 'formats.short', :scope => 'date' + # I18n.t 'short', :scope => 'date.formats' + # I18n.t 'short', :scope => %w(date formats) + # + # *INTERPOLATION* + # + # Translations can contain interpolation variables which will be replaced by + # values passed to #translate as part of the options hash, with the keys matching + # the interpolation variable names. + # + # E.g., with a translation :foo => "foo %{bar}" the option + # value for the key +bar+ will be interpolated into the translation: + # I18n.t :foo, :bar => 'baz' # => 'foo baz' + # + # *PLURALIZATION* + # + # Translation data can contain pluralized translations. Pluralized translations + # are arrays of singular/plural versions of translations like ['Foo', 'Foos']. + # + # Note that I18n::Backend::Simple only supports an algorithm for English + # pluralization rules. Other algorithms can be supported by custom backends. + # + # This returns the singular version of a pluralized translation: + # I18n.t :foo, :count => 1 # => 'Foo' + # + # These both return the plural version of a pluralized translation: + # I18n.t :foo, :count => 0 # => 'Foos' + # I18n.t :foo, :count => 2 # => 'Foos' + # + # The :count option can be used both for pluralization and interpolation. + # E.g., with the translation + # :foo => ['%{count} foo', '%{count} foos'], count will + # be interpolated to the pluralized translation: + # I18n.t :foo, :count => 1 # => '1 foo' + # + # *DEFAULTS* + # + # This returns the translation for :foo or default if no translation was found: + # I18n.t :foo, :default => 'default' + # + # This returns the translation for :foo or the translation for :bar if no + # translation for :foo was found: + # I18n.t :foo, :default => :bar + # + # Returns the translation for :foo or the translation for :bar + # or default if no translations for :foo and :bar were found. + # I18n.t :foo, :default => [:bar, 'default'] + # + # BULK LOOKUP + # + # This returns an array with the translations for :foo and :bar. + # I18n.t [:foo, :bar] + # + # Can be used with dot-separated nested keys: + # I18n.t [:'baz.foo', :'baz.bar'] + # + # Which is the same as using a scope option: + # I18n.t [:foo, :bar], :scope => :baz + # + # *LAMBDAS* + # + # Both translations and defaults can be given as Ruby lambdas. Lambdas will be + # called and passed the key and options. + # + # E.g. assuming the key :salutation resolves to: + # lambda { |key, options| options[:gender] == 'm' ? "Mr. #{options[:name]}" : "Mrs. #{options[:name]}" } + # + # Then I18n.t(:salutation, :gender => 'w', :name => 'Smith') will result in "Mrs. Smith". + # + # Note that the string returned by lambda will go through string interpolation too, + # so the following lambda would give the same result: + # lambda { |key, options| options[:gender] == 'm' ? "Mr. %{name}" : "Mrs. %{name}" } + # + # It is recommended to use/implement lambdas in an "idempotent" way. E.g. when + # a cache layer is put in front of I18n.translate it will generate a cache key + # from the argument values passed to #translate. Therefore your lambdas should + # always return the same translations/values per unique combination of argument + # values. + # + # Ruby 2.7+ keyword arguments warning + # + # This method uses keyword arguments. + # There is a breaking change in ruby that produces warning with ruby 2.7 and won't work as expected with ruby 3.0 + # The "hash" parameter must be passed as keyword argument. + # + # Good: + # I18n.t(:salutation, :gender => 'w', :name => 'Smith') + # I18n.t(:salutation, **{ :gender => 'w', :name => 'Smith' }) + # I18n.t(:salutation, **any_hash) + # + # Bad: + # I18n.t(:salutation, { :gender => 'w', :name => 'Smith' }) + # I18n.t(:salutation, any_hash) + # + def translate(key = nil, throw: false, raise: false, locale: nil, **options) # TODO deprecate :raise + locale ||= config.locale + raise Disabled.new('t') if locale == false + enforce_available_locales!(locale) + + backend = config.backend + + if key.is_a?(Array) + key.map do |k| + translate_key(k, throw, raise, locale, backend, options) + end + else + translate_key(key, throw, raise, locale, backend, options) + end + end + alias :t :translate + + # Wrapper for translate that adds :raise => true. With + # this option, if no translation is found, it will raise I18n::MissingTranslationData + def translate!(key, **options) + translate(key, **options, raise: true) + end + alias :t! :translate! + + # Returns an array of interpolation keys for the given translation key + # + # *Examples* + # + # Suppose we have the following: + # I18n.t 'example.zero' == 'Zero interpolations' + # I18n.t 'example.one' == 'One interpolation %{foo}' + # I18n.t 'example.two' == 'Two interpolations %{foo} %{bar}' + # I18n.t 'example.three' == ['One %{foo}', 'Two %{bar}', 'Three %{baz}'] + # I18n.t 'example.one', locale: :other == 'One interpolation %{baz}' + # + # Then we can expect the following results: + # I18n.interpolation_keys('example.zero') #=> [] + # I18n.interpolation_keys('example.one') #=> ['foo'] + # I18n.interpolation_keys('example.two') #=> ['foo', 'bar'] + # I18n.interpolation_keys('example.three') #=> ['foo', 'bar', 'baz'] + # I18n.interpolation_keys('one', scope: 'example', locale: :other) #=> ['baz'] + # I18n.interpolation_keys('does-not-exist') #=> [] + # I18n.interpolation_keys('example') #=> [] + def interpolation_keys(key, **options) + raise I18n::ArgumentError if !key.is_a?(String) || key.empty? + + return [] unless exists?(key, **options.slice(:locale, :scope)) + + translation = translate(key, **options.slice(:locale, :scope)) + interpolation_keys_from_translation(translation) + .flatten.compact + end + + # Returns true if a translation exists for a given key, otherwise returns false. + def exists?(key, _locale = nil, locale: _locale, **options) + locale ||= config.locale + raise Disabled.new('exists?') if locale == false + raise I18n::ArgumentError if (key.is_a?(String) && key.empty?) || key.nil? + + config.backend.exists?(locale, key, options) + end + + # Transliterates UTF-8 characters to ASCII. By default this method will + # transliterate only Latin strings to an ASCII approximation: + # + # I18n.transliterate("Ærøskøbing") + # # => "AEroskobing" + # + # I18n.transliterate("日本語") + # # => "???" + # + # It's also possible to add support for per-locale transliterations. I18n + # expects transliteration rules to be stored at + # i18n.transliterate.rule. + # + # Transliteration rules can either be a Hash or a Proc. Procs must accept a + # single string argument. Hash rules inherit the default transliteration + # rules, while Procs do not. + # + # *Examples* + # + # Setting a Hash in .yml: + # + # i18n: + # transliterate: + # rule: + # ü: "ue" + # ö: "oe" + # + # Setting a Hash using Ruby: + # + # store_translations(:de, i18n: { + # transliterate: { + # rule: { + # 'ü' => 'ue', + # 'ö' => 'oe' + # } + # } + # }) + # + # Setting a Proc: + # + # translit = lambda {|string| MyTransliterator.transliterate(string) } + # store_translations(:xx, :i18n => {:transliterate => {:rule => translit}) + # + # Transliterating strings: + # + # I18n.locale = :en + # I18n.transliterate("Jürgen") # => "Jurgen" + # I18n.locale = :de + # I18n.transliterate("Jürgen") # => "Juergen" + # I18n.transliterate("Jürgen", :locale => :en) # => "Jurgen" + # I18n.transliterate("Jürgen", :locale => :de) # => "Juergen" + def transliterate(key, throw: false, raise: false, locale: nil, replacement: nil, **options) + locale ||= config.locale + raise Disabled.new('transliterate') if locale == false + enforce_available_locales!(locale) + + config.backend.transliterate(locale, key, replacement) + rescue I18n::ArgumentError => exception + handle_exception((throw && :throw || raise && :raise), exception, locale, key, options) + end + + # Localizes certain objects, such as dates and numbers to local formatting. + def localize(object, locale: nil, format: nil, **options) + locale ||= config.locale + raise Disabled.new('l') if locale == false + enforce_available_locales!(locale) + + format ||= :default + config.backend.localize(locale, object, format, options) + end + alias :l :localize + + # Executes block with given I18n.locale set. + def with_locale(tmp_locale = nil) + if tmp_locale == nil + yield + else + current_locale = self.locale + self.locale = tmp_locale + begin + yield + ensure + self.locale = current_locale + end + end + end + + # Merges the given locale, key and scope into a single array of keys. + # Splits keys that contain dots into multiple keys. Makes sure all + # keys are Symbols. + def normalize_keys(locale, key, scope, separator = nil) + separator ||= I18n.default_separator + + [ + *normalize_key(locale, separator), + *normalize_key(scope, separator), + *normalize_key(key, separator) + ] + end + + # Returns true when the passed locale, which can be either a String or a + # Symbol, is in the list of available locales. Returns false otherwise. + def locale_available?(locale) + I18n.config.available_locales_set.include?(locale) + end + + # Raises an InvalidLocale exception when the passed locale is not available. + def enforce_available_locales!(locale) + if locale != false && config.enforce_available_locales + raise I18n::InvalidLocale.new(locale) if !locale_available?(locale) + end + end + + def available_locales_initialized? + config.available_locales_initialized? + end + + private + + def translate_key(key, throw, raise, locale, backend, options) + result = catch(:exception) do + backend.translate(locale, key, options) + end + + if result.is_a?(MissingTranslation) + handle_exception((throw && :throw || raise && :raise), result, locale, key, options) + else + result + end + end + + # Any exceptions thrown in translate will be sent to the @@exception_handler + # which can be a Symbol, a Proc or any other Object unless they're forced to + # be raised or thrown (MissingTranslation). + # + # If exception_handler is a Symbol then it will simply be sent to I18n as + # a method call. A Proc will simply be called. In any other case the + # method #call will be called on the exception_handler object. + # + # Examples: + # + # I18n.exception_handler = :custom_exception_handler # this is the default + # I18n.custom_exception_handler(exception, locale, key, options) # will be called like this + # + # I18n.exception_handler = lambda { |*args| ... } # a lambda + # I18n.exception_handler.call(exception, locale, key, options) # will be called like this + # + # I18n.exception_handler = I18nExceptionHandler.new # an object + # I18n.exception_handler.call(exception, locale, key, options) # will be called like this + def handle_exception(handling, exception, locale, key, options) + case handling + when :raise + raise exception.respond_to?(:to_exception) ? exception.to_exception : exception + when :throw + throw :exception, exception + else + case handler = options[:exception_handler] || config.exception_handler + when Symbol + send(handler, exception, locale, key, options) + else + handler.call(exception, locale, key, options) + end + end + end + + @@normalized_key_cache = I18n.new_double_nested_cache + + def normalize_key(key, separator) + @@normalized_key_cache[separator][key] ||= + case key + when Array + key.flat_map { |k| normalize_key(k, separator) } + else + keys = key.to_s.split(separator) + keys.delete('') + keys.map! do |k| + case k + when /\A[-+]?([1-9]\d*|0)\z/ # integer + k.to_i + when 'true' + true + when 'false' + false + else + k.to_sym + end + end + keys + end + end + + def interpolation_keys_from_translation(translation) + case translation + when ::String + translation.scan(Regexp.union(I18n.config.interpolation_patterns)) + when ::Array + translation.map { |element| interpolation_keys_from_translation(element) } + else + [] + end + end + end + + extend Base +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend.rb new file mode 100644 index 00000000..863d6187 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module I18n + module Backend + autoload :Base, 'i18n/backend/base' + autoload :Cache, 'i18n/backend/cache' + autoload :CacheFile, 'i18n/backend/cache_file' + autoload :Cascade, 'i18n/backend/cascade' + autoload :Chain, 'i18n/backend/chain' + autoload :Fallbacks, 'i18n/backend/fallbacks' + autoload :Flatten, 'i18n/backend/flatten' + autoload :Gettext, 'i18n/backend/gettext' + autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler' + autoload :KeyValue, 'i18n/backend/key_value' + autoload :LazyLoadable, 'i18n/backend/lazy_loadable' + autoload :Memoize, 'i18n/backend/memoize' + autoload :Metadata, 'i18n/backend/metadata' + autoload :Pluralization, 'i18n/backend/pluralization' + autoload :Simple, 'i18n/backend/simple' + autoload :Transliterator, 'i18n/backend/transliterator' + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/base.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/base.rb new file mode 100644 index 00000000..0c59cd72 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/base.rb @@ -0,0 +1,314 @@ +# frozen_string_literal: true + +require 'yaml' +require 'json' + +module I18n + module Backend + module Base + include I18n::Backend::Transliterator + + # Accepts a list of paths to translation files. Loads translations from + # plain Ruby (*.rb), YAML files (*.yml), or JSON files (*.json). See #load_rb, #load_yml, and #load_json + # for details. + def load_translations(*filenames) + filenames = I18n.load_path if filenames.empty? + filenames.flatten.each do |filename| + loaded_translations = load_file(filename) + yield filename, loaded_translations if block_given? + end + end + + # This method receives a locale, a data hash and options for storing translations. + # Should be implemented + def store_translations(locale, data, options = EMPTY_HASH) + raise NotImplementedError + end + + def translate(locale, key, options = EMPTY_HASH) + raise I18n::ArgumentError if (key.is_a?(String) || key.is_a?(Symbol)) && key.empty? + raise InvalidLocale.new(locale) unless locale + return nil if key.nil? && !options.key?(:default) + + entry = lookup(locale, key, options[:scope], options) unless key.nil? + + if entry.nil? && options.key?(:default) + entry = default(locale, key, options[:default], options) + else + entry = resolve_entry(locale, key, entry, options) + end + + count = options[:count] + + if entry.nil? && (subtrees? || !count) + if (options.key?(:default) && !options[:default].nil?) || !options.key?(:default) + throw(:exception, I18n::MissingTranslation.new(locale, key, options)) + end + end + + entry = entry.dup if entry.is_a?(String) + entry = pluralize(locale, entry, count) if count + + if entry.nil? && !subtrees? + throw(:exception, I18n::MissingTranslation.new(locale, key, options)) + end + + deep_interpolation = options[:deep_interpolation] + skip_interpolation = options[:skip_interpolation] + values = Utils.except(options, *RESERVED_KEYS) unless options.empty? + if !skip_interpolation && values && !values.empty? + entry = if deep_interpolation + deep_interpolate(locale, entry, values) + else + interpolate(locale, entry, values) + end + elsif entry.is_a?(String) && entry =~ I18n.reserved_keys_pattern + raise ReservedInterpolationKey.new($1.to_sym, entry) + end + entry + end + + def exists?(locale, key, options = EMPTY_HASH) + lookup(locale, key, options[:scope]) != nil + end + + # Acts the same as +strftime+, but uses a localized version of the + # format string. Takes a key from the date/time formats translations as + # a format argument (e.g., :short in :'date.formats'). + def localize(locale, object, format = :default, options = EMPTY_HASH) + if object.nil? && options.include?(:default) + return options[:default] + end + raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime) + + if Symbol === format + key = format + type = object.respond_to?(:sec) ? 'time' : 'date' + options = options.merge(:raise => true, :object => object, :locale => locale) + format = I18n.t(:"#{type}.formats.#{key}", **options) + end + + format = translate_localization_format(locale, object, format, options) + object.strftime(format) + end + + # Returns an array of locales for which translations are available + # ignoring the reserved translation meta data key :i18n. + def available_locales + raise NotImplementedError + end + + def reload! + eager_load! if eager_loaded? + end + + def eager_load! + @eager_loaded = true + end + + protected + + def eager_loaded? + @eager_loaded ||= false + end + + # The method which actually looks up for the translation in the store. + def lookup(locale, key, scope = [], options = EMPTY_HASH) + raise NotImplementedError + end + + def subtrees? + true + end + + # Evaluates defaults. + # If given subject is an Array, it walks the array and returns the + # first translation that can be resolved. Otherwise it tries to resolve + # the translation directly. + def default(locale, object, subject, options = EMPTY_HASH) + if options.size == 1 && options.has_key?(:default) + options = {} + else + options = Utils.except(options, :default) + end + + case subject + when Array + subject.each do |item| + result = resolve(locale, object, item, options) + return result unless result.nil? + end and nil + else + resolve(locale, object, subject, options) + end + end + + # Resolves a translation. + # If the given subject is a Symbol, it will be translated with the + # given options. If it is a Proc then it will be evaluated. All other + # subjects will be returned directly. + def resolve(locale, object, subject, options = EMPTY_HASH) + return subject if options[:resolve] == false + result = catch(:exception) do + case subject + when Symbol + I18n.translate( + subject, + **options.merge( + :locale => locale, + :throw => true, + :skip_interpolation => true + ) + ) + when Proc + date_or_time = options.delete(:object) || object + resolve(locale, object, subject.call(date_or_time, **options)) + else + subject + end + end + result unless result.is_a?(MissingTranslation) + end + alias_method :resolve_entry, :resolve + + # Picks a translation from a pluralized mnemonic subkey according to English + # pluralization rules : + # - It will pick the :one subkey if count is equal to 1. + # - It will pick the :other subkey otherwise. + # - It will pick the :zero subkey in the special case where count is + # equal to 0 and there is a :zero subkey present. This behaviour is + # not standard with regards to the CLDR pluralization rules. + # Other backends can implement more flexible or complex pluralization rules. + def pluralize(locale, entry, count) + entry = entry.reject { |k, _v| k == :attributes } if entry.is_a?(Hash) + return entry unless entry.is_a?(Hash) && count + + key = pluralization_key(entry, count) + raise InvalidPluralizationData.new(entry, count, key) unless entry.has_key?(key) + entry[key] + end + + # Interpolates values into a given subject. + # + # if the given subject is a string then: + # method interpolates "file %{file} opened by %%{user}", :file => 'test.txt', :user => 'Mr. X' + # # => "file test.txt opened by %{user}" + # + # if the given subject is an array then: + # each element of the array is recursively interpolated (until it finds a string) + # method interpolates ["yes, %{user}", ["maybe no, %{user}", "no, %{user}"]], :user => "bartuz" + # # => ["yes, bartuz", ["maybe no, bartuz", "no, bartuz"]] + def interpolate(locale, subject, values = EMPTY_HASH) + return subject if values.empty? + + case subject + when ::String then I18n.interpolate(subject, values) + when ::Array then subject.map { |element| interpolate(locale, element, values) } + else + subject + end + end + + # Deep interpolation + # + # deep_interpolate { people: { ann: "Ann is %{ann}", john: "John is %{john}" } }, + # ann: 'good', john: 'big' + # #=> { people: { ann: "Ann is good", john: "John is big" } } + def deep_interpolate(locale, data, values = EMPTY_HASH) + return data if values.empty? + + case data + when ::String + I18n.interpolate(data, values) + when ::Hash + data.each_with_object({}) do |(k, v), result| + result[k] = deep_interpolate(locale, v, values) + end + when ::Array + data.map do |v| + deep_interpolate(locale, v, values) + end + else + data + end + end + + # Loads a single translations file by delegating to #load_rb or + # #load_yml depending on the file extension and directly merges the + # data to the existing translations. Raises I18n::UnknownFileType + # for all other file extensions. + def load_file(filename) + type = File.extname(filename).tr('.', '').downcase + raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}", true) + data, keys_symbolized = send(:"load_#{type}", filename) + unless data.is_a?(Hash) + raise InvalidLocaleData.new(filename, 'expects it to return a hash, but does not') + end + data.each { |locale, d| store_translations(locale, d || {}, skip_symbolize_keys: keys_symbolized) } + + data + end + + # Loads a plain Ruby translations file. eval'ing the file must yield + # a Hash containing translation data with locales as toplevel keys. + def load_rb(filename) + translations = eval(IO.read(filename), binding, filename.to_s) + [translations, false] + end + + # Loads a YAML translations file. The data must have locales as + # toplevel keys. + def load_yml(filename) + begin + if YAML.respond_to?(:unsafe_load_file) # Psych 4.0 way + [YAML.unsafe_load_file(filename, symbolize_names: true, freeze: true), true] + else + [YAML.load_file(filename), false] + end + rescue TypeError, ScriptError, StandardError => e + raise InvalidLocaleData.new(filename, e.inspect) + end + end + alias_method :load_yaml, :load_yml + + # Loads a JSON translations file. The data must have locales as + # toplevel keys. + def load_json(filename) + begin + # Use #load_file as a proxy for a version of JSON where symbolize_names and freeze are supported. + if ::JSON.respond_to?(:load_file) + [::JSON.load_file(filename, symbolize_names: true, freeze: true), true] + else + [::JSON.parse(File.read(filename)), false] + end + rescue TypeError, StandardError => e + raise InvalidLocaleData.new(filename, e.inspect) + end + end + + def translate_localization_format(locale, object, format, options) + format.to_s.gsub(/%(|\^)[aAbBpP]/) do |match| + case match + when '%a' then I18n.t!(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday] + when '%^a' then I18n.t!(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday].upcase + when '%A' then I18n.t!(:"date.day_names", :locale => locale, :format => format)[object.wday] + when '%^A' then I18n.t!(:"date.day_names", :locale => locale, :format => format)[object.wday].upcase + when '%b' then I18n.t!(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon] + when '%^b' then I18n.t!(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon].upcase + when '%B' then I18n.t!(:"date.month_names", :locale => locale, :format => format)[object.mon] + when '%^B' then I18n.t!(:"date.month_names", :locale => locale, :format => format)[object.mon].upcase + when '%p' then I18n.t!(:"time.#{(object.respond_to?(:hour) ? object.hour : 0) < 12 ? :am : :pm}", :locale => locale, :format => format).upcase + when '%P' then I18n.t!(:"time.#{(object.respond_to?(:hour) ? object.hour : 0) < 12 ? :am : :pm}", :locale => locale, :format => format).downcase + end + end + rescue MissingTranslationData => e + e.message + end + + def pluralization_key(entry, count) + key = :zero if count == 0 && entry.has_key?(:zero) + key ||= count == 1 ? :one : :other + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/cache.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/cache.rb new file mode 100644 index 00000000..40c18d65 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/cache.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +# This module allows you to easily cache all responses from the backend - thus +# speeding up the I18n aspects of your application quite a bit. +# +# To enable caching you can simply include the Cache module to the Simple +# backend - or whatever other backend you are using: +# +# I18n::Backend::Simple.send(:include, I18n::Backend::Cache) +# +# You will also need to set a cache store implementation that you want to use: +# +# I18n.cache_store = ActiveSupport::Cache.lookup_store(:memory_store) +# +# You can use any cache implementation you want that provides the same API as +# ActiveSupport::Cache (only the methods #fetch and #write are being used). +# +# The cache_key implementation by default assumes you pass values that return +# a valid key from #hash (see +# https://www.ruby-doc.org/core/classes/Object.html#M000337). However, you can +# configure your own digest method via which responds to #hexdigest (see +# https://ruby-doc.org/stdlib/libdoc/openssl/rdoc/OpenSSL/Digest.html): +# +# I18n.cache_key_digest = OpenSSL::Digest::SHA256.new +# +# If you use a lambda as a default value in your translation like this: +# +# I18n.t(:"date.order", :default => lambda {[:month, :day, :year]}) +# +# Then you will always have a cache miss, because each time this method +# is called the lambda will have a different hash value. If you know +# the result of the lambda is a constant as in the example above, then +# to cache this you can make the lambda a constant, like this: +# +# DEFAULT_DATE_ORDER = lambda {[:month, :day, :year]} +# ... +# I18n.t(:"date.order", :default => DEFAULT_DATE_ORDER) +# +# If the lambda may result in different values for each call then consider +# also using the Memoize backend. +# +module I18n + class << self + @@cache_store = nil + @@cache_namespace = nil + @@cache_key_digest = nil + + def cache_store + @@cache_store + end + + def cache_store=(store) + @@cache_store = store + end + + def cache_namespace + @@cache_namespace + end + + def cache_namespace=(namespace) + @@cache_namespace = namespace + end + + def cache_key_digest + @@cache_key_digest + end + + def cache_key_digest=(key_digest) + @@cache_key_digest = key_digest + end + + def perform_caching? + !cache_store.nil? + end + end + + module Backend + # TODO Should the cache be cleared if new translations are stored? + module Cache + def translate(locale, key, options = EMPTY_HASH) + I18n.perform_caching? ? fetch(cache_key(locale, key, options)) { super } : super + end + + protected + + def fetch(cache_key, &block) + result = _fetch(cache_key, &block) + throw(:exception, result) if result.is_a?(MissingTranslation) + result = result.dup if result.frozen? rescue result + result + end + + def _fetch(cache_key, &block) + result = I18n.cache_store.read(cache_key) + return result unless result.nil? + result = catch(:exception, &block) + I18n.cache_store.write(cache_key, result) unless result.is_a?(Proc) + result + end + + def cache_key(locale, key, options) + # This assumes that only simple, native Ruby values are passed to I18n.translate. + "i18n/#{I18n.cache_namespace}/#{locale}/#{digest_item(key)}/#{digest_item(options)}" + end + + private + + def digest_item(key) + I18n.cache_key_digest ? I18n.cache_key_digest.hexdigest(key.to_s) : key.to_s.hash + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/cache_file.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/cache_file.rb new file mode 100644 index 00000000..0c5e1922 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/cache_file.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'openssl' + +module I18n + module Backend + # Overwrites the Base load_file method to cache loaded file contents. + module CacheFile + # Optionally provide path_roots array to normalize filename paths, + # to make the cached i18n data portable across environments. + attr_accessor :path_roots + + protected + + # Track loaded translation files in the `i18n.load_file` scope, + # and skip loading the file if its contents are still up-to-date. + def load_file(filename) + initialized = !respond_to?(:initialized?) || initialized? + key = I18n::Backend::Flatten.escape_default_separator(normalized_path(filename)) + old_mtime, old_digest = initialized && lookup(:i18n, key, :load_file) + return if (mtime = File.mtime(filename).to_i) == old_mtime || + (digest = OpenSSL::Digest::SHA256.file(filename).hexdigest) == old_digest + super + store_translations(:i18n, load_file: { key => [mtime, digest] }) + end + + # Translate absolute filename to relative path for i18n key. + def normalized_path(file) + return file unless path_roots + path = path_roots.find(&file.method(:start_with?)) || + raise(InvalidLocaleData.new(file, 'outside expected path roots')) + file.sub(path, path_roots.index(path).to_s) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/cascade.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/cascade.rb new file mode 100644 index 00000000..782b07b5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/cascade.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +# The Cascade module adds the ability to do cascading lookups to backends that +# are compatible to the Simple backend. +# +# By cascading lookups we mean that for any key that can not be found the +# Cascade module strips one segment off the scope part of the key and then +# tries to look up the key in that scope. +# +# E.g. when a lookup for the key :"foo.bar.baz" does not yield a result then +# the segment :bar will be stripped off the scope part :"foo.bar" and the new +# scope :foo will be used to look up the key :baz. If that does not succeed +# then the remaining scope segment :foo will be omitted, too, and again the +# key :baz will be looked up (now with no scope). +# +# To enable a cascading lookup one passes the :cascade option: +# +# I18n.t(:'foo.bar.baz', :cascade => true) +# +# This will return the first translation found for :"foo.bar.baz", :"foo.baz" +# or :baz in this order. +# +# The cascading lookup takes precedence over resolving any given defaults. +# I.e. defaults will kick in after the cascading lookups haven't succeeded. +# +# This behavior is useful for libraries like ActiveRecord validations where +# the library wants to give users a bunch of more or less fine-grained options +# of scopes for a particular key. +# +# Thanks to Clemens Kofler for the initial idea and implementation! See +# http://github.com/clemens/i18n-cascading-backend + +module I18n + module Backend + module Cascade + def lookup(locale, key, scope = [], options = EMPTY_HASH) + return super unless cascade = options[:cascade] + + cascade = { :step => 1 } unless cascade.is_a?(Hash) + step = cascade[:step] || 1 + offset = cascade[:offset] || 1 + separator = options[:separator] || I18n.default_separator + skip_root = cascade.has_key?(:skip_root) ? cascade[:skip_root] : true + + scope = I18n.normalize_keys(nil, key, scope, separator) + key = (scope.slice!(-offset, offset) || []).join(separator) + + begin + result = super + return result unless result.nil? + scope = scope.dup + end while (!scope.empty? || !skip_root) && scope.slice!(-step, step) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/chain.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/chain.rb new file mode 100644 index 00000000..e081a91c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/chain.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +module I18n + module Backend + # Backend that chains multiple other backends and checks each of them when + # a translation needs to be looked up. This is useful when you want to use + # standard translations with a Simple backend but store custom application + # translations in a database or other backends. + # + # To use the Chain backend instantiate it and set it to the I18n module. + # You can add chained backends through the initializer or backends + # accessor: + # + # # preserves the existing Simple backend set to I18n.backend + # I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n.backend) + # + # The implementation assumes that all backends added to the Chain implement + # a lookup method with the same API as Simple backend does. + # + # Fallback translations using the :default option are only used by the last backend of a chain. + class Chain + module Implementation + include Base + + attr_accessor :backends + + def initialize(*backends) + self.backends = backends + end + + def initialized? + backends.all? do |backend| + backend.instance_eval do + return false unless initialized? + end + end + true + end + + def reload! + backends.each { |backend| backend.reload! } + end + + def eager_load! + backends.each { |backend| backend.eager_load! } + end + + def store_translations(locale, data, options = EMPTY_HASH) + backends.first.store_translations(locale, data, options) + end + + def available_locales + backends.map { |backend| backend.available_locales }.flatten.uniq + end + + def translate(locale, key, default_options = EMPTY_HASH) + namespace = nil + options = Utils.except(default_options, :default) + + backends.each do |backend| + catch(:exception) do + options = default_options if backend == backends.last + translation = backend.translate(locale, key, options) + if namespace_lookup?(translation, options) + namespace = _deep_merge(translation, namespace || {}) + elsif !translation.nil? || (options.key?(:default) && options[:default].nil?) + return translation + end + end + end + + return namespace if namespace + throw(:exception, I18n::MissingTranslation.new(locale, key, options)) + end + + def exists?(locale, key, options = EMPTY_HASH) + backends.any? do |backend| + backend.exists?(locale, key, options) + end + end + + def localize(locale, object, format = :default, options = EMPTY_HASH) + backends.each do |backend| + catch(:exception) do + result = backend.localize(locale, object, format, options) and return result + end + end + throw(:exception, I18n::MissingTranslation.new(locale, format, options)) + end + + protected + def init_translations + backends.each do |backend| + backend.send(:init_translations) + end + end + + def translations + backends.reverse.each_with_object({}) do |backend, memo| + partial_translations = backend.instance_eval do + init_translations unless initialized? + translations + end + Utils.deep_merge!(memo, partial_translations) { |_, a, b| b || a } + end + end + + def namespace_lookup?(result, options) + result.is_a?(Hash) && !options.has_key?(:count) + end + + private + # This is approximately what gets used in ActiveSupport. + # However since we are not guaranteed to run in an ActiveSupport context + # it is wise to have our own copy. We underscore it + # to not pollute the namespace of the including class. + def _deep_merge(hash, other_hash) + copy = hash.dup + other_hash.each_pair do |k,v| + value_from_other = hash[k] + copy[k] = value_from_other.is_a?(Hash) && v.is_a?(Hash) ? _deep_merge(value_from_other, v) : v + end + copy + end + end + + include Implementation + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/fallbacks.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/fallbacks.rb new file mode 100644 index 00000000..caa4e66e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/fallbacks.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +# I18n locale fallbacks are useful when you want your application to use +# translations from other locales when translations for the current locale are +# missing. E.g. you might want to use :en translations when translations in +# your applications main locale :de are missing. +# +# To enable locale fallbacks you can simply include the Fallbacks module to +# the Simple backend - or whatever other backend you are using: +# +# I18n::Backend::Simple.include(I18n::Backend::Fallbacks) +module I18n + @@fallbacks = nil + + class << self + # Returns the current fallbacks implementation. Defaults to +I18n::Locale::Fallbacks+. + def fallbacks + @@fallbacks ||= I18n::Locale::Fallbacks.new + Thread.current[:i18n_fallbacks] || @@fallbacks + end + + # Sets the current fallbacks implementation. Use this to set a different fallbacks implementation. + def fallbacks=(fallbacks) + @@fallbacks = fallbacks.is_a?(Array) ? I18n::Locale::Fallbacks.new(fallbacks) : fallbacks + Thread.current[:i18n_fallbacks] = @@fallbacks + end + end + + module Backend + module Fallbacks + # Overwrites the Base backend translate method so that it will try each + # locale given by I18n.fallbacks for the given locale. E.g. for the + # locale :"de-DE" it might try the locales :"de-DE", :de and :en + # (depends on the fallbacks implementation) until it finds a result with + # the given options. If it does not find any result for any of the + # locales it will then throw MissingTranslation as usual. + # + # The default option takes precedence over fallback locales only when + # it's a Symbol. When the default contains a String, Proc or Hash + # it is evaluated last after all the fallback locales have been tried. + def translate(locale, key, options = EMPTY_HASH) + return super unless options.fetch(:fallback, true) + return super if options[:fallback_in_progress] + default = extract_non_symbol_default!(options) if options[:default] + + fallback_options = options.merge(:fallback_in_progress => true, fallback_original_locale: locale) + I18n.fallbacks[locale].each do |fallback| + begin + catch(:exception) do + result = super(fallback, key, fallback_options) + unless result.nil? + on_fallback(locale, fallback, key, options) if locale.to_s != fallback.to_s + return result + end + end + rescue I18n::InvalidLocale + # we do nothing when the locale is invalid, as this is a fallback anyways. + end + end + + return if options.key?(:default) && options[:default].nil? + + return super(locale, nil, options.merge(:default => default)) if default + throw(:exception, I18n::MissingTranslation.new(locale, key, options)) + end + + def resolve_entry(locale, object, subject, options = EMPTY_HASH) + return subject if options[:resolve] == false + result = catch(:exception) do + options.delete(:fallback_in_progress) if options.key?(:fallback_in_progress) + + case subject + when Symbol + I18n.translate(subject, **options.merge( + :locale => options[:fallback_original_locale], + :throw => true, + :skip_interpolation => true + )) + when Proc + date_or_time = options.delete(:object) || object + resolve_entry(options[:fallback_original_locale], object, subject.call(date_or_time, **options)) + else + subject + end + end + result unless result.is_a?(MissingTranslation) + end + + def extract_non_symbol_default!(options) + defaults = [options[:default]].flatten + first_non_symbol_default = defaults.detect{|default| !default.is_a?(Symbol)} + if first_non_symbol_default + options[:default] = defaults[0, defaults.index(first_non_symbol_default)] + end + return first_non_symbol_default + end + + def exists?(locale, key, options = EMPTY_HASH) + return super unless options.fetch(:fallback, true) + I18n.fallbacks[locale].each do |fallback| + begin + return true if super(fallback, key, options) + rescue I18n::InvalidLocale + # we do nothing when the locale is invalid, as this is a fallback anyways. + end + end + + false + end + + private + + # Overwrite on_fallback to add specified logic when the fallback succeeds. + def on_fallback(_original_locale, _fallback_locale, _key, _options) + nil + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/flatten.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/flatten.rb new file mode 100644 index 00000000..e9bd9d53 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/flatten.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +module I18n + module Backend + # This module contains several helpers to assist flattening translations. + # You may want to flatten translations for: + # + # 1) speed up lookups, as in the Memoize backend; + # 2) In case you want to store translations in a data store, as in ActiveRecord backend; + # + # You can check both backends above for some examples. + # This module also keeps all links in a hash so they can be properly resolved when flattened. + module Flatten + SEPARATOR_ESCAPE_CHAR = "\001" + FLATTEN_SEPARATOR = "." + + # normalize_keys the flatten way. This method is significantly faster + # and creates way less objects than the one at I18n.normalize_keys. + # It also handles escaping the translation keys. + def self.normalize_flat_keys(locale, key, scope, separator) + keys = [scope, key] + keys.flatten! + keys.compact! + + separator ||= I18n.default_separator + + if separator != FLATTEN_SEPARATOR + from_str = "#{FLATTEN_SEPARATOR}#{separator}" + to_str = "#{SEPARATOR_ESCAPE_CHAR}#{FLATTEN_SEPARATOR}" + + keys.map! { |k| k.to_s.tr from_str, to_str } + end + + keys.join(".") + end + + # Receives a string and escape the default separator. + def self.escape_default_separator(key) #:nodoc: + key.to_s.tr(FLATTEN_SEPARATOR, SEPARATOR_ESCAPE_CHAR) + end + + # Shortcut to I18n::Backend::Flatten.normalize_flat_keys + # and then resolve_links. + def normalize_flat_keys(locale, key, scope, separator) + key = I18n::Backend::Flatten.normalize_flat_keys(locale, key, scope, separator) + resolve_link(locale, key) + end + + # Store flattened links. + def links + @links ||= I18n.new_double_nested_cache + end + + # Flatten keys for nested Hashes by chaining up keys: + # + # >> { "a" => { "b" => { "c" => "d", "e" => "f" }, "g" => "h" }, "i" => "j"}.wind + # => { "a.b.c" => "d", "a.b.e" => "f", "a.g" => "h", "i" => "j" } + # + def flatten_keys(hash, escape, prev_key=nil, &block) + hash.each_pair do |key, value| + key = escape_default_separator(key) if escape + curr_key = [prev_key, key].compact.join(FLATTEN_SEPARATOR).to_sym + yield curr_key, value + flatten_keys(value, escape, curr_key, &block) if value.is_a?(Hash) + end + end + + # Receives a hash of translations (where the key is a locale and + # the value is another hash) and return a hash with all + # translations flattened. + # + # Nested hashes are included in the flattened hash just if subtree + # is true and Symbols are automatically stored as links. + def flatten_translations(locale, data, escape, subtree) + hash = {} + flatten_keys(data, escape) do |key, value| + if value.is_a?(Hash) + hash[key] = value if subtree + else + store_link(locale, key, value) if value.is_a?(Symbol) + hash[key] = value + end + end + hash + end + + protected + + def store_link(locale, key, link) + links[locale.to_sym][key.to_s] = link.to_s + end + + def resolve_link(locale, key) + key, locale = key.to_s, locale.to_sym + links = self.links[locale] + + if links.key?(key) + links[key] + elsif link = find_link(locale, key) + store_link(locale, key, key.gsub(*link)) + else + key + end + end + + def find_link(locale, key) #:nodoc: + links[locale].each_pair do |from, to| + return [from, to] if key[0, from.length] == from + end && nil + end + + def escape_default_separator(key) #:nodoc: + I18n::Backend::Flatten.escape_default_separator(key) + end + + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/gettext.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/gettext.rb new file mode 100644 index 00000000..07696463 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/gettext.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require 'i18n/gettext' +require 'i18n/gettext/po_parser' + +module I18n + module Backend + # Experimental support for using Gettext po files to store translations. + # + # To use this you can simply include the module to the Simple backend - or + # whatever other backend you are using. + # + # I18n::Backend::Simple.include(I18n::Backend::Gettext) + # + # Now you should be able to include your Gettext translation (*.po) files to + # the +I18n.load_path+ so they're loaded to the backend and you can use them as + # usual: + # + # I18n.load_path += Dir["path/to/locales/*.po"] + # + # Following the Gettext convention this implementation expects that your + # translation files are named by their locales. E.g. the file en.po would + # contain the translations for the English locale. + # + # To translate text you must use one of the translate methods provided by + # I18n::Gettext::Helpers. + # + # include I18n::Gettext::Helpers + # puts _("some string") + # + # Without it strings containing periods (".") will not be translated. + + module Gettext + class PoData < Hash + def set_comment(msgid_or_sym, comment) + # ignore + end + end + + protected + def load_po(filename) + locale = ::File.basename(filename, '.po').to_sym + data = normalize(locale, parse(filename)) + [{ locale => data }, false] + end + + def parse(filename) + GetText::PoParser.new.parse(::File.read(filename), PoData.new) + end + + def normalize(locale, data) + data.inject({}) do |result, (key, value)| + unless key.nil? || key.empty? + key = key.gsub(I18n::Gettext::CONTEXT_SEPARATOR, '|') + key, value = normalize_pluralization(locale, key, value) if key.index("\000") + + parts = key.split('|').reverse + normalized = parts.inject({}) do |_normalized, part| + { part => _normalized.empty? ? value : _normalized } + end + + Utils.deep_merge!(result, normalized) + end + result + end + end + + def normalize_pluralization(locale, key, value) + # FIXME po_parser includes \000 chars that can not be turned into Symbols + key = key.gsub("\000", I18n::Gettext::PLURAL_SEPARATOR).split(I18n::Gettext::PLURAL_SEPARATOR).first + + keys = I18n::Gettext.plural_keys(locale) + values = value.split("\000") + raise "invalid number of plurals: #{values.size}, keys: #{keys.inspect} on #{locale} locale for msgid #{key.inspect} with values #{values.inspect}" if values.size != keys.size + + result = {} + values.each_with_index { |_value, ix| result[keys[ix]] = _value } + [key, result] + end + + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/interpolation_compiler.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/interpolation_compiler.rb new file mode 100644 index 00000000..e37b6799 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/interpolation_compiler.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +# The InterpolationCompiler module contains optimizations that can tremendously +# speed up the interpolation process on the Simple backend. +# +# It works by defining a pre-compiled method on stored translation Strings that +# already bring all the knowledge about contained interpolation variables etc. +# so that the actual recurring interpolation will be very fast. +# +# To enable pre-compiled interpolations you can simply include the +# InterpolationCompiler module to the Simple backend: +# +# I18n::Backend::Simple.include(I18n::Backend::InterpolationCompiler) +# +# Note that InterpolationCompiler does not yield meaningful results and consequently +# should not be used with Ruby 1.9 (YARV) but improves performance everywhere else +# (jRuby, Rubinius). +module I18n + module Backend + module InterpolationCompiler + module Compiler + extend self + + TOKENIZER = /(%%?\{[^}]+\})/ + + def compile_if_an_interpolation(string) + if interpolated_str?(string) + string.instance_eval <<-RUBY_EVAL, __FILE__, __LINE__ + def i18n_interpolate(v = {}) + "#{compiled_interpolation_body(string)}" + end + RUBY_EVAL + end + + string + end + + def interpolated_str?(str) + str.kind_of?(::String) && str =~ TOKENIZER + end + + protected + # tokenize("foo %{bar} baz %%{buz}") # => ["foo ", "%{bar}", " baz ", "%%{buz}"] + def tokenize(str) + str.split(TOKENIZER) + end + + def compiled_interpolation_body(str) + tokenize(str).map do |token| + token.match(TOKENIZER) ? handle_interpolation_token(token) : escape_plain_str(token) + end.join + end + + def handle_interpolation_token(token) + token.start_with?('%%') ? token[1..] : compile_interpolation_token(token[2..-2]) + end + + def compile_interpolation_token(key) + "\#{#{interpolate_or_raise_missing(key)}}" + end + + def interpolate_or_raise_missing(key) + escaped_key = escape_key_sym(key) + RESERVED_KEYS.include?(key) ? reserved_key(escaped_key) : interpolate_key(escaped_key) + end + + def interpolate_key(key) + [direct_key(key), nil_key(key), missing_key(key)].join('||') + end + + def direct_key(key) + "((t = v[#{key}]) && t.respond_to?(:call) ? t.call : t)" + end + + def nil_key(key) + "(v.has_key?(#{key}) && '')" + end + + def missing_key(key) + "I18n.config.missing_interpolation_argument_handler.call(#{key}, v, self)" + end + + def reserved_key(key) + "raise(ReservedInterpolationKey.new(#{key}, self))" + end + + def escape_plain_str(str) + str.gsub(/"|\\|#/) {|x| "\\#{x}"} + end + + def escape_key_sym(key) + # rely on Ruby to do all the hard work :) + key.to_sym.inspect + end + end + + def interpolate(locale, string, values) + if string.respond_to?(:i18n_interpolate) + string.i18n_interpolate(values) + elsif values + super + else + string + end + end + + def store_translations(locale, data, options = EMPTY_HASH) + compile_all_strings_in(data) + super + end + + protected + def compile_all_strings_in(data) + data.each_value do |value| + Compiler.compile_if_an_interpolation(value) + compile_all_strings_in(value) if value.kind_of?(Hash) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/key_value.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/key_value.rb new file mode 100644 index 00000000..b937e253 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/key_value.rb @@ -0,0 +1,204 @@ +# frozen_string_literal: true + +require 'i18n/backend/base' + +module I18n + + begin + require 'oj' + class JSON + class << self + def encode(value) + Oj::Rails.encode(value) + end + def decode(value) + Oj.load(value) + end + end + end + rescue LoadError + require 'active_support/json' + JSON = ActiveSupport::JSON + end + + module Backend + # This is a basic backend for key value stores. It receives on + # initialization the store, which should respond to three methods: + # + # * store#[](key) - Used to get a value + # * store#[]=(key, value) - Used to set a value + # * store#keys - Used to get all keys + # + # Since these stores only supports string, all values are converted + # to JSON before being stored, allowing it to also store booleans, + # hashes and arrays. However, this store does not support Procs. + # + # As the ActiveRecord backend, Symbols are just supported when loading + # translations from the filesystem or through explicit store translations. + # + # Also, avoid calling I18n.available_locales since it's a somehow + # expensive operation in most stores. + # + # == Example + # + # To setup I18n to use TokyoCabinet in memory is quite straightforward: + # + # require 'rufus/tokyo/cabinet' # gem install rufus-tokyo + # I18n.backend = I18n::Backend::KeyValue.new(Rufus::Tokyo::Cabinet.new('*')) + # + # == Performance + # + # You may make this backend even faster by including the Memoize module. + # However, notice that you should properly clear the cache if you change + # values directly in the key-store. + # + # == Subtrees + # + # In most backends, you are allowed to retrieve part of a translation tree: + # + # I18n.backend.store_translations :en, :foo => { :bar => :baz } + # I18n.t "foo" #=> { :bar => :baz } + # + # This backend supports this feature by default, but it slows down the storage + # of new data considerably and makes hard to delete entries. That said, you are + # allowed to disable the storage of subtrees on initialization: + # + # I18n::Backend::KeyValue.new(@store, false) + # + # This is useful if you are using a KeyValue backend chained to a Simple backend. + class KeyValue + module Implementation + attr_accessor :store + + include Base, Flatten + + def initialize(store, subtrees=true) + @store, @subtrees = store, subtrees + end + + def initialized? + !@store.nil? + end + + def store_translations(locale, data, options = EMPTY_HASH) + escape = options.fetch(:escape, true) + flatten_translations(locale, data, escape, @subtrees).each do |key, value| + key = "#{locale}.#{key}" + + case value + when Hash + if @subtrees && (old_value = @store[key]) + old_value = JSON.decode(old_value) + value = Utils.deep_merge!(Utils.deep_symbolize_keys(old_value), value) if old_value.is_a?(Hash) + end + when Proc + raise "Key-value stores cannot handle procs" + end + + @store[key] = JSON.encode(value) unless value.is_a?(Symbol) + end + end + + def available_locales + locales = @store.keys.map { |k| k =~ /\./; $` } + locales.uniq! + locales.compact! + locales.map! { |k| k.to_sym } + locales + end + + protected + + # Queries the translations from the key-value store and converts + # them into a hash such as the one returned from loading the + # haml files + def translations + @translations = Utils.deep_symbolize_keys(@store.keys.clone.map do |main_key| + main_value = JSON.decode(@store[main_key]) + main_key.to_s.split(".").reverse.inject(main_value) do |value, key| + {key.to_sym => value} + end + end.inject{|hash, elem| Utils.deep_merge!(hash, elem)}) + end + + def init_translations + # NO OP + # This call made also inside Simple Backend and accessed by + # other plugins like I18n-js and babilu and + # to use it along with the Chain backend we need to + # provide a uniform API even for protected methods :S + end + + def subtrees? + @subtrees + end + + def lookup(locale, key, scope = [], options = EMPTY_HASH) + key = normalize_flat_keys(locale, key, scope, options[:separator]) + value = @store["#{locale}.#{key}"] + value = JSON.decode(value) if value + + if value.is_a?(Hash) + Utils.deep_symbolize_keys(value) + elsif !value.nil? + value + elsif !@subtrees + SubtreeProxy.new("#{locale}.#{key}", @store) + end + end + + def pluralize(locale, entry, count) + if subtrees? + super + else + return entry unless entry.is_a?(Hash) + key = pluralization_key(entry, count) + entry[key] + end + end + end + + class SubtreeProxy + def initialize(master_key, store) + @master_key = master_key + @store = store + @subtree = nil + end + + def has_key?(key) + @subtree && @subtree.has_key?(key) || self[key] + end + + def [](key) + unless @subtree && value = @subtree[key] + value = @store["#{@master_key}.#{key}"] + if value + value = JSON.decode(value) + (@subtree ||= {})[key] = value + end + end + value + end + + def is_a?(klass) + Hash == klass || super + end + alias :kind_of? :is_a? + + def instance_of?(klass) + Hash == klass || super + end + + def nil? + @subtree.nil? + end + + def inspect + @subtree.inspect + end + end + + include Implementation + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/lazy_loadable.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/lazy_loadable.rb new file mode 100644 index 00000000..575b32bf --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/lazy_loadable.rb @@ -0,0 +1,184 @@ +# frozen_string_literal: true + +module I18n + module Backend + # Backend that lazy loads translations based on the current locale. This + # implementation avoids loading all translations up front. Instead, it only + # loads the translations that belong to the current locale. This offers a + # performance incentive in local development and test environments for + # applications with many translations for many different locales. It's + # particularly useful when the application only refers to a single locales' + # translations at a time (ex. A Rails workload). The implementation + # identifies which translation files from the load path belong to the + # current locale by pattern matching against their path name. + # + # Specifically, a translation file is considered to belong to a locale if: + # a) the filename is in the I18n load path + # b) the filename ends in a supported extension (ie. .yml, .json, .po, .rb) + # c) the filename starts with the locale identifier + # d) the locale identifier and optional proceeding text is separated by an underscore, ie. "_". + # + # Examples: + # Valid files that will be selected by this backend: + # + # "files/locales/en_translation.yml" (Selected for locale "en") + # "files/locales/fr.po" (Selected for locale "fr") + # + # Invalid files that won't be selected by this backend: + # + # "files/locales/translation-file" + # "files/locales/en-translation.unsupported" + # "files/locales/french/translation.yml" + # "files/locales/fr/translation.yml" + # + # The implementation uses this assumption to defer the loading of + # translation files until the current locale actually requires them. + # + # The backend has two working modes: lazy_load and eager_load. + # + # Note: This backend should only be enabled in test environments! + # When the mode is set to false, the backend behaves exactly like the + # Simple backend, with an additional check that the paths being loaded + # abide by the format. If paths can't be matched to the format, an error is raised. + # + # You can configure lazy loaded backends through the initializer or backends + # accessor: + # + # # In test environments + # + # I18n.backend = I18n::Backend::LazyLoadable.new(lazy_load: true) + # + # # In other environments, such as production and CI + # + # I18n.backend = I18n::Backend::LazyLoadable.new(lazy_load: false) # default + # + class LocaleExtractor + class << self + def locale_from_path(path) + name = File.basename(path, ".*") + locale = name.split("_").first + locale.to_sym unless locale.nil? + end + end + end + + class LazyLoadable < Simple + def initialize(lazy_load: false) + @lazy_load = lazy_load + end + + # Returns whether the current locale is initialized. + def initialized? + if lazy_load? + initialized_locales[I18n.locale] + else + super + end + end + + # Clean up translations and uninitialize all locales. + def reload! + if lazy_load? + @initialized_locales = nil + @translations = nil + else + super + end + end + + # Eager loading is not supported in the lazy context. + def eager_load! + if lazy_load? + raise UnsupportedMethod.new(__method__, self.class, "Cannot eager load translations because backend was configured with lazy_load: true.") + else + super + end + end + + # Parse the load path and extract all locales. + def available_locales + if lazy_load? + I18n.load_path.map { |path| LocaleExtractor.locale_from_path(path) }.uniq + else + super + end + end + + def lookup(locale, key, scope = [], options = EMPTY_HASH) + if lazy_load? + I18n.with_locale(locale) do + super + end + else + super + end + end + + protected + + + # Load translations from files that belong to the current locale. + def init_translations + file_errors = if lazy_load? + initialized_locales[I18n.locale] = true + load_translations_and_collect_file_errors(filenames_for_current_locale) + else + @initialized = true + load_translations_and_collect_file_errors(I18n.load_path) + end + + raise InvalidFilenames.new(file_errors) unless file_errors.empty? + end + + def initialized_locales + @initialized_locales ||= Hash.new(false) + end + + private + + def lazy_load? + @lazy_load + end + + class FilenameIncorrect < StandardError + def initialize(file, expected_locale, unexpected_locales) + super "#{file} can only load translations for \"#{expected_locale}\". Found translations for: #{unexpected_locales}." + end + end + + # Loads each file supplied and asserts that the file only loads + # translations as expected by the name. The method returns a list of + # errors corresponding to offending files. + def load_translations_and_collect_file_errors(files) + errors = [] + + load_translations(files) do |file, loaded_translations| + assert_file_named_correctly!(file, loaded_translations) + rescue FilenameIncorrect => e + errors << e + end + + errors + end + + # Select all files from I18n load path that belong to current locale. + # These files must start with the locale identifier (ie. "en", "pt-BR"), + # followed by an "_" demarcation to separate proceeding text. + def filenames_for_current_locale + I18n.load_path.flatten.select do |path| + LocaleExtractor.locale_from_path(path) == I18n.locale + end + end + + # Checks if a filename is named in correspondence to the translations it loaded. + # The locale extracted from the path must be the single locale loaded in the translations. + def assert_file_named_correctly!(file, translations) + loaded_locales = translations.keys.map(&:to_sym) + expected_locale = LocaleExtractor.locale_from_path(file) + unexpected_locales = loaded_locales.reject { |locale| locale == expected_locale } + + raise FilenameIncorrect.new(file, expected_locale, unexpected_locales) unless unexpected_locales.empty? + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/memoize.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/memoize.rb new file mode 100644 index 00000000..3293d2b4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/memoize.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +# Memoize module simply memoizes the values returned by lookup using +# a flat hash and can tremendously speed up the lookup process in a backend. +# +# To enable it you can simply include the Memoize module to your backend: +# +# I18n::Backend::Simple.include(I18n::Backend::Memoize) +# +# Notice that it's the responsibility of the backend to define whenever the +# cache should be cleaned. +module I18n + module Backend + module Memoize + def available_locales + @memoized_locales ||= super + end + + def store_translations(locale, data, options = EMPTY_HASH) + reset_memoizations!(locale) + super + end + + def reload! + reset_memoizations! + super + end + + def eager_load! + memoized_lookup + available_locales + super + end + + protected + + def lookup(locale, key, scope = nil, options = EMPTY_HASH) + flat_key = I18n::Backend::Flatten.normalize_flat_keys(locale, + key, scope, options[:separator]).to_sym + flat_hash = memoized_lookup[locale.to_sym] + flat_hash.key?(flat_key) ? flat_hash[flat_key] : (flat_hash[flat_key] = super) + end + + def memoized_lookup + @memoized_lookup ||= I18n.new_double_nested_cache + end + + def reset_memoizations!(locale=nil) + @memoized_locales = nil + (locale ? memoized_lookup[locale.to_sym] : memoized_lookup).clear + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/metadata.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/metadata.rb new file mode 100644 index 00000000..51ea7a2a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/metadata.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +# I18n translation metadata is useful when you want to access information +# about how a translation was looked up, pluralized or interpolated in +# your application. +# +# msg = I18n.t(:message, :default => 'Hi!', :scope => :foo) +# msg.translation_metadata +# # => { :key => :message, :scope => :foo, :default => 'Hi!' } +# +# If a :count option was passed to #translate it will be set to the metadata. +# Likewise, if any interpolation variables were passed they will also be set. +# +# To enable translation metadata you can simply include the Metadata module +# into the Simple backend class - or whatever other backend you are using: +# +# I18n::Backend::Simple.include(I18n::Backend::Metadata) +# +module I18n + module Backend + module Metadata + class << self + def included(base) + Object.class_eval do + def translation_metadata + unless self.frozen? + @translation_metadata ||= {} + else + {} + end + end + + def translation_metadata=(translation_metadata) + @translation_metadata = translation_metadata unless self.frozen? + end + end unless Object.method_defined?(:translation_metadata) + end + end + + def translate(locale, key, options = EMPTY_HASH) + metadata = { + :locale => locale, + :key => key, + :scope => options[:scope], + :default => options[:default], + :separator => options[:separator], + :values => options.reject { |name, _value| RESERVED_KEYS.include?(name) } + } + with_metadata(metadata) { super } + end + + def interpolate(locale, entry, values = EMPTY_HASH) + metadata = entry.translation_metadata.merge(:original => entry) + with_metadata(metadata) { super } + end + + def pluralize(locale, entry, count) + with_metadata(:count => count) { super } + end + + protected + + def with_metadata(metadata, &block) + result = yield + result.translation_metadata = result.translation_metadata.merge(metadata) if result + result + end + + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/pluralization.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/pluralization.rb new file mode 100644 index 00000000..1d3277b8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/pluralization.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +# I18n Pluralization are useful when you want your application to +# customize pluralization rules. +# +# To enable locale specific pluralizations you can simply include the +# Pluralization module to the Simple backend - or whatever other backend you +# are using. +# +# I18n::Backend::Simple.include(I18n::Backend::Pluralization) +# +# You also need to make sure to provide pluralization algorithms to the +# backend, i.e. include them to your I18n.load_path accordingly. +module I18n + module Backend + module Pluralization + # Overwrites the Base backend translate method so that it will check the + # translation meta data space (:i18n) for a locale specific pluralization + # rule and use it to pluralize the given entry. I.e., the library expects + # pluralization rules to be stored at I18n.t(:'i18n.plural.rule') + # + # Pluralization rules are expected to respond to #call(count) and + # return a pluralization key. Valid keys depend on the pluralization + # rules for the locale, as defined in the CLDR. + # As of v41, 6 locale-specific plural categories are defined: + # :few, :many, :one, :other, :two, :zero + # + # n.b., The :one plural category does not imply the number 1. + # Instead, :one is a category for any number that behaves like 1 in + # that locale. For example, in some locales, :one is used for numbers + # that end in "1" (like 1, 21, 151) but that don't end in + # 11 (like 11, 111, 10311). + # Similar notes apply to the :two, and :zero plural categories. + # + # If you want to have different strings for the categories of count == 0 + # (e.g. "I don't have any cars") or count == 1 (e.g. "I have a single car") + # use the explicit `"0"` and `"1"` keys. + # https://unicode-org.github.io/cldr/ldml/tr35-numbers.html#Explicit_0_1_rules + def pluralize(locale, entry, count) + return entry unless entry.is_a?(Hash) && count + + pluralizer = pluralizer(locale) + if pluralizer.respond_to?(:call) + # Deprecation: The use of the `zero` key in this way is incorrect. + # Users that want a different string for the case of `count == 0` should use the explicit "0" key instead. + # We keep this incorrect behaviour for now for backwards compatibility until we can remove it. + # Ref: https://github.com/ruby-i18n/i18n/issues/629 + return entry[:zero] if count == 0 && entry.has_key?(:zero) + + # "0" and "1" are special cases + # https://unicode-org.github.io/cldr/ldml/tr35-numbers.html#Explicit_0_1_rules + if count == 0 || count == 1 + value = entry[symbolic_count(count)] + return value if value + end + + # Lateral Inheritance of "count" attribute (http://www.unicode.org/reports/tr35/#Lateral_Inheritance): + # > If there is no value for a path, and that path has a [@count="x"] attribute and value, then: + # > 1. If "x" is numeric, the path falls back to the path with [@count=«the plural rules category for x for that locale»], within that the same locale. + # > 2. If "x" is anything but "other", it falls back to a path [@count="other"], within that the same locale. + # > 3. If "x" is "other", it falls back to the path that is completely missing the count item, within that the same locale. + # Note: We don't yet implement #3 above, since we haven't decided how lateral inheritance attributes should be represented. + plural_rule_category = pluralizer.call(count) + + value = if entry.has_key?(plural_rule_category) || entry.has_key?(:other) + entry[plural_rule_category] || entry[:other] + else + raise InvalidPluralizationData.new(entry, count, plural_rule_category) + end + else + super + end + end + + protected + + def pluralizers + @pluralizers ||= {} + end + + def pluralizer(locale) + pluralizers[locale] ||= I18n.t(:'i18n.plural.rule', :locale => locale, :resolve => false) + end + + private + + # Normalizes categories of 0.0 and 1.0 + # and returns the symbolic version + def symbolic_count(count) + count = 0 if count == 0 + count = 1 if count == 1 + count.to_s.to_sym + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/simple.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/simple.rb new file mode 100644 index 00000000..2cac2452 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/simple.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +require 'i18n/backend/base' + +module I18n + module Backend + # A simple backend that reads translations from YAML files and stores them in + # an in-memory hash. Relies on the Base backend. + # + # The implementation is provided by a Implementation module allowing to easily + # extend Simple backend's behavior by including modules. E.g.: + # + # module I18n::Backend::Pluralization + # def pluralize(*args) + # # extended pluralization logic + # super + # end + # end + # + # I18n::Backend::Simple.include(I18n::Backend::Pluralization) + class Simple + module Implementation + include Base + + # Mutex to ensure that concurrent translations loading will be thread-safe + MUTEX = Mutex.new + + def initialized? + @initialized ||= false + end + + # Stores translations for the given locale in memory. + # This uses a deep merge for the translations hash, so existing + # translations will be overwritten by new ones only at the deepest + # level of the hash. + def store_translations(locale, data, options = EMPTY_HASH) + if I18n.enforce_available_locales && + I18n.available_locales_initialized? && + !I18n.locale_available?(locale) + return data + end + locale = locale.to_sym + translations[locale] ||= Concurrent::Hash.new + data = Utils.deep_symbolize_keys(data) unless options.fetch(:skip_symbolize_keys, false) + Utils.deep_merge!(translations[locale], data) + end + + # Get available locales from the translations hash + def available_locales + init_translations unless initialized? + translations.inject([]) do |locales, (locale, data)| + locales << locale unless data.size <= 1 && (data.empty? || data.has_key?(:i18n)) + locales + end + end + + # Clean up translations hash and set initialized to false on reload! + def reload! + @initialized = false + @translations = nil + super + end + + def eager_load! + init_translations unless initialized? + super + end + + def translations(do_init: false) + # To avoid returning empty translations, + # call `init_translations` + init_translations if do_init && !initialized? + + @translations ||= Concurrent::Hash.new do |h, k| + MUTEX.synchronize do + h[k] = Concurrent::Hash.new + end + end + end + + protected + + def init_translations + load_translations + @initialized = true + end + + # Looks up a translation from the translations hash. Returns nil if + # either key is nil, or locale, scope or key do not exist as a key in the + # nested translations hash. Splits keys or scopes containing dots + # into multiple keys, i.e. currency.format is regarded the same as + # %w(currency format). + def lookup(locale, key, scope = [], options = EMPTY_HASH) + init_translations unless initialized? + keys = I18n.normalize_keys(locale, key, scope, options[:separator]) + + keys.inject(translations) do |result, _key| + return nil unless result.is_a?(Hash) + unless result.has_key?(_key) + _key = _key.to_s.to_sym + return nil unless result.has_key?(_key) + end + result = result[_key] + result = resolve_entry(locale, _key, result, Utils.except(options.merge(:scope => nil), :count)) if result.is_a?(Symbol) + result + end + end + end + + include Implementation + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/transliterator.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/transliterator.rb new file mode 100644 index 00000000..70c0df3d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/backend/transliterator.rb @@ -0,0 +1,108 @@ +# encoding: utf-8 +# frozen_string_literal: true + +module I18n + module Backend + module Transliterator + DEFAULT_REPLACEMENT_CHAR = "?" + + # Given a locale and a UTF-8 string, return the locale's ASCII + # approximation for the string. + def transliterate(locale, string, replacement = nil) + @transliterators ||= {} + @transliterators[locale] ||= Transliterator.get I18n.t(:'i18n.transliterate.rule', + :locale => locale, :resolve => false, :default => {}) + @transliterators[locale].transliterate(string, replacement) + end + + # Get a transliterator instance. + def self.get(rule = nil) + if !rule || rule.kind_of?(Hash) + HashTransliterator.new(rule) + elsif rule.kind_of? Proc + ProcTransliterator.new(rule) + else + raise I18n::ArgumentError, "Transliteration rule must be a proc or a hash." + end + end + + # A transliterator which accepts a Proc as its transliteration rule. + class ProcTransliterator + def initialize(rule) + @rule = rule + end + + def transliterate(string, replacement = nil) + @rule.call(string) + end + end + + # A transliterator which accepts a Hash of characters as its translation + # rule. + class HashTransliterator + DEFAULT_APPROXIMATIONS = { + "À"=>"A", "Á"=>"A", "Â"=>"A", "Ã"=>"A", "Ä"=>"A", "Å"=>"A", "Æ"=>"AE", + "Ç"=>"C", "È"=>"E", "É"=>"E", "Ê"=>"E", "Ë"=>"E", "Ì"=>"I", "Í"=>"I", + "Î"=>"I", "Ï"=>"I", "Ð"=>"D", "Ñ"=>"N", "Ò"=>"O", "Ó"=>"O", "Ô"=>"O", + "Õ"=>"O", "Ö"=>"O", "×"=>"x", "Ø"=>"O", "Ù"=>"U", "Ú"=>"U", "Û"=>"U", + "Ü"=>"U", "Ý"=>"Y", "Þ"=>"Th", "ß"=>"ss", "ẞ"=>"SS", "à"=>"a", + "á"=>"a", "â"=>"a", "ã"=>"a", "ä"=>"a", "å"=>"a", "æ"=>"ae", "ç"=>"c", + "è"=>"e", "é"=>"e", "ê"=>"e", "ë"=>"e", "ì"=>"i", "í"=>"i", "î"=>"i", + "ï"=>"i", "ð"=>"d", "ñ"=>"n", "ò"=>"o", "ó"=>"o", "ô"=>"o", "õ"=>"o", + "ö"=>"o", "ø"=>"o", "ù"=>"u", "ú"=>"u", "û"=>"u", "ü"=>"u", "ý"=>"y", + "þ"=>"th", "ÿ"=>"y", "Ā"=>"A", "ā"=>"a", "Ă"=>"A", "ă"=>"a", "Ą"=>"A", + "ą"=>"a", "Ć"=>"C", "ć"=>"c", "Ĉ"=>"C", "ĉ"=>"c", "Ċ"=>"C", "ċ"=>"c", + "Č"=>"C", "č"=>"c", "Ď"=>"D", "ď"=>"d", "Đ"=>"D", "đ"=>"d", "Ē"=>"E", + "ē"=>"e", "Ĕ"=>"E", "ĕ"=>"e", "Ė"=>"E", "ė"=>"e", "Ę"=>"E", "ę"=>"e", + "Ě"=>"E", "ě"=>"e", "Ĝ"=>"G", "ĝ"=>"g", "Ğ"=>"G", "ğ"=>"g", "Ġ"=>"G", + "ġ"=>"g", "Ģ"=>"G", "ģ"=>"g", "Ĥ"=>"H", "ĥ"=>"h", "Ħ"=>"H", "ħ"=>"h", + "Ĩ"=>"I", "ĩ"=>"i", "Ī"=>"I", "ī"=>"i", "Ĭ"=>"I", "ĭ"=>"i", "Į"=>"I", + "į"=>"i", "İ"=>"I", "ı"=>"i", "IJ"=>"IJ", "ij"=>"ij", "Ĵ"=>"J", "ĵ"=>"j", + "Ķ"=>"K", "ķ"=>"k", "ĸ"=>"k", "Ĺ"=>"L", "ĺ"=>"l", "Ļ"=>"L", "ļ"=>"l", + "Ľ"=>"L", "ľ"=>"l", "Ŀ"=>"L", "ŀ"=>"l", "Ł"=>"L", "ł"=>"l", "Ń"=>"N", + "ń"=>"n", "Ņ"=>"N", "ņ"=>"n", "Ň"=>"N", "ň"=>"n", "ʼn"=>"'n", "Ŋ"=>"NG", + "ŋ"=>"ng", "Ō"=>"O", "ō"=>"o", "Ŏ"=>"O", "ŏ"=>"o", "Ő"=>"O", "ő"=>"o", + "Œ"=>"OE", "œ"=>"oe", "Ŕ"=>"R", "ŕ"=>"r", "Ŗ"=>"R", "ŗ"=>"r", "Ř"=>"R", + "ř"=>"r", "Ś"=>"S", "ś"=>"s", "Ŝ"=>"S", "ŝ"=>"s", "Ş"=>"S", "ş"=>"s", + "Š"=>"S", "š"=>"s", "Ţ"=>"T", "ţ"=>"t", "Ť"=>"T", "ť"=>"t", "Ŧ"=>"T", + "ŧ"=>"t", "Ũ"=>"U", "ũ"=>"u", "Ū"=>"U", "ū"=>"u", "Ŭ"=>"U", "ŭ"=>"u", + "Ů"=>"U", "ů"=>"u", "Ű"=>"U", "ű"=>"u", "Ų"=>"U", "ų"=>"u", "Ŵ"=>"W", + "ŵ"=>"w", "Ŷ"=>"Y", "ŷ"=>"y", "Ÿ"=>"Y", "Ź"=>"Z", "ź"=>"z", "Ż"=>"Z", + "ż"=>"z", "Ž"=>"Z", "ž"=>"z" + }.freeze + + def initialize(rule = nil) + @rule = rule + add_default_approximations + add rule if rule + end + + def transliterate(string, replacement = nil) + replacement ||= DEFAULT_REPLACEMENT_CHAR + string.gsub(/[^\x00-\x7f]/u) do |char| + approximations[char] || replacement + end + end + + private + + def approximations + @approximations ||= {} + end + + def add_default_approximations + DEFAULT_APPROXIMATIONS.each do |key, value| + approximations[key] = value + end + end + + # Add transliteration rules to the approximations hash. + def add(hash) + hash.each do |key, value| + approximations[key.to_s] = value.to_s + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/config.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/config.rb new file mode 100644 index 00000000..9878e02e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/config.rb @@ -0,0 +1,165 @@ +# frozen_string_literal: true + +require 'set' + +module I18n + class Config + # The only configuration value that is not global and scoped to thread is :locale. + # It defaults to the default_locale. + def locale + defined?(@locale) && @locale != nil ? @locale : default_locale + end + + # Sets the current locale pseudo-globally, i.e. in the Thread.current hash. + def locale=(locale) + I18n.enforce_available_locales!(locale) + @locale = locale && locale.to_sym + end + + # Returns the current backend. Defaults to +Backend::Simple+. + def backend + @@backend ||= Backend::Simple.new + end + + # Sets the current backend. Used to set a custom backend. + def backend=(backend) + @@backend = backend + end + + # Returns the current default locale. Defaults to :'en' + def default_locale + @@default_locale ||= :en + end + + # Sets the current default locale. Used to set a custom default locale. + def default_locale=(locale) + I18n.enforce_available_locales!(locale) + @@default_locale = locale && locale.to_sym + end + + # Returns an array of locales for which translations are available. + # Unless you explicitly set these through I18n.available_locales= + # the call will be delegated to the backend. + def available_locales + @@available_locales ||= nil + @@available_locales || backend.available_locales + end + + # Caches the available locales list as both strings and symbols in a Set, so + # that we can have faster lookups to do the available locales enforce check. + def available_locales_set #:nodoc: + @@available_locales_set ||= available_locales.inject(Set.new) do |set, locale| + set << locale.to_s << locale.to_sym + end + end + + # Sets the available locales. + def available_locales=(locales) + @@available_locales = Array(locales).map { |locale| locale.to_sym } + @@available_locales = nil if @@available_locales.empty? + @@available_locales_set = nil + end + + # Returns true if the available_locales have been initialized + def available_locales_initialized? + ( !!defined?(@@available_locales) && !!@@available_locales ) + end + + # Clears the available locales set so it can be recomputed again after I18n + # gets reloaded. + def clear_available_locales_set #:nodoc: + @@available_locales_set = nil + end + + # Returns the current default scope separator. Defaults to '.' + def default_separator + @@default_separator ||= '.' + end + + # Sets the current default scope separator. + def default_separator=(separator) + @@default_separator = separator + end + + # Returns the current exception handler. Defaults to an instance of + # I18n::ExceptionHandler. + def exception_handler + @@exception_handler ||= ExceptionHandler.new + end + + # Sets the exception handler. + def exception_handler=(exception_handler) + @@exception_handler = exception_handler + end + + # Returns the current handler for situations when interpolation argument + # is missing. MissingInterpolationArgument will be raised by default. + def missing_interpolation_argument_handler + @@missing_interpolation_argument_handler ||= lambda do |missing_key, provided_hash, string| + raise MissingInterpolationArgument.new(missing_key, provided_hash, string) + end + end + + # Sets the missing interpolation argument handler. It can be any + # object that responds to #call. The arguments that will be passed to #call + # are the same as for MissingInterpolationArgument initializer. Use +Proc.new+ + # if you don't care about arity. + # + # == Example: + # You can suppress raising an exception and return string instead: + # + # I18n.config.missing_interpolation_argument_handler = Proc.new do |key| + # "#{key} is missing" + # end + def missing_interpolation_argument_handler=(exception_handler) + @@missing_interpolation_argument_handler = exception_handler + end + + # Allow clients to register paths providing translation data sources. The + # backend defines acceptable sources. + # + # E.g. the provided SimpleBackend accepts a list of paths to translation + # files which are either named *.rb and contain plain Ruby Hashes or are + # named *.yml and contain YAML data. So for the SimpleBackend clients may + # register translation files like this: + # I18n.load_path << 'path/to/locale/en.yml' + def load_path + @@load_path ||= [] + end + + # Sets the load path instance. Custom implementations are expected to + # behave like a Ruby Array. + def load_path=(load_path) + @@load_path = load_path + @@available_locales_set = nil + backend.reload! + end + + # Whether or not to verify if locales are in the list of available locales. + # Defaults to true. + @@enforce_available_locales = true + def enforce_available_locales + @@enforce_available_locales + end + + def enforce_available_locales=(enforce_available_locales) + @@enforce_available_locales = enforce_available_locales + end + + # Returns the current interpolation patterns. Defaults to + # I18n::DEFAULT_INTERPOLATION_PATTERNS. + def interpolation_patterns + @@interpolation_patterns ||= I18n::DEFAULT_INTERPOLATION_PATTERNS.dup + end + + # Sets the current interpolation patterns. Used to set a interpolation + # patterns. + # + # E.g. using {{}} as a placeholder like "{{hello}}, world!": + # + # I18n.config.interpolation_patterns << /\{\{(\w+)\}\}/ + def interpolation_patterns=(interpolation_patterns) + @@interpolation_patterns = interpolation_patterns + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/exceptions.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/exceptions.rb new file mode 100644 index 00000000..23ca46ec --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/exceptions.rb @@ -0,0 +1,157 @@ +# frozen_string_literal: true + +require 'cgi' + +module I18n + class ExceptionHandler + def call(exception, _locale, _key, _options) + if exception.is_a?(MissingTranslation) + exception.message + else + raise exception + end + end + end + + class ArgumentError < ::ArgumentError; end + + class Disabled < ArgumentError + def initialize(method) + super(<<~MESSAGE) + I18n.#{method} is currently disabled, likely because your application is still in its loading phase. + + This method is meant to display text in the user locale, so calling it before the user locale has + been set is likely to display text from the wrong locale to some users. + + If you have a legitimate reason to access i18n data outside of the user flow, you can do so by passing + the desired locale explicitly with the `locale` argument, e.g. `I18n.#{method}(..., locale: :en)` + MESSAGE + end + end + + class InvalidLocale < ArgumentError + attr_reader :locale + def initialize(locale) + @locale = locale + super "#{locale.inspect} is not a valid locale" + end + end + + class InvalidLocaleData < ArgumentError + attr_reader :filename + def initialize(filename, exception_message) + @filename, @exception_message = filename, exception_message + super "can not load translations from #{filename}: #{exception_message}" + end + end + + class MissingTranslation < ArgumentError + module Base + PERMITTED_KEYS = [:scope, :default].freeze + + attr_reader :locale, :key, :options + + def initialize(locale, key, options = EMPTY_HASH) + @key, @locale, @options = key, locale, options.slice(*PERMITTED_KEYS) + options.each { |k, v| self.options[k] = v.inspect if v.is_a?(Proc) } + end + + def keys + @keys ||= I18n.normalize_keys(locale, key, options[:scope]).tap do |keys| + keys << 'no key' if keys.size < 2 + end + end + + def message + if (default = options[:default]).is_a?(Array) && default.any? + other_options = ([key, *default]).map { |k| normalized_option(k).prepend('- ') }.join("\n") + "Translation missing. Options considered were:\n#{other_options}" + else + "Translation missing: #{keys.join('.')}" + end + end + + def normalized_option(key) + I18n.normalize_keys(locale, key, options[:scope]).join('.') + end + + alias :to_s :message + + def to_exception + MissingTranslationData.new(locale, key, options) + end + end + + include Base + end + + class MissingTranslationData < ArgumentError + include MissingTranslation::Base + end + + class InvalidPluralizationData < ArgumentError + attr_reader :entry, :count, :key + def initialize(entry, count, key) + @entry, @count, @key = entry, count, key + super "translation data #{entry.inspect} can not be used with :count => #{count}. key '#{key}' is missing." + end + end + + class MissingInterpolationArgument < ArgumentError + attr_reader :key, :values, :string + def initialize(key, values, string) + @key, @values, @string = key, values, string + super "missing interpolation argument #{key.inspect} in #{string.inspect} (#{values.inspect} given)" + end + end + + class ReservedInterpolationKey < ArgumentError + attr_reader :key, :string + def initialize(key, string) + @key, @string = key, string + super "reserved key #{key.inspect} used in #{string.inspect}" + end + end + + class UnknownFileType < ArgumentError + attr_reader :type, :filename + def initialize(type, filename) + @type, @filename = type, filename + super "can not load translations from #{filename}, the file type #{type} is not known" + end + end + + class UnsupportedMethod < ArgumentError + attr_reader :method, :backend_klass, :msg + def initialize(method, backend_klass, msg) + @method = method + @backend_klass = backend_klass + @msg = msg + super "#{backend_klass} does not support the ##{method} method. #{msg}" + end + end + + class InvalidFilenames < ArgumentError + NUMBER_OF_ERRORS_SHOWN = 20 + def initialize(file_errors) + super <<~MSG + Found #{file_errors.count} error(s). + The first #{[file_errors.count, NUMBER_OF_ERRORS_SHOWN].min} error(s): + #{file_errors.map(&:message).first(NUMBER_OF_ERRORS_SHOWN).join("\n")} + + To use the LazyLoadable backend: + 1. Filenames must start with the locale. + 2. An underscore must separate the locale with any optional text that follows. + 3. The file must only contain translation data for the single locale. + + Example: + "/config/locales/fr.yml" which contains: + ```yml + fr: + dog: + chien + ``` + MSG + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/gettext.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/gettext.rb new file mode 100644 index 00000000..858daff4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/gettext.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module I18n + module Gettext + PLURAL_SEPARATOR = "\001" + CONTEXT_SEPARATOR = "\004" + + autoload :Helpers, 'i18n/gettext/helpers' + + @@plural_keys = { :en => [:one, :other] } + + class << self + # returns an array of plural keys for the given locale or the whole hash + # of locale mappings to plural keys so that we can convert from gettext's + # integer-index based style + # TODO move this information to the pluralization module + def plural_keys(*args) + args.empty? ? @@plural_keys : @@plural_keys[args.first] || @@plural_keys[:en] + end + + def extract_scope(msgid, separator) + scope = msgid.to_s.split(separator) + msgid = scope.pop + [scope, msgid] + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/gettext/helpers.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/gettext/helpers.rb new file mode 100644 index 00000000..d077619f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/gettext/helpers.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require 'i18n/gettext' + +module I18n + module Gettext + # Implements classical Gettext style accessors. To use this include the + # module to the global namespace or wherever you want to use it. + # + # include I18n::Gettext::Helpers + module Helpers + # Makes dynamic translation messages readable for the gettext parser. + # _(fruit) cannot be understood by the gettext parser. To help the parser find all your translations, + # you can add fruit = N_("Apple") which does not translate, but tells the parser: "Apple" needs translation. + # * msgid: the message id. + # * Returns: msgid. + def N_(msgsid) + msgsid + end + + def gettext(msgid, options = EMPTY_HASH) + I18n.t(msgid, **{:default => msgid, :separator => '|'}.merge(options)) + end + alias _ gettext + + def sgettext(msgid, separator = '|') + scope, msgid = I18n::Gettext.extract_scope(msgid, separator) + I18n.t(msgid, :scope => scope, :default => msgid, :separator => separator) + end + alias s_ sgettext + + def pgettext(msgctxt, msgid) + separator = I18n::Gettext::CONTEXT_SEPARATOR + sgettext([msgctxt, msgid].join(separator), separator) + end + alias p_ pgettext + + def ngettext(msgid, msgid_plural, n = 1) + nsgettext(msgid, msgid_plural, n) + end + alias n_ ngettext + + # Method signatures: + # nsgettext('Fruits|apple', 'apples', 2) + # nsgettext(['Fruits|apple', 'apples'], 2) + def nsgettext(msgid, msgid_plural, n = 1, separator = '|') + if msgid.is_a?(Array) + msgid, msgid_plural, n, separator = msgid[0], msgid[1], msgid_plural, n + separator = '|' unless separator.is_a?(::String) + end + + scope, msgid = I18n::Gettext.extract_scope(msgid, separator) + default = { :one => msgid, :other => msgid_plural } + I18n.t(msgid, :default => default, :count => n, :scope => scope, :separator => separator) + end + alias ns_ nsgettext + + # Method signatures: + # npgettext('Fruits', 'apple', 'apples', 2) + # npgettext('Fruits', ['apple', 'apples'], 2) + def npgettext(msgctxt, msgid, msgid_plural, n = 1) + separator = I18n::Gettext::CONTEXT_SEPARATOR + + if msgid.is_a?(Array) + msgid_plural, msgid, n = msgid[1], [msgctxt, msgid[0]].join(separator), msgid_plural + else + msgid = [msgctxt, msgid].join(separator) + end + + nsgettext(msgid, msgid_plural, n, separator) + end + alias np_ npgettext + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/gettext/po_parser.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/gettext/po_parser.rb new file mode 100644 index 00000000..a07fdc58 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/gettext/po_parser.rb @@ -0,0 +1,329 @@ +=begin + poparser.rb - Generate a .mo + + Copyright (C) 2003-2009 Masao Mutoh + + You may redistribute it and/or modify it under the same + license terms as Ruby. +=end + +#MODIFIED +# removed include GetText etc +# added stub translation method _(x) +require 'racc/parser' + +module GetText + + class PoParser < Racc::Parser + + def _(x) + x + end + +module_eval <<'..end src/poparser.ry modeval..id7a99570e05', 'src/poparser.ry', 108 + def unescape(orig) + ret = orig.gsub(/\\n/, "\n") + ret.gsub!(/\\t/, "\t") + ret.gsub!(/\\r/, "\r") + ret.gsub!(/\\"/, "\"") + ret + end + + def parse(str, data, ignore_fuzzy = true) + @comments = [] + @data = data + @fuzzy = false + @msgctxt = "" + $ignore_fuzzy = ignore_fuzzy + + str.strip! + @q = [] + until str.empty? do + case str + when /\A\s+/ + str = $' + when /\Amsgctxt/ + @q.push [:MSGCTXT, $&] + str = $' + when /\Amsgid_plural/ + @q.push [:MSGID_PLURAL, $&] + str = $' + when /\Amsgid/ + @q.push [:MSGID, $&] + str = $' + when /\Amsgstr/ + @q.push [:MSGSTR, $&] + str = $' + when /\A\[(\d+)\]/ + @q.push [:PLURAL_NUM, $1] + str = $' + when /\A\#~(.*)/ + $stderr.print _("Warning: obsolete msgid exists.\n") + $stderr.print " #{$&}\n" + @q.push [:COMMENT, $&] + str = $' + when /\A\#(.*)/ + @q.push [:COMMENT, $&] + str = $' + when /\A\"(.*)\"/ + @q.push [:STRING, $1] + str = $' + else + #c = str[0,1] + #@q.push [:STRING, c] + str = str[1..-1] + end + end + @q.push [false, '$end'] + if $DEBUG + @q.each do |a,b| + puts "[#{a}, #{b}]" + end + end + @yydebug = true if $DEBUG + do_parse + + if @comments.size > 0 + @data.set_comment(:last, @comments.join("\n")) + end + @data + end + + def next_token + @q.shift + end + + def on_message(msgid, msgstr) + if msgstr.size > 0 + @data[msgid] = msgstr + @data.set_comment(msgid, @comments.join("\n")) + end + @comments.clear + @msgctxt = "" + end + + def on_comment(comment) + @fuzzy = true if (/fuzzy/ =~ comment) + @comments << comment + end + + +..end src/poparser.ry modeval..id7a99570e05 + +##### racc 1.4.5 generates ### + +racc_reduce_table = [ + 0, 0, :racc_error, + 0, 10, :_reduce_none, + 2, 10, :_reduce_none, + 2, 10, :_reduce_none, + 2, 10, :_reduce_none, + 2, 12, :_reduce_5, + 1, 13, :_reduce_none, + 1, 13, :_reduce_none, + 4, 15, :_reduce_8, + 5, 16, :_reduce_9, + 2, 17, :_reduce_10, + 1, 17, :_reduce_none, + 3, 18, :_reduce_12, + 1, 11, :_reduce_13, + 2, 14, :_reduce_14, + 1, 14, :_reduce_15 ] + +racc_reduce_n = 16 + +racc_shift_n = 26 + +racc_action_table = [ + 3, 13, 5, 7, 9, 15, 16, 17, 20, 17, + 13, 17, 13, 13, 11, 17, 23, 20, 13, 17 ] + +racc_action_check = [ + 1, 16, 1, 1, 1, 12, 12, 12, 18, 18, + 7, 14, 15, 9, 3, 19, 20, 21, 23, 25 ] + +racc_action_pointer = [ + nil, 0, nil, 14, nil, nil, nil, 3, nil, 6, + nil, nil, 0, nil, 4, 5, -6, nil, 2, 8, + 8, 11, nil, 11, nil, 12 ] + +racc_action_default = [ + -1, -16, -2, -16, -3, -13, -4, -16, -6, -16, + -7, 26, -16, -15, -5, -16, -16, -14, -16, -8, + -16, -9, -11, -16, -10, -12 ] + +racc_goto_table = [ + 12, 22, 14, 4, 24, 6, 2, 8, 18, 19, + 10, 21, 1, nil, nil, nil, 25 ] + +racc_goto_check = [ + 5, 9, 5, 3, 9, 4, 2, 6, 5, 5, + 7, 8, 1, nil, nil, nil, 5 ] + +racc_goto_pointer = [ + nil, 12, 5, 2, 4, -7, 6, 9, -7, -17 ] + +racc_goto_default = [ + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil ] + +racc_token_table = { + false => 0, + Object.new => 1, + :COMMENT => 2, + :MSGID => 3, + :MSGCTXT => 4, + :MSGID_PLURAL => 5, + :MSGSTR => 6, + :STRING => 7, + :PLURAL_NUM => 8 } + +racc_use_result_var = true + +racc_nt_base = 9 + +Racc_arg = [ + racc_action_table, + racc_action_check, + racc_action_default, + racc_action_pointer, + racc_goto_table, + racc_goto_check, + racc_goto_default, + racc_goto_pointer, + racc_nt_base, + racc_reduce_table, + racc_token_table, + racc_shift_n, + racc_reduce_n, + racc_use_result_var ] + +Racc_token_to_s_table = [ +'$end', +'error', +'COMMENT', +'MSGID', +'MSGCTXT', +'MSGID_PLURAL', +'MSGSTR', +'STRING', +'PLURAL_NUM', +'$start', +'msgfmt', +'comment', +'msgctxt', +'message', +'string_list', +'single_message', +'plural_message', +'msgstr_plural', +'msgstr_plural_line'] + +Racc_debug_parser = true + +##### racc system variables end ##### + + # reduce 0 omitted + + # reduce 1 omitted + + # reduce 2 omitted + + # reduce 3 omitted + + # reduce 4 omitted + +module_eval <<'.,.,', 'src/poparser.ry', 25 + def _reduce_5( val, _values, result ) + @msgctxt = unescape(val[1]) + "\004" + result + end +.,., + + # reduce 6 omitted + + # reduce 7 omitted + +module_eval <<'.,.,', 'src/poparser.ry', 48 + def _reduce_8( val, _values, result ) + if @fuzzy and $ignore_fuzzy + if val[1] != "" + $stderr.print _("Warning: fuzzy message was ignored.\n") + $stderr.print " msgid '#{val[1]}'\n" + else + on_message('', unescape(val[3])) + end + @fuzzy = false + else + on_message(@msgctxt + unescape(val[1]), unescape(val[3])) + end + result = "" + result + end +.,., + +module_eval <<'.,.,', 'src/poparser.ry', 65 + def _reduce_9( val, _values, result ) + if @fuzzy and $ignore_fuzzy + if val[1] != "" + $stderr.print _("Warning: fuzzy message was ignored.\n") + $stderr.print "msgid = '#{val[1]}\n" + else + on_message('', unescape(val[3])) + end + @fuzzy = false + else + on_message(@msgctxt + unescape(val[1]) + "\000" + unescape(val[3]), unescape(val[4])) + end + result = "" + result + end +.,., + +module_eval <<'.,.,', 'src/poparser.ry', 76 + def _reduce_10( val, _values, result ) + if val[0].size > 0 + result = val[0] + "\000" + val[1] + else + result = "" + end + result + end +.,., + + # reduce 11 omitted + +module_eval <<'.,.,', 'src/poparser.ry', 84 + def _reduce_12( val, _values, result ) + result = val[2] + result + end +.,., + +module_eval <<'.,.,', 'src/poparser.ry', 91 + def _reduce_13( val, _values, result ) + on_comment(val[0]) + result + end +.,., + +module_eval <<'.,.,', 'src/poparser.ry', 99 + def _reduce_14( val, _values, result ) + result = val.delete_if{|item| item == ""}.join + result + end +.,., + +module_eval <<'.,.,', 'src/poparser.ry', 103 + def _reduce_15( val, _values, result ) + result = val[0] + result + end +.,., + + def _reduce_none( val, _values, result ) + result + end + + end # class PoParser + +end # module GetText diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/interpolate/ruby.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/interpolate/ruby.rb new file mode 100644 index 00000000..5b50593f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/interpolate/ruby.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +# heavily based on Masao Mutoh's gettext String interpolation extension +# http://github.com/mutoh/gettext/blob/f6566738b981fe0952548c421042ad1e0cdfb31e/lib/gettext/core_ext/string.rb + +module I18n + DEFAULT_INTERPOLATION_PATTERNS = [ + /%%/, + /%\{([\w|]+)\}/, # matches placeholders like "%{foo} or %{foo|word}" + /%<(\w+)>([^\d]*?\d*\.?\d*[bBdiouxXeEfgGcps])/ # matches placeholders like "%.d" + ].freeze + INTERPOLATION_PATTERN = Regexp.union(DEFAULT_INTERPOLATION_PATTERNS) + deprecate_constant :INTERPOLATION_PATTERN + + INTERPOLATION_PATTERNS_CACHE = Hash.new do |hash, patterns| + hash[patterns] = Regexp.union(patterns) + end + private_constant :INTERPOLATION_PATTERNS_CACHE + + class << self + # Return String or raises MissingInterpolationArgument exception. + # Missing argument's logic is handled by I18n.config.missing_interpolation_argument_handler. + def interpolate(string, values) + raise ReservedInterpolationKey.new($1.to_sym, string) if string =~ I18n.reserved_keys_pattern + raise ArgumentError.new('Interpolation values must be a Hash.') unless values.kind_of?(Hash) + interpolate_hash(string, values) + end + + def interpolate_hash(string, values) + pattern = INTERPOLATION_PATTERNS_CACHE[config.interpolation_patterns] + interpolated = false + + interpolated_string = string.gsub(pattern) do |match| + interpolated = true + + if match == '%%' + '%' + else + key = ($1 || $2 || match.tr("%{}", "")).to_sym + value = if values.key?(key) + values[key] + else + config.missing_interpolation_argument_handler.call(key, values, string) + end + value = value.call(values) if value.respond_to?(:call) + $3 ? sprintf("%#{$3}", value) : value + end + end + + interpolated ? interpolated_string : string + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/locale.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/locale.rb new file mode 100644 index 00000000..c4078e61 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/locale.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module I18n + module Locale + autoload :Fallbacks, 'i18n/locale/fallbacks' + autoload :Tag, 'i18n/locale/tag' + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/locale/fallbacks.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/locale/fallbacks.rb new file mode 100644 index 00000000..56d08d71 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/locale/fallbacks.rb @@ -0,0 +1,107 @@ +# Locale Fallbacks +# +# Extends the I18n module to hold a fallbacks instance which is set to an +# instance of I18n::Locale::Fallbacks by default but can be swapped with a +# different implementation. +# +# Locale fallbacks will compute a number of fallback locales for a given locale. +# For example: +# +#
    
    +# I18n.fallbacks[:"es-MX"] # => [:"es-MX", :es, :en] 
    +# +# Locale fallbacks always fall back to +# +# * all parent locales of a given locale (e.g. :es for :"es-MX") first, +# * the current default locales and all of their parents second +# +# The default locales are set to [] by default but can be set to something else. +# +# One can additionally add any number of additional fallback locales manually. +# These will be added before the default locales to the fallback chain. For +# example: +# +# # using a custom locale as default fallback locale +# +# I18n.fallbacks = I18n::Locale::Fallbacks.new(:"en-GB", :"de-AT" => :de, :"de-CH" => :de) +# I18n.fallbacks[:"de-AT"] # => [:"de-AT", :de, :"en-GB", :en] +# I18n.fallbacks[:"de-CH"] # => [:"de-CH", :de, :"en-GB", :en] +# +# # mapping fallbacks to an existing instance +# +# # people speaking Catalan also speak Spanish as spoken in Spain +# fallbacks = I18n.fallbacks +# fallbacks.map(:ca => :"es-ES") +# fallbacks[:ca] # => [:ca, :"es-ES", :es, :"en-US", :en] +# +# # people speaking Arabian as spoken in Palestine also speak Hebrew as spoken in Israel +# fallbacks.map(:"ar-PS" => :"he-IL") +# fallbacks[:"ar-PS"] # => [:"ar-PS", :ar, :"he-IL", :he, :"en-US", :en] +# fallbacks[:"ar-EG"] # => [:"ar-EG", :ar, :"en-US", :en] +# +# # people speaking Sami as spoken in Finland also speak Swedish and Finnish as spoken in Finland +# fallbacks.map(:sms => [:"se-FI", :"fi-FI"]) +# fallbacks[:sms] # => [:sms, :"se-FI", :se, :"fi-FI", :fi, :"en-US", :en] + +module I18n + module Locale + class Fallbacks < Hash + def initialize(*mappings) + @map = {} + map(mappings.pop) if mappings.last.is_a?(Hash) + self.defaults = mappings.empty? ? [] : mappings + end + + def defaults=(defaults) + @defaults = defaults.flat_map { |default| compute(default, false) } + end + attr_reader :defaults + + def [](locale) + raise InvalidLocale.new(locale) if locale.nil? + raise Disabled.new('fallback#[]') if locale == false + locale = locale.to_sym + super || store(locale, compute(locale)) + end + + def map(*args, &block) + if args.count == 1 && !block_given? + mappings = args.first + mappings.each do |from, to| + from, to = from.to_sym, Array(to) + to.each do |_to| + @map[from] ||= [] + @map[from] << _to.to_sym + end + end + else + @map.map(*args, &block) + end + end + + def empty? + @map.empty? && @defaults.empty? + end + + def inspect + "#<#{self.class.name} @map=#{@map.inspect} @defaults=#{@defaults.inspect}>" + end + + protected + + def compute(tags, include_defaults = true, exclude = []) + result = [] + Array(tags).each do |tag| + tags = I18n::Locale::Tag.tag(tag).self_and_parents.map! { |t| t.to_sym } - exclude + result += tags + tags.each { |_tag| result += compute(@map[_tag], false, exclude + result) if @map[_tag] } + end + + result.push(*defaults) if include_defaults + result.uniq! + result.compact! + result + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/locale/tag.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/locale/tag.rb new file mode 100644 index 00000000..a640b446 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/locale/tag.rb @@ -0,0 +1,28 @@ +# encoding: utf-8 + +module I18n + module Locale + module Tag + autoload :Parents, 'i18n/locale/tag/parents' + autoload :Rfc4646, 'i18n/locale/tag/rfc4646' + autoload :Simple, 'i18n/locale/tag/simple' + + class << self + # Returns the current locale tag implementation. Defaults to +I18n::Locale::Tag::Simple+. + def implementation + @@implementation ||= Simple + end + + # Sets the current locale tag implementation. Use this to set a different locale tag implementation. + def implementation=(implementation) + @@implementation = implementation + end + + # Factory method for locale tags. Delegates to the current locale tag implementation. + def tag(tag) + implementation.tag(tag) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/locale/tag/parents.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/locale/tag/parents.rb new file mode 100644 index 00000000..6283e667 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/locale/tag/parents.rb @@ -0,0 +1,24 @@ +module I18n + module Locale + module Tag + module Parents + def parent + @parent ||= + begin + segs = to_a + segs.compact! + segs.length > 1 ? self.class.tag(*segs[0..(segs.length - 2)].join('-')) : nil + end + end + + def self_and_parents + @self_and_parents ||= [self].concat parents + end + + def parents + @parents ||= parent ? [parent].concat(parent.parents) : [] + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/locale/tag/rfc4646.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/locale/tag/rfc4646.rb new file mode 100644 index 00000000..4ce4c751 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/locale/tag/rfc4646.rb @@ -0,0 +1,74 @@ +# RFC 4646/47 compliant Locale tag implementation that parses locale tags to +# subtags such as language, script, region, variant etc. +# +# For more information see by http://en.wikipedia.org/wiki/IETF_language_tag +# +# Rfc4646::Parser does not implement grandfathered tags. + +module I18n + module Locale + module Tag + RFC4646_SUBTAGS = [ :language, :script, :region, :variant, :extension, :privateuse, :grandfathered ] + RFC4646_FORMATS = { :language => :downcase, :script => :capitalize, :region => :upcase, :variant => :downcase } + + class Rfc4646 < Struct.new(*RFC4646_SUBTAGS) + class << self + # Parses the given tag and returns a Tag instance if it is valid. + # Returns false if the given tag is not valid according to RFC 4646. + def tag(tag) + matches = parser.match(tag) + new(*matches) if matches + end + + def parser + @@parser ||= Rfc4646::Parser + end + + def parser=(parser) + @@parser = parser + end + end + + include Parents + + RFC4646_FORMATS.each do |name, format| + define_method(name) { self[name].send(format) unless self[name].nil? } + end + + def to_sym + to_s.to_sym + end + + def to_s + @tag ||= to_a.compact.join("-") + end + + def to_a + members.collect { |attr| self.send(attr) } + end + + module Parser + PATTERN = %r{\A(?: + ([a-z]{2,3}(?:(?:-[a-z]{3}){0,3})?|[a-z]{4}|[a-z]{5,8}) # language + (?:-([a-z]{4}))? # script + (?:-([a-z]{2}|\d{3}))? # region + (?:-([0-9a-z]{5,8}|\d[0-9a-z]{3}))* # variant + (?:-([0-9a-wyz](?:-[0-9a-z]{2,8})+))* # extension + (?:-(x(?:-[0-9a-z]{1,8})+))?| # privateuse subtag + (x(?:-[0-9a-z]{1,8})+)| # privateuse tag + /* ([a-z]{1,3}(?:-[0-9a-z]{2,8}){1,2}) */ # grandfathered + )\z}xi + + class << self + def match(tag) + c = PATTERN.match(tag.to_s).captures + c[0..4] << (c[5].nil? ? c[6] : c[5]) << c[7] # TODO c[7] is grandfathered, throw a NotImplemented exception here? + rescue + false + end + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/locale/tag/simple.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/locale/tag/simple.rb new file mode 100644 index 00000000..18d55c28 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/locale/tag/simple.rb @@ -0,0 +1,39 @@ +# Simple Locale tag implementation that computes subtags by simply splitting +# the locale tag at '-' occurrences. +module I18n + module Locale + module Tag + class Simple + class << self + def tag(tag) + new(tag) + end + end + + include Parents + + attr_reader :tag + + def initialize(*tag) + @tag = tag.join('-').to_sym + end + + def subtags + @subtags = tag.to_s.split('-').map!(&:to_s) + end + + def to_sym + tag + end + + def to_s + tag.to_s + end + + def to_a + subtags + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/middleware.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/middleware.rb new file mode 100644 index 00000000..59b377e2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/middleware.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module I18n + class Middleware + + def initialize(app) + @app = app + end + + def call(env) + @app.call(env) + ensure + Thread.current[:i18n_config] = I18n::Config.new + end + + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests.rb new file mode 100644 index 00000000..30d0ed53 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module I18n + module Tests + autoload :Basics, 'i18n/tests/basics' + autoload :Defaults, 'i18n/tests/defaults' + autoload :Interpolation, 'i18n/tests/interpolation' + autoload :Link, 'i18n/tests/link' + autoload :Localization, 'i18n/tests/localization' + autoload :Lookup, 'i18n/tests/lookup' + autoload :Pluralization, 'i18n/tests/pluralization' + autoload :Procs, 'i18n/tests/procs' + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/basics.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/basics.rb new file mode 100644 index 00000000..833762be --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/basics.rb @@ -0,0 +1,58 @@ +module I18n + module Tests + module Basics + def teardown + I18n.available_locales = nil + end + + test "available_locales returns the available_locales produced by the backend, by default" do + I18n.backend.store_translations('de', :foo => 'bar') + I18n.backend.store_translations('en', :foo => 'foo') + + assert_equal I18n.available_locales, I18n.backend.available_locales + end + + test "available_locales can be set to something else independently from the actual locale data" do + I18n.backend.store_translations('de', :foo => 'bar') + I18n.backend.store_translations('en', :foo => 'foo') + + I18n.available_locales = :foo + assert_equal [:foo], I18n.available_locales + + I18n.available_locales = [:foo, 'bar'] + assert_equal [:foo, :bar], I18n.available_locales + + I18n.available_locales = nil + assert_equal I18n.available_locales, I18n.backend.available_locales + end + + test "available_locales memoizes when set explicitly" do + I18n.backend.expects(:available_locales).never + I18n.available_locales = [:foo] + I18n.backend.store_translations('de', :bar => 'baz') + I18n.reload! + assert_equal [:foo], I18n.available_locales + end + + test "available_locales delegates to the backend when not set explicitly" do + original_available_locales_value = I18n.backend.available_locales + I18n.backend.expects(:available_locales).returns(original_available_locales_value).twice + assert_equal I18n.backend.available_locales, I18n.available_locales + end + + test "exists? is implemented by the backend" do + I18n.backend.store_translations(:foo, :bar => 'baz') + assert I18n.exists?(:bar, :foo) + end + + test "storing a nil value as a translation removes it from the available locale data" do + I18n.backend.store_translations(:en, :to_be_deleted => 'bar') + assert_equal 'bar', I18n.t(:to_be_deleted, :default => 'baz') + + I18n.cache_store.clear if I18n.respond_to?(:cache_store) && I18n.cache_store + I18n.backend.store_translations(:en, :to_be_deleted => nil) + assert_equal 'baz', I18n.t(:to_be_deleted, :default => 'baz') + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/defaults.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/defaults.rb new file mode 100644 index 00000000..e5db3365 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/defaults.rb @@ -0,0 +1,59 @@ +# encoding: utf-8 + +module I18n + module Tests + module Defaults + def setup + super + I18n.backend.store_translations(:en, :foo => { :bar => 'bar', :baz => 'baz' }) + end + + test "defaults: given nil as a key it returns the given default" do + assert_equal 'default', I18n.t(nil, :default => 'default') + end + + test "defaults: given a symbol as a default it translates the symbol" do + assert_equal 'bar', I18n.t(nil, :default => :'foo.bar') + end + + test "defaults: given a symbol as a default and a scope it stays inside the scope when looking up the symbol" do + assert_equal 'bar', I18n.t(:missing, :default => :bar, :scope => :foo) + end + + test "defaults: given an array as a default it returns the first match" do + assert_equal 'bar', I18n.t(:does_not_exist, :default => [:does_not_exist_2, :'foo.bar']) + end + + test "defaults: given an array as a default with false it returns false" do + assert_equal false, I18n.t(:does_not_exist, :default => [false]) + end + + test "defaults: given false it returns false" do + assert_equal false, I18n.t(:does_not_exist, :default => false) + end + + test "defaults: given nil it returns nil" do + assert_nil I18n.t(:does_not_exist, :default => nil) + end + + test "defaults: given an array of missing keys it raises a MissingTranslationData exception" do + assert_raises I18n::MissingTranslationData do + I18n.t(:does_not_exist, :default => [:does_not_exist_2, :does_not_exist_3], :raise => true) + end + end + + test "defaults: using a custom scope separator" do + # data must have been stored using the custom separator when using the ActiveRecord backend + I18n.backend.store_translations(:en, { :foo => { :bar => 'bar' } }, { :separator => '|' }) + assert_equal 'bar', I18n.t(nil, :default => :'foo|bar', :separator => '|') + end + + # Addresses issue: #599 + test "defaults: only interpolates once when resolving defaults" do + I18n.backend.store_translations(:en, :greeting => 'hey %{name}') + assert_equal 'hey %{dont_interpolate_me}', + I18n.t(:does_not_exist, :name => '%{dont_interpolate_me}', default: [:greeting]) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/interpolation.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/interpolation.rb new file mode 100644 index 00000000..03c67db7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/interpolation.rb @@ -0,0 +1,185 @@ +# encoding: utf-8 + +module I18n + module Tests + module Interpolation + # If no interpolation parameter is not given, I18n should not alter the string. + # This behavior is due to three reasons: + # + # * Checking interpolation keys in all strings hits performance, badly; + # + # * This allows us to retrieve untouched values through I18n. For example + # I could have a middleware that returns I18n lookup results in JSON + # to be processed through Javascript. Leaving the keys untouched allows + # the interpolation to happen at the javascript level; + # + # * Security concerns: if I allow users to translate a web site, they can + # insert %{} in messages causing the I18n lookup to fail in every request. + # + test "interpolation: given no values it does not alter the string" do + assert_equal 'Hi %{name}!', interpolate(:default => 'Hi %{name}!') + end + + test "interpolation: given values it interpolates them into the string" do + assert_equal 'Hi David!', interpolate(:default => 'Hi %{name}!', :name => 'David') + end + + test "interpolation: works with a pipe" do + assert_equal 'Hi david!', interpolate(:default => 'Hi %{name|lowercase}!', :'name|lowercase' => 'david') + end + + test "interpolation: given a nil value it still interpolates it into the string" do + assert_equal 'Hi !', interpolate(:default => 'Hi %{name}!', :name => nil) + end + + test "interpolation: given a lambda as a value it calls it if the string contains the key" do + assert_equal 'Hi David!', interpolate(:default => 'Hi %{name}!', :name => lambda { |*args| 'David' }) + end + + test "interpolation: given a lambda as a value it does not call it if the string does not contain the key" do + assert_nothing_raised { interpolate(:default => 'Hi!', :name => lambda { |*args| raise 'fail' }) } + end + + test "interpolation: given values but missing a key it raises I18n::MissingInterpolationArgument" do + assert_raises(I18n::MissingInterpolationArgument) do + interpolate(:default => '%{foo}', :bar => 'bar') + end + end + + test "interpolation: it does not raise I18n::MissingInterpolationArgument for escaped variables" do + assert_nothing_raised do + assert_equal 'Barr %{foo}', interpolate(:default => '%{bar} %%{foo}', :bar => 'Barr') + end + end + + test "interpolation: it does not change the original, stored translation string" do + I18n.backend.store_translations(:en, :interpolate => 'Hi %{name}!') + assert_equal 'Hi David!', interpolate(:interpolate, :name => 'David') + assert_equal 'Hi Yehuda!', interpolate(:interpolate, :name => 'Yehuda') + end + + test "interpolation: given an array interpolates each element" do + I18n.backend.store_translations(:en, :array_interpolate => ['Hi', 'Mr. %{name}', 'or sir %{name}']) + assert_equal ['Hi', 'Mr. Bartuz', 'or sir Bartuz'], interpolate(:array_interpolate, :name => 'Bartuz') + end + + test "interpolation: given the translation is in utf-8 it still works" do + assert_equal 'Häi David!', interpolate(:default => 'Häi %{name}!', :name => 'David') + end + + test "interpolation: given the value is in utf-8 it still works" do + assert_equal 'Hi ゆきひろ!', interpolate(:default => 'Hi %{name}!', :name => 'ゆきひろ') + end + + test "interpolation: given the translation and the value are in utf-8 it still works" do + assert_equal 'こんにちは、ゆきひろさん!', interpolate(:default => 'こんにちは、%{name}さん!', :name => 'ゆきひろ') + end + + if Object.const_defined?(:Encoding) + test "interpolation: given a euc-jp translation and a utf-8 value it raises Encoding::CompatibilityError" do + assert_raises(Encoding::CompatibilityError) do + interpolate(:default => euc_jp('こんにちは、%{name}さん!'), :name => 'ゆきひろ') + end + end + + test "interpolation: given a utf-8 translation and a euc-jp value it raises Encoding::CompatibilityError" do + assert_raises(Encoding::CompatibilityError) do + interpolate(:default => 'こんにちは、%{name}さん!', :name => euc_jp('ゆきひろ')) + end + end + + test "interpolation: ASCII strings in the backend should be encoded to UTF8 if interpolation options are in UTF8" do + I18n.backend.store_translations 'en', 'encoding' => ('%{who} let me go'.force_encoding("ASCII")) + result = I18n.t 'encoding', :who => "måmmå miå" + assert_equal Encoding::UTF_8, result.encoding + end + + test "interpolation: UTF8 strings in the backend are still returned as UTF8 with ASCII interpolation" do + I18n.backend.store_translations 'en', 'encoding' => 'måmmå miå %{what}' + result = I18n.t 'encoding', :what => 'let me go'.force_encoding("ASCII") + assert_equal Encoding::UTF_8, result.encoding + end + + test "interpolation: UTF8 strings in the backend are still returned as UTF8 even with numbers interpolation" do + I18n.backend.store_translations 'en', 'encoding' => '%{count} times: måmmå miå' + result = I18n.t 'encoding', :count => 3 + assert_equal Encoding::UTF_8, result.encoding + end + end + + test "interpolation: given a translations containing a reserved key it raises I18n::ReservedInterpolationKey" do + assert_raises(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{exception_handler}') } + assert_raises(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{default}') } + assert_raises(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{separator}') } + assert_raises(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{scope}') } + assert_raises(I18n::ReservedInterpolationKey) { interpolate(:default => '%{scope}') } + + I18n.backend.store_translations(:en, :interpolate => 'Hi %{scope}!') + assert_raises(I18n::ReservedInterpolationKey) { interpolate(:interpolate) } + end + + test "interpolation: it does not raise I18n::ReservedInterpolationKey for escaped variables" do + assert_nothing_raised do + assert_equal '%{separator}', interpolate(:foo => :bar, :default => '%%{separator}') + end + + # Note: The two interpolations below do not remove the escape character (%) because + # I18n should not alter the strings when no interpolation parameters are given, + # see the comment at the top of this file. + assert_nothing_raised do + assert_equal '%%{scope}', interpolate(:default => '%%{scope}') + end + + I18n.backend.store_translations(:en, :interpolate => 'Hi %%{scope}!') + assert_nothing_raised do + assert_equal 'Hi %%{scope}!', interpolate(:interpolate) + end + end + + test "interpolation: deep interpolation for default string" do + assert_equal 'Hi %{name}!', interpolate(:default => 'Hi %{name}!', :deep_interpolation => true) + end + + test "interpolation: deep interpolation for interpolated string" do + assert_equal 'Hi Ann!', interpolate(:default => 'Hi %{name}!', :name => 'Ann', :deep_interpolation => true) + end + + test "interpolation: deep interpolation for Hash" do + people = { :people => { :ann => 'Ann is %{ann}', :john => 'John is %{john}' } } + interpolated_people = { :people => { :ann => 'Ann is good', :john => 'John is big' } } + assert_equal interpolated_people, interpolate(:default => people, :ann => 'good', :john => 'big', :deep_interpolation => true) + end + + test "interpolation: deep interpolation for Array" do + people = { :people => ['Ann is %{ann}', 'John is %{john}'] } + interpolated_people = { :people => ['Ann is good', 'John is big'] } + assert_equal interpolated_people, interpolate(:default => people, :ann => 'good', :john => 'big', :deep_interpolation => true) + end + + protected + + def capture(stream) + begin + stream = stream.to_s + eval "$#{stream} = StringIO.new" + yield + result = eval("$#{stream}").string + ensure + eval("$#{stream} = #{stream.upcase}") + end + + result + end + + def euc_jp(string) + string.encode!(Encoding::EUC_JP) + end + + def interpolate(*args) + options = args.last.is_a?(Hash) ? args.pop : {} + key = args.pop + I18n.backend.translate('en', key, options) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/link.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/link.rb new file mode 100644 index 00000000..d2f20e80 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/link.rb @@ -0,0 +1,66 @@ +# encoding: utf-8 + +module I18n + module Tests + module Link + test "linked lookup: if a key resolves to a symbol it looks up the symbol" do + I18n.backend.store_translations 'en', { + :link => :linked, + :linked => 'linked' + } + assert_equal 'linked', I18n.backend.translate('en', :link) + end + + test "linked lookup: if a key resolves to a dot-separated symbol it looks up the symbol" do + I18n.backend.store_translations 'en', { + :link => :"foo.linked", + :foo => { :linked => 'linked' } + } + assert_equal('linked', I18n.backend.translate('en', :link)) + end + + test "linked lookup: if a dot-separated key resolves to a symbol it looks up the symbol" do + I18n.backend.store_translations 'en', { + :foo => { :link => :linked }, + :linked => 'linked' + } + assert_equal('linked', I18n.backend.translate('en', :'foo.link')) + end + + test "linked lookup: if a dot-separated key resolves to a dot-separated symbol it looks up the symbol" do + I18n.backend.store_translations 'en', { + :foo => { :link => :"bar.linked" }, + :bar => { :linked => 'linked' } + } + assert_equal('linked', I18n.backend.translate('en', :'foo.link')) + end + + test "linked lookup: links always refer to the absolute key" do + I18n.backend.store_translations 'en', { + :foo => { :link => :linked, :linked => 'linked in foo' }, + :linked => 'linked absolutely' + } + assert_equal 'linked absolutely', I18n.backend.translate('en', :link, :scope => :foo) + end + + test "linked lookup: a link can resolve to a namespace in the middle of a dot-separated key" do + I18n.backend.store_translations 'en', { + :activemodel => { :errors => { :messages => { :blank => "can't be blank" } } }, + :activerecord => { :errors => { :messages => :"activemodel.errors.messages" } } + } + assert_equal "can't be blank", I18n.t(:"activerecord.errors.messages.blank") + assert_equal "can't be blank", I18n.t(:"activerecord.errors.messages.blank") + end + + test "linked lookup: a link can resolve with option :count" do + I18n.backend.store_translations 'en', { + :counter => :counted, + :counted => { :foo => { :one => "one", :other => "other" }, :bar => "bar" } + } + assert_equal "one", I18n.t(:'counter.foo', count: 1) + assert_equal "other", I18n.t(:'counter.foo', count: 2) + assert_equal "bar", I18n.t(:'counter.bar', count: 3) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/localization.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/localization.rb new file mode 100644 index 00000000..53b15029 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/localization.rb @@ -0,0 +1,19 @@ +module I18n + module Tests + module Localization + autoload :Date, 'i18n/tests/localization/date' + autoload :DateTime, 'i18n/tests/localization/date_time' + autoload :Time, 'i18n/tests/localization/time' + autoload :Procs, 'i18n/tests/localization/procs' + + def self.included(base) + base.class_eval do + include I18n::Tests::Localization::Date + include I18n::Tests::Localization::DateTime + include I18n::Tests::Localization::Procs + include I18n::Tests::Localization::Time + end + end + end + end +end \ No newline at end of file diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/localization/date.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/localization/date.rb new file mode 100644 index 00000000..c21fbbf3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/localization/date.rb @@ -0,0 +1,122 @@ +# encoding: utf-8 + +module I18n + module Tests + module Localization + module Date + def setup + super + setup_date_translations + @date = ::Date.new(2008, 3, 1) + end + + test "localize Date: given the short format it uses it" do + assert_equal '01. Mär', I18n.l(@date, :format => :short, :locale => :de) + end + + test "localize Date: given the long format it uses it" do + assert_equal '01. März 2008', I18n.l(@date, :format => :long, :locale => :de) + end + + test "localize Date: given the default format it uses it" do + assert_equal '01.03.2008', I18n.l(@date, :format => :default, :locale => :de) + end + + test "localize Date: given a day name format it returns the correct day name" do + assert_equal 'Samstag', I18n.l(@date, :format => '%A', :locale => :de) + end + + test "localize Date: given a uppercased day name format it returns the correct day name in upcase" do + assert_equal 'samstag'.upcase, I18n.l(@date, :format => '%^A', :locale => :de) + end + + test "localize Date: given an abbreviated day name format it returns the correct abbreviated day name" do + assert_equal 'Sa', I18n.l(@date, :format => '%a', :locale => :de) + end + + test "localize Date: given an meridian indicator format it returns the correct meridian indicator" do + assert_equal 'AM', I18n.l(@date, :format => '%p', :locale => :de) + assert_equal 'am', I18n.l(@date, :format => '%P', :locale => :de) + end + + test "localize Date: given an abbreviated and uppercased day name format it returns the correct abbreviated day name in upcase" do + assert_equal 'sa'.upcase, I18n.l(@date, :format => '%^a', :locale => :de) + end + + test "localize Date: given a month name format it returns the correct month name" do + assert_equal 'März', I18n.l(@date, :format => '%B', :locale => :de) + end + + test "localize Date: given a uppercased month name format it returns the correct month name in upcase" do + assert_equal 'märz'.upcase, I18n.l(@date, :format => '%^B', :locale => :de) + end + + test "localize Date: given an abbreviated month name format it returns the correct abbreviated month name" do + assert_equal 'Mär', I18n.l(@date, :format => '%b', :locale => :de) + end + + test "localize Date: given an abbreviated and uppercased month name format it returns the correct abbreviated month name in upcase" do + assert_equal 'mär'.upcase, I18n.l(@date, :format => '%^b', :locale => :de) + end + + test "localize Date: given a date format with the month name upcased it returns the correct value" do + assert_equal '1. FEBRUAR 2008', I18n.l(::Date.new(2008, 2, 1), :format => "%-d. %^B %Y", :locale => :de) + end + + test "localize Date: given missing translations it returns the correct error message" do + assert_equal 'Translation missing: fr.date.abbr_month_names', I18n.l(@date, :format => '%b', :locale => :fr) + end + + test "localize Date: given an unknown format it does not fail" do + assert_nothing_raised { I18n.l(@date, :format => '%x') } + end + + test "localize Date: does not modify the options hash" do + options = { :format => '%b', :locale => :de } + assert_equal 'Mär', I18n.l(@date, **options) + assert_equal({ :format => '%b', :locale => :de }, options) + assert_nothing_raised { I18n.l(@date, **options.freeze) } + end + + test "localize Date: given nil with default value it returns default" do + assert_equal 'default', I18n.l(nil, :default => 'default') + end + + test "localize Date: given nil it raises I18n::ArgumentError" do + assert_raises(I18n::ArgumentError) { I18n.l(nil) } + end + + test "localize Date: given a plain Object it raises I18n::ArgumentError" do + assert_raises(I18n::ArgumentError) { I18n.l(Object.new) } + end + + test "localize Date: given a format is missing it raises I18n::MissingTranslationData" do + assert_raises(I18n::MissingTranslationData) { I18n.l(@date, :format => :missing) } + end + + test "localize Date: it does not alter the format string" do + assert_equal '01. Februar 2009', I18n.l(::Date.parse('2009-02-01'), :format => :long, :locale => :de) + assert_equal '01. Oktober 2009', I18n.l(::Date.parse('2009-10-01'), :format => :long, :locale => :de) + end + + protected + + def setup_date_translations + I18n.backend.store_translations :de, { + :date => { + :formats => { + :default => "%d.%m.%Y", + :short => "%d. %b", + :long => "%d. %B %Y", + }, + :day_names => %w(Sonntag Montag Dienstag Mittwoch Donnerstag Freitag Samstag), + :abbr_day_names => %w(So Mo Di Mi Do Fr Sa), + :month_names => %w(Januar Februar März April Mai Juni Juli August September Oktober November Dezember).unshift(nil), + :abbr_month_names => %w(Jan Feb Mär Apr Mai Jun Jul Aug Sep Okt Nov Dez).unshift(nil) + } + } + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/localization/date_time.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/localization/date_time.rb new file mode 100644 index 00000000..b5d3527d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/localization/date_time.rb @@ -0,0 +1,103 @@ +# encoding: utf-8 + +module I18n + module Tests + module Localization + module DateTime + def setup + super + setup_datetime_translations + @datetime = ::DateTime.new(2008, 3, 1, 6) + @other_datetime = ::DateTime.new(2008, 3, 1, 18) + end + + test "localize DateTime: given the short format it uses it" do + assert_equal '01. Mär 06:00', I18n.l(@datetime, :format => :short, :locale => :de) + end + + test "localize DateTime: given the long format it uses it" do + assert_equal '01. März 2008 06:00', I18n.l(@datetime, :format => :long, :locale => :de) + end + + test "localize DateTime: given the default format it uses it" do + assert_equal 'Sa, 01. Mär 2008 06:00:00 +0000', I18n.l(@datetime, :format => :default, :locale => :de) + end + + test "localize DateTime: given a day name format it returns the correct day name" do + assert_equal 'Samstag', I18n.l(@datetime, :format => '%A', :locale => :de) + end + + test "localize DateTime: given a uppercased day name format it returns the correct day name in upcase" do + assert_equal 'samstag'.upcase, I18n.l(@datetime, :format => '%^A', :locale => :de) + end + + test "localize DateTime: given an abbreviated day name format it returns the correct abbreviated day name" do + assert_equal 'Sa', I18n.l(@datetime, :format => '%a', :locale => :de) + end + + test "localize DateTime: given an abbreviated and uppercased day name format it returns the correct abbreviated day name in upcase" do + assert_equal 'sa'.upcase, I18n.l(@datetime, :format => '%^a', :locale => :de) + end + + test "localize DateTime: given a month name format it returns the correct month name" do + assert_equal 'März', I18n.l(@datetime, :format => '%B', :locale => :de) + end + + test "localize DateTime: given a uppercased month name format it returns the correct month name in upcase" do + assert_equal 'märz'.upcase, I18n.l(@datetime, :format => '%^B', :locale => :de) + end + + test "localize DateTime: given an abbreviated month name format it returns the correct abbreviated month name" do + assert_equal 'Mär', I18n.l(@datetime, :format => '%b', :locale => :de) + end + + test "localize DateTime: given an abbreviated and uppercased month name format it returns the correct abbreviated month name in upcase" do + assert_equal 'mär'.upcase, I18n.l(@datetime, :format => '%^b', :locale => :de) + end + + test "localize DateTime: given a date format with the month name upcased it returns the correct value" do + assert_equal '1. FEBRUAR 2008', I18n.l(::DateTime.new(2008, 2, 1, 6), :format => "%-d. %^B %Y", :locale => :de) + end + + test "localize DateTime: given missing translations it returns the correct error message" do + assert_equal 'Translation missing: fr.date.abbr_month_names', I18n.l(@datetime, :format => '%b', :locale => :fr) + end + + test "localize DateTime: given a meridian indicator format it returns the correct meridian indicator" do + assert_equal 'AM', I18n.l(@datetime, :format => '%p', :locale => :de) + assert_equal 'PM', I18n.l(@other_datetime, :format => '%p', :locale => :de) + end + + test "localize DateTime: given a meridian indicator format it returns the correct meridian indicator in downcase" do + assert_equal 'am', I18n.l(@datetime, :format => '%P', :locale => :de) + assert_equal 'pm', I18n.l(@other_datetime, :format => '%P', :locale => :de) + end + + test "localize DateTime: given an unknown format it does not fail" do + assert_nothing_raised { I18n.l(@datetime, :format => '%x') } + end + + test "localize DateTime: given a format is missing it raises I18n::MissingTranslationData" do + assert_raises(I18n::MissingTranslationData) { I18n.l(@datetime, :format => :missing) } + end + + protected + + def setup_datetime_translations + # time translations might have been set up in Tests::Api::Localization::Time + I18n.backend.store_translations :de, { + :time => { + :formats => { + :default => "%a, %d. %b %Y %H:%M:%S %z", + :short => "%d. %b %H:%M", + :long => "%d. %B %Y %H:%M" + }, + :am => 'am', + :pm => 'pm' + } + } + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/localization/procs.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/localization/procs.rb new file mode 100644 index 00000000..2c5d8e15 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/localization/procs.rb @@ -0,0 +1,118 @@ +# encoding: utf-8 + +module I18n + module Tests + module Localization + module Procs + test "localize: using day names from lambdas" do + setup_time_proc_translations + time = ::Time.utc(2008, 3, 1, 6, 0) + assert_match(/Суббота/, I18n.l(time, :format => "%A, %d %B", :locale => :ru)) + assert_match(/суббота/, I18n.l(time, :format => "%d %B (%A)", :locale => :ru)) + end + + test "localize: using month names from lambdas" do + setup_time_proc_translations + time = ::Time.utc(2008, 3, 1, 6, 0) + assert_match(/марта/, I18n.l(time, :format => "%d %B %Y", :locale => :ru)) + assert_match(/Март /, I18n.l(time, :format => "%B %Y", :locale => :ru)) + end + + test "localize: using abbreviated day names from lambdas" do + setup_time_proc_translations + time = ::Time.utc(2008, 3, 1, 6, 0) + assert_match(/марта/, I18n.l(time, :format => "%d %b %Y", :locale => :ru)) + assert_match(/март /, I18n.l(time, :format => "%b %Y", :locale => :ru)) + end + + test "localize Date: given a format that resolves to a Proc it calls the Proc with the object" do + setup_time_proc_translations + date = ::Date.new(2008, 3, 1) + assert_equal '[Sat, 01 Mar 2008, {}]', I18n.l(date, :format => :proc, :locale => :ru) + end + + test "localize Date: given a format that resolves to a Proc it calls the Proc with the object and extra options" do + setup_time_proc_translations + date = ::Date.new(2008, 3, 1) + assert_equal %|[Sat, 01 Mar 2008, #{{:foo=>"foo"}}]|, I18n.l(date, :format => :proc, :foo => 'foo', :locale => :ru) + end + + test "localize DateTime: given a format that resolves to a Proc it calls the Proc with the object" do + setup_time_proc_translations + datetime = ::DateTime.new(2008, 3, 1, 6) + assert_equal '[Sat, 01 Mar 2008 06:00:00 +00:00, {}]', I18n.l(datetime, :format => :proc, :locale => :ru) + end + + test "localize DateTime: given a format that resolves to a Proc it calls the Proc with the object and extra options" do + setup_time_proc_translations + datetime = ::DateTime.new(2008, 3, 1, 6) + assert_equal %|[Sat, 01 Mar 2008 06:00:00 +00:00, #{{:foo=>"foo"}}]|, I18n.l(datetime, :format => :proc, :foo => 'foo', :locale => :ru) + end + + test "localize Time: given a format that resolves to a Proc it calls the Proc with the object" do + setup_time_proc_translations + time = ::Time.utc(2008, 3, 1, 6, 0) + assert_equal I18n::Tests::Localization::Procs.inspect_args([time], {}), I18n.l(time, :format => :proc, :locale => :ru) + end + + test "localize Time: given a format that resolves to a Proc it calls the Proc with the object and extra options" do + setup_time_proc_translations + time = ::Time.utc(2008, 3, 1, 6, 0) + options = { :foo => 'foo' } + assert_equal I18n::Tests::Localization::Procs.inspect_args([time], options), I18n.l(time, **options.merge(:format => :proc, :locale => :ru)) + end + + protected + + def self.inspect_args(args, kwargs) + args << kwargs + args = args.map do |arg| + case arg + when ::Time, ::DateTime + arg.strftime('%a, %d %b %Y %H:%M:%S %Z').sub('+0000', '+00:00') + when ::Date + arg.strftime('%a, %d %b %Y') + when Hash + arg.delete(:fallback_in_progress) + arg.delete(:fallback_original_locale) + arg.inspect + else + arg.inspect + end + end + "[#{args.join(', ')}]" + end + + def setup_time_proc_translations + I18n.backend.store_translations :ru, { + :time => { + :formats => { + :proc => lambda { |*args, **kwargs| I18n::Tests::Localization::Procs.inspect_args(args, kwargs) } + } + }, + :date => { + :formats => { + :proc => lambda { |*args, **kwargs| I18n::Tests::Localization::Procs.inspect_args(args, kwargs) } + }, + :'day_names' => lambda { |key, options| + (options[:format] =~ /^%A/) ? + %w(Воскресенье Понедельник Вторник Среда Четверг Пятница Суббота) : + %w(воскресенье понедельник вторник среда четверг пятница суббота) + }, + :'month_names' => lambda { |key, options| + (options[:format] =~ /(%d|%e)(\s*)?(%B)/) ? + %w(января февраля марта апреля мая июня июля августа сентября октября ноября декабря).unshift(nil) : + %w(Январь Февраль Март Апрель Май Июнь Июль Август Сентябрь Октябрь Ноябрь Декабрь).unshift(nil) + }, + :'abbr_month_names' => lambda { |key, options| + (options[:format] =~ /(%d|%e)(\s*)(%b)/) ? + %w(янв. февр. марта апр. мая июня июля авг. сент. окт. нояб. дек.).unshift(nil) : + %w(янв. февр. март апр. май июнь июль авг. сент. окт. нояб. дек.).unshift(nil) + }, + } + } + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/localization/time.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/localization/time.rb new file mode 100644 index 00000000..456a7602 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/localization/time.rb @@ -0,0 +1,103 @@ +# encoding: utf-8 + +module I18n + module Tests + module Localization + module Time + def setup + super + setup_time_translations + @time = ::Time.utc(2008, 3, 1, 6, 0) + @other_time = ::Time.utc(2008, 3, 1, 18, 0) + end + + test "localize Time: given the short format it uses it" do + assert_equal '01. Mär 06:00', I18n.l(@time, :format => :short, :locale => :de) + end + + test "localize Time: given the long format it uses it" do + assert_equal '01. März 2008 06:00', I18n.l(@time, :format => :long, :locale => :de) + end + + # TODO Seems to break on Windows because ENV['TZ'] is ignored. What's a better way to do this? + # def test_localize_given_the_default_format_it_uses_it + # assert_equal 'Sa, 01. Mar 2008 06:00:00 +0000', I18n.l(@time, :format => :default, :locale => :de) + # end + + test "localize Time: given a day name format it returns the correct day name" do + assert_equal 'Samstag', I18n.l(@time, :format => '%A', :locale => :de) + end + + test "localize Time: given a uppercased day name format it returns the correct day name in upcase" do + assert_equal 'samstag'.upcase, I18n.l(@time, :format => '%^A', :locale => :de) + end + + test "localize Time: given an abbreviated day name format it returns the correct abbreviated day name" do + assert_equal 'Sa', I18n.l(@time, :format => '%a', :locale => :de) + end + + test "localize Time: given an abbreviated and uppercased day name format it returns the correct abbreviated day name in upcase" do + assert_equal 'sa'.upcase, I18n.l(@time, :format => '%^a', :locale => :de) + end + + test "localize Time: given a month name format it returns the correct month name" do + assert_equal 'März', I18n.l(@time, :format => '%B', :locale => :de) + end + + test "localize Time: given a uppercased month name format it returns the correct month name in upcase" do + assert_equal 'märz'.upcase, I18n.l(@time, :format => '%^B', :locale => :de) + end + + test "localize Time: given an abbreviated month name format it returns the correct abbreviated month name" do + assert_equal 'Mär', I18n.l(@time, :format => '%b', :locale => :de) + end + + test "localize Time: given an abbreviated and uppercased month name format it returns the correct abbreviated month name in upcase" do + assert_equal 'mär'.upcase, I18n.l(@time, :format => '%^b', :locale => :de) + end + + test "localize Time: given a date format with the month name upcased it returns the correct value" do + assert_equal '1. FEBRUAR 2008', I18n.l(::Time.utc(2008, 2, 1, 6, 0), :format => "%-d. %^B %Y", :locale => :de) + end + + test "localize Time: given missing translations it returns the correct error message" do + assert_equal 'Translation missing: fr.date.abbr_month_names', I18n.l(@time, :format => '%b', :locale => :fr) + end + + test "localize Time: given a meridian indicator format it returns the correct meridian indicator" do + assert_equal 'AM', I18n.l(@time, :format => '%p', :locale => :de) + assert_equal 'PM', I18n.l(@other_time, :format => '%p', :locale => :de) + end + + test "localize Time: given a meridian indicator format it returns the correct meridian indicator in upcase" do + assert_equal 'am', I18n.l(@time, :format => '%P', :locale => :de) + assert_equal 'pm', I18n.l(@other_time, :format => '%P', :locale => :de) + end + + test "localize Time: given an unknown format it does not fail" do + assert_nothing_raised { I18n.l(@time, :format => '%x') } + end + + test "localize Time: given a format is missing it raises I18n::MissingTranslationData" do + assert_raises(I18n::MissingTranslationData) { I18n.l(@time, :format => :missing) } + end + + protected + + def setup_time_translations + I18n.backend.store_translations :de, { + :time => { + :formats => { + :default => "%a, %d. %b %Y %H:%M:%S %z", + :short => "%d. %b %H:%M", + :long => "%d. %B %Y %H:%M", + }, + :am => 'am', + :pm => 'pm' + } + } + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/lookup.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/lookup.rb new file mode 100644 index 00000000..f1bee792 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/lookup.rb @@ -0,0 +1,87 @@ +# encoding: utf-8 + +module I18n + module Tests + module Lookup + def setup + super + I18n.backend.store_translations(:en, :foo => { :bar => 'bar', :baz => 'baz' }, :falsy => false, :truthy => true, + :string => "a", :array => %w(a b c), :hash => { "a" => "b" }) + end + + test "lookup: it returns a string" do + assert_equal("a", I18n.t(:string)) + end + + test "lookup: it returns hash" do + assert_equal({ :a => "b" }, I18n.t(:hash)) + end + + test "lookup: it returns an array" do + assert_equal(%w(a b c), I18n.t(:array)) + end + + test "lookup: it returns a native true" do + assert I18n.t(:truthy) === true + end + + test "lookup: it returns a native false" do + assert I18n.t(:falsy) === false + end + + test "lookup: given a missing key, no default and no raise option it returns an error message" do + assert_equal "Translation missing: en.missing", I18n.t(:missing) + end + + test "lookup: given a missing key, no default and the raise option it raises MissingTranslationData" do + assert_raises(I18n::MissingTranslationData) { I18n.t(:missing, :raise => true) } + end + + test "lookup: does not raise an exception if no translation data is present for the given locale" do + assert_nothing_raised { I18n.t(:foo, :locale => :xx) } + end + + test "lookup: does not modify the options hash" do + options = {} + assert_equal "a", I18n.t(:string, **options) + assert_equal({}, options) + assert_nothing_raised { I18n.t(:string, **options.freeze) } + end + + test "lookup: given an array of keys it translates all of them" do + assert_equal %w(bar baz), I18n.t([:bar, :baz], :scope => [:foo]) + end + + test "lookup: using a custom scope separator" do + # data must have been stored using the custom separator when using the ActiveRecord backend + I18n.backend.store_translations(:en, { :foo => { :bar => 'bar' } }, { :separator => '|' }) + assert_equal 'bar', I18n.t('foo|bar', :separator => '|') + end + + # In fact it probably *should* fail but Rails currently relies on using the default locale instead. + # So we'll stick to this for now until we get it fixed in Rails. + test "lookup: given nil as a locale it does not raise but use the default locale" do + # assert_raises(I18n::InvalidLocale) { I18n.t(:bar, :locale => nil) } + assert_nothing_raised { I18n.t(:bar, :locale => nil) } + end + + test "lookup: a resulting String is not frozen" do + assert !I18n.t(:string).frozen? + end + + test "lookup: a resulting Array is not frozen" do + assert !I18n.t(:array).frozen? + end + + test "lookup: a resulting Hash is not frozen" do + assert !I18n.t(:hash).frozen? + end + + # Addresses issue: #599 + test "lookup: only interpolates once when resolving symbols" do + I18n.backend.store_translations(:en, foo: :bar, bar: '%{value}') + assert_equal '%{dont_interpolate_me}', I18n.t(:foo, value: '%{dont_interpolate_me}') + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/pluralization.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/pluralization.rb new file mode 100644 index 00000000..19e73f37 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/pluralization.rb @@ -0,0 +1,35 @@ +# encoding: utf-8 + +module I18n + module Tests + module Pluralization + test "pluralization: given 0 it returns the :zero translation if it is defined" do + assert_equal 'zero', I18n.t(:default => { :zero => 'zero' }, :count => 0) + end + + test "pluralization: given 0 it returns the :other translation if :zero is not defined" do + assert_equal 'bars', I18n.t(:default => { :other => 'bars' }, :count => 0) + end + + test "pluralization: given 1 it returns the singular translation" do + assert_equal 'bar', I18n.t(:default => { :one => 'bar' }, :count => 1) + end + + test "pluralization: given 2 it returns the :other translation" do + assert_equal 'bars', I18n.t(:default => { :other => 'bars' }, :count => 2) + end + + test "pluralization: given 3 it returns the :other translation" do + assert_equal 'bars', I18n.t(:default => { :other => 'bars' }, :count => 3) + end + + test "pluralization: given nil it returns the whole entry" do + assert_equal({ :one => 'bar' }, I18n.t(:default => { :one => 'bar' }, :count => nil)) + end + + test "pluralization: given incomplete pluralization data it raises I18n::InvalidPluralizationData" do + assert_raises(I18n::InvalidPluralizationData) { I18n.t(:default => { :one => 'bar' }, :count => 2) } + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/procs.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/procs.rb new file mode 100644 index 00000000..d377117d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/tests/procs.rb @@ -0,0 +1,71 @@ +# encoding: utf-8 + +module I18n + module Tests + module Procs + test "lookup: given a translation is a proc it calls the proc with the key and interpolation values" do + I18n.backend.store_translations(:en, :a_lambda => lambda { |*args| I18n::Tests::Procs.filter_args(*args) }) + assert_equal %|[:a_lambda, #{{:foo=>"foo"}}]|, I18n.t(:a_lambda, :foo => 'foo') + end + + test "lookup: given a translation is a proc it passes the interpolation values as keyword arguments" do + I18n.backend.store_translations(:en, :a_lambda => lambda { |key, foo:, **| I18n::Tests::Procs.filter_args(key, foo: foo) }) + assert_equal %|[:a_lambda, #{{:foo=>"foo"}}]|, I18n.t(:a_lambda, :foo => 'foo') + end + + test "defaults: given a default is a Proc it calls it with the key and interpolation values" do + proc = lambda { |*args| I18n::Tests::Procs.filter_args(*args) } + assert_equal %|[nil, #{{:foo=>"foo"}}]|, I18n.t(nil, :default => proc, :foo => 'foo') + end + + test "defaults: given a default is a key that resolves to a Proc it calls it with the key and interpolation values" do + the_lambda = lambda { |*args| I18n::Tests::Procs.filter_args(*args) } + I18n.backend.store_translations(:en, :a_lambda => the_lambda) + assert_equal %|[:a_lambda, #{{:foo=>"foo"}}]|, I18n.t(nil, :default => :a_lambda, :foo => 'foo') + assert_equal %|[:a_lambda, #{{:foo=>"foo"}}]|, I18n.t(nil, :default => [nil, :a_lambda], :foo => 'foo') + end + + test "interpolation: given an interpolation value is a lambda it calls it with key and values before interpolating it" do + proc = lambda { |*args| I18n::Tests::Procs.filter_args(*args) } + if RUBY_VERSION < "3.4" + assert_match %r(\[\{:foo=>#\}\]), I18n.t(nil, :default => '%{foo}', :foo => proc) + else + assert_match %r(\[\{foo: #\}\]), I18n.t(nil, :default => '%{foo}', :foo => proc) + end + end + + test "interpolation: given a key resolves to a Proc that returns a string then interpolation still works" do + proc = lambda { |*args| "%{foo}: " + I18n::Tests::Procs.filter_args(*args) } + assert_equal %|foo: [nil, #{{:foo=>"foo"}}]|, I18n.t(nil, :default => proc, :foo => 'foo') + end + + test "pluralization: given a key resolves to a Proc that returns valid data then pluralization still works" do + proc = lambda { |*args| { :zero => 'zero', :one => 'one', :other => 'other' } } + assert_equal 'zero', I18n.t(:default => proc, :count => 0) + assert_equal 'one', I18n.t(:default => proc, :count => 1) + assert_equal 'other', I18n.t(:default => proc, :count => 2) + end + + test "lookup: given the option :resolve => false was passed it does not resolve proc translations" do + I18n.backend.store_translations(:en, :a_lambda => lambda { |*args| I18n::Tests::Procs.filter_args(*args) }) + assert_equal Proc, I18n.t(:a_lambda, :resolve => false).class + end + + test "lookup: given the option :resolve => false was passed it does not resolve proc default" do + assert_equal Proc, I18n.t(nil, :default => lambda { |*args| I18n::Tests::Procs.filter_args(*args) }, :resolve => false).class + end + + + def self.filter_args(*args) + args.map do |arg| + if arg.is_a?(Hash) + arg.delete(:fallback_in_progress) + arg.delete(:fallback_original_locale) + arg.delete(:skip_interpolation) + end + arg + end.inspect + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/utils.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/utils.rb new file mode 100644 index 00000000..88415615 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/utils.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module I18n + module Utils + class << self + if Hash.method_defined?(:except) + def except(hash, *keys) + hash.except(*keys) + end + else + def except(hash, *keys) + hash = hash.dup + keys.each { |k| hash.delete(k) } + hash + end + end + + def deep_merge(hash, other_hash, &block) + deep_merge!(hash.dup, other_hash, &block) + end + + def deep_merge!(hash, other_hash, &block) + hash.merge!(other_hash) do |key, this_val, other_val| + if this_val.is_a?(Hash) && other_val.is_a?(Hash) + deep_merge(this_val, other_val, &block) + elsif block_given? + yield key, this_val, other_val + else + other_val + end + end + end + + def deep_symbolize_keys(hash) + hash.each_with_object({}) do |(key, value), result| + result[key.respond_to?(:to_sym) ? key.to_sym : key] = deep_symbolize_keys_in_object(value) + result + end + end + + private + + def deep_symbolize_keys_in_object(value) + case value + when Hash + deep_symbolize_keys(value) + when Array + value.map { |e| deep_symbolize_keys_in_object(e) } + else + value + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/version.rb b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/version.rb new file mode 100644 index 00000000..bdeb7172 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/i18n-1.14.7/lib/i18n/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module I18n + VERSION = "1.14.7" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/.document b/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/.document new file mode 100644 index 00000000..e7488550 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/.document @@ -0,0 +1,5 @@ +LICENSE.txt +README.md +docs/ +ext/ +lib/io/console/size.rb diff --git a/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/BSDL b/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/BSDL new file mode 100644 index 00000000..66d93598 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/BSDL @@ -0,0 +1,22 @@ +Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/COPYING b/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/COPYING new file mode 100644 index 00000000..48e5a96d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/COPYING @@ -0,0 +1,56 @@ +Ruby is copyrighted free software by Yukihiro Matsumoto . +You can redistribute it and/or modify it under either the terms of the +2-clause BSDL (see the file BSDL), or the conditions below: + +1. You may make and give away verbatim copies of the source form of the + software without restriction, provided that you duplicate all of the + original copyright notices and associated disclaimers. + +2. You may modify your copy of the software in any way, provided that + you do at least ONE of the following: + + a. place your modifications in the Public Domain or otherwise + make them Freely Available, such as by posting said + modifications to Usenet or an equivalent medium, or by allowing + the author to include your modifications in the software. + + b. use the modified software only within your corporation or + organization. + + c. give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d. make other distribution arrangements with the author. + +3. You may distribute the software in object code or binary form, + provided that you do at least ONE of the following: + + a. distribute the binaries and library files of the software, + together with instructions (in the manual page or equivalent) + on where to get the original distribution. + + b. accompany the distribution with the machine-readable source of + the software. + + c. give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d. make other distribution arrangements with the author. + +4. You may modify and include the part of the software into any other + software (possibly commercial). But some files in the distribution + are not written by the author, so that they are not under these terms. + + For the list of those files and their copying conditions, see the + file LEGAL. + +5. The scripts and library files supplied as input to or produced as + output from the software do not automatically fall under the + copyright of the software, but belong to whomever generated them, + and may be sold commercially, and may be aggregated with this + software. + +6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. diff --git a/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/README.md b/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/README.md new file mode 100644 index 00000000..83262a3d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/README.md @@ -0,0 +1,46 @@ +# IO.console + +Add console capabilities to IO instances. + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'io-console' +``` + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install io-console + +## Usage + +```ruby +require 'io/console' + +IO.console -> # +IO.console(sym, *args) +``` + +Returns a File instance opened console. + +If `sym` is given, it will be sent to the opened console with `args` and the result will be returned instead of the console IO itself. + +## Development + +After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. + +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/io-console. + +## License + +The gem is available as open source under the terms of the [2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause). diff --git a/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/ext/io/console/Makefile b/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/ext/io/console/Makefile new file mode 100644 index 00000000..7d6f823e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/ext/io/console/Makefile @@ -0,0 +1,271 @@ + +SHELL = /bin/sh + +# V=0 quiet, V=1 verbose. other values don't work. +V = 0 +V0 = $(V:0=) +Q1 = $(V:1=) +Q = $(Q1:0=@) +ECHO1 = $(V:1=@ :) +ECHO = $(ECHO1:0=@ echo) +NULLCMD = : + +#### Start of system configuration section. #### + +srcdir = . +topdir = /usr/include/ruby-3.2.0 +hdrdir = $(topdir) +arch_hdrdir = /usr/include/x86_64-linux-gnu/ruby-3.2.0 +PATH_SEPARATOR = : +VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby +prefix = $(DESTDIR)/usr +rubysitearchprefix = $(sitearchlibdir)/$(RUBY_BASE_NAME) +rubyarchprefix = $(archlibdir)/$(RUBY_BASE_NAME) +rubylibprefix = $(libdir)/$(RUBY_BASE_NAME) +exec_prefix = $(prefix) +vendorarchhdrdir = $(sitearchincludedir)/$(RUBY_VERSION_NAME)/vendor_ruby +sitearchhdrdir = $(sitearchincludedir)/$(RUBY_VERSION_NAME)/site_ruby +rubyarchhdrdir = $(archincludedir)/$(RUBY_VERSION_NAME) +vendorhdrdir = $(rubyhdrdir)/vendor_ruby +sitehdrdir = $(rubyhdrdir)/site_ruby +rubyhdrdir = $(includedir)/$(RUBY_VERSION_NAME) +vendorarchdir = $(rubysitearchprefix)/vendor_ruby/$(ruby_version) +vendorlibdir = $(vendordir)/$(ruby_version) +vendordir = $(rubylibprefix)/vendor_ruby +sitearchdir = $(DESTDIR)/usr/local/lib/x86_64-linux-gnu/site_ruby +sitelibdir = $(sitedir)/$(ruby_version) +sitedir = $(DESTDIR)/usr/local/lib/site_ruby +rubyarchdir = $(rubyarchprefix)/$(ruby_version) +rubylibdir = $(rubylibprefix)/$(ruby_version) +sitearchincludedir = $(includedir)/$(sitearch) +archincludedir = $(includedir)/$(arch) +sitearchlibdir = $(libdir)/$(sitearch) +archlibdir = $(libdir)/$(arch) +ridir = $(datarootdir)/$(RI_BASE_NAME) +mandir = $(datarootdir)/man +localedir = $(datarootdir)/locale +libdir = $(exec_prefix)/lib +psdir = $(docdir) +pdfdir = $(docdir) +dvidir = $(docdir) +htmldir = $(docdir) +infodir = $(datarootdir)/info +docdir = $(datarootdir)/doc/$(PACKAGE) +oldincludedir = $(DESTDIR)/usr/include +includedir = $(prefix)/include +runstatedir = $(DESTDIR)/var/run +localstatedir = $(DESTDIR)/var +sharedstatedir = $(prefix)/com +sysconfdir = $(DESTDIR)/etc +datadir = $(datarootdir) +datarootdir = $(prefix)/share +libexecdir = $(exec_prefix)/libexec +sbindir = $(exec_prefix)/sbin +bindir = $(exec_prefix)/bin +archdir = $(rubyarchdir) + + +CC_WRAPPER = +CC = x86_64-linux-gnu-gcc +CXX = x86_64-linux-gnu-g++ +LIBRUBY = $(LIBRUBY_SO) +LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a +LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME) +LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static $(MAINLIBS) +empty = +OUTFLAG = -o $(empty) +COUTFLAG = -o $(empty) +CSRCFLAG = $(empty) + +RUBY_EXTCONF_H = +cflags = $(optflags) $(debugflags) $(warnflags) +cxxflags = +optflags = -O3 -fno-fast-math +debugflags = -ggdb3 +warnflags = -Wall -Wextra -Wdeprecated-declarations -Wdiv-by-zero -Wduplicated-cond -Wimplicit-function-declaration -Wimplicit-int -Wmisleading-indentation -Wpointer-arith -Wwrite-strings -Wold-style-definition -Wimplicit-fallthrough=0 -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-packed-bitfield-compat -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wsuggest-attribute=format -Wsuggest-attribute=noreturn -Wunused-variable -Wundef +cppflags = +CCDLFLAGS = -fPIC +CFLAGS = $(CCDLFLAGS) -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 -fPIC $(ARCH_FLAG) +INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir) +DEFS = +CPPFLAGS = -DHAVE_RB_SYSERR_FAIL_STR -DHAVE_RB_INTERNED_STR_CSTR -DHAVE_RB_IO_DESCRIPTOR -DHAVE_RB_IO_GET_WRITE_IO -DHAVE_RB_RACTOR_LOCAL_STORAGE_VALUE_NEWKEY -DHAVE_TERMIOS_H -DHAVE_CFMAKERAW -DHAVE_SYS_IOCTL_H -DHAVE_RB_IO_WAIT=1 -DHAVE_TTYNAME_R -Wdate-time -D_FORTIFY_SOURCE=3 $(DEFS) $(cppflags) +CXXFLAGS = $(CCDLFLAGS) -g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=BUILDDIR=. -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=BUILDDIR=/usr/src/ruby3.2-3.2.3-1ubuntu0.24.04.5 $(ARCH_FLAG) +ldflags = -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed +dldflags = -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now +ARCH_FLAG = +DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG) +LDSHARED = $(CC) -shared +LDSHAREDXX = $(CXX) -shared +AR = x86_64-linux-gnu-gcc-ar +EXEEXT = + +RUBY_INSTALL_NAME = $(RUBY_BASE_NAME)3.2 +RUBY_SO_NAME = ruby-3.2 +RUBYW_INSTALL_NAME = +RUBY_VERSION_NAME = $(RUBY_BASE_NAME)-$(ruby_version) +RUBYW_BASE_NAME = rubyw +RUBY_BASE_NAME = ruby + +arch = x86_64-linux-gnu +sitearch = $(arch) +ruby_version = 3.2.0 +ruby = $(bindir)/$(RUBY_BASE_NAME)3.2 +RUBY = $(ruby) +BUILTRUBY = $(bindir)/$(RUBY_BASE_NAME)3.2 +ruby_headers = $(hdrdir)/ruby.h $(hdrdir)/ruby/backward.h $(hdrdir)/ruby/ruby.h $(hdrdir)/ruby/defines.h $(hdrdir)/ruby/missing.h $(hdrdir)/ruby/intern.h $(hdrdir)/ruby/st.h $(hdrdir)/ruby/subst.h $(arch_hdrdir)/ruby/config.h + +RM = rm -f +RM_RF = rm -fr +RMDIRS = rmdir --ignore-fail-on-non-empty -p +MAKEDIRS = /bin/mkdir -p +INSTALL = /usr/bin/install -c +INSTALL_PROG = $(INSTALL) -m 0755 +INSTALL_DATA = $(INSTALL) -m 644 +COPY = cp +TOUCH = exit > + +#### End of system configuration section. #### + +preload = +libpath = . $(archlibdir) +LIBPATH = -L. -L$(archlibdir) +DEFFILE = + +CLEANFILES = mkmf.log +DISTCLEANFILES = +DISTCLEANDIRS = + +extout = +extout_prefix = +target_prefix = /io +LOCAL_LIBS = +LIBS = $(LIBRUBYARG_SHARED) -lm -lpthread -lc +ORIG_SRCS = console.c +SRCS = $(ORIG_SRCS) +OBJS = console.o +HDRS = +LOCAL_HDRS = +TARGET = console +TARGET_NAME = console +TARGET_ENTRY = Init_$(TARGET_NAME) +DLLIB = $(TARGET).so +EXTSTATIC = +STATIC_LIB = + +TIMESTAMP_DIR = . +BINDIR = $(bindir) +RUBYCOMMONDIR = $(sitedir)$(target_prefix) +RUBYLIBDIR = $(sitelibdir)$(target_prefix) +RUBYARCHDIR = $(sitearchdir)$(target_prefix) +HDRDIR = $(sitehdrdir)$(target_prefix) +ARCHHDRDIR = $(sitearchhdrdir)$(target_prefix) +TARGET_SO_DIR = +TARGET_SO = $(TARGET_SO_DIR)$(DLLIB) +CLEANLIBS = $(TARGET_SO) false +CLEANOBJS = $(OBJS) *.bak +TARGET_SO_DIR_TIMESTAMP = $(TIMESTAMP_DIR)/.sitearchdir.-.io.time + +VK_HEADER = + +all: $(DLLIB) +static: $(STATIC_LIB) +.PHONY: all install static install-so install-rb +.PHONY: clean clean-so clean-static clean-rb + +clean-static:: +clean-rb-default:: +clean-rb:: +clean-so:: +clean: clean-so clean-static clean-rb-default clean-rb + -$(Q)$(RM_RF) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) .*.time + +distclean-rb-default:: +distclean-rb:: +distclean-so:: +distclean-static:: +distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb + -$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log + -$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES) + -$(Q)$(RMDIRS) $(DISTCLEANDIRS) 2> /dev/null || true + +realclean: distclean +install: install-so install-rb + +install-so: $(DLLIB) $(TARGET_SO_DIR_TIMESTAMP) + $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR) +clean-static:: + -$(Q)$(RM) $(STATIC_LIB) +install-rb: pre-install-rb do-install-rb install-rb-default +install-rb-default: pre-install-rb-default do-install-rb-default +pre-install-rb: Makefile +pre-install-rb-default: Makefile +do-install-rb: +do-install-rb-default: +pre-install-rb-default: + @$(NULLCMD) +$(TARGET_SO_DIR_TIMESTAMP): + $(Q) $(MAKEDIRS) $(@D) $(RUBYARCHDIR) + $(Q) $(TOUCH) $@ + +site-install: site-install-so site-install-rb +site-install-so: install-so +site-install-rb: install-rb + +.SUFFIXES: .c .m .cc .mm .cxx .cpp .o .S + +.cc.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cc.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.mm.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.mm.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.cxx.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cxx.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.cpp.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cpp.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.c.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.c.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.m.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.m.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +$(TARGET_SO): $(OBJS) Makefile + $(ECHO) linking shared-object io/$(DLLIB) + -$(Q)$(RM) $(@) + $(Q) $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS) + + + +$(OBJS): $(HDRS) $(ruby_headers) diff --git a/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/ext/io/console/console.c b/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/ext/io/console/console.c new file mode 100644 index 00000000..80c1cddd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/ext/io/console/console.c @@ -0,0 +1,1969 @@ +/* -*- c-file-style: "ruby"; indent-tabs-mode: t -*- */ +/* + * console IO module + */ + +static const char *const +IO_CONSOLE_VERSION = "0.8.0"; + +#include "ruby.h" +#include "ruby/io.h" +#include "ruby/thread.h" + +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif + +#if defined HAVE_TERMIOS_H +# include +typedef struct termios conmode; + +static int +setattr(int fd, conmode *t) +{ + while (tcsetattr(fd, TCSANOW, t)) { + if (errno != EINTR) return 0; + } + return 1; +} +# define getattr(fd, t) (tcgetattr(fd, t) == 0) +#elif defined HAVE_TERMIO_H +# include +typedef struct termio conmode; +# define setattr(fd, t) (ioctl(fd, TCSETAF, t) == 0) +# define getattr(fd, t) (ioctl(fd, TCGETA, t) == 0) +#elif defined HAVE_SGTTY_H +# include +typedef struct sgttyb conmode; +# ifdef HAVE_STTY +# define setattr(fd, t) (stty(fd, t) == 0) +# else +# define setattr(fd, t) (ioctl((fd), TIOCSETP, (t)) == 0) +# endif +# ifdef HAVE_GTTY +# define getattr(fd, t) (gtty(fd, t) == 0) +# else +# define getattr(fd, t) (ioctl((fd), TIOCGETP, (t)) == 0) +# endif +#elif defined _WIN32 +#include +#include +typedef DWORD conmode; + +#define LAST_ERROR rb_w32_map_errno(GetLastError()) +#define SET_LAST_ERROR (errno = LAST_ERROR, 0) + +static int +setattr(int fd, conmode *t) +{ + int x = SetConsoleMode((HANDLE)rb_w32_get_osfhandle(fd), *t); + if (!x) errno = LAST_ERROR; + return x; +} + +static int +getattr(int fd, conmode *t) +{ + int x = GetConsoleMode((HANDLE)rb_w32_get_osfhandle(fd), t); + if (!x) errno = LAST_ERROR; + return x; +} +#endif +#ifndef SET_LAST_ERROR +#define SET_LAST_ERROR (0) +#endif + +#define CSI "\x1b\x5b" + +static ID id_getc, id_close; +static ID id_gets, id_flush, id_chomp_bang; + +#ifndef HAVE_RB_INTERNED_STR_CSTR +# define rb_str_to_interned_str(str) rb_str_freeze(str) +# define rb_interned_str_cstr(str) rb_str_freeze(rb_usascii_str_new_cstr(str)) +#endif + +#if defined HAVE_RUBY_FIBER_SCHEDULER_H +# include "ruby/fiber/scheduler.h" +#elif defined HAVE_RB_SCHEDULER_TIMEOUT +extern VALUE rb_scheduler_timeout(struct timeval *timeout); +# define rb_fiber_scheduler_make_timeout rb_scheduler_timeout +#endif + +#ifndef HAVE_RB_IO_DESCRIPTOR +static int +io_descriptor_fallback(VALUE io) +{ + rb_io_t *fptr; + GetOpenFile(io, fptr); + return fptr->fd; +} +#define rb_io_descriptor io_descriptor_fallback +#endif + +#ifndef HAVE_RB_IO_PATH +static VALUE +io_path_fallback(VALUE io) +{ + rb_io_t *fptr; + GetOpenFile(io, fptr); + return fptr->pathv; +} +#define rb_io_path io_path_fallback +#endif + +#ifndef HAVE_RB_IO_GET_WRITE_IO +static VALUE +io_get_write_io_fallback(VALUE io) +{ + rb_io_t *fptr; + GetOpenFile(io, fptr); + VALUE wio = fptr->tied_io_for_writing; + return wio ? wio : io; +} +#define rb_io_get_write_io io_get_write_io_fallback +#endif + +#ifndef DHAVE_RB_SYSERR_FAIL_STR +# define rb_syserr_fail_str(e, mesg) rb_exc_raise(rb_syserr_new_str(e, mesg)) +#endif + +#define sys_fail(io) do { \ + int err = errno; \ + rb_syserr_fail_str(err, rb_io_path(io)); \ +} while (0) + +#ifndef HAVE_RB_F_SEND +#ifndef RB_PASS_CALLED_KEYWORDS +# define rb_funcallv_kw(recv, mid, arg, argv, kw_splat) rb_funcallv(recv, mid, arg, argv) +#endif + +static ID id___send__; + +static VALUE +rb_f_send(int argc, VALUE *argv, VALUE recv) +{ + VALUE sym = argv[0]; + ID vid = rb_check_id(&sym); + if (vid) { + --argc; + ++argv; + } + else { + vid = id___send__; + } + return rb_funcallv_kw(recv, vid, argc, argv, RB_PASS_CALLED_KEYWORDS); +} +#endif + +enum rawmode_opt_ids { + kwd_min, + kwd_time, + kwd_intr, + rawmode_opt_id_count +}; +static ID rawmode_opt_ids[rawmode_opt_id_count]; + +typedef struct { + int vmin; + int vtime; + int intr; +} rawmode_arg_t; + +#ifndef UNDEF_P +# define UNDEF_P(obj) ((obj) == Qundef) +#endif +#ifndef NIL_OR_UNDEF_P +# define NIL_OR_UNDEF_P(obj) (NIL_P(obj) || UNDEF_P(obj)) +#endif + +static rawmode_arg_t * +rawmode_opt(int *argcp, VALUE *argv, int min_argc, int max_argc, rawmode_arg_t *opts) +{ + int argc = *argcp; + rawmode_arg_t *optp = NULL; + VALUE vopts = Qnil; + VALUE optvals[rawmode_opt_id_count]; +#ifdef RB_SCAN_ARGS_PASS_CALLED_KEYWORDS + argc = rb_scan_args(argc, argv, "*:", NULL, &vopts); +#else + if (argc > min_argc) { + vopts = rb_check_hash_type(argv[argc-1]); + if (!NIL_P(vopts)) { + argv[argc-1] = vopts; + vopts = rb_extract_keywords(&argv[argc-1]); + if (!argv[argc-1]) *argcp = --argc; + if (!vopts) vopts = Qnil; + } + } +#endif + rb_check_arity(argc, min_argc, max_argc); + if (rb_get_kwargs(vopts, rawmode_opt_ids, + 0, rawmode_opt_id_count, optvals)) { + VALUE vmin = optvals[kwd_min]; + VALUE vtime = optvals[kwd_time]; + VALUE intr = optvals[kwd_intr]; + /* default values by `stty raw` */ + opts->vmin = 1; + opts->vtime = 0; + opts->intr = 0; + if (!NIL_OR_UNDEF_P(vmin)) { + opts->vmin = NUM2INT(vmin); + optp = opts; + } + if (!NIL_OR_UNDEF_P(vtime)) { + VALUE v10 = INT2FIX(10); + vtime = rb_funcall3(vtime, '*', 1, &v10); + opts->vtime = NUM2INT(vtime); + optp = opts; + } + switch (intr) { + case Qtrue: + opts->intr = 1; + optp = opts; + break; + case Qfalse: + opts->intr = 0; + optp = opts; + break; + case Qundef: + case Qnil: + break; + default: + rb_raise(rb_eArgError, "true or false expected as intr: %"PRIsVALUE, + intr); + } + } + return optp; +} + +static void +set_rawmode(conmode *t, void *arg) +{ +#ifdef HAVE_CFMAKERAW + cfmakeraw(t); + t->c_lflag &= ~(ECHOE|ECHOK); +#elif defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H + t->c_iflag &= ~(IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK|ISTRIP|INLCR|IGNCR|ICRNL|IXON|IXOFF|IXANY|IMAXBEL); + t->c_oflag &= ~OPOST; + t->c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|ICANON|ISIG|IEXTEN|XCASE); + t->c_cflag &= ~(CSIZE|PARENB); + t->c_cflag |= CS8; + t->c_cc[VMIN] = 1; + t->c_cc[VTIME] = 0; +#elif defined HAVE_SGTTY_H + t->sg_flags &= ~ECHO; + t->sg_flags |= RAW; +#elif defined _WIN32 + *t = 0; +#endif + if (arg) { + const rawmode_arg_t *r = arg; +#ifdef VMIN + if (r->vmin >= 0) t->c_cc[VMIN] = r->vmin; +#endif +#ifdef VTIME + if (r->vtime >= 0) t->c_cc[VTIME] = r->vtime; +#endif +#ifdef ISIG + if (r->intr) { + t->c_iflag |= BRKINT; + t->c_lflag |= ISIG; + t->c_oflag |= OPOST; + } +#endif + (void)r; + } +} + +static void +set_cookedmode(conmode *t, void *arg) +{ +#if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H + t->c_iflag |= (BRKINT|ISTRIP|ICRNL|IXON); + t->c_oflag |= OPOST; + t->c_lflag |= (ECHO|ECHOE|ECHOK|ECHONL|ICANON|ISIG|IEXTEN); +#elif defined HAVE_SGTTY_H + t->sg_flags |= ECHO; + t->sg_flags &= ~RAW; +#elif defined _WIN32 + *t |= ENABLE_ECHO_INPUT|ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT; +#endif +} + +static void +set_noecho(conmode *t, void *arg) +{ +#if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H + t->c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); +#elif defined HAVE_SGTTY_H + t->sg_flags &= ~ECHO; +#elif defined _WIN32 + *t &= ~ENABLE_ECHO_INPUT; +#endif +} + +static void +set_echo(conmode *t, void *arg) +{ +#if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H + t->c_lflag |= (ECHO | ECHOE | ECHOK | ECHONL); +#elif defined HAVE_SGTTY_H + t->sg_flags |= ECHO; +#elif defined _WIN32 + *t |= ENABLE_ECHO_INPUT; +#endif +} + +static int +echo_p(conmode *t) +{ +#if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H + return (t->c_lflag & (ECHO | ECHONL)) != 0; +#elif defined HAVE_SGTTY_H + return (t->sg_flags & ECHO) != 0; +#elif defined _WIN32 + return (*t & ENABLE_ECHO_INPUT) != 0; +#endif +} + +static int +set_ttymode(int fd, conmode *t, void (*setter)(conmode *, void *), void *arg) +{ + conmode r; + if (!getattr(fd, t)) return 0; + r = *t; + setter(&r, arg); + return setattr(fd, &r); +} + +#define GetReadFD(io) rb_io_descriptor(io) +#define GetWriteFD(io) rb_io_descriptor(rb_io_get_write_io(io)) + +#define FD_PER_IO 2 + +static VALUE +ttymode(VALUE io, VALUE (*func)(VALUE), VALUE farg, void (*setter)(conmode *, void *), void *arg) +{ + int status = -1; + int error = 0; + int fd[FD_PER_IO]; + conmode t[FD_PER_IO]; + VALUE result = Qnil; + + fd[0] = GetReadFD(io); + if (fd[0] != -1) { + if (set_ttymode(fd[0], t+0, setter, arg)) { + status = 0; + } + else { + error = errno; + fd[0] = -1; + } + } + fd[1] = GetWriteFD(io); + if (fd[1] != -1 && fd[1] != fd[0]) { + if (set_ttymode(fd[1], t+1, setter, arg)) { + status = 0; + } + else { + error = errno; + fd[1] = -1; + } + } + if (status == 0) { + result = rb_protect(func, farg, &status); + } + if (fd[0] != -1 && fd[0] == GetReadFD(io)) { + if (!setattr(fd[0], t+0)) { + error = errno; + status = -1; + } + } + if (fd[1] != -1 && fd[1] != fd[0] && fd[1] == GetWriteFD(io)) { + if (!setattr(fd[1], t+1)) { + error = errno; + status = -1; + } + } + if (status) { + if (status == -1) { + rb_syserr_fail(error, 0); + } + rb_jump_tag(status); + } + return result; +} + +#if !defined _WIN32 +struct ttymode_callback_args { + VALUE (*func)(VALUE, VALUE); + VALUE io; + VALUE farg; +}; + +static VALUE +ttymode_callback(VALUE args) +{ + struct ttymode_callback_args *argp = (struct ttymode_callback_args *)args; + return argp->func(argp->io, argp->farg); +} + +static VALUE +ttymode_with_io(VALUE io, VALUE (*func)(VALUE, VALUE), VALUE farg, void (*setter)(conmode *, void *), void *arg) +{ + struct ttymode_callback_args cargs; + cargs.func = func; + cargs.io = io; + cargs.farg = farg; + return ttymode(io, ttymode_callback, (VALUE)&cargs, setter, arg); +} +#endif + +/* + * call-seq: + * io.raw(min: nil, time: nil, intr: nil) {|io| } + * + * Yields +self+ within raw mode, and returns the result of the block. + * + * STDIN.raw(&:gets) + * + * will read and return a line without echo back and line editing. + * + * The parameter +min+ specifies the minimum number of bytes that + * should be received when a read operation is performed. (default: 1) + * + * The parameter +time+ specifies the timeout in _seconds_ with a + * precision of 1/10 of a second. (default: 0) + * + * If the parameter +intr+ is +true+, enables break, interrupt, quit, + * and suspend special characters. + * + * Refer to the manual page of termios for further details. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_raw(int argc, VALUE *argv, VALUE io) +{ + rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 0, &opts); + return ttymode(io, rb_yield, io, set_rawmode, optp); +} + +/* + * call-seq: + * io.raw!(min: nil, time: nil, intr: nil) -> io + * + * Enables raw mode, and returns +io+. + * + * If the terminal mode needs to be back, use io.raw { ... }. + * + * See IO#raw for details on the parameters. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_set_raw(int argc, VALUE *argv, VALUE io) +{ + conmode t; + rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 0, &opts); + int fd = GetReadFD(io); + if (!getattr(fd, &t)) sys_fail(io); + set_rawmode(&t, optp); + if (!setattr(fd, &t)) sys_fail(io); + return io; +} + +/* + * call-seq: + * io.cooked {|io| } + * + * Yields +self+ within cooked mode. + * + * STDIN.cooked(&:gets) + * + * will read and return a line with echo back and line editing. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_cooked(VALUE io) +{ + return ttymode(io, rb_yield, io, set_cookedmode, NULL); +} + +/* + * call-seq: + * io.cooked! + * + * Enables cooked mode. + * + * If the terminal mode needs to be back, use io.cooked { ... }. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_set_cooked(VALUE io) +{ + conmode t; + int fd = GetReadFD(io); + if (!getattr(fd, &t)) sys_fail(io); + set_cookedmode(&t, NULL); + if (!setattr(fd, &t)) sys_fail(io); + return io; +} + +#ifndef _WIN32 +static VALUE +getc_call(VALUE io) +{ + return rb_funcallv(io, id_getc, 0, 0); +} +#else +static void * +nogvl_getch(void *p) +{ + int len = 0; + wint_t *buf = p, c = _getwch(); + + switch (c) { + case WEOF: + break; + case 0x00: + case 0xe0: + buf[len++] = c; + c = _getwch(); + /* fall through */ + default: + buf[len++] = c; + break; + } + return (void *)(VALUE)len; +} +#endif + +/* + * call-seq: + * io.getch(min: nil, time: nil, intr: nil) -> char + * + * Reads and returns a character in raw mode. + * + * See IO#raw for details on the parameters. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_getch(int argc, VALUE *argv, VALUE io) +{ + rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 0, &opts); +#ifndef _WIN32 + return ttymode(io, getc_call, io, set_rawmode, optp); +#else + rb_io_t *fptr; + VALUE str; + wint_t c; + int len; + char buf[8]; + wint_t wbuf[2]; +# ifndef HAVE_RB_IO_WAIT + struct timeval *to = NULL, tv; +# else + VALUE timeout = Qnil; +# endif + + GetOpenFile(io, fptr); + if (optp) { + if (optp->vtime) { +# ifndef HAVE_RB_IO_WAIT + to = &tv; +# else + struct timeval tv; +# endif + tv.tv_sec = optp->vtime / 10; + tv.tv_usec = (optp->vtime % 10) * 100000; +# ifdef HAVE_RB_IO_WAIT + timeout = rb_fiber_scheduler_make_timeout(&tv); +# endif + } + switch (optp->vmin) { + case 1: /* default */ + break; + case 0: /* return nil when timed out */ + if (optp->vtime) break; + /* fallthru */ + default: + rb_warning("min option larger than 1 ignored"); + } + if (optp->intr) { +# ifndef HAVE_RB_IO_WAIT + int w = rb_wait_for_single_fd(fptr->fd, RB_WAITFD_IN, to); + if (w < 0) rb_eof_error(); + if (!(w & RB_WAITFD_IN)) return Qnil; +# else + VALUE result = rb_io_wait(io, RB_INT2NUM(RUBY_IO_READABLE), timeout); + if (!RTEST(result)) return Qnil; +# endif + } + else if (optp->vtime) { + rb_warning("Non-zero vtime option ignored if intr flag is unset"); + } + } + len = (int)(VALUE)rb_thread_call_without_gvl(nogvl_getch, wbuf, RUBY_UBF_IO, 0); + switch (len) { + case 0: + return Qnil; + case 2: + buf[0] = (char)wbuf[0]; + c = wbuf[1]; + len = 1; + do { + buf[len++] = (unsigned char)c; + } while ((c >>= CHAR_BIT) && len < (int)sizeof(buf)); + return rb_str_new(buf, len); + default: + c = wbuf[0]; + len = rb_uv_to_utf8(buf, c); + str = rb_utf8_str_new(buf, len); + return rb_str_conv_enc(str, NULL, rb_default_external_encoding()); + } +#endif +} + +/* + * call-seq: + * io.noecho {|io| } + * + * Yields +self+ with disabling echo back. + * + * STDIN.noecho(&:gets) + * + * will read and return a line without echo back. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_noecho(VALUE io) +{ + return ttymode(io, rb_yield, io, set_noecho, NULL); +} + +/* + * call-seq: + * io.echo = flag + * + * Enables/disables echo back. + * On some platforms, all combinations of this flags and raw/cooked + * mode may not be valid. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_set_echo(VALUE io, VALUE f) +{ + conmode t; + int fd = GetReadFD(io); + + if (!getattr(fd, &t)) sys_fail(io); + + if (RTEST(f)) + set_echo(&t, NULL); + else + set_noecho(&t, NULL); + + if (!setattr(fd, &t)) sys_fail(io); + + return io; +} + +/* + * call-seq: + * io.echo? -> true or false + * + * Returns +true+ if echo back is enabled. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_echo_p(VALUE io) +{ + conmode t; + int fd = GetReadFD(io); + + if (!getattr(fd, &t)) sys_fail(io); + return echo_p(&t) ? Qtrue : Qfalse; +} + +static const rb_data_type_t conmode_type = { + "console-mode", + {0, RUBY_TYPED_DEFAULT_FREE,}, + 0, 0, + RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; +static VALUE cConmode; + +static VALUE +conmode_alloc(VALUE klass) +{ + return rb_data_typed_object_zalloc(klass, sizeof(conmode), &conmode_type); +} + +static VALUE +conmode_new(VALUE klass, const conmode *t) +{ + VALUE obj = conmode_alloc(klass); + *(conmode *)DATA_PTR(obj) = *t; + return obj; +} + +static VALUE +conmode_init_copy(VALUE obj, VALUE obj2) +{ + conmode *t = rb_check_typeddata(obj, &conmode_type); + conmode *t2 = rb_check_typeddata(obj2, &conmode_type); + *t = *t2; + return obj; +} + +static VALUE +conmode_set_echo(VALUE obj, VALUE f) +{ + conmode *t = rb_check_typeddata(obj, &conmode_type); + if (RTEST(f)) + set_echo(t, NULL); + else + set_noecho(t, NULL); + return obj; +} + +static VALUE +conmode_set_raw(int argc, VALUE *argv, VALUE obj) +{ + conmode *t = rb_check_typeddata(obj, &conmode_type); + rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 0, &opts); + + set_rawmode(t, optp); + return obj; +} + +static VALUE +conmode_raw_new(int argc, VALUE *argv, VALUE obj) +{ + conmode *r = rb_check_typeddata(obj, &conmode_type); + conmode t = *r; + rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 0, &opts); + + set_rawmode(&t, optp); + return conmode_new(rb_obj_class(obj), &t); +} + +/* + * call-seq: + * io.console_mode -> mode + * + * Returns a data represents the current console mode. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_conmode_get(VALUE io) +{ + conmode t; + int fd = GetReadFD(io); + + if (!getattr(fd, &t)) sys_fail(io); + + return conmode_new(cConmode, &t); +} + +/* + * call-seq: + * io.console_mode = mode + * + * Sets the console mode to +mode+. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_conmode_set(VALUE io, VALUE mode) +{ + conmode *t, r; + int fd = GetReadFD(io); + + TypedData_Get_Struct(mode, conmode, &conmode_type, t); + r = *t; + + if (!setattr(fd, &r)) sys_fail(io); + + return mode; +} + +#if defined TIOCGWINSZ +typedef struct winsize rb_console_size_t; +#define getwinsize(fd, buf) (ioctl((fd), TIOCGWINSZ, (buf)) == 0) +#define setwinsize(fd, buf) (ioctl((fd), TIOCSWINSZ, (buf)) == 0) +#define winsize_row(buf) (buf)->ws_row +#define winsize_col(buf) (buf)->ws_col +#elif defined _WIN32 +typedef CONSOLE_SCREEN_BUFFER_INFO rb_console_size_t; +#define getwinsize(fd, buf) ( \ + GetConsoleScreenBufferInfo((HANDLE)rb_w32_get_osfhandle(fd), (buf)) || \ + SET_LAST_ERROR) +#define winsize_row(buf) ((buf)->srWindow.Bottom - (buf)->srWindow.Top + 1) +#define winsize_col(buf) (buf)->dwSize.X +#endif + +#if defined TIOCGWINSZ || defined _WIN32 +#define USE_CONSOLE_GETSIZE 1 +#endif + +#ifdef USE_CONSOLE_GETSIZE +/* + * call-seq: + * io.winsize -> [rows, columns] + * + * Returns console size. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_winsize(VALUE io) +{ + rb_console_size_t ws; + int fd = GetWriteFD(io); + if (!getwinsize(fd, &ws)) sys_fail(io); + return rb_assoc_new(INT2NUM(winsize_row(&ws)), INT2NUM(winsize_col(&ws))); +} + +/* + * call-seq: + * io.winsize = [rows, columns] + * + * Tries to set console size. The effect depends on the platform and + * the running environment. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_set_winsize(VALUE io, VALUE size) +{ + rb_console_size_t ws; +#if defined _WIN32 + HANDLE wh; + int newrow, newcol; + BOOL ret; +#endif + VALUE row, col, xpixel, ypixel; + const VALUE *sz; + long sizelen; + int fd; + + size = rb_Array(size); + if ((sizelen = RARRAY_LEN(size)) != 2 && sizelen != 4) { + rb_raise(rb_eArgError, "wrong number of arguments (given %ld, expected 2 or 4)", sizelen); + } + sz = RARRAY_CONST_PTR(size); + row = sz[0], col = sz[1], xpixel = ypixel = Qnil; + if (sizelen == 4) xpixel = sz[2], ypixel = sz[3]; + fd = GetWriteFD(io); +#if defined TIOCSWINSZ + ws.ws_row = ws.ws_col = ws.ws_xpixel = ws.ws_ypixel = 0; +#define SET(m) ws.ws_##m = NIL_P(m) ? 0 : (unsigned short)NUM2UINT(m) + SET(row); + SET(col); + SET(xpixel); + SET(ypixel); +#undef SET + if (!setwinsize(fd, &ws)) sys_fail(io); +#elif defined _WIN32 + wh = (HANDLE)rb_w32_get_osfhandle(fd); +#define SET(m) new##m = NIL_P(m) ? 0 : (unsigned short)NUM2UINT(m) + SET(row); + SET(col); +#undef SET + if (!NIL_P(xpixel)) (void)NUM2UINT(xpixel); + if (!NIL_P(ypixel)) (void)NUM2UINT(ypixel); + if (!GetConsoleScreenBufferInfo(wh, &ws)) { + rb_syserr_fail(LAST_ERROR, "GetConsoleScreenBufferInfo"); + } + ws.dwSize.X = newcol; + ret = SetConsoleScreenBufferSize(wh, ws.dwSize); + ws.srWindow.Left = 0; + ws.srWindow.Top = 0; + ws.srWindow.Right = newcol-1; + ws.srWindow.Bottom = newrow-1; + if (!SetConsoleWindowInfo(wh, TRUE, &ws.srWindow)) { + rb_syserr_fail(LAST_ERROR, "SetConsoleWindowInfo"); + } + /* retry when shrinking buffer after shrunk window */ + if (!ret && !SetConsoleScreenBufferSize(wh, ws.dwSize)) { + rb_syserr_fail(LAST_ERROR, "SetConsoleScreenBufferInfo"); + } + /* remove scrollbar if possible */ + if (!SetConsoleWindowInfo(wh, TRUE, &ws.srWindow)) { + rb_syserr_fail(LAST_ERROR, "SetConsoleWindowInfo"); + } +#endif + return io; +} +#endif + +#ifdef _WIN32 +/* + * call-seq: + * io.check_winsize_changed { ... } -> io + * + * Yields while console input events are queued. + * + * This method is Windows only. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_check_winsize_changed(VALUE io) +{ + HANDLE h; + DWORD num; + + h = (HANDLE)rb_w32_get_osfhandle(GetReadFD(io)); + while (GetNumberOfConsoleInputEvents(h, &num) && num > 0) { + INPUT_RECORD rec; + if (ReadConsoleInput(h, &rec, 1, &num)) { + if (rec.EventType == WINDOW_BUFFER_SIZE_EVENT) { + rb_yield(Qnil); + } + } + } + return io; +} +#else +#define console_check_winsize_changed rb_f_notimplement +#endif + +/* + * call-seq: + * io.iflush + * + * Flushes input buffer in kernel. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_iflush(VALUE io) +{ +#if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H + int fd = GetReadFD(io); + if (tcflush(fd, TCIFLUSH)) sys_fail(io); +#endif + + return io; +} + +/* + * call-seq: + * io.oflush + * + * Flushes output buffer in kernel. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_oflush(VALUE io) +{ + int fd = GetWriteFD(io); +#if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H + if (tcflush(fd, TCOFLUSH)) sys_fail(io); +#endif + (void)fd; + return io; +} + +/* + * call-seq: + * io.ioflush + * + * Flushes input and output buffers in kernel. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_ioflush(VALUE io) +{ +#if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H + int fd1 = GetReadFD(io); + int fd2 = GetWriteFD(io); + + if (fd2 != -1 && fd1 != fd2) { + if (tcflush(fd1, TCIFLUSH)) sys_fail(io); + if (tcflush(fd2, TCOFLUSH)) sys_fail(io); + } + else { + if (tcflush(fd1, TCIOFLUSH)) sys_fail(io); + } +#endif + + return io; +} + +/* + * call-seq: + * io.beep + * + * Beeps on the output console. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_beep(VALUE io) +{ +#ifdef _WIN32 + MessageBeep(0); +#else + int fd = GetWriteFD(io); + if (write(fd, "\a", 1) < 0) sys_fail(io); +#endif + return io; +} + +static int +mode_in_range(VALUE val, int high, const char *modename) +{ + int mode; + if (NIL_P(val)) return 0; + if (!RB_INTEGER_TYPE_P(val)) { + wrong_value: + rb_raise(rb_eArgError, "wrong %s mode: %"PRIsVALUE, modename, val); + } + if ((mode = NUM2INT(val)) < 0 || mode > high) { + goto wrong_value; + } + return mode; +} + +#if defined _WIN32 +static void +constat_clear(HANDLE handle, WORD attr, DWORD len, COORD pos) +{ + DWORD written; + + FillConsoleOutputAttribute(handle, attr, len, pos, &written); + FillConsoleOutputCharacterW(handle, L' ', len, pos, &written); +} + +static VALUE +console_scroll(VALUE io, int line) +{ + HANDLE h; + rb_console_size_t ws; + + h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); + if (!GetConsoleScreenBufferInfo(h, &ws)) { + rb_syserr_fail(LAST_ERROR, 0); + } + if (line) { + SMALL_RECT scroll; + COORD destination; + CHAR_INFO fill; + scroll.Left = 0; + scroll.Top = line > 0 ? line : 0; + scroll.Right = winsize_col(&ws) - 1; + scroll.Bottom = winsize_row(&ws) - 1 + (line < 0 ? line : 0); + destination.X = 0; + destination.Y = line < 0 ? -line : 0; + fill.Char.UnicodeChar = L' '; + fill.Attributes = ws.wAttributes; + + ScrollConsoleScreenBuffer(h, &scroll, NULL, destination, &fill); + } + return io; +} + +#define GPERF_DOWNCASE 1 +#define GPERF_CASE_STRCMP 1 +#define gperf_case_strcmp STRCASECMP +#include "win32_vk.inc" + +/* + * call-seq: + * io.pressed?(key) -> bool + * + * Returns +true+ if +key+ is pressed. +key+ may be a virtual key + * code or its name (String or Symbol) with out "VK_" prefix. + * + * This method is Windows only. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_key_pressed_p(VALUE io, VALUE k) +{ + int vk = -1; + + if (FIXNUM_P(k)) { + vk = NUM2UINT(k); + } + else { + const struct vktable *t; + const char *kn; + if (SYMBOL_P(k)) { + k = rb_sym2str(k); + kn = RSTRING_PTR(k); + } + else { + kn = StringValuePtr(k); + } + t = console_win32_vk(kn, RSTRING_LEN(k)); + if (!t || (vk = (short)t->vk) == -1) { + rb_raise(rb_eArgError, "unknown virtual key code: % "PRIsVALUE, k); + } + } + return GetKeyState(vk) & 0x80 ? Qtrue : Qfalse; +} +#else +struct query_args { + char qstr[6]; + unsigned char opt; +}; + +static int +direct_query(VALUE io, const struct query_args *query) +{ + if (RB_TYPE_P(io, T_FILE)) { + VALUE wio = rb_io_get_write_io(io); + VALUE s = rb_str_new_cstr(query->qstr); + rb_io_write(wio, s); + rb_io_flush(wio); + return 1; + } + return 0; +} + +static VALUE +read_vt_response(VALUE io, VALUE query) +{ + struct query_args *qargs = (struct query_args *)query; + VALUE result, b; + int opt = 0; + int num = 0; + if (qargs) { + opt = qargs->opt; + if (!direct_query(io, qargs)) return Qnil; + } + if (rb_io_getbyte(io) != INT2FIX(0x1b)) return Qnil; + if (rb_io_getbyte(io) != INT2FIX('[')) return Qnil; + result = rb_ary_new(); + while (!NIL_P(b = rb_io_getbyte(io))) { + int c = NUM2UINT(b); + if (c == ';') { + rb_ary_push(result, INT2NUM(num)); + num = 0; + } + else if (ISDIGIT(c)) { + num = num * 10 + c - '0'; + } + else if (opt && c == opt) { + opt = 0; + } + else { + char last = (char)c; + rb_ary_push(result, INT2NUM(num)); + b = rb_str_new(&last, 1); + break; + } + } + return rb_ary_push(result, b); +} + +static VALUE +console_vt_response(int argc, VALUE *argv, VALUE io, const struct query_args *qargs) +{ + rawmode_arg_t opts, *optp = rawmode_opt(&argc, argv, 0, 1, &opts); + VALUE query = (VALUE)qargs; + VALUE ret = ttymode_with_io(io, read_vt_response, query, set_rawmode, optp); + return ret; +} + +static VALUE +console_scroll(VALUE io, int line) +{ + if (line) { + VALUE s = rb_sprintf(CSI "%d%c", line < 0 ? -line : line, + line < 0 ? 'T' : 'S'); + rb_io_write(io, s); + } + return io; +} + +# define console_key_pressed_p rb_f_notimplement +#endif + +/* + * call-seq: + * io.cursor -> [row, column] + * + * Returns the current cursor position as a two-element array of integers (row, column) + * + * io.cursor # => [3, 5] + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_cursor_pos(VALUE io) +{ +#ifdef _WIN32 + rb_console_size_t ws; + int fd = GetWriteFD(io); + if (!GetConsoleScreenBufferInfo((HANDLE)rb_w32_get_osfhandle(fd), &ws)) { + rb_syserr_fail(LAST_ERROR, 0); + } + return rb_assoc_new(UINT2NUM(ws.dwCursorPosition.Y), UINT2NUM(ws.dwCursorPosition.X)); +#else + static const struct query_args query = {"\033[6n", 0}; + VALUE resp = console_vt_response(0, 0, io, &query); + VALUE row, column, term; + unsigned int r, c; + if (!RB_TYPE_P(resp, T_ARRAY) || RARRAY_LEN(resp) != 3) return Qnil; + term = RARRAY_AREF(resp, 2); + if (!RB_TYPE_P(term, T_STRING) || RSTRING_LEN(term) != 1) return Qnil; + if (RSTRING_PTR(term)[0] != 'R') return Qnil; + row = RARRAY_AREF(resp, 0); + column = RARRAY_AREF(resp, 1); + rb_ary_resize(resp, 2); + r = NUM2UINT(row) - 1; + c = NUM2UINT(column) - 1; + RARRAY_ASET(resp, 0, INT2NUM(r)); + RARRAY_ASET(resp, 1, INT2NUM(c)); + return resp; +#endif +} + +/* + * call-seq: + * io.goto(line, column) -> io + * + * Set the cursor position at +line+ and +column+. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_goto(VALUE io, VALUE y, VALUE x) +{ +#ifdef _WIN32 + COORD pos; + int fd = GetWriteFD(io); + pos.X = NUM2UINT(x); + pos.Y = NUM2UINT(y); + if (!SetConsoleCursorPosition((HANDLE)rb_w32_get_osfhandle(fd), pos)) { + rb_syserr_fail(LAST_ERROR, 0); + } +#else + rb_io_write(io, rb_sprintf(CSI "%d;%dH", NUM2UINT(y)+1, NUM2UINT(x)+1)); +#endif + return io; +} + +static VALUE +console_move(VALUE io, int y, int x) +{ +#ifdef _WIN32 + HANDLE h; + rb_console_size_t ws; + COORD *pos = &ws.dwCursorPosition; + + h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); + if (!GetConsoleScreenBufferInfo(h, &ws)) { + rb_syserr_fail(LAST_ERROR, 0); + } + pos->X += x; + pos->Y += y; + if (!SetConsoleCursorPosition(h, *pos)) { + rb_syserr_fail(LAST_ERROR, 0); + } +#else + if (x || y) { + VALUE s = rb_str_new_cstr(""); + if (y) rb_str_catf(s, CSI "%d%c", y < 0 ? -y : y, y < 0 ? 'A' : 'B'); + if (x) rb_str_catf(s, CSI "%d%c", x < 0 ? -x : x, x < 0 ? 'D' : 'C'); + rb_io_write(io, s); + rb_io_flush(io); + } +#endif + return io; +} + +/* + * call-seq: + * io.goto_column(column) -> io + * + * Set the cursor position at +column+ in the same line of the current + * position. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_goto_column(VALUE io, VALUE val) +{ +#ifdef _WIN32 + HANDLE h; + rb_console_size_t ws; + COORD *pos = &ws.dwCursorPosition; + + h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); + if (!GetConsoleScreenBufferInfo(h, &ws)) { + rb_syserr_fail(LAST_ERROR, 0); + } + pos->X = NUM2INT(val); + if (!SetConsoleCursorPosition(h, *pos)) { + rb_syserr_fail(LAST_ERROR, 0); + } +#else + rb_io_write(io, rb_sprintf(CSI "%dG", NUM2UINT(val)+1)); +#endif + return io; +} + +/* + * call-seq: + * io.erase_line(mode) -> io + * + * Erases the line at the cursor corresponding to +mode+. + * +mode+ may be either: + * 0: after cursor + * 1: before and cursor + * 2: entire line + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_erase_line(VALUE io, VALUE val) +{ + int mode = mode_in_range(val, 2, "line erase"); +#ifdef _WIN32 + HANDLE h; + rb_console_size_t ws; + COORD *pos = &ws.dwCursorPosition; + DWORD w; + + h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); + if (!GetConsoleScreenBufferInfo(h, &ws)) { + rb_syserr_fail(LAST_ERROR, 0); + } + w = winsize_col(&ws); + switch (mode) { + case 0: /* after cursor */ + w -= pos->X; + break; + case 1: /* before *and* cursor */ + w = pos->X + 1; + pos->X = 0; + break; + case 2: /* entire line */ + pos->X = 0; + break; + } + constat_clear(h, ws.wAttributes, w, *pos); + return io; +#else + rb_io_write(io, rb_sprintf(CSI "%dK", mode)); +#endif + return io; +} + +/* + * call-seq: + * io.erase_screen(mode) -> io + * + * Erases the screen at the cursor corresponding to +mode+. + * +mode+ may be either: + * 0: after cursor + * 1: before and cursor + * 2: entire screen + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_erase_screen(VALUE io, VALUE val) +{ + int mode = mode_in_range(val, 3, "screen erase"); +#ifdef _WIN32 + HANDLE h; + rb_console_size_t ws; + COORD *pos = &ws.dwCursorPosition; + DWORD w; + + h = (HANDLE)rb_w32_get_osfhandle(GetWriteFD(io)); + if (!GetConsoleScreenBufferInfo(h, &ws)) { + rb_syserr_fail(LAST_ERROR, 0); + } + w = winsize_col(&ws); + switch (mode) { + case 0: /* erase after cursor */ + w = (w * (ws.srWindow.Bottom - pos->Y + 1) - pos->X); + break; + case 1: /* erase before *and* cursor */ + w = (w * (pos->Y - ws.srWindow.Top) + pos->X + 1); + pos->X = 0; + pos->Y = ws.srWindow.Top; + break; + case 2: /* erase entire screen */ + w = (w * winsize_row(&ws)); + pos->X = 0; + pos->Y = ws.srWindow.Top; + break; + case 3: /* erase entire screen */ + w = (w * ws.dwSize.Y); + pos->X = 0; + pos->Y = 0; + break; + } + constat_clear(h, ws.wAttributes, w, *pos); +#else + rb_io_write(io, rb_sprintf(CSI "%dJ", mode)); +#endif + return io; +} + +/* + * call-seq: + * io.cursor = [line, column] -> io + * + * Same as io.goto(line, column) + * + * See IO#goto. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_cursor_set(VALUE io, VALUE cpos) +{ + cpos = rb_convert_type(cpos, T_ARRAY, "Array", "to_ary"); + if (RARRAY_LEN(cpos) != 2) rb_raise(rb_eArgError, "expected 2D coordinate"); + return console_goto(io, RARRAY_AREF(cpos, 0), RARRAY_AREF(cpos, 1)); +} + +/* + * call-seq: + * io.cursor_up(n) -> io + * + * Moves the cursor up +n+ lines. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_cursor_up(VALUE io, VALUE val) +{ + return console_move(io, -NUM2INT(val), 0); +} + +/* + * call-seq: + * io.cursor_down(n) -> io + * + * Moves the cursor down +n+ lines. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_cursor_down(VALUE io, VALUE val) +{ + return console_move(io, +NUM2INT(val), 0); +} + +/* + * call-seq: + * io.cursor_left(n) -> io + * + * Moves the cursor left +n+ columns. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_cursor_left(VALUE io, VALUE val) +{ + return console_move(io, 0, -NUM2INT(val)); +} + +/* + * call-seq: + * io.cursor_right(n) -> io + * + * Moves the cursor right +n+ columns. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_cursor_right(VALUE io, VALUE val) +{ + return console_move(io, 0, +NUM2INT(val)); +} + +/* + * call-seq: + * io.scroll_forward(n) -> io + * + * Scrolls the entire scrolls forward +n+ lines. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_scroll_forward(VALUE io, VALUE val) +{ + return console_scroll(io, +NUM2INT(val)); +} + +/* + * call-seq: + * io.scroll_backward(n) -> io + * + * Scrolls the entire scrolls backward +n+ lines. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_scroll_backward(VALUE io, VALUE val) +{ + return console_scroll(io, -NUM2INT(val)); +} + +/* + * call-seq: + * io.clear_screen -> io + * + * Clears the entire screen and moves the cursor top-left corner. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_clear_screen(VALUE io) +{ + console_erase_screen(io, INT2FIX(2)); + console_goto(io, INT2FIX(0), INT2FIX(0)); + return io; +} + +#ifndef HAVE_RB_IO_OPEN_DESCRIPTOR +static VALUE +io_open_descriptor_fallback(VALUE klass, int descriptor, int mode, VALUE path, VALUE timeout, void *encoding) +{ + VALUE arguments[2] = { + (rb_update_max_fd(descriptor), INT2NUM(descriptor)), + INT2FIX(mode), + }; + + VALUE self = rb_class_new_instance(2, arguments, klass); + + rb_io_t *fptr; + GetOpenFile(self, fptr); + fptr->pathv = path; + fptr->mode |= mode; + + return self; +} +#define rb_io_open_descriptor io_open_descriptor_fallback +#endif + +#ifndef HAVE_RB_IO_CLOSED_P +static VALUE +rb_io_closed_p(VALUE io) +{ + rb_io_t *fptr = RFILE(io)->fptr; + return fptr->fd == -1 ? Qtrue : Qfalse; +} +#endif + +#if defined(RB_EXT_RACTOR_SAFE) && defined(HAVE_RB_RACTOR_LOCAL_STORAGE_VALUE_NEWKEY) +# define USE_RACTOR_STORAGE 1 +#else +# define USE_RACTOR_STORAGE 0 +#endif + +#if USE_RACTOR_STORAGE +#include "ruby/ractor.h" +static rb_ractor_local_key_t key_console_dev; + +static bool +console_dev_get(VALUE klass, VALUE *dev) +{ + return rb_ractor_local_storage_value_lookup(key_console_dev, dev); +} + +static void +console_dev_set(VALUE klass, VALUE value) +{ + rb_ractor_local_storage_value_set(key_console_dev, value); +} + +static void +console_dev_remove(VALUE klass) +{ + console_dev_set(klass, Qnil); +} + +#else + +static ID id_console; + +static int +console_dev_get(VALUE klass, VALUE *dev) +{ + if (rb_const_defined(klass, id_console)) { + *dev = rb_const_get(klass, id_console); + return 1; + } + return 0; +} + +static void +console_dev_set(VALUE klass, VALUE value) +{ + rb_const_set(klass, id_console, value); +} + +static void +console_dev_remove(VALUE klass) +{ + rb_const_remove(klass, id_console); +} + +#endif + +/* + * call-seq: + * IO.console -> # + * IO.console(sym, *args) + * + * Returns an File instance opened console. + * + * If +sym+ is given, it will be sent to the opened console with + * +args+ and the result will be returned instead of the console IO + * itself. + * + * You must require 'io/console' to use this method. + */ +static VALUE +console_dev(int argc, VALUE *argv, VALUE klass) +{ + VALUE con = 0; + VALUE sym = 0; + + rb_check_arity(argc, 0, UNLIMITED_ARGUMENTS); + + if (argc) { + Check_Type(sym = argv[0], T_SYMBOL); + } + + // Force the class to be File. + if (klass == rb_cIO) klass = rb_cFile; + + if (console_dev_get(klass, &con)) { + if (!RB_TYPE_P(con, T_FILE) || RTEST(rb_io_closed_p(con))) { + console_dev_remove(klass); + con = 0; + } + } + + if (sym) { + if (sym == ID2SYM(id_close) && argc == 1) { + if (con) { + rb_io_close(con); + console_dev_remove(klass); + con = 0; + } + return Qnil; + } + } + + if (!con) { +#if defined HAVE_TERMIOS_H || defined HAVE_TERMIO_H || defined HAVE_SGTTY_H +# define CONSOLE_DEVICE "/dev/tty" +#elif defined _WIN32 +# define CONSOLE_DEVICE "con$" +# define CONSOLE_DEVICE_FOR_READING "conin$" +# define CONSOLE_DEVICE_FOR_WRITING "conout$" +#endif +#ifndef CONSOLE_DEVICE_FOR_READING +# define CONSOLE_DEVICE_FOR_READING CONSOLE_DEVICE +#endif +#ifdef CONSOLE_DEVICE_FOR_WRITING + VALUE out; +#endif + int fd; + VALUE path = rb_obj_freeze(rb_str_new2(CONSOLE_DEVICE)); + +#ifdef CONSOLE_DEVICE_FOR_WRITING + fd = rb_cloexec_open(CONSOLE_DEVICE_FOR_WRITING, O_RDWR, 0); + if (fd < 0) return Qnil; + out = rb_io_open_descriptor(klass, fd, FMODE_WRITABLE | FMODE_SYNC, path, Qnil, NULL); +#endif + fd = rb_cloexec_open(CONSOLE_DEVICE_FOR_READING, O_RDWR, 0); + if (fd < 0) { +#ifdef CONSOLE_DEVICE_FOR_WRITING + rb_io_close(out); +#endif + return Qnil; + } + + con = rb_io_open_descriptor(klass, fd, FMODE_READWRITE | FMODE_SYNC, path, Qnil, NULL); +#ifdef CONSOLE_DEVICE_FOR_WRITING + rb_io_set_write_io(con, out); +#endif + console_dev_set(klass, con); + } + + if (sym) { + return rb_f_send(argc, argv, con); + } + + return con; +} + +/* + * call-seq: + * io.getch(min: nil, time: nil, intr: nil) -> char + * + * See IO#getch. + */ +static VALUE +io_getch(int argc, VALUE *argv, VALUE io) +{ + return rb_funcallv(io, id_getc, argc, argv); +} + +static VALUE +puts_call(VALUE io) +{ + return rb_io_write(io, rb_default_rs); +} + +static VALUE +gets_call(VALUE io) +{ + return rb_funcallv(io, id_gets, 0, 0); +} + +static VALUE +getpass_call(VALUE io) +{ + return ttymode(io, rb_io_gets, io, set_noecho, NULL); +} + +static void +prompt(int argc, VALUE *argv, VALUE io) +{ + if (argc > 0 && !NIL_P(argv[0])) { + VALUE str = argv[0]; + StringValueCStr(str); + rb_io_write(io, str); + } +} + +static VALUE +str_chomp(VALUE str) +{ + if (!NIL_P(str)) { + const VALUE rs = rb_default_rs; /* rvalue in TruffleRuby */ + rb_funcallv(str, id_chomp_bang, 1, &rs); + } + return str; +} + +/* + * call-seq: + * io.getpass(prompt=nil) -> string + * + * Reads and returns a line without echo back. + * Prints +prompt+ unless it is +nil+. + * + * The newline character that terminates the + * read line is removed from the returned string, + * see String#chomp!. + * + * You must require 'io/console' to use this method. + * + * require 'io/console' + * IO::console.getpass("Enter password:") + * Enter password: + * # => "mypassword" + * + */ +static VALUE +console_getpass(int argc, VALUE *argv, VALUE io) +{ + VALUE str, wio; + + rb_check_arity(argc, 0, 1); + wio = rb_io_get_write_io(io); + if (wio == io && io == rb_stdin) wio = rb_stderr; + prompt(argc, argv, wio); + rb_io_flush(wio); + str = rb_ensure(getpass_call, io, puts_call, wio); + return str_chomp(str); +} + +/* + * call-seq: + * io.getpass(prompt=nil) -> string + * + * See IO#getpass. + */ +static VALUE +io_getpass(int argc, VALUE *argv, VALUE io) +{ + VALUE str; + + rb_check_arity(argc, 0, 1); + prompt(argc, argv, io); + rb_check_funcall(io, id_flush, 0, 0); + str = rb_ensure(gets_call, io, puts_call, io); + return str_chomp(str); +} + +#if defined(_WIN32) || defined(HAVE_TTYNAME_R) || defined(HAVE_TTYNAME) +/* + * call-seq: + * io.ttyname -> string or nil + * + * Returns name of associated terminal (tty) if +io+ is not a tty. + * Returns +nil+ otherwise. + */ +static VALUE +console_ttyname(VALUE io) +{ + int fd = rb_io_descriptor(io); + if (!isatty(fd)) return Qnil; +# if defined _WIN32 + return rb_usascii_str_new_lit("con"); +# elif defined HAVE_TTYNAME_R + { + char termname[1024], *tn = termname; + size_t size = sizeof(termname); + int e; + if (ttyname_r(fd, tn, size) == 0) + return rb_interned_str_cstr(tn); + if ((e = errno) == ERANGE) { + VALUE s = rb_str_new(0, size); + while (1) { + tn = RSTRING_PTR(s); + size = rb_str_capacity(s); + if (ttyname_r(fd, tn, size) == 0) { + return rb_str_to_interned_str(rb_str_resize(s, strlen(tn))); + } + if ((e = errno) != ERANGE) break; + if ((size *= 2) >= INT_MAX/2) break; + rb_str_resize(s, size); + } + } + rb_syserr_fail_str(e, rb_sprintf("ttyname_r(%d)", fd)); + UNREACHABLE_RETURN(Qnil); + } +# elif defined HAVE_TTYNAME + { + const char *tn = ttyname(fd); + if (!tn) { + int e = errno; + rb_syserr_fail_str(e, rb_sprintf("ttyname(%d)", fd)); + } + return rb_interned_str_cstr(tn); + } +# else +# error No ttyname function +# endif +} +#else +# define console_ttyname rb_f_notimplement +#endif + +/* + * IO console methods + */ +void +Init_console(void) +{ +#if USE_RACTOR_STORAGE + RB_EXT_RACTOR_SAFE(true); +#endif + +#undef rb_intern +#if USE_RACTOR_STORAGE + key_console_dev = rb_ractor_local_storage_value_newkey(); +#else + id_console = rb_intern("console"); +#endif + id_getc = rb_intern("getc"); + id_gets = rb_intern("gets"); + id_flush = rb_intern("flush"); + id_chomp_bang = rb_intern("chomp!"); + id_close = rb_intern("close"); +#define init_rawmode_opt_id(name) \ + rawmode_opt_ids[kwd_##name] = rb_intern(#name) + init_rawmode_opt_id(min); + init_rawmode_opt_id(time); + init_rawmode_opt_id(intr); +#ifndef HAVE_RB_F_SEND + id___send__ = rb_intern("__send__"); +#endif + InitVM(console); +} + +void +InitVM_console(void) +{ + rb_define_method(rb_cIO, "raw", console_raw, -1); + rb_define_method(rb_cIO, "raw!", console_set_raw, -1); + rb_define_method(rb_cIO, "cooked", console_cooked, 0); + rb_define_method(rb_cIO, "cooked!", console_set_cooked, 0); + rb_define_method(rb_cIO, "getch", console_getch, -1); + rb_define_method(rb_cIO, "echo=", console_set_echo, 1); + rb_define_method(rb_cIO, "echo?", console_echo_p, 0); + rb_define_method(rb_cIO, "console_mode", console_conmode_get, 0); + rb_define_method(rb_cIO, "console_mode=", console_conmode_set, 1); + rb_define_method(rb_cIO, "noecho", console_noecho, 0); + rb_define_method(rb_cIO, "winsize", console_winsize, 0); + rb_define_method(rb_cIO, "winsize=", console_set_winsize, 1); + rb_define_method(rb_cIO, "iflush", console_iflush, 0); + rb_define_method(rb_cIO, "oflush", console_oflush, 0); + rb_define_method(rb_cIO, "ioflush", console_ioflush, 0); + rb_define_method(rb_cIO, "beep", console_beep, 0); + rb_define_method(rb_cIO, "goto", console_goto, 2); + rb_define_method(rb_cIO, "cursor", console_cursor_pos, 0); + rb_define_method(rb_cIO, "cursor=", console_cursor_set, 1); + rb_define_method(rb_cIO, "cursor_up", console_cursor_up, 1); + rb_define_method(rb_cIO, "cursor_down", console_cursor_down, 1); + rb_define_method(rb_cIO, "cursor_left", console_cursor_left, 1); + rb_define_method(rb_cIO, "cursor_right", console_cursor_right, 1); + rb_define_method(rb_cIO, "goto_column", console_goto_column, 1); + rb_define_method(rb_cIO, "erase_line", console_erase_line, 1); + rb_define_method(rb_cIO, "erase_screen", console_erase_screen, 1); + rb_define_method(rb_cIO, "scroll_forward", console_scroll_forward, 1); + rb_define_method(rb_cIO, "scroll_backward", console_scroll_backward, 1); + rb_define_method(rb_cIO, "clear_screen", console_clear_screen, 0); + rb_define_method(rb_cIO, "pressed?", console_key_pressed_p, 1); + rb_define_method(rb_cIO, "check_winsize_changed", console_check_winsize_changed, 0); + rb_define_method(rb_cIO, "getpass", console_getpass, -1); + rb_define_method(rb_cIO, "ttyname", console_ttyname, 0); + rb_define_singleton_method(rb_cIO, "console", console_dev, -1); + { + /* :stopdoc: */ + VALUE mReadable = rb_define_module_under(rb_cIO, "generic_readable"); + /* :startdoc: */ + rb_define_method(mReadable, "getch", io_getch, -1); + rb_define_method(mReadable, "getpass", io_getpass, -1); + } + { + /* :stopdoc: */ + cConmode = rb_define_class_under(rb_cIO, "ConsoleMode", rb_cObject); + rb_define_const(cConmode, "VERSION", rb_obj_freeze(rb_str_new_cstr(IO_CONSOLE_VERSION))); + rb_define_alloc_func(cConmode, conmode_alloc); + rb_undef_method(cConmode, "initialize"); + rb_define_method(cConmode, "initialize_copy", conmode_init_copy, 1); + rb_define_method(cConmode, "echo=", conmode_set_echo, 1); + rb_define_method(cConmode, "raw!", conmode_set_raw, -1); + rb_define_method(cConmode, "raw", conmode_raw_new, -1); + /* :startdoc: */ + } +} diff --git a/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/ext/io/console/extconf.rb b/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/ext/io/console/extconf.rb new file mode 100644 index 00000000..4ad7ed69 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/ext/io/console/extconf.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: false +require 'mkmf' + +# `--target-rbconfig` compatibility for Ruby 3.3 or earlier +# See https://bugs.ruby-lang.org/issues/20345 +MakeMakefile::RbConfig ||= ::RbConfig + +have_func("rb_syserr_fail_str(0, Qnil)") or +have_func("rb_syserr_new_str(0, Qnil)") or + abort + +have_func("rb_interned_str_cstr") +have_func("rb_io_path") +have_func("rb_io_descriptor") +have_func("rb_io_get_write_io") +have_func("rb_io_closed_p") +have_func("rb_io_open_descriptor") +have_func("rb_ractor_local_storage_value_newkey") + +is_wasi = /wasi/ =~ MakeMakefile::RbConfig::CONFIG["platform"] +# `ok` can be `true`, `false`, or `nil`: +# * `true` : Required headers and functions available, proceed regular build. +# * `false`: Required headers or functions not available, abort build. +# * `nil` : Unsupported compilation target, generate dummy Makefile. +# +# Skip building io/console on WASI, as it does not support termios.h. +ok = true if (RUBY_ENGINE == "ruby" && !is_wasi) || RUBY_ENGINE == "truffleruby" +hdr = nil +case +when macro_defined?("_WIN32", "") + # rb_w32_map_errno: 1.8.7 + vk_header = File.exist?("#$srcdir/win32_vk.list") ? "chksum" : "inc" + vk_header = "#{'{$(srcdir)}' if $nmake == ?m}win32_vk.#{vk_header}" +when hdr = %w"termios.h termio.h".find {|h| have_header(h)} + have_func("cfmakeraw", hdr) +when have_header(hdr = "sgtty.h") + %w"stty gtty".each {|f| have_func(f, hdr)} +else + ok = false +end if ok +case ok +when true + have_header("sys/ioctl.h") if hdr + # rb_check_hash_type: 1.9.3 + # rb_io_get_write_io: 1.9.1 + # rb_cloexec_open: 2.0.0 + # rb_funcallv: 2.1.0 + # RARRAY_CONST_PTR: 2.1.0 + # rb_sym2str: 2.2.0 + if have_macro("HAVE_RUBY_FIBER_SCHEDULER_H") + $defs << "-D""HAVE_RB_IO_WAIT=1" + elsif have_func("rb_scheduler_timeout") # 3.0 + have_func("rb_io_wait") + end + have_func("ttyname_r") or have_func("ttyname") + create_makefile("io/console") {|conf| + conf << "\n""VK_HEADER = #{vk_header}\n" + } +when nil + File.write("Makefile", dummy_makefile($srcdir).join("")) +end diff --git a/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/ext/io/console/win32_vk.inc b/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/ext/io/console/win32_vk.inc new file mode 100644 index 00000000..348e6be5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/ext/io/console/win32_vk.inc @@ -0,0 +1,1390 @@ +#define UNDEFINED_VK (unsigned short)-1 +#ifndef VK_LBUTTON +# define VK_LBUTTON UNDEFINED_VK +#endif +#ifndef VK_RBUTTON +# define VK_RBUTTON UNDEFINED_VK +#endif +#ifndef VK_CANCEL +# define VK_CANCEL UNDEFINED_VK +#endif +#ifndef VK_MBUTTON +# define VK_MBUTTON UNDEFINED_VK +#endif +#ifndef VK_XBUTTON1 +# define VK_XBUTTON1 UNDEFINED_VK +#endif +#ifndef VK_XBUTTON2 +# define VK_XBUTTON2 UNDEFINED_VK +#endif +#ifndef VK_BACK +# define VK_BACK UNDEFINED_VK +#endif +#ifndef VK_TAB +# define VK_TAB UNDEFINED_VK +#endif +#ifndef VK_CLEAR +# define VK_CLEAR UNDEFINED_VK +#endif +#ifndef VK_RETURN +# define VK_RETURN UNDEFINED_VK +#endif +#ifndef VK_SHIFT +# define VK_SHIFT UNDEFINED_VK +#endif +#ifndef VK_CONTROL +# define VK_CONTROL UNDEFINED_VK +#endif +#ifndef VK_MENU +# define VK_MENU UNDEFINED_VK +#endif +#ifndef VK_PAUSE +# define VK_PAUSE UNDEFINED_VK +#endif +#ifndef VK_CAPITAL +# define VK_CAPITAL UNDEFINED_VK +#endif +#ifndef VK_KANA +# define VK_KANA UNDEFINED_VK +#endif +#ifndef VK_HANGEUL +# define VK_HANGEUL UNDEFINED_VK +#endif +#ifndef VK_HANGUL +# define VK_HANGUL UNDEFINED_VK +#endif +#ifndef VK_JUNJA +# define VK_JUNJA UNDEFINED_VK +#endif +#ifndef VK_FINAL +# define VK_FINAL UNDEFINED_VK +#endif +#ifndef VK_HANJA +# define VK_HANJA UNDEFINED_VK +#endif +#ifndef VK_KANJI +# define VK_KANJI UNDEFINED_VK +#endif +#ifndef VK_ESCAPE +# define VK_ESCAPE UNDEFINED_VK +#endif +#ifndef VK_CONVERT +# define VK_CONVERT UNDEFINED_VK +#endif +#ifndef VK_NONCONVERT +# define VK_NONCONVERT UNDEFINED_VK +#endif +#ifndef VK_ACCEPT +# define VK_ACCEPT UNDEFINED_VK +#endif +#ifndef VK_MODECHANGE +# define VK_MODECHANGE UNDEFINED_VK +#endif +#ifndef VK_SPACE +# define VK_SPACE UNDEFINED_VK +#endif +#ifndef VK_PRIOR +# define VK_PRIOR UNDEFINED_VK +#endif +#ifndef VK_NEXT +# define VK_NEXT UNDEFINED_VK +#endif +#ifndef VK_END +# define VK_END UNDEFINED_VK +#endif +#ifndef VK_HOME +# define VK_HOME UNDEFINED_VK +#endif +#ifndef VK_LEFT +# define VK_LEFT UNDEFINED_VK +#endif +#ifndef VK_UP +# define VK_UP UNDEFINED_VK +#endif +#ifndef VK_RIGHT +# define VK_RIGHT UNDEFINED_VK +#endif +#ifndef VK_DOWN +# define VK_DOWN UNDEFINED_VK +#endif +#ifndef VK_SELECT +# define VK_SELECT UNDEFINED_VK +#endif +#ifndef VK_PRINT +# define VK_PRINT UNDEFINED_VK +#endif +#ifndef VK_EXECUTE +# define VK_EXECUTE UNDEFINED_VK +#endif +#ifndef VK_SNAPSHOT +# define VK_SNAPSHOT UNDEFINED_VK +#endif +#ifndef VK_INSERT +# define VK_INSERT UNDEFINED_VK +#endif +#ifndef VK_DELETE +# define VK_DELETE UNDEFINED_VK +#endif +#ifndef VK_HELP +# define VK_HELP UNDEFINED_VK +#endif +#ifndef VK_LWIN +# define VK_LWIN UNDEFINED_VK +#endif +#ifndef VK_RWIN +# define VK_RWIN UNDEFINED_VK +#endif +#ifndef VK_APPS +# define VK_APPS UNDEFINED_VK +#endif +#ifndef VK_SLEEP +# define VK_SLEEP UNDEFINED_VK +#endif +#ifndef VK_NUMPAD0 +# define VK_NUMPAD0 UNDEFINED_VK +#endif +#ifndef VK_NUMPAD1 +# define VK_NUMPAD1 UNDEFINED_VK +#endif +#ifndef VK_NUMPAD2 +# define VK_NUMPAD2 UNDEFINED_VK +#endif +#ifndef VK_NUMPAD3 +# define VK_NUMPAD3 UNDEFINED_VK +#endif +#ifndef VK_NUMPAD4 +# define VK_NUMPAD4 UNDEFINED_VK +#endif +#ifndef VK_NUMPAD5 +# define VK_NUMPAD5 UNDEFINED_VK +#endif +#ifndef VK_NUMPAD6 +# define VK_NUMPAD6 UNDEFINED_VK +#endif +#ifndef VK_NUMPAD7 +# define VK_NUMPAD7 UNDEFINED_VK +#endif +#ifndef VK_NUMPAD8 +# define VK_NUMPAD8 UNDEFINED_VK +#endif +#ifndef VK_NUMPAD9 +# define VK_NUMPAD9 UNDEFINED_VK +#endif +#ifndef VK_MULTIPLY +# define VK_MULTIPLY UNDEFINED_VK +#endif +#ifndef VK_ADD +# define VK_ADD UNDEFINED_VK +#endif +#ifndef VK_SEPARATOR +# define VK_SEPARATOR UNDEFINED_VK +#endif +#ifndef VK_SUBTRACT +# define VK_SUBTRACT UNDEFINED_VK +#endif +#ifndef VK_DECIMAL +# define VK_DECIMAL UNDEFINED_VK +#endif +#ifndef VK_DIVIDE +# define VK_DIVIDE UNDEFINED_VK +#endif +#ifndef VK_F1 +# define VK_F1 UNDEFINED_VK +#endif +#ifndef VK_F2 +# define VK_F2 UNDEFINED_VK +#endif +#ifndef VK_F3 +# define VK_F3 UNDEFINED_VK +#endif +#ifndef VK_F4 +# define VK_F4 UNDEFINED_VK +#endif +#ifndef VK_F5 +# define VK_F5 UNDEFINED_VK +#endif +#ifndef VK_F6 +# define VK_F6 UNDEFINED_VK +#endif +#ifndef VK_F7 +# define VK_F7 UNDEFINED_VK +#endif +#ifndef VK_F8 +# define VK_F8 UNDEFINED_VK +#endif +#ifndef VK_F9 +# define VK_F9 UNDEFINED_VK +#endif +#ifndef VK_F10 +# define VK_F10 UNDEFINED_VK +#endif +#ifndef VK_F11 +# define VK_F11 UNDEFINED_VK +#endif +#ifndef VK_F12 +# define VK_F12 UNDEFINED_VK +#endif +#ifndef VK_F13 +# define VK_F13 UNDEFINED_VK +#endif +#ifndef VK_F14 +# define VK_F14 UNDEFINED_VK +#endif +#ifndef VK_F15 +# define VK_F15 UNDEFINED_VK +#endif +#ifndef VK_F16 +# define VK_F16 UNDEFINED_VK +#endif +#ifndef VK_F17 +# define VK_F17 UNDEFINED_VK +#endif +#ifndef VK_F18 +# define VK_F18 UNDEFINED_VK +#endif +#ifndef VK_F19 +# define VK_F19 UNDEFINED_VK +#endif +#ifndef VK_F20 +# define VK_F20 UNDEFINED_VK +#endif +#ifndef VK_F21 +# define VK_F21 UNDEFINED_VK +#endif +#ifndef VK_F22 +# define VK_F22 UNDEFINED_VK +#endif +#ifndef VK_F23 +# define VK_F23 UNDEFINED_VK +#endif +#ifndef VK_F24 +# define VK_F24 UNDEFINED_VK +#endif +#ifndef VK_NUMLOCK +# define VK_NUMLOCK UNDEFINED_VK +#endif +#ifndef VK_SCROLL +# define VK_SCROLL UNDEFINED_VK +#endif +#ifndef VK_OEM_NEC_EQUAL +# define VK_OEM_NEC_EQUAL UNDEFINED_VK +#endif +#ifndef VK_OEM_FJ_JISHO +# define VK_OEM_FJ_JISHO UNDEFINED_VK +#endif +#ifndef VK_OEM_FJ_MASSHOU +# define VK_OEM_FJ_MASSHOU UNDEFINED_VK +#endif +#ifndef VK_OEM_FJ_TOUROKU +# define VK_OEM_FJ_TOUROKU UNDEFINED_VK +#endif +#ifndef VK_OEM_FJ_LOYA +# define VK_OEM_FJ_LOYA UNDEFINED_VK +#endif +#ifndef VK_OEM_FJ_ROYA +# define VK_OEM_FJ_ROYA UNDEFINED_VK +#endif +#ifndef VK_LSHIFT +# define VK_LSHIFT UNDEFINED_VK +#endif +#ifndef VK_RSHIFT +# define VK_RSHIFT UNDEFINED_VK +#endif +#ifndef VK_LCONTROL +# define VK_LCONTROL UNDEFINED_VK +#endif +#ifndef VK_RCONTROL +# define VK_RCONTROL UNDEFINED_VK +#endif +#ifndef VK_LMENU +# define VK_LMENU UNDEFINED_VK +#endif +#ifndef VK_RMENU +# define VK_RMENU UNDEFINED_VK +#endif +#ifndef VK_BROWSER_BACK +# define VK_BROWSER_BACK UNDEFINED_VK +#endif +#ifndef VK_BROWSER_FORWARD +# define VK_BROWSER_FORWARD UNDEFINED_VK +#endif +#ifndef VK_BROWSER_REFRESH +# define VK_BROWSER_REFRESH UNDEFINED_VK +#endif +#ifndef VK_BROWSER_STOP +# define VK_BROWSER_STOP UNDEFINED_VK +#endif +#ifndef VK_BROWSER_SEARCH +# define VK_BROWSER_SEARCH UNDEFINED_VK +#endif +#ifndef VK_BROWSER_FAVORITES +# define VK_BROWSER_FAVORITES UNDEFINED_VK +#endif +#ifndef VK_BROWSER_HOME +# define VK_BROWSER_HOME UNDEFINED_VK +#endif +#ifndef VK_VOLUME_MUTE +# define VK_VOLUME_MUTE UNDEFINED_VK +#endif +#ifndef VK_VOLUME_DOWN +# define VK_VOLUME_DOWN UNDEFINED_VK +#endif +#ifndef VK_VOLUME_UP +# define VK_VOLUME_UP UNDEFINED_VK +#endif +#ifndef VK_MEDIA_NEXT_TRACK +# define VK_MEDIA_NEXT_TRACK UNDEFINED_VK +#endif +#ifndef VK_MEDIA_PREV_TRACK +# define VK_MEDIA_PREV_TRACK UNDEFINED_VK +#endif +#ifndef VK_MEDIA_STOP +# define VK_MEDIA_STOP UNDEFINED_VK +#endif +#ifndef VK_MEDIA_PLAY_PAUSE +# define VK_MEDIA_PLAY_PAUSE UNDEFINED_VK +#endif +#ifndef VK_LAUNCH_MAIL +# define VK_LAUNCH_MAIL UNDEFINED_VK +#endif +#ifndef VK_LAUNCH_MEDIA_SELECT +# define VK_LAUNCH_MEDIA_SELECT UNDEFINED_VK +#endif +#ifndef VK_LAUNCH_APP1 +# define VK_LAUNCH_APP1 UNDEFINED_VK +#endif +#ifndef VK_LAUNCH_APP2 +# define VK_LAUNCH_APP2 UNDEFINED_VK +#endif +#ifndef VK_OEM_1 +# define VK_OEM_1 UNDEFINED_VK +#endif +#ifndef VK_OEM_PLUS +# define VK_OEM_PLUS UNDEFINED_VK +#endif +#ifndef VK_OEM_COMMA +# define VK_OEM_COMMA UNDEFINED_VK +#endif +#ifndef VK_OEM_MINUS +# define VK_OEM_MINUS UNDEFINED_VK +#endif +#ifndef VK_OEM_PERIOD +# define VK_OEM_PERIOD UNDEFINED_VK +#endif +#ifndef VK_OEM_2 +# define VK_OEM_2 UNDEFINED_VK +#endif +#ifndef VK_OEM_3 +# define VK_OEM_3 UNDEFINED_VK +#endif +#ifndef VK_OEM_4 +# define VK_OEM_4 UNDEFINED_VK +#endif +#ifndef VK_OEM_5 +# define VK_OEM_5 UNDEFINED_VK +#endif +#ifndef VK_OEM_6 +# define VK_OEM_6 UNDEFINED_VK +#endif +#ifndef VK_OEM_7 +# define VK_OEM_7 UNDEFINED_VK +#endif +#ifndef VK_OEM_8 +# define VK_OEM_8 UNDEFINED_VK +#endif +#ifndef VK_OEM_AX +# define VK_OEM_AX UNDEFINED_VK +#endif +#ifndef VK_OEM_102 +# define VK_OEM_102 UNDEFINED_VK +#endif +#ifndef VK_ICO_HELP +# define VK_ICO_HELP UNDEFINED_VK +#endif +#ifndef VK_ICO_00 +# define VK_ICO_00 UNDEFINED_VK +#endif +#ifndef VK_PROCESSKEY +# define VK_PROCESSKEY UNDEFINED_VK +#endif +#ifndef VK_ICO_CLEAR +# define VK_ICO_CLEAR UNDEFINED_VK +#endif +#ifndef VK_PACKET +# define VK_PACKET UNDEFINED_VK +#endif +#ifndef VK_OEM_RESET +# define VK_OEM_RESET UNDEFINED_VK +#endif +#ifndef VK_OEM_JUMP +# define VK_OEM_JUMP UNDEFINED_VK +#endif +#ifndef VK_OEM_PA1 +# define VK_OEM_PA1 UNDEFINED_VK +#endif +#ifndef VK_OEM_PA2 +# define VK_OEM_PA2 UNDEFINED_VK +#endif +#ifndef VK_OEM_PA3 +# define VK_OEM_PA3 UNDEFINED_VK +#endif +#ifndef VK_OEM_WSCTRL +# define VK_OEM_WSCTRL UNDEFINED_VK +#endif +#ifndef VK_OEM_CUSEL +# define VK_OEM_CUSEL UNDEFINED_VK +#endif +#ifndef VK_OEM_ATTN +# define VK_OEM_ATTN UNDEFINED_VK +#endif +#ifndef VK_OEM_FINISH +# define VK_OEM_FINISH UNDEFINED_VK +#endif +#ifndef VK_OEM_COPY +# define VK_OEM_COPY UNDEFINED_VK +#endif +#ifndef VK_OEM_AUTO +# define VK_OEM_AUTO UNDEFINED_VK +#endif +#ifndef VK_OEM_ENLW +# define VK_OEM_ENLW UNDEFINED_VK +#endif +#ifndef VK_OEM_BACKTAB +# define VK_OEM_BACKTAB UNDEFINED_VK +#endif +#ifndef VK_ATTN +# define VK_ATTN UNDEFINED_VK +#endif +#ifndef VK_CRSEL +# define VK_CRSEL UNDEFINED_VK +#endif +#ifndef VK_EXSEL +# define VK_EXSEL UNDEFINED_VK +#endif +#ifndef VK_EREOF +# define VK_EREOF UNDEFINED_VK +#endif +#ifndef VK_PLAY +# define VK_PLAY UNDEFINED_VK +#endif +#ifndef VK_ZOOM +# define VK_ZOOM UNDEFINED_VK +#endif +#ifndef VK_NONAME +# define VK_NONAME UNDEFINED_VK +#endif +#ifndef VK_PA1 +# define VK_PA1 UNDEFINED_VK +#endif +#ifndef VK_OEM_CLEAR +# define VK_OEM_CLEAR UNDEFINED_VK +#endif +/* ANSI-C code produced by gperf version 3.1 */ +/* Command-line: gperf --ignore-case -L ANSI-C -E -C -P -p -j1 -i 1 -g -o -t -K ofs -N console_win32_vk -k'*' win32_vk.list */ + +#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ + && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ + && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ + && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ + && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ + && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ + && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ + && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ + && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ + && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ + && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ + && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ + && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ + && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ + && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \ + && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ + && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ + && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ + && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ + && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ + && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ + && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ + && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)) +/* The character set is not based on ISO-646. */ +#error "gperf generated tables don't work with this execution character set. Please report a bug to ." +#endif + +#line 1 "win32_vk.list" + +struct vktable {short ofs; unsigned short vk;}; +static const struct vktable *console_win32_vk(const char *, size_t); +#line 5 "win32_vk.list" +struct vktable; +/* maximum key range = 245, duplicates = 0 */ + +#ifndef GPERF_DOWNCASE +#define GPERF_DOWNCASE 1 +static unsigned char gperf_downcase[256] = + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, + 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, + 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, + 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, + 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, + 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, + 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, + 255 + }; +#endif + +#ifndef GPERF_CASE_STRCMP +#define GPERF_CASE_STRCMP 1 +static int +gperf_case_strcmp (register const char *s1, register const char *s2) +{ + for (;;) + { + unsigned char c1 = gperf_downcase[(unsigned char)*s1++]; + unsigned char c2 = gperf_downcase[(unsigned char)*s2++]; + if (c1 != 0 && c1 == c2) + continue; + return (int)c1 - (int)c2; + } +} +#endif + +#ifdef __GNUC__ +__inline +#else +#ifdef __cplusplus +inline +#endif +#endif +static unsigned int +hash (register const char *str, register size_t len) +{ + static const unsigned short asso_values[] = + { + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 51, 74, + 80, 116, 127, 124, 95, 140, 77, 53, 7, 3, + 257, 257, 257, 257, 257, 1, 11, 1, 55, 1, + 25, 84, 31, 33, 13, 16, 2, 28, 8, 1, + 6, 10, 1, 1, 3, 4, 45, 18, 73, 79, + 30, 257, 257, 257, 257, 5, 257, 1, 11, 1, + 55, 1, 25, 84, 31, 33, 13, 16, 2, 28, + 8, 1, 6, 10, 1, 1, 3, 4, 45, 18, + 73, 79, 30, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 257, 257, 257, 257, 257 + }; + register unsigned int hval = (unsigned int)len; + + switch (hval) + { + default: + hval += asso_values[(unsigned char)str[18]]; + /*FALLTHROUGH*/ + case 18: + hval += asso_values[(unsigned char)str[17]]; + /*FALLTHROUGH*/ + case 17: + hval += asso_values[(unsigned char)str[16]]; + /*FALLTHROUGH*/ + case 16: + hval += asso_values[(unsigned char)str[15]]; + /*FALLTHROUGH*/ + case 15: + hval += asso_values[(unsigned char)str[14]]; + /*FALLTHROUGH*/ + case 14: + hval += asso_values[(unsigned char)str[13]]; + /*FALLTHROUGH*/ + case 13: + hval += asso_values[(unsigned char)str[12]]; + /*FALLTHROUGH*/ + case 12: + hval += asso_values[(unsigned char)str[11]]; + /*FALLTHROUGH*/ + case 11: + hval += asso_values[(unsigned char)str[10]]; + /*FALLTHROUGH*/ + case 10: + hval += asso_values[(unsigned char)str[9]]; + /*FALLTHROUGH*/ + case 9: + hval += asso_values[(unsigned char)str[8]]; + /*FALLTHROUGH*/ + case 8: + hval += asso_values[(unsigned char)str[7]]; + /*FALLTHROUGH*/ + case 7: + hval += asso_values[(unsigned char)str[6]]; + /*FALLTHROUGH*/ + case 6: + hval += asso_values[(unsigned char)str[5]]; + /*FALLTHROUGH*/ + case 5: + hval += asso_values[(unsigned char)str[4]]; + /*FALLTHROUGH*/ + case 4: + hval += asso_values[(unsigned char)str[3]]; + /*FALLTHROUGH*/ + case 3: + hval += asso_values[(unsigned char)str[2]+2]; + /*FALLTHROUGH*/ + case 2: + hval += asso_values[(unsigned char)str[1]]; + /*FALLTHROUGH*/ + case 1: + hval += asso_values[(unsigned char)str[0]]; + break; + } + return (unsigned int)hval; +} + +struct stringpool_t + { + char stringpool_str12[sizeof("UP")]; + char stringpool_str13[sizeof("APPS")]; + char stringpool_str14[sizeof("CRSEL")]; + char stringpool_str15[sizeof("SPACE")]; + char stringpool_str16[sizeof("SCROLL")]; + char stringpool_str17[sizeof("ESCAPE")]; + char stringpool_str18[sizeof("CANCEL")]; + char stringpool_str19[sizeof("ACCEPT")]; + char stringpool_str20[sizeof("SEPARATOR")]; + char stringpool_str21[sizeof("SELECT")]; + char stringpool_str22[sizeof("CONTROL")]; + char stringpool_str23[sizeof("OEM_CLEAR")]; + char stringpool_str24[sizeof("OEM_RESET")]; + char stringpool_str25[sizeof("OEM_AUTO")]; + char stringpool_str26[sizeof("OEM_CUSEL")]; + char stringpool_str28[sizeof("KANA")]; + char stringpool_str29[sizeof("OEM_PLUS")]; + char stringpool_str30[sizeof("PRIOR")]; + char stringpool_str31[sizeof("OEM_ATTN")]; + char stringpool_str32[sizeof("PAUSE")]; + char stringpool_str33[sizeof("BACK")]; + char stringpool_str34[sizeof("PACKET")]; + char stringpool_str35[sizeof("RCONTROL")]; + char stringpool_str36[sizeof("LCONTROL")]; + char stringpool_str37[sizeof("END")]; + char stringpool_str38[sizeof("HOME")]; + char stringpool_str39[sizeof("PRINT")]; + char stringpool_str40[sizeof("NUMLOCK")]; + char stringpool_str41[sizeof("LEFT")]; + char stringpool_str42[sizeof("JUNJA")]; + char stringpool_str43[sizeof("MENU")]; + char stringpool_str44[sizeof("OEM_WSCTRL")]; + char stringpool_str45[sizeof("OEM_ENLW")]; + char stringpool_str46[sizeof("NEXT")]; + char stringpool_str47[sizeof("RWIN")]; + char stringpool_str48[sizeof("LWIN")]; + char stringpool_str49[sizeof("CAPITAL")]; + char stringpool_str50[sizeof("HELP")]; + char stringpool_str51[sizeof("NONAME")]; + char stringpool_str52[sizeof("RBUTTON")]; + char stringpool_str53[sizeof("LBUTTON")]; + char stringpool_str54[sizeof("OEM_NEC_EQUAL")]; + char stringpool_str56[sizeof("INSERT")]; + char stringpool_str57[sizeof("HANJA")]; + char stringpool_str60[sizeof("SNAPSHOT")]; + char stringpool_str61[sizeof("ATTN")]; + char stringpool_str62[sizeof("TAB")]; + char stringpool_str63[sizeof("OEM_BACKTAB")]; + char stringpool_str64[sizeof("ICO_CLEAR")]; + char stringpool_str65[sizeof("CONVERT")]; + char stringpool_str66[sizeof("RETURN")]; + char stringpool_str67[sizeof("OEM_JUMP")]; + char stringpool_str71[sizeof("BROWSER_STOP")]; + char stringpool_str72[sizeof("FINAL")]; + char stringpool_str73[sizeof("ZOOM")]; + char stringpool_str74[sizeof("KANJI")]; + char stringpool_str75[sizeof("DELETE")]; + char stringpool_str76[sizeof("OEM_COMMA")]; + char stringpool_str77[sizeof("SUBTRACT")]; + char stringpool_str79[sizeof("MBUTTON")]; + char stringpool_str80[sizeof("F9")]; + char stringpool_str81[sizeof("SHIFT")]; + char stringpool_str82[sizeof("RSHIFT")]; + char stringpool_str83[sizeof("LSHIFT")]; + char stringpool_str84[sizeof("ADD")]; + char stringpool_str85[sizeof("NONCONVERT")]; + char stringpool_str86[sizeof("EXSEL")]; + char stringpool_str87[sizeof("OEM_1")]; + char stringpool_str88[sizeof("OEM_AX")]; + char stringpool_str89[sizeof("BROWSER_BACK")]; + char stringpool_str90[sizeof("OEM_8")]; + char stringpool_str91[sizeof("OEM_MINUS")]; + char stringpool_str92[sizeof("PLAY")]; + char stringpool_str93[sizeof("OEM_2")]; + char stringpool_str94[sizeof("CLEAR")]; + char stringpool_str95[sizeof("OEM_FJ_TOUROKU")]; + char stringpool_str96[sizeof("OEM_PA1")]; + char stringpool_str97[sizeof("ICO_HELP")]; + char stringpool_str98[sizeof("BROWSER_SEARCH")]; + char stringpool_str99[sizeof("SLEEP")]; + char stringpool_str101[sizeof("F1")]; + char stringpool_str102[sizeof("OEM_PA2")]; + char stringpool_str103[sizeof("OEM_COPY")]; + char stringpool_str104[sizeof("F8")]; + char stringpool_str105[sizeof("F19")]; + char stringpool_str106[sizeof("RIGHT")]; + char stringpool_str107[sizeof("F2")]; + char stringpool_str108[sizeof("OEM_6")]; + char stringpool_str109[sizeof("F18")]; + char stringpool_str111[sizeof("VOLUME_UP")]; + char stringpool_str114[sizeof("MEDIA_STOP")]; + char stringpool_str115[sizeof("OEM_PERIOD")]; + char stringpool_str117[sizeof("EREOF")]; + char stringpool_str121[sizeof("BROWSER_HOME")]; + char stringpool_str122[sizeof("F6")]; + char stringpool_str124[sizeof("BROWSER_REFRESH")]; + char stringpool_str126[sizeof("PA1")]; + char stringpool_str127[sizeof("PROCESSKEY")]; + char stringpool_str128[sizeof("DECIMAL")]; + char stringpool_str129[sizeof("OEM_3")]; + char stringpool_str130[sizeof("RMENU")]; + char stringpool_str131[sizeof("LMENU")]; + char stringpool_str132[sizeof("OEM_FJ_MASSHOU")]; + char stringpool_str133[sizeof("NUMPAD0")]; + char stringpool_str134[sizeof("HANGUL")]; + char stringpool_str135[sizeof("NUMPAD9")]; + char stringpool_str136[sizeof("HANGEUL")]; + char stringpool_str137[sizeof("OEM_5")]; + char stringpool_str138[sizeof("OEM_PA3")]; + char stringpool_str139[sizeof("VOLUME_MUTE")]; + char stringpool_str140[sizeof("OEM_4")]; + char stringpool_str141[sizeof("LAUNCH_MAIL")]; + char stringpool_str142[sizeof("OEM_FJ_JISHO")]; + char stringpool_str143[sizeof("F3")]; + char stringpool_str144[sizeof("OEM_FJ_ROYA")]; + char stringpool_str145[sizeof("OEM_FJ_LOYA")]; + char stringpool_str147[sizeof("DOWN")]; + char stringpool_str149[sizeof("OEM_FINISH")]; + char stringpool_str151[sizeof("F5")]; + char stringpool_str153[sizeof("OEM_7")]; + char stringpool_str154[sizeof("F4")]; + char stringpool_str155[sizeof("F17")]; + char stringpool_str156[sizeof("NUMPAD1")]; + char stringpool_str157[sizeof("ICO_00")]; + char stringpool_str159[sizeof("NUMPAD8")]; + char stringpool_str162[sizeof("NUMPAD2")]; + char stringpool_str164[sizeof("LAUNCH_APP1")]; + char stringpool_str165[sizeof("BROWSER_FORWARD")]; + char stringpool_str167[sizeof("F7")]; + char stringpool_str170[sizeof("LAUNCH_APP2")]; + char stringpool_str171[sizeof("MULTIPLY")]; + char stringpool_str174[sizeof("EXECUTE")]; + char stringpool_str176[sizeof("BROWSER_FAVORITES")]; + char stringpool_str177[sizeof("NUMPAD6")]; + char stringpool_str179[sizeof("F16")]; + char stringpool_str182[sizeof("F10")]; + char stringpool_str185[sizeof("VOLUME_DOWN")]; + char stringpool_str188[sizeof("F20")]; + char stringpool_str189[sizeof("MEDIA_PREV_TRACK")]; + char stringpool_str191[sizeof("MODECHANGE")]; + char stringpool_str197[sizeof("F14")]; + char stringpool_str198[sizeof("NUMPAD3")]; + char stringpool_str199[sizeof("XBUTTON1")]; + char stringpool_str203[sizeof("F24")]; + char stringpool_str205[sizeof("XBUTTON2")]; + char stringpool_str206[sizeof("NUMPAD5")]; + char stringpool_str209[sizeof("NUMPAD4")]; + char stringpool_str215[sizeof("MEDIA_PLAY_PAUSE")]; + char stringpool_str217[sizeof("LAUNCH_MEDIA_SELECT")]; + char stringpool_str218[sizeof("F11")]; + char stringpool_str220[sizeof("OEM_102")]; + char stringpool_str221[sizeof("MEDIA_NEXT_TRACK")]; + char stringpool_str222[sizeof("NUMPAD7")]; + char stringpool_str224[sizeof("F21")]; + char stringpool_str226[sizeof("F13")]; + char stringpool_str229[sizeof("F12")]; + char stringpool_str232[sizeof("F23")]; + char stringpool_str235[sizeof("F22")]; + char stringpool_str242[sizeof("F15")]; + char stringpool_str256[sizeof("DIVIDE")]; + }; +static const struct stringpool_t stringpool_contents = + { + "UP", + "APPS", + "CRSEL", + "SPACE", + "SCROLL", + "ESCAPE", + "CANCEL", + "ACCEPT", + "SEPARATOR", + "SELECT", + "CONTROL", + "OEM_CLEAR", + "OEM_RESET", + "OEM_AUTO", + "OEM_CUSEL", + "KANA", + "OEM_PLUS", + "PRIOR", + "OEM_ATTN", + "PAUSE", + "BACK", + "PACKET", + "RCONTROL", + "LCONTROL", + "END", + "HOME", + "PRINT", + "NUMLOCK", + "LEFT", + "JUNJA", + "MENU", + "OEM_WSCTRL", + "OEM_ENLW", + "NEXT", + "RWIN", + "LWIN", + "CAPITAL", + "HELP", + "NONAME", + "RBUTTON", + "LBUTTON", + "OEM_NEC_EQUAL", + "INSERT", + "HANJA", + "SNAPSHOT", + "ATTN", + "TAB", + "OEM_BACKTAB", + "ICO_CLEAR", + "CONVERT", + "RETURN", + "OEM_JUMP", + "BROWSER_STOP", + "FINAL", + "ZOOM", + "KANJI", + "DELETE", + "OEM_COMMA", + "SUBTRACT", + "MBUTTON", + "F9", + "SHIFT", + "RSHIFT", + "LSHIFT", + "ADD", + "NONCONVERT", + "EXSEL", + "OEM_1", + "OEM_AX", + "BROWSER_BACK", + "OEM_8", + "OEM_MINUS", + "PLAY", + "OEM_2", + "CLEAR", + "OEM_FJ_TOUROKU", + "OEM_PA1", + "ICO_HELP", + "BROWSER_SEARCH", + "SLEEP", + "F1", + "OEM_PA2", + "OEM_COPY", + "F8", + "F19", + "RIGHT", + "F2", + "OEM_6", + "F18", + "VOLUME_UP", + "MEDIA_STOP", + "OEM_PERIOD", + "EREOF", + "BROWSER_HOME", + "F6", + "BROWSER_REFRESH", + "PA1", + "PROCESSKEY", + "DECIMAL", + "OEM_3", + "RMENU", + "LMENU", + "OEM_FJ_MASSHOU", + "NUMPAD0", + "HANGUL", + "NUMPAD9", + "HANGEUL", + "OEM_5", + "OEM_PA3", + "VOLUME_MUTE", + "OEM_4", + "LAUNCH_MAIL", + "OEM_FJ_JISHO", + "F3", + "OEM_FJ_ROYA", + "OEM_FJ_LOYA", + "DOWN", + "OEM_FINISH", + "F5", + "OEM_7", + "F4", + "F17", + "NUMPAD1", + "ICO_00", + "NUMPAD8", + "NUMPAD2", + "LAUNCH_APP1", + "BROWSER_FORWARD", + "F7", + "LAUNCH_APP2", + "MULTIPLY", + "EXECUTE", + "BROWSER_FAVORITES", + "NUMPAD6", + "F16", + "F10", + "VOLUME_DOWN", + "F20", + "MEDIA_PREV_TRACK", + "MODECHANGE", + "F14", + "NUMPAD3", + "XBUTTON1", + "F24", + "XBUTTON2", + "NUMPAD5", + "NUMPAD4", + "MEDIA_PLAY_PAUSE", + "LAUNCH_MEDIA_SELECT", + "F11", + "OEM_102", + "MEDIA_NEXT_TRACK", + "NUMPAD7", + "F21", + "F13", + "F12", + "F23", + "F22", + "F15", + "DIVIDE" + }; +#define stringpool ((const char *) &stringpool_contents) +const struct vktable * +console_win32_vk (register const char *str, register size_t len) +{ + enum + { + TOTAL_KEYWORDS = 160, + MIN_WORD_LENGTH = 2, + MAX_WORD_LENGTH = 19, + MIN_HASH_VALUE = 12, + MAX_HASH_VALUE = 256 + }; + + static const struct vktable wordlist[] = + { + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, +#line 40 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str12, VK_UP}, +#line 52 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str13, VK_APPS}, +#line 159 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str14, VK_CRSEL}, +#line 34 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str15, VK_SPACE}, +#line 95 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str16, VK_SCROLL}, +#line 29 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str17, VK_ESCAPE}, +#line 9 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str18, VK_CANCEL}, +#line 32 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str19, VK_ACCEPT}, +#line 66 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str20, VK_SEPARATOR}, +#line 43 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str21, VK_SELECT}, +#line 18 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str22, VK_CONTROL}, +#line 166 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str23, VK_OEM_CLEAR}, +#line 145 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str24, VK_OEM_RESET}, +#line 155 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str25, VK_OEM_AUTO}, +#line 151 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str26, VK_OEM_CUSEL}, + {-1}, +#line 22 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str28, VK_KANA}, +#line 127 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str29, VK_OEM_PLUS}, +#line 35 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str30, VK_PRIOR}, +#line 152 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str31, VK_OEM_ATTN}, +#line 20 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str32, VK_PAUSE}, +#line 13 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str33, VK_BACK}, +#line 144 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str34, VK_PACKET}, +#line 105 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str35, VK_RCONTROL}, +#line 104 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str36, VK_LCONTROL}, +#line 37 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str37, VK_END}, +#line 38 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str38, VK_HOME}, +#line 44 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str39, VK_PRINT}, +#line 94 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str40, VK_NUMLOCK}, +#line 39 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str41, VK_LEFT}, +#line 25 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str42, VK_JUNJA}, +#line 19 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str43, VK_MENU}, +#line 150 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str44, VK_OEM_WSCTRL}, +#line 156 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str45, VK_OEM_ENLW}, +#line 36 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str46, VK_NEXT}, +#line 51 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str47, VK_RWIN}, +#line 50 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str48, VK_LWIN}, +#line 21 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str49, VK_CAPITAL}, +#line 49 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str50, VK_HELP}, +#line 164 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str51, VK_NONAME}, +#line 8 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str52, VK_RBUTTON}, +#line 7 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str53, VK_LBUTTON}, +#line 96 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str54, VK_OEM_NEC_EQUAL}, + {-1}, +#line 47 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str56, VK_INSERT}, +#line 27 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str57, VK_HANJA}, + {-1}, {-1}, +#line 46 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str60, VK_SNAPSHOT}, +#line 158 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str61, VK_ATTN}, +#line 14 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str62, VK_TAB}, +#line 157 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str63, VK_OEM_BACKTAB}, +#line 143 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str64, VK_ICO_CLEAR}, +#line 30 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str65, VK_CONVERT}, +#line 16 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str66, VK_RETURN}, +#line 146 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str67, VK_OEM_JUMP}, + {-1}, {-1}, {-1}, +#line 111 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str71, VK_BROWSER_STOP}, +#line 26 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str72, VK_FINAL}, +#line 163 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str73, VK_ZOOM}, +#line 28 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str74, VK_KANJI}, +#line 48 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str75, VK_DELETE}, +#line 128 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str76, VK_OEM_COMMA}, +#line 67 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str77, VK_SUBTRACT}, + {-1}, +#line 10 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str79, VK_MBUTTON}, +#line 78 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str80, VK_F9}, +#line 17 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str81, VK_SHIFT}, +#line 103 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str82, VK_RSHIFT}, +#line 102 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str83, VK_LSHIFT}, +#line 65 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str84, VK_ADD}, +#line 31 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str85, VK_NONCONVERT}, +#line 160 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str86, VK_EXSEL}, +#line 126 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str87, VK_OEM_1}, +#line 138 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str88, VK_OEM_AX}, +#line 108 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str89, VK_BROWSER_BACK}, +#line 137 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str90, VK_OEM_8}, +#line 129 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str91, VK_OEM_MINUS}, +#line 162 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str92, VK_PLAY}, +#line 131 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str93, VK_OEM_2}, +#line 15 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str94, VK_CLEAR}, +#line 99 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str95, VK_OEM_FJ_TOUROKU}, +#line 147 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str96, VK_OEM_PA1}, +#line 140 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str97, VK_ICO_HELP}, +#line 112 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str98, VK_BROWSER_SEARCH}, +#line 53 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str99, VK_SLEEP}, + {-1}, +#line 70 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str101, VK_F1}, +#line 148 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str102, VK_OEM_PA2}, +#line 154 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str103, VK_OEM_COPY}, +#line 77 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str104, VK_F8}, +#line 88 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str105, VK_F19}, +#line 41 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str106, VK_RIGHT}, +#line 71 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str107, VK_F2}, +#line 135 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str108, VK_OEM_6}, +#line 87 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str109, VK_F18}, + {-1}, +#line 117 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str111, VK_VOLUME_UP}, + {-1}, {-1}, +#line 120 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str114, VK_MEDIA_STOP}, +#line 130 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str115, VK_OEM_PERIOD}, + {-1}, +#line 161 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str117, VK_EREOF}, + {-1}, {-1}, {-1}, +#line 114 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str121, VK_BROWSER_HOME}, +#line 75 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str122, VK_F6}, + {-1}, +#line 110 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str124, VK_BROWSER_REFRESH}, + {-1}, +#line 165 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str126, VK_PA1}, +#line 142 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str127, VK_PROCESSKEY}, +#line 68 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str128, VK_DECIMAL}, +#line 132 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str129, VK_OEM_3}, +#line 107 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str130, VK_RMENU}, +#line 106 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str131, VK_LMENU}, +#line 98 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str132, VK_OEM_FJ_MASSHOU}, +#line 54 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str133, VK_NUMPAD0}, +#line 24 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str134, VK_HANGUL}, +#line 63 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str135, VK_NUMPAD9}, +#line 23 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str136, VK_HANGEUL}, +#line 134 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str137, VK_OEM_5}, +#line 149 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str138, VK_OEM_PA3}, +#line 115 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str139, VK_VOLUME_MUTE}, +#line 133 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str140, VK_OEM_4}, +#line 122 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str141, VK_LAUNCH_MAIL}, +#line 97 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str142, VK_OEM_FJ_JISHO}, +#line 72 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str143, VK_F3}, +#line 101 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str144, VK_OEM_FJ_ROYA}, +#line 100 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str145, VK_OEM_FJ_LOYA}, + {-1}, +#line 42 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str147, VK_DOWN}, + {-1}, +#line 153 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str149, VK_OEM_FINISH}, + {-1}, +#line 74 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str151, VK_F5}, + {-1}, +#line 136 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str153, VK_OEM_7}, +#line 73 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str154, VK_F4}, +#line 86 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str155, VK_F17}, +#line 55 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str156, VK_NUMPAD1}, +#line 141 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str157, VK_ICO_00}, + {-1}, +#line 62 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str159, VK_NUMPAD8}, + {-1}, {-1}, +#line 56 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str162, VK_NUMPAD2}, + {-1}, +#line 124 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str164, VK_LAUNCH_APP1}, +#line 109 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str165, VK_BROWSER_FORWARD}, + {-1}, +#line 76 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str167, VK_F7}, + {-1}, {-1}, +#line 125 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str170, VK_LAUNCH_APP2}, +#line 64 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str171, VK_MULTIPLY}, + {-1}, {-1}, +#line 45 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str174, VK_EXECUTE}, + {-1}, +#line 113 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str176, VK_BROWSER_FAVORITES}, +#line 60 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str177, VK_NUMPAD6}, + {-1}, +#line 85 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str179, VK_F16}, + {-1}, {-1}, +#line 79 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str182, VK_F10}, + {-1}, {-1}, +#line 116 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str185, VK_VOLUME_DOWN}, + {-1}, {-1}, +#line 89 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str188, VK_F20}, +#line 119 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str189, VK_MEDIA_PREV_TRACK}, + {-1}, +#line 33 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str191, VK_MODECHANGE}, + {-1}, {-1}, {-1}, {-1}, {-1}, +#line 83 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str197, VK_F14}, +#line 57 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str198, VK_NUMPAD3}, +#line 11 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str199, VK_XBUTTON1}, + {-1}, {-1}, {-1}, +#line 93 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str203, VK_F24}, + {-1}, +#line 12 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str205, VK_XBUTTON2}, +#line 59 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str206, VK_NUMPAD5}, + {-1}, {-1}, +#line 58 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str209, VK_NUMPAD4}, + {-1}, {-1}, {-1}, {-1}, {-1}, +#line 121 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str215, VK_MEDIA_PLAY_PAUSE}, + {-1}, +#line 123 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str217, VK_LAUNCH_MEDIA_SELECT}, +#line 80 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str218, VK_F11}, + {-1}, +#line 139 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str220, VK_OEM_102}, +#line 118 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str221, VK_MEDIA_NEXT_TRACK}, +#line 61 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str222, VK_NUMPAD7}, + {-1}, +#line 90 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str224, VK_F21}, + {-1}, +#line 82 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str226, VK_F13}, + {-1}, {-1}, +#line 81 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str229, VK_F12}, + {-1}, {-1}, +#line 92 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str232, VK_F23}, + {-1}, {-1}, +#line 91 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str235, VK_F22}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, +#line 84 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str242, VK_F15}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, +#line 69 "win32_vk.list" + {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str256, VK_DIVIDE} + }; + + if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) + { + register unsigned int key = hash (str, len); + + if (key <= MAX_HASH_VALUE) + { + register int o = wordlist[key].ofs; + if (o >= 0) + { + register const char *s = o + stringpool; + + if ((((unsigned char)*str ^ (unsigned char)*s) & ~32) == 0 && !gperf_case_strcmp (str, s)) + return &wordlist[key]; + } + } + } + return 0; +} diff --git a/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/lib/io/console.so b/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/lib/io/console.so new file mode 100755 index 00000000..4003c126 Binary files /dev/null and b/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/lib/io/console.so differ diff --git a/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/lib/io/console/size.rb b/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/lib/io/console/size.rb new file mode 100644 index 00000000..14b9a74b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/io-console-0.8.0/lib/io/console/size.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: false +# fallback to console window size +def IO.default_console_size + [ + ENV["LINES"].to_i.nonzero? || 25, + ENV["COLUMNS"].to_i.nonzero? || 80, + ] +end + +begin + require 'io/console' +rescue LoadError + class << IO + alias console_size default_console_size + end +else + # returns console window size + def IO.console_size + console.winsize + rescue NoMethodError + default_console_size + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/Gemfile b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/Gemfile new file mode 100644 index 00000000..b4e8955a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/Gemfile @@ -0,0 +1,29 @@ +source "https://rubygems.org" + +gemspec + +is_unix = RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i +is_truffleruby = RUBY_DESCRIPTION =~ /truffleruby/ + +if is_unix && ENV['WITH_VTERM'] + gem "vterm", github: "ruby/vterm-gem" + gem "yamatanooroti", github: "ruby/yamatanooroti" +end + +gem "stackprof" if is_unix && !is_truffleruby + +gem "reline", github: "ruby/reline" if ENV["WITH_LATEST_RELINE"] == "true" +gem "rake" +gem "test-unit" +gem "test-unit-ruby-core" + +gem "rubocop" + +gem "tracer" if !is_truffleruby +gem "debug", github: "ruby/debug", platforms: [:mri, :mswin] + +gem "rdoc", ">= 6.11.0" + +if RUBY_VERSION >= "3.0.0" && !is_truffleruby + gem "repl_type_completor" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/LICENSE.txt b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/LICENSE.txt new file mode 100644 index 00000000..66d93598 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/README.md b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/README.md new file mode 100644 index 00000000..b08ba26c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/README.md @@ -0,0 +1,125 @@ +# IRB + +[![Gem Version](https://badge.fury.io/rb/irb.svg)](https://badge.fury.io/rb/irb) +[![Static Badge](https://img.shields.io/badge/RDoc-flat?style=flat&label=documentation&link=https%3A%2F%2Fruby.github.io%2Firb%2F)](https://ruby.github.io/irb/) +[![build](https://github.com/ruby/irb/actions/workflows/test.yml/badge.svg)](https://github.com/ruby/irb/actions/workflows/test.yml) + + +IRB stands for "interactive Ruby" and is a tool to interactively execute Ruby expressions read from the standard input. + +The `irb` command from your shell will start the interpreter. + +## Installation + +> [!Note] +> +> IRB is a default gem of Ruby so you shouldn't need to install it separately. +> +> But if you're using Ruby 2.6 or later and want to upgrade/install a specific version of IRB, please follow these steps. + +To install it with `bundler`, add this line to your application's Gemfile: + +```ruby +gem 'irb' +``` + +And then execute: + +```shell +$ bundle +``` + +Or install it directly with: + +```shell +$ gem install irb +``` + +## Usage + +> [!Note] +> +> We're working hard to match Pry's variety of powerful features in IRB, and you can track our progress or find contribution ideas in [this document](https://ruby.github.io/irb/COMPARED_WITH_PRY_md.html). + +### The `irb` Executable + +You can start a fresh IRB session by typing `irb` in your terminal. + +In the session, you can evaluate Ruby expressions or even prototype a small Ruby script. An input is executed when it is syntactically complete. + +```shell +$ irb +irb(main):001> 1 + 2 +=> 3 +irb(main):002* class Foo +irb(main):003* def foo +irb(main):004* puts 1 +irb(main):005* end +irb(main):006> end +=> :foo +irb(main):007> Foo.new.foo +1 +=> nil +``` + +### The `binding.irb` Breakpoint + +If you use Ruby 2.5 or later versions, you can also use `binding.irb` in your program as breakpoints. + +Once a `binding.irb` is evaluated, a new IRB session will be started with the surrounding context: + +```shell +$ ruby test.rb + +From: test.rb @ line 2 : + + 1: def greet(word) + => 2: binding.irb + 3: puts "Hello #{word}" + 4: end + 5: + 6: greet("World") + +irb(main):001:0> word +=> "World" +irb(main):002:0> exit +Hello World +``` + +### Debugging + +You can use IRB as a debugging console with `debug.gem` with these options: + +- In `binding.irb`, use the `debug` command to start an `irb:rdbg` session with access to all `debug.gem` commands. +- Use the `RUBY_DEBUG_IRB_CONSOLE=1` environment variable to make `debug.gem` use IRB as the debugging console. + +To learn more about debugging with IRB, see [Debugging with IRB](https://ruby.github.io/irb/#label-Debugging+with+IRB). + +## Documentation + +https://ruby.github.io/irb/ provides a comprehensive guide to IRB's features and usage. + +## Configuration + +See the [Configuration page](https://ruby.github.io/irb/Configurations_md.html) in the documentation. + +## Extending IRB + +IRB `v1.13.0` and later versions allows users/libraries to extend its functionality through official APIs. + +For more information, please visit the [IRB Extension Guide](https://ruby.github.io/irb/EXTEND_IRB_md.html). + +## Contributing + +See [CONTRIBUTING.md](./CONTRIBUTING.md) for more information. + +## Releasing + +``` +rake release +gh release create vX.Y.Z --generate-notes +``` + +## License + +The gem is available as open source under the terms of the [2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause). diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/Rakefile b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/Rakefile new file mode 100644 index 00000000..e956107a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/Rakefile @@ -0,0 +1,52 @@ +require "bundler/gem_tasks" +require "rake/testtask" +require "rdoc/task" + +Rake::TestTask.new(:test) do |t| + t.libs << "test" << "test/lib" + t.libs << "lib" + t.ruby_opts << "-rhelper" + t.test_files = FileList["test/irb/**/test_*.rb"] +end + +# To make sure they have been correctly setup for Ruby CI. +desc "Run each irb test file in isolation." +task :test_in_isolation do + failed = false + + FileList["test/irb/**/test_*.rb"].each do |test_file| + ENV["TEST"] = test_file + begin + Rake::Task["test"].execute + rescue + failed = true + msg = "Test '#{test_file}' failed when being executed in isolation. Please make sure 'rake test TEST=#{test_file}' passes." + separation_line = '=' * msg.length + + puts <<~MSG + #{separation_line} + #{msg} + #{separation_line} + MSG + end + end + + fail "Some tests failed when being executed in isolation" if failed +end + +Rake::TestTask.new(:test_yamatanooroti) do |t| + t.libs << 'test' << "test/lib" + t.libs << 'lib' + #t.loader = :direct + t.ruby_opts << "-rhelper" + t.pattern = 'test/irb/yamatanooroti/test_*.rb' +end + +task :default => :test + +RDoc::Task.new do |rdoc| + rdoc.title = "IRB Documentation" + rdoc.main = "Index.md" + rdoc.rdoc_dir = "_site" + rdoc.options.push("lib") +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/bin/console b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/bin/console new file mode 100755 index 00000000..4405b232 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/bin/console @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +require_relative "../lib/irb" + +IRB.start(__FILE__) diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/bin/setup b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/bin/setup new file mode 100755 index 00000000..cf4ad25e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/bin/setup @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/doc/irb/irb-tools.rd.ja b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/doc/irb/irb-tools.rd.ja new file mode 100644 index 00000000..b997f0ed --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/doc/irb/irb-tools.rd.ja @@ -0,0 +1,184 @@ +irb関連おまけコマンドとライブラリ + $Release Version: 0.7.1 $ + $Revision$ + by Keiju ISHITSUKA(Nihon Rational Co.,Ltd.) + +=begin + +:コマンド: +* rtags -- ruby tags command + +:関数ライブラリ: +* xmp -- irb version of gotoken xmp-function + +:クラスライブラリ: +* frame.rb -- frame tracer +* completion.rb -- irb completor + += rtags + +rtagsはemacs及びvi用の, TAGファイルをつくるコマンドです. + +== 使い方 + + rtags [-vi] file.... + +カレントディレクトリにemacs用のTAGSファイルができます. -viオプションを +つけた時にはvi用のtagsファイルを作成します. + +emacsの場合, 通常のetags.elがそのまま使えます. 検索可能なのは, + +* クラス +* メソッド +* 特異メソッド +* alias +* attrで宣言されたアクセサ(パラメータがシンボルか文字列リテラルに限る) +* attr_XXXで宣言されたアクセサ(パラメータがシンボルか文字列リテラルに限る) + +です. + +Cなどで使っているのと違うのは, コンプリーションに関する部分で, + +関数名は, + + 関数名( + +クラスは, + + ::クラス名::....::クラス名 + +メソッドは, + + ::クラス名::....::クラス名#メソッド名 + +特異メソッド(クラスメソッド)は + + ::クラス名::....::クラス名.メソッド名 + +でコンプリーションを行なうところです. + += xmp.rb + +ごとけんxmpの上位互換バージョンです. ただ, 非常に重いのでごとけんxmpで +は対応できない時に, 使用すると良いでしょう. + +== 使い方 + +=== 関数として使う. + + require "irb/xmp" + xmp <1 + foo + ==>1 + +=== XMPインスタンスを用いる. + +この場合は, XMPがコンテキスト情報を持つので, 変数の値などを保持してい +ます. + + require "irb/xmp" + xmp = XMP.new + xmp.puts <1 + foo + ==>1 + foo + ==>1 + +== コンテキストに関して + +XMPメソッド群のコンテキストは, 呼び出す前のコンテキストで評価されます. +明示的にコンテキストを指定するとそのコンテキストで評価します. + +例: + + xmp "foo", an_binding + +:注: +マルチスレッドには対応していません. + += frame.rb +現在実行中のフレーム情報を取り扱うためのクラスです. + +* IRB::Frame.top(n = 0) + 上からn番目のコンテキストを取り出します. nは0が最上位になります. +* IRB::Frame.bottom(n = 0) + 下からn番目のコンテキストを取り出します. nは0が最下位になります. +* IRB::Frame.sender + センダになっているオブジェクトを取り出します. センダとは, そのメソッ + ドを呼び出した側のselfのことです. + +:注: +set_trace_funcを用いてRubyの実行をトレースしています. マルチスレッドに +は対応していません. + += completion.rb +irbのcompletion機能を提供するものです. + +== 使い方 + + % irb -r irb/completion + +とするか, ~/.irbrc 中に + + require "irb/completion" + +を入れてください. irb実行中に require "irb/completion" してもよいです. + +irb実行中に (TAB) を押すとコンプレーションします. + +トップレベルで(TAB)を押すとすべての構文要素, クラス, メソッドの候補がで +ます. 候補が唯一ならば完全に補完します. + + irb(main):001:0> in + in inspect instance_eval + include install_alias_method instance_of? + initialize install_aliases instance_variables + irb(main):001:0> inspect + "main" + irb(main):002:0> foo = Object.new + # + + ((|変数名.|))の後に(TAB)を押すと, そのオブジェクトのメソッド一覧がでま + す. + + irb(main):003:0> foo. + foo.== foo.frozen? foo.protected_methods + foo.=== foo.hash foo.public_methods + foo.=~ foo.id foo.respond_to? + foo.__id__ foo.inspect foo.send + foo.__send__ foo.instance_eval foo.singleton_methods + foo.class foo.instance_of? foo.taint + foo.clone foo.instance_variables foo.tainted? + foo.display foo.is_a? foo.to_a + foo.dup foo.kind_of? foo.to_s + foo.eql? foo.method foo.type + foo.equal? foo.methods foo.untaint + foo.extend foo.nil? + foo.freeze foo.private_methods + +=end + +% Begin Emacs Environment +% Local Variables: +% mode: text +% comment-column: 0 +% comment-start: "%" +% comment-end: "\n" +% End: +% + diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/doc/irb/irb.rd.ja b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/doc/irb/irb.rd.ja new file mode 100644 index 00000000..c51e0bd6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/doc/irb/irb.rd.ja @@ -0,0 +1,425 @@ +irb -- interactive ruby + $Release Version: 0.9.5 $ + $Revision$ + by Keiju ISHITSUKA(keiju@ruby-lang.org) +=begin += irbとは? + +irbはinteractive rubyの略です. rubyの式を標準入力から簡単に入力/実行する +ためのツールです. + += 起動 + + % irb + +で行ないます. + += 使い方 + +irbの使い方は, Rubyさえ知っていればいたって簡単です. 基本的には irb と +いうコマンドを実行するだけです. irbを実行すると, 以下のようなプロンプ +トが表れてきます. 後は, rubyの式を入れて下さい. 式が完結した時点で実行 +されます. + + dim% irb + irb(main):001:0> 1+2 + 3 + irb(main):002:0> class Foo + irb(main):003:1> def foo + irb(main):004:2> print 1 + irb(main):005:2> end + irb(main):006:1> end + nil + irb(main):007:0> + +また, irbはReadlineモジュールにも対応しています. Readlineモジュールが +インストールされている時には, それを使うのが標準の動作になります. + += コマンドオプション + + irb.rb [options] file_name opts + options: + -f ~/.irbrc を読み込まない. + -d $DEBUG をtrueにする(ruby -d と同じ) + -r load-module ruby -r と同じ. + -I path $LOAD_PATH に path を追加する. + -U ruby -U と同じ. + -E enc ruby -E と同じ. + -w ruby -w と同じ. + -W[level=2] ruby -W と同じ. + --context-mode n 新しいワークスペースを作成した時に関連する Binding + オブジェクトの作成方法を 0 から 3 のいずれかに設定する. + --echo 実行結果を表示する(デフォルト). + --noecho 実行結果を表示しない. + --echo-on-assignment + 代入時に実行結果を表示する. + --noecho-on-assignment + 代入時に実行結果を表示しない. + --truncate-echo-on-assignment + 代入時に省略された実行結果を表示する(デフォルト). + --inspect 結果出力にinspectを用いる. + --noinspect 結果出力にinspectを用いない. + --singleline シングルラインエディタを利用する. + --nosingleline シングルラインエディタを利用しない. デフォルトの動 + 作は, inf-ruby-mode以外でシングルラインエディタを利 + 用しようとする. + --colorize 色付けを利用する. + --nocolorize 色付けを利用しない. + --autocomplete オートコンプリートを利用する. + --noautocomplete オートコンプリートを利用しない. + --prompt prompt-mode + --prompt-mode prompt-mode + プロンプトモードを切替えます. 現在定義されているプ + ロンプトモードは, default, simple, xmp, inf-rubyが + 用意されています. + --inf-ruby-mode emacsのinf-ruby-mode用のプロンプト表示を行なう. 特 + に指定がない限り, ラインエディタは使わなくなる. + --simple-prompt + 非常にシンプルなプロンプトを用いるモードです. + --noprompt プロンプト表示を行なわない. + --single-irb irb 中で self を実行して得られるオブジェクトをサ + ブ irb と共有する. + --tracer コマンド実行時にトレースを行なう. + --back-trace-limit n + バックトレース表示をバックトレースの頭から n, 後ろ + からnだけ行なう. デフォルトは16 + + --verbose 詳細なメッセージを出力する. + --noverbose 詳細なメッセージを出力しない(デフォルト). + -v, --version irbのバージョンを表示する. + -h, --help irb のヘルプを表示する. + -- 以降のコマンドライン引数をオプションとして扱わない. + += コンフィギュレーション + +irb起動時に``~/.irbrc''を読み込みます. もし存在しない場合は, +``.irbrc'', ``irb.rc'', ``_irbrc'', ``$irbrc''の順にloadを試みます. + +オプションを設定する代わりに, 以下のコマンドでもデフォルトの動作を設定 +できます. + + IRB.conf[:IRB_NAME]="irb" + IRB.conf[:USE_TRACER]=false + IRB.conf[:USE_LOADER]=false + IRB.conf[:IGNORE_SIGINT]=true + IRB.conf[:IGNORE_EOF]=false + IRB.conf[:INSPECT_MODE]=nil + IRB.conf[:IRB_RC] = nil + IRB.conf[:BACK_TRACE_LIMIT]=16 + IRB.conf[:USE_LOADER] = false + IRB.conf[:USE_SINGLELINE] = nil + IRB.conf[:USE_TRACER] = false + IRB.conf[:IGNORE_SIGINT] = true + IRB.conf[:IGNORE_EOF] = false + IRB.conf[:PROMPT_MODE] = :DEFAULT + IRB.conf[:PROMPT] = {...} + IRB.conf[:VERBOSE]=true + +== プロンプトの設定 + +プロンプトをカスタマイズしたい時には, + + IRB.conf[:PROMPT] + +を用います. 例えば, .irbrcの中で下のような式を記述します: + + IRB.conf[:PROMPT][:MY_PROMPT] = { # プロンプトモードの名前 + :PROMPT_I => nil, # 通常のプロンプト + :PROMPT_S => nil, # 文字列などの継続行のプロンプト + :PROMPT_C => nil, # 式が継続している時のプロンプト + :RETURN => " ==>%s\n" # リターン時のプロンプト + } + +プロンプトモードを指定したい時には, + + irb --prompt my-prompt + +でそのプロンプトモードで起動されます. または, .irbrcに下式を記述しても +OKです. + + IRB.conf[:PROMPT_MODE] = :MY_PROMPT + +PROMPT_I, PROMPT_S, PROMPT_Cは, フォーマットを指定します. + + %N 起動しているコマンド名が出力される. + %m mainオブジェクト(self)がto_sで出力される. + %M mainオブジェクト(self)がinspectされて出力される. + %l 文字列中のタイプを表す(", ', /, ], `]'は%wの中の時) + %NNi インデントのレベルを表す. NNは数字が入りprintfの%NNdと同じ. 省 + 略可能 + %NNn 行番号を表します. + %% % + +例えば, デフォルトのプロンプトモードは: + + IRB.conf[:PROMPT][:DEFAULT] = { + :PROMPT_I => "%N(%m):%03n:%i> ", + :PROMPT_S => "%N(%m):%03n:%i%l ", + :PROMPT_C => "%N(%m):%03n:%i* ", + :RETURN => "=> %s\n" + } + +となっています. + +RETURNは, 現在のところprintf形式です. 将来仕様が変わるかも知れません. + +== サブirbの設定 + +コマンドラインオプションおよびIRB.confは(サブ)irb起動時のデフォルトの +設定を決めるもので, `5. コマンド'にあるconfで個別の(サブ)irbの設定がで +きるようになっています. + +IRB.conf[:IRB_RC]にprocが設定されていると, サブirbを起動する時にその +procをirbのコンテキストを引数として呼び出します. これによって個別のサ +ブirbごとに設定を変えることができるようになります. + + += コマンド + +irb拡張コマンドは, 簡単な名前と頭に`irb_'をつけた名前と両方定義されて +います. これは, 簡単な名前がoverrideされた時のためです. + +--- exit, quit, irb_exit + 終了する. + サブirbの場合, そのサブirbを終了する. + +--- conf, irb_context + irbの現在の設定を表示する. 設定の変更は, confにメッセージを送るこ + とによって行なえる. + +--- conf.eval_history = N + 実行結果のヒストリ機能の設定. + nnは整数かnilで nn>0 であればその数だけヒストリにためる。nn==0の時は + 無制限に記憶する、nilだとヒストリ機能はやめる(デフォルト). + +--- Conf.back_trace_limit + バックトレース表示をバックトレースの頭からn, 後ろからnだけ行なう. + デフォルトは16 + +--- conf.ignore_eof = true/false + ^Dが入力された時の動作を設定する. trueの時は^Dを無視する, falseの + 時はirbを終了する. + +--- conf.ignore_sigint= true/false + ^Cが入力された時の動作を設定する. false時は, irbを終了する. trueの + 時の動作は以下のようになる: + 入力中: これまで入力したものをキャンセルしトップレベルに戻る. + 実行中: 実行を中止する. + +--- conf.inf_ruby_mode = true/false + inf-ruby-mode用のプロンプト表示を行なう. デフォルトはfalse. 特に指定 + がない限り, ラインエディタは使わなくなる. + +--- conf.inspect_mode = true/false/nil + インスペクトモードを設定する. + true: インスペクトして表示する. + false: 通常のprintで表示する. + nil: 通常モードであれば, inspect modeとなり, mathモードの時は, non + inspect modeとなる. + +--- conf.use_loader = true/false + load/require時にirbのfile読み込み機能を用いるモードのスイッチ(デフォ + ルトは用いない). このモードはIRB全体に反映される. + +--- conf.prompt_c + ifの直後など, 行が継続している時のプロンプト. + +--- conf.prompt_i + 通常のプロンプト. + +--- conf.prompt_s + 文字列中などを表すプロンプト. + +--- conf.rc + ~/.irbrcを読み込んだかどうか? + +--- conf.use_prompt = true/false + プロンプト表示するかどうか? デフォルトではプロンプトを表示する. + +--- conf.use_multiline = true/false/nil + マルチラインエディタを使うかどうか? + true: マルチラインエディタを使う. + false: マルチラインエディタを使わない. + nil: (デフォルト)inf-ruby-mode以外でマルチラインエディタを利用しよう + とする. + +--- conf.use_singleline = true/false/nil + シングルラインエディタを使うかどうか? + true: シングルラインエディタを使う. + false: シングルラインエディタを使わない. + nil: (デフォルト)inf-ruby-modeとマルチラインエディタ以外でシングルラ + インエディタを利用しようとする. +# +#--- conf.verbose=T/F +# irbからいろいろなメッセージを出力するか? + +--- cws, chws, irb_cws, irb_chws, irb_change_workspace [obj] + objをselfとする. objが省略されたときは, home workspace, すなわち + irbを起動したときのmain objectをselfとする. + +--- pushws, irb_pushws, irb_push_workspace [obj] + UNIXシェルコマンドのpushdと同様. + +--- popws, irb_popws, irb_pop_workspace + UNIXシェルコマンドのpopdと同様. + +--- irb [obj] + サブirbを立ちあげる. objが指定された時は, そのobjをselfとする. + +--- jobs, irb_jobs + サブirbのリスト + +--- fg n, irb_fg n + 指定したサブirbにスイッチする. nは, 次のものを指定する. + + irb番号 + スレッド + irbオブジェクト + self(irb objで起動した時のobj) + +--- kill n, irb_kill n + サブirbをkillする. nはfgと同じ. + +--- source, irb_source path + UNIXシェルコマンドのsourceと似ている. 現在の環境上でpath内のスクリ + プトを評価する. + +--- irb_load path, prev + + Rubyのloadのirb版. + += システム変数 + +--- _ + 前の計算の実行結果を覚えている(ローカル変数). +--- __ + 実行結果の履歴を覚えている. + __[line_no]で、その行で実行した結果を得ることができる. line_noが負の + 時には、最新の結果から-line_no前の結果を得ることができる. + += 使用例 + +以下のような感じです. + + dim% ruby irb.rb + irb(main):001:0> irb # サブirbの立ちあげ + irb#1(main):001:0> jobs # サブirbのリスト + #0->irb on main (# : stop) + #1->irb#1 on main (# : running) + nil + irb#1(main):002:0> fg 0 # jobのスイッチ + nil + irb(main):002:0> class Foo;end + nil + irb(main):003:0> irb Foo # Fooをコンテキストしてirb + # 立ちあげ + irb#2(Foo):001:0> def foo # Foo#fooの定義 + irb#2(Foo):002:1> print 1 + irb#2(Foo):003:1> end + nil + irb#2(Foo):004:0> fg 0 # jobをスイッチ + nil + irb(main):004:0> jobs # jobのリスト + #0->irb on main (# : running) + #1->irb#1 on main (# : stop) + #2->irb#2 on Foo (# : stop) + nil + irb(main):005:0> Foo.instance_methods # Foo#fooがちゃんと定義さ + # れている + ["foo"] + irb(main):006:0> fg 2 # jobをスイッチ + nil + irb#2(Foo):005:0> def bar # Foo#barを定義 + irb#2(Foo):006:1> print "bar" + irb#2(Foo):007:1> end + nil + irb#2(Foo):010:0> Foo.instance_methods + ["bar", "foo"] + irb#2(Foo):011:0> fg 0 + nil + irb(main):007:0> f = Foo.new + # + irb(main):008:0> irb f # Fooのインスタンスでirbを + # 立ちあげる. + irb#3(#):001:0> jobs + #0->irb on main (# : stop) + #1->irb#1 on main (# : stop) + #2->irb#2 on Foo (# : stop) + #3->irb#3 on # (# : running) + nil + irb#3(#):002:0> foo # f.fooの実行 + nil + irb#3(#):003:0> bar # f.barの実行 + barnil + irb#3(#):004:0> kill 1, 2, 3# jobのkill + nil + irb(main):009:0> jobs + #0->irb on main (# : running) + nil + irb(main):010:0> exit # 終了 + dim% + += 使用上の制限 + +irbは, 評価できる時点(式が閉じた時点)での逐次実行を行ないます. したがっ +て, rubyを直接使った時と, 若干異なる動作を行なう場合があります. + +現在明らかになっている問題点を説明します. + +== ローカル変数の宣言 + +rubyでは, 以下のプログラムはエラーになります. + + eval "foo = 0" + foo + -- + -:2: undefined local variable or method `foo' for # (NameError) + --- + NameError + +ところが, irbを用いると + + >> eval "foo = 0" + => 0 + >> foo + => 0 + +となり, エラーを起こしません. これは, rubyが最初にスクリプト全体をコン +パイルしてローカル変数を決定するからです. それに対し, irbは実行可能に +なる(式が閉じる)と自動的に評価しているからです. 上記の例では, + + eval "foo = 0" + +を行なった時点で評価を行ない, その時点で変数が定義されるため, 次式で +変数fooは定義されているからです. + +このようなrubyとirbの動作の違いを解決したい場合は, begin...endで括って +バッチ的に実行して下さい: + + >> begin + ?> eval "foo = 0" + >> foo + >> end + NameError: undefined local variable or method `foo' for # + (irb):3 + (irb_local_binding):1:in `eval' + +== ヒアドキュメント + +現在のところヒアドキュメントの実装は不完全です. + +== シンボル + +シンボルであるかどうかの判断を間違えることがあります. 具体的には式が完了 +しているのに継続行と見なすことがあります. + +=end + +% Begin Emacs Environment +% Local Variables: +% mode: text +% comment-column: 0 +% comment-start: "%" +% comment-end: "\n" +% End: +% diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/exe/irb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/exe/irb new file mode 100755 index 00000000..12f41e4f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/exe/irb @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +# +# irb.rb - interactive ruby +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +require "irb" + +IRB.start(__FILE__) diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/irb.gemspec b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/irb.gemspec new file mode 100644 index 00000000..af14713f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/irb.gemspec @@ -0,0 +1,48 @@ +begin + require_relative "lib/irb/version" +rescue LoadError + # for Ruby core repository + require_relative "version" +end + +Gem::Specification.new do |spec| + spec.name = "irb" + spec.version = IRB::VERSION + spec.authors = ["aycabta", "Keiju ISHITSUKA"] + spec.email = ["aycabta@gmail.com", "keiju@ruby-lang.org"] + + spec.summary = %q{Interactive Ruby command-line tool for REPL (Read Eval Print Loop).} + spec.description = %q{Interactive Ruby command-line tool for REPL (Read Eval Print Loop).} + spec.homepage = "https://github.com/ruby/irb" + spec.licenses = ["Ruby", "BSD-2-Clause"] + + spec.metadata["homepage_uri"] = spec.homepage + spec.metadata["source_code_uri"] = spec.homepage + spec.metadata["documentation_uri"] = "https://ruby.github.io/irb/" + spec.metadata["changelog_uri"] = "#{spec.homepage}/releases" + + spec.files = [ + "Gemfile", + "LICENSE.txt", + "README.md", + "Rakefile", + "bin/console", + "bin/setup", + "doc/irb/irb-tools.rd.ja", + "doc/irb/irb.rd.ja", + "exe/irb", + "irb.gemspec", + "man/irb.1", + ] + Dir.chdir(File.expand_path('..', __FILE__)) do + Dir.glob("lib/**/*").map {|f| f unless File.directory?(f) }.compact + end + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + + spec.required_ruby_version = Gem::Requirement.new(">= 2.7") + + spec.add_dependency "reline", ">= 0.4.2" + spec.add_dependency "rdoc", ">= 4.0.0" + spec.add_dependency "pp", ">= 0.6.0" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb.rb new file mode 100644 index 00000000..fd0bfe35 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb.rb @@ -0,0 +1,736 @@ +# frozen_string_literal: true + +# :markup: markdown +# irb.rb - irb main module +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +require "ripper" +require "reline" + +require_relative "irb/init" +require_relative "irb/context" +require_relative "irb/default_commands" + +require_relative "irb/ruby-lex" +require_relative "irb/statement" +require_relative "irb/history" +require_relative "irb/input-method" +require_relative "irb/locale" +require_relative "irb/color" + +require_relative "irb/version" +require_relative "irb/easter-egg" +require_relative "irb/debug" +require_relative "irb/pager" + +module IRB + + # An exception raised by IRB.irb_abort + class Abort < Exception;end # :nodoc: + + class << self + # The current IRB::Context of the session, see IRB.conf + # + # irb + # irb(main):001:0> IRB.CurrentContext.irb_name = "foo" + # foo(main):002:0> IRB.conf[:MAIN_CONTEXT].irb_name #=> "foo" + def CurrentContext # :nodoc: + conf[:MAIN_CONTEXT] + end + + # Initializes IRB and creates a new Irb.irb object at the `TOPLEVEL_BINDING` + def start(ap_path = nil) + STDOUT.sync = true + $0 = File::basename(ap_path, ".rb") if ap_path + + setup(ap_path) + + if @CONF[:SCRIPT] + irb = Irb.new(nil, @CONF[:SCRIPT]) + else + irb = Irb.new + end + irb.run(@CONF) + end + + # Quits irb + def irb_exit(*) # :nodoc: + throw :IRB_EXIT, false + end + + # Aborts then interrupts irb. + # + # Will raise an Abort exception, or the given `exception`. + def irb_abort(irb, exception = Abort) # :nodoc: + irb.context.thread.raise exception, "abort then interrupt!" + end + end + + class Irb + # Note: instance and index assignment expressions could also be written like: + # "foo.bar=(1)" and "foo.[]=(1, bar)", when expressed that way, the former be + # parsed as :assign and echo will be suppressed, but the latter is parsed as a + # :method_add_arg and the output won't be suppressed + + PROMPT_MAIN_TRUNCATE_LENGTH = 32 + PROMPT_MAIN_TRUNCATE_OMISSION = '...' + CONTROL_CHARACTERS_PATTERN = "\x00-\x1F" + + # Returns the current context of this irb session + attr_reader :context + # The lexer used by this irb session + attr_accessor :scanner + + attr_reader :from_binding + + # Creates a new irb session + def initialize(workspace = nil, input_method = nil, from_binding: false) + @from_binding = from_binding + @context = Context.new(self, workspace, input_method) + @context.workspace.load_helper_methods_to_main + @signal_status = :IN_IRB + @scanner = RubyLex.new + @line_no = 1 + end + + # A hook point for `debug` command's breakpoint after :IRB_EXIT as well as its + # clean-up + def debug_break + # it means the debug integration has been activated + if defined?(DEBUGGER__) && DEBUGGER__.respond_to?(:capture_frames_without_irb) + # after leaving this initial breakpoint, revert the capture_frames patch + DEBUGGER__.singleton_class.send(:alias_method, :capture_frames, :capture_frames_without_irb) + # and remove the redundant method + DEBUGGER__.singleton_class.send(:undef_method, :capture_frames_without_irb) + end + end + + def debug_readline(binding) + workspace = IRB::WorkSpace.new(binding) + context.replace_workspace(workspace) + context.workspace.load_helper_methods_to_main + @line_no += 1 + + # When users run: + # 1. Debugging commands, like `step 2` + # 2. Any input that's not irb-command, like `foo = 123` + # + # + # Irb#eval_input will simply return the input, and we need to pass it to the + # debugger. + input = nil + forced_exit = catch(:IRB_EXIT) do + if History.save_history? && context.io.support_history_saving? + # Previous IRB session's history has been saved when `Irb#run` is exited We need + # to make sure the saved history is not saved again by resetting the counter + context.io.reset_history_counter + + begin + input = eval_input + ensure + context.io.save_history + end + else + input = eval_input + end + false + end + + Kernel.exit if forced_exit + + if input&.include?("\n") + @line_no += input.count("\n") - 1 + end + + input + end + + def run(conf = IRB.conf) + in_nested_session = !!conf[:MAIN_CONTEXT] + conf[:IRB_RC].call(context) if conf[:IRB_RC] + prev_context = conf[:MAIN_CONTEXT] + conf[:MAIN_CONTEXT] = context + + load_history = !in_nested_session && context.io.support_history_saving? + save_history = load_history && History.save_history? + + if load_history + context.io.load_history + end + + prev_trap = trap("SIGINT") do + signal_handle + end + + begin + if defined?(RubyVM.keep_script_lines) + keep_script_lines_backup = RubyVM.keep_script_lines + RubyVM.keep_script_lines = true + end + + forced_exit = catch(:IRB_EXIT) do + eval_input + end + ensure + # Do not restore to nil. It will cause IRB crash when used with threads. + IRB.conf[:MAIN_CONTEXT] = prev_context if prev_context + + RubyVM.keep_script_lines = keep_script_lines_backup if defined?(RubyVM.keep_script_lines) + trap("SIGINT", prev_trap) + conf[:AT_EXIT].each{|hook| hook.call} + + context.io.save_history if save_history + Kernel.exit if forced_exit + end + end + + # Evaluates input for this session. + def eval_input + configure_io + + each_top_level_statement do |statement, line_no| + signal_status(:IN_EVAL) do + begin + # If the integration with debugger is activated, we return certain input if it + # should be dealt with by debugger + if @context.with_debugger && statement.should_be_handled_by_debugger? + return statement.code + end + + @context.evaluate(statement, line_no) + + if @context.echo? && !statement.suppresses_echo? + if statement.is_assignment? + if @context.echo_on_assignment? + output_value(@context.echo_on_assignment? == :truncate) + end + else + output_value + end + end + rescue SystemExit, SignalException + raise + rescue Interrupt, Exception => exc + handle_exception(exc) + @context.workspace.local_variable_set(:_, exc) + end + end + end + end + + def read_input(prompt) + signal_status(:IN_INPUT) do + @context.io.prompt = prompt + if l = @context.io.gets + print l if @context.verbose? + else + if @context.ignore_eof? and @context.io.readable_after_eof? + l = "\n" + if @context.verbose? + printf "Use \"exit\" to leave %s\n", @context.ap_name + end + else + print "\n" if @context.prompting? + end + end + l + end + end + + def readmultiline + prompt = generate_prompt([], false, 0) + + # multiline + return read_input(prompt) if @context.io.respond_to?(:check_termination) + + # nomultiline + code = +'' + line_offset = 0 + loop do + line = read_input(prompt) + unless line + return code.empty? ? nil : code + end + + code << line + return code if command?(code) + + tokens, opens, terminated = @scanner.check_code_state(code, local_variables: @context.local_variables) + return code if terminated + + line_offset += 1 + continue = @scanner.should_continue?(tokens) + prompt = generate_prompt(opens, continue, line_offset) + end + end + + def each_top_level_statement + loop do + code = readmultiline + break unless code + yield parse_input(code), @line_no + @line_no += code.count("\n") + rescue RubyLex::TerminateLineInput + end + end + + def parse_input(code) + if code.match?(/\A\n*\z/) + return Statement::EmptyInput.new + end + + code = code.dup.force_encoding(@context.io.encoding) + is_assignment_expression = @scanner.assignment_expression?(code, local_variables: @context.local_variables) + + @context.parse_input(code, is_assignment_expression) + end + + def command?(code) + parse_input(code).is_a?(Statement::Command) + end + + def configure_io + if @context.io.respond_to?(:check_termination) + @context.io.check_termination do |code| + if Reline::IOGate.in_pasting? + rest = @scanner.check_termination_in_prev_line(code, local_variables: @context.local_variables) + if rest + Reline.delete_text + rest.bytes.reverse_each do |c| + Reline.ungetc(c) + end + true + else + false + end + else + next true if command?(code) + + _tokens, _opens, terminated = @scanner.check_code_state(code, local_variables: @context.local_variables) + terminated + end + end + end + if @context.io.respond_to?(:dynamic_prompt) + @context.io.dynamic_prompt do |lines| + tokens = RubyLex.ripper_lex_without_warning(lines.map{ |l| l + "\n" }.join, local_variables: @context.local_variables) + line_results = IRB::NestingParser.parse_by_line(tokens) + tokens_until_line = [] + line_results.map.with_index do |(line_tokens, _prev_opens, next_opens, _min_depth), line_num_offset| + line_tokens.each do |token, _s| + # Avoid appending duplicated token. Tokens that include "n" like multiline + # tstring_content can exist in multiple lines. + tokens_until_line << token if token != tokens_until_line.last + end + continue = @scanner.should_continue?(tokens_until_line) + generate_prompt(next_opens, continue, line_num_offset) + end + end + end + + if @context.io.respond_to?(:auto_indent) and @context.auto_indent_mode + @context.io.auto_indent do |lines, line_index, byte_pointer, is_newline| + next nil if lines == [nil] # Workaround for exit IRB with CTRL+d + next nil if !is_newline && lines[line_index]&.byteslice(0, byte_pointer)&.match?(/\A\s*\z/) + + code = lines[0..line_index].map { |l| "#{l}\n" }.join + tokens = RubyLex.ripper_lex_without_warning(code, local_variables: @context.local_variables) + @scanner.process_indent_level(tokens, lines, line_index, is_newline) + end + end + end + + def convert_invalid_byte_sequence(str, enc) + str.force_encoding(enc) + str.scrub { |c| + c.bytes.map{ |b| "\\x#{b.to_s(16).upcase}" }.join + } + end + + def encode_with_invalid_byte_sequence(str, enc) + conv = Encoding::Converter.new(str.encoding, enc) + dst = String.new + begin + ret = conv.primitive_convert(str, dst) + case ret + when :invalid_byte_sequence + conv.insert_output(conv.primitive_errinfo[3].dump[1..-2]) + redo + when :undefined_conversion + c = conv.primitive_errinfo[3].dup.force_encoding(conv.primitive_errinfo[1]) + conv.insert_output(c.dump[1..-2]) + redo + when :incomplete_input + conv.insert_output(conv.primitive_errinfo[3].dump[1..-2]) + when :finished + end + break + end while nil + dst + end + + def handle_exception(exc) + if exc.backtrace[0] =~ /\/irb(2)?(\/.*|-.*|\.rb)?:/ && exc.class.to_s !~ /^IRB/ && + !(SyntaxError === exc) && !(EncodingError === exc) + # The backtrace of invalid encoding hash (ex. {"\xAE": 1}) raises EncodingError without lineno. + irb_bug = true + else + irb_bug = false + # To support backtrace filtering while utilizing Exception#full_message, we need to clone + # the exception to avoid modifying the original exception's backtrace. + exc = exc.clone + filtered_backtrace = exc.backtrace.map { |l| @context.workspace.filter_backtrace(l) }.compact + backtrace_filter = IRB.conf[:BACKTRACE_FILTER] + + if backtrace_filter + if backtrace_filter.respond_to?(:call) + filtered_backtrace = backtrace_filter.call(filtered_backtrace) + else + warn "IRB.conf[:BACKTRACE_FILTER] #{backtrace_filter} should respond to `call` method" + end + end + + exc.set_backtrace(filtered_backtrace) + end + + highlight = Color.colorable? + + order = + if RUBY_VERSION < '3.0.0' + STDOUT.tty? ? :bottom : :top + else # '3.0.0' <= RUBY_VERSION + :top + end + + message = exc.full_message(order: order, highlight: highlight) + message = convert_invalid_byte_sequence(message, exc.message.encoding) + message = encode_with_invalid_byte_sequence(message, IRB.conf[:LC_MESSAGES].encoding) unless message.encoding.to_s.casecmp?(IRB.conf[:LC_MESSAGES].encoding.to_s) + message = message.gsub(/((?:^\t.+$\n)+)/) { |m| + case order + when :top + lines = m.split("\n") + when :bottom + lines = m.split("\n").reverse + end + unless irb_bug + if lines.size > @context.back_trace_limit + omit = lines.size - @context.back_trace_limit + lines = lines[0..(@context.back_trace_limit - 1)] + lines << "\t... %d levels..." % omit + end + end + lines = lines.reverse if order == :bottom + lines.map{ |l| l + "\n" }.join + } + # The "" in "(irb)" may be the top level of IRB so imitate the main object. + message = message.gsub(/\(irb\):(?\d+):in (?[`'])<(?top \(required\))>'/) { "(irb):#{$~[:num]}:in #{$~[:open_quote]}
    '" } + puts message + + if irb_bug + puts "This may be an issue with IRB. If you believe this is an unexpected behavior, please report it to https://github.com/ruby/irb/issues" + end + rescue Exception => handler_exc + begin + puts exc.inspect + puts "backtraces are hidden because #{handler_exc} was raised when processing them" + rescue Exception + puts 'Uninspectable exception occurred' + end + end + + # Evaluates the given block using the given `path` as the Context#irb_path and + # `name` as the Context#irb_name. + # + # Used by the irb command `source`, see IRB@IRB+Sessions for more information. + def suspend_name(path = nil, name = nil) + @context.irb_path, back_path = path, @context.irb_path if path + @context.irb_name, back_name = name, @context.irb_name if name + begin + yield back_path, back_name + ensure + @context.irb_path = back_path if path + @context.irb_name = back_name if name + end + end + + # Evaluates the given block using the given `workspace` as the + # Context#workspace. + # + # Used by the irb command `irb_load`, see IRB@IRB+Sessions for more information. + def suspend_workspace(workspace) + current_workspace = @context.workspace + @context.replace_workspace(workspace) + yield + ensure + @context.replace_workspace current_workspace + end + + # Evaluates the given block using the given `input_method` as the Context#io. + # + # Used by the irb commands `source` and `irb_load`, see IRB@IRB+Sessions for + # more information. + def suspend_input_method(input_method) + back_io = @context.io + @context.instance_eval{@io = input_method} + begin + yield back_io + ensure + @context.instance_eval{@io = back_io} + end + end + + # Handler for the signal SIGINT, see Kernel#trap for more information. + def signal_handle + unless @context.ignore_sigint? + print "\nabort!\n" if @context.verbose? + exit + end + + case @signal_status + when :IN_INPUT + print "^C\n" + raise RubyLex::TerminateLineInput + when :IN_EVAL + IRB.irb_abort(self) + when :IN_LOAD + IRB.irb_abort(self, LoadAbort) + when :IN_IRB + # ignore + else + # ignore other cases as well + end + end + + # Evaluates the given block using the given `status`. + def signal_status(status) + return yield if @signal_status == :IN_LOAD + + signal_status_back = @signal_status + @signal_status = status + begin + yield + ensure + @signal_status = signal_status_back + end + end + + def output_value(omit = false) # :nodoc: + unless @context.return_format.include?('%') + puts @context.return_format + return + end + + winheight, winwidth = @context.io.winsize + if omit + content, overflow = Pager.take_first_page(winwidth, 1) do |out| + @context.inspect_last_value(out) + end + if overflow + content = "\n#{content}" if @context.newline_before_multiline_output? + content = "#{content}..." + content = "#{content}\e[0m" if Color.colorable? + end + puts format(@context.return_format, content.chomp) + elsif Pager.should_page? && @context.inspector_support_stream_output? + formatter_proc = ->(content, multipage) do + content = content.chomp + content = "\n#{content}" if @context.newline_before_multiline_output? && (multipage || content.include?("\n")) + format(@context.return_format, content) + end + Pager.page_with_preview(winwidth, winheight, formatter_proc) do |out| + @context.inspect_last_value(out) + end + else + content = @context.inspect_last_value.chomp + content = "\n#{content}" if @context.newline_before_multiline_output? && content.include?("\n") + Pager.page_content(format(@context.return_format, content), retain_content: true) + end + end + + # Outputs the local variables to this current session, including #signal_status + # and #context, using IRB::Locale. + def inspect + ary = [] + for iv in instance_variables + case (iv = iv.to_s) + when "@signal_status" + ary.push format("%s=:%s", iv, @signal_status.id2name) + when "@context" + ary.push format("%s=%s", iv, eval(iv).__to_s__) + else + ary.push format("%s=%s", iv, eval(iv)) + end + end + format("#<%s: %s>", self.class, ary.join(", ")) + end + + private + + def generate_prompt(opens, continue, line_offset) + ltype = @scanner.ltype_from_open_tokens(opens) + indent = @scanner.calc_indent_level(opens) + continue = opens.any? || continue + line_no = @line_no + line_offset + + if ltype + f = @context.prompt_s + elsif continue + f = @context.prompt_c + else + f = @context.prompt_i + end + f = "" unless f + if @context.prompting? + p = format_prompt(f, ltype, indent, line_no) + else + p = "" + end + if @context.auto_indent_mode and !@context.io.respond_to?(:auto_indent) + unless ltype + prompt_i = @context.prompt_i.nil? ? "" : @context.prompt_i + ind = format_prompt(prompt_i, ltype, indent, line_no)[/.*\z/].size + + indent * 2 - p.size + p += " " * ind if ind > 0 + end + end + p + end + + def truncate_prompt_main(str) # :nodoc: + str = str.tr(CONTROL_CHARACTERS_PATTERN, ' ') + if str.size <= PROMPT_MAIN_TRUNCATE_LENGTH + str + else + str[0, PROMPT_MAIN_TRUNCATE_LENGTH - PROMPT_MAIN_TRUNCATE_OMISSION.size] + PROMPT_MAIN_TRUNCATE_OMISSION + end + end + + def format_prompt(format, ltype, indent, line_no) # :nodoc: + format.gsub(/%([0-9]+)?([a-zA-Z%])/) do + case $2 + when "N" + @context.irb_name + when "m" + main_str = @context.safe_method_call_on_main(:to_s) rescue "!#{$!.class}" + truncate_prompt_main(main_str) + when "M" + main_str = @context.safe_method_call_on_main(:inspect) rescue "!#{$!.class}" + truncate_prompt_main(main_str) + when "l" + ltype + when "i" + if indent < 0 + if $1 + "-".rjust($1.to_i) + else + "-" + end + else + if $1 + format("%" + $1 + "d", indent) + else + indent.to_s + end + end + when "n" + if $1 + format("%" + $1 + "d", line_no) + else + line_no.to_s + end + when "%" + "%" unless $1 + end + end + end + end +end + +class Binding + # Opens an IRB session where `binding.irb` is called which allows for + # interactive debugging. You can call any methods or variables available in the + # current scope, and mutate state if you need to. + # + # Given a Ruby file called `potato.rb` containing the following code: + # + # class Potato + # def initialize + # @cooked = false + # binding.irb + # puts "Cooked potato: #{@cooked}" + # end + # end + # + # Potato.new + # + # Running `ruby potato.rb` will open an IRB session where `binding.irb` is + # called, and you will see the following: + # + # $ ruby potato.rb + # + # From: potato.rb @ line 4 : + # + # 1: class Potato + # 2: def initialize + # 3: @cooked = false + # => 4: binding.irb + # 5: puts "Cooked potato: #{@cooked}" + # 6: end + # 7: end + # 8: + # 9: Potato.new + # + # irb(#):001:0> + # + # You can type any valid Ruby code and it will be evaluated in the current + # context. This allows you to debug without having to run your code repeatedly: + # + # irb(#):001:0> @cooked + # => false + # irb(#):002:0> self.class + # => Potato + # irb(#):003:0> caller.first + # => ".../2.5.1/lib/ruby/2.5.0/irb/workspace.rb:85:in `eval'" + # irb(#):004:0> @cooked = true + # => true + # + # You can exit the IRB session with the `exit` command. Note that exiting will + # resume execution where `binding.irb` had paused it, as you can see from the + # output printed to standard output in this example: + # + # irb(#):005:0> exit + # Cooked potato: true + # + # See IRB for more information. + def irb(show_code: true) + # Setup IRB with the current file's path and no command line arguments + IRB.setup(source_location[0], argv: []) unless IRB.initialized? + # Create a new workspace using the current binding + workspace = IRB::WorkSpace.new(self) + # Print the code around the binding if show_code is true + STDOUT.print(workspace.code_around_binding) if show_code + # Get the original IRB instance + debugger_irb = IRB.instance_variable_get(:@debugger_irb) + + irb_path = File.expand_path(source_location[0]) + + if debugger_irb + # If we're already in a debugger session, set the workspace and irb_path for the original IRB instance + debugger_irb.context.replace_workspace(workspace) + debugger_irb.context.irb_path = irb_path + # If we've started a debugger session and hit another binding.irb, we don't want + # to start an IRB session instead, we want to resume the irb:rdbg session. + IRB::Debug.setup(debugger_irb) + IRB::Debug.insert_debug_break + debugger_irb.debug_break + else + # If we're not in a debugger session, create a new IRB instance with the current + # workspace + binding_irb = IRB::Irb.new(workspace, from_binding: true) + binding_irb.context.irb_path = irb_path + binding_irb.run(IRB.conf) + binding_irb.debug_break + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/cmd/nop.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/cmd/nop.rb new file mode 100644 index 00000000..9d2e3c4d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/cmd/nop.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +# This file is just a placeholder for backward-compatibility. +# Please require 'irb' and inherit your command from `IRB::Command::Base` instead. diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/color.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/color.rb new file mode 100644 index 00000000..a7e31108 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/color.rb @@ -0,0 +1,263 @@ +# frozen_string_literal: true +require 'reline' +require 'ripper' +require_relative 'ruby-lex' + +module IRB # :nodoc: + module Color + CLEAR = 0 + BOLD = 1 + UNDERLINE = 4 + REVERSE = 7 + BLACK = 30 + RED = 31 + GREEN = 32 + YELLOW = 33 + BLUE = 34 + MAGENTA = 35 + CYAN = 36 + WHITE = 37 + + TOKEN_KEYWORDS = { + on_kw: ['nil', 'self', 'true', 'false', '__FILE__', '__LINE__', '__ENCODING__'], + on_const: ['ENV'], + } + private_constant :TOKEN_KEYWORDS + + # A constant of all-bit 1 to match any Ripper's state in #dispatch_seq + ALL = -1 + private_constant :ALL + + begin + # Following pry's colors where possible, but sometimes having a compromise like making + # backtick and regexp as red (string's color, because they're sharing tokens). + TOKEN_SEQ_EXPRS = { + on_CHAR: [[BLUE, BOLD], ALL], + on_backtick: [[RED, BOLD], ALL], + on_comment: [[BLUE, BOLD], ALL], + on_const: [[BLUE, BOLD, UNDERLINE], ALL], + on_embexpr_beg: [[RED], ALL], + on_embexpr_end: [[RED], ALL], + on_embvar: [[RED], ALL], + on_float: [[MAGENTA, BOLD], ALL], + on_gvar: [[GREEN, BOLD], ALL], + on_backref: [[GREEN, BOLD], ALL], + on_heredoc_beg: [[RED], ALL], + on_heredoc_end: [[RED], ALL], + on_ident: [[BLUE, BOLD], Ripper::EXPR_ENDFN], + on_imaginary: [[BLUE, BOLD], ALL], + on_int: [[BLUE, BOLD], ALL], + on_kw: [[GREEN], ALL], + on_label: [[MAGENTA], ALL], + on_label_end: [[RED, BOLD], ALL], + on_qsymbols_beg: [[RED, BOLD], ALL], + on_qwords_beg: [[RED, BOLD], ALL], + on_rational: [[BLUE, BOLD], ALL], + on_regexp_beg: [[RED, BOLD], ALL], + on_regexp_end: [[RED, BOLD], ALL], + on_symbeg: [[YELLOW], ALL], + on_symbols_beg: [[RED, BOLD], ALL], + on_tstring_beg: [[RED, BOLD], ALL], + on_tstring_content: [[RED], ALL], + on_tstring_end: [[RED, BOLD], ALL], + on_words_beg: [[RED, BOLD], ALL], + on_parse_error: [[RED, REVERSE], ALL], + compile_error: [[RED, REVERSE], ALL], + on_assign_error: [[RED, REVERSE], ALL], + on_alias_error: [[RED, REVERSE], ALL], + on_class_name_error:[[RED, REVERSE], ALL], + on_param_error: [[RED, REVERSE], ALL], + on___end__: [[GREEN], ALL], + } + rescue NameError + # Give up highlighting Ripper-incompatible older Ruby + TOKEN_SEQ_EXPRS = {} + end + private_constant :TOKEN_SEQ_EXPRS + + ERROR_TOKENS = TOKEN_SEQ_EXPRS.keys.select { |k| k.to_s.end_with?('error') } + private_constant :ERROR_TOKENS + + class << self + def colorable? + supported = $stdout.tty? && (/mswin|mingw/.match?(RUBY_PLATFORM) || (ENV.key?('TERM') && ENV['TERM'] != 'dumb')) + + # because ruby/debug also uses irb's color module selectively, + # irb won't be activated in that case. + if IRB.respond_to?(:conf) + supported && !!IRB.conf.fetch(:USE_COLORIZE, true) + else + supported + end + end + + def inspect_colorable?(obj, seen: {}.compare_by_identity) + case obj + when String, Symbol, Regexp, Integer, Float, FalseClass, TrueClass, NilClass + true + when Hash + without_circular_ref(obj, seen: seen) do + obj.all? { |k, v| inspect_colorable?(k, seen: seen) && inspect_colorable?(v, seen: seen) } + end + when Array + without_circular_ref(obj, seen: seen) do + obj.all? { |o| inspect_colorable?(o, seen: seen) } + end + when Range + inspect_colorable?(obj.begin, seen: seen) && inspect_colorable?(obj.end, seen: seen) + when Module + !obj.name.nil? + else + false + end + end + + def clear(colorable: colorable?) + return '' unless colorable + "\e[#{CLEAR}m" + end + + def colorize(text, seq, colorable: colorable?) + return text unless colorable + seq = seq.map { |s| "\e[#{const_get(s)}m" }.join('') + "#{seq}#{text}#{clear(colorable: colorable)}" + end + + # If `complete` is false (code is incomplete), this does not warn compile_error. + # This option is needed to avoid warning a user when the compile_error is happening + # because the input is not wrong but just incomplete. + def colorize_code(code, complete: true, ignore_error: false, colorable: colorable?, local_variables: []) + return code unless colorable + + symbol_state = SymbolState.new + colored = +'' + lvars_code = RubyLex.generate_local_variables_assign_code(local_variables) + code_with_lvars = lvars_code ? "#{lvars_code}\n#{code}" : code + + scan(code_with_lvars, allow_last_error: !complete) do |token, str, expr| + # handle uncolorable code + if token.nil? + colored << Reline::Unicode.escape_for_print(str) + next + end + + # IRB::ColorPrinter skips colorizing fragments with any invalid token + if ignore_error && ERROR_TOKENS.include?(token) + return Reline::Unicode.escape_for_print(code) + end + + in_symbol = symbol_state.scan_token(token) + str.each_line do |line| + line = Reline::Unicode.escape_for_print(line) + if seq = dispatch_seq(token, expr, line, in_symbol: in_symbol) + colored << seq.map { |s| "\e[#{s}m" }.join('') + colored << line.sub(/\Z/, clear(colorable: colorable)) + else + colored << line + end + end + end + + if lvars_code + raise "#{lvars_code.dump} should have no \\n" if lvars_code.include?("\n") + colored.sub!(/\A.+\n/, '') # delete_prefix lvars_code with colors + end + colored + end + + private + + def without_circular_ref(obj, seen:, &block) + return false if seen.key?(obj) + seen[obj] = true + block.call + ensure + seen.delete(obj) + end + + def scan(code, allow_last_error:) + verbose, $VERBOSE = $VERBOSE, nil + RubyLex.compile_with_errors_suppressed(code) do |inner_code, line_no| + lexer = Ripper::Lexer.new(inner_code, '(ripper)', line_no) + byte_pos = 0 + line_positions = [0] + inner_code.lines.each do |line| + line_positions << line_positions.last + line.bytesize + end + + on_scan = proc do |elem| + start_pos = line_positions[elem.pos[0] - 1] + elem.pos[1] + + # yield uncolorable code + if byte_pos < start_pos + yield(nil, inner_code.byteslice(byte_pos...start_pos), nil) + end + + if byte_pos <= start_pos + str = elem.tok + yield(elem.event, str, elem.state) + byte_pos = start_pos + str.bytesize + end + end + + lexer.scan.each do |elem| + next if allow_last_error and /meets end of file|unexpected end-of-input/ =~ elem.message + on_scan.call(elem) + end + # yield uncolorable DATA section + yield(nil, inner_code.byteslice(byte_pos...inner_code.bytesize), nil) if byte_pos < inner_code.bytesize + end + ensure + $VERBOSE = verbose + end + + def dispatch_seq(token, expr, str, in_symbol:) + if ERROR_TOKENS.include?(token) + TOKEN_SEQ_EXPRS[token][0] + elsif in_symbol + [YELLOW] + elsif TOKEN_KEYWORDS.fetch(token, []).include?(str) + [CYAN, BOLD] + elsif (seq, exprs = TOKEN_SEQ_EXPRS[token]; (expr & (exprs || 0)) != 0) + seq + else + nil + end + end + end + + # A class to manage a state to know whether the current token is for Symbol or not. + class SymbolState + def initialize + # Push `true` to detect Symbol. `false` to increase the nest level for non-Symbol. + @stack = [] + end + + # Return true if the token is a part of Symbol. + def scan_token(token) + prev_state = @stack.last + case token + when :on_symbeg, :on_symbols_beg, :on_qsymbols_beg + @stack << true + when :on_ident, :on_op, :on_const, :on_ivar, :on_cvar, :on_gvar, :on_kw, :on_backtick + if @stack.last # Pop only when it's Symbol + @stack.pop + return prev_state + end + when :on_tstring_beg + @stack << false + when :on_embexpr_beg + @stack << false + return prev_state + when :on_tstring_end # :on_tstring_end may close Symbol + @stack.pop + return prev_state + when :on_embexpr_end + @stack.pop + end + @stack.last + end + end + private_constant :SymbolState + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/color_printer.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/color_printer.rb new file mode 100644 index 00000000..7a7e8178 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/color_printer.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true +require 'pp' +require_relative 'color' + +module IRB + class ColorPrinter < ::PP + class << self + def pp(obj, out = $>, width = screen_width, colorize: true) + q = ColorPrinter.new(out, width, colorize: colorize) + q.guard_inspect_key {q.pp obj} + q.flush + out << "\n" + end + + private + + def screen_width + Reline.get_screen_size.last + rescue Errno::EINVAL # in `winsize': Invalid argument - + 79 + end + end + + def initialize(out, width, colorize: true) + @colorize = colorize + + super(out, width) + end + + def pp(obj) + if String === obj + # Avoid calling Ruby 2.4+ String#pretty_print that splits a string by "\n" + text(obj.inspect) + else + super + end + end + + def text(str, width = nil) + unless str.is_a?(String) + str = str.inspect + end + width ||= str.length + + case str + when '' + when ',', '=>', '[', ']', '{', '}', '..', '...', /\A@\w+\z/ + super(str, width) + when /\A#' + super(@colorize ? Color.colorize(str, [:GREEN]) : str, width) + else + super(@colorize ? Color.colorize_code(str, ignore_error: true) : str, width) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command.rb new file mode 100644 index 00000000..68a4b527 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true +# +# irb/command.rb - irb command +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +require_relative "command/base" + +module IRB # :nodoc: + module Command + @commands = {} + + class << self + attr_reader :commands + + # Registers a command with the given name. + # Aliasing is intentionally not supported at the moment. + def register(name, command_class) + @commands[name.to_sym] = [command_class, []] + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/backtrace.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/backtrace.rb new file mode 100644 index 00000000..687bb075 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/backtrace.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module Command + class Backtrace < DebugCommand + def execute(arg) + execute_debug_command(pre_cmds: "backtrace #{arg}") + end + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/base.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/base.rb new file mode 100644 index 00000000..2f39b75c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/base.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true +# +# nop.rb - +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +module IRB + module Command + class CommandArgumentError < StandardError; end # :nodoc: + + class << self + def extract_ruby_args(*args, **kwargs) # :nodoc: + throw :EXTRACT_RUBY_ARGS, [args, kwargs] + end + end + + class Base + class << self + def category(category = nil) + @category = category if category + @category || "No category" + end + + def description(description = nil) + @description = description if description + @description || "No description provided." + end + + def help_message(help_message = nil) + @help_message = help_message if help_message + @help_message + end + + def execute(irb_context, arg) + new(irb_context).execute(arg) + rescue CommandArgumentError => e + puts e.message + end + + private + + def highlight(text) + Color.colorize(text, [:BOLD, :BLUE]) + end + end + + def initialize(irb_context) + @irb_context = irb_context + end + + attr_reader :irb_context + + def execute(arg) + #nop + end + end + + Nop = Base # :nodoc: + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/break.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/break.rb new file mode 100644 index 00000000..a8f81fe6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/break.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module Command + class Break < DebugCommand + def execute(arg) + execute_debug_command(pre_cmds: "break #{arg}") + end + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/catch.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/catch.rb new file mode 100644 index 00000000..529dcbca --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/catch.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module Command + class Catch < DebugCommand + def execute(arg) + execute_debug_command(pre_cmds: "catch #{arg}") + end + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/cd.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/cd.rb new file mode 100644 index 00000000..b83c8689 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/cd.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module IRB + module Command + class CD < Base + category "Workspace" + description "Move into the given object or leave the current context." + + help_message(<<~HELP) + Usage: cd ([target]|..) + + IRB uses a stack of workspaces to keep track of context(s), with `pushws` and `popws` commands to manipulate the stack. + The `cd` command is an attempt to simplify the operation and will be subject to change. + + When given: + - an object, cd will use that object as the new context by pushing it onto the workspace stack. + - "..", cd will leave the current context by popping the top workspace off the stack. + - no arguments, cd will move to the top workspace on the stack by popping off all workspaces. + + Examples: + + cd Foo + cd Foo.new + cd @ivar + cd .. + cd + HELP + + def execute(arg) + case arg + when ".." + irb_context.pop_workspace + when "" + # TODO: decide what workspace commands should be kept, and underlying APIs should look like, + # and perhaps add a new API to clear the workspace stack. + prev_workspace = irb_context.pop_workspace + while prev_workspace + prev_workspace = irb_context.pop_workspace + end + else + begin + obj = eval(arg, irb_context.workspace.binding) + irb_context.push_workspace(obj) + rescue StandardError => e + warn "Error: #{e}" + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/chws.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/chws.rb new file mode 100644 index 00000000..ef456d09 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/chws.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true +# +# change-ws.rb - +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# +require_relative "../ext/change-ws" + +module IRB + # :stopdoc: + + module Command + + class CurrentWorkingWorkspace < Base + category "Workspace" + description "Show the current workspace." + + def execute(_arg) + puts "Current workspace: #{irb_context.main}" + end + end + + class ChangeWorkspace < Base + category "Workspace" + description "Change the current workspace to an object." + + def execute(arg) + if arg.empty? + irb_context.change_workspace + else + obj = eval(arg, irb_context.workspace.binding) + irb_context.change_workspace(obj) + end + + puts "Current workspace: #{irb_context.main}" + end + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/context.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/context.rb new file mode 100644 index 00000000..b4fc8073 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/context.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module IRB + module Command + class Context < Base + category "IRB" + description "Displays current configuration." + + def execute(_arg) + # This command just displays the configuration. + # Modifying the configuration is achieved by sending a message to IRB.conf. + Pager.page_content(IRB.CurrentContext.inspect) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/continue.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/continue.rb new file mode 100644 index 00000000..0daa029b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/continue.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module Command + class Continue < DebugCommand + def execute(arg) + execute_debug_command(do_cmds: "continue #{arg}") + end + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/copy.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/copy.rb new file mode 100644 index 00000000..93410b87 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/copy.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +module IRB + module Command + class Copy < Base + category "Misc" + description "Copy expression output to clipboard" + + help_message(<<~HELP) + Usage: copy ([expression]) + + When given: + - an expression, copy the inspect result of the expression to the clipboard. + - no arguments, copy the last evaluated result (`_`) to the clipboard. + + Examples: + + copy Foo.new + copy User.all.to_a + copy + HELP + + def execute(arg) + # Copy last value if no expression was supplied + arg = '_' if arg.to_s.strip.empty? + + value = irb_context.workspace.binding.eval(arg) + output = irb_context.inspect_method.inspect_value(value, +'', colorize: false).chomp + + if clipboard_available? + copy_to_clipboard(output) + else + warn "System clipboard not found" + end + rescue StandardError => e + warn "Error: #{e}" + end + + private + + def copy_to_clipboard(text) + IO.popen(clipboard_program, 'w') do |io| + io.write(text) + end + + raise IOError.new("Copying to clipboard failed") unless $? == 0 + + puts "Copied to system clipboard" + rescue Errno::ENOENT => e + warn e.message + warn "Is IRB.conf[:COPY_COMMAND] set to a bad value?" + end + + def clipboard_program + @clipboard_program ||= if IRB.conf[:COPY_COMMAND] + IRB.conf[:COPY_COMMAND] + elsif executable?("pbcopy") + "pbcopy" + elsif executable?("xclip") + "xclip -selection clipboard" + end + end + + def executable?(command) + system("which #{command} > /dev/null 2>&1") + end + + def clipboard_available? + !!clipboard_program + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/debug.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/debug.rb new file mode 100644 index 00000000..3ebb57fe --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/debug.rb @@ -0,0 +1,73 @@ +require_relative "../debug" + +module IRB + # :stopdoc: + + module Command + class Debug < Base + category "Debugging" + description "Start the debugger of debug.gem." + + def execute(_arg) + execute_debug_command + end + + def execute_debug_command(pre_cmds: nil, do_cmds: nil) + pre_cmds = pre_cmds&.rstrip + do_cmds = do_cmds&.rstrip + + if irb_context.with_debugger + # If IRB is already running with a debug session, throw the command and IRB.debug_readline will pass it to the debugger. + if cmd = pre_cmds || do_cmds + throw :IRB_EXIT, cmd + else + puts "IRB is already running with a debug session." + return + end + else + # If IRB is not running with a debug session yet, then: + # 1. Check if the debugging command is run from a `binding.irb` call. + # 2. If so, try setting up the debug gem. + # 3. Insert a debug breakpoint at `Irb#debug_break` with the intended command. + # 4. Exit the current Irb#run call via `throw :IRB_EXIT`. + # 5. `Irb#debug_break` will be called and trigger the breakpoint, which will run the intended command. + unless irb_context.from_binding? + puts "Debugging commands are only available when IRB is started with binding.irb" + return + end + + if IRB.respond_to?(:JobManager) + warn "Can't start the debugger when IRB is running in a multi-IRB session." + return + end + + unless IRB::Debug.setup(irb_context.irb) + puts <<~MSG + You need to install the debug gem before using this command. + If you use `bundle exec`, please add `gem "debug"` into your Gemfile. + MSG + return + end + + IRB::Debug.insert_debug_break(pre_cmds: pre_cmds, do_cmds: do_cmds) + + # exit current Irb#run call + throw :IRB_EXIT + end + end + end + + class DebugCommand < Debug + class << self + def category + "Debugging" + end + + def description + command_name = self.name.split("::").last.downcase + "Start the debugger of debug.gem and run its `#{command_name}` command." + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/delete.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/delete.rb new file mode 100644 index 00000000..2a57a4a3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/delete.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module Command + class Delete < DebugCommand + def execute(arg) + execute_debug_command(pre_cmds: "delete #{arg}") + end + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/disable_irb.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/disable_irb.rb new file mode 100644 index 00000000..0b00d030 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/disable_irb.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module IRB + # :stopdoc: + + module Command + class DisableIrb < Base + category "IRB" + description "Disable binding.irb." + + def execute(*) + ::Binding.define_method(:irb) {} + IRB.irb_exit + end + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/edit.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/edit.rb new file mode 100644 index 00000000..cb7e0c48 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/edit.rb @@ -0,0 +1,63 @@ +require 'shellwords' + +require_relative "../color" +require_relative "../source_finder" + +module IRB + # :stopdoc: + + module Command + class Edit < Base + include RubyArgsExtractor + + category "Misc" + description 'Open a file or source location.' + help_message <<~HELP_MESSAGE + Usage: edit [FILE or constant or method signature] + + Open a file in the editor specified in #{highlight('ENV["VISUAL"]')} or #{highlight('ENV["EDITOR"]')} + + - If no arguments are provided, IRB will attempt to open the file the current context was defined in. + - If FILE is provided, IRB will open the file. + - If a constant or method signature is provided, IRB will attempt to locate the source file and open it. + + Examples: + + edit + edit foo.rb + edit Foo + edit Foo#bar + HELP_MESSAGE + + def execute(arg) + # Accept string literal for backward compatibility + path = unwrap_string_literal(arg) + + if path.nil? + path = @irb_context.irb_path + elsif !File.exist?(path) + source = SourceFinder.new(@irb_context).find_source(path) + + if source&.file_exist? && !source.binary_file? + path = source.file + end + end + + unless File.exist?(path) + puts "Can not find file: #{path}" + return + end + + if editor = (ENV['VISUAL'] || ENV['EDITOR']) + puts "command: '#{editor}'" + puts " path: #{path}" + system(*Shellwords.split(editor), path) + else + puts "Can not find editor setting: ENV['VISUAL'] or ENV['EDITOR']" + end + end + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/exit.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/exit.rb new file mode 100644 index 00000000..b4436f03 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/exit.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module IRB + # :stopdoc: + + module Command + class Exit < Base + category "IRB" + description "Exit the current irb session." + + def execute(_arg) + IRB.irb_exit + end + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/finish.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/finish.rb new file mode 100644 index 00000000..3311a0e6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/finish.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module Command + class Finish < DebugCommand + def execute(arg) + execute_debug_command(do_cmds: "finish #{arg}") + end + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/force_exit.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/force_exit.rb new file mode 100644 index 00000000..14086aa8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/force_exit.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module IRB + # :stopdoc: + + module Command + class ForceExit < Base + category "IRB" + description "Exit the current process." + + def execute(_arg) + throw :IRB_EXIT, true + end + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/help.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/help.rb new file mode 100644 index 00000000..12b468fe --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/help.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +module IRB + module Command + class Help < Base + category "Help" + description "List all available commands. Use `help ` to get information about a specific command." + + def execute(command_name) + content = + if command_name.empty? + help_message + else + if command_class = Command.load_command(command_name) + command_class.help_message || command_class.description + else + "Can't find command `#{command_name}`. Please check the command name and try again.\n\n" + end + end + Pager.page_content(content) + end + + private + + def help_message + commands_info = IRB::Command.all_commands_info + helper_methods_info = IRB::HelperMethod.all_helper_methods_info + commands_grouped_by_categories = commands_info.group_by { |cmd| cmd[:category] } + commands_grouped_by_categories["Helper methods"] = helper_methods_info + + if irb_context.with_debugger + # Remove the original "Debugging" category + commands_grouped_by_categories.delete("Debugging") + end + + longest_cmd_name_length = commands_info.map { |c| c[:display_name].length }.max + + output = StringIO.new + + help_cmds = commands_grouped_by_categories.delete("Help") + no_category_cmds = commands_grouped_by_categories.delete("No category") + aliases = irb_context.instance_variable_get(:@command_aliases).map do |alias_name, target| + { display_name: alias_name, description: "Alias for `#{target}`" } + end + + # Display help commands first + add_category_to_output("Help", help_cmds, output, longest_cmd_name_length) + + # Display the rest of the commands grouped by categories + commands_grouped_by_categories.each do |category, cmds| + add_category_to_output(category, cmds, output, longest_cmd_name_length) + end + + # Display commands without a category + if no_category_cmds + add_category_to_output("No category", no_category_cmds, output, longest_cmd_name_length) + end + + # Display aliases + add_category_to_output("Aliases", aliases, output, longest_cmd_name_length) + + # Append the debugger help at the end + if irb_context.with_debugger + # Add "Debugging (from debug.gem)" category as title + add_category_to_output("Debugging (from debug.gem)", [], output, longest_cmd_name_length) + output.puts DEBUGGER__.help + end + + output.string + end + + def add_category_to_output(category, cmds, output, longest_cmd_name_length) + output.puts Color.colorize(category, [:BOLD]) + + cmds.each do |cmd| + output.puts " #{cmd[:display_name].to_s.ljust(longest_cmd_name_length)} #{cmd[:description]}" + end + + output.puts + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/history.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/history.rb new file mode 100644 index 00000000..e385c661 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/history.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "stringio" + +require_relative "../pager" + +module IRB + # :stopdoc: + + module Command + class History < Base + category "IRB" + description "Shows the input history. `-g [query]` or `-G [query]` allows you to filter the output." + + def execute(arg) + + if (match = arg&.match(/(-g|-G)\s+(?.+)\s*\z/)) + grep = Regexp.new(match[:grep]) + end + + formatted_inputs = irb_context.io.class::HISTORY.each_with_index.reverse_each.filter_map do |input, index| + next if grep && !input.match?(grep) + + header = "#{index}: " + + first_line, *other_lines = input.split("\n") + first_line = "#{header}#{first_line}" + + truncated_lines = other_lines.slice!(1..) # Show 1 additional line (2 total) + other_lines << "..." if truncated_lines&.any? + + other_lines.map! do |line| + " " * header.length + line + end + + [first_line, *other_lines].join("\n") + "\n" + end + + Pager.page_content(formatted_inputs.join) + end + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/info.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/info.rb new file mode 100644 index 00000000..d08ce00a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/info.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module Command + class Info < DebugCommand + def execute(arg) + execute_debug_command(pre_cmds: "info #{arg}") + end + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/internal_helpers.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/internal_helpers.rb new file mode 100644 index 00000000..a01ddb1d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/internal_helpers.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module IRB + module Command + # Internal use only, for default command's backward compatibility. + module RubyArgsExtractor # :nodoc: + def unwrap_string_literal(str) + return if str.empty? + + sexp = Ripper.sexp(str) + if sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal + @irb_context.workspace.binding.eval(str).to_s + else + str + end + end + + def ruby_args(arg) + # Use throw and catch to handle arg that includes `;` + # For example: "1, kw: (2; 3); 4" will be parsed to [[1], { kw: 3 }] + catch(:EXTRACT_RUBY_ARGS) do + @irb_context.workspace.binding.eval "::IRB::Command.extract_ruby_args #{arg}" + end || [[], {}] + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/irb_info.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/irb_info.rb new file mode 100644 index 00000000..6d868de9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/irb_info.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module IRB + # :stopdoc: + + module Command + class IrbInfo < Base + category "IRB" + description "Show information about IRB." + + def execute(_arg) + str = "Ruby version: #{RUBY_VERSION}\n" + str += "IRB version: #{IRB.version}\n" + str += "InputMethod: #{IRB.CurrentContext.io.inspect}\n" + str += "Completion: #{IRB.CurrentContext.io.respond_to?(:completion_info) ? IRB.CurrentContext.io.completion_info : 'off'}\n" + rc_files = IRB.irbrc_files + str += ".irbrc paths: #{rc_files.join(", ")}\n" if rc_files.any? + str += "RUBY_PLATFORM: #{RUBY_PLATFORM}\n" + str += "LANG env: #{ENV["LANG"]}\n" if ENV["LANG"] && !ENV["LANG"].empty? + str += "LC_ALL env: #{ENV["LC_ALL"]}\n" if ENV["LC_ALL"] && !ENV["LC_ALL"].empty? + str += "East Asian Ambiguous Width: #{Reline.ambiguous_width.inspect}\n" + if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/ + codepage = `chcp`.b.sub(/.*: (\d+)\n/, '\1') + str += "Code page: #{codepage}\n" + end + puts str + nil + end + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/load.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/load.rb new file mode 100644 index 00000000..1cd3f279 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/load.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true +# +# load.rb - +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# +require_relative "../ext/loader" + +module IRB + # :stopdoc: + + module Command + class LoaderCommand < Base + include RubyArgsExtractor + include IrbLoader + + def raise_cmd_argument_error + raise CommandArgumentError.new("Please specify the file name.") + end + end + + class Load < LoaderCommand + category "IRB" + description "Load a Ruby file." + + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(file_name = nil, priv = nil) + raise_cmd_argument_error unless file_name + irb_load(file_name, priv) + end + end + + class Require < LoaderCommand + category "IRB" + description "Require a Ruby file." + + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(file_name = nil) + raise_cmd_argument_error unless file_name + + rex = Regexp.new("#{Regexp.quote(file_name)}(\.o|\.rb)?") + return false if $".find{|f| f =~ rex} + + case file_name + when /\.rb$/ + begin + if irb_load(file_name) + $".push file_name + return true + end + rescue LoadError + end + when /\.(so|o|sl)$/ + return ruby_require(file_name) + end + + begin + irb_load(f = file_name + ".rb") + $".push f + return true + rescue LoadError + return ruby_require(file_name) + end + end + end + + class Source < LoaderCommand + category "IRB" + description "Loads a given file in the current session." + + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(file_name = nil) + raise_cmd_argument_error unless file_name + + source_file(file_name) + end + end + end + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/ls.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/ls.rb new file mode 100644 index 00000000..944efd75 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/ls.rb @@ -0,0 +1,167 @@ +# frozen_string_literal: true + +require "reline" +require "stringio" + +require_relative "../pager" +require_relative "../color" + +module IRB + # :stopdoc: + + module Command + class Ls < Base + class EvaluationError < StandardError; end + + category "Context" + description "Show methods, constants, and variables." + + help_message <<~HELP_MESSAGE + Usage: ls [obj] [-g [query]] + + -g [query] Filter the output with a query. + HELP_MESSAGE + + def evaluate(code) + @irb_context.workspace.binding.eval(code) + rescue Exception => e + puts "#{e.class}: #{e.message}" + raise EvaluationError + end + + def execute(arg) + if match = arg.match(/\A(?.+\s|)(-g|-G)\s+(?.+)$/) + target = match[:target] + grep = Regexp.new(match[:grep]) + elsif match = arg.match(/\A((?.+),|)\s*grep:(?.+)/) + # Legacy style `ls obj, grep: /regexp/` + # Evaluation order should be eval(target) then eval(grep) + target = match[:target] || '' + grep_regexp_code = match[:grep] + else + target = arg.strip + end + + if target.empty? + obj = irb_context.workspace.main + locals = irb_context.workspace.binding.local_variables + else + obj = evaluate(target) + end + + if grep_regexp_code + grep = evaluate(grep_regexp_code) + end + + o = Output.new(grep: grep) + + klass = (obj.class == Class || obj.class == Module ? obj : obj.class) + + o.dump("constants", obj.constants) if obj.respond_to?(:constants) + dump_methods(o, klass, obj) + o.dump("instance variables", obj.instance_variables) + o.dump("class variables", klass.class_variables) + o.dump("locals", locals) if locals + o.print_result + rescue EvaluationError + end + + def dump_methods(o, klass, obj) + singleton_class = begin obj.singleton_class; rescue TypeError; nil end + dumped_mods = Array.new + ancestors = klass.ancestors + ancestors = ancestors.reject { |c| c >= Object } if klass < Object + singleton_ancestors = (singleton_class&.ancestors || []).reject { |c| c >= Class } + + # singleton_class' ancestors should be at the front + maps = class_method_map(singleton_ancestors, dumped_mods) + class_method_map(ancestors, dumped_mods) + maps.each do |mod, methods| + name = mod == singleton_class ? "#{klass}.methods" : "#{mod}#methods" + o.dump(name, methods) + end + end + + def class_method_map(classes, dumped_mods) + dumped_methods = Array.new + classes.map do |mod| + next if dumped_mods.include? mod + + dumped_mods << mod + + methods = mod.public_instance_methods(false).select do |method| + if dumped_methods.include? method + false + else + dumped_methods << method + true + end + end + + [mod, methods] + end.compact + end + + class Output + MARGIN = " " + + def initialize(grep: nil) + @grep = grep + @line_width = screen_width - MARGIN.length # right padding + @io = StringIO.new + end + + def print_result + Pager.page_content(@io.string) + end + + def dump(name, strs) + strs = strs.grep(@grep) if @grep + strs = strs.sort + return if strs.empty? + + # Attempt a single line + @io.print "#{Color.colorize(name, [:BOLD, :BLUE])}: " + if fits_on_line?(strs, cols: strs.size, offset: "#{name}: ".length) + @io.puts strs.join(MARGIN) + return + end + @io.puts + + # Dump with the largest # of columns that fits on a line + cols = strs.size + until fits_on_line?(strs, cols: cols, offset: MARGIN.length) || cols == 1 + cols -= 1 + end + widths = col_widths(strs, cols: cols) + strs.each_slice(cols) do |ss| + @io.puts ss.map.with_index { |s, i| "#{MARGIN}%-#{widths[i]}s" % s }.join + end + end + + private + + def fits_on_line?(strs, cols:, offset: 0) + width = col_widths(strs, cols: cols).sum + MARGIN.length * (cols - 1) + width <= @line_width - offset + end + + def col_widths(strs, cols:) + cols.times.map do |col| + (col...strs.size).step(cols).map do |i| + strs[i].length + end.max + end + end + + def screen_width + Reline.get_screen_size.last + rescue Errno::EINVAL # in `winsize': Invalid argument - + 80 + end + end + private_constant :Output + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/measure.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/measure.rb new file mode 100644 index 00000000..f96be20d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/measure.rb @@ -0,0 +1,49 @@ +module IRB + # :stopdoc: + + module Command + class Measure < Base + include RubyArgsExtractor + + category "Misc" + description "`measure` enables the mode to measure processing time. `measure :off` disables it." + + def initialize(*args) + super(*args) + end + + def execute(arg) + if arg&.match?(/^do$|^do[^\w]|^\{/) + warn 'Configure IRB.conf[:MEASURE_PROC] to add custom measure methods.' + return + end + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(type = nil, arg = nil) + # Please check IRB.init_config in lib/irb/init.rb that sets + # IRB.conf[:MEASURE_PROC] to register default "measure" methods, + # "measure :time" (abbreviated as "measure") and "measure :stackprof". + + case type + when :off + IRB.unset_measure_callback(arg) + when :list + IRB.conf[:MEASURE_CALLBACKS].each do |type_name, _, arg_val| + puts "- #{type_name}" + (arg_val ? "(#{arg_val.inspect})" : '') + end + when :on + added = IRB.set_measure_callback(arg) + puts "#{added[0]} is added." if added + else + added = IRB.set_measure_callback(type, arg) + puts "#{added[0]} is added." if added + end + nil + end + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/next.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/next.rb new file mode 100644 index 00000000..3fc6b68d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/next.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module Command + class Next < DebugCommand + def execute(arg) + execute_debug_command(do_cmds: "next #{arg}") + end + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/pushws.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/pushws.rb new file mode 100644 index 00000000..b51928c6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/pushws.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true +# +# change-ws.rb - +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +require_relative "../ext/workspaces" + +module IRB + # :stopdoc: + + module Command + class Workspaces < Base + category "Workspace" + description "Show workspaces." + + def execute(_arg) + inspection_resuls = irb_context.instance_variable_get(:@workspace_stack).map do |ws| + truncated_inspect(ws.main) + end + + puts "[" + inspection_resuls.join(", ") + "]" + end + + private + + def truncated_inspect(obj) + obj_inspection = obj.inspect + + if obj_inspection.size > 20 + obj_inspection = obj_inspection[0, 19] + "...>" + end + + obj_inspection + end + end + + class PushWorkspace < Workspaces + category "Workspace" + description "Push an object to the workspace stack." + + def execute(arg) + if arg.empty? + irb_context.push_workspace + else + obj = eval(arg, irb_context.workspace.binding) + irb_context.push_workspace(obj) + end + super + end + end + + class PopWorkspace < Workspaces + category "Workspace" + description "Pop a workspace from the workspace stack." + + def execute(_arg) + irb_context.pop_workspace + super + end + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/show_doc.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/show_doc.rb new file mode 100644 index 00000000..8a2188e4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/show_doc.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module IRB + module Command + class ShowDoc < Base + include RubyArgsExtractor + + category "Context" + description "Look up documentation with RI." + + help_message <<~HELP_MESSAGE + Usage: show_doc [name] + + When name is provided, IRB will look up the documentation for the given name. + When no name is provided, a RI session will be started. + + Examples: + + show_doc + show_doc Array + show_doc Array#each + + HELP_MESSAGE + + def execute(arg) + # Accept string literal for backward compatibility + name = unwrap_string_literal(arg) + require 'rdoc/ri/driver' + + unless ShowDoc.const_defined?(:Ri) + opts = RDoc::RI::Driver.process_args([]) + ShowDoc.const_set(:Ri, RDoc::RI::Driver.new(opts)) + end + + if name.nil? + Ri.interactive + else + begin + Ri.display_name(name) + rescue RDoc::RI::Error + puts $!.message + end + end + + nil + rescue LoadError, SystemExit + warn "Can't display document because `rdoc` is not installed." + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/show_source.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/show_source.rb new file mode 100644 index 00000000..f4c6f104 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/show_source.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require_relative "../source_finder" +require_relative "../pager" +require_relative "../color" + +module IRB + module Command + class ShowSource < Base + include RubyArgsExtractor + + category "Context" + description "Show the source code of a given method, class/module, or constant." + + help_message <<~HELP_MESSAGE + Usage: show_source [target] [-s] + + -s Show the super method. You can stack it like `-ss` to show the super of the super, etc. + + Examples: + + show_source Foo + show_source Foo#bar + show_source Foo#bar -s + show_source Foo.baz + show_source Foo::BAR + HELP_MESSAGE + + def execute(arg) + # Accept string literal for backward compatibility + str = unwrap_string_literal(arg) + unless str.is_a?(String) + puts "Error: Expected a string but got #{str.inspect}" + return + end + + str, esses = str.split(" -") + super_level = esses ? esses.count("s") : 0 + source = SourceFinder.new(@irb_context).find_source(str, super_level) + + if source + show_source(source) + elsif super_level > 0 + puts "Error: Couldn't locate a super definition for #{str}" + else + puts "Error: Couldn't locate a definition for #{str}" + end + nil + end + + private + + def show_source(source) + if source.binary_file? + content = "\n#{bold('Defined in binary file')}: #{source.file}\n\n" + else + code = source.colorized_content || 'Source not available' + content = <<~CONTENT + + #{bold("From")}: #{source.file}:#{source.line} + + #{code.chomp} + + CONTENT + end + Pager.page_content(content) + end + + def bold(str) + Color.colorize(str, [:BOLD]) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/step.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/step.rb new file mode 100644 index 00000000..29e5e35a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/step.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module Command + class Step < DebugCommand + def execute(arg) + execute_debug_command(do_cmds: "step #{arg}") + end + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/subirb.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/subirb.rb new file mode 100644 index 00000000..85af28c1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/subirb.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true +# +# multi.rb - +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +module IRB + # :stopdoc: + + module Command + class MultiIRBCommand < Base + include RubyArgsExtractor + + private + + def print_deprecated_warning + warn <<~MSG + Multi-irb commands are deprecated and will be removed in IRB 2.0.0. Please use workspace commands instead. + If you have any use case for multi-irb, please leave a comment at https://github.com/ruby/irb/issues/653 + MSG + end + + def extend_irb_context + # this extension patches IRB context like IRB.CurrentContext + require_relative "../ext/multi-irb" + end + + def print_debugger_warning + warn "Multi-IRB commands are not available when the debugger is enabled." + end + end + + class IrbCommand < MultiIRBCommand + category "Multi-irb (DEPRECATED)" + description "Start a child IRB." + + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(*obj) + print_deprecated_warning + + if irb_context.with_debugger + print_debugger_warning + return + end + + extend_irb_context + IRB.irb(nil, *obj) + puts IRB.JobManager.inspect + end + end + + class Jobs < MultiIRBCommand + category "Multi-irb (DEPRECATED)" + description "List of current sessions." + + def execute(_arg) + print_deprecated_warning + + if irb_context.with_debugger + print_debugger_warning + return + end + + extend_irb_context + puts IRB.JobManager.inspect + end + end + + class Foreground < MultiIRBCommand + category "Multi-irb (DEPRECATED)" + description "Switches to the session of the given number." + + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(key = nil) + print_deprecated_warning + + if irb_context.with_debugger + print_debugger_warning + return + end + + extend_irb_context + + raise CommandArgumentError.new("Please specify the id of target IRB job (listed in the `jobs` command).") unless key + IRB.JobManager.switch(key) + puts IRB.JobManager.inspect + end + end + + class Kill < MultiIRBCommand + category "Multi-irb (DEPRECATED)" + description "Kills the session with the given number." + + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(*keys) + print_deprecated_warning + + if irb_context.with_debugger + print_debugger_warning + return + end + + extend_irb_context + IRB.JobManager.kill(*keys) + puts IRB.JobManager.inspect + end + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/whereami.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/whereami.rb new file mode 100644 index 00000000..c8439f12 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/command/whereami.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module IRB + # :stopdoc: + + module Command + class Whereami < Base + category "Context" + description "Show the source code around binding.irb again." + + def execute(_arg) + code = irb_context.workspace.code_around_binding + if code + puts code + else + puts "The current context doesn't have code." + end + end + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/completion.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/completion.rb new file mode 100644 index 00000000..3e970470 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/completion.rb @@ -0,0 +1,504 @@ +# frozen_string_literal: true +# +# irb/completion.rb - +# by Keiju ISHITSUKA(keiju@ishitsuka.com) +# From Original Idea of shugo@ruby-lang.org +# + +require_relative 'ruby-lex' + +module IRB + class BaseCompletor # :nodoc: + + # Set of reserved words used by Ruby, you should not use these for + # constants or variables + ReservedWords = %w[ + __ENCODING__ __LINE__ __FILE__ + BEGIN END + alias and + begin break + case class + def defined? do + else elsif end ensure + false for + if in + module + next nil not + or + redo rescue retry return + self super + then true + undef unless until + when while + yield + ] + + HELP_COMMAND_PREPOSING = /\Ahelp\s+/ + + def completion_candidates(preposing, target, postposing, bind:) + raise NotImplementedError + end + + def doc_namespace(preposing, matched, postposing, bind:) + raise NotImplementedError + end + + GEM_PATHS = + if defined?(Gem::Specification) + Gem::Specification.latest_specs(true).map { |s| + s.require_paths.map { |p| + if File.absolute_path?(p) + p + else + File.join(s.full_gem_path, p) + end + } + }.flatten + else + [] + end.freeze + + def retrieve_gem_and_system_load_path + candidates = (GEM_PATHS | $LOAD_PATH) + candidates.map do |p| + if p.respond_to?(:to_path) + p.to_path + else + String(p) rescue nil + end + end.compact.sort + end + + def retrieve_files_to_require_from_load_path + @files_from_load_path ||= + ( + shortest = [] + rest = retrieve_gem_and_system_load_path.each_with_object([]) { |path, result| + begin + names = Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: path) + rescue Errno::ENOENT + nil + end + next if names.empty? + names.map! { |n| n.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '') }.sort! + shortest << names.shift + result.concat(names) + } + shortest.sort! | rest + ) + end + + def command_candidates(target) + if !target.empty? + IRB::Command.command_names.select { _1.start_with?(target) } + else + [] + end + end + + def retrieve_files_to_require_relative_from_current_dir + @files_from_current_dir ||= Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: '.').map { |path| + path.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '') + } + end + end + + class TypeCompletor < BaseCompletor # :nodoc: + def initialize(context) + @context = context + end + + def inspect + ReplTypeCompletor.info + end + + def completion_candidates(preposing, target, _postposing, bind:) + # When completing the argument of `help` command, only commands should be candidates + return command_candidates(target) if preposing.match?(HELP_COMMAND_PREPOSING) + + commands = if preposing.empty? + command_candidates(target) + # It doesn't make sense to propose commands with other preposing + else + [] + end + + result = ReplTypeCompletor.analyze(preposing + target, binding: bind, filename: @context.irb_path) + + return commands unless result + + commands | result.completion_candidates.map { target + _1 } + end + + def doc_namespace(preposing, matched, _postposing, bind:) + result = ReplTypeCompletor.analyze(preposing + matched, binding: bind, filename: @context.irb_path) + result&.doc_namespace('') + end + end + + class RegexpCompletor < BaseCompletor # :nodoc: + KERNEL_METHODS = ::Kernel.instance_method(:methods) + KERNEL_PRIVATE_METHODS = ::Kernel.instance_method(:private_methods) + KERNEL_INSTANCE_VARIABLES = ::Kernel.instance_method(:instance_variables) + OBJECT_CLASS_INSTANCE_METHOD = ::Object.instance_method(:class) + MODULE_CONSTANTS_INSTANCE_METHOD = ::Module.instance_method(:constants) + + using Module.new { + refine ::Binding do + def eval_methods + KERNEL_METHODS.bind_call(receiver) + end + + def eval_private_methods + KERNEL_PRIVATE_METHODS.bind_call(receiver) + end + + def eval_instance_variables + KERNEL_INSTANCE_VARIABLES.bind_call(receiver) + end + + def eval_global_variables + ::Kernel.global_variables + end + + def eval_class_constants + klass = OBJECT_CLASS_INSTANCE_METHOD.bind_call(receiver) + MODULE_CONSTANTS_INSTANCE_METHOD.bind_call(klass) + end + end + } + + def inspect + 'RegexpCompletor' + end + + def complete_require_path(target, preposing, postposing) + if target =~ /\A(['"])([^'"]+)\Z/ + quote = $1 + actual_target = $2 + else + return nil # It's not String literal + end + tokens = RubyLex.ripper_lex_without_warning(preposing.gsub(/\s*\z/, '')) + tok = nil + tokens.reverse_each do |t| + unless [:on_lparen, :on_sp, :on_ignored_sp, :on_nl, :on_ignored_nl, :on_comment].include?(t.event) + tok = t + break + end + end + return unless tok&.event == :on_ident && tok.state == Ripper::EXPR_CMDARG + + case tok.tok + when 'require' + retrieve_files_to_require_from_load_path.select { |path| + path.start_with?(actual_target) + }.map { |path| + quote + path + } + when 'require_relative' + retrieve_files_to_require_relative_from_current_dir.select { |path| + path.start_with?(actual_target) + }.map { |path| + quote + path + } + end + end + + def completion_candidates(preposing, target, postposing, bind:) + if result = complete_require_path(target, preposing, postposing) + return result + end + + commands = command_candidates(target) + + # When completing the argument of `help` command, only commands should be candidates + return commands if preposing.match?(HELP_COMMAND_PREPOSING) + + # It doesn't make sense to propose commands with other preposing + commands = [] unless preposing.empty? + + completion_data = retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map{ |i| i.encode(Encoding.default_external) } + commands | completion_data + end + + def doc_namespace(_preposing, matched, _postposing, bind:) + retrieve_completion_data(matched, bind: bind, doc_namespace: true) + end + + def retrieve_completion_data(input, bind:, doc_namespace:) + case input + # this regexp only matches the closing character because of irb's Reline.completer_quote_characters setting + # details are described in: https://github.com/ruby/irb/pull/523 + when /^(.*["'`])\.([^.]*)$/ + # String + receiver = $1 + message = $2 + + if doc_namespace + "String.#{message}" + else + candidates = String.instance_methods.collect{|m| m.to_s} + select_message(receiver, message, candidates) + end + + # this regexp only matches the closing character because of irb's Reline.completer_quote_characters setting + # details are described in: https://github.com/ruby/irb/pull/523 + when /^(.*\/)\.([^.]*)$/ + # Regexp + receiver = $1 + message = $2 + + if doc_namespace + "Regexp.#{message}" + else + candidates = Regexp.instance_methods.collect{|m| m.to_s} + select_message(receiver, message, candidates) + end + + when /^([^\]]*\])\.([^.]*)$/ + # Array + receiver = $1 + message = $2 + + if doc_namespace + "Array.#{message}" + else + candidates = Array.instance_methods.collect{|m| m.to_s} + select_message(receiver, message, candidates) + end + + when /^([^\}]*\})\.([^.]*)$/ + # Hash or Proc + receiver = $1 + message = $2 + + if doc_namespace + ["Hash.#{message}", "Proc.#{message}"] + else + hash_candidates = Hash.instance_methods.collect{|m| m.to_s} + proc_candidates = Proc.instance_methods.collect{|m| m.to_s} + select_message(receiver, message, hash_candidates | proc_candidates) + end + + when /^(:[^:.]+)$/ + # Symbol + if doc_namespace + nil + else + sym = $1 + candidates = Symbol.all_symbols.collect do |s| + s.inspect + rescue EncodingError + # ignore + end + candidates.grep(/^#{Regexp.quote(sym)}/) + end + when /^::([A-Z][^:\.\(\)]*)$/ + # Absolute Constant or class methods + receiver = $1 + + candidates = Object.constants.collect{|m| m.to_s} + + if doc_namespace + candidates.find { |i| i == receiver } + else + candidates.grep(/^#{Regexp.quote(receiver)}/).collect{|e| "::" + e} + end + + when /^([A-Z].*)::([^:.]*)$/ + # Constant or class methods + receiver = $1 + message = $2 + + if doc_namespace + "#{receiver}::#{message}" + else + begin + candidates = eval("#{receiver}.constants.collect{|m| m.to_s}", bind) + candidates |= eval("#{receiver}.methods.collect{|m| m.to_s}", bind) + rescue Exception + candidates = [] + end + + select_message(receiver, message, candidates.sort, "::") + end + + when /^(:[^:.]+)(\.|::)([^.]*)$/ + # Symbol + receiver = $1 + sep = $2 + message = $3 + + if doc_namespace + "Symbol.#{message}" + else + candidates = Symbol.instance_methods.collect{|m| m.to_s} + select_message(receiver, message, candidates, sep) + end + + when /^(?-?(?:0[dbo])?[0-9_]+(?:\.[0-9_]+)?(?:(?:[eE][+-]?[0-9]+)?i?|r)?)(?\.|::)(?[^.]*)$/ + # Numeric + receiver = $~[:num] + sep = $~[:sep] + message = $~[:mes] + + begin + instance = eval(receiver, bind) + + if doc_namespace + "#{instance.class.name}.#{message}" + else + candidates = instance.methods.collect{|m| m.to_s} + select_message(receiver, message, candidates, sep) + end + rescue Exception + if doc_namespace + nil + else + [] + end + end + + when /^(-?0x[0-9a-fA-F_]+)(\.|::)([^.]*)$/ + # Numeric(0xFFFF) + receiver = $1 + sep = $2 + message = $3 + + begin + instance = eval(receiver, bind) + if doc_namespace + "#{instance.class.name}.#{message}" + else + candidates = instance.methods.collect{|m| m.to_s} + select_message(receiver, message, candidates, sep) + end + rescue Exception + if doc_namespace + nil + else + [] + end + end + + when /^(\$[^.]*)$/ + # global var + gvar = $1 + all_gvars = global_variables.collect{|m| m.to_s} + + if doc_namespace + all_gvars.find{ |i| i == gvar } + else + all_gvars.grep(Regexp.new(Regexp.quote(gvar))) + end + + when /^([^.:"].*)(\.|::)([^.]*)$/ + # variable.func or func.func + receiver = $1 + sep = $2 + message = $3 + + gv = bind.eval_global_variables.collect{|m| m.to_s}.push("true", "false", "nil") + lv = bind.local_variables.collect{|m| m.to_s} + iv = bind.eval_instance_variables.collect{|m| m.to_s} + cv = bind.eval_class_constants.collect{|m| m.to_s} + + if (gv | lv | iv | cv).include?(receiver) or /^[A-Z]/ =~ receiver && /\./ !~ receiver + # foo.func and foo is var. OR + # foo::func and foo is var. OR + # foo::Const and foo is var. OR + # Foo::Bar.func + begin + candidates = [] + rec = eval(receiver, bind) + if sep == "::" and rec.kind_of?(Module) + candidates = rec.constants.collect{|m| m.to_s} + end + candidates |= rec.methods.collect{|m| m.to_s} + rescue Exception + candidates = [] + end + else + # func1.func2 + candidates = [] + end + + if doc_namespace + rec_class = rec.is_a?(Module) ? rec : rec.class + "#{rec_class.name}#{sep}#{candidates.find{ |i| i == message }}" rescue nil + else + select_message(receiver, message, candidates, sep) + end + + when /^\.([^.]*)$/ + # unknown(maybe String) + + receiver = "" + message = $1 + + candidates = String.instance_methods(true).collect{|m| m.to_s} + + if doc_namespace + "String.#{candidates.find{ |i| i == message }}" + else + select_message(receiver, message, candidates.sort) + end + when /^\s*$/ + # empty input + if doc_namespace + nil + else + [] + end + else + if doc_namespace + vars = (bind.local_variables | bind.eval_instance_variables).collect{|m| m.to_s} + perfect_match_var = vars.find{|m| m.to_s == input} + if perfect_match_var + eval("#{perfect_match_var}.class.name", bind) rescue nil + else + candidates = (bind.eval_methods | bind.eval_private_methods | bind.local_variables | bind.eval_instance_variables | bind.eval_class_constants).collect{|m| m.to_s} + candidates |= ReservedWords + candidates.find{ |i| i == input } + end + else + candidates = (bind.eval_methods | bind.eval_private_methods | bind.local_variables | bind.eval_instance_variables | bind.eval_class_constants).collect{|m| m.to_s} + candidates |= ReservedWords + candidates.grep(/^#{Regexp.quote(input)}/).sort + end + end + end + + # Set of available operators in Ruby + Operators = %w[% & * ** + - / < << <= <=> == === =~ > >= >> [] []= ^ ! != !~] + + def select_message(receiver, message, candidates, sep = ".") + candidates.grep(/^#{Regexp.quote(message)}/).collect do |e| + case e + when /^[a-zA-Z_]/ + receiver + sep + e + when /^[0-9]/ + when *Operators + #receiver + " " + e + end + end + end + end + + module InputCompletor + class << self + private def regexp_completor + @regexp_completor ||= RegexpCompletor.new + end + + def retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace.binding, doc_namespace: false) + regexp_completor.retrieve_completion_data(input, bind: bind, doc_namespace: doc_namespace) + end + end + CompletionProc = ->(target, preposing = nil, postposing = nil) { + regexp_completor.completion_candidates(preposing || '', target, postposing || '', bind: IRB.conf[:MAIN_CONTEXT].workspace.binding) + } + end + deprecate_constant :InputCompletor +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/context.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/context.rb new file mode 100644 index 00000000..395d9081 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/context.rb @@ -0,0 +1,751 @@ +# frozen_string_literal: true +# +# irb/context.rb - irb context +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +require_relative "workspace" +require_relative "inspector" +require_relative "input-method" +require_relative "output-method" + +module IRB + # A class that wraps the current state of the irb session, including the + # configuration of IRB.conf. + class Context + KERNEL_PUBLIC_METHOD = ::Kernel.instance_method(:public_method) + KERNEL_METHOD = ::Kernel.instance_method(:method) + + ASSIGN_OPERATORS_REGEXP = Regexp.union(%w[= += -= *= /= %= **= &= |= &&= ||= ^= <<= >>=]) + # Creates a new IRB context. + # + # The optional +input_method+ argument: + # + # +nil+:: uses stdin or Reline or Readline + # +String+:: uses a File + # +other+:: uses this as InputMethod + def initialize(irb, workspace = nil, input_method = nil) + @irb = irb + @workspace_stack = [] + if workspace + @workspace_stack << workspace + else + @workspace_stack << WorkSpace.new + end + @thread = Thread.current + + # copy of default configuration + @ap_name = IRB.conf[:AP_NAME] + @rc = IRB.conf[:RC] + @load_modules = IRB.conf[:LOAD_MODULES] + + if IRB.conf.has_key?(:USE_SINGLELINE) + @use_singleline = IRB.conf[:USE_SINGLELINE] + elsif IRB.conf.has_key?(:USE_READLINE) # backward compatibility + @use_singleline = IRB.conf[:USE_READLINE] + else + @use_singleline = nil + end + if IRB.conf.has_key?(:USE_MULTILINE) + @use_multiline = IRB.conf[:USE_MULTILINE] + elsif IRB.conf.has_key?(:USE_RELINE) # backward compatibility + warn <<~MSG.strip + USE_RELINE is deprecated, please use USE_MULTILINE instead. + MSG + @use_multiline = IRB.conf[:USE_RELINE] + elsif IRB.conf.has_key?(:USE_REIDLINE) + warn <<~MSG.strip + USE_REIDLINE is deprecated, please use USE_MULTILINE instead. + MSG + @use_multiline = IRB.conf[:USE_REIDLINE] + else + @use_multiline = nil + end + @use_autocomplete = IRB.conf[:USE_AUTOCOMPLETE] + @verbose = IRB.conf[:VERBOSE] + @io = nil + + self.inspect_mode = IRB.conf[:INSPECT_MODE] + self.use_tracer = IRB.conf[:USE_TRACER] + self.use_loader = IRB.conf[:USE_LOADER] if IRB.conf[:USE_LOADER] + self.eval_history = IRB.conf[:EVAL_HISTORY] if IRB.conf[:EVAL_HISTORY] + + @ignore_sigint = IRB.conf[:IGNORE_SIGINT] + @ignore_eof = IRB.conf[:IGNORE_EOF] + + @back_trace_limit = IRB.conf[:BACK_TRACE_LIMIT] + + self.prompt_mode = IRB.conf[:PROMPT_MODE] + + @irb_name = IRB.conf[:IRB_NAME] + + unless IRB.conf[:SINGLE_IRB] or !defined?(IRB::JobManager) + @irb_name = @irb_name + "#" + IRB.JobManager.n_jobs.to_s + end + + self.irb_path = "(" + @irb_name + ")" + + case input_method + when nil + @io = nil + case use_multiline? + when nil + if term_interactive? && IRB.conf[:PROMPT_MODE] != :INF_RUBY && !use_singleline? + # Both of multiline mode and singleline mode aren't specified. + @io = RelineInputMethod.new(build_completor) + else + @io = nil + end + when false + @io = nil + when true + @io = RelineInputMethod.new(build_completor) + end + unless @io + case use_singleline? + when nil + if (defined?(ReadlineInputMethod) && term_interactive? && + IRB.conf[:PROMPT_MODE] != :INF_RUBY) + @io = ReadlineInputMethod.new + else + @io = nil + end + when false + @io = nil + when true + if defined?(ReadlineInputMethod) + @io = ReadlineInputMethod.new + else + @io = nil + end + else + @io = nil + end + end + @io = StdioInputMethod.new unless @io + + when '-' + @io = FileInputMethod.new($stdin) + @irb_name = '-' + self.irb_path = '-' + when String + @io = FileInputMethod.new(input_method) + @irb_name = File.basename(input_method) + self.irb_path = input_method + else + @io = input_method + end + @extra_doc_dirs = IRB.conf[:EXTRA_DOC_DIRS] + + @echo = IRB.conf[:ECHO] + if @echo.nil? + @echo = true + end + + @echo_on_assignment = IRB.conf[:ECHO_ON_ASSIGNMENT] + if @echo_on_assignment.nil? + @echo_on_assignment = :truncate + end + + @newline_before_multiline_output = IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT] + if @newline_before_multiline_output.nil? + @newline_before_multiline_output = true + end + + @command_aliases = IRB.conf[:COMMAND_ALIASES].dup + end + + def use_tracer=(val) + require_relative "ext/tracer" if val + IRB.conf[:USE_TRACER] = val + end + + def eval_history=(val) + self.class.remove_method(__method__) + require_relative "ext/eval_history" + __send__(__method__, val) + end + + def use_loader=(val) + self.class.remove_method(__method__) + require_relative "ext/use-loader" + __send__(__method__, val) + end + + def save_history=(val) + IRB.conf[:SAVE_HISTORY] = val + end + + def save_history + IRB.conf[:SAVE_HISTORY] + end + + # A copy of the default IRB.conf[:HISTORY_FILE] + def history_file + IRB.conf[:HISTORY_FILE] + end + + # Set IRB.conf[:HISTORY_FILE] to the given +hist+. + def history_file=(hist) + IRB.conf[:HISTORY_FILE] = hist + end + + # Workspace in the current context. + def workspace + @workspace_stack.last + end + + # Replace the current workspace with the given +workspace+. + def replace_workspace(workspace) + @workspace_stack.pop + @workspace_stack.push(workspace) + end + + # The top-level workspace, see WorkSpace#main + def main + workspace.main + end + + # The toplevel workspace, see #home_workspace + attr_reader :workspace_home + # The current thread in this context. + attr_reader :thread + # The current input method. + # + # Can be either StdioInputMethod, ReadlineInputMethod, + # RelineInputMethod, FileInputMethod or other specified when the + # context is created. See ::new for more # information on +input_method+. + attr_accessor :io + + # Current irb session. + attr_accessor :irb + # A copy of the default IRB.conf[:AP_NAME] + attr_accessor :ap_name + # A copy of the default IRB.conf[:RC] + attr_accessor :rc + # A copy of the default IRB.conf[:LOAD_MODULES] + attr_accessor :load_modules + # Can be either name from IRB.conf[:IRB_NAME], or the number of + # the current job set by JobManager, such as irb#2 + attr_accessor :irb_name + + # Can be one of the following: + # - the #irb_name surrounded by parenthesis + # - the +input_method+ passed to Context.new + # - the file path of the current IRB context in a binding.irb session + attr_reader :irb_path + + # Sets @irb_path to the given +path+ as well as @eval_path + # @eval_path is used for evaluating code in the context of IRB session + # It's the same as irb_path, but with the IRB name postfix + # This makes sure users can distinguish the methods defined in the IRB session + # from the methods defined in the current file's context, especially with binding.irb + def irb_path=(path) + @irb_path = path + + if File.exist?(path) + @eval_path = "#{path}(#{IRB.conf[:IRB_NAME]})" + else + @eval_path = path + end + end + + # Whether multiline editor mode is enabled or not. + # + # A copy of the default IRB.conf[:USE_MULTILINE] + attr_reader :use_multiline + # Whether singleline editor mode is enabled or not. + # + # A copy of the default IRB.conf[:USE_SINGLELINE] + attr_reader :use_singleline + # Whether colorization is enabled or not. + # + # A copy of the default IRB.conf[:USE_AUTOCOMPLETE] + attr_reader :use_autocomplete + # A copy of the default IRB.conf[:INSPECT_MODE] + attr_reader :inspect_mode + # Inspector for the current context + attr_reader :inspect_method + + # A copy of the default IRB.conf[:PROMPT_MODE] + attr_reader :prompt_mode + # Standard IRB prompt. + # + # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. + attr_accessor :prompt_i + # IRB prompt for continuated strings. + # + # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. + attr_accessor :prompt_s + # IRB prompt for continuated statement. (e.g. immediately after an +if+) + # + # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. + attr_accessor :prompt_c + + # TODO: Remove this when developing v2.0 + def prompt_n + warn "IRB::Context#prompt_n is deprecated and will be removed in the next major release." + "" + end + + # TODO: Remove this when developing v2.0 + def prompt_n=(_) + warn "IRB::Context#prompt_n= is deprecated and will be removed in the next major release." + "" + end + + # Can be either the default IRB.conf[:AUTO_INDENT], or the + # mode set by #prompt_mode= + # + # To disable auto-indentation in irb: + # + # IRB.conf[:AUTO_INDENT] = false + # + # or + # + # irb_context.auto_indent_mode = false + # + # or + # + # IRB.CurrentContext.auto_indent_mode = false + # + # See IRB@Configuration for more information. + attr_accessor :auto_indent_mode + # The format of the return statement, set by #prompt_mode= using the + # +:RETURN+ of the +mode+ passed to set the current #prompt_mode. + attr_accessor :return_format + + # Whether ^C (+control-c+) will be ignored or not. + # + # If set to +false+, ^C will quit irb. + # + # If set to +true+, + # + # * during input: cancel input then return to top level. + # * during execute: abandon current execution. + attr_accessor :ignore_sigint + # Whether ^D (+control-d+) will be ignored or not. + # + # If set to +false+, ^D will quit irb. + attr_accessor :ignore_eof + # Specify the installation locations of the ri file to be displayed in the + # document dialog. + attr_accessor :extra_doc_dirs + # Whether to echo the return value to output or not. + # + # Uses IRB.conf[:ECHO] if available, or defaults to +true+. + # + # puts "hello" + # # hello + # #=> nil + # IRB.CurrentContext.echo = false + # puts "omg" + # # omg + attr_accessor :echo + # Whether to echo for assignment expressions. + # + # If set to +false+, the value of assignment will not be shown. + # + # If set to +true+, the value of assignment will be shown. + # + # If set to +:truncate+, the value of assignment will be shown and truncated. + # + # It defaults to +:truncate+. + # + # a = "omg" + # #=> omg + # + # a = "omg" * 10 + # #=> omgomgomgomgomgomgomg... + # + # IRB.CurrentContext.echo_on_assignment = false + # a = "omg" + # + # IRB.CurrentContext.echo_on_assignment = true + # a = "omg" * 10 + # #=> omgomgomgomgomgomgomgomgomgomg + # + # To set the behaviour of showing on assignment in irb: + # + # IRB.conf[:ECHO_ON_ASSIGNMENT] = :truncate or true or false + # + # or + # + # irb_context.echo_on_assignment = :truncate or true or false + # + # or + # + # IRB.CurrentContext.echo_on_assignment = :truncate or true or false + attr_accessor :echo_on_assignment + # Whether a newline is put before multiline output. + # + # Uses IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT] if available, + # or defaults to +true+. + # + # "abc\ndef" + # #=> + # abc + # def + # IRB.CurrentContext.newline_before_multiline_output = false + # "abc\ndef" + # #=> abc + # def + attr_accessor :newline_before_multiline_output + # Whether verbose messages are displayed or not. + # + # A copy of the default IRB.conf[:VERBOSE] + attr_accessor :verbose + + # The limit of backtrace lines displayed as top +n+ and tail +n+. + # + # The default value is 16. + # + # Can also be set using the +--back-trace-limit+ command line option. + attr_accessor :back_trace_limit + + # User-defined IRB command aliases + attr_accessor :command_aliases + + attr_accessor :with_debugger + + # Alias for #use_multiline + alias use_multiline? use_multiline + # Alias for #use_singleline + alias use_singleline? use_singleline + # backward compatibility + alias use_reline use_multiline + # backward compatibility + alias use_reline? use_multiline + # backward compatibility + alias use_readline use_singleline + # backward compatibility + alias use_readline? use_singleline + # Alias for #use_autocomplete + alias use_autocomplete? use_autocomplete + # Alias for #rc + alias rc? rc + alias ignore_sigint? ignore_sigint + alias ignore_eof? ignore_eof + alias echo? echo + alias echo_on_assignment? echo_on_assignment + alias newline_before_multiline_output? newline_before_multiline_output + + # Returns whether messages are displayed or not. + def verbose? + if @verbose.nil? + if @io.kind_of?(RelineInputMethod) + false + elsif defined?(ReadlineInputMethod) && @io.kind_of?(ReadlineInputMethod) + false + elsif !STDIN.tty? or @io.kind_of?(FileInputMethod) + true + else + false + end + else + @verbose + end + end + + # Whether #verbose? is +true+, and +input_method+ is either + # StdioInputMethod or RelineInputMethod or ReadlineInputMethod, see #io + # for more information. + def prompting? + verbose? || @io.prompting? + end + + # The return value of the last statement evaluated. + attr_reader :last_value + + # Sets the return value from the last statement evaluated in this context + # to #last_value. + def set_last_value(value) + @last_value = value + workspace.local_variable_set :_, value + end + + # Sets the +mode+ of the prompt in this context. + # + # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. + def prompt_mode=(mode) + @prompt_mode = mode + pconf = IRB.conf[:PROMPT][mode] + @prompt_i = pconf[:PROMPT_I] + @prompt_s = pconf[:PROMPT_S] + @prompt_c = pconf[:PROMPT_C] + @return_format = pconf[:RETURN] + @return_format = "%s\n" if @return_format == nil + if ai = pconf.include?(:AUTO_INDENT) + @auto_indent_mode = ai + else + @auto_indent_mode = IRB.conf[:AUTO_INDENT] + end + end + + # Whether #inspect_mode is set or not, see #inspect_mode= for more detail. + def inspect? + @inspect_mode.nil? or @inspect_mode + end + + # Whether #io uses a File for the +input_method+ passed when creating the + # current context, see ::new + def file_input? + @io.class == FileInputMethod + end + + # Specifies the inspect mode with +opt+: + # + # +true+:: display +inspect+ + # +false+:: display +to_s+ + # +nil+:: inspect mode in non-math mode, + # non-inspect mode in math mode + # + # See IRB::Inspector for more information. + # + # Can also be set using the +--inspect+ and +--noinspect+ command line + # options. + def inspect_mode=(opt) + + if i = Inspector::INSPECTORS[opt] + @inspect_mode = opt + @inspect_method = i + i.init + else + case opt + when nil + if Inspector.keys_with_inspector(Inspector::INSPECTORS[true]).include?(@inspect_mode) + self.inspect_mode = false + elsif Inspector.keys_with_inspector(Inspector::INSPECTORS[false]).include?(@inspect_mode) + self.inspect_mode = true + else + puts "Can't switch inspect mode." + return + end + when /^\s*\{.*\}\s*$/ + begin + inspector = eval "proc#{opt}" + rescue Exception + puts "Can't switch inspect mode(#{opt})." + return + end + self.inspect_mode = inspector + when Proc + self.inspect_mode = IRB::Inspector(opt) + when Inspector + prefix = "usr%d" + i = 1 + while Inspector::INSPECTORS[format(prefix, i)]; i += 1; end + @inspect_mode = format(prefix, i) + @inspect_method = opt + Inspector.def_inspector(format(prefix, i), @inspect_method) + else + puts "Can't switch inspect mode(#{opt})." + return + end + end + print "Switch to#{unless @inspect_mode; ' non';end} inspect mode.\n" if verbose? + @inspect_mode + end + + def evaluate(statement, line_no) # :nodoc: + @line_no = line_no + + case statement + when Statement::EmptyInput + return + when Statement::Expression + result = evaluate_expression(statement.code, line_no) + set_last_value(result) + when Statement::Command + statement.command_class.execute(self, statement.arg) + when Statement::IncorrectAlias + warn statement.message + end + + nil + end + + def from_binding? + @irb.from_binding + end + + def evaluate_expression(code, line_no) # :nodoc: + result = nil + if IRB.conf[:MEASURE] && IRB.conf[:MEASURE_CALLBACKS].empty? + IRB.set_measure_callback + end + + if IRB.conf[:MEASURE] && !IRB.conf[:MEASURE_CALLBACKS].empty? + last_proc = proc do + result = workspace.evaluate(code, @eval_path, line_no) + end + IRB.conf[:MEASURE_CALLBACKS].inject(last_proc) do |chain, item| + _name, callback, arg = item + proc do + callback.(self, code, line_no, arg) do + chain.call + end + end + end.call + else + result = workspace.evaluate(code, @eval_path, line_no) + end + result + end + + def parse_input(code, is_assignment_expression) + command_name, arg = code.strip.split(/\s+/, 2) + arg ||= '' + + # command can only be 1 line + if code.lines.size != 1 || + # command name is required + command_name.nil? || + # local variable have precedence over command + local_variables.include?(command_name.to_sym) || + # assignment expression is not a command + (is_assignment_expression || + (arg.start_with?(ASSIGN_OPERATORS_REGEXP) && !arg.start_with?(/==|=~/))) + return Statement::Expression.new(code, is_assignment_expression) + end + + command = command_name.to_sym + + # Check command aliases + if aliased_name = command_aliases[command] + if command_class = Command.load_command(aliased_name) + command = aliased_name + elsif HelperMethod.helper_methods[aliased_name] + message = <<~MESSAGE + Using command alias `#{command}` for helper method `#{aliased_name}` is not supported. + Please check the value of `IRB.conf[:COMMAND_ALIASES]`. + MESSAGE + return Statement::IncorrectAlias.new(message) + else + message = <<~MESSAGE + You're trying to use command alias `#{command}` for command `#{aliased_name}`, but `#{aliased_name}` does not exist. + Please check the value of `IRB.conf[:COMMAND_ALIASES]`. + MESSAGE + return Statement::IncorrectAlias.new(message) + end + else + command_class = Command.load_command(command) + end + + # Check visibility + public_method = !!KERNEL_PUBLIC_METHOD.bind_call(main, command) rescue false + private_method = !public_method && !!KERNEL_METHOD.bind_call(main, command) rescue false + if command_class && Command.execute_as_command?(command, public_method: public_method, private_method: private_method) + Statement::Command.new(code, command_class, arg) + else + Statement::Expression.new(code, is_assignment_expression) + end + end + + def colorize_input(input, complete:) + if IRB.conf[:USE_COLORIZE] && IRB::Color.colorable? + lvars = local_variables || [] + parsed_input = parse_input(input, false) + if parsed_input.is_a?(Statement::Command) + name, sep, arg = input.split(/(\s+)/, 2) + arg = IRB::Color.colorize_code(arg, complete: complete, local_variables: lvars) + "#{IRB::Color.colorize(name, [:BOLD])}\e[m#{sep}#{arg}" + else + IRB::Color.colorize_code(input, complete: complete, local_variables: lvars) + end + else + Reline::Unicode.escape_for_print(input) + end + end + + def inspect_last_value(output = +'') # :nodoc: + @inspect_method.inspect_value(@last_value, output) + end + + def inspector_support_stream_output? + @inspect_method.support_stream_output? + end + + NOPRINTING_IVARS = ["@last_value"] # :nodoc: + NO_INSPECTING_IVARS = ["@irb", "@io"] # :nodoc: + IDNAME_IVARS = ["@prompt_mode"] # :nodoc: + + alias __inspect__ inspect + def inspect # :nodoc: + array = [] + for ivar in instance_variables.sort{|e1, e2| e1 <=> e2} + ivar = ivar.to_s + name = ivar.sub(/^@(.*)$/, '\1') + val = instance_eval(ivar) + case ivar + when *NOPRINTING_IVARS + array.push format("conf.%s=%s", name, "...") + when *NO_INSPECTING_IVARS + array.push format("conf.%s=%s", name, val.to_s) + when *IDNAME_IVARS + array.push format("conf.%s=:%s", name, val.id2name) + else + array.push format("conf.%s=%s", name, val.inspect) + end + end + array.join("\n") + end + alias __to_s__ to_s + alias to_s inspect + + def local_variables # :nodoc: + workspace.binding.local_variables + end + + def safe_method_call_on_main(method_name) + main_object = main + Object === main_object ? main_object.__send__(method_name) : Object.instance_method(method_name).bind_call(main_object) + end + + private + + def term_interactive? + return true if ENV['TEST_IRB_FORCE_INTERACTIVE'] + STDIN.tty? && ENV['TERM'] != 'dumb' + end + + def build_completor + completor_type = IRB.conf[:COMPLETOR] + + # Gem repl_type_completor is added to bundled gems in Ruby 3.4. + # Use :type as default completor only in Ruby 3.4 or later. + verbose = !!completor_type + completor_type ||= RUBY_VERSION >= '3.4' ? :type : :regexp + + case completor_type + when :regexp + return RegexpCompletor.new + when :type + completor = build_type_completor(verbose: verbose) + return completor if completor + else + warn "Invalid value for IRB.conf[:COMPLETOR]: #{completor_type}" + end + # Fallback to RegexpCompletor + RegexpCompletor.new + end + + def build_type_completor(verbose:) + if RUBY_ENGINE == 'truffleruby' + # Avoid SyntaxError. truffleruby does not support endless method definition yet. + warn 'TypeCompletor is not supported on TruffleRuby yet' if verbose + return + end + + begin + require 'repl_type_completor' + rescue LoadError => e + warn "TypeCompletor requires `gem repl_type_completor`: #{e.message}" if verbose + return + end + + ReplTypeCompletor.preload_rbs + TypeCompletor.new(self) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/debug.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/debug.rb new file mode 100644 index 00000000..59be1365 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/debug.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +module IRB + module Debug + IRB_DIR = File.expand_path('..', __dir__) + + class << self + def insert_debug_break(pre_cmds: nil, do_cmds: nil) + options = { oneshot: true, hook_call: false } + + if pre_cmds || do_cmds + options[:command] = ['irb', pre_cmds, do_cmds] + end + if DEBUGGER__::LineBreakpoint.instance_method(:initialize).parameters.include?([:key, :skip_src]) + options[:skip_src] = true + end + + # To make debugger commands like `next` or `continue` work without asking + # the user to quit IRB after that, we need to exit IRB first and then hit + # a TracePoint on #debug_break. + file, lineno = IRB::Irb.instance_method(:debug_break).source_location + DEBUGGER__::SESSION.add_line_breakpoint(file, lineno + 1, **options) + end + + def setup(irb) + # When debug session is not started at all + unless defined?(DEBUGGER__::SESSION) + begin + require "debug/session" + rescue LoadError # debug.gem is not written in Gemfile + return false unless load_bundled_debug_gem + end + DEBUGGER__::CONFIG.set_config + configure_irb_for_debugger(irb) + + DEBUGGER__.initialize_session{ IRB::Debug::UI.new(irb) } + end + + # When debug session was previously started but not by IRB + if defined?(DEBUGGER__::SESSION) && !irb.context.with_debugger + configure_irb_for_debugger(irb) + DEBUGGER__::SESSION.reset_ui(IRB::Debug::UI.new(irb)) + end + + # Apply patches to debug gem so it skips IRB frames + unless DEBUGGER__.respond_to?(:capture_frames_without_irb) + DEBUGGER__.singleton_class.send(:alias_method, :capture_frames_without_irb, :capture_frames) + + def DEBUGGER__.capture_frames(*args) + frames = capture_frames_without_irb(*args) + frames.reject! do |frame| + frame.realpath&.start_with?(IRB_DIR) || frame.path == "" + end + frames + end + + DEBUGGER__::ThreadClient.prepend(SkipPathHelperForIRB) + end + + if !DEBUGGER__::CONFIG[:no_hint] && irb.context.io.is_a?(RelineInputMethod) + Reline.output_modifier_proc = proc do |input, complete:| + unless input.strip.empty? + cmd = input.split(/\s/, 2).first + + if !complete && DEBUGGER__.commands.key?(cmd) + input = input.sub(/\n$/, " # debug command\n") + end + end + + irb.context.colorize_input(input, complete: complete) + end + end + + true + end + + private + + def configure_irb_for_debugger(irb) + require 'irb/debug/ui' + IRB.instance_variable_set(:@debugger_irb, irb) + irb.context.with_debugger = true + irb.context.irb_name += ":rdbg" + irb.context.io.load_history if irb.context.io.class < HistorySavingAbility + end + + module SkipPathHelperForIRB + def skip_internal_path?(path) + # The latter can be removed once https://github.com/ruby/debug/issues/866 is resolved + super || path.match?(IRB_DIR) || path.match?('') + end + end + + # This is used when debug.gem is not written in Gemfile. Even if it's not + # installed by `bundle install`, debug.gem is installed by default because + # it's a bundled gem. This method tries to activate and load that. + def load_bundled_debug_gem + # Discover latest debug.gem under GEM_PATH + debug_gem = Gem.paths.path.flat_map { |path| Dir.glob("#{path}/gems/debug-*") }.select do |path| + File.basename(path).match?(/\Adebug-\d+\.\d+\.\d+(\w+)?\z/) + end.sort_by do |path| + Gem::Version.new(File.basename(path).delete_prefix('debug-')) + end.last + return false unless debug_gem + + # Discover debug/debug.so under extensions for Ruby 3.2+ + ext_name = "/debug/debug.#{RbConfig::CONFIG['DLEXT']}" + ext_path = Gem.paths.path.flat_map do |path| + Dir.glob("#{path}/extensions/**/#{File.basename(debug_gem)}#{ext_name}") + end.first + + # Attempt to forcibly load the bundled gem + if ext_path + $LOAD_PATH << ext_path.delete_suffix(ext_name) + end + $LOAD_PATH << "#{debug_gem}/lib" + begin + require "debug/session" + puts "Loaded #{File.basename(debug_gem)}" + true + rescue LoadError + false + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/debug/ui.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/debug/ui.rb new file mode 100644 index 00000000..a21ec6b1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/debug/ui.rb @@ -0,0 +1,101 @@ +require 'io/console/size' +require 'debug/console' + +module IRB + module Debug + class UI < DEBUGGER__::UI_Base + def initialize(irb) + @irb = irb + end + + def remote? + false + end + + def activate session, on_fork: false + end + + def deactivate + end + + def width + if (w = IO.console_size[1]) == 0 # for tests PTY + 80 + else + w + end + end + + def quit n + yield + exit n + end + + def ask prompt + setup_interrupt do + print prompt + ($stdin.gets || '').strip + end + end + + def puts str = nil + case str + when Array + str.each{|line| + $stdout.puts line.chomp + } + when String + Pager.page_content(str, retain_content: true) + when nil + $stdout.puts + end + end + + def readline _ + setup_interrupt do + tc = DEBUGGER__::SESSION.instance_variable_get(:@tc) + cmd = @irb.debug_readline(tc.current_frame.eval_binding || TOPLEVEL_BINDING) + + case cmd + when nil # when user types C-d + "continue" + else + cmd + end + end + end + + def setup_interrupt + DEBUGGER__::SESSION.intercept_trap_sigint false do + current_thread = Thread.current # should be session_server thread + + prev_handler = trap(:INT){ + current_thread.raise Interrupt + } + + yield + ensure + trap(:INT, prev_handler) + end + end + + def after_fork_parent + parent_pid = Process.pid + + at_exit{ + DEBUGGER__::SESSION.intercept_trap_sigint_end + trap(:SIGINT, :IGNORE) + + if Process.pid == parent_pid + # only check child process from its parent + begin + # wait for all child processes to keep terminal + Process.waitpid + rescue Errno::ESRCH, Errno::ECHILD + end + end + } + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/default_commands.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/default_commands.rb new file mode 100644 index 00000000..9820a1f3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/default_commands.rb @@ -0,0 +1,279 @@ +# frozen_string_literal: true + +require_relative "command" +require_relative "command/internal_helpers" +require_relative "command/backtrace" +require_relative "command/break" +require_relative "command/catch" +require_relative "command/cd" +require_relative "command/chws" +require_relative "command/context" +require_relative "command/continue" +require_relative "command/copy" +require_relative "command/debug" +require_relative "command/delete" +require_relative "command/disable_irb" +require_relative "command/edit" +require_relative "command/exit" +require_relative "command/finish" +require_relative "command/force_exit" +require_relative "command/help" +require_relative "command/history" +require_relative "command/info" +require_relative "command/irb_info" +require_relative "command/load" +require_relative "command/ls" +require_relative "command/measure" +require_relative "command/next" +require_relative "command/pushws" +require_relative "command/show_doc" +require_relative "command/show_source" +require_relative "command/step" +require_relative "command/subirb" +require_relative "command/whereami" + +module IRB + module Command + NO_OVERRIDE = 0 + OVERRIDE_PRIVATE_ONLY = 0x01 + OVERRIDE_ALL = 0x02 + + class << self + # This API is for IRB's internal use only and may change at any time. + # Please do NOT use it. + def _register_with_aliases(name, command_class, *aliases) + @commands[name.to_sym] = [command_class, aliases] + end + + def all_commands_info + user_aliases = IRB.CurrentContext.command_aliases.each_with_object({}) do |(alias_name, target), result| + result[target] ||= [] + result[target] << alias_name + end + + commands.map do |command_name, (command_class, aliases)| + aliases = aliases.map { |a| a.first } + + if additional_aliases = user_aliases[command_name] + aliases += additional_aliases + end + + display_name = aliases.shift || command_name + { + display_name: display_name, + description: command_class.description, + category: command_class.category + } + end + end + + def command_override_policies + @@command_override_policies ||= commands.flat_map do |cmd_name, (cmd_class, aliases)| + [[cmd_name, OVERRIDE_ALL]] + aliases + end.to_h + end + + def execute_as_command?(name, public_method:, private_method:) + case command_override_policies[name] + when OVERRIDE_ALL + true + when OVERRIDE_PRIVATE_ONLY + !public_method + when NO_OVERRIDE + !public_method && !private_method + end + end + + def command_names + command_override_policies.keys.map(&:to_s) + end + + # Convert a command name to its implementation class if such command exists + def load_command(command) + command = command.to_sym + commands.each do |command_name, (command_class, aliases)| + if command_name == command || aliases.any? { |alias_name, _| alias_name == command } + return command_class + end + end + nil + end + end + + _register_with_aliases(:irb_context, Command::Context, + [:context, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_exit, Command::Exit, + [:exit, OVERRIDE_PRIVATE_ONLY], + [:quit, OVERRIDE_PRIVATE_ONLY], + [:irb_quit, OVERRIDE_PRIVATE_ONLY] + ) + + _register_with_aliases(:irb_exit!, Command::ForceExit, + [:exit!, OVERRIDE_PRIVATE_ONLY] + ) + + _register_with_aliases(:irb_current_working_workspace, Command::CurrentWorkingWorkspace, + [:cwws, NO_OVERRIDE], + [:pwws, NO_OVERRIDE], + [:irb_print_working_workspace, OVERRIDE_ALL], + [:irb_cwws, OVERRIDE_ALL], + [:irb_pwws, OVERRIDE_ALL], + [:irb_current_working_binding, OVERRIDE_ALL], + [:irb_print_working_binding, OVERRIDE_ALL], + [:irb_cwb, OVERRIDE_ALL], + [:irb_pwb, OVERRIDE_ALL], + ) + + _register_with_aliases(:irb_change_workspace, Command::ChangeWorkspace, + [:chws, NO_OVERRIDE], + [:cws, NO_OVERRIDE], + [:irb_chws, OVERRIDE_ALL], + [:irb_cws, OVERRIDE_ALL], + [:irb_change_binding, OVERRIDE_ALL], + [:irb_cb, OVERRIDE_ALL], + [:cb, NO_OVERRIDE], + ) + + _register_with_aliases(:irb_workspaces, Command::Workspaces, + [:workspaces, NO_OVERRIDE], + [:irb_bindings, OVERRIDE_ALL], + [:bindings, NO_OVERRIDE], + ) + + _register_with_aliases(:irb_push_workspace, Command::PushWorkspace, + [:pushws, NO_OVERRIDE], + [:irb_pushws, OVERRIDE_ALL], + [:irb_push_binding, OVERRIDE_ALL], + [:irb_pushb, OVERRIDE_ALL], + [:pushb, NO_OVERRIDE], + ) + + _register_with_aliases(:irb_pop_workspace, Command::PopWorkspace, + [:popws, NO_OVERRIDE], + [:irb_popws, OVERRIDE_ALL], + [:irb_pop_binding, OVERRIDE_ALL], + [:irb_popb, OVERRIDE_ALL], + [:popb, NO_OVERRIDE], + ) + + _register_with_aliases(:irb_load, Command::Load) + _register_with_aliases(:irb_require, Command::Require) + _register_with_aliases(:irb_source, Command::Source, + [:source, NO_OVERRIDE] + ) + + _register_with_aliases(:irb, Command::IrbCommand) + _register_with_aliases(:irb_jobs, Command::Jobs, + [:jobs, NO_OVERRIDE] + ) + _register_with_aliases(:irb_fg, Command::Foreground, + [:fg, NO_OVERRIDE] + ) + _register_with_aliases(:irb_kill, Command::Kill, + [:kill, OVERRIDE_PRIVATE_ONLY] + ) + + _register_with_aliases(:irb_debug, Command::Debug, + [:debug, NO_OVERRIDE] + ) + _register_with_aliases(:irb_edit, Command::Edit, + [:edit, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_break, Command::Break, + [:break, OVERRIDE_ALL] + ) + _register_with_aliases(:irb_catch, Command::Catch, + [:catch, OVERRIDE_PRIVATE_ONLY] + ) + _register_with_aliases(:irb_next, Command::Next, + [:next, OVERRIDE_ALL] + ) + _register_with_aliases(:irb_delete, Command::Delete, + [:delete, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_step, Command::Step, + [:step, NO_OVERRIDE] + ) + _register_with_aliases(:irb_continue, Command::Continue, + [:continue, NO_OVERRIDE] + ) + _register_with_aliases(:irb_finish, Command::Finish, + [:finish, NO_OVERRIDE] + ) + _register_with_aliases(:irb_backtrace, Command::Backtrace, + [:backtrace, NO_OVERRIDE], + [:bt, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_debug_info, Command::Info, + [:info, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_help, Command::Help, + [:help, NO_OVERRIDE], + [:show_cmds, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_show_doc, Command::ShowDoc, + [:show_doc, NO_OVERRIDE], + [:ri, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_info, Command::IrbInfo) + + _register_with_aliases(:irb_ls, Command::Ls, + [:ls, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_measure, Command::Measure, + [:measure, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_show_source, Command::ShowSource, + [:show_source, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_whereami, Command::Whereami, + [:whereami, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_history, Command::History, + [:history, NO_OVERRIDE], + [:hist, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_disable_irb, Command::DisableIrb, + [:disable_irb, NO_OVERRIDE] + ) + + register(:cd, Command::CD) + register(:copy, Command::Copy) + end + + ExtendCommand = Command + + # For backward compatibility, we need to keep this module: + # - As a container of helper methods + # - As a place to register commands with the deprecated def_extend_command method + module ExtendCommandBundle + # For backward compatibility + NO_OVERRIDE = Command::NO_OVERRIDE + OVERRIDE_PRIVATE_ONLY = Command::OVERRIDE_PRIVATE_ONLY + OVERRIDE_ALL = Command::OVERRIDE_ALL + + # Deprecated. Doesn't have any effect. + @EXTEND_COMMANDS = [] + + class << self + # Drepcated. Use Command.regiser instead. + def def_extend_command(cmd_name, cmd_class, _, *aliases) + Command._register_with_aliases(cmd_name, cmd_class, *aliases) + Command.class_variable_set(:@@command_override_policies, nil) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/easter-egg.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/easter-egg.rb new file mode 100644 index 00000000..07b6137b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/easter-egg.rb @@ -0,0 +1,152 @@ +require "reline" + +module IRB + class << self + class Vec + def initialize(x, y, z) + @x, @y, @z = x, y, z + end + + attr_reader :x, :y, :z + + def sub(other) + Vec.new(@x - other.x, @y - other.y, @z - other.z) + end + + def dot(other) + @x*other.x + @y*other.y + @z*other.z + end + + def cross(other) + ox, oy, oz = other.x, other.y, other.z + Vec.new(@y*oz-@z*oy, @z*ox-@x*oz, @x*oy-@y*ox) + end + + def normalize + r = Math.sqrt(self.dot(self)) + Vec.new(@x / r, @y / r, @z / r) + end + end + + class Canvas + def initialize((h, w)) + @data = (0..h-2).map { [0] * w } + @scale = [w / 2.0, h-2].min + @center = Complex(w / 2, h-2) + end + + def line((x1, y1), (x2, y2)) + p1 = Complex(x1, y1) / 2 * @scale + @center + p2 = Complex(x2, y2) / 2 * @scale + @center + line0(p1, p2) + end + + private def line0(p1, p2) + mid = (p1 + p2) / 2 + if (p1 - p2).abs < 1 + x, y = mid.rect + @data[y / 2][x] |= (y % 2 > 1 ? 2 : 1) + else + line0(p1, mid) + line0(p2, mid) + end + end + + def draw + @data.each {|row| row.fill(0) } + yield + @data.map {|row| row.map {|n| " ',;"[n] }.join }.join("\n") + end + end + + class RubyModel + def initialize + @faces = init_ruby_model + end + + def init_ruby_model + cap_vertices = (0..5).map {|i| Vec.new(*Complex.polar(1, i * Math::PI / 3).rect, 1) } + middle_vertices = (0..5).map {|i| Vec.new(*Complex.polar(2, (i + 0.5) * Math::PI / 3).rect, 0) } + bottom_vertex = Vec.new(0, 0, -2) + + faces = [cap_vertices] + 6.times do |j| + i = j-1 + faces << [cap_vertices[i], middle_vertices[i], cap_vertices[j]] + faces << [cap_vertices[j], middle_vertices[i], middle_vertices[j]] + faces << [middle_vertices[i], bottom_vertex, middle_vertices[j]] + end + + faces + end + + def render_frame(i) + angle = i / 10.0 + dir = Vec.new(*Complex.polar(1, angle).rect, Math.sin(angle)).normalize + dir2 = Vec.new(*Complex.polar(1, angle - Math::PI/2).rect, 0) + up = dir.cross(dir2) + nm = dir.cross(up) + @faces.each do |vertices| + v0, v1, v2, = vertices + if v1.sub(v0).cross(v2.sub(v0)).dot(dir) > 0 + points = vertices.map {|p| [nm.dot(p), up.dot(p)] } + (points + [points[0]]).each_cons(2) do |p1, p2| + yield p1, p2 + end + end + end + end + end + + private def easter_egg_logo(type) + @easter_egg_logos ||= File.read(File.join(__dir__, 'ruby_logo.aa'), encoding: 'UTF-8:UTF-8') + .split(/TYPE: ([A-Z_]+)\n/)[1..] + .each_slice(2) + .to_h + @easter_egg_logos[type.to_s.upcase] + end + + private def easter_egg(type = nil) + print "\e[?1049h" + type ||= [:logo, :dancing].sample + case type + when :logo + Pager.page do |io| + logo_type = STDOUT.external_encoding == Encoding::UTF_8 ? :unicode_large : :ascii_large + io.write easter_egg_logo(logo_type) + STDIN.raw { STDIN.getc } if io == STDOUT + end + when :dancing + STDOUT.cooked do + interrupted = false + prev_trap = trap("SIGINT") { interrupted = true } + canvas = Canvas.new(Reline.get_screen_size) + Reline::IOGate.set_winch_handler do + canvas = Canvas.new(Reline.get_screen_size) + end + ruby_model = RubyModel.new + print "\e[?25l" # hide cursor + 0.step do |i| # TODO (0..).each needs Ruby 2.6 or later + buff = canvas.draw do + ruby_model.render_frame(i) do |p1, p2| + canvas.line(p1, p2) + end + end + buff[0, 20] = "\e[0mPress Ctrl+C to stop\e[31m\e[1m" + print "\e[H" + buff + sleep 0.05 + break if interrupted + end + rescue Interrupt + ensure + print "\e[?25h" # show cursor + trap("SIGINT", prev_trap) + end + end + ensure + print "\e[0m\e[?1049l" + end + end +end + +IRB.__send__(:easter_egg, ARGV[0]&.to_sym) if $0 == __FILE__ diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ext/change-ws.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ext/change-ws.rb new file mode 100644 index 00000000..60e8afe3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ext/change-ws.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true +# +# irb/ext/cb.rb - +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +module IRB # :nodoc: + class Context + + # Inherited from +TOPLEVEL_BINDING+. + def home_workspace + if defined? @home_workspace + @home_workspace + else + @home_workspace = workspace + end + end + + # Changes the current workspace to given object or binding. + # + # If the optional argument is omitted, the workspace will be + # #home_workspace which is inherited from +TOPLEVEL_BINDING+ or the main + # object, IRB.conf[:MAIN_CONTEXT] when irb was initialized. + # + # See IRB::WorkSpace.new for more information. + def change_workspace(*_main) + if _main.empty? + replace_workspace(home_workspace) + return main + end + + workspace = WorkSpace.new(_main[0]) + replace_workspace(workspace) + workspace.load_helper_methods_to_main + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ext/eval_history.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ext/eval_history.rb new file mode 100644 index 00000000..6c21ff00 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ext/eval_history.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true +# +# history.rb - +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +module IRB # :nodoc: + + class Context + + NOPRINTING_IVARS.push "@eval_history_values" + + # See #set_last_value + alias _set_last_value set_last_value + + def set_last_value(value) + _set_last_value(value) + + if defined?(@eval_history) && @eval_history + @eval_history_values.push @line_no, @last_value + workspace.evaluate "__ = IRB.CurrentContext.instance_eval{@eval_history_values}" + end + + @last_value + end + + remove_method :eval_history= if method_defined?(:eval_history=) + # The command result history limit. This method is not available until + # #eval_history= was called with non-nil value (directly or via + # setting IRB.conf[:EVAL_HISTORY] in .irbrc). + attr_reader :eval_history + # Sets command result history limit. Default value is set from + # IRB.conf[:EVAL_HISTORY]. + # + # +no+ is an Integer or +nil+. + # + # Returns +no+ of history items if greater than 0. + # + # If +no+ is 0, the number of history items is unlimited. + # + # If +no+ is +nil+, execution result history isn't used (default). + # + # EvalHistory values are available via __ variable, see + # IRB::EvalHistory. + def eval_history=(no) + if no + if defined?(@eval_history) && @eval_history + @eval_history_values.size(no) + else + @eval_history_values = EvalHistory.new(no) + IRB.conf[:__TMP__EHV__] = @eval_history_values + workspace.evaluate("__ = IRB.conf[:__TMP__EHV__]") + IRB.conf.delete(:__TMP_EHV__) + end + else + @eval_history_values = nil + end + @eval_history = no + end + end + + # Represents history of results of previously evaluated commands. + # + # Available via __ variable, only if IRB.conf[:EVAL_HISTORY] + # or IRB::CurrentContext().eval_history is non-nil integer value + # (by default it is +nil+). + # + # Example (in `irb`): + # + # # Initialize history + # IRB::CurrentContext().eval_history = 10 + # # => 10 + # + # # Perform some commands... + # 1 + 2 + # # => 3 + # puts 'x' + # # x + # # => nil + # raise RuntimeError + # # ...error raised + # + # # Inspect history (format is " ": + # __ + # # => 1 10 + # # 2 3 + # # 3 nil + # + # __[1] + # # => 10 + # + class EvalHistory + + def initialize(size = 16) # :nodoc: + @size = size + @contents = [] + end + + def size(size) # :nodoc: + if size != 0 && size < @size + @contents = @contents[@size - size .. @size] + end + @size = size + end + + # Get one item of the content (both positive and negative indexes work). + def [](idx) + begin + if idx >= 0 + @contents.find{|no, val| no == idx}[1] + else + @contents[idx][1] + end + rescue NameError + nil + end + end + + def push(no, val) # :nodoc: + @contents.push [no, val] + @contents.shift if @size != 0 && @contents.size > @size + end + + alias real_inspect inspect + + def inspect # :nodoc: + if @contents.empty? + return real_inspect + end + + unless (last = @contents.pop)[1].equal?(self) + @contents.push last + last = nil + end + str = @contents.collect{|no, val| + if val.equal?(self) + "#{no} ...self-history..." + else + "#{no} #{val.inspect}" + end + }.join("\n") + if str == "" + str = "Empty." + end + @contents.push last if last + str + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ext/loader.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ext/loader.rb new file mode 100644 index 00000000..df5aaa8e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ext/loader.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true +# +# loader.rb - +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +module IRB # :nodoc: + # Raised in the event of an exception in a file loaded from an Irb session + class LoadAbort < Exception;end + + # Provides a few commands for loading files within an irb session. + # + # See ExtendCommandBundle for more information. + module IrbLoader + + alias ruby_load load + alias ruby_require require + + # Loads the given file similarly to Kernel#load + def irb_load(fn, priv = nil) + path = search_file_from_ruby_path(fn) + raise LoadError, "No such file to load -- #{fn}" unless path + + load_file(path, priv) + end + + def search_file_from_ruby_path(fn) # :nodoc: + if File.absolute_path?(fn) + return fn if File.exist?(fn) + return nil + end + + for path in $: + if File.exist?(f = File.join(path, fn)) + return f + end + end + return nil + end + + # Loads a given file in the current session and displays the source lines + # + # See Irb#suspend_input_method for more information. + def source_file(path) + irb = irb_context.irb + irb.suspend_name(path, File.basename(path)) do + FileInputMethod.open(path) do |io| + irb.suspend_input_method(io) do + |back_io| + irb.signal_status(:IN_LOAD) do + if back_io.kind_of?(FileInputMethod) + irb.eval_input + else + begin + irb.eval_input + rescue LoadAbort + print "load abort!!\n" + end + end + end + end + end + end + end + + # Loads the given file in the current session's context and evaluates it. + # + # See Irb#suspend_input_method for more information. + def load_file(path, priv = nil) + irb = irb_context.irb + irb.suspend_name(path, File.basename(path)) do + + if priv + ws = WorkSpace.new(Module.new) + else + ws = WorkSpace.new + end + irb.suspend_workspace(ws) do + FileInputMethod.open(path) do |io| + irb.suspend_input_method(io) do + |back_io| + irb.signal_status(:IN_LOAD) do + if back_io.kind_of?(FileInputMethod) + irb.eval_input + else + begin + irb.eval_input + rescue LoadAbort + print "load abort!!\n" + end + end + end + end + end + end + end + end + + def old # :nodoc: + back_io = @io + back_path = irb_path + back_name = @irb_name + back_scanner = @irb.scanner + begin + @io = FileInputMethod.new(path) + @irb_name = File.basename(path) + self.irb_path = path + @irb.signal_status(:IN_LOAD) do + if back_io.kind_of?(FileInputMethod) + @irb.eval_input + else + begin + @irb.eval_input + rescue LoadAbort + print "load abort!!\n" + end + end + end + ensure + @io = back_io + @irb_name = back_name + self.irb_path = back_path + @irb.scanner = back_scanner + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ext/multi-irb.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ext/multi-irb.rb new file mode 100644 index 00000000..9f234f0c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ext/multi-irb.rb @@ -0,0 +1,258 @@ +# frozen_string_literal: true +# +# irb/multi-irb.rb - multiple irb module +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +module IRB + class JobManager # :nodoc: + + # Creates a new JobManager object + def initialize + @jobs = [] + @current_job = nil + end + + # The active irb session + attr_accessor :current_job + + # The total number of irb sessions, used to set +irb_name+ of the current + # Context. + def n_jobs + @jobs.size + end + + # Returns the thread for the given +key+ object, see #search for more + # information. + def thread(key) + th, = search(key) + th + end + + # Returns the irb session for the given +key+ object, see #search for more + # information. + def irb(key) + _, irb = search(key) + irb + end + + # Returns the top level thread. + def main_thread + @jobs[0][0] + end + + # Returns the top level irb session. + def main_irb + @jobs[0][1] + end + + # Add the given +irb+ session to the jobs Array. + def insert(irb) + @jobs.push [Thread.current, irb] + end + + # Changes the current active irb session to the given +key+ in the jobs + # Array. + # + # Raises an IrbAlreadyDead exception if the given +key+ is no longer alive. + # + # If the given irb session is already active, an IrbSwitchedToCurrentThread + # exception is raised. + def switch(key) + th, irb = search(key) + fail IrbAlreadyDead unless th.alive? + fail IrbSwitchedToCurrentThread if th == Thread.current + @current_job = irb + th.run + Thread.stop + @current_job = irb(Thread.current) + end + + # Terminates the irb sessions specified by the given +keys+. + # + # Raises an IrbAlreadyDead exception if one of the given +keys+ is already + # terminated. + # + # See Thread#exit for more information. + def kill(*keys) + for key in keys + th, _ = search(key) + fail IrbAlreadyDead unless th.alive? + th.exit + end + end + + # Returns the associated job for the given +key+. + # + # If given an Integer, it will return the +key+ index for the jobs Array. + # + # When an instance of Irb is given, it will return the irb session + # associated with +key+. + # + # If given an instance of Thread, it will return the associated thread + # +key+ using Object#=== on the jobs Array. + # + # Otherwise returns the irb session with the same top-level binding as the + # given +key+. + # + # Raises a NoSuchJob exception if no job can be found with the given +key+. + def search(key) + job = case key + when Integer + @jobs[key] + when Irb + @jobs.find{|k, v| v.equal?(key)} + when Thread + @jobs.assoc(key) + else + @jobs.find{|k, v| v.context.main.equal?(key)} + end + fail NoSuchJob, key if job.nil? + job + end + + # Deletes the job at the given +key+. + def delete(key) + case key + when Integer + fail NoSuchJob, key unless @jobs[key] + @jobs[key] = nil + else + catch(:EXISTS) do + @jobs.each_index do + |i| + if @jobs[i] and (@jobs[i][0] == key || + @jobs[i][1] == key || + @jobs[i][1].context.main.equal?(key)) + @jobs[i] = nil + throw :EXISTS + end + end + fail NoSuchJob, key + end + end + until assoc = @jobs.pop; end unless @jobs.empty? + @jobs.push assoc + end + + # Outputs a list of jobs, see the irb command +irb_jobs+, or +jobs+. + def inspect + ary = [] + @jobs.each_index do + |i| + th, irb = @jobs[i] + next if th.nil? + + if th.alive? + if th.stop? + t_status = "stop" + else + t_status = "running" + end + else + t_status = "exited" + end + ary.push format("#%d->%s on %s (%s: %s)", + i, + irb.context.irb_name, + irb.context.main, + th, + t_status) + end + ary.join("\n") + end + end + + @JobManager = JobManager.new + + # The current JobManager in the session + def IRB.JobManager # :nodoc: + @JobManager + end + + # The current Context in this session + def IRB.CurrentContext # :nodoc: + IRB.JobManager.irb(Thread.current).context + end + + # Creates a new IRB session, see Irb.new. + # + # The optional +file+ argument is given to Context.new, along with the + # workspace created with the remaining arguments, see WorkSpace.new + def IRB.irb(file = nil, *main) # :nodoc: + workspace = WorkSpace.new(*main) + parent_thread = Thread.current + Thread.start do + begin + irb = Irb.new(workspace, file) + rescue + print "Subirb can't start with context(self): ", workspace.main.inspect, "\n" + print "return to main irb\n" + Thread.pass + Thread.main.wakeup + Thread.exit + end + @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC] + @JobManager.insert(irb) + @JobManager.current_job = irb + begin + system_exit = false + catch(:IRB_EXIT) do + irb.eval_input + end + rescue SystemExit + system_exit = true + raise + #fail + ensure + unless system_exit + @JobManager.delete(irb) + if @JobManager.current_job == irb + if parent_thread.alive? + @JobManager.current_job = @JobManager.irb(parent_thread) + parent_thread.run + else + @JobManager.current_job = @JobManager.main_irb + @JobManager.main_thread.run + end + end + end + end + end + Thread.stop + @JobManager.current_job = @JobManager.irb(Thread.current) + end + + @CONF[:SINGLE_IRB_MODE] = false + @JobManager.insert(@CONF[:MAIN_CONTEXT].irb) + @JobManager.current_job = @CONF[:MAIN_CONTEXT].irb + + class Irb + def signal_handle + unless @context.ignore_sigint? + print "\nabort!!\n" if @context.verbose? + exit + end + + case @signal_status + when :IN_INPUT + print "^C\n" + IRB.JobManager.thread(self).raise RubyLex::TerminateLineInput + when :IN_EVAL + IRB.irb_abort(self) + when :IN_LOAD + IRB.irb_abort(self, LoadAbort) + when :IN_IRB + # ignore + else + # ignore other cases as well + end + end + end + + trap("SIGINT") do + @JobManager.current_job.signal_handle + Thread.stop + end + +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ext/tracer.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ext/tracer.rb new file mode 100644 index 00000000..fd6daa88 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ext/tracer.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true +# +# irb/lib/tracer.rb - +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# +# Loading the gem "tracer" will cause it to extend IRB commands with: +# https://github.com/ruby/tracer/blob/v0.2.2/lib/tracer/irb.rb +begin + require "tracer" +rescue LoadError + $stderr.puts "Tracer extension of IRB is enabled but tracer gem wasn't found." + return # This is about to disable loading below +end + +module IRB + class CallTracer < ::CallTracer + IRB_DIR = File.expand_path('../..', __dir__) + + def skip?(tp) + super || tp.path.match?(IRB_DIR) || tp.path.match?('') + end + end + class WorkSpace + alias __evaluate__ evaluate + # Evaluate the context of this workspace and use the Tracer library to + # output the exact lines of code are being executed in chronological order. + # + # See https://github.com/ruby/tracer for more information. + def evaluate(statements, file = __FILE__, line = __LINE__) + if IRB.conf[:USE_TRACER] == true + CallTracer.new(colorize: Color.colorable?).start do + __evaluate__(statements, file, line) + end + else + __evaluate__(statements, file, line) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ext/use-loader.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ext/use-loader.rb new file mode 100644 index 00000000..c8a3ea1f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ext/use-loader.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true +# +# use-loader.rb - +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +require_relative "../command/load" +require_relative "loader" + +class Object + alias __original__load__IRB_use_loader__ load + alias __original__require__IRB_use_loader__ require +end + +module IRB + module ExtendCommandBundle + remove_method :irb_load if method_defined?(:irb_load) + # Loads the given file similarly to Kernel#load, see IrbLoader#irb_load + def irb_load(*opts, &b) + Command::Load.execute(irb_context, *opts, &b) + end + remove_method :irb_require if method_defined?(:irb_require) + # Loads the given file similarly to Kernel#require + def irb_require(*opts, &b) + Command::Require.execute(irb_context, *opts, &b) + end + end + + class Context + + IRB.conf[:USE_LOADER] = false + + # Returns whether +irb+'s own file reader method is used by + # +load+/+require+ or not. + # + # This mode is globally affected (irb-wide). + def use_loader + IRB.conf[:USE_LOADER] + end + + alias use_loader? use_loader + + remove_method :use_loader= if method_defined?(:use_loader=) + # Sets IRB.conf[:USE_LOADER] + # + # See #use_loader for more information. + def use_loader=(opt) + + if IRB.conf[:USE_LOADER] != opt + IRB.conf[:USE_LOADER] = opt + if opt + (class< 1 + # swap the top two workspaces + previous_workspace, current_workspace = @workspace_stack.pop(2) + @workspace_stack.push current_workspace, previous_workspace + end + else + new_workspace = WorkSpace.new(workspace.binding, _main[0]) + @workspace_stack.push new_workspace + new_workspace.load_helper_methods_to_main + end + end + + # Removes the last element from the current #workspaces stack and returns + # it, or +nil+ if the current workspace stack is empty. + # + # Also, see #push_workspace. + def pop_workspace + @workspace_stack.pop if @workspace_stack.size > 1 + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/frame.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/frame.rb new file mode 100644 index 00000000..4b697c87 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/frame.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true +# +# frame.rb - +# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) +# + +module IRB + class Frame + class FrameOverflow < StandardError + def initialize + super("frame overflow") + end + end + class FrameUnderflow < StandardError + def initialize + super("frame underflow") + end + end + + # Default number of stack frames + INIT_STACK_TIMES = 3 + # Default number of frames offset + CALL_STACK_OFFSET = 3 + + # Creates a new stack frame + def initialize + @frames = [TOPLEVEL_BINDING] * INIT_STACK_TIMES + end + + # Used by Kernel#set_trace_func to register each event in the call stack + def trace_func(event, file, line, id, binding) + case event + when 'call', 'class' + @frames.push binding + when 'return', 'end' + @frames.pop + end + end + + # Returns the +n+ number of frames on the call stack from the last frame + # initialized. + # + # Raises FrameUnderflow if there are no frames in the given stack range. + def top(n = 0) + bind = @frames[-(n + CALL_STACK_OFFSET)] + fail FrameUnderflow unless bind + bind + end + + # Returns the +n+ number of frames on the call stack from the first frame + # initialized. + # + # Raises FrameOverflow if there are no frames in the given stack range. + def bottom(n = 0) + bind = @frames[n] + fail FrameOverflow unless bind + bind + end + + # Convenience method for Frame#bottom + def Frame.bottom(n = 0) + @backtrace.bottom(n) + end + + # Convenience method for Frame#top + def Frame.top(n = 0) + @backtrace.top(n) + end + + # Returns the binding context of the caller from the last frame initialized + def Frame.sender + eval "self", @backtrace.top + end + + @backtrace = Frame.new + set_trace_func proc{|event, file, line, id, binding, klass| + @backtrace.trace_func(event, file, line, id, binding) + } + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/help.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/help.rb new file mode 100644 index 00000000..a24bc10a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/help.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true +# +# irb/help.rb - print usage module +# by Keiju ISHITSUKA(keiju@ishitsuka.com) +# + +module IRB + # Outputs the irb help message, see IRB@Command-Line+Options. + def IRB.print_usage # :nodoc: + lc = IRB.conf[:LC_MESSAGES] + path = lc.find("irb/help-message") + space_line = false + File.open(path){|f| + f.each_line do |l| + if /^\s*$/ =~ l + lc.puts l unless space_line + space_line = true + next + end + space_line = false + + l.sub!(/#.*$/, "") + next if /^\s*$/ =~ l + lc.puts l + end + } + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/helper_method.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/helper_method.rb new file mode 100644 index 00000000..f1f6fff9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/helper_method.rb @@ -0,0 +1,29 @@ +require_relative "helper_method/base" + +module IRB + module HelperMethod + @helper_methods = {} + + class << self + attr_reader :helper_methods + + def register(name, helper_class) + @helper_methods[name] = helper_class + + if defined?(HelpersContainer) + HelpersContainer.install_helper_methods + end + end + + def all_helper_methods_info + @helper_methods.map do |name, helper_class| + { display_name: name, description: helper_class.description } + end + end + end + + # Default helper_methods + require_relative "helper_method/conf" + register(:conf, HelperMethod::Conf) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/helper_method/base.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/helper_method/base.rb new file mode 100644 index 00000000..a68001ed --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/helper_method/base.rb @@ -0,0 +1,16 @@ +require "singleton" + +module IRB + module HelperMethod + class Base + include Singleton + + class << self + def description(description = nil) + @description = description if description + @description + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/helper_method/conf.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/helper_method/conf.rb new file mode 100644 index 00000000..718ed279 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/helper_method/conf.rb @@ -0,0 +1,11 @@ +module IRB + module HelperMethod + class Conf < Base + description "Returns the current IRB context." + + def execute + IRB.CurrentContext + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/history.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/history.rb new file mode 100644 index 00000000..0beff155 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/history.rb @@ -0,0 +1,116 @@ +require "pathname" + +module IRB + module History + DEFAULT_ENTRY_LIMIT = 1000 + + class << self + # Integer representation of IRB.conf[:HISTORY_FILE]. + def save_history + return 0 if IRB.conf[:SAVE_HISTORY] == false + return DEFAULT_ENTRY_LIMIT if IRB.conf[:SAVE_HISTORY] == true + IRB.conf[:SAVE_HISTORY].to_i + end + + def save_history? + !save_history.zero? + end + + def infinite? + save_history.negative? + end + + # Might be nil when HOME and XDG_CONFIG_HOME are not available. + def history_file + if (history_file = IRB.conf[:HISTORY_FILE]) + File.expand_path(history_file) + else + IRB.rc_file("_history") + end + end + end + end + + module HistorySavingAbility # :nodoc: + def support_history_saving? + true + end + + def reset_history_counter + @loaded_history_lines = self.class::HISTORY.size + end + + def load_history + history_file = History.history_file + return unless File.exist?(history_file.to_s) + + history = self.class::HISTORY + + File.open(history_file, "r:#{IRB.conf[:LC_MESSAGES].encoding}") do |f| + f.each { |l| + l = l.chomp + if self.class == RelineInputMethod and history.last&.end_with?("\\") + history.last.delete_suffix!("\\") + history.last << "\n" << l + else + history << l + end + } + end + @loaded_history_lines = history.size + @loaded_history_mtime = File.mtime(history_file) + end + + def save_history + return unless History.save_history? + return unless (history_file = History.history_file) + unless ensure_history_file_writable(history_file) + warn <<~WARN + Can't write history to #{History.history_file.inspect} due to insufficient permissions. + Please verify the value of `IRB.conf[:HISTORY_FILE]`. Ensure the folder exists and that both the folder and file (if it exists) are writable. + WARN + return + end + + history = self.class::HISTORY.to_a + + if File.exist?(history_file) && + File.mtime(history_file) != @loaded_history_mtime + history = history[@loaded_history_lines..-1] if @loaded_history_lines + append_history = true + end + + File.open(history_file, (append_history ? "a" : "w"), 0o600, encoding: IRB.conf[:LC_MESSAGES]&.encoding) do |f| + hist = history.map { |l| l.scrub.split("\n").join("\\\n") } + + unless append_history || History.infinite? + # Check size before slicing because array.last(huge_number) raises RangeError. + hist = hist.last(History.save_history) if hist.size > History.save_history + end + + f.puts(hist) + end + end + + private + + # Returns boolean whether writing to +history_file+ will be possible. + # Permissions of already existing +history_file+ are changed to + # owner-only-readable if necessary [BUG #7694]. + def ensure_history_file_writable(history_file) + history_file = Pathname.new(history_file) + + return false unless history_file.dirname.writable? + return true unless history_file.exist? + + begin + if history_file.stat.mode & 0o66 != 0 + history_file.chmod 0o600 + end + true + rescue Errno::EPERM # no permissions + false + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/init.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/init.rb new file mode 100644 index 00000000..720c4fec --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/init.rb @@ -0,0 +1,540 @@ +# frozen_string_literal: true +# +# irb/init.rb - irb initialize module +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +module IRB # :nodoc: + @CONF = {} + @INITIALIZED = false + # Displays current configuration. + # + # Modifying the configuration is achieved by sending a message to IRB.conf. + # + # See IRB@Configuration for more information. + def IRB.conf + @CONF + end + + def @CONF.inspect + array = [] + for k, v in sort{|a1, a2| a1[0].id2name <=> a2[0].id2name} + case k + when :MAIN_CONTEXT, :__TMP__EHV__ + array.push format("CONF[:%s]=...myself...", k.id2name) + when :PROMPT + s = v.collect{ + |kk, vv| + ss = vv.collect{|kkk, vvv| ":#{kkk.id2name}=>#{vvv.inspect}"} + format(":%s=>{%s}", kk.id2name, ss.join(", ")) + } + array.push format("CONF[:%s]={%s}", k.id2name, s.join(", ")) + else + array.push format("CONF[:%s]=%s", k.id2name, v.inspect) + end + end + array.join("\n") + end + + # Returns the current version of IRB, including release version and last + # updated date. + def IRB.version + format("irb %s (%s)", @RELEASE_VERSION, @LAST_UPDATE_DATE) + end + + def IRB.initialized? + !!@INITIALIZED + end + + # initialize config + def IRB.setup(ap_path, argv: ::ARGV) + IRB.init_config(ap_path) + IRB.init_error + IRB.parse_opts(argv: argv) + IRB.run_config + IRB.validate_config + IRB.load_modules + + unless @CONF[:PROMPT][@CONF[:PROMPT_MODE]] + fail UndefinedPromptMode, @CONF[:PROMPT_MODE] + end + @INITIALIZED = true + end + + # @CONF default setting + def IRB.init_config(ap_path) + # default configurations + unless ap_path and @CONF[:AP_NAME] + ap_path = File.join(File.dirname(File.dirname(__FILE__)), "irb.rb") + end + @CONF[:VERSION] = version + @CONF[:AP_NAME] = File::basename(ap_path, ".rb") + + @CONF[:IRB_NAME] = "irb" + @CONF[:IRB_LIB_PATH] = File.dirname(__FILE__) + + @CONF[:RC] = true + @CONF[:LOAD_MODULES] = [] + @CONF[:IRB_RC] = nil + + @CONF[:USE_SINGLELINE] = false unless defined?(ReadlineInputMethod) + @CONF[:USE_COLORIZE] = (nc = ENV['NO_COLOR']).nil? || nc.empty? + @CONF[:USE_AUTOCOMPLETE] = ENV.fetch("IRB_USE_AUTOCOMPLETE", "true") != "false" + @CONF[:COMPLETOR] = ENV["IRB_COMPLETOR"]&.to_sym + @CONF[:INSPECT_MODE] = true + @CONF[:USE_TRACER] = false + @CONF[:USE_LOADER] = false + @CONF[:IGNORE_SIGINT] = true + @CONF[:IGNORE_EOF] = false + @CONF[:USE_PAGER] = true + @CONF[:EXTRA_DOC_DIRS] = [] + @CONF[:ECHO] = nil + @CONF[:ECHO_ON_ASSIGNMENT] = nil + @CONF[:VERBOSE] = nil + + @CONF[:EVAL_HISTORY] = nil + @CONF[:SAVE_HISTORY] = History::DEFAULT_ENTRY_LIMIT + + @CONF[:BACK_TRACE_LIMIT] = 16 + + @CONF[:PROMPT] = { + :NULL => { + :PROMPT_I => nil, + :PROMPT_S => nil, + :PROMPT_C => nil, + :RETURN => "%s\n" + }, + :DEFAULT => { + :PROMPT_I => "%N(%m):%03n> ", + :PROMPT_S => "%N(%m):%03n%l ", + :PROMPT_C => "%N(%m):%03n* ", + :RETURN => "=> %s\n" + }, + :CLASSIC => { + :PROMPT_I => "%N(%m):%03n:%i> ", + :PROMPT_S => "%N(%m):%03n:%i%l ", + :PROMPT_C => "%N(%m):%03n:%i* ", + :RETURN => "%s\n" + }, + :SIMPLE => { + :PROMPT_I => ">> ", + :PROMPT_S => "%l> ", + :PROMPT_C => "?> ", + :RETURN => "=> %s\n" + }, + :INF_RUBY => { + :PROMPT_I => "%N(%m):%03n> ", + :PROMPT_S => nil, + :PROMPT_C => nil, + :RETURN => "%s\n", + :AUTO_INDENT => true + }, + :XMP => { + :PROMPT_I => nil, + :PROMPT_S => nil, + :PROMPT_C => nil, + :RETURN => " ==>%s\n" + } + } + + @CONF[:PROMPT_MODE] = (STDIN.tty? ? :DEFAULT : :NULL) + @CONF[:AUTO_INDENT] = true + + @CONF[:CONTEXT_MODE] = 4 # use a copy of TOPLEVEL_BINDING + @CONF[:SINGLE_IRB] = false + + @CONF[:MEASURE] = false + @CONF[:MEASURE_PROC] = {} + @CONF[:MEASURE_PROC][:TIME] = proc { |context, code, line_no, &block| + time = Time.now + result = block.() + now = Time.now + puts 'processing time: %fs' % (now - time) if IRB.conf[:MEASURE] + result + } + # arg can be either a symbol for the mode (:cpu, :wall, ..) or a hash for + # a more complete configuration. + # See https://github.com/tmm1/stackprof#all-options. + @CONF[:MEASURE_PROC][:STACKPROF] = proc { |context, code, line_no, arg, &block| + return block.() unless IRB.conf[:MEASURE] + success = false + begin + require 'stackprof' + success = true + rescue LoadError + puts 'Please run "gem install stackprof" before measuring by StackProf.' + end + if success + result = nil + arg = { mode: arg || :cpu } unless arg.is_a?(Hash) + stackprof_result = StackProf.run(**arg) do + result = block.() + end + case stackprof_result + when File + puts "StackProf report saved to #{stackprof_result.path}" + when Hash + StackProf::Report.new(stackprof_result).print_text + else + puts "Stackprof ran with #{arg.inspect}" + end + result + else + block.() + end + } + @CONF[:MEASURE_CALLBACKS] = [] + + @CONF[:LC_MESSAGES] = Locale.new + + @CONF[:AT_EXIT] = [] + + @CONF[:COMMAND_ALIASES] = { + # Symbol aliases + :'$' => :show_source, + :'@' => :whereami, + } + + @CONF[:COPY_COMMAND] = ENV.fetch("IRB_COPY_COMMAND", nil) + end + + def IRB.set_measure_callback(type = nil, arg = nil, &block) + added = nil + if type + type_sym = type.upcase.to_sym + if IRB.conf[:MEASURE_PROC][type_sym] + added = [type_sym, IRB.conf[:MEASURE_PROC][type_sym], arg] + end + elsif IRB.conf[:MEASURE_PROC][:CUSTOM] + added = [:CUSTOM, IRB.conf[:MEASURE_PROC][:CUSTOM], arg] + elsif block_given? + added = [:BLOCK, block, arg] + found = IRB.conf[:MEASURE_CALLBACKS].find{ |m| m[0] == added[0] && m[2] == added[2] } + if found + found[1] = block + return added + else + IRB.conf[:MEASURE_CALLBACKS] << added + return added + end + else + added = [:TIME, IRB.conf[:MEASURE_PROC][:TIME], arg] + end + if added + IRB.conf[:MEASURE] = true + found = IRB.conf[:MEASURE_CALLBACKS].find{ |m| m[0] == added[0] && m[2] == added[2] } + if found + # already added + nil + else + IRB.conf[:MEASURE_CALLBACKS] << added if added + added + end + else + nil + end + end + + def IRB.unset_measure_callback(type = nil) + if type.nil? + IRB.conf[:MEASURE_CALLBACKS].clear + else + type_sym = type.upcase.to_sym + IRB.conf[:MEASURE_CALLBACKS].reject!{ |t, | t == type_sym } + end + IRB.conf[:MEASURE] = nil if IRB.conf[:MEASURE_CALLBACKS].empty? + end + + def IRB.init_error + @CONF[:LC_MESSAGES].load("irb/error.rb") + end + + # option analyzing + def IRB.parse_opts(argv: ::ARGV) + load_path = [] + while opt = argv.shift + case opt + when "-f" + @CONF[:RC] = false + when "-d" + $DEBUG = true + $VERBOSE = true + when "-w" + Warning[:deprecated] = $VERBOSE = true + when /^-W(.+)?/ + opt = $1 || argv.shift + case opt + when "0" + $VERBOSE = nil + when "1" + $VERBOSE = false + else + Warning[:deprecated] = $VERBOSE = true + end + when /^-r(.+)?/ + opt = $1 || argv.shift + @CONF[:LOAD_MODULES].push opt if opt + when /^-I(.+)?/ + opt = $1 || argv.shift + load_path.concat(opt.split(File::PATH_SEPARATOR)) if opt + when '-U' + set_encoding("UTF-8", "UTF-8") + when /^-E(.+)?/, /^--encoding(?:=(.+))?/ + opt = $1 || argv.shift + set_encoding(*opt.split(':', 2)) + when "--inspect" + if /^-/ !~ argv.first + @CONF[:INSPECT_MODE] = argv.shift + else + @CONF[:INSPECT_MODE] = true + end + when "--noinspect" + @CONF[:INSPECT_MODE] = false + when "--no-pager" + @CONF[:USE_PAGER] = false + when "--singleline", "--readline", "--legacy" + @CONF[:USE_SINGLELINE] = true + when "--nosingleline", "--noreadline" + @CONF[:USE_SINGLELINE] = false + when "--multiline", "--reidline" + if opt == "--reidline" + warn <<~MSG.strip + --reidline is deprecated, please use --multiline instead. + MSG + end + + @CONF[:USE_MULTILINE] = true + when "--nomultiline", "--noreidline" + if opt == "--noreidline" + warn <<~MSG.strip + --noreidline is deprecated, please use --nomultiline instead. + MSG + end + + @CONF[:USE_MULTILINE] = false + when /^--extra-doc-dir(?:=(.+))?/ + opt = $1 || argv.shift + @CONF[:EXTRA_DOC_DIRS] << opt + when "--echo" + @CONF[:ECHO] = true + when "--noecho" + @CONF[:ECHO] = false + when "--echo-on-assignment" + @CONF[:ECHO_ON_ASSIGNMENT] = true + when "--noecho-on-assignment" + @CONF[:ECHO_ON_ASSIGNMENT] = false + when "--truncate-echo-on-assignment" + @CONF[:ECHO_ON_ASSIGNMENT] = :truncate + when "--verbose" + @CONF[:VERBOSE] = true + when "--noverbose" + @CONF[:VERBOSE] = false + when "--colorize" + @CONF[:USE_COLORIZE] = true + when "--nocolorize" + @CONF[:USE_COLORIZE] = false + when "--autocomplete" + @CONF[:USE_AUTOCOMPLETE] = true + when "--noautocomplete" + @CONF[:USE_AUTOCOMPLETE] = false + when "--regexp-completor" + @CONF[:COMPLETOR] = :regexp + when "--type-completor" + @CONF[:COMPLETOR] = :type + when /^--prompt-mode(?:=(.+))?/, /^--prompt(?:=(.+))?/ + opt = $1 || argv.shift + prompt_mode = opt.upcase.tr("-", "_").intern + @CONF[:PROMPT_MODE] = prompt_mode + when "--noprompt" + @CONF[:PROMPT_MODE] = :NULL + when "--script" + noscript = false + when "--noscript" + noscript = true + when "--inf-ruby-mode" + @CONF[:PROMPT_MODE] = :INF_RUBY + when "--sample-book-mode", "--simple-prompt" + @CONF[:PROMPT_MODE] = :SIMPLE + when "--tracer" + @CONF[:USE_TRACER] = true + when /^--back-trace-limit(?:=(.+))?/ + @CONF[:BACK_TRACE_LIMIT] = ($1 || argv.shift).to_i + when /^--context-mode(?:=(.+))?/ + @CONF[:CONTEXT_MODE] = ($1 || argv.shift).to_i + when "--single-irb" + @CONF[:SINGLE_IRB] = true + when "-v", "--version" + print IRB.version, "\n" + exit 0 + when "-h", "--help" + require_relative "help" + IRB.print_usage + exit 0 + when "--" + if !noscript && (opt = argv.shift) + @CONF[:SCRIPT] = opt + $0 = opt + end + break + when /^-./ + fail UnrecognizedSwitch, opt + else + if noscript + argv.unshift(opt) + else + @CONF[:SCRIPT] = opt + $0 = opt + end + break + end + end + + load_path.collect! do |path| + /\A\.\// =~ path ? path : File.expand_path(path) + end + $LOAD_PATH.unshift(*load_path) + end + + # Run the config file + def IRB.run_config + if @CONF[:RC] + irbrc_files.each do |rc| + load rc + rescue StandardError, ScriptError => e + warn "Error loading RC file '#{rc}':\n#{e.full_message(highlight: false)}" + end + end + end + + IRBRC_EXT = "rc" + + def IRB.rc_file(ext) + prepare_irbrc_name_generators + + # When irbrc exist in default location + if (rcgen = @existing_rc_name_generators.first) + return rcgen.call(ext) + end + + # When irbrc does not exist in default location + rc_file_generators do |rcgen| + return rcgen.call(ext) + end + + # When HOME and XDG_CONFIG_HOME are not available + nil + end + + def IRB.irbrc_files + prepare_irbrc_name_generators + @irbrc_files + end + + def IRB.validate_config + conf[:IRB_NAME] = conf[:IRB_NAME].to_s + + irb_rc = conf[:IRB_RC] + unless irb_rc.nil? || irb_rc.respond_to?(:call) + raise_validation_error "IRB.conf[:IRB_RC] should be a callable object. Got #{irb_rc.inspect}." + end + + back_trace_limit = conf[:BACK_TRACE_LIMIT] + unless back_trace_limit.is_a?(Integer) + raise_validation_error "IRB.conf[:BACK_TRACE_LIMIT] should be an integer. Got #{back_trace_limit.inspect}." + end + + prompt = conf[:PROMPT] + unless prompt.is_a?(Hash) + msg = "IRB.conf[:PROMPT] should be a Hash. Got #{prompt.inspect}." + + if prompt.is_a?(Symbol) + msg += " Did you mean to set `IRB.conf[:PROMPT_MODE]`?" + end + + raise_validation_error msg + end + + eval_history = conf[:EVAL_HISTORY] + unless eval_history.nil? || eval_history.is_a?(Integer) + raise_validation_error "IRB.conf[:EVAL_HISTORY] should be an integer. Got #{eval_history.inspect}." + end + end + + def IRB.raise_validation_error(msg) + raise TypeError, msg, @irbrc_files + end + + # loading modules + def IRB.load_modules + for m in @CONF[:LOAD_MODULES] + begin + require m + rescue LoadError => err + warn "#{err.class}: #{err}", uplevel: 0 + end + end + end + + class << IRB + private + + def prepare_irbrc_name_generators + return if @existing_rc_name_generators + + @existing_rc_name_generators = [] + @irbrc_files = [] + rc_file_generators do |rcgen| + irbrc = rcgen.call(IRBRC_EXT) + if File.exist?(irbrc) + @irbrc_files << irbrc + @existing_rc_name_generators << rcgen + end + end + generate_current_dir_irbrc_files.each do |irbrc| + @irbrc_files << irbrc if File.exist?(irbrc) + end + @irbrc_files.uniq! + end + + # enumerate possible rc-file base name generators + def rc_file_generators + if irbrc = ENV["IRBRC"] + yield proc{|rc| rc == "rc" ? irbrc : irbrc+rc} + end + if xdg_config_home = ENV["XDG_CONFIG_HOME"] + irb_home = File.join(xdg_config_home, "irb") + if File.directory?(irb_home) + yield proc{|rc| irb_home + "/irb#{rc}"} + end + end + if home = ENV["HOME"] + yield proc{|rc| home+"/.irb#{rc}"} + if xdg_config_home.nil? || xdg_config_home.empty? + yield proc{|rc| home+"/.config/irb/irb#{rc}"} + end + end + end + + # possible irbrc files in current directory + def generate_current_dir_irbrc_files + current_dir = Dir.pwd + %w[.irbrc irbrc _irbrc $irbrc].map { |file| "#{current_dir}/#{file}" } + end + + def set_encoding(extern, intern = nil, override: true) + verbose, $VERBOSE = $VERBOSE, nil + Encoding.default_external = extern unless extern.nil? || extern.empty? + Encoding.default_internal = intern unless intern.nil? || intern.empty? + [$stdin, $stdout, $stderr].each do |io| + io.set_encoding(extern, intern) + end + if override + @CONF[:LC_MESSAGES].instance_variable_set(:@override_encoding, extern) + else + @CONF[:LC_MESSAGES].instance_variable_set(:@encoding, extern) + end + ensure + $VERBOSE = verbose + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/input-method.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/input-method.rb new file mode 100644 index 00000000..b9bbdeb1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/input-method.rb @@ -0,0 +1,521 @@ +# frozen_string_literal: true +# +# irb/input-method.rb - input methods used irb +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +require_relative 'completion' +require_relative "history" +require 'io/console' +require 'reline' + +module IRB + class InputMethod + BASIC_WORD_BREAK_CHARACTERS = " \t\n`><=;|&{(" + + # The irb prompt associated with this input method + attr_accessor :prompt + + # Reads the next line from this input method. + # + # See IO#gets for more information. + def gets + fail NotImplementedError + end + public :gets + + def winsize + if instance_variable_defined?(:@stdout) && @stdout.tty? + winsize = @stdout.winsize + # If width or height is 0, something is wrong. + return winsize unless winsize.include? 0 + end + [24, 80] + end + + # Whether this input method is still readable when there is no more data to + # read. + # + # See IO#eof for more information. + def readable_after_eof? + false + end + + def support_history_saving? + false + end + + def prompting? + false + end + + # For debug message + def inspect + 'Abstract InputMethod' + end + end + + class StdioInputMethod < InputMethod + # Creates a new input method object + def initialize + @line_no = 0 + @line = [] + @stdin = IO.open(STDIN.to_i, :external_encoding => IRB.conf[:LC_MESSAGES].encoding, :internal_encoding => "-") + @stdout = IO.open(STDOUT.to_i, 'w', :external_encoding => IRB.conf[:LC_MESSAGES].encoding, :internal_encoding => "-") + end + + # Reads the next line from this input method. + # + # See IO#gets for more information. + def gets + # Workaround for debug compatibility test https://github.com/ruby/debug/pull/1100 + puts if ENV['RUBY_DEBUG_TEST_UI'] + + print @prompt + line = @stdin.gets + @line[@line_no += 1] = line + end + + # Whether the end of this input method has been reached, returns +true+ if + # there is no more data to read. + # + # See IO#eof? for more information. + def eof? + if @stdin.wait_readable(0.00001) + c = @stdin.getc + result = c.nil? ? true : false + @stdin.ungetc(c) unless c.nil? + result + else # buffer is empty + false + end + end + + # Whether this input method is still readable when there is no more data to + # read. + # + # See IO#eof for more information. + def readable_after_eof? + true + end + + def prompting? + STDIN.tty? + end + + # Returns the current line number for #io. + # + # #line counts the number of times #gets is called. + # + # See IO#lineno for more information. + def line(line_no) + @line[line_no] + end + + # The external encoding for standard input. + def encoding + @stdin.external_encoding + end + + # For debug message + def inspect + 'StdioInputMethod' + end + end + + # Use a File for IO with irb, see InputMethod + class FileInputMethod < InputMethod + class << self + def open(file, &block) + begin + io = new(file) + block.call(io) + ensure + io&.close + end + end + end + + # Creates a new input method object + def initialize(file) + @io = file.is_a?(IO) ? file : File.open(file) + @external_encoding = @io.external_encoding + end + + # Whether the end of this input method has been reached, returns +true+ if + # there is no more data to read. + # + # See IO#eof? for more information. + def eof? + @io.closed? || @io.eof? + end + + # Reads the next line from this input method. + # + # See IO#gets for more information. + def gets + print @prompt + @io.gets + end + + # The external encoding for standard input. + def encoding + @external_encoding + end + + # For debug message + def inspect + 'FileInputMethod' + end + + def close + @io.close + end + end + + class ReadlineInputMethod < StdioInputMethod + class << self + def initialize_readline + return if defined?(self::Readline) + + begin + require 'readline' + const_set(:Readline, ::Readline) + rescue LoadError + const_set(:Readline, ::Reline) + end + const_set(:HISTORY, self::Readline::HISTORY) + end + end + + include HistorySavingAbility + + # Creates a new input method object using Readline + def initialize + self.class.initialize_readline + if Readline.respond_to?(:encoding_system_needs) + IRB.__send__(:set_encoding, Readline.encoding_system_needs.name, override: false) + end + + super + + @eof = false + @completor = RegexpCompletor.new + + if Readline.respond_to?("basic_word_break_characters=") + Readline.basic_word_break_characters = BASIC_WORD_BREAK_CHARACTERS + end + Readline.completion_append_character = nil + Readline.completion_proc = ->(target) { + bind = IRB.conf[:MAIN_CONTEXT].workspace.binding + @completor.completion_candidates('', target, '', bind: bind) + } + end + + def completion_info + 'RegexpCompletor' + end + + # Reads the next line from this input method. + # + # See IO#gets for more information. + def gets + Readline.input = @stdin + Readline.output = @stdout + if l = Readline.readline(@prompt, false) + Readline::HISTORY.push(l) if !l.empty? + @line[@line_no += 1] = l + "\n" + else + @eof = true + l + end + end + + # Whether the end of this input method has been reached, returns +true+ + # if there is no more data to read. + # + # See IO#eof? for more information. + def eof? + @eof + end + + def prompting? + true + end + + # For debug message + def inspect + readline_impl = Readline == ::Reline ? 'Reline' : 'ext/readline' + str = "ReadlineInputMethod with #{readline_impl} #{Readline::VERSION}" + inputrc_path = File.expand_path(ENV['INPUTRC'] || '~/.inputrc') + str += " and #{inputrc_path}" if File.exist?(inputrc_path) + str + end + end + + class RelineInputMethod < StdioInputMethod + HISTORY = Reline::HISTORY + include HistorySavingAbility + # Creates a new input method object using Reline + def initialize(completor) + IRB.__send__(:set_encoding, Reline.encoding_system_needs.name, override: false) + + super() + + @eof = false + @completor = completor + + Reline.basic_word_break_characters = BASIC_WORD_BREAK_CHARACTERS + Reline.completion_append_character = nil + Reline.completer_quote_characters = '' + Reline.completion_proc = ->(target, preposing, postposing) { + bind = IRB.conf[:MAIN_CONTEXT].workspace.binding + @completion_params = [preposing, target, postposing, bind] + @completor.completion_candidates(preposing, target, postposing, bind: bind) + } + Reline.output_modifier_proc = proc do |input, complete:| + IRB.CurrentContext.colorize_input(input, complete: complete) + end + Reline.dig_perfect_match_proc = ->(matched) { display_document(matched) } + Reline.autocompletion = IRB.conf[:USE_AUTOCOMPLETE] + + if IRB.conf[:USE_AUTOCOMPLETE] + begin + require 'rdoc' + Reline.add_dialog_proc(:show_doc, show_doc_dialog_proc, Reline::DEFAULT_DIALOG_CONTEXT) + rescue LoadError + end + end + end + + def completion_info + autocomplete_message = Reline.autocompletion ? 'Autocomplete' : 'Tab Complete' + "#{autocomplete_message}, #{@completor.inspect}" + end + + def check_termination(&block) + @check_termination_proc = block + end + + def dynamic_prompt(&block) + @prompt_proc = block + end + + def auto_indent(&block) + @auto_indent_proc = block + end + + def retrieve_doc_namespace(matched) + preposing, _target, postposing, bind = @completion_params + @completor.doc_namespace(preposing, matched, postposing, bind: bind) + end + + def rdoc_ri_driver + return @rdoc_ri_driver if defined?(@rdoc_ri_driver) + + begin + require 'rdoc' + rescue LoadError + @rdoc_ri_driver = nil + else + options = {} + options[:extra_doc_dirs] = IRB.conf[:EXTRA_DOC_DIRS] unless IRB.conf[:EXTRA_DOC_DIRS].empty? + @rdoc_ri_driver = RDoc::RI::Driver.new(options) + end + end + + def show_doc_dialog_proc + input_method = self # self is changed in the lambda below. + ->() { + dialog.trap_key = nil + alt_d = [ + [27, 100], # Normal Alt+d when convert-meta isn't used. + # When option/alt is not configured as a meta key in terminal emulator, + # option/alt + d will send a unicode character depend on OS keyboard setting. + [195, 164], # "ä" in somewhere (FIXME: environment information is unknown). + [226, 136, 130] # "∂" Alt+d on Mac keyboard. + ] + + if just_cursor_moving and completion_journey_data.nil? + return nil + end + cursor_pos_to_render, result, pointer, autocomplete_dialog = context.pop(4) + return nil if result.nil? or pointer.nil? or pointer < 0 + + name = input_method.retrieve_doc_namespace(result[pointer]) + # Use first one because document dialog does not support multiple namespaces. + name = name.first if name.is_a?(Array) + + show_easter_egg = name&.match?(/\ARubyVM/) && !ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER'] + + driver = input_method.rdoc_ri_driver + + if key.match?(dialog.name) + if show_easter_egg + IRB.__send__(:easter_egg) + else + # RDoc::RI::Driver#display_names uses pager command internally. + # Some pager command like `more` doesn't use alternate screen + # so we need to turn on and off alternate screen manually. + begin + print "\e[?1049h" + driver.display_names([name]) + rescue RDoc::RI::Driver::NotFoundError + ensure + print "\e[?1049l" + end + end + end + + begin + name = driver.expand_name(name) + rescue RDoc::RI::Driver::NotFoundError + return nil + rescue + return nil # unknown error + end + doc = nil + used_for_class = false + if not name =~ /#|\./ + found, klasses, includes, extends = driver.classes_and_includes_and_extends_for(name) + if not found.empty? + doc = driver.class_document(name, found, klasses, includes, extends) + used_for_class = true + end + end + unless used_for_class + doc = RDoc::Markup::Document.new + begin + driver.add_method(doc, name) + rescue RDoc::RI::Driver::NotFoundError + doc = nil + rescue + return nil # unknown error + end + end + return nil if doc.nil? + width = 40 + + right_x = cursor_pos_to_render.x + autocomplete_dialog.width + if right_x + width > screen_width + right_width = screen_width - (right_x + 1) + left_x = autocomplete_dialog.column - width + left_x = 0 if left_x < 0 + left_width = width > autocomplete_dialog.column ? autocomplete_dialog.column : width + if right_width.positive? and left_width.positive? + if right_width >= left_width + width = right_width + x = right_x + else + width = left_width + x = left_x + end + elsif right_width.positive? and left_width <= 0 + width = right_width + x = right_x + elsif right_width <= 0 and left_width.positive? + width = left_width + x = left_x + else # Both are negative width. + return nil + end + else + x = right_x + end + formatter = RDoc::Markup::ToAnsi.new + formatter.width = width + dialog.trap_key = alt_d + mod_key = RUBY_PLATFORM.match?(/darwin/) ? "Option" : "Alt" + if show_easter_egg + type = STDOUT.external_encoding == Encoding::UTF_8 ? :unicode : :ascii + contents = IRB.send(:easter_egg_logo, type).split("\n") + message = "Press #{mod_key}+d to see more" + contents[0][0, message.size] = message + else + message = "Press #{mod_key}+d to read the full document" + contents = [message] + doc.accept(formatter).split("\n") + end + contents = contents.take(preferred_dialog_height) + + y = cursor_pos_to_render.y + Reline::DialogRenderInfo.new(pos: Reline::CursorPos.new(x, y), contents: contents, width: width, bg_color: '49') + } + end + + def display_document(matched) + driver = rdoc_ri_driver + return unless driver + + if matched =~ /\A(?:::)?RubyVM/ and not ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER'] + IRB.__send__(:easter_egg) + return + end + + namespace = retrieve_doc_namespace(matched) + return unless namespace + + if namespace.is_a?(Array) + out = RDoc::Markup::Document.new + namespace.each do |m| + begin + driver.add_method(out, m) + rescue RDoc::RI::Driver::NotFoundError + end + end + driver.display(out) + else + begin + driver.display_names([namespace]) + rescue RDoc::RI::Driver::NotFoundError + end + end + end + + # Reads the next line from this input method. + # + # See IO#gets for more information. + def gets + Reline.input = @stdin + Reline.output = @stdout + Reline.prompt_proc = @prompt_proc + Reline.auto_indent_proc = @auto_indent_proc if @auto_indent_proc + if l = Reline.readmultiline(@prompt, false, &@check_termination_proc) + Reline::HISTORY.push(l) if !l.empty? + @line[@line_no += 1] = l + "\n" + else + @eof = true + l + end + end + + # Whether the end of this input method has been reached, returns +true+ + # if there is no more data to read. + # + # See IO#eof? for more information. + def eof? + @eof + end + + def prompting? + true + end + + # For debug message + def inspect + config = Reline::Config.new + str = "RelineInputMethod with Reline #{Reline::VERSION}" + inputrc_path = File.expand_path(config.inputrc_path) + str += " and #{inputrc_path}" if File.exist?(inputrc_path) + str + end + end + + class ReidlineInputMethod < RelineInputMethod + def initialize + warn <<~MSG.strip + IRB::ReidlineInputMethod is deprecated, please use IRB::RelineInputMethod instead. + MSG + super + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/inspector.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/inspector.rb new file mode 100644 index 00000000..75a257b4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/inspector.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true +# +# irb/inspector.rb - inspect methods +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +module IRB # :nodoc: + + # Convenience method to create a new Inspector, using the given +inspect+ + # proc, and optional +init+ proc and passes them to Inspector.new + # + # irb(main):001:0> ins = IRB::Inspector(proc{ |v| "omg! #{v}" }) + # irb(main):001:0> IRB.CurrentContext.inspect_mode = ins # => omg! # + # irb(main):001:0> "what?" #=> omg! what? + # + def IRB::Inspector(inspect, init = nil) + Inspector.new(inspect, init) + end + + # An irb inspector + # + # In order to create your own custom inspector there are two things you + # should be aware of: + # + # Inspector uses #inspect_value, or +inspect_proc+, for output of return values. + # + # This also allows for an optional #init+, or +init_proc+, which is called + # when the inspector is activated. + # + # Knowing this, you can create a rudimentary inspector as follows: + # + # irb(main):001:0> ins = IRB::Inspector.new(proc{ |v| "omg! #{v}" }) + # irb(main):001:0> IRB.CurrentContext.inspect_mode = ins # => omg! # + # irb(main):001:0> "what?" #=> omg! what? + # + class Inspector + KERNEL_INSPECT = Object.instance_method(:inspect) + # Default inspectors available to irb, this includes: + # + # +:pp+:: Using Kernel#pretty_inspect + # +:yaml+:: Using YAML.dump + # +:marshal+:: Using Marshal.dump + INSPECTORS = {} + + class << self + # Determines the inspector to use where +inspector+ is one of the keys passed + # during inspector definition. + def keys_with_inspector(inspector) + INSPECTORS.select{|k, v| v == inspector}.collect{|k, v| k} + end + + # Example + # + # Inspector.def_inspector(key, init_p=nil){|v| v.inspect} + # Inspector.def_inspector([key1,..], init_p=nil){|v| v.inspect} + # Inspector.def_inspector(key, inspector) + # Inspector.def_inspector([key1,...], inspector) + def def_inspector(key, arg=nil, &block) + if block_given? + inspector = IRB::Inspector(block, arg) + else + inspector = arg + end + + case key + when Array + for k in key + def_inspector(k, inspector) + end + when Symbol + INSPECTORS[key] = inspector + INSPECTORS[key.to_s] = inspector + when String + INSPECTORS[key] = inspector + INSPECTORS[key.intern] = inspector + else + INSPECTORS[key] = inspector + end + end + end + + # Creates a new inspector object, using the given +inspect_proc+ when + # output return values in irb. + def initialize(inspect_proc, init_proc = nil) + @init = init_proc + @inspect = inspect_proc + end + + # Proc to call when the inspector is activated, good for requiring + # dependent libraries. + def init + @init.call if @init + end + + def support_stream_output? + second_parameter_type = @inspect.parameters[1]&.first + second_parameter_type == :req || second_parameter_type == :opt + end + + # Proc to call when the input is evaluated and output in irb. + def inspect_value(v, output, colorize: true) + support_stream_output? ? @inspect.call(v, output, colorize: colorize) : output << @inspect.call(v, colorize: colorize) + rescue => e + puts "An error occurred when inspecting the object: #{e.inspect}" + + begin + puts "Result of Kernel#inspect: #{KERNEL_INSPECT.bind_call(v)}" + '' + rescue => e + puts "An error occurred when running Kernel#inspect: #{e.inspect}" + puts e.backtrace.join("\n") + '' + end + end + end + + Inspector.def_inspector([false, :to_s, :raw]){|v| v.to_s} + Inspector.def_inspector([:p, :inspect]){|v, colorize: true| + Color.colorize_code(v.inspect, colorable: colorize && Color.colorable? && Color.inspect_colorable?(v)) + } + Inspector.def_inspector([true, :pp, :pretty_inspect], proc{require_relative "color_printer"}){|v, output, colorize: true| + IRB::ColorPrinter.pp(v, output, colorize: colorize) + } + Inspector.def_inspector([:yaml, :YAML], proc{require "yaml"}){|v| + begin + YAML.dump(v) + rescue + puts "(can't dump yaml. use inspect)" + v.inspect + end + } + + Inspector.def_inspector([:marshal, :Marshal, :MARSHAL, Marshal]){|v| + Marshal.dump(v) + } +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/lc/error.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/lc/error.rb new file mode 100644 index 00000000..ee0f0478 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/lc/error.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true +# +# irb/lc/error.rb - +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +module IRB + # :stopdoc: + + class UnrecognizedSwitch < StandardError + def initialize(val) + super("Unrecognized switch: #{val}") + end + end + class CantReturnToNormalMode < StandardError + def initialize + super("Can't return to normal mode.") + end + end + class IllegalParameter < StandardError + def initialize(val) + super("Invalid parameter(#{val}).") + end + end + class IrbAlreadyDead < StandardError + def initialize + super("Irb is already dead.") + end + end + class IrbSwitchedToCurrentThread < StandardError + def initialize + super("Switched to current thread.") + end + end + class NoSuchJob < StandardError + def initialize(val) + super("No such job(#{val}).") + end + end + class CantChangeBinding < StandardError + def initialize(val) + super("Can't change binding to (#{val}).") + end + end + class UndefinedPromptMode < StandardError + def initialize(val) + super("Undefined prompt mode(#{val}).") + end + end + + # :startdoc: +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/lc/help-message b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/lc/help-message new file mode 100644 index 00000000..37347306 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/lc/help-message @@ -0,0 +1,55 @@ +Usage: irb.rb [options] [programfile] [arguments] + -f Don't initialize from configuration file. + -d Set $DEBUG and $VERBOSE to true (same as 'ruby -d'). + -r load-module Require load-module (same as 'ruby -r'). + -I path Specify $LOAD_PATH directory (same as 'ruby -I'). + -U Set external and internal encodings to UTF-8. + -E ex[:in] Set default external (ex) and internal (in) encodings + (same as 'ruby -E'). + -w Suppress warnings (same as 'ruby -w'). + -W[level=2] Set warning level: 0=silence, 1=medium, 2=verbose + (same as 'ruby -W'). + --context-mode n Set n[0-4] to method to create Binding Object, + when new workspace was created. + --extra-doc-dir Add an extra doc dir for the doc dialog. + --echo Show result (default). + --noecho Don't show result. + --echo-on-assignment + Show result on assignment. + --noecho-on-assignment + Don't show result on assignment. + --truncate-echo-on-assignment + Show truncated result on assignment (default). + --inspect Use 'inspect' for output. + --noinspect Don't use 'inspect' for output. + --no-pager Don't use pager. + --multiline Use multiline editor module (default). + --nomultiline Don't use multiline editor module. + --singleline Use single line editor module. + --nosingleline Don't use single line editor module (default). + --colorize Use color-highlighting (default). + --nocolorize Don't use color-highlighting. + --autocomplete Use auto-completion (default). + --noautocomplete Don't use auto-completion. + --regexp-completor + Use regexp based completion (default). + --type-completor Use type based completion. + --prompt prompt-mode, --prompt-mode prompt-mode + Set prompt mode. Pre-defined prompt modes are: + 'default', 'classic', 'simple', 'inf-ruby', 'xmp', 'null'. + --inf-ruby-mode Use prompt appropriate for inf-ruby-mode on emacs. + Suppresses --multiline and --singleline. + --sample-book-mode, --simple-prompt + Set prompt mode to 'simple'. + --noprompt Don't output prompt. + --script Script mode (default, treat first argument as script) + --noscript No script mode (leave arguments in argv) + --single-irb Share self with sub-irb. + --tracer Show stack trace for each command. + --back-trace-limit n[=16] + Display backtrace top n and bottom n. + --verbose Show details. + --noverbose Don't show details. + -v, --version Print the version of irb. + -h, --help Print help. + -- Separate options of irb from the list of command-line args. diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/lc/ja/error.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/lc/ja/error.rb new file mode 100644 index 00000000..9e2e5b88 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/lc/ja/error.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true +# +# irb/lc/ja/error.rb - +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +module IRB + # :stopdoc: + + class UnrecognizedSwitch < StandardError + def initialize(val) + super("スイッチ(#{val})が分りません") + end + end + class CantReturnToNormalMode < StandardError + def initialize + super("Normalモードに戻れません.") + end + end + class IllegalParameter < StandardError + def initialize(val) + super("パラメータ(#{val})が間違っています.") + end + end + class IrbAlreadyDead < StandardError + def initialize + super("Irbは既に死んでいます.") + end + end + class IrbSwitchedToCurrentThread < StandardError + def initialize + super("カレントスレッドに切り替わりました.") + end + end + class NoSuchJob < StandardError + def initialize(val) + super("そのようなジョブ(#{val})はありません.") + end + end + class CantChangeBinding < StandardError + def initialize(val) + super("バインディング(#{val})に変更できません.") + end + end + class UndefinedPromptMode < StandardError + def initialize(val) + super("プロンプトモード(#{val})は定義されていません.") + end + end + + # :startdoc: +end +# vim:fileencoding=utf-8 diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/lc/ja/help-message b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/lc/ja/help-message new file mode 100644 index 00000000..844c67bb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/lc/ja/help-message @@ -0,0 +1,58 @@ +Usage: irb.rb [options] [programfile] [arguments] + -f ~/.irbrc を読み込まない. + -d $DEBUG をtrueにする(ruby -d と同じ) + -r load-module ruby -r と同じ. + -I path $LOAD_PATH に path を追加する. + -U ruby -U と同じ. + -E enc ruby -E と同じ. + -w ruby -w と同じ. + -W[level=2] ruby -W と同じ. + --context-mode n 新しいワークスペースを作成した時に関連する Binding + オブジェクトの作成方法を 0 から 4 のいずれかに設定する. + --extra-doc-dir 指定したディレクトリのドキュメントを追加で読み込む. + --echo 実行結果を表示する(デフォルト). + --noecho 実行結果を表示しない. + --echo-on-assignment + 代入結果を表示する. + --noecho-on-assignment + 代入結果を表示しない. + --truncate-echo-on-assignment + truncateされた代入結果を表示する(デフォルト). + --inspect 結果出力にinspectを用いる. + --noinspect 結果出力にinspectを用いない. + --no-pager ページャを使用しない. + --multiline マルチラインエディタを利用する. + --nomultiline マルチラインエディタを利用しない. + --singleline シングルラインエディタを利用する. + --nosingleline シングルラインエディタを利用しない. + --colorize 色付けを利用する. + --nocolorize 色付けを利用しない. + --autocomplete オートコンプリートを利用する. + --noautocomplete オートコンプリートを利用しない. + --regexp-completor + 補完に正規表現を利用する. + --type-completor 補完に型情報を利用する. + --prompt prompt-mode/--prompt-mode prompt-mode + プロンプトモードを切り替える. + 現在定義されているプロンプトモードは, + default, classic, simple, inf-ruby, xmp, null. + --inf-ruby-mode emacsのinf-ruby-mode用のプロンプト表示を行なう. 特 + に指定がない限り, シングルラインエディタとマルチラ + インエディタは使わなくなる. + --sample-book-mode/--simple-prompt + 非常にシンプルなプロンプトを用いるモードです. + --noprompt プロンプト表示を行なわない. + --script スクリプトモード(最初の引数をスクリプトファイルとして扱う、デフォルト) + --noscript 引数をargvとして扱う. + --single-irb irb 中で self を実行して得られるオブジェクトをサ + ブ irb と共有する. + --tracer コマンド実行時にトレースを行なう. + --back-trace-limit n + バックトレース表示をバックトレースの頭から n, 後ろ + からnだけ行なう. デフォルトは16 + + --verbose 詳細なメッセージを出力する. + --noverbose 詳細なメッセージを出力しない(デフォルト). + -v, --version irbのバージョンを表示する. + -h, --help irb のヘルプを表示する. + -- 以降のコマンドライン引数をオプションとして扱わない. diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/locale.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/locale.rb new file mode 100644 index 00000000..2abcc735 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/locale.rb @@ -0,0 +1,153 @@ +# frozen_string_literal: true +# +# irb/locale.rb - internationalization module +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +module IRB # :nodoc: + class Locale + + LOCALE_NAME_RE = %r[ + (?[[:alpha:]]{2,3}) + (?:_ (?[[:alpha:]]{2,3}) )? + (?:\. (?[^@]+) )? + (?:@ (?.*) )? + ]x + LOCALE_DIR = "/lc/" + + LEGACY_ENCODING_ALIAS_MAP = { + 'ujis' => Encoding::EUC_JP, + 'euc' => Encoding::EUC_JP + } + + @@loaded = [] + + def initialize(locale = nil) + @override_encoding = nil + @lang = @territory = @encoding_name = @modifier = nil + @locale = locale || ENV["IRB_LANG"] || ENV["LC_MESSAGES"] || ENV["LC_ALL"] || ENV["LANG"] || "C" + if m = LOCALE_NAME_RE.match(@locale) + @lang, @territory, @encoding_name, @modifier = m[:language], m[:territory], m[:codeset], m[:modifier] + + if @encoding_name + if @encoding = LEGACY_ENCODING_ALIAS_MAP[@encoding_name] + warn(("%s is obsolete. use %s" % ["#{@lang}_#{@territory}.#{@encoding_name}", "#{@lang}_#{@territory}.#{@encoding.name}"]), uplevel: 1) + else + @encoding = Encoding.find(@encoding_name) rescue nil + end + end + end + @encoding ||= (Encoding.find('locale') rescue Encoding::ASCII_8BIT) + end + + attr_reader :lang, :territory, :modifier + + def encoding + @override_encoding || @encoding + end + + def String(mes) + mes = super(mes) + if encoding + mes.encode(encoding, undef: :replace) + else + mes + end + end + + def format(*opts) + String(super(*opts)) + end + + def gets(*rs) + String(super(*rs)) + end + + def readline(*rs) + String(super(*rs)) + end + + def print(*opts) + ary = opts.collect{|opt| String(opt)} + super(*ary) + end + + def printf(*opts) + s = format(*opts) + print s + end + + def puts(*opts) + ary = opts.collect{|opt| String(opt)} + super(*ary) + end + + def load(file) + found = find(file) + if found + unless @@loaded.include?(found) + @@loaded << found # cache + Kernel.load(found) + end + else + raise LoadError, "No such file to load -- #{file}" + end + end + + def find(file, paths = $:) + dir = File.dirname(file) + dir = "" if dir == "." + base = File.basename(file) + + if dir.start_with?('/') + return each_localized_path(dir, base).find{|full_path| File.readable? full_path} + else + return search_file(paths, dir, base) + end + end + + # @param paths load paths in which IRB find a localized file. + # @param dir directory + # @param file basename to be localized + # + # typically, for the parameters and a in paths, it searches + # /// + def search_file(lib_paths, dir, file) + each_localized_path(dir, file) do |lc_path| + lib_paths.each do |libpath| + full_path = File.join(libpath, lc_path) + return full_path if File.readable?(full_path) + end + redo if defined?(Gem) and Gem.try_activate(lc_path) + end + nil + end + + def each_localized_path(dir, file) + return enum_for(:each_localized_path) unless block_given? + each_sublocale do |lc| + yield lc.nil? ? File.join(dir, LOCALE_DIR, file) : File.join(dir, LOCALE_DIR, lc, file) + end + end + + def each_sublocale + if @lang + if @territory + if @encoding_name + yield "#{@lang}_#{@territory}.#{@encoding_name}@#{@modifier}" if @modifier + yield "#{@lang}_#{@territory}.#{@encoding_name}" + end + yield "#{@lang}_#{@territory}@#{@modifier}" if @modifier + yield "#{@lang}_#{@territory}" + end + if @encoding_name + yield "#{@lang}.#{@encoding_name}@#{@modifier}" if @modifier + yield "#{@lang}.#{@encoding_name}" + end + yield "#{@lang}@#{@modifier}" if @modifier + yield "#{@lang}" + end + yield nil + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/nesting_parser.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/nesting_parser.rb new file mode 100644 index 00000000..c1c9a5cc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/nesting_parser.rb @@ -0,0 +1,239 @@ +# frozen_string_literal: true +module IRB + module NestingParser + IGNORE_TOKENS = %i[on_sp on_ignored_nl on_comment on_embdoc_beg on_embdoc on_embdoc_end] + + class << self + # Scan each token and call the given block with array of token and other information for parsing + def scan_opens(tokens) + opens = [] + pending_heredocs = [] + first_token_on_line = true + tokens.each do |t| + skip = false + last_tok, state, args = opens.last + case state + when :in_alias_undef + skip = t.event == :on_kw + when :in_unquoted_symbol + unless IGNORE_TOKENS.include?(t.event) + opens.pop + skip = true + end + when :in_lambda_head + opens.pop if t.event == :on_tlambeg || (t.event == :on_kw && t.tok == 'do') + when :in_method_head + unless IGNORE_TOKENS.include?(t.event) + next_args = [] + body = nil + if args.include?(:receiver) + case t.event + when :on_lparen, :on_ivar, :on_gvar, :on_cvar + # def (receiver). | def @ivar. | def $gvar. | def @@cvar. + next_args << :dot + when :on_kw + case t.tok + when 'self', 'true', 'false', 'nil' + # def self(arg) | def self. + next_args.push(:arg, :dot) + else + # def if(arg) + skip = true + next_args << :arg + end + when :on_op, :on_backtick + # def +(arg) + skip = true + next_args << :arg + when :on_ident, :on_const + # def a(arg) | def a. + next_args.push(:arg, :dot) + end + end + if args.include?(:dot) + # def receiver.name + next_args << :name if t.event == :on_period || (t.event == :on_op && t.tok == '::') + end + if args.include?(:name) + if %i[on_ident on_const on_op on_kw on_backtick].include?(t.event) + # def name(arg) | def receiver.name(arg) + next_args << :arg + skip = true + end + end + if args.include?(:arg) + case t.event + when :on_nl, :on_semicolon + # def receiver.f; + body = :normal + when :on_lparen + # def receiver.f() + next_args << :eq + else + if t.event == :on_op && t.tok == '=' + # def receiver.f = + body = :oneliner + else + # def receiver.f arg + next_args << :arg_without_paren + end + end + end + if args.include?(:eq) + if t.event == :on_op && t.tok == '=' + body = :oneliner + else + body = :normal + end + end + if args.include?(:arg_without_paren) + if %i[on_semicolon on_nl].include?(t.event) + # def f a; + body = :normal + else + # def f a, b + next_args << :arg_without_paren + end + end + if body == :oneliner + opens.pop + elsif body + opens[-1] = [last_tok, nil] + else + opens[-1] = [last_tok, :in_method_head, next_args] + end + end + when :in_for_while_until_condition + if t.event == :on_semicolon || t.event == :on_nl || (t.event == :on_kw && t.tok == 'do') + skip = true if t.event == :on_kw && t.tok == 'do' + opens[-1] = [last_tok, nil] + end + end + + unless skip + case t.event + when :on_kw + case t.tok + when 'begin', 'class', 'module', 'do', 'case' + opens << [t, nil] + when 'end' + opens.pop + when 'def' + opens << [t, :in_method_head, [:receiver, :name]] + when 'if', 'unless' + unless t.state.allbits?(Ripper::EXPR_LABEL) + opens << [t, nil] + end + when 'while', 'until' + unless t.state.allbits?(Ripper::EXPR_LABEL) + opens << [t, :in_for_while_until_condition] + end + when 'ensure', 'rescue' + unless t.state.allbits?(Ripper::EXPR_LABEL) + opens.pop + opens << [t, nil] + end + when 'alias' + opens << [t, :in_alias_undef, 2] + when 'undef' + opens << [t, :in_alias_undef, 1] + when 'elsif', 'else', 'when' + opens.pop + opens << [t, nil] + when 'for' + opens << [t, :in_for_while_until_condition] + when 'in' + if last_tok&.event == :on_kw && %w[case in].include?(last_tok.tok) && first_token_on_line + opens.pop + opens << [t, nil] + end + end + when :on_tlambda + opens << [t, :in_lambda_head] + when :on_lparen, :on_lbracket, :on_lbrace, :on_tlambeg, :on_embexpr_beg, :on_embdoc_beg + opens << [t, nil] + when :on_rparen, :on_rbracket, :on_rbrace, :on_embexpr_end, :on_embdoc_end + opens.pop + when :on_heredoc_beg + pending_heredocs << t + when :on_heredoc_end + opens.pop + when :on_backtick + opens << [t, nil] unless t.state == Ripper::EXPR_ARG + when :on_tstring_beg, :on_words_beg, :on_qwords_beg, :on_symbols_beg, :on_qsymbols_beg, :on_regexp_beg + opens << [t, nil] + when :on_tstring_end, :on_regexp_end, :on_label_end + opens.pop + when :on_symbeg + if t.tok == ':' + opens << [t, :in_unquoted_symbol] + else + opens << [t, nil] + end + end + end + if t.event == :on_nl || t.event == :on_semicolon + first_token_on_line = true + elsif t.event != :on_sp + first_token_on_line = false + end + if pending_heredocs.any? && t.tok.include?("\n") + pending_heredocs.reverse_each { |t| opens << [t, nil] } + pending_heredocs = [] + end + if opens.last && opens.last[1] == :in_alias_undef && !IGNORE_TOKENS.include?(t.event) && t.event != :on_heredoc_end + tok, state, arg = opens.pop + opens << [tok, state, arg - 1] if arg >= 1 + end + yield t, opens if block_given? + end + opens.map(&:first) + pending_heredocs.reverse + end + + def open_tokens(tokens) + # scan_opens without block will return a list of open tokens at last token position + scan_opens(tokens) + end + + # Calculates token information [line_tokens, prev_opens, next_opens, min_depth] for each line. + # Example code + # ["hello + # world"+( + # First line + # line_tokens: [[lbracket, '['], [tstring_beg, '"'], [tstring_content("hello\nworld"), "hello\n"]] + # prev_opens: [] + # next_tokens: [lbracket, tstring_beg] + # min_depth: 0 (minimum at beginning of line) + # Second line + # line_tokens: [[tstring_content("hello\nworld"), "world"], [tstring_end, '"'], [op, '+'], [lparen, '(']] + # prev_opens: [lbracket, tstring_beg] + # next_tokens: [lbracket, lparen] + # min_depth: 1 (minimum just after tstring_end) + def parse_by_line(tokens) + line_tokens = [] + prev_opens = [] + min_depth = 0 + output = [] + last_opens = scan_opens(tokens) do |t, opens| + depth = t == opens.last&.first ? opens.size - 1 : opens.size + min_depth = depth if depth < min_depth + if t.tok.include?("\n") + t.tok.each_line do |line| + line_tokens << [t, line] + next if line[-1] != "\n" + next_opens = opens.map(&:first) + output << [line_tokens, prev_opens, next_opens, min_depth] + prev_opens = next_opens + min_depth = prev_opens.size + line_tokens = [] + end + else + line_tokens << [t, t.tok] + end + end + output << [line_tokens, prev_opens, last_opens, min_depth] if line_tokens.any? + output + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/notifier.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/notifier.rb new file mode 100644 index 00000000..dc1b9ef1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/notifier.rb @@ -0,0 +1,230 @@ +# frozen_string_literal: true +# +# notifier.rb - output methods used by irb +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +require_relative "output-method" + +module IRB + # An output formatter used internally by the lexer. + module Notifier + class ErrUndefinedNotifier < StandardError + def initialize(val) + super("undefined notifier level: #{val} is specified") + end + end + class ErrUnrecognizedLevel < StandardError + def initialize(val) + super("unrecognized notifier level: #{val} is specified") + end + end + + # Define a new Notifier output source, returning a new CompositeNotifier + # with the given +prefix+ and +output_method+. + # + # The optional +prefix+ will be appended to all objects being inspected + # during output, using the given +output_method+ as the output source. If + # no +output_method+ is given, StdioOutputMethod will be used, and all + # expressions will be sent directly to STDOUT without any additional + # formatting. + def def_notifier(prefix = "", output_method = StdioOutputMethod.new) + CompositeNotifier.new(prefix, output_method) + end + module_function :def_notifier + + # An abstract class, or superclass, for CompositeNotifier and + # LeveledNotifier to inherit. It provides several wrapper methods for the + # OutputMethod object used by the Notifier. + class AbstractNotifier + # Creates a new Notifier object + def initialize(prefix, base_notifier) + @prefix = prefix + @base_notifier = base_notifier + end + + # The +prefix+ for this Notifier, which is appended to all objects being + # inspected during output. + attr_reader :prefix + + # A wrapper method used to determine whether notifications are enabled. + # + # Defaults to +true+. + def notify? + true + end + + # See OutputMethod#print for more detail. + def print(*opts) + @base_notifier.print prefix, *opts if notify? + end + + # See OutputMethod#printn for more detail. + def printn(*opts) + @base_notifier.printn prefix, *opts if notify? + end + + # See OutputMethod#printf for more detail. + def printf(format, *opts) + @base_notifier.printf(prefix + format, *opts) if notify? + end + + # See OutputMethod#puts for more detail. + def puts(*objs) + if notify? + @base_notifier.puts(*objs.collect{|obj| prefix + obj.to_s}) + end + end + + # Same as #ppx, except it uses the #prefix given during object + # initialization. + # See OutputMethod#ppx for more detail. + def pp(*objs) + if notify? + @base_notifier.ppx @prefix, *objs + end + end + + # Same as #pp, except it concatenates the given +prefix+ with the #prefix + # given during object initialization. + # + # See OutputMethod#ppx for more detail. + def ppx(prefix, *objs) + if notify? + @base_notifier.ppx @prefix+prefix, *objs + end + end + + # Execute the given block if notifications are enabled. + def exec_if + yield(@base_notifier) if notify? + end + end + + # A class that can be used to create a group of notifier objects with the + # intent of representing a leveled notification system for irb. + # + # This class will allow you to generate other notifiers, and assign them + # the appropriate level for output. + # + # The Notifier class provides a class-method Notifier.def_notifier to + # create a new composite notifier. Using the first composite notifier + # object you create, sibling notifiers can be initialized with + # #def_notifier. + class CompositeNotifier < AbstractNotifier + # Create a new composite notifier object with the given +prefix+, and + # +base_notifier+ to use for output. + def initialize(prefix, base_notifier) + super + + @notifiers = [D_NOMSG] + @level_notifier = D_NOMSG + end + + # List of notifiers in the group + attr_reader :notifiers + + # Creates a new LeveledNotifier in the composite #notifiers group. + # + # The given +prefix+ will be assigned to the notifier, and +level+ will + # be used as the index of the #notifiers Array. + # + # This method returns the newly created instance. + def def_notifier(level, prefix = "") + notifier = LeveledNotifier.new(self, level, prefix) + @notifiers[level] = notifier + notifier + end + + # Returns the leveled notifier for this object + attr_reader :level_notifier + alias level level_notifier + + # Sets the leveled notifier for this object. + # + # When the given +value+ is an instance of AbstractNotifier, + # #level_notifier is set to the given object. + # + # When an Integer is given, #level_notifier is set to the notifier at the + # index +value+ in the #notifiers Array. + # + # If no notifier exists at the index +value+ in the #notifiers Array, an + # ErrUndefinedNotifier exception is raised. + # + # An ErrUnrecognizedLevel exception is raised if the given +value+ is not + # found in the existing #notifiers Array, or an instance of + # AbstractNotifier + def level_notifier=(value) + case value + when AbstractNotifier + @level_notifier = value + when Integer + l = @notifiers[value] + raise ErrUndefinedNotifier, value unless l + @level_notifier = l + else + raise ErrUnrecognizedLevel, value unless l + end + end + + alias level= level_notifier= + end + + # A leveled notifier is comparable to the composite group from + # CompositeNotifier#notifiers. + class LeveledNotifier < AbstractNotifier + include Comparable + + # Create a new leveled notifier with the given +base+, and +prefix+ to + # send to AbstractNotifier.new + # + # The given +level+ is used to compare other leveled notifiers in the + # CompositeNotifier group to determine whether or not to output + # notifications. + def initialize(base, level, prefix) + super(prefix, base) + + @level = level + end + + # The current level of this notifier object + attr_reader :level + + # Compares the level of this notifier object with the given +other+ + # notifier. + # + # See the Comparable module for more information. + def <=>(other) + @level <=> other.level + end + + # Whether to output messages to the output method, depending on the level + # of this notifier object. + def notify? + @base_notifier.level >= self + end + end + + # NoMsgNotifier is a LeveledNotifier that's used as the default notifier + # when creating a new CompositeNotifier. + # + # This notifier is used as the +zero+ index, or level +0+, for + # CompositeNotifier#notifiers, and will not output messages of any sort. + class NoMsgNotifier < LeveledNotifier + # Creates a new notifier that should not be used to output messages. + def initialize + @base_notifier = nil + @level = 0 + @prefix = "" + end + + # Ensures notifications are ignored, see AbstractNotifier#notify? for + # more information. + def notify? + false + end + end + + D_NOMSG = NoMsgNotifier.new # :nodoc: + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/output-method.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/output-method.rb new file mode 100644 index 00000000..69942f47 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/output-method.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true +# +# output-method.rb - output methods used by irb +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +module IRB + # An abstract output class for IO in irb. This is mainly used internally by + # IRB::Notifier. You can define your own output method to use with Irb.new, + # or Context.new + class OutputMethod + # Open this method to implement your own output method, raises a + # NotImplementedError if you don't define #print in your own class. + def print(*opts) + raise NotImplementedError + end + + # Prints the given +opts+, with a newline delimiter. + def printn(*opts) + print opts.join(" "), "\n" + end + + # Extends IO#printf to format the given +opts+ for Kernel#sprintf using + # #parse_printf_format + def printf(format, *opts) + if /(%*)%I/ =~ format + format, opts = parse_printf_format(format, opts) + end + print sprintf(format, *opts) + end + + # Returns an array of the given +format+ and +opts+ to be used by + # Kernel#sprintf, if there was a successful Regexp match in the given + # +format+ from #printf + # + # % + # [#0- +] + # (\*|\*[1-9][0-9]*\$|[1-9][0-9]*) + # .(\*|\*[1-9][0-9]*\$|[1-9][0-9]*|)? + # #(hh|h|l|ll|L|q|j|z|t) + # [diouxXeEfgGcsb%] + def parse_printf_format(format, opts) + return format, opts if $1.size % 2 == 1 + end + + # Calls #print on each element in the given +objs+, followed by a newline + # character. + def puts(*objs) + for obj in objs + print(*obj) + print "\n" + end + end + + # Prints the given +objs+ calling Object#inspect on each. + # + # See #puts for more detail. + def pp(*objs) + puts(*objs.collect{|obj| obj.inspect}) + end + + # Prints the given +objs+ calling Object#inspect on each and appending the + # given +prefix+. + # + # See #puts for more detail. + def ppx(prefix, *objs) + puts(*objs.collect{|obj| prefix+obj.inspect}) + end + + end + + # A standard output printer + class StdioOutputMethod < OutputMethod + # Prints the given +opts+ to standard output, see IO#print for more + # information. + def print(*opts) + STDOUT.print(*opts) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/pager.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/pager.rb new file mode 100644 index 00000000..89e1e710 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/pager.rb @@ -0,0 +1,213 @@ +# frozen_string_literal: true + +require 'reline' + +module IRB + # The implementation of this class is borrowed from RDoc's lib/rdoc/ri/driver.rb. + # Please do NOT use this class directly outside of IRB. + class Pager + PAGE_COMMANDS = [ENV['RI_PAGER'], ENV['PAGER'], 'less', 'more'].compact.uniq + + class << self + def page_content(content, **options) + if content_exceeds_screen_height?(content) + page(**options) do |io| + io.puts content + end + else + $stdout.puts content + end + end + + def page(retain_content: false) + if should_page? && pager = setup_pager(retain_content: retain_content) + begin + pid = pager.pid + yield pager + ensure + pager.close + end + else + yield $stdout + end + # When user presses Ctrl-C, IRB would raise `IRB::Abort` + # But since Pager is implemented by running paging commands like `less` in another process with `IO.popen`, + # the `IRB::Abort` exception only interrupts IRB's execution but doesn't affect the pager + # So to properly terminate the pager with Ctrl-C, we need to catch `IRB::Abort` and kill the pager process + rescue IRB::Abort + begin + begin + Process.kill("TERM", pid) if pid + rescue Errno::EINVAL + # SIGTERM not supported (windows) + Process.kill("KILL", pid) + end + rescue Errno::ESRCH + # Pager process already terminated + end + nil + rescue Errno::EPIPE + end + + def should_page? + IRB.conf[:USE_PAGER] && STDIN.tty? && (ENV.key?("TERM") && ENV["TERM"] != "dumb") + end + + def page_with_preview(width, height, formatter_proc) + overflow_callback = ->(lines) do + modified_output = formatter_proc.call(lines.join, true) + content, = take_first_page(width, [height - 2, 0].max) {|o| o.write modified_output } + content = content.chomp + content = "#{content}\e[0m" if Color.colorable? + $stdout.puts content + $stdout.puts 'Preparing full inspection value...' + end + out = PageOverflowIO.new(width, height, overflow_callback, delay: 0.1) + yield out + content = formatter_proc.call(out.string, out.multipage?) + if out.multipage? + page(retain_content: true) do |io| + io.puts content + end + else + $stdout.puts content + end + end + + def take_first_page(width, height) + overflow_callback = proc do |lines| + return lines.join, true + end + out = Pager::PageOverflowIO.new(width, height, overflow_callback) + yield out + [out.string, false] + end + + private + + def content_exceeds_screen_height?(content) + screen_height, screen_width = begin + Reline.get_screen_size + rescue Errno::EINVAL + [24, 80] + end + + pageable_height = screen_height - 3 # leave some space for previous and the current prompt + + return true if content.lines.size > pageable_height + + _, overflow = take_first_page(screen_width, pageable_height) {|out| out.write content } + overflow + end + + def setup_pager(retain_content:) + require 'shellwords' + + PAGE_COMMANDS.each do |pager_cmd| + cmd = Shellwords.split(pager_cmd) + next if cmd.empty? + + if cmd.first == 'less' + cmd << '-R' unless cmd.include?('-R') + cmd << '-X' if retain_content && !cmd.include?('-X') + end + + begin + io = IO.popen(cmd, 'w') + rescue + next + end + + if $? && $?.pid == io.pid && $?.exited? # pager didn't work + next + end + + return io + end + + nil + end + end + + # Writable IO that has page overflow callback + class PageOverflowIO + attr_reader :string, :first_page_lines + + # Maximum size of a single cell in terminal + # Assumed worst case: "\e[1;3;4;9;38;2;255;128;128;48;2;128;128;255mA\e[0m" + # bold, italic, underline, crossed_out, RGB forgound, RGB background + MAX_CHAR_PER_CELL = 50 + + def initialize(width, height, overflow_callback, delay: nil) + @lines = [] + @first_page_lines = nil + @width = width + @height = height + @buffer = +'' + @overflow_callback = overflow_callback + @col = 0 + @string = +'' + @multipage = false + @delay_until = (Time.now + delay if delay) + end + + def puts(text = '') + text = text.to_s unless text.is_a?(String) + write(text) + write("\n") unless text.end_with?("\n") + end + + def write(text) + text = text.to_s unless text.is_a?(String) + @string << text + if @multipage + if @delay_until && Time.now > @delay_until + @overflow_callback.call(@first_page_lines) + @delay_until = nil + end + return + end + + overflow_size = (@width * (@height - @lines.size) + @width - @col) * MAX_CHAR_PER_CELL + if text.size >= overflow_size + text = text[0, overflow_size] + overflow = true + end + @buffer << text + @col += Reline::Unicode.calculate_width(text, true) + if text.include?("\n") || @col >= @width + @buffer.lines.each do |line| + wrapped_lines = Reline::Unicode.split_by_width(line.chomp, @width).first.compact + wrapped_lines.pop if wrapped_lines.last == '' + @lines.concat(wrapped_lines) + if line.end_with?("\n") + if @lines.empty? || @lines.last.end_with?("\n") + @lines << "\n" + else + @lines[-1] += "\n" + end + end + end + @buffer.clear + @buffer << @lines.pop if !@lines.empty? && !@lines.last.end_with?("\n") + @col = Reline::Unicode.calculate_width(@buffer, true) + end + if overflow || @lines.size > @height || (@lines.size == @height && @col > 0) + @first_page_lines = @lines.take(@height) + if !@delay_until || Time.now > @delay_until + @overflow_callback.call(@first_page_lines) + @delay_until = nil + end + @multipage = true + end + end + + def multipage? + @multipage + end + + alias print write + alias << write + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ruby-lex.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ruby-lex.rb new file mode 100644 index 00000000..3abb53b4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ruby-lex.rb @@ -0,0 +1,476 @@ +# frozen_string_literal: true +# +# irb/ruby-lex.rb - ruby lexcal analyzer +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +require "ripper" +require "jruby" if RUBY_ENGINE == "jruby" +require_relative "nesting_parser" + +module IRB + # :stopdoc: + class RubyLex + ASSIGNMENT_NODE_TYPES = [ + # Local, instance, global, class, constant, instance, and index assignment: + # "foo = bar", + # "@foo = bar", + # "$foo = bar", + # "@@foo = bar", + # "::Foo = bar", + # "a::Foo = bar", + # "Foo = bar" + # "foo.bar = 1" + # "foo[1] = bar" + :assign, + + # Operation assignment: + # "foo += bar" + # "foo -= bar" + # "foo ||= bar" + # "foo &&= bar" + :opassign, + + # Multiple assignment: + # "foo, bar = 1, 2 + :massign, + ] + + ERROR_TOKENS = [ + :on_parse_error, + :compile_error, + :on_assign_error, + :on_alias_error, + :on_class_name_error, + :on_param_error + ] + + LTYPE_TOKENS = %i[ + on_heredoc_beg on_tstring_beg + on_regexp_beg on_symbeg on_backtick + on_symbols_beg on_qsymbols_beg + on_words_beg on_qwords_beg + ] + + class TerminateLineInput < StandardError + def initialize + super("Terminate Line Input") + end + end + + class << self + def compile_with_errors_suppressed(code, line_no: 1) + begin + result = yield code, line_no + rescue ArgumentError + # Ruby can issue an error for the code if there is an + # incomplete magic comment for encoding in it. Force an + # expression with a new line before the code in this + # case to prevent magic comment handling. To make sure + # line numbers in the lexed code remain the same, + # decrease the line number by one. + code = ";\n#{code}" + line_no -= 1 + result = yield code, line_no + end + result + end + + def generate_local_variables_assign_code(local_variables) + "#{local_variables.join('=')}=nil;" unless local_variables.empty? + end + + # Some part of the code is not included in Ripper's token. + # Example: DATA part, token after heredoc_beg when heredoc has unclosed embexpr. + # With interpolated tokens, tokens.map(&:tok).join will be equal to code. + def interpolate_ripper_ignored_tokens(code, tokens) + line_positions = [0] + code.lines.each do |line| + line_positions << line_positions.last + line.bytesize + end + prev_byte_pos = 0 + interpolated = [] + prev_line = 1 + tokens.each do |t| + line, col = t.pos + byte_pos = line_positions[line - 1] + col + if prev_byte_pos < byte_pos + tok = code.byteslice(prev_byte_pos...byte_pos) + pos = [prev_line, prev_byte_pos - line_positions[prev_line - 1]] + interpolated << Ripper::Lexer::Elem.new(pos, :on_ignored_by_ripper, tok, 0) + prev_line += tok.count("\n") + end + interpolated << t + prev_byte_pos = byte_pos + t.tok.bytesize + prev_line += t.tok.count("\n") + end + if prev_byte_pos < code.bytesize + tok = code.byteslice(prev_byte_pos..) + pos = [prev_line, prev_byte_pos - line_positions[prev_line - 1]] + interpolated << Ripper::Lexer::Elem.new(pos, :on_ignored_by_ripper, tok, 0) + end + interpolated + end + + def ripper_lex_without_warning(code, local_variables: []) + verbose, $VERBOSE = $VERBOSE, nil + lvars_code = generate_local_variables_assign_code(local_variables) + original_code = code + if lvars_code + code = "#{lvars_code}\n#{code}" + line_no = 0 + else + line_no = 1 + end + + compile_with_errors_suppressed(code, line_no: line_no) do |inner_code, line_no| + lexer = Ripper::Lexer.new(inner_code, '-', line_no) + tokens = [] + lexer.scan.each do |t| + next if t.pos.first == 0 + prev_tk = tokens.last + position_overlapped = prev_tk && t.pos[0] == prev_tk.pos[0] && t.pos[1] < prev_tk.pos[1] + prev_tk.tok.bytesize + if position_overlapped + tokens[-1] = t if ERROR_TOKENS.include?(prev_tk.event) && !ERROR_TOKENS.include?(t.event) + else + tokens << t + end + end + interpolate_ripper_ignored_tokens(original_code, tokens) + end + ensure + $VERBOSE = verbose + end + end + + def check_code_state(code, local_variables:) + tokens = self.class.ripper_lex_without_warning(code, local_variables: local_variables) + opens = NestingParser.open_tokens(tokens) + [tokens, opens, code_terminated?(code, tokens, opens, local_variables: local_variables)] + end + + def code_terminated?(code, tokens, opens, local_variables:) + case check_code_syntax(code, local_variables: local_variables) + when :unrecoverable_error + true + when :recoverable_error + false + when :other_error + opens.empty? && !should_continue?(tokens) + when :valid + !should_continue?(tokens) + end + end + + def assignment_expression?(code, local_variables:) + # Try to parse the code and check if the last of possibly multiple + # expressions is an assignment type. + + # If the expression is invalid, Ripper.sexp should return nil which will + # result in false being returned. Any valid expression should return an + # s-expression where the second element of the top level array is an + # array of parsed expressions. The first element of each expression is the + # expression's type. + verbose, $VERBOSE = $VERBOSE, nil + code = "#{RubyLex.generate_local_variables_assign_code(local_variables) || 'nil;'}\n#{code}" + # Get the last node_type of the line. drop(1) is to ignore the local_variables_assign_code part. + node_type = Ripper.sexp(code)&.dig(1)&.drop(1)&.dig(-1, 0) + ASSIGNMENT_NODE_TYPES.include?(node_type) + ensure + $VERBOSE = verbose + end + + def should_continue?(tokens) + # Look at the last token and check if IRB need to continue reading next line. + # Example code that should continue: `a\` `a +` `a.` + # Trailing spaces, newline, comments are skipped + return true if tokens.last&.event == :on_sp && tokens.last.tok == "\\\n" + + tokens.reverse_each do |token| + case token.event + when :on_sp, :on_nl, :on_ignored_nl, :on_comment, :on_embdoc_beg, :on_embdoc, :on_embdoc_end + # Skip + when :on_regexp_end, :on_heredoc_end, :on_semicolon + # State is EXPR_BEG but should not continue + return false + else + # Endless range should not continue + return false if token.event == :on_op && token.tok.match?(/\A\.\.\.?\z/) + + # EXPR_DOT and most of the EXPR_BEG should continue + return token.state.anybits?(Ripper::EXPR_BEG | Ripper::EXPR_DOT) + end + end + false + end + + def check_code_syntax(code, local_variables:) + lvars_code = RubyLex.generate_local_variables_assign_code(local_variables) + code = "#{lvars_code}\n#{code}" + + begin # check if parser error are available + verbose, $VERBOSE = $VERBOSE, nil + case RUBY_ENGINE + when 'ruby' + self.class.compile_with_errors_suppressed(code) do |inner_code, line_no| + RubyVM::InstructionSequence.compile(inner_code, nil, nil, line_no) + end + when 'jruby' + JRuby.compile_ir(code) + else + catch(:valid) do + eval("BEGIN { throw :valid, true }\n#{code}") + false + end + end + rescue EncodingError + # This is for a hash with invalid encoding symbol, {"\xAE": 1} + :unrecoverable_error + rescue SyntaxError => e + case e.message + when /unexpected keyword_end/ + # "syntax error, unexpected keyword_end" + # + # example: + # if ( + # end + # + # example: + # end + return :unrecoverable_error + when /unexpected '\.'/ + # "syntax error, unexpected '.'" + # + # example: + # . + return :unrecoverable_error + when /unexpected tREGEXP_BEG/ + # "syntax error, unexpected tREGEXP_BEG, expecting keyword_do or '{' or '('" + # + # example: + # method / f / + return :unrecoverable_error + when /unterminated (?:string|regexp) meets end of file/ + # "unterminated regexp meets end of file" + # + # example: + # / + # + # "unterminated string meets end of file" + # + # example: + # ' + return :recoverable_error + when /unexpected end-of-input/ + # "syntax error, unexpected end-of-input, expecting keyword_end" + # + # example: + # if true + # hoge + # if false + # fuga + # end + return :recoverable_error + else + return :other_error + end + ensure + $VERBOSE = verbose + end + :valid + end + + def calc_indent_level(opens) + indent_level = 0 + opens.each_with_index do |t, index| + case t.event + when :on_heredoc_beg + if opens[index + 1]&.event != :on_heredoc_beg + if t.tok.match?(/^<<[~-]/) + indent_level += 1 + else + indent_level = 0 + end + end + when :on_tstring_beg, :on_regexp_beg, :on_symbeg, :on_backtick + # No indent: "", //, :"", `` + # Indent: %(), %r(), %i(), %x() + indent_level += 1 if t.tok.start_with? '%' + when :on_embdoc_beg + indent_level = 0 + else + indent_level += 1 unless t.tok == 'alias' || t.tok == 'undef' + end + end + indent_level + end + + FREE_INDENT_TOKENS = %i[on_tstring_beg on_backtick on_regexp_beg on_symbeg] + + def free_indent_token?(token) + FREE_INDENT_TOKENS.include?(token&.event) + end + + # Calculates the difference of pasted code's indent and indent calculated from tokens + def indent_difference(lines, line_results, line_index) + loop do + _tokens, prev_opens, _next_opens, min_depth = line_results[line_index] + open_token = prev_opens.last + if !open_token || (open_token.event != :on_heredoc_beg && !free_indent_token?(open_token)) + # If the leading whitespace is an indent, return the difference + indent_level = calc_indent_level(prev_opens.take(min_depth)) + calculated_indent = 2 * indent_level + actual_indent = lines[line_index][/^ */].size + return actual_indent - calculated_indent + elsif open_token.event == :on_heredoc_beg && open_token.tok.match?(/^<<[^-~]/) + return 0 + end + # If the leading whitespace is not an indent but part of a multiline token + # Calculate base_indent of the multiline token's beginning line + line_index = open_token.pos[0] - 1 + end + end + + def process_indent_level(tokens, lines, line_index, is_newline) + line_results = NestingParser.parse_by_line(tokens) + result = line_results[line_index] + if result + _tokens, prev_opens, next_opens, min_depth = result + else + # When last line is empty + prev_opens = next_opens = line_results.last[2] + min_depth = next_opens.size + end + + # To correctly indent line like `end.map do`, we use shortest open tokens on each line for indent calculation. + # Shortest open tokens can be calculated by `opens.take(min_depth)` + indent = 2 * calc_indent_level(prev_opens.take(min_depth)) + + preserve_indent = lines[line_index - (is_newline ? 1 : 0)][/^ */].size + + prev_open_token = prev_opens.last + next_open_token = next_opens.last + + # Calculates base indent for pasted code on the line where prev_open_token is located + # irb(main):001:1* if a # base_indent is 2, indent calculated from tokens is 0 + # irb(main):002:1* if b # base_indent is 6, indent calculated from tokens is 2 + # irb(main):003:0> c # base_indent is 6, indent calculated from tokens is 4 + if prev_open_token + base_indent = [0, indent_difference(lines, line_results, prev_open_token.pos[0] - 1)].max + else + base_indent = 0 + end + + if free_indent_token?(prev_open_token) + if is_newline && prev_open_token.pos[0] == line_index + # First newline inside free-indent token + base_indent + indent + else + # Accept any number of indent inside free-indent token + preserve_indent + end + elsif prev_open_token&.event == :on_embdoc_beg || next_open_token&.event == :on_embdoc_beg + if prev_open_token&.event == next_open_token&.event + # Accept any number of indent inside embdoc content + preserve_indent + else + # =begin or =end + 0 + end + elsif prev_open_token&.event == :on_heredoc_beg + tok = prev_open_token.tok + if prev_opens.size <= next_opens.size + if is_newline && lines[line_index].empty? && line_results[line_index - 1][1].last != next_open_token + # First line in heredoc + tok.match?(/^<<[-~]/) ? base_indent + indent : indent + elsif tok.match?(/^<<~/) + # Accept extra indent spaces inside `<<~` heredoc + [base_indent + indent, preserve_indent].max + else + # Accept any number of indent inside other heredoc + preserve_indent + end + else + # Heredoc close + prev_line_indent_level = calc_indent_level(prev_opens) + tok.match?(/^<<[~-]/) ? base_indent + 2 * (prev_line_indent_level - 1) : 0 + end + else + base_indent + indent + end + end + + def ltype_from_open_tokens(opens) + start_token = opens.reverse_each.find do |tok| + LTYPE_TOKENS.include?(tok.event) + end + return nil unless start_token + + case start_token&.event + when :on_tstring_beg + case start_token&.tok + when ?" then ?" + when /^%.$/ then ?" + when /^%Q.$/ then ?" + when ?' then ?' + when /^%q.$/ then ?' + end + when :on_regexp_beg then ?/ + when :on_symbeg then ?: + when :on_backtick then ?` + when :on_qwords_beg then ?] + when :on_words_beg then ?] + when :on_qsymbols_beg then ?] + when :on_symbols_beg then ?] + when :on_heredoc_beg + start_token&.tok =~ /<<[-~]?(['"`])\w+\1/ + $1 || ?" + else + nil + end + end + + def check_termination_in_prev_line(code, local_variables:) + tokens = self.class.ripper_lex_without_warning(code, local_variables: local_variables) + past_first_newline = false + index = tokens.rindex do |t| + # traverse first token before last line + if past_first_newline + if t.tok.include?("\n") + true + end + elsif t.tok.include?("\n") + past_first_newline = true + false + else + false + end + end + + if index + first_token = nil + last_line_tokens = tokens[(index + 1)..(tokens.size - 1)] + last_line_tokens.each do |t| + unless [:on_sp, :on_ignored_sp, :on_comment].include?(t.event) + first_token = t + break + end + end + + if first_token && first_token.state != Ripper::EXPR_DOT + tokens_without_last_line = tokens[0..index] + code_without_last_line = tokens_without_last_line.map(&:tok).join + opens_without_last_line = NestingParser.open_tokens(tokens_without_last_line) + if code_terminated?(code_without_last_line, tokens_without_last_line, opens_without_last_line, local_variables: local_variables) + return last_line_tokens.map(&:tok).join + end + end + end + false + end + end + # :startdoc: +end + +RubyLex = IRB::RubyLex +Object.deprecate_constant(:RubyLex) diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ruby_logo.aa b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ruby_logo.aa new file mode 100644 index 00000000..d0143a44 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ruby_logo.aa @@ -0,0 +1,118 @@ +TYPE: ASCII_LARGE + + ,,,;;;;;;;;;;;;;;;;;;;;;;,, + ,,,;;;;;;;;;,, ,;;;' ''';;, + ,,;;;''' ';;;, ,,;;'' '';, + ,;;'' ;;;;;;;;,,,,,, ';; + ,;;'' ;;;;';;;'''';;;;;;;;;,,,;; + ,,;'' ;;;; ';;, ''''';;, + ,;;' ;;;' ';;, ;; + ,;;' ,;;; '';,, ;; + ,;;' ;;; ';;, ,;; + ;;' ;;;' '';,, ;;; + ,;' ;;;; ';;, ;;' + ,;;' ,;;;;' ,,,,,,,,,,,,;;;;; + ,;' ,;;;;;;;;;;;;;;;;;;;;'''''''';;; + ;;' ,;;;;;;;;;,, ;;;; + ;;' ,;;;'' ;;, ';;,, ,;;;; + ;;' ,;;;' ;; '';;, ,;';;; + ;;' ,;;;' ;;, '';;,, ,;',;;; + ,;;; ,;;;' ;; '';;,, ,;' ;;;' + ;;;; ,,;;;' ;;, ';;;' ;;; +,;;; ,;;;;' ;; ,;;; ;;; +;;;;; ,,;;;;;' ;;, ,;';; ;;; +;;;;;, ,,;;;;;;;' ;; ,;;' ;;; ;;; +;;;;;;;,,,,,,,;;;;;;;;;;;;;;,,, ;;, ,;' ;; ;;; +;;' ;;;;;;;;;;'''' ,;';; ''';;;;,,, ;; ,;; ;; ;;; +;; ;;;'' ;; ';; ''';;;;,,,, ;;, ,;;' ;;, ;; +;; ;;;;, ;;' ';; ''';;;;,,;;;;' ';; ;; +;;;;;;';, ,;; ;; '';;;;, ;;,;; +;;; ;; ;;, ;; ;; ,;;' ';;, ;;;;; +;; ;;; ;;, ;;' ;; ,,;'' ';;, ;;;;; +;; ;; ;; ;; ;; ,;;' '';, ;;;; +;;,;; ;; ;;' ;; ,;;'' ';,, ;;;' + ;;;; ';; ,;; ;;,,;;'' ';;, ;;; + ';;; ';; ;; ,;;;;;;;;;;;;;,,,,,,,,,,,, ';;;;; + ';, ';,;;' ,,,;;'' '''''''';;;;;;;;;;;;;;;;;;; + ';;,,, ;;;; ,,,,;;;;;;,,,,,;;;;;;;;;;;;;;;;;;;'''''''''''''' + ''';;;;;;;;;;;;;;''''''''''''''' +TYPE: ASCII + ,,,;;;;''''';;;'';, + ,,;'' ';;,;;; ', + ,,'' ;;'';'''';;;;;; + ,;' ;; ',, ; + ,;' ,;' ';, ; + ;' ,;; ',,,; + ,' ,;;,,,,,,,,,,,;;;; + ;' ;;';;;; ,;; + ;' ,;' ;; '',, ,;;; + ;; ,;' ; '';, ,; ;' +;; ,;;' ;; ;; ;; +;;, ,,;;' ; ;'; ;; +;';;,,,,;;;;;;;,,, ;; ,' ; ;; +; ;;''' ,;'; ''';,,, ; ,;' ;;;; +;;;;, ; '; ''';;;' ';;; +;'; ;, ;' '; ,;' ', ;;; +;;; ; ,; '; ,,' ',, ;; +;;; '; ;' ';,,'' ';,;; + '; ';,; ,,;''''''''';;;;;;,,;;; + ';,,;;,,;;;;;;;;;;'''''''''''''' +TYPE: UNICODE_LARGE + + ⣀⣤⣴⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣤⣄⡀ + ⢀⣀⣤⣴⣾⣿⣿⣿⠿⣿⣿⣿⣿⣦⣀ ⢀⣤⣶⣿⠿⠛⠁⠈⠉⠙⠻⢿⣷⣦⡀ + ⢀⣠⣴⣾⡿⠿⠛⠉⠉ ⠈⠙⢿⣿⣷⣤⡀ ⣠⣴⣾⡿⠟⠉ ⠉⠻⣿⣦ + ⢀⣤⣶⣿⠟⠋⠁ ⢿⣿⣿⣿⣿⣿⣿⣧⣤⣤⣤⣀⣀⣀⡀ ⠘⢿⣷⡀ + ⢀⣠⣾⡿⠟⠉ ⢸⣿⣿⣿⠟⢿⣿⣯⡙⠛⠛⠛⠿⠿⠿⢿⣿⣿⣶⣶⣶⣦⣤⣬⣿⣧ + ⣠⣴⣿⠟⠋ ⢸⣿⣿⡿ ⠈⠻⣿⣶⣄ ⠉⠉⠉⠙⠛⢻⣿⡆ + ⣠⣾⡿⠛⠁ ⣼⣿⣿⠃ ⠈⠙⢿⣷⣤⡀ ⠈⣿⡇ + ⣠⣾⡿⠋ ⢠⣿⣿⡏ ⠙⠻⣿⣦⣀ ⣿⡇ + ⣠⣾⡿⠋ ⢀⣿⣿⣿ ⠈⠛⢿⣷⣄⡀ ⢠⣿⡇ + ⢀⣾⡿⠋ ⢀⣾⣿⣿⠇ ⠙⠻⣿⣦⣀ ⢸⣿⡇ + ⢀⣴⣿⠟⠁ ⢀⣾⣿⣿⡟ ⠈⠻⢿⣷⣄ ⣾⣿⠇ + ⢠⣾⡿⠃ ⣠⣿⣿⣿⣿⠃ ⣀⣀⣀⣀⣀⣀⣀⣀⣤⣤⣤⣤⣽⣿⣿⣿⣿ + ⣰⣿⠟ ⣴⣿⣿⣿⣿⣿⣶⣶⣿⣿⣿⣿⣿⠿⠿⠿⠿⠿⠿⠿⠿⠛⠛⠛⠛⠛⠛⠛⠛⣿⣿⣿ + ⣼⣿⠏ ⢠⣾⣿⣿⣿⡿⣿⣿⢿⣷⣦⣄ ⣼⣿⣿⣿ + ⣼⣿⠃ ⢀⣴⣿⣿⣿⠟⠋ ⢸⣿⡆⠈⠛⠿⣿⣦⣄⡀ ⣰⣿⣿⣿⡇ + ⢀⣾⣿⠃ ⢀⣴⣿⣿⣿⠟⠁ ⣿⣷ ⠈⠙⠻⣿⣶⣄⡀ ⣰⣿⠟⣿⣿⡇ + ⢀⣾⣿⠇ ⢀⣴⣿⣿⣿⠟⠁ ⢸⣿⡆ ⠙⠻⢿⣷⣤⣀ ⣰⣿⠏⢠⣿⣿⡇ + ⢠⣿⣿⡟ ⢀⣴⣿⣿⡿⠛⠁ ⣿⣷ ⠉⠻⢿⣷⣦⣀ ⣴⣿⠏ ⢸⣿⣿⠃ + ⣿⣿⣿⡇ ⣠⣴⣿⣿⡿⠋ ⢸⣿⡆ ⠈⠛⢿⣿⣿⠃ ⢸⣿⣿ +⢠⣿⣿⣿ ⢀⣴⣾⣿⣿⡿⠋ ⠈⣿⣧ ⢠⣾⣿⣿ ⢸⣿⣿ +⢸⣿⣿⣿⡇ ⣀⣴⣾⣿⣿⣿⡿⠋ ⢹⣿⡆ ⣴⣿⠟⢹⣿⡀ ⢸⣿⡿ +⢸⣿⡟⣿⣿⣄ ⣀⣤⣶⣿⣿⣿⣿⣿⡟⠉ ⠈⣿⣷ ⢠⣾⡿⠋ ⢸⣿⡇ ⣼⣿⡇ +⢸⣿⡇⢹⣿⣿⣷⣦⣤⣤⣤⣤⣤⣴⣶⣾⣿⣿⣿⣿⡿⠿⣿⣿⣿⣿⣷⣶⣤⣤⣀⡀ ⢹⣿⡆ ⢀⣴⣿⠟ ⣿⣧ ⣿⣿⡇ +⢸⣿⠃ ⢿⣿⣿⣿⣿⣿⣿⡿⠿⠿⠛⠛⠉⠉⠁ ⢰⣿⠟⣿⣷⡀⠉⠙⠛⠿⢿⣿⣶⣦⣤⣀⡀ ⠈⣿⣷ ⣠⣿⡿⠁ ⢿⣿ ⣿⣿⡇ +⢸⣿ ⢀⣾⣿⣿⠋⠉⠁ ⢀⣿⡿ ⠘⣿⣷⡀ ⠉⠙⠛⠿⠿⣿⣶⣦⣤⣄⣀ ⢹⣿⡄ ⣠⣾⡿⠋ ⢸⣿⡆ ⣿⣿ +⣸⣿⢀⣾⣿⣿⣿⣆ ⣸⣿⠃ ⠘⢿⣷⡀ ⠈⠉⠛⠻⠿⣿⣷⣶⣤⣌⣿⣷⣾⡿⠋ ⠘⣿⡇ ⣿⣿ +⣿⣿⣾⡿⣿⡿⠹⣿⡆ ⢠⣿⡏ ⠈⢿⣷⡀ ⠈⠉⠙⣻⣿⣿⣿⣀ ⣿⣷⢰⣿⣿ +⣿⣿⡿⢁⣿⡇ ⢻⣿⡄ ⣾⣿ ⠈⢿⣷⡀ ⢀⣤⣾⡿⠋⠈⠻⢿⣷⣄ ⢻⣿⢸⣿⡟ +⣿⣿⠁⢸⣿⡇ ⢻⣿⡄ ⢸⣿⠇ ⠈⢿⣷⡀ ⣀⣴⣿⠟⠋ ⠙⢿⣷⣤⡀ ⢸⣿⣿⣿⡇ +⣿⣿ ⢸⣿⠁ ⠈⢿⣷⡀ ⢀⣿⡟ ⠈⢿⣷⡀ ⢀⣤⣾⡿⠛⠁ ⠙⠻⣿⣦⡀ ⠈⣿⣿⣿⡇ +⢸⣿⡄⣿⣿ ⠈⣿⣷⡀ ⣼⣿⠃ ⠈⢿⣷⡀ ⢀⣠⣶⣿⠟⠋ ⠈⠻⣿⣦⣄ ⣿⣿⣿⠇ +⠈⣿⣷⣿⡿ ⠘⣿⣧ ⢠⣿⡏ ⠈⢿⣷⣄⣤⣶⣿⠟⠋ ⠈⠛⢿⣷⣄ ⢸⣿⣿ + ⠘⣿⣿⡇ ⠘⣿⣧ ⣾⣿ ⢀⣠⣼⣿⣿⣿⣿⣿⣷⣶⣶⣶⣶⣶⣶⣤⣤⣤⣤⣤⣤⣀⣀⣀⣀⣀⣀⡀ ⠙⢿⣷⣼⣿⣿ + ⠈⠻⣿⣦⡀ ⠹⣿⣆⢸⣿⠇ ⣀⣠⣴⣾⡿⠟⠋⠁ ⠉⠉⠉⠉⠉⠉⠛⠛⣛⣛⣛⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣿⣿⡿ + ⠈⠻⢿⣷⣦⣄⣀⡀ ⢹⣿⣿⡟ ⢀⣀⣀⣤⣤⣶⣾⣿⣿⣿⣯⣥⣤⣤⣤⣤⣶⣶⣶⣶⣶⣶⣶⣾⣿⣿⣿⣿⠿⠿⠿⠿⠿⠿⠿⠟⠛⠛⠛⠛⠛⠛⠛⠉⠉⠉⠉⠉⠉ + ⠉⠙⠛⠿⠿⠿⣿⣿⣿⣿⠿⠿⠿⠿⠿⠿⠿⠛⠛⠛⠛⠛⠛⠛⠋⠉⠉⠉⠉⠉⠉⠉ +TYPE: UNICODE + ⣀⣤⣴⣾⣿⣿⣿⡛⠛⠛⠛⠛⣻⣿⠿⠛⠛⠶⣤⡀ + ⣀⣴⠾⠛⠉⠁ ⠙⣿⣶⣤⣶⣟⣉ ⠈⠻⣦ + ⣀⣴⠟⠋ ⢸⣿⠟⠻⣯⡙⠛⠛⠛⠶⠶⠶⢶⣽⣇ + ⣠⡾⠋⠁ ⣾⡿ ⠈⠛⢦⣄ ⣿ + ⣠⡾⠋ ⣰⣿⠃ ⠙⠷⣤⡀ ⣿ + ⢀⡾⠋ ⣰⣿⡏ ⠈⠻⣦⣄⢠⣿ + ⣰⠟⠁ ⣴⣿⣿⣁⣀⣠⣤⣤⣤⣤⣤⣤⣤⣴⠶⠿⣿⡏ + ⣼⠏ ⢀⣾⣿⠟⣿⠿⣯⣍⠁ ⣰⣿⡇ + ⢀⣼⠋ ⢀⣴⣿⠟⠁ ⢸⡇ ⠙⠻⢦⣄⡀ ⢠⡿⣿⡇ +⢀⣾⡏ ⢀⣴⣿⠟⠁ ⣿ ⠉⠻⢶⣄⡀⣰⡟ ⣿⠃ +⣾⣿⠁ ⣠⣶⡿⠋⠁ ⢹⡇ ⠈⣿⡏ ⢸⣿ +⣿⣿⡆ ⢀⣠⣴⣿⡿⠋ ⠈⣿ ⢀⡾⠋⣿ ⢸⣿ +⣿⠸⣿⣶⣤⣤⣤⣤⣶⣾⠿⠿⣿⣿⠶⣤⣤⣀⡀ ⢹⡇ ⣴⠟⠁ ⣿⡀⢸⣿ +⣿⢀⣿⣟⠛⠋⠉⠁ ⢰⡟⠹⣧ ⠈⠉⠛⠻⠶⢦⣤⣀⡀ ⠈⣿ ⣠⡾⠃ ⢸⡇⢸⡇ +⣿⣾⣿⢿⡄ ⣿⠁ ⠘⣧ ⠉⠙⠛⠷⣿⣿⡋ ⠸⣇⣸⡇ +⣿⠃⣿⠈⢿⡄ ⣸⠇ ⠘⣧ ⢀⣤⠾⠋⠈⠻⣦⡀ ⣿⣿⡇ +⣿⢸⡏ ⠈⣷⡀ ⢠⡿ ⠘⣧⡀ ⣠⡴⠟⠁ ⠈⠻⣦⣀ ⢿⣿⠁ +⢻⣾⡇ ⠘⣷ ⣼⠃ ⠘⣷⣠⣴⠟⠋ ⠙⢷⣄⢸⣿ + ⠻⣧⡀ ⠘⣧⣰⡏ ⢀⣠⣤⠶⠛⠉⠛⠛⠛⠛⠛⠛⠻⢶⣶⣶⣶⣶⣶⣤⣤⣽⣿⣿ + ⠈⠛⠷⢦⣤⣽⣿⣥⣤⣶⣶⡿⠿⠿⠶⠶⠶⠶⠾⠛⠛⠛⠛⠛⠛⠛⠋⠉⠉⠉⠉⠉⠉⠁ diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/source_finder.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/source_finder.rb new file mode 100644 index 00000000..6e1e5806 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/source_finder.rb @@ -0,0 +1,138 @@ +# frozen_string_literal: true + +require_relative "ruby-lex" + +module IRB + class SourceFinder + class EvaluationError < StandardError; end + + class Source + attr_reader :file, :line + def initialize(file, line, ast_source = nil) + @file = file + @line = line + @ast_source = ast_source + end + + def file_exist? + File.exist?(@file) + end + + def binary_file? + # If the line is zero, it means that the target's source is probably in a binary file. + @line.zero? + end + + def file_content + @file_content ||= File.read(@file) + end + + def colorized_content + if !binary_file? && file_exist? + end_line = find_end + # To correctly colorize, we need to colorize full content and extract the relevant lines. + colored = IRB::Color.colorize_code(file_content) + colored.lines[@line - 1...end_line].join + elsif @ast_source + IRB::Color.colorize_code(@ast_source) + end + end + + private + + def find_end + lex = RubyLex.new + code = file_content + lines = code.lines[(@line - 1)..-1] + tokens = RubyLex.ripper_lex_without_warning(lines.join) + prev_tokens = [] + + # chunk with line number + tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk| + code = lines[0..lnum].join + prev_tokens.concat chunk + continue = lex.should_continue?(prev_tokens) + syntax = lex.check_code_syntax(code, local_variables: []) + if !continue && syntax == :valid + return @line + lnum + end + end + @line + end + end + + private_constant :Source + + def initialize(irb_context) + @irb_context = irb_context + end + + def find_source(signature, super_level = 0) + case signature + when /\A(::)?[A-Z]\w*(::[A-Z]\w*)*\z/ # ConstName, ::ConstName, ConstPath::ConstName + eval_receiver_or_owner(signature) # trigger autoload + *parts, name = signature.split('::', -1) + base = + if parts.empty? # ConstName + find_const_owner(name) + elsif parts == [''] # ::ConstName + Object + else # ConstPath::ConstName + eval_receiver_or_owner(parts.join('::')) + end + file, line = base.const_source_location(name) + when /\A(?[A-Z]\w*(::[A-Z]\w*)*)#(?[^ :.]+)\z/ # Class#method + owner = eval_receiver_or_owner(Regexp.last_match[:owner]) + method = Regexp.last_match[:method] + return unless owner.respond_to?(:instance_method) + method = method_target(owner, super_level, method, "owner") + file, line = method&.source_location + when /\A((?.+)(\.|::))?(?[^ :.]+)\z/ # method, receiver.method, receiver::method + receiver = eval_receiver_or_owner(Regexp.last_match[:receiver] || 'self') + method = Regexp.last_match[:method] + return unless receiver.respond_to?(method, true) + method = method_target(receiver, super_level, method, "receiver") + file, line = method&.source_location + end + return unless file && line + + if File.exist?(file) + Source.new(file, line) + elsif method + # Method defined with eval, probably in IRB session + source = RubyVM::InstructionSequence.of(method)&.script_lines&.join rescue nil + Source.new(file, line, source) + end + rescue EvaluationError + nil + end + + private + + def method_target(owner_receiver, super_level, method, type) + case type + when "owner" + target_method = owner_receiver.instance_method(method) + when "receiver" + target_method = owner_receiver.method(method) + end + super_level.times do |s| + target_method = target_method.super_method if target_method + end + target_method + rescue NameError + nil + end + + def eval_receiver_or_owner(code) + @irb_context.workspace.binding.eval(code) + rescue Exception + raise EvaluationError + end + + def find_const_owner(name) + module_nesting = @irb_context.workspace.binding.eval('::Module.nesting') + module_nesting.find { |mod| mod.const_defined?(name, false) } || module_nesting.find { |mod| mod.const_defined?(name) } || Object + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/statement.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/statement.rb new file mode 100644 index 00000000..6a959995 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/statement.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +module IRB + class Statement + attr_reader :code + + def is_assignment? + raise NotImplementedError + end + + def suppresses_echo? + raise NotImplementedError + end + + def should_be_handled_by_debugger? + raise NotImplementedError + end + + class EmptyInput < Statement + def is_assignment? + false + end + + def suppresses_echo? + true + end + + # Debugger takes empty input to repeat the last command + def should_be_handled_by_debugger? + true + end + + def code + "" + end + end + + class Expression < Statement + def initialize(code, is_assignment) + @code = code + @is_assignment = is_assignment + end + + def suppresses_echo? + @code.match?(/;\s*\z/) + end + + def should_be_handled_by_debugger? + true + end + + def is_assignment? + @is_assignment + end + end + + class IncorrectAlias < Statement + attr_reader :message + + def initialize(message) + @code = "" + @message = message + end + + def should_be_handled_by_debugger? + false + end + + def is_assignment? + false + end + + def suppresses_echo? + true + end + end + + class Command < Statement + attr_reader :command_class, :arg + + def initialize(original_code, command_class, arg) + @code = original_code + @command_class = command_class + @arg = arg + end + + def is_assignment? + false + end + + def suppresses_echo? + true + end + + def should_be_handled_by_debugger? + require_relative 'command/debug' + IRB::Command::DebugCommand > @command_class + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/version.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/version.rb new file mode 100644 index 00000000..95037011 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/version.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true +# +# irb/version.rb - irb version definition file +# by Keiju ISHITSUKA(keiju@ishitsuka.com) +# + +module IRB # :nodoc: + VERSION = "1.15.2" + @RELEASE_VERSION = VERSION + @LAST_UPDATE_DATE = "2025-04-03" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/workspace.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/workspace.rb new file mode 100644 index 00000000..ced9d786 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/workspace.rb @@ -0,0 +1,171 @@ +# frozen_string_literal: true +# +# irb/workspace-binding.rb - +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +require_relative "helper_method" + +IRB::TOPLEVEL_BINDING = binding +module IRB # :nodoc: + class WorkSpace + # Creates a new workspace. + # + # set self to main if specified, otherwise + # inherit main from TOPLEVEL_BINDING. + def initialize(*main) + if Binding === main[0] + @binding = main.shift + elsif IRB.conf[:SINGLE_IRB] + @binding = TOPLEVEL_BINDING + else + case IRB.conf[:CONTEXT_MODE] + when 0 # binding in proc on TOPLEVEL_BINDING + @binding = eval("proc{binding}.call", + TOPLEVEL_BINDING, + __FILE__, + __LINE__) + when 1 # binding in loaded file + require "tempfile" + f = Tempfile.open("irb-binding") + f.print <IRB.conf[:__MAIN__] + attr_reader :main + + def load_helper_methods_to_main + # Do not load helper methods to frozen objects and BasicObject + return unless Object === @main && !@main.frozen? + + ancestors = class<' : '', current_pos + 1, lines[current_pos]) + end.join("") + + "\nFrom: #{file} @ line #{pos + 1} :\n\n#{body}#{Color.clear}\n" + end + end + + module HelpersContainer + class << self + def install_helper_methods + HelperMethod.helper_methods.each do |name, helper_method_class| + define_method name do |*args, **opts, &block| + helper_method_class.instance.execute(*args, **opts, &block) + end unless method_defined?(name) + end + end + end + + install_helper_methods + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ws-for-case-2.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ws-for-case-2.rb new file mode 100644 index 00000000..03f42d73 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/ws-for-case-2.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true +# +# irb/ws-for-case-2.rb - +# by Keiju ISHITSUKA(keiju@ruby-lang.org) +# + +while true + IRB::BINDING_QUEUE.push _ = binding +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/xmp.rb b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/xmp.rb new file mode 100644 index 00000000..b1bc5328 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/lib/irb/xmp.rb @@ -0,0 +1,164 @@ +# frozen_string_literal: true +# +# xmp.rb - irb version of gotoken xmp +# by Keiju ISHITSUKA(Nippon Rational Inc.) +# + +require_relative "../irb" +require_relative "frame" + +# An example printer for irb. +# +# It's much like the standard library PrettyPrint, that shows the value of each +# expression as it runs. +# +# In order to use this library, you must first require it: +# +# require 'irb/xmp' +# +# Now, you can take advantage of the Object#xmp convenience method. +# +# xmp < foo = "bar" +# #==>"bar" +# #=> baz = 42 +# #==>42 +# +# You can also create an XMP object, with an optional binding to print +# expressions in the given binding: +# +# ctx = binding +# x = XMP.new ctx +# x.puts +# #=> today = "a good day" +# #==>"a good day" +# ctx.eval 'today # is what?' +# #=> "a good day" +class XMP + + # Creates a new XMP object. + # + # The top-level binding or, optional +bind+ parameter will be used when + # creating the workspace. See WorkSpace.new for more information. + # + # This uses the +:XMP+ prompt mode. + # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. + def initialize(bind = nil) + IRB.init_config(nil) + + IRB.conf[:PROMPT_MODE] = :XMP + + bind = IRB::Frame.top(1) unless bind + ws = IRB::WorkSpace.new(bind) + @io = StringInputMethod.new + @irb = IRB::Irb.new(ws, @io) + @irb.context.ignore_sigint = false + + IRB.conf[:MAIN_CONTEXT] = @irb.context + end + + # Evaluates the given +exps+, for example: + # + # require 'irb/xmp' + # x = XMP.new + # + # x.puts '{:a => 1, :b => 2, :c => 3}' + # #=> {:a => 1, :b => 2, :c => 3} + # # ==>{:a=>1, :b=>2, :c=>3} + # x.puts 'foo = "bar"' + # # => foo = "bar" + # # ==>"bar" + def puts(exps) + @io.puts exps + + if @irb.context.ignore_sigint + begin + trap_proc_b = trap("SIGINT"){@irb.signal_handle} + catch(:IRB_EXIT) do + @irb.eval_input + end + ensure + trap("SIGINT", trap_proc_b) + end + else + catch(:IRB_EXIT) do + @irb.eval_input + end + end + end + + # A custom InputMethod class used by XMP for evaluating string io. + class StringInputMethod < IRB::InputMethod + # Creates a new StringInputMethod object + def initialize + super + @exps = [] + end + + # Whether there are any expressions left in this printer. + def eof? + @exps.empty? + end + + # Reads the next expression from this printer. + # + # See IO#gets for more information. + def gets + while l = @exps.shift + next if /^\s+$/ =~ l + l.concat "\n" + print @prompt, l + break + end + l + end + + # Concatenates all expressions in this printer, separated by newlines. + # + # An Encoding::CompatibilityError is raised of the given +exps+'s encoding + # doesn't match the previous expression evaluated. + def puts(exps) + if @encoding and exps.encoding != @encoding + enc = Encoding.compatible?(@exps.join("\n"), exps) + if enc.nil? + raise Encoding::CompatibilityError, "Encoding in which the passed expression is encoded is not compatible to the preceding's one" + else + @encoding = enc + end + else + @encoding = exps.encoding + end + @exps.concat exps.split(/\n/) + end + + # Returns the encoding of last expression printed by #puts. + attr_reader :encoding + end +end + +# A convenience method that's only available when the you require the IRB::XMP standard library. +# +# Creates a new XMP object, using the given expressions as the +exps+ +# parameter, and optional binding as +bind+ or uses the top-level binding. Then +# evaluates the given expressions using the +:XMP+ prompt mode. +# +# For example: +# +# require 'irb/xmp' +# ctx = binding +# xmp 'foo = "bar"', ctx +# #=> foo = "bar" +# #==>"bar" +# ctx.eval 'foo' +# #=> "bar" +# +# See XMP.new for more information. +def xmp(exps, bind = nil) + bind = IRB::Frame.top(1) unless bind + xmp = XMP.new(bind) + xmp.puts exps + xmp +end diff --git a/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/man/irb.1 b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/man/irb.1 new file mode 100644 index 00000000..48586a3b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/irb-1.15.2/man/irb.1 @@ -0,0 +1,292 @@ +.\"Ruby is copyrighted by Yukihiro Matsumoto . +.Dd August 11, 2019 +.Dt IRB \&1 "Ruby Programmer's Reference Guide" +.Os UNIX +.Sh NAME +.Nm irb +.Nd Interactive Ruby Shell +.Sh SYNOPSIS +.Nm +.Op Fl -version +.Op Fl dfUw +.Op Fl I Ar directory +.Op Fl r Ar library +.Op Fl E Ar external Ns Op : Ns Ar internal +.Op Fl W Ns Op Ar level +.Op Fl - Ns Oo no Oc Ns inspect +.Op Fl - Ns Oo no Oc Ns multiline +.Op Fl - Ns Oo no Oc Ns singleline +.Op Fl - Ns Oo no Oc Ns echo +.Op Fl - Ns Oo no Oc Ns colorize +.Op Fl - Ns Oo no Oc Ns autocomplete +.Op Fl - Ns Oo no Oc Ns verbose +.Op Fl -prompt Ar mode +.Op Fl -prompt-mode Ar mode +.Op Fl -inf-ruby-mode +.Op Fl -simple-prompt +.Op Fl -noprompt +.Op Fl -tracer +.Op Fl -back-trace-limit Ar n +.Op Fl - +.Op program_file +.Op argument ... +.Pp +.Sh DESCRIPTION +.Nm +is the REPL(read-eval-print loop) environment for Ruby programs. +.Pp +.Sh OPTIONS +.Bl -tag -width "1234567890123" -compact +.Pp +.It Fl -version +Prints the version of +.Nm . +.Pp +.It Fl E Ar external Ns Op : Ns Ar internal +.It Fl -encoding Ar external Ns Op : Ns Ar internal +Same as `ruby -E' . +Specifies the default value(s) for external encodings and internal encoding. Values should be separated with colon (:). +.Pp +You can omit the one for internal encodings, then the value +.Pf ( Li "Encoding.default_internal" ) will be nil. +.Pp +.It Fl I Ar path +Same as `ruby -I' . +Specifies +.Li $LOAD_PATH +directory +.Pp +.It Fl U +Same as `ruby -U' . +Sets the default value for internal encodings +.Pf ( Li "Encoding.default_internal" ) to UTF-8. +.Pp +.It Fl d +Same as `ruby -d' . +Sets +.Li $DEBUG +to true. +.Pp +.It Fl f +Suppresses read of +.Pa ~/.irbrc . +.Pp +.It Fl w +Same as `ruby -w' . +.Pp +.Pp +.It Fl W +Same as `ruby -W' . +.Pp +.It Fl h +.It Fl -help +Prints a summary of the options. +.Pp +.It Fl r Ar library +Same as `ruby -r'. +Causes irb to load the library using require. +.Pp +.It Fl -inspect +Uses `inspect' for output (default except for bc mode) +.Pp +.It Fl -noinspect +Doesn't use inspect for output +.Pp +.It Fl -multiline +Uses multiline editor module. +.Pp +.It Fl -nomultiline +Doesn't use multiline editor module. +.Pp +.It Fl -singleline +Uses singleline editor module. +.Pp +.It Fl -nosingleline +Doesn't use singleline editor module. +.Pp +.Pp +.It Fl -extra-doc-dir +Add an extra doc dir for the doc dialog. +.Pp +.Pp +.It Fl -echo +Show result (default). +.Pp +.It Fl -noecho +Don't show result. +.Pp +.Pp +.It Fl -echo-on-assignment +Show result on assignment. +.Pp +.It Fl -noecho-on-assignment +Don't show result on assignment. +.Pp +.It Fl -truncate-echo-on-assignment +Show truncated result on assignment (default). +.Pp +.Pp +.It Fl -colorize +Use colorization. +.Pp +.It Fl -nocolorize +Don't use colorization. +.Pp +.Pp +.It Fl -autocomplete +Use autocompletion. +.Pp +.It Fl -noautocomplete +Don't use autocompletion. +.Pp +.Pp +.It Fl -regexp-completor +Use regexp based completion. +.Pp +.It Fl -type-completor +Use type based completion. +.Pp +.Pp +.It Fl -verbose +Show details. +.Pp +.It Fl -noverbose +Don't show details. +.Pp +.It Fl -prompt Ar mode +.It Fl -prompt-mode Ar mode +Switch prompt mode. Pre-defined prompt modes are +`default', `simple', `xmp' and `inf-ruby'. +.Pp +.It Fl -inf-ruby-mode +Uses prompt appropriate for inf-ruby-mode on emacs. +Suppresses --multiline and --singleline. +.Pp +.It Fl -simple-prompt +Makes prompts simple. +.Pp +.It Fl -noprompt +No prompt mode. +.Pp +.It Fl -tracer +Displays trace for each execution of commands. +.Pp +.It Fl -back-trace-limit Ar n +Displays backtrace top +.Ar n +and tail +.Ar n Ns . +The default value is 16. +.El +.Pp +.Sh ENVIRONMENT +.Bl -tag -compact -width "IRB_USE_AUTOCOMPLETE" +.It Ev IRB_LANG +The locale used for +.Nm . +.Pp +.It Ev IRBRC +The path to the personal initialization file. +.Pp +.It Ev XDG_CONFIG_HOME +.Nm +respects XDG_CONFIG_HOME. If it is set and +.Ev IRBRC +is unset, load +.Pa $XDG_CONFIG_HOME/irb/irbrc +as a personal initialization file. +.Pp +.It Ev RI_PAGER +The command specified would be used as a pager. +.Pp +.It Ev PAGER +The command specified would be used as a pager if +.Ev RI_PAGER +is unset. +.Pp +.It Ev VISUAL +Its value would be used to open files by the edit command. +.Pp +.It Ev EDITOR +Its value would be used to open files by the edit command if +.Ev VISUAL +is unset. +.Pp +.It Ev NO_COLOR +Assigning a value to it disables colorization. +.Pp +.It Ev IRB_USE_AUTOCOMPLETE +Assigning +.Sy false +to it disables autocompletion. +.Pp +.It Ev IRB_COMPLETOR +Autocompletion behavior. Allowed values are +.Sy regexp +or +.Sy type +. +.Pp +.It Ev IRB_COPY_COMMAND +Overrides the default program used to interface with the system clipboard. +.El +.Pp +Also +.Nm +depends on same variables as +.Xr ruby 1 . +.Pp +.Sh FILES +.Bl -tag -compact +.It Pa ~/.irbrc +Personal irb initialization. If +.Ev IRBRC +is set, read +.Pa $IRBRC +instead. If +.Ev IRBRC +is not set and +.Ev XDG_CONFIG_HOME +is set, +.Pa $XDG_CONFIG_HOME/irb/irbrc +is loaded. +.Pp +.El +.Pp +.Sh EXAMPLES +.Dl % irb +.Dl irb(main):001:0> Ic 1 + 1 +.Dl 2 +.Dl irb(main):002:0> Ic def t(x) +.Dl irb(main):003:1> Ic x + 1 +.Dl irb(main):004:1> Ic end +.Dl => :t +.Dl irb(main):005:0> Ic t(3) +.Dl => 4 +.Dl irb(main):006:0> Ic if t(3) == 4 +.Dl irb(main):007:1> Ic p :ok +.Dl irb(main):008:1> Ic end +.Dl :ok +.Dl => :ok +.Dl irb(main):009:0> Ic quit +.Dl % +.Pp +.Sh SEE ALSO +.Xr ruby 1 . +.Pp +.Sh REPORTING BUGS +.Bl -bullet +.It +Security vulnerabilities should be reported via an email to +.Mt security@ruby-lang.org . +Reported problems will be published after being fixed. +.Pp +.It +Other bugs and feature requests can be reported via the +Ruby Issue Tracking System +.Pq Lk https://bugs.ruby-lang.org/ . +Do not report security vulnerabilities +via this system because it publishes the vulnerabilities immediately. +.El +.Sh AUTHORS +Written by Keiju ISHITSUKA. diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/AUTHORS b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/AUTHORS new file mode 100644 index 00000000..c0be88c3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/AUTHORS @@ -0,0 +1,119 @@ +Tim Rudat +Joakim Antman +Jeff Lindsay +A.B +shields +Bob Aman +Emilio Cristalli +Egon Zemmer +Zane Shannon +Nikita Shatov +Paul Battley +Oliver +blackanger +Ville Lautanala +Tyler Pickett +James Stonehill +Adam Michael +Martin Emde +Saverio Trioni +Peter M. Goldstein +Korstiaan de Ridder +Richard Larocque +Andrew Davis +Yason Khaburzaniya +Klaas Jan Wierenga +Nick Hammond +Bart de Water +Steve Sloan +Antonis Berkakis +Bill Mill +Kevin Olbrich +Simon Fish +jb08 +lukas +Rodrigo López Dato +ojab +Ritikesh +sawyerzhang +Larry Lv +smudge +wohlgejm +Tom Wey +yann ARMAND +Brian Flethcer +Jurriaan Pruis +Erik Michaels-Ober +Matthew Simpson +Steven Davidovitz +Nicolas Leger +Pierre Michard +RahulBajaj +Rob Wygand +Ryan Brushett +Ryan McIlmoyl +Ryan Metzler +Severin Schoepke +Shaun Guth +Steve Teti +T.J. Schuck +Taiki Sugawara +Takehiro Adachi +Tobias Haar +Toby Pinder +Tomé Duarte +Travis Hunter +Yuji Yaginuma +Zuzanna Stolińska +aarongray +danielgrippi +fusagiko/takayamaki +mai fujii +nycvotes-dev +revodoge +rono23 +antonmorant +Adam Greene +Alexander Boyd +Alexandr Kostrikov +Aman Gupta +Ariel Salomon +Arnaud Mesureur +Artsiom Kuts +Austin Kabiru +B +Bouke van der Bijl +Brandon Keepers +Dan Leyden +Dave Grijalva +Dmitry Pashkevich +Dorian Marié +Ernie Miller +Evgeni Golov +Ewoud Kohl van Wijngaarden +HoneyryderChuck +Igor Victor +Ilyaaaaaaaaaaaaa Zhitomirskiy +Jens Hausherr +Jeremiah Wuenschel +John Downey +Jordan Brough +Josh Bodah +JotaSe +Juanito Fatas +Julio Lopez +Katelyn Kasperowicz +Leonardo Saraiva +Lowell Kirsh +Loïc Lengrand +Lucas Mazza +Makoto Chiba +Manuel Bustillo +Marco Adkins +Meredith Leu +Micah Gates +Michał Begejowicz +Mike Eirih +Mike Pastore +Mingan +Mitch Birti diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/CHANGELOG.md b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/CHANGELOG.md new file mode 100644 index 00000000..31d3fb8d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/CHANGELOG.md @@ -0,0 +1,991 @@ +# Changelog + +## [v3.1.2](https://github.com/jwt/ruby-jwt/tree/v3.1.2) (2025-06-28) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v3.1.1...v3.1.2) + +**Fixes and enhancements:** + +- Avoid using the same digest across calls in JWT::JWA::Ecdsa and JWT::JWA::Rsa [#697](https://github.com/jwt/ruby-jwt/pull/697) +- Fix signing with a EC JWK [#699](https://github.com/jwt/ruby-jwt/pull/699) ([@anakinj](https://github.com/anakinj)) + +## [v3.1.1](https://github.com/jwt/ruby-jwt/tree/v3.1.1) (2025-06-24) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v3.1.0...v3.1.1) + +**Fixes and enhancements:** + +- Require the algorithm to be provided when signing and verifying tokens using JWKs [#695](https://github.com/jwt/ruby-jwt/pull/695) ([@anakinj](https://github.com/anakinj)) + +## [v3.1.0](https://github.com/jwt/ruby-jwt/tree/v3.1.0) (2025-06-23) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v3.0.0...v3.1.0) + +**Features:** + +- Add support for x5t header parameter for X.509 certificate thumbprint verification [#669](https://github.com/jwt/ruby-jwt/pull/669) ([@hieuk09](https://github.com/hieuk09)) +- Raise an error if the ECDSA signing or verification key is not an instance of `OpenSSL::PKey::EC` [#688](https://github.com/jwt/ruby-jwt/pull/688) ([@anakinj](https://github.com/anakinj)) +- Allow `OpenSSL::PKey::EC::Point` to be used as the verification key in ECDSA [#689](https://github.com/jwt/ruby-jwt/pull/689) ([@anakinj](https://github.com/anakinj)) +- Require claims to have been verified before accessing the `JWT::EncodedToken#payload` [#690](https://github.com/jwt/ruby-jwt/pull/690) ([@anakinj](https://github.com/anakinj)) +- Support signing and verifying tokens using a JWK [#692](https://github.com/jwt/ruby-jwt/pull/692) ([@anakinj](https://github.com/anakinj)) + +## [v3.0.0](https://github.com/jwt/ruby-jwt/tree/v3.0.0) (2025-06-14) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.10.1...v3.0.0) + +**Breaking changes:** + +- Require token signature to be verified before accessing payload [#648](https://github.com/jwt/ruby-jwt/pull/648) ([@anakinj](https://github.com/anakinj)) +- Drop support for the HS512256 algorithm [#650](https://github.com/jwt/ruby-jwt/pull/650) ([@anakinj](https://github.com/anakinj)) +- Remove deprecated claim verification methods [#654](https://github.com/jwt/ruby-jwt/pull/654) ([@anakinj](https://github.com/anakinj)) +- Remove dependency to rbnacl [#655](https://github.com/jwt/ruby-jwt/pull/655) ([@anakinj](https://github.com/anakinj)) +- Support only stricter base64 decoding (RFC 4648) [#658](https://github.com/jwt/ruby-jwt/pull/658) ([@anakinj](https://github.com/anakinj)) +- Custom algorithms are required to include `JWT::JWA::SigningAlgorithm` [#660](https://github.com/jwt/ruby-jwt/pull/660) ([@anakinj](https://github.com/anakinj)) +- Require RSA keys to be at least 2048 bits [#661](https://github.com/jwt/ruby-jwt/pull/661) ([@anakinj](https://github.com/anakinj)) +- Base64 encode and decode the k value for HMAC JWKs [#662](https://github.com/jwt/ruby-jwt/pull/662) ([@anakinj](https://github.com/anakinj)) + +Take a look at the [upgrade guide](UPGRADING.md) for more details. + +**Features:** + +- JWT::EncodedToken#verify! method that bundles signature and claim validation [#647](https://github.com/jwt/ruby-jwt/pull/647) ([@anakinj](https://github.com/anakinj)) +- Do not override the alg header if already given [#659](https://github.com/jwt/ruby-jwt/pull/659) ([@anakinj](https://github.com/anakinj)) +- Make `JWK::KeyFinder` compatible with `JWT::EncodedToken` [#663](https://github.com/jwt/ruby-jwt/pull/663) ([@anakinj](https://github.com/anakinj)) + +**Fixes and enhancements:** + +- Ruby 3.4 to CI matrix [#649](https://github.com/jwt/ruby-jwt/pull/649) ([@anakinj](https://github.com/anakinj)) +- Add logger as development dependency [#670](https://github.com/jwt/ruby-jwt/pull/670) ([@hieuk09](https://github.com/hieuk09)) + +## [v2.10.1](https://github.com/jwt/ruby-jwt/tree/v2.10.1) (2024-12-26) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.10.0...v2.10.1) + +**Fixes and enhancements:** + +- Make version constants public again [#646](https://github.com/jwt/ruby-jwt/pull/646) ([@anakinj](https://github.com/anakinj)) + +## [v2.10.0](https://github.com/jwt/ruby-jwt/tree/v2.10.0) (2024-12-25) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.9.3...v2.10.0) + +**Features:** + +- JWT::Token and JWT::EncodedToken for signing and verifying tokens [#621](https://github.com/jwt/ruby-jwt/pull/621) ([@anakinj](https://github.com/anakinj)) +- Detached payload support for JWT::Token and JWT::EncodedToken [#630](https://github.com/jwt/ruby-jwt/pull/630) ([@anakinj](https://github.com/anakinj)) +- Skip decoding payload if b64 header is present and false [#631](https://github.com/jwt/ruby-jwt/pull/631) ([@anakinj](https://github.com/anakinj)) +- Remove a few custom Rubocop configs [#638](https://github.com/jwt/ruby-jwt/pull/638) ([@anakinj](https://github.com/anakinj)) + +**Fixes and enhancements:** + +- Deprecation warnings for deprecated methods and classes [#629](https://github.com/jwt/ruby-jwt/pull/629) ([@anakinj](https://github.com/anakinj)) +- Improved documentation for public apis [#629](https://github.com/jwt/ruby-jwt/pull/629) ([@anakinj](https://github.com/anakinj)) +- Use correct methods when raising error during signing/verification with EdDSA [#633](https://github.com/jwt/ruby-jwt/pull/633) +- Fix JWT::EncodedToken behavior with empty string as token [#640](https://github.com/jwt/ruby-jwt/pull/640) ([@ragalie](https://github.com/ragalie)) +- Deprecation warnings for rbnacl backed functionality [#641](https://github.com/jwt/ruby-jwt/pull/641) ([@anakinj](https://github.com/anakinj)) + +## [v2.9.3](https://github.com/jwt/ruby-jwt/tree/v2.9.3) (2024-10-03) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.9.2...v2.9.3) + +**Fixes and enhancements:** + +- Return truthy value for `::JWT::ClaimsValidator#validate!` and `::JWT::Verify.verify_claims` [#628](https://github.com/jwt/ruby-jwt/pull/628) ([@anakinj](https://github.com/anakinj)) + +## [v2.9.2](https://github.com/jwt/ruby-jwt/tree/v2.9.2) (2024-10-03) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.9.1...v2.9.2) + +**Features:** + +- Standalone claim verification interface [#626](https://github.com/jwt/ruby-jwt/pull/626) ([@anakinj](https://github.com/anakinj)) + +**Fixes and enhancements:** + +- Updated README to correctly document `OpenSSL::HMAC` documentation [#617](https://github.com/jwt/ruby-jwt/pull/617) ([@aedryan](https://github.com/aedryan)) +- Verify JWT header format [#622](https://github.com/jwt/ruby-jwt/pull/622) ([@304](https://github.com/304)) +- Bring back `::JWT::ClaimsValidator`, `::JWT::Verify` and a few other removed interfaces for preserved backwards compatibility [#624](https://github.com/jwt/ruby-jwt/pull/624) ([@anakinj](https://github.com/anakinj)) + +## [v2.9.1](https://github.com/jwt/ruby-jwt/tree/v2.9.1) (2024-09-23) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.9.0...v2.9.1) + +**Fixes and enhancements:** + +- Fix regression in `iss` and `aud` claim validation [#619](https://github.com/jwt/ruby-jwt/pull/619) ([@anakinj](https://github.com/anakinj)) + +## [v2.9.0](https://github.com/jwt/ruby-jwt/tree/v2.9.0) (2024-09-15) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.8.2...v2.9.0) + +**Features:** + +- Build and push gem using a GH action [#612](https://github.com/jwt/ruby-jwt/pull/612) ([@anakinj](https://github.com/anakinj)) + +**Fixes and enhancements:** + +- Refactor claim validators into their own classes [#605](https://github.com/jwt/ruby-jwt/pull/605) ([@anakinj](https://github.com/anakinj), [@MatteoPierro](https://github.com/MatteoPierro)) +- Allow extending available algorithms [#607](https://github.com/jwt/ruby-jwt/pull/607) ([@anakinj](https://github.com/anakinj)) +- Do not include the EdDSA algorithm if rbnacl not available [#613](https://github.com/jwt/ruby-jwt/pull/613) ([@anakinj](https://github.com/anakinj)) + +## [v2.8.2](https://github.com/jwt/ruby-jwt/tree/v2.8.2) (2024-06-18) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.8.1...v2.8.2) + +**Fixes and enhancements:** + +- Print deprecation warnings only on when token decoding succeeds [#600](https://github.com/jwt/ruby-jwt/pull/600) ([@anakinj](https://github.com/anakinj)) +- Unify code style [#602](https://github.com/jwt/ruby-jwt/pull/602) ([@anakinj](https://github.com/anakinj)) + +## [v2.8.1](https://github.com/jwt/ruby-jwt/tree/v2.8.1) (2024-02-29) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.8.0...v2.8.1) + +**Features:** + +- Configurable base64 decode behaviour [#589](https://github.com/jwt/ruby-jwt/pull/589) ([@anakinj](https://github.com/anakinj)) + +**Fixes and enhancements:** + +- Output deprecation warnings once [#589](https://github.com/jwt/ruby-jwt/pull/589) ([@anakinj](https://github.com/anakinj)) + +## [v2.8.0](https://github.com/jwt/ruby-jwt/tree/v2.8.0) (2024-02-17) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.7.1...v2.8.0) + +**Features:** + +- Updated rubocop to 1.56 [#573](https://github.com/jwt/ruby-jwt/pull/573) ([@anakinj](https://github.com/anakinj)) +- Run CI on Ruby 3.3 [#577](https://github.com/jwt/ruby-jwt/pull/577) ([@anakinj](https://github.com/anakinj)) +- Deprecation warning added for the HMAC algorithm HS512256 (HMAC-SHA-512 truncated to 256-bits) [#575](https://github.com/jwt/ruby-jwt/pull/575) ([@anakinj](https://github.com/anakinj)) +- Stop using RbNaCl for standard HMAC algorithms [#575](https://github.com/jwt/ruby-jwt/pull/575) ([@anakinj](https://github.com/anakinj)) + +**Fixes and enhancements:** + +- Fix signature has expired error if payload is a string [#555](https://github.com/jwt/ruby-jwt/pull/555) ([@GobinathAL](https://github.com/GobinathAL)) +- Fix key base equality and spaceship operators [#569](https://github.com/jwt/ruby-jwt/pull/569) ([@magneland](https://github.com/magneland)) +- Remove explicit base64 require from x5c_key_finder [#580](https://github.com/jwt/ruby-jwt/pull/580) ([@anakinj](https://github.com/anakinj)) +- Performance improvements and cleanup of tests [#581](https://github.com/jwt/ruby-jwt/pull/581) ([@anakinj](https://github.com/anakinj)) +- Repair EC x/y coordinates when importing JWK [#585](https://github.com/jwt/ruby-jwt/pull/585) ([@julik](https://github.com/julik)) +- Explicit dependency to the base64 gem [#582](https://github.com/jwt/ruby-jwt/pull/582) ([@anakinj](https://github.com/anakinj)) +- Deprecation warning for decoding content not compliant with RFC 4648 [#582](https://github.com/jwt/ruby-jwt/pull/582) ([@anakinj](https://github.com/anakinj)) +- Algorithms moved under the `::JWT::JWA` module ([@anakinj](https://github.com/anakinj)) + +## [v2.7.1](https://github.com/jwt/ruby-jwt/tree/v2.8.0) (2023-06-09) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.7.0...v2.7.1) + +**Fixes and enhancements:** + +- Handle invalid algorithm when decoding JWT [#559](https://github.com/jwt/ruby-jwt/pull/559) ([@nataliastanko](https://github.com/nataliastanko)) +- Do not raise error when verifying bad HMAC signature [#563](https://github.com/jwt/ruby-jwt/pull/563) ([@hieuk09](https://github.com/hieuk09)) + +## [v2.7.0](https://github.com/jwt/ruby-jwt/tree/v2.7.0) (2023-02-01) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.6.0...v2.7.0) + +**Features:** + +- Support OKP (Ed25519) keys for JWKs [#540](https://github.com/jwt/ruby-jwt/pull/540) ([@anakinj](https://github.com/anakinj)) +- JWK Sets can now be used for tokens with nil kid [#543](https://github.com/jwt/ruby-jwt/pull/543) ([@bellebaum](https://github.com/bellebaum)) + +**Fixes and enhancements:** + +- Fix issue with multiple keys returned by keyfinder and multiple allowed algorithms [#545](https://github.com/jwt/ruby-jwt/pull/545) ([@mpospelov](https://github.com/mpospelov)) +- Non-string `kid` header values are now rejected [#543](https://github.com/jwt/ruby-jwt/pull/543) ([@bellebaum](https://github.com/bellebaum)) + +## [v2.6.0](https://github.com/jwt/ruby-jwt/tree/v2.6.0) (2022-12-22) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.5.0...v2.6.0) + +**Features:** + +- Support custom algorithms by passing algorithm objects [#512](https://github.com/jwt/ruby-jwt/pull/512) ([@anakinj](https://github.com/anakinj)) +- Support descriptive (not key related) JWK parameters [#520](https://github.com/jwt/ruby-jwt/pull/520) ([@bellebaum](https://github.com/bellebaum)) +- Support for JSON Web Key Sets [#525](https://github.com/jwt/ruby-jwt/pull/525) ([@bellebaum](https://github.com/bellebaum)) +- Support HMAC keys over 32 chars when using RbNaCl [#521](https://github.com/jwt/ruby-jwt/pull/521) ([@anakinj](https://github.com/anakinj)) + +**Fixes and enhancements:** + +- Raise descriptive error on empty hmac_secret and OpenSSL 3.0/openssl gem <3.0.1 [#530](https://github.com/jwt/ruby-jwt/pull/530) ([@jonmchan](https://github.com/jonmchan)) + +## [v2.5.0](https://github.com/jwt/ruby-jwt/tree/v2.5.0) (2022-08-25) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.4.1...v2.5.0) + +**Features:** + +- Support JWK thumbprints as key ids [#481](https://github.com/jwt/ruby-jwt/pull/481) ([@anakinj](https://github.com/anakinj)) +- Support OpenSSL >= 3.0 [#496](https://github.com/jwt/ruby-jwt/pull/496) ([@anakinj](https://github.com/anakinj)) + +**Fixes and enhancements:** + +- Bring back the old Base64 (RFC2045) deocode mechanisms [#488](https://github.com/jwt/ruby-jwt/pull/488) ([@anakinj](https://github.com/anakinj)) +- Rescue RbNaCl exception for EdDSA wrong key [#491](https://github.com/jwt/ruby-jwt/pull/491) ([@n-studio](https://github.com/n-studio)) +- New parameter name for cases when kid is not found using JWK key loader proc [#501](https://github.com/jwt/ruby-jwt/pull/501) ([@anakinj](https://github.com/anakinj)) +- Fix NoMethodError when a 2 segment token is missing 'alg' header [#502](https://github.com/jwt/ruby-jwt/pull/502) ([@cmrd-senya](https://github.com/cmrd-senya)) + +## [v2.4.1](https://github.com/jwt/ruby-jwt/tree/v2.4.1) (2022-06-07) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.4.0...v2.4.1) + +**Fixes and enhancements:** + +- Raise JWT::DecodeError on invalid signature [\#484](https://github.com/jwt/ruby-jwt/pull/484) ([@freakyfelt!](https://github.com/freakyfelt!)) + +## [v2.4.0](https://github.com/jwt/ruby-jwt/tree/v2.4.0) (2022-06-06) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.3.0...v2.4.0) + +**Features:** + +- Dropped support for Ruby 2.5 and older [#453](https://github.com/jwt/ruby-jwt/pull/453) - ([@anakinj](https://github.com/anakinj)) +- Use Ruby built-in url-safe base64 methods [#454](https://github.com/jwt/ruby-jwt/pull/454) - ([@bdewater](https://github.com/bdewater)) +- Updated rubocop to 1.23.0 [#457](https://github.com/jwt/ruby-jwt/pull/457) - ([@anakinj](https://github.com/anakinj)) +- Add x5c header key finder [#338](https://github.com/jwt/ruby-jwt/pull/338) - ([@bdewater](https://github.com/bdewater)) +- Author driven changelog process [#463](https://github.com/jwt/ruby-jwt/pull/463) - ([@anakinj](https://github.com/anakinj)) +- Allow regular expressions and procs to verify issuer [\#437](https://github.com/jwt/ruby-jwt/pull/437) ([rewritten](https://github.com/rewritten)) +- Add Support to be able to verify from multiple keys [\#425](https://github.com/jwt/ruby-jwt/pull/425) ([ritikesh](https://github.com/ritikesh)) + +**Fixes and enhancements:** + +- Readme: Typo fix re MissingRequiredClaim [\#451](https://github.com/jwt/ruby-jwt/pull/451) ([antonmorant](https://github.com/antonmorant)) +- Fix RuboCop TODOs [\#476](https://github.com/jwt/ruby-jwt/pull/476) ([typhoon2099](https://github.com/typhoon2099)) +- Make specific algorithms in README linkable [\#472](https://github.com/jwt/ruby-jwt/pull/472) ([milieu](https://github.com/milieu)) +- Update note about supported JWK types [\#475](https://github.com/jwt/ruby-jwt/pull/475) ([dpashkevich](https://github.com/dpashkevich)) +- Create CODE_OF_CONDUCT.md [\#449](https://github.com/jwt/ruby-jwt/pull/449) ([loic5](https://github.com/loic5)) + +## [v2.3.0](https://github.com/jwt/ruby-jwt/tree/v2.3.0) (2021-10-03) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.2.3...v2.3.0) + +**Closed issues:** + +- \[SECURITY\] Algorithm Confusion Through kid Header [\#440](https://github.com/jwt/ruby-jwt/issues/440) +- JWT to memory [\#436](https://github.com/jwt/ruby-jwt/issues/436) +- ArgumentError: wrong number of arguments \(given 2, expected 1\) [\#429](https://github.com/jwt/ruby-jwt/issues/429) +- HMAC section of README outdated [\#421](https://github.com/jwt/ruby-jwt/issues/421) +- NoMethodError: undefined method `zero?' for nil:NilClass if JWT has no 'alg' field [\#410](https://github.com/jwt/ruby-jwt/issues/410) +- Release new version [\#409](https://github.com/jwt/ruby-jwt/issues/409) +- NameError: uninitialized constant JWT::JWK [\#403](https://github.com/jwt/ruby-jwt/issues/403) + +**Merged pull requests:** + +- Release 2.3.0 [\#448](https://github.com/jwt/ruby-jwt/pull/448) ([excpt](https://github.com/excpt)) +- Fix Style/MultilineIfModifier issues [\#447](https://github.com/jwt/ruby-jwt/pull/447) ([anakinj](https://github.com/anakinj)) +- feat\(EdDSA\): Accept EdDSA as algorithm header [\#446](https://github.com/jwt/ruby-jwt/pull/446) ([Pierre-Michard](https://github.com/Pierre-Michard)) +- Pass kid param through JWT::JWK.create_from [\#445](https://github.com/jwt/ruby-jwt/pull/445) ([shaun-guth-allscripts](https://github.com/shaun-guth-allscripts)) +- fix document about passing JWKs as a simple Hash [\#443](https://github.com/jwt/ruby-jwt/pull/443) ([takayamaki](https://github.com/takayamaki)) +- Tests for mixing JWK keys with mismatching algorithms [\#441](https://github.com/jwt/ruby-jwt/pull/441) ([anakinj](https://github.com/anakinj)) +- verify_claims test shouldnt be within the verify_sub test [\#431](https://github.com/jwt/ruby-jwt/pull/431) ([andyjdavis](https://github.com/andyjdavis)) +- Allow decode options to specify required claims [\#430](https://github.com/jwt/ruby-jwt/pull/430) ([andyjdavis](https://github.com/andyjdavis)) +- Fix OpenSSL::PKey::EC public_key handing in tests [\#427](https://github.com/jwt/ruby-jwt/pull/427) ([anakinj](https://github.com/anakinj)) +- Add documentation for find_key [\#426](https://github.com/jwt/ruby-jwt/pull/426) ([ritikesh](https://github.com/ritikesh)) +- Give ruby 3.0 as a string to avoid number formatting issues [\#424](https://github.com/jwt/ruby-jwt/pull/424) ([anakinj](https://github.com/anakinj)) +- Tests for iat verification behaviour [\#423](https://github.com/jwt/ruby-jwt/pull/423) ([anakinj](https://github.com/anakinj)) +- Remove HMAC with nil secret from documentation [\#422](https://github.com/jwt/ruby-jwt/pull/422) ([boardfish](https://github.com/boardfish)) +- Update broken link in README [\#420](https://github.com/jwt/ruby-jwt/pull/420) ([severin](https://github.com/severin)) +- Add metadata for RubyGems [\#418](https://github.com/jwt/ruby-jwt/pull/418) ([nickhammond](https://github.com/nickhammond)) +- Fixed a typo about class name [\#417](https://github.com/jwt/ruby-jwt/pull/417) ([mai-f](https://github.com/mai-f)) +- Fix references for v2.2.3 on CHANGELOG [\#416](https://github.com/jwt/ruby-jwt/pull/416) ([vyper](https://github.com/vyper)) +- Raise IncorrectAlgorithm if token has no alg header [\#411](https://github.com/jwt/ruby-jwt/pull/411) ([bouk](https://github.com/bouk)) + +## [v2.2.3](https://github.com/jwt/ruby-jwt/tree/v2.2.3) (2021-04-19) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.2.2...v2.2.3) + +**Implemented enhancements:** + +- Verify algorithm before evaluating keyfinder [\#343](https://github.com/jwt/ruby-jwt/issues/343) +- Why jwt depends on json \< 2.0 ? [\#179](https://github.com/jwt/ruby-jwt/issues/179) +- Support for JWK in-lieu of rsa_public [\#158](https://github.com/jwt/ruby-jwt/issues/158) +- Fix rspec `raise_error` warning [\#413](https://github.com/jwt/ruby-jwt/pull/413) ([excpt](https://github.com/excpt)) +- Add support for JWKs with HMAC key type. [\#372](https://github.com/jwt/ruby-jwt/pull/372) ([phlegx](https://github.com/phlegx)) +- Improve 'none' algorithm handling [\#365](https://github.com/jwt/ruby-jwt/pull/365) ([danleyden](https://github.com/danleyden)) +- Handle parsed JSON JWKS input with string keys [\#348](https://github.com/jwt/ruby-jwt/pull/348) ([martinemde](https://github.com/martinemde)) +- Allow Numeric values during encoding [\#327](https://github.com/jwt/ruby-jwt/pull/327) ([fanfilmu](https://github.com/fanfilmu)) + +**Closed issues:** + +- "Signature verification raised", yet jwt.io says "Signature Verified" [\#401](https://github.com/jwt/ruby-jwt/issues/401) +- truffleruby-head build is failing [\#396](https://github.com/jwt/ruby-jwt/issues/396) +- JWT::JWK::EC needs `require 'forwardable'` [\#392](https://github.com/jwt/ruby-jwt/issues/392) +- How to use a 'signing key' as used by next-auth [\#389](https://github.com/jwt/ruby-jwt/issues/389) +- undefined method `verify' for nil:NilClass when validate a JWT with JWK [\#383](https://github.com/jwt/ruby-jwt/issues/383) +- Make specifying "algorithm" optional on decode [\#380](https://github.com/jwt/ruby-jwt/issues/380) +- ADFS created access tokens can't be validated due to missing 'kid' header [\#370](https://github.com/jwt/ruby-jwt/issues/370) +- new version? [\#355](https://github.com/jwt/ruby-jwt/issues/355) +- JWT gitlab OmniAuth provider setup support [\#354](https://github.com/jwt/ruby-jwt/issues/354) +- Release with support for RSA.import for ruby \< 2.4 hasn't been released [\#347](https://github.com/jwt/ruby-jwt/issues/347) +- cannot load such file -- jwt [\#339](https://github.com/jwt/ruby-jwt/issues/339) + +**Merged pull requests:** + +- Prepare 2.2.3 release [\#415](https://github.com/jwt/ruby-jwt/pull/415) ([excpt](https://github.com/excpt)) +- Remove codeclimate code coverage dev dependency [\#414](https://github.com/jwt/ruby-jwt/pull/414) ([excpt](https://github.com/excpt)) +- Add forwardable dependency [\#408](https://github.com/jwt/ruby-jwt/pull/408) ([anakinj](https://github.com/anakinj)) +- Ignore casing of algorithm [\#405](https://github.com/jwt/ruby-jwt/pull/405) ([johnnyshields](https://github.com/johnnyshields)) +- Document function and add tests for verify claims method [\#404](https://github.com/jwt/ruby-jwt/pull/404) ([yasonk](https://github.com/yasonk)) +- documenting calling verify_jti callback with 2 arguments in the readme [\#402](https://github.com/jwt/ruby-jwt/pull/402) ([HoneyryderChuck](https://github.com/HoneyryderChuck)) +- Target the master branch on the build status badge [\#399](https://github.com/jwt/ruby-jwt/pull/399) ([anakinj](https://github.com/anakinj)) +- Improving the local development experience [\#397](https://github.com/jwt/ruby-jwt/pull/397) ([anakinj](https://github.com/anakinj)) +- Fix sourcelevel broken links [\#395](https://github.com/jwt/ruby-jwt/pull/395) ([anakinj](https://github.com/anakinj)) +- Don't recommend installing gem with sudo [\#391](https://github.com/jwt/ruby-jwt/pull/391) ([tjschuck](https://github.com/tjschuck)) +- Enable rubocop locally and on ci [\#390](https://github.com/jwt/ruby-jwt/pull/390) ([anakinj](https://github.com/anakinj)) +- Ci and test cleanup [\#387](https://github.com/jwt/ruby-jwt/pull/387) ([anakinj](https://github.com/anakinj)) +- Make JWT::JWK::EC compatible with Ruby 2.3 [\#386](https://github.com/jwt/ruby-jwt/pull/386) ([anakinj](https://github.com/anakinj)) +- Support JWKs for pre 2.3 rubies [\#382](https://github.com/jwt/ruby-jwt/pull/382) ([anakinj](https://github.com/anakinj)) +- Replace Travis CI with GitHub Actions \(also favor openssl/rbnacl combinations over rails compatibility tests\) [\#381](https://github.com/jwt/ruby-jwt/pull/381) ([anakinj](https://github.com/anakinj)) +- Add auth0 sponsor message [\#379](https://github.com/jwt/ruby-jwt/pull/379) ([excpt](https://github.com/excpt)) +- Adapt HMAC to JWK RSA code style. [\#378](https://github.com/jwt/ruby-jwt/pull/378) ([phlegx](https://github.com/phlegx)) +- Disable Rails cops [\#376](https://github.com/jwt/ruby-jwt/pull/376) ([anakinj](https://github.com/anakinj)) +- Support exporting RSA JWK private keys [\#375](https://github.com/jwt/ruby-jwt/pull/375) ([anakinj](https://github.com/anakinj)) +- Ebert is SourceLevel nowadays [\#374](https://github.com/jwt/ruby-jwt/pull/374) ([anakinj](https://github.com/anakinj)) +- Add support for JWKs with EC key type [\#371](https://github.com/jwt/ruby-jwt/pull/371) ([richardlarocque](https://github.com/richardlarocque)) +- Add Truffleruby head to CI [\#368](https://github.com/jwt/ruby-jwt/pull/368) ([gogainda](https://github.com/gogainda)) +- Add more docs about JWK support [\#341](https://github.com/jwt/ruby-jwt/pull/341) ([take](https://github.com/take)) + +## [v2.2.2](https://github.com/jwt/ruby-jwt/tree/v2.2.2) (2020-08-18) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.2.1...v2.2.2) + +**Implemented enhancements:** + +- JWK does not decode. [\#332](https://github.com/jwt/ruby-jwt/issues/332) +- Inconsistent use of symbol and string keys in args \(exp and alrogithm\). [\#331](https://github.com/jwt/ruby-jwt/issues/331) +- Pin simplecov to \< 0.18 [\#356](https://github.com/jwt/ruby-jwt/pull/356) ([anakinj](https://github.com/anakinj)) +- verifies algorithm before evaluating keyfinder [\#346](https://github.com/jwt/ruby-jwt/pull/346) ([jb08](https://github.com/jb08)) +- Update Rails 6 appraisal to use actual release version [\#336](https://github.com/jwt/ruby-jwt/pull/336) ([smudge](https://github.com/smudge)) +- Update Travis [\#326](https://github.com/jwt/ruby-jwt/pull/326) ([berkos](https://github.com/berkos)) +- Improvement/encode hmac without key [\#312](https://github.com/jwt/ruby-jwt/pull/312) ([JotaSe](https://github.com/JotaSe)) + +**Fixed bugs:** + +- v2.2.1 warning: already initialized constant JWT Error [\#335](https://github.com/jwt/ruby-jwt/issues/335) +- 2.2.1 is no longer raising `JWT::DecodeError` on `nil` verification key [\#328](https://github.com/jwt/ruby-jwt/issues/328) +- Fix algorithm picking from decode options [\#359](https://github.com/jwt/ruby-jwt/pull/359) ([excpt](https://github.com/excpt)) +- Raise error when verification key is empty [\#358](https://github.com/jwt/ruby-jwt/pull/358) ([anakinj](https://github.com/anakinj)) + +**Closed issues:** + +- JWT RSA: is it possible to encrypt using the public key? [\#366](https://github.com/jwt/ruby-jwt/issues/366) +- Example unsigned token that bypasses verification [\#364](https://github.com/jwt/ruby-jwt/issues/364) +- Verify exp claim/field even if it's not present [\#363](https://github.com/jwt/ruby-jwt/issues/363) +- Decode any token [\#360](https://github.com/jwt/ruby-jwt/issues/360) +- \[question\] example of using a pub/priv keys for signing? [\#351](https://github.com/jwt/ruby-jwt/issues/351) +- JWT::ExpiredSignature raised for non-JSON payloads [\#350](https://github.com/jwt/ruby-jwt/issues/350) +- verify_aud only verifies that at least one aud is expected [\#345](https://github.com/jwt/ruby-jwt/issues/345) +- Sinatra 4.90s TTFB [\#344](https://github.com/jwt/ruby-jwt/issues/344) +- How to Logout [\#342](https://github.com/jwt/ruby-jwt/issues/342) +- jwt token decoding even when wrong token is provided for some letters [\#337](https://github.com/jwt/ruby-jwt/issues/337) +- Need to use `symbolize_keys` everywhere! [\#330](https://github.com/jwt/ruby-jwt/issues/330) +- eval\(\) used in Forwardable limits usage in iOS App Store [\#324](https://github.com/jwt/ruby-jwt/issues/324) +- HS512256 OpenSSL Exception: First num too large [\#322](https://github.com/jwt/ruby-jwt/issues/322) +- Can we change the separator character? [\#321](https://github.com/jwt/ruby-jwt/issues/321) +- Verifying iat without leeway may break with poorly synced clocks [\#319](https://github.com/jwt/ruby-jwt/issues/319) +- Adding support for 'hd' hosted domain string [\#314](https://github.com/jwt/ruby-jwt/issues/314) +- There is no "typ" header in version 2.0.0 [\#233](https://github.com/jwt/ruby-jwt/issues/233) + +**Merged pull requests:** + +- Release v2.2.2 [\#367](https://github.com/jwt/ruby-jwt/pull/367) ([excpt](https://github.com/excpt)) +- Fix 'already initialized constant JWT Error' [\#357](https://github.com/jwt/ruby-jwt/pull/357) ([excpt](https://github.com/excpt)) +- Support RSA.import for all Ruby versions. [\#333](https://github.com/jwt/ruby-jwt/pull/333) ([rabajaj0509](https://github.com/rabajaj0509)) +- Removed forwardable dependency [\#325](https://github.com/jwt/ruby-jwt/pull/325) ([anakinj](https://github.com/anakinj)) + +## [v2.2.1](https://github.com/jwt/ruby-jwt/tree/v2.2.1) (2019-05-24) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.2.0...v2.2.1) + +**Fixed bugs:** + +- need to `require 'forwardable'` to use `Forwardable` [\#316](https://github.com/jwt/ruby-jwt/issues/316) +- Add forwardable dependency for JWK RSA KeyFinder [\#317](https://github.com/jwt/ruby-jwt/pull/317) ([excpt](https://github.com/excpt)) + +**Merged pull requests:** + +- Release 2.2.1 [\#318](https://github.com/jwt/ruby-jwt/pull/318) ([excpt](https://github.com/excpt)) + +## [v2.2.0](https://github.com/jwt/ruby-jwt/tree/v2.2.0) (2019-05-23) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.2.0.pre.beta.0...v2.2.0) + +**Closed issues:** + +- misspelled es512 curve name [\#310](https://github.com/jwt/ruby-jwt/issues/310) +- With Base64 decode i can read the hashed content [\#306](https://github.com/jwt/ruby-jwt/issues/306) +- hide post-it's for graphviz views [\#303](https://github.com/jwt/ruby-jwt/issues/303) + +**Merged pull requests:** + +- Release 2.2.0 [\#315](https://github.com/jwt/ruby-jwt/pull/315) ([excpt](https://github.com/excpt)) + +## [v2.2.0.pre.beta.0](https://github.com/jwt/ruby-jwt/tree/v2.2.0.pre.beta.0) (2019-03-20) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.1.0...v2.2.0.pre.beta.0) + +**Implemented enhancements:** + +- Use iat_leeway option [\#273](https://github.com/jwt/ruby-jwt/issues/273) +- Use of global state in latest version breaks thread safety of JWT.decode [\#268](https://github.com/jwt/ruby-jwt/issues/268) +- JSON support [\#246](https://github.com/jwt/ruby-jwt/issues/246) +- Change the Github homepage URL to https [\#301](https://github.com/jwt/ruby-jwt/pull/301) ([ekohl](https://github.com/ekohl)) +- Fix Salt length for conformance with PS family specification. [\#300](https://github.com/jwt/ruby-jwt/pull/300) ([tobypinder](https://github.com/tobypinder)) +- Add support for Ruby 2.6 [\#299](https://github.com/jwt/ruby-jwt/pull/299) ([bustikiller](https://github.com/bustikiller)) +- update homepage in gemspec to use HTTPS [\#298](https://github.com/jwt/ruby-jwt/pull/298) ([evgeni](https://github.com/evgeni)) +- Make sure alg parameter value isn't added twice [\#297](https://github.com/jwt/ruby-jwt/pull/297) ([korstiaan](https://github.com/korstiaan)) +- Claims Validation [\#295](https://github.com/jwt/ruby-jwt/pull/295) ([jamesstonehill](https://github.com/jamesstonehill)) +- JWT::Encode refactorings, alg and exp related bugfixes [\#293](https://github.com/jwt/ruby-jwt/pull/293) ([anakinj](https://github.com/anakinj)) +- Proposal of simple JWK support [\#289](https://github.com/jwt/ruby-jwt/pull/289) ([anakinj](https://github.com/anakinj)) +- Add RSASSA-PSS signature signing support [\#285](https://github.com/jwt/ruby-jwt/pull/285) ([oliver-hohn](https://github.com/oliver-hohn)) +- Add note about using a hard coded algorithm in README [\#280](https://github.com/jwt/ruby-jwt/pull/280) ([revodoge](https://github.com/revodoge)) +- Add Appraisal support [\#278](https://github.com/jwt/ruby-jwt/pull/278) ([olbrich](https://github.com/olbrich)) +- Fix decode threading issue [\#269](https://github.com/jwt/ruby-jwt/pull/269) ([ab320012](https://github.com/ab320012)) +- Removed leeway from verify_iat [\#257](https://github.com/jwt/ruby-jwt/pull/257) ([ab320012](https://github.com/ab320012)) + +**Fixed bugs:** + +- Inconsistent handling of payload claim data types [\#282](https://github.com/jwt/ruby-jwt/issues/282) +- Issued at validation [\#247](https://github.com/jwt/ruby-jwt/issues/247) +- Fix bug and simplify segment validation [\#292](https://github.com/jwt/ruby-jwt/pull/292) ([anakinj](https://github.com/anakinj)) + +**Security fixes:** + +- Decoding JWT with ES256 and secp256k1 curve [\#277](https://github.com/jwt/ruby-jwt/issues/277) + +**Closed issues:** + +- RS256, public and private keys [\#291](https://github.com/jwt/ruby-jwt/issues/291) +- Allow passing current time to `decode` [\#288](https://github.com/jwt/ruby-jwt/issues/288) +- Verify exp claim without verifying jwt [\#281](https://github.com/jwt/ruby-jwt/issues/281) +- Audience as an array - how to specify? [\#276](https://github.com/jwt/ruby-jwt/issues/276) +- signature validation using decode method for JWT [\#271](https://github.com/jwt/ruby-jwt/issues/271) +- JWT is easily breakable [\#267](https://github.com/jwt/ruby-jwt/issues/267) +- Ruby JWT Token [\#265](https://github.com/jwt/ruby-jwt/issues/265) +- ECDSA supported algorithms constant is defined as a string, not an array [\#264](https://github.com/jwt/ruby-jwt/issues/264) +- NoMethodError: undefined method `group' for \ [\#261](https://github.com/jwt/ruby-jwt/issues/261) +- 'DecodeError'will replace 'ExpiredSignature' [\#260](https://github.com/jwt/ruby-jwt/issues/260) +- TypeError: no implicit conversion of OpenSSL::PKey::RSA into String [\#259](https://github.com/jwt/ruby-jwt/issues/259) +- NameError: uninitialized constant JWT::Algos::Eddsa::RbNaCl [\#258](https://github.com/jwt/ruby-jwt/issues/258) +- Get new token if curren token expired [\#256](https://github.com/jwt/ruby-jwt/issues/256) +- Infer algorithm from header [\#254](https://github.com/jwt/ruby-jwt/issues/254) +- Why is the result of decode is an array? [\#252](https://github.com/jwt/ruby-jwt/issues/252) +- Add support for headless token [\#251](https://github.com/jwt/ruby-jwt/issues/251) +- Leeway or exp_leeway [\#215](https://github.com/jwt/ruby-jwt/issues/215) +- Could you describe purpose of cert fixtures and their cryptokey lengths. [\#185](https://github.com/jwt/ruby-jwt/issues/185) + +**Merged pull requests:** + +- Release v2.2.0-beta.0 [\#302](https://github.com/jwt/ruby-jwt/pull/302) ([excpt](https://github.com/excpt)) +- Misc config improvements [\#296](https://github.com/jwt/ruby-jwt/pull/296) ([jamesstonehill](https://github.com/jamesstonehill)) +- Fix JSON conflict between \#293 and \#292 [\#294](https://github.com/jwt/ruby-jwt/pull/294) ([anakinj](https://github.com/anakinj)) +- Drop Ruby 2.2 from test matrix [\#290](https://github.com/jwt/ruby-jwt/pull/290) ([anakinj](https://github.com/anakinj)) +- Remove broken reek config [\#283](https://github.com/jwt/ruby-jwt/pull/283) ([excpt](https://github.com/excpt)) +- Add missing test, Update common files [\#275](https://github.com/jwt/ruby-jwt/pull/275) ([excpt](https://github.com/excpt)) +- Remove iat_leeway option [\#274](https://github.com/jwt/ruby-jwt/pull/274) ([wohlgejm](https://github.com/wohlgejm)) +- improving code quality of jwt module [\#266](https://github.com/jwt/ruby-jwt/pull/266) ([ab320012](https://github.com/ab320012)) +- fixed ECDSA supported versions const [\#263](https://github.com/jwt/ruby-jwt/pull/263) ([starbeast](https://github.com/starbeast)) +- Added my name to contributor list [\#262](https://github.com/jwt/ruby-jwt/pull/262) ([ab320012](https://github.com/ab320012)) +- Use `Class#new` Shorthand For Error Subclasses [\#255](https://github.com/jwt/ruby-jwt/pull/255) ([akabiru](https://github.com/akabiru)) +- \[CI\] Test against Ruby 2.5 [\#253](https://github.com/jwt/ruby-jwt/pull/253) ([nicolasleger](https://github.com/nicolasleger)) +- Fix README [\#250](https://github.com/jwt/ruby-jwt/pull/250) ([rono23](https://github.com/rono23)) +- Fix link format [\#248](https://github.com/jwt/ruby-jwt/pull/248) ([y-yagi](https://github.com/y-yagi)) + +## [v2.1.0](https://github.com/jwt/ruby-jwt/tree/v2.1.0) (2017-10-06) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.0.0...v2.1.0) + +**Implemented enhancements:** + +- Ed25519 support planned? [\#217](https://github.com/jwt/ruby-jwt/issues/217) +- Verify JTI Proc [\#207](https://github.com/jwt/ruby-jwt/issues/207) +- Allow a list of algorithms for decode [\#241](https://github.com/jwt/ruby-jwt/pull/241) ([lautis](https://github.com/lautis)) +- verify takes 2 params, second being payload closes: \#207 [\#238](https://github.com/jwt/ruby-jwt/pull/238) ([ab320012](https://github.com/ab320012)) +- simplified logic for keyfinder [\#237](https://github.com/jwt/ruby-jwt/pull/237) ([ab320012](https://github.com/ab320012)) +- Show backtrace if rbnacl-libsodium not loaded [\#231](https://github.com/jwt/ruby-jwt/pull/231) ([buzztaiki](https://github.com/buzztaiki)) +- Support for ED25519 [\#229](https://github.com/jwt/ruby-jwt/pull/229) ([ab320012](https://github.com/ab320012)) + +**Fixed bugs:** + +- JWT.encode failing on encode for string [\#235](https://github.com/jwt/ruby-jwt/issues/235) +- The README says it uses an algorithm by default [\#226](https://github.com/jwt/ruby-jwt/issues/226) +- Fix string payload issue [\#236](https://github.com/jwt/ruby-jwt/pull/236) ([excpt](https://github.com/excpt)) + +**Security fixes:** + +- Add HS256 algorithm to decode default options [\#228](https://github.com/jwt/ruby-jwt/pull/228) ([marcoadkins](https://github.com/marcoadkins)) + +**Closed issues:** + +- Change from 1.5.6 to 2.0.0 and appears a "Completed 401 Unauthorized" [\#240](https://github.com/jwt/ruby-jwt/issues/240) +- Why doesn't the decode function use a default algorithm? [\#227](https://github.com/jwt/ruby-jwt/issues/227) + +**Merged pull requests:** + +- Release 2.1.0 preparations [\#243](https://github.com/jwt/ruby-jwt/pull/243) ([excpt](https://github.com/excpt)) +- Update README.md [\#242](https://github.com/jwt/ruby-jwt/pull/242) ([excpt](https://github.com/excpt)) +- Update ebert configuration [\#232](https://github.com/jwt/ruby-jwt/pull/232) ([excpt](https://github.com/excpt)) +- added algos/strategy classes + structs for inputs [\#230](https://github.com/jwt/ruby-jwt/pull/230) ([ab320012](https://github.com/ab320012)) + +## [v2.0.0](https://github.com/jwt/ruby-jwt/tree/v2.0.0) (2017-09-03) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.0.0.beta1...v2.0.0) + +**Fixed bugs:** + +- Support versions outside 2.1 [\#209](https://github.com/jwt/ruby-jwt/issues/209) +- Verifying expiration without leeway throws exception [\#206](https://github.com/jwt/ruby-jwt/issues/206) +- Ruby interpreter warning [\#200](https://github.com/jwt/ruby-jwt/issues/200) +- TypeError: no implicit conversion of String into Integer [\#188](https://github.com/jwt/ruby-jwt/issues/188) +- Fix JWT.encode\(nil\) [\#203](https://github.com/jwt/ruby-jwt/pull/203) ([tmm1](https://github.com/tmm1)) + +**Closed issues:** + +- Possibility to disable claim verifications [\#222](https://github.com/jwt/ruby-jwt/issues/222) +- Proper way to verify Firebase id tokens [\#216](https://github.com/jwt/ruby-jwt/issues/216) + +**Merged pull requests:** + +- Release 2.0.0 preparations :\) [\#225](https://github.com/jwt/ruby-jwt/pull/225) ([excpt](https://github.com/excpt)) +- Skip 'exp' claim validation for array payloads [\#224](https://github.com/jwt/ruby-jwt/pull/224) ([excpt](https://github.com/excpt)) +- Use a default leeway of 0 [\#223](https://github.com/jwt/ruby-jwt/pull/223) ([travisofthenorth](https://github.com/travisofthenorth)) +- Fix reported codesmells [\#221](https://github.com/jwt/ruby-jwt/pull/221) ([excpt](https://github.com/excpt)) +- Add fancy gem version badge [\#220](https://github.com/jwt/ruby-jwt/pull/220) ([excpt](https://github.com/excpt)) +- Add missing dist option to .travis.yml [\#219](https://github.com/jwt/ruby-jwt/pull/219) ([excpt](https://github.com/excpt)) +- Fix ruby version requirements in gemspec file [\#218](https://github.com/jwt/ruby-jwt/pull/218) ([excpt](https://github.com/excpt)) +- Fix a little typo in the readme [\#214](https://github.com/jwt/ruby-jwt/pull/214) ([RyanBrushett](https://github.com/RyanBrushett)) +- Update README.md [\#212](https://github.com/jwt/ruby-jwt/pull/212) ([zuzannast](https://github.com/zuzannast)) +- Fix typo in HS512256 algorithm description [\#211](https://github.com/jwt/ruby-jwt/pull/211) ([ojab](https://github.com/ojab)) +- Allow configuration of multiple acceptable issuers [\#210](https://github.com/jwt/ruby-jwt/pull/210) ([ojab](https://github.com/ojab)) +- Enforce `exp` to be an `Integer` [\#205](https://github.com/jwt/ruby-jwt/pull/205) ([lucasmazza](https://github.com/lucasmazza)) +- ruby 1.9.3 support message upd [\#204](https://github.com/jwt/ruby-jwt/pull/204) ([maokomioko](https://github.com/maokomioko)) + +## [v2.0.0.beta1](https://github.com/jwt/ruby-jwt/tree/v2.0.0.beta1) (2017-02-27) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v1.5.6...v2.0.0.beta1) + +**Implemented enhancements:** + +- Error with method sign for String [\#171](https://github.com/jwt/ruby-jwt/issues/171) +- Refactor the encondig code [\#121](https://github.com/jwt/ruby-jwt/issues/121) +- Refactor [\#196](https://github.com/jwt/ruby-jwt/pull/196) ([EmilioCristalli](https://github.com/EmilioCristalli)) +- Move signature logic to its own module [\#195](https://github.com/jwt/ruby-jwt/pull/195) ([EmilioCristalli](https://github.com/EmilioCristalli)) +- Add options for claim-specific leeway [\#187](https://github.com/jwt/ruby-jwt/pull/187) ([EmilioCristalli](https://github.com/EmilioCristalli)) +- Add user friendly encode error if private key is a String, \#171 [\#176](https://github.com/jwt/ruby-jwt/pull/176) ([ogonki-vetochki](https://github.com/ogonki-vetochki)) +- Return empty string if signature less than byte_size \#155 [\#175](https://github.com/jwt/ruby-jwt/pull/175) ([ogonki-vetochki](https://github.com/ogonki-vetochki)) +- Remove 'typ' optional parameter [\#174](https://github.com/jwt/ruby-jwt/pull/174) ([ogonki-vetochki](https://github.com/ogonki-vetochki)) +- Pass payload to keyfinder [\#172](https://github.com/jwt/ruby-jwt/pull/172) ([CodeMonkeySteve](https://github.com/CodeMonkeySteve)) +- Use RbNaCl for HMAC if available with fallback to OpenSSL [\#149](https://github.com/jwt/ruby-jwt/pull/149) ([mwpastore](https://github.com/mwpastore)) + +**Fixed bugs:** + +- ruby-jwt::raw_to_asn1: Fails for signatures less than byte_size [\#155](https://github.com/jwt/ruby-jwt/issues/155) +- The leeway parameter is applies to all time based verifications [\#129](https://github.com/jwt/ruby-jwt/issues/129) +- Make algorithm option required to verify signature [\#184](https://github.com/jwt/ruby-jwt/pull/184) ([EmilioCristalli](https://github.com/EmilioCristalli)) +- Validate audience when payload is a scalar and options is an array [\#183](https://github.com/jwt/ruby-jwt/pull/183) ([steti](https://github.com/steti)) + +**Closed issues:** + +- Different encoded value between servers with same password [\#197](https://github.com/jwt/ruby-jwt/issues/197) +- Signature is different at each run [\#190](https://github.com/jwt/ruby-jwt/issues/190) +- Include custom headers with password [\#189](https://github.com/jwt/ruby-jwt/issues/189) +- can't create token - 'NotImplementedError: Unsupported signing method' [\#186](https://github.com/jwt/ruby-jwt/issues/186) +- Cannot verify JWT at all?? [\#177](https://github.com/jwt/ruby-jwt/issues/177) +- verify_iss: true is raising JWT::DecodeError instead of JWT::InvalidIssuerError [\#170](https://github.com/jwt/ruby-jwt/issues/170) + +**Merged pull requests:** + +- Version bump 2.0.0.beta1 [\#199](https://github.com/jwt/ruby-jwt/pull/199) ([excpt](https://github.com/excpt)) +- Update CHANGELOG.md and minor fixes [\#198](https://github.com/jwt/ruby-jwt/pull/198) ([excpt](https://github.com/excpt)) +- Add Codacy coverage reporter [\#194](https://github.com/jwt/ruby-jwt/pull/194) ([excpt](https://github.com/excpt)) +- Add minimum required ruby version to gemspec [\#193](https://github.com/jwt/ruby-jwt/pull/193) ([excpt](https://github.com/excpt)) +- Code smell fixes [\#192](https://github.com/jwt/ruby-jwt/pull/192) ([excpt](https://github.com/excpt)) +- Version bump to 2.0.0.dev [\#191](https://github.com/jwt/ruby-jwt/pull/191) ([excpt](https://github.com/excpt)) +- Basic encode module refactoring \#121 [\#182](https://github.com/jwt/ruby-jwt/pull/182) ([ogonki-vetochki](https://github.com/ogonki-vetochki)) +- Fix travis ci build configuration [\#181](https://github.com/jwt/ruby-jwt/pull/181) ([excpt](https://github.com/excpt)) +- Fix travis ci build configuration [\#180](https://github.com/jwt/ruby-jwt/pull/180) ([excpt](https://github.com/excpt)) +- Fix typo in README [\#178](https://github.com/jwt/ruby-jwt/pull/178) ([tomeduarte](https://github.com/tomeduarte)) +- Fix code style [\#173](https://github.com/jwt/ruby-jwt/pull/173) ([excpt](https://github.com/excpt)) +- Fixed a typo in a spec name [\#169](https://github.com/jwt/ruby-jwt/pull/169) ([mingan](https://github.com/mingan)) + +## [v1.5.6](https://github.com/jwt/ruby-jwt/tree/v1.5.6) (2016-09-19) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v1.5.5...v1.5.6) + +**Fixed bugs:** + +- Fix missing symbol handling in aud verify code [\#166](https://github.com/jwt/ruby-jwt/pull/166) ([excpt](https://github.com/excpt)) + +**Merged pull requests:** + +- Update changelog [\#168](https://github.com/jwt/ruby-jwt/pull/168) ([excpt](https://github.com/excpt)) +- Fix rubocop code smells [\#167](https://github.com/jwt/ruby-jwt/pull/167) ([excpt](https://github.com/excpt)) + +## [v1.5.5](https://github.com/jwt/ruby-jwt/tree/v1.5.5) (2016-09-16) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v1.5.4...v1.5.5) + +**Implemented enhancements:** + +- JWT.decode always raises JWT::ExpiredSignature for tokens created with Time objects passed as the `exp` parameter [\#148](https://github.com/jwt/ruby-jwt/issues/148) + +**Fixed bugs:** + +- expiration check does not give "Signature has expired" error for the exact time of expiration [\#157](https://github.com/jwt/ruby-jwt/issues/157) +- JTI claim broken? [\#152](https://github.com/jwt/ruby-jwt/issues/152) +- Audience Claim broken? [\#151](https://github.com/jwt/ruby-jwt/issues/151) +- 1.5.3 breaks compatibility with 1.5.2 [\#133](https://github.com/jwt/ruby-jwt/issues/133) +- Version 1.5.3 breaks 1.9.3 compatibility, but not documented as such [\#132](https://github.com/jwt/ruby-jwt/issues/132) +- Fix: exp claim check [\#161](https://github.com/jwt/ruby-jwt/pull/161) ([excpt](https://github.com/excpt)) + +**Security fixes:** + +- \[security\] Signature verified after expiration/sub/iss checks [\#153](https://github.com/jwt/ruby-jwt/issues/153) +- Signature validation before claim verification [\#160](https://github.com/jwt/ruby-jwt/pull/160) ([excpt](https://github.com/excpt)) + +**Closed issues:** + +- Rendering Json Results in JWT::DecodeError [\#162](https://github.com/jwt/ruby-jwt/issues/162) +- PHP Libraries [\#154](https://github.com/jwt/ruby-jwt/issues/154) +- Is ruby-jwt thread-safe? [\#150](https://github.com/jwt/ruby-jwt/issues/150) +- JWT 1.5.3 [\#143](https://github.com/jwt/ruby-jwt/issues/143) +- gem install v 1.5.3 returns error [\#141](https://github.com/jwt/ruby-jwt/issues/141) +- Adding a CHANGELOG [\#140](https://github.com/jwt/ruby-jwt/issues/140) + +**Merged pull requests:** + +- Bump version [\#165](https://github.com/jwt/ruby-jwt/pull/165) ([excpt](https://github.com/excpt)) +- Improve error message for exp claim in payload [\#164](https://github.com/jwt/ruby-jwt/pull/164) ([excpt](https://github.com/excpt)) +- Fix \#151 and code refactoring [\#163](https://github.com/jwt/ruby-jwt/pull/163) ([excpt](https://github.com/excpt)) +- Create specs for README.md examples [\#159](https://github.com/jwt/ruby-jwt/pull/159) ([excpt](https://github.com/excpt)) +- Tiny Readme Improvement [\#156](https://github.com/jwt/ruby-jwt/pull/156) ([b264](https://github.com/b264)) +- Added test execution to Rakefile [\#147](https://github.com/jwt/ruby-jwt/pull/147) ([jabbrwcky](https://github.com/jabbrwcky)) +- Bump version [\#145](https://github.com/jwt/ruby-jwt/pull/145) ([excpt](https://github.com/excpt)) +- Add a changelog file [\#142](https://github.com/jwt/ruby-jwt/pull/142) ([excpt](https://github.com/excpt)) +- Return decoded_segments [\#139](https://github.com/jwt/ruby-jwt/pull/139) ([akostrikov](https://github.com/akostrikov)) + +## [v1.5.4](https://github.com/jwt/ruby-jwt/tree/v1.5.4) (2016-03-24) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v1.5.3...v1.5.4) + +**Closed issues:** + +- 404 at [https://rubygems.global.ssl.fastly.net/gems/jwt-1.5.3.gem](https://rubygems.global.ssl.fastly.net/gems/jwt-1.5.3.gem) [\#137](https://github.com/jwt/ruby-jwt/issues/137) + +**Merged pull requests:** + +- Update README.md [\#138](https://github.com/jwt/ruby-jwt/pull/138) ([excpt](https://github.com/excpt)) +- Fix base64url_decode [\#136](https://github.com/jwt/ruby-jwt/pull/136) ([excpt](https://github.com/excpt)) +- Fix ruby 1.9.3 compatibility [\#135](https://github.com/jwt/ruby-jwt/pull/135) ([excpt](https://github.com/excpt)) +- iat can be a float value [\#134](https://github.com/jwt/ruby-jwt/pull/134) ([llimllib](https://github.com/llimllib)) + +## [v1.5.3](https://github.com/jwt/ruby-jwt/tree/v1.5.3) (2016-02-24) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-1.5.2...v1.5.3) + +**Implemented enhancements:** + +- Refactor obsolete code for ruby 1.8 support [\#120](https://github.com/jwt/ruby-jwt/issues/120) +- Fix "Rubocop/Metrics/CyclomaticComplexity" issue in lib/jwt.rb [\#106](https://github.com/jwt/ruby-jwt/issues/106) +- Fix "Rubocop/Metrics/CyclomaticComplexity" issue in lib/jwt.rb [\#105](https://github.com/jwt/ruby-jwt/issues/105) +- Allow a proc to be passed for JTI verification [\#126](https://github.com/jwt/ruby-jwt/pull/126) ([yahooguntu](https://github.com/yahooguntu)) +- Relax restrictions on "jti" claim verification [\#113](https://github.com/jwt/ruby-jwt/pull/113) ([lwe](https://github.com/lwe)) + +**Closed issues:** + +- Verifications not functioning in latest release [\#128](https://github.com/jwt/ruby-jwt/issues/128) +- Base64 is generating invalid length base64 strings - cross language interop [\#127](https://github.com/jwt/ruby-jwt/issues/127) +- Digest::Digest is deprecated; use Digest [\#119](https://github.com/jwt/ruby-jwt/issues/119) +- verify_rsa no method 'verify' for class String [\#115](https://github.com/jwt/ruby-jwt/issues/115) +- Add a changelog [\#111](https://github.com/jwt/ruby-jwt/issues/111) + +**Merged pull requests:** + +- Drop ruby 1.9.3 support [\#131](https://github.com/jwt/ruby-jwt/pull/131) ([excpt](https://github.com/excpt)) +- Allow string hash keys in validation configurations [\#130](https://github.com/jwt/ruby-jwt/pull/130) ([tpickett66](https://github.com/tpickett66)) +- Add ruby 2.3.0 for travis ci testing [\#123](https://github.com/jwt/ruby-jwt/pull/123) ([excpt](https://github.com/excpt)) +- Remove obsolete json code [\#122](https://github.com/jwt/ruby-jwt/pull/122) ([excpt](https://github.com/excpt)) +- Add fancy badges to README.md [\#118](https://github.com/jwt/ruby-jwt/pull/118) ([excpt](https://github.com/excpt)) +- Refactor decode and verify functionality [\#117](https://github.com/jwt/ruby-jwt/pull/117) ([excpt](https://github.com/excpt)) +- Drop echoe dependency for gem releases [\#116](https://github.com/jwt/ruby-jwt/pull/116) ([excpt](https://github.com/excpt)) +- Updated readme for iss/aud options [\#114](https://github.com/jwt/ruby-jwt/pull/114) ([ryanmcilmoyl](https://github.com/ryanmcilmoyl)) +- Fix error misspelling [\#112](https://github.com/jwt/ruby-jwt/pull/112) ([kat3kasper](https://github.com/kat3kasper)) + +## [jwt-1.5.2](https://github.com/jwt/ruby-jwt/tree/jwt-1.5.2) (2015-10-27) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-1.5.1...jwt-1.5.2) + +**Implemented enhancements:** + +- Must we specify algorithm when calling decode to avoid vulnerabilities? [\#107](https://github.com/jwt/ruby-jwt/issues/107) +- Code review: Rspec test refactoring [\#85](https://github.com/jwt/ruby-jwt/pull/85) ([excpt](https://github.com/excpt)) + +**Fixed bugs:** + +- aud verifies if aud is passed in, :sub does not [\#102](https://github.com/jwt/ruby-jwt/issues/102) +- iat check does not use leeway so nbf could pass, but iat fail [\#83](https://github.com/jwt/ruby-jwt/issues/83) + +**Closed issues:** + +- Test ticket from Code Climate [\#104](https://github.com/jwt/ruby-jwt/issues/104) +- Test ticket from Code Climate [\#100](https://github.com/jwt/ruby-jwt/issues/100) +- Is it possible to decode the payload without validating the signature? [\#97](https://github.com/jwt/ruby-jwt/issues/97) +- What is audience? [\#96](https://github.com/jwt/ruby-jwt/issues/96) +- Options hash uses both symbols and strings as keys. [\#95](https://github.com/jwt/ruby-jwt/issues/95) + +**Merged pull requests:** + +- Fix incorrect `iat` examples [\#109](https://github.com/jwt/ruby-jwt/pull/109) ([kjwierenga](https://github.com/kjwierenga)) +- Update docs to include instructions for the algorithm parameter. [\#108](https://github.com/jwt/ruby-jwt/pull/108) ([aarongray](https://github.com/aarongray)) +- make sure :sub check behaves like :aud check [\#103](https://github.com/jwt/ruby-jwt/pull/103) ([skippy](https://github.com/skippy)) +- Change hash syntax [\#101](https://github.com/jwt/ruby-jwt/pull/101) ([excpt](https://github.com/excpt)) +- Include LICENSE and README.md in gem [\#99](https://github.com/jwt/ruby-jwt/pull/99) ([bkeepers](https://github.com/bkeepers)) +- Remove unused variable in the sample code. [\#98](https://github.com/jwt/ruby-jwt/pull/98) ([hypermkt](https://github.com/hypermkt)) +- Fix iat claim example [\#94](https://github.com/jwt/ruby-jwt/pull/94) ([larrylv](https://github.com/larrylv)) +- Fix wrong description in README.md [\#93](https://github.com/jwt/ruby-jwt/pull/93) ([larrylv](https://github.com/larrylv)) +- JWT and JWA are now RFC. [\#92](https://github.com/jwt/ruby-jwt/pull/92) ([aj-michael](https://github.com/aj-michael)) +- Update README.md [\#91](https://github.com/jwt/ruby-jwt/pull/91) ([nsarno](https://github.com/nsarno)) +- Fix missing verify parameter in docs [\#90](https://github.com/jwt/ruby-jwt/pull/90) ([ernie](https://github.com/ernie)) +- Iat check uses leeway. [\#89](https://github.com/jwt/ruby-jwt/pull/89) ([aj-michael](https://github.com/aj-michael)) +- nbf check allows exact time matches. [\#88](https://github.com/jwt/ruby-jwt/pull/88) ([aj-michael](https://github.com/aj-michael)) + +## [jwt-1.5.1](https://github.com/jwt/ruby-jwt/tree/jwt-1.5.1) (2015-06-22) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-1.5.0...jwt-1.5.1) + +**Implemented enhancements:** + +- Fix either README or source code [\#78](https://github.com/jwt/ruby-jwt/issues/78) +- Validate against draft 20 [\#38](https://github.com/jwt/ruby-jwt/issues/38) + +**Fixed bugs:** + +- ECDSA signature verification fails for valid tokens [\#84](https://github.com/jwt/ruby-jwt/issues/84) +- Shouldn't verification of additional claims, like iss, aud etc. be enforced when in options? [\#81](https://github.com/jwt/ruby-jwt/issues/81) +- decode fails with 'none' algorithm and verify [\#75](https://github.com/jwt/ruby-jwt/issues/75) + +**Closed issues:** + +- Doc mismatch: uninitialized constant JWT::ExpiredSignature [\#79](https://github.com/jwt/ruby-jwt/issues/79) +- TypeError when specifying a wrong algorithm [\#77](https://github.com/jwt/ruby-jwt/issues/77) +- jti verification doesn't prevent replays [\#73](https://github.com/jwt/ruby-jwt/issues/73) + +**Merged pull requests:** + +- Correctly sign ECDSA JWTs [\#87](https://github.com/jwt/ruby-jwt/pull/87) ([jurriaan](https://github.com/jurriaan)) +- fixed results of decoded tokens in readme [\#86](https://github.com/jwt/ruby-jwt/pull/86) ([piscolomo](https://github.com/piscolomo)) +- Force verification of "iss" and "aud" claims [\#82](https://github.com/jwt/ruby-jwt/pull/82) ([lwe](https://github.com/lwe)) + +## [jwt-1.5.0](https://github.com/jwt/ruby-jwt/tree/jwt-1.5.0) (2015-05-09) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-1.4.1...jwt-1.5.0) + +**Implemented enhancements:** + +- Needs to support asymmetric key signatures over shared secrets [\#46](https://github.com/jwt/ruby-jwt/issues/46) +- Implement Elliptic Curve Crypto Signatures [\#74](https://github.com/jwt/ruby-jwt/pull/74) ([jtdowney](https://github.com/jtdowney)) +- Add an option to verify the signature on decode [\#71](https://github.com/jwt/ruby-jwt/pull/71) ([javawizard](https://github.com/javawizard)) + +**Closed issues:** + +- Check JWT vulnerability [\#76](https://github.com/jwt/ruby-jwt/issues/76) + +**Merged pull requests:** + +- Fixed some examples to make them copy-pastable [\#72](https://github.com/jwt/ruby-jwt/pull/72) ([jer](https://github.com/jer)) + +## [jwt-1.4.1](https://github.com/jwt/ruby-jwt/tree/jwt-1.4.1) (2015-03-12) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-1.4.0...jwt-1.4.1) + +**Fixed bugs:** + +- jti verification not working per the spec [\#68](https://github.com/jwt/ruby-jwt/issues/68) +- Verify ISS should be off by default [\#66](https://github.com/jwt/ruby-jwt/issues/66) + +**Merged pull requests:** + +- Fix \#66 \#68 [\#69](https://github.com/jwt/ruby-jwt/pull/69) ([excpt](https://github.com/excpt)) +- When throwing errors, mention expected/received values [\#65](https://github.com/jwt/ruby-jwt/pull/65) ([rolodato](https://github.com/rolodato)) + +## [jwt-1.4.0](https://github.com/jwt/ruby-jwt/tree/jwt-1.4.0) (2015-03-10) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-1.3.0...jwt-1.4.0) + +**Closed issues:** + +- The behavior using 'json' differs from 'multi_json' [\#41](https://github.com/jwt/ruby-jwt/issues/41) + +**Merged pull requests:** + +- Release 1.4.0 [\#64](https://github.com/jwt/ruby-jwt/pull/64) ([excpt](https://github.com/excpt)) +- Update README.md and remove dead code [\#63](https://github.com/jwt/ruby-jwt/pull/63) ([excpt](https://github.com/excpt)) +- Add 'iat/ aud/ sub/ jti' support for ruby-jwt [\#62](https://github.com/jwt/ruby-jwt/pull/62) ([ZhangHanDong](https://github.com/ZhangHanDong)) +- Add 'iss' support for ruby-jwt [\#61](https://github.com/jwt/ruby-jwt/pull/61) ([ZhangHanDong](https://github.com/ZhangHanDong)) +- Clarify .encode API in README [\#60](https://github.com/jwt/ruby-jwt/pull/60) ([jbodah](https://github.com/jbodah)) + +## [jwt-1.3.0](https://github.com/jwt/ruby-jwt/tree/jwt-1.3.0) (2015-02-24) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-1.2.1...jwt-1.3.0) + +**Closed issues:** + +- Signature Verification to Return Verification Error rather than decode error [\#57](https://github.com/jwt/ruby-jwt/issues/57) +- Incorrect readme for leeway [\#55](https://github.com/jwt/ruby-jwt/issues/55) +- What is the reason behind stripping the = in base64 encoding? [\#54](https://github.com/jwt/ruby-jwt/issues/54) +- Preperations for version 2.x [\#50](https://github.com/jwt/ruby-jwt/issues/50) +- Release a new version [\#47](https://github.com/jwt/ruby-jwt/issues/47) +- Catch up for ActiveWhatever 4.1.1 series [\#40](https://github.com/jwt/ruby-jwt/issues/40) + +**Merged pull requests:** + +- raise verification error for signiture verification [\#58](https://github.com/jwt/ruby-jwt/pull/58) ([punkle](https://github.com/punkle)) +- Added support for not before claim verification [\#56](https://github.com/jwt/ruby-jwt/pull/56) ([punkle](https://github.com/punkle)) + +## [jwt-1.2.1](https://github.com/jwt/ruby-jwt/tree/jwt-1.2.1) (2015-01-22) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-1.2.0...jwt-1.2.1) + +**Closed issues:** + +- JWT.encode\({"exp": 10}, "secret"\) [\#52](https://github.com/jwt/ruby-jwt/issues/52) +- JWT.encode\({"exp": 10}, "secret"\) [\#51](https://github.com/jwt/ruby-jwt/issues/51) + +**Merged pull requests:** + +- Accept expiration claims as string [\#53](https://github.com/jwt/ruby-jwt/pull/53) ([yarmand](https://github.com/yarmand)) + +## [jwt-1.2.0](https://github.com/jwt/ruby-jwt/tree/jwt-1.2.0) (2014-11-24) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-0.1.13...jwt-1.2.0) + +**Closed issues:** + +- set token to expire [\#42](https://github.com/jwt/ruby-jwt/issues/42) + +**Merged pull requests:** + +- Added support for `exp` claim [\#45](https://github.com/jwt/ruby-jwt/pull/45) ([zshannon](https://github.com/zshannon)) +- rspec 3 breaks passing tests [\#44](https://github.com/jwt/ruby-jwt/pull/44) ([zshannon](https://github.com/zshannon)) + +## [jwt-0.1.13](https://github.com/jwt/ruby-jwt/tree/jwt-0.1.13) (2014-05-08) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-1.0.0...jwt-0.1.13) + +**Closed issues:** + +- yanking of version 0.1.12 causes issues [\#39](https://github.com/jwt/ruby-jwt/issues/39) +- Semantic versioning [\#37](https://github.com/jwt/ruby-jwt/issues/37) +- Update gem to get latest changes [\#36](https://github.com/jwt/ruby-jwt/issues/36) + +## [jwt-1.0.0](https://github.com/jwt/ruby-jwt/tree/jwt-1.0.0) (2014-05-07) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-0.1.11...jwt-1.0.0) + +**Closed issues:** + +- API request - JWT::decoded_header\(\) [\#26](https://github.com/jwt/ruby-jwt/issues/26) + +**Merged pull requests:** + +- return header along with playload after decoding [\#35](https://github.com/jwt/ruby-jwt/pull/35) ([sawyerzhang](https://github.com/sawyerzhang)) +- Raise JWT::DecodeError on nil token [\#34](https://github.com/jwt/ruby-jwt/pull/34) ([tjmw](https://github.com/tjmw)) +- Make MultiJson optional for Ruby 1.9+ [\#33](https://github.com/jwt/ruby-jwt/pull/33) ([petergoldstein](https://github.com/petergoldstein)) +- Allow access to header and payload without signature verification [\#32](https://github.com/jwt/ruby-jwt/pull/32) ([petergoldstein](https://github.com/petergoldstein)) +- Update specs to use RSpec 3.0.x syntax [\#31](https://github.com/jwt/ruby-jwt/pull/31) ([petergoldstein](https://github.com/petergoldstein)) +- Travis - Add Ruby 2.0.0, 2.1.0, Rubinius [\#30](https://github.com/jwt/ruby-jwt/pull/30) ([petergoldstein](https://github.com/petergoldstein)) + +## [jwt-0.1.11](https://github.com/jwt/ruby-jwt/tree/jwt-0.1.11) (2014-01-17) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-0.1.10...jwt-0.1.11) + +**Closed issues:** + +- url safe encode and decode [\#28](https://github.com/jwt/ruby-jwt/issues/28) +- Release [\#27](https://github.com/jwt/ruby-jwt/issues/27) + +**Merged pull requests:** + +- fixed urlsafe base64 encoding [\#29](https://github.com/jwt/ruby-jwt/pull/29) ([tobscher](https://github.com/tobscher)) + +## [jwt-0.1.10](https://github.com/jwt/ruby-jwt/tree/jwt-0.1.10) (2014-01-10) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-0.1.8...jwt-0.1.10) + +**Closed issues:** + +- change to signature of JWT.decode method [\#14](https://github.com/jwt/ruby-jwt/issues/14) + +**Merged pull requests:** + +- Fix warning: assigned but unused variable - e [\#25](https://github.com/jwt/ruby-jwt/pull/25) ([sferik](https://github.com/sferik)) +- Echoe doesn't define a license= method [\#24](https://github.com/jwt/ruby-jwt/pull/24) ([sferik](https://github.com/sferik)) +- Use OpenSSL::Digest instead of deprecated OpenSSL::Digest::Digest [\#23](https://github.com/jwt/ruby-jwt/pull/23) ([JuanitoFatas](https://github.com/JuanitoFatas)) +- Handle some invalid JWTs [\#22](https://github.com/jwt/ruby-jwt/pull/22) ([steved](https://github.com/steved)) +- Add MIT license to gemspec [\#21](https://github.com/jwt/ruby-jwt/pull/21) ([nycvotes-dev](https://github.com/nycvotes-dev)) +- Tweaks and improvements [\#20](https://github.com/jwt/ruby-jwt/pull/20) ([threedaymonk](https://github.com/threedaymonk)) +- Don't leave errors in OpenSSL.errors when there is a decoding error. [\#19](https://github.com/jwt/ruby-jwt/pull/19) ([lowellk](https://github.com/lowellk)) + +## [jwt-0.1.8](https://github.com/jwt/ruby-jwt/tree/jwt-0.1.8) (2013-03-14) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-0.1.7...jwt-0.1.8) + +**Merged pull requests:** + +- Contrib and update [\#18](https://github.com/jwt/ruby-jwt/pull/18) ([threedaymonk](https://github.com/threedaymonk)) +- Verify if verify is truthy \(not just true\) [\#17](https://github.com/jwt/ruby-jwt/pull/17) ([threedaymonk](https://github.com/threedaymonk)) + +## [jwt-0.1.7](https://github.com/jwt/ruby-jwt/tree/jwt-0.1.7) (2013-03-07) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-0.1.6...jwt-0.1.7) + +**Merged pull requests:** + +- Catch MultiJson::LoadError and reraise as JWT::DecodeError [\#16](https://github.com/jwt/ruby-jwt/pull/16) ([rwygand](https://github.com/rwygand)) + +## [jwt-0.1.6](https://github.com/jwt/ruby-jwt/tree/jwt-0.1.6) (2013-03-05) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-0.1.5...jwt-0.1.6) + +**Merged pull requests:** + +- Fixes a theoretical timing attack [\#15](https://github.com/jwt/ruby-jwt/pull/15) ([mgates](https://github.com/mgates)) +- Use StandardError as parent for DecodeError [\#13](https://github.com/jwt/ruby-jwt/pull/13) ([Oscil8](https://github.com/Oscil8)) + +## [jwt-0.1.5](https://github.com/jwt/ruby-jwt/tree/jwt-0.1.5) (2012-07-20) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-0.1.4...jwt-0.1.5) + +**Closed issues:** + +- Unable to specify signature header fields [\#7](https://github.com/jwt/ruby-jwt/issues/7) + +**Merged pull requests:** + +- MultiJson dependency uses ~\> but should be \>= [\#12](https://github.com/jwt/ruby-jwt/pull/12) ([sporkmonger](https://github.com/sporkmonger)) +- Oops. :-\) [\#11](https://github.com/jwt/ruby-jwt/pull/11) ([sporkmonger](https://github.com/sporkmonger)) +- Fix issue with signature verification in JRuby [\#10](https://github.com/jwt/ruby-jwt/pull/10) ([sporkmonger](https://github.com/sporkmonger)) +- Depend on MultiJson [\#9](https://github.com/jwt/ruby-jwt/pull/9) ([lautis](https://github.com/lautis)) +- Allow for custom headers on encode and decode [\#8](https://github.com/jwt/ruby-jwt/pull/8) ([dgrijalva](https://github.com/dgrijalva)) +- Missing development dependency for echoe gem. [\#6](https://github.com/jwt/ruby-jwt/pull/6) ([sporkmonger](https://github.com/sporkmonger)) + +## [jwt-0.1.4](https://github.com/jwt/ruby-jwt/tree/jwt-0.1.4) (2011-11-11) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/jwt-0.1.3...jwt-0.1.4) + +**Merged pull requests:** + +- Fix for RSA verification [\#5](https://github.com/jwt/ruby-jwt/pull/5) ([jordan-brough](https://github.com/jordan-brough)) + +## [jwt-0.1.3](https://github.com/jwt/ruby-jwt/tree/jwt-0.1.3) (2011-06-30) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/10d7492ea325c65fce41191c73cd90d4de494772...jwt-0.1.3) + +**Closed issues:** + +- signatures calculated incorrectly \(hexdigest instead of digest\) [\#1](https://github.com/jwt/ruby-jwt/issues/1) + +**Merged pull requests:** + +- Bumped a version and added a .gemspec using rake build_gemspec [\#3](https://github.com/jwt/ruby-jwt/pull/3) ([zhitomirskiyi](https://github.com/zhitomirskiyi)) +- Added RSA support [\#2](https://github.com/jwt/ruby-jwt/pull/2) ([zhitomirskiyi](https://github.com/zhitomirskiyi)) diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/CODE_OF_CONDUCT.md b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..1d65f7ae --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/CODE_OF_CONDUCT.md @@ -0,0 +1,84 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or + advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email + address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at . All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, +available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html). + +Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are available at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/CONTRIBUTING.md b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/CONTRIBUTING.md new file mode 100644 index 00000000..e163e4af --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/CONTRIBUTING.md @@ -0,0 +1,98 @@ +# Contributing to [ruby-jwt](https://github.com/jwt/ruby-jwt) + +## Forking the project + +Fork the project on GitHub and clone your own fork. Instuctions on forking can be found from the [GitHub Docs](https://docs.github.com/en/get-started/quickstart/fork-a-repo) + +```bash +git clone git@github.com:you/ruby-jwt.git +cd ruby-jwt +git remote add upstream https://github.com/jwt/ruby-jwt +``` + +## Create a branch for your implementation + +Make sure you have the latest upstream main branch of the project. + +```bash +git fetch --all +git checkout main +git rebase upstream/main +git push origin main +git checkout -b fix-a-little-problem +``` + +## Running the tests and linter + +Before you start with your implementation make sure you are able to get a successful test run with the current revision. + +The tests are written with rspec and [Appraisal](https://github.com/thoughtbot/appraisal) is used to ensure compatibility with 3rd party dependencies providing cryptographic features. + +[Rubocop](https://github.com/rubocop/rubocop) is used to enforce the Ruby style. + +To run the complete set of tests and linter run the following + +```bash +bundle install +bundle exec appraisal rake test +bundle exec rubocop +``` + +## Implement your feature + +Implement tests and your change. Don't be shy adding a little something in the [README](README.md). +Add a short description of the change in either the `Features` or `Fixes` section in the [CHANGELOG](CHANGELOG.md) file. + +The form of the row (You need to return to the row when you know the pull request id) + +```markdown +- Fix a little problem [#123](https://github.com/jwt/ruby-jwt/pull/123) - [@you](https://github.com/you). +``` + +## Push your branch and create a pull request + +Before pushing make sure the tests pass and RuboCop is happy. + +```bash +bundle exec appraisal rake test +bundle exec rubocop +git push origin fix-a-little-problem +``` + +Make a new pull request on the [ruby-jwt project](https://github.com/jwt/ruby-jwt/pulls) with a description what the change is about. + +## Update the CHANGELOG, again + +Update the [CHANGELOG](CHANGELOG.md) with the pull request id from the previous step. + +You can amend the previous commit with the updated changelog change and force push your branch. The PR will get automatically updated. + +```bash +git add CHANGELOG.md +git commit --amend --no-edit +git push origin fix-a-little-problem -f +``` + +## Keep an eye on your pull request + +A maintainer will review and probably merge you changes when time allows, be patient. + +## Keeping your branch up-to-date + +It's recommended that you keep your branch up-to-date by rebasing to the upstream main. + +```bash +git fetch upstream +git checkout fix-a-little-problem +git rebase upstream/main +git push origin fix-a-little-problem -f +``` + +## Releasing a new version + +The version is using the [Semantic Versioning](http://semver.org/) and the version is located in the [version.rb](lib/jwt/version.rb) file. +Also update the [CHANGELOG](CHANGELOG.md) to reflect the upcoming version release. + +```bash +rake release +``` diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/LICENSE b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/LICENSE new file mode 100644 index 00000000..927c375f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2011 Jeff Lindsay + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/README.md b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/README.md new file mode 100644 index 00000000..0e8122ed --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/README.md @@ -0,0 +1,782 @@ +# JWT + +[![Gem Version](https://badge.fury.io/rb/jwt.svg)](https://badge.fury.io/rb/jwt) +[![Build Status](https://github.com/jwt/ruby-jwt/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/jwt/ruby-jwt/actions) +[![Maintainability](https://qlty.sh/badges/6f61c5a6-6e23-41a7-8896-a3ce8b006655/maintainability.svg)](https://qlty.sh/gh/jwt/projects/ruby-jwt) +[![Code Coverage](https://qlty.sh/badges/6f61c5a6-6e23-41a7-8896-a3ce8b006655/test_coverage.svg)](https://qlty.sh/gh/jwt/projects/ruby-jwt) + +A ruby implementation of the [RFC 7519 OAuth JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) standard. + +If you have further questions related to development or usage, join us: [ruby-jwt google group](https://groups.google.com/forum/#!forum/ruby-jwt). + +See [CHANGELOG.md](CHANGELOG.md) for a complete set of changes and [upgrade guide](UPGRADING.md) for upgrading between major versions. + +## Sponsors + +| Logo | Message | +| ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| ![auth0 logo](https://user-images.githubusercontent.com/83319/31722733-de95bbde-b3ea-11e7-96bf-4f4e8f915588.png) | If you want to quickly add secure token-based authentication to Ruby projects, feel free to check Auth0's Ruby SDK and free plan at [auth0.com/developers](https://auth0.com/developers?utm_source=GHsponsor&utm_medium=GHsponsor&utm_campaign=rubyjwt&utm_content=auth) | + +## Installing + +### Using Rubygems + +```bash +gem install jwt +``` + +### Using Bundler + +Add the following to your Gemfile + +```bash +gem 'jwt' +``` + +And run `bundle install` + +Finally require the gem in your application + +```ruby +require 'jwt' +``` + +## Algorithms and Usage + +The jwt gem natively supports the NONE, HMAC, RSASSA, ECDSA and RSASSA-PSS algorithms via the openssl library. The gem can be extended with additional or alternative implementations of the algorithms via extensions. + +Additionally the EdDSA algorithm is supported via a the [jwt-eddsa gem](https://rubygems.org/gems/jwt-eddsa). + +For safe cryptographic signing, you need to specify the algorithm in the options hash whenever you call `JWT.decode` to ensure that an attacker [cannot bypass the algorithm verification step](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/). **It is strongly recommended that you hard code the algorithm, as you may leave yourself vulnerable by dynamically picking the algorithm** + +See [JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS](https://tools.ietf.org/html/rfc7518#section-3.1) + +### **NONE** + +- none - unsigned token + +```ruby +payload = { data: 'test' } +token = JWT.encode(payload, nil, 'none') +# => "eyJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9." + +decoded_token = JWT.decode(token, nil, true, { algorithm: 'none' }) +# => [ +# {"data"=>"test"}, # payload +# {"alg"=>"none"} # header +# ] +``` + +### **HMAC** + +- HS256 - HMAC using SHA-256 hash algorithm +- HS384 - HMAC using SHA-384 hash algorithm +- HS512 - HMAC using SHA-512 hash algorithm + +```ruby +payload = { data: 'test' } +hmac_secret = 'my$ecretK3y' + +token = JWT.encode(payload, hmac_secret, 'HS256') +# => "eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pNIWIL34Jo13LViZAJACzK6Yf0qnvT_BuwOxiMCPE-Y" + +decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' }) +# => [ +# {"data"=>"test"}, # payload +# {"alg"=>"HS256"} # header +# ] +``` + +### **RSA** + +- RS256 - RSA using SHA-256 hash algorithm +- RS384 - RSA using SHA-384 hash algorithm +- RS512 - RSA using SHA-512 hash algorithm + +```ruby +payload = { data: 'test' } +rsa_private = OpenSSL::PKey::RSA.generate(2048) +rsa_public = rsa_private.public_key + +token = JWT.encode(payload, rsa_private, 'RS256') +# => "eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.CCkO35qFPijW8Gwhbt8a80PB9fc9FJ19hCMnXSgoDF6Mlvlt0A4G-ah..." + +decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'RS256' }) +# => [ +# {"data"=>"test"}, # payload +# {"alg"=>"RS256"} # header +# ] +``` + +### **ECDSA** + +- ES256 - ECDSA using P-256 and SHA-256 +- ES384 - ECDSA using P-384 and SHA-384 +- ES512 - ECDSA using P-521 and SHA-512 +- ES256K - ECDSA using P-256K and SHA-256 + +```ruby +payload = { data: 'test' } +ecdsa_key = OpenSSL::PKey::EC.generate('prime256v1') + +token = JWT.encode(payload, ecdsa_key, 'ES256') +# => "eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg" + +decoded_token = JWT.decode(token, ecdsa_key, true, { algorithm: 'ES256' }) +# => [ +# {"test"=>"data"}, # payload +# {"alg"=>"ES256"} # header +# ] +``` + +### **EdDSA** + +Since version 3.0, the EdDSA algorithm has been moved to the [jwt-eddsa gem](https://rubygems.org/gems/jwt-eddsa). + +### **RSASSA-PSS** + +- PS256 - RSASSA-PSS using SHA-256 hash algorithm +- PS384 - RSASSA-PSS using SHA-384 hash algorithm +- PS512 - RSASSA-PSS using SHA-512 hash algorithm + +```ruby +payload = { data: 'test' } +rsa_private = OpenSSL::PKey::RSA.generate(2048) +rsa_public = rsa_private.public_key + +token = JWT.encode(payload, rsa_private, 'PS256') +# => "eyJhbGciOiJQUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.BRWizdUjD5zAWw-EDBcrl3dDpQDAePz9Ol3XKC43SggU47G8OWwveA_..." + +decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'PS256' }) +# => [ +# {"data"=>"test"}, # payload +# {"alg"=>"PS256"} # header +# ] +``` + +### **Custom algorithms** + +When encoding or decoding a token, you can pass in a custom object through the `algorithm` option to handle signing or verification. This custom object must include or extend the `JWT::JWA::SigningAlgorithm` module and implement certain methods: + +- For decoding/verifying: The object must implement the methods `alg` and `verify`. +- For encoding/signing: The object must implement the methods `alg` and `sign`. + +For customization options check the details from `JWT::JWA::SigningAlgorithm`. + +```ruby +module CustomHS512Algorithm + extend JWT::JWA::SigningAlgorithm + + def self.alg + 'HS512' + end + + def self.sign(data:, signing_key:) + OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), signing_key, data) + end + + def self.verify(data:, signature:, verification_key:) + ::OpenSSL.secure_compare(sign(data: data, signing_key: verification_key), signature) + end +end + +payload = { data: 'test' } +token = JWT.encode(payload, 'secret', CustomHS512Algorithm) +# => "eyJhbGciOiJIUzUxMiJ9.eyJkYXRhIjoidGVzdCJ9.aBNoejLEM2WMF3TxzRDKlehYdG2ATvFpGNauTI4GSD2VJseS_sC8covrVMlgslf0aJM4SKb3EIeORJBFPtZ33w" + +decoded_token = JWT.decode(token, 'secret', true, algorithm: CustomHS512Algorithm) +# => [ +# {"data"=>"test"}, # payload +# {"alg"=>"HS512"} # header +# ] +``` + +### Add custom header fields + +The ruby-jwt gem supports custom [header fields](https://tools.ietf.org/html/rfc7519#section-5) +To add custom header fields you need to pass `header_fields` parameter + +```ruby +payload = { data: 'test' } + +token = JWT.encode(payload, nil, 'none', { typ: 'JWT' }) +# => "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9." + +decoded_token = JWT.decode(token, nil, true, { algorithm: 'none' }) +# => [ +# {"data"=>"test"}, # payload +# {"typ"=>"JWT", "alg"=>"none"} # header +# ] +``` + +## `JWT::Token` and `JWT::EncodedToken` + +The `JWT::Token` and `JWT::EncodedToken` classes can be used to manage your JWTs. + +### Signing and encoding a token + +```ruby +payload = { exp: Time.now.to_i + 60, jti: '1234', sub: "my-subject" } +header = { kid: 'hmac' } + +token = JWT::Token.new(payload: payload, header: header) +token.sign!(algorithm: 'HS256', key: "secret") + +token.jwt +# => "eyJraWQiOiJobWFjIiwiYWxnIjoiSFMyNTYifQ.eyJleHAiOjE3NTAwMDU0NzksImp0aSI6IjEyMzQiLCJzdWIiOiJteS1zdWJqZWN0In0.NRLcK6fYr3IdNfmncJePMWLQ34M4n14EgqSYrQIjL9w" +``` + +### Verifying and decoding a token + +The `JWT::EncodedToken` can be used as a token object that allows verification of signatures and claims. + +```ruby +encoded_token = JWT::EncodedToken.new(token.jwt) + +encoded_token.verify_signature!(algorithm: 'HS256', key: "secret") +encoded_token.verify_signature!(algorithm: 'HS256', key: "wrong_secret") # raises JWT::VerificationError +encoded_token.verify_claims!(:exp, :jti) +encoded_token.verify_claims!(sub: ["not-my-subject"]) # raises JWT::InvalidSubError +encoded_token.claim_errors(sub: ["not-my-subject"]).map(&:message) # => ["Invalid subject. Expected [\"not-my-subject\"], received my-subject"] +encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' } +encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'} +``` + +The `JWT::EncodedToken#verify!` method can be used to verify signature and claim verification in one go. The `exp` claim is verified by default. + +```ruby +encoded_token = JWT::EncodedToken.new(token.jwt) +encoded_token.verify!(signature: {algorithm: 'HS256', key: "secret"}) +encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' } +encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'} +``` + +A JWK can be used to sign and verify the token if it's possible to derive the signing algorithm from the key. + +```ruby +jwk_json = '{ + "kty": "oct", + "k": "c2VjcmV0", + "alg": "HS256", + "kid": "hmac" +}' + +jwk = JWT::JWK.import(JSON.parse(jwk_json)) + +token = JWT::Token.new(payload: payload, header: header) + +token.sign!(key: jwk, algorithm: 'HS256') + +encoded_token = JWT::EncodedToken.new(token.jwt) +encoded_token.verify!(signature: { algorithm: ["HS256", "HS512"], key: jwk}) +``` + +#### Using a keyfinder + +A keyfinder can be used to verify a signature. A keyfinder is an object responding to the `#call` method. The method expects to receive one argument, which is the token to be verified. + +An example on using the built-in JWK keyfinder. + +```ruby +# Create and sign a token +jwk = JWT::JWK.new(OpenSSL::PKey::RSA.generate(2048)) +token = JWT::Token.new(payload: { pay: 'load' }, header: { kid: jwk.kid }) +token.sign!(algorithm: 'RS256', key: jwk.signing_key) + +# Create keyfinder object, verify and decode token +key_finder = JWT::JWK::KeyFinder.new(jwks: JWT::JWK::Set.new(jwk)) +encoded_token = JWT::EncodedToken.new(token.jwt) +encoded_token.verify!(signature: { algorithm: 'RS256', key_finder: key_finder}) +encoded_token.payload # => { 'pay' => 'load' } +``` + +Using a custom keyfinder proc. + +```ruby +# Create and sign a token +key = OpenSSL::PKey::RSA.generate(2048) +token = JWT::Token.new(payload: { pay: 'load' }) +token.sign!(algorithm: 'RS256', key: key) + +# Verify and decode token +encoded_token = JWT::EncodedToken.new(token.jwt) +encoded_token.verify!(signature: { algorithm: 'RS256', key_finder: ->(_token){ key.public_key }}) +encoded_token.payload # => { 'pay' => 'load' } +``` + +### Detached payload + +The `::JWT::Token#detach_payload!` method can be use to detach the payload from the JWT. + +```ruby +token = JWT::Token.new(payload: { pay: 'load' }) +token.sign!(algorithm: 'HS256', key: "secret") +token.detach_payload! +token.jwt # => "eyJhbGciOiJIUzI1NiJ9..UEhDY1Qlj29ammxuVRA_-gBah4qTy5FngIWg0yEAlC0" +token.encoded_payload # => "eyJwYXkiOiJsb2FkIn0" +``` + +The `JWT::EncodedToken` class can be used to decode a token with a detached payload by providing the payload to the token instance in separate. + +```ruby +encoded_token = JWT::EncodedToken.new(token.jwt) +encoded_token.encoded_payload = "eyJwYXkiOiJsb2FkIn0" +encoded_token.verify_signature!(algorithm: 'HS256', key: "secret") +encoded_token.payload # => {"pay"=>"load"} +``` + +## Claims + +JSON Web Token defines some reserved claim names and defines how they should be +used. JWT supports these reserved claim names: + +- 'exp' (Expiration Time) Claim +- 'nbf' (Not Before Time) Claim +- 'iss' (Issuer) Claim +- 'aud' (Audience) Claim +- 'jti' (JWT ID) Claim +- 'iat' (Issued At) Claim +- 'sub' (Subject) Claim + +### Expiration Time Claim + +From [Oauth JSON Web Token 4.1.4. "exp" (Expiration Time) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.4): + +> The `exp` (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the `exp` claim requires that the current date/time MUST be before the expiration date/time listed in the `exp` claim. Implementers MAY provide for some small `leeway`, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a **_NumericDate_** value. Use of this claim is OPTIONAL. + +```ruby +exp = Time.now.to_i + 4 * 3600 +exp_payload = { data: 'data', exp: exp } + +token = JWT.encode(exp_payload, hmac_secret, 'HS256') + +begin + decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' }) +rescue JWT::ExpiredSignature + # Handle expired token, e.g. logout user or deny access +end +``` + +The Expiration Claim verification can be disabled. + +```ruby +# Decode token without raising JWT::ExpiredSignature error +JWT.decode(token, hmac_secret, true, { verify_expiration: false, algorithm: 'HS256' }) +``` + +Leeway and the exp claim. + +```ruby +exp = Time.now.to_i - 10 +leeway = 30 # seconds + +exp_payload = { data: 'data', exp: exp } + +# build expired token +token = JWT.encode(exp_payload, hmac_secret, 'HS256') + +begin + # add leeway to ensure the token is still accepted + decoded_token = JWT.decode(token, hmac_secret, true, { exp_leeway: leeway, algorithm: 'HS256' }) +rescue JWT::ExpiredSignature + # Handle expired token, e.g. logout user or deny access +end +``` + +### Not Before Time Claim + +From [Oauth JSON Web Token 4.1.5. "nbf" (Not Before) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.5): + +> The `nbf` (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the `nbf` claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the `nbf` claim. Implementers MAY provide for some small `leeway`, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a **_NumericDate_** value. Use of this claim is OPTIONAL. + +```ruby +nbf = Time.now.to_i - 3600 +nbf_payload = { data: 'data', nbf: nbf } + +token = JWT.encode(nbf_payload, hmac_secret, 'HS256') + +begin + decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' }) +rescue JWT::ImmatureSignature + # Handle invalid token, e.g. logout user or deny access +end +``` + +The Not Before Claim verification can be disabled. + +```ruby +# Decode token without raising JWT::ImmatureSignature error +JWT.decode(token, hmac_secret, true, { verify_not_before: false, algorithm: 'HS256' }) +``` + +Leeway and the nbf claim. + +```ruby +nbf = Time.now.to_i + 10 +leeway = 30 + +nbf_payload = { data: 'data', nbf: nbf } + +# build expired token +token = JWT.encode(nbf_payload, hmac_secret, 'HS256') + +begin + # add leeway to ensure the token is valid + decoded_token = JWT.decode(token, hmac_secret, true, { nbf_leeway: leeway, algorithm: 'HS256' }) +rescue JWT::ImmatureSignature + # Handle invalid token, e.g. logout user or deny access +end +``` + +### Issuer Claim + +From [Oauth JSON Web Token 4.1.1. "iss" (Issuer) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.1): + +> The `iss` (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The `iss` value is a case-sensitive string containing a **_StringOrURI_** value. Use of this claim is OPTIONAL. + +You can pass multiple allowed issuers as an Array, verification will pass if one of them matches the `iss` value in the payload. + +```ruby +iss = 'My Awesome Company Inc. or https://my.awesome.website/' +iss_payload = { data: 'data', iss: iss } + +token = JWT.encode(iss_payload, hmac_secret, 'HS256') + +begin + # Add iss to the validation to check if the token has been manipulated + decoded_token = JWT.decode(token, hmac_secret, true, { iss: iss, verify_iss: true, algorithm: 'HS256' }) +rescue JWT::InvalidIssuerError + # Handle invalid token, e.g. logout user or deny access +end +``` + +You can also pass a Regexp or Proc (with arity 1), verification will pass if the regexp matches or the proc returns truthy. +On supported ruby versions (>= 2.5) you can also delegate to methods, on older versions you will have +to convert them to proc (using `to_proc`) + +```ruby +JWT.decode(token, hmac_secret, true, + iss: %r'https://my.awesome.website/', + verify_iss: true, + algorithm: 'HS256') +``` + +```ruby +JWT.decode(token, hmac_secret, true, + iss: ->(issuer) { issuer.start_with?('My Awesome Company Inc') }, + verify_iss: true, + algorithm: 'HS256') +``` + +```ruby +JWT.decode(token, hmac_secret, true, + iss: method(:valid_issuer?), + verify_iss: true, + algorithm: 'HS256') + +# somewhere in the same class: +def valid_issuer?(issuer) + # custom validation +end +``` + +### Audience Claim + +From [Oauth JSON Web Token 4.1.3. "aud" (Audience) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.3): + +> The `aud` (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the `aud` claim when this claim is present, then the JWT MUST be rejected. In the general case, the `aud` value is an array of case-sensitive strings, each containing a **_StringOrURI_** value. In the special case when the JWT has one audience, the `aud` value MAY be a single case-sensitive string containing a **_StringOrURI_** value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL. + +```ruby +aud = ['Young', 'Old'] +aud_payload = { data: 'data', aud: aud } + +token = JWT.encode(aud_payload, hmac_secret, 'HS256') + +begin + # Add aud to the validation to check if the token has been manipulated + decoded_token = JWT.decode(token, hmac_secret, true, { aud: aud, verify_aud: true, algorithm: 'HS256' }) +rescue JWT::InvalidAudError + # Handle invalid token, e.g. logout user or deny access + puts 'Audience Error' +end +``` + +### JWT ID Claim + +From [Oauth JSON Web Token 4.1.7. "jti" (JWT ID) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.7): + +> The `jti` (JWT ID) claim provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. The `jti` claim can be used to prevent the JWT from being replayed. The `jti` value is a case-sensitive string. Use of this claim is OPTIONAL. + +```ruby +# Use the secret and iat to create a unique key per request to prevent replay attacks +jti_raw = [hmac_secret, iat].join(':').to_s +jti = Digest::MD5.hexdigest(jti_raw) +jti_payload = { data: 'data', iat: iat, jti: jti } + +token = JWT.encode(jti_payload, hmac_secret, 'HS256') + +begin + # If :verify_jti is true, validation will pass if a JTI is present + #decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: true, algorithm: 'HS256' }) + # Alternatively, pass a proc with your own code to check if the JTI has already been used + decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: proc { |jti| my_validation_method(jti) }, algorithm: 'HS256' }) + # or + decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: proc { |jti, payload| my_validation_method(jti, payload) }, algorithm: 'HS256' }) +rescue JWT::InvalidJtiError + # Handle invalid token, e.g. logout user or deny access + puts 'Error' +end +``` + +### Issued At Claim + +From [Oauth JSON Web Token 4.1.6. "iat" (Issued At) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.6): + +> The `iat` (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. The `leeway` option is not taken into account when verifying this claim. The `iat_leeway` option was removed in version 2.2.0. Its value MUST be a number containing a **_NumericDate_** value. Use of this claim is OPTIONAL. + +```ruby +iat = Time.now.to_i +iat_payload = { data: 'data', iat: iat } + +token = JWT.encode(iat_payload, hmac_secret, 'HS256') + +begin + # Add iat to the validation to check if the token has been manipulated + decoded_token = JWT.decode(token, hmac_secret, true, { verify_iat: true, algorithm: 'HS256' }) +rescue JWT::InvalidIatError + # Handle invalid token, e.g. logout user or deny access +end +``` + +### Subject Claim + +From [Oauth JSON Web Token 4.1.2. "sub" (Subject) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.2): + +> The `sub` (subject) claim identifies the principal that is the subject of the JWT. The Claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The processing of this claim is generally application specific. The sub value is a case-sensitive string containing a **_StringOrURI_** value. Use of this claim is OPTIONAL. + +```ruby +sub = 'Subject' +sub_payload = { data: 'data', sub: sub } + +token = JWT.encode(sub_payload, hmac_secret, 'HS256') + +begin + # Add sub to the validation to check if the token has been manipulated + decoded_token = JWT.decode(token, hmac_secret, true, { sub: sub, verify_sub: true, algorithm: 'HS256' }) +rescue JWT::InvalidSubError + # Handle invalid token, e.g. logout user or deny access +end +``` + +### Standalone claim verification + +The JWT claim verifications can be used to verify any Hash to include expected keys and values. + +A few example on verifying the claims for a payload: + +```ruby +JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10}, :numeric, :exp) +JWT::Claims.valid_payload?({"exp" => Time.now.to_i + 10}, :exp) +# => true +JWT::Claims.payload_errors({"exp" => Time.now.to_i - 10}, :exp) +# => [#] +JWT::Claims.verify_payload!({"exp" => Time.now.to_i - 10}, exp: { leeway: 11}) + +JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10, "sub" => "subject"}, :exp, sub: "subject") +``` + +### Finding a Key + +To dynamically find the key for verifying the JWT signature, pass a block to the decode block. The block receives headers and the original payload as parameters. It should return with the key to verify the signature that was used to sign the JWT. + +```ruby +issuers = %w[My_Awesome_Company1 My_Awesome_Company2] +iss_payload = { data: 'data', iss: issuers.first } + +secrets = { issuers.first => hmac_secret, issuers.last => 'hmac_secret2' } + +token = JWT.encode(iss_payload, hmac_secret, 'HS256') + +begin + # Add iss to the validation to check if the token has been manipulated + decoded_token = JWT.decode(token, nil, true, { iss: issuers, verify_iss: true, algorithm: 'HS256' }) do |_headers, payload| + secrets[payload['iss']] + end +rescue JWT::InvalidIssuerError + # Handle invalid token, e.g. logout user or deny access +end +``` + +### Required Claims + +You can specify claims that must be present for decoding to be successful. JWT::MissingRequiredClaim will be raised if any are missing + +```ruby +# Will raise a JWT::MissingRequiredClaim error if the 'exp' claim is absent +JWT.decode(token, hmac_secret, true, { required_claims: ['exp'], algorithm: 'HS256' }) +``` + +### X.509 certificates in x5c header + +A JWT signature can be verified using certificate(s) given in the `x5c` header. Before doing that, the trustworthiness of these certificate(s) must be established. This is done in accordance with RFC 5280 which (among other things) verifies the certificate(s) are issued by a trusted root certificate, the timestamps are valid, and none of the certificate(s) are revoked (i.e. being present in the root certificate's Certificate Revocation List). + +```ruby +root_certificates = [] # trusted `OpenSSL::X509::Certificate` objects +crl_uris = root_certificates.map(&:crl_uris) +crls = crl_uris.map do |uri| + # look up cached CRL by `uri` and return it if found, otherwise continue + crl = Net::HTTP.get(uri) + crl = OpenSSL::X509::CRL.new(crl) + # cache `crl` using `uri` as the key, expiry set to `crl.next_update` timestamp +end + +begin + JWT.decode(token, nil, true, { x5c: { root_certificates: root_certificates, crls: crls } }) +rescue JWT::DecodeError + # Handle error, e.g. x5c header certificate revoked or expired +end +``` + +## JSON Web Key (JWK) + +JWK is a JSON structure representing a cryptographic key. This gem currently supports RSA, EC, OKP and HMAC keys. OKP support requires [RbNaCl](https://github.com/RubyCrypto/rbnacl) and currently only supports the Ed25519 curve. + +To encode a JWT using your JWK: + +```ruby +optional_parameters = { kid: 'my-kid', use: 'sig', alg: 'RS512' } +jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), optional_parameters) + +# Encoding +payload = { data: 'data' } +token = JWT.encode(payload, jwk.signing_key, jwk[:alg], kid: jwk[:kid]) + +# JSON Web Key Set for advertising your signing keys +jwks_hash = JWT::JWK::Set.new(jwk).export +``` + +To decode a JWT using a trusted entity's JSON Web Key Set (JWKS): + +```ruby +jwks = JWT::JWK::Set.new(jwks_hash) +jwks.filter! {|key| key[:use] == 'sig' } # Signing keys only! +algorithms = jwks.map { |key| key[:alg] }.compact.uniq +JWT.decode(token, nil, true, algorithms: algorithms, jwks: jwks) +``` + +The `jwks` option can also be given as a lambda that evaluates every time a key identifier is resolved. +This can be used to implement caching of remotely fetched JWK Sets. + +Key identifiers can be specified using `kid`, `x5t` header parameters. +If the requested identifier is not found from the given set the loader will be called a second time with the `kid_not_found` option set to `true`. +The application can choose to implement some kind of JWK cache invalidation or other mechanism to handle such cases. + +Tokens without a specified key identifier (`kid` or `x5t`) are rejected by default. +This behaviour may be overwritten by setting the `allow_nil_kid` option for `decode` to `true`. + +```ruby +jwks_loader = ->(options) do + # The jwk loader would fetch the set of JWKs from a trusted source. + # To avoid malicious requests triggering cache invalidations there needs to be + # some kind of grace time or other logic for determining the validity of the invalidation. + # This example only allows cache invalidations every 5 minutes. + if options[:kid_not_found] && @cache_last_update < Time.now.to_i - 300 + logger.info("Invalidating JWK cache. #{options[:kid]} not found from previous cache") + @cached_keys = nil + end + @cached_keys ||= begin + @cache_last_update = Time.now.to_i + # Replace with your own JWKS fetching routine + jwks = JWT::JWK::Set.new(jwks_hash) + jwks.select! { |key| key[:use] == 'sig' } # Signing Keys only + jwks + end +end + +begin + JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwks_loader }) +rescue JWT::JWKError + # Handle problems with the provided JWKs +rescue JWT::DecodeError + # Handle other decode related issues e.g. no kid in header, no matching public key found etc. +end +``` + +### Importing and exporting JSON Web Keys + +The ::JWT::JWK class can be used to import both JSON Web Keys and OpenSSL keys +and export to either format with and without the private key included. + +To include the private key in the export pass the `include_private` parameter to the export method. + +```ruby +# Import a JWK Hash (showing an HMAC example) +jwk = JWT::JWK.new({ kty: 'oct', k: 'my-secret', kid: 'my-kid' }) + +# Import an OpenSSL key +# You can optionally add descriptive parameters to the JWK +desc_params = { kid: 'my-kid', use: 'sig' } +jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), desc_params) + +# Export as JWK Hash (public key only by default) +jwk_hash = jwk.export +jwk_hash_with_private_key = jwk.export(include_private: true) + +# Export as OpenSSL key +public_key = jwk.verify_key +private_key = jwk.signing_key if jwk.private? + +# You can also import and export entire JSON Web Key Sets +jwks_hash = { keys: [{ kty: 'oct', k: 'my-secret', kid: 'my-kid' }] } +jwks = JWT::JWK::Set.new(jwks_hash) +jwks_hash = jwks.export +``` + +### Key ID (kid) and JWKs + +The key id (kid) generation in the gem is a custom algorithm and not based on any standards. +To use a standardized JWK thumbprint (RFC 7638) as the kid for JWKs a generator type can be specified in the global configuration +or can be given to the JWK instance on initialization. + +```ruby +JWT.configuration.jwk.kid_generator_type = :rfc7638_thumbprint +# OR +JWT.configuration.jwk.kid_generator = ::JWT::JWK::Thumbprint +# OR +jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), nil, kid_generator: ::JWT::JWK::Thumbprint) + +jwk_hash = jwk.export + +thumbprint_as_the_kid = jwk_hash[:kid] +``` + +## Development and testing + +The tests are written with rspec. [Appraisal](https://github.com/thoughtbot/appraisal) is used to ensure compatibility with 3rd party dependencies providing cryptographic features. + +```bash +bundle install +bundle exec appraisal rake test +``` + +## Releasing + +To cut a new release adjust the [version.rb](lib/jwt/version.rb) and [CHANGELOG](CHANGELOG.md) with desired version numbers and dates and commit the changes. Tag the release with the version number using the following command: + +```bash +rake release:source_control_push +``` + +This will tag a new version an trigger a [GitHub action](.github/workflows/push_gem.yml) that eventually will push the gem to rubygems.org. + +## How to contribute + +See [CONTRIBUTING](CONTRIBUTING.md). + +## Contributors + +See [AUTHORS](AUTHORS). + +## License + +See [LICENSE](LICENSE). diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/UPGRADING.md b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/UPGRADING.md new file mode 100644 index 00000000..10c95d1e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/UPGRADING.md @@ -0,0 +1,47 @@ +# Upgrading ruby-jwt to >= 3.0.0 + +## Removal of the indirect [RbNaCl](https://github.com/RubyCrypto/rbnacl) dependency + +Historically, the set of supported algorithms was extended by including the `rbnacl` gem in the application's Gemfile. On load, ruby-jwt tried to load the gem and, if available, extend the algorithms to those provided by the `rbnacl/libsodium` libraries. This indirect dependency has caused some maintenance pain and confusion about which versions of the gem are supported. + +Some work to ease the way alternative algorithms can be implemented has been done. This enables the extraction of the algorithm provided by `rbnacl`. + +The extracted algorithms now live in the [jwt-eddsa](https://rubygems.org/gems/jwt-eddsa) gem. + +### Dropped support for HS512256 + +The algorithm HS512256 (HMAC-SHA-512 truncated to 256-bits) is not part of any JWA/JWT RFC and therefore will not be supported anymore. It was part of the HMAC algorithms provided by the indirect [RbNaCl](https://github.com/RubyCrypto/rbnacl) dependency. Currently, there are no direct substitutes for the algorithm. + +### `JWT::EncodedToken#payload` will raise before token is verified + +To avoid accidental use of unverified tokens, the `JWT::EncodedToken#payload` method will raise an error if accessed before the token signature has been verified. + +To access the payload before verification, use the method `JWT::EncodedToken#unverified_payload`. + +## Stricter requirements on Base64 encoded data + +Base64 decoding will no longer fallback on the looser RFC 2045. The biggest difference is that the looser version was ignoring whitespaces and newlines, whereas the stricter version raises errors in such cases. + +If you, for example, read tokens from files, there could be problems with trailing newlines. Make sure you trim your input before passing it to the decoding mechanisms. + +## Claim verification revamp + +Claim verification has been [split into separate classes](https://github.com/jwt/ruby-jwt/pull/605) and has [a new API](https://github.com/jwt/ruby-jwt/pull/626), leading to the following deprecations: + +- The `::JWT::ClaimsValidator` class will be removed in favor of the functionality provided by `::JWT::Claims`. +- The `::JWT::Claims::verify!` method will be removed in favor of `::JWT::Claims::verify_payload!`. +- The `::JWT::JWA.create` method will be removed. +- The `::JWT::Verify` class will be removed in favor of the functionality provided by `::JWT::Claims`. +- Calling `::JWT::Claims::Numeric.new` with a payload will be removed in favor of `::JWT::Claims::verify_payload!(payload, :numeric)`. +- Calling `::JWT::Claims::Numeric.verify!` with a payload will be removed in favor of `::JWT::Claims::verify_payload!(payload, :numeric)`. + +## Algorithm restructuring + +The internal algorithms were [restructured](https://github.com/jwt/ruby-jwt/pull/607) to support extensions from separate libraries. The changes led to a few deprecations and new requirements: + +- The `sign` and `verify` static methods on all the algorithms (`::JWT::JWA`) will be removed. +- Custom algorithms are expected to include the `JWT::JWA::SigningAlgorithm` module. + +## Base64 the `k´ value for HMAC JWKs + +The gem was missing the Base64 encoding and decoding when representing and parsing a HMAC key as a JWK. This issue is now addressed. The added encoding will break compatibility with JWKs produced by older versions of the gem. diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt.rb new file mode 100644 index 00000000..86ac2e6a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'jwt/version' +require 'jwt/base64' +require 'jwt/json' +require 'jwt/decode' +require 'jwt/configuration' +require 'jwt/encode' +require 'jwt/error' +require 'jwt/jwk' +require 'jwt/claims' +require 'jwt/encoded_token' +require 'jwt/token' + +# JSON Web Token implementation +# +# Should be up to date with the latest spec: +# https://tools.ietf.org/html/rfc7519 +module JWT + extend ::JWT::Configuration + + module_function + + # Encodes a payload into a JWT. + # + # @param payload [Hash] the payload to encode. + # @param key [String] the key used to sign the JWT. + # @param algorithm [String] the algorithm used to sign the JWT. + # @param header_fields [Hash] additional headers to include in the JWT. + # @return [String] the encoded JWT. + def encode(payload, key, algorithm = 'HS256', header_fields = {}) + Encode.new(payload: payload, + key: key, + algorithm: algorithm, + headers: header_fields).segments + end + + # Decodes a JWT to extract the payload and header + # + # @param jwt [String] the JWT to decode. + # @param key [String] the key used to verify the JWT. + # @param verify [Boolean] whether to verify the JWT signature. + # @param options [Hash] additional options for decoding. + # @return [Array] the decoded payload and headers. + def decode(jwt, key = nil, verify = true, options = {}, &keyfinder) # rubocop:disable Style/OptionalBooleanParameter + Decode.new(jwt, key, verify, configuration.decode.to_h.merge(options), &keyfinder).decode_segments + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/base64.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/base64.rb new file mode 100644 index 00000000..fdf1bf95 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/base64.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'base64' + +module JWT + # Base64 encoding and decoding + # @api private + class Base64 + class << self + # Encode a string with URL-safe Base64 complying with RFC 4648 (not padded). + # @api private + def url_encode(str) + ::Base64.urlsafe_encode64(str, padding: false) + end + + # Decode a string with URL-safe Base64 complying with RFC 4648. + # @api private + def url_decode(str) + ::Base64.urlsafe_decode64(str) + rescue ArgumentError => e + raise unless e.message == 'invalid base64' + + raise Base64DecodeError, 'Invalid base64 encoding' + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims.rb new file mode 100644 index 00000000..45ed547b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require_relative 'claims/audience' +require_relative 'claims/crit' +require_relative 'claims/decode_verifier' +require_relative 'claims/expiration' +require_relative 'claims/issued_at' +require_relative 'claims/issuer' +require_relative 'claims/jwt_id' +require_relative 'claims/not_before' +require_relative 'claims/numeric' +require_relative 'claims/required' +require_relative 'claims/subject' +require_relative 'claims/verifier' + +module JWT + # JWT Claim verifications + # https://datatracker.ietf.org/doc/html/rfc7519#section-4 + # + # Verification is supported for the following claims: + # exp + # nbf + # iss + # iat + # jti + # aud + # sub + # required + # numeric + module Claims + # Represents a claim verification error + Error = Struct.new(:message, keyword_init: true) + + class << self + # Checks if the claims in the JWT payload are valid. + # @example + # + # ::JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10}, :exp) + # ::JWT::Claims.verify_payload!({"exp" => Time.now.to_i - 10}, exp: { leeway: 11}) + # + # @param payload [Hash] the JWT payload. + # @param options [Array] the options for verifying the claims. + # @return [void] + # @raise [JWT::DecodeError] if any claim is invalid. + def verify_payload!(payload, *options) + Verifier.verify!(VerificationContext.new(payload: payload), *options) + end + + # Checks if the claims in the JWT payload are valid. + # + # @param payload [Hash] the JWT payload. + # @param options [Array] the options for verifying the claims. + # @return [Boolean] true if the claims are valid, false otherwise + def valid_payload?(payload, *options) + payload_errors(payload, *options).empty? + end + + # Returns the errors in the claims of the JWT token. + # + # @param options [Array] the options for verifying the claims. + # @return [Array] the errors in the claims of the JWT + def payload_errors(payload, *options) + Verifier.errors(VerificationContext.new(payload: payload), *options) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/audience.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/audience.rb new file mode 100644 index 00000000..f828fc59 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/audience.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module JWT + module Claims + # The Audience class is responsible for validating the audience claim ('aud') in a JWT token. + class Audience + # Initializes a new Audience instance. + # + # @param expected_audience [String, Array] the expected audience(s) for the JWT token. + def initialize(expected_audience:) + @expected_audience = expected_audience + end + + # Verifies the audience claim ('aud') in the JWT token. + # + # @param context [Object] the context containing the JWT payload. + # @param _args [Hash] additional arguments (not used). + # @raise [JWT::InvalidAudError] if the audience claim is invalid. + # @return [nil] + def verify!(context:, **_args) + aud = context.payload['aud'] + raise JWT::InvalidAudError, "Invalid audience. Expected #{expected_audience}, received #{aud || ''}" if ([*aud] & [*expected_audience]).empty? + end + + private + + attr_reader :expected_audience + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/crit.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/crit.rb new file mode 100644 index 00000000..ac339eb0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/crit.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module JWT + module Claims + # Responsible of validation the crit header + class Crit + # Initializes a new Crit instance. + # + # @param expected_crits [String] the expected crit header values for the JWT token. + def initialize(expected_crits:) + @expected_crits = Array(expected_crits) + end + + # Verifies the critical claim ('crit') in the JWT token header. + # + # @param context [Object] the context containing the JWT payload and header. + # @param _args [Hash] additional arguments (not used). + # @raise [JWT::InvalidCritError] if the crit claim is invalid. + # @return [nil] + def verify!(context:, **_args) + raise(JWT::InvalidCritError, 'Crit header missing') unless context.header['crit'] + raise(JWT::InvalidCritError, 'Crit header should be an array') unless context.header['crit'].is_a?(Array) + + missing = (expected_crits - context.header['crit']) + raise(JWT::InvalidCritError, "Crit header missing expected values: #{missing.join(', ')}") if missing.any? + + nil + end + + private + + attr_reader :expected_crits + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/decode_verifier.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/decode_verifier.rb new file mode 100644 index 00000000..411bb97c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/decode_verifier.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module JWT + module Claims + # Context class to contain the data passed to individual claim validators + # + # @api private + VerificationContext = Struct.new(:payload, keyword_init: true) + + # Verifiers to support the ::JWT.decode method + # + # @api private + module DecodeVerifier + VERIFIERS = { + verify_expiration: ->(options) { Claims::Expiration.new(leeway: options[:exp_leeway] || options[:leeway]) }, + verify_not_before: ->(options) { Claims::NotBefore.new(leeway: options[:nbf_leeway] || options[:leeway]) }, + verify_iss: ->(options) { options[:iss] && Claims::Issuer.new(issuers: options[:iss]) }, + verify_iat: ->(*) { Claims::IssuedAt.new }, + verify_jti: ->(options) { Claims::JwtId.new(validator: options[:verify_jti]) }, + verify_aud: ->(options) { options[:aud] && Claims::Audience.new(expected_audience: options[:aud]) }, + verify_sub: ->(options) { options[:sub] && Claims::Subject.new(expected_subject: options[:sub]) }, + required_claims: ->(options) { Claims::Required.new(required_claims: options[:required_claims]) } + }.freeze + + private_constant(:VERIFIERS) + + class << self + # @api private + def verify!(payload, options) + VERIFIERS.each do |key, verifier_builder| + next unless options[key] || options[key.to_s] + + verifier_builder&.call(options)&.verify!(context: VerificationContext.new(payload: payload)) + end + nil + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/expiration.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/expiration.rb new file mode 100644 index 00000000..0412dc4c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/expiration.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module JWT + module Claims + # The Expiration class is responsible for validating the expiration claim ('exp') in a JWT token. + class Expiration + # Initializes a new Expiration instance. + # + # @param leeway [Integer] the amount of leeway (in seconds) to allow when validating the expiration time. Default: 0. + def initialize(leeway:) + @leeway = leeway || 0 + end + + # Verifies the expiration claim ('exp') in the JWT token. + # + # @param context [Object] the context containing the JWT payload. + # @param _args [Hash] additional arguments (not used). + # @raise [JWT::ExpiredSignature] if the token has expired. + # @return [nil] + def verify!(context:, **_args) + return unless context.payload.is_a?(Hash) + return unless context.payload.key?('exp') + + raise JWT::ExpiredSignature, 'Signature has expired' if context.payload['exp'].to_i <= (Time.now.to_i - leeway) + end + + private + + attr_reader :leeway + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/issued_at.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/issued_at.rb new file mode 100644 index 00000000..0eb08446 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/issued_at.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module JWT + module Claims + # The IssuedAt class is responsible for validating the issued at claim ('iat') in a JWT token. + class IssuedAt + # Verifies the issued at claim ('iat') in the JWT token. + # + # @param context [Object] the context containing the JWT payload. + # @param _args [Hash] additional arguments (not used). + # @raise [JWT::InvalidIatError] if the issued at claim is invalid. + # @return [nil] + def verify!(context:, **_args) + return unless context.payload.is_a?(Hash) + return unless context.payload.key?('iat') + + iat = context.payload['iat'] + raise(JWT::InvalidIatError, 'Invalid iat') if !iat.is_a?(::Numeric) || iat.to_f > Time.now.to_f + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/issuer.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/issuer.rb new file mode 100644 index 00000000..ca878375 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/issuer.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module JWT + module Claims + # The Issuer class is responsible for validating the issuer claim ('iss') in a JWT token. + class Issuer + # Initializes a new Issuer instance. + # + # @param issuers [String, Symbol, Array] the expected issuer(s) for the JWT token. + def initialize(issuers:) + @issuers = Array(issuers).map { |item| item.is_a?(Symbol) ? item.to_s : item } + end + + # Verifies the issuer claim ('iss') in the JWT token. + # + # @param context [Object] the context containing the JWT payload. + # @param _args [Hash] additional arguments (not used). + # @raise [JWT::InvalidIssuerError] if the issuer claim is invalid. + # @return [nil] + def verify!(context:, **_args) + case (iss = context.payload['iss']) + when *issuers + nil + else + raise JWT::InvalidIssuerError, "Invalid issuer. Expected #{issuers}, received #{iss || ''}" + end + end + + private + + attr_reader :issuers + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/jwt_id.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/jwt_id.rb new file mode 100644 index 00000000..8d17fdcc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/jwt_id.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module JWT + module Claims + # The JwtId class is responsible for validating the JWT ID claim ('jti') in a JWT token. + class JwtId + # Initializes a new JwtId instance. + # + # @param validator [#call] an object responding to `call` to validate the JWT ID. + def initialize(validator:) + @validator = validator + end + + # Verifies the JWT ID claim ('jti') in the JWT token. + # + # @param context [Object] the context containing the JWT payload. + # @param _args [Hash] additional arguments (not used). + # @raise [JWT::InvalidJtiError] if the JWT ID claim is invalid or missing. + # @return [nil] + def verify!(context:, **_args) + jti = context.payload['jti'] + if validator.respond_to?(:call) + verified = validator.arity == 2 ? validator.call(jti, context.payload) : validator.call(jti) + raise(JWT::InvalidJtiError, 'Invalid jti') unless verified + elsif jti.to_s.strip.empty? + raise(JWT::InvalidJtiError, 'Missing jti') + end + end + + private + + attr_reader :validator + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/not_before.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/not_before.rb new file mode 100644 index 00000000..879ad031 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/not_before.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module JWT + module Claims + # The NotBefore class is responsible for validating the 'nbf' (Not Before) claim in a JWT token. + class NotBefore + # Initializes a new NotBefore instance. + # + # @param leeway [Integer] the amount of leeway (in seconds) to allow when validating the 'nbf' claim. Defaults to 0. + def initialize(leeway:) + @leeway = leeway || 0 + end + + # Verifies the 'nbf' (Not Before) claim in the JWT token. + # + # @param context [Object] the context containing the JWT payload. + # @param _args [Hash] additional arguments (not used). + # @raise [JWT::ImmatureSignature] if the 'nbf' claim has not been reached. + # @return [nil] + def verify!(context:, **_args) + return unless context.payload.is_a?(Hash) + return unless context.payload.key?('nbf') + + raise JWT::ImmatureSignature, 'Signature nbf has not been reached' if context.payload['nbf'].to_i > (Time.now.to_i + leeway) + end + + private + + attr_reader :leeway + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/numeric.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/numeric.rb new file mode 100644 index 00000000..7589dc6b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/numeric.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module JWT + module Claims + # The Numeric class is responsible for validating numeric claims in a JWT token. + # The numeric claims are: exp, iat and nbf + class Numeric + # List of numeric claims that can be validated. + NUMERIC_CLAIMS = %i[ + exp + iat + nbf + ].freeze + + private_constant(:NUMERIC_CLAIMS) + + # Verifies the numeric claims in the JWT context. + # + # @param context [Object] the context containing the JWT payload. + # @raise [JWT::InvalidClaimError] if any numeric claim is invalid. + # @return [nil] + def verify!(context:) + validate_numeric_claims(context.payload) + end + + private + + def validate_numeric_claims(payload) + NUMERIC_CLAIMS.each do |claim| + validate_is_numeric(payload, claim) + end + end + + def validate_is_numeric(payload, claim) + return unless payload.is_a?(Hash) + return unless payload.key?(claim) || + payload.key?(claim.to_s) + + return if payload[claim].is_a?(::Numeric) || payload[claim.to_s].is_a?(::Numeric) + + raise InvalidPayload, "#{claim} claim must be a Numeric value but it is a #{(payload[claim] || payload[claim.to_s]).class}" + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/required.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/required.rb new file mode 100644 index 00000000..e0f0e1d7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/required.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module JWT + module Claims + # The Required class is responsible for validating that all required claims are present in a JWT token. + class Required + # Initializes a new Required instance. + # + # @param required_claims [Array] the list of required claims. + def initialize(required_claims:) + @required_claims = required_claims + end + + # Verifies that all required claims are present in the JWT payload. + # + # @param context [Object] the context containing the JWT payload. + # @param _args [Hash] additional arguments (not used). + # @raise [JWT::MissingRequiredClaim] if any required claim is missing. + # @return [nil] + def verify!(context:, **_args) + required_claims.each do |required_claim| + next if context.payload.is_a?(Hash) && context.payload.key?(required_claim) + + raise JWT::MissingRequiredClaim, "Missing required claim #{required_claim}" + end + end + + private + + attr_reader :required_claims + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/subject.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/subject.rb new file mode 100644 index 00000000..18b26eeb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/subject.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module JWT + module Claims + # The Subject class is responsible for validating the subject claim ('sub') in a JWT token. + class Subject + # Initializes a new Subject instance. + # + # @param expected_subject [String] the expected subject for the JWT token. + def initialize(expected_subject:) + @expected_subject = expected_subject.to_s + end + + # Verifies the subject claim ('sub') in the JWT token. + # + # @param context [Object] the context containing the JWT payload. + # @param _args [Hash] additional arguments (not used). + # @raise [JWT::InvalidSubError] if the subject claim is invalid. + # @return [nil] + def verify!(context:, **_args) + sub = context.payload['sub'] + raise(JWT::InvalidSubError, "Invalid subject. Expected #{expected_subject}, received #{sub || ''}") unless sub.to_s == expected_subject + end + + private + + attr_reader :expected_subject + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/verifier.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/verifier.rb new file mode 100644 index 00000000..81ce8a23 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/claims/verifier.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module JWT + module Claims + # @api private + module Verifier + VERIFIERS = { + exp: ->(options) { Claims::Expiration.new(leeway: options.dig(:exp, :leeway)) }, + nbf: ->(options) { Claims::NotBefore.new(leeway: options.dig(:nbf, :leeway)) }, + iss: ->(options) { Claims::Issuer.new(issuers: options[:iss]) }, + iat: ->(*) { Claims::IssuedAt.new }, + jti: ->(options) { Claims::JwtId.new(validator: options[:jti]) }, + aud: ->(options) { Claims::Audience.new(expected_audience: options[:aud]) }, + sub: ->(options) { Claims::Subject.new(expected_subject: options[:sub]) }, + crit: ->(options) { Claims::Crit.new(expected_crits: options[:crit]) }, + required: ->(options) { Claims::Required.new(required_claims: options[:required]) }, + numeric: ->(*) { Claims::Numeric.new } + }.freeze + + private_constant(:VERIFIERS) + + class << self + # @api private + def verify!(context, *options) + iterate_verifiers(*options) do |verifier, verifier_options| + verify_one!(context, verifier, verifier_options) + end + nil + end + + # @api private + def errors(context, *options) + errors = [] + iterate_verifiers(*options) do |verifier, verifier_options| + verify_one!(context, verifier, verifier_options) + rescue ::JWT::DecodeError => e + errors << Error.new(message: e.message) + end + errors + end + + private + + def iterate_verifiers(*options) + options.each do |element| + if element.is_a?(Hash) + element.each_key { |key| yield(key, element) } + else + yield(element, {}) + end + end + end + + def verify_one!(context, verifier, options) + verifier_builder = VERIFIERS.fetch(verifier) { raise ArgumentError, "#{verifier} not a valid claim verifier" } + verifier_builder.call(options || {}).verify!(context: context) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/configuration.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/configuration.rb new file mode 100644 index 00000000..cdd37a4a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/configuration.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require_relative 'configuration/container' + +module JWT + # The Configuration module provides methods to configure JWT settings. + module Configuration + # Configures the JWT settings. + # + # @yield [config] Gives the current configuration to the block. + # @yieldparam config [JWT::Configuration::Container] the configuration container. + def configure + yield(configuration) + end + + # Returns the JWT configuration container. + # + # @return [JWT::Configuration::Container] the configuration container. + def configuration + @configuration ||= ::JWT::Configuration::Container.new + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/configuration/container.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/configuration/container.rb new file mode 100644 index 00000000..9351d965 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/configuration/container.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require_relative 'decode_configuration' +require_relative 'jwk_configuration' + +module JWT + module Configuration + # The Container class holds the configuration settings for JWT. + class Container + # @!attribute [rw] decode + # @return [DecodeConfiguration] the decode configuration. + # @!attribute [rw] jwk + # @return [JwkConfiguration] the JWK configuration. + # @!attribute [rw] strict_base64_decoding + # @return [Boolean] whether strict Base64 decoding is enabled. + attr_accessor :decode, :jwk, :strict_base64_decoding + + # @!attribute [r] deprecation_warnings + # @return [Symbol] the deprecation warnings setting. + attr_reader :deprecation_warnings + + # Initializes a new Container instance and resets the configuration. + def initialize + reset! + end + + # Resets the configuration to default values. + # + # @return [void] + def reset! + @decode = DecodeConfiguration.new + @jwk = JwkConfiguration.new + + self.deprecation_warnings = :once + end + + DEPRECATION_WARNINGS_VALUES = %i[once warn silent].freeze + private_constant(:DEPRECATION_WARNINGS_VALUES) + # Sets the deprecation warnings setting. + # + # @param value [Symbol] the deprecation warnings setting. Must be one of `:once`, `:warn`, or `:silent`. + # @raise [ArgumentError] if the value is not one of the supported values. + # @return [void] + def deprecation_warnings=(value) + raise ArgumentError, "Invalid deprecation_warnings value #{value}. Supported values: #{DEPRECATION_WARNINGS_VALUES}" unless DEPRECATION_WARNINGS_VALUES.include?(value) + + @deprecation_warnings = value + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/configuration/decode_configuration.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/configuration/decode_configuration.rb new file mode 100644 index 00000000..4acfd3eb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/configuration/decode_configuration.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module JWT + module Configuration + # The DecodeConfiguration class holds the configuration settings for decoding JWT tokens. + class DecodeConfiguration + # @!attribute [rw] verify_expiration + # @return [Boolean] whether to verify the expiration claim. + # @!attribute [rw] verify_not_before + # @return [Boolean] whether to verify the not before claim. + # @!attribute [rw] verify_iss + # @return [Boolean] whether to verify the issuer claim. + # @!attribute [rw] verify_iat + # @return [Boolean] whether to verify the issued at claim. + # @!attribute [rw] verify_jti + # @return [Boolean] whether to verify the JWT ID claim. + # @!attribute [rw] verify_aud + # @return [Boolean] whether to verify the audience claim. + # @!attribute [rw] verify_sub + # @return [Boolean] whether to verify the subject claim. + # @!attribute [rw] leeway + # @return [Integer] the leeway in seconds for time-based claims. + # @!attribute [rw] algorithms + # @return [Array] the list of acceptable algorithms. + # @!attribute [rw] required_claims + # @return [Array] the list of required claims. + + attr_accessor :verify_expiration, + :verify_not_before, + :verify_iss, + :verify_iat, + :verify_jti, + :verify_aud, + :verify_sub, + :leeway, + :algorithms, + :required_claims + + # Initializes a new DecodeConfiguration instance with default settings. + def initialize + @verify_expiration = true + @verify_not_before = true + @verify_iss = false + @verify_iat = false + @verify_jti = false + @verify_aud = false + @verify_sub = false + @leeway = 0 + @algorithms = ['HS256'] + @required_claims = [] + end + + # @api private + def to_h + { + verify_expiration: verify_expiration, + verify_not_before: verify_not_before, + verify_iss: verify_iss, + verify_iat: verify_iat, + verify_jti: verify_jti, + verify_aud: verify_aud, + verify_sub: verify_sub, + leeway: leeway, + algorithms: algorithms, + required_claims: required_claims + } + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/configuration/jwk_configuration.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/configuration/jwk_configuration.rb new file mode 100644 index 00000000..f1373bce --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/configuration/jwk_configuration.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require_relative '../jwk/kid_as_key_digest' +require_relative '../jwk/thumbprint' + +module JWT + module Configuration + # @api private + class JwkConfiguration + def initialize + self.kid_generator_type = :key_digest + end + + def kid_generator_type=(value) + self.kid_generator = case value + when :key_digest + JWT::JWK::KidAsKeyDigest + when :rfc7638_thumbprint + JWT::JWK::Thumbprint + else + raise ArgumentError, "#{value} is not a valid kid generator type." + end + end + + attr_accessor :kid_generator + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/decode.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/decode.rb new file mode 100644 index 00000000..9a8a0a60 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/decode.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +require 'json' +require 'jwt/x5c_key_finder' + +module JWT + # The Decode class is responsible for decoding and verifying JWT tokens. + class Decode + # Order is very important - first check for string keys, next for symbols + ALGORITHM_KEYS = ['algorithm', + :algorithm, + 'algorithms', + :algorithms].freeze + # Initializes a new Decode instance. + # + # @param jwt [String] the JWT to decode. + # @param key [String, Array] the key(s) to use for verification. + # @param verify [Boolean] whether to verify the token's signature. + # @param options [Hash] additional options for decoding and verification. + # @param keyfinder [Proc] an optional key finder block to dynamically find the key for verification. + # @raise [JWT::DecodeError] if decoding or verification fails. + def initialize(jwt, key, verify, options, &keyfinder) + raise JWT::DecodeError, 'Nil JSON web token' unless jwt + + @token = EncodedToken.new(jwt) + @key = key + @options = options + @verify = verify + @keyfinder = keyfinder + end + + # Decodes the JWT token and verifies its segments if verification is enabled. + # + # @return [Array] an array containing the decoded payload and header. + def decode_segments + validate_segment_count! + if @verify + verify_algo + set_key + verify_signature + Claims::DecodeVerifier.verify!(token.unverified_payload, @options) + end + + [token.unverified_payload, token.header] + end + + private + + attr_reader :token + + def verify_signature + return if none_algorithm? + + raise JWT::DecodeError, 'No verification key available' unless @key + + token.verify_signature!(algorithm: allowed_and_valid_algorithms, key: @key) + end + + def verify_algo + raise JWT::IncorrectAlgorithm, 'An algorithm must be specified' if allowed_algorithms.empty? + raise JWT::DecodeError, 'Token header not a JSON object' unless token.header.is_a?(Hash) + raise JWT::IncorrectAlgorithm, 'Token is missing alg header' unless alg_in_header + raise JWT::IncorrectAlgorithm, 'Expected a different algorithm' if allowed_and_valid_algorithms.empty? + end + + def set_key + @key = find_key(&@keyfinder) if @keyfinder + if @options[:jwks] + @key = ::JWT::JWK::KeyFinder.new( + jwks: @options[:jwks], + allow_nil_kid: @options[:allow_nil_kid], + key_fields: @options[:key_fields] + ).call(token) + end + + return unless (x5c_options = @options[:x5c]) + + @key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(token.header['x5c']) + end + + def allowed_and_valid_algorithms + @allowed_and_valid_algorithms ||= allowed_algorithms.select { |alg| alg.valid_alg?(alg_in_header) } + end + + def given_algorithms + alg_key = ALGORITHM_KEYS.find { |key| @options[key] } + Array(@options[alg_key]) + end + + def allowed_algorithms + @allowed_algorithms ||= resolve_allowed_algorithms + end + + def resolve_allowed_algorithms + given_algorithms.map { |alg| JWA.resolve(alg) } + end + + def find_key(&keyfinder) + key = (keyfinder.arity == 2 ? yield(token.header, token.unverified_payload) : yield(token.header)) + # key can be of type [string, nil, OpenSSL::PKey, Array] + return key if key && !Array(key).empty? + + raise JWT::DecodeError, 'No verification key available' + end + + def validate_segment_count! + segment_count = token.jwt.count('.') + 1 + return if segment_count == 3 + return if !@verify && segment_count == 2 # If no verifying required, the signature is not needed + return if segment_count == 2 && none_algorithm? + + raise JWT::DecodeError, 'Not enough or too many segments' + end + + def none_algorithm? + alg_in_header == 'none' + end + + def alg_in_header + token.header['alg'] + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/encode.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/encode.rb new file mode 100644 index 00000000..32164e09 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/encode.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require_relative 'jwa' + +module JWT + # The Encode class is responsible for encoding JWT tokens. + class Encode + # Initializes a new Encode instance. + # + # @param options [Hash] the options for encoding the JWT token. + # @option options [Hash] :payload the payload of the JWT token. + # @option options [Hash] :headers the headers of the JWT token. + # @option options [String] :key the key used to sign the JWT token. + # @option options [String] :algorithm the algorithm used to sign the JWT token. + def initialize(options) + @token = Token.new(payload: options[:payload], header: options[:headers]) + @key = options[:key] + @algorithm = options[:algorithm] + end + + # Encodes the JWT token and returns its segments. + # + # @return [String] the encoded JWT token. + def segments + @token.verify_claims!(:numeric) + @token.sign!(algorithm: @algorithm, key: @key) + @token.jwt + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/encoded_token.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/encoded_token.rb new file mode 100644 index 00000000..cbaec1c8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/encoded_token.rb @@ -0,0 +1,236 @@ +# frozen_string_literal: true + +module JWT + # Represents an encoded JWT token + # + # Processing an encoded and signed token: + # + # token = JWT::Token.new(payload: {pay: 'load'}) + # token.sign!(algorithm: 'HS256', key: 'secret') + # + # encoded_token = JWT::EncodedToken.new(token.jwt) + # encoded_token.verify_signature!(algorithm: 'HS256', key: 'secret') + # encoded_token.payload # => {'pay' => 'load'} + class EncodedToken + # @private + # Allow access to the unverified payload for claim verification. + class ClaimsContext + extend Forwardable + + def_delegators :@token, :header, :unverified_payload + + def initialize(token) + @token = token + end + + def payload + unverified_payload + end + end + + DEFAULT_CLAIMS = [:exp].freeze + + private_constant(:DEFAULT_CLAIMS) + + # Returns the original token provided to the class. + # @return [String] The JWT token. + attr_reader :jwt + + # Initializes a new EncodedToken instance. + # + # @param jwt [String] the encoded JWT token. + # @raise [ArgumentError] if the provided JWT is not a String. + def initialize(jwt) + raise ArgumentError, 'Provided JWT must be a String' unless jwt.is_a?(String) + + @jwt = jwt + @signature_verified = false + @claims_verified = false + + @encoded_header, @encoded_payload, @encoded_signature = jwt.split('.') + end + + # Returns the decoded signature of the JWT token. + # + # @return [String] the decoded signature. + def signature + @signature ||= ::JWT::Base64.url_decode(encoded_signature || '') + end + + # Returns the encoded signature of the JWT token. + # + # @return [String] the encoded signature. + attr_reader :encoded_signature + + # Returns the decoded header of the JWT token. + # + # @return [Hash] the header. + def header + @header ||= parse_and_decode(@encoded_header) + end + + # Returns the encoded header of the JWT token. + # + # @return [String] the encoded header. + attr_reader :encoded_header + + # Returns the payload of the JWT token. Access requires the signature and claims to have been verified. + # + # @return [Hash] the payload. + # @raise [JWT::DecodeError] if the signature has not been verified. + def payload + raise JWT::DecodeError, 'Verify the token signature before accessing the payload' unless @signature_verified + raise JWT::DecodeError, 'Verify the token claims before accessing the payload' unless @claims_verified + + decoded_payload + end + + # Returns the payload of the JWT token without requiring the signature to have been verified. + # @return [Hash] the payload. + def unverified_payload + decoded_payload + end + + # Sets or returns the encoded payload of the JWT token. + # + # @return [String] the encoded payload. + attr_accessor :encoded_payload + + # Returns the signing input of the JWT token. + # + # @return [String] the signing input. + def signing_input + [encoded_header, encoded_payload].join('.') + end + + # Verifies the token signature and claims. + # By default it verifies the 'exp' claim. + # + # @example + # encoded_token.verify!(signature: { algorithm: 'HS256', key: 'secret' }, claims: [:exp]) + # + # @param signature [Hash] the parameters for signature verification (see {#verify_signature!}). + # @param claims [Array, Hash] the claims to verify (see {#verify_claims!}). + # @return [nil] + # @raise [JWT::DecodeError] if the signature or claim verification fails. + def verify!(signature:, claims: nil) + verify_signature!(**signature) + claims.is_a?(Array) ? verify_claims!(*claims) : verify_claims!(claims) + nil + end + + # Verifies the token signature and claims. + # By default it verifies the 'exp' claim. + + # @param signature [Hash] the parameters for signature verification (see {#verify_signature!}). + # @param claims [Array, Hash] the claims to verify (see {#verify_claims!}). + # @return [Boolean] true if the signature and claims are valid, false otherwise. + def valid?(signature:, claims: nil) + valid_signature?(**signature) && + (claims.is_a?(Array) ? valid_claims?(*claims) : valid_claims?(claims)) + end + + # Verifies the signature of the JWT token. + # + # @param algorithm [String, Array, Object, Array] the algorithm(s) to use for verification. + # @param key [String, Array] the key(s) to use for verification. + # @param key_finder [#call] an object responding to `call` to find the key for verification. + # @return [nil] + # @raise [JWT::VerificationError] if the signature verification fails. + # @raise [ArgumentError] if neither key nor key_finder is provided, or if both are provided. + def verify_signature!(algorithm:, key: nil, key_finder: nil) + return if valid_signature?(algorithm: algorithm, key: key, key_finder: key_finder) + + raise JWT::VerificationError, 'Signature verification failed' + end + + # Checks if the signature of the JWT token is valid. + # + # @param algorithm [String, Array, Object, Array] the algorithm(s) to use for verification. + # @param key [String, Array, JWT::JWK::KeyBase, Array] the key(s) to use for verification. + # @param key_finder [#call] an object responding to `call` to find the key for verification. + # @return [Boolean] true if the signature is valid, false otherwise. + def valid_signature?(algorithm: nil, key: nil, key_finder: nil) + raise ArgumentError, 'Provide either key or key_finder, not both or neither' if key.nil? == key_finder.nil? + + keys = Array(key || key_finder.call(self)) + verifiers = JWA.create_verifiers(algorithms: algorithm, keys: keys, preferred_algorithm: header['alg']) + + raise JWT::VerificationError, 'No algorithm provided' if verifiers.empty? + + valid = verifiers.any? do |jwa| + jwa.verify(data: signing_input, signature: signature) + end + valid.tap { |verified| @signature_verified = verified } + end + + # Verifies the claims of the token. + # @param options [Array, Hash] the claims to verify. By default, it checks the 'exp' claim. + # @raise [JWT::DecodeError] if the claims are invalid. + def verify_claims!(*options) + Claims::Verifier.verify!(ClaimsContext.new(self), *claims_options(options)).tap do + @claims_verified = true + end + rescue StandardError + @claims_verified = false + raise + end + + # Returns the errors of the claims of the token. + # @param options [Array, Hash] the claims to verify. By default, it checks the 'exp' claim. + # @return [Array] the errors of the claims. + def claim_errors(*options) + Claims::Verifier.errors(ClaimsContext.new(self), *claims_options(options)) + end + + # Returns whether the claims of the token are valid. + # @param options [Array, Hash] the claims to verify. By default, it checks the 'exp' claim. + # @return [Boolean] whether the claims are valid. + def valid_claims?(*options) + claim_errors(*claims_options(options)).empty?.tap { |verified| @claims_verified = verified } + end + + alias to_s jwt + + private + + def claims_options(options) + return DEFAULT_CLAIMS if options.first.nil? + + options + end + + def decode_payload + raise JWT::DecodeError, 'Encoded payload is empty' if encoded_payload == '' + + if unencoded_payload? + verify_claims!(crit: ['b64']) + return parse_unencoded(encoded_payload) + end + + parse_and_decode(encoded_payload) + end + + def unencoded_payload? + header['b64'] == false + end + + def parse_and_decode(segment) + parse(::JWT::Base64.url_decode(segment || '')) + end + + def parse_unencoded(segment) + parse(segment) + end + + def parse(segment) + JWT::JSON.parse(segment) + rescue ::JSON::ParserError + raise JWT::DecodeError, 'Invalid segment encoding' + end + + def decoded_payload + @decoded_payload ||= decode_payload + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/error.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/error.rb new file mode 100644 index 00000000..2a0f8a2c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/error.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module JWT + # The EncodeError class is raised when there is an error encoding a JWT. + class EncodeError < StandardError; end + + # The DecodeError class is raised when there is an error decoding a JWT. + class DecodeError < StandardError; end + + # The VerificationError class is raised when there is an error verifying a JWT. + class VerificationError < DecodeError; end + + # The ExpiredSignature class is raised when the JWT signature has expired. + class ExpiredSignature < DecodeError; end + + # The IncorrectAlgorithm class is raised when the JWT algorithm is incorrect. + class IncorrectAlgorithm < DecodeError; end + + # The ImmatureSignature class is raised when the JWT signature is immature. + class ImmatureSignature < DecodeError; end + + # The InvalidIssuerError class is raised when the JWT issuer is invalid. + class InvalidIssuerError < DecodeError; end + + # The UnsupportedEcdsaCurve class is raised when the ECDSA curve is unsupported. + class UnsupportedEcdsaCurve < IncorrectAlgorithm; end + + # The InvalidIatError class is raised when the JWT issued at (iat) claim is invalid. + class InvalidIatError < DecodeError; end + + # The InvalidAudError class is raised when the JWT audience (aud) claim is invalid. + class InvalidAudError < DecodeError; end + + # The InvalidSubError class is raised when the JWT subject (sub) claim is invalid. + class InvalidSubError < DecodeError; end + + # The InvalidCritError class is raised when the JWT crit header is invalid. + class InvalidCritError < DecodeError; end + + # The InvalidJtiError class is raised when the JWT ID (jti) claim is invalid. + class InvalidJtiError < DecodeError; end + + # The InvalidPayload class is raised when the JWT payload is invalid. + class InvalidPayload < DecodeError; end + + # The MissingRequiredClaim class is raised when a required claim is missing from the JWT. + class MissingRequiredClaim < DecodeError; end + + # The Base64DecodeError class is raised when there is an error decoding a Base64-encoded string. + class Base64DecodeError < DecodeError; end + + # The JWKError class is raised when there is an error with the JSON Web Key (JWK). + class JWKError < DecodeError; end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/json.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/json.rb new file mode 100644 index 00000000..90ae4585 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/json.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'json' + +module JWT + # @api private + class JSON + class << self + def generate(data) + ::JSON.generate(data) + end + + def parse(data) + ::JSON.parse(data) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa.rb new file mode 100644 index 00000000..11f23c4b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require 'openssl' + +require_relative 'jwa/signing_algorithm' +require_relative 'jwa/ecdsa' +require_relative 'jwa/hmac' +require_relative 'jwa/none' +require_relative 'jwa/ps' +require_relative 'jwa/rsa' +require_relative 'jwa/unsupported' + +module JWT + # The JWA module contains all supported algorithms. + module JWA + # @api private + class VerifierContext + attr_reader :jwa + + def initialize(jwa:, keys:) + @jwa = jwa + @keys = Array(keys) + end + + def verify(*args, **kwargs) + @keys.any? do |key| + @jwa.verify(*args, **kwargs, verification_key: key) + end + end + end + + # @api private + class SignerContext + attr_reader :jwa + + def initialize(jwa:, key:) + @jwa = jwa + @key = key + end + + def sign(*args, **kwargs) + @jwa.sign(*args, **kwargs, signing_key: @key) + end + end + + class << self + # @api private + def resolve(algorithm) + return find(algorithm) if algorithm.is_a?(String) || algorithm.is_a?(Symbol) + + raise ArgumentError, 'Algorithm must be provided' if algorithm.nil? + + raise ArgumentError, 'Custom algorithms are required to include JWT::JWA::SigningAlgorithm' unless algorithm.is_a?(SigningAlgorithm) + + algorithm + end + + # @api private + def resolve_and_sort(algorithms:, preferred_algorithm:) + Array(algorithms).map { |alg| JWA.resolve(alg) } + .partition { |alg| alg.valid_alg?(preferred_algorithm) } + .flatten + end + + # @api private + def create_signer(algorithm:, key:) + if key.is_a?(JWK::KeyBase) + validate_jwk_algorithms!(key, algorithm, DecodeError) + + return key + end + + SignerContext.new(jwa: resolve(algorithm), key: key) + end + + # @api private + def create_verifiers(algorithms:, keys:, preferred_algorithm:) + jwks, other_keys = keys.partition { |key| key.is_a?(JWK::KeyBase) } + + validate_jwk_algorithms!(jwks, algorithms, VerificationError) + + jwks + resolve_and_sort(algorithms: algorithms, + preferred_algorithm: preferred_algorithm) + .map { |jwa| VerifierContext.new(jwa: jwa, keys: other_keys) } + end + + # @api private + def validate_jwk_algorithms!(jwks, algorithms, error_class) + algorithms = Array(algorithms) + + return if algorithms.empty? + + return if Array(jwks).all? do |jwk| + algorithms.any? do |alg| + jwk.jwa.valid_alg?(alg) + end + end + + raise error_class, "Provided JWKs do not support one of the specified algorithms: #{algorithms.join(', ')}" + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/ecdsa.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/ecdsa.rb new file mode 100644 index 00000000..9840621f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/ecdsa.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +module JWT + module JWA + # ECDSA signing algorithm + class Ecdsa + include JWT::JWA::SigningAlgorithm + + def initialize(alg, digest) + @alg = alg + @digest = digest + end + + def sign(data:, signing_key:) + raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::EC instance") unless signing_key.is_a?(::OpenSSL::PKey::EC) + raise_sign_error!('The given key is not a private key') unless signing_key.private? + + curve_definition = curve_by_name(signing_key.group.curve_name) + key_algorithm = curve_definition[:algorithm] + + raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} signing key was provided" if alg != key_algorithm + + asn1_to_raw(signing_key.dsa_sign_asn1(OpenSSL::Digest.new(digest).digest(data)), signing_key) + end + + def verify(data:, signature:, verification_key:) + verification_key = self.class.create_public_key_from_point(verification_key) if verification_key.is_a?(::OpenSSL::PKey::EC::Point) + + raise_verify_error!("The given key is a #{verification_key.class}. It has to be an OpenSSL::PKey::EC instance") unless verification_key.is_a?(::OpenSSL::PKey::EC) + + curve_definition = curve_by_name(verification_key.group.curve_name) + key_algorithm = curve_definition[:algorithm] + raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} verification key was provided" if alg != key_algorithm + + verification_key.dsa_verify_asn1(OpenSSL::Digest.new(digest).digest(data), raw_to_asn1(signature, verification_key)) + rescue OpenSSL::PKey::PKeyError + raise JWT::VerificationError, 'Signature verification raised' + end + + NAMED_CURVES = { + 'prime256v1' => { + algorithm: 'ES256', + digest: 'sha256' + }, + 'secp256r1' => { # alias for prime256v1 + algorithm: 'ES256', + digest: 'sha256' + }, + 'secp384r1' => { + algorithm: 'ES384', + digest: 'sha384' + }, + 'secp521r1' => { + algorithm: 'ES512', + digest: 'sha512' + }, + 'secp256k1' => { + algorithm: 'ES256K', + digest: 'sha256' + } + }.freeze + + NAMED_CURVES.each_value do |v| + register_algorithm(new(v[:algorithm], v[:digest])) + end + + # @api private + def self.curve_by_name(name) + NAMED_CURVES.fetch(name) do + raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported" + end + end + + if ::JWT.openssl_3? + def self.create_public_key_from_point(point) + sequence = OpenSSL::ASN1::Sequence([ + OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(point.group.curve_name)]), + OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed)) + ]) + OpenSSL::PKey::EC.new(sequence.to_der) + end + else + def self.create_public_key_from_point(point) + OpenSSL::PKey::EC.new(point.group.curve_name).tap do |key| + key.public_key = point + end + end + end + + private + + attr_reader :digest + + def curve_by_name(name) + self.class.curve_by_name(name) + end + + def raw_to_asn1(signature, private_key) + byte_size = (private_key.group.degree + 7) / 8 + sig_bytes = signature[0..(byte_size - 1)] + sig_char = signature[byte_size..-1] || '' + OpenSSL::ASN1::Sequence.new([sig_bytes, sig_char].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der + end + + def asn1_to_raw(signature, public_key) + byte_size = (public_key.group.degree + 7) / 8 + OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/hmac.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/hmac.rb new file mode 100644 index 00000000..86b3278c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/hmac.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module JWT + module JWA + # Implementation of the HMAC family of algorithms + class Hmac + include JWT::JWA::SigningAlgorithm + + def initialize(alg, digest) + @alg = alg + @digest = digest + end + + def sign(data:, signing_key:) + signing_key ||= '' + raise_verify_error!('HMAC key expected to be a String') unless signing_key.is_a?(String) + + OpenSSL::HMAC.digest(digest.new, signing_key, data) + rescue OpenSSL::HMACError => e + raise_verify_error!('OpenSSL 3.0 does not support nil or empty hmac_secret') if signing_key == '' && e.message == 'EVP_PKEY_new_mac_key: malloc failure' + + raise e + end + + def verify(data:, signature:, verification_key:) + SecurityUtils.secure_compare(signature, sign(data: data, signing_key: verification_key)) + end + + register_algorithm(new('HS256', OpenSSL::Digest::SHA256)) + register_algorithm(new('HS384', OpenSSL::Digest::SHA384)) + register_algorithm(new('HS512', OpenSSL::Digest::SHA512)) + + private + + attr_reader :digest + + # Copy of https://github.com/rails/rails/blob/v7.0.3.1/activesupport/lib/active_support/security_utils.rb + # rubocop:disable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate + module SecurityUtils + # Constant time string comparison, for fixed length strings. + # + # The values compared should be of fixed length, such as strings + # that have already been processed by HMAC. Raises in case of length mismatch. + + if defined?(OpenSSL.fixed_length_secure_compare) + def fixed_length_secure_compare(a, b) + OpenSSL.fixed_length_secure_compare(a, b) + end + else + # :nocov: + def fixed_length_secure_compare(a, b) + raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize + + l = a.unpack "C#{a.bytesize}" + + res = 0 + b.each_byte { |byte| res |= byte ^ l.shift } + res == 0 + end + # :nocov: + end + module_function :fixed_length_secure_compare + + # Secure string comparison for strings of variable length. + # + # While a timing attack would not be able to discern the content of + # a secret compared via secure_compare, it is possible to determine + # the secret length. This should be considered when using secure_compare + # to compare weak, short secrets to user input. + def secure_compare(a, b) + a.bytesize == b.bytesize && fixed_length_secure_compare(a, b) + end + module_function :secure_compare + end + # rubocop:enable Naming/MethodParameterName, Style/StringLiterals, Style/NumericPredicate + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/none.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/none.rb new file mode 100644 index 00000000..ddac9495 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/none.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module JWT + module JWA + # Implementation of the none algorithm + class None + include JWT::JWA::SigningAlgorithm + + def initialize + @alg = 'none' + end + + def sign(*) + '' + end + + def verify(*) + true + end + + register_algorithm(new) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/ps.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/ps.rb new file mode 100644 index 00000000..85ef615a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/ps.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module JWT + module JWA + # Implementation of the RSASSA-PSS family of algorithms + class Ps + include JWT::JWA::SigningAlgorithm + + def initialize(alg) + @alg = alg + @digest_algorithm = alg.sub('PS', 'sha') + end + + def sign(data:, signing_key:) + raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance.") unless signing_key.is_a?(::OpenSSL::PKey::RSA) + raise_sign_error!('The key length must be greater than or equal to 2048 bits') if signing_key.n.num_bits < 2048 + + signing_key.sign_pss(digest_algorithm, data, salt_length: :digest, mgf1_hash: digest_algorithm) + end + + def verify(data:, signature:, verification_key:) + verification_key.verify_pss(digest_algorithm, signature, data, salt_length: :auto, mgf1_hash: digest_algorithm) + rescue OpenSSL::PKey::PKeyError + raise JWT::VerificationError, 'Signature verification raised' + end + + register_algorithm(new('PS256')) + register_algorithm(new('PS384')) + register_algorithm(new('PS512')) + + private + + attr_reader :digest_algorithm + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/rsa.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/rsa.rb new file mode 100644 index 00000000..d25b5764 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/rsa.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module JWT + module JWA + # Implementation of the RSA family of algorithms + class Rsa + include JWT::JWA::SigningAlgorithm + + def initialize(alg) + @alg = alg + @digest = alg.sub('RS', 'SHA') + end + + def sign(data:, signing_key:) + raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance") unless signing_key.is_a?(OpenSSL::PKey::RSA) + raise_sign_error!('The key length must be greater than or equal to 2048 bits') if signing_key.n.num_bits < 2048 + + signing_key.sign(OpenSSL::Digest.new(digest), data) + end + + def verify(data:, signature:, verification_key:) + verification_key.verify(OpenSSL::Digest.new(digest), signature, data) + rescue OpenSSL::PKey::PKeyError + raise JWT::VerificationError, 'Signature verification raised' + end + + register_algorithm(new('RS256')) + register_algorithm(new('RS384')) + register_algorithm(new('RS512')) + + private + + attr_reader :digest + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/signing_algorithm.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/signing_algorithm.rb new file mode 100644 index 00000000..b4590a8b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/signing_algorithm.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module JWT + # JSON Web Algorithms + module JWA + # Base functionality for signing algorithms + module SigningAlgorithm + # Class methods for the SigningAlgorithm module + module ClassMethods + def register_algorithm(algo) + ::JWT::JWA.register_algorithm(algo) + end + end + + def self.included(klass) + klass.extend(ClassMethods) + end + + attr_reader :alg + + def valid_alg?(alg_to_check) + alg&.casecmp(alg_to_check)&.zero? == true + end + + def header(*) + { 'alg' => alg } + end + + def sign(*) + raise_sign_error!('Algorithm implementation is missing the sign method') + end + + def verify(*) + raise_verify_error!('Algorithm implementation is missing the verify method') + end + + def raise_verify_error!(message) + raise(DecodeError.new(message).tap { |e| e.set_backtrace(caller(1)) }) + end + + def raise_sign_error!(message) + raise(EncodeError.new(message).tap { |e| e.set_backtrace(caller(1)) }) + end + end + + class << self + def register_algorithm(algo) + algorithms[algo.alg.to_s.downcase] = algo + end + + def find(algo) + algorithms.fetch(algo.to_s.downcase, Unsupported) + end + + private + + def algorithms + @algorithms ||= {} + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/unsupported.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/unsupported.rb new file mode 100644 index 00000000..beb4be1f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwa/unsupported.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module JWT + module JWA + # Represents an unsupported algorithm + module Unsupported + class << self + include JWT::JWA::SigningAlgorithm + + def sign(*) + raise_sign_error!('Unsupported signing method') + end + + def verify(*) + raise JWT::VerificationError, 'Algorithm not supported' + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk.rb new file mode 100644 index 00000000..d717bac7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require_relative 'jwk/key_finder' +require_relative 'jwk/set' + +module JWT + # JSON Web Key (JWK) + module JWK + class << self + def create_from(key, params = nil, options = {}) + if key.is_a?(Hash) + jwk_kty = key[:kty] || key['kty'] + raise JWT::JWKError, 'Key type (kty) not provided' unless jwk_kty + + return mappings.fetch(jwk_kty.to_s) do |kty| + raise JWT::JWKError, "Key type #{kty} not supported" + end.new(key, params, options) + end + + mappings.fetch(key.class) do |klass| + raise JWT::JWKError, "Cannot create JWK from a #{klass.name}" + end.new(key, params, options) + end + + def classes + @mappings = nil # reset the cached mappings + @classes ||= [] + end + + alias new create_from + alias import create_from + + private + + def mappings + @mappings ||= generate_mappings + end + + def generate_mappings + classes.each_with_object({}) do |klass, hash| + next unless klass.const_defined?('KTYS') + + Array(klass::KTYS).each do |kty| + hash[kty] = klass + end + end + end + end + end +end + +require_relative 'jwk/key_base' +require_relative 'jwk/ec' +require_relative 'jwk/rsa' +require_relative 'jwk/hmac' diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/ec.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/ec.rb new file mode 100644 index 00000000..e240aa04 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/ec.rb @@ -0,0 +1,240 @@ +# frozen_string_literal: true + +require 'forwardable' + +module JWT + module JWK + # JWK representation for Elliptic Curve (EC) keys + class EC < KeyBase # rubocop:disable Metrics/ClassLength + KTY = 'EC' + KTYS = [KTY, OpenSSL::PKey::EC, JWT::JWK::EC].freeze + BINARY = 2 + EC_PUBLIC_KEY_ELEMENTS = %i[kty crv x y].freeze + EC_PRIVATE_KEY_ELEMENTS = %i[d].freeze + EC_KEY_ELEMENTS = (EC_PRIVATE_KEY_ELEMENTS + EC_PUBLIC_KEY_ELEMENTS).freeze + ZERO_BYTE = "\0".b.freeze + + def initialize(key, params = nil, options = {}) + params ||= {} + + # For backwards compatibility when kid was a String + params = { kid: params } if params.is_a?(String) + + key_params = extract_key_params(key) + + params = params.transform_keys(&:to_sym) + check_jwk_params!(key_params, params) + + super(options, key_params.merge(params)) + end + + def keypair + ec_key + end + + def private? + ec_key.private_key? + end + + def signing_key + ec_key + end + + def verify_key + ec_key + end + + def public_key + ec_key + end + + def members + EC_PUBLIC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] } + end + + def export(options = {}) + exported = parameters.clone + exported.reject! { |k, _| EC_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true + exported + end + + def key_digest + _crv, x_octets, y_octets = keypair_components(ec_key) + sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(x_octets, BINARY)), + OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(y_octets, BINARY))]) + OpenSSL::Digest::SHA256.hexdigest(sequence.to_der) + end + + def []=(key, value) + raise ArgumentError, 'cannot overwrite cryptographic key attributes' if EC_KEY_ELEMENTS.include?(key.to_sym) + + super + end + + def jwa + return super if self[:alg] + + curve_name = self.class.to_openssl_curve(self[:crv]) + JWA.resolve(JWA::Ecdsa.curve_by_name(curve_name)[:algorithm]) + end + + private + + def ec_key + @ec_key ||= create_ec_key(self[:crv], self[:x], self[:y], self[:d]) + end + + def extract_key_params(key) + case key + when JWT::JWK::EC + key.export(include_private: true) + when OpenSSL::PKey::EC # Accept OpenSSL key as input + @ec_key = key # Preserve the object to avoid recreation + parse_ec_key(key) + when Hash + key.transform_keys(&:to_sym) + else + raise ArgumentError, 'key must be of type OpenSSL::PKey::EC or Hash with key parameters' + end + end + + def check_jwk_params!(key_params, params) + raise ArgumentError, 'cannot overwrite cryptographic key attributes' unless (EC_KEY_ELEMENTS & params.keys).empty? + raise JWT::JWKError, "Incorrect 'kty' value: #{key_params[:kty]}, expected #{KTY}" unless key_params[:kty] == KTY + raise JWT::JWKError, 'Key format is invalid for EC' unless key_params[:crv] && key_params[:x] && key_params[:y] + end + + def keypair_components(ec_keypair) + encoded_point = ec_keypair.public_key.to_bn.to_s(BINARY) + case ec_keypair.group.curve_name + when 'prime256v1' + crv = 'P-256' + x_octets, y_octets = encoded_point.unpack('xa32a32') + when 'secp256k1' + crv = 'P-256K' + x_octets, y_octets = encoded_point.unpack('xa32a32') + when 'secp384r1' + crv = 'P-384' + x_octets, y_octets = encoded_point.unpack('xa48a48') + when 'secp521r1' + crv = 'P-521' + x_octets, y_octets = encoded_point.unpack('xa66a66') + else + raise JWT::JWKError, "Unsupported curve '#{ec_keypair.group.curve_name}'" + end + [crv, x_octets, y_octets] + end + + def encode_octets(octets) + return unless octets + + ::JWT::Base64.url_encode(octets) + end + + def parse_ec_key(key) + crv, x_octets, y_octets = keypair_components(key) + octets = key.private_key&.to_bn&.to_s(BINARY) + { + kty: KTY, + crv: crv, + x: encode_octets(x_octets), + y: encode_octets(y_octets), + d: encode_octets(octets) + }.compact + end + + def create_point(jwk_crv, jwk_x, jwk_y) + curve = EC.to_openssl_curve(jwk_crv) + x_octets = decode_octets(jwk_x) + y_octets = decode_octets(jwk_y) + + # The details of the `Point` instantiation are covered in: + # - https://docs.ruby-lang.org/en/2.4.0/OpenSSL/PKey/EC.html + # - https://www.openssl.org/docs/manmaster/man3/EC_POINT_new.html + # - https://tools.ietf.org/html/rfc5480#section-2.2 + # - https://www.secg.org/SEC1-Ver-1.0.pdf + # Section 2.3.3 of the last of these references specifies that the + # encoding of an uncompressed point consists of the byte `0x04` followed + # by the x value then the y value. + OpenSSL::PKey::EC::Point.new( + OpenSSL::PKey::EC::Group.new(curve), + OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2) + ) + end + + if ::JWT.openssl_3? + def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d) + point = create_point(jwk_crv, jwk_x, jwk_y) + + return ::JWT::JWA::Ecdsa.create_public_key_from_point(point) unless jwk_d + + # https://datatracker.ietf.org/doc/html/rfc5915.html + # ECPrivateKey ::= SEQUENCE { + # version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), + # privateKey OCTET STRING, + # parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, + # publicKey [1] BIT STRING OPTIONAL + # } + + sequence = OpenSSL::ASN1::Sequence([ + OpenSSL::ASN1::Integer(1), + OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)), + OpenSSL::ASN1::ObjectId(point.group.curve_name, 0, :EXPLICIT), + OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT) + ]) + OpenSSL::PKey::EC.new(sequence.to_der) + end + else + def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d) + point = create_point(jwk_crv, jwk_x, jwk_y) + + ::JWT::JWA::Ecdsa.create_public_key_from_point(point).tap do |key| + key.private_key = OpenSSL::BN.new(decode_octets(jwk_d), 2) if jwk_d + end + end + end + + def decode_octets(base64_encoded_coordinate) + bytes = ::JWT::Base64.url_decode(base64_encoded_coordinate) + # Some base64 encoders on some platform omit a single 0-byte at + # the start of either Y or X coordinate of the elliptic curve point. + # This leads to an encoding error when data is passed to OpenSSL BN. + # It is know to have happened to exported JWKs on a Java application and + # on a Flutter/Dart application (both iOS and Android). All that is + # needed to fix the problem is adding a leading 0-byte. We know the + # required byte is 0 because with any other byte the point is no longer + # on the curve - and OpenSSL will actually communicate this via another + # exception. The indication of a stripped byte will be the fact that the + # coordinates - once decoded into bytes - should always be an even + # bytesize. For example, with a P-521 curve, both x and y must be 66 bytes. + # With a P-256 curve, both x and y must be 32 and so on. The simplest way + # to check for this truncation is thus to check whether the number of bytes + # is odd, and restore the leading 0-byte if it is. + if bytes.bytesize.odd? + ZERO_BYTE + bytes + else + bytes + end + end + + class << self + def import(jwk_data) + new(jwk_data) + end + + def to_openssl_curve(crv) + # The JWK specs and OpenSSL use different names for the same curves. + # See https://tools.ietf.org/html/rfc5480#section-2.1.1.1 for some + # pointers on different names for common curves. + case crv + when 'P-256' then 'prime256v1' + when 'P-384' then 'secp384r1' + when 'P-521' then 'secp521r1' + when 'P-256K' then 'secp256k1' + else raise JWT::JWKError, 'Invalid curve provided' + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/hmac.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/hmac.rb new file mode 100644 index 00000000..6813367a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/hmac.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +module JWT + module JWK + # JWK for HMAC keys + class HMAC < KeyBase + KTY = 'oct' + KTYS = [KTY, String, JWT::JWK::HMAC].freeze + HMAC_PUBLIC_KEY_ELEMENTS = %i[kty].freeze + HMAC_PRIVATE_KEY_ELEMENTS = %i[k].freeze + HMAC_KEY_ELEMENTS = (HMAC_PRIVATE_KEY_ELEMENTS + HMAC_PUBLIC_KEY_ELEMENTS).freeze + + def initialize(key, params = nil, options = {}) + params ||= {} + + # For backwards compatibility when kid was a String + params = { kid: params } if params.is_a?(String) + + key_params = extract_key_params(key) + + params = params.transform_keys(&:to_sym) + check_jwk(key_params, params) + + super(options, key_params.merge(params)) + end + + def keypair + secret + end + + def private? + true + end + + def public_key + nil + end + + def verify_key + secret + end + + def signing_key + secret + end + + # See https://tools.ietf.org/html/rfc7517#appendix-A.3 + def export(options = {}) + exported = parameters.clone + exported.reject! { |k, _| HMAC_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true + exported + end + + def members + HMAC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] } + end + + def key_digest + sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::UTF8String.new(signing_key), + OpenSSL::ASN1::UTF8String.new(KTY)]) + OpenSSL::Digest::SHA256.hexdigest(sequence.to_der) + end + + def []=(key, value) + raise ArgumentError, 'cannot overwrite cryptographic key attributes' if HMAC_KEY_ELEMENTS.include?(key.to_sym) + + super + end + + private + + def secret + @secret ||= ::JWT::Base64.url_decode(self[:k]) + end + + def extract_key_params(key) + case key + when JWT::JWK::HMAC + key.export(include_private: true) + when String # Accept String key as input + { kty: KTY, k: ::JWT::Base64.url_encode(key) } + when Hash + key.transform_keys(&:to_sym) + else + raise ArgumentError, 'key must be of type String or Hash with key parameters' + end + end + + def check_jwk(keypair, params) + raise ArgumentError, 'cannot overwrite cryptographic key attributes' unless (HMAC_KEY_ELEMENTS & params.keys).empty? + raise JWT::JWKError, "Incorrect 'kty' value: #{keypair[:kty]}, expected #{KTY}" unless keypair[:kty] == KTY + raise JWT::JWKError, 'Key format is invalid for HMAC' unless keypair[:k] + end + + class << self + def import(jwk_data) + new(jwk_data) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/key_base.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/key_base.rb new file mode 100644 index 00000000..ac9d9b91 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/key_base.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +module JWT + module JWK + # Base for JWK implementations + class KeyBase + def self.inherited(klass) + super + ::JWT::JWK.classes << klass + end + + def initialize(options, params = {}) + options ||= {} + + @parameters = params.transform_keys(&:to_sym) # Uniform interface + + # For backwards compatibility, kid_generator may be specified in the parameters + options[:kid_generator] ||= @parameters.delete(:kid_generator) + + # Make sure the key has a kid + kid_generator = options[:kid_generator] || ::JWT.configuration.jwk.kid_generator + self[:kid] ||= kid_generator.new(self).generate + end + + def kid + self[:kid] + end + + def hash + self[:kid].hash + end + + def [](key) + @parameters[key.to_sym] + end + + def []=(key, value) + @parameters[key.to_sym] = value + end + + def ==(other) + other.is_a?(::JWT::JWK::KeyBase) && self[:kid] == other[:kid] + end + + def verify(**kwargs) + jwa.verify(**kwargs, verification_key: verify_key) + end + + def sign(**kwargs) + jwa.sign(**kwargs, signing_key: signing_key) + end + + alias eql? == + + def <=>(other) + return nil unless other.is_a?(::JWT::JWK::KeyBase) + + self[:kid] <=> other[:kid] + end + + def jwa + raise JWT::JWKError, 'Could not resolve the JWA, the "alg" parameter is missing' unless self[:alg] + + JWA.resolve(self[:alg]).tap do |jwa| + raise JWT::JWKError, 'none algorithm usage not supported via JWK' if jwa.is_a?(JWA::None) + end + end + + attr_reader :parameters + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/key_finder.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/key_finder.rb new file mode 100644 index 00000000..c7387841 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/key_finder.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +module JWT + module JWK + # JSON Web Key keyfinder + # To find the key for a given kid + class KeyFinder + # Initializes a new KeyFinder instance. + # @param [Hash] options the options to create a KeyFinder with + # @option options [Proc, JWT::JWK::Set] :jwks the jwks or a loader proc + # @option options [Boolean] :allow_nil_kid whether to allow nil kid + # @option options [Array] :key_fields the fields to use for key matching, + # the order of the fields are used to determine + # the priority of the keys. + def initialize(options) + @allow_nil_kid = options[:allow_nil_kid] + jwks_or_loader = options[:jwks] + + @jwks_loader = if jwks_or_loader.respond_to?(:call) + jwks_or_loader + else + ->(_options) { jwks_or_loader } + end + + @key_fields = options[:key_fields] || %i[kid] + end + + # Returns the verification key for the given kid + # @param [String] kid the key id + def key_for(kid, key_field = :kid) + raise ::JWT::DecodeError, "Invalid type for #{key_field} header parameter" unless kid.nil? || kid.is_a?(String) + + jwk = resolve_key(kid, key_field) + + raise ::JWT::DecodeError, 'No keys found in jwks' unless @jwks.any? + raise ::JWT::DecodeError, "Could not find public key for kid #{kid}" unless jwk + + jwk.verify_key + end + + # Returns the key for the given token + # @param [JWT::EncodedToken] token the token + def call(token) + @key_fields.each do |key_field| + field_value = token.header[key_field.to_s] + + return key_for(field_value, key_field) if field_value + end + + raise ::JWT::DecodeError, 'No key id (kid) or x5t found from token headers' unless @allow_nil_kid + + kid = token.header['kid'] + key_for(kid) + end + + private + + def resolve_key(kid, key_field) + key_matcher = ->(key) { (kid.nil? && @allow_nil_kid) || key[key_field] == kid } + + # First try without invalidation to facilitate application caching + @jwks ||= JWT::JWK::Set.new(@jwks_loader.call(key_field => kid)) + jwk = @jwks.find { |key| key_matcher.call(key) } + + return jwk if jwk + + # Second try, invalidate for backwards compatibility + @jwks = JWT::JWK::Set.new(@jwks_loader.call(invalidate: true, kid_not_found: true, key_field => kid)) + @jwks.find { |key| key_matcher.call(key) } + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/kid_as_key_digest.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/kid_as_key_digest.rb new file mode 100644 index 00000000..08a1d2a7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/kid_as_key_digest.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module JWT + module JWK + # @api private + class KidAsKeyDigest + def initialize(jwk) + @jwk = jwk + end + + def generate + @jwk.key_digest + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/rsa.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/rsa.rb new file mode 100644 index 00000000..c4918602 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/rsa.rb @@ -0,0 +1,206 @@ +# frozen_string_literal: true + +module JWT + module JWK + # JSON Web Key (JWK) representation of a RSA key + class RSA < KeyBase # rubocop:disable Metrics/ClassLength + BINARY = 2 + KTY = 'RSA' + KTYS = [KTY, OpenSSL::PKey::RSA, JWT::JWK::RSA].freeze + RSA_PUBLIC_KEY_ELEMENTS = %i[kty n e].freeze + RSA_PRIVATE_KEY_ELEMENTS = %i[d p q dp dq qi].freeze + RSA_KEY_ELEMENTS = (RSA_PRIVATE_KEY_ELEMENTS + RSA_PUBLIC_KEY_ELEMENTS).freeze + + RSA_OPT_PARAMS = %i[p q dp dq qi].freeze + RSA_ASN1_SEQUENCE = (%i[n e d] + RSA_OPT_PARAMS).freeze # https://www.rfc-editor.org/rfc/rfc3447#appendix-A.1.2 + + def initialize(key, params = nil, options = {}) + params ||= {} + + # For backwards compatibility when kid was a String + params = { kid: params } if params.is_a?(String) + + key_params = extract_key_params(key) + + params = params.transform_keys(&:to_sym) + check_jwk_params!(key_params, params) + + super(options, key_params.merge(params)) + end + + def keypair + rsa_key + end + + def private? + rsa_key.private? + end + + def public_key + rsa_key.public_key + end + + def signing_key + rsa_key if private? + end + + def verify_key + rsa_key.public_key + end + + def export(options = {}) + exported = parameters.clone + exported.reject! { |k, _| RSA_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true + + exported + end + + def members + RSA_PUBLIC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] } + end + + def key_digest + sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(public_key.n), + OpenSSL::ASN1::Integer.new(public_key.e)]) + OpenSSL::Digest::SHA256.hexdigest(sequence.to_der) + end + + def []=(key, value) + raise ArgumentError, 'cannot overwrite cryptographic key attributes' if RSA_KEY_ELEMENTS.include?(key.to_sym) + + super + end + + private + + def rsa_key + @rsa_key ||= self.class.create_rsa_key(jwk_attributes(*(RSA_KEY_ELEMENTS - [:kty]))) + end + + def extract_key_params(key) + case key + when JWT::JWK::RSA + key.export(include_private: true) + when OpenSSL::PKey::RSA # Accept OpenSSL key as input + @rsa_key = key # Preserve the object to avoid recreation + parse_rsa_key(key) + when Hash + key.transform_keys(&:to_sym) + else + raise ArgumentError, 'key must be of type OpenSSL::PKey::RSA or Hash with key parameters' + end + end + + def check_jwk_params!(key_params, params) + raise ArgumentError, 'cannot overwrite cryptographic key attributes' unless (RSA_KEY_ELEMENTS & params.keys).empty? + raise JWT::JWKError, "Incorrect 'kty' value: #{key_params[:kty]}, expected #{KTY}" unless key_params[:kty] == KTY + raise JWT::JWKError, 'Key format is invalid for RSA' unless key_params[:n] && key_params[:e] + end + + def parse_rsa_key(key) + { + kty: KTY, + n: encode_open_ssl_bn(key.n), + e: encode_open_ssl_bn(key.e), + d: encode_open_ssl_bn(key.d), + p: encode_open_ssl_bn(key.p), + q: encode_open_ssl_bn(key.q), + dp: encode_open_ssl_bn(key.dmp1), + dq: encode_open_ssl_bn(key.dmq1), + qi: encode_open_ssl_bn(key.iqmp) + }.compact + end + + def jwk_attributes(*attributes) + attributes.each_with_object({}) do |attribute, hash| + hash[attribute] = decode_open_ssl_bn(self[attribute]) + end + end + + def encode_open_ssl_bn(key_part) + return unless key_part + + ::JWT::Base64.url_encode(key_part.to_s(BINARY)) + end + + def decode_open_ssl_bn(jwk_data) + self.class.decode_open_ssl_bn(jwk_data) + end + + class << self + def import(jwk_data) + new(jwk_data) + end + + def decode_open_ssl_bn(jwk_data) + return nil unless jwk_data + + OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY) + end + + def create_rsa_key_using_der(rsa_parameters) + validate_rsa_parameters!(rsa_parameters) + + sequence = RSA_ASN1_SEQUENCE.each_with_object([]) do |key, arr| + next if rsa_parameters[key].nil? + + arr << OpenSSL::ASN1::Integer.new(rsa_parameters[key]) + end + + if sequence.size > 2 # Append "two-prime" version for private key + sequence.unshift(OpenSSL::ASN1::Integer.new(0)) + + raise JWT::JWKError, 'Creating a RSA key with a private key requires the CRT parameters to be defined' if sequence.size < RSA_ASN1_SEQUENCE.size + end + + OpenSSL::PKey::RSA.new(OpenSSL::ASN1::Sequence(sequence).to_der) + end + + def create_rsa_key_using_sets(rsa_parameters) + validate_rsa_parameters!(rsa_parameters) + + OpenSSL::PKey::RSA.new.tap do |rsa_key| + rsa_key.set_key(rsa_parameters[:n], rsa_parameters[:e], rsa_parameters[:d]) + rsa_key.set_factors(rsa_parameters[:p], rsa_parameters[:q]) if rsa_parameters[:p] && rsa_parameters[:q] + rsa_key.set_crt_params(rsa_parameters[:dp], rsa_parameters[:dq], rsa_parameters[:qi]) if rsa_parameters[:dp] && rsa_parameters[:dq] && rsa_parameters[:qi] + end + end + + # :nocov: + # Before openssl 2.0, we need to use the accessors to set the key + def create_rsa_key_using_accessors(rsa_parameters) # rubocop:disable Metrics/AbcSize + validate_rsa_parameters!(rsa_parameters) + + OpenSSL::PKey::RSA.new.tap do |rsa_key| + rsa_key.n = rsa_parameters[:n] + rsa_key.e = rsa_parameters[:e] + rsa_key.d = rsa_parameters[:d] if rsa_parameters[:d] + rsa_key.p = rsa_parameters[:p] if rsa_parameters[:p] + rsa_key.q = rsa_parameters[:q] if rsa_parameters[:q] + rsa_key.dmp1 = rsa_parameters[:dp] if rsa_parameters[:dp] + rsa_key.dmq1 = rsa_parameters[:dq] if rsa_parameters[:dq] + rsa_key.iqmp = rsa_parameters[:qi] if rsa_parameters[:qi] + end + end + # :nocov: + + def validate_rsa_parameters!(rsa_parameters) + return unless rsa_parameters.key?(:d) + + parameters = RSA_OPT_PARAMS - rsa_parameters.keys + return if parameters.empty? || parameters.size == RSA_OPT_PARAMS.size + + raise JWT::JWKError, 'When one of p, q, dp, dq or qi is given all the other optimization parameters also needs to be defined' # https://www.rfc-editor.org/rfc/rfc7518.html#section-6.3.2 + end + + if ::JWT.openssl_3? + alias create_rsa_key create_rsa_key_using_der + elsif OpenSSL::PKey::RSA.new.respond_to?(:set_key) + alias create_rsa_key create_rsa_key_using_sets + else + alias create_rsa_key create_rsa_key_using_accessors + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/set.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/set.rb new file mode 100644 index 00000000..6f93e56e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/set.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require 'forwardable' + +module JWT + module JWK + # JSON Web Key Set (JWKS) representation + # https://tools.ietf.org/html/rfc7517 + class Set + include Enumerable + extend Forwardable + + attr_reader :keys + + def initialize(jwks = nil, options = {}) # rubocop:disable Metrics/CyclomaticComplexity + jwks ||= {} + + @keys = case jwks + when JWT::JWK::Set # Simple duplication + jwks.keys + when JWT::JWK::KeyBase # Singleton + [jwks] + when Hash + jwks = jwks.transform_keys(&:to_sym) + [*jwks[:keys]].map { |k| JWT::JWK.new(k, nil, options) } + when Array + jwks.map { |k| JWT::JWK.new(k, nil, options) } + else + raise ArgumentError, 'Can only create new JWKS from Hash, Array and JWK' + end + end + + def export(options = {}) + { keys: @keys.map { |k| k.export(options) } } + end + + def_delegators :@keys, :each, :size, :delete, :dig + + def select!(&block) + return @keys.select! unless block + + self if @keys.select!(&block) + end + + def reject!(&block) + return @keys.reject! unless block + + self if @keys.reject!(&block) + end + + def uniq!(&block) + self if @keys.uniq!(&block) + end + + def merge(enum) + @keys += JWT::JWK::Set.new(enum.to_a).keys + self + end + + def union(enum) + dup.merge(enum) + end + + def add(key) + @keys << JWT::JWK.new(key) + self + end + + def ==(other) + other.is_a?(JWT::JWK::Set) && keys.sort == other.keys.sort + end + + alias eql? == + alias filter! select! + alias length size + # For symbolic manipulation + alias | union + alias + union + alias << add + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/thumbprint.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/thumbprint.rb new file mode 100644 index 00000000..3583f578 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/jwk/thumbprint.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module JWT + module JWK + # https://tools.ietf.org/html/rfc7638 + class Thumbprint + attr_reader :jwk + + def initialize(jwk) + @jwk = jwk + end + + def generate + ::Base64.urlsafe_encode64( + Digest::SHA256.digest( + JWT::JSON.generate( + jwk.members.sort.to_h + ) + ), padding: false + ) + end + + alias to_s generate + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/token.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/token.rb new file mode 100644 index 00000000..0c643886 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/token.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +module JWT + # Represents a JWT token + # + # Basic token signed using the HS256 algorithm: + # + # token = JWT::Token.new(payload: {pay: 'load'}) + # token.sign!(algorithm: 'HS256', key: 'secret') + # token.jwt # => eyJhb.... + # + # Custom headers will be combined with generated headers: + # token = JWT::Token.new(payload: {pay: 'load'}, header: {custom: "value"}) + # token.sign!(algorithm: 'HS256', key: 'secret') + # token.header # => {"custom"=>"value", "alg"=>"HS256"} + # + class Token + # Initializes a new Token instance. + # + # @param header [Hash] the header of the JWT token. + # @param payload [Hash] the payload of the JWT token. + def initialize(payload:, header: {}) + @header = header&.transform_keys(&:to_s) + @payload = payload + end + + # Returns the decoded signature of the JWT token. + # + # @return [String] the decoded signature of the JWT token. + def signature + @signature ||= ::JWT::Base64.url_decode(encoded_signature || '') + end + + # Returns the encoded signature of the JWT token. + # + # @return [String] the encoded signature of the JWT token. + def encoded_signature + @encoded_signature ||= ::JWT::Base64.url_encode(signature) + end + + # Returns the decoded header of the JWT token. + # + # @return [Hash] the header of the JWT token. + attr_reader :header + + # Returns the encoded header of the JWT token. + # + # @return [String] the encoded header of the JWT token. + def encoded_header + @encoded_header ||= ::JWT::Base64.url_encode(JWT::JSON.generate(header)) + end + + # Returns the payload of the JWT token. + # + # @return [Hash] the payload of the JWT token. + attr_reader :payload + + # Returns the encoded payload of the JWT token. + # + # @return [String] the encoded payload of the JWT token. + def encoded_payload + @encoded_payload ||= ::JWT::Base64.url_encode(JWT::JSON.generate(payload)) + end + + # Returns the signing input of the JWT token. + # + # @return [String] the signing input of the JWT token. + def signing_input + @signing_input ||= [encoded_header, encoded_payload].join('.') + end + + # Returns the JWT token as a string. + # + # @return [String] the JWT token as a string. + # @raise [JWT::EncodeError] if the token is not signed or other encoding issues + def jwt + @jwt ||= (@signature && [encoded_header, @detached_payload ? '' : encoded_payload, encoded_signature].join('.')) || raise(::JWT::EncodeError, 'Token is not signed') + end + + # Detaches the payload according to https://datatracker.ietf.org/doc/html/rfc7515#appendix-F + # + def detach_payload! + @detached_payload = true + + nil + end + + # Signs the JWT token. + # + # @param key [String, JWT::JWK::KeyBase] the key to use for signing. + # @param algorithm [String, Object] the algorithm to use for signing. + # @return [void] + # @raise [JWT::EncodeError] if the token is already signed or other problems when signing + def sign!(key:, algorithm:) + raise ::JWT::EncodeError, 'Token already signed' if @signature + + JWA.create_signer(algorithm: algorithm, key: key).tap do |signer| + header.merge!(signer.jwa.header) { |_key, old, _new| old } + @signature = signer.sign(data: signing_input) + end + + nil + end + + # Verifies the claims of the token. + # @param options [Array, Hash] the claims to verify. + # @raise [JWT::DecodeError] if the claims are invalid. + def verify_claims!(*options) + Claims::Verifier.verify!(self, *options) + end + + # Returns the errors of the claims of the token. + # @param options [Array, Hash] the claims to verify. + # @return [Array] the errors of the claims. + def claim_errors(*options) + Claims::Verifier.errors(self, *options) + end + + # Returns whether the claims of the token are valid. + # @param options [Array, Hash] the claims to verify. + # @return [Boolean] whether the claims are valid. + def valid_claims?(*options) + claim_errors(*options).empty? + end + + # Returns the JWT token as a string. + # + # @return [String] the JWT token as a string. + alias to_s jwt + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/version.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/version.rb new file mode 100644 index 00000000..34436db0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/version.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +# JSON Web Token implementation +# +# Should be up to date with the latest spec: +# https://tools.ietf.org/html/rfc7519 +module JWT + # Returns the gem version of the JWT library. + # + # @return [Gem::Version] the gem version. + def self.gem_version + Gem::Version.new(VERSION::STRING) + end + + # Version constants + module VERSION + MAJOR = 3 + MINOR = 1 + TINY = 2 + PRE = nil + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') + end + + # Checks if the OpenSSL version is 3 or greater. + # + # @return [Boolean] true if OpenSSL version is 3 or greater, false otherwise. + # @api private + def self.openssl_3? + return false if OpenSSL::OPENSSL_VERSION.include?('LibreSSL') + + true if 3 * 0x10000000 <= OpenSSL::OPENSSL_VERSION_NUMBER + end + + # Checks if there is an OpenSSL 3 HMAC empty key regression. + # + # @return [Boolean] true if there is an OpenSSL 3 HMAC empty key regression, false otherwise. + # @api private + def self.openssl_3_hmac_empty_key_regression? + openssl_3? && openssl_version <= ::Gem::Version.new('3.0.0') + end + + # Returns the OpenSSL version. + # + # @return [Gem::Version] the OpenSSL version. + # @api private + def self.openssl_version + @openssl_version ||= ::Gem::Version.new(OpenSSL::VERSION) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/x5c_key_finder.rb b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/x5c_key_finder.rb new file mode 100644 index 00000000..26540855 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/lib/jwt/x5c_key_finder.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module JWT + # If the x5c header certificate chain can be validated by trusted root + # certificates, and none of the certificates are revoked, returns the public + # key from the first certificate. + # See https://tools.ietf.org/html/rfc7515#section-4.1.6 + class X5cKeyFinder + def initialize(root_certificates, crls = nil) + raise ArgumentError, 'Root certificates must be specified' unless root_certificates + + @store = build_store(root_certificates, crls) + end + + def from(x5c_header_or_certificates) + signing_certificate, *certificate_chain = parse_certificates(x5c_header_or_certificates) + store_context = OpenSSL::X509::StoreContext.new(@store, signing_certificate, certificate_chain) + + if store_context.verify + signing_certificate.public_key + else + error = "Certificate verification failed: #{store_context.error_string}." + if (current_cert = store_context.current_cert) + error = "#{error} Certificate subject: #{current_cert.subject}." + end + + raise JWT::VerificationError, error + end + end + + private + + def build_store(root_certificates, crls) + store = OpenSSL::X509::Store.new + store.purpose = OpenSSL::X509::PURPOSE_ANY + store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK | OpenSSL::X509::V_FLAG_CRL_CHECK_ALL + root_certificates.each { |certificate| store.add_cert(certificate) } + crls&.each { |crl| store.add_crl(crl) } + store + end + + def parse_certificates(x5c_header_or_certificates) + if x5c_header_or_certificates.all? { |obj| obj.is_a?(OpenSSL::X509::Certificate) } + x5c_header_or_certificates + else + x5c_header_or_certificates.map do |encoded| + OpenSSL::X509::Certificate.new(::JWT::Base64.url_decode(encoded)) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/ruby-jwt.gemspec b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/ruby-jwt.gemspec new file mode 100644 index 00000000..1c469c46 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/jwt-3.1.2/ruby-jwt.gemspec @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +lib = File.expand_path('lib', __dir__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'jwt/version' + +Gem::Specification.new do |spec| + spec.name = 'jwt' + spec.version = JWT.gem_version + spec.authors = [ + 'Tim Rudat' + ] + spec.email = 'timrudat@gmail.com' + spec.summary = 'JSON Web Token implementation in Ruby' + spec.description = 'A pure ruby implementation of the RFC 7519 OAuth JSON Web Token (JWT) standard.' + spec.homepage = 'https://github.com/jwt/ruby-jwt' + spec.license = 'MIT' + spec.required_ruby_version = '>= 2.5' + spec.metadata = { + 'bug_tracker_uri' => 'https://github.com/jwt/ruby-jwt/issues', + 'changelog_uri' => "https://github.com/jwt/ruby-jwt/blob/v#{JWT.gem_version}/CHANGELOG.md", + 'rubygems_mfa_required' => 'true' + } + + spec.files = `git ls-files -z`.split("\x0").reject do |f| + f.match(%r{^(spec|gemfiles|coverage|bin)/}) || # Irrelevant folders + f.match(/^\.+/) || # Files and folders starting with . + f.match(/^(Appraisals|Gemfile|Rakefile)$/) # Irrelevant files + end + + spec.executables = [] + spec.require_paths = %w[lib] + + spec.add_dependency 'base64' + + spec.add_development_dependency 'appraisal' + spec.add_development_dependency 'bundler' + spec.add_development_dependency 'irb' + spec.add_development_dependency 'logger' + spec.add_development_dependency 'rake' + spec.add_development_dependency 'rspec' + spec.add_development_dependency 'rubocop' + spec.add_development_dependency 'simplecov' +end diff --git a/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/.document b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/.document new file mode 100644 index 00000000..e2b95699 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/.document @@ -0,0 +1,4 @@ +BSDL +COPYING +README.md +lib/ diff --git a/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/.rdoc_options b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/.rdoc_options new file mode 100644 index 00000000..2d29a059 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/.rdoc_options @@ -0,0 +1,3 @@ +--- +main_page: README.md +title: Documentation for Logger diff --git a/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/BSDL b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/BSDL new file mode 100644 index 00000000..66d93598 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/BSDL @@ -0,0 +1,22 @@ +Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/COPYING b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/COPYING new file mode 100644 index 00000000..48e5a96d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/COPYING @@ -0,0 +1,56 @@ +Ruby is copyrighted free software by Yukihiro Matsumoto . +You can redistribute it and/or modify it under either the terms of the +2-clause BSDL (see the file BSDL), or the conditions below: + +1. You may make and give away verbatim copies of the source form of the + software without restriction, provided that you duplicate all of the + original copyright notices and associated disclaimers. + +2. You may modify your copy of the software in any way, provided that + you do at least ONE of the following: + + a. place your modifications in the Public Domain or otherwise + make them Freely Available, such as by posting said + modifications to Usenet or an equivalent medium, or by allowing + the author to include your modifications in the software. + + b. use the modified software only within your corporation or + organization. + + c. give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d. make other distribution arrangements with the author. + +3. You may distribute the software in object code or binary form, + provided that you do at least ONE of the following: + + a. distribute the binaries and library files of the software, + together with instructions (in the manual page or equivalent) + on where to get the original distribution. + + b. accompany the distribution with the machine-readable source of + the software. + + c. give non-standard binaries non-standard names, with + instructions on where to get the original software distribution. + + d. make other distribution arrangements with the author. + +4. You may modify and include the part of the software into any other + software (possibly commercial). But some files in the distribution + are not written by the author, so that they are not under these terms. + + For the list of those files and their copying conditions, see the + file LEGAL. + +5. The scripts and library files supplied as input to or produced as + output from the software do not automatically fall under the + copyright of the software, but belong to whomever generated them, + and may be sold commercially, and may be aggregated with this + software. + +6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. diff --git a/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/README.md b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/README.md new file mode 100644 index 00000000..ceb1a213 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/README.md @@ -0,0 +1,104 @@ +# Logger + +Logger is a simple but powerful logging utility to output messages in your Ruby program. + +Logger has the following features: + + * Print messages to different levels such as `info` and `error` + * Auto-rolling of log files + * Setting the format of log messages + * Specifying a program name in conjunction with the message + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'logger' +``` + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install logger + +## Usage + +### Simple Example + +```ruby +require 'logger' + +# Create a Logger that prints to STDOUT +log = Logger.new(STDOUT) +log.debug("Created Logger") + +log.info("Program finished") + +# Create a Logger that prints to STDERR +error_log = Logger.new(STDERR) +error_log = error_log.error("fatal error") +``` + +## Development + +After checking out the repo, run the following to install dependencies. + +``` +$ bin/setup +``` + +Then, run the tests as: + +``` +$ rake test +``` + +To install this gem onto your local machine, run + +``` +$ rake install +``` + +To release a new version, update the version number in `lib/logger/version.rb`, and then run + +``` +$ rake release +``` + +which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). + +## Advanced Development + +### Run tests of a specific file + +``` +$ ruby test/logger/test_logger.rb +``` + +### Run tests filtering test methods by a name + +`--name` option is available as: + +``` +$ ruby test/logger/test_logger.rb --name test_lshift +``` + +### Publish documents to GitHub Pages + +``` +$ rake gh-pages +``` + +Then, git commit and push the generated HTMLs onto `gh-pages` branch. + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/logger. + +## License + +The gem is available as open source under the terms of the [BSD-2-Clause](BSDL). diff --git a/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger.rb b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger.rb new file mode 100644 index 00000000..4911a3f3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger.rb @@ -0,0 +1,789 @@ +# frozen_string_literal: true +# logger.rb - simple logging utility +# Copyright (C) 2000-2003, 2005, 2008, 2011 NAKAMURA, Hiroshi . +# +# Documentation:: NAKAMURA, Hiroshi and Gavin Sinclair +# License:: +# You can redistribute it and/or modify it under the same terms of Ruby's +# license; either the dual license version in 2003, or any later version. +# Revision:: $Id$ +# +# A simple system for logging messages. See Logger for more documentation. + +require 'fiber' +require 'monitor' +require 'rbconfig' + +require_relative 'logger/version' +require_relative 'logger/formatter' +require_relative 'logger/log_device' +require_relative 'logger/severity' +require_relative 'logger/errors' + +# \Class \Logger provides a simple but sophisticated logging utility that +# you can use to create one or more +# {event logs}[https://en.wikipedia.org/wiki/Logging_(software)#Event_logs] +# for your program. +# Each such log contains a chronological sequence of entries +# that provides a record of the program's activities. +# +# == About the Examples +# +# All examples on this page assume that \Logger has been required: +# +# require 'logger' +# +# == Synopsis +# +# Create a log with Logger.new: +# +# # Single log file. +# logger = Logger.new('t.log') +# # Size-based rotated logging: 3 10-megabyte files. +# logger = Logger.new('t.log', 3, 10485760) +# # Period-based rotated logging: daily (also allowed: 'weekly', 'monthly'). +# logger = Logger.new('t.log', 'daily') +# # Log to an IO stream. +# logger = Logger.new($stdout) +# +# Add entries (level, message) with Logger#add: +# +# logger.add(Logger::DEBUG, 'Maximal debugging info') +# logger.add(Logger::INFO, 'Non-error information') +# logger.add(Logger::WARN, 'Non-error warning') +# logger.add(Logger::ERROR, 'Non-fatal error') +# logger.add(Logger::FATAL, 'Fatal error') +# logger.add(Logger::UNKNOWN, 'Most severe') +# +# Close the log with Logger#close: +# +# logger.close +# +# == Entries +# +# You can add entries with method Logger#add: +# +# logger.add(Logger::DEBUG, 'Maximal debugging info') +# logger.add(Logger::INFO, 'Non-error information') +# logger.add(Logger::WARN, 'Non-error warning') +# logger.add(Logger::ERROR, 'Non-fatal error') +# logger.add(Logger::FATAL, 'Fatal error') +# logger.add(Logger::UNKNOWN, 'Most severe') +# +# These shorthand methods also add entries: +# +# logger.debug('Maximal debugging info') +# logger.info('Non-error information') +# logger.warn('Non-error warning') +# logger.error('Non-fatal error') +# logger.fatal('Fatal error') +# logger.unknown('Most severe') +# +# When you call any of these methods, +# the entry may or may not be written to the log, +# depending on the entry's severity and on the log level; +# see {Log Level}[rdoc-ref:Logger@Log+Level] +# +# An entry always has: +# +# - A severity (the required argument to #add). +# - An automatically created timestamp. +# +# And may also have: +# +# - A message. +# - A program name. +# +# Example: +# +# logger = Logger.new($stdout) +# logger.add(Logger::INFO, 'My message.', 'mung') +# # => I, [2022-05-07T17:21:46.536234 #20536] INFO -- mung: My message. +# +# The default format for an entry is: +# +# "%s, [%s #%d] %5s -- %s: %s\n" +# +# where the values to be formatted are: +# +# - \Severity (one letter). +# - Timestamp. +# - Process id. +# - \Severity (word). +# - Program name. +# - Message. +# +# You can use a different entry format by: +# +# - Setting a custom format proc (affects following entries); +# see {formatter=}[Logger.html#attribute-i-formatter]. +# - Calling any of the methods above with a block +# (affects only the one entry). +# Doing so can have two benefits: +# +# - Context: the block can evaluate the entire program context +# and create a context-dependent message. +# - Performance: the block is not evaluated unless the log level +# permits the entry actually to be written: +# +# logger.error { my_slow_message_generator } +# +# Contrast this with the string form, where the string is +# always evaluated, regardless of the log level: +# +# logger.error("#{my_slow_message_generator}") +# +# === \Severity +# +# The severity of a log entry has two effects: +# +# - Determines whether the entry is selected for inclusion in the log; +# see {Log Level}[rdoc-ref:Logger@Log+Level]. +# - Indicates to any log reader (whether a person or a program) +# the relative importance of the entry. +# +# === Timestamp +# +# The timestamp for a log entry is generated automatically +# when the entry is created. +# +# The logged timestamp is formatted by method +# {Time#strftime}[https://docs.ruby-lang.org/en/master/Time.html#method-i-strftime] +# using this format string: +# +# '%Y-%m-%dT%H:%M:%S.%6N' +# +# Example: +# +# logger = Logger.new($stdout) +# logger.add(Logger::INFO) +# # => I, [2022-05-07T17:04:32.318331 #20536] INFO -- : nil +# +# You can set a different format using method #datetime_format=. +# +# === Message +# +# The message is an optional argument to an entry method: +# +# logger = Logger.new($stdout) +# logger.add(Logger::INFO, 'My message') +# # => I, [2022-05-07T18:15:37.647581 #20536] INFO -- : My message +# +# For the default entry formatter, Logger::Formatter, +# the message object may be: +# +# - A string: used as-is. +# - An Exception: message.message is used. +# - Anything else: message.inspect is used. +# +# *Note*: Logger::Formatter does not escape or sanitize +# the message passed to it. +# Developers should be aware that malicious data (user input) +# may be in the message, and should explicitly escape untrusted data. +# +# You can use a custom formatter to escape message data; +# see the example at {formatter=}[Logger.html#attribute-i-formatter]. +# +# === Program Name +# +# The program name is an optional argument to an entry method: +# +# logger = Logger.new($stdout) +# logger.add(Logger::INFO, 'My message', 'mung') +# # => I, [2022-05-07T18:17:38.084716 #20536] INFO -- mung: My message +# +# The default program name for a new logger may be set in the call to +# Logger.new via optional keyword argument +progname+: +# +# logger = Logger.new('t.log', progname: 'mung') +# +# The default program name for an existing logger may be set +# by a call to method #progname=: +# +# logger.progname = 'mung' +# +# The current program name may be retrieved with method +# {progname}[Logger.html#attribute-i-progname]: +# +# logger.progname # => "mung" +# +# == Log Level +# +# The log level setting determines whether an entry is actually +# written to the log, based on the entry's severity. +# +# These are the defined severities (least severe to most severe): +# +# logger = Logger.new($stdout) +# logger.add(Logger::DEBUG, 'Maximal debugging info') +# # => D, [2022-05-07T17:57:41.776220 #20536] DEBUG -- : Maximal debugging info +# logger.add(Logger::INFO, 'Non-error information') +# # => I, [2022-05-07T17:59:14.349167 #20536] INFO -- : Non-error information +# logger.add(Logger::WARN, 'Non-error warning') +# # => W, [2022-05-07T18:00:45.337538 #20536] WARN -- : Non-error warning +# logger.add(Logger::ERROR, 'Non-fatal error') +# # => E, [2022-05-07T18:02:41.592912 #20536] ERROR -- : Non-fatal error +# logger.add(Logger::FATAL, 'Fatal error') +# # => F, [2022-05-07T18:05:24.703931 #20536] FATAL -- : Fatal error +# logger.add(Logger::UNKNOWN, 'Most severe') +# # => A, [2022-05-07T18:07:54.657491 #20536] ANY -- : Most severe +# +# The default initial level setting is Logger::DEBUG, the lowest level, +# which means that all entries are to be written, regardless of severity: +# +# logger = Logger.new($stdout) +# logger.level # => 0 +# logger.add(0, "My message") +# # => D, [2022-05-11T15:10:59.773668 #20536] DEBUG -- : My message +# +# You can specify a different setting in a new logger +# using keyword argument +level+ with an appropriate value: +# +# logger = Logger.new($stdout, level: Logger::ERROR) +# logger = Logger.new($stdout, level: 'error') +# logger = Logger.new($stdout, level: :error) +# logger.level # => 3 +# +# With this level, entries with severity Logger::ERROR and higher +# are written, while those with lower severities are not written: +# +# logger = Logger.new($stdout, level: Logger::ERROR) +# logger.add(3) +# # => E, [2022-05-11T15:17:20.933362 #20536] ERROR -- : nil +# logger.add(2) # Silent. +# +# You can set the log level for an existing logger +# with method #level=: +# +# logger.level = Logger::ERROR +# +# These shorthand methods also set the level: +# +# logger.debug! # => 0 +# logger.info! # => 1 +# logger.warn! # => 2 +# logger.error! # => 3 +# logger.fatal! # => 4 +# +# You can retrieve the log level with method #level. +# +# logger.level = Logger::ERROR +# logger.level # => 3 +# +# These methods return whether a given +# level is to be written: +# +# logger.level = Logger::ERROR +# logger.debug? # => false +# logger.info? # => false +# logger.warn? # => false +# logger.error? # => true +# logger.fatal? # => true +# +# == Log File Rotation +# +# By default, a log file is a single file that grows indefinitely +# (until explicitly closed); there is no file rotation. +# +# To keep log files to a manageable size, +# you can use _log_ _file_ _rotation_, which uses multiple log files: +# +# - Each log file has entries for a non-overlapping +# time interval. +# - Only the most recent log file is open and active; +# the others are closed and inactive. +# +# === Size-Based Rotation +# +# For size-based log file rotation, call Logger.new with: +# +# - Argument +logdev+ as a file path. +# - Argument +shift_age+ with a positive integer: +# the number of log files to be in the rotation. +# - Argument +shift_size+ as a positive integer: +# the maximum size (in bytes) of each log file; +# defaults to 1048576 (1 megabyte). +# +# Examples: +# +# logger = Logger.new('t.log', 3) # Three 1-megabyte files. +# logger = Logger.new('t.log', 5, 10485760) # Five 10-megabyte files. +# +# For these examples, suppose: +# +# logger = Logger.new('t.log', 3) +# +# Logging begins in the new log file, +t.log+; +# the log file is "full" and ready for rotation +# when a new entry would cause its size to exceed +shift_size+. +# +# The first time +t.log+ is full: +# +# - +t.log+ is closed and renamed to +t.log.0+. +# - A new file +t.log+ is opened. +# +# The second time +t.log+ is full: +# +# - +t.log.0 is renamed as +t.log.1+. +# - +t.log+ is closed and renamed to +t.log.0+. +# - A new file +t.log+ is opened. +# +# Each subsequent time that +t.log+ is full, +# the log files are rotated: +# +# - +t.log.1+ is removed. +# - +t.log.0 is renamed as +t.log.1+. +# - +t.log+ is closed and renamed to +t.log.0+. +# - A new file +t.log+ is opened. +# +# === Periodic Rotation +# +# For periodic rotation, call Logger.new with: +# +# - Argument +logdev+ as a file path. +# - Argument +shift_age+ as a string period indicator. +# +# Examples: +# +# logger = Logger.new('t.log', 'daily') # Rotate log files daily. +# logger = Logger.new('t.log', 'weekly') # Rotate log files weekly. +# logger = Logger.new('t.log', 'monthly') # Rotate log files monthly. +# +# Example: +# +# logger = Logger.new('t.log', 'daily') +# +# When the given period expires: +# +# - The base log file, +t.log+ is closed and renamed +# with a date-based suffix such as +t.log.20220509+. +# - A new log file +t.log+ is opened. +# - Nothing is removed. +# +# The default format for the suffix is '%Y%m%d', +# which produces a suffix similar to the one above. +# You can set a different format using create-time option +# +shift_period_suffix+; +# see details and suggestions at +# {Time#strftime}[https://docs.ruby-lang.org/en/master/Time.html#method-i-strftime]. +# +class Logger + _, name, rev = %w$Id$ + if name + name = name.chomp(",v") + else + name = File.basename(__FILE__) + end + rev ||= "v#{VERSION}" + ProgName = "#{name}/#{rev}" + + include Severity + + # Logging severity threshold (e.g. Logger::INFO). + def level + level_override[level_key] || @level + end + + # Sets the log level; returns +severity+. + # See {Log Level}[rdoc-ref:Logger@Log+Level]. + # + # Argument +severity+ may be an integer, a string, or a symbol: + # + # logger.level = Logger::ERROR # => 3 + # logger.level = 3 # => 3 + # logger.level = 'error' # => "error" + # logger.level = :error # => :error + # + # Logger#sev_threshold= is an alias for Logger#level=. + # + def level=(severity) + @level = Severity.coerce(severity) + end + + # Adjust the log level during the block execution for the current Fiber only + # + # logger.with_level(:debug) do + # logger.debug { "Hello" } + # end + def with_level(severity) + prev, level_override[level_key] = level, Severity.coerce(severity) + begin + yield + ensure + if prev + level_override[level_key] = prev + else + level_override.delete(level_key) + end + end + end + + # Program name to include in log messages. + attr_accessor :progname + + # Sets the date-time format. + # + # Argument +datetime_format+ should be either of these: + # + # - A string suitable for use as a format for method + # {Time#strftime}[https://docs.ruby-lang.org/en/master/Time.html#method-i-strftime]. + # - +nil+: the logger uses '%Y-%m-%dT%H:%M:%S.%6N'. + # + def datetime_format=(datetime_format) + @default_formatter.datetime_format = datetime_format + end + + # Returns the date-time format; see #datetime_format=. + # + def datetime_format + @default_formatter.datetime_format + end + + # Sets or retrieves the logger entry formatter proc. + # + # When +formatter+ is +nil+, the logger uses Logger::Formatter. + # + # When +formatter+ is a proc, a new entry is formatted by the proc, + # which is called with four arguments: + # + # - +severity+: The severity of the entry. + # - +time+: A Time object representing the entry's timestamp. + # - +progname+: The program name for the entry. + # - +msg+: The message for the entry (string or string-convertible object). + # + # The proc should return a string containing the formatted entry. + # + # This custom formatter uses + # {String#dump}[https://docs.ruby-lang.org/en/master/String.html#method-i-dump] + # to escape the message string: + # + # logger = Logger.new($stdout, progname: 'mung') + # original_formatter = logger.formatter || Logger::Formatter.new + # logger.formatter = proc { |severity, time, progname, msg| + # original_formatter.call(severity, time, progname, msg.dump) + # } + # logger.add(Logger::INFO, "hello \n ''") + # logger.add(Logger::INFO, "\f\x00\xff\\\"") + # + # Output: + # + # I, [2022-05-13T13:16:29.637488 #8492] INFO -- mung: "hello \n ''" + # I, [2022-05-13T13:16:29.637610 #8492] INFO -- mung: "\f\x00\xFF\\\"" + # + attr_accessor :formatter + + alias sev_threshold level + alias sev_threshold= level= + + # Returns +true+ if the log level allows entries with severity + # Logger::DEBUG to be written, +false+ otherwise. + # See {Log Level}[rdoc-ref:Logger@Log+Level]. + # + def debug?; level <= DEBUG; end + + # Sets the log level to Logger::DEBUG. + # See {Log Level}[rdoc-ref:Logger@Log+Level]. + # + def debug!; self.level = DEBUG; end + + # Returns +true+ if the log level allows entries with severity + # Logger::INFO to be written, +false+ otherwise. + # See {Log Level}[rdoc-ref:Logger@Log+Level]. + # + def info?; level <= INFO; end + + # Sets the log level to Logger::INFO. + # See {Log Level}[rdoc-ref:Logger@Log+Level]. + # + def info!; self.level = INFO; end + + # Returns +true+ if the log level allows entries with severity + # Logger::WARN to be written, +false+ otherwise. + # See {Log Level}[rdoc-ref:Logger@Log+Level]. + # + def warn?; level <= WARN; end + + # Sets the log level to Logger::WARN. + # See {Log Level}[rdoc-ref:Logger@Log+Level]. + # + def warn!; self.level = WARN; end + + # Returns +true+ if the log level allows entries with severity + # Logger::ERROR to be written, +false+ otherwise. + # See {Log Level}[rdoc-ref:Logger@Log+Level]. + # + def error?; level <= ERROR; end + + # Sets the log level to Logger::ERROR. + # See {Log Level}[rdoc-ref:Logger@Log+Level]. + # + def error!; self.level = ERROR; end + + # Returns +true+ if the log level allows entries with severity + # Logger::FATAL to be written, +false+ otherwise. + # See {Log Level}[rdoc-ref:Logger@Log+Level]. + # + def fatal?; level <= FATAL; end + + # Sets the log level to Logger::FATAL. + # See {Log Level}[rdoc-ref:Logger@Log+Level]. + # + def fatal!; self.level = FATAL; end + + # :call-seq: + # Logger.new(logdev, shift_age = 0, shift_size = 1048576, **options) + # + # With the single argument +logdev+, + # returns a new logger with all default options: + # + # Logger.new('t.log') # => # + # + # Argument +logdev+ must be one of: + # + # - A string filepath: entries are to be written + # to the file at that path; if the file at that path exists, + # new entries are appended. + # - An IO stream (typically $stdout, $stderr. or + # an open file): entries are to be written to the given stream. + # - +nil+ or +File::NULL+: no entries are to be written. + # + # Argument +shift_age+ must be one of: + # + # - The number of log files to be in the rotation. + # See {Size-Based Rotation}[rdoc-ref:Logger@Size-Based+Rotation]. + # - A string period indicator. + # See {Periodic Rotation}[rdoc-ref:Logger@Periodic+Rotation]. + # + # Argument +shift_size+ is the maximum size (in bytes) of each log file. + # See {Size-Based Rotation}[rdoc-ref:Logger@Size-Based+Rotation]. + # + # Examples: + # + # Logger.new('t.log') + # Logger.new($stdout) + # + # The keyword options are: + # + # - +level+: sets the log level; default value is Logger::DEBUG. + # See {Log Level}[rdoc-ref:Logger@Log+Level]: + # + # Logger.new('t.log', level: Logger::ERROR) + # + # - +progname+: sets the default program name; default is +nil+. + # See {Program Name}[rdoc-ref:Logger@Program+Name]: + # + # Logger.new('t.log', progname: 'mung') + # + # - +formatter+: sets the entry formatter; default is +nil+. + # See {formatter=}[Logger.html#attribute-i-formatter]. + # + # - +datetime_format+: sets the format for entry timestamp; + # default is +nil+. + # See #datetime_format=. + # + # - +binmode+: sets whether the logger writes in binary mode; + # default is +false+. + # + # - +shift_period_suffix+: sets the format for the filename suffix + # for periodic log file rotation; default is '%Y%m%d'. + # See {Periodic Rotation}[rdoc-ref:Logger@Periodic+Rotation]. + # + # - +reraise_write_errors+: An array of exception classes, which will + # be reraised if there is an error when writing to the log device. + # The default is to swallow all exceptions raised. + # - +skip_header+: If +true+, prevents the logger from writing a header + # when creating a new log file. The default is +false+, meaning + # the header will be written as usual. + # + def initialize(logdev, shift_age = 0, shift_size = 1048576, level: DEBUG, + progname: nil, formatter: nil, datetime_format: nil, + binmode: false, shift_period_suffix: '%Y%m%d', + reraise_write_errors: [], skip_header: false) + self.level = level + self.progname = progname + @default_formatter = Formatter.new + self.datetime_format = datetime_format + self.formatter = formatter + @logdev = nil + @level_override = {} + if logdev && logdev != File::NULL + @logdev = LogDevice.new(logdev, shift_age: shift_age, + shift_size: shift_size, + shift_period_suffix: shift_period_suffix, + binmode: binmode, + reraise_write_errors: reraise_write_errors, + skip_header: skip_header) + end + end + + # Sets the logger's output stream: + # + # - If +logdev+ is +nil+, reopens the current output stream. + # - If +logdev+ is a filepath, opens the indicated file for append. + # - If +logdev+ is an IO stream + # (usually $stdout, $stderr, or an open File object), + # opens the stream for append. + # + # Example: + # + # logger = Logger.new('t.log') + # logger.add(Logger::ERROR, 'one') + # logger.close + # logger.add(Logger::ERROR, 'two') # Prints 'log writing failed. closed stream' + # logger.reopen + # logger.add(Logger::ERROR, 'three') + # logger.close + # File.readlines('t.log') + # # => + # # ["# Logfile created on 2022-05-12 14:21:19 -0500 by logger.rb/v1.5.0\n", + # # "E, [2022-05-12T14:21:27.596726 #22428] ERROR -- : one\n", + # # "E, [2022-05-12T14:23:05.847241 #22428] ERROR -- : three\n"] + # + def reopen(logdev = nil, shift_age = nil, shift_size = nil, shift_period_suffix: nil, binmode: nil) + @logdev&.reopen(logdev, shift_age: shift_age, shift_size: shift_size, + shift_period_suffix: shift_period_suffix, binmode: binmode) + self + end + + # Creates a log entry, which may or may not be written to the log, + # depending on the entry's severity and on the log level. + # See {Log Level}[rdoc-ref:Logger@Log+Level] + # and {Entries}[rdoc-ref:Logger@Entries] for details. + # + # Examples: + # + # logger = Logger.new($stdout, progname: 'mung') + # logger.add(Logger::INFO) + # logger.add(Logger::ERROR, 'No good') + # logger.add(Logger::ERROR, 'No good', 'gnum') + # + # Output: + # + # I, [2022-05-12T16:25:31.469726 #36328] INFO -- mung: mung + # E, [2022-05-12T16:25:55.349414 #36328] ERROR -- mung: No good + # E, [2022-05-12T16:26:35.841134 #36328] ERROR -- gnum: No good + # + # These convenience methods have implicit severity: + # + # - #debug. + # - #info. + # - #warn. + # - #error. + # - #fatal. + # - #unknown. + # + def add(severity, message = nil, progname = nil) + severity ||= UNKNOWN + if @logdev.nil? or severity < level + return true + end + if progname.nil? + progname = @progname + end + if message.nil? + if block_given? + message = yield + else + message = progname + progname = @progname + end + end + @logdev.write( + format_message(format_severity(severity), Time.now, progname, message)) + true + end + alias log add + + # Writes the given +msg+ to the log with no formatting; + # returns the number of characters written, + # or +nil+ if no log device exists: + # + # logger = Logger.new($stdout) + # logger << 'My message.' # => 10 + # + # Output: + # + # My message. + # + def <<(msg) + @logdev&.write(msg) + end + + # Equivalent to calling #add with severity Logger::DEBUG. + # + def debug(progname = nil, &block) + add(DEBUG, nil, progname, &block) + end + + # Equivalent to calling #add with severity Logger::INFO. + # + def info(progname = nil, &block) + add(INFO, nil, progname, &block) + end + + # Equivalent to calling #add with severity Logger::WARN. + # + def warn(progname = nil, &block) + add(WARN, nil, progname, &block) + end + + # Equivalent to calling #add with severity Logger::ERROR. + # + def error(progname = nil, &block) + add(ERROR, nil, progname, &block) + end + + # Equivalent to calling #add with severity Logger::FATAL. + # + def fatal(progname = nil, &block) + add(FATAL, nil, progname, &block) + end + + # Equivalent to calling #add with severity Logger::UNKNOWN. + # + def unknown(progname = nil, &block) + add(UNKNOWN, nil, progname, &block) + end + + # Closes the logger; returns +nil+: + # + # logger = Logger.new('t.log') + # logger.close # => nil + # logger.info('foo') # Prints "log writing failed. closed stream" + # + # Related: Logger#reopen. + def close + @logdev&.close + end + +private + + # \Severity label for logging (max 5 chars). + SEV_LABEL = %w(DEBUG INFO WARN ERROR FATAL ANY).freeze + + def format_severity(severity) + SEV_LABEL[severity] || 'ANY' + end + + # Guarantee the existence of this ivar even when subclasses don't call the superclass constructor. + def level_override + unless defined?(@level_override) + bad = self.class.instance_method(:initialize) + file, line = bad.source_location + Kernel.warn <<~";;;", uplevel: 2 + Logger not initialized properly + #{file}:#{line}: info: #{bad.owner}\##{bad.name}: \ + does not call super probably + ;;; + end + @level_override ||= {} + end + + def level_key + Fiber.current + end + + def format_message(severity, datetime, progname, msg) + (@formatter || @default_formatter).call(severity, datetime, progname, msg) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger/errors.rb b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger/errors.rb new file mode 100644 index 00000000..88581793 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger/errors.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Logger + # not used after 1.2.7. just for compat. + class Error < RuntimeError # :nodoc: + end + class ShiftingError < Error # :nodoc: + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger/formatter.rb b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger/formatter.rb new file mode 100644 index 00000000..c634dbf3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger/formatter.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +class Logger + # Default formatter for log messages. + class Formatter + Format = "%.1s, [%s #%d] %5s -- %s: %s\n" + DatetimeFormat = "%Y-%m-%dT%H:%M:%S.%6N" + + attr_accessor :datetime_format + + def initialize + @datetime_format = nil + end + + def call(severity, time, progname, msg) + sprintf(Format, severity, format_datetime(time), Process.pid, severity, progname, msg2str(msg)) + end + + private + + def format_datetime(time) + time.strftime(@datetime_format || DatetimeFormat) + end + + def msg2str(msg) + case msg + when ::String + msg + when ::Exception + "#{ msg.message } (#{ msg.class })\n#{ msg.backtrace.join("\n") if msg.backtrace }" + else + msg.inspect + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger/log_device.rb b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger/log_device.rb new file mode 100644 index 00000000..e16f3b70 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger/log_device.rb @@ -0,0 +1,265 @@ +# frozen_string_literal: true + +require_relative 'period' + +class Logger + # Device used for logging messages. + class LogDevice + include Period + + attr_reader :dev + attr_reader :filename + include MonitorMixin + + def initialize( + log = nil, shift_age: nil, shift_size: nil, shift_period_suffix: nil, + binmode: false, reraise_write_errors: [], skip_header: false + ) + @dev = @filename = @shift_age = @shift_size = @shift_period_suffix = nil + @binmode = binmode + @reraise_write_errors = reraise_write_errors + @skip_header = skip_header + mon_initialize + set_dev(log) + set_file(shift_age, shift_size, shift_period_suffix) if @filename + end + + def write(message) + handle_write_errors("writing") do + synchronize do + if @shift_age and @dev.respond_to?(:stat) + handle_write_errors("shifting") {check_shift_log} + end + handle_write_errors("writing") {@dev.write(message)} + end + end + end + + def close + begin + synchronize do + @dev.close rescue nil + end + rescue Exception + @dev.close rescue nil + end + end + + def reopen(log = nil, shift_age: nil, shift_size: nil, shift_period_suffix: nil, binmode: nil) + # reopen the same filename if no argument, do nothing for IO + log ||= @filename if @filename + @binmode = binmode unless binmode.nil? + if log + synchronize do + if @filename and @dev + @dev.close rescue nil # close only file opened by Logger + @filename = nil + end + set_dev(log) + set_file(shift_age, shift_size, shift_period_suffix) if @filename + end + end + self + end + + private + + # :stopdoc: + + MODE = File::WRONLY | File::APPEND + # TruffleRuby < 24.2 does not have File::SHARE_DELETE + if File.const_defined? :SHARE_DELETE + MODE_TO_OPEN = MODE | File::SHARE_DELETE | File::BINARY + else + MODE_TO_OPEN = MODE | File::BINARY + end + MODE_TO_CREATE = MODE_TO_OPEN | File::CREAT | File::EXCL + + def set_dev(log) + if log.respond_to?(:write) and log.respond_to?(:close) + @dev = log + if log.respond_to?(:path) and path = log.path + if File.exist?(path) + @filename = path + end + end + else + @dev = open_logfile(log) + @filename = log + end + end + + def set_file(shift_age, shift_size, shift_period_suffix) + @shift_age = shift_age || @shift_age || 7 + @shift_size = shift_size || @shift_size || 1048576 + @shift_period_suffix = shift_period_suffix || @shift_period_suffix || '%Y%m%d' + + unless @shift_age.is_a?(Integer) + base_time = @dev.respond_to?(:stat) ? @dev.stat.mtime : Time.now + @next_rotate_time = next_rotate_time(base_time, @shift_age) + end + end + + if MODE_TO_OPEN == MODE + def fixup_mode(dev) + dev + end + else + def fixup_mode(dev) + return dev if @binmode + dev.autoclose = false + old_dev = dev + dev = File.new(dev.fileno, mode: MODE, path: dev.path) + old_dev.close + PathAttr.set_path(dev, filename) if defined?(PathAttr) + dev + end + end + + def open_logfile(filename) + begin + dev = File.open(filename, MODE_TO_OPEN) + rescue Errno::ENOENT + create_logfile(filename) + else + dev = fixup_mode(dev) + dev.sync = true + dev.binmode if @binmode + dev + end + end + + def create_logfile(filename) + begin + logdev = File.open(filename, MODE_TO_CREATE) + logdev.flock(File::LOCK_EX) + logdev = fixup_mode(logdev) + logdev.sync = true + logdev.binmode if @binmode + add_log_header(logdev) unless @skip_header + logdev.flock(File::LOCK_UN) + logdev + rescue Errno::EEXIST + # file is created by another process + open_logfile(filename) + end + end + + def handle_write_errors(mesg) + yield + rescue *@reraise_write_errors + raise + rescue + warn("log #{mesg} failed. #{$!}") + end + + def add_log_header(file) + file.write( + "# Logfile created on %s by %s\n" % [Time.now.to_s, Logger::ProgName] + ) if file.size == 0 + end + + def check_shift_log + if @shift_age.is_a?(Integer) + # Note: always returns false if '0'. + if @filename && (@shift_age > 0) && (@dev.stat.size > @shift_size) + lock_shift_log { shift_log_age } + end + else + now = Time.now + if now >= @next_rotate_time + @next_rotate_time = next_rotate_time(now, @shift_age) + lock_shift_log { shift_log_period(previous_period_end(now, @shift_age)) } + end + end + end + + def lock_shift_log + retry_limit = 8 + retry_sleep = 0.1 + begin + File.open(@filename, MODE_TO_OPEN) do |lock| + lock.flock(File::LOCK_EX) # inter-process locking. will be unlocked at closing file + if File.identical?(@filename, lock) and File.identical?(lock, @dev) + yield # log shifting + else + # log shifted by another process (i-node before locking and i-node after locking are different) + @dev.close rescue nil + @dev = open_logfile(@filename) + end + end + true + rescue Errno::ENOENT + # @filename file would not exist right after #rename and before #create_logfile + if retry_limit <= 0 + warn("log rotation inter-process lock failed. #{$!}") + else + sleep retry_sleep + retry_limit -= 1 + retry_sleep *= 2 + retry + end + end + rescue + warn("log rotation inter-process lock failed. #{$!}") + end + + def shift_log_age + (@shift_age-3).downto(0) do |i| + if FileTest.exist?("#{@filename}.#{i}") + File.rename("#{@filename}.#{i}", "#{@filename}.#{i+1}") + end + end + shift_log_file("#{@filename}.0") + end + + def shift_log_period(period_end) + suffix = period_end.strftime(@shift_period_suffix) + age_file = "#{@filename}.#{suffix}" + if FileTest.exist?(age_file) + # try to avoid filename crash caused by Timestamp change. + idx = 0 + # .99 can be overridden; avoid too much file search with 'loop do' + while idx < 100 + idx += 1 + age_file = "#{@filename}.#{suffix}.#{idx}" + break unless FileTest.exist?(age_file) + end + end + shift_log_file(age_file) + end + + def shift_log_file(shifted) + stat = @dev.stat + @dev.close rescue nil + File.rename(@filename, shifted) + @dev = create_logfile(@filename) + mode, uid, gid = stat.mode, stat.uid, stat.gid + begin + @dev.chmod(mode) if mode + mode = nil + @dev.chown(uid, gid) + rescue Errno::EPERM + if mode + # failed to chmod, probably nothing can do more. + elsif uid + uid = nil + retry # to change gid only + end + end + return true + end + end +end + +File.open(__FILE__) do |f| + File.new(f.fileno, autoclose: false, path: "").path +rescue IOError + module PathAttr # :nodoc: + attr_reader :path + + def self.set_path(file, path) + file.extend(self).instance_variable_set(:@path, path) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger/period.rb b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger/period.rb new file mode 100644 index 00000000..a0359def --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger/period.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +class Logger + module Period + module_function + + SiD = 24 * 60 * 60 + + def next_rotate_time(now, shift_age) + case shift_age + when 'daily', :daily + t = Time.mktime(now.year, now.month, now.mday) + SiD + when 'weekly', :weekly + t = Time.mktime(now.year, now.month, now.mday) + SiD * (7 - now.wday) + when 'monthly', :monthly + t = Time.mktime(now.year, now.month, 1) + SiD * 32 + return Time.mktime(t.year, t.month, 1) + when 'now', 'everytime', :now, :everytime + return now + else + raise ArgumentError, "invalid :shift_age #{shift_age.inspect}, should be daily, weekly, monthly, or everytime" + end + if t.hour.nonzero? or t.min.nonzero? or t.sec.nonzero? + hour = t.hour + t = Time.mktime(t.year, t.month, t.mday) + t += SiD if hour > 12 + end + t + end + + def previous_period_end(now, shift_age) + case shift_age + when 'daily', :daily + t = Time.mktime(now.year, now.month, now.mday) - SiD / 2 + when 'weekly', :weekly + t = Time.mktime(now.year, now.month, now.mday) - (SiD * now.wday + SiD / 2) + when 'monthly', :monthly + t = Time.mktime(now.year, now.month, 1) - SiD / 2 + when 'now', 'everytime', :now, :everytime + return now + else + raise ArgumentError, "invalid :shift_age #{shift_age.inspect}, should be daily, weekly, monthly, or everytime" + end + Time.mktime(t.year, t.month, t.mday, 23, 59, 59) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger/severity.rb b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger/severity.rb new file mode 100644 index 00000000..e96fb0d3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger/severity.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +class Logger + # Logging severity. + module Severity + # Low-level information, mostly for developers. + DEBUG = 0 + # Generic (useful) information about system operation. + INFO = 1 + # A warning. + WARN = 2 + # A handleable error condition. + ERROR = 3 + # An unhandleable error that results in a program crash. + FATAL = 4 + # An unknown message that should always be logged. + UNKNOWN = 5 + + LEVELS = { + "debug" => DEBUG, + "info" => INFO, + "warn" => WARN, + "error" => ERROR, + "fatal" => FATAL, + "unknown" => UNKNOWN, + } + private_constant :LEVELS + + def self.coerce(severity) + if severity.is_a?(Integer) + severity + else + key = severity.to_s.downcase + LEVELS[key] || raise(ArgumentError, "invalid log level: #{severity}") + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger/version.rb b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger/version.rb new file mode 100644 index 00000000..0d74beca --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/logger-1.7.0/lib/logger/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Logger + VERSION = "1.7.0" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/CHANGELOG.md b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/CHANGELOG.md new file mode 100644 index 00000000..5b7f39d7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/CHANGELOG.md @@ -0,0 +1,598 @@ +# Changelog + +## 2.24.1 / 2025-05-12 + +### Ruby support + +* Import only what's needed from `cgi` for support for Ruby 3.5 #296 @Earlopain + + +## 2.24.0 / 2024-12-24 + +### Added + +* Built-in scrubber `:double_breakpoint` which sees `

    ` and wraps the surrounding content in `

    ` tags. #279, #284 @josecolella @torihuang + +### Improved + +* Built-in scrubber `:targetblank` now skips `a` tags whose `href` attribute is an anchor link. Previously, all `a` tags were modified to have `target='_blank'`. #291 @fnando + + +## 2.23.1 / 2024-10-25 + +### Added + +* Allow CSS properties `min-height` and `max-height`. [#288] @lazyatom + + +## 2.23.0 / 2024-10-24 + +### Added + +* Allow CSS property `min-width`. [#287] @lazyatom + + +## 2.22.0 / 2023-11-13 + +### Added + +* A `:targetblank` HTML scrubber which ensures all hyperlinks have `target="_blank"`. [#275] @stefannibrasil and @thdaraujo +* A `:noreferrer` HTML scrubber which ensures all hyperlinks have `rel=noreferrer`, similar to the `:nofollow` and `:noopener` scrubbers. [#277] @wynksaiddestroy + + +## 2.21.4 / 2023-10-10 + +### Fixed + +* `Loofah::HTML5::Scrub.scrub_css` is more consistent in preserving whitespace (and lack of whitespace) in CSS property values. In particular, `.scrub_css` no longer inserts whitespace between tokens that did not already have whitespace between them. [[#273](https://github.com/flavorjones/loofah/issues/273), fixes [#271](https://github.com/flavorjones/loofah/issues/271)] + + +## 2.21.3 / 2023-05-15 + +### Fixed + +* Quash "instance variable not initialized" warning in Ruby < 3.0. [[#268](https://github.com/flavorjones/loofah/issues/268)] (Thanks, [@dharamgollapudi](https://github.com/dharamgollapudi)!) + + +## 2.21.2 / 2023-05-11 + +### Dependencies + +* Update the dependency on Nokogiri to be `>= 1.12.0`. The dependency in 2.21.0 and 2.21.1 was left at `>= 1.5.9` but versions before 1.12 would result in a `NameError` exception. [[#266](https://github.com/flavorjones/loofah/issues/266)] + + +## 2.21.1 / 2023-05-10 + +### Fixed + +* Don't define `HTML5::Document` and `HTML5::DocumentFragment` when Nokogiri is `< 1.14`. In 2.21.0 these classes were defined whenever `Nokogiri::HTML5` was defined, but Nokogiri v1.12 and v1.13 do not support Loofah subclassing properly. + + +## 2.21.0 / 2023-05-10 + +### HTML5 Support + +Classes `Loofah::HTML5::Document` and `Loofah::HTML5::DocumentFragment` are introduced, along with helper methods: + +- `Loofah.html5_document` +- `Loofah.html5_fragment` +- `Loofah.scrub_html5_document` +- `Loofah.scrub_html5_fragment` + +These classes and methods use Nokogiri's HTML5 parser to ensure modern web standards are used. + +⚠ HTML5 functionality is only available with Nokogiri v1.14.0 and higher. + +⚠ HTML5 functionality is not available for JRuby. Please see [this upstream Nokogiri issue](https://github.com/sparklemotion/nokogiri/issues/2227) if you're interested in helping implement and support HTML5 support. + + +### `Loofah::HTML4` module and namespace + +`Loofah::HTML` has been renamed to `Loofah::HTML4`, and `Loofah::HTML` is aliased to preserve backwards-compatibility. `Nokogiri::HTML` and `Nokogiri::HTML4` parse methods still use libxml2's (or NekoHTML's) HTML4 parser. + +Take special note that if you rely on the class name of an object in your code, objects will now report a class of `Loofah::HTML4::Foo` where they previously reported `Loofah::HTML::Foo`. Instead of relying on the string returned by `Object#class`, prefer `Class#===` or `Object#is_a?` or `Object#instance_of?`. + +Future releases of Nokogiri may deprecate `HTML` classes and methods or otherwise change this behavior, so please start using `HTML4` in place of `HTML`. + + +### Official support for JRuby + +This version introduces official support for JRuby. Previously, the test suite had never been green due to differences in behavior in the underlying HTML parser used by Nokogiri. We've updated the test suite to accommodate those differences, and have added JRuby to the CI suite. + + +## 2.20.0 / 2023-04-01 + +### Features + +* Allow SVG attributes `color-profile`, `cursor`, `filter`, `marker`, and `mask`. [[#246](https://github.com/flavorjones/loofah/issues/246)] +* Allow SVG elements `altGlyph`, `cursor`, `feImage`, `pattern`, and `tref`. [[#246](https://github.com/flavorjones/loofah/issues/246)] +* Allow protocols `fax` and `modem`. [[#255](https://github.com/flavorjones/loofah/issues/255)] (Thanks, [@cjba7](https://github.com/cjba7)!) + + +## 2.19.1 / 2022-12-13 + +### Security + +* Address CVE-2022-23514, inefficient regular expression complexity. See [GHSA-486f-hjj9-9vhh](https://github.com/flavorjones/loofah/security/advisories/GHSA-486f-hjj9-9vhh) for more information. +* Address CVE-2022-23515, improper neutralization of data URIs. See [GHSA-228g-948r-83gx](https://github.com/flavorjones/loofah/security/advisories/GHSA-228g-948r-83gx) for more information. +* Address CVE-2022-23516, uncontrolled recursion. See [GHSA-3x8r-x6xp-q4vm](https://github.com/flavorjones/loofah/security/advisories/GHSA-3x8r-x6xp-q4vm) for more information. + + +## 2.19.0 / 2022-09-14 + +### Features + +* Allow SVG 1.0 color keyword names in CSS attributes. These colors are part of the [CSS Color Module Level 3](https://www.w3.org/TR/css-color-3/#svg-color) recommendation released 2022-01-18. [[#243](https://github.com/flavorjones/loofah/issues/243)] + + +## 2.18.0 / 2022-05-11 + +### Features + +* Allow CSS property `aspect-ratio`. [[#236](https://github.com/flavorjones/loofah/issues/236)] (Thanks, [@louim](https://github.com/louim)!) + + +## 2.17.0 / 2022-04-28 + +### Features + +* Allow ARIA attributes. [[#232](https://github.com/flavorjones/loofah/issues/232), [#233](https://github.com/flavorjones/loofah/issues/233)] (Thanks, [@nick-desteffen](https://github.com/nick-desteffen)!) + + +## 2.16.0 / 2022-04-01 + +### Features + +* Allow MathML elements `menclose` and `ms`, and MathML attributes `dir`, `href`, `lquote`, `mathsize`, `notation`, and `rquote`. [[#231](https://github.com/flavorjones/loofah/issues/231)] (Thanks, [@nick-desteffen](https://github.com/nick-desteffen)!) + + +## 2.15.0 / 2022-03-14 + +### Features + +* Expand set of allowed protocols to include `sms:`. [[#228](https://github.com/flavorjones/loofah/issues/228)] (Thanks, [@brendon](https://github.com/brendon)!) + + +## 2.14.0 / 2022-02-11 + +### Features + +* The `#to_text` method on `Loofah::HTML::{Document,DocumentFragment}` replaces `
    ` line break elements with a newline. [[#225](https://github.com/flavorjones/loofah/issues/225)] + + +## 2.13.0 / 2021-12-10 + +### Bug fixes + +* Loofah::HTML::DocumentFragment#text no longer serializes top-level comment children. [[#221](https://github.com/flavorjones/loofah/issues/221)] + + +## 2.12.0 / 2021-08-11 + +### Features + +* Support empty HTML5 data attributes. [[#215](https://github.com/flavorjones/loofah/issues/215)] + + +## 2.11.0 / 2021-07-31 + +### Features + +* Allow HTML5 element `wbr`. +* Allow all CSS property values for `border-collapse`. [[#201](https://github.com/flavorjones/loofah/issues/201)] + + +### Changes + +* Deprecating `Loofah::HTML5::SafeList::VOID_ELEMENTS` which is not a canonical list of void HTML4 or HTML5 elements. +* Removed some elements from `Loofah::HTML5::SafeList::VOID_ELEMENTS` that either are not acceptable elements or aren't considered "void" by libxml2. + + +## 2.10.0 / 2021-06-06 + +### Features + +* Allow CSS properties `overflow-x` and `overflow-y`. [[#206](https://github.com/flavorjones/loofah/issues/206)] (Thanks, [@sampokuokkanen](https://github.com/sampokuokkanen)!) + + +## 2.9.1 / 2021-04-07 + +### Bug fixes + +* Fix a regression in v2.9.0 which inappropriately removed CSS properties with quoted string values. [[#202](https://github.com/flavorjones/loofah/issues/202)] + + +## 2.9.0 / 2021-01-14 + +### Features + +* Handle CSS functions in a CSS shorthand property (like `background`). [[#199](https://github.com/flavorjones/loofah/issues/199), [#200](https://github.com/flavorjones/loofah/issues/200)] + + +## 2.8.0 / 2020-11-25 + +### Features + +* Allow CSS properties `order`, `flex-direction`, `flex-grow`, `flex-wrap`, `flex-shrink`, `flex-flow`, `flex-basis`, `flex`, `justify-content`, `align-self`, `align-items`, and `align-content`. [[#197](https://github.com/flavorjones/loofah/issues/197)] (Thanks, [@miguelperez](https://github.com/miguelperez)!) + + +## 2.7.0 / 2020-08-26 + +### Features + +* Allow CSS properties `page-break-before`, `page-break-inside`, and `page-break-after`. [[#190](https://github.com/flavorjones/loofah/issues/190)] (Thanks, [@ahorek](https://github.com/ahorek)!) + + +### Fixes + +* Don't drop the `!important` rule from some CSS properties. [[#191](https://github.com/flavorjones/loofah/issues/191)] (Thanks, [@b7kich](https://github.com/b7kich)!) + + +## 2.6.0 / 2020-06-16 + +### Features + +* Allow CSS `border-style` keywords. [[#188](https://github.com/flavorjones/loofah/issues/188)] (Thanks, [@tarcisiozf](https://github.com/tarcisiozf)!) + + +## 2.5.0 / 2020-04-05 + +### Features + +* Allow more CSS length units: "ch", "vw", "vh", "Q", "lh", "vmin", "vmax". [[#178](https://github.com/flavorjones/loofah/issues/178)] (Thanks, [@JuanitoFatas](https://github.com/JuanitoFatas)!) + + +### Fixes + +* Remove comments from `Loofah::HTML::Document`s that exist outside the `html` element. [[#80](https://github.com/flavorjones/loofah/issues/80)] + + +### Other changes + +* Gem metadata being set [[#181](https://github.com/flavorjones/loofah/issues/181)] (Thanks, [@JuanitoFatas](https://github.com/JuanitoFatas)!) +* Test files removed from gem file [[#180](https://github.com/flavorjones/loofah/issues/180),[#166](https://github.com/flavorjones/loofah/issues/166),[#159](https://github.com/flavorjones/loofah/issues/159)] (Thanks, [@JuanitoFatas](https://github.com/JuanitoFatas) and [@greysteil](https://github.com/greysteil)!) + + +## 2.4.0 / 2019-11-25 + +### Features + +* Allow CSS property `max-width` [[#175](https://github.com/flavorjones/loofah/issues/175)] (Thanks, [@bchaney](https://github.com/bchaney)!) +* Allow CSS sizes expressed in `rem` [[#176](https://github.com/flavorjones/loofah/issues/176), [#177](https://github.com/flavorjones/loofah/issues/177)] +* Add `frozen_string_literal: true` magic comment to all `lib` files. [[#118](https://github.com/flavorjones/loofah/issues/118)] + + +## 2.3.1 / 2019-10-22 + +### Security + +Address CVE-2019-15587: Unsanitized JavaScript may occur in sanitized output when a crafted SVG element is republished. + +This CVE's public notice is at [#171](https://github.com/flavorjones/loofah/issues/171) + + +## 2.3.0 / 2019-09-28 + +### Features + +* Expand set of allowed protocols to include `tel:` and `line:`. [[#104](https://github.com/flavorjones/loofah/issues/104), [#147](https://github.com/flavorjones/loofah/issues/147)] +* Expand set of allowed CSS functions. [related to [#122](https://github.com/flavorjones/loofah/issues/122)] +* Allow greater precision in shorthand CSS values. [[#149](https://github.com/flavorjones/loofah/issues/149)] (Thanks, [@danfstucky](https://github.com/danfstucky)!) +* Allow CSS property `list-style` [[#162](https://github.com/flavorjones/loofah/issues/162)] (Thanks, [@jaredbeck](https://github.com/jaredbeck)!) +* Allow CSS keywords `thick` and `thin` [[#168](https://github.com/flavorjones/loofah/issues/168)] (Thanks, [@georgeclaghorn](https://github.com/georgeclaghorn)!) +* Allow HTML property `contenteditable` [[#167](https://github.com/flavorjones/loofah/issues/167)] (Thanks, [@andreynering](https://github.com/andreynering)!) + + +### Bug fixes + +* CSS hex values are no longer limited to lowercase hex. Previously uppercase hex were scrubbed. [[#165](https://github.com/flavorjones/loofah/issues/165)] (Thanks, [@asok](https://github.com/asok)!) + + +### Deprecations / Name Changes + +The following method and constants are hereby deprecated, and will be completely removed in a future release: + +* Deprecate `Loofah::Helpers::ActionView.white_list_sanitizer`, please use `Loofah::Helpers::ActionView.safe_list_sanitizer` instead. +* Deprecate `Loofah::Helpers::ActionView::WhiteListSanitizer`, please use `Loofah::Helpers::ActionView::SafeListSanitizer` instead. +* Deprecate `Loofah::HTML5::WhiteList`, please use `Loofah::HTML5::SafeList` instead. + +Thanks to [@JuanitoFatas](https://github.com/JuanitoFatas) for submitting these changes in [#164](https://github.com/flavorjones/loofah/issues/164) and for making the language used in Loofah more inclusive. + + +## 2.2.3 / 2018-10-30 + +### Security + +Address CVE-2018-16468: Unsanitized JavaScript may occur in sanitized output when a crafted SVG element is republished. + +This CVE's public notice is at [#154](https://github.com/flavorjones/loofah/issues/154) + + +## Meta / 2018-10-27 + +The mailing list is now on Google Groups [#146](https://github.com/flavorjones/loofah/issues/146): + +* Mail: loofah-talk@googlegroups.com +* Archive: https://groups.google.com/forum/#!forum/loofah-talk + +This change was made because librelist no longer appears to be maintained. + + +## 2.2.2 / 2018-03-22 + +Make public `Loofah::HTML5::Scrub.force_correct_attribute_escaping!`, +which was previously a private method. This is so that downstream gems +(like rails-html-sanitizer) can use this logic directly for their own +attribute scrubbers should they need to address CVE-2018-8048. + + +## 2.2.1 / 2018-03-19 + +### Security + +Addresses CVE-2018-8048. Loofah allowed non-whitelisted attributes to be present in sanitized output when input with specially-crafted HTML fragments. + +This CVE's public notice is at [#144](https://github.com/flavorjones/loofah/issues/144) + + +## 2.2.0 / 2018-02-11 + +### Features: + +* Support HTML5 `

    ` tag. [#133](https://github.com/flavorjones/loofah/issues/133) (Thanks, [@MothOnMars](https://github.com/MothOnMars)!) +* Recognize HTML5 block elements. [#136](https://github.com/flavorjones/loofah/issues/136) (Thanks, [@MothOnMars](https://github.com/MothOnMars)!) +* Support SVG `` tag. [#131](https://github.com/flavorjones/loofah/issues/131) (Thanks, [@baopham](https://github.com/baopham)!) +* Support for whitelisting CSS functions, initially just `calc` and `rgb`. [#122](https://github.com/flavorjones/loofah/issues/122)/[#123](https://github.com/flavorjones/loofah/issues/123)/[#129](https://github.com/flavorjones/loofah/issues/129) (Thanks, [@NikoRoberts](https://github.com/NikoRoberts)!) +* Whitelist CSS property `list-style-type`. [#68](https://github.com/flavorjones/loofah/issues/68)/[#137](https://github.com/flavorjones/loofah/issues/137)/[#142](https://github.com/flavorjones/loofah/issues/142) (Thanks, [@andela-ysanni](https://github.com/andela-ysanni) and [@NikoRoberts](https://github.com/NikoRoberts)!) + +### Bugfixes: + +* Properly handle nested `script` tags. [#127](https://github.com/flavorjones/loofah/issues/127). + + +## 2.1.1 / 2017-09-24 + +### Bugfixes: + +* Removed warning for unused variable. [#124](https://github.com/flavorjones/loofah/issues/124) (Thanks, [@y-yagi](https://github.com/y-yagi)!) + + +## 2.1.0 / 2017-09-24 + +### Notes: + +* Re-implemented CSS parsing and sanitization using the [crass](https://github.com/rgrove/crass) library. [#91](https://github.com/flavorjones/loofah/issues/91) + + +### Features: + +* Added :noopener HTML scrubber (Thanks, [@tastycode](https://github.com/tastycode)!) +* Support `data` URIs with the following media types: text/plain, text/css, image/png, image/gif, image/jpeg, image/svg+xml. [#101](https://github.com/flavorjones/loofah/issues/101), [#120](https://github.com/flavorjones/loofah/issues/120). (Thanks, [@mrpasquini](https://github.com/mrpasquini)!) + + +### Bugfixes: + +* The :unprintable scrubber now scrubs unprintable characters in CDATA nodes (like `" + +doc = Loofah.html5_fragment(unsafe_html).scrub!(:prune) +doc.to_s # => "ohai!
    div is safe
    " +``` + +and `text` to return plain text: + +``` ruby +doc.text # => "ohai! div is safe " +``` + +Also, `to_text` is available, which does the right thing with whitespace around block-level and line break elements. + +``` ruby +doc = Loofah.html5_fragment("

    Title

    Content
    Next line
    ") +doc.text # => "TitleContentNext line" # probably not what you want +doc.to_text # => "\nTitle\n\nContent\nNext line\n" # better +``` + +### `Loofah::HTML4::Document` and `Loofah::HTML4::DocumentFragment` + +These classes are subclasses of `Nokogiri::HTML4::Document` and `Nokogiri::HTML4::DocumentFragment`. + +The module methods `Loofah.html4_document` and `Loofah.html4_fragment` will parse either an HTML document and an HTML fragment, respectively. + +``` ruby +Loofah.html4_document(unsafe_html).is_a?(Nokogiri::HTML4::Document) # => true +Loofah.html4_fragment(unsafe_html).is_a?(Nokogiri::HTML4::DocumentFragment) # => true +``` + +### `Loofah::XML::Document` and `Loofah::XML::DocumentFragment` + +These classes are subclasses of `Nokogiri::XML::Document` and `Nokogiri::XML::DocumentFragment`. + +The module methods `Loofah.xml_document` and `Loofah.xml_fragment` will parse an XML document and an XML fragment, respectively. + +``` ruby +Loofah.xml_document(bad_xml).is_a?(Nokogiri::XML::Document) # => true +Loofah.xml_fragment(bad_xml).is_a?(Nokogiri::XML::DocumentFragment) # => true +``` + +### Nodes and Node Sets + +Nokogiri's `Node` and `NodeSet` classes also get a `scrub!` method, which makes it easy to scrub subtrees. + +The following code will apply the `employee_scrubber` only to the `employee` nodes (and their subtrees) in the document: + +``` ruby +Loofah.xml_document(bad_xml).xpath("//employee").scrub!(employee_scrubber) +``` + +And this code will only scrub the first `employee` node and its subtree: + +``` ruby +Loofah.xml_document(bad_xml).at_xpath("//employee").scrub!(employee_scrubber) +``` + +### `Loofah::Scrubber` + +A Scrubber wraps up a block (or method) that is run on a document node: + +``` ruby +# change all tags to
    tags +span2div = Loofah::Scrubber.new do |node| + node.name = "div" if node.name == "span" +end +``` + +This can then be run on a document: + +``` ruby +Loofah.html5_fragment("foo

    bar

    ").scrub!(span2div).to_s +# => "
    foo

    bar

    " +``` + +Scrubbers can be run on a document in either a top-down traversal (the default) or bottom-up. Top-down scrubbers can optionally return `Scrubber::STOP` to terminate the traversal of a subtree. Read below and in the `Loofah::Scrubber` class for more detailed usage. + +Here's an XML example: + +``` ruby +# remove all tags that have a "deceased" attribute set to true +bring_out_your_dead = Loofah::Scrubber.new do |node| + if node.name == "employee" and node["deceased"] == "true" + node.remove + Loofah::Scrubber::STOP # don't bother with the rest of the subtree + end +end +Loofah.xml_document(File.read('plague.xml')).scrub!(bring_out_your_dead) +``` + +### Built-In HTML Scrubbers + +Loofah comes with a set of sanitizing scrubbers that use `html5lib`'s safelist algorithm: + +``` ruby +doc = Loofah.html5_document(input) +doc.scrub!(:strip) # replaces unknown/unsafe tags with their inner text +doc.scrub!(:prune) # removes unknown/unsafe tags and their children +doc.scrub!(:escape) # escapes unknown/unsafe tags, like this: <script> +doc.scrub!(:whitewash) # removes unknown/unsafe/namespaced tags and their children, + # and strips all node attributes +``` + +Loofah also comes with built-in scrubers for some common transformation tasks: + +``` ruby +doc.scrub!(:nofollow) # adds rel="nofollow" attribute to links +doc.scrub!(:noopener) # adds rel="noopener" attribute to links +doc.scrub!(:noreferrer) # adds rel="noreferrer" attribute to links +doc.scrub!(:unprintable) # removes unprintable characters from text nodes +doc.scrub!(:targetblank) # adds target="_blank" attribute to links +doc.scrub!(:double_breakpoint) # where `

    ` appears in a `p` tag, close the `p` and open a new one +``` + +See `Loofah::Scrubbers` for more details and example usage. + + +### Chaining Scrubbers + +You can chain scrubbers: + +``` ruby +Loofah.html5_fragment("hello ") \ + .scrub!(:prune) \ + .scrub!(span2div).to_s +# => "
    hello
    " +``` + +### Shorthand + +The class methods `Loofah.scrub_html5_fragment` and `Loofah.scrub_html5_document` (and the corresponding HTML4 methods) are shorthand. + +These methods: + +``` ruby +Loofah.scrub_html5_fragment(unsafe_html, :prune) +Loofah.scrub_html5_document(unsafe_html, :prune) +Loofah.scrub_html4_fragment(unsafe_html, :prune) +Loofah.scrub_html4_document(unsafe_html, :prune) +Loofah.scrub_xml_fragment(bad_xml, custom_scrubber) +Loofah.scrub_xml_document(bad_xml, custom_scrubber) +``` + +do the same thing as (and arguably semantically clearer than): + +``` ruby +Loofah.html5_fragment(unsafe_html).scrub!(:prune) +Loofah.html5_document(unsafe_html).scrub!(:prune) +Loofah.html4_fragment(unsafe_html).scrub!(:prune) +Loofah.html4_document(unsafe_html).scrub!(:prune) +Loofah.xml_fragment(bad_xml).scrub!(custom_scrubber) +Loofah.xml_document(bad_xml).scrub!(custom_scrubber) +``` + + +### View Helpers + +Loofah has two "view helpers": `Loofah::Helpers.sanitize` and `Loofah::Helpers.strip_tags`, both of which are drop-in replacements for the Rails Action View helpers of the same name. + +These are not required automatically. You must require `loofah/helpers` to use them. + + +## Requirements + +* Nokogiri >= 1.5.9 + + +## Installation + +Unsurprisingly: + +> gem install loofah + +Requirements: + +* Ruby >= 2.5 + + +## Support + +The bug tracker is available here: + +* https://github.com/flavorjones/loofah/issues + +And the mailing list is on Google Groups: + +* Mail: loofah-talk@googlegroups.com +* Archive: https://groups.google.com/forum/#!forum/loofah-talk + +Consider subscribing to [Tidelift][tidelift] which provides license assurances and timely security notifications for your open source dependencies, including Loofah. [Tidelift][tidelift] subscriptions also help the Loofah maintainers fund our [automated testing](https://ci.nokogiri.org) which in turn allows us to ship releases, bugfixes, and security updates more often. + + [tidelift]: https://tidelift.com/subscription/pkg/rubygems-loofah?utm_source=undefined&utm_medium=referral&utm_campaign=enterprise + + +## Security + +See [`SECURITY.md`](SECURITY.md) for vulnerability reporting details. + + +## Related Links + +* loofah-activerecord: https://github.com/flavorjones/loofah-activerecord +* Nokogiri: http://nokogiri.org +* libxml2: http://xmlsoft.org +* html5lib: https://github.com/html5lib/ + + +## Authors + +* [Mike Dalessio](http://mike.daless.io) ([@flavorjones](https://twitter.com/flavorjones)) +* Bryan Helmkamp + +Featuring code contributed by: + +* [@flavorjones](https://github.com/flavorjones) +* [@brynary](https://github.com/brynary) +* [@olleolleolle](https://github.com/olleolleolle) +* [@JuanitoFatas](https://github.com/JuanitoFatas) +* [@kaspth](https://github.com/kaspth) +* [@tenderlove](https://github.com/tenderlove) +* [@ktdreyer](https://github.com/ktdreyer) +* [@orien](https://github.com/orien) +* [@asok](https://github.com/asok) +* [@junaruga](https://github.com/junaruga) +* [@MothOnMars](https://github.com/MothOnMars) +* [@nick-desteffen](https://github.com/nick-desteffen) +* [@NikoRoberts](https://github.com/NikoRoberts) +* [@trans](https://github.com/trans) +* [@andreynering](https://github.com/andreynering) +* [@aried3r](https://github.com/aried3r) +* [@baopham](https://github.com/baopham) +* [@batter](https://github.com/batter) +* [@brendon](https://github.com/brendon) +* [@cjba7](https://github.com/cjba7) +* [@christiankisssner](https://github.com/christiankisssner) +* [@dacort](https://github.com/dacort) +* [@danfstucky](https://github.com/danfstucky) +* [@david-a-wheeler](https://github.com/david-a-wheeler) +* [@dharamgollapudi](https://github.com/dharamgollapudi) +* [@georgeclaghorn](https://github.com/georgeclaghorn) +* [@gogainda](https://github.com/gogainda) +* [@jaredbeck](https://github.com/jaredbeck) +* [@ThatHurleyGuy](https://github.com/ThatHurleyGuy) +* [@jstorimer](https://github.com/jstorimer) +* [@jbarnette](https://github.com/jbarnette) +* [@queso](https://github.com/queso) +* [@technicalpickles](https://github.com/technicalpickles) +* [@kyoshidajp](https://github.com/kyoshidajp) +* [@kristianfreeman](https://github.com/kristianfreeman) +* [@louim](https://github.com/louim) +* [@mrpasquini](https://github.com/mrpasquini) +* [@olivierlacan](https://github.com/olivierlacan) +* [@pauldix](https://github.com/pauldix) +* [@sampokuokkanen](https://github.com/sampokuokkanen) +* [@stefannibrasil](https://github.com/stefannibrasil) +* [@tastycode](https://github.com/tastycode) +* [@vipulnsward](https://github.com/vipulnsward) +* [@joncalhoun](https://github.com/joncalhoun) +* [@ahorek](https://github.com/ahorek) +* [@rmacklin](https://github.com/rmacklin) +* [@y-yagi](https://github.com/y-yagi) +* [@lazyatom](https://github.com/lazyatom) + +And a big shout-out to Corey Innis for the name, and feedback on the API. + + +## Thank You + +The following people have generously funded Loofah with financial sponsorship: + +* Bill Harding +* [Sentry](https://sentry.io/) @getsentry + + +## Historical Note + +This library was once named "Dryopteris", which was a very bad name that nobody could spell properly. + + +## License + +Distributed under the MIT License. See `MIT-LICENSE.txt` for details. diff --git a/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/SECURITY.md b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/SECURITY.md new file mode 100644 index 00000000..3eba31ba --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/SECURITY.md @@ -0,0 +1,18 @@ +# Security and Vulnerability Reporting + +The Loofah core contributors take security very seriously and investigate all reported vulnerabilities. + +If you would like to report a vulnerablity or have a security concern regarding Loofah, please [report it via HackerOne](https://hackerone.com/loofah/reports/new). + +Your report will be acknowledged within 24 hours, and you'll receive a more detailed response within 72 hours indicating next steps in handling your report. + +If you have not received a reply to your submission within 48 hours, there are a few steps you can take: + +* Contact the current security coordinator (Mike Dalessio ) +* Email the Loofah user group at loofah-talk@googlegroups.com (archive at https://groups.google.com/forum/#!forum/loofah-talk) + +Please note, the user group list is a public area. When escalating in that venue, please do not discuss your issue. Simply say that you're trying to get a hold of someone from the core team. + +The information you share with the Loofah core contributors as part of this process will be kept confidential within the team, unless or until we need to share information upstream with our dependent libraries' core teams, at which point we will notify you. + +If a vulnerability is first reported by you, we will credit you with the discovery in the public disclosure. diff --git a/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah.rb b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah.rb new file mode 100644 index 00000000..c5fe508c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah.rb @@ -0,0 +1,173 @@ +# frozen_string_literal: true + +require "nokogiri" + +module Loofah + class << self + def html5_support? + # Note that Loofah can only support HTML5 in Nokogiri >= 1.14.0 because it requires the + # subclassing fix from https://github.com/sparklemotion/nokogiri/pull/2534 + return @html5_support if defined? @html5_support + + @html5_support = + Gem::Version.new(Nokogiri::VERSION) > Gem::Version.new("1.14.0") && + Nokogiri.uses_gumbo? + end + end +end + +require_relative "loofah/version" +require_relative "loofah/metahelpers" +require_relative "loofah/elements" + +require_relative "loofah/html5/safelist" +require_relative "loofah/html5/libxml2_workarounds" +require_relative "loofah/html5/scrub" + +require_relative "loofah/scrubber" +require_relative "loofah/scrubbers" + +require_relative "loofah/concerns" +require_relative "loofah/xml/document" +require_relative "loofah/xml/document_fragment" +require_relative "loofah/html4/document" +require_relative "loofah/html4/document_fragment" + +if Loofah.html5_support? + require_relative "loofah/html5/document" + require_relative "loofah/html5/document_fragment" +end + +# == Strings and IO Objects as Input +# +# The following methods accept any IO object in addition to accepting a string: +# +# - Loofah.html4_document +# - Loofah.html4_fragment +# - Loofah.scrub_html4_document +# - Loofah.scrub_html4_fragment +# +# - Loofah.html5_document +# - Loofah.html5_fragment +# - Loofah.scrub_html5_document +# - Loofah.scrub_html5_fragment +# +# - Loofah.xml_document +# - Loofah.xml_fragment +# - Loofah.scrub_xml_document +# - Loofah.scrub_xml_fragment +# +# - Loofah.document +# - Loofah.fragment +# - Loofah.scrub_document +# - Loofah.scrub_fragment +# +# That IO object could be a file, or a socket, or a StringIO, or anything that responds to +read+ +# and +close+. +# +module Loofah + # Alias for Loofah::HTML4 + HTML = HTML4 + + class << self + # Shortcut for Loofah::HTML4::Document.parse(*args, &block) + # + # This method accepts the same parameters as Nokogiri::HTML4::Document.parse + def html4_document(*args, &block) + Loofah::HTML4::Document.parse(*args, &block) + end + + # Shortcut for Loofah::HTML4::DocumentFragment.parse(*args, &block) + # + # This method accepts the same parameters as Nokogiri::HTML4::DocumentFragment.parse + def html4_fragment(*args, &block) + Loofah::HTML4::DocumentFragment.parse(*args, &block) + end + + # Shortcut for Loofah::HTML4::Document.parse(string_or_io).scrub!(method) + def scrub_html4_document(string_or_io, method) + Loofah::HTML4::Document.parse(string_or_io).scrub!(method) + end + + # Shortcut for Loofah::HTML4::DocumentFragment.parse(string_or_io).scrub!(method) + def scrub_html4_fragment(string_or_io, method) + Loofah::HTML4::DocumentFragment.parse(string_or_io).scrub!(method) + end + + if Loofah.html5_support? + # Shortcut for Loofah::HTML5::Document.parse(*args, &block) + # + # This method accepts the same parameters as Nokogiri::HTML5::Document.parse + def html5_document(*args, &block) + Loofah::HTML5::Document.parse(*args, &block) + end + + # Shortcut for Loofah::HTML5::DocumentFragment.parse(*args, &block) + # + # This method accepts the same parameters as Nokogiri::HTML5::DocumentFragment.parse + def html5_fragment(*args, &block) + Loofah::HTML5::DocumentFragment.parse(*args, &block) + end + + # Shortcut for Loofah::HTML5::Document.parse(string_or_io).scrub!(method) + def scrub_html5_document(string_or_io, method) + Loofah::HTML5::Document.parse(string_or_io).scrub!(method) + end + + # Shortcut for Loofah::HTML5::DocumentFragment.parse(string_or_io).scrub!(method) + def scrub_html5_fragment(string_or_io, method) + Loofah::HTML5::DocumentFragment.parse(string_or_io).scrub!(method) + end + else + def html5_document(*args, &block) + raise NotImplementedError, "Loofah::HTML5 is not supported by your version of Nokogiri" + end + + def html5_fragment(*args, &block) + raise NotImplementedError, "Loofah::HTML5 is not supported by your version of Nokogiri" + end + + def scrub_html5_document(string_or_io, method) + raise NotImplementedError, "Loofah::HTML5 is not supported by your version of Nokogiri" + end + + def scrub_html5_fragment(string_or_io, method) + raise NotImplementedError, "Loofah::HTML5 is not supported by your version of Nokogiri" + end + end + + alias_method :document, :html4_document + alias_method :fragment, :html4_fragment + alias_method :scrub_document, :scrub_html4_document + alias_method :scrub_fragment, :scrub_html4_fragment + + # Shortcut for Loofah::XML::Document.parse(*args, &block) + # + # This method accepts the same parameters as Nokogiri::XML::Document.parse + def xml_document(*args, &block) + Loofah::XML::Document.parse(*args, &block) + end + + # Shortcut for Loofah::XML::DocumentFragment.parse(*args, &block) + # + # This method accepts the same parameters as Nokogiri::XML::DocumentFragment.parse + def xml_fragment(*args, &block) + Loofah::XML::DocumentFragment.parse(*args, &block) + end + + # Shortcut for Loofah.xml_fragment(string_or_io).scrub!(method) + def scrub_xml_fragment(string_or_io, method) + Loofah.xml_fragment(string_or_io).scrub!(method) + end + + # Shortcut for Loofah.xml_document(string_or_io).scrub!(method) + def scrub_xml_document(string_or_io, method) + Loofah.xml_document(string_or_io).scrub!(method) + end + + # A helper to remove extraneous whitespace from text-ified HTML + def remove_extraneous_whitespace(string) + string.gsub(/\n\s*\n\s*\n/, "\n\n") + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/concerns.rb b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/concerns.rb new file mode 100644 index 00000000..9e0f5d7d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/concerns.rb @@ -0,0 +1,207 @@ +# frozen_string_literal: true + +module Loofah + # + # Mixes +scrub!+ into Document, DocumentFragment, Node and NodeSet. + # + # Traverse the document or fragment, invoking the +scrubber+ on each node. + # + # +scrubber+ must either be one of the symbols representing the built-in scrubbers (see + # Scrubbers), or a Scrubber instance. + # + # span2div = Loofah::Scrubber.new do |node| + # node.name = "div" if node.name == "span" + # end + # Loofah.html5_fragment("foo

    bar

    ").scrub!(span2div).to_s + # # => "
    foo

    bar

    " + # + # or + # + # unsafe_html = "ohai!
    div is safe
    " + # Loofah.html5_fragment(unsafe_html).scrub!(:strip).to_s + # # => "ohai!
    div is safe
    " + # + # Note that this method is called implicitly from the shortcuts Loofah.scrub_html5_fragment et + # al. + # + # Please see Scrubber for more information on implementation and traversal, and README.rdoc for + # more example usage. + # + module ScrubBehavior + module Node # :nodoc: + def scrub!(scrubber) + # + # yes. this should be three separate methods. but nokogiri decorates (or not) based on + # whether the module name has already been included. and since documents get decorated just + # like their constituent nodes, we need to jam all the logic into a single module. + # + scrubber = ScrubBehavior.resolve_scrubber(scrubber) + case self + when Nokogiri::XML::Document + scrubber.traverse(root) if root + when Nokogiri::XML::DocumentFragment + children.scrub!(scrubber) + else + scrubber.traverse(self) + end + self + end + end + + module NodeSet # :nodoc: + def scrub!(scrubber) + each { |node| node.scrub!(scrubber) } + self + end + end + + class << self + def resolve_scrubber(scrubber) # :nodoc: + scrubber = Scrubbers::MAP[scrubber].new if Scrubbers::MAP[scrubber] + unless scrubber.is_a?(Loofah::Scrubber) + raise Loofah::ScrubberNotFound, "not a Scrubber or a scrubber name: #{scrubber.inspect}" + end + + scrubber + end + end + end + + # + # Overrides +text+ in Document and DocumentFragment classes, and mixes in +to_text+. + # + module TextBehavior + # + # Returns a plain-text version of the markup contained by the document, with HTML entities + # encoded. + # + # This method is significantly faster than #to_text, but isn't clever about whitespace around + # block elements. + # + # Loofah.html5_document("

    Title

    Content
    ").text + # # => "TitleContent" + # + # By default, the returned text will have HTML entities escaped. If you want unescaped + # entities, and you understand that the result is unsafe to render in a browser, then you can + # pass an argument as shown: + # + # frag = Loofah.html5_fragment("<script>alert('EVIL');</script>") + # # ok for browser: + # frag.text # => "<script>alert('EVIL');</script>" + # # decidedly not ok for browser: + # frag.text(:encode_special_chars => false) # => "" + # + def text(options = {}) + result = if serialize_root + serialize_root.children.reject(&:comment?).map(&:inner_text).join("") + else + "" + end + if options[:encode_special_chars] == false + result # possibly dangerous if rendered in a browser + else + encode_special_chars(result) + end + end + + alias_method :inner_text, :text + alias_method :to_str, :text + + # + # Returns a plain-text version of the markup contained by the fragment, with HTML entities + # encoded. + # + # This method is slower than #text, but is clever about whitespace around block elements and + # line break elements. + # + # Loofah.html5_document("

    Title

    Content
    Next line
    ").to_text + # # => "\nTitle\n\nContent\nNext line\n" + # + def to_text(options = {}) + Loofah.remove_extraneous_whitespace(dup.scrub!(:newline_block_elements).text(options)) + end + end + + module DocumentDecorator # :nodoc: + def initialize(*args, &block) + super + decorators(Nokogiri::XML::Node) << ScrubBehavior::Node + decorators(Nokogiri::XML::NodeSet) << ScrubBehavior::NodeSet + end + end + + module HtmlDocumentBehavior # :nodoc: + module ClassMethods + def parse(*args, &block) + remove_comments_before_html_element(super) + end + + private + + # remove comments that exist outside of the HTML element. + # + # these comments are allowed by the HTML spec: + # + # https://www.w3.org/TR/html401/struct/global.html#h-7.1 + # + # but are not scrubbed by Loofah because these nodes don't meet + # the contract that scrubbers expect of a node (e.g., it can be + # replaced, sibling and children nodes can be created). + def remove_comments_before_html_element(doc) + doc.children.each do |child| + child.unlink if child.comment? + end + doc + end + end + + class << self + def included(base) + base.extend(ClassMethods) + end + end + + def serialize_root + at_xpath("/html/body") + end + end + + module HtmlFragmentBehavior # :nodoc: + module ClassMethods + def parse(tags, encoding = nil) + doc = document_klass.new + + encoding ||= tags.respond_to?(:encoding) ? tags.encoding.name : "UTF-8" + doc.encoding = encoding + + new(doc, tags) + end + + def document_klass + @document_klass ||= if Loofah.html5_support? && self == Loofah::HTML5::DocumentFragment + Loofah::HTML5::Document + elsif self == Loofah::HTML4::DocumentFragment + Loofah::HTML4::Document + else + raise ArgumentError, "unexpected class: #{self}" + end + end + end + + class << self + def included(base) + base.extend(ClassMethods) + end + end + + def to_s + serialize_root.children.to_s + end + + alias_method :serialize, :to_s + + def serialize_root + at_xpath("./body") || self + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/elements.rb b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/elements.rb new file mode 100644 index 00000000..cf3600e7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/elements.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +require "set" + +module Loofah + module Elements + STRICT_BLOCK_LEVEL_HTML4 = Set.new([ + "address", + "blockquote", + "center", + "dir", + "div", + "dl", + "fieldset", + "form", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "hr", + "isindex", + "menu", + "noframes", + "noscript", + "ol", + "p", + "pre", + "table", + "ul", + ]) + + # https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements + STRICT_BLOCK_LEVEL_HTML5 = Set.new([ + "address", + "article", + "aside", + "blockquote", + "canvas", + "dd", + "div", + "dl", + "dt", + "fieldset", + "figcaption", + "figure", + "footer", + "form", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "header", + "hgroup", + "hr", + "li", + "main", + "nav", + "noscript", + "ol", + "output", + "p", + "pre", + "section", + "table", + "tfoot", + "ul", + "video", + ]) + + # The following elements may also be considered block-level + # elements since they may contain block-level elements + LOOSE_BLOCK_LEVEL = Set.new([ + "dd", + "dt", + "frameset", + "li", + "tbody", + "td", + "tfoot", + "th", + "thead", + "tr", + ]) + + # Elements that aren't block but should generate a newline in #to_text + INLINE_LINE_BREAK = Set.new(["br"]) + + STRICT_BLOCK_LEVEL = STRICT_BLOCK_LEVEL_HTML4 + STRICT_BLOCK_LEVEL_HTML5 + BLOCK_LEVEL = STRICT_BLOCK_LEVEL + LOOSE_BLOCK_LEVEL + LINEBREAKERS = BLOCK_LEVEL + INLINE_LINE_BREAK + end + + ::Loofah::MetaHelpers.add_downcased_set_members_to_all_set_constants(::Loofah::Elements) +end diff --git a/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/helpers.rb b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/helpers.rb new file mode 100644 index 00000000..6aebb4f3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/helpers.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +module Loofah + module Helpers + class << self + # + # A replacement for Rails's built-in +strip_tags+ helper. + # + # Loofah::Helpers.strip_tags("
    Hello there
    ") # => "Hello there" + # + def strip_tags(string_or_io) + Loofah.html4_fragment(string_or_io).text + end + + # + # A replacement for Rails's built-in +sanitize+ helper. + # + # Loofah::Helpers.sanitize("") + # # => "<script src=\"http://ha.ckers.org/xss.js\"></script>" + # + def sanitize(string_or_io) + loofah_fragment = Loofah.html4_fragment(string_or_io) + loofah_fragment.scrub!(:strip) + loofah_fragment.xpath("./form").each(&:remove) + loofah_fragment.to_s + end + + # + # A replacement for Rails's built-in +sanitize_css+ helper. + # + # Loofah::Helpers.sanitize_css("display:block;background-image:url(http://example.com/foo.jpg)") + # # => "display: block;" + # + def sanitize_css(style_string) + ::Loofah::HTML5::Scrub.scrub_css(style_string) + end + + # + # A helper to remove extraneous whitespace from text-ified HTML. + # + # TODO: remove this in a future major-point-release. + # + def remove_extraneous_whitespace(string) + Loofah.remove_extraneous_whitespace(string) + end + end + + module ActionView + module ClassMethods # :nodoc: + def full_sanitizer + @full_sanitizer ||= ::Loofah::Helpers::ActionView::FullSanitizer.new + end + + def safe_list_sanitizer + @safe_list_sanitizer ||= ::Loofah::Helpers::ActionView::SafeListSanitizer.new + end + + def white_list_sanitizer + warn("warning: white_list_sanitizer is deprecated, please use safe_list_sanitizer instead.") + safe_list_sanitizer + end + end + + # + # Replacement class for Rails's HTML::FullSanitizer. + # + # To use by default, call this in an application initializer: + # + # ActionView::Helpers::SanitizeHelper.full_sanitizer = \ + # Loofah::Helpers::ActionView::FullSanitizer.new + # + # Or, to generally opt-in to Loofah's view sanitizers: + # + # Loofah::Helpers::ActionView.set_as_default_sanitizer + # + class FullSanitizer + def sanitize(html, *args) + Loofah::Helpers.strip_tags(html) + end + end + + # + # Replacement class for Rails's HTML::WhiteListSanitizer. + # + # To use by default, call this in an application initializer: + # + # ActionView::Helpers::SanitizeHelper.safe_list_sanitizer = \ + # Loofah::Helpers::ActionView::SafeListSanitizer.new + # + # Or, to generally opt-in to Loofah's view sanitizers: + # + # Loofah::Helpers::ActionView.set_as_default_sanitizer + # + class SafeListSanitizer + def sanitize(html, *args) + Loofah::Helpers.sanitize(html) + end + + def sanitize_css(style_string, *args) + Loofah::Helpers.sanitize_css(style_string) + end + end + + WhiteListSanitizer = SafeListSanitizer + if Object.respond_to?(:deprecate_constant) + deprecate_constant :WhiteListSanitizer + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html4/document.rb b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html4/document.rb new file mode 100644 index 00000000..593380b2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html4/document.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Loofah + module HTML4 # :nodoc: + # + # Subclass of Nokogiri::HTML4::Document. + # + # See Loofah::ScrubBehavior and Loofah::TextBehavior for additional methods. + # + class Document < Nokogiri::HTML4::Document + include Loofah::ScrubBehavior::Node + include Loofah::DocumentDecorator + include Loofah::TextBehavior + include Loofah::HtmlDocumentBehavior + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html4/document_fragment.rb b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html4/document_fragment.rb new file mode 100644 index 00000000..988f4b9b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html4/document_fragment.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Loofah + module HTML4 # :nodoc: + # + # Subclass of Nokogiri::HTML4::DocumentFragment. + # + # See Loofah::ScrubBehavior and Loofah::TextBehavior for additional methods. + # + class DocumentFragment < Nokogiri::HTML4::DocumentFragment + include Loofah::TextBehavior + include Loofah::HtmlFragmentBehavior + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html5/document.rb b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html5/document.rb new file mode 100644 index 00000000..03d9bc44 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html5/document.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Loofah + module HTML5 # :nodoc: + # + # Subclass of Nokogiri::HTML5::Document. + # + # See Loofah::ScrubBehavior and Loofah::TextBehavior for additional methods. + # + class Document < Nokogiri::HTML5::Document + include Loofah::ScrubBehavior::Node + include Loofah::DocumentDecorator + include Loofah::TextBehavior + include Loofah::HtmlDocumentBehavior + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html5/document_fragment.rb b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html5/document_fragment.rb new file mode 100644 index 00000000..79e18575 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html5/document_fragment.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Loofah + module HTML5 # :nodoc: + # + # Subclass of Nokogiri::HTML5::DocumentFragment. + # + # See Loofah::ScrubBehavior and Loofah::TextBehavior for additional methods. + # + class DocumentFragment < Nokogiri::HTML5::DocumentFragment + include Loofah::TextBehavior + include Loofah::HtmlFragmentBehavior + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html5/libxml2_workarounds.rb b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html5/libxml2_workarounds.rb new file mode 100644 index 00000000..cd95fd9e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html5/libxml2_workarounds.rb @@ -0,0 +1,28 @@ +# coding: utf-8 +# frozen_string_literal: true + +require "set" + +module Loofah + # + # constants related to working around unhelpful libxml2 behavior + # + # ಠ_ಠ + # + module LibxmlWorkarounds + # + # these attributes and qualifying parent tags are determined by the code at: + # + # https://git.gnome.org/browse/libxml2/tree/HTMLtree.c?h=v2.9.2#n714 + # + # see comments about CVE-2018-8048 within the tests for more information + # + BROKEN_ESCAPING_ATTRIBUTES = Set.new([ + "href", + "action", + "src", + "name", + ]) + BROKEN_ESCAPING_ATTRIBUTES_QUALIFYING_TAG = { "name" => "a" } + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html5/safelist.rb b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html5/safelist.rb new file mode 100644 index 00000000..142c7583 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html5/safelist.rb @@ -0,0 +1,1058 @@ +# frozen_string_literal: true + +require "set" + +module Loofah + module HTML5 # :nodoc: + # + # HTML safelist lifted from HTML5lib sanitizer code: + # + # http://code.google.com/p/html5lib/ + # + # + # + # Copyright (c) 2006-2008 The Authors + # + # Contributors: + # James Graham - jg307@cam.ac.uk + # Anne van Kesteren - annevankesteren@gmail.com + # Lachlan Hunt - lachlan.hunt@lachy.id.au + # Matt McDonald - kanashii@kanashii.ca + # Sam Ruby - rubys@intertwingly.net + # Ian Hickson (Google) - ian@hixie.ch + # Thomas Broyer - t.broyer@ltgt.net + # Jacques Distler - distler@golem.ph.utexas.edu + # Henri Sivonen - hsivonen@iki.fi + # The Mozilla Foundation (contributions from Henri Sivonen since 2008) + # + # Permission is hereby granted, free of charge, to any person + # obtaining a copy of this software and associated documentation + # files (the "Software"), to deal in the Software without + # restriction, including without limitation the rights to use, copy, + # modify, merge, publish, distribute, sublicense, and/or sell copies + # of the Software, and to permit persons to whom the Software is + # furnished to do so, subject to the following conditions: + # + # The above copyright notice and this permission notice shall be + # included in all copies or substantial portions of the Software. + # + # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + # DEALINGS IN THE SOFTWARE. + # + # + module SafeList + ACCEPTABLE_ELEMENTS = Set.new([ + "a", + "abbr", + "acronym", + "address", + "area", + "article", + "aside", + "audio", + "b", + "bdi", + "bdo", + "big", + "blockquote", + "br", + "button", + "canvas", + "caption", + "center", + "cite", + "code", + "col", + "colgroup", + "command", + "datalist", + "dd", + "del", + "details", + "dfn", + "dir", + "div", + "dl", + "dt", + "em", + "fieldset", + "figcaption", + "figure", + "font", + "footer", + "form", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "header", + "hr", + "i", + "img", + "input", + "ins", + "kbd", + "label", + "legend", + "li", + "main", + "map", + "mark", + "menu", + "meter", + "nav", + "ol", + "optgroup", + "option", + "output", + "p", + "pre", + "q", + "s", + "samp", + "section", + "select", + "small", + "span", + "strike", + "strong", + "sub", + "summary", + "sup", + "table", + "tbody", + "td", + "textarea", + "tfoot", + "th", + "thead", + "time", + "tr", + "tt", + "u", + "ul", + "var", + "video", + "wbr", + ]) + + MATHML_ELEMENTS = Set.new([ + "annotation", + "annotation-xml", + "maction", + "math", + "menclose", + "merror", + "mfenced", + "mfrac", + "mi", + "mmultiscripts", + "mn", + "mo", + "mover", + "mpadded", + "mphantom", + "mprescripts", + "mroot", + "mrow", + "ms", + "mspace", + "msqrt", + "mstyle", + "msub", + "msubsup", + "msup", + "mtable", + "mtd", + "mtext", + "mtr", + "munder", + "munderover", + "none", + "semantics", + ]) + + SVG_ELEMENTS = Set.new([ + "a", + "altGlyph", + "animate", + "animateColor", + "animateMotion", + "animateTransform", + "circle", + "clipPath", + "cursor", + "defs", + "desc", + "ellipse", + "feGaussianBlur", + "feImage", + "filter", + "font-face", + "font-face-name", + "font-face-src", + "foreignObject", + "g", + "glyph", + "hkern", + "line", + "linearGradient", + "marker", + "mask", + "metadata", + "missing-glyph", + "mpath", + "path", + "pattern", + "polygon", + "polyline", + "radialGradient", + "rect", + "set", + "stop", + "svg", + "switch", + "symbol", + "text", + "textPath", + "title", + "tref", + "tspan", + "use", + ]) + + ACCEPTABLE_ATTRIBUTES = Set.new([ + "abbr", + "accept", + "accept-charset", + "accesskey", + "action", + "align", + "alt", + "axis", + "border", + "cellpadding", + "cellspacing", + "char", + "charoff", + "charset", + "checked", + "cite", + "class", + "clear", + "color", + "cols", + "colspan", + "compact", + "contenteditable", + "coords", + "datetime", + "dir", + "disabled", + "enctype", + "for", + "frame", + "headers", + "height", + "href", + "hreflang", + "hspace", + "id", + "ismap", + "label", + "lang", + "longdesc", + "loop", + "loopcount", + "loopend", + "loopstart", + "maxlength", + "media", + "method", + "multiple", + "name", + "nohref", + "noshade", + "nowrap", + "poster", + "preload", + "prompt", + "readonly", + "rel", + "rev", + "rows", + "rowspan", + "rules", + "scope", + "selected", + "shape", + "size", + "span", + "src", + "start", + "style", + "summary", + "tabindex", + "target", + "title", + "type", + "usemap", + "valign", + "value", + "vspace", + "width", + "xml:lang", + ]) + + MATHML_ATTRIBUTES = Set.new([ + "actiontype", + "align", + "close", + "columnalign", + "columnlines", + "columnspacing", + "columnspan", + "depth", + "dir", + "display", + "displaystyle", + "encoding", + "equalcolumns", + "equalrows", + "fence", + "fontstyle", + "fontweight", + "frame", + "height", + "href", + "linethickness", + "lquote", + "lspace", + "mathbackground", + "mathcolor", + "mathsize", + "mathvariant", + "maxsize", + "minsize", + "notation", + "open", + "other", + "rowalign", + "rowlines", + "rowspacing", + "rowspan", + "rquote", + "rspace", + "scriptlevel", + "selection", + "separator", + "separators", + "stretchy", + "width", + "xlink:href", + "xlink:show", + "xlink:type", + "xmlns", + "xmlns:xlink", + ]) + + SVG_ATTRIBUTES = Set.new([ + "accent-height", + "accumulate", + "additive", + "alphabetic", + "arabic-form", + "ascent", + "attributeName", + "attributeType", + "baseProfile", + "bbox", + "begin", + "calcMode", + "cap-height", + "class", + "clip-path", + "clip-rule", + "color", + "color-interpolation-filters", + "color-profile", + "color-rendering", + "content", + "cursor", + "cx", + "cy", + "d", + "descent", + "display", + "dur", + "dx", + "dy", + "end", + "fill", + "fill-opacity", + "fill-rule", + "filter", + "filterRes", + "filterUnits", + "font-family", + "font-size", + "font-stretch", + "font-style", + "font-variant", + "font-weight", + "fx", + "fy", + "g1", + "g2", + "glyph-name", + "gradientUnits", + "hanging", + "height", + "horiz-adv-x", + "horiz-origin-x", + "id", + "ideographic", + "k", + "keyPoints", + "keySplines", + "keyTimes", + "lang", + "marker", + "marker-end", + "marker-mid", + "marker-start", + "markerHeight", + "markerUnits", + "markerWidth", + "mask", + "maskContentUnits", + "maskUnits", + "mathematical", + "max", + "method", + "min", + "name", + "offset", + "opacity", + "orient", + "origin", + "overline-position", + "overline-thickness", + "panose-1", + "path", + "pathLength", + "patternContentUnits", + "patternTransform", + "patternUnits", + "points", + "preserveAspectRatio", + "primitiveUnits", + "r", + "refX", + "refY", + "repeatCount", + "repeatDur", + "requiredExtensions", + "requiredFeatures", + "restart", + "rotate", + "rx", + "ry", + "slope", + "spacing", + "startOffset", + "stdDeviation", + "stemh", + "stemv", + "stop-color", + "stop-opacity", + "strikethrough-position", + "strikethrough-thickness", + "stroke", + "stroke-dasharray", + "stroke-dashoffset", + "stroke-linecap", + "stroke-linejoin", + "stroke-miterlimit", + "stroke-opacity", + "stroke-width", + "systemLanguage", + "target", + "text-anchor", + "transform", + "type", + "u1", + "u2", + "underline-position", + "underline-thickness", + "unicode", + "unicode-range", + "units-per-em", + "version", + "viewBox", + "visibility", + "width", + "widths", + "x", + "x-height", + "x1", + "x2", + "xlink:actuate", + "xlink:arcrole", + "xlink:href", + "xlink:role", + "xlink:show", + "xlink:title", + "xlink:type", + "xml:base", + "xml:lang", + "xml:space", + "xmlns", + "xmlns:xlink", + "y", + "y1", + "y2", + "zoomAndPan", + ]) + + ARIA_ATTRIBUTES = Set.new([ + "aria-activedescendant", + "aria-atomic", + "aria-autocomplete", + "aria-braillelabel", + "aria-brailleroledescription", + "aria-busy", + "aria-checked", + "aria-colcount", + "aria-colindex", + "aria-colindextext", + "aria-colspan", + "aria-controls", + "aria-current", + "aria-describedby", + "aria-description", + "aria-details", + "aria-disabled", + "aria-dropeffect", + "aria-errormessage", + "aria-expanded", + "aria-flowto", + "aria-grabbed", + "aria-haspopup", + "aria-hidden", + "aria-invalid", + "aria-keyshortcuts", + "aria-label", + "aria-labelledby", + "aria-level", + "aria-live", + "aria-multiline", + "aria-multiselectable", + "aria-orientation", + "aria-owns", + "aria-placeholder", + "aria-posinset", + "aria-pressed", + "aria-readonly", + "aria-relevant", + "aria-required", + "aria-roledescription", + "aria-rowcount", + "aria-rowindex", + "aria-rowindextext", + "aria-rowspan", + "aria-selected", + "aria-setsize", + "aria-sort", + "aria-valuemax", + "aria-valuemin", + "aria-valuenow", + "aria-valuetext", + "role", + ]) + + ATTR_VAL_IS_URI = Set.new([ + "action", + "cite", + "href", + "longdesc", + "poster", + "preload", + "src", + "xlink:href", + "xml:base", + ]) + + SVG_ATTR_VAL_ALLOWS_REF = Set.new([ + "clip-path", + "color-profile", + "cursor", + "fill", + "filter", + "marker", + "marker-end", + "marker-mid", + "marker-start", + "mask", + "stroke", + ]) + + SVG_ALLOW_LOCAL_HREF = Set.new([ + "altGlyph", + "animate", + "animateColor", + "animateMotion", + "animateTransform", + "cursor", + "feImage", + "filter", + "linearGradient", + "pattern", + "radialGradient", + "set", + "textpath", + "tref", + "use", + ]) + + ACCEPTABLE_CSS_PROPERTIES = Set.new([ + "azimuth", + "align-content", + "align-items", + "align-self", + "aspect-ratio", + "background-color", + "border-bottom-color", + "border-collapse", + "border-color", + "border-left-color", + "border-right-color", + "border-top-color", + "clear", + "color", + "cursor", + "direction", + "display", + "elevation", + "flex", + "flex-basis", + "flex-direction", + "flex-flow", + "flex-grow", + "flex-shrink", + "flex-wrap", + "float", + "font", + "font-family", + "font-size", + "font-style", + "font-variant", + "font-weight", + "height", + "justify-content", + "letter-spacing", + "line-height", + "list-style", + "list-style-type", + "max-height", + "max-width", + "min-height", + "min-width", + "order", + "overflow", + "overflow-x", + "overflow-y", + "page-break-after", + "page-break-before", + "page-break-inside", + "pause", + "pause-after", + "pause-before", + "pitch", + "pitch-range", + "richness", + "speak", + "speak-header", + "speak-numeral", + "speak-punctuation", + "speech-rate", + "stress", + "text-align", + "text-decoration", + "text-indent", + "unicode-bidi", + "vertical-align", + "voice-family", + "volume", + "white-space", + "width", + ]) + + ACCEPTABLE_CSS_KEYWORDS = Set.new([ + "!important", + "auto", + "block", + "bold", + "both", + "bottom", + "center", + "collapse", + "dashed", + "dotted", + "double", + "groove", + "hidden", + "inherit", + "initial", + "inset", + "italic", + "left", + "medium", + "none", + "normal", + "nowrap", + "outset", + "pointer", + "revert", + "ridge", + "right", + "separate", + "solid", + "thick", + "thin", + "top", + "transparent", + "underline", + "unset", + ]) + + # https://www.w3.org/TR/css-color-3/#html4 + ACCEPTABLE_CSS_COLORS = Set.new([ + "aqua", + "black", + "blue", + "fuchsia", + "gray", + "green", + "lime", + "maroon", + "navy", + "olive", + "purple", + "red", + "silver", + "teal", + "white", + "yellow", + ]) + + # https://www.w3.org/TR/css-color-3/#svg-color + ACCEPTABLE_CSS_EXTENDED_COLORS = Set.new([ + "aliceblue", + "antiquewhite", + "aqua", + "aquamarine", + "azure", + "beige", + "bisque", + "black", + "blanchedalmond", + "blue", + "blueviolet", + "brown", + "burlywood", + "cadetblue", + "chartreuse", + "chocolate", + "coral", + "cornflowerblue", + "cornsilk", + "crimson", + "cyan", + "darkblue", + "darkcyan", + "darkgoldenrod", + "darkgray", + "darkgreen", + "darkgrey", + "darkkhaki", + "darkmagenta", + "darkolivegreen", + "darkorange", + "darkorchid", + "darkred", + "darksalmon", + "darkseagreen", + "darkslateblue", + "darkslategray", + "darkslategrey", + "darkturquoise", + "darkviolet", + "deeppink", + "deepskyblue", + "dimgray", + "dimgrey", + "dodgerblue", + "firebrick", + "floralwhite", + "forestgreen", + "fuchsia", + "gainsboro", + "ghostwhite", + "gold", + "goldenrod", + "gray", + "green", + "greenyellow", + "grey", + "honeydew", + "hotpink", + "indianred", + "indigo", + "ivory", + "khaki", + "lavender", + "lavenderblush", + "lawngreen", + "lemonchiffon", + "lightblue", + "lightcoral", + "lightcyan", + "lightgoldenrodyellow", + "lightgray", + "lightgreen", + "lightgrey", + "lightpink", + "lightsalmon", + "lightseagreen", + "lightskyblue", + "lightslategray", + "lightslategrey", + "lightsteelblue", + "lightyellow", + "lime", + "limegreen", + "linen", + "magenta", + "maroon", + "mediumaquamarine", + "mediumblue", + "mediumorchid", + "mediumpurple", + "mediumseagreen", + "mediumslateblue", + "mediumspringgreen", + "mediumturquoise", + "mediumvioletred", + "midnightblue", + "mintcream", + "mistyrose", + "moccasin", + "navajowhite", + "navy", + "oldlace", + "olive", + "olivedrab", + "orange", + "orangered", + "orchid", + "palegoldenrod", + "palegreen", + "paleturquoise", + "palevioletred", + "papayawhip", + "peachpuff", + "peru", + "pink", + "plum", + "powderblue", + "purple", + "red", + "rosybrown", + "royalblue", + "saddlebrown", + "salmon", + "sandybrown", + "seagreen", + "seashell", + "sienna", + "silver", + "skyblue", + "slateblue", + "slategray", + "slategrey", + "snow", + "springgreen", + "steelblue", + "tan", + "teal", + "thistle", + "tomato", + "turquoise", + "violet", + "wheat", + "white", + "whitesmoke", + "yellow", + "yellowgreen", + ]) + + # see https://www.quackit.com/css/functions/ + # omit `url` and `image` from that list + ACCEPTABLE_CSS_FUNCTIONS = Set.new([ + "attr", + "blur", + "brightness", + "calc", + "circle", + "contrast", + "counter", + "counters", + "cubic-bezier", + "drop-shadow", + "ellipse", + "grayscale", + "hsl", + "hsla", + "hue-rotate", + "hwb", + "inset", + "invert", + "linear-gradient", + "matrix", + "matrix3d", + "opacity", + "perspective", + "polygon", + "radial-gradient", + "repeating-linear-gradient", + "repeating-radial-gradient", + "rgb", + "rgba", + "rotate", + "rotate3d", + "rotateX", + "rotateY", + "rotateZ", + "saturate", + "sepia", + "scale", + "scale3d", + "scaleX", + "scaleY", + "scaleZ", + "skew", + "skewX", + "skewY", + "symbols", + "translate", + "translate3d", + "translateX", + "translateY", + "translateZ", + ]) + + SHORTHAND_CSS_PROPERTIES = Set.new([ + "background", + "border", + "margin", + "padding", + ]) + + ACCEPTABLE_SVG_PROPERTIES = Set.new([ + "fill", + "fill-opacity", + "fill-rule", + "stroke", + "stroke-width", + "stroke-linecap", + "stroke-linejoin", + "stroke-opacity", + ]) + + PROTOCOL_SEPARATOR = /:|(�*58)|(p)|(�*3a)|(%|%)3A/i + + ACCEPTABLE_PROTOCOLS = Set.new([ + "afs", + "aim", + "callto", + "data", + "ed2k", + "fax", + "feed", + "ftp", + "gopher", + "http", + "https", + "irc", + "line", + "mailto", + "modem", + "news", + "nntp", + "rsync", + "rtsp", + "sftp", + "sms", + "ssh", + "tag", + "tel", + "telnet", + "urn", + "webcal", + "xmpp", + ]) + + ACCEPTABLE_URI_DATA_MEDIATYPES = Set.new([ + "image/gif", + "image/jpeg", + "image/png", + "text/css", + "text/plain", + ]) + + # subclasses may define their own versions of these constants + ALLOWED_ELEMENTS = ACCEPTABLE_ELEMENTS + MATHML_ELEMENTS + SVG_ELEMENTS + ALLOWED_ATTRIBUTES = ACCEPTABLE_ATTRIBUTES + MATHML_ATTRIBUTES + SVG_ATTRIBUTES + ARIA_ATTRIBUTES + ALLOWED_CSS_PROPERTIES = ACCEPTABLE_CSS_PROPERTIES + ALLOWED_CSS_KEYWORDS = ACCEPTABLE_CSS_KEYWORDS + ACCEPTABLE_CSS_COLORS + ACCEPTABLE_CSS_EXTENDED_COLORS + ALLOWED_CSS_FUNCTIONS = ACCEPTABLE_CSS_FUNCTIONS + ALLOWED_SVG_PROPERTIES = ACCEPTABLE_SVG_PROPERTIES + ALLOWED_PROTOCOLS = ACCEPTABLE_PROTOCOLS + ALLOWED_URI_DATA_MEDIATYPES = ACCEPTABLE_URI_DATA_MEDIATYPES + + # TODO: remove VOID_ELEMENTS in a future major release + # and put it in the tests (it is used only for testing, not for functional behavior) + VOID_ELEMENTS = Set.new([ + "area", + "br", + "hr", + "img", + "input", + ]) + + # additional tags we should consider safe since we have libxml2 fixing up our documents. + TAGS_SAFE_WITH_LIBXML2 = Set.new([ + "body", + "head", + "html", + ]) + ALLOWED_ELEMENTS_WITH_LIBXML2 = ALLOWED_ELEMENTS + TAGS_SAFE_WITH_LIBXML2 + end + + WhiteList = SafeList + if Object.respond_to?(:deprecate_constant) + deprecate_constant :WhiteList + end + + ::Loofah::MetaHelpers.add_downcased_set_members_to_all_set_constants(::Loofah::HTML5::SafeList) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html5/scrub.rb b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html5/scrub.rb new file mode 100644 index 00000000..b774132d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/html5/scrub.rb @@ -0,0 +1,230 @@ +# frozen_string_literal: true + +require "cgi/escape" +require "cgi/util" if RUBY_VERSION < "3.5" +require "crass" + +module Loofah + module HTML5 # :nodoc: + module Scrub + CONTROL_CHARACTERS = /[`\u0000-\u0020\u007f\u0080-\u0101]/ + CSS_KEYWORDISH = /\A(#[0-9a-fA-F]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|-?\d{0,3}\.?\d{0,10}(ch|cm|r?em|ex|in|lh|mm|pc|pt|px|Q|vmax|vmin|vw|vh|%|,|\))?)\z/ # rubocop:disable Layout/LineLength + CRASS_SEMICOLON = { node: :semicolon, raw: ";" } + CSS_IMPORTANT = "!important" + CSS_WHITESPACE = " " + CSS_PROPERTY_STRING_WITHOUT_EMBEDDED_QUOTES = /\A(["'])?[^"']+\1\z/ + DATA_ATTRIBUTE_NAME = /\Adata-[\w-]+\z/ + + class << self + def allowed_element?(element_name) + ::Loofah::HTML5::SafeList::ALLOWED_ELEMENTS_WITH_LIBXML2.include?(element_name) + end + + # alternative implementation of the html5lib attribute scrubbing algorithm + def scrub_attributes(node) + node.attribute_nodes.each do |attr_node| + attr_name = if attr_node.namespace + "#{attr_node.namespace.prefix}:#{attr_node.node_name}" + else + attr_node.node_name + end + + if DATA_ATTRIBUTE_NAME.match?(attr_name) + next + end + + unless SafeList::ALLOWED_ATTRIBUTES.include?(attr_name) + attr_node.remove + next + end + + if SafeList::ATTR_VAL_IS_URI.include?(attr_name) + next if scrub_uri_attribute(attr_node) + end + + if SafeList::SVG_ATTR_VAL_ALLOWS_REF.include?(attr_name) + scrub_attribute_that_allows_local_ref(attr_node) + end + + next unless SafeList::SVG_ALLOW_LOCAL_HREF.include?(node.name) && + attr_name == "xlink:href" && + attr_node.value =~ /^\s*[^#\s].*/m + + attr_node.remove + next + end + + scrub_css_attribute(node) + + node.attribute_nodes.each do |attr_node| + if attr_node.value !~ /[^[:space:]]/ && attr_node.name !~ DATA_ATTRIBUTE_NAME + node.remove_attribute(attr_node.name) + end + end + + force_correct_attribute_escaping!(node) + end + + def scrub_css_attribute(node) + style = node.attributes["style"] + style.value = scrub_css(style.value) if style + end + + def scrub_css(style) + url_flags = [:url, :bad_url] + style_tree = Crass.parse_properties(style) + sanitized_tree = [] + + style_tree.each do |node| + next unless node[:node] == :property + next if node[:children].any? do |child| + url_flags.include?(child[:node]) + end + + name = node[:name].downcase + next unless SafeList::ALLOWED_CSS_PROPERTIES.include?(name) || + SafeList::ALLOWED_SVG_PROPERTIES.include?(name) || + SafeList::SHORTHAND_CSS_PROPERTIES.include?(name.split("-").first) + + value = node[:children].map do |child| + case child[:node] + when :whitespace + CSS_WHITESPACE + when :string + if CSS_PROPERTY_STRING_WITHOUT_EMBEDDED_QUOTES.match?(child[:raw]) + Crass::Parser.stringify(child) + end + when :function + if SafeList::ALLOWED_CSS_FUNCTIONS.include?(child[:name].downcase) + Crass::Parser.stringify(child) + end + when :ident + keyword = child[:value] + if !SafeList::SHORTHAND_CSS_PROPERTIES.include?(name.split("-").first) || + SafeList::ALLOWED_CSS_KEYWORDS.include?(keyword) || + (keyword =~ CSS_KEYWORDISH) + keyword + end + else + child[:raw] + end + end.compact.join.strip + + next if value.empty? + + value << CSS_WHITESPACE << CSS_IMPORTANT if node[:important] + propstring = format("%s:%s", name, value) + sanitized_node = Crass.parse_properties(propstring).first + sanitized_tree << sanitized_node << CRASS_SEMICOLON + end + + Crass::Parser.stringify(sanitized_tree) + end + + def scrub_attribute_that_allows_local_ref(attr_node) + return unless attr_node.value + + nodes = Crass::Parser.new(attr_node.value).parse_component_values + + values = nodes.map do |node| + case node[:node] + when :url + if node[:value].start_with?("#") + node[:raw] + end + when :hash, :ident, :string + node[:raw] + end + end.compact + + attr_node.value = values.join(" ") + end + + def scrub_uri_attribute(attr_node) + # this block lifted nearly verbatim from HTML5 sanitization + val_unescaped = CGI.unescapeHTML(attr_node.value).gsub(CONTROL_CHARACTERS, "").downcase + if val_unescaped =~ /^[a-z0-9][-+.a-z0-9]*:/ && + !SafeList::ALLOWED_PROTOCOLS.include?(val_unescaped.split(SafeList::PROTOCOL_SEPARATOR)[0]) + attr_node.remove + return true + elsif val_unescaped.split(SafeList::PROTOCOL_SEPARATOR)[0] == "data" + # permit only allowed data mediatypes + mediatype = val_unescaped.split(SafeList::PROTOCOL_SEPARATOR)[1] + mediatype, _ = mediatype.split(";")[0..1] if mediatype + if mediatype && !SafeList::ALLOWED_URI_DATA_MEDIATYPES.include?(mediatype) + attr_node.remove + return true + end + end + false + end + + # + # libxml2 >= 2.9.2 fails to escape comments within some attributes. + # + # see comments about CVE-2018-8048 within the tests for more information + # + def force_correct_attribute_escaping!(node) + return unless Nokogiri::VersionInfo.instance.libxml2? + + node.attribute_nodes.each do |attr_node| + next unless LibxmlWorkarounds::BROKEN_ESCAPING_ATTRIBUTES.include?(attr_node.name) + + tag_name = LibxmlWorkarounds::BROKEN_ESCAPING_ATTRIBUTES_QUALIFYING_TAG[attr_node.name] + next unless tag_name.nil? || tag_name == node.name + + # + # this block is just like CGI.escape in Ruby 2.4, but + # only encodes space and double-quote, to mimic + # pre-2.9.2 behavior + # + encoding = attr_node.value.encoding + attr_node.value = attr_node.value.gsub(/[ "]/) do |m| + "%" + m.unpack("H2" * m.bytesize).join("%").upcase + end.force_encoding(encoding) + end + end + + def cdata_needs_escaping?(node) + # Nokogiri's HTML4 parser on JRuby doesn't flag the child of a `style` tag as cdata, but it acts that way + node.cdata? || (Nokogiri.jruby? && node.text? && node.parent.name == "style") + end + + def cdata_escape(node) + escaped_text = escape_tags(node.text) + if Nokogiri.jruby? + node.document.create_text_node(escaped_text) + else + node.document.create_cdata(escaped_text) + end + end + + TABLE_FOR_ESCAPE_HTML__ = { + "<" => "<", + ">" => ">", + "&" => "&", + } + + def escape_tags(string) + # modified version of CGI.escapeHTML from ruby 3.1 + enc = string.encoding + if enc.ascii_compatible? + string = string.b + string.gsub!(/[<>&]/, TABLE_FOR_ESCAPE_HTML__) + string.force_encoding(enc) + else + if enc.dummy? + origenc = enc + enc = Encoding::Converter.asciicompat_encoding(enc) + string = enc ? string.encode(enc) : string.b + end + table = Hash[TABLE_FOR_ESCAPE_HTML__.map { |pair| pair.map { |s| s.encode(enc) } }] + string = string.gsub(/#{"[<>&]".encode(enc)}/, table) + string.encode!(origenc) if origenc + string + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/metahelpers.rb b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/metahelpers.rb new file mode 100644 index 00000000..7507f71d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/metahelpers.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Loofah + module MetaHelpers # :nodoc: + class << self + def add_downcased_set_members_to_all_set_constants(mojule) + mojule.constants.each do |constant_sym| + constant = mojule.const_get(constant_sym) + next unless Set === constant + + constant.dup.each do |member| + constant.add(member.downcase) + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/scrubber.rb b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/scrubber.rb new file mode 100644 index 00000000..c06595a9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/scrubber.rb @@ -0,0 +1,140 @@ +# frozen_string_literal: true + +module Loofah + # + # A RuntimeError raised when Loofah could not find an appropriate scrubber. + # + class ScrubberNotFound < RuntimeError; end + + # + # A Scrubber wraps up a block (or method) that is run on an HTML node (element): + # + # # change all tags to
    tags + # span2div = Loofah::Scrubber.new do |node| + # node.name = "div" if node.name == "span" + # end + # + # Alternatively, this scrubber could have been implemented as: + # + # class Span2Div < Loofah::Scrubber + # def scrub(node) + # node.name = "div" if node.name == "span" + # end + # end + # span2div = Span2Div.new + # + # This can then be run on a document: + # + # Loofah.html5_fragment("foo

    bar

    ").scrub!(span2div).to_s + # # => "
    foo

    bar

    " + # + # Scrubbers can be run on a document in either a top-down traversal (the + # default) or bottom-up. Top-down scrubbers can optionally return + # Scrubber::STOP to terminate the traversal of a subtree. + # + class Scrubber + # Top-down Scrubbers may return CONTINUE to indicate that the subtree should be traversed. + CONTINUE = Object.new.freeze + + # Top-down Scrubbers may return STOP to indicate that the subtree should not be traversed. + STOP = Object.new.freeze + + # When a scrubber is initialized, the :direction may be specified + # as :top_down (the default) or :bottom_up. + attr_reader :direction + + # When a scrubber is initialized, the optional block is saved as + # :block. Note that, if no block is passed, then the +scrub+ + # method is assumed to have been implemented. + attr_reader :block + + # + # Options may include + # :direction => :top_down (the default) + # or + # :direction => :bottom_up + # + # For top_down traversals, if the block returns + # Loofah::Scrubber::STOP, then the traversal will be terminated + # for the current node's subtree. + # + # Alternatively, a Scrubber may inherit from Loofah::Scrubber, + # and implement +scrub+, which is slightly faster than using a + # block. + # + def initialize(options = {}, &block) + direction = options[:direction] || :top_down + unless [:top_down, :bottom_up].include?(direction) + raise ArgumentError, "direction #{direction} must be one of :top_down or :bottom_up" + end + + @direction = direction + @block = block + end + + # + # Calling +traverse+ will cause the document to be traversed by + # either the lambda passed to the initializer or the +scrub+ + # method, in the direction specified at +new+ time. + # + def traverse(node) + direction == :bottom_up ? traverse_conditionally_bottom_up(node) : traverse_conditionally_top_down(node) + end + + # + # When +new+ is not passed a block, the class may implement + # +scrub+, which will be called for each document node. + # + def scrub(node) + raise ScrubberNotFound, "No scrub method has been defined on #{self.class}" + end + + # + # If the attribute is not set, add it + # If the attribute is set, don't overwrite the existing value + # + def append_attribute(node, attribute, value) + current_value = node.get_attribute(attribute) || "" + current_values = current_value.split(/\s+/) + updated_value = current_values | [value] + node.set_attribute(attribute, updated_value.join(" ")) + end + + private + + def html5lib_sanitize(node) + case node.type + when Nokogiri::XML::Node::ELEMENT_NODE + if HTML5::Scrub.allowed_element?(node.name) + HTML5::Scrub.scrub_attributes(node) + return Scrubber::CONTINUE + end + when Nokogiri::XML::Node::TEXT_NODE, Nokogiri::XML::Node::CDATA_SECTION_NODE + if HTML5::Scrub.cdata_needs_escaping?(node) + node.before(HTML5::Scrub.cdata_escape(node)) + return Scrubber::STOP + end + return Scrubber::CONTINUE + end + Scrubber::STOP + end + + def traverse_conditionally_top_down(node) + if block + return if block.call(node) == STOP + elsif scrub(node) == STOP + return + end + node.children.each { |j| traverse_conditionally_top_down(j) } + end + + def traverse_conditionally_bottom_up(node) + node.children.each { |j| traverse_conditionally_bottom_up(j) } + if block + block.call(node) + else + scrub(node) + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/scrubbers.rb b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/scrubbers.rb new file mode 100644 index 00000000..e994240c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/scrubbers.rb @@ -0,0 +1,430 @@ +# frozen_string_literal: true + +module Loofah + # + # Loofah provides some built-in scrubbers for sanitizing with + # HTML5lib's safelist and for accomplishing some common + # transformation tasks. + # + # + # === Loofah::Scrubbers::Strip / scrub!(:strip) + # + # +:strip+ removes unknown/unsafe tags, but leaves behind the pristine contents: + # + # unsafe_html = "ohai!
    div is safe
    but foo is not" + # Loofah.html5_fragment(unsafe_html).scrub!(:strip) + # => "ohai!
    div is safe
    but foo is not" + # + # + # === Loofah::Scrubbers::Prune / scrub!(:prune) + # + # +:prune+ removes unknown/unsafe tags and their contents (including their subtrees): + # + # unsafe_html = "ohai!
    div is safe
    but foo is not" + # Loofah.html5_fragment(unsafe_html).scrub!(:prune) + # => "ohai!
    div is safe
    " + # + # + # === Loofah::Scrubbers::Escape / scrub!(:escape) + # + # +:escape+ performs HTML entity escaping on the unknown/unsafe tags: + # + # unsafe_html = "ohai!
    div is safe
    but foo is not" + # Loofah.html5_fragment(unsafe_html).scrub!(:escape) + # => "ohai!
    div is safe
    <foo>but foo is <b>not</b></foo>" + # + # + # === Loofah::Scrubbers::Whitewash / scrub!(:whitewash) + # + # +:whitewash+ removes all comments, styling and attributes in + # addition to doing markup-fixer-uppery and pruning unsafe tags. I + # like to call this "whitewashing", since it's like putting a new + # layer of paint on top of the HTML input to make it look nice. + # + # messy_markup = "ohai!
    div with attributes
    " + # Loofah.html5_fragment(messy_markup).scrub!(:whitewash) + # => "ohai!
    div with attributes
    " + # + # One use case for this scrubber is to clean up HTML that was + # cut-and-pasted from Microsoft Word into a WYSIWYG editor or a + # rich text editor. Microsoft's software is famous for injecting + # all kinds of cruft into its HTML output. Who needs that crap? + # Certainly not me. + # + # + # === Loofah::Scrubbers::NoFollow / scrub!(:nofollow) + # + # +:nofollow+ adds a rel="nofollow" attribute to all links + # + # link_farmers_markup = "ohai! I like your blog post" + # Loofah.html5_fragment(link_farmers_markup).scrub!(:nofollow) + # => "ohai! I like your blog post" + # + # + # === Loofah::Scrubbers::TargetBlank / scrub!(:targetblank) + # + # +:targetblank+ adds a target="_blank" attribute to all links + # + # link_farmers_markup = "ohai! I like your blog post" + # Loofah.html5_fragment(link_farmers_markup).scrub!(:targetblank) + # => "ohai! I like your blog post" + # + # + # === Loofah::Scrubbers::NoOpener / scrub!(:noopener) + # + # +:noopener+ adds a rel="noopener" attribute to all links + # + # link_farmers_markup = "ohai! I like your blog post" + # Loofah.html5_fragment(link_farmers_markup).scrub!(:noopener) + # => "ohai! I like your blog post" + # + # === Loofah::Scrubbers::NoReferrer / scrub!(:noreferrer) + # + # +:noreferrer+ adds a rel="noreferrer" attribute to all links + # + # link_farmers_markup = "ohai! I like your blog post" + # Loofah.html5_fragment(link_farmers_markup).scrub!(:noreferrer) + # => "ohai! I like your blog post" + # + # + # === Loofah::Scrubbers::Unprintable / scrub!(:unprintable) + # + # +:unprintable+ removes unprintable Unicode characters. + # + # markup = "

    Some text with an unprintable character at the end\u2028

    " + # Loofah.html5_fragment(markup).scrub!(:unprintable) + # => "

    Some text with an unprintable character at the end

    " + # + # You may not be able to see the unprintable character in the above example, but there is a + # U+2028 character right before the closing

    tag. These characters can cause issues if + # the content is ever parsed by JavaScript - more information here: + # + # http://timelessrepo.com/json-isnt-a-javascript-subset + # + module Scrubbers + # + # === scrub!(:strip) + # + # +:strip+ removes unknown/unsafe tags, but leaves behind the pristine contents: + # + # unsafe_html = "ohai!
    div is safe
    but foo is not" + # Loofah.html5_fragment(unsafe_html).scrub!(:strip) + # => "ohai!
    div is safe
    but foo is not" + # + class Strip < Scrubber + def initialize # rubocop:disable Lint/MissingSuper + @direction = :bottom_up + end + + def scrub(node) + return CONTINUE if html5lib_sanitize(node) == CONTINUE + + node.before(node.children) + node.remove + STOP + end + end + + # + # === scrub!(:prune) + # + # +:prune+ removes unknown/unsafe tags and their contents (including their subtrees): + # + # unsafe_html = "ohai!
    div is safe
    but foo is not" + # Loofah.html5_fragment(unsafe_html).scrub!(:prune) + # => "ohai!
    div is safe
    " + # + class Prune < Scrubber + def initialize # rubocop:disable Lint/MissingSuper + @direction = :top_down + end + + def scrub(node) + return CONTINUE if html5lib_sanitize(node) == CONTINUE + + node.remove + STOP + end + end + + # + # === scrub!(:escape) + # + # +:escape+ performs HTML entity escaping on the unknown/unsafe tags: + # + # unsafe_html = "ohai!
    div is safe
    but foo is not" + # Loofah.html5_fragment(unsafe_html).scrub!(:escape) + # => "ohai!
    div is safe
    <foo>but foo is <b>not</b></foo>" + # + class Escape < Scrubber + def initialize # rubocop:disable Lint/MissingSuper + @direction = :top_down + end + + def scrub(node) + return CONTINUE if html5lib_sanitize(node) == CONTINUE + + node.add_next_sibling(Nokogiri::XML::Text.new(node.to_s, node.document)) + node.remove + STOP + end + end + + # + # === scrub!(:whitewash) + # + # +:whitewash+ removes all comments, styling and attributes in + # addition to doing markup-fixer-uppery and pruning unsafe tags. I + # like to call this "whitewashing", since it's like putting a new + # layer of paint on top of the HTML input to make it look nice. + # + # messy_markup = "ohai!
    div with attributes
    " + # Loofah.html5_fragment(messy_markup).scrub!(:whitewash) + # => "ohai!
    div with attributes
    " + # + # One use case for this scrubber is to clean up HTML that was + # cut-and-pasted from Microsoft Word into a WYSIWYG editor or a + # rich text editor. Microsoft's software is famous for injecting + # all kinds of cruft into its HTML output. Who needs that crap? + # Certainly not me. + # + class Whitewash < Scrubber + def initialize # rubocop:disable Lint/MissingSuper + @direction = :top_down + end + + def scrub(node) + case node.type + when Nokogiri::XML::Node::ELEMENT_NODE + if HTML5::Scrub.allowed_element?(node.name) + node.attributes.each { |attr| node.remove_attribute(attr.first) } + return CONTINUE if node.namespaces.empty? + end + when Nokogiri::XML::Node::TEXT_NODE, Nokogiri::XML::Node::CDATA_SECTION_NODE + return CONTINUE + end + node.remove + STOP + end + end + + # + # === scrub!(:nofollow) + # + # +:nofollow+ adds a rel="nofollow" attribute to all links + # + # link_farmers_markup = "ohai! I like your blog post" + # Loofah.html5_fragment(link_farmers_markup).scrub!(:nofollow) + # => "ohai! I like your blog post" + # + class NoFollow < Scrubber + def initialize # rubocop:disable Lint/MissingSuper + @direction = :top_down + end + + def scrub(node) + return CONTINUE unless (node.type == Nokogiri::XML::Node::ELEMENT_NODE) && (node.name == "a") + + append_attribute(node, "rel", "nofollow") + STOP + end + end + + # + # === scrub!(:targetblank) + # + # +:targetblank+ adds a target="_blank" attribute to all links. + # If there is a target already set, replaces it with target="_blank". + # + # link_farmers_markup = "ohai! I like your blog post" + # Loofah.html5_fragment(link_farmers_markup).scrub!(:targetblank) + # => "ohai! I like your blog post" + # + # On modern browsers, setting target="_blank" on anchor elements implicitly provides the same + # behavior as setting rel="noopener". + # + class TargetBlank < Scrubber + def initialize # rubocop:disable Lint/MissingSuper + @direction = :top_down + end + + def scrub(node) + return CONTINUE unless (node.type == Nokogiri::XML::Node::ELEMENT_NODE) && (node.name == "a") + + href = node["href"] + + node.set_attribute("target", "_blank") if href && href[0] != "#" + + STOP + end + end + + # + # === scrub!(:noopener) + # + # +:noopener+ adds a rel="noopener" attribute to all links + # + # link_farmers_markup = "ohai! I like your blog post" + # Loofah.html5_fragment(link_farmers_markup).scrub!(:noopener) + # => "ohai! I like your blog post" + # + class NoOpener < Scrubber + def initialize # rubocop:disable Lint/MissingSuper + @direction = :top_down + end + + def scrub(node) + return CONTINUE unless (node.type == Nokogiri::XML::Node::ELEMENT_NODE) && (node.name == "a") + + append_attribute(node, "rel", "noopener") + STOP + end + end + + # + # === scrub!(:noreferrer) + # + # +:noreferrer+ adds a rel="noreferrer" attribute to all links + # + # link_farmers_markup = "ohai! I like your blog post" + # Loofah.html5_fragment(link_farmers_markup).scrub!(:noreferrer) + # => "ohai! I like your blog post" + # + class NoReferrer < Scrubber + def initialize # rubocop:disable Lint/MissingSuper + @direction = :top_down + end + + def scrub(node) + return CONTINUE unless (node.type == Nokogiri::XML::Node::ELEMENT_NODE) && (node.name == "a") + + append_attribute(node, "rel", "noreferrer") + STOP + end + end + + # This class probably isn't useful publicly, but is used for #to_text's current implemention + class NewlineBlockElements < Scrubber # :nodoc: + def initialize # rubocop:disable Lint/MissingSuper + @direction = :bottom_up + end + + def scrub(node) + return CONTINUE unless Loofah::Elements::LINEBREAKERS.include?(node.name) + + replacement = if Loofah::Elements::INLINE_LINE_BREAK.include?(node.name) + "\n" + else + "\n#{node.content}\n" + end + node.add_next_sibling(Nokogiri::XML::Text.new(replacement, node.document)) + node.remove + end + end + + # + # === scrub!(:unprintable) + # + # +:unprintable+ removes unprintable Unicode characters. + # + # markup = "

    Some text with an unprintable character at the end\u2028

    " + # Loofah.html5_fragment(markup).scrub!(:unprintable) + # => "

    Some text with an unprintable character at the end

    " + # + # You may not be able to see the unprintable character in the above example, but there is a + # U+2028 character right before the closing

    tag. These characters can cause issues if + # the content is ever parsed by JavaScript - more information here: + # + # http://timelessrepo.com/json-isnt-a-javascript-subset + # + class Unprintable < Scrubber + def initialize # rubocop:disable Lint/MissingSuper + @direction = :top_down + end + + def scrub(node) + if node.type == Nokogiri::XML::Node::TEXT_NODE || node.type == Nokogiri::XML::Node::CDATA_SECTION_NODE + node.content = node.content.gsub(/\u2028|\u2029/, "") + end + CONTINUE + end + end + + # + # === scrub!(:double_breakpoint) + # + # +:double_breakpoint+ replaces double-break tags with closing/opening paragraph tags. + # + # markup = "

    Some text here in a logical paragraph.

    Some more text, apparently a second paragraph.

    " + # Loofah.html5_fragment(markup).scrub!(:double_breakpoint) + # => "

    Some text here in a logical paragraph.

    Some more text, apparently a second paragraph.

    " + # + class DoubleBreakpoint < Scrubber + def initialize # rubocop:disable Lint/MissingSuper + @direction = :top_down + end + + def scrub(node) + return CONTINUE unless (node.type == Nokogiri::XML::Node::ELEMENT_NODE) && (node.name == "p") + + paragraph_with_break_point_nodes = node.xpath("//p[br[following-sibling::br]]") + + paragraph_with_break_point_nodes.each do |paragraph_node| + new_paragraph = paragraph_node.add_previous_sibling("

    ").first + + paragraph_node.children.each do |child| + remove_blank_text_nodes(child) + end + + paragraph_node.children.each do |child| + # already unlinked + next if child.parent.nil? + + if child.name == "br" && child.next_sibling.name == "br" + new_paragraph = paragraph_node.add_previous_sibling("

    ").first + child.next_sibling.unlink + child.unlink + else + child.parent = new_paragraph + end + end + + paragraph_node.unlink + end + + CONTINUE + end + + private + + def remove_blank_text_nodes(node) + node.unlink if node.text? && node.blank? + end + end + # + # A hash that maps a symbol (like +:prune+) to the appropriate Scrubber (Loofah::Scrubbers::Prune). + # + MAP = { + escape: Escape, + prune: Prune, + whitewash: Whitewash, + strip: Strip, + nofollow: NoFollow, + noopener: NoOpener, + noreferrer: NoReferrer, + targetblank: TargetBlank, + newline_block_elements: NewlineBlockElements, + unprintable: Unprintable, + double_breakpoint: DoubleBreakpoint, + } + + class << self + # + # Returns an array of symbols representing the built-in scrubbers + # + def scrubber_symbols + MAP.keys + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/version.rb b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/version.rb new file mode 100644 index 00000000..4f0bc510 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/version.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +module Loofah + # The version of Loofah you are using + VERSION = "2.24.1" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/xml/document.rb b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/xml/document.rb new file mode 100644 index 00000000..cbdc8043 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/xml/document.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Loofah + module XML # :nodoc: + # + # Subclass of Nokogiri::XML::Document. + # + # See Loofah::ScrubBehavior and Loofah::DocumentDecorator for additional methods. + # + class Document < Nokogiri::XML::Document + include Loofah::ScrubBehavior::Node + include Loofah::DocumentDecorator + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/xml/document_fragment.rb b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/xml/document_fragment.rb new file mode 100644 index 00000000..aecf65af --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/loofah-2.24.1/lib/loofah/xml/document_fragment.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Loofah + module XML # :nodoc: + # + # Subclass of Nokogiri::XML::DocumentFragment. + # + # See Loofah::ScrubBehavior for additional methods. + # + class DocumentFragment < Nokogiri::XML::DocumentFragment + class << self + def parse(tags) + doc = Loofah::XML::Document.new + doc.encoding = tags.encoding.name if tags.respond_to?(:encoding) + new(doc, tags) + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/.github/workflows/ci.yml b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/.github/workflows/ci.yml new file mode 100644 index 00000000..17c98d97 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/.github/workflows/ci.yml @@ -0,0 +1,51 @@ +name: Mini Mime Tests + +on: + pull_request: + push: + branches: + - main + +jobs: + build: + runs-on: ${{ matrix.os }}-latest + name: "Ruby ${{ matrix.ruby }} / ${{ matrix.os }} / Failure allowed: ${{ matrix.experimental }}" + continue-on-error: ${{ matrix.experimental }} + timeout-minutes: 5 + + strategy: + fail-fast: false + matrix: + os: ["ubuntu"] + ruby: ["2.6", "2.7", "3.0", "3.1", "3.2"] + experimental: [false] + include: + - ruby: "3.2" + os: "windows" + experimental: false + - ruby: "ruby-head" + os: "ubuntu" + experimental: true + - ruby: "truffleruby-head" + os: "ubuntu" + experimental: true + - ruby: "jruby-head" + os: "ubuntu" + experimental: true + - ruby: "jruby-9.3.9.0" + os: "ubuntu" + experimental: true + - ruby: "jruby-9.4.0.0" + os: "ubuntu" + experimental: true + steps: + - uses: actions/checkout@v3 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + - name: Rubocop + run: bundle exec rubocop + if: "!contains(matrix.ruby, 'jruby')" + - name: Tests + run: bundle exec rake test diff --git a/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/.github/workflows/db.yml b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/.github/workflows/db.yml new file mode 100644 index 00000000..b95e8f8c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/.github/workflows/db.yml @@ -0,0 +1,26 @@ +name: Update MIME type DB + +on: + schedule: + # 10am on the 1st every month https://crontab.guru/#0_10_1_*_* + - cron: "0 10 1 * *" + workflow_dispatch: + +jobs: + update_db: + runs-on: ubuntu-latest + name: "Update MIME type DB" + steps: + - uses: actions/checkout@v3 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "2.7" + bundler-cache: true + - name: Update mime-types-data + run: bundle update mime-types-data + - name: Update DB + run: bundle exec rake rebuild_db + - name: Create PR + run: bin/db_pull_request + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/.gitignore b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/.gitignore new file mode 100644 index 00000000..0cb6eeb0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/.gitignore @@ -0,0 +1,9 @@ +/.bundle/ +/.yardoc +/Gemfile.lock +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ diff --git a/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/.rubocop.yml b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/.rubocop.yml new file mode 100644 index 00000000..05f85624 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/.rubocop.yml @@ -0,0 +1,5 @@ +inherit_gem: + rubocop-discourse: default.yml +inherit_mode: + merge: + - Exclude diff --git a/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/CHANGELOG b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/CHANGELOG new file mode 100644 index 00000000..99b1d312 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/CHANGELOG @@ -0,0 +1,65 @@ +08-08-2023 + - Version 1.1.5 + - Update mime types from upstream + +08-08-2023 + - Version 1.1.4 + - Version 1.1.3 had issues on Windows which does not support pread, added a polyfill + +04-08-2023 + - Version 1.1.3 + - Added fork safety by migrating from seek+read to pread + +11-10-2021 + - Version 1.1.2 + - update mime types from upstream + +23-08-2021 + - Version 1.1.1 + - update mime types from upstream + +05-04-2021 + - Version 1.1.0 + - MiniMime.lookup_by_extension is now case insensitive + +26-03-2021 + - Version 1.0.3 + - Update mime types from upstream + +08-07-2019 + - Version 1.0.2 + - Update mime types from upstream + +14-08-2018 + - Version 1.0.1 + - Update mime types from upstream + - Add lookup_by_extension to the public API + +08-11-2017 + - Version 1.0.0 + - Other than the version number, no difference from 0.1.4 + +11-08-2017 + - Version 0.1.4 + - Return preferred extension when looking up by content type + + +28-03-2016 + + - Version 0.1.3 + - Prefer non-obsolete mime types to obsolete ones + +14-12-2016 + + - Version 0.1.2 + - Backwards compat with ancient Ruby to match mail gem + +14-12-2016 + + - Version 0.1.1 + - Adjusted API to be more consistent + +14-12-2016 + + - Version 0.1.0 + - Initial version diff --git a/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/CODE_OF_CONDUCT.md b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..951cdde6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at sam.saffron@gmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/Gemfile b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/Gemfile new file mode 100644 index 00000000..1e367900 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/Gemfile @@ -0,0 +1,9 @@ +# frozen_string_literal: true +source 'https://rubygems.org' + +# Specify your gem's dependencies in mini_mime.gemspec +gemspec + +gem "mime-types", "~> 3" if RUBY_VERSION > '2' +gem "memory_profiler" +gem "benchmark-ips" diff --git a/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/LICENSE.txt b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/LICENSE.txt new file mode 100644 index 00000000..e2ea531e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Discourse Construction Kit, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/README.md b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/README.md new file mode 100644 index 00000000..e44033ff --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/README.md @@ -0,0 +1,114 @@ +# MiniMime + +Minimal mime type implementation for use with the mail and rest-client gem. + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'mini_mime' +``` + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install mini_mime + +## Usage + +``` +require 'mini_mime' + +MiniMime.lookup_by_filename("a.txt").content_type +# => "text/plain" + +MiniMime.lookup_by_extension("txt").content_type +# => "text/plain" + +MiniMime.lookup_by_content_type("text/plain").extension +# => "txt" + +MiniMime.lookup_by_content_type("text/plain").binary? +# => false + +``` + +## Configuration + +If you'd like to add your own mime types, try using custom database files: + +``` +MiniMime::Configuration.ext_db_path = "path_to_file_extension_db" +MiniMime::Configuration.content_type_db_path = "path_to_content_type_db" +``` + +Check out the [default databases](lib/db) for proper formatting and structure hints. + +## Performance + +MiniMime is optimised to minimize memory usage. It keeps a cache of 100 mime type lookups (and 100 misses). There are benchmarks in the [bench directory](https://github.com/discourse/mini_mime/blob/master/bench/bench.rb) + +``` +Memory stats for requiring mime/types/columnar +Total allocated: 8712144 bytes (98242 objects) +Total retained: 3372545 bytes (33599 objects) + +Memory stats for requiring mini_mime +Total allocated: 42625 bytes (369 objects) +Total retained: 8992 bytes (72 objects) +Warming up -------------------------------------- +cached content_type lookup MiniMime + 85.109k i/100ms +content_type lookup MIME::Types + 17.879k i/100ms +Calculating ------------------------------------- +cached content_type lookup MiniMime + 1.105M (± 4.1%) i/s - 5.532M in 5.014895s +content_type lookup MIME::Types + 193.528k (± 7.1%) i/s - 965.466k in 5.013925s +Warming up -------------------------------------- +uncached content_type lookup MiniMime + 1.410k i/100ms +content_type lookup MIME::Types + 18.012k i/100ms +Calculating ------------------------------------- +uncached content_type lookup MiniMime + 14.689k (± 4.2%) i/s - 73.320k in 5.000779s +content_type lookup MIME::Types + 193.459k (± 6.9%) i/s - 972.648k in 5.050731s +``` + +As a general guideline, cached lookups are 6x faster than MIME::Types equivalent. Uncached lookups are 10x slower. + +Note: It was run on macOS 10.14.2, and versions of Ruby and gems are below. + +- Ruby 2.6.0 +- mini_mime (1.0.1) +- mime-types (3.2.2) +- mime-types-data (3.2018.0812) + +## Development + +MiniMime uses the officially maintained list of mime types at [mime-types-data](https://github.com/mime-types/mime-types-data) repo to build the internal database. + +To update the database run: + +```ruby +bundle exec rake rebuild_db +``` + +After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. + +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/discourse/mini_mime. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. + +## License + +The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). diff --git a/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/Rakefile b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/Rakefile new file mode 100644 index 00000000..31a35147 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/Rakefile @@ -0,0 +1,97 @@ +# frozen_string_literal: true +require "bundler/gem_tasks" +require "rake/testtask" + +Rake::TestTask.new(:test) do |t| + t.libs << "test" + t.libs << "lib" + t.test_files = FileList['test/**/*_test.rb'] +end + +task default: :test + +def pad(array) + max = [] + array.each do |row| + i = 0 + row.each do |col| + max[i] = [max[i] || 0, col.length].max + i += 1 + end + end + + array.each do |row| + i = 0 + row.each do |col| + col << " " * (max[i] - col.length) + i += 1 + end + end + +end + +desc "generate mime type database" +task :rebuild_db do + puts "Generating mime type DB" + require 'mime/types' + index = {} + + MIME::Types.each do |type| + type.extensions.each { |ext| (index[ext.downcase] ||= []) << type } + end + + index.each do |k, list| + list.sort! { |a, b| a.priority_compare(b) } + end + + buffer = [] + + index.each do |ext, list| + mime_type = list.detect { |t| !t.obsolete? } + mime_type ||= list.detect(&:registered) + mime_type ||= list.first + buffer << [ext.dup, mime_type.content_type.dup, mime_type.encoding.dup] + end + + pad(buffer) + + buffer.sort! { |a, b| a[0] <=> b[0] } + + File.open("lib/db/ext_mime.db", File::CREAT | File::TRUNC | File::RDWR) do |f| + buffer.each do |row| + f.write "#{row[0]} #{row[1]} #{row[2]}\n" + end + end + + puts "#{buffer.count} rows written to lib/db/ext_mime.db" + + buffer.sort! { |a, b| [a[1], a[0]] <=> [b[1], b[0]] } + + # strip cause we are going to re-pad + buffer.each do |row| + row.each do |col| + col.strip! + end + end + + # we got to confirm we pick the right extension for each type + buffer.each do |row| + row[0] = MIME::Types.type_for("xyz.#{row[0].strip}")[0].extensions[0].dup + end + + pad(buffer) + + File.open("lib/db/content_type_mime.db", File::CREAT | File::TRUNC | File::RDWR) do |f| + last = nil + count = 0 + buffer.each do |row| + unless last == row[1] + f.write "#{row[0]} #{row[1]} #{row[2]}\n" + count += 1 + end + last = row[1] + end + puts "#{count} rows written to lib/db/content_type_mime.db" + end + +end diff --git a/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/bench/bench.rb b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/bench/bench.rb new file mode 100644 index 00000000..a246a49a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/bench/bench.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true +require 'memory_profiler' +require 'benchmark/ips' + +$: << File.expand_path('../../lib', __FILE__) + +puts +puts "Memory stats for requiring mime/types/columnar" +result = MemoryProfiler.report do + require 'mime/types/columnar' +end + +puts "Total allocated: #{result.total_allocated_memsize} bytes (#{result.total_allocated} objects)" +puts "Total retained: #{result.total_retained_memsize} bytes (#{result.total_retained} objects)" + +puts +puts "Memory stats for requiring mini_mime" +result = MemoryProfiler.report do + require 'mini_mime' +end + +puts "Total allocated: #{result.total_allocated_memsize} bytes (#{result.total_allocated} objects)" +puts "Total retained: #{result.total_retained_memsize} bytes (#{result.total_retained} objects)" + +Benchmark.ips do |bm| + bm.report 'cached content_type lookup MiniMime' do + MiniMime.lookup_by_filename("a.txt").content_type + end + + bm.report 'content_type lookup MIME::Types' do + MIME::Types.type_for("a.txt")[0].content_type + end +end + +module MiniMime + class Db + class RandomAccessDb + alias_method :lookup, :lookup_uncached + end + end +end + +Benchmark.ips do |bm| + bm.report 'uncached content_type lookup MiniMime' do + MiniMime.lookup_by_filename("a.txt").content_type + end + + bm.report 'content_type lookup MIME::Types' do + MIME::Types.type_for("a.txt")[0].content_type + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/bin/console b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/bin/console new file mode 100755 index 00000000..184d7086 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/bin/console @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "bundler/setup" +require "mini_mime" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require "irb" +IRB.start diff --git a/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/bin/db_pull_request b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/bin/db_pull_request new file mode 100755 index 00000000..55bba04f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/bin/db_pull_request @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "time" + +if `git status --porcelain lib/db`.empty? + puts "Skipping, no DB changes to commit..." + return +end + +moment = Time.now.utc +branch_name = "db-updates-#{moment.strftime("%Y%m%d%H%M%S")}" + +system("git", "checkout", "-b", branch_name) || abort("Unable to create branch") +system("git", "add", "lib/db") +system("git", "config", "--local", "user.email", "actions@github.com") +system("git", "config", "--local", "user.name", "github-actions") +system("git", "commit", "-m", "DB updates #{moment.iso8601}") || abort("Unable to commit changes") +system("git", "push", "-u", "origin", branch_name) || abort("Unable to push branch") +system("gh", "pr", "create", "--title", "DB updates #{moment.iso8601}", "--body", "From Github Actions") || abort("Unable to create PR") diff --git a/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/bin/setup b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/bin/setup new file mode 100755 index 00000000..dce67d86 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/lib/db/content_type_mime.db b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/lib/db/content_type_mime.db new file mode 100644 index 00000000..e571adac --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/lib/db/content_type_mime.db @@ -0,0 +1,880 @@ +ez application/andrew-inset base64 +aw application/applixware base64 +atom application/atom+xml 8bit +atomcat application/atomcat+xml 8bit +atomsvc application/atomsvc+xml 8bit +ccxml application/ccxml+xml base64 +cdmia application/cdmi-capability base64 +cdmic application/cdmi-container base64 +cdmid application/cdmi-domain base64 +cdmio application/cdmi-object base64 +cdmiq application/cdmi-queue base64 +cu application/cu-seeme base64 +davmount application/davmount+xml base64 +dcm application/dicom base64 +dbk application/docbook+xml base64 +dssc application/dssc+der base64 +xdssc application/dssc+xml base64 +ecma application/ecmascript base64 +emma application/emma+xml base64 +epub application/epub+zip base64 +exi application/exi base64 +pfr application/font-tdpfr base64 +gml application/gml+xml base64 +gpx application/gpx+xml base64 +gxf application/gxf base64 +gz application/gzip base64 +stk application/hyperstudio base64 +ink application/inkml+xml base64 +ipfix application/ipfix base64 +jar application/java-archive base64 +ser application/java-serialized-object base64 +js application/javascript 8bit +json application/json 8bit +jsonml application/jsonml+json base64 +lostxml application/lost+xml base64 +hqx application/mac-binhex40 8bit +mads application/mads+xml base64 +webmanifest application/manifest+json base64 +mrc application/marc base64 +mrcx application/marcxml+xml base64 +ma application/mathematica base64 +mathml application/mathml+xml base64 +mbox application/mbox base64 +mscml application/mediaservercontrol+xml base64 +metalink application/metalink+xml base64 +meta4 application/metalink4+xml base64 +mets application/mets+xml base64 +mods application/mods+xml base64 +m21 application/mp21 base64 +mp4 application/mp4 base64 +doc application/msword base64 +mxf application/mxf base64 +nc application/netcdf base64 +bin application/octet-stream base64 +oda application/oda base64 +opf application/oebps-package+xml base64 +ogx application/ogg base64 +omdoc application/omdoc+xml base64 +onepkg application/onenote base64 +oxps application/oxps base64 +xer application/patch-ops-error+xml base64 +pdf application/pdf base64 +asc application/pgp-signature base64 +prf application/pics-rules base64 +p10 application/pkcs10 base64 +p7m application/pkcs7-mime base64 +p7s application/pkcs7-signature base64 +p8 application/pkcs8 base64 +ac application/pkix-attr-cert base64 +cer application/pkix-cert base64 +crl application/pkix-crl base64 +pkipath application/pkix-pkipath base64 +pki application/pkixcmp base64 +pls application/pls+xml base64 +eps application/postscript 8bit +cw application/prs.cww base64 +rnd application/prs.nprend base64 +pskcxml application/pskc+xml base64 +rdf application/rdf+xml 8bit +rif application/reginfo+xml base64 +rnc application/relax-ng-compact-syntax base64 +rl application/resource-lists+xml base64 +rld application/resource-lists-diff+xml base64 +rs application/rls-services+xml base64 +gbr application/rpki-ghostbusters base64 +mft application/rpki-manifest base64 +roa application/rpki-roa base64 +rsd application/rsd+xml base64 +rss application/rss+xml base64 +rtf application/rtf base64 +sbml application/sbml+xml base64 +scq application/scvp-cv-request base64 +scs application/scvp-cv-response base64 +spq application/scvp-vp-request base64 +spp application/scvp-vp-response base64 +sdp application/sdp base64 +setpay application/set-payment-initiation base64 +setreg application/set-registration-initiation base64 +sgml application/sgml base64 +soc application/sgml-open-catalog base64 +shf application/shf+xml base64 +siv application/sieve base64 +smi application/smil+xml 8bit +rq application/sparql-query base64 +srx application/sparql-results+xml base64 +gram application/srgs base64 +grxml application/srgs+xml base64 +sru application/sru+xml base64 +ssdl application/ssdl+xml base64 +ssml application/ssml+xml base64 +tei application/tei+xml base64 +tfi application/thraud+xml base64 +tsd application/timestamped-data base64 +pwn application/vnd.3M.Post-it-Notes base64 +plb application/vnd.3gpp.pic-bw-large base64 +psb application/vnd.3gpp.pic-bw-small base64 +pvb application/vnd.3gpp.pic-bw-var base64 +sms application/vnd.3gpp.sms base64 +tcap application/vnd.3gpp2.tcap base64 +gph application/vnd.FloGraphIt base64 +zmm application/vnd.HandHeld-Entertainment+xml base64 +kne application/vnd.Kinar base64 +mwf application/vnd.MFER base64 +daf application/vnd.Mobius.DAF base64 +dis application/vnd.Mobius.DIS base64 +mbk application/vnd.Mobius.MBK base64 +mqy application/vnd.Mobius.MQY base64 +msl application/vnd.Mobius.MSL base64 +plc application/vnd.Mobius.PLC base64 +txf application/vnd.Mobius.TXF base64 +qxd application/vnd.Quark.QuarkXPress 8bit +twd application/vnd.SimTech-MindMapper base64 +aso application/vnd.accpac.simply.aso base64 +imp application/vnd.accpac.simply.imp base64 +acu application/vnd.acucobol base64 +atc application/vnd.acucorp 7bit +air application/vnd.adobe.air-application-installer-package+zip base64 +fcdt application/vnd.adobe.formscentral.fcdt base64 +fxp application/vnd.adobe.fxp base64 +xdp application/vnd.adobe.xdp+xml base64 +xfdf application/vnd.adobe.xfdf base64 +ahead application/vnd.ahead.space base64 +azf application/vnd.airzip.filesecure.azf base64 +azs application/vnd.airzip.filesecure.azs base64 +azw application/vnd.amazon.ebook base64 +acc application/vnd.americandynamics.acc base64 +ami application/vnd.amiga.ami base64 +apk application/vnd.android.package-archive base64 +cii application/vnd.anser-web-certificate-issue-initiation base64 +fti application/vnd.anser-web-funds-transfer-initiation base64 +atx application/vnd.antix.game-component base64 +mpkg application/vnd.apple.installer+xml base64 +m3u8 application/vnd.apple.mpegurl base64 +pkpass application/vnd.apple.pkpass base64 +swi application/vnd.aristanetworks.swi base64 +iota application/vnd.astraea-software.iota base64 +aep application/vnd.audiograph base64 +mpm application/vnd.blueice.multipass base64 +bmi application/vnd.bmi base64 +rep application/vnd.businessobjects base64 +cdxml application/vnd.chemdraw+xml base64 +mmd application/vnd.chipnuts.karaoke-mmd base64 +cdy application/vnd.cinderella base64 +cla application/vnd.claymore base64 +rp9 application/vnd.cloanto.rp9 base64 +c4d application/vnd.clonk.c4group base64 +c11amc application/vnd.cluetrust.cartomobile-config base64 +c11amz application/vnd.cluetrust.cartomobile-config-pkg base64 +csp application/vnd.commonspace base64 +cdbcmsg application/vnd.contact.cmsg base64 +cmc application/vnd.cosmocaller base64 +clkx application/vnd.crick.clicker base64 +clkk application/vnd.crick.clicker.keyboard base64 +clkp application/vnd.crick.clicker.palette base64 +clkt application/vnd.crick.clicker.template base64 +clkw application/vnd.crick.clicker.wordbank base64 +wbs application/vnd.criticaltools.wbs+xml base64 +pml application/vnd.ctc-posml base64 +ppd application/vnd.cups-ppd base64 +curl application/vnd.curl base64 +car application/vnd.curl.car base64 +pcurl application/vnd.curl.pcurl base64 +dart application/vnd.dart base64 +rdz application/vnd.data-vision.rdz base64 +uvd application/vnd.dece.data base64 +uvt application/vnd.dece.ttml+xml base64 +uvvx application/vnd.dece.unspecified base64 +uvvz application/vnd.dece.zip base64 +fe_launch application/vnd.denovo.fcselayout-link base64 +dna application/vnd.dna base64 +mlp application/vnd.dolby.mlp base64 +dpg application/vnd.dpgraph base64 +dfac application/vnd.dreamfactory base64 +kpxx application/vnd.ds-keypoint base64 +ait application/vnd.dvb.ait base64 +svc application/vnd.dvb.service base64 +geo application/vnd.dynageo base64 +mag application/vnd.ecowin.chart base64 +nml application/vnd.enliven base64 +esf application/vnd.epson.esf base64 +msf application/vnd.epson.msf base64 +qam application/vnd.epson.quickanime base64 +slt application/vnd.epson.salt base64 +ssf application/vnd.epson.ssf base64 +es3 application/vnd.eszigno3+xml base64 +ez2 application/vnd.ezpix-album base64 +ez3 application/vnd.ezpix-package base64 +fdf application/vnd.fdf base64 +mseed application/vnd.fdsn.mseed base64 +dataless application/vnd.fdsn.seed base64 +ftc application/vnd.fluxtime.clip base64 +frm application/vnd.framemaker base64 +fnc application/vnd.frogans.fnc base64 +ltf application/vnd.frogans.ltf base64 +fsc application/vnd.fsc.weblaunch 7bit +oas application/vnd.fujitsu.oasys base64 +oa2 application/vnd.fujitsu.oasys2 base64 +oa3 application/vnd.fujitsu.oasys3 base64 +fg5 application/vnd.fujitsu.oasysgp base64 +bh2 application/vnd.fujitsu.oasysprs base64 +ddd application/vnd.fujixerox.ddd base64 +xdw application/vnd.fujixerox.docuworks base64 +xbd application/vnd.fujixerox.docuworks.binder base64 +fzs application/vnd.fuzzysheet base64 +txd application/vnd.genomatix.tuxedo base64 +ggb application/vnd.geogebra.file base64 +ggs application/vnd.geogebra.slides base64 +ggt application/vnd.geogebra.tool base64 +gex application/vnd.geometry-explorer base64 +gxt application/vnd.geonext base64 +g2w application/vnd.geoplan base64 +g3w application/vnd.geospace base64 +gmx application/vnd.gmx base64 +kml application/vnd.google-earth.kml+xml 8bit +kmz application/vnd.google-earth.kmz 8bit +gqf application/vnd.grafeq base64 +gac application/vnd.groove-account base64 +ghf application/vnd.groove-help base64 +gim application/vnd.groove-identity-message base64 +grv application/vnd.groove-injector base64 +gtm application/vnd.groove-tool-message base64 +tpl application/vnd.groove-tool-template base64 +vcg application/vnd.groove-vcard base64 +hal application/vnd.hal+xml base64 +hbci application/vnd.hbci base64 +les application/vnd.hhe.lesson-player base64 +plt application/vnd.hp-HPGL base64 +pcl application/vnd.hp-PCL base64 +pclxl application/vnd.hp-PCLXL base64 +hpid application/vnd.hp-hpid base64 +hps application/vnd.hp-hps base64 +jlt application/vnd.hp-jlyt base64 +sfd-hdstx application/vnd.hydrostatix.sof-data base64 +mpy application/vnd.ibm.MiniPay base64 +emm application/vnd.ibm.electronic-media base64 +afp application/vnd.ibm.modcap base64 +irm application/vnd.ibm.rights-management base64 +sc application/vnd.ibm.secure-container base64 +icc application/vnd.iccprofile base64 +igl application/vnd.igloader base64 +ivp application/vnd.immervision-ivp base64 +ivu application/vnd.immervision-ivu base64 +igm application/vnd.insors.igm base64 +xpw application/vnd.intercon.formnet base64 +i2g application/vnd.intergeo base64 +qbo application/vnd.intu.qbo base64 +qfx application/vnd.intu.qfx base64 +rcprofile application/vnd.ipunplugged.rcprofile base64 +irp application/vnd.irepository.package+xml base64 +xpr application/vnd.is-xpr base64 +fcs application/vnd.isac.fcs base64 +jam application/vnd.jam base64 +rms application/vnd.jcp.javame.midlet-rms base64 +jisp application/vnd.jisp base64 +joda application/vnd.joost.joda-archive base64 +ktr application/vnd.kahootz base64 +karbon application/vnd.kde.karbon base64 +chrt application/vnd.kde.kchart base64 +kfo application/vnd.kde.kformula base64 +flw application/vnd.kde.kivio base64 +kon application/vnd.kde.kontour base64 +kpr application/vnd.kde.kpresenter base64 +ksp application/vnd.kde.kspread base64 +kwd application/vnd.kde.kword base64 +htke application/vnd.kenameaapp base64 +kia application/vnd.kidspiration base64 +skd application/vnd.koan base64 +sse application/vnd.kodak-descriptor base64 +lasxml application/vnd.las.las+xml base64 +lbd application/vnd.llamagraphics.life-balance.desktop base64 +lbe application/vnd.llamagraphics.life-balance.exchange+xml base64 +wks application/vnd.lotus-1-2-3 base64 +apr application/vnd.lotus-approach base64 +pre application/vnd.lotus-freelance base64 +nsf application/vnd.lotus-notes base64 +org application/vnd.lotus-organizer base64 +scm application/vnd.lotus-screencam base64 +lwp application/vnd.lotus-wordpro base64 +portpkg application/vnd.macports.portpkg base64 +mcd application/vnd.mcd base64 +mc1 application/vnd.medcalcdata base64 +cdkey application/vnd.mediastation.cdkey base64 +mfm application/vnd.mfmp base64 +flo application/vnd.micrografx.flo base64 +igx application/vnd.micrografx.igx base64 +mif application/vnd.mif base64 +mpn application/vnd.mophun.application base64 +mpc application/vnd.mophun.certificate base64 +xul application/vnd.mozilla.xul+xml base64 +cil application/vnd.ms-artgalry base64 +asf application/vnd.ms-asf base64 +cab application/vnd.ms-cab-compressed base64 +xls application/vnd.ms-excel base64 +xlam application/vnd.ms-excel.addin.macroEnabled.12 base64 +xlsb application/vnd.ms-excel.sheet.binary.macroEnabled.12 base64 +xlsm application/vnd.ms-excel.sheet.macroEnabled.12 base64 +xltm application/vnd.ms-excel.template.macroEnabled.12 base64 +eot application/vnd.ms-fontobject base64 +chm application/vnd.ms-htmlhelp base64 +ims application/vnd.ms-ims base64 +lrm application/vnd.ms-lrm base64 +thmx application/vnd.ms-officetheme base64 +msg application/vnd.ms-outlook base64 +cat application/vnd.ms-pki.seccat base64 +stl application/vnd.ms-pki.stl base64 +ppt application/vnd.ms-powerpoint base64 +ppam application/vnd.ms-powerpoint.addin.macroEnabled.12 base64 +pptm application/vnd.ms-powerpoint.presentation.macroEnabled.12 base64 +sldm application/vnd.ms-powerpoint.slide.macroEnabled.12 base64 +ppsm application/vnd.ms-powerpoint.slideshow.macroEnabled.12 base64 +potm application/vnd.ms-powerpoint.template.macroEnabled.12 base64 +mpp application/vnd.ms-project base64 +docm application/vnd.ms-word.document.macroEnabled.12 base64 +dotm application/vnd.ms-word.template.macroEnabled.12 base64 +wcm application/vnd.ms-works base64 +wpl application/vnd.ms-wpl base64 +xps application/vnd.ms-xpsdocument 8bit +mseq application/vnd.mseq base64 +mus application/vnd.musician base64 +msty application/vnd.muvee.style base64 +taglet application/vnd.mynfc base64 +ent application/vnd.nervana base64 +nlu application/vnd.neurolanguage.nlu base64 +nitf application/vnd.nitf base64 +nnd application/vnd.noblenet-directory base64 +nns application/vnd.noblenet-sealer base64 +nnw application/vnd.noblenet-web base64 +ngdat application/vnd.nokia.n-gage.data base64 +n-gage application/vnd.nokia.n-gage.symbian.install base64 +rpst application/vnd.nokia.radio-preset base64 +rpss application/vnd.nokia.radio-presets base64 +edm application/vnd.novadigm.EDM base64 +edx application/vnd.novadigm.EDX base64 +ext application/vnd.novadigm.EXT base64 +odc application/vnd.oasis.opendocument.chart base64 +odc application/vnd.oasis.opendocument.chart-template base64 +odb application/vnd.oasis.opendocument.database base64 +odf application/vnd.oasis.opendocument.formula base64 +odf application/vnd.oasis.opendocument.formula-template base64 +odg application/vnd.oasis.opendocument.graphics base64 +otg application/vnd.oasis.opendocument.graphics-template base64 +odi application/vnd.oasis.opendocument.image base64 +odi application/vnd.oasis.opendocument.image-template base64 +odp application/vnd.oasis.opendocument.presentation base64 +otp application/vnd.oasis.opendocument.presentation-template base64 +ods application/vnd.oasis.opendocument.spreadsheet base64 +ots application/vnd.oasis.opendocument.spreadsheet-template base64 +odt application/vnd.oasis.opendocument.text base64 +odm application/vnd.oasis.opendocument.text-master base64 +ott application/vnd.oasis.opendocument.text-template base64 +oth application/vnd.oasis.opendocument.text-web base64 +xo application/vnd.olpc-sugar base64 +dd2 application/vnd.oma.dd2+xml base64 +oxt application/vnd.openofficeorg.extension base64 +pptx application/vnd.openxmlformats-officedocument.presentationml.presentation base64 +sldx application/vnd.openxmlformats-officedocument.presentationml.slide base64 +ppsx application/vnd.openxmlformats-officedocument.presentationml.slideshow base64 +potx application/vnd.openxmlformats-officedocument.presentationml.template base64 +xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet base64 +xltx application/vnd.openxmlformats-officedocument.spreadsheetml.template base64 +docx application/vnd.openxmlformats-officedocument.wordprocessingml.document base64 +dotx application/vnd.openxmlformats-officedocument.wordprocessingml.template base64 +mgp application/vnd.osgeo.mapguide.package base64 +dp application/vnd.osgi.dp base64 +esa application/vnd.osgi.subsystem base64 +prc application/vnd.palm base64 +paw application/vnd.pawaafile base64 +str application/vnd.pg.format base64 +ei6 application/vnd.pg.osasli base64 +efif application/vnd.picsel base64 +wg application/vnd.pmi.widget base64 +plf application/vnd.pocketlearn base64 +pbd application/vnd.powerbuilder6 base64 +box application/vnd.previewsystems.box base64 +mgz application/vnd.proteus.magazine base64 +qps application/vnd.publishare-delta-tree base64 +pti application/vnd.pvi.ptid1 base64 +bed application/vnd.realvnc.bed base64 +mxl application/vnd.recordare.musicxml base64 +musicxml application/vnd.recordare.musicxml+xml base64 +cryptonote application/vnd.rig.cryptonote base64 +cod application/vnd.rim.cod base64 +rm application/vnd.rn-realmedia base64 +rmvb application/vnd.rn-realmedia-vbr base64 +link66 application/vnd.route66.link66+xml base64 +st application/vnd.sailingtracker.track base64 +sdoc application/vnd.sealed.doc base64 +seml application/vnd.sealed.eml base64 +smht application/vnd.sealed.mht base64 +sppt application/vnd.sealed.ppt base64 +sxls application/vnd.sealed.xls base64 +stml application/vnd.sealedmedia.softseal.html base64 +spdf application/vnd.sealedmedia.softseal.pdf base64 +see application/vnd.seemail base64 +sema application/vnd.sema base64 +semd application/vnd.semd base64 +semf application/vnd.semf base64 +ifm application/vnd.shana.informed.formdata base64 +itp application/vnd.shana.informed.formtemplate base64 +iif application/vnd.shana.informed.interchange base64 +ipk application/vnd.shana.informed.package base64 +mmf application/vnd.smaf base64 +teacher application/vnd.smart.teacher base64 +sdkd application/vnd.solent.sdkm+xml base64 +dxp application/vnd.spotfire.dxp base64 +sfs application/vnd.spotfire.sfs base64 +sdc application/vnd.stardivision.calc base64 +sds application/vnd.stardivision.chart base64 +sda application/vnd.stardivision.draw base64 +sdd application/vnd.stardivision.impress base64 +sdf application/vnd.stardivision.math base64 +sdw application/vnd.stardivision.writer base64 +sgl application/vnd.stardivision.writer-global base64 +smzip application/vnd.stepmania.package base64 +sm application/vnd.stepmania.stepchart base64 +sxc application/vnd.sun.xml.calc base64 +stc application/vnd.sun.xml.calc.template base64 +sxd application/vnd.sun.xml.draw base64 +std application/vnd.sun.xml.draw.template base64 +sxi application/vnd.sun.xml.impress base64 +sti application/vnd.sun.xml.impress.template base64 +sxm application/vnd.sun.xml.math base64 +sxw application/vnd.sun.xml.writer base64 +sxg application/vnd.sun.xml.writer.global base64 +stw application/vnd.sun.xml.writer.template base64 +sus application/vnd.sus-calendar base64 +svd application/vnd.svd base64 +sis application/vnd.symbian.install base64 +xsm application/vnd.syncml+xml base64 +bdm application/vnd.syncml.dm+wbxml base64 +xdm application/vnd.syncml.dm+xml base64 +tao application/vnd.tao.intent-module-archive base64 +cap application/vnd.tcpdump.pcap base64 +tmo application/vnd.tmobile-livetv base64 +tpt application/vnd.trid.tpt base64 +mxs application/vnd.triscape.mxs base64 +tra application/vnd.trueapp base64 +ufd application/vnd.ufdl base64 +utz application/vnd.uiq.theme base64 +umj application/vnd.umajin base64 +unityweb application/vnd.unity base64 +uoml application/vnd.uoml+xml base64 +vcx application/vnd.vcx base64 +vsc application/vnd.vidsoft.vidconference 8bit +vsd application/vnd.visio base64 +vis application/vnd.visionary base64 +vsf application/vnd.vsf base64 +sic application/vnd.wap.sic base64 +slc application/vnd.wap.slc base64 +wbxml application/vnd.wap.wbxml base64 +wmlc application/vnd.wap.wmlc base64 +wmlsc application/vnd.wap.wmlscriptc base64 +wtb application/vnd.webturbo base64 +nbp application/vnd.wolfram.player base64 +wpd application/vnd.wordperfect base64 +wqd application/vnd.wqd base64 +stf application/vnd.wt.stf base64 +wv application/vnd.wv.csp+wbxml base64 +xar application/vnd.xara base64 +xfdl application/vnd.xfdl base64 +hvd application/vnd.yamaha.hv-dic base64 +hvs application/vnd.yamaha.hv-script base64 +hvp application/vnd.yamaha.hv-voice base64 +osf application/vnd.yamaha.openscoreformat base64 +osfpvg application/vnd.yamaha.openscoreformat.osfpvg+xml base64 +saf application/vnd.yamaha.smaf-audio base64 +spf application/vnd.yamaha.smaf-phrase base64 +cmp application/vnd.yellowriver-custom-menu base64 +zir application/vnd.zul base64 +zaz application/vnd.zzazz.deck+xml base64 +vxml application/voicexml+xml base64 +wasm application/wasm 8bit +wif application/watcherinfo+xml base64 +wgt application/widget base64 +wp5 application/wordperfect5.1 base64 +wsdl application/wsdl+xml base64 +wspolicy application/wspolicy+xml base64 +wk application/x-123 base64 +7z application/x-7z-compressed base64 +bck application/x-VMSBACKUP base64 +wz application/x-Wingz base64 +abw application/x-abiword base64 +ace application/x-ace-compressed base64 +dmg application/x-apple-diskimage base64 +aab application/x-authorware-bin base64 +aam application/x-authorware-map base64 +aas application/x-authorware-seg base64 +bcpio application/x-bcpio base64 +torrent application/x-bittorrent base64 +bleep application/x-bleeper base64 +blb application/x-blorb base64 +bz application/x-bzip base64 +boz application/x-bzip2 base64 +cb7 application/x-cbr base64 +vcd application/x-cdlink base64 +cfs application/x-cfs-compressed base64 +chat application/x-chat base64 +pgn application/x-chess-pgn base64 +crx application/x-chrome-extension base64 +z application/x-compressed base64 +nsc application/x-conference base64 +cpio application/x-cpio base64 +csh application/x-csh 8bit +csm application/x-cu-seeme base64 +deb application/x-debian-package base64 +dgc application/x-dgc-compressed base64 +dcr application/x-director base64 +wad application/x-doom base64 +ncx application/x-dtbncx+xml base64 +dtb application/x-dtbook+xml base64 +res application/x-dtbresource+xml base64 +dvi application/x-dvi base64 +evy application/x-envoy base64 +eva application/x-eva base64 +bdf application/x-font-bdf base64 +gsf application/x-font-ghostscript base64 +psf application/x-font-linux-psf base64 +pcf application/x-font-pcf base64 +snf application/x-font-snf base64 +afm application/x-font-type1 base64 +arc application/x-freearc base64 +spl application/x-futuresplash base64 +gca application/x-gca-compressed base64 +ulx application/x-glulx base64 +gnumeric application/x-gnumeric base64 +gramps application/x-gramps-xml base64 +gtar application/x-gtar base64 +hdf application/x-hdf base64 +hep application/x-hep base64 +rhtml application/x-html+ruby 8bit +phtml application/x-httpd-php 8bit +ibooks application/x-ibooks+zip base64 +ica application/x-ica base64 +imagemap application/x-imagemap 8bit +install application/x-install-instructions base64 +iso application/x-iso9660-image base64 +key application/x-iwork-keynote-sffkey base64 +numbers application/x-iwork-numbers-sffnumbers base64 +pages application/x-iwork-pages-sffpages base64 +jnlp application/x-java-jnlp-file base64 +ltx application/x-latex 8bit +cpt application/x-mac-compactpro base64 +mie application/x-mie base64 +mobi application/x-mobipocket-ebook base64 +application application/x-ms-application base64 +exe application/x-ms-dos-executable base64 +lnk application/x-ms-shortcut base64 +wmd application/x-ms-wmd base64 +wmz application/x-ms-wmz base64 +xbap application/x-ms-xbap base64 +mda application/x-msaccess base64 +obd application/x-msbinder base64 +crd application/x-mscardfile base64 +clp application/x-msclip base64 +cmd application/x-msdos-program base64 +exe application/x-msdownload base64 +m13 application/x-msmediaview base64 +emf application/x-msmetafile base64 +mny application/x-msmoney base64 +pub application/x-mspublisher base64 +scd application/x-msschedule base64 +trm application/x-msterminal base64 +wri application/x-mswrite base64 +pac application/x-ns-proxy-autoconfig base64 +nzb application/x-nzb base64 +oex application/x-opera-extension base64 +pm application/x-pagemaker base64 +pl application/x-perl 8bit +p12 application/x-pkcs12 base64 +p7b application/x-pkcs7-certificates base64 +p7r application/x-pkcs7-certreqresp base64 +py application/x-python 8bit +qtl application/x-quicktimeplayer base64 +rar application/x-rar-compressed base64 +ris application/x-research-info-systems base64 +rb application/x-ruby 8bit +sh application/x-sh 8bit +shar application/x-shar 8bit +swf application/x-shockwave-flash base64 +xap application/x-silverlight-app base64 +notebook application/x-smarttech-notebook base64 +sav application/x-spss base64 +sql application/x-sql base64 +sit application/x-stuffit base64 +sitx application/x-stuffitx base64 +srt application/x-subrip base64 +sv4cpio application/x-sv4cpio base64 +sv4crc application/x-sv4crc base64 +t3 application/x-t3vm-image base64 +gam application/x-tads base64 +tar application/x-tar base64 +tcl application/x-tcl 8bit +tex application/x-tex 8bit +tfm application/x-tex-tfm base64 +texinfo application/x-texinfo 8bit +obj application/x-tgif base64 +tbk application/x-toolbook base64 +ustar application/x-ustar base64 +src application/x-wais-source base64 +webapp application/x-web-app-manifest+json base64 +wp6 application/x-wordperfect6.1 base64 +crt application/x-x509-ca-cert base64 +fig application/x-xfig base64 +xlf application/x-xliff+xml base64 +xpi application/x-xpinstall base64 +xz application/x-xz base64 +z1 application/x-zmachine base64 +xaml application/xaml+xml base64 +xdf application/xcap-diff+xml base64 +xenc application/xenc+xml base64 +xht application/xhtml+xml 8bit +xml application/xml 8bit +dtd application/xml-dtd 8bit +xop application/xop+xml base64 +xpl application/xproc+xml base64 +xslt application/xslt+xml base64 +xspf application/xspf+xml base64 +mxml application/xv+xml base64 +yang application/yang base64 +yin application/yin+xml base64 +zip application/zip base64 +amr audio/AMR base64 +awb audio/AMR-WB base64 +evc audio/EVRC base64 +l16 audio/L16 base64 +smv audio/SMV base64 +adp audio/adpcm base64 +au audio/basic base64 +kar audio/midi base64 +mp4 audio/mp4 base64 +mpga audio/mpeg base64 +oga audio/ogg base64 +s3m audio/s3m base64 +sil audio/silk base64 +uva audio/vnd.dece.audio base64 +eol audio/vnd.digital-winds 7bit +dra audio/vnd.dra base64 +dts audio/vnd.dts base64 +dtshd audio/vnd.dts.hd base64 +plj audio/vnd.everad.plj base64 +lvp audio/vnd.lucent.voice base64 +pya audio/vnd.ms-playready.media.pya base64 +mxmf audio/vnd.nokia.mobile-xmf base64 +vbk audio/vnd.nortel.vbk base64 +ecelp4800 audio/vnd.nuera.ecelp4800 base64 +ecelp7470 audio/vnd.nuera.ecelp7470 base64 +ecelp9600 audio/vnd.nuera.ecelp9600 base64 +qcp audio/vnd.qcelp base64 +rip audio/vnd.rip base64 +smp3 audio/vnd.sealedmedia.softseal.mpeg base64 +wav audio/wav base64 +weba audio/webm base64 +aac audio/x-aac base64 +aif audio/x-aiff base64 +caf audio/x-caf base64 +flac audio/x-flac base64 +mka audio/x-matroska base64 +m3u audio/x-mpegurl base64 +wax audio/x-ms-wax base64 +wma audio/x-ms-wma base64 +wmv audio/x-ms-wmv base64 +ra audio/x-pn-realaudio base64 +rmp audio/x-pn-realaudio-plugin base64 +xm audio/xm base64 +cdx chemical/x-cdx base64 +cif chemical/x-cif base64 +cmdf chemical/x-cmdf base64 +cml chemical/x-cml base64 +csml chemical/x-csml base64 +ttc font/collection base64 +otf font/otf base64 +ttf font/ttf base64 +woff font/woff base64 +woff2 font/woff2 base64 +avif image/avif base64 +bmp image/bmp base64 +cgm image/cgm base64 +g3 image/g3fax base64 +gif image/gif base64 +heic image/heic base64 +heics image/heic-sequence base64 +heif image/heif base64 +heifs image/heif-sequence base64 +ief image/ief base64 +jp2 image/jp2 base64 +jpeg image/jpeg base64 +jpm image/jpm base64 +jpx image/jpx base64 +ktx image/ktx base64 +png image/png base64 +btif image/prs.btif base64 +sgi image/sgi base64 +svg image/svg+xml 8bit +tiff image/tiff base64 +psd image/vnd.adobe.photoshop base64 +uvg image/vnd.dece.graphic base64 +djvu image/vnd.djvu base64 +sub image/vnd.dvb.subtitle base64 +dwg image/vnd.dwg base64 +dxf image/vnd.dxf base64 +fbs image/vnd.fastbidsheet base64 +fpx image/vnd.fpx base64 +fst image/vnd.fst base64 +mmr image/vnd.fujixerox.edmics-mmr base64 +rlc image/vnd.fujixerox.edmics-rlc base64 +pgb image/vnd.globalgraphics.pgb base64 +ico image/vnd.microsoft.icon base64 +mdi image/vnd.ms-modi base64 +wdp image/vnd.ms-photo base64 +npx image/vnd.net-fpx base64 +wbmp image/vnd.wap.wbmp base64 +xif image/vnd.xiff base64 +webp image/webp base64 +3ds image/x-3ds base64 +dng image/x-adobe-dng base64 +cr2 image/x-canon-cr2 base64 +crw image/x-canon-crw base64 +ras image/x-cmu-raster base64 +cmx image/x-cmx base64 +xcfbz2 image/x-compressed-xcf base64 +erf image/x-epson-erf base64 +fh image/x-freehand base64 +raf image/x-fuji-raf base64 +3fr image/x-hasselblad-3fr base64 +k25 image/x-kodak-k25 base64 +kdc image/x-kodak-kdc base64 +mrw image/x-minolta-mrw base64 +sid image/x-mrsid-image base64 +nef image/x-nikon-nef base64 +orf image/x-olympus-orf base64 +psp image/x-paintshoppro base64 +raw image/x-panasonic-raw base64 +pcx image/x-pcx base64 +pef image/x-pentax-pef base64 +pct image/x-pict base64 +pnm image/x-portable-anymap base64 +pbm image/x-portable-bitmap base64 +pgm image/x-portable-graymap base64 +ppm image/x-portable-pixmap base64 +rgb image/x-rgb base64 +x3f image/x-sigma-x3f base64 +arw image/x-sony-arw base64 +sr2 image/x-sony-sr2 base64 +srf image/x-sony-srf base64 +tga image/x-targa base64 +dgn image/x-vnd.dgn base64 +xbm image/x-xbitmap 7bit +xcf image/x-xcf base64 +xpm image/x-xpixmap 8bit +xwd image/x-xwindowdump base64 +eml message/rfc822 8bit +igs model/iges base64 +msh model/mesh base64 +dae model/vnd.collada+xml base64 +dwf model/vnd.dwf base64 +gdl model/vnd.gdl base64 +gtw model/vnd.gtw base64 +mts model/vnd.mts base64 +x_b model/vnd.parasolid.transmit.binary base64 +x_t model/vnd.parasolid.transmit.text quoted-printable +vtu model/vnd.vtu base64 +wrl model/vrml base64 +x3db model/x3d+binary base64 +x3dv model/x3d+vrml base64 +x3d model/x3d+xml base64 +appcache text/cache-manifest quoted-printable +ics text/calendar quoted-printable +css text/css 8bit +csv text/csv 8bit +html text/html 8bit +js text/javascript quoted-printable +markdown text/markdown quoted-printable +n3 text/n3 quoted-printable +txt text/plain quoted-printable +dsc text/prs.lines.tag quoted-printable +rtx text/richtext 8bit +sgml text/sgml quoted-printable +tsv text/tab-separated-values quoted-printable +t text/troff 8bit +ttl text/turtle quoted-printable +uri text/uri-list quoted-printable +vcard text/vcard quoted-printable +dcurl text/vnd.curl.dcurl quoted-printable +mcurl text/vnd.curl.mcurl quoted-printable +scurl text/vnd.curl.scurl quoted-printable +fly text/vnd.fly quoted-printable +flx text/vnd.fmi.flexstor quoted-printable +gv text/vnd.graphviz quoted-printable +3dml text/vnd.in3d.3dml quoted-printable +spot text/vnd.in3d.spot quoted-printable +ccc text/vnd.net2phone.commcenter.command quoted-printable +jad text/vnd.sun.j2me.app-descriptor 8bit +si text/vnd.wap.si quoted-printable +sl text/vnd.wap.sl quoted-printable +wml text/vnd.wap.wml quoted-printable +wmls text/vnd.wap.wmlscript quoted-printable +vtt text/vtt quoted-printable +asm text/x-asm quoted-printable +c text/x-c quoted-printable +coffee text/x-coffescript 8bit +htc text/x-component 8bit +f text/x-fortran quoted-printable +java text/x-java-source quoted-printable +nfo text/x-nfo quoted-printable +opml text/x-opml quoted-printable +p text/x-pascal quoted-printable +etx text/x-setext quoted-printable +sfv text/x-sfv quoted-printable +uu text/x-uuencode quoted-printable +vcs text/x-vcalendar 8bit +vcf text/x-vcard 8bit +yaml text/x-yaml 8bit +xml text/xml 8bit +3gp video/3gpp base64 +3g2 video/3gpp2 base64 +dv video/DV base64 +h261 video/H261 base64 +h263 video/H263 base64 +h264 video/H264 base64 +jpgv video/JPEG base64 +mj2 video/MJ2 base64 +ts video/MP2T base64 +mp4 video/mp4 base64 +mp2 video/mpeg base64 +ogg video/ogg base64 +qt video/quicktime base64 +uvh video/vnd.dece.hd base64 +uvm video/vnd.dece.mobile base64 +uvp video/vnd.dece.pd base64 +uvs video/vnd.dece.sd base64 +uvv video/vnd.dece.video base64 +dvb video/vnd.dvb.file base64 +fvt video/vnd.fvt base64 +mxu video/vnd.mpegurl 8bit +pyv video/vnd.ms-playready.media.pyv base64 +nim video/vnd.nokia.interleaved-multimedia base64 +mp4 video/vnd.objectvideo base64 +s11 video/vnd.sealed.mpeg1 base64 +smpg video/vnd.sealed.mpeg4 base64 +sswf video/vnd.sealed.swf base64 +smov video/vnd.sealedmedia.softseal.mov base64 +uvu video/vnd.uvvu.mp4 base64 +viv video/vnd.vivo base64 +dl video/x-dl base64 +fli video/x-fli base64 +flv video/x-flv base64 +gl video/x-gl base64 +ivf video/x-ivf base64 +mk3d video/x-matroska base64 +mng video/x-mng base64 +mjpg video/x-motion-jpeg base64 +asf video/x-ms-asf base64 +vob video/x-ms-vob base64 +wm video/x-ms-wm base64 +wmx video/x-ms-wmx base64 +wvx video/x-ms-wvx base64 +avi video/x-msvideo base64 +movie video/x-sgi-movie base64 +xyz x-chemical/x-xyz base64 +ice x-conference/x-cooltalk base64 diff --git a/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/lib/db/ext_mime.db b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/lib/db/ext_mime.db new file mode 100644 index 00000000..5cf98a56 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/lib/db/ext_mime.db @@ -0,0 +1,1201 @@ +123 application/vnd.lotus-1-2-3 base64 +3dml text/vnd.in3d.3dml quoted-printable +3ds image/x-3ds base64 +3fr image/x-hasselblad-3fr base64 +3g2 video/3gpp2 base64 +3gp video/3gpp base64 +3gpp video/3gpp base64 +3gpp2 video/3gpp2 base64 +7z application/x-7z-compressed base64 +@dir application/x-director base64 +@dxr application/x-director base64 +aab application/x-authorware-bin base64 +aac audio/x-aac base64 +aam application/x-authorware-map base64 +aas application/x-authorware-seg base64 +abw application/x-abiword base64 +ac application/pkix-attr-cert base64 +acc application/vnd.americandynamics.acc base64 +ace application/x-ace-compressed base64 +acu application/vnd.acucobol base64 +acutc application/vnd.acucorp 7bit +adp audio/adpcm base64 +aep application/vnd.audiograph base64 +afm application/x-font-type1 base64 +afp application/vnd.ibm.modcap base64 +ahead application/vnd.ahead.space base64 +ai application/pdf base64 +aif audio/x-aiff base64 +aifc audio/x-aiff base64 +aiff audio/x-aiff base64 +air application/vnd.adobe.air-application-installer-package+zip base64 +ait application/vnd.dvb.ait base64 +ami application/vnd.amiga.ami base64 +amr audio/AMR base64 +ani application/octet-stream base64 +apk application/vnd.android.package-archive base64 +appcache text/cache-manifest quoted-printable +application application/x-ms-application base64 +apr application/vnd.lotus-approach base64 +arc application/x-freearc base64 +arw image/x-sony-arw base64 +asc application/pgp-signature base64 +asf application/vnd.ms-asf base64 +asm text/x-asm quoted-printable +aso application/vnd.accpac.simply.aso base64 +asx video/x-ms-asf base64 +atc application/vnd.acucorp 7bit +atom application/atom+xml 8bit +atomcat application/atomcat+xml 8bit +atomsvc application/atomsvc+xml 8bit +atx application/vnd.antix.game-component base64 +au audio/basic base64 +avi video/x-msvideo base64 +avif image/avif base64 +aw application/applixware base64 +awb audio/AMR-WB base64 +azf application/vnd.airzip.filesecure.azf base64 +azs application/vnd.airzip.filesecure.azs base64 +azw application/vnd.amazon.ebook base64 +bat application/x-msdos-program base64 +bck application/x-VMSBACKUP base64 +bcpio application/x-bcpio base64 +bdf application/x-font-bdf base64 +bdm application/vnd.syncml.dm+wbxml base64 +bed application/vnd.realvnc.bed base64 +bh2 application/vnd.fujitsu.oasysprs base64 +bin application/octet-stream base64 +bkm application/vnd.nervana base64 +blb application/x-blorb base64 +bleep application/x-bleeper base64 +blorb application/x-blorb base64 +bmi application/vnd.bmi base64 +bmp image/bmp base64 +book application/vnd.framemaker base64 +box application/vnd.previewsystems.box base64 +boz application/x-bzip2 base64 +bpd application/vnd.hbci base64 +bpk application/octet-stream base64 +btif image/prs.btif base64 +bz application/x-bzip base64 +bz2 application/x-bzip2 base64 +c text/plain quoted-printable +c11amc application/vnd.cluetrust.cartomobile-config base64 +c11amz application/vnd.cluetrust.cartomobile-config-pkg base64 +c4d application/vnd.clonk.c4group base64 +c4f application/vnd.clonk.c4group base64 +c4g application/vnd.clonk.c4group base64 +c4p application/vnd.clonk.c4group base64 +c4u application/vnd.clonk.c4group base64 +cab application/vnd.ms-cab-compressed base64 +caf audio/x-caf base64 +cap application/vnd.tcpdump.pcap base64 +car application/vnd.curl.car base64 +cat application/vnd.ms-pki.seccat base64 +cb7 application/x-cbr base64 +cba application/x-cbr base64 +cbr application/x-cbr base64 +cbt application/x-cbr base64 +cbz application/x-cbr base64 +cc text/plain quoted-printable +ccc text/vnd.net2phone.commcenter.command quoted-printable +cct application/x-director base64 +ccxml application/ccxml+xml base64 +cdbcmsg application/vnd.contact.cmsg base64 +cdf application/netcdf base64 +cdkey application/vnd.mediastation.cdkey base64 +cdmia application/cdmi-capability base64 +cdmic application/cdmi-container base64 +cdmid application/cdmi-domain base64 +cdmio application/cdmi-object base64 +cdmiq application/cdmi-queue base64 +cdx chemical/x-cdx base64 +cdxml application/vnd.chemdraw+xml base64 +cdy application/vnd.cinderella base64 +cer application/pkix-cert base64 +cfs application/x-cfs-compressed base64 +cgm image/cgm base64 +chat application/x-chat base64 +chm application/vnd.ms-htmlhelp base64 +chrt application/vnd.kde.kchart base64 +cif chemical/x-cif base64 +cii application/vnd.anser-web-certificate-issue-initiation base64 +cil application/vnd.ms-artgalry base64 +cjs text/javascript quoted-printable +cla application/vnd.claymore base64 +class application/octet-stream base64 +clkk application/vnd.crick.clicker.keyboard base64 +clkp application/vnd.crick.clicker.palette base64 +clkt application/vnd.crick.clicker.template base64 +clkw application/vnd.crick.clicker.wordbank base64 +clkx application/vnd.crick.clicker base64 +clp application/x-msclip base64 +clpi video/MP2T base64 +cmc application/vnd.cosmocaller base64 +cmd application/x-msdos-program base64 +cmdf chemical/x-cmdf base64 +cml chemical/x-cml base64 +cmp application/vnd.yellowriver-custom-menu base64 +cmx image/x-cmx base64 +cod application/vnd.rim.cod base64 +coffee text/x-coffescript 8bit +com application/x-msdos-program base64 +conf text/plain quoted-printable +cpi video/MP2T base64 +cpio application/x-cpio base64 +cpp text/plain quoted-printable +cpt application/x-mac-compactpro base64 +cr2 image/x-canon-cr2 base64 +crd application/x-mscardfile base64 +crl application/pkix-crl base64 +crt application/x-x509-ca-cert base64 +crw image/x-canon-crw base64 +crx application/x-chrome-extension base64 +cryptonote application/vnd.rig.cryptonote base64 +csh application/x-csh 8bit +csm application/x-cu-seeme base64 +csml chemical/x-csml base64 +csp application/vnd.commonspace base64 +css text/css 8bit +cst application/x-director base64 +csv text/csv 8bit +cu application/cu-seeme base64 +curl application/vnd.curl base64 +cw application/prs.cww base64 +cww application/prs.cww base64 +cxt application/x-director base64 +cxx text/x-c quoted-printable +dae model/vnd.collada+xml base64 +daf application/vnd.Mobius.DAF base64 +dart application/vnd.dart base64 +dat text/plain quoted-printable +dataless application/vnd.fdsn.seed base64 +davmount application/davmount+xml base64 +dbk application/docbook+xml base64 +dcm application/dicom base64 +dcr application/x-director base64 +dcurl text/vnd.curl.dcurl quoted-printable +dd2 application/vnd.oma.dd2+xml base64 +ddd application/vnd.fujixerox.ddd base64 +deb application/x-debian-package base64 +def text/plain quoted-printable +deploy application/octet-stream base64 +der application/x-x509-ca-cert base64 +dfac application/vnd.dreamfactory base64 +dgc application/x-dgc-compressed base64 +dgn image/x-vnd.dgn base64 +dic text/x-c quoted-printable +dir application/x-director base64 +dis application/vnd.Mobius.DIS base64 +dist application/octet-stream base64 +distz application/octet-stream base64 +djv image/vnd.djvu base64 +djvu image/vnd.djvu base64 +dl video/x-dl base64 +dll application/octet-stream base64 +dmg application/x-apple-diskimage base64 +dmp application/vnd.tcpdump.pcap base64 +dms application/octet-stream base64 +dna application/vnd.dna base64 +dng image/x-adobe-dng base64 +doc application/msword base64 +docm application/vnd.ms-word.document.macroEnabled.12 base64 +docx application/vnd.openxmlformats-officedocument.wordprocessingml.document base64 +dot application/msword base64 +dotm application/vnd.ms-word.template.macroEnabled.12 base64 +dotx application/vnd.openxmlformats-officedocument.wordprocessingml.template base64 +dp application/vnd.osgi.dp base64 +dpg application/vnd.dpgraph base64 +dra audio/vnd.dra base64 +dsc text/prs.lines.tag quoted-printable +dssc application/dssc+der base64 +dtb application/x-dtbook+xml base64 +dtd application/xml-dtd 8bit +dts audio/vnd.dts base64 +dtshd audio/vnd.dts.hd base64 +dump application/octet-stream base64 +dv video/DV base64 +dvb video/vnd.dvb.file base64 +dvi application/x-dvi base64 +dwf model/vnd.dwf base64 +dwg image/vnd.dwg base64 +dxf image/vnd.dxf base64 +dxp application/vnd.spotfire.dxp base64 +dxr application/x-director base64 +dylib application/octet-stream base64 +ecelp4800 audio/vnd.nuera.ecelp4800 base64 +ecelp7470 audio/vnd.nuera.ecelp7470 base64 +ecelp9600 audio/vnd.nuera.ecelp9600 base64 +ecma application/ecmascript base64 +edm application/vnd.novadigm.EDM base64 +edx application/vnd.novadigm.EDX base64 +efif application/vnd.picsel base64 +ei6 application/vnd.pg.osasli base64 +elc application/octet-stream base64 +emf application/x-msmetafile base64 +eml message/rfc822 8bit +emm application/vnd.ibm.electronic-media base64 +emma application/emma+xml base64 +emz application/x-msmetafile base64 +ent application/vnd.nervana base64 +entity application/vnd.nervana base64 +eol audio/vnd.digital-winds 7bit +eot application/vnd.ms-fontobject base64 +eps application/postscript 8bit +epub application/epub+zip base64 +erf image/x-epson-erf base64 +es application/ecmascript base64 +es3 application/vnd.eszigno3+xml base64 +esa application/vnd.osgi.subsystem base64 +esf application/vnd.epson.esf base64 +et3 application/vnd.eszigno3+xml base64 +etx text/x-setext quoted-printable +eva application/x-eva base64 +evc audio/EVRC base64 +evy application/x-envoy base64 +exe application/x-ms-dos-executable base64 +exi application/exi base64 +ext application/vnd.novadigm.EXT base64 +ez application/andrew-inset base64 +ez2 application/vnd.ezpix-album base64 +ez3 application/vnd.ezpix-package base64 +f text/x-fortran quoted-printable +f4a audio/mp4 base64 +f4b audio/mp4 base64 +f4p video/mp4 base64 +f4v video/mp4 base64 +f77 text/x-fortran quoted-printable +f90 text/x-fortran quoted-printable +fb application/vnd.framemaker base64 +fbdoc application/vnd.framemaker base64 +fbs image/vnd.fastbidsheet base64 +fcdt application/vnd.adobe.formscentral.fcdt base64 +fcs application/vnd.isac.fcs base64 +fdf application/vnd.fdf base64 +fe_launch application/vnd.denovo.fcselayout-link base64 +fg5 application/vnd.fujitsu.oasysgp base64 +fgd application/x-director base64 +fh image/x-freehand base64 +fh4 image/x-freehand base64 +fh5 image/x-freehand base64 +fh7 image/x-freehand base64 +fhc image/x-freehand base64 +fig application/x-xfig base64 +flac audio/x-flac base64 +fli video/x-fli base64 +flo application/vnd.micrografx.flo base64 +flv video/x-flv base64 +flw application/vnd.kde.kivio base64 +flx text/vnd.fmi.flexstor quoted-printable +fly text/vnd.fly quoted-printable +fm application/vnd.framemaker base64 +fnc application/vnd.frogans.fnc base64 +for text/x-fortran quoted-printable +fpx image/vnd.fpx base64 +frame application/vnd.framemaker base64 +frm application/vnd.framemaker base64 +fsc application/vnd.fsc.weblaunch 7bit +fst image/vnd.fst base64 +ftc application/vnd.fluxtime.clip base64 +fti application/vnd.anser-web-funds-transfer-initiation base64 +fvt video/vnd.fvt base64 +fxp application/vnd.adobe.fxp base64 +fxpl application/vnd.adobe.fxp base64 +fzs application/vnd.fuzzysheet base64 +g2w application/vnd.geoplan base64 +g3 image/g3fax base64 +g3w application/vnd.geospace base64 +gac application/vnd.groove-account base64 +gam application/x-tads base64 +gbr application/rpki-ghostbusters base64 +gca application/x-gca-compressed base64 +gdl model/vnd.gdl base64 +geo application/vnd.dynageo base64 +gex application/vnd.geometry-explorer base64 +ggb application/vnd.geogebra.file base64 +ggs application/vnd.geogebra.slides base64 +ggt application/vnd.geogebra.tool base64 +ghf application/vnd.groove-help base64 +gif image/gif base64 +gim application/vnd.groove-identity-message base64 +gl video/x-gl base64 +gml application/gml+xml base64 +gmx application/vnd.gmx base64 +gnumeric application/x-gnumeric base64 +gpg application/octet-stream base64 +gph application/vnd.FloGraphIt base64 +gpx application/gpx+xml base64 +gqf application/vnd.grafeq base64 +gqs application/vnd.grafeq base64 +gram application/srgs base64 +gramps application/x-gramps-xml base64 +gre application/vnd.geometry-explorer base64 +grv application/vnd.groove-injector base64 +grxml application/srgs+xml base64 +gsf application/x-font-ghostscript base64 +gtar application/x-gtar base64 +gtm application/vnd.groove-tool-message base64 +gtw model/vnd.gtw base64 +gv text/vnd.graphviz quoted-printable +gxf application/gxf base64 +gxt application/vnd.geonext base64 +gz application/gzip base64 +h text/plain quoted-printable +h261 video/H261 base64 +h263 video/H263 base64 +h264 video/H264 base64 +hal application/vnd.hal+xml base64 +hbc application/vnd.hbci base64 +hbci application/vnd.hbci base64 +hdf application/x-hdf base64 +heic image/heic base64 +heics image/heic-sequence base64 +heif image/heif base64 +heifs image/heif-sequence base64 +hep application/x-hep base64 +hh text/plain quoted-printable +hif image/heic base64 +hlp text/plain quoted-printable +hpgl application/vnd.hp-HPGL base64 +hpid application/vnd.hp-hpid base64 +hpp text/plain quoted-printable +hps application/vnd.hp-hps base64 +hqx application/mac-binhex40 8bit +htc text/x-component 8bit +htke application/vnd.kenameaapp base64 +htm text/html 8bit +html text/html 8bit +htmlx text/html 8bit +htx text/html 8bit +hvd application/vnd.yamaha.hv-dic base64 +hvp application/vnd.yamaha.hv-voice base64 +hvs application/vnd.yamaha.hv-script base64 +i2g application/vnd.intergeo base64 +ibooks application/x-ibooks+zip base64 +ica application/x-ica base64 +icc application/vnd.iccprofile base64 +ice x-conference/x-cooltalk base64 +icm application/vnd.iccprofile base64 +ico image/vnd.microsoft.icon base64 +ics text/calendar quoted-printable +ief image/ief base64 +ifb text/calendar quoted-printable +ifm application/vnd.shana.informed.formdata base64 +iges model/iges base64 +igl application/vnd.igloader base64 +igm application/vnd.insors.igm base64 +igs model/iges base64 +igx application/vnd.micrografx.igx base64 +iif application/vnd.shana.informed.interchange base64 +imagemap application/x-imagemap 8bit +imap application/x-imagemap 8bit +imp application/vnd.accpac.simply.imp base64 +ims application/vnd.ms-ims base64 +in text/plain quoted-printable +ink application/inkml+xml base64 +inkml application/inkml+xml base64 +install application/x-install-instructions base64 +iota application/vnd.astraea-software.iota base64 +ipa application/octet-stream base64 +ipfix application/ipfix base64 +ipk application/vnd.shana.informed.package base64 +irm application/vnd.ibm.rights-management base64 +irp application/vnd.irepository.package+xml base64 +iso application/x-iso9660-image base64 +itp application/vnd.shana.informed.formtemplate base64 +ivf video/x-ivf base64 +ivp application/vnd.immervision-ivp base64 +ivu application/vnd.immervision-ivu base64 +jad text/vnd.sun.j2me.app-descriptor 8bit +jam application/vnd.jam base64 +jar application/java-archive base64 +java text/x-java-source quoted-printable +jfif image/jpeg base64 +jisp application/vnd.jisp base64 +jlt application/vnd.hp-jlyt base64 +jnlp application/x-java-jnlp-file base64 +joda application/vnd.joost.joda-archive base64 +jp2 image/jp2 base64 +jpe image/jpeg base64 +jpeg image/jpeg base64 +jpf image/jpx base64 +jpg image/jpeg base64 +jpg2 image/jp2 base64 +jpgm image/jpm base64 +jpgv video/JPEG base64 +jpm image/jpm base64 +jpx image/jpx base64 +js text/javascript quoted-printable +json application/json 8bit +jsonml application/jsonml+json base64 +k25 image/x-kodak-k25 base64 +kar audio/midi base64 +karbon application/vnd.kde.karbon base64 +kcm application/vnd.nervana base64 +kdc image/x-kodak-kdc base64 +key application/x-iwork-keynote-sffkey base64 +kfo application/vnd.kde.kformula base64 +kia application/vnd.kidspiration base64 +kml application/vnd.google-earth.kml+xml 8bit +kmz application/vnd.google-earth.kmz 8bit +kne application/vnd.Kinar base64 +knp application/vnd.Kinar base64 +kom application/vnd.hbci base64 +kon application/vnd.kde.kontour base64 +kpr application/vnd.kde.kpresenter base64 +kpt application/vnd.kde.kpresenter base64 +kpxx application/vnd.ds-keypoint base64 +ksp application/vnd.kde.kspread base64 +ktr application/vnd.kahootz base64 +ktx image/ktx base64 +ktz application/vnd.kahootz base64 +kwd application/vnd.kde.kword base64 +kwt application/vnd.kde.kword base64 +l16 audio/L16 base64 +lasxml application/vnd.las.las+xml base64 +latex application/x-latex 8bit +lbd application/vnd.llamagraphics.life-balance.desktop base64 +lbe application/vnd.llamagraphics.life-balance.exchange+xml base64 +les application/vnd.hhe.lesson-player base64 +lha application/octet-stream base64 +link66 application/vnd.route66.link66+xml base64 +list text/plain quoted-printable +list3820 application/vnd.ibm.modcap base64 +listafp application/vnd.ibm.modcap base64 +lnk application/x-ms-shortcut base64 +log text/plain quoted-printable +lostxml application/lost+xml base64 +lrf application/octet-stream base64 +lrm application/vnd.ms-lrm base64 +ltf application/vnd.frogans.ltf base64 +ltx application/x-latex 8bit +lvp audio/vnd.lucent.voice base64 +lwp application/vnd.lotus-wordpro base64 +lzh application/octet-stream base64 +m13 application/x-msmediaview base64 +m14 application/x-msmediaview base64 +m1v video/mpeg base64 +m21 application/mp21 base64 +m2a audio/mpeg base64 +m2ts video/MP2T base64 +m2v video/mpeg base64 +m3a audio/mpeg base64 +m3u audio/x-mpegurl base64 +m3u8 application/vnd.apple.mpegurl base64 +m4a audio/mp4 base64 +m4u video/vnd.mpegurl 8bit +m4v video/vnd.objectvideo base64 +ma application/mathematica base64 +mads application/mads+xml base64 +mag application/vnd.ecowin.chart base64 +maker application/vnd.framemaker base64 +man text/troff 8bit +manifest text/cache-manifest quoted-printable +mar application/octet-stream base64 +markdown text/markdown quoted-printable +mathml application/mathml+xml base64 +mb application/mathematica base64 +mbk application/vnd.Mobius.MBK base64 +mbox application/mbox base64 +mc1 application/vnd.medcalcdata base64 +mcd application/vnd.mcd base64 +mcurl text/vnd.curl.mcurl quoted-printable +md text/markdown quoted-printable +mda application/x-msaccess base64 +mdb application/x-msaccess base64 +mde application/x-msaccess base64 +mdf application/x-msaccess base64 +mdi image/vnd.ms-modi base64 +me text/troff 8bit +mesh model/mesh base64 +meta4 application/metalink4+xml base64 +metalink application/metalink+xml base64 +mets application/mets+xml base64 +mfm application/vnd.mfmp base64 +mft application/rpki-manifest base64 +mgp application/vnd.osgeo.mapguide.package base64 +mgz application/vnd.proteus.magazine base64 +mid audio/midi base64 +midi audio/midi base64 +mie application/x-mie base64 +mif application/vnd.mif base64 +mime message/rfc822 8bit +mj2 video/MJ2 base64 +mjp2 video/MJ2 base64 +mjpeg video/x-motion-jpeg base64 +mjpg video/x-motion-jpeg base64 +mjs text/javascript quoted-printable +mk3d video/x-matroska base64 +mka audio/x-matroska base64 +mkd text/markdown quoted-printable +mks video/x-matroska base64 +mkv video/x-matroska base64 +mlp application/vnd.dolby.mlp base64 +mmd application/vnd.chipnuts.karaoke-mmd base64 +mmf application/vnd.smaf base64 +mmr image/vnd.fujixerox.edmics-mmr base64 +mng video/x-mng base64 +mny application/x-msmoney base64 +mobi application/x-mobipocket-ebook base64 +mods application/mods+xml base64 +mov video/quicktime base64 +movie video/x-sgi-movie base64 +mp2 audio/mpeg base64 +mp21 application/mp21 base64 +mp2a audio/mpeg base64 +mp3 audio/mpeg base64 +mp3g video/mpeg base64 +mp4 application/mp4 base64 +mp4a audio/mp4 base64 +mp4s application/mp4 base64 +mp4v video/mp4 base64 +mpc application/vnd.mophun.certificate base64 +mpe video/mpeg base64 +mpeg video/mpeg base64 +mpg video/mpeg base64 +mpg4 application/mp4 base64 +mpga audio/mpeg base64 +mpkg application/vnd.apple.installer+xml base64 +mpl video/MP2T base64 +mpls video/MP2T base64 +mpm application/vnd.blueice.multipass base64 +mpn application/vnd.mophun.application base64 +mpp application/vnd.ms-project base64 +mpt application/vnd.ms-project base64 +mpy application/vnd.ibm.MiniPay base64 +mqy application/vnd.Mobius.MQY base64 +mrc application/marc base64 +mrcx application/marcxml+xml base64 +mrw image/x-minolta-mrw base64 +ms text/troff 8bit +mscml application/mediaservercontrol+xml base64 +mseed application/vnd.fdsn.mseed base64 +mseq application/vnd.mseq base64 +msf application/vnd.epson.msf base64 +msg application/vnd.ms-outlook base64 +msh model/mesh base64 +msi application/x-msdownload base64 +msl application/vnd.Mobius.MSL base64 +msty application/vnd.muvee.style base64 +mts model/vnd.mts base64 +mus application/vnd.musician base64 +musicxml application/vnd.recordare.musicxml+xml base64 +mvb application/x-msmediaview base64 +mwf application/vnd.MFER base64 +mxf application/mxf base64 +mxl application/vnd.recordare.musicxml base64 +mxmf audio/vnd.nokia.mobile-xmf base64 +mxml application/xv+xml base64 +mxs application/vnd.triscape.mxs base64 +mxu video/vnd.mpegurl 8bit +n-gage application/vnd.nokia.n-gage.symbian.install base64 +n3 text/n3 quoted-printable +nb application/mathematica base64 +nbp application/vnd.wolfram.player base64 +nc application/netcdf base64 +ncx application/x-dtbncx+xml base64 +nef image/x-nikon-nef base64 +nfo text/x-nfo quoted-printable +ngdat application/vnd.nokia.n-gage.data base64 +nim video/vnd.nokia.interleaved-multimedia base64 +nitf application/vnd.nitf base64 +nlu application/vnd.neurolanguage.nlu base64 +nml application/vnd.enliven base64 +nnd application/vnd.noblenet-directory base64 +nns application/vnd.noblenet-sealer base64 +nnw application/vnd.noblenet-web base64 +notebook application/x-smarttech-notebook base64 +npx image/vnd.net-fpx base64 +nsc application/x-conference base64 +nsf application/vnd.lotus-notes base64 +ntf application/vnd.nitf base64 +numbers application/x-iwork-numbers-sffnumbers base64 +nzb application/x-nzb base64 +oa2 application/vnd.fujitsu.oasys2 base64 +oa3 application/vnd.fujitsu.oasys3 base64 +oas application/vnd.fujitsu.oasys base64 +obd application/x-msbinder base64 +obj application/x-tgif base64 +oda application/oda base64 +odb application/vnd.oasis.opendocument.database base64 +odc application/vnd.oasis.opendocument.chart base64 +odf application/vnd.oasis.opendocument.formula base64 +odft application/vnd.oasis.opendocument.formula-template base64 +odg application/vnd.oasis.opendocument.graphics base64 +odi application/vnd.oasis.opendocument.image base64 +odm application/vnd.oasis.opendocument.text-master base64 +odp application/vnd.oasis.opendocument.presentation base64 +ods application/vnd.oasis.opendocument.spreadsheet base64 +odt application/vnd.oasis.opendocument.text base64 +oex application/x-opera-extension base64 +oga audio/ogg base64 +ogg audio/ogg base64 +ogv video/ogg base64 +ogx application/ogg base64 +omdoc application/omdoc+xml base64 +onepkg application/onenote base64 +onetmp application/onenote base64 +onetoc application/onenote base64 +onetoc2 application/onenote base64 +opf application/oebps-package+xml base64 +opml text/x-opml quoted-printable +oprc application/vnd.palm base64 +opus audio/ogg base64 +orf image/x-olympus-orf base64 +org application/vnd.lotus-organizer base64 +osf application/vnd.yamaha.openscoreformat base64 +osfpvg application/vnd.yamaha.openscoreformat.osfpvg+xml base64 +otc application/vnd.oasis.opendocument.chart-template base64 +otf font/otf base64 +otg application/vnd.oasis.opendocument.graphics-template base64 +oth application/vnd.oasis.opendocument.text-web base64 +oti application/vnd.oasis.opendocument.image-template base64 +otp application/vnd.oasis.opendocument.presentation-template base64 +ots application/vnd.oasis.opendocument.spreadsheet-template base64 +ott application/vnd.oasis.opendocument.text-template base64 +oxps application/oxps base64 +oxt application/vnd.openofficeorg.extension base64 +p text/x-pascal quoted-printable +p10 application/pkcs10 base64 +p12 application/x-pkcs12 base64 +p7b application/x-pkcs7-certificates base64 +p7c application/pkcs7-mime base64 +p7m application/pkcs7-mime base64 +p7r application/x-pkcs7-certreqresp base64 +p7s application/pkcs7-signature base64 +p8 application/pkcs8 base64 +pac application/x-ns-proxy-autoconfig base64 +pages application/x-iwork-pages-sffpages base64 +pas text/x-pascal quoted-printable +paw application/vnd.pawaafile base64 +pbd application/vnd.powerbuilder6 base64 +pbm image/x-portable-bitmap base64 +pcap application/vnd.tcpdump.pcap base64 +pcf application/x-font-pcf base64 +pcl application/vnd.hp-PCL base64 +pclxl application/vnd.hp-PCLXL base64 +pct image/x-pict base64 +pcurl application/vnd.curl.pcurl base64 +pcx image/x-pcx base64 +pdb application/vnd.palm base64 +pdf application/pdf base64 +pef image/x-pentax-pef base64 +pfa application/x-font-type1 base64 +pfb application/x-font-type1 base64 +pfm application/x-font-type1 base64 +pfr application/font-tdpfr base64 +pfx application/x-pkcs12 base64 +pgb image/vnd.globalgraphics.pgb base64 +pgm image/x-portable-graymap base64 +pgn application/x-chess-pgn base64 +pgp application/octet-stream base64 +php application/x-httpd-php 8bit +pht application/x-httpd-php 8bit +phtml application/x-httpd-php 8bit +pic image/x-pict base64 +pkd application/vnd.hbci base64 +pkg application/octet-stream base64 +pki application/pkixcmp base64 +pkipath application/pkix-pkipath base64 +pkpass application/vnd.apple.pkpass base64 +pl application/x-perl 8bit +plb application/vnd.3gpp.pic-bw-large base64 +plc application/vnd.Mobius.PLC base64 +plf application/vnd.pocketlearn base64 +plj audio/vnd.everad.plj base64 +pls application/pls+xml base64 +plt application/vnd.hp-HPGL base64 +pm application/x-pagemaker base64 +pm5 application/x-pagemaker base64 +pml application/vnd.ctc-posml base64 +png image/png base64 +pnm image/x-portable-anymap base64 +portpkg application/vnd.macports.portpkg base64 +pot application/vnd.ms-powerpoint base64 +potm application/vnd.ms-powerpoint.template.macroEnabled.12 base64 +potx application/vnd.openxmlformats-officedocument.presentationml.template base64 +ppam application/vnd.ms-powerpoint.addin.macroEnabled.12 base64 +ppd application/vnd.cups-ppd base64 +ppm image/x-portable-pixmap base64 +pps application/vnd.ms-powerpoint base64 +ppsm application/vnd.ms-powerpoint.slideshow.macroEnabled.12 base64 +ppsx application/vnd.openxmlformats-officedocument.presentationml.slideshow base64 +ppt application/vnd.ms-powerpoint base64 +pptm application/vnd.ms-powerpoint.presentation.macroEnabled.12 base64 +pptx application/vnd.openxmlformats-officedocument.presentationml.presentation base64 +pqa application/vnd.palm base64 +prc application/vnd.palm base64 +pre application/vnd.lotus-freelance base64 +prf application/pics-rules base64 +ps application/postscript 8bit +ps1 application/x-msdos-program base64 +psb application/vnd.3gpp.pic-bw-small base64 +psd image/vnd.adobe.photoshop base64 +psf application/x-font-linux-psf base64 +pskcxml application/pskc+xml base64 +psp image/x-paintshoppro base64 +pspimage image/x-paintshoppro base64 +pt5 application/x-pagemaker base64 +pti application/vnd.pvi.ptid1 base64 +ptid application/vnd.pvi.ptid1 base64 +pub application/x-mspublisher base64 +pvb application/vnd.3gpp.pic-bw-var base64 +pwn application/vnd.3M.Post-it-Notes base64 +py application/x-python 8bit +pya audio/vnd.ms-playready.media.pya base64 +pyv video/vnd.ms-playready.media.pyv base64 +qam application/vnd.epson.quickanime base64 +qbo application/vnd.intu.qbo base64 +qcp audio/vnd.qcelp base64 +qfx application/vnd.intu.qfx base64 +qps application/vnd.publishare-delta-tree base64 +qt video/quicktime base64 +qtl application/x-quicktimeplayer base64 +qwd application/vnd.Quark.QuarkXPress 8bit +qwt application/vnd.Quark.QuarkXPress 8bit +qxb application/vnd.Quark.QuarkXPress 8bit +qxd application/vnd.Quark.QuarkXPress 8bit +qxl application/vnd.Quark.QuarkXPress 8bit +qxt application/vnd.Quark.QuarkXPress 8bit +ra audio/x-pn-realaudio base64 +raf image/x-fuji-raf base64 +ram audio/x-pn-realaudio base64 +rar application/x-rar-compressed base64 +ras image/x-cmu-raster base64 +raw image/x-panasonic-raw base64 +rb application/x-ruby 8bit +rbw application/x-ruby 8bit +rcprofile application/vnd.ipunplugged.rcprofile base64 +rct application/prs.nprend base64 +rdf application/rdf+xml 8bit +rdz application/vnd.data-vision.rdz base64 +reg application/x-msdos-program base64 +rep application/vnd.businessobjects base64 +req application/vnd.nervana base64 +request application/vnd.nervana base64 +res application/x-dtbresource+xml base64 +rgb image/x-rgb base64 +rhtml application/x-html+ruby 8bit +rif application/reginfo+xml base64 +rip audio/vnd.rip base64 +ris application/x-research-info-systems base64 +rl application/resource-lists+xml base64 +rlc image/vnd.fujixerox.edmics-rlc base64 +rld application/resource-lists-diff+xml base64 +rm application/vnd.rn-realmedia base64 +rmi audio/midi base64 +rmp audio/x-pn-realaudio-plugin base64 +rms application/vnd.jcp.javame.midlet-rms base64 +rmvb application/vnd.rn-realmedia-vbr base64 +rnc application/relax-ng-compact-syntax base64 +rnd application/prs.nprend base64 +roa application/rpki-roa base64 +roff text/troff 8bit +rp9 application/vnd.cloanto.rp9 base64 +rpm audio/x-pn-realaudio-plugin base64 +rpss application/vnd.nokia.radio-presets base64 +rpst application/vnd.nokia.radio-preset base64 +rq application/sparql-query base64 +rs application/rls-services+xml base64 +rsd application/rsd+xml base64 +rss application/rss+xml base64 +rst text/plain quoted-printable +rtf application/rtf base64 +rtx text/richtext 8bit +s text/x-asm quoted-printable +s11 video/vnd.sealed.mpeg1 base64 +s14 video/vnd.sealed.mpeg4 base64 +s1a application/vnd.sealedmedia.softseal.pdf base64 +s1e application/vnd.sealed.xls base64 +s1h application/vnd.sealedmedia.softseal.html base64 +s1m audio/vnd.sealedmedia.softseal.mpeg base64 +s1p application/vnd.sealed.ppt base64 +s1q video/vnd.sealedmedia.softseal.mov base64 +s1w application/vnd.sealed.doc base64 +s3m audio/s3m base64 +saf application/vnd.yamaha.smaf-audio base64 +sav application/x-spss base64 +sbml application/sbml+xml base64 +sbs application/x-spss base64 +sc application/vnd.ibm.secure-container base64 +scd application/x-msschedule base64 +scm application/vnd.lotus-screencam base64 +scq application/scvp-cv-request base64 +scs application/scvp-cv-response base64 +scurl text/vnd.curl.scurl quoted-printable +sda application/vnd.stardivision.draw base64 +sdc application/vnd.stardivision.calc base64 +sdd application/vnd.stardivision.impress base64 +sdf application/vnd.Kinar base64 +sdkd application/vnd.solent.sdkm+xml base64 +sdkm application/vnd.solent.sdkm+xml base64 +sdo application/vnd.sealed.doc base64 +sdoc application/vnd.sealed.doc base64 +sdp application/sdp base64 +sds application/vnd.stardivision.chart base64 +sdw application/vnd.stardivision.writer base64 +see application/vnd.seemail base64 +seed application/vnd.fdsn.seed base64 +sem application/vnd.sealed.eml base64 +sema application/vnd.sema base64 +semd application/vnd.semd base64 +semf application/vnd.semf base64 +seml application/vnd.sealed.eml base64 +ser application/java-serialized-object base64 +setpay application/set-payment-initiation base64 +setreg application/set-registration-initiation base64 +sfd-hdstx application/vnd.hydrostatix.sof-data base64 +sfs application/vnd.spotfire.sfs base64 +sfv text/x-sfv quoted-printable +sgi image/sgi base64 +sgl application/vnd.stardivision.writer-global base64 +sgm text/sgml quoted-printable +sgml application/sgml base64 +sh application/x-sh 8bit +shar application/x-shar 8bit +shf application/shf+xml base64 +shtml text/html 8bit +si text/vnd.wap.si quoted-printable +sic application/vnd.wap.sic base64 +sid image/x-mrsid-image base64 +sig application/pgp-signature base64 +sil audio/silk base64 +silo model/mesh base64 +sis application/vnd.symbian.install base64 +sisx application/vnd.symbian.install base64 +sit application/x-stuffit base64 +sitx application/x-stuffitx base64 +siv application/sieve base64 +sj application/javascript 8bit +skd application/vnd.koan base64 +skm application/vnd.koan base64 +skp application/vnd.koan base64 +skt application/vnd.koan base64 +sl text/vnd.wap.sl quoted-printable +slc application/vnd.wap.slc base64 +sldm application/vnd.ms-powerpoint.slide.macroEnabled.12 base64 +sldx application/vnd.openxmlformats-officedocument.presentationml.slide base64 +slt application/vnd.epson.salt base64 +sm application/vnd.stepmania.stepchart base64 +smf application/vnd.stardivision.math base64 +smh application/vnd.sealed.mht base64 +smht application/vnd.sealed.mht base64 +smi application/smil+xml 8bit +smil application/smil+xml 8bit +smo video/vnd.sealedmedia.softseal.mov base64 +smov video/vnd.sealedmedia.softseal.mov base64 +smp audio/vnd.sealedmedia.softseal.mpeg base64 +smp3 audio/vnd.sealedmedia.softseal.mpeg base64 +smpg video/vnd.sealed.mpeg4 base64 +sms application/vnd.3gpp.sms base64 +smv audio/SMV base64 +smzip application/vnd.stepmania.package base64 +snd audio/basic base64 +snf application/x-font-snf base64 +so application/octet-stream base64 +soc application/sgml-open-catalog base64 +spc application/x-pkcs7-certificates base64 +spd application/vnd.sealedmedia.softseal.pdf base64 +spdf application/vnd.sealedmedia.softseal.pdf base64 +spf application/vnd.yamaha.smaf-phrase base64 +spl application/x-futuresplash base64 +spo application/x-spss base64 +spot text/vnd.in3d.spot quoted-printable +spp application/scvp-vp-response base64 +sppt application/vnd.sealed.ppt base64 +spq application/scvp-vp-request base64 +sps application/x-spss base64 +spx audio/ogg base64 +sql application/x-sql base64 +sr2 image/x-sony-sr2 base64 +src application/x-wais-source base64 +srf image/x-sony-srf base64 +srt application/x-subrip base64 +sru application/sru+xml base64 +srx application/sparql-results+xml base64 +ssdl application/ssdl+xml base64 +sse application/vnd.kodak-descriptor base64 +ssf application/vnd.epson.ssf base64 +ssml application/ssml+xml base64 +ssw video/vnd.sealed.swf base64 +sswf video/vnd.sealed.swf base64 +st application/vnd.sailingtracker.track base64 +stc application/vnd.sun.xml.calc.template base64 +std application/vnd.sun.xml.draw.template base64 +stf application/vnd.wt.stf base64 +sti application/vnd.sun.xml.impress.template base64 +stk application/hyperstudio base64 +stl application/vnd.ms-pki.stl base64 +stm application/vnd.sealedmedia.softseal.html base64 +stml application/vnd.sealedmedia.softseal.html base64 +str application/vnd.pg.format base64 +stw application/vnd.sun.xml.writer.template base64 +sub image/vnd.dvb.subtitle base64 +sus application/vnd.sus-calendar base64 +susp application/vnd.sus-calendar base64 +sv4cpio application/x-sv4cpio base64 +sv4crc application/x-sv4crc base64 +svc application/vnd.dvb.service base64 +svd application/vnd.svd base64 +svg image/svg+xml 8bit +svgz image/svg+xml 8bit +swa application/x-director base64 +swf application/x-shockwave-flash base64 +swi application/vnd.aristanetworks.swi base64 +sxc application/vnd.sun.xml.calc base64 +sxd application/vnd.sun.xml.draw base64 +sxg application/vnd.sun.xml.writer.global base64 +sxi application/vnd.sun.xml.impress base64 +sxl application/vnd.sealed.xls base64 +sxls application/vnd.sealed.xls base64 +sxm application/vnd.sun.xml.math base64 +sxw application/vnd.sun.xml.writer base64 +t text/troff 8bit +t3 application/x-t3vm-image base64 +taglet application/vnd.mynfc base64 +tao application/vnd.tao.intent-module-archive base64 +tar application/x-tar base64 +tbk application/x-toolbook base64 +tbz application/x-gtar base64 +tbz2 application/x-gtar base64 +tcap application/vnd.3gpp2.tcap base64 +tcl application/x-tcl 8bit +teacher application/vnd.smart.teacher base64 +tei application/tei+xml base64 +teicorpus application/tei+xml base64 +tex application/x-tex 8bit +texi application/x-texinfo 8bit +texinfo application/x-texinfo 8bit +text text/plain quoted-printable +textile text/plain quoted-printable +tfi application/thraud+xml base64 +tfm application/x-tex-tfm base64 +tga image/x-targa base64 +tgz application/x-gtar base64 +thmx application/vnd.ms-officetheme base64 +tif image/tiff base64 +tiff image/tiff base64 +tmo application/vnd.tmobile-livetv base64 +torrent application/x-bittorrent base64 +tpl application/vnd.groove-tool-template base64 +tpt application/vnd.trid.tpt base64 +tr text/troff 8bit +tra application/vnd.trueapp base64 +trm application/x-msterminal base64 +troff text/troff 8bit +ts video/MP2T base64 +tsd application/timestamped-data base64 +tsv text/tab-separated-values quoted-printable +ttc font/collection base64 +ttf font/ttf base64 +ttl text/turtle quoted-printable +twd application/vnd.SimTech-MindMapper base64 +twds application/vnd.SimTech-MindMapper base64 +txd application/vnd.genomatix.tuxedo base64 +txf application/vnd.Mobius.TXF base64 +txt text/plain quoted-printable +u32 application/x-authorware-bin base64 +udeb application/x-debian-package base64 +ufd application/vnd.ufdl base64 +ufdl application/vnd.ufdl base64 +ulx application/x-glulx base64 +umj application/vnd.umajin base64 +unityweb application/vnd.unity base64 +uoml application/vnd.uoml+xml base64 +upa application/vnd.hbci base64 +uri text/uri-list quoted-printable +uris text/uri-list quoted-printable +urls text/uri-list quoted-printable +ustar application/x-ustar base64 +utz application/vnd.uiq.theme base64 +uu text/x-uuencode quoted-printable +uva audio/vnd.dece.audio base64 +uvd application/vnd.dece.data base64 +uvf application/vnd.dece.data base64 +uvg image/vnd.dece.graphic base64 +uvh video/vnd.dece.hd base64 +uvi image/vnd.dece.graphic base64 +uvm video/vnd.dece.mobile base64 +uvp video/vnd.dece.pd base64 +uvs video/vnd.dece.sd base64 +uvt application/vnd.dece.ttml+xml base64 +uvu video/vnd.uvvu.mp4 base64 +uvv video/vnd.dece.video base64 +uvva audio/vnd.dece.audio base64 +uvvd application/vnd.dece.data base64 +uvvf application/vnd.dece.data base64 +uvvg image/vnd.dece.graphic base64 +uvvh video/vnd.dece.hd base64 +uvvi image/vnd.dece.graphic base64 +uvvm video/vnd.dece.mobile base64 +uvvp video/vnd.dece.pd base64 +uvvs video/vnd.dece.sd base64 +uvvt application/vnd.dece.ttml+xml base64 +uvvu video/vnd.uvvu.mp4 base64 +uvvv video/vnd.dece.video base64 +uvvx application/vnd.dece.unspecified base64 +uvvz application/vnd.dece.zip base64 +uvx application/vnd.dece.unspecified base64 +uvz application/vnd.dece.zip base64 +vbk audio/vnd.nortel.vbk base64 +vbs application/x-msdos-program base64 +vcard text/vcard quoted-printable +vcd application/x-cdlink base64 +vcf text/x-vcard 8bit +vcg application/vnd.groove-vcard base64 +vcs text/x-vcalendar 8bit +vcx application/vnd.vcx base64 +vis application/vnd.visionary base64 +viv video/vnd.vivo base64 +vivo video/vnd.vivo base64 +vob video/x-ms-vob base64 +vor application/vnd.stardivision.writer base64 +vox application/x-authorware-bin base64 +vrml model/vrml base64 +vsc application/vnd.vidsoft.vidconference 8bit +vsd application/vnd.visio base64 +vsf application/vnd.vsf base64 +vss application/vnd.visio base64 +vst application/vnd.visio base64 +vsw application/vnd.visio base64 +vtt text/vtt quoted-printable +vtu model/vnd.vtu base64 +vxml application/voicexml+xml base64 +w3d application/x-director base64 +wad application/x-doom base64 +wasm application/wasm 8bit +wav audio/wav base64 +wax audio/x-ms-wax base64 +wbmp image/vnd.wap.wbmp base64 +wbs application/vnd.criticaltools.wbs+xml base64 +wbxml application/vnd.wap.wbxml base64 +wcm application/vnd.ms-works base64 +wdb application/vnd.ms-works base64 +wdp image/vnd.ms-photo base64 +weba audio/webm base64 +webapp application/x-web-app-manifest+json base64 +webm audio/webm base64 +webmanifest application/manifest+json base64 +webp image/webp base64 +wg application/vnd.pmi.widget base64 +wgt application/widget base64 +wif application/watcherinfo+xml base64 +wk application/x-123 base64 +wks application/vnd.lotus-1-2-3 base64 +wkz application/x-Wingz base64 +wm video/x-ms-wm base64 +wma audio/x-ms-wma base64 +wmd application/x-ms-wmd base64 +wmf application/x-msmetafile base64 +wml text/vnd.wap.wml quoted-printable +wmlc application/vnd.wap.wmlc base64 +wmls text/vnd.wap.wmlscript quoted-printable +wmlsc application/vnd.wap.wmlscriptc base64 +wmv audio/x-ms-wmv base64 +wmx video/x-ms-wmx base64 +wmz application/x-ms-wmz base64 +woff font/woff base64 +woff2 font/woff2 base64 +wp application/wordperfect5.1 base64 +wp5 application/wordperfect5.1 base64 +wp6 application/x-wordperfect6.1 base64 +wpd application/vnd.wordperfect base64 +wpl application/vnd.ms-wpl base64 +wps application/vnd.ms-works base64 +wqd application/vnd.wqd base64 +wrd application/msword base64 +wri application/x-mswrite base64 +wrl model/vrml base64 +wsdl application/wsdl+xml base64 +wspolicy application/wspolicy+xml base64 +wtb application/vnd.webturbo base64 +wv application/vnd.wv.csp+wbxml base64 +wvx video/x-ms-wvx base64 +wz application/x-Wingz base64 +x32 application/x-authorware-bin base64 +x3d model/x3d+xml base64 +x3db model/x3d+binary base64 +x3dbz model/x3d+binary base64 +x3dv model/x3d+vrml base64 +x3dvz model/x3d+vrml base64 +x3dz model/x3d+xml base64 +x3f image/x-sigma-x3f base64 +x_b model/vnd.parasolid.transmit.binary base64 +x_t model/vnd.parasolid.transmit.text quoted-printable +xaml application/xaml+xml base64 +xap application/x-silverlight-app base64 +xar application/vnd.xara base64 +xbap application/x-ms-xbap base64 +xbd application/vnd.fujixerox.docuworks.binder base64 +xbm image/x-xbitmap 7bit +xcf image/x-xcf base64 +xcfbz2 image/x-compressed-xcf base64 +xcfgz image/x-compressed-xcf base64 +xdf application/xcap-diff+xml base64 +xdm application/vnd.syncml.dm+xml base64 +xdp application/vnd.adobe.xdp+xml base64 +xdssc application/dssc+xml base64 +xdw application/vnd.fujixerox.docuworks base64 +xenc application/xenc+xml base64 +xer application/patch-ops-error+xml base64 +xfdf application/vnd.adobe.xfdf base64 +xfdl application/vnd.xfdl base64 +xht application/xhtml+xml 8bit +xhtml application/xhtml+xml 8bit +xhvml application/xv+xml base64 +xif image/vnd.xiff base64 +xla application/vnd.ms-excel base64 +xlam application/vnd.ms-excel.addin.macroEnabled.12 base64 +xlc application/vnd.ms-excel base64 +xlf application/x-xliff+xml base64 +xlm application/vnd.ms-excel base64 +xls application/vnd.ms-excel base64 +xlsb application/vnd.ms-excel.sheet.binary.macroEnabled.12 base64 +xlsm application/vnd.ms-excel.sheet.macroEnabled.12 base64 +xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet base64 +xlt application/vnd.ms-excel base64 +xltm application/vnd.ms-excel.template.macroEnabled.12 base64 +xltx application/vnd.openxmlformats-officedocument.spreadsheetml.template base64 +xlw application/vnd.ms-excel base64 +xm audio/xm base64 +xml application/xml 8bit +xmt_bin model/vnd.parasolid.transmit.binary base64 +xmt_txt model/vnd.parasolid.transmit.text quoted-printable +xo application/vnd.olpc-sugar base64 +xop application/xop+xml base64 +xpi application/x-xpinstall base64 +xpl application/xproc+xml base64 +xpm image/x-xpixmap 8bit +xpr application/vnd.is-xpr base64 +xps application/vnd.ms-xpsdocument 8bit +xpw application/vnd.intercon.formnet base64 +xpx application/vnd.intercon.formnet base64 +xsd text/xml 8bit +xsl application/xml 8bit +xslt application/xslt+xml base64 +xsm application/vnd.syncml+xml base64 +xspf application/xspf+xml base64 +xul application/vnd.mozilla.xul+xml base64 +xvm application/xv+xml base64 +xvml application/xv+xml base64 +xwd image/x-xwindowdump base64 +xyz x-chemical/x-xyz base64 +xz application/x-xz base64 +yaml text/x-yaml 8bit +yang application/yang base64 +yin application/yin+xml base64 +yml text/x-yaml 8bit +z application/x-compressed base64 +z1 application/x-zmachine base64 +z2 application/x-zmachine base64 +z3 application/x-zmachine base64 +z4 application/x-zmachine base64 +z5 application/x-zmachine base64 +z6 application/x-zmachine base64 +z7 application/x-zmachine base64 +z8 application/x-zmachine base64 +zaz application/vnd.zzazz.deck+xml base64 +zip application/zip base64 +zir application/vnd.zul base64 +zirz application/vnd.zul base64 +zmm application/vnd.HandHeld-Entertainment+xml base64 diff --git a/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/lib/mini_mime.rb b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/lib/mini_mime.rb new file mode 100644 index 00000000..eadde552 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/lib/mini_mime.rb @@ -0,0 +1,186 @@ +# frozen_string_literal: true +require "mini_mime/version" +require "thread" + +module MiniMime + def self.lookup_by_filename(filename) + Db.lookup_by_filename(filename) + end + + def self.lookup_by_extension(extension) + Db.lookup_by_extension(extension) + end + + def self.lookup_by_content_type(mime) + Db.lookup_by_content_type(mime) + end + + module Configuration + class << self + attr_accessor :ext_db_path + attr_accessor :content_type_db_path + end + + self.ext_db_path = File.expand_path("../db/ext_mime.db", __FILE__) + self.content_type_db_path = File.expand_path("../db/content_type_mime.db", __FILE__) + end + + class Info + BINARY_ENCODINGS = %w(base64 8bit) + + attr_accessor :extension, :content_type, :encoding + + def initialize(buffer) + @extension, @content_type, @encoding = buffer.split(/\s+/).map!(&:freeze) + end + + def [](idx) + if idx == 0 + @extension + elsif idx == 1 + @content_type + elsif idx == 2 + @encoding + end + end + + def binary? + BINARY_ENCODINGS.include?(encoding) + end + end + + class Db + def self.lookup_by_filename(filename) + extension = File.extname(filename) + return if extension.empty? + extension = extension[1..-1] + lookup_by_extension(extension) + end + + def self.lookup_by_extension(extension) + @db ||= new + @db.lookup_by_extension(extension) || + @db.lookup_by_extension(extension.downcase) + end + + def self.lookup_by_content_type(content_type) + @db ||= new + @db.lookup_by_content_type(content_type) + end + + class Cache + def initialize(size) + @size = size + @hash = {} + end + + def []=(key, val) + rval = @hash[key] = val + @hash.shift if @hash.length > @size + rval + end + + def fetch(key, &blk) + @hash.fetch(key, &blk) + end + end + + if ::File.method_defined?(:pread) + PReadFile = ::File + else + # For Windows support + class PReadFile + def initialize(filename) + @mutex = Mutex.new + # We must open the file in binary mode + # otherwise Ruby's automatic line terminator + # translation will skew the row size + @file = ::File.open(filename, 'rb') + end + + def readline(*args) + @file.readline(*args) + end + + def pread(size, offset) + @mutex.synchronize do + @file.seek(offset, IO::SEEK_SET) + @file.read(size) + end + end + end + end + + class RandomAccessDb + MAX_CACHED = 100 + + def initialize(path, sort_order) + @path = path + @file = PReadFile.new(@path) + + @row_length = @file.readline("\n").length + @file_length = File.size(@path) + @rows = @file_length / @row_length + + @hit_cache = Cache.new(MAX_CACHED) + @miss_cache = Cache.new(MAX_CACHED) + + @sort_order = sort_order + end + + def lookup(val) + @hit_cache.fetch(val) do + @miss_cache.fetch(val) do + data = lookup_uncached(val) + if data + @hit_cache[val] = data + else + @miss_cache[val] = nil + end + + data + end + end + end + + # lifted from marcandre/backports + def lookup_uncached(val) + from = 0 + to = @rows - 1 + result = nil + + while from <= to do + midpoint = from + (to - from).div(2) + current = resolve(midpoint) + data = current[@sort_order] + if data > val + to = midpoint - 1 + elsif data < val + from = midpoint + 1 + else + result = current + break + end + end + result + end + + def resolve(row) + Info.new(@file.pread(@row_length, row * @row_length).force_encoding(Encoding::UTF_8)) + end + end + + def initialize + @ext_db = RandomAccessDb.new(Configuration.ext_db_path, 0) + @content_type_db = RandomAccessDb.new(Configuration.content_type_db_path, 1) + end + + def lookup_by_extension(extension) + @ext_db.lookup(extension) + end + + def lookup_by_content_type(content_type) + @content_type_db.lookup(content_type) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/lib/mini_mime/version.rb b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/lib/mini_mime/version.rb new file mode 100644 index 00000000..968cf0b4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/lib/mini_mime/version.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true +module MiniMime + VERSION = "1.1.5" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/mini_mime.gemspec b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/mini_mime.gemspec new file mode 100644 index 00000000..2ab398f3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/mini_mime-1.1.5/mini_mime.gemspec @@ -0,0 +1,31 @@ +# coding: utf-8 +# frozen_string_literal: true +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'mini_mime/version' + +Gem::Specification.new do |spec| + spec.name = "mini_mime" + spec.version = MiniMime::VERSION + spec.authors = ["Sam Saffron"] + spec.email = ["sam.saffron@gmail.com"] + + spec.summary = %q{A minimal mime type library} + spec.description = %q{A minimal mime type library} + spec.homepage = "https://github.com/discourse/mini_mime" + spec.license = "MIT" + + spec.files = `git ls-files -z`.split("\x0").reject do |f| + f.match(%r{^(test|spec|features)/}) + end + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + spec.required_ruby_version = ">= 2.6.0" + + spec.add_development_dependency "bundler" + spec.add_development_dependency "rake" + spec.add_development_dependency "minitest" + spec.add_development_dependency "rubocop" + spec.add_development_dependency "rubocop-discourse" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/.autotest b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/.autotest new file mode 100644 index 00000000..b6fbce53 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/.autotest @@ -0,0 +1,34 @@ +# -*- ruby -*- + +require 'autotest/restart' +require 'autotest/rcov' if ENV['RCOV'] + +Autotest.add_hook :initialize do |at| + at.testlib = 'minitest/autorun' + + bench_tests = %w(TestMinitestBenchmark) + mock_tests = %w(TestMinitestMock TestMinitestStub) + spec_tests = %w(TestMinitestReporter TestMetaStatic TestMeta + TestSpecInTestCase) + unit_tests = %w(TestMinitestGuard TestMinitestRunnable + TestMinitestRunner TestMinitestTest TestMinitestUnit + TestMinitestUnitInherited TestMinitestUnitOrder + TestMinitestUnitRecording TestMinitestUnitTestCase) + + { + bench_tests => "test/minitest/test_minitest_benchmark.rb", + mock_tests => "test/minitest/test_minitest_mock.rb", + spec_tests => "test/minitest/test_minitest_reporter.rb", + unit_tests => "test/minitest/test_minitest_unit.rb", + }.each do |klasses, file| + klasses.each do |klass| + at.extra_class_map[klass] = file + end + end + + at.add_exception 'coverage.info' + at.add_exception 'coverage' +end + +# require 'autotest/rcov' +# Autotest::RCov.command = 'rcov_info' diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/History.rdoc b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/History.rdoc new file mode 100644 index 00000000..c6e8946a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/History.rdoc @@ -0,0 +1,1690 @@ +=== 5.25.5 / 2025-03-12 + +* 4 bug fixes: + + * Bumped minimum ruby to 2.7. + * Fixed expectation docs for must/wont_pattern_match. (jaredcwhite) + * Reorder Minitest::Test.ancestors to allow reaching Minitest::Assertions#skipped? (Edouard-chin) + * Update the ruby and rails compatibility tables. (bquorning) + +=== 5.25.4 / 2024-12-03 + +* 1 bug fix: + + * Fix for must_verify definition if only requiring minitest/mock (but why?). + +=== 5.25.3 / 2024-12-03 + +* 5 bug fixes: + + * Fixed assert_mock to fail instead of raise on unmet mock expectations. + * Fixed assert_mock to take an optional message argument. + * Fixed formatting of unmet mock expectation messages. + * Fixed missing must_verify expectation to match assert_mock. + * minitest/pride: Fixed to use true colors with *-direct terminals (bk2204) + +=== 5.25.2 / 2024-11-21 + +* 4 bug fixes: + + * Include class name in spec name. (thomasmarshall) + * Fixed 'redefining object_id' warning from ruby 3.4. (mattbrictson) + * Minitest top-level namespace no longer includes entire contents of README.rdoc. Too much! + * Refactored spec's describe to more cleanly determine the superclass and name + +=== 5.25.1 / 2024-08-16 + +* 2 bug fixes: + + * Fix incompatibility caused by minitest-hooks & rails invading minitest internals. + * Revert change from =~ to match? to allow for nil if $TERM undefined. + +=== 5.25.0 / 2024-08-13 + +* 2 minor enhancements: + + * Fixed some inefficiencies filtering and matching (mostly backtraces). + * Refactored siginfo handler to reduce runtime costs. Saved ~30%! + +* 5 bug fixes: + + * Added missing rdoc to get back to 100% coverage. + * Cleaning up ancient code checking for defined?(Encoding) and the like. + * Disambiguated some shadowed variables in minitest/compress. + * Fixed an ironic bug if using string-literals AND Werror. + * Improve description of test:slow task. (stomar) + +=== 5.24.1 / 2024-06-29 + +* 1 bug fix: + + * Fix the error message when an extension is invalid value. (y-yagi) + +=== 5.24.0 / 2024-06-18 + +* 2 minor enhancements: + + * Added Minitest.register_plugin. + * Extended plugin system to work with modules/classes for opt-out plugins. + +* 1 bug fix: + + * Removed anacronism, but allow load_plugins to exit gracefully if --disable=gems. + +=== 5.23.1 / 2024-05-21 + +* 1 bug fix: + + * Fully qualify the Queue class to avoid conflicts with other libraries. (rafaelfranca) + +=== 5.23.0 / 2024-05-15 + +* 3 minor enhancements: + + * Added -Werror to raise on any warning output. (byroot) + * Added UnexpectedWarning as a failure summary type, added count to output if activated. + * Added minitest/manual_plugins.rb w/ new Minitest.load method. (tenderlove) + +* 2 bug fixes: + + * Allow empty_run! and reporter to display summary for empty runs. (zzak) + * Make test task verbose using either rake's -v or -t (was just -t). + +=== 5.22.3 / 2024-03-13 + +* 1 minor enhancement: + + * MASSIVE improvement of minitest's pride plugin output: Frequencies doubled! Sine waves shifted!! Comments improved!!! Colors rotated!!!! (havenwood) + +* 3 bug fixes: + + * Improved wording on Minitest::Test#parallelize_me! to clarify it goes INSIDE your test class/describe. + * Minor changes to tests to pass when tests ran with extra flags (eg -p). + * Support Ruby 3.4's new error message format. (mame) + +=== 5.22.2 / 2024-02-07 + +* 1 bug fix: + + * Third time's a charm? Remember: 'ensure' is almost always the + wrong way to go (for results... it's great for cleaning up). + +=== 5.22.1 / 2024-02-06 + +* 1 bug fix: + + * Don't exit non-zero if no tests ran and no filter (aka, the test file is empty). + (I'm starting to think the exit 1 thing for @tenderlove was a mistake...) + +=== 5.22.0 / 2024-02-05 + +* 1 minor enhancement: + + * Added "did you mean" output if your --name filter matches nothing. (tenderlove) + +* 2 bug fixes: + + * Big cleanup of test filtering. Much prettier / more functional. + * Fix situation where Assertion#location can't find the location. (pftg) + +=== 5.21.2 / 2024-01-17 + +* 1 bug fix: + + * Fixed bug in Minitest::Compress#compress formatting w/ nested patterns. Now recurses properly. + +=== 5.21.1 / 2024-01-11 + +* 1 bug fix: + + * Rails' default backtrace filter can't currently work with caller_locations, so reverting back to caller. + +=== 5.21.0 / 2024-01-11 + +* 10 minor enhancements: + + * Add include_all kw arg to assert_respond_to and refute_respond_to. + * Added --quiet flag to skip ProgressReporter (prints the dots). Minor speedup. + * Added Minitest::Compress#compress and added it to UnexpectedError. + * Added ability to initialize BacktraceFilter w/ custom regexp. + * Filter failure backtraces using backtrace_filter before calculating location. (thomasmarshall) + * Make BacktraceFilter#filter compatible with locations (still compares strings). + * Optimized Assertion#location ~30%. + * Output relative paths for all failures/errors/backtraces. + * Refactored location information in assertions, now using locations. + * Removed thread and mutex_m dependencies. (hsbt, eregon) + +* 2 bug fixes: + + * Drop undocumented bt arg in #skip. Dunno why that ever happened, prolly for testing? + * Fix mock to work with ruby debugger enabled. (keithlayne) + +=== 5.20.0 / 2023-09-06 + +* 1 minor enhancement: + + * Optionally allow autorun exit hook to remain active in forked child. (casperisfine) + +=== 5.19.0 / 2023-07-26 + +* 2 minor enhancements: + + * Add metadata lazy accessor to Runnable / Result. (matteeyah) + * Only load minitest/unit (aka ancient MiniTest compatibility layer) if \ENV[\"MT_COMPAT\"] + +* 1 bug fix: + + * Minitest::TestTask enthusiastically added itself to default. (ParadoxV5) + +=== 5.18.1 / 2023-06-16 + +* 3 bug fixes: + + * Avoid extra string allocations when filtering tests. (tenderlove) + * Only mention deprecated \ENV[\'N\'] if it is an integer string. + * Push up test_order to Minitest::Runnable to fix minitest/hell. (koic) + +=== 5.18.0 / 2023-03-04 + +* 2 major enhancements: + + * Added assert_pattern & refute_pattern for pattern matching. (flavorjones) + * Added matching must_pattern_match & wont_pattern_match to minitest/spec. + +* 1 bug fix: + + * Support the new message format of NameError in Ruby 3.3 (mame) + +=== 5.17.0 / 2022-12-31 + +* 1 minor enhancement: + + * Refactor setup hooks into a SETUP_METHODS constant. (MSP-Greg) + +* 3 bug fixes: + + * Fix kwargs for Mock calls to delegator. (blowmage) + * Fix kwargs for expectations. (bobmazanec, blowmage) + * Remove check for .b method. (tenderlove) + +=== 5.16.3 / 2022-08-17 + +* 2 bug fixes: + + * Fixed exception sanitization by removing TypeError restriction on rescue. + * Use A instead of deprecated TESTOPTS in rake test:slow. (davidstosik) + +=== 5.16.2 / 2022-07-03 + +* 4 bug fixes: + + * Added MT_KWARGS_HACK kludge for stub to deal with ruby 2.7 kwargs nastiness. (tsugimoto) + * In #expect, pop Hash class from args if $MT_KWARGS_HACK. (casperisfine) + * In above scenario, set expected kwargs (as Objects) based on actual kwargs. + * Nuke ivars if exception fails to marshal twice (eg better_errors). (irphilli) + +=== 5.16.1 / 2022-06-20 + +* 2 bug fixes: + + * Apparently adding real kwarg support to mocks/stubs broke some code. Fixed. + * Use `MT_KWARGS_HACK=1` to activate the kludgy kwargs support w/ caveats. + * Clarified some doco wrt the block on #stub. + +=== 5.16.0 / 2022-06-14 + +* 2 major enhancements: + + * Added Minitest::TestTask. + * Dropping ruby 2.2 - 2.5. 2.6 is DTM soon too. + +* 11 minor enhancements: + + * Added --show-skips option to show skips at end of run but not require --verbose. (MSP-Greg) + * Added Minitest.seed, the random seed used by the run. + * Calling `srand Minitest.seed` before all shuffles to ensure determinism. + * Extended #stub to handle kwargs for both block and call args. (SampsonCrowley) + * Extended Mock#__call to display kwargs. + * Extended Mock#expect to record kwargs. + * Extended Mock#method_missing to take kwargs & compare them against expected. + * Mock#method_missing displays better errors on arity mismatch. + * Removed minor optimization removing empty suites before run. + * Simplified test randomization (test order will change even with fixed seed). + * assert_match now returns the MatchData on success. (Nakilon) + +* 3 bug fixes: + + * (Re)Fixed marshalling of exceptions, neutering them in 2 passes. + * Fixed more problems with rdoc. + * Had to patch up mock and stub to deal with <=2.7 kwargs oddities + +=== 5.15.0 / 2021-12-14 + +* 1 major enhancement: + + * assert_throws returns the value returned, if any. (volmer) + +* 3 minor enhancements: + + * Added -S option to skip reporting of certain types of output + * Enable Ruby deprecation warnings by default. (casperisfine) + * Use Etc.nprocessors by default in order to maximize cpu usage. (tonytonyjan) + +* 6 bug fixes: + + * Close then unlink tempfiles on Windows. (nobu) + * Fixed #skip_until for windows paths. (MSP-Greg) + * Fixed a bunch of tests for jruby and windows. (MSP-Greg) + * Fixed marshalling of specs if they error. (tenderlove, jeremyevans, et al) + * Updated deprecation message for block expectations. (blowmage) + * Use Kernel.warn directly in expectations in case CUT defines their own warn. (firien) + +=== 5.14.4 / 2021-02-23 + +* 1 bug fix: + + * Fixed deprecation warning using stub with methods using keyword arguments. (Nakilon) + +=== 5.14.3 / 2021-01-05 + +* 1 bug fix: + + * Bumped require_ruby_version to < 4 (trunk = 3.1). + +=== 5.14.2 / 2020-08-31 + +* 1 bug fix: + + * Bumped ruby version to include 3.0 (trunk). + +=== 5.14.1 / 2020-05-15 + +* 3 minor enhancements: + + * Minitest.filter_backtrace returns original backtrace if filter comes back empty. + * Minitest::BacktraceFilter now returns entire backtrace if $MT_DEBUG set in env. + * Return true on a successful refute. (jusleg) + +* 1 bug fix: + + * Fixed expectation doco to not use global expectations. + +=== 5.14.0 / 2020-01-11 + +* 2 minor enhancements: + + * Block-assertions (eg assert_output) now error if raised inside the block. (casperisfine) + * Changed assert_raises to only catch Assertion since that covers Skip and friends. + +* 3 bug fixes: + + * Added example for value wrapper with block to Expectations module. (stomar) + * Fixed use of must/wont_be_within_delta on Expectation instance. (stomar) + * Renamed UnexpectedError#exception to #error to avoid problems with reraising. (casperisfine) + +=== 5.13.0 / 2019-10-29 + +* 9 minor enhancements: + + * Added Minitest::Guard#osx? + * Added examples to documentation for assert_raises. (lxxxvi) + * Added expectations #path_must_exist and #path_wont_exist. Not thrilled with the names. + * Added fail_after(year, month, day, msg) to allow time-bombing after a deadline. + * Added skip_until(year, month, day, msg) to allow deferring until a deadline. + * Deprecated Minitest::Guard#maglev? + * Deprecated Minitest::Guard#rubinius? + * Finally added assert_path_exists and refute_path_exists. (deivid-rodriguez) + * Refactored and pulled Assertions#things_to_diff out of #diff. (BurdetteLamar) + +* 3 bug fixes: + + * Fix autorun bug that affects fork exit status in tests. (dylanahsmith/jhawthorn) + * Improved documentation for _/value/expect, especially for blocks. (svoop) + * Support new Proc#to_s format. (ko1) + +=== 5.12.2 / 2019-09-28 + +* 1 bug fix: + + * After chatting w/ @y-yagi and others, decided to lower support to include ruby 2.2. + +=== 5.12.1 / 2019-09-28 + +* 1 minor enhancement: + + * Added documentation for Reporter classes. (sshaw) + +* 3 bug fixes: + + * Avoid using 'match?' to support older ruby versions. (y-yagi) + * Fixed broken link to reference on goodness-of-fit testing. (havenwood) + * Update requirements in readme and Rakefile/hoe spec. + +=== 5.12.0 / 2019-09-22 + +* 8 minor enhancements: + + * Added a descriptive error if assert_output or assert_raises called without a block. (okuramasafumi) + * Changed mu_pp_for_diff to make having both \n and \\n easier to debug. + * Deprecated $N for specifying number of parallel test runners. Use MT_CPU. + * Deprecated use of global expectations. To be removed from MT6. + * Extended Assertions#mu_pp to encoding validity output for strings to improve diffs. + * Extended Assertions#mu_pp to output encoding and validity if invalid to improve diffs. + * Extended Assertions#mu_pp_for_diff to make escaped newlines more obvious in diffs. + * Fail gracefully when expectation used outside of `it`. + +* 3 bug fixes: + + * Check \option[:filter] klass before match. Fixes 2.6 warning. (y-yagi) + * Fixed Assertions#diff from recalculating if set to nil + * Fixed spec section of readme to not use deprecated global expectations. (CheezItMan) + +=== 5.11.3 / 2018-01-26 + +* 1 bug fix: + + * Pushed #error? up to Reportable module. (composerinteralia) + +=== 5.11.2 / 2018-01-25 + +* 1 minor enhancement: + + * Reversed Test < Result. Back to < Runnable and using Reportable for shared code. + +* 2 bug fixes: + + * Fixed Result#location for instances of Test. (alexisbernard) + * Fixed deprecation message for Runnable#marshal_dump. (y-yagi) + +=== 5.11.1 / 2018-01-02 + +* 1 bug fix: + + * Fixed Result (a superclass of Test) overriding Runnable's name accessors. (y-yagi, MSP-Greg) + +=== 5.11.0 / 2018-01-01 + +* 2 major enhancements: + + * Added Minitest::Result and Minitest::Result.from(runnable). + * Changed Minitest::Test to subclass Result and refactored methods up. + +* 7 minor enhancements: + + * Added --no-plugins and MT_NO_PLUGINS to bypass MT plugin autoloading. Helps with bad actors installed globally. + * Added bench_performance_{logarithmic,power} for spec-style benchmarks. (rickhull) + * Added deprecation warning for Runnable#marshal_dump. + * Minitest.run_one_method now checks for instance of Result, not exact same class. + * Minitest::Test.run returns a Result version of self, not self. + * ProgressReporter#prerecord now explicitly prints klass.name. Allows for fakers. + +* 4 bug fixes: + + * Object.stub no longer calls the passed block if stubbed with a callable. + * Object.stub now passes blocks down to the callable result. + * Pushed Minitest::Test#time & #time_it up to Runnable. + * Test nil equality directly in assert_equal. Fixes #679. (voxik) + +=== 5.11.0b1 / 2017-12-20 + +* 2 major enhancements: + + * Added Minitest::Result and Minitest::Result.from(runnable). + * Changed Minitest::Test to subclass Result and refactored methods up. + +* 6 minor enhancements: + + * Added --no-plugins and MT_NO_PLUGINS to bypass MT plugin autoloading. Helps with bad actors installed globally. + * Added bench_performance_{logarithmic,power} for spec-style benchmarks. (rickhull) + * Minitest.run_one_method now checks for instance of Result, not exact same class. + * Minitest::Test.run returns a Result version of self, not self. + * ProgressReporter#prerecord now explicitly prints klass.name. Allows for fakers. + * Removed Runnable.marshal_dump/load. + +* 4 bug fixes: + + * Object.stub no longer calls the passed block if stubbed with a callable. + * Object.stub now passes blocks down to the callable result. + * Pushed Minitest::Test#time & #time_it up to Runnable. + * Test nil equality directly in assert_equal. Fixes #679. (voxik) + +=== 5.10.3 / 2017-07-21 + +* 1 minor enhancement: + + * Extended documentation for Mock#expect for multiple calls to mock object. (insti) + +* 2 bug fixes: + + * Finished off missing doco. + * Fixed verbose output on parallelize_me! classes. (chanks) + +=== 5.10.2 / 2017-05-09 + +* 1 minor enhancement: + + * Added suggestion in minitest/hell to install minitest/proveit. + +* 7 bug fixes: + + * Expand MT6 to Minitest 6. (xaviershay) + * Fixed location of assert_send deprecation. (rab) + * Fixed location of nil assert_equal deprecation to work with expectations. (jeremyevans) + * Fixed minitest/hell to use parallelize_me! (azul) + * Made deprecation use warn so -W0 will silence it. + * Workaround for rdoc nodoc generation bug that totally f'd up minitest doco. (Paxa) + * Write aggregated_results directly to the IO object to avoid mixed encoding errors. (tenderlove) + +=== 5.10.1 / 2016-12-01 + +* 1 bug fix: + + * Added a hack/kludge to deal with missing #prerecord on reporters that aren't properly subclassing AbstractReporter (I'm looking at you minitest-reporters) + +=== 5.10.0 / 2016-11-30 + +* 1 major enhancement: + + * Deprecated ruby 1.8, 1.9, possibly 2.0, assert_send, & old MiniTest namespace. + +* 3 minor enhancements: + + * Warn if assert_equal expects a nil. This will fail in minitest 6+. (tenderlove) + * Added AbstractReporter#prerecord and extended ProgressReporter and CompositeReporter to use it. + * Minor optimization: remove runnables with no runnable methods before run. + +* 3 bug fixes: + + * Fix assert_throw rescuing any NameError and ArgumentError. (waldyr) + * Clean up (most of the) last remaining vestiges of minitest/unit. + * 2.4: removed deprecation warnings when referring to Fixnum. + +=== 5.9.1 / 2016-09-25 + +* 2 bug fixes: + + * Re-release to refresh gem certificate signing. ugh. + * Fixed hoe/minitest to not augment load path if we're actually testing minitest. + +=== 5.9.0 / 2016-05-16 + +* 8 minor enhancements: + + * Added Minitest.info_signal accessors to customize signal for test run info. (nate) + * Added assert_mock to make it more clear that you're testing w/ them. + * Added negative filter by test name. (utilum) + * Added warning to README that 1.8 and 1.9 support will be dropped in minitest 6. + * Automatically activate minitest/hell if $MT_HELL is defined. + * Improved default error messages for assert and refute. (bhenderson) + * minitest/hell now tries to require minitest/proveit + * mu_pp for strings prints out non-standard encodings to improve assert_equal diffs. + +* 1 bug fix: + + * Removed Interrupt from PASSTHROUGH_EXCEPTIONS (already handled). (waldyr) + +=== 5.8.5 / 2016-09-25 + +* 2 bug fixes: + + * Re-release to refresh gem certificate signing. ugh. + * Fixed hoe/minitest to not augment load path if we're actually testing minitest. + +=== 5.8.4 / 2016-01-21 + +* 1 bug fix: + + * Allow Minitest::Assertion to pass through assert_raises so inner failures are dealt with first. + +=== 5.8.3 / 2015-11-17 + +* 1 minor enhancement: + + * Added extra note about mocks and threads to readme. (zamith) + +* 1 bug fix: + + * Fixed bug in Mock#verify. (pithub/zamith) + +=== 5.8.2 / 2015-10-26 + +* 1 bug fix: + + * Fixed using parallelize_me! and capture_io (or any locking io). (arlt/tenderlove) + +=== 5.8.1 / 2015-09-23 + +* 1 minor enhancement: + + * Refactor assert_raises to be cleaner and to pass SystemExit and SignalException. (bhenderson) + +=== 5.8.0 / 2015-08-06 + +* 2 minor enhancements: + + * Add optional delegation mechanism to extend object with a mock. (zamith) + * Return early if there are no filtered methods. (jeremyevans) + +* 1 bug fix: + + * Don't extend io with pride if io is not a tty. (toy) + +=== 5.7.0 / 2015-05-27 + +* 1 major enhancement: + + * assert_raises now matches subclasses of the expected exception types. (jeremyevans) + +* 3 minor enhancements: + + * Added :block type for minitest/spec's #infect_an_assertion. (jeremyevans) + * Inline verification error messages in minitest/mock for GC performance. (zamith) + * assert_raises defaults to RuntimeError if not specified. (jeremyevans) + +* 4 bug fixes: + + * Added 'class' to minitest/mock's overridden_methods list. (zamith) + * Added file/line to infect_an_assertion's class_eval call. (jeremyevans) + * Cleared UnexpectedError's mesg w/ generic string. + * Fixed non-proc-oriented expectations when used on proc target. (jeremyevans) + +=== 5.6.1 / 2015-04-27 + +* 2 bug fixes: + + * Added Minitest.clock_time and switched all Time.now to it. (tenderlove) + * Moved Minitest::Expectations#_ into Minitest::Spec::DSL. + +=== 5.6.0 / 2015-04-13 + +* 4 major enhancements: + + * Added Minitest::Expectation value monad. + * Added Minitest::Expectations#_ that returns an Expectation. Aliased to value. + * All expectations are added to Minitest::Expectation. + * At some point, the methods on Object will be deprecated and then removed. + +* 4 minor enhancements: + + * Added a note about bundle exec pitfall in ruby 2.2+. (searls) + * Lazily start the parallel executor. (tenderlove) + * Make mocks more debugger-friendly (edward) + * Print out the current test run on interrupt. (riffraff) + +* 3 bug fixes: + + * Fix failing test under Windows. (kimhmadsen) + * Record mocked calls before they happen so mocks can raise exceptions easier (tho I'm not a fan). (corecode) + * Tried to clarify mocks vs stubs terminology better. (kkirsche) + +=== 5.5.1 / 2015-01-09 + +* 1 bug fix: + + * Fixed doco problems. (zzak) + +=== 5.5.0 / 2014-12-12 // mri 2.2.0 (as a real gem) + +* 1 minor enhancement: + + * Allow seed to be given via ENV for rake test loader sadness: eg rake SEED=42. + +=== 5.4.3 / 2014-11-11 + +* 2 bug fixes: + + * Clarified requirements for ruby are now 1.8.7 or better. + * Force encode error output in case mal-encoded exception is raised. (jasonrclark) + +=== 5.4.2 / 2014-09-26 + +* 2 minor enhancements: + + * Extract teardown method list. + * Thanks to minitest-gcstats got a 5-10% speedup via reduced GC! + +=== 5.4.1 / 2014-08-28 + +* 1 bug fix: + + * Fixed specs hidden by nesting/ordering bug (blowmage/apotonick) + +=== 5.4.0 / 2014-07-07 + +* 2 minor enhancements: + + * Kernel#describe extended to splat additional_desc. + * Spec#spec_type extended to take a splat of additional items, passed to matcher procs. + +* 1 bug fix: + + * minitest/spec should require minitest/test, not minitest/unit. (doudou) + +=== 5.3.5 / 2014-06-17 + +* 1 minor enhancement: + + * Spit and polish (mostly spit). + +=== 5.3.4 / 2014-05-15 + +* 1 minor enhancement: + + * Test classes are randomized before running. (judofyr) + +=== 5.3.3 / 2014-04-14 + +* 1 bug fix: + + * Fixed using expectations w/ DSL in Test class w/o describe. (blowmage+others) + +=== 5.3.2 / 2014-04-02 + +* 1 bug fix: + + * Fixed doco on Assertions.assertions. (xaviershay) + +=== 5.3.1 / 2014-03-14 + +* 1 minor enhancement: + + * Modified verbage on bad 'let' names to be more helpful. (Archytaus) + +* 1 bug fix: + + * Fixed 2 cases still using MiniTest. (mikesea) + +=== 5.3.0 / 2014-02-25 + +* 1 minor enhancement: + + * Mocked methods can take a block to verify state. Seattle.rb 12 bday present from ernie! Thanks!! + +=== 5.2.3 / 2014-02-10 + +* 1 bug fix: + + * Fixed Spec#let check to allow overriding of other lets. (mvz) + +=== 5.2.2 / 2014-01-22 + +* 1 minor enhancement: + + * Spec#let raises ArgumentError if you override _any_ instance method (except subject). (rynr) + +* 1 bug fix: + + * Fixed up benchmark spec doco and added a test to demonstrate. (bhenderson) + +=== 5.2.1 / 2014-01-07 + +* 1 bug fix: + + * Properly deal with horrible mix of runtime load errors + other at_exit handlers. (dougo/chqr) + +=== 5.2.0 / 2013-12-13 + +* 1 minor enhancement: + + * Change expectations to allow calling most on procs (but not calling the proc). (bhenderson+others) + +=== 5.1.0 / 2013-12-05 + +* 1 minor enhancement: + + * Use a Queue for scheduling parallel tests. (tenderlove) + +* 1 bug fix: + + * Fixed misspelling in doco. (amatsuda) + +=== 5.0.8 / 2013-09-20 + +* 1 bug fix: + + * Fixed siginfo handler by rearranging reporters and fixing to_s. (tenderlove) + +=== 5.0.7 / 2013-09-05 + +* 2 minor enhancements: + + * Added clarification about the use of thread local variables in expectations. (jemc) + * Added extra message about skipped tests, if any. Disable globally with $MT_NO_SKIP_MSG. + +* 2 bug fixes: + + * Only require minitest, not minitest/autorun in pride_plugin. (judofyr) + * Require rubygems in load_plugins in case you're not using minitest/autorun. + +=== 5.0.6 / 2013-06-28 + +* 3 minor enhancements: + + * Allow stub to pass args to blocks. (swindsor) + * Improved warning message about minitest/autorun to address 1.9's minitest/autorun. + * Made minitest/test require minitest as needed. For lib writers. (erikh) + +* 1 bug fix: + + * Fixed missing require in minitest/test. (erikh) + +=== 4.7.5 / 2013-06-21 // mri 2.1.1 + +* 2 bug fixes: + + * Fix Spec#describe_stack to be thread local. + * Fix multithreaded test failures by defining Time local to mock test namespace + +=== 5.0.5 / 2013-06-20 + +* 6 bug fixes: + + * DOH! Fixed the rest of the new casing on Minitest. (splattael) + * Fixed typo on minitest/mock rdoc. (mrgilman/guiceolin) + * Make Spec::DSL.describe_stack thread local to avoid failing on my own tests. + * Make a fake Time.now local to the tests so they won't interfere with real reporter timings. + * Make everything mockable by wrapping all 'special' methods in a smarter wrapper. (bestie) + * Raise ArgumentError if let name starts with 'test'. (johnmaxwell) + +=== 5.0.4 / 2013-06-07 + +* 5 minor enhancements: + + * Added AbstractReporter, defining required Reporter API to quack properly. + * Added doco for writing reporters. + * Refactored Reporter into ProgressReporter and SummaryReporter. (idea: phiggins, code:me+scotch) + * Refactored SummaryReporter pushing up to StatisticsReporter. (phiggins) + * Removed Reporter#run_and_report... cleaner, but doesn't "fit" in the API. + +=== 5.0.3 / 2013-05-29 + +* 4 minor enhancements: + + * Added Runnable.with_info_handler and Runnable.on_signal. + * Moved io.sync restore to Reporter#run_and_report. + * Refactored inner loop of Reporter#report to #to_s. Callable for status updates. + * Restored MT4's mid-run report (^t). (tenderlove). + +=== 5.0.2 / 2013-05-20 + +* 3 bug fixes: + + * Gem.find_files is smarter than I remember... cause I wrote it that way. *sigh* I'm getting old. + * Pride wasn't doing puts through its #io. (tmiller/tenderlove) + * Replaced Runnable#dup and Test#dup with marshal_dump/load. Too many problems cropping up on untested rails code. (tenderlove/rubys) + +=== 5.0.1 / 2013-05-14 + +* 2 bug fixes: + + * Documented Assertions' need for @assertions to be defined by the includer. + * Only load one plugin version per name. Tries for latest. + +=== 5.0.0 / 2013-05-10 + +Oh god... here we go... + +Minitest 5: + +* 4 deaths in the family: + + * MiniTest.runner is dead. No more manager objects. + * MiniTest::Unit#record is dead. Use a Reporter instance instead. + * MiniTest::Unit._run_* is dead. Runnable things are responsible for their own runs. + * MiniTest::Unit.output is dead. No more centralized IO. + +* 12 major (oft incompatible) changes: + + * Renamed MiniTest to Minitest. Your pinkies will thank me. (aliased to MiniTest) + * Removed MiniTest::Unit entirely. No more manager objects. + * Added Minitest::Runnable. Everything minitest can run subclasses this. + * Renamed MiniTest::Unit::TestCase to Minitest::Test (subclassing Runnable). + * Added Minitest::Benchmark. + * Your benchmarks need to move to their own subclass. + * Benchmarks using the spec DSL have to have "Bench" somewhere in their describe. + * MiniTest::Unit.after_tests moved to Minitest.after_run + * MiniTest::Unit.autorun is now Minitest.autorun. Just require minitest/autorun pls. + * Removed ParallelEach#grep since it isn't used anywhere. + * Renamed Runnable#__name__ to Runnable#name (but uses @NAME internally). + * Runnable#run needs to return self. Allows for swapping of results as needed. + +* 8 minor moves: + + * Moved Assertions module to minitest/assertions.rb + * Moved Expectations module to minitest/expectations.rb + * Moved Test to minitest/test.rb + * Moved everything else in minitest/unit.rb to minitest.rb + * minitest/unit.rb is now just a small (user-test only) compatibility layer. + * Moved most of minitest/pride into minitest/pride_plugin. + * minitest/pride now just activates pride. + * Moved ParallelEach under Minitest. + +* 9 additions: + + * Added a plugin system that can extend command-line options. + * Added Minitest.extensions. + * Added Minitest.reporter (only available during startup). + * Added Minitest.run(args). This is the very top of any Minitest run. + * Added Minitest::Reporter. Everything minitest can report goes through here. + * Minitest.reporter is a composite so you can add your own. + * Added Minitest::CompositeReporter. Much easier to extend with your own reporters. + * Added UnexpectedError, an Assertion subclass, to wrap up errors. + * Minitest::Test#run is now freakin' beautiful. 47 -> 17 loc + +* 11 other: + + * Removed Object.infect_with_assertions (it was already dead code). + * Runnables are responsible for knowing their result_code (eg "." or "F"). + * Minitest.autorun now returns boolean, not exit code. + * Added FAQ entry for extending via modules. (phiggins) + * Implement Runnable#dup to cleanse state back to test results. Helps with serialization. pair:tenderlove + * Moved ParallelEach under Minitest. + * Runnable#run needs to return self. Allows for swapping of results as needed. + * Minitest.init_plugins passes down options. + * Minitest.load_plugins only loads once. + * Fixed minitest/pride to work with rake test loader again. (tmiller) + * Added count/size to ParallelEach to fix use w/in stdlib's test/unit. :( (btaitelb) + +* 5 voodoo: + + * Removed mutex from minitest.rb (phiggins) + * Removed mutex from test.rb (phiggins) + * Removed Minitest::Reporter.synchronize (phiggins) + * Removed Minitest::Test.synchronize (phiggins) + * Upon loading minitest/parallel_each, record, capture_io and capture_subprocess_io are doped with synchronization code. (phiggins) + +=== 4.7.4 / 2013-05-01 + +This is probably the last release of the 4.x series. It will be merged +to ruby and will be put into maintenance mode there. + +I'm not set in stone on this, but at this point further development of +minitest (5+) will be gem-only. It is just too hard to work w/in +ruby-core w/ test-unit compatibility holding minitest development +back. + +* 2 minor enhancements: + + * Added count/size to ParallelEach to fix use w/in stdlib's test/unit. :( (btaitelb) + * Allow disabling of info_signal handler in runner. (erikh) + +=== 4.7.3 / 2013-04-20 + +* 1 bug fix: + + * Reverted stubbing of module methods change. Stub the user, not the impl. (ab9/tyabe) + +=== 4.7.2 / 2013-04-18 + +* 2 bug fixes: + + * Fixed inconsistency in refute_in_delta/epsilon. I double negatived my logic. (nettsundere) + * Fixed stubbing of module methods (eg Kernel#sleep). (steveklabnik) + +=== 4.7.1 / 2013-04-09 + +* 1 minor enhancement: + + * Added FAQ section to README + +* 1 bug fix: + + * Fixed bug where guard runs tests bypassing minitest/autorun and an ivar isn't set right. (darrencauthon) + +=== 4.7.0 / 2013-03-18 + +* 1 major enhancement: + + * Refactored MiniTest::Spec into MiniTest::Spec::DSL. + +* 1 bug fix: + + * Removed $DEBUG handler that detected when test/unit and minitest were both loaded. (tenderlove) + +=== 4.6.2 / 2013-02-27 + +* 1 minor enhancement: + + * Change error output to match Class#method, making it easier to use -n filter. + +=== 4.6.1 / 2013-02-14 + +* 1 bug fix: + + * Fixed an option processing bug caused by test/unit's irresponsibly convoluted code. (floehopper) + +=== 4.6.0 / 2013-02-07 + +* 3 major enhancements: + + * Removed ::reset_setup_teardown_hooks + * Removed the long deprecated assert_block + * Removed the long deprecated lifecycle hooks: add_(setup|teardown)_hook + +* 1 minor enhancement: + + * Allow filtering tests by suite name as well as test name. (lazyatom) + +* 2 bug fixes: + + * Made hex handling (eg object_ids) in mu_pp_for_diff more specific. (maxim) + * nodoc top-level module. (zzak) + +=== 4.5.0 / 2013-01-22 + +* 1 major enhancement: + + * Rearranged minitest/unit.rb so NO parallelization code is loaded/used until you opt-in. + +* 4 minor enhancements: + + * Added TestCase#skipped? for teardown guards + * Added maglev? guard + * Document that record can be sent twice if teardown fails or errors (randycoulman) + * Errors in teardown are now recorded. (randycoulman) + +* 3 bug fixes: + + * Added hacks and skips to get clean test runs on maglev + * Modified float tests for maglev float output differences. Not sure this is right. Not sure I care. + * Test for existance of diff.exe instead of assuming they have devkit. (blowmage/Cumbayah) + +=== 4.4.0 / 2013-01-07 + +* 3 minor enhancements: + + * Added fit_logarithic and assert_performance_logarithmic. (ktheory) + * Merge processed options so others can mess with defaults. (tenderlove) + * TestCase#message can now take another proc to defer custom message cost. (ordinaryzelig/bhenderson) + +* 1 bug fix: + + * TestCase#passed? now true if test is skipped. (qanhd) + +=== 4.3.3 / 2012-12-06 + +* 1 bug fix: + + * Updated information about stubbing. (daviddavis) + +=== 4.3.2 / 2012-11-27 // mri 2.0.0 + +* 1 minor enhancement: + + * Improved assert_equals error message to point you at #== of member objects. (kcurtin) + +=== 4.3.1 / 2012-11-23 + +* 1 bug fix: + + * Moved test_children to serial testcase to prevent random failures. + +=== 4.3.0 / 2012-11-17 + +* 4 minor enhancements: + + * Allow #autorun to run even if loaded with other test libs that call exit. (sunaku) + * Do not include Expectations in Object if $MT_NO_EXPECTATIONS is set (experimental?) + * Gave some much needed love to assert_raises. + * Mock#expect can take a block to custom-validate args. (gmoothart) + +=== 4.2.0 / 2012-11-02 + +* 4 major enhancements: + + * Added minitest/hell - run all your tests through the ringer! + * Added support for :parallel test_order to run test cases in parallel. + * Removed last_error and refactored runner code to be threadsafe. + * _run_suites now runs suites in parallel if they opt-in. + +* 4 minor enhancements: + + * Added TestCase#synchronize + * Added TestCase.make_my_diffs_pretty! + * Added TestCase.parallelize_me! + * Lock on capture_io for thread safety (tenderlove) + +=== 4.1.0 / 2012-10-05 + +* 2 minor enhancements: + + * Added skip example to readme. (dissolved) + * Extracted backtrace filter to object. (tenderlove) + +* 1 bug fix: + + * OMG I'm so dumb. Fixed access to deprecated hook class methods. I hate ruby modules. (route) + +=== 4.0.0 / 2012-09-28 + +* 1 major enhancement: + + * The names of a privately-used undocumented constants are Super Important™. + +* 1 minor enhancement: + + * Support stubbing methods that would be handled via method_missing. (jhsu) + +* 3 bug fixes: + + * Add include_private param to MiniTest::Mock#respond_to? (rf-) + * Fixed use of minitest/pride with --help. (zw963) + * Made 'No visible difference.' message more clear. (ckrailo) + +=== 3.5.0 / 2012-09-21 + +* 1 minor enhancement: + + * Added #capture_subprocess_io. (route) + +=== 3.4.0 / 2012-09-05 + +* 2 minor enhancements: + + * assert_output can now take regexps for expected values. (suggested by stomar) + * Clarified that ruby 1.9/2.0's phony gems cause serious confusion for rubygems. + +=== 3.3.0 / 2012-07-26 + +* 1 major enhancement: + + * Deprecated add_(setup|teardown)_hook in favor of (before|after)_(setup|teardown) [2013-01-01] + +* 4 minor enhancements: + + * Refactored deprecated hook system into a module. + * Refactored lifecycle hooks into a module. + * Removed after_setup/before_teardown + run_X_hooks from Spec. + * Spec#before/after now do a simple define_method and call super. DUR. + +* 2 bug fixes: + + * Fixed #passed? when used against a test that called flunk. (floehopper) + * Fixed rdoc bug preventing doco for some expectations. (stomar). + +=== 3.2.0 / 2012-06-26 + +* 1 minor enhancement: + + * Stubs now yield self. (peterhellberg) + +* 1 bug fix: + + * Fixed verbose test that only fails when run in verbose mode. mmmm irony. + +=== 3.1.0 / 2012-06-13 + +* 2 minor enhancements: + + * Removed LONG deprecated Unit.out accessor + * Removed generated method name munging from minitest/spec. (ordinaryzelig/tenderlove) + +=== 3.0.1 / 2012-05-24 + +* 1 bug fix: + + * I'm a dumbass and refactored into Mock#call. Renamed to #__call so you can mock #call. (mschuerig) + +=== 3.0.0 / 2012-05-08 + +* 3 major enhancements: + + * Added Object#stub (in minitest/mock.rb). + * Mock#expect mocks are used in the order they're given. + * Mock#verify now strictly compares against expect calls. + +* 3 minor enhancements: + + * Added caller to deprecation message. + * Mock error messages are much prettier. + * Removed String check for RHS of assert/refute_match. This lets #to_str work properly. + +* 1 bug fix: + + * Support drive letter on Windows. Patch provided from MRI by Usaku NAKAMURA. (ayumin) + +=== 2.12.1 / 2012-04-10 + +* 1 minor enhancement: + + * Added ruby releases to History.txt to make it easier to see what you're missing + +* 1 bug fix: + + * Rolled my own deprecate msg to allow MT to work with rubygems < 1.7 + +=== 2.12.0 / 2012-04-03 + +* 4 minor enhancements: + + * ::it returns test method name (wojtekmach) + * Added #record method to runner so runner subclasses can cleanly gather data. + * Added Minitest alias for MiniTest because even I forget. + * Deprecated assert_block!! Yay!!! + +* 1 bug fix: + + * Fixed warning in i_suck_and_my_tests_are_order_dependent! (phiggins) + +=== 2.11.4 / 2012-03-20 + +* 2 minor enhancements: + + * Updated known extensions + * You got your unicode in my tests! You got your tests in my unicode! (fl00r) + +* 1 bug fix: + + * Fixed MiniTest::Mock example in the readme. (conradwt) + +=== 2.11.3 / 2012-02-29 + +* 2 bug fixes: + + * Clarified that assert_raises returns the exception for further testing + * Fixed assert_in_epsilon when both args are negative. (tamc) + +=== 2.11.2 / 2012-02-14 + +* 1 minor enhancement: + + * Display failures/errors on SIGINFO. (tenderlove) + +* 1 bug fix: + + * Fixed MiniTest::Unit.after_tests for Ruby 1.9.3. (ysbaddaden) + +=== 2.11.1 / 2012-02-01 + +* 3 bug fixes: + + * Improved description for --name argument. (drd) + * Ensure Mock#expect's expected args is an Array. (mperham) + * Ensure Mock#verify verifies multiple expects of the same method. (chastell) + +=== 2.11.0 / 2012-01-25 + +* 2 minor enhancements: + + * Added before / after hooks for setup and teardown. (tenderlove) + * Pushed run_setup_hooks down to Spec. (tenderlove) + +=== 2.10.1 / 2012-01-17 + +* 1 bug fix: + + * Fixed stupid 1.9 path handling grumble grumble. (graaff) + +=== 2.10.0 / 2011-12-20 + +* 3 minor enhancements: + + * Added specs for must/wont be_empty/respond_to/be_kind_of and others. + * Added tests for assert/refute predicate. + * Split minitest/excludes.rb out to its own gem. + +* 1 bug fix: + + * Fixed must_be_empty and wont_be_empty argument handling. (mrsimo) + +=== 2.9.1 / 2011-12-13 + +* 4 minor enhancements: + + * Added a ton of tests on spec error message output. + * Cleaned up consistency of msg handling on unary expectations. + * Improved error messages on assert/refute_in_delta. + * infect_an_assertion no longer checks arity and better handles args. + +* 1 bug fix: + + * Fixed error message on specs when 2+ args and custom message provided. (chastell) + +=== 2.9.0 / 2011-12-07 + +* 4 minor enhancements: + + * Added TestCase.exclude and load_excludes for programmatic filtering of tests. + * Added guard methods so you can cleanly skip based on platform/impl + * Holy crap! 100% doco! `rdoc -C` ftw + * Switch assert_output to test stderr before stdout to possibly improve debugging + +=== 2.8.1 / 2011-11-17 + +* 1 bug fix: + + * Ugh. 1.9's test/unit violates my internals. Added const_missing. + +=== 2.8.0 / 2011-11-08 + +* 2 minor enhancements: + + * Add a method so that code can be run around a particular test case (tenderlove) + * Turn off backtrace filtering if we're running inside a ruby checkout. (drbrain) + +* 2 bug fixes: + + * Fixed 2 typos and 2 doc glitches. (splattael) + * Remove unused block arguments to avoid creating Proc objects. (k-tsj) + +=== 2.7.0 / 2011-10-25 + +* 2 minor enhancements: + + * Include failed values in the expected arg output in MockExpectationError. (nono) + * Make minitest/pride work with other 256 color capable terms. (sunaku) + +* 2 bug fixes: + + * Clarified the documentation of minitest/benchmark (eregon) + * Fixed using expectations in regular unit tests. (sunaku) + +=== 2.6.2 / 2011-10-19 + +* 1 minor enhancement: + + * Added link to vim bundle. (sunaku) + +* 2 bug fixes: + + * Force gem activation in hoe minitest plugin + * Support RUBY_VERSION='2.0.0' (nagachika) + +=== 2.6.1 / 2011-09-27 + +* 2 bug fixes: + + * Alias Spec.name from Spec.to_s so it works when @name is nil (nathany) + * Fixed assert and refute_operator where second object has a bad == method. + +=== 2.6.0 / 2011-09-13 + +* 2 minor enhancements: + + * Added specify alias for it and made desc optional. + * Spec#must_be and #wont_be can be used with predicates (metaskills) + +* 1 bug fix: + + * Fixed Mock.respond_to?(var) to work with strings. (holli) + +=== 2.5.1 / 2011-08-27 // ruby 1.9.3: p0, p125, p34579 + +* 2 minor enhancements: + + * Added gem activation for minitest in minitest/autoload to help out 1.9 users + * Extended Spec.register_spec_type to allow for procs instead of just regexps. + +=== 2.5.0 / 2011-08-18 + +* 4 minor enhancements: + + * Added 2 more arguments against rspec: let & subject in 9 loc! (emmanuel/luis) + * Added TestCase.i_suck_and_my_tests_are_order_dependent! + * Extended describe to take an optional method name (2 line change!). (emmanuel) + * Refactored and extended minitest/pride to do full 256 color support. (lolcat) + +* 1 bug fix: + + * Doc fixes. (chastell) + +=== 2.4.0 / 2011-08-09 + +* 4 minor enhancements: + + * Added simple examples for all expectations. + * Improved Mock error output when args mismatch. + * Moved all expectations from Object to MiniTest::Expectations. + * infect_with_assertions has been removed due to excessive clever + +* 4 bug fixes: + + * Fix Assertions#mu_pp to deal with immutable encoded strings. (ferrous26) + * Fix minitest/pride for MacRuby (ferrous26) + * Made error output less fancy so it is more readable + * Mock shouldn't undef === and inspect. (dgraham) + +=== 2.3.1 / 2011-06-22 + +* 1 bug fix: + + * Fixed minitest hoe plugin to be a spermy dep and not depend on itself. + +=== 2.3.0 / 2011-06-15 + +* 5 minor enhancements: + + * Add setup and teardown hooks to MiniTest::TestCase. (phiggins) + * Added nicer error messages for MiniTest::Mock. (phiggins) + * Allow for less specific expected arguments in Mock. (bhenderson/phiggins) + * Made MiniTest::Mock a blank slate. (phiggins) + * Refactored minitest/spec to use the hooks instead of define_inheritable_method. (phiggins) + +* 2 bug fixes: + + * Fixed TestCase's inherited hook. (dchelimsky/phiggins/jamis, the 'good' neighbor) + * MiniTest::Assertions#refute_empty should use mu_pp in the default message. (whatthejeff) + +=== 2.2.2 / 2011-06-01 + +* 2 bug fixes: + + * Got rid of the trailing period in message for assert_equal. (tenderlove) + * Windows needs more flushing. (Akio Tajima) + +=== 2.2.1 / 2011-05-31 + +* 1 bug fix: + + * My _ONE_ non-rubygems-using minitest user goes to Seattle.rb! + +=== 2.2.0 / 2011-05-29 + +* 6 minor enhancements: + + * assert_equal (and must_equal) now tries to diff output where it makes sense. + * Added Assertions#diff(exp, act) to be used by assert_equal. + * Added Assertions#mu_pp_for_diff + * Added Assertions.diff and diff= + * Moved minitest hoe-plugin from hoe-seattlerb. (erikh) + * Skipped tests only output details in verbose mode. (tenderlove+zenspider=xoxo) + +=== 2.1.0 / 2011-04-11 + +* 5 minor enhancements: + + * Added MiniTest::Spec.register_spec_type(matcher, klass) and spec_type(desc) + * Added ability for specs to share code via subclassing of Spec. (metaskills) + * Clarified (or tried to) bench_performance_linear's use of threshold. + * MiniTest::Unit.runner=(runner) provides an easy way of creating custom test runners for specialized needs. (justinweiss) + * Reverse order of inheritance in teardowns of specs. (deepfryed) + +* 3 bug fixes: + + * FINALLY fixed problems of inheriting specs in describe/it/describe scenario. (MGPalmer) + * Fixed a new warning in 1.9.3. + * Fixed assert_block's message handling. (nobu) + +=== 2.0.2 / 2010-12-24 + +* 1 minor enhancement: + + * Completed doco on minitest/benchmark for specs. + +* 1 bug fix: + + * Benchmarks in specs that didn't call bench_range would die. (zzak). + +=== 2.0.1 / 2010-12-15 + +* 4 minor enhancements: + + * Do not filter backtrace if $DEBUG + * Exit autorun via nested at_exit handler, in case other libs call exit + * Make options accesor lazy. + * Split printing of test name and its time. (nurse) + +* 1 bug fix: + + * Fix bug when ^T is hit before runner start + +=== 2.0.0 / 2010-11-11 + +* 3 major enhancements: + + * Added minitest/benchmark! Assert your performance! YAY! + * Refactored runner to allow for more extensibility. See minitest/benchmark. + * This makes the runner backwards incompatible in some ways! + +* 9 minor enhancements: + + * Added MiniTest::Unit.after_tests { ... } + * Improved output by adding test rates and a more sortable verbose format + * Improved readme based on feedback from others + * Added io method to TestCase. If used, it'll supplant '.EF' output. + * Refactored IO in MiniTest::Unit. + * Refactored _run_anything to _run_suite to make it easier to wrap (ngauthier) + * Spec class names are now the unmunged descriptions (btakita) + * YAY for not having redundant rdoc/readmes! + * Help output is now generated from the flags you passed straight up. + +* 4 bug fixes: + + * Fixed scoping issue on minitest/mock (srbaker/prosperity) + * Fixed some of the assertion default messages + * Fixes autorun when on windows with ruby install on different drive (larsch) + * Fixed rdoc output bug in spec.rb + +=== 1.7.2 / 2010-09-23 + +* 3 bug fixes: + + * Fixed doco for expectations and Spec. + * Fixed test_capture_io on 1.9.3+ (sora_h) + * assert_raises now lets MiniTest::Skip through. (shyouhei) + +=== 1.7.1 / 2010-09-01 + +* 1 bug fix: + + * 1.9.2 fixes for spec tests + +=== 1.7.0 / 2010-07-15 + +* 5 minor enhancements: + + * Added assert_output (mapped to must_output). + * Added assert_silent (mapped to must_be_silent). + * Added examples to readme (Mike Dalessio) + * Added options output at the top of the run, for fatal run debugging (tenderlove) + * Spec's describe method returns created class + +=== 1.6.0 / 2010-03-27 // ruby 1.9.2-p290 + +* 10 minor enhancements: + + * Added --seed argument so you can reproduce a random order for debugging. + * Added documentation for assertions + * Added more rdoc and tons of :nodoc: + * Added output to give you all the options you need to reproduce that run. + * Added proper argument parsing to minitest. + * Added unique serial # to spec names so order can be preserved (needs tests). (phrogz) + * Empty 'it' fails with default msg. (phrogz) + * Remove previous method on expect to remove 1.9 warnings + * Spec#it is now order-proof wrt subclasses/nested describes. + * assert_same error message now reports in decimal, eg: oid=123. (mattkent) + +* 2 bug fixes: + + * Fixed message on refute_same to be consistent with assert_same. + * Fixed method randomization to be stable for testing. + +=== 1.5.0 / 2010-01-06 + +* 4 minor enhancements: + + * Added ability to specify what assertions should have their args flipped. + * Don't flip arguments on *include and *respond_to assertions. + * Refactored Module.infect_an_assertion from Module.infect_with_assertions. + * before/after :all now bitches and acts like :each + +* 3 bug fixes: + + * Nested describes now map to nested test classes to avoid namespace collision. + * Using undef_method instead of remove_method to clean out inherited specs. + * assert_raises was ignoring passed in message. + +=== 1.4.2 / 2009-06-25 + +* 1 bug fix: + + * Fixed info handler for systems that don't have siginfo. + +=== 1.4.1 / 2009-06-23 + +* 1 major enhancement: + + * Handle ^C and other fatal exceptions by failing + +* 1 minor enhancement: + + * Added something to catch mixed use of test/unit and minitest if $DEBUG + +* 1 bug fix: + + * Added SIGINFO handler for finding slow tests without verbose + +=== 1.4.0 / 2009-06-18 + +* 5 minor enhancement: + + * Added clarification doco. + * Added specs and mocks to autorun. + * Changed spec test class creation to be non-destructive. + * Updated rakefile for new hoe capabilities. + * describes are nestable (via subclass). before/after/def inherits, specs don't. + +* 3 bug fixes: + + * Fixed location on must/wont. + * Switched to __name__ to avoid common ivar name. + * Fixed indentation in test file (1.9). + +=== 1.3.1 / 2009-01-20 // ruby 1.9.1-p431 + +* 1 minor enhancement: + + * Added miniunit/autorun.rb as replacement for test/unit.rb's autorun. + +* 16 bug fixes: + + * 1.9 test fixes. + * Bug fixes from nobu and akira for really odd scenarios. They run ruby funny. + * Fixed (assert|refute)_match's argument order. + * Fixed LocalJumpError in autorun if exception thrown before at_exit. + * Fixed assert_in_delta (should be >=, not >). + * Fixed assert_raises to match Modules. + * Fixed capture_io to not dup IOs. + * Fixed indentation of capture_io for ruby 1.9 warning. + * Fixed location to deal better with custom assertions and load paths. (Yuki) + * Fixed order of (must|wont)_include in MiniTest::Spec. + * Fixed skip's backtrace. + * Got arg order wrong in *_match in tests, message wrong as a result. + * Made describe private. For some reason I thought that an attribute of Kernel. + * Removed disable_autorun method, added autorun.rb instead. + * assert_match escapes if passed string for pattern. + * instance_of? is different from ===, use instance_of. + +=== 1.3.0 / 2008-10-09 + +* 2 major enhancements: + + * renamed to minitest and pulled out test/unit compatibility. + * mini/test.rb is now minitest/unit.rb, everything else maps directly. + +* 12 minor enhancements: + + * assert_match now checks that act can call =~ and converts exp to a + regexp only if needed. + * Added assert_send... seems useless to me tho. + * message now forces to string... ruby-core likes to pass classes and arrays :( + * Added -v handling and switched to @verbose from $DEBUG. + * Verbose output now includes test class name and adds a sortable running time! + * Switched message generation into procs for message deferment. + * Added skip and renamed fail to flunk. + * Improved output failure messages for assert_instance_of, assert_kind_of + * Improved output for assert_respond_to, assert_same. + * at_exit now exits false instead of errors+failures. + * Made the tests happier and more readable imhfo. + * Switched index(s) == 0 to rindex(s, 0) on nobu's suggestion. Faster. + +* 5 bug fixes: + + * 1.9: Added encoding normalization in mu_pp. + * 1.9: Fixed backtrace filtering (BTs are expanded now) + * Added back exception_details to assert_raises. DOH. + * Fixed shadowed variable in mock.rb + * Fixed stupid muscle memory message bug in assert_send. + +=== 1.2.1 / 2008-06-10 + +* 7 minor enhancements: + + * Added deprecations everywhere in test/unit. + * Added test_order to TestCase. :random on mini, :sorted on test/unit (for now). + * Big cleanup in test/unit for rails. Thanks Jeremy Kemper! + * Minor readability cleanup. + * Pushed setup/run/teardown down to testcase allowing specialized testcases. + * Removed pp. Tests run 2x faster. :/ + * Renamed deprecation methods and moved to test/unit/deprecate.rb. + +=== 1.2.0 / 2008-06-09 + +* 2 major enhancements: + + * Added Mini::Spec. + * Added Mini::Mock. Thanks Steven Baker!! + +* 23 minor enhancements: + + * Added bin/use_miniunit to make it easy to test out miniunit. + * Added -n filtering, thanks to Phil Hagelberg! + * Added args argument to #run, takes ARGV from at_exit. + * Added test name output if $DEBUG. + * Added a refute (was deny) for every assert. + * Added capture_io and a bunch of nice assertions from zentest. + * Added deprecation mechanism for assert_no/not methods to test/unit/assertions. + * Added pp output when available. + * Added tests for all assertions. Pretty much maxed out coverage. + * Added tests to verify consistency and good naming. + * Aliased and deprecated all ugly assertions. + * Cleaned out test/unit. Moved autorun there. + * Code cleanup to make extensions easier. Thanks Chad! + * Got spec args reversed in all but a couple assertions. Much more readable. + * Improved error messages across the board. Adds your message to the default. + * Moved into Mini namespace, renamed to Mini::Test and Mini::Spec. + * Pulled the assertions into their own module... + * Removed as much code as I could while still maintaining full functionality. + * Moved filter_backtrace into MiniTest. + * Removed MiniTest::Unit::run. Unnecessary. + * Removed location_of_failure. Unnecessary. + * Rewrote test/unit's filter_backtrace. Flog from 37.0 to 18.1 + * Removed assert_send. Google says it is never used. + * Renamed MiniTest::Unit.autotest to #run. + * Renamed deny to refute. + * Rewrote some ugly/confusing default assertion messages. + * assert_in_delta now defaults to 0.001 precision. Makes specs prettier. + +* 9 bug fixes: + + * Fixed assert_raises to raise outside of the inner-begin/rescue. + * Fixed for ruby 1.9 and rubinius. + * No longer exits 0 if exception in code PRE-test run causes early exit. + * Removed implementors method list from mini/test.rb - too stale. + * assert_nothing_raised takes a class as an arg. wtf? STUPID + * ".EF" output is now unbuffered. + * Bunch of changes to get working with rails... UGH. + * Added stupid hacks to deal with rails not requiring their dependencies. + * Now bitch loudly if someone defines one of my classes instead of requiring. + * Fixed infect method to work better on 1.9. + * Fixed all shadowed variable warnings in 1.9. + +=== 1.1.0 / 2007-11-08 + +* 4 major enhancements: + + * Finished writing all missing assertions. + * Output matches original test/unit. + * Documented every method needed by language implementor. + * Fully switched over to self-testing setup. + +* 2 minor enhancements: + + * Added deny (assert ! test), our favorite extension to test/unit. + * Added .autotest and fairly complete unit tests. (thanks Chad for help here) + +=== 1.0.0 / 2006-10-30 + +* 1 major enhancement + + * Birthday! diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/Manifest.txt b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/Manifest.txt new file mode 100644 index 00000000..abdd95f8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/Manifest.txt @@ -0,0 +1,32 @@ +.autotest +History.rdoc +Manifest.txt +README.rdoc +Rakefile +design_rationale.rb +lib/hoe/minitest.rb +lib/minitest.rb +lib/minitest/assertions.rb +lib/minitest/autorun.rb +lib/minitest/benchmark.rb +lib/minitest/compress.rb +lib/minitest/error_on_warning.rb +lib/minitest/expectations.rb +lib/minitest/hell.rb +lib/minitest/manual_plugins.rb +lib/minitest/mock.rb +lib/minitest/parallel.rb +lib/minitest/pride.rb +lib/minitest/pride_plugin.rb +lib/minitest/spec.rb +lib/minitest/test.rb +lib/minitest/test_task.rb +lib/minitest/unit.rb +test/minitest/metametameta.rb +test/minitest/test_minitest_assertions.rb +test/minitest/test_minitest_benchmark.rb +test/minitest/test_minitest_mock.rb +test/minitest/test_minitest_reporter.rb +test/minitest/test_minitest_spec.rb +test/minitest/test_minitest_test.rb +test/minitest/test_minitest_test_task.rb diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/README.rdoc b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/README.rdoc new file mode 100644 index 00000000..e2855b76 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/README.rdoc @@ -0,0 +1,842 @@ += minitest/{test,spec,mock,benchmark} + +home :: https://github.com/minitest/minitest +bugs :: https://github.com/minitest/minitest/issues +rdoc :: https://docs.seattlerb.org/minitest +clog :: https://github.com/minitest/minitest/blob/master/History.rdoc +vim :: https://github.com/sunaku/vim-ruby-minitest +emacs:: https://github.com/arthurnn/minitest-emacs + +== DESCRIPTION: + +minitest provides a complete suite of testing facilities supporting +TDD, BDD, mocking, and benchmarking. + + "I had a class with Jim Weirich on testing last week and we were + allowed to choose our testing frameworks. Kirk Haines and I were + paired up and we cracked open the code for a few test + frameworks... + + I MUST say that minitest is *very* readable / understandable + compared to the 'other two' options we looked at. Nicely done and + thank you for helping us keep our mental sanity." + + -- Wayne E. Seguin + +minitest/test is a small and incredibly fast unit testing framework. +It provides a rich set of assertions to make your tests clean and +readable. + +minitest/spec is a functionally complete spec engine. It hooks onto +minitest/test and seamlessly bridges test assertions over to spec +expectations. + +minitest/benchmark is an awesome way to assert the performance of your +algorithms in a repeatable manner. Now you can assert that your newb +co-worker doesn't replace your linear algorithm with an exponential +one! + +minitest/mock by Steven Baker, is a beautifully tiny mock (and stub) +object framework. + +minitest/pride shows pride in testing and adds coloring to your test +output. I guess it is an example of how to write IO pipes too. :P + +minitest/test is meant to have a clean implementation for language +implementors that need a minimal set of methods to bootstrap a working +test suite. For example, there is no magic involved for test-case +discovery. + + "Again, I can't praise enough the idea of a testing/specing + framework that I can actually read in full in one sitting!" + + -- Piotr Szotkowski + +Comparing to rspec: + + rspec is a testing DSL. minitest is ruby. + + -- Adam Hawkins, "Bow Before MiniTest" + +minitest doesn't reinvent anything that ruby already provides, like: +classes, modules, inheritance, methods. This means you only have to +learn ruby to use minitest and all of your regular OO practices like +extract-method refactorings still apply. + +== FEATURES/PROBLEMS: + +* minitest/autorun - the easy and explicit way to run all your tests. +* minitest/test - a very fast, simple, and clean test system. +* minitest/spec - a very fast, simple, and clean spec system. +* minitest/mock - a simple and clean mock/stub system. +* minitest/benchmark - an awesome way to assert your algorithm's performance. +* minitest/pride - show your pride in testing! +* minitest/test_task - a full-featured and clean rake task generator. +* Incredibly small and fast runner, but no bells and whistles. +* Written by squishy human beings. Software can never be perfect. We will all eventually die. + +== RATIONALE: + +See design_rationale.rb to see how specs and tests work in minitest. + +== SYNOPSIS: + +Given that you'd like to test the following class: + + class Meme + def i_can_has_cheezburger? + "OHAI!" + end + + def will_it_blend? + "YES!" + end + end + +=== Unit tests + +Define your tests as methods beginning with +test_+. + + require "minitest/autorun" + + class TestMeme < Minitest::Test + def setup + @meme = Meme.new + end + + def test_that_kitty_can_eat + assert_equal "OHAI!", @meme.i_can_has_cheezburger? + end + + def test_that_it_will_not_blend + refute_match /^no/i, @meme.will_it_blend? + end + + def test_that_will_be_skipped + skip "test this later" + end + end + +=== Specs + + require "minitest/autorun" + + describe Meme do + before do + @meme = Meme.new + end + + describe "when asked about cheeseburgers" do + it "must respond positively" do + _(@meme.i_can_has_cheezburger?).must_equal "OHAI!" + end + end + + describe "when asked about blending possibilities" do + it "won't say no" do + _(@meme.will_it_blend?).wont_match /^no/i + end + end + end + +For matchers support check out: + +* https://github.com/wojtekmach/minitest-matchers +* https://github.com/rmm5t/minitest-matchers_vaccine + +=== Benchmarks + +Add benchmarks to your tests. + + # optionally run benchmarks, good for CI-only work! + require "minitest/benchmark" if ENV["BENCH"] + + class TestMeme < Minitest::Benchmark + # Override self.bench_range or default range is [1, 10, 100, 1_000, 10_000] + def bench_my_algorithm + assert_performance_linear 0.9999 do |n| # n is a range value + @obj.my_algorithm(n) + end + end + end + +Or add them to your specs. If you make benchmarks optional, you'll +need to wrap your benchmarks in a conditional since the methods won't +be defined. In minitest 5, the describe name needs to match +/Bench(mark)?$/. + + describe "Meme Benchmark" do + if ENV["BENCH"] then + bench_performance_linear "my_algorithm", 0.9999 do |n| + 100.times do + @obj.my_algorithm(n) + end + end + end + end + +outputs something like: + + # Running benchmarks: + + TestBlah 100 1000 10000 + bench_my_algorithm 0.006167 0.079279 0.786993 + bench_other_algorithm 0.061679 0.792797 7.869932 + +Output is tab-delimited to make it easy to paste into a spreadsheet. + +=== Mocks + +Mocks and stubs defined using terminology by Fowler & Meszaros at +https://www.martinfowler.com/bliki/TestDouble.html: + +"Mocks are pre-programmed with expectations which form a specification +of the calls they are expected to receive. They can throw an exception +if they receive a call they don't expect and are checked during +verification to ensure they got all the calls they were expecting." + + class MemeAsker + def initialize(meme) + @meme = meme + end + + def ask(question) + method = question.tr(" ", "_") + "?" + @meme.__send__(method) + end + end + + require "minitest/autorun" + + describe MemeAsker, :ask do + describe "when passed an unpunctuated question" do + it "should invoke the appropriate predicate method on the meme" do + @meme = Minitest::Mock.new + @meme_asker = MemeAsker.new @meme + @meme.expect :will_it_blend?, :return_value + + @meme_asker.ask "will it blend" + + @meme.verify + end + end + end + +==== Multi-threading and Mocks + +Minitest mocks do not support multi-threading. If it works, fine, if it doesn't +you can use regular ruby patterns and facilities like local variables. Here's +an example of asserting that code inside a thread is run: + + def test_called_inside_thread + called = false + pr = Proc.new { called = true } + thread = Thread.new(&pr) + thread.join + assert called, "proc not called" + end + +=== Stubs + +Mocks and stubs are defined using terminology by Fowler & Meszaros at +https://www.martinfowler.com/bliki/TestDouble.html: + +"Stubs provide canned answers to calls made during the test". + +Minitest's stub method overrides a single method for the duration of +the block. + + def test_stale_eh + obj_under_test = Something.new + + refute obj_under_test.stale? + + Time.stub :now, Time.at(0) do # stub goes away once the block is done + assert obj_under_test.stale? + end + end + +A note on stubbing: In order to stub a method, the method must +actually exist prior to stubbing. Use a singleton method to create a +new non-existing method: + + def obj_under_test.fake_method + ... + end + +=== Running Your Tests + +Ideally, you'll use a rake task to run your tests (see below), either +piecemeal or all at once. BUT! You don't have to: + + % ruby -Ilib:test test/minitest/test_minitest_test.rb + Run options: --seed 37685 + + # Running: + + ...................................................................... (etc) + + Finished in 0.107130s, 1446.8403 runs/s, 2959.0217 assertions/s. + + 155 runs, 317 assertions, 0 failures, 0 errors, 0 skips + +There are runtime options available, both from minitest itself, and also +provided via plugins. To see them, simply run with +--help+: + + % ruby -Ilib:test test/minitest/test_minitest_test.rb --help + minitest options: + -h, --help Display this help. + -s, --seed SEED Sets random seed. Also via env. Eg: SEED=n rake + -v, --verbose Verbose. Show progress processing files. + -n, --name PATTERN Filter run on /regexp/ or string. + -e, --exclude PATTERN Exclude /regexp/ or string from run. + + Known extensions: pride, autotest + -p, --pride Pride. Show your testing pride! + -a, --autotest Connect to autotest server. + +=== Rake Tasks + +You can set up a rake task to run all your tests by adding this to your Rakefile: + + require "minitest/test_task" + + Minitest::TestTask.create # named test, sensible defaults + + # or more explicitly: + + Minitest::TestTask.create(:test) do |t| + t.libs << "test" + t.libs << "lib" + t.warning = false + t.test_globs = ["test/**/*_test.rb"] + end + + task :default => :test + +Each of these will generate 4 tasks: + + rake test :: Run the test suite. + rake test:cmd :: Print out the test command. + rake test:isolated :: Show which test files fail when run separately. + rake test:slow :: Show bottom 25 tests sorted by time. + +=== Rake Task Variables + +There are a bunch of variables you can supply to rake to modify the run. + + MT_LIB_EXTRAS :: Extra libs to dynamically override/inject for custom runs. + N :: -n: Tests to run (string or /regexp/). + X :: -x: Tests to exclude (string or /regexp/). + A :: Any extra arguments. Honors shell quoting. + MT_CPU :: How many threads to use for parallel test runs + SEED :: -s --seed Sets random seed. + TESTOPTS :: Deprecated, same as A + FILTER :: Deprecated, same as A + +== Writing Extensions + +To define a plugin, add a file named minitest/XXX_plugin.rb to your +project/gem. That file must be discoverable via ruby's LOAD_PATH (via +rubygems or otherwise). Minitest will find and require that file using +Gem.find_files. It will then try to call +plugin_XXX_init+ during +startup. The option processor will also try to call +plugin_XXX_options+ +passing the OptionParser instance and the current options hash. This +lets you register your own command-line options. Here's a totally +bogus example: + + # minitest/bogus_plugin.rb: + + module Minitest + def self.plugin_bogus_options(opts, options) + opts.on "--myci", "Report results to my CI" do + options[:myci] = true + options[:myci_addr] = get_myci_addr + options[:myci_port] = get_myci_port + end + end + + def self.plugin_bogus_init(options) + self.reporter << MyCI.new(options) if options[:myci] + end + end + +=== Adding custom reporters + +Minitest uses composite reporter to output test results using multiple +reporter instances. You can add new reporters to the composite during +the init_plugins phase. As we saw in +plugin_bogus_init+ above, you +simply add your reporter instance to the composite via <<. + ++AbstractReporter+ defines the API for reporters. You may subclass it +and override any method you want to achieve your desired behavior. + +start :: Called when the run has started. +record :: Called for each result, passed or otherwise. +report :: Called at the end of the run. +passed? :: Called to see if you detected any problems. + +Using our example above, here is how we might implement MyCI: + + # minitest/bogus_plugin.rb + + module Minitest + class MyCI < AbstractReporter + attr_accessor :results, :addr, :port + + def initialize options + self.results = [] + self.addr = options[:myci_addr] + self.port = options[:myci_port] + end + + def record result + self.results << result + end + + def report + CI.connect(addr, port).send_results self.results + end + end + + # code from above... + end + +== FAQ + +=== What versions are compatible with what? Or what versions are supported? + +Minitest is a dependency of rails, which until very recently had an +overzealous backwards compatibility policy. As such, I'm stuck +supporting versions of ruby that are long past EOL. Hopefully I'll be +able to support only current versions of ruby sometime in the near +future. + +NOTICE: At this point, I will only locally test/dev against the +currently 3 supported (non-EOL) versions of ruby. I cannot and will +not maintain that many builds. + +(As of 2025-02-03) + +Current versions of rails: (https://endoflife.date/rails) + + | rails | min ruby | minitest | status | EOL Date | + |-------+----------+----------+----------+------------| + | 8.0 | >= 3.2 | >= 5.1 | Current | 2026-11-07 | + | 7.2 | >= 3.1 | >= 5.1 | Current | 2026-08-09 | + | 7.1 | >= 2.7 | >= 5.1 | Security | 2025-10-01 | + | 7.0 | >= 2.7 | >= 5.1 | Security | 2025-04-01 | + | 6.1 | >= 2.5 | >= 5.1 | EOL | 2024-10-01 | + | 6.0 | >= 2.5 | >= 5.1 | EOL | 2023-06-01 | + | 5.2 | >= 2.2.2 | ~> 5.1 | EOL | 2022-06-01 | + +If you want to look at the requirements for a specific version, run: + + gem spec -r --ruby rails -v 8.0.0 + +Current versions of ruby: (https://endoflife.date/ruby) + + | ruby | Status | EOL Date | + |------+---------+------------| + | 3.4 | Current | 2028-03-31 | + | 3.3 | Maint | 2027-03-31 | + | 3.2 | Security| 2026-03-31 | + | 3.1 | EOL | 2025-03-31 | + | 3.0 | EOL | 2024-03-31 | + | 2.7 | EOL | 2023-03-31 | + | 2.6 | EOL | 2022-03-31 | + | 2.5 | EOL | 2021-03-31 | + +=== How to test SimpleDelegates? + +The following implementation and test: + + class Worker < SimpleDelegator + def work + end + end + + describe Worker do + before do + @worker = Worker.new(Object.new) + end + + it "must respond to work" do + _(@worker).must_respond_to :work + end + end + +outputs a failure: + + 1) Failure: + Worker#test_0001_must respond to work [bug11.rb:16]: + Expected # (Object) to respond to #work. + +Worker is a SimpleDelegate which in 1.9+ is a subclass of BasicObject. +Expectations are put on Object (one level down) so the Worker +(SimpleDelegate) hits +method_missing+ and delegates down to the ++Object.new+ instance. That object doesn't respond to work so the test +fails. + +You can bypass SimpleDelegate#method_missing by extending the worker +with Minitest::Expectations. You can either do that in your setup at +the instance level, like: + + before do + @worker = Worker.new(Object.new) + @worker.extend Minitest::Expectations + end + +or you can extend the Worker class (within the test file!), like: + + class Worker + include ::Minitest::Expectations + end + +=== How to share code across test classes? + +Use a module. That's exactly what they're for: + + module UsefulStuff + def useful_method + # ... + end + end + + describe Blah do + include UsefulStuff + + def test_whatever + # useful_method available here + end + end + +Remember, +describe+ simply creates test classes. It's just ruby at +the end of the day and all your normal Good Ruby Rules (tm) apply. If +you want to extend your test using setup/teardown via a module, just +make sure you ALWAYS call super. before/after automatically call super +for you, so make sure you don't do it twice. + +=== How to run code before a group of tests? + +Use a constant with begin...end like this: + + describe Blah do + SETUP = begin + # ... this runs once when describe Blah starts + end + # ... + end + +This can be useful for expensive initializations or sharing state. +Remember, this is just ruby code, so you need to make sure this +technique and sharing state doesn't interfere with your tests. + +=== Why am I seeing uninitialized constant MiniTest::Test (NameError)? + +Are you running the test with Bundler (e.g. via bundle exec )? If so, +in order to require minitest, you must first add the gem 'minitest' +to your Gemfile and run +bundle+. Once it's installed, you should be +able to require minitest and run your tests. + +== Prominent Projects using Minitest: + +* arel +* journey +* mime-types +* nokogiri +* rails (active_support et al) +* rake +* rdoc +* ...and of course, everything from seattle.rb... + +== Developing Minitest: + +Minitest requires {Hoe}[https://rubygems.org/gems/hoe]. + +=== Minitest's own tests require UTF-8 external encoding. + +This is a common problem in Windows, where the default external Encoding is +often CP850, but can affect any platform. +Minitest can run test suites using any Encoding, but to run Minitest's +own tests you must have a default external Encoding of UTF-8. + +If your encoding is wrong, you'll see errors like: + + --- expected + +++ actual + @@ -1,2 +1,3 @@ + # encoding: UTF-8 + -"Expected /\\w+/ to not match \"blah blah blah\"." + +"Expected /\\w+/ to not match # encoding: UTF-8 + +\"blah blah blah\"." + +To check your current encoding, run: + + ruby -e 'puts Encoding.default_external' + +If your output is something other than UTF-8, you can set the RUBYOPTS +env variable to a value of '-Eutf-8'. Something like: + + RUBYOPT='-Eutf-8' ruby -e 'puts Encoding.default_external' + +Check your OS/shell documentation for the precise syntax (the above +will not work on a basic Windows CMD prompt, look for the SET command). +Once you've got it successfully outputing UTF-8, use the same setting +when running rake in Minitest. + +=== Minitest's own tests require GNU (or similar) diff. + +This is also a problem primarily affecting Windows developers. PowerShell +has a command called diff, but it is not suitable for use with Minitest. + +If you see failures like either of these, you are probably missing diff tool: + + 4) Failure: + TestMinitestUnitTestCase#test_assert_equal_different_long [D:/ruby/seattlerb/minitest/test/minitest/test_minitest_test.rb:936]: + Expected: "--- expected\n+++ actual\n@@ -1 +1 @@\n-\"hahahahahahahahahahahahahahahahahahahaha\"\n+\"blahblahblahblahblahblahblahblahblahblah\"\n" + Actual: "Expected: \"hahahahahahahahahahahahahahahahahahahaha\"\n Actual: \"blahblahblahblahblahblahblahblahblahblah\"" + + + 5) Failure: + TestMinitestUnitTestCase#test_assert_equal_different_collection_hash_hex_invisible [D:/ruby/seattlerb/minitest/test/minitest/test_minitest_test.rb:845]: + Expected: "No visible difference in the Hash#inspect output.\nYou should look at the implementation of #== on Hash or its members.\n + {1=>#}" + Actual: "Expected: {1=>#}\n Actual: {1=>#}" + + +If you use Cygwin or MSYS2 or similar there are packages that include a +GNU diff for Windows. If you don't, you can download GNU diffutils from +http://gnuwin32.sourceforge.net/packages/diffutils.htm +(make sure to add it to your PATH). + +You can make sure it's installed and path is configured properly with: + + diff.exe -v + +There are multiple lines of output, the first should be something like: + + diff (GNU diffutils) 2.8.1 + +If you are using PowerShell make sure you run diff.exe, not just diff, +which will invoke the PowerShell built in function. + +== Known Extensions: + +capybara_minitest_spec :: Bridge between Capybara RSpec matchers and + Minitest::Spec expectations (e.g. + page.must_have_content("Title")). +color_pound_spec_reporter :: Test names print Ruby Object types in color with + your Minitest Spec style tests. +minispec-metadata :: Metadata for describe/it blocks & CLI tag filter. + E.g. it "requires JS driver", js: true do & + ruby test.rb --tag js runs tests tagged :js. +minispec-rails :: Minimal support to use Spec style in Rails 5+. +mini-apivore :: for swagger based automated API testing. +minitest-around :: Around block for minitest. An alternative to + setup/teardown dance. +minitest-assert_errors :: Adds Minitest assertions to test for errors raised + or not raised by Minitest itself. +minitest-autotest :: autotest is a continuous testing facility meant to + be used during development. +minitest-bacon :: minitest-bacon extends minitest with bacon-like + functionality. +minitest-bang :: Adds support for RSpec-style let! to immediately + invoke let statements before each test. +minitest-bisect :: Helps you isolate and debug random test failures. +minitest-blink1_reporter :: Display test results with a Blink1. +minitest-capistrano :: Assertions and expectations for testing + Capistrano recipes. +minitest-capybara :: Capybara matchers support for minitest unit and + spec. +minitest-cc :: It provides minimal information about code coverage. +minitest-chef-handler :: Run Minitest suites as Chef report handlers +minitest-ci :: CI reporter plugin for Minitest. +minitest-context :: Defines contexts for code reuse in Minitest + specs that share common expectations. +minitest-debugger :: Wraps assert so failed assertions drop into + the ruby debugger. +minitest-display :: Patches Minitest to allow for an easily + configurable output. +minitest-documentation :: Minimal documentation format inspired by rspec's. +minitest-doc_reporter :: Detailed output inspired by rspec's documentation + format. +minitest-emoji :: Print out emoji for your test passes, fails, and + skips. +minitest-english :: Semantically symmetric aliases for assertions and + expectations. +minitest-excludes :: Clean API for excluding certain tests you + don't want to run under certain conditions. +minitest-fail-fast :: Reimplements RSpec's "fail fast" feature +minitest-filecontent :: Support unit tests with expectation results in files. + Differing results will be stored again in files. +minitest-filesystem :: Adds assertion and expectation to help testing + filesystem contents. +minitest-firemock :: Makes your Minitest mocks more resilient. +minitest-focus :: Focus on one test at a time. +minitest-gcstats :: A minitest plugin that adds a report of the top + tests by number of objects allocated. +minitest-global_expectations:: Support minitest expectation methods for all objects +minitest-great_expectations :: Generally useful additions to minitest's + assertions and expectations. +minitest-growl :: Test notifier for minitest via growl. +minitest-happy :: GLOBALLY ACTIVATE MINITEST PRIDE! RAWR! +minitest-have_tag :: Adds Minitest assertions to test for the existence of + HTML tags, including contents, within a provided string. +minitest-heat :: Reporting that builds a heat map of failure locations +minitest-hooks :: Around and before_all/after_all/around_all hooks +minitest-hyper :: Pretty, single-page HTML reports for your Minitest runs +minitest-implicit-subject :: Implicit declaration of the test subject. +minitest-instrument :: Instrument ActiveSupport::Notifications when + test method is executed. +minitest-instrument-db :: Store information about speed of test execution + provided by minitest-instrument in database. +minitest-junit :: JUnit-style XML reporter for minitest. +minitest-keyword :: Use Minitest assertions with keyword arguments. +minitest-libnotify :: Test notifier for minitest via libnotify. +minitest-line :: Run test at line number. +minitest-logger :: Define assert_log and enable minitest to test log messages. + Supports Logger and Log4r::Logger. +minitest-macruby :: Provides extensions to minitest for macruby UI + testing. +minitest-matchers :: Adds support for RSpec-style matchers to + minitest. +minitest-matchers_vaccine :: Adds assertions that adhere to the matcher spec, + but without any expectation infections. +minitest-metadata :: Annotate tests with metadata (key-value). +minitest-mock_expectations :: Provides method call assertions for minitest. +minitest-mongoid :: Mongoid assertion matchers for Minitest. +minitest-must_not :: Provides must_not as an alias for wont in + Minitest. +minitest-optional_retry :: Automatically retry failed test to help with flakiness. +minitest-osx :: Reporter for the Mac OS X notification center. +minitest-parallel_fork :: Fork-based parallelization +minitest-parallel-db :: Run tests in parallel with a single database. +minitest-power_assert :: PowerAssert for Minitest. +minitest-predicates :: Adds support for .predicate? methods. +minitest-profile :: List the 10 slowest tests in your suite. +minitest-rails :: Minitest integration for Rails 3.x. +minitest-rails-capybara :: Capybara integration for Minitest::Rails. +minitest-reporters :: Create customizable Minitest output formats. +minitest-rg :: Colored red/green output for Minitest. +minitest-rspec_mocks :: Use RSpec Mocks with Minitest. +minitest-server :: minitest-server provides a client/server setup + with your minitest process, allowing your test + run to send its results directly to a handler. +minitest-sequel :: Minitest assertions to speed-up development and + testing of Ruby Sequel database setups. +minitest-shared_description :: Support for shared specs and shared spec + subclasses +minitest-should_syntax :: RSpec-style x.should == y assertions for + Minitest. +minitest-shouldify :: Adding all manner of shoulds to Minitest (bad + idea) +minitest-snail :: Print a list of tests that take too long +minitest-spec-context :: Provides rspec-ish context method to + Minitest::Spec. +minitest-spec-expect :: Expect syntax for Minitest::Spec (e.g. + expect(sequences).to_include :celery_man). +minitest-spec-magic :: Minitest::Spec extensions for Rails and beyond. +minitest-spec-rails :: Drop in Minitest::Spec superclass for + ActiveSupport::TestCase. +minitest-sprint :: Runs (Get it? It's fast!) your tests and makes + it easier to rerun individual failures. +minitest-stately :: Find leaking state between tests +minitest-stub_any_instance :: Stub any instance of a method on the given class + for the duration of a block. +minitest-stub-const :: Stub constants for the duration of a block. +minitest-tags :: Add tags for minitest. +minitest-unordered :: Adds a new assertion to minitest for checking the + contents of a collection, ignoring element order. +minitest-vcr :: Automatic cassette management with Minitest::Spec + and VCR. +minitest_log :: Adds structured logging, data explication, and verdicts. +minitest_owrapper :: Get tests results as a TestResult object. +minitest_should :: Shoulda style syntax for minitest test::unit. +minitest_tu_shim :: Bridges between test/unit and minitest. +mongoid-minitest :: Minitest matchers for Mongoid. +mutant-minitest :: Minitest integration for mutant. +pry-rescue :: A pry plugin w/ minitest support. See + pry-rescue/minitest.rb. +rematch :: Declutter your test files from large hardcoded data + and update them automatically when your code changes. +rspec2minitest :: Easily translate any RSpec matchers to Minitest + assertions and expectations. +stubberry :: Multiple stubbing 'berries', sweet and useful + stub helpers and assertions. ( stub_must, + assert_method_called, stubbing ORM objects by id ) + +== Unknown Extensions: + +Authors... Please send me a pull request with a description of your minitest extension. + +* assay-minitest +* detroit-minitest +* em-minitest-spec +* flexmock-minitest +* guard-minitest +* guard-minitest-decisiv +* minitest-activemodel +* minitest-ar-assertions +* minitest-capybara-unit +* minitest-colorer +* minitest-deluxe +* minitest-extra-assertions +* minitest-rails-shoulda +* minitest-spec +* minitest-spec-should +* minitest-sugar +* spork-minitest + +== Minitest related goods + +* minitest/pride fabric: https://www.spoonflower.com/fabric/3928730-again-by-katie_allen + +== REQUIREMENTS: + +* Ruby 2.3+. No magic is involved. I hope. + +== INSTALL: + + sudo gem install minitest + +On 1.9, you already have it. To get newer candy you can still install +the gem, and then requiring "minitest/autorun" should automatically +pull it in. If not, you'll need to do it yourself: + + gem "minitest" # ensures you"re using the gem, and not the built-in MT + require "minitest/autorun" + + # ... usual testing stuffs ... + +DO NOTE: There is a serious problem with the way that ruby 1.9/2.0 +packages their own gems. They install a gem specification file, but +don't install the gem contents in the gem path. This messes up +Gem.find_files and many other things (gem which, gem contents, etc). + +Just install minitest as a gem for real and you'll be happier. + +== LICENSE: + +(The MIT License) + +Copyright (c) Ryan Davis, seattle.rb + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/Rakefile b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/Rakefile new file mode 100644 index 00000000..d53528b5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/Rakefile @@ -0,0 +1,81 @@ +# -*- ruby -*- + +require "rubygems" +require "hoe" +$:.unshift "lib" # to pick up lib/minitest/test_task.rb when minitest not installed + +Hoe.plugin :seattlerb +Hoe.plugin :rdoc + +Hoe.spec "minitest" do + developer "Ryan Davis", "ryand-ruby@zenspider.com" + + license "MIT" + + require_ruby_version [">= 2.7", "< 4.0"] +end + +desc "Find missing expectations" +task :specs do + $:.unshift "lib" + require "minitest/test" + require "minitest/spec" + + pos_prefix, neg_prefix = "must", "wont" + skip_re = /^(must|wont)$|wont_(throw)|must_(block|not?_|nothing|send|raise$)/x + dont_flip_re = /(must|wont)_(include|respond_to)/ + + map = { + /(must_throw)s/ => '\1', + /(?!not)_same/ => "_be_same_as", + /_in_/ => "_be_within_", + /_operator/ => "_be", + /_includes/ => "_include", + /(must|wont)_(.*_of|nil|silent|empty)/ => '\1_be_\2', + /must_raises/ => "must_raise", + /(must|wont)_pattern/ => '\1_pattern_match', + /(must|wont)_predicate/ => '\1_be', + /(must|wont)_path_exists/ => 'path_\1_exist', + } + + expectations = Minitest::Expectations.public_instance_methods.map(&:to_s) + assertions = Minitest::Assertions.public_instance_methods.map(&:to_s) + + assertions.sort.each do |assertion| + expectation = case assertion + when /^assert/ then + assertion.sub(/^assert/, pos_prefix.to_s) + when /^refute/ then + assertion.sub(/^refute/, neg_prefix.to_s) + end + + next unless expectation + next if expectation =~ skip_re + + regexp, replacement = map.find { |re, _| expectation =~ re } + expectation.sub! regexp, replacement if replacement + + next if expectations.include? expectation + + args = [assertion, expectation].map(&:to_sym).map(&:inspect) + args << :reverse if expectation =~ dont_flip_re + + puts + puts "##" + puts "# :method: #{expectation}" + puts "# See Minitest::Assertions##{assertion}" + puts + puts "infect_an_assertion #{args.join ", "}" + end +end + +task :bugs do + sh "for f in bug*.rb ; do echo $f; echo; #{Gem.ruby} -Ilib $f && rm $f ; done" +end + +Minitest::TestTask.create :testW0 do |t| + t.warning = false + t.test_prelude = "$-w = nil" +end + +# vim: syntax=Ruby diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/design_rationale.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/design_rationale.rb new file mode 100644 index 00000000..a3fcc378 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/design_rationale.rb @@ -0,0 +1,52 @@ +# Specs: # Equivalent Unit Tests: +############################################################################### +describe Thingy do # class TestThingy < Minitest::Test + before do # def setup + do_some_setup # super + end # do_some_setup + # end + it "should do the first thing" do # + 1.must_equal 1 # def test_first_thing + end # assert_equal 1, 1 + # end + describe SubThingy do # end + before do # + do_more_setup # class TestSubThingy < TestThingy + end # def setup + # super + it "should do the second thing" do # do_more_setup + 2.must_equal 2 # end + end # + end # def test_second_thing +end # assert_equal 2, 2 + # end + # end +############################################################################### +# runs 2 specs # runs 3 tests +############################################################################### +# The specs generate: + +class ThingySpec < Minitest::Spec + def setup + super + do_some_setup + end + + def test_should_do_the_first_thing + assert_equal 1, 1 + end +end + +class SubThingySpec < ThingySpec + def setup + super + do_more_setup + end + + # because only setup/teardown is inherited, not specs + remove_method :test_should_do_the_first_thing + + def test_should_do_the_second_thing + assert_equal 2, 2 + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/hoe/minitest.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/hoe/minitest.rb new file mode 100644 index 00000000..4f59d3af --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/hoe/minitest.rb @@ -0,0 +1,29 @@ +# :stopdoc: + +class Hoe + # empty +end + +module Hoe::Minitest + def minitest? + self.name == "minitest" + end + + def initialize_minitest + unless minitest? then + dir = "../../minitest/dev/lib" + Hoe.add_include_dirs dir if File.directory? dir + end + + gem "minitest" + require "minitest" + version = Minitest::VERSION.split(".").first(2).join "." + + dependency "minitest", "~> #{version}", :development unless + minitest? or ENV["MT_NO_ISOLATE"] + end + + def define_minitest_tasks + self.testlib = :minitest + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest.rb new file mode 100644 index 00000000..b3a3bd71 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest.rb @@ -0,0 +1,1235 @@ +require "optparse" +require "stringio" +require "etc" + +require_relative "minitest/parallel" +require_relative "minitest/compress" + +## +# The top-level namespace for Minitest. Also the location of the main +# runtime. See +Minitest.run+ for more information. + +module Minitest + VERSION = "5.25.5" # :nodoc: + + @@installed_at_exit ||= false + @@after_run = [] + @extensions = [] + + def self.cattr_accessor name # :nodoc: + (class << self; self; end).attr_accessor name + end + + ## + # The random seed used for this run. This is used to srand at the + # start of the run and between each +Runnable.run+. + # + # Set via Minitest.run after processing args. + + cattr_accessor :seed + + ## + # Parallel test executor + + cattr_accessor :parallel_executor + + warn "DEPRECATED: use MT_CPU instead of N for parallel test runs" if ENV["N"] && ENV["N"].to_i > 0 + n_threads = (ENV["MT_CPU"] || ENV["N"] || Etc.nprocessors).to_i + + self.parallel_executor = Parallel::Executor.new n_threads + + ## + # Filter object for backtraces. + + cattr_accessor :backtrace_filter + + ## + # Reporter object to be used for all runs. + # + # NOTE: This accessor is only available during setup, not during runs. + + cattr_accessor :reporter + + ## + # Names of known extension plugins. + + cattr_accessor :extensions + + ## + # The signal to use for dumping information to STDERR. Defaults to "INFO". + + cattr_accessor :info_signal + self.info_signal = "INFO" + + cattr_accessor :allow_fork + self.allow_fork = false + + ## + # Registers Minitest to run at process exit + + def self.autorun + Warning[:deprecated] = true if + Object.const_defined?(:Warning) && Warning.respond_to?(:[]=) + + at_exit { + next if $! and not ($!.kind_of? SystemExit and $!.success?) + + exit_code = nil + + pid = Process.pid + at_exit { + next if !Minitest.allow_fork && Process.pid != pid + @@after_run.reverse_each(&:call) + exit exit_code || false + } + + exit_code = Minitest.run ARGV + } unless @@installed_at_exit + @@installed_at_exit = true + end + + ## + # A simple hook allowing you to run a block of code after everything + # is done running. Eg: + # + # Minitest.after_run { p $debugging_info } + + def self.after_run &block + @@after_run << block + end + + ## + # Register a plugin to be used. Does NOT require / load it. + + def self.register_plugin name_or_mod + self.extensions << name_or_mod + nil + end + + def self.load_plugins # :nodoc: + return unless defined? Gem + + seen = {} + + Gem.find_files("minitest/*_plugin.rb").each do |plugin_path| + name = File.basename plugin_path, "_plugin.rb" + + next if seen[name] + seen[name] = true + + require plugin_path + self.extensions << name + end + end + + def self.init_plugins options # :nodoc: + self.extensions.each do |mod_or_meth| + case mod_or_meth + when Symbol, String then + name = mod_or_meth + msg = "plugin_#{name}_init" + next unless self.respond_to? msg + send msg, options + when Module then + recv = mod_or_meth + next unless recv.respond_to? :minitest_plugin_init + recv.minitest_plugin_init options + else + raise ArgumentError, "plugin is %p, but it must be a symbol, string or module" % [mod_or_meth] + end + end + end + + def self.process_args args = [] # :nodoc: + options = { + :io => $stdout, + } + orig_args = args.dup + + OptionParser.new do |opts| + opts.banner = "minitest options:" + opts.version = Minitest::VERSION + + opts.on "-h", "--help", "Display this help." do + puts opts + exit + end + + opts.on "--no-plugins", "Bypass minitest plugin auto-loading (or set $MT_NO_PLUGINS)." + + desc = "Sets random seed. Also via env. Eg: SEED=n rake" + opts.on "-s", "--seed SEED", Integer, desc do |m| + options[:seed] = m.to_i + end + + opts.on "-v", "--verbose", "Verbose. Show progress processing files." do + options[:verbose] = true + end + + opts.on "-q", "--quiet", "Quiet. Show no progress processing files." do + options[:quiet] = true + end + + opts.on "--show-skips", "Show skipped at the end of run." do + options[:show_skips] = true + end + + opts.on "-n", "--name PATTERN", "Filter run on /regexp/ or string." do |a| + options[:filter] = a + end + + opts.on "-e", "--exclude PATTERN", "Exclude /regexp/ or string from run." do |a| + options[:exclude] = a + end + + opts.on "-S", "--skip CODES", String, "Skip reporting of certain types of results (eg E)." do |s| + options[:skip] = s.chars.to_a + end + + ruby27plus = ::Warning.respond_to? :[]= + + opts.on "-W[error]", String, "Turn Ruby warnings into errors" do |s| + options[:Werror] = true + case s + when "error", "all", nil then + require "minitest/error_on_warning" + $VERBOSE = true + ::Warning[:deprecated] = true if ruby27plus + else + ::Warning[s.to_sym] = true if ruby27plus # check validity of category + end + end + + unless extensions.empty? + opts.separator "" + opts.separator "Known extensions: #{extensions.join ", "}" + + extensions.each do |mod_or_meth| + case mod_or_meth + when Symbol, String then + meth = mod_or_meth + msg = "plugin_#{meth}_options" + send msg, opts, options if respond_to? msg + when Module + recv = mod_or_meth + next unless recv.respond_to? :minitest_plugin_options + recv.minitest_plugin_options opts, options + else + raise ArgumentError, "plugin is %p, but it must be a symbol, string or module" % [mod_or_meth] + end + end + end + + begin + opts.parse! args + rescue OptionParser::InvalidOption => e + puts + puts e + puts + puts opts + exit 1 + end + + orig_args -= args + end + + unless options[:seed] then + srand + options[:seed] = (ENV["SEED"] || srand).to_i % 0xFFFF + orig_args << "--seed" << options[:seed].to_s + end + + options[:args] = orig_args.map { |s| + s.match?(/[\s|&<>$()]/) ? s.inspect : s + }.join " " + + options + end + + ## + # This is the top-level run method. Everything starts from here. It + # tells each Runnable sub-class to run, and each of those are + # responsible for doing whatever they do. + # + # The overall structure of a run looks like this: + # + # Minitest.autorun + # Minitest.run(args) + # Minitest.load_plugins + # Minitest.process_args + # Minitest.init_plugins + # Minitest.__run(reporter, options) + # Runnable.runnables.each + # runnable_klass.run(reporter, options) + # self.runnable_methods.each + # self.run_one_method(self, runnable_method, reporter) + # Minitest.run_one_method(klass, runnable_method) + # klass.new(runnable_method).run + + def self.run args = [] + self.load_plugins unless args.delete("--no-plugins") || ENV["MT_NO_PLUGINS"] + + options = process_args args + + Minitest.seed = options[:seed] + srand Minitest.seed + + reporter = CompositeReporter.new + reporter << SummaryReporter.new(options[:io], options) + reporter << ProgressReporter.new(options[:io], options) unless options[:quiet] + + self.reporter = reporter # this makes it available to plugins + self.init_plugins options + self.reporter = nil # runnables shouldn't depend on the reporter, ever + + self.parallel_executor.start if parallel_executor.respond_to? :start + reporter.start + begin + __run reporter, options + rescue Interrupt + warn "Interrupted. Exiting..." + end + self.parallel_executor.shutdown + + # might have been removed/replaced during init_plugins: + summary = reporter.reporters.grep(SummaryReporter).first + + reporter.report + + return empty_run! options if summary && summary.count == 0 + reporter.passed? + end + + def self.empty_run! options # :nodoc: + filter = options[:filter] + return true unless filter # no filter, but nothing ran == success + + warn "Nothing ran for filter: %s" % [filter] + + require "did_you_mean" # soft dependency, punt if it doesn't load + + ms = Runnable.runnables.flat_map(&:runnable_methods) + cs = DidYouMean::SpellChecker.new(dictionary: ms).correct filter + + warn DidYouMean::Formatter.message_for cs unless cs.empty? + rescue LoadError + # do nothing + end + + ## + # Internal run method. Responsible for telling all Runnable + # sub-classes to run. + + def self.__run reporter, options + suites = Runnable.runnables.shuffle + parallel, serial = suites.partition { |s| s.test_order == :parallel } + + # If we run the parallel tests before the serial tests, the parallel tests + # could run in parallel with the serial tests. This would be bad because + # the serial tests won't lock around Reporter#record. Run the serial tests + # first, so that after they complete, the parallel tests will lock when + # recording results. + serial.map { |suite| suite.run reporter, options } + + parallel.map { |suite| suite.run reporter, options } + end + + def self.filter_backtrace bt # :nodoc: + result = backtrace_filter.filter bt + result = bt.dup if result.empty? + result + end + + ## + # Represents anything "runnable", like Test, Spec, Benchmark, or + # whatever you can dream up. + # + # Subclasses of this are automatically registered and available in + # Runnable.runnables. + + class Runnable + ## + # Number of assertions executed in this run. + + attr_accessor :assertions + + ## + # An assertion raised during the run, if any. + + attr_accessor :failures + + ## + # The time it took to run. + + attr_accessor :time + + def time_it # :nodoc: + t0 = Minitest.clock_time + + yield + ensure + self.time = Minitest.clock_time - t0 + end + + ## + # Name of the run. + + def name + @NAME + end + + ## + # Set the name of the run. + + def name= o + @NAME = o + end + + ## + # Returns all instance methods matching the pattern +re+. + + def self.methods_matching re + public_instance_methods(true).grep(re).map(&:to_s) + end + + def self.reset # :nodoc: + @@runnables = [] + end + + reset + + ## + # Responsible for running all runnable methods in a given class, + # each in its own instance. Each instance is passed to the + # reporter to record. + + def self.run reporter, options = {} + pos = options[:filter] + neg = options[:exclude] + + pos = Regexp.new $1 if pos.kind_of?(String) && pos =~ %r%/(.*)/% + neg = Regexp.new $1 if neg.kind_of?(String) && neg =~ %r%/(.*)/% + + filtered_methods = self.runnable_methods + .select { |m| !pos || pos === m || pos === "#{self}##{m}" } + .reject { |m| neg && (neg === m || neg === "#{self}##{m}") } + + return if filtered_methods.empty? + + t0 = name = nil + + @_info_handler = lambda do + unless reporter.passed? then + warn "Current results:" + warn reporter.reporters.grep(SummaryReporter).first + end + + warn "Current: %s#%s %.2fs" % [self, name, Minitest.clock_time - t0] + end + + with_info_handler reporter do + filtered_methods.each do |method_name| + name = method_name + t0 = Minitest.clock_time + + run_one_method self, method_name, reporter + end + end + end + + ## + # Runs a single method and has the reporter record the result. + # This was considered internal API but is factored out of run so + # that subclasses can specialize the running of an individual + # test. See Minitest::ParallelTest::ClassMethods for an example. + + def self.run_one_method klass, method_name, reporter + reporter.prerecord klass, method_name + reporter.record Minitest.run_one_method(klass, method_name) + end + + ## + # Defines the order to run tests (:random by default). Override + # this or use a convenience method to change it for your tests. + + def self.test_order + :random + end + + def self.with_info_handler reporter, &block # :nodoc: + on_signal ::Minitest.info_signal, @_info_handler, &block + end + + SIGNALS = Signal.list # :nodoc: + + def self.on_signal name, action # :nodoc: + supported = SIGNALS[name] + + old_trap = trap name do + old_trap.call if old_trap.respond_to? :call + action.call + end if supported + + yield + ensure + trap name, old_trap if supported + end + + ## + # Each subclass of Runnable is responsible for overriding this + # method to return all runnable methods. See #methods_matching. + + def self.runnable_methods + raise NotImplementedError, "subclass responsibility" + end + + ## + # Returns all subclasses of Runnable. + + def self.runnables + @@runnables + end + + @@marshal_dump_warned = false + + def marshal_dump # :nodoc: + unless @@marshal_dump_warned then + warn ["Minitest::Runnable#marshal_dump is deprecated.", + "You might be violating internals. From", caller(1..1).first].join " " + @@marshal_dump_warned = true + end + + [self.name, self.failures, self.assertions, self.time] + end + + def marshal_load ary # :nodoc: + self.name, self.failures, self.assertions, self.time = ary + end + + def failure # :nodoc: + self.failures.first + end + + def initialize name # :nodoc: + self.name = name + self.failures = [] + self.assertions = 0 + # lazy initializer for metadata + end + + ## + # Metadata you attach to the test results that get sent to the reporter. + # + # Lazily initializes to a hash, to keep memory down. + # + # NOTE: this data *must* be plain (read: marshal-able) data! + # Hashes! Arrays! Strings! + + def metadata + @metadata ||= {} + end + + ## + # Sets metadata, mainly used for +Result.from+. + + attr_writer :metadata + + ## + # Returns true if metadata exists. + + def metadata? + defined? @metadata + end + + ## + # Runs a single method. Needs to return self. + + def run + raise NotImplementedError, "subclass responsibility" + end + + ## + # Did this run pass? + # + # Note: skipped runs are not considered passing, but they don't + # cause the process to exit non-zero. + + def passed? + raise NotImplementedError, "subclass responsibility" + end + + ## + # Returns a single character string to print based on the result + # of the run. One of ".", "F", + # "E" or "S". + + def result_code + raise NotImplementedError, "subclass responsibility" + end + + ## + # Was this run skipped? See #passed? for more information. + + def skipped? + raise NotImplementedError, "subclass responsibility" + end + end + + ## + # Shared code for anything that can get passed to a Reporter. See + # Minitest::Test & Minitest::Result. + + module Reportable + ## + # Did this run pass? + # + # Note: skipped runs are not considered passing, but they don't + # cause the process to exit non-zero. + + def passed? + not self.failure + end + + BASE_DIR = "#{Dir.pwd}/" # :nodoc: + + ## + # The location identifier of this test. Depends on a method + # existing called class_name. + + def location + loc = " [#{self.failure.location.delete_prefix BASE_DIR}]" unless passed? or error? + "#{self.class_name}##{self.name}#{loc}" + end + + def class_name # :nodoc: + raise NotImplementedError, "subclass responsibility" + end + + ## + # Returns ".", "F", or "E" based on the result of the run. + + def result_code + self.failure and self.failure.result_code or "." + end + + ## + # Was this run skipped? + + def skipped? + self.failure and Skip === self.failure + end + + ## + # Did this run error? + + def error? + self.failures.any? UnexpectedError + end + end + + ## + # This represents a test result in a clean way that can be + # marshalled over a wire. Tests can do anything they want to the + # test instance and can create conditions that cause Marshal.dump to + # blow up. By using Result.from(a_test) you can be reasonably sure + # that the test result can be marshalled. + + class Result < Runnable + include Minitest::Reportable + + undef_method :marshal_dump + undef_method :marshal_load + + ## + # The class name of the test result. + + attr_accessor :klass + + ## + # The location of the test method. + + attr_accessor :source_location + + ## + # Create a new test result from a Runnable instance. + + def self.from runnable + o = runnable + + r = self.new o.name + r.klass = o.class.name + r.assertions = o.assertions + r.failures = o.failures.dup + r.time = o.time + r.metadata = o.metadata if o.metadata? + + r.source_location = o.method(o.name).source_location rescue ["unknown", -1] + + r + end + + def class_name # :nodoc: + self.klass # for Minitest::Reportable + end + + def to_s # :nodoc: + return location if passed? and not skipped? + + failures.map { |failure| + "#{failure.result_label}:\n#{self.location}:\n#{failure.message}\n" + }.join "\n" + end + end + + ## + # Defines the API for Reporters. Subclass this and override whatever + # you want. Go nuts. + + class AbstractReporter + + def initialize # :nodoc: + @mutex = Mutex.new + end + + ## + # Starts reporting on the run. + + def start + end + + ## + # About to start running a test. This allows a reporter to show + # that it is starting or that we are in the middle of a test run. + + def prerecord klass, name + end + + ## + # Output and record the result of the test. Call + # {result#result_code}[rdoc-ref:Runnable#result_code] to get the + # result character string. Stores the result of the run if the run + # did not pass. + + def record result + end + + ## + # Outputs the summary of the run. + + def report + end + + ## + # Did this run pass? + + def passed? + true + end + + def synchronize &block # :nodoc: + @mutex.synchronize(&block) + end + end + + class Reporter < AbstractReporter # :nodoc: + ## + # The IO used to report. + + attr_accessor :io + + ## + # Command-line options for this run. + + attr_accessor :options + + def initialize io = $stdout, options = {} # :nodoc: + super() + self.io = io + self.options = options + end + end + + ## + # A very simple reporter that prints the "dots" during the run. + # + # This is added to the top-level CompositeReporter at the start of + # the run. If you want to change the output of minitest via a + # plugin, pull this out of the composite and replace it with your + # own. + + class ProgressReporter < Reporter + def prerecord klass, name # :nodoc: + return unless options[:verbose] + + io.print "%s#%s = " % [klass.name, name] + io.flush + end + + def record result # :nodoc: + io.print "%.2f s = " % [result.time] if options[:verbose] + io.print result.result_code + io.puts if options[:verbose] + end + end + + ## + # A reporter that gathers statistics about a test run. Does not do + # any IO because meant to be used as a parent class for a reporter + # that does. + # + # If you want to create an entirely different type of output (eg, + # CI, HTML, etc), this is the place to start. + # + # Example: + # + # class JenkinsCIReporter < StatisticsReporter + # def report + # super # Needed to calculate some statistics + # + # print " #{new_bt.size}" + error.set_backtrace new_bt + end + + self.error = error + end + + def backtrace # :nodoc: + self.error.backtrace + end + + BASE_RE = %r%#{Dir.pwd}/% # :nodoc: + + def message # :nodoc: + bt = Minitest.filter_backtrace(self.backtrace).join("\n ") + .gsub(BASE_RE, "") + "#{self.error.class}: #{self.error.message}\n #{bt}" + end + + def result_label # :nodoc: + "Error" + end + end + + ## + # Assertion raised on warning when running in -Werror mode. + + class UnexpectedWarning < Assertion + def result_label # :nodoc: + "Warning" + end + end + + ## + # Provides a simple set of guards that you can use in your tests + # to skip execution if it is not applicable. These methods are + # mixed into Test as both instance and class methods so you + # can use them inside or outside of the test methods. + # + # def test_something_for_mri + # skip "bug 1234" if jruby? + # # ... + # end + # + # if windows? then + # # ... lots of test methods ... + # end + + module Guard + + ## + # Is this running on jruby? + + def jruby? platform = RUBY_PLATFORM + "java" == platform + end + + ## + # Is this running on maglev? + + def maglev? platform = defined?(RUBY_ENGINE) && RUBY_ENGINE + where = Minitest.filter_backtrace(caller).first + where = where.split(":in ", 2).first # clean up noise + warn "DEPRECATED: `maglev?` called from #{where}. This will fail in Minitest 6." + "maglev" == platform + end + + ## + # Is this running on mri? + + def mri? platform = RUBY_DESCRIPTION + platform.start_with? "ruby" + end + + ## + # Is this running on macOS? + + def osx? platform = RUBY_PLATFORM + platform.include? "darwin" + end + + ## + # Is this running on rubinius? + + def rubinius? platform = defined?(RUBY_ENGINE) && RUBY_ENGINE + where = Minitest.filter_backtrace(caller).first + where = where.split(":in ", 2).first # clean up noise + warn "DEPRECATED: `rubinius?` called from #{where}. This will fail in Minitest 6." + "rbx" == platform + end + + ## + # Is this running on windows? + + def windows? platform = RUBY_PLATFORM + /mswin|mingw/.match? platform + end + end + + ## + # The standard backtrace filter for minitest. + # + # See Minitest.backtrace_filter=. + + class BacktraceFilter + + MT_RE = %r%lib/minitest|internal:warning% # :nodoc: + + ## + # The regular expression to use to filter backtraces. Defaults to +MT_RE+. + + attr_accessor :regexp + + def initialize regexp = MT_RE # :nodoc: + self.regexp = regexp + end + + ## + # Filter +bt+ to something useful. Returns the whole thing if + # $DEBUG (ruby) or $MT_DEBUG (env). + + def filter bt + return ["No backtrace"] unless bt + + return bt.dup if $DEBUG || ENV["MT_DEBUG"] + + new_bt = bt.take_while { |line| !regexp.match? line.to_s } + new_bt = bt.select { |line| !regexp.match? line.to_s } if new_bt.empty? + new_bt = bt.dup if new_bt.empty? + + new_bt + end + end + + self.backtrace_filter = BacktraceFilter.new + + def self.run_one_method klass, method_name # :nodoc: + result = klass.new(method_name).run + raise "#{klass}#run _must_ return a Result" unless Result === result + result + end + + # :stopdoc: + + if defined? Process::CLOCK_MONOTONIC # :nodoc: + def self.clock_time + Process.clock_gettime Process::CLOCK_MONOTONIC + end + else + def self.clock_time + Time.now + end + end + + class Runnable # re-open + def self.inherited klass # :nodoc: + self.runnables << klass + super + end + end + + # :startdoc: +end + +require "minitest/test" diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/assertions.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/assertions.rb new file mode 100644 index 00000000..bbc9aab9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/assertions.rb @@ -0,0 +1,855 @@ +require "rbconfig" +require "tempfile" +require "stringio" + +module Minitest + ## + # Minitest Assertions. All assertion methods accept a +msg+ which is + # printed if the assertion fails. + # + # Protocol: Nearly everything here boils up to +assert+, which + # expects to be able to increment an instance accessor named + # +assertions+. This is not provided by Assertions and must be + # provided by the thing including Assertions. See Minitest::Runnable + # for an example. + + module Assertions + UNDEFINED = Object.new # :nodoc: + + def UNDEFINED.inspect # :nodoc: + "UNDEFINED" # again with the rdoc bugs... :( + end + + ## + # Returns the diff command to use in #diff. Tries to intelligently + # figure out what diff to use. + + def self.diff + return @diff if defined? @diff + + @diff = if (RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ and + system "diff.exe", __FILE__, __FILE__) then + "diff.exe -u" + elsif system "gdiff", __FILE__, __FILE__ then + "gdiff -u" # solaris and kin suck + elsif system "diff", __FILE__, __FILE__ then + "diff -u" + else + nil + end + end + + ## + # Set the diff command to use in #diff. + + def self.diff= o + @diff = o + end + + ## + # Returns a diff between +exp+ and +act+. If there is no known + # diff command or if it doesn't make sense to diff the output + # (single line, short output), then it simply returns a basic + # comparison between the two. + # + # See +things_to_diff+ for more info. + + def diff exp, act + result = nil + + expect, butwas = things_to_diff exp, act + + return "Expected: #{mu_pp exp}\n Actual: #{mu_pp act}" unless + expect + + Tempfile.open "expect" do |a| + a.puts expect + a.flush + + Tempfile.open "butwas" do |b| + b.puts butwas + b.flush + + result = `#{Minitest::Assertions.diff} #{a.path} #{b.path}` + result.sub!(/^\-\-\- .+/, "--- expected") + result.sub!(/^\+\+\+ .+/, "+++ actual") + + if result.empty? then + klass = exp.class + result = [ + "No visible difference in the #{klass}#inspect output.\n", + "You should look at the implementation of #== on ", + "#{klass} or its members.\n", + expect, + ].join + end + end + end + + result + end + + ## + # Returns things to diff [expect, butwas], or [nil, nil] if nothing to diff. + # + # Criterion: + # + # 1. Strings include newlines or escaped newlines, but not both. + # 2. or: String lengths are > 30 characters. + # 3. or: Strings are equal to each other (but maybe different encodings?). + # 4. and: we found a diff executable. + + def things_to_diff exp, act + expect = mu_pp_for_diff exp + butwas = mu_pp_for_diff act + + e1, e2 = expect.include?("\n"), expect.include?("\\n") + b1, b2 = butwas.include?("\n"), butwas.include?("\\n") + + need_to_diff = + (e1 ^ e2 || + b1 ^ b2 || + expect.size > 30 || + butwas.size > 30 || + expect == butwas) && + Minitest::Assertions.diff + + need_to_diff && [expect, butwas] + end + + ## + # This returns a human-readable version of +obj+. By default + # #inspect is called. You can override this to use #pretty_inspect + # if you want. + # + # See Minitest::Test.make_my_diffs_pretty! + + def mu_pp obj + s = obj.inspect.encode Encoding.default_external + + return s unless String === obj && + (obj.encoding != Encoding.default_external || !obj.valid_encoding?) + + enc = "# encoding: #{obj.encoding}" + val = "# valid: #{obj.valid_encoding?}" + + [enc, val, s].join "\n" + end + + ## + # This returns a diff-able more human-readable version of +obj+. + # This differs from the regular mu_pp because it expands escaped + # newlines and makes hex-values (like object_ids) generic. This + # uses mu_pp to do the first pass and then cleans it up. + + def mu_pp_for_diff obj + str = mu_pp obj + + # both '\n' & '\\n' (_after_ mu_pp (aka inspect)) + single = str.match?(/(?exp == act printing the difference between + # the two, if possible. + # + # If there is no visible difference but the assertion fails, you + # should suspect that your #== is buggy, or your inspect output is + # missing crucial details. For nicer structural diffing, set + # Minitest::Test.make_my_diffs_pretty! + # + # For floats use assert_in_delta. + # + # See also: Minitest::Assertions.diff + + def assert_equal exp, act, msg = nil + msg = message(msg, E) { diff exp, act } + result = assert exp == act, msg + + if nil == exp then + if Minitest::VERSION >= "6" then + refute_nil exp, "Use assert_nil if expecting nil." + else + warn "DEPRECATED: Use assert_nil if expecting nil from #{_where}. This will fail in Minitest 6." + end + end + + result + end + + ## + # For comparing Floats. Fails unless +exp+ and +act+ are within +delta+ + # of each other. + # + # assert_in_delta Math::PI, (22.0 / 7.0), 0.01 + + def assert_in_delta exp, act, delta = 0.001, msg = nil + n = (exp - act).abs + msg = message(msg) { + "Expected |#{exp} - #{act}| (#{n}) to be <= #{delta}" + } + assert delta >= n, msg + end + + ## + # For comparing Floats. Fails unless +exp+ and +act+ have a relative + # error less than +epsilon+. + + def assert_in_epsilon exp, act, epsilon = 0.001, msg = nil + assert_in_delta exp, act, [exp.abs, act.abs].min * epsilon, msg + end + + ## + # Fails unless +collection+ includes +obj+. + + def assert_includes collection, obj, msg = nil + msg = message(msg) { + "Expected #{mu_pp collection} to include #{mu_pp obj}" + } + assert_respond_to collection, :include? + assert collection.include?(obj), msg + end + + ## + # Fails unless +obj+ is an instance of +cls+. + + def assert_instance_of cls, obj, msg = nil + msg = message(msg) { + "Expected #{mu_pp obj} to be an instance of #{cls}, not #{obj.class}" + } + + assert obj.instance_of?(cls), msg + end + + ## + # Fails unless +obj+ is a kind of +cls+. + + def assert_kind_of cls, obj, msg = nil + msg = message(msg) { + "Expected #{mu_pp obj} to be a kind of #{cls}, not #{obj.class}" + } + + assert obj.kind_of?(cls), msg + end + + ## + # Fails unless +matcher+ =~ +obj+. + + def assert_match matcher, obj, msg = nil + msg = message(msg) { "Expected #{mu_pp matcher} to match #{mu_pp obj}" } + assert_respond_to matcher, :=~ + matcher = Regexp.new Regexp.escape matcher if String === matcher + assert matcher =~ obj, msg + + Regexp.last_match + end + + ## + # Fails unless +obj+ is nil + + def assert_nil obj, msg = nil + msg = message(msg) { "Expected #{mu_pp obj} to be nil" } + assert obj.nil?, msg + end + + ## + # For testing with binary operators. Eg: + # + # assert_operator 5, :<=, 4 + + def assert_operator o1, op, o2 = UNDEFINED, msg = nil + return assert_predicate o1, op, msg if UNDEFINED == o2 + msg = message(msg) { "Expected #{mu_pp o1} to be #{op} #{mu_pp o2}" } + assert o1.__send__(op, o2), msg + end + + ## + # Fails if stdout or stderr do not output the expected results. + # Pass in nil if you don't care about that streams output. Pass in + # "" if you require it to be silent. Pass in a regexp if you want + # to pattern match. + # + # assert_output(/hey/) { method_with_output } + # + # NOTE: this uses #capture_io, not #capture_subprocess_io. + # + # See also: #assert_silent + + def assert_output stdout = nil, stderr = nil + flunk "assert_output requires a block to capture output." unless + block_given? + + out, err = capture_io do + yield + end + + err_msg = Regexp === stderr ? :assert_match : :assert_equal if stderr + out_msg = Regexp === stdout ? :assert_match : :assert_equal if stdout + + y = send err_msg, stderr, err, "In stderr" if err_msg + x = send out_msg, stdout, out, "In stdout" if out_msg + + (!stdout || x) && (!stderr || y) + rescue Assertion + raise + rescue => e + raise UnexpectedError, e + end + + ## + # Fails unless +path+ exists. + + def assert_path_exists path, msg = nil + msg = message(msg) { "Expected path '#{path}' to exist" } + assert File.exist?(path), msg + end + + ## + # For testing with pattern matching (only supported with Ruby 3.0 and later) + # + # # pass + # assert_pattern { [1,2,3] => [Integer, Integer, Integer] } + # + # # fail "length mismatch (given 3, expected 1)" + # assert_pattern { [1,2,3] => [Integer] } + # + # The bare => pattern will raise a NoMatchingPatternError on failure, which would + # normally be counted as a test error. This assertion rescues NoMatchingPatternError and + # generates a test failure. Any other exception will be raised as normal and generate a test + # error. + + def assert_pattern + raise NotImplementedError, "only available in Ruby 3.0+" unless RUBY_VERSION >= "3.0" + flunk "assert_pattern requires a block to capture errors." unless block_given? + + begin # TODO: remove after ruby 2.6 dropped + yield + pass + rescue NoMatchingPatternError => e + flunk e.message + end + end + + ## + # For testing with predicates. Eg: + # + # assert_predicate str, :empty? + # + # This is really meant for specs and is front-ended by assert_operator: + # + # str.must_be :empty? + + def assert_predicate o1, op, msg = nil + msg = message(msg) { "Expected #{mu_pp o1} to be #{op}" } + assert o1.__send__(op), msg + end + + ## + # Fails unless the block raises one of +exp+. Returns the + # exception matched so you can check the message, attributes, etc. + # + # +exp+ takes an optional message on the end to help explain + # failures and defaults to StandardError if no exception class is + # passed. Eg: + # + # assert_raises(CustomError) { method_with_custom_error } + # + # With custom error message: + # + # assert_raises(CustomError, 'This should have raised CustomError') { method_with_custom_error } + # + # Using the returned object: + # + # error = assert_raises(CustomError) do + # raise CustomError, 'This is really bad' + # end + # + # assert_equal 'This is really bad', error.message + + def assert_raises *exp + flunk "assert_raises requires a block to capture errors." unless + block_given? + + msg = "#{exp.pop}.\n" if String === exp.last + exp << StandardError if exp.empty? + + begin + yield + rescue *exp => e + pass # count assertion + return e + rescue Minitest::Assertion # incl Skip & UnexpectedError + # don't count assertion + raise + rescue SignalException, SystemExit + raise + rescue Exception => e + flunk proc { + exception_details(e, "#{msg}#{mu_pp exp} exception expected, not") + } + end + + exp = exp.first if exp.size == 1 + + flunk "#{msg}#{mu_pp exp} expected but nothing was raised." + end + + ## + # Fails unless +obj+ responds to +meth+. + # include_all defaults to false to match Object#respond_to? + + def assert_respond_to obj, meth, msg = nil, include_all: false + msg = message(msg) { + "Expected #{mu_pp obj} (#{obj.class}) to respond to ##{meth}" + } + assert obj.respond_to?(meth, include_all), msg + end + + ## + # Fails unless +exp+ and +act+ are #equal? + + def assert_same exp, act, msg = nil + msg = message(msg) { + data = [mu_pp(act), act.object_id, mu_pp(exp), exp.object_id] + "Expected %s (oid=%d) to be the same as %s (oid=%d)" % data + } + assert exp.equal?(act), msg + end + + ## + # +send_ary+ is a receiver, message and arguments. + # + # Fails unless the call returns a true value + + def assert_send send_ary, m = nil + warn "DEPRECATED: assert_send. From #{_where}" + + recv, msg, *args = send_ary + m = message(m) { + "Expected #{mu_pp recv}.#{msg}(*#{mu_pp args}) to return true" + } + assert recv.__send__(msg, *args), m + end + + ## + # Fails if the block outputs anything to stderr or stdout. + # + # See also: #assert_output + + def assert_silent + assert_output "", "" do + yield + end + end + + ## + # Fails unless the block throws +sym+ + + def assert_throws sym, msg = nil + default = "Expected #{mu_pp sym} to have been thrown" + caught = true + value = catch sym do + begin + yield + rescue ThreadError => e # wtf?!? 1.8 + threads == suck + default += ", not :#{e.message[/uncaught throw \`(\w+?)\'/, 1]}" + rescue ArgumentError => e # 1.9 exception + raise e unless e.message.include? "uncaught throw" + default += ", not #{e.message.split(/ /).last}" + rescue NameError => e # 1.8 exception + raise e unless e.name == sym + default += ", not #{e.name.inspect}" + end + caught = false + end + + assert caught, message(msg) { default } + value + rescue Assertion + raise + rescue => e + raise UnexpectedError, e + end + + ## + # Captures $stdout and $stderr into strings: + # + # out, err = capture_io do + # puts "Some info" + # warn "You did a bad thing" + # end + # + # assert_match %r%info%, out + # assert_match %r%bad%, err + # + # NOTE: For efficiency, this method uses StringIO and does not + # capture IO for subprocesses. Use #capture_subprocess_io for + # that. + + def capture_io + _synchronize do + begin + captured_stdout, captured_stderr = StringIO.new, StringIO.new + + orig_stdout, orig_stderr = $stdout, $stderr + $stdout, $stderr = captured_stdout, captured_stderr + + yield + + return captured_stdout.string, captured_stderr.string + ensure + $stdout = orig_stdout + $stderr = orig_stderr + end + end + end + + ## + # Captures $stdout and $stderr into strings, using Tempfile to + # ensure that subprocess IO is captured as well. + # + # out, err = capture_subprocess_io do + # system "echo Some info" + # system "echo You did a bad thing 1>&2" + # end + # + # assert_match %r%info%, out + # assert_match %r%bad%, err + # + # NOTE: This method is approximately 10x slower than #capture_io so + # only use it when you need to test the output of a subprocess. + + def capture_subprocess_io + _synchronize do + begin + require "tempfile" + + captured_stdout, captured_stderr = Tempfile.new("out"), Tempfile.new("err") + + orig_stdout, orig_stderr = $stdout.dup, $stderr.dup + $stdout.reopen captured_stdout + $stderr.reopen captured_stderr + + yield + + $stdout.rewind + $stderr.rewind + + return captured_stdout.read, captured_stderr.read + ensure + $stdout.reopen orig_stdout + $stderr.reopen orig_stderr + + orig_stdout.close + orig_stderr.close + captured_stdout.close! + captured_stderr.close! + end + end + end + + ## + # Returns details for exception +e+ + + def exception_details e, msg + [ + msg, + "Class: <#{e.class}>", + "Message: <#{e.message.inspect}>", + "---Backtrace---", + Minitest.filter_backtrace(e.backtrace), + "---------------", + ].join "\n" + end + + ## + # Fails after a given date (in the local time zone). This allows + # you to put time-bombs in your tests if you need to keep + # something around until a later date lest you forget about it. + + def fail_after y, m, d, msg + flunk msg if Time.now > Time.local(y, m, d) + end + + ## + # Fails with +msg+. + + def flunk msg = nil + msg ||= "Epic Fail!" + assert false, msg + end + + ## + # Returns a proc that will output +msg+ along with the default message. + + def message msg = nil, ending = nil, &default + proc { + msg = msg.call.chomp(".") if Proc === msg + custom_message = "#{msg}.\n" unless msg.nil? or msg.to_s.empty? + "#{custom_message}#{default.call}#{ending || "."}" + } + end + + ## + # used for counting assertions + + def pass _msg = nil + assert true + end + + ## + # Fails if +test+ is truthy. + + def refute test, msg = nil + msg ||= message { "Expected #{mu_pp test} to not be truthy" } + assert !test, msg + end + + ## + # Fails if +obj+ is empty. + + def refute_empty obj, msg = nil + msg = message(msg) { "Expected #{mu_pp obj} to not be empty" } + assert_respond_to obj, :empty? + refute obj.empty?, msg + end + + ## + # Fails if exp == act. + # + # For floats use refute_in_delta. + + def refute_equal exp, act, msg = nil + msg = message(msg) { + "Expected #{mu_pp act} to not be equal to #{mu_pp exp}" + } + refute exp == act, msg + end + + ## + # For comparing Floats. Fails if +exp+ is within +delta+ of +act+. + # + # refute_in_delta Math::PI, (22.0 / 7.0) + + def refute_in_delta exp, act, delta = 0.001, msg = nil + n = (exp - act).abs + msg = message(msg) { + "Expected |#{exp} - #{act}| (#{n}) to not be <= #{delta}" + } + refute delta >= n, msg + end + + ## + # For comparing Floats. Fails if +exp+ and +act+ have a relative error + # less than +epsilon+. + + def refute_in_epsilon a, b, epsilon = 0.001, msg = nil + refute_in_delta a, b, a * epsilon, msg + end + + ## + # Fails if +collection+ includes +obj+. + + def refute_includes collection, obj, msg = nil + msg = message(msg) { + "Expected #{mu_pp collection} to not include #{mu_pp obj}" + } + assert_respond_to collection, :include? + refute collection.include?(obj), msg + end + + ## + # Fails if +obj+ is an instance of +cls+. + + def refute_instance_of cls, obj, msg = nil + msg = message(msg) { + "Expected #{mu_pp obj} to not be an instance of #{cls}" + } + refute obj.instance_of?(cls), msg + end + + ## + # Fails if +obj+ is a kind of +cls+. + + def refute_kind_of cls, obj, msg = nil + msg = message(msg) { "Expected #{mu_pp obj} to not be a kind of #{cls}" } + refute obj.kind_of?(cls), msg + end + + ## + # Fails if +matcher+ =~ +obj+. + + def refute_match matcher, obj, msg = nil + msg = message(msg) { "Expected #{mu_pp matcher} to not match #{mu_pp obj}" } + assert_respond_to matcher, :=~ + matcher = Regexp.new Regexp.escape matcher if String === matcher + refute matcher =~ obj, msg + end + + ## + # Fails if +obj+ is nil. + + def refute_nil obj, msg = nil + msg = message(msg) { "Expected #{mu_pp obj} to not be nil" } + refute obj.nil?, msg + end + + ## + # For testing with pattern matching (only supported with Ruby 3.0 and later) + # + # # pass + # refute_pattern { [1,2,3] => [String] } + # + # # fail "NoMatchingPatternError expected, but nothing was raised." + # refute_pattern { [1,2,3] => [Integer, Integer, Integer] } + # + # This assertion expects a NoMatchingPatternError exception, and will fail if none is raised. Any + # other exceptions will be raised as normal and generate a test error. + + def refute_pattern + raise NotImplementedError, "only available in Ruby 3.0+" unless RUBY_VERSION >= "3.0" + flunk "refute_pattern requires a block to capture errors." unless block_given? + + begin + yield + flunk "NoMatchingPatternError expected, but nothing was raised." + rescue NoMatchingPatternError + pass + end + end + + ## + # Fails if +o1+ is not +op+ +o2+. Eg: + # + # refute_operator 1, :>, 2 #=> pass + # refute_operator 1, :<, 2 #=> fail + + def refute_operator o1, op, o2 = UNDEFINED, msg = nil + return refute_predicate o1, op, msg if UNDEFINED == o2 + msg = message(msg) { "Expected #{mu_pp o1} to not be #{op} #{mu_pp o2}" } + refute o1.__send__(op, o2), msg + end + + ## + # Fails if +path+ exists. + + def refute_path_exists path, msg = nil + msg = message(msg) { "Expected path '#{path}' to not exist" } + refute File.exist?(path), msg + end + + ## + # For testing with predicates. + # + # refute_predicate str, :empty? + # + # This is really meant for specs and is front-ended by refute_operator: + # + # str.wont_be :empty? + + def refute_predicate o1, op, msg = nil + msg = message(msg) { "Expected #{mu_pp o1} to not be #{op}" } + refute o1.__send__(op), msg + end + + ## + # Fails if +obj+ responds to the message +meth+. + # include_all defaults to false to match Object#respond_to? + + def refute_respond_to obj, meth, msg = nil, include_all: false + msg = message(msg) { "Expected #{mu_pp obj} to not respond to #{meth}" } + + refute obj.respond_to?(meth, include_all), msg + end + + ## + # Fails if +exp+ is the same (by object identity) as +act+. + + def refute_same exp, act, msg = nil + msg = message(msg) { + data = [mu_pp(act), act.object_id, mu_pp(exp), exp.object_id] + "Expected %s (oid=%d) to not be the same as %s (oid=%d)" % data + } + refute exp.equal?(act), msg + end + + ## + # Skips the current run. If run in verbose-mode, the skipped run + # gets listed at the end of the run but doesn't cause a failure + # exit code. + + def skip msg = nil, _ignored = nil + msg ||= "Skipped, no message given" + @skip = true + raise Minitest::Skip, msg + end + + ## + # Skips the current run until a given date (in the local time + # zone). This allows you to put some fixes on hold until a later + # date, but still holds you accountable and prevents you from + # forgetting it. + + def skip_until y, m, d, msg + skip msg if Time.now < Time.local(y, m, d) + where = caller(1..1).first.rpartition(":in").reject(&:empty?).first + warn "Stale skip_until %p at %s" % [msg, where] + end + + ## + # Was this testcase skipped? Meant for #teardown. + + def skipped? + defined?(@skip) and @skip + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/autorun.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/autorun.rb new file mode 100644 index 00000000..0555eff9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/autorun.rb @@ -0,0 +1,6 @@ +require "minitest" +require "minitest/spec" +require "minitest/mock" +require "minitest/hell" if ENV["MT_HELL"] + +Minitest.autorun diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/benchmark.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/benchmark.rb new file mode 100644 index 00000000..defdbcb3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/benchmark.rb @@ -0,0 +1,452 @@ +require "minitest/test" +require "minitest/spec" + +module Minitest + ## + # Subclass Benchmark to create your own benchmark runs. Methods + # starting with "bench_" get executed on a per-class. + # + # See Minitest::Assertions + + class Benchmark < Test + def self.io # :nodoc: + @io + end + + def io # :nodoc: + self.class.io + end + + def self.run reporter, options = {} # :nodoc: + @io = reporter.io + super + end + + def self.runnable_methods # :nodoc: + methods_matching(/^bench_/) + end + + ## + # Returns a set of ranges stepped exponentially from +min+ to + # +max+ by powers of +base+. Eg: + # + # bench_exp(2, 16, 2) # => [2, 4, 8, 16] + + def self.bench_exp min, max, base = 10 + min = (Math.log10(min) / Math.log10(base)).to_i + max = (Math.log10(max) / Math.log10(base)).to_i + + (min..max).map { |m| base ** m }.to_a + end + + ## + # Returns a set of ranges stepped linearly from +min+ to +max+ by + # +step+. Eg: + # + # bench_linear(20, 40, 10) # => [20, 30, 40] + + def self.bench_linear min, max, step = 10 + (min..max).step(step).to_a + end + + ## + # Specifies the ranges used for benchmarking for that class. + # Defaults to exponential growth from 1 to 10k by powers of 10. + # Override if you need different ranges for your benchmarks. + # + # See also: ::bench_exp and ::bench_linear. + + def self.bench_range + bench_exp 1, 10_000 + end + + ## + # Runs the given +work+, gathering the times of each run. Range + # and times are then passed to a given +validation+ proc. Outputs + # the benchmark name and times in tab-separated format, making it + # easy to paste into a spreadsheet for graphing or further + # analysis. + # + # Ranges are specified by ::bench_range. + # + # Eg: + # + # def bench_algorithm + # validation = proc { |x, y| ... } + # assert_performance validation do |n| + # @obj.algorithm(n) + # end + # end + + def assert_performance validation, &work + range = self.class.bench_range + + io.print self.name + + times = [] + + range.each do |x| + GC.start + t0 = Minitest.clock_time + instance_exec(x, &work) + t = Minitest.clock_time - t0 + + io.print "\t%9.6f" % t + times << t + end + io.puts + + validation[range, times] + end + + ## + # Runs the given +work+ and asserts that the times gathered fit to + # match a constant rate (eg, linear slope == 0) within a given + # +threshold+. Note: because we're testing for a slope of 0, R^2 + # is not a good determining factor for the fit, so the threshold + # is applied against the slope itself. As such, you probably want + # to tighten it from the default. + # + # See https://www.graphpad.com/guides/prism/8/curve-fitting/reg_intepretingnonlinr2.htm + # for more details. + # + # Fit is calculated by #fit_linear. + # + # Ranges are specified by ::bench_range. + # + # Eg: + # + # def bench_algorithm + # assert_performance_constant 0.9999 do |n| + # @obj.algorithm(n) + # end + # end + + def assert_performance_constant threshold = 0.99, &work + validation = proc do |range, times| + a, b, rr = fit_linear range, times + assert_in_delta 0, b, 1 - threshold + [a, b, rr] + end + + assert_performance validation, &work + end + + ## + # Runs the given +work+ and asserts that the times gathered fit to + # match a exponential curve within a given error +threshold+. + # + # Fit is calculated by #fit_exponential. + # + # Ranges are specified by ::bench_range. + # + # Eg: + # + # def bench_algorithm + # assert_performance_exponential 0.9999 do |n| + # @obj.algorithm(n) + # end + # end + + def assert_performance_exponential threshold = 0.99, &work + assert_performance validation_for_fit(:exponential, threshold), &work + end + + ## + # Runs the given +work+ and asserts that the times gathered fit to + # match a logarithmic curve within a given error +threshold+. + # + # Fit is calculated by #fit_logarithmic. + # + # Ranges are specified by ::bench_range. + # + # Eg: + # + # def bench_algorithm + # assert_performance_logarithmic 0.9999 do |n| + # @obj.algorithm(n) + # end + # end + + def assert_performance_logarithmic threshold = 0.99, &work + assert_performance validation_for_fit(:logarithmic, threshold), &work + end + + ## + # Runs the given +work+ and asserts that the times gathered fit to + # match a straight line within a given error +threshold+. + # + # Fit is calculated by #fit_linear. + # + # Ranges are specified by ::bench_range. + # + # Eg: + # + # def bench_algorithm + # assert_performance_linear 0.9999 do |n| + # @obj.algorithm(n) + # end + # end + + def assert_performance_linear threshold = 0.99, &work + assert_performance validation_for_fit(:linear, threshold), &work + end + + ## + # Runs the given +work+ and asserts that the times gathered curve + # fit to match a power curve within a given error +threshold+. + # + # Fit is calculated by #fit_power. + # + # Ranges are specified by ::bench_range. + # + # Eg: + # + # def bench_algorithm + # assert_performance_power 0.9999 do |x| + # @obj.algorithm + # end + # end + + def assert_performance_power threshold = 0.99, &work + assert_performance validation_for_fit(:power, threshold), &work + end + + ## + # Takes an array of x/y pairs and calculates the general R^2 value. + # + # See: https://en.wikipedia.org/wiki/Coefficient_of_determination + + def fit_error xys + y_bar = sigma(xys) { |_, y| y } / xys.size.to_f + ss_tot = sigma(xys) { |_, y| (y - y_bar) ** 2 } + ss_err = sigma(xys) { |x, y| (yield(x) - y) ** 2 } + + 1 - (ss_err / ss_tot) + end + + ## + # To fit a functional form: y = ae^(bx). + # + # Takes x and y values and returns [a, b, r^2]. + # + # See: https://mathworld.wolfram.com/LeastSquaresFittingExponential.html + + def fit_exponential xs, ys + n = xs.size + xys = xs.zip ys + sxlny = sigma(xys) { |x, y| x * Math.log(y) } + slny = sigma(xys) { |_, y| Math.log(y) } + sx2 = sigma(xys) { |x, _| x * x } + sx = sigma xs + + c = n * sx2 - sx ** 2 + a = (slny * sx2 - sx * sxlny) / c + b = ( n * sxlny - sx * slny ) / c + + return Math.exp(a), b, fit_error(xys) { |x| Math.exp(a + b * x) } + end + + ## + # To fit a functional form: y = a + b*ln(x). + # + # Takes x and y values and returns [a, b, r^2]. + # + # See: https://mathworld.wolfram.com/LeastSquaresFittingLogarithmic.html + + def fit_logarithmic xs, ys + n = xs.size + xys = xs.zip ys + slnx2 = sigma(xys) { |x, _| Math.log(x) ** 2 } + slnx = sigma(xys) { |x, _| Math.log(x) } + sylnx = sigma(xys) { |x, y| y * Math.log(x) } + sy = sigma(xys) { |_, y| y } + + c = n * slnx2 - slnx ** 2 + b = ( n * sylnx - sy * slnx ) / c + a = (sy - b * slnx) / n + + return a, b, fit_error(xys) { |x| a + b * Math.log(x) } + end + + ## + # Fits the functional form: a + bx. + # + # Takes x and y values and returns [a, b, r^2]. + # + # See: https://mathworld.wolfram.com/LeastSquaresFitting.html + + def fit_linear xs, ys + n = xs.size + xys = xs.zip ys + sx = sigma xs + sy = sigma ys + sx2 = sigma(xs) { |x| x ** 2 } + sxy = sigma(xys) { |x, y| x * y } + + c = n * sx2 - sx**2 + a = (sy * sx2 - sx * sxy) / c + b = ( n * sxy - sx * sy ) / c + + return a, b, fit_error(xys) { |x| a + b * x } + end + + ## + # To fit a functional form: y = ax^b. + # + # Takes x and y values and returns [a, b, r^2]. + # + # See: https://mathworld.wolfram.com/LeastSquaresFittingPowerLaw.html + + def fit_power xs, ys + n = xs.size + xys = xs.zip ys + slnxlny = sigma(xys) { |x, y| Math.log(x) * Math.log(y) } + slnx = sigma(xs) { |x | Math.log(x) } + slny = sigma(ys) { | y| Math.log(y) } + slnx2 = sigma(xs) { |x | Math.log(x) ** 2 } + + b = (n * slnxlny - slnx * slny) / (n * slnx2 - slnx ** 2) + a = (slny - b * slnx) / n + + return Math.exp(a), b, fit_error(xys) { |x| (Math.exp(a) * (x ** b)) } + end + + ## + # Enumerates over +enum+ mapping +block+ if given, returning the + # sum of the result. Eg: + # + # sigma([1, 2, 3]) # => 1 + 2 + 3 => 6 + # sigma([1, 2, 3]) { |n| n ** 2 } # => 1 + 4 + 9 => 14 + + def sigma enum, &block + enum = enum.map(&block) if block + enum.sum + end + + ## + # Returns a proc that calls the specified fit method and asserts + # that the error is within a tolerable threshold. + + def validation_for_fit msg, threshold + proc do |range, times| + a, b, rr = send "fit_#{msg}", range, times + assert_operator rr, :>=, threshold + [a, b, rr] + end + end + end +end + +module Minitest + ## + # The spec version of Minitest::Benchmark. + + class BenchSpec < Benchmark + extend Minitest::Spec::DSL + + ## + # This is used to define a new benchmark method. You usually don't + # use this directly and is intended for those needing to write new + # performance curve fits (eg: you need a specific polynomial fit). + # + # See ::bench_performance_linear for an example of how to use this. + + def self.bench name, &block + define_method "bench_#{name.gsub(/\W+/, "_")}", &block + end + + ## + # Specifies the ranges used for benchmarking for that class. + # + # bench_range do + # bench_exp(2, 16, 2) + # end + # + # See Minitest::Benchmark#bench_range for more details. + + def self.bench_range &block + return super unless block + + meta = (class << self; self; end) + meta.send :define_method, "bench_range", &block + end + + ## + # Create a benchmark that verifies that the performance is linear. + # + # describe "my class Bench" do + # bench_performance_linear "fast_algorithm", 0.9999 do |n| + # @obj.fast_algorithm(n) + # end + # end + + def self.bench_performance_linear name, threshold = 0.99, &work + bench name do + assert_performance_linear threshold, &work + end + end + + ## + # Create a benchmark that verifies that the performance is constant. + # + # describe "my class Bench" do + # bench_performance_constant "zoom_algorithm!" do |n| + # @obj.zoom_algorithm!(n) + # end + # end + + def self.bench_performance_constant name, threshold = 0.99, &work + bench name do + assert_performance_constant threshold, &work + end + end + + ## + # Create a benchmark that verifies that the performance is exponential. + # + # describe "my class Bench" do + # bench_performance_exponential "algorithm" do |n| + # @obj.algorithm(n) + # end + # end + + def self.bench_performance_exponential name, threshold = 0.99, &work + bench name do + assert_performance_exponential threshold, &work + end + end + + ## + # Create a benchmark that verifies that the performance is logarithmic. + # + # describe "my class Bench" do + # bench_performance_logarithmic "algorithm" do |n| + # @obj.algorithm(n) + # end + # end + + def self.bench_performance_logarithmic name, threshold = 0.99, &work + bench name do + assert_performance_logarithmic threshold, &work + end + end + + ## + # Create a benchmark that verifies that the performance is power. + # + # describe "my class Bench" do + # bench_performance_power "algorithm" do |n| + # @obj.algorithm(n) + # end + # end + + def self.bench_performance_power name, threshold = 0.99, &work + bench name do + assert_performance_power threshold, &work + end + end + end + + Minitest::Spec.register_spec_type(/Bench(mark)?$/, Minitest::BenchSpec) +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/compress.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/compress.rb new file mode 100644 index 00000000..7ba0c536 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/compress.rb @@ -0,0 +1,94 @@ +module Minitest + ## + # Compresses backtraces. + + module Compress + + ## + # Takes a backtrace (array of strings) and compresses repeating + # cycles in it to make it more readable. + + def compress orig + ary = orig + + eswo = ->(a, n, off) { # each_slice_with_offset + if off.zero? then + a.each_slice n + else + # [ ...off... [...n...] [...n...] ... ] + front, back = a.take(off), a.drop(off) + [front].chain back.each_slice n + end + } + + 3.times do # maybe don't use loop do here? + index = ary # [ a b c b c b c d ] + .size + .times # 0...size + .group_by { |i| ary[i] } # { a: [0] b: [1 3 5], c: [2 4 6], d: [7] } + + order = index + .reject { |k, v| v.size == 1 } # { b: [1 3 5], c: [2 4 6] } + .sort_by { |k, a1| ### sort by max dist + min offset + d = a1.each_cons(2).sum { |a2, b| b-a2 } + [-d, a1.first] + } # b: [1 3 5] c: [2 4 6] + + ranges = order + .map { |k, a1| # [[1..2 3..4] [2..3 4..5]] + a1 + .each_cons(2) + .map { |a2, b| a2..b-1 } + } + + big_ranges = ranges + .flat_map { |a| # [1..2 3..4 2..3 4..5] + a.sort_by { |r| [-r.size, r.first] }.first 5 + } + .first(100) + + culprits = big_ranges + .map { |r| + eswo[ary, r.size, r.begin] # [o1 s1 s1 s2 s2] + .chunk_while { |a, b| a == b } # [[o1] [s1 s1] [s2 s2]] + .map { |a| [a.size, a.first] } # [[1 o1] [2 s1] [2 s2]] + } + .select { |chunks| + chunks.any? { |a| a.first > 1 } # compressed anything? + } + + min = culprits + .min_by { |a| a.flatten.size } # most compressed + + break unless min + + ary = min.flat_map { |(n, lines)| + if n > 1 then + [[n, compress(lines)]] # [o1 [2 s1] [2 s2]] + else + lines + end + } + end + + format = ->(lines) { + lines.flat_map { |line| + case line + when Array then + n, lines = line + lines = format[lines] + [ + " +->> #{n} cycles of #{lines.size} lines:", + *lines.map { |s| " | #{s}" }, + " +-<<", + ] + else + line + end + } + } + + format[ary] + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/error_on_warning.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/error_on_warning.rb new file mode 100644 index 00000000..d9dc16c8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/error_on_warning.rb @@ -0,0 +1,11 @@ +module Minitest + + module ErrorOnWarning # :nodoc: + def warn message, category: nil + message = "[#{category}] #{message}" if category + raise UnexpectedWarning, message + end + end + + ::Warning.singleton_class.prepend ErrorOnWarning +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/expectations.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/expectations.rb new file mode 100644 index 00000000..ef144d46 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/expectations.rb @@ -0,0 +1,321 @@ +## +# It's where you hide your "assertions". +# +# Please note, because of the way that expectations are implemented, +# all expectations (eg must_equal) are dependent upon a thread local +# variable +:current_spec+. If your specs rely on mixing threads into +# the specs themselves, you're better off using assertions or the new +# _(value) wrapper. For example: +# +# it "should still work in threads" do +# my_threaded_thingy do +# (1+1).must_equal 2 # bad +# assert_equal 2, 1+1 # good +# _(1 + 1).must_equal 2 # good +# value(1 + 1).must_equal 2 # good, also #expect +# _ { 1 + "1" }.must_raise TypeError # good +# end +# end + +module Minitest::Expectations + + ## + # See Minitest::Assertions#assert_empty. + # + # _(collection).must_be_empty + # + # :method: must_be_empty + + infect_an_assertion :assert_empty, :must_be_empty, :unary + + ## + # See Minitest::Assertions#assert_equal + # + # _(a).must_equal b + # + # :method: must_equal + + infect_an_assertion :assert_equal, :must_equal + + ## + # See Minitest::Assertions#assert_in_delta + # + # _(n).must_be_close_to m [, delta] + # + # :method: must_be_close_to + + infect_an_assertion :assert_in_delta, :must_be_close_to + + infect_an_assertion :assert_in_delta, :must_be_within_delta # :nodoc: + + ## + # See Minitest::Assertions#assert_in_epsilon + # + # _(n).must_be_within_epsilon m [, epsilon] + # + # :method: must_be_within_epsilon + + infect_an_assertion :assert_in_epsilon, :must_be_within_epsilon + + ## + # See Minitest::Assertions#assert_includes + # + # _(collection).must_include obj + # + # :method: must_include + + infect_an_assertion :assert_includes, :must_include, :reverse + + ## + # See Minitest::Assertions#assert_instance_of + # + # _(obj).must_be_instance_of klass + # + # :method: must_be_instance_of + + infect_an_assertion :assert_instance_of, :must_be_instance_of + + ## + # See Minitest::Assertions#assert_kind_of + # + # _(obj).must_be_kind_of mod + # + # :method: must_be_kind_of + + infect_an_assertion :assert_kind_of, :must_be_kind_of + + ## + # See Minitest::Assertions#assert_match + # + # _(a).must_match b + # + # :method: must_match + + infect_an_assertion :assert_match, :must_match + + ## + # See Minitest::Assertions#assert_nil + # + # _(obj).must_be_nil + # + # :method: must_be_nil + + infect_an_assertion :assert_nil, :must_be_nil, :unary + + ## + # See Minitest::Assertions#assert_operator + # + # _(n).must_be :<=, 42 + # + # This can also do predicates: + # + # _(str).must_be :empty? + # + # :method: must_be + + infect_an_assertion :assert_operator, :must_be, :reverse + + ## + # See Minitest::Assertions#assert_output + # + # _ { ... }.must_output out_or_nil [, err] + # + # :method: must_output + + infect_an_assertion :assert_output, :must_output, :block + + ## + # See Minitest::Assertions#assert_pattern + # + # _ { ... }.must_pattern_match [...] + # + # :method: must_pattern_match + + infect_an_assertion :assert_pattern, :must_pattern_match, :block + + ## + # See Minitest::Assertions#assert_raises + # + # _ { ... }.must_raise exception + # + # :method: must_raise + + infect_an_assertion :assert_raises, :must_raise, :block + + ## + # See Minitest::Assertions#assert_respond_to + # + # _(obj).must_respond_to msg + # + # :method: must_respond_to + + infect_an_assertion :assert_respond_to, :must_respond_to, :reverse + + ## + # See Minitest::Assertions#assert_same + # + # _(a).must_be_same_as b + # + # :method: must_be_same_as + + infect_an_assertion :assert_same, :must_be_same_as + + ## + # See Minitest::Assertions#assert_silent + # + # _ { ... }.must_be_silent + # + # :method: must_be_silent + + infect_an_assertion :assert_silent, :must_be_silent, :block + + ## + # See Minitest::Assertions#assert_throws + # + # _ { ... }.must_throw sym + # + # :method: must_throw + + infect_an_assertion :assert_throws, :must_throw, :block + + ## + # See Minitest::Assertions#assert_path_exists + # + # _(some_path).path_must_exist + # + # :method: path_must_exist + + infect_an_assertion :assert_path_exists, :path_must_exist, :unary + + ## + # See Minitest::Assertions#refute_path_exists + # + # _(some_path).path_wont_exist + # + # :method: path_wont_exist + + infect_an_assertion :refute_path_exists, :path_wont_exist, :unary + + ## + # See Minitest::Assertions#refute_empty + # + # _(collection).wont_be_empty + # + # :method: wont_be_empty + + infect_an_assertion :refute_empty, :wont_be_empty, :unary + + ## + # See Minitest::Assertions#refute_equal + # + # _(a).wont_equal b + # + # :method: wont_equal + + infect_an_assertion :refute_equal, :wont_equal + + ## + # See Minitest::Assertions#refute_in_delta + # + # _(n).wont_be_close_to m [, delta] + # + # :method: wont_be_close_to + + infect_an_assertion :refute_in_delta, :wont_be_close_to + + infect_an_assertion :refute_in_delta, :wont_be_within_delta # :nodoc: + + ## + # See Minitest::Assertions#refute_in_epsilon + # + # _(n).wont_be_within_epsilon m [, epsilon] + # + # :method: wont_be_within_epsilon + + infect_an_assertion :refute_in_epsilon, :wont_be_within_epsilon + + ## + # See Minitest::Assertions#refute_includes + # + # _(collection).wont_include obj + # + # :method: wont_include + + infect_an_assertion :refute_includes, :wont_include, :reverse + + ## + # See Minitest::Assertions#refute_instance_of + # + # _(obj).wont_be_instance_of klass + # + # :method: wont_be_instance_of + + infect_an_assertion :refute_instance_of, :wont_be_instance_of + + ## + # See Minitest::Assertions#refute_kind_of + # + # _(obj).wont_be_kind_of mod + # + # :method: wont_be_kind_of + + infect_an_assertion :refute_kind_of, :wont_be_kind_of + + ## + # See Minitest::Assertions#refute_match + # + # _(a).wont_match b + # + # :method: wont_match + + infect_an_assertion :refute_match, :wont_match + + ## + # See Minitest::Assertions#refute_nil + # + # _(obj).wont_be_nil + # + # :method: wont_be_nil + + infect_an_assertion :refute_nil, :wont_be_nil, :unary + + ## + # See Minitest::Assertions#refute_operator + # + # _(n).wont_be :<=, 42 + # + # This can also do predicates: + # + # str.wont_be :empty? + # + # :method: wont_be + + infect_an_assertion :refute_operator, :wont_be, :reverse + + ## + # See Minitest::Assertions#refute_pattern + # + # _ { ... }.wont_pattern_match [...] + # + # :method: wont_pattern_match + + infect_an_assertion :refute_pattern, :wont_pattern_match, :block + + ## + # See Minitest::Assertions#refute_respond_to + # + # _(obj).wont_respond_to msg + # + # :method: wont_respond_to + + infect_an_assertion :refute_respond_to, :wont_respond_to, :reverse + + ## + # See Minitest::Assertions#refute_same + # + # _(a).wont_be_same_as b + # + # :method: wont_be_same_as + + infect_an_assertion :refute_same, :wont_be_same_as +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/hell.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/hell.rb new file mode 100644 index 00000000..73c88acd --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/hell.rb @@ -0,0 +1,11 @@ +require "minitest/parallel" + +class Minitest::Test + parallelize_me! +end + +begin + require "minitest/proveit" +rescue LoadError + warn "NOTE: `gem install minitest-proveit` for even more hellish tests" +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/manual_plugins.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/manual_plugins.rb new file mode 100644 index 00000000..1f3d6529 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/manual_plugins.rb @@ -0,0 +1,16 @@ +require "minitest" + +ARGV << "--no-plugins" + +module Minitest + ## + # Manually load plugins by name. + + def self.load *names + names.each do |name| + require "minitest/#{name}_plugin" + + self.extensions << name.to_s + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/mock.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/mock.rb new file mode 100644 index 00000000..6b6e2454 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/mock.rb @@ -0,0 +1,347 @@ +class MockExpectationError < StandardError; end # :nodoc: + +module Minitest # :nodoc: + + ## + # A simple and clean mock object framework. + # + # All mock objects are an instance of Mock + + class Mock + alias __respond_to? respond_to? + + overridden_methods = %i[ + === + class + inspect + instance_eval + instance_variables + object_id + public_send + respond_to_missing? + send + to_s + ] + + overridden_methods << :singleton_method_added if defined?(::DEBUGGER__) + + instance_methods.each do |m| + undef_method m unless overridden_methods.include?(m) || m =~ /^__/ + end + + overridden_methods.map(&:to_sym).each do |method_id| + old_w, $-w = $-w, nil + define_method method_id do |*args, **kwargs, &b| + if @expected_calls.key? method_id then + if kwargs.empty? then # FIX: drop this after 2.7 dead + method_missing(method_id, *args, &b) + else + method_missing(method_id, *args, **kwargs, &b) + end + else + if kwargs.empty? then # FIX: drop this after 2.7 dead + super(*args, &b) + else + super(*args, **kwargs, &b) + end + end + end + ensure + $-w = old_w + end + + def initialize delegator = nil # :nodoc: + @delegator = delegator + @expected_calls = Hash.new { |calls, name| calls[name] = [] } + @actual_calls = Hash.new { |calls, name| calls[name] = [] } + end + + @@KW_WARNED = false # :nodoc: + + ## + # Expect that method +name+ is called, optionally with +args+ (and + # +kwargs+ or a +blk+), and returns +retval+. + # + # @mock.expect(:meaning_of_life, 42) + # @mock.meaning_of_life # => 42 + # + # @mock.expect(:do_something_with, true, [some_obj, true]) + # @mock.do_something_with(some_obj, true) # => true + # + # @mock.expect(:do_something_else, true) do |a1, a2| + # a1 == "buggs" && a2 == :bunny + # end + # + # +args+ is compared to the expected args using case equality (ie, the + # '===' operator), allowing for less specific expectations. + # + # @mock.expect(:uses_any_string, true, [String]) + # @mock.uses_any_string("foo") # => true + # @mock.verify # => true + # + # @mock.expect(:uses_one_string, true, ["foo"]) + # @mock.uses_one_string("bar") # => raises MockExpectationError + # + # If a method will be called multiple times, specify a new expect for each one. + # They will be used in the order you define them. + # + # @mock.expect(:ordinal_increment, 'first') + # @mock.expect(:ordinal_increment, 'second') + # + # @mock.ordinal_increment # => 'first' + # @mock.ordinal_increment # => 'second' + # @mock.ordinal_increment # => raises MockExpectationError "No more expects available for :ordinal_increment" + # + + def expect name, retval, args = [], **kwargs, &blk + name = name.to_sym + + if blk then + raise ArgumentError, "args ignored when block given" unless args.empty? + raise ArgumentError, "kwargs ignored when block given" unless kwargs.empty? + @expected_calls[name] << { :retval => retval, :block => blk } + else + raise ArgumentError, "args must be an array" unless Array === args + + if ENV["MT_KWARGS_HAC\K"] && (Hash === args.last || + Hash == args.last) then + if kwargs.empty? then + kwargs = args.pop + else + unless @@KW_WARNED then + from = caller(1..1).first + warn "Using MT_KWARGS_HAC\K yet passing kwargs. From #{from}" + @@KW_WARNED = true + end + end + end + + @expected_calls[name] << + { :retval => retval, :args => args, :kwargs => kwargs } + end + self + end + + def __call name, data # :nodoc: + case data + when Hash then + args = data[:args].inspect[1..-2] + kwargs = data[:kwargs] + if kwargs && !kwargs.empty? then + args << ", " unless args.empty? + args << kwargs.inspect[1..-2] + end + "#{name}(#{args}) => #{data[:retval].inspect}" + else + data.map { |d| __call name, d }.join ", " + end + end + + ## + # Verify that all methods were called as expected. Raises + # +MockExpectationError+ if the mock object was not called as + # expected. + + def verify + @expected_calls.each do |name, expected| + actual = @actual_calls.fetch name, nil # defaults to [] + raise MockExpectationError, "Expected #{__call name, expected[0]}" unless actual + raise MockExpectationError, "Expected #{__call name, expected[actual.size]}, got [#{__call name, actual}]" if + actual.size < expected.size + end + true + end + + def method_missing sym, *args, **kwargs, &block # :nodoc: + unless @expected_calls.key? sym then + if @delegator && @delegator.respond_to?(sym) + if kwargs.empty? then # FIX: drop this after 2.7 dead + return @delegator.public_send(sym, *args, &block) + else + return @delegator.public_send(sym, *args, **kwargs, &block) + end + else + raise NoMethodError, "unmocked method %p, expected one of %p" % + [sym, @expected_calls.keys.sort_by(&:to_s)] + end + end + + index = @actual_calls[sym].length + expected_call = @expected_calls[sym][index] + + unless expected_call then + raise MockExpectationError, "No more expects available for %p: %p %p" % + [sym, args, kwargs] + end + + expected_args, expected_kwargs, retval, val_block = + expected_call.values_at :args, :kwargs, :retval, :block + + expected_kwargs = kwargs.to_h { |ak, av| [ak, Object] } if + Hash == expected_kwargs + + if val_block then + # keep "verify" happy + @actual_calls[sym] << expected_call + + raise MockExpectationError, "mocked method %p failed block w/ %p %p" % + [sym, args, kwargs] unless val_block.call(*args, **kwargs, &block) + + return retval + end + + if expected_args.size != args.size then + raise ArgumentError, "mocked method %p expects %d arguments, got %p" % + [sym, expected_args.size, args] + end + + if expected_kwargs.size != kwargs.size then + raise ArgumentError, "mocked method %p expects %d keyword arguments, got %p" % + [sym, expected_kwargs.size, kwargs] + end + + zipped_args = expected_args.zip args + fully_matched = zipped_args.all? { |mod, a| + mod === a or mod == a + } + + unless fully_matched then + fmt = "mocked method %p called with unexpected arguments %p" + raise MockExpectationError, fmt % [sym, args] + end + + unless expected_kwargs.keys.sort == kwargs.keys.sort then + fmt = "mocked method %p called with unexpected keywords %p vs %p" + raise MockExpectationError, fmt % [sym, expected_kwargs.keys, kwargs.keys] + end + + zipped_kwargs = expected_kwargs.to_h { |ek, ev| + av = kwargs[ek] + [ek, [ev, av]] + } + + fully_matched = zipped_kwargs.all? { |ek, (ev, av)| + ev === av or ev == av + } + + unless fully_matched then + fmt = "mocked method %p called with unexpected keyword arguments %p vs %p" + raise MockExpectationError, fmt % [sym, expected_kwargs, kwargs] + end + + @actual_calls[sym] << { + :retval => retval, + :args => zipped_args.map { |e, a| e === a ? e : a }, + :kwargs => zipped_kwargs.to_h { |k, (e, a)| [k, e === a ? e : a] }, + } + + retval + end + + def respond_to? sym, include_private = false # :nodoc: + return true if @expected_calls.key? sym.to_sym + return true if @delegator && @delegator.respond_to?(sym, include_private) + __respond_to? sym, include_private + end + end +end + +module Minitest::Assertions + ## + # Assert that the mock verifies correctly and fail if not. + + def assert_mock mock, msg = nil + assert mock.verify + rescue MockExpectationError => e + msg = message(msg) { e.message } + flunk msg + end +end + +module Minitest::Expectations + ## + # See Minitest::Assertions#assert_mock. + # + # _(collection).must_verify + # + # :method: must_verify + + infect_an_assertion :assert_mock, :must_verify, :unary if + defined?(infect_an_assertion) +end + +## +# Object extensions for Minitest::Mock. + +class Object + + ## + # Add a temporary stubbed method replacing +name+ for the duration + # of the +block+. If +val_or_callable+ responds to #call, then it + # returns the result of calling it, otherwise returns the value + # as-is. If stubbed method yields a block, +block_args+ will be + # passed along. Cleans up the stub at the end of the +block+. The + # method +name+ must exist before stubbing. + # + # def test_stale_eh + # obj_under_test = Something.new + # refute obj_under_test.stale? + # + # Time.stub :now, Time.at(0) do + # assert obj_under_test.stale? + # end + # end + #-- + # NOTE: keyword args in callables are NOT checked for correctness + # against the existing method. Too many edge cases to be worth it. + + def stub name, val_or_callable, *block_args, **block_kwargs, &block + new_name = "__minitest_stub__#{name}" + + metaclass = class << self; self; end + + if respond_to? name and not methods.map(&:to_s).include? name.to_s then + metaclass.send :define_method, name do |*args, **kwargs| + super(*args, **kwargs) + end + end + + metaclass.send :alias_method, new_name, name + + if ENV["MT_KWARGS_HAC\K"] then + metaclass.send :define_method, name do |*args, &blk| + if val_or_callable.respond_to? :call then + val_or_callable.call(*args, &blk) + else + blk.call(*block_args, **block_kwargs) if blk + val_or_callable + end + end + else + metaclass.send :define_method, name do |*args, **kwargs, &blk| + if val_or_callable.respond_to? :call then + if kwargs.empty? then # FIX: drop this after 2.7 dead + val_or_callable.call(*args, &blk) + else + val_or_callable.call(*args, **kwargs, &blk) + end + else + if blk then + if block_kwargs.empty? then # FIX: drop this after 2.7 dead + blk.call(*block_args) + else + blk.call(*block_args, **block_kwargs) + end + end + val_or_callable + end + end + end + + block[self] + ensure + metaclass.send :undef_method, name + metaclass.send :alias_method, name, new_name + metaclass.send :undef_method, new_name + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/parallel.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/parallel.rb new file mode 100644 index 00000000..6b517883 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/parallel.rb @@ -0,0 +1,70 @@ +module Minitest + module Parallel # :nodoc: + + ## + # The engine used to run multiple tests in parallel. + + class Executor + + ## + # The size of the pool of workers. + + attr_reader :size + + ## + # Create a parallel test executor of with +size+ workers. + + def initialize size + @size = size + @queue = Thread::Queue.new + @pool = nil + end + + ## + # Start the executor + + def start + @pool = Array.new(size) { + Thread.new @queue do |queue| + Thread.current.abort_on_exception = true + while job = queue.pop do + klass, method, reporter = job + reporter.synchronize { reporter.prerecord klass, method } + result = Minitest.run_one_method klass, method + reporter.synchronize { reporter.record result } + end + end + } + end + + ## + # Add a job to the queue + + def << work; @queue << work; end + + ## + # Shuts down the pool of workers by signalling them to quit and + # waiting for them all to finish what they're currently working + # on. + + def shutdown + size.times { @queue << nil } + @pool.each(&:join) + end + end + + module Test # :nodoc: + def _synchronize; Minitest::Test.io_lock.synchronize { yield }; end # :nodoc: + + module ClassMethods # :nodoc: + def run_one_method klass, method_name, reporter + Minitest.parallel_executor << [klass, method_name, reporter] + end + + def test_order + :parallel + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/pride.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/pride.rb new file mode 100644 index 00000000..f3b8e474 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/pride.rb @@ -0,0 +1,4 @@ +require "minitest" + +Minitest.load_plugins +Minitest::PrideIO.pride! diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/pride_plugin.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/pride_plugin.rb new file mode 100644 index 00000000..04b9d88c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/pride_plugin.rb @@ -0,0 +1,135 @@ +require "minitest" + +module Minitest + def self.plugin_pride_options opts, _options # :nodoc: + opts.on "-p", "--pride", "Pride. Show your testing pride!" do + PrideIO.pride! + end + end + + def self.plugin_pride_init options # :nodoc: + return unless PrideIO.pride? + + klass = ENV["TERM"] =~ /^xterm|-(?:256color|direct)$/ ? PrideLOL : PrideIO + io = klass.new options[:io] + + self.reporter.reporters.grep(Minitest::Reporter).each do |rep| + rep.io = io if rep.io.tty? + end + end + + ## + # Show your testing pride! + + class PrideIO + ## + # Activate the pride plugin. Called from both -p option and minitest/pride + + def self.pride! + @pride = true + end + + ## + # Are we showing our testing pride? + + def self.pride? + @pride ||= false + end + + # Start an escape sequence + ESC = "\e[" + + # End the escape sequence + NND = "#{ESC}0m" + + # The IO we're going to pipe through. + attr_reader :io + + def initialize io # :nodoc: + @io = io + # stolen from /System/Library/Perl/5.10.0/Term/ANSIColor.pm + # also reference https://en.wikipedia.org/wiki/ANSI_escape_code + @colors ||= (31..36).to_a + @size = @colors.size + @index = 0 + end + + ## + # Wrap print to colorize the output. + + def print o + case o + when ".", "S" then + io.print pride o + when "E", "F" then + io.print "#{ESC}41m#{ESC}37m#{o}#{NND}" + else + io.print o + end + end + + def puts *o # :nodoc: + o.map! { |s| + s.to_s.sub("Finished") { + @index = 0 + "Fabulous run".chars.map { |c| pride(c) }.join + } + } + + io.puts(*o) + end + + ## + # Color a string. + + def pride string + string = "*" if string == "." + c = @colors[@index % @size] + @index += 1 + "#{ESC}#{c}m#{string}#{NND}" + end + + def method_missing msg, *args # :nodoc: + io.send(msg, *args) + end + end + + ## + # If you thought the PrideIO was colorful... + # + # (Inspired by lolcat, but with clean math) + + class PrideLOL < PrideIO + PI_3 = Math::PI / 3 # :nodoc: + + def initialize io # :nodoc: + # walk red, green, and blue around a circle separated by equal thirds. + # + # To visualize, type this into wolfram-alpha: + # + # plot (3*sin(x)+3), (3*sin(x+2*pi/3)+3), (3*sin(x+4*pi/3)+3) + + @colors = Array.new(6 * 7) { |n| + n *= 1.0 / 3 + r = (3 * Math.sin(n ) + 3).to_i + g = (3 * Math.sin(n + 4 * PI_3) + 3).to_i + b = (3 * Math.sin(n + 2 * PI_3) + 3).to_i + + # Then we take rgb and encode them in a single number using + # base 6, shifted by 16 for the base 16 ansi colors. + 36 * r + 6 * g + b + 16 + }.rotate(4) # puts "red" first + + super + end + + ## + # Make the string even more colorful. Damnit. + + def pride string + c = @colors[@index % @size] + @index += 1 + "#{ESC}38;5;#{c}m#{string}#{NND}" + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/spec.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/spec.rb new file mode 100644 index 00000000..9ec1597d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/spec.rb @@ -0,0 +1,350 @@ +require "minitest/test" + +class Module # :nodoc: + def infect_an_assertion meth, new_name, dont_flip = false # :nodoc: + block = dont_flip == :block + dont_flip = false if block + target_obj = block ? "_{obj.method}" : "_(obj)" + + # https://eregon.me/blog/2021/02/13/correct-delegation-in-ruby-2-27-3.html + # Drop this when we can drop ruby 2.6 (aka after rails 6.1 EOL, ~2024-06) + kw_extra = "ruby2_keywords %p" % [new_name] if respond_to? :ruby2_keywords, true + + # warn "%-22p -> %p %p" % [meth, new_name, dont_flip] + self.class_eval <<-EOM, __FILE__, __LINE__ + 1 + def #{new_name} *args + where = Minitest.filter_backtrace(caller).first + where = where.split(/:in /, 2).first # clean up noise + Kernel.warn "DEPRECATED: global use of #{new_name} from #\{where}. Use #{target_obj}.#{new_name} instead. This will fail in Minitest 6." + Minitest::Expectation.new(self, Minitest::Spec.current).#{new_name}(*args) + end + #{kw_extra} + EOM + + Minitest::Expectation.class_eval <<-EOM, __FILE__, __LINE__ + 1 + def #{new_name} *args + raise "Calling ##{new_name} outside of test." unless ctx + case + when #{!!dont_flip} then + ctx.#{meth}(target, *args) + when #{block} && Proc === target then + ctx.#{meth}(*args, &target) + else + ctx.#{meth}(args.first, target, *args[1..-1]) + end + end + #{kw_extra} + EOM + end +end + +Minitest::Expectation = Struct.new :target, :ctx # :nodoc: + +## +# Kernel extensions for minitest + +module Kernel + ## + # Describe a series of expectations for a given target +desc+. + # + # Defines a test class subclassing from either Minitest::Spec or + # from the surrounding describe's class. The surrounding class may + # subclass Minitest::Spec manually in order to easily share code: + # + # class MySpec < Minitest::Spec + # # ... shared code ... + # end + # + # class TestStuff < MySpec + # it "does stuff" do + # # shared code available here + # end + # describe "inner stuff" do + # it "still does stuff" do + # # ...and here + # end + # end + # end + # + # For more information on getting started with writing specs, see: + # + # http://www.rubyinside.com/a-minitestspec-tutorial-elegant-spec-style-testing-that-comes-with-ruby-5354.html + # + # For some suggestions on how to improve your specs, try: + # + # https://betterspecs.org + # + # but do note that several items there are debatable or specific to + # rspec. + # + # For more information about expectations, see Minitest::Expectations. + + def describe desc, *additional_desc, &block # :doc: + stack = Minitest::Spec.describe_stack + is_spec_class = Class === self && kind_of?(Minitest::Spec::DSL) + name = [stack.last, desc, *additional_desc] + name.prepend self if stack.empty? && is_spec_class + sclas = + stack.last \ + || (is_spec_class && self) \ + || Minitest::Spec.spec_type(desc, *additional_desc) + + cls = sclas.create name.compact.join("::"), desc + + stack.push cls + cls.class_eval(&block) + stack.pop + cls + end + private :describe +end + +## +# Minitest::Spec -- The faster, better, less-magical spec framework! +# +# For a list of expectations, see Minitest::Expectations. + +class Minitest::Spec < Minitest::Test + + def self.current # :nodoc: + Thread.current[:current_spec] + end + + def initialize name # :nodoc: + super + Thread.current[:current_spec] = self + end + + ## + # Oh look! A Minitest::Spec::DSL module! Eat your heart out DHH. + + module DSL + ## + # Contains pairs of matchers and Spec classes to be used to + # calculate the superclass of a top-level describe. This allows for + # automatically customizable spec types. + # + # See: register_spec_type and spec_type + + TYPES = [[//, Minitest::Spec]] + + ## + # Register a new type of spec that matches the spec's description. + # This method can take either a Regexp and a spec class or a spec + # class and a block that takes the description and returns true if + # it matches. + # + # Eg: + # + # register_spec_type(/Controller$/, Minitest::Spec::Rails) + # + # or: + # + # register_spec_type(Minitest::Spec::RailsModel) do |desc| + # desc.superclass == ActiveRecord::Base + # end + + def register_spec_type *args, &block + if block then + matcher, klass = block, args.first + else + matcher, klass = *args + end + TYPES.unshift [matcher, klass] + end + + ## + # Figure out the spec class to use based on a spec's description. Eg: + # + # spec_type("BlahController") # => Minitest::Spec::Rails + + def spec_type desc, *additional + TYPES.find { |matcher, _klass| + if matcher.respond_to? :call then + matcher.call desc, *additional + else + matcher === desc.to_s + end + }.last + end + + def describe_stack # :nodoc: + Thread.current[:describe_stack] ||= [] + end + + def children # :nodoc: + @children ||= [] + end + + def nuke_test_methods! # :nodoc: + self.public_instance_methods.grep(/^test_/).each do |name| + self.send :undef_method, name + end + end + + ## + # Define a 'before' action. Inherits the way normal methods should. + # + # NOTE: +type+ is ignored and is only there to make porting easier. + # + # Equivalent to Minitest::Test#setup. + + def before _type = nil, &block + define_method :setup do + super() + self.instance_eval(&block) + end + end + + ## + # Define an 'after' action. Inherits the way normal methods should. + # + # NOTE: +type+ is ignored and is only there to make porting easier. + # + # Equivalent to Minitest::Test#teardown. + + def after _type = nil, &block + define_method :teardown do + self.instance_eval(&block) + super() + end + end + + ## + # Define an expectation with name +desc+. Name gets morphed to a + # proper test method name. For some freakish reason, people who + # write specs don't like class inheritance, so this goes way out of + # its way to make sure that expectations aren't inherited. + # + # This is also aliased to #specify and doesn't require a +desc+ arg. + # + # Hint: If you _do_ want inheritance, use minitest/test. You can mix + # and match between assertions and expectations as much as you want. + + def it desc = "anonymous", &block + block ||= proc { skip "(no tests defined)" } + + @specs ||= 0 + @specs += 1 + + name = "test_%04d_%s" % [ @specs, desc ] + + undef_klasses = self.children.reject { |c| c.public_method_defined? name } + + define_method name, &block + + undef_klasses.each do |undef_klass| + undef_klass.send :undef_method, name + end + + name + end + + ## + # Essentially, define an accessor for +name+ with +block+. + # + # Why use let instead of def? I honestly don't know. + + def let name, &block + name = name.to_s + pre, post = "let '#{name}' cannot ", ". Please use another name." + methods = Minitest::Spec.instance_methods.map(&:to_s) - %w[subject] + raise ArgumentError, "#{pre}begin with 'test'#{post}" if + name.start_with? "test" + raise ArgumentError, "#{pre}override a method in Minitest::Spec#{post}" if + methods.include? name + + define_method name do + @_memoized ||= {} + @_memoized.fetch(name) { |k| @_memoized[k] = instance_eval(&block) } + end + end + + ## + # Another lazy man's accessor generator. Made even more lazy by + # setting the name for you to +subject+. + + def subject &block + let :subject, &block + end + + def create name, desc # :nodoc: + cls = Class.new self do + @name = name + @desc = desc + + nuke_test_methods! + end + + children << cls + + cls + end + + def name # :nodoc: + defined?(@name) ? @name : super + end + + def to_s # :nodoc: + name # Can't alias due to 1.8.7, not sure why + end + + attr_reader :desc # :nodoc: + alias specify it + + ## + # Rdoc... why are you so dumb? + + module InstanceMethods + ## + # Takes a value or a block and returns a value monad that has + # all of Expectations methods available to it. + # + # _(1 + 1).must_equal 2 + # + # And for blocks: + # + # _ { 1 + "1" }.must_raise TypeError + # + # This method of expectation-based testing is preferable to + # straight-expectation methods (on Object) because it stores its + # test context, bypassing our hacky use of thread-local variables. + # + # NOTE: At some point, the methods on Object will be deprecated + # and then removed. + # + # It is also aliased to #value and #expect for your aesthetic + # pleasure: + # + # _(1 + 1).must_equal 2 + # value(1 + 1).must_equal 2 + # expect(1 + 1).must_equal 2 + + def _ value = nil, &block + Minitest::Expectation.new block || value, self + end + + alias value _ + alias expect _ + + def before_setup # :nodoc: + super + Thread.current[:current_spec] = self + end + end + + def self.extended obj # :nodoc: + obj.send :include, InstanceMethods + end + end + + extend DSL + + TYPES = DSL::TYPES # :nodoc: +end + +require "minitest/expectations" + +class Object # :nodoc: + include Minitest::Expectations unless ENV["MT_NO_EXPECTATIONS"] +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/test.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/test.rb new file mode 100644 index 00000000..5085dae5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/test.rb @@ -0,0 +1,237 @@ +require "minitest" unless defined? Minitest::Runnable + +module Minitest + ## + # Subclass Test to create your own tests. Typically you'll want a + # Test subclass per implementation class. + # + # See Minitest::Assertions + + class Test < Runnable + require "minitest/assertions" + include Minitest::Reportable + include Minitest::Assertions + + def class_name # :nodoc: + self.class.name # for Minitest::Reportable + end + + PASSTHROUGH_EXCEPTIONS = [NoMemoryError, SignalException, SystemExit] # :nodoc: + + SETUP_METHODS = %w[ before_setup setup after_setup ] # :nodoc: + + TEARDOWN_METHODS = %w[ before_teardown teardown after_teardown ] # :nodoc: + + # :stopdoc: + class << self; attr_accessor :io_lock; end + self.io_lock = Mutex.new + # :startdoc: + + ## + # Call this at the top of your tests when you absolutely + # positively need to have ordered tests. In doing so, you're + # admitting that you suck and your tests are weak. + + def self.i_suck_and_my_tests_are_order_dependent! + class << self + undef_method :test_order if method_defined? :test_order + define_method :test_order do :alpha end + end + end + + ## + # Make diffs for this Test use #pretty_inspect so that diff + # in assert_equal can have more details. NOTE: this is much slower + # than the regular inspect but much more usable for complex + # objects. + + def self.make_my_diffs_pretty! + require "pp" + + define_method :mu_pp, &:pretty_inspect + end + + ## + # Call this at the top of your tests (inside the +Minitest::Test+ + # subclass or +describe+ block) when you want to run your tests in + # parallel. In doing so, you're admitting that you rule and your + # tests are awesome. + + def self.parallelize_me! + include Minitest::Parallel::Test + extend Minitest::Parallel::Test::ClassMethods + end + + ## + # Returns all instance methods starting with "test_". Based on + # #test_order, the methods are either sorted, randomized + # (default), or run in parallel. + + def self.runnable_methods + methods = methods_matching(/^test_/) + + case self.test_order + when :random, :parallel then + srand Minitest.seed + methods.sort.shuffle + when :alpha, :sorted then + methods.sort + else + raise "Unknown test_order: #{self.test_order.inspect}" + end + end + + ## + # Runs a single test with setup/teardown hooks. + + def run + time_it do + capture_exceptions do + SETUP_METHODS.each do |hook| + self.send hook + end + + self.send self.name + end + + TEARDOWN_METHODS.each do |hook| + capture_exceptions do + self.send hook + end + end + end + + Result.from self # per contract + end + + ## + # Provides before/after hooks for setup and teardown. These are + # meant for library writers, NOT for regular test authors. See + # #before_setup for an example. + + module LifecycleHooks + + ## + # Runs before every test, before setup. This hook is meant for + # libraries to extend minitest. It is not meant to be used by + # test developers. + # + # As a simplistic example: + # + # module MyMinitestPlugin + # def before_setup + # super + # # ... stuff to do before setup is run + # end + # + # def after_setup + # # ... stuff to do after setup is run + # super + # end + # + # def before_teardown + # super + # # ... stuff to do before teardown is run + # end + # + # def after_teardown + # # ... stuff to do after teardown is run + # super + # end + # end + # + # class Minitest::Test + # include MyMinitestPlugin + # end + + def before_setup; end + + ## + # Runs before every test. Use this to set up before each test + # run. + + def setup; end + + ## + # Runs before every test, after setup. This hook is meant for + # libraries to extend minitest. It is not meant to be used by + # test developers. + # + # See #before_setup for an example. + + def after_setup; end + + ## + # Runs after every test, before teardown. This hook is meant for + # libraries to extend minitest. It is not meant to be used by + # test developers. + # + # See #before_setup for an example. + + def before_teardown; end + + ## + # Runs after every test. Use this to clean up after each test + # run. + + def teardown; end + + ## + # Runs after every test, after teardown. This hook is meant for + # libraries to extend minitest. It is not meant to be used by + # test developers. + # + # See #before_setup for an example. + + def after_teardown; end + end # LifecycleHooks + + def capture_exceptions # :nodoc: + yield + rescue *PASSTHROUGH_EXCEPTIONS + raise + rescue Assertion => e + self.failures << e + rescue Exception => e + self.failures << UnexpectedError.new(sanitize_exception e) + end + + def sanitize_exception e # :nodoc: + Marshal.dump e + e # good: use as-is + rescue + neuter_exception e + end + + def neuter_exception e # :nodoc: + bt = e.backtrace + msg = e.message.dup + + new_exception e.class, msg, bt # e.class can be a problem... + rescue + msg.prepend "Neutered Exception #{e.class}: " + + new_exception RuntimeError, msg, bt, true # but if this raises, we die + end + + def new_exception klass, msg, bt, kill = false # :nodoc: + ne = klass.new msg + ne.set_backtrace bt + + if kill then + ne.instance_variables.each do |v| + ne.remove_instance_variable v + end + end + + Marshal.dump ne # can raise TypeError + ne + end + + include LifecycleHooks + include Guard + extend Guard + end # Test +end + +require "minitest/unit" if ENV["MT_COMPAT"] # compatibility layer only diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/test_task.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/test_task.rb new file mode 100644 index 00000000..b98dac6e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/test_task.rb @@ -0,0 +1,307 @@ +require "shellwords" +require "rbconfig" + +begin + require "rake/tasklib" +rescue LoadError => e + warn e.message + return +end + +module Minitest # :nodoc: + + ## + # Minitest::TestTask is a rake helper that generates several rake + # tasks under the main test task's name-space. + # + # task :: the main test task + # task :cmd :: prints the command to use + # task :deps :: runs each test file by itself to find dependency errors + # task :slow :: runs the tests and reports the slowest 25 tests. + # + # Examples: + # + # Minitest::TestTask.create + # + # The most basic and default setup. + # + # Minitest::TestTask.create :my_tests + # + # The most basic/default setup, but with a custom name + # + # Minitest::TestTask.create :unit do |t| + # t.test_globs = ["test/unit/**/*_test.rb"] + # t.warning = false + # end + # + # Customize the name and only run unit tests. + # + # NOTE: To hook this task up to the default, make it a dependency: + # + # task default: :unit + + class TestTask < Rake::TaskLib + WINDOWS = RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ # :nodoc: + + ## + # Create several test-oriented tasks under +name+. Takes an + # optional block to customize variables. + + def self.create name = :test, &block + task = new name + task.instance_eval(&block) if block + task.process_env + task.define + task + end + + ## + # Extra arguments to pass to the tests. Defaults empty but gets + # populated by a number of enviroment variables: + # + # N (-n flag) :: a string or regexp of tests to run. + # X (-e flag) :: a string or regexp of tests to exclude. + # A (arg) :: quick way to inject an arbitrary argument (eg A=--help). + # + # See #process_env + + attr_accessor :extra_args + + ## + # The code to load the framework. Defaults to requiring + # minitest/autorun... + # + # Why do I have this as an option? + + attr_accessor :framework + + ## + # Extra library directories to include. Defaults to %w[lib test + # .]. Also uses $MT_LIB_EXTRAS allowing you to dynamically + # override/inject directories for custom runs. + + attr_accessor :libs + + ## + # The name of the task and base name for the other tasks generated. + + attr_accessor :name + + ## + # File globs to find test files. Defaults to something sensible to + # find test files under the test directory. + + attr_accessor :test_globs + + ## + # Turn on ruby warnings (-w flag). Defaults to true. + + attr_accessor :warning + + ## + # Optional: Additional ruby to run before the test framework is loaded. + + attr_accessor :test_prelude + + ## + # Print out commands as they run. Defaults to Rake's +trace+ (-t + # flag) option. + + attr_accessor :verbose + + ## + # Use TestTask.create instead. + + def initialize name = :test # :nodoc: + self.extra_args = [] + self.framework = %(require "minitest/autorun") + self.libs = %w[lib test .] + self.name = name + self.test_globs = ["test/**/test_*.rb", + "test/**/*_test.rb"] + self.test_prelude = nil + self.verbose = Rake.application.options.trace || Rake.verbose == true + self.warning = true + end + + ## + # Extract variables from the environment and convert them to + # command line arguments. See #extra_args. + # + # Environment Variables: + # + # MT_LIB_EXTRAS :: Extra libs to dynamically override/inject for custom runs. + # N :: Tests to run (string or /regexp/). + # X :: Tests to exclude (string or /regexp/). + # A :: Any extra arguments. Honors shell quoting. + # + # Deprecated: + # + # TESTOPTS :: For argument passing, use +A+. + # N :: For parallel testing, use +MT_CPU+. + # FILTER :: Same as +TESTOPTS+. + + def process_env + warn "TESTOPTS is deprecated in Minitest::TestTask. Use A instead" if + ENV["TESTOPTS"] + warn "FILTER is deprecated in Minitest::TestTask. Use A instead" if + ENV["FILTER"] + warn "N is deprecated in Minitest::TestTask. Use MT_CPU instead" if + ENV["N"] && ENV["N"].to_i > 0 + + lib_extras = (ENV["MT_LIB_EXTRAS"] || "").split File::PATH_SEPARATOR + self.libs[0, 0] = lib_extras + + extra_args << "-n" << ENV["N"] if ENV["N"] + extra_args << "-e" << ENV["X"] if ENV["X"] + extra_args.concat Shellwords.split(ENV["TESTOPTS"]) if ENV["TESTOPTS"] + extra_args.concat Shellwords.split(ENV["FILTER"]) if ENV["FILTER"] + extra_args.concat Shellwords.split(ENV["A"]) if ENV["A"] + + ENV.delete "N" if ENV["N"] + + # TODO? RUBY_DEBUG = ENV["RUBY_DEBUG"] + # TODO? ENV["RUBY_FLAGS"] + + extra_args.compact! + end + + def define # :nodoc: + desc "Run the test suite. Use N, X, A, and TESTOPTS to add flags/args." + task name do + ruby make_test_cmd, verbose: verbose + end + + desc "Print out the test command. Good for profiling and other tools." + task "#{name}:cmd" do + puts "ruby #{make_test_cmd}" + end + + desc "Show which test files fail when run in isolation." + task "#{name}:isolated" do + tests = Dir[*self.test_globs].uniq + + # 3 seems to be the magic number... (tho not by that much) + bad, good, n = {}, [], (ENV.delete("K") || 3).to_i + file = ENV.delete "F" + times = {} + + tt0 = Time.now + + n.threads_do tests.sort do |path| + t0 = Time.now + output = `#{Gem.ruby} #{make_test_cmd path} 2>&1` + t1 = Time.now - t0 + + times[path] = t1 + + if $?.success? + $stderr.print "." + good << path + else + $stderr.print "x" + bad[path] = output + end + end + + puts "done" + puts "Ran in %.2f seconds" % [ Time.now - tt0 ] + + if file then + require "json" + File.open file, "w" do |io| + io.puts JSON.pretty_generate times + end + end + + unless good.empty? + puts + puts "# Good tests:" + puts + good.sort.each do |path| + puts "%.2fs: %s" % [times[path], path] + end + end + + unless bad.empty? + puts + puts "# Bad tests:" + puts + bad.keys.sort.each do |path| + puts "%.2fs: %s" % [times[path], path] + end + puts + puts "# Bad Test Output:" + puts + bad.sort.each do |path, output| + puts + puts "# #{path}:" + puts output + end + exit 1 + end + end + + task "#{name}:deps" => "#{name}:isolated" # now just an alias + + desc "Run the test suite and report the slowest 25 tests." + task "#{name}:slow" do + sh ["rake #{name} A=-v", + "egrep '#test_.* s = .'", + "sort -n -k2 -t=", + "tail -25"].join " | " + end + end + + ## + # Generate the test command-line. + + def make_test_cmd globs = test_globs + tests = [] + tests.concat Dir[*globs].sort.shuffle # TODO: SEED -> srand first? + tests.map! { |f| %(require "#{f}") } + + runner = [] + runner << test_prelude if test_prelude + runner << framework + runner.concat tests + runner = runner.join "; " + + args = [] + args << "-I#{libs.join File::PATH_SEPARATOR}" unless libs.empty? + args << "-w" if warning + args << "-e" + args << "'#{runner}'" + args << "--" + args << extra_args.map(&:shellescape) + + args.join " " + end + end +end + +class Work < Queue # :nodoc: + def initialize jobs = [] # :nodoc: + super() + + jobs.each do |job| + self << job + end + + close + end +end + +class Integer # :nodoc: + def threads_do jobs # :nodoc: + q = Work.new jobs + + Array.new(self) { + Thread.new do + while job = q.pop # go until quit value + yield job + end + end + }.each(&:join) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/unit.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/unit.rb new file mode 100644 index 00000000..3c60a707 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/lib/minitest/unit.rb @@ -0,0 +1,42 @@ +unless defined?(Minitest) then + # all of this crap is just to avoid circular requires and is only + # needed if a user requires "minitest/unit" directly instead of + # "minitest/autorun", so we also warn + + from = caller.reject { |s| s =~ /rubygems/ }.join("\n ") + warn "Warning: you should require 'minitest/autorun' instead." + warn %(Warning: or add 'gem "minitest"' before 'require "minitest/autorun"') + warn "From:\n #{from}" + + module Minitest # :nodoc: + end + MiniTest = Minitest # :nodoc: # prevents minitest.rb from requiring back to us + require "minitest" +end + +MiniTest = Minitest unless defined?(MiniTest) + +module Minitest + class Unit # :nodoc: + VERSION = Minitest::VERSION + class TestCase < Minitest::Test # :nodoc: + def self.inherited klass # :nodoc: + from = caller.first + warn "MiniTest::Unit::TestCase is now Minitest::Test. From #{from}" + super + end + end + + def self.autorun # :nodoc: + from = caller.first + warn "MiniTest::Unit.autorun is now Minitest.autorun. From #{from}" + Minitest.autorun + end + + def self.after_tests &b # :nodoc: + from = caller.first + warn "MiniTest::Unit.after_tests is now Minitest.after_run. From #{from}" + Minitest.after_run(&b) + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/metametameta.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/metametameta.rb new file mode 100644 index 00000000..fde506d4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/metametameta.rb @@ -0,0 +1,150 @@ +require "tempfile" +require "stringio" +require "minitest/autorun" + +class Minitest::Test + def with_empty_backtrace_filter + with_backtrace_filter Minitest::BacktraceFilter.new %r%.% do + yield + end + end + + def with_backtrace_filter filter + original = Minitest.backtrace_filter + + Minitest::Test.io_lock.synchronize do # try not to trounce in parallel + begin + Minitest.backtrace_filter = filter + yield + ensure + Minitest.backtrace_filter = original + end + end + end + + def error_on_warn? + defined?(Minitest::ErrorOnWarning) + end + + def assert_deprecation re = /DEPRECATED/ + re = // if $-w.nil? # "skip" if running `rake testW0` + assert_output "", re do + yield + end + rescue Minitest::UnexpectedWarning => e # raised if -Werror was used + assert_match re, e.message + end +end + +class FakeNamedTest < Minitest::Test + @@count = 0 + + def self.name + @fake_name ||= begin + @@count += 1 + "FakeNamedTest%02d" % @@count + end + end +end + +module MyModule; end +class AnError < StandardError; include MyModule; end + +class MetaMetaMetaTestCase < Minitest::Test + attr_accessor :reporter, :output, :tu + + def with_stderr err + old = $stderr + $stderr = err + yield + ensure + $stderr = old + end + + def run_tu_with_fresh_reporter flags = %w[--seed 42] + options = Minitest.process_args flags + + @output = StringIO.new(+"") + + self.reporter = Minitest::CompositeReporter.new + reporter << Minitest::SummaryReporter.new(@output, options) + reporter << Minitest::ProgressReporter.new(@output, options) + + with_stderr @output do + reporter.start + + yield reporter if block_given? + + @tus ||= [@tu] + @tus.each do |tu| + Minitest::Runnable.runnables.delete tu + + tu.run reporter, options + end + + reporter.report + end + end + + def first_reporter + reporter.reporters.first + end + + def assert_report expected, flags = %w[--seed 42], &block + header = <<~EOM + Run options: #{flags.map { |s| s.include?("|") ? s.inspect : s }.join " "} + + # Running: + + EOM + + run_tu_with_fresh_reporter flags, &block + + output = normalize_output @output.string.dup + + assert_equal header + expected, output + end + + def normalize_output output + output.sub!(/Finished in .*/, "Finished in 0.00") + output.sub!(/Loaded suite .*/, "Loaded suite blah") + + output.gsub!(/FakeNamedTest\d+/, "FakeNamedTestXX") + output.gsub!(/ = \d+.\d\d s = /, " = 0.00 s = ") + output.gsub!(/0x[A-Fa-f0-9]+/, "0xXXX") + output.gsub!(/ +$/, "") + + file = ->(s) { s.start_with?("/") ? "FULLFILE" : "FILE" } + + if windows? then + output.gsub!(/\[(?:[A-Za-z]:)?[^\]:]+:\d+\]/, "[FILE:LINE]") + output.gsub!(/^(\s+)(?:[A-Za-z]:)?[^:]+:\d+:in [`']/, '\1FILE:LINE:in \'') + else + output.gsub!(/\[([^\]:]+):\d+\]/) { "[#{file[$1]}:LINE]" } + output.gsub!(/^(\s+)([^:]+):\d+:in [`']/) { "#{$1}#{file[$2]}:LINE:in '" } + end + + output.gsub!(/in [`']block in (?:([^']+)[#.])?/, "in 'block in") + output.gsub!(/in [`'](?:([^']+)[#.])?/, "in '") + + output.gsub!(/( at )([^:]+):\d+/) { "#{$1}[#{file[$2]}:LINE]" } # eval? + + output + end + + def restore_env + old_value = ENV["MT_NO_SKIP_MSG"] + ENV.delete "MT_NO_SKIP_MSG" + + yield + ensure + ENV["MT_NO_SKIP_MSG"] = old_value + end + + def setup + super + Minitest.seed = 42 + Minitest::Test.reset + @tu = nil + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_assertions.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_assertions.rb new file mode 100644 index 00000000..77adbed6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_assertions.rb @@ -0,0 +1,1720 @@ +require "minitest/autorun" +require_relative "metametameta" + +e = Encoding.default_external +if e != Encoding::UTF_8 then + warn "" + warn "" + warn "NOTE: External encoding #{e} is not UTF-8. Tests WILL fail." + warn " Run tests with `RUBYOPT=-Eutf-8 rake` to avoid errors." + warn "" + warn "" +end + +SomeError = Class.new Exception + +unless defined? MyModule then + module MyModule; end + class AnError < StandardError; include MyModule; end +end + +class TestMinitestAssertions < Minitest::Test + # do not call parallelize_me! - teardown accesses @tc._assertions + # which is not threadsafe. Nearly every method in here is an + # assertion test so it isn't worth splitting it out further. + + # not included in JRuby + RE_LEVELS = /\(\d+ levels\) / + + class DummyTest + include Minitest::Assertions + + attr_accessor :assertions, :failure + + def initialize + self.assertions = 0 + self.failure = nil + end + end + + def setup + super + + Minitest::Test.reset + + @tc = DummyTest.new + @zomg = "zomg ponies!" # TODO: const + @assertion_count = 1 + end + + def teardown + assert_equal(@assertion_count, @tc.assertions, + "expected #{@assertion_count} assertions to be fired during the test, not #{@tc.assertions}") + end + + def assert_triggered expected, klass = Minitest::Assertion + e = assert_raises klass do + yield + end + + msg = e.message.sub(/(---Backtrace---).*/m, '\1') + msg.gsub!(/\(oid=[-0-9]+\)/, "(oid=N)") + msg.gsub!(/(\d\.\d{6})\d+/, '\1xxx') # normalize: ruby version, impl, platform + + assert_msg = Regexp === expected ? :assert_match : :assert_equal + self.send assert_msg, expected, msg + end + + def assert_unexpected expected + expected = Regexp.new expected if String === expected + + assert_triggered expected, Minitest::UnexpectedError do + yield + end + end + + def non_verbose + orig_verbose = $VERBOSE + $VERBOSE = false + + yield + ensure + $VERBOSE = orig_verbose + end + + def test_assert + @assertion_count = 2 + + @tc.assert_equal true, @tc.assert(true), "returns true on success" + end + + def test_assert__triggered + assert_triggered "Expected false to be truthy." do + @tc.assert false + end + end + + def test_assert__triggered_message + assert_triggered @zomg do + @tc.assert false, @zomg + end + end + + def test_assert__triggered_lambda + assert_triggered "whoops" do + @tc.assert false, lambda { "whoops" } + end + end + + def test_assert_empty + @assertion_count = 2 + + @tc.assert_empty [] + end + + def test_assert_empty_triggered + @assertion_count = 2 + + assert_triggered "Expected [1] to be empty." do + @tc.assert_empty [1] + end + end + + def test_assert_equal + @tc.assert_equal 1, 1 + end + + def test_assert_equal_different_collection_array_hex_invisible + exp = Object.new + act = Object.new + msg = <<~EOM.chomp + No visible difference in the Array#inspect output. + You should look at the implementation of #== on Array or its members. + [#] + EOM + assert_triggered msg do + @tc.assert_equal [exp], [act] + end + end + + def test_assert_equal_different_collection_hash_hex_invisible + exp, act = {}, {} + exp[1] = Object.new + act[1] = Object.new + act_obj = act[1] + # TODO: switch to endless when 2.7 is dropped + act_obj.define_singleton_method(:inspect) { "#" } + msg = <<~EOM.chomp % [act] + No visible difference in the Hash#inspect output. + You should look at the implementation of #== on Hash or its members. + %p + EOM + + assert_triggered msg do + @tc.assert_equal exp, act + end + end + + def test_assert_equal_different_diff_deactivated + without_diff do + assert_triggered util_msg("haha" * 10, "blah" * 10) do + exp = "haha" * 10 + act = "blah" * 10 + + @tc.assert_equal exp, act + end + end + end + + def test_assert_equal_different_message + assert_triggered "whoops.\nExpected: 1\n Actual: 2" do + @tc.assert_equal 1, 2, message { "whoops" } + end + end + + def test_assert_equal_different_lambda + assert_triggered "whoops.\nExpected: 1\n Actual: 2" do + @tc.assert_equal 1, 2, lambda { "whoops" } + end + end + + def test_assert_equal_different_hex + c = Class.new do + def initialize s; @name = s; end + end + + exp = c.new "a" + act = c.new "b" + msg = <<~EOS + --- expected + +++ actual + @@ -1 +1 @@ + -#<#:0xXXXXXX @name="a"> + +#<#:0xXXXXXX @name="b"> + EOS + + assert_triggered msg do + @tc.assert_equal exp, act + end + end + + def test_assert_equal_different_hex_invisible + exp = Object.new + act = Object.new + + msg = <<~EOM.chomp + No visible difference in the Object#inspect output. + You should look at the implementation of #== on Object or its members. + # + EOM + + assert_triggered msg do + @tc.assert_equal exp, act + end + end + + def test_assert_equal_different_long + msg = <<~EOM + --- expected + +++ actual + @@ -1 +1 @@ + -"hahahahahahahahahahahahahahahahahahahaha" + +"blahblahblahblahblahblahblahblahblahblah" + EOM + + assert_triggered msg do + exp = "haha" * 10 + act = "blah" * 10 + + @tc.assert_equal exp, act + end + end + + def test_assert_equal_different_long_invisible + msg = <<~EOM.chomp + No visible difference in the String#inspect output. + You should look at the implementation of #== on String or its members. + "blahblahblahblahblahblahblahblahblahblah" + EOM + + assert_triggered msg do + exp = "blah" * 10 + act = "blah" * 10 + def exp.== _ + false + end + @tc.assert_equal exp, act + end + end + + def test_assert_equal_different_long_msg + msg = <<~EOM + message. + --- expected + +++ actual + @@ -1 +1 @@ + -"hahahahahahahahahahahahahahahahahahahaha" + +"blahblahblahblahblahblahblahblahblahblah" + EOM + + assert_triggered msg do + exp = "haha" * 10 + act = "blah" * 10 + @tc.assert_equal exp, act, "message" + end + end + + def test_assert_equal_different_short + assert_triggered util_msg(1, 2) do + @tc.assert_equal 1, 2 + end + end + + def test_assert_equal_different_short_msg + assert_triggered util_msg(1, 2, "message") do + @tc.assert_equal 1, 2, "message" + end + end + + def test_assert_equal_different_short_multiline + msg = "--- expected\n+++ actual\n@@ -1,2 +1,2 @@\n \"a\n-b\"\n+c\"\n" + assert_triggered msg do + @tc.assert_equal "a\nb", "a\nc" + end + end + + def test_assert_equal_does_not_allow_lhs_nil + if Minitest::VERSION >= "6" then + warn "Time to strip the MT5 test" + + @assertion_count += 1 + assert_triggered(/Use assert_nil if expecting nil/) do + @tc.assert_equal nil, nil + end + else + err_re = /Use assert_nil if expecting nil from .*test_minitest_\w+.rb/ + err_re = "" if $-w.nil? + + assert_deprecation err_re do + @tc.assert_equal nil, nil + end + end + end + + def test_assert_equal_does_not_allow_lhs_nil_triggered + assert_triggered "Expected: nil\n Actual: false" do + @tc.assert_equal nil, false + end + end + + def test_assert_equal_string_bug791 + exp = <<~EOM.chomp + Expected: "\\\\n" + Actual: "\\\\" + EOM + assert_triggered exp do + @tc.assert_equal "\\n", "\\" + end + end + + def test_assert_equal_string_both_escaped_unescaped_newlines + msg = <<~EOM + --- expected + +++ actual + @@ -1,2 +1 @@ + -"A\\n + -B" + +"A\\n\\\\nB" + EOM + + assert_triggered msg do + exp = "A\\nB" + act = "A\n\\nB" + + @tc.assert_equal exp, act + end + end + + def test_assert_equal_string_encodings + msg = <<~EOM + --- expected + +++ actual + @@ -1,3 +1,3 @@ + -# encoding: UTF-8 + -# valid: false + +# encoding: #{Encoding::BINARY.name} + +# valid: true + "bad-utf8-\\xF1.txt" + EOM + + assert_triggered msg do + exp = "bad-utf8-\xF1.txt" + act = exp.dup.b + @tc.assert_equal exp, act + end + end + + def test_assert_equal_string_encodings_both_different + msg = <<~EOM + --- expected + +++ actual + @@ -1,3 +1,3 @@ + -# encoding: US-ASCII + -# valid: false + +# encoding: #{Encoding::BINARY.name} + +# valid: true + "bad-utf8-\\xF1.txt" + EOM + + assert_triggered msg do + exp = "bad-utf8-\xF1.txt".dup.force_encoding Encoding::ASCII + act = exp.dup.b + @tc.assert_equal exp, act + end + end + + def test_assert_equal_unescape_newlines + msg = <<~'EOM' # NOTE single quotes on heredoc + --- expected + +++ actual + @@ -1,2 +1,2 @@ + -"hello + +"hello\n + world" + EOM + + assert_triggered msg do + exp = "hello\nworld" + act = 'hello\nworld' # notice single quotes + + @tc.assert_equal exp, act + end + end + + def test_assert_in_delta + @tc.assert_in_delta 0.0, 1.0 / 1000, 0.1 + end + + def test_assert_in_delta_triggered + x = "1.0e-06" + assert_triggered "Expected |0.0 - 0.001| (0.001) to be <= #{x}." do + @tc.assert_in_delta 0.0, 1.0 / 1000, 0.000001 + end + end + + def test_assert_in_epsilon + @assertion_count = 10 + + @tc.assert_in_epsilon 10_000, 9991 + @tc.assert_in_epsilon 9991, 10_000 + @tc.assert_in_epsilon 1.0, 1.001 + @tc.assert_in_epsilon 1.001, 1.0 + + @tc.assert_in_epsilon 10_000, 9999.1, 0.0001 + @tc.assert_in_epsilon 9999.1, 10_000, 0.0001 + @tc.assert_in_epsilon 1.0, 1.0001, 0.0001 + @tc.assert_in_epsilon 1.0001, 1.0, 0.0001 + + @tc.assert_in_epsilon(-1, -1) + @tc.assert_in_epsilon(-10_000, -9991) + end + + def test_assert_in_epsilon_triggered + assert_triggered "Expected |10000 - 9990| (10) to be <= 9.99." do + @tc.assert_in_epsilon 10_000, 9990 + end + end + + def test_assert_in_epsilon_triggered_negative_case + x = "0.100000xxx" + y = "0.1" + assert_triggered "Expected |-1.1 - -1| (#{x}) to be <= #{y}." do + @tc.assert_in_epsilon(-1.1, -1, 0.1) + end + end + + def test_assert_includes + @assertion_count = 2 + + @tc.assert_includes [true], true + end + + def test_assert_includes_triggered + @assertion_count = 3 + + e = @tc.assert_raises Minitest::Assertion do + @tc.assert_includes [true], false + end + + expected = "Expected [true] to include false." + assert_equal expected, e.message + end + + def test_assert_instance_of + @tc.assert_instance_of String, "blah" + end + + def test_assert_instance_of_triggered + assert_triggered 'Expected "blah" to be an instance of Array, not String.' do + @tc.assert_instance_of Array, "blah" + end + end + + def test_assert_kind_of + @tc.assert_kind_of String, "blah" + end + + def test_assert_kind_of_triggered + assert_triggered 'Expected "blah" to be a kind of Array, not String.' do + @tc.assert_kind_of Array, "blah" + end + end + + def test_assert_match + @assertion_count = 2 + m = @tc.assert_match(/\w+/, "blah blah blah") + + assert_kind_of MatchData, m + assert_equal "blah", m[0] + end + + def test_assert_match_matchee_to_str + @assertion_count = 2 + + obj = Object.new + def obj.to_str; "blah" end + + @tc.assert_match "blah", obj + end + + def test_assert_match_matcher_object + @assertion_count = 2 + + pattern = Object.new + def pattern.=~ _; true end + + @tc.assert_match pattern, 5 + end + + def test_assert_match_object_triggered + @assertion_count = 2 + + pattern = Object.new + def pattern.=~ _; false end + def pattern.inspect; "[Object]" end + + assert_triggered "Expected [Object] to match 5." do + @tc.assert_match pattern, 5 + end + end + + def test_assert_match_triggered + @assertion_count = 2 + assert_triggered 'Expected /\d+/ to match "blah blah blah".' do + @tc.assert_match(/\d+/, "blah blah blah") + end + end + + def test_assert_nil + @tc.assert_nil nil + end + + def test_assert_nil_triggered + assert_triggered "Expected 42 to be nil." do + @tc.assert_nil 42 + end + end + + def test_assert_operator + @tc.assert_operator 2, :>, 1 + end + + def test_assert_operator_bad_object + bad = Object.new + def bad.== _; true end + + @tc.assert_operator bad, :equal?, bad + end + + def test_assert_operator_triggered + assert_triggered "Expected 2 to be < 1." do + @tc.assert_operator 2, :<, 1 + end + end + + def test_assert_output_both + @assertion_count = 2 + + @tc.assert_output "yay", "blah" do + print "yay" + $stderr.print "blah" + end + end + + def test_assert_output_both_regexps + @assertion_count = 4 + + @tc.assert_output(/y.y/, /bl.h/) do + print "yay" + $stderr.print "blah" + end + end + + def test_assert_output_err + @tc.assert_output nil, "blah" do + $stderr.print "blah" + end + end + + def test_assert_output_neither + @assertion_count = 0 + + @tc.assert_output do + # do nothing + end + end + + def test_assert_output_out + @tc.assert_output "blah" do + print "blah" + end + end + + def test_assert_output_triggered_both + assert_triggered util_msg("blah", "blah blah", "In stderr") do + @tc.assert_output "yay", "blah" do + print "boo" + $stderr.print "blah blah" + end + end + end + + def test_assert_output_triggered_err + assert_triggered util_msg("blah", "blah blah", "In stderr") do + @tc.assert_output nil, "blah" do + $stderr.print "blah blah" + end + end + end + + def test_assert_output_triggered_out + assert_triggered util_msg("blah", "blah blah", "In stdout") do + @tc.assert_output "blah" do + print "blah blah" + end + end + end + + def test_assert_output_no_block + assert_triggered "assert_output requires a block to capture output." do + @tc.assert_output "blah" + end + end + + def test_assert_output_nested_assert_uncaught + @assertion_count = 1 + + assert_triggered "Epic Fail!" do + @tc.assert_output "blah\n" do + puts "blah" + @tc.flunk + end + end + end + + def test_assert_output_nested_raise + @assertion_count = 2 + + @tc.assert_output "blah\n" do + @tc.assert_raises RuntimeError do + puts "blah" + raise "boom!" + end + end + end + + def test_assert_output_nested_raise_bad + @assertion_count = 0 + + assert_unexpected "boom!" do + @tc.assert_raises do # 2) bypassed via UnexpectedError + @tc.assert_output "blah\n" do # 1) captures and raises UnexpectedError + puts "not_blah" + raise "boom!" + end + end + end + end + + def test_assert_output_nested_raise_mismatch + # this test is redundant, but illustrative + @assertion_count = 0 + + assert_unexpected "boom!" do + @tc.assert_raises RuntimeError do # 2) bypassed via UnexpectedError + @tc.assert_output "blah\n" do # 1) captures and raises UnexpectedError + puts "not_blah" + raise ArgumentError, "boom!" + end + end + end + end + + def test_assert_output_nested_throw_caught + @assertion_count = 2 + + @tc.assert_output "blah\n" do + @tc.assert_throws :boom! do + puts "blah" + throw :boom! + end + end + end + + def test_assert_output_nested_throw_caught_bad + @assertion_count = 1 # want 0; can't prevent throw from escaping :( + + @tc.assert_throws :boom! do # 2) captured via catch + @tc.assert_output "blah\n" do # 1) bypassed via throw + puts "not_blah" + throw :boom! + end + end + end + + def test_assert_output_nested_throw_mismatch + @assertion_count = 0 + + assert_unexpected "uncaught throw :boom!" do + @tc.assert_throws :not_boom! do # 2) captured via assert_throws+rescue + @tc.assert_output "blah\n" do # 1) bypassed via throw + puts "not_blah" + throw :boom! + end + end + end + end + + def test_assert_output_uncaught_raise + @assertion_count = 0 + + assert_unexpected "RuntimeError: boom!" do + @tc.assert_output "blah\n" do + puts "not_blah" + raise "boom!" + end + end + end + + def test_assert_output_uncaught_throw + @assertion_count = 0 + + assert_unexpected "uncaught throw :boom!" do + @tc.assert_output "blah\n" do + puts "not_blah" + throw :boom! + end + end + end + + def test_assert_predicate + @tc.assert_predicate "", :empty? + end + + def test_assert_predicate_triggered + assert_triggered 'Expected "blah" to be empty?.' do + @tc.assert_predicate "blah", :empty? + end + end + + def test_assert_raises + @tc.assert_raises RuntimeError do + raise "blah" + end + end + + def test_assert_raises_default + @tc.assert_raises do + raise StandardError, "blah" + end + end + + def test_assert_raises_default_triggered + e = assert_raises Minitest::Assertion do + @tc.assert_raises do + raise SomeError, "blah" + end + end + + expected = <<~EOM.chomp + [StandardError] exception expected, not + Class: + Message: <"blah"> + ---Backtrace--- + FILE:LINE:in 'block in test_assert_raises_default_triggered' + --------------- + EOM + + actual = e.message.gsub(/^.+:\d+/, "FILE:LINE") + actual.gsub! RE_LEVELS, "" unless jruby? + actual.gsub!(/[`']block in (?:TestMinitestAssertions#)?/, "'block in ") + + assert_equal expected, actual + end + + def test_assert_raises_exit + @tc.assert_raises SystemExit do + exit 1 + end + end + + def test_assert_raises_module + @tc.assert_raises MyModule do + raise AnError + end + end + + def test_assert_raises_signals + @tc.assert_raises SignalException do + raise SignalException, :INT + end + end + + def test_assert_raises_throw_nested_bad + @assertion_count = 0 + + assert_unexpected "RuntimeError: boom!" do + @tc.assert_raises do + @tc.assert_throws :blah do + raise "boom!" + throw :not_blah + end + end + end + end + + ## + # *sigh* This is quite an odd scenario, but it is from real (albeit + # ugly) test code in ruby-core: + + # https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=29259 + + def test_assert_raises_skip + @assertion_count = 0 + + assert_triggered "skipped", Minitest::Skip do + @tc.assert_raises ArgumentError do + begin + raise "blah" + rescue + skip "skipped" + end + end + end + end + + def test_assert_raises_subclass + @tc.assert_raises StandardError do + raise AnError + end + end + + def test_assert_raises_subclass_triggered + e = assert_raises Minitest::Assertion do + @tc.assert_raises SomeError do + raise AnError, "some message" + end + end + + expected = <<~EOM + [SomeError] exception expected, not + Class: + Message: <"some message"> + ---Backtrace--- + FILE:LINE:in 'block in test_assert_raises_subclass_triggered' + --------------- + EOM + + actual = e.message.gsub(/^.+:\d+/, "FILE:LINE") + actual.gsub! RE_LEVELS, "" unless jruby? + actual.gsub!(/[`']block in (?:TestMinitestAssertions#)?/, "'block in ") + + assert_equal expected.chomp, actual + end + + def test_assert_raises_triggered_different + e = assert_raises Minitest::Assertion do + @tc.assert_raises RuntimeError do + raise SyntaxError, "icky" + end + end + + expected = <<~EOM.chomp + [RuntimeError] exception expected, not + Class: + Message: <"icky"> + ---Backtrace--- + FILE:LINE:in 'block in test_assert_raises_triggered_different' + --------------- + EOM + + actual = e.message.gsub(/^.+:\d+/, "FILE:LINE") + actual.gsub! RE_LEVELS, "" unless jruby? + actual.gsub!(/[`']block in (?:TestMinitestAssertions#)?/, "'block in ") + + assert_equal expected, actual + end + + def test_assert_raises_triggered_different_msg + e = assert_raises Minitest::Assertion do + @tc.assert_raises RuntimeError, "XXX" do + raise SyntaxError, "icky" + end + end + + expected = <<~EOM + XXX. + [RuntimeError] exception expected, not + Class: + Message: <"icky"> + ---Backtrace--- + FILE:LINE:in 'block in test_assert_raises_triggered_different_msg' + --------------- + EOM + + actual = e.message.gsub(/^.+:\d+/, "FILE:LINE") + actual.gsub! RE_LEVELS, "" unless jruby? + actual.gsub!(/[`']block in (?:TestMinitestAssertions#)?/, "'block in ") + + assert_equal expected.chomp, actual + end + + def test_assert_raises_triggered_none + e = assert_raises Minitest::Assertion do + @tc.assert_raises Minitest::Assertion do + # do nothing + end + end + + expected = "Minitest::Assertion expected but nothing was raised." + + assert_equal expected, e.message + end + + def test_assert_raises_triggered_none_msg + e = assert_raises Minitest::Assertion do + @tc.assert_raises Minitest::Assertion, "XXX" do + # do nothing + end + end + + expected = "XXX.\nMinitest::Assertion expected but nothing was raised." + + assert_equal expected, e.message + end + + def test_assert_raises_without_block + assert_triggered "assert_raises requires a block to capture errors." do + @tc.assert_raises StandardError + end + end + + def test_assert_respond_to + @tc.assert_respond_to "blah", :empty? + end + + def test_assert_respond_to_triggered + assert_triggered 'Expected "blah" (String) to respond to #rawr!.' do + @tc.assert_respond_to "blah", :rawr! + end + end + + def test_assert_respond_to__include_all + @tc.assert_respond_to @tc, :exit, include_all: true + end + + def test_assert_respond_to__include_all_triggered + assert_triggered(/Expected .+::DummyTest. to respond to #exit\?/) do + @tc.assert_respond_to @tc, :exit?, include_all: true + end + end + + def test_assert_same + @assertion_count = 3 + + o = "blah" + @tc.assert_same 1, 1 + @tc.assert_same :blah, :blah + @tc.assert_same o, o + end + + def test_assert_same_triggered + @assertion_count = 2 + + assert_triggered "Expected 2 (oid=N) to be the same as 1 (oid=N)." do + @tc.assert_same 1, 2 + end + + s1 = +"blah" + s2 = +"blah" + + assert_triggered 'Expected "blah" (oid=N) to be the same as "blah" (oid=N).' do + @tc.assert_same s1, s2 + end + end + + def test_assert_send + @assertion_count = 0 if error_on_warn? + assert_deprecation(/DEPRECATED: assert_send/) do + @tc.assert_send [1, :<, 2] + end + end + + def test_assert_send_bad + if error_on_warn? then + @assertion_count = 0 + assert_deprecation(/DEPRECATED: assert_send/) do + @tc.assert_send [1, :>, 2] + end + else + assert_triggered "Expected 1.>(*[2]) to return true." do + assert_deprecation(/DEPRECATED: assert_send/) do + @tc.assert_send [1, :>, 2] + end + end + end + end + + def test_assert_silent + @assertion_count = 2 + + @tc.assert_silent do + # do nothing + end + end + + def test_assert_silent_triggered_err + assert_triggered util_msg("", "blah blah", "In stderr") do + @tc.assert_silent do + $stderr.print "blah blah" + end + end + end + + def test_assert_silent_triggered_out + @assertion_count = 2 + + assert_triggered util_msg("", "blah blah", "In stdout") do + @tc.assert_silent do + print "blah blah" + end + end + end + + def test_assert_throws + v = @tc.assert_throws :blah do + throw :blah + end + + assert_nil v + end + + def test_assert_throws_value + v = @tc.assert_throws :blah do + throw :blah, 42 + end + + assert_equal 42, v + end + + def test_assert_throws_argument_exception + @assertion_count = 0 + + assert_unexpected "ArgumentError" do + @tc.assert_throws :blah do + raise ArgumentError + end + end + end + + def test_assert_throws_different + assert_triggered "Expected :blah to have been thrown, not :not_blah." do + @tc.assert_throws :blah do + throw :not_blah + end + end + end + + def test_assert_throws_name_error + @assertion_count = 0 + + assert_unexpected "NameError" do + @tc.assert_throws :blah do + raise NameError + end + end + end + + def test_assert_throws_unthrown + assert_triggered "Expected :blah to have been thrown." do + @tc.assert_throws :blah do + # do nothing + end + end + end + + def test_assert_path_exists + @tc.assert_path_exists __FILE__ + end + + def test_assert_path_exists_triggered + assert_triggered "Expected path 'blah' to exist." do + @tc.assert_path_exists "blah" + end + end + + def test_assert_pattern + if RUBY_VERSION > "3" then + @tc.assert_pattern do + exp = if RUBY_VERSION.start_with? "3.0" + "(eval):1: warning: One-line pattern matching is experimental, and the behavior may change in future versions of Ruby!\n" + else + "" + end + assert_output nil, exp do + eval "[1,2,3] => [Integer, Integer, Integer]" # eval to escape parser for ruby<3 + end + end + else + @assertion_count = 0 + + assert_raises NotImplementedError do + @tc.assert_pattern do + # do nothing + end + end + end + end + + def test_assert_pattern_traps_nomatchingpatternerror + skip unless RUBY_VERSION > "3" + exp = if RUBY_VERSION.start_with? "3.0" then + "[1, 2, 3]" # terrible error message! + else + /length mismatch/ + end + + assert_triggered exp do + @tc.assert_pattern do + capture_io do # 3.0 is noisy + eval "[1,2,3] => [Integer, Integer]" # eval to escape parser for ruby<3 + end + end + end + end + + def test_assert_pattern_raises_other_exceptions + skip unless RUBY_VERSION >= "3.0" + + @assertion_count = 0 + + assert_raises RuntimeError do + @tc.assert_pattern do + raise "boom" + end + end + end + + def test_assert_pattern_with_no_block + skip unless RUBY_VERSION >= "3.0" + + assert_triggered "assert_pattern requires a block to capture errors." do + @tc.assert_pattern + end + end + + def test_capture_io + @assertion_count = 0 + + non_verbose do + out, err = capture_io do + puts "hi" + $stderr.puts "bye!" + end + + assert_equal "hi\n", out + assert_equal "bye!\n", err + end + end + + def test_capture_subprocess_io + @assertion_count = 0 + + non_verbose do + out, err = capture_subprocess_io do + system "echo hi" + system "echo bye! 1>&2" + end + + assert_equal "hi\n", out + assert_equal "bye!", err.strip + end + end + + def test_class_asserts_match_refutes + @assertion_count = 0 + + methods = Minitest::Assertions.public_instance_methods.map(&:to_s) + + # These don't have corresponding refutes _on purpose_. They're + # useless and will never be added, so don't bother. + ignores = %w[assert_output assert_raises assert_send + assert_silent assert_throws assert_mock] + + ignores += %w[assert_allocations] # for minitest-gcstats + + asserts = methods.grep(/^assert/).sort - ignores + refutes = methods.grep(/^refute/).sort - ignores + + assert_empty refutes.map { |n| n.sub(/^refute/, "assert") } - asserts + assert_empty asserts.map { |n| n.sub(/^assert/, "refute") } - refutes + end + + def test_delta_consistency + @assertion_count = 2 + + @tc.assert_in_delta 0, 1, 1 + + assert_triggered "Expected |0 - 1| (1) to not be <= 1." do + @tc.refute_in_delta 0, 1, 1 + end + end + + def test_epsilon_consistency + @assertion_count = 2 + + @tc.assert_in_epsilon 1.0, 1.001 + + msg = "Expected |1.0 - 1.001| (0.000999xxx) to not be <= 0.001." + assert_triggered msg do + @tc.refute_in_epsilon 1.0, 1.001 + end + end + + def assert_fail_after t + @tc.fail_after t.year, t.month, t.day, "remove the deprecations" + end + + def test_fail_after + d0 = Time.now + d1 = d0 + 86_400 # I am an idiot + + assert_silent do + assert_fail_after d1 + end + + assert_triggered "remove the deprecations" do + assert_fail_after d0 + end + end + + def test_flunk + assert_triggered "Epic Fail!" do + @tc.flunk + end + end + + def test_flunk_message + assert_triggered @zomg do + @tc.flunk @zomg + end + end + + def test_pass + @tc.pass + end + + def test_refute + @assertion_count = 2 + + @tc.assert_equal true, @tc.refute(false), "returns true on success" + end + + def test_refute_empty + @assertion_count = 2 + + @tc.refute_empty [1] + end + + def test_refute_empty_triggered + @assertion_count = 2 + + assert_triggered "Expected [] to not be empty." do + @tc.refute_empty [] + end + end + + def test_refute_equal + @tc.refute_equal "blah", "yay" + end + + def test_refute_equal_triggered + assert_triggered 'Expected "blah" to not be equal to "blah".' do + @tc.refute_equal "blah", "blah" + end + end + + def test_refute_in_delta + @tc.refute_in_delta 0.0, 1.0 / 1000, 0.000001 + end + + def test_refute_in_delta_triggered + x = "0.1" + assert_triggered "Expected |0.0 - 0.001| (0.001) to not be <= #{x}." do + @tc.refute_in_delta 0.0, 1.0 / 1000, 0.1 + end + end + + def test_refute_in_epsilon + @tc.refute_in_epsilon 10_000, 9990-1 + end + + def test_refute_in_epsilon_triggered + assert_triggered "Expected |10000 - 9990| (10) to not be <= 10.0." do + @tc.refute_in_epsilon 10_000, 9990 + flunk + end + end + + def test_refute_includes + @assertion_count = 2 + + @tc.refute_includes [true], false + end + + def test_refute_includes_triggered + @assertion_count = 3 + + e = @tc.assert_raises Minitest::Assertion do + @tc.refute_includes [true], true + end + + expected = "Expected [true] to not include true." + assert_equal expected, e.message + end + + def test_refute_instance_of + @tc.refute_instance_of Array, "blah" + end + + def test_refute_instance_of_triggered + assert_triggered 'Expected "blah" to not be an instance of String.' do + @tc.refute_instance_of String, "blah" + end + end + + def test_refute_kind_of + @tc.refute_kind_of Array, "blah" + end + + def test_refute_kind_of_triggered + assert_triggered 'Expected "blah" to not be a kind of String.' do + @tc.refute_kind_of String, "blah" + end + end + + def test_refute_match + @assertion_count = 2 + @tc.refute_match(/\d+/, "blah blah blah") + end + + def test_refute_match_matcher_object + @assertion_count = 2 + pattern = Object.new + def pattern.=~ _; false end + @tc.refute_match pattern, 5 + end + + def test_refute_match_object_triggered + @assertion_count = 2 + + pattern = Object.new + def pattern.=~ _; true end + def pattern.inspect; "[Object]" end + + assert_triggered "Expected [Object] to not match 5." do + @tc.refute_match pattern, 5 + end + end + + def test_refute_match_triggered + @assertion_count = 2 + assert_triggered 'Expected /\w+/ to not match "blah blah blah".' do + @tc.refute_match(/\w+/, "blah blah blah") + end + end + + def test_refute_nil + @tc.refute_nil 42 + end + + def test_refute_nil_triggered + assert_triggered "Expected nil to not be nil." do + @tc.refute_nil nil + end + end + + def test_refute_operator + @tc.refute_operator 2, :<, 1 + end + + def test_refute_operator_bad_object + bad = Object.new + def bad.== _; true end + + @tc.refute_operator true, :equal?, bad + end + + def test_refute_operator_triggered + assert_triggered "Expected 2 to not be > 1." do + @tc.refute_operator 2, :>, 1 + end + end + + def test_refute_pattern + if RUBY_VERSION >= "3.0" + @tc.refute_pattern do + capture_io do # 3.0 is noisy + eval "[1,2,3] => [Integer, Integer, String]" + end + end + else + @assertion_count = 0 + + assert_raises NotImplementedError do + @tc.refute_pattern do + eval "[1,2,3] => [Integer, Integer, String]" + end + end + end + end + + def test_refute_pattern_expects_nomatchingpatternerror + skip unless RUBY_VERSION > "3" + + assert_triggered(/NoMatchingPatternError expected, but nothing was raised./) do + @tc.refute_pattern do + capture_io do # 3.0 is noisy + eval "[1,2,3] => [Integer, Integer, Integer]" + end + end + end + end + + def test_refute_pattern_raises_other_exceptions + skip unless RUBY_VERSION >= "3.0" + + @assertion_count = 0 + + assert_raises RuntimeError do + @tc.refute_pattern do + raise "boom" + end + end + end + + def test_refute_pattern_with_no_block + skip unless RUBY_VERSION >= "3.0" + + assert_triggered "refute_pattern requires a block to capture errors." do + @tc.refute_pattern + end + end + + def test_refute_predicate + @tc.refute_predicate "42", :empty? + end + + def test_refute_predicate_triggered + assert_triggered 'Expected "" to not be empty?.' do + @tc.refute_predicate "", :empty? + end + end + + def test_refute_respond_to + @tc.refute_respond_to "blah", :rawr! + end + + def test_refute_respond_to_triggered + assert_triggered 'Expected "blah" to not respond to empty?.' do + @tc.refute_respond_to "blah", :empty? + end + end + + def test_refute_respond_to__include_all + @tc.refute_respond_to "blah", :missing, include_all: true + end + + def test_refute_respond_to__include_all_triggered + assert_triggered(/Expected .*DummyTest.* to not respond to exit./) do + @tc.refute_respond_to @tc, :exit, include_all: true + end + end + + def test_refute_same + @tc.refute_same 1, 2 + end + + def test_refute_same_triggered + assert_triggered "Expected 1 (oid=N) to not be the same as 1 (oid=N)." do + @tc.refute_same 1, 1 + end + end + + def test_refute_path_exists + @tc.refute_path_exists "blah" + end + + def test_refute_path_exists_triggered + assert_triggered "Expected path '#{__FILE__}' to not exist." do + @tc.refute_path_exists __FILE__ + end + end + + def test_skip + @assertion_count = 0 + + assert_triggered "haha!", Minitest::Skip do + @tc.skip "haha!" + end + end + + def assert_skip_until t, msg + @tc.skip_until t.year, t.month, t.day, msg + end + + def test_skip_until + @assertion_count = 0 + + d0 = Time.now + d1 = d0 + 86_400 # I am an idiot + + assert_deprecation(/Stale skip_until \"not yet\" at .*?:\d+$/) do + assert_skip_until d0, "not yet" + end + + assert_triggered "not ready yet", Minitest::Skip do + assert_skip_until d1, "not ready yet" + end + end + + def util_msg exp, act, msg = nil + s = "Expected: #{exp.inspect}\n Actual: #{act.inspect}" + s = "#{msg}.\n#{s}" if msg + s + end + + def without_diff + old_diff = Minitest::Assertions.diff + Minitest::Assertions.diff = nil + + yield + ensure + Minitest::Assertions.diff = old_diff + end +end + +class TestMinitestAssertionHelpers < Minitest::Test + def assert_mu_pp exp, input, raw = false + act = mu_pp input + + if String === input && !raw then + assert_equal "\"#{exp}\"", act + else + assert_equal exp, act + end + end + + def assert_mu_pp_for_diff exp, input, raw = false + act = mu_pp_for_diff input + + if String === input && !raw then + assert_equal "\"#{exp}\"", act + else + assert_equal exp, act + end + end + + def test_diff_equal + msg = <<~EOM.chomp + No visible difference in the String#inspect output. + You should look at the implementation of #== on String or its members. + "blahblahblahblahblahblahblahblahblahblah" + EOM + + o1 = "blah" * 10 + o2 = "blah" * 10 + def o1.== _ + false + end + + assert_equal msg, diff(o1, o2) + end + + def test_diff_str_mixed + msg = <<~'EOM' # NOTE single quotes on heredoc + --- expected + +++ actual + @@ -1 +1 @@ + -"A\\n\nB" + +"A\n\\nB" + EOM + + exp = "A\\n\nB" + act = "A\n\\nB" + + assert_equal msg, diff(exp, act) + end + + def test_diff_str_multiline + msg = <<~EOM + --- expected + +++ actual + @@ -1,2 +1,2 @@ + "A + -B" + +C" + EOM + + exp = "A\nB" + act = "A\nC" + + assert_equal msg, diff(exp, act) + end + + def test_diff_str_simple + msg = <<~EOM.chomp + Expected: "A" + Actual: "B" + EOM + + exp = "A" + act = "B" + + assert_equal msg, diff(exp, act) + end + + def test_message + assert_equal "blah2.", message { "blah2" }.call + assert_equal "blah2.", message("") { "blah2" }.call + assert_equal "blah1.\nblah2.", message(:blah1) { "blah2" }.call + assert_equal "blah1.\nblah2.", message("blah1") { "blah2" }.call + + message = proc { "blah1" } + assert_equal "blah1.\nblah2.", message(message) { "blah2" }.call + + message = message { "blah1" } + assert_equal "blah1.\nblah2.", message(message) { "blah2" }.call + end + + def test_message_deferred + var = nil + + msg = message { var = "blah" } + + assert_nil var + + msg.call + + assert_equal "blah", var + end + + def test_mu_pp + assert_mu_pp 42.inspect, 42 + assert_mu_pp %w[a b c].inspect, %w[a b c] + assert_mu_pp "A B", "A B" + assert_mu_pp "A\\nB", "A\nB" + assert_mu_pp "A\\\\nB", 'A\nB' # notice single quotes + end + + def test_mu_pp_for_diff + assert_mu_pp_for_diff "#", Object.new + assert_mu_pp_for_diff "A B", "A B" + assert_mu_pp_for_diff [1, 2, 3].inspect, [1, 2, 3] + assert_mu_pp_for_diff "A\nB", "A\nB" + end + + def test_mu_pp_for_diff_str_bad_encoding + str = "\666".dup.force_encoding Encoding::UTF_8 + exp = "# encoding: UTF-8\n# valid: false\n\"\\xB6\"" + + assert_mu_pp_for_diff exp, str, :raw + end + + def test_mu_pp_for_diff_str_bad_encoding_both + str = "\666A\\n\nB".dup.force_encoding Encoding::UTF_8 + exp = "# encoding: UTF-8\n# valid: false\n\"\\xB6A\\\\n\\nB\"" + + assert_mu_pp_for_diff exp, str, :raw + end + + def test_mu_pp_for_diff_str_encoding + str = "A\nB".b + exp = "# encoding: #{Encoding::BINARY.name}\n# valid: true\n\"A\nB\"" + + assert_mu_pp_for_diff exp, str, :raw + end + + def test_mu_pp_for_diff_str_encoding_both + str = "A\\n\nB".b + exp = "# encoding: #{Encoding::BINARY.name}\n# valid: true\n\"A\\\\n\\nB\"" + + assert_mu_pp_for_diff exp, str, :raw + end + + def test_mu_pp_for_diff_str_nerd + assert_mu_pp_for_diff "A\\nB\\\\nC", "A\nB\\nC" + assert_mu_pp_for_diff "\\nB\\\\nC", "\nB\\nC" + assert_mu_pp_for_diff "\\nB\\\\n", "\nB\\n" + assert_mu_pp_for_diff "\\n\\\\n", "\n\\n" + assert_mu_pp_for_diff "\\\\n\\n", "\\n\n" + assert_mu_pp_for_diff "\\\\nB\\n", "\\nB\n" + assert_mu_pp_for_diff "\\\\nB\\nC", "\\nB\nC" + assert_mu_pp_for_diff "A\\\\n\\nB", "A\\n\nB" + assert_mu_pp_for_diff "A\\n\\\\nB", "A\n\\nB" + assert_mu_pp_for_diff "\\\\n\\n", "\\n\n" + assert_mu_pp_for_diff "\\n\\\\n", "\n\\n" + end + + def test_mu_pp_for_diff_str_normal + assert_mu_pp_for_diff "", "" + assert_mu_pp_for_diff "A\\n\n", "A\\n" + assert_mu_pp_for_diff "A\\n\nB", "A\\nB" + assert_mu_pp_for_diff "A\n", "A\n" + assert_mu_pp_for_diff "A\nB", "A\nB" + assert_mu_pp_for_diff "\\n\n", "\\n" + assert_mu_pp_for_diff "\n", "\n" + assert_mu_pp_for_diff "\\n\nA", "\\nA" + assert_mu_pp_for_diff "\nA", "\nA" + end + + def test_mu_pp_str_bad_encoding + str = "\666".dup.force_encoding Encoding::UTF_8 + exp = "# encoding: UTF-8\n# valid: false\n\"\\xB6\"" + + assert_mu_pp exp, str, :raw + end + + def test_mu_pp_str_encoding + str = "A\nB".b + exp = "# encoding: #{Encoding::BINARY.name}\n# valid: true\n\"A\\nB\"" + + assert_mu_pp exp, str, :raw + end + + def test_mu_pp_str_immutable + printer = Class.new { extend Minitest::Assertions } + str = "test".freeze + assert_equal '"test"', printer.mu_pp(str) + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_benchmark.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_benchmark.rb new file mode 100644 index 00000000..18ce890c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_benchmark.rb @@ -0,0 +1,137 @@ +require "minitest/autorun" +require "minitest/benchmark" + +## +# Used to verify data: +# https://www.wolframalpha.com/examples/RegressionAnalysis.html + +class TestMinitestBenchmark < Minitest::Test + def test_cls_bench_exp + assert_equal [2, 4, 8, 16, 32], Minitest::Benchmark.bench_exp(2, 32, 2) + end + + def test_cls_bench_linear + assert_equal [2, 4, 6, 8, 10], Minitest::Benchmark.bench_linear(2, 10, 2) + end + + def test_cls_runnable_methods + assert_equal [], Minitest::Benchmark.runnable_methods + + c = Class.new Minitest::Benchmark do + def bench_blah + end + end + + assert_equal ["bench_blah"], c.runnable_methods + end + + def test_cls_bench_range + assert_equal [1, 10, 100, 1_000, 10_000], Minitest::Benchmark.bench_range + end + + def test_fit_exponential_clean + x = [1.0, 2.0, 3.0, 4.0, 5.0] + y = x.map { |n| 1.1 * Math.exp(2.1 * n) } + + assert_fit :exponential, x, y, 1.0, 1.1, 2.1 + end + + def test_fit_exponential_noisy + x = [1.0, 1.9, 2.6, 3.4, 5.0] + y = [12, 10, 8.2, 6.9, 5.9] + + # verified with Numbers and R + assert_fit :exponential, x, y, 0.95, 13.81148, -0.1820 + end + + def test_fit_logarithmic_clean + x = [1.0, 2.0, 3.0, 4.0, 5.0] + y = x.map { |n| 1.1 + 2.1 * Math.log(n) } + + assert_fit :logarithmic, x, y, 1.0, 1.1, 2.1 + end + + def test_fit_logarithmic_noisy + x = [1.0, 2.0, 3.0, 4.0, 5.0] + # Generated with + # y = x.map { |n| jitter = 0.999 + 0.002 * rand; (Math.log(n) ) * jitter } + y = [0.0, 0.6935, 1.0995, 1.3873, 1.6097] + + assert_fit :logarithmic, x, y, 0.95, 0, 1 + end + + def test_fit_constant_clean + x = (1..5).to_a + y = [5.0, 5.0, 5.0, 5.0, 5.0] + + assert_fit :linear, x, y, nil, 5.0, 0 + end + + def test_fit_constant_noisy + x = (1..5).to_a + y = [1.0, 1.2, 1.0, 0.8, 1.0] + + # verified in numbers and R + assert_fit :linear, x, y, nil, 1.12, -0.04 + end + + def test_fit_linear_clean + # y = m * x + b where m = 2.2, b = 3.1 + x = (1..5).to_a + y = x.map { |n| 2.2 * n + 3.1 } + + assert_fit :linear, x, y, 1.0, 3.1, 2.2 + end + + def test_fit_linear_noisy + x = [ 60, 61, 62, 63, 65] + y = [3.1, 3.6, 3.8, 4.0, 4.1] + + # verified in numbers and R + assert_fit :linear, x, y, 0.8315, -7.9635, 0.1878 + end + + def test_fit_power_clean + # y = A x ** B, where B = b and A = e ** a + # if, A = 1, B = 2, then + + x = [1.0, 2.0, 3.0, 4.0, 5.0] + y = [1.0, 4.0, 9.0, 16.0, 25.0] + + assert_fit :power, x, y, 1.0, 1.0, 2.0 + end + + def test_fit_power_noisy + # from www.engr.uidaho.edu/thompson/courses/ME330/lecture/least_squares.html + x = [10, 12, 15, 17, 20, 22, 25, 27, 30, 32, 35] + y = [95, 105, 125, 141, 173, 200, 253, 298, 385, 459, 602] + + # verified in numbers + assert_fit :power, x, y, 0.90, 2.6217, 1.4556 + + # income to % of households below income amount + # https://library.wolfram.com/infocenter/Conferences/6461/PowerLaws.nb + x = [15_000, 25_000, 35_000, 50_000, 75_000, 100_000] + y = [0.154, 0.283, 0.402, 0.55, 0.733, 0.843] + + # verified in numbers + assert_fit :power, x, y, 0.96, 3.119e-5, 0.8959 + end + + def assert_fit msg, x, y, fit, exp_a, exp_b + bench = Minitest::Benchmark.new :blah + + a, b, rr = bench.send "fit_#{msg}", x, y + + assert_operator rr, :>=, fit if fit + assert_in_delta exp_a, a + assert_in_delta exp_b, b + end +end + +describe "my class Bench" do + klass = self + it "should provide bench methods" do + klass.must_respond_to :bench + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_mock.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_mock.rb new file mode 100644 index 00000000..3823f7eb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_mock.rb @@ -0,0 +1,1218 @@ +require "minitest/autorun" + +def with_kwargs_env + ENV["MT_KWARGS_HAC\K"] = "1" + + yield +ensure + ENV.delete "MT_KWARGS_HAC\K" +end + +class TestMinitestMock < Minitest::Test + def setup + @mock = Minitest::Mock.new + .expect(:foo, nil) + .expect(:meaning_of_life, 42) + end + + def test_create_stub_method + assert_nil @mock.foo + end + + def test_allow_return_value_specification + assert_equal 42, @mock.meaning_of_life + end + + def test_blow_up_if_not_called + @mock.foo + + util_verify_bad "Expected meaning_of_life() => 42" + end + + def test_not_blow_up_if_everything_called + @mock.foo + @mock.meaning_of_life + + assert_mock @mock + end + + def test_allow_expectations_to_be_added_after_creation + @mock.expect :bar, true + assert @mock.bar + end + + def test_not_verify_if_new_expected_method_is_not_called + @mock.foo + @mock.meaning_of_life + @mock.expect :bar, true + + util_verify_bad "Expected bar() => true" + end + + def test_blow_up_on_wrong_number_of_arguments + @mock.foo + @mock.meaning_of_life + @mock.expect :sum, 3, [1, 2] + + e = assert_raises ArgumentError do + @mock.sum + end + + assert_equal "mocked method :sum expects 2 arguments, got []", e.message + end + + def test_return_mock_does_not_raise + retval = Minitest::Mock.new + mock = Minitest::Mock.new + mock.expect :foo, retval + mock.foo + + assert_mock mock + end + + def test_mock_args_does_not_raise + arg = Minitest::Mock.new + mock = Minitest::Mock.new + mock.expect :foo, nil, [arg] + mock.foo arg + + assert_mock mock + end + + def test_set_expectation_on_special_methods + mock = Minitest::Mock.new + + mock.expect :object_id, "received object_id" + assert_equal "received object_id", mock.object_id + + mock.expect :respond_to_missing?, "received respond_to_missing?" + assert_equal "received respond_to_missing?", mock.respond_to_missing? + + mock.expect :===, "received ===" + assert_equal "received ===", mock.=== + + mock.expect :inspect, "received inspect" + assert_equal "received inspect", mock.inspect + + mock.expect :to_s, "received to_s" + assert_equal "received to_s", mock.to_s + + mock.expect :public_send, "received public_send" + assert_equal "received public_send", mock.public_send + + mock.expect :send, "received send" + assert_equal "received send", mock.send + + assert_mock mock + end + + def test_expectations_can_be_satisfied_via_send + @mock.send :foo + @mock.send :meaning_of_life + + assert_mock @mock + end + + def test_expectations_can_be_satisfied_via_public_send + @mock.public_send :foo + @mock.public_send :meaning_of_life + + assert_mock @mock + end + + def test_blow_up_on_wrong_arguments + @mock.foo + @mock.meaning_of_life + @mock.expect :sum, 3, [1, 2] + + e = assert_raises MockExpectationError do + @mock.sum 2, 4 + end + + exp = "mocked method :sum called with unexpected arguments [2, 4]" + assert_equal exp, e.message + end + + def test_expect_with_non_array_args + e = assert_raises ArgumentError do + @mock.expect :blah, 3, false + end + + assert_match "args must be an array", e.message + end + + def test_respond_appropriately + assert @mock.respond_to?(:foo) + assert @mock.respond_to?(:foo, true) + assert @mock.respond_to?("foo") + assert !@mock.respond_to?(:bar) + end + + def test_no_method_error_on_unexpected_methods + e = assert_raises NoMethodError do + @mock.bar + end + + expected = "unmocked method :bar, expected one of [:foo, :meaning_of_life]" + + assert_match expected, e.message + end + + def test_assign_per_mock_return_values + a = Minitest::Mock.new + b = Minitest::Mock.new + + a.expect :foo, :a + b.expect :foo, :b + + assert_equal :a, a.foo + assert_equal :b, b.foo + end + + def test_do_not_create_stub_method_on_new_mocks + a = Minitest::Mock.new + a.expect :foo, :a + + assert !Minitest::Mock.new.respond_to?(:foo) + end + + def test_mock_is_a_blank_slate + @mock.expect :kind_of?, true, [String] + @mock.expect :==, true, [1] + + assert @mock.kind_of?(String), "didn't mock :kind_of?" + assert @mock == 1, "didn't mock :==" + end + + def test_assert_mock__pass + mock = Minitest::Mock.new + mock.expect :loose_expectation, true, [Integer] + mock.loose_expectation 1 + + result = assert_mock mock + + assert_equal true, result + end + + def assert_bad_mock klass, msg + mock = Minitest::Mock.new + mock.expect :foo, nil, [:bar] + mock.expect :foo, nil, [:baz] + + mock.foo :bar + + e = assert_raises klass do + yield mock + end + + assert_equal msg, e.message + end + + def test_verify__error + exp = "Expected foo(:baz) => nil, got [foo(:bar) => nil]" + assert_bad_mock MockExpectationError, exp do |mock| + mock.verify + end + end + + def test_assert_mock__fail + exp = "Expected foo(:baz) => nil, got [foo(:bar) => nil]." + assert_bad_mock Minitest::Assertion, exp do |mock| + assert_mock mock + end + end + + def test_assert_mock__fail_msg + exp = "BLAH.\nExpected foo(:baz) => nil, got [foo(:bar) => nil]." + assert_bad_mock Minitest::Assertion, exp do |mock| + assert_mock mock, "BLAH" + end + end + + def test_assert_mock__fail_exp + exp = "Expected foo(:baz) => nil, got [foo(:bar) => nil]." + assert_bad_mock Minitest::Assertion, exp do |mock| + describe "X" do + it "y" do + _(mock).must_verify + end + end.new(:blah).send(:test_0001_y) + end + end + + def test_assert_mock__fail_exp_msg + exp = "BLAH.\nExpected foo(:baz) => nil, got [foo(:bar) => nil]." + assert_bad_mock Minitest::Assertion, exp do |mock| + describe "X" do + it "y" do + _(mock).must_verify "BLAH" + end + end.new(:blah).send(:test_0001_y) + end + end + + def test_verify_allows_called_args_to_be_loosely_specified + mock = Minitest::Mock.new + mock.expect :loose_expectation, true, [Integer] + mock.loose_expectation 1 + + assert_mock mock + end + + def test_verify_raises_with_strict_args + mock = Minitest::Mock.new + mock.expect :strict_expectation, true, [2] + + e = assert_raises MockExpectationError do + mock.strict_expectation 1 + end + + exp = "mocked method :strict_expectation called with unexpected arguments [1]" + assert_equal exp, e.message + end + + def test_method_missing_empty + mock = Minitest::Mock.new + + mock.expect :a, nil + + mock.a + + e = assert_raises MockExpectationError do + mock.a + end + + assert_equal "No more expects available for :a: [] {}", e.message + end + + def test_same_method_expects_are_verified_when_all_called + mock = Minitest::Mock.new + mock.expect :foo, nil, [:bar] + mock.expect :foo, nil, [:baz] + + mock.foo :bar + mock.foo :baz + + assert_mock mock + end + + def test_same_method_expects_blow_up_when_not_all_called + mock = Minitest::Mock.new + mock.expect :foo, nil, [:bar] + mock.expect :foo, nil, [:baz] + + mock.foo :bar + + e = assert_raises(MockExpectationError) { mock.verify } + + exp = "Expected foo(:baz) => nil, got [foo(:bar) => nil]" + + assert_equal exp, e.message + end + + def test_same_method_expects_with_same_args_blow_up_when_not_all_called + mock = Minitest::Mock.new + mock.expect :foo, nil, [:bar] + mock.expect :foo, nil, [:bar] + + mock.foo :bar + + e = assert_raises(MockExpectationError) { mock.verify } + + exp = "Expected foo(:bar) => nil, got [foo(:bar) => nil]" + + assert_equal exp, e.message + end + + def test_delegator_calls_are_propagated + delegator = Object.new + mock = Minitest::Mock.new delegator + + refute delegator.nil? + refute mock.nil? + assert_mock mock + end + + def test_handles_kwargs_in_error_message + mock = Minitest::Mock.new + + mock.expect :foo, nil, [], kw: true + mock.expect :foo, nil, [], kw: false + + mock.foo kw: true + + e = assert_raises(MockExpectationError) { mock.verify } + + exp = "Expected foo(%p) => nil, got [foo(%p) => nil]" \ + % [{ :kw => false }, { :kw => true }] + + assert_equal exp.delete("{}"), e.message + end + + def test_verify_passes_when_mock_block_returns_true + mock = Minitest::Mock.new + mock.expect :foo, nil do + true + end + + mock.foo + + assert_mock mock + end + + def test_mock_block_is_passed_function_params + arg1, arg2, arg3 = :bar, [1, 2, 3], { :a => "a" } + mock = Minitest::Mock.new + mock.expect :foo, nil do |a1, a2, a3| + a1 == arg1 && a2 == arg2 && a3 == arg3 + end + + assert_silent do + if RUBY_VERSION > "3" then + mock.foo arg1, arg2, arg3 + else + mock.foo arg1, arg2, **arg3 # oddity just for ruby 2.7 + end + end + + assert_mock mock + end + + def test_mock_block_is_passed_keyword_args__block + arg1, arg2, arg3 = :bar, [1, 2, 3], { :a => "a" } + mock = Minitest::Mock.new + mock.expect :foo, nil do |k1:, k2:, k3:| + k1 == arg1 && k2 == arg2 && k3 == arg3 + end + + mock.foo k1: arg1, k2: arg2, k3: arg3 + + assert_mock mock + end + + def test_mock_block_is_passed_keyword_args__block_bad_missing + arg1, arg2, arg3 = :bar, [1, 2, 3], { :a => "a" } + mock = Minitest::Mock.new + mock.expect :foo, nil do |k1:, k2:, k3:| + k1 == arg1 && k2 == arg2 && k3 == arg3 + end + + e = assert_raises ArgumentError do + mock.foo k1: arg1, k2: arg2 + end + + # basically testing ruby ... need ? for ruby < 2.7 :( + assert_match(/missing keyword: :?k3/, e.message) + end + + def test_mock_block_is_passed_keyword_args__block_bad_extra + arg1, arg2, arg3 = :bar, [1, 2, 3], { :a => "a" } + mock = Minitest::Mock.new + mock.expect :foo, nil do |k1:, k2:| + k1 == arg1 && k2 == arg2 && k3 == arg3 + end + + e = assert_raises ArgumentError do + mock.foo k1: arg1, k2: arg2, k3: arg3 + end + + # basically testing ruby ... need ? for ruby < 2.7 :( + assert_match(/unknown keyword: :?k3/, e.message) + end + + def test_mock_block_is_passed_keyword_args__block_bad_value + arg1, arg2, arg3 = :bar, [1, 2, 3], { :a => "a" } + mock = Minitest::Mock.new + mock.expect :foo, nil do |k1:, k2:, k3:| + k1 == arg1 && k2 == arg2 && k3 == arg3 + end + + e = assert_raises MockExpectationError do + mock.foo k1: arg1, k2: arg2, k3: :BAD! + end + + exp = "mocked method :foo failed block w/ [] %p" \ + % [{ :k1 => :bar, :k2 => [1, 2, 3], :k3 => :BAD! }] + + assert_equal exp, e.message + end + + def test_mock_block_is_passed_keyword_args__args + arg1, arg2, arg3 = :bar, [1, 2, 3], { :a => "a" } + mock = Minitest::Mock.new + mock.expect :foo, nil, k1: arg1, k2: arg2, k3: arg3 + + mock.foo k1: arg1, k2: arg2, k3: arg3 + + assert_mock mock + end + + def test_mock_allow_all_kwargs__old_style_env + with_kwargs_env do + mock = Minitest::Mock.new + mock.expect :foo, true, [Hash] + assert_equal true, mock.foo(bar: 42) + end + end + + def test_mock_allow_all_kwargs__old_style_env__rewrite + with_kwargs_env do + mock = Minitest::Mock.new + mock.expect :foo, true, [], bar: Integer + assert_equal true, mock.foo(bar: 42) + end + end + + def test_mock_block_is_passed_keyword_args__args__old_style_bad + arg1, arg2, arg3 = :bar, [1, 2, 3], { :a => "a" } + mock = Minitest::Mock.new + mock.expect :foo, nil, [{ k1: arg1, k2: arg2, k3: arg3 }] + + e = assert_raises ArgumentError do + mock.foo k1: arg1, k2: arg2, k3: arg3 + end + + assert_equal "mocked method :foo expects 1 arguments, got []", e.message + end + + def test_mock_block_is_passed_keyword_args__args__old_style_env + with_kwargs_env do + arg1, arg2, arg3 = :bar, [1, 2, 3], { :a => "a" } + mock = Minitest::Mock.new + mock.expect :foo, nil, [{ k1: arg1, k2: arg2, k3: arg3 }] + + mock.foo k1: arg1, k2: arg2, k3: arg3 + + assert_mock mock + end + end + + def test_mock_block_is_passed_keyword_args__args__old_style_both + with_kwargs_env do + arg1, arg2, arg3 = :bar, [1, 2, 3], { :a => "a" } + mock = Minitest::Mock.new + + assert_deprecation(/Using MT_KWARGS_HAC. yet passing kwargs/) do + mock.expect :foo, nil, [{}], k1: arg1, k2: arg2, k3: arg3 + end + + skip "-Werror" if error_on_warn? # mock above raised, so this is dead + + mock.foo({}, k1: arg1, k2: arg2, k3: arg3) + + assert_mock mock + end + end + + def test_mock_block_is_passed_keyword_args__args_bad_missing + arg1, arg2, arg3 = :bar, [1, 2, 3], { :a => "a" } + mock = Minitest::Mock.new + mock.expect :foo, nil, k1: arg1, k2: arg2, k3: arg3 + + e = assert_raises ArgumentError do + mock.foo k1: arg1, k2: arg2 + end + + assert_equal "mocked method :foo expects 3 keyword arguments, got %p" % { k1: arg1, k2: arg2 }, e.message + end + + def test_mock_block_is_passed_keyword_args__args_bad_extra + arg1, arg2, arg3 = :bar, [1, 2, 3], { :a => "a" } + mock = Minitest::Mock.new + mock.expect :foo, nil, k1: arg1, k2: arg2 + + e = assert_raises ArgumentError do + mock.foo k1: arg1, k2: arg2, k3: arg3 + end + + assert_equal "mocked method :foo expects 2 keyword arguments, got %p" % { k1: arg1, k2: arg2, k3: arg3 }, e.message + end + + def test_mock_block_is_passed_keyword_args__args_bad_key + arg1, arg2, arg3 = :bar, [1, 2, 3], { :a => "a" } + mock = Minitest::Mock.new + mock.expect :foo, nil, k1: arg1, k2: arg2, k3: arg3 + + e = assert_raises MockExpectationError do + mock.foo k1: arg1, k2: arg2, BAD: arg3 + end + + assert_includes e.message, "unexpected keywords [:k1, :k2, :k3]" + assert_includes e.message, "vs [:k1, :k2, :BAD]" + end + + def test_mock_block_is_passed_keyword_args__args_bad_val + arg1, arg2, arg3 = :bar, [1, 2, 3], { :a => "a" } + mock = Minitest::Mock.new + mock.expect :foo, nil, k1: arg1, k2: arg2, k3: arg3 + + e = assert_raises MockExpectationError do + mock.foo k1: arg1, k2: :BAD!, k3: arg3 + end + + bad = { :k2 => :BAD! }.inspect.delete "{}" + assert_match(/unexpected keyword arguments.* vs .*#{bad}/, e.message) + end + + def test_mock_block_is_passed_function_block + mock = Minitest::Mock.new + block = proc { "bar" } + mock.expect :foo, nil do |arg, &blk| + arg == "foo" && blk == block + end + mock.foo "foo", &block + assert_mock mock + end + + def test_mock_forward_keyword_arguments + mock = Minitest::Mock.new + mock.expect(:foo, nil) { |bar:| bar == "bar" } + mock.foo bar: "bar" + assert_mock mock + end + + def test_verify_fails_when_mock_block_returns_false + mock = Minitest::Mock.new + mock.expect :foo, nil do + false + end + + e = assert_raises(MockExpectationError) { mock.foo } + exp = "mocked method :foo failed block w/ [] {}" + + assert_equal exp, e.message + end + + def test_mock_block_raises_if_args_passed + mock = Minitest::Mock.new + + e = assert_raises ArgumentError do + mock.expect :foo, nil, [:a, :b, :c] do + true + end + end + + exp = "args ignored when block given" + + assert_match exp, e.message + end + + def test_mock_block_raises_if_kwargs_passed + mock = Minitest::Mock.new + + e = assert_raises ArgumentError do + mock.expect :foo, nil, kwargs: 1 do + true + end + end + + exp = "kwargs ignored when block given" + + assert_match exp, e.message + end + + def test_mock_returns_retval_when_called_with_block + mock = Minitest::Mock.new + mock.expect :foo, 32 do + true + end + + rs = mock.foo + + assert_equal rs, 32 + end + + def util_verify_bad exp + e = assert_raises MockExpectationError do + @mock.verify + end + + assert_equal exp, e.message + end + + def test_mock_called_via_send + mock = Minitest::Mock.new + mock.expect :foo, true + + mock.send :foo + assert_mock mock + end + + def test_mock_called_via___send__ + mock = Minitest::Mock.new + mock.expect :foo, true + + mock.__send__ :foo + assert_mock mock + end + + def test_mock_called_via_send_with_args + mock = Minitest::Mock.new + mock.expect :foo, true, [1, 2, 3] + + mock.send :foo, 1, 2, 3 + assert_mock mock + end + +end + +require "minitest/metametameta" + +class TestMinitestStub < Minitest::Test + # Do not parallelize since we're calling stub on class methods + + def setup + super + Minitest::Test.reset + + @tc = Minitest::Test.new "fake tc" + @assertion_count = 1 + end + + def teardown + super + assert_equal @assertion_count, @tc.assertions if self.passed? + end + + class Time + def self.now + 24 + end + end + + def assert_stub val_or_callable + @assertion_count += 1 + + t = Time.now.to_i + + Time.stub :now, val_or_callable do + @tc.assert_equal 42, Time.now + end + + @tc.assert_operator Time.now.to_i, :>=, t + end + + def test_stub_private_module_method + @assertion_count += 1 + + t0 = Time.now + + self.stub :sleep, nil do + @tc.assert_nil sleep(10) + end + + @tc.assert_operator Time.now - t0, :<=, 1 + end + + def test_stub_private_module_method_indirect + @assertion_count += 1 + + fail_clapper = Class.new do + def fail_clap + raise + :clap + end + end.new + + fail_clapper.stub :raise, nil do |safe_clapper| + @tc.assert_equal :clap, safe_clapper.fail_clap # either form works + @tc.assert_equal :clap, fail_clapper.fail_clap # yay closures + end + end + + def test_stub_public_module_method + Math.stub :log10, :stubbed do + @tc.assert_equal :stubbed, Math.log10(1000) + end + end + + def test_stub_value__literal + assert_stub 42 + end + + def test_stub_block + assert_stub lambda { 42 } + end + + def test_stub_block_args + @assertion_count += 1 + + t = Time.now.to_i + + Time.stub :now, lambda { |n| n * 2 } do + @tc.assert_equal 42, Time.now(21) + end + + @tc.assert_operator Time.now.to_i, :>=, t + end + + def test_stub_callable + obj = Object.new + + def obj.call + 42 + end + + assert_stub obj + end + + def test_stub_yield_self + obj = +"foo" + + val = obj.stub :to_s, "bar" do |s| + s.to_s + end + + @tc.assert_equal "bar", val + end + + def test_dynamic_method + @assertion_count = 2 + + dynamic = Class.new do + def self.respond_to? meth + meth == :found + end + + def self.method_missing meth, *args, &block + if meth == :found + false + else + super + end + end + end + + val = dynamic.stub :found, true do |s| + s.found + end + + @tc.assert_equal true, val + @tc.assert_equal false, dynamic.found + end + + def test_stub_NameError + e = @tc.assert_raises NameError do + Time.stub :nope_nope_nope, 42 do + # do nothing + end + end + + exp = if jruby? then + /Undefined method nope_nope_nope for '#{self.class}::Time'/ + else + /undefined method [`']nope_nope_nope' for( class)? [`']#{self.class}::Time'/ + end + assert_match exp, e.message + end + + def test_mock_with_yield + mock = Minitest::Mock.new + mock.expect :write, true do + true + end + rs = nil + + File.stub :open, true, mock do + File.open "foo.txt", "r" do |f| + rs = f.write + end + end + @tc.assert_equal true, rs + end + + def test_mock_with_yield_kwargs + mock = Minitest::Mock.new + rs = nil + + File.stub :open, true, mock, kw: 42 do + File.open "foo.txt", "r" do |f, kw:| + rs = kw + end + end + + @tc.assert_equal 42, rs + end + + ## Permutation Sets: + + # [:value, :lambda] + # [:*, :block, :block_call] + # [:**, :block_args] + # + # Where: + # + # :value = a normal value + # :lambda = callable or lambda + # :* = no block + # :block = normal block + # :block_call = :lambda invokes the block (N/A for :value) + # :** = no args + # :args = args passed to stub + + ## Permutations + + # [:call, :*, :**] =>5 callable+block FIX: CALL BOTH (bug) + # [:call, :*, :**] =>6 callable + + # [:lambda, :*, :**] => lambda result + + # [:lambda, :*, :args] => lambda result NO ARGS + + # [:lambda, :block, :**] =>5 lambda result FIX: CALL BOTH (bug) + # [:lambda, :block, :**] =>6 lambda result + + # [:lambda, :block, :args] =>5 lambda result FIX: CALL BOTH (bug) + # [:lambda, :block, :args] =>6 lambda result + # [:lambda, :block, :args] =>7 raise ArgumentError + + # [:lambda, :block_call, :**] =>5 lambda FIX: BUG!-not passed block to lambda + # [:lambda, :block_call, :**] =>6 lambda+block result + + # [:lambda, :block_call, :args] =>5 lambda FIX: BUG!-not passed block to lambda + # [:lambda, :block_call, :args] =>6 lambda+block result + + # [:value, :*, :**] => value + + # [:value, :*, :args] => value, ignore args + + # [:value, :block, :**] =>5 value, call block + # [:value, :block, :**] =>6 value + + # [:value, :block, :args] =>5 value, call block w/ args + # [:value, :block, :args] =>6 value, call block w/ args, deprecated + # [:value, :block, :args] =>7 raise ArgumentError + + # [:value, :block_call, :**] => N/A + + # [:value, :block_call, :args] => N/A + + class Bar + def call &_ # to ignore unused block + puts "hi" + end + end + + class Foo + def self.blocking + yield + end + end + + class Thingy + def self.identity arg + arg + end + end + + class Keywords + def self.args req, kw1:, kw2: 24 + [req, kw1, kw2] + end + end + + def test_stub_callable_keyword_args + Keywords.stub :args, ->(*args, **kws) { [args, kws] } do + @tc.assert_equal [["woot"], { kw1: 42 }], Keywords.args("woot", kw1: 42) + end + end + + def test_stub__hash_as_last_real_arg + with_kwargs_env do + token = Object.new + def token.create_with_retry _u, _p; raise "shouldn't see this"; end + + controller = Object.new + controller.define_singleton_method :create do |u, p| + token.create_with_retry u, p + end + + params = Object.new + def params.to_hash; raise "nah"; end + + token.stub(:create_with_retry, ->(u, p) { 42 }) do + act = controller.create :u, params + @tc.assert_equal 42, act + end + end + end + + def test_stub_callable_block_5 # from tenderlove + @assertion_count += 1 + Foo.stub5 :blocking, Bar.new do + @tc.assert_output "hi\n", "" do + Foo.blocking do + @tc.flunk "shouldn't ever hit this" + end + end + end + end + + def test_stub_callable_block_6 # from tenderlove + skip_stub6 + + @assertion_count += 1 + Foo.stub6 :blocking, Bar.new do + @tc.assert_output "hi\n", "" do + Foo.blocking do + @tc.flunk "shouldn't ever hit this" + end + end + end + end + + def test_stub_lambda + Thread.stub :new, lambda { 21+21 } do + @tc.assert_equal 42, Thread.new + end + end + + def test_stub_lambda_args + Thread.stub :new, lambda { 21+21 }, :wtf do + @tc.assert_equal 42, Thread.new + end + end + + def test_stub_lambda_block_5 + Thread.stub5 :new, lambda { 21+21 } do + result = Thread.new do + @tc.flunk "shouldn't ever hit this" + end + @tc.assert_equal 42, result + end + end + + def test_stub_lambda_block_6 + skip_stub6 + + Thread.stub6 :new, lambda { 21+21 } do + result = Thread.new do + @tc.flunk "shouldn't ever hit this" + end + @tc.assert_equal 42, result + end + end + + def test_stub_lambda_block_args_5 + @assertion_count += 1 + Thingy.stub5 :identity, lambda { |y| @tc.assert_equal :nope, y; 21+21 }, :WTF? do + result = Thingy.identity :nope do |x| + @tc.flunk "shouldn't reach this" + end + @tc.assert_equal 42, result + end + end + + def test_stub_lambda_block_args_6 + skip_stub6 + + @assertion_count += 1 + Thingy.stub6 :identity, lambda { |y| @tc.assert_equal :nope, y; 21+21 }, :WTF? do + result = Thingy.identity :nope do |x| + @tc.flunk "shouldn't reach this" + end + @tc.assert_equal 42, result + end + end + + def test_stub_lambda_block_args_6_2 + skip_stub6 + + @tc.assert_raises ArgumentError do + Thingy.stub6_2 :identity, lambda { |y| :__not_run__ }, :WTF? do + # doesn't matter + end + end + end + + def test_stub_lambda_block_call_5 + @assertion_count += 1 + rs = nil + io = StringIO.new(+"", "w") + File.stub5 :open, lambda { |p, m, &blk| blk and blk.call io } do + File.open "foo.txt", "r" do |f| + rs = f && f.write("woot") + end + end + @tc.assert_equal 4, rs + @tc.assert_equal "woot", io.string + end + + def test_stub_lambda_block_call_6 + skip_stub6 + + @assertion_count += 1 + rs = nil + io = StringIO.new(+"", "w") + File.stub6 :open, lambda { |p, m, &blk| blk.call io } do + File.open "foo.txt", "r" do |f| + rs = f.write "woot" + end + end + @tc.assert_equal 4, rs + @tc.assert_equal "woot", io.string + end + + def test_stub_lambda_block_call_args_5 + @assertion_count += 1 + rs = nil + io = StringIO.new(+"", "w") + File.stub5(:open, lambda { |p, m, &blk| blk and blk.call io }, :WTF?) do + File.open "foo.txt", "r" do |f| + rs = f.write "woot" + end + end + @tc.assert_equal 4, rs + @tc.assert_equal "woot", io.string + end + + def test_stub_lambda_block_call_args_6 + skip_stub6 + + @assertion_count += 1 + rs = nil + io = StringIO.new(+"", "w") + File.stub6(:open, lambda { |p, m, &blk| blk.call io }, :WTF?) do + File.open "foo.txt", "r" do |f| + rs = f.write "woot" + end + end + @tc.assert_equal 4, rs + @tc.assert_equal "woot", io.string + end + + def test_stub_lambda_block_call_args_6_2 + skip_stub6 + + @assertion_count += 2 + rs = nil + io = StringIO.new(+"", "w") + @tc.assert_raises ArgumentError do + File.stub6_2(:open, lambda { |p, m, &blk| blk.call io }, :WTF?) do + File.open "foo.txt", "r" do |f| + rs = f.write "woot" + end + end + end + @tc.assert_nil rs + @tc.assert_equal "", io.string + end + + def test_stub_value + Thread.stub :new, 42 do + result = Thread.new + @tc.assert_equal 42, result + end + end + + def test_stub_value_args + Thread.stub :new, 42, :WTF? do + result = Thread.new + @tc.assert_equal 42, result + end + end + + def test_stub_value_block_5 + @assertion_count += 1 + Thread.stub5 :new, 42 do + result = Thread.new do + @tc.assert true + end + @tc.assert_equal 42, result + end + end + + def test_stub_value_block_6 + skip_stub6 + + Thread.stub6 :new, 42 do + result = Thread.new do + @tc.flunk "shouldn't hit this" + end + @tc.assert_equal 42, result + end + end + + def test_stub_value_block_args_5 + @assertion_count += 2 + rs = nil + io = StringIO.new(+"", "w") + File.stub5 :open, :value, io do + result = File.open "foo.txt", "r" do |f| + rs = f.write "woot" + end + @tc.assert_equal :value, result + end + @tc.assert_equal 4, rs + @tc.assert_equal "woot", io.string + end + + def test_stub_value_block_args_5__break_if_not_passed + e = @tc.assert_raises NoMethodError do + File.stub5 :open, :return_value do # intentionally bad setup w/ no args + File.open "foo.txt", "r" do |f| + f.write "woot" + end + end + end + exp = /undefined method [`']write' for nil/ + assert_match exp, e.message + end + + def test_stub_value_block_args_6 + skip_stub6 + + @assertion_count += 2 + rs = nil + io = StringIO.new(+"", "w") + assert_deprecated do + File.stub6 :open, :value, io do + result = File.open "foo.txt", "r" do |f| + rs = f.write "woot" + end + @tc.assert_equal :value, result + end + end + @tc.assert_equal 4, rs + @tc.assert_equal "woot", io.string + end + + def test_stub_value_block_args_6_2 + skip_stub6 + + @assertion_count += 2 + rs = nil + io = StringIO.new(+"", "w") + @tc.assert_raises ArgumentError do + File.stub6_2 :open, :value, io do + result = File.open "foo.txt", "r" do |f| + @tc.flunk "shouldn't hit this" + end + @tc.assert_equal :value, result + end + end + @tc.assert_nil rs + @tc.assert_equal "", io.string + end + + def assert_deprecated re = /deprecated/ + assert_output "", re do + yield + end + end + + def skip_stub6 + skip "not yet" unless STUB6 + end +end + +STUB6 = ENV["STUB6"] + +if STUB6 then + require "minitest/mock6" if STUB6 +else + class Object + alias stub5 stub + alias stub6 stub + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_reporter.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_reporter.rb new file mode 100644 index 00000000..63c0683d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_reporter.rb @@ -0,0 +1,436 @@ +require "minitest/autorun" +require "minitest/metametameta" +require "forwardable" + +class FakeTest < Minitest::Test + def woot + assert true + end +end + +class TestMinitestReporter < MetaMetaMetaTestCase + + attr_accessor :r, :io + + def new_composite_reporter + reporter = Minitest::CompositeReporter.new + reporter << Minitest::SummaryReporter.new(self.io) + reporter << Minitest::ProgressReporter.new(self.io) + + # eg reporter.results -> reporters.first.results + reporter.extend Forwardable + reporter.delegate :first => :reporters + reporter.delegate %i[results count assertions options to_s] => :first + + reporter + end + + def setup + super + self.io = StringIO.new(+"") + self.r = new_composite_reporter + end + + def error_test + unless defined? @et then + @et = FakeTest.new :woot + @et.failures << Minitest::UnexpectedError.new(begin + raise "no" + rescue => e + e + end) + @et = Minitest::Result.from @et + end + @et + end + + def system_stack_error_test + unless defined? @sse then + + ex = SystemStackError.new + + pre = ("a".."c").to_a + mid = ("aa".."ad").to_a * 67 + post = ("d".."f").to_a + ary = pre + mid + post + + ex.set_backtrace ary + + @sse = FakeTest.new :woot + @sse.failures << Minitest::UnexpectedError.new(ex) + @sse = Minitest::Result.from @sse + end + @sse + end + + def fail_test + unless defined? @ft then + @ft = FakeTest.new :woot + @ft.failures << begin + raise Minitest::Assertion, "boo" + rescue Minitest::Assertion => e + e + end + @ft = Minitest::Result.from @ft + end + @ft + end + + def passing_test + @pt ||= Minitest::Result.from FakeTest.new(:woot) + end + + def passing_test_with_metadata + test = FakeTest.new :woot + test.metadata[:meta] = :data + @pt ||= Minitest::Result.from test + end + + def skip_test + unless defined? @st then + @st = FakeTest.new :woot + @st.failures << begin + raise Minitest::Skip + rescue Minitest::Assertion => e + e + end + @st = Minitest::Result.from @st + end + @st + end + + def test_to_s + r.record passing_test + r.record fail_test + assert_match "woot", r.to_s + end + + def test_options_skip_F + r.options[:skip] = "F" + + r.record passing_test + r.record fail_test + + refute_match "woot", r.to_s + end + + def test_options_skip_E + r.options[:skip] = "E" + + r.record passing_test + r.record error_test + + refute_match "RuntimeError: no", r.to_s + end + + def test_passed_eh_empty + assert_predicate r, :passed? + end + + def test_passed_eh_failure + r.results << fail_test + + refute_predicate r, :passed? + end + + SKIP_MSG = "\n\nYou have skipped tests. Run with --verbose for details." + + def test_passed_eh_error + r.start + + r.results << error_test + + refute_predicate r, :passed? + + r.report + + refute_match SKIP_MSG, io.string + end + + def test_passed_eh_skipped + r.start + r.results << skip_test + assert r.passed? + + restore_env do + r.report + end + + assert_match SKIP_MSG, io.string + end + + def test_passed_eh_skipped_verbose + r.options[:verbose] = true + + r.start + r.results << skip_test + assert r.passed? + r.report + + refute_match SKIP_MSG, io.string + end + + def test_start + r.start + + exp = "Run options: \n\n# Running:\n\n" + + assert_equal exp, io.string + end + + def test_record_pass + r.record passing_test + + assert_equal ".", io.string + assert_empty r.results + assert_equal 1, r.count + assert_equal 0, r.assertions + end + + def test_record_pass_with_metadata + reporter = self.r + + def reporter.metadata + @metadata + end + + def reporter.record result + super + @metadata = result.metadata if result.metadata? + end + + r.record passing_test_with_metadata + + exp = { :meta => :data } + assert_equal exp, reporter.metadata + + assert_equal ".", io.string + assert_empty r.results + assert_equal 1, r.count + assert_equal 0, r.assertions + end + + def test_record_fail + fail_test = self.fail_test + r.record fail_test + + assert_equal "F", io.string + assert_equal [fail_test], r.results + assert_equal 1, r.count + assert_equal 0, r.assertions + end + + def test_record_error + error_test = self.error_test + r.record error_test + + assert_equal "E", io.string + assert_equal [error_test], r.results + assert_equal 1, r.count + assert_equal 0, r.assertions + end + + def test_record_skip + skip_test = self.skip_test + r.record skip_test + + assert_equal "S", io.string + assert_equal [skip_test], r.results + assert_equal 1, r.count + assert_equal 0, r.assertions + end + + def test_report_empty + r.start + r.report + + exp = <<~EOM + Run options: + + # Running: + + + + Finished in 0.00 + + 0 runs, 0 assertions, 0 failures, 0 errors, 0 skips + EOM + + assert_equal exp, normalize_output(io.string) + end + + def test_report_passing + r.start + r.record passing_test + r.report + + exp = <<~EOM + Run options: + + # Running: + + . + + Finished in 0.00 + + 1 runs, 0 assertions, 0 failures, 0 errors, 0 skips + EOM + + assert_equal exp, normalize_output(io.string) + end + + def test_report_failure + r.start + r.record fail_test + r.report + + exp = <<~EOM + Run options: + + # Running: + + F + + Finished in 0.00 + + 1) Failure: + FakeTest#woot [FILE:LINE]: + boo + + 1 runs, 0 assertions, 1 failures, 0 errors, 0 skips + EOM + + assert_equal exp, normalize_output(io.string) + end + + def test_report_error + r.start + r.record error_test + r.report + + exp = <<~EOM + Run options: + + # Running: + + E + + Finished in 0.00 + + 1) Error: + FakeTest#woot: + RuntimeError: no + FILE:LINE:in 'error_test' + FILE:LINE:in 'test_report_error' + + 1 runs, 0 assertions, 0 failures, 1 errors, 0 skips + EOM + + assert_equal exp, normalize_output(io.string) + end + + def test_report_error__sse + r.start + r.record system_stack_error_test + r.report + + exp = <<~EOM + Run options: + + # Running: + + E + + Finished in 0.00 + + 1) Error: + FakeTest#woot: + SystemStackError: 274 -> 12 + a + b + c + +->> 67 cycles of 4 lines: + | aa + | ab + | ac + | ad + +-<< + d + e + f + + 1 runs, 0 assertions, 0 failures, 1 errors, 0 skips + EOM + + assert_equal exp, normalize_output(io.string) + end + + def test_report_skipped + r.start + r.record skip_test + + restore_env do + r.report + end + + exp = <<~EOM + Run options: + + # Running: + + S + + Finished in 0.00 + + 1 runs, 0 assertions, 0 failures, 0 errors, 1 skips + + You have skipped tests. Run with --verbose for details. + EOM + + assert_equal exp, normalize_output(io.string) + end + + def test_report_failure_uses_backtrace_filter + filter = Minitest::BacktraceFilter.new + def filter.filter _bt + ["foo.rb:123:in 'foo'"] + end + + with_backtrace_filter filter do + r.start + r.record fail_test + r.report + end + + exp = "FakeTest#woot [foo.rb:123]" + + assert_includes io.string, exp + end + + def test_report_failure_uses_backtrace_filter_complex_sorbet + backtrace = <<~EOBT + /Users/user/.gem/ruby/3.2.2/gems/minitest-5.20.0/lib/minitest/assertions.rb:183:in 'assert' + example_test.rb:9:in 'assert_false' + /Users/user/.gem/ruby/3.2.2/gems/sorbet-runtime-0.5.11068/lib/types/private/methods/call_validation.rb:256:in 'bind_call' + /Users/user/.gem/ruby/3.2.2/gems/sorbet-runtime-0.5.11068/lib/types/private/methods/call_validation.rb:256:in 'validate_call' + /Users/user/.gem/ruby/3.2.2/gems/sorbet-runtime-0.5.11068/lib/types/private/methods/_methods.rb:275:in 'block in _on_method_added' + example_test.rb:25:in 'test_something' + /Users/user/.gem/ruby/3.2.2/gems/minitest-5.20.0/lib/minitest/test.rb:94:in 'block (3 levels) in run' + /Users/user/.gem/ruby/3.2.2/gems/minitest-5.20.0/lib/minitest/test.rb:191:in 'capture_exceptions' + /Users/user/.gem/ruby/3.2.2/gems/minitest-5.20.0/lib/minitest/test.rb:89:in 'block (2 levels) in run' + ... so many lines ... + EOBT + + filter = Minitest::BacktraceFilter.new %r%lib/minitest|gems/sorbet% + + with_backtrace_filter filter do + begin + assert_equal 1, 2 + rescue Minitest::Assertion => e + e.set_backtrace backtrace.lines.map(&:chomp) + + assert_match "example_test.rb:25", e.location + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_spec.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_spec.rb new file mode 100644 index 00000000..6286685d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_spec.rb @@ -0,0 +1,1163 @@ +require "minitest/metametameta" +require "stringio" + +class MiniSpecA < Minitest::Spec; end +class MiniSpecB < Minitest::Test; extend Minitest::Spec::DSL; end +class MiniSpecC < MiniSpecB; end +class NamedExampleA < MiniSpecA; end +class NamedExampleB < MiniSpecB; end +class NamedExampleC < MiniSpecC; end +class ExampleA; end +class ExampleB < ExampleA; end + +describe Minitest::Spec do + # do not parallelize this suite... it just can"t handle it. + + def assert_triggered expected = "blah", klass = Minitest::Assertion + @assertion_count += 1 + + e = assert_raises klass do + yield + end + + msg = e.message.sub(/(---Backtrace---).*/m, '\1') + msg.gsub!(/\(oid=[-0-9]+\)/, "(oid=N)") + msg.gsub!(/(\d\.\d{6})\d+/, '\1xxx') # normalize: ruby version, impl, platform + msg.gsub!(/:0x[Xa-fA-F0-9]{4,}[ @].+?>/, ":0xXXXXXX@PATH>") + + return unless expected + + @assertion_count += 1 + case expected + when String then + assert_equal expected, msg + when Regexp then + @assertion_count += 1 + assert_match expected, msg + else + flunk "Unknown: #{expected.inspect}" + end + end + + def assert_success spec + assert_equal true, spec + end + + before do + @assertion_count = 4 + end + + after do + _(self.assertions).must_equal @assertion_count if passed? and not skipped? + end + + it "needs to be able to catch a Minitest::Assertion exception" do + @assertion_count = 1 + + assert_triggered "Expected 1 to not be equal to 1." do + _(1).wont_equal 1 + end + end + + it "needs to check for file existence" do + @assertion_count = 3 + + assert_success _(__FILE__).path_must_exist + + assert_triggered "Expected path 'blah' to exist." do + _("blah").path_must_exist + end + end + + it "needs to check for file non-existence" do + @assertion_count = 3 + + assert_success _("blah").path_wont_exist + + assert_triggered "Expected path '#{__FILE__}' to not exist." do + _(__FILE__).path_wont_exist + end + end + + it "needs to be sensible about must_include order" do + @assertion_count += 3 # must_include is 2 assertions + + assert_success _([1, 2, 3]).must_include(2) + + assert_triggered "Expected [1, 2, 3] to include 5." do + _([1, 2, 3]).must_include 5 + end + + assert_triggered "msg.\nExpected [1, 2, 3] to include 5." do + _([1, 2, 3]).must_include 5, "msg" + end + end + + it "needs to be sensible about wont_include order" do + @assertion_count += 3 # wont_include is 2 assertions + + assert_success _([1, 2, 3]).wont_include(5) + + assert_triggered "Expected [1, 2, 3] to not include 2." do + _([1, 2, 3]).wont_include 2 + end + + assert_triggered "msg.\nExpected [1, 2, 3] to not include 2." do + _([1, 2, 3]).wont_include 2, "msg" + end + end + + it "needs to catch an expected exception" do + @assertion_count = 2 + + expect { raise "blah" }.must_raise RuntimeError + expect { raise Minitest::Assertion }.must_raise Minitest::Assertion + end + + it "needs to catch an unexpected exception" do + @assertion_count -= 2 # no positive + + msg = <<-EOM.gsub(/^ {6}/, "").chomp + [RuntimeError] exception expected, not + Class: + Message: <"woot"> + ---Backtrace--- + EOM + + assert_triggered msg do + expect { raise StandardError, "woot" }.must_raise RuntimeError + end + + assert_triggered "msg.\n#{msg}" do + expect { raise StandardError, "woot" }.must_raise RuntimeError, "msg" + end + end + + def good_pattern + capture_io do # 3.0 is noisy + eval "[1,2,3] => [Integer, Integer, Integer]" # eval to escape parser for ruby<3 + end + end + + def bad_pattern + capture_io do # 3.0 is noisy + eval "[1,2,3] => [Integer, Integer]" # eval to escape parser for ruby<3 + end + end + + it "needs to pattern match" do + @assertion_count = 1 + + if RUBY_VERSION > "3" then + expect { good_pattern }.must_pattern_match + else + assert_raises NotImplementedError do + expect {}.must_pattern_match + end + end + end + + it "needs to error on bad pattern match" do + skip unless RUBY_VERSION > "3" + + @assertion_count = 1 + + exp = if RUBY_VERSION.start_with? "3.0" + "[1, 2, 3]" # terrible error message! + else + /length mismatch/ + end + + assert_triggered exp do + expect { bad_pattern }.must_pattern_match + end + end + + it "needs to ensure silence" do + @assertion_count -= 1 # no msg + @assertion_count += 2 # assert_output is 2 assertions + + assert_success expect {}.must_be_silent + + assert_triggered "In stdout.\nExpected: \"\"\n Actual: \"xxx\"" do + expect { print "xxx" }.must_be_silent + end + end + + it "needs to have all methods named well" do + skip "N/A" if ENV["MT_NO_EXPECTATIONS"] + + @assertion_count = 2 + + methods = Minitest::Expectations.public_instance_methods.grep(/must|wont/) + methods.map!(&:to_s) if Symbol === methods.first + + musts, wonts = methods.sort.partition { |m| m.include? "must" } + + expected_musts = %w[must_be + must_be_close_to + must_be_empty + must_be_instance_of + must_be_kind_of + must_be_nil + must_be_same_as + must_be_silent + must_be_within_delta + must_be_within_epsilon + must_equal + must_include + must_match + must_output + must_pattern_match + must_raise + must_respond_to + must_throw + must_verify + path_must_exist] + + bad = %w[not raise throw send output be_silent verify] + + expected_wonts = expected_musts.map { |m| m.sub("must", "wont") }.sort + expected_wonts.reject! { |m| m =~ /wont_#{Regexp.union(*bad)}/ } + + _(musts).must_equal expected_musts + _(wonts).must_equal expected_wonts + end + + it "needs to raise if an expected exception is not raised" do + @assertion_count -= 2 # no positive test + + assert_triggered "RuntimeError expected but nothing was raised." do + expect { 42 }.must_raise RuntimeError + end + + assert_triggered "msg.\nRuntimeError expected but nothing was raised." do + expect { 42 }.must_raise RuntimeError, "msg" + end + end + + it "needs to verify binary messages" do + assert_success _(42).wont_be(:<, 24) + + assert_triggered "Expected 24 to not be < 42." do + _(24).wont_be :<, 42 + end + + assert_triggered "msg.\nExpected 24 to not be < 42." do + _(24).wont_be :<, 42, "msg" + end + end + + it "needs to verify emptyness" do + @assertion_count += 3 # empty is 2 assertions + + assert_success _([]).must_be_empty + + assert_triggered "Expected [42] to be empty." do + _([42]).must_be_empty + end + + assert_triggered "msg.\nExpected [42] to be empty." do + _([42]).must_be_empty "msg" + end + end + + it "needs to verify equality" do + @assertion_count += 1 + + assert_success _(6 * 7).must_equal(42) + + assert_triggered "Expected: 42\n Actual: 54" do + _(6 * 9).must_equal 42 + end + + assert_triggered "msg.\nExpected: 42\n Actual: 54" do + _(6 * 9).must_equal 42, "msg" + end + + assert_triggered(/^-42\n\+#\n/) do + _(proc { 42 }).must_equal 42 # proc isn't called, so expectation fails + end + end + + it "needs to warn on equality with nil" do + @assertion_count = 3 + @assertion_count += 2 unless error_on_warn? # 2 extra assertions + + exp = /DEPRECATED: Use assert_nil if expecting nil from .* This will fail in Minitest 6./ + + assert_deprecation exp do + assert_success _(nil).must_equal(nil) + end + end + + it "needs to verify floats outside a delta" do + @assertion_count += 1 # extra test + + assert_success _(24).wont_be_close_to(42) + + assert_triggered "Expected |42 - 42.0| (0.0) to not be <= 0.001." do + _(6 * 7.0).wont_be_close_to 42 + end + + x = "1.0e-05" + assert_triggered "Expected |42 - 42.0| (0.0) to not be <= #{x}." do + _(6 * 7.0).wont_be_close_to 42, 0.00001 + end + + assert_triggered "msg.\nExpected |42 - 42.0| (0.0) to not be <= #{x}." do + _(6 * 7.0).wont_be_close_to 42, 0.00001, "msg" + end + end + + it "needs to verify floats outside an epsilon" do + @assertion_count += 1 # extra test + + assert_success _(24).wont_be_within_epsilon(42) + + x = "0.042" + assert_triggered "Expected |42 - 42.0| (0.0) to not be <= #{x}." do + _(6 * 7.0).wont_be_within_epsilon 42 + end + + x = "0.00042" + assert_triggered "Expected |42 - 42.0| (0.0) to not be <= #{x}." do + _(6 * 7.0).wont_be_within_epsilon 42, 0.00001 + end + + assert_triggered "msg.\nExpected |42 - 42.0| (0.0) to not be <= #{x}." do + _(6 * 7.0).wont_be_within_epsilon 42, 0.00001, "msg" + end + end + + it "needs to verify floats within a delta" do + @assertion_count += 1 # extra test + + assert_success _(6.0 * 7).must_be_close_to(42.0) + + assert_triggered "Expected |0.0 - 0.01| (0.01) to be <= 0.001." do + _(1.0 / 100).must_be_close_to 0.0 + end + + x = "1.0e-06" + assert_triggered "Expected |0.0 - 0.001| (0.001) to be <= #{x}." do + _(1.0 / 1000).must_be_close_to 0.0, 0.000001 + end + + assert_triggered "msg.\nExpected |0.0 - 0.001| (0.001) to be <= #{x}." do + _(1.0 / 1000).must_be_close_to 0.0, 0.000001, "msg" + end + end + + it "needs to verify floats within an epsilon" do + @assertion_count += 1 # extra test + + assert_success _(6.0 * 7).must_be_within_epsilon(42.0) + + assert_triggered "Expected |0.0 - 0.01| (0.01) to be <= 0.0." do + _(1.0 / 100).must_be_within_epsilon 0.0 + end + + assert_triggered "Expected |0.0 - 0.001| (0.001) to be <= 0.0." do + _(1.0 / 1000).must_be_within_epsilon 0.0, 0.000001 + end + + assert_triggered "msg.\nExpected |0.0 - 0.001| (0.001) to be <= 0.0." do + _(1.0 / 1000).must_be_within_epsilon 0.0, 0.000001, "msg" + end + end + + it "needs to verify identity" do + assert_success _(1).must_be_same_as(1) + + assert_triggered "Expected 1 (oid=N) to be the same as 2 (oid=N)." do + _(1).must_be_same_as 2 + end + + assert_triggered "msg.\nExpected 1 (oid=N) to be the same as 2 (oid=N)." do + _(1).must_be_same_as 2, "msg" + end + end + + it "needs to verify inequality" do + @assertion_count += 2 + assert_success _(42).wont_equal(6 * 9) + assert_success _(proc {}).wont_equal(42) + + assert_triggered "Expected 1 to not be equal to 1." do + _(1).wont_equal 1 + end + + assert_triggered "msg.\nExpected 1 to not be equal to 1." do + _(1).wont_equal 1, "msg" + end + end + + it "needs to verify instances of a class" do + assert_success _(42).wont_be_instance_of(String) + + assert_triggered "Expected 42 to not be a kind of Integer." do + _(42).wont_be_kind_of Integer + end + + assert_triggered "msg.\nExpected 42 to not be an instance of Integer." do + _(42).wont_be_instance_of Integer, "msg" + end + end + + it "needs to verify kinds of a class" do + @assertion_count += 2 + + assert_success _(42).wont_be_kind_of(String) + assert_success _(proc {}).wont_be_kind_of(String) + + assert_triggered "Expected 42 to not be a kind of Integer." do + _(42).wont_be_kind_of Integer + end + + assert_triggered "msg.\nExpected 42 to not be a kind of Integer." do + _(42).wont_be_kind_of Integer, "msg" + end + end + + it "needs to verify kinds of objects" do + @assertion_count += 3 # extra test + + assert_success _(6 * 7).must_be_kind_of(Integer) + assert_success _(6 * 7).must_be_kind_of(Numeric) + + assert_triggered "Expected 42 to be a kind of String, not Integer." do + _(6 * 7).must_be_kind_of String + end + + assert_triggered "msg.\nExpected 42 to be a kind of String, not Integer." do + _(6 * 7).must_be_kind_of String, "msg" + end + + exp = "Expected # to be a kind of String, not Proc." + assert_triggered exp do + _(proc {}).must_be_kind_of String + end + end + + it "needs to verify mismatch" do + @assertion_count += 3 # match is 2 + + assert_success _("blah").wont_match(/\d+/) + + assert_triggered "Expected /\\w+/ to not match \"blah\"." do + _("blah").wont_match(/\w+/) + end + + assert_triggered "msg.\nExpected /\\w+/ to not match \"blah\"." do + _("blah").wont_match(/\w+/, "msg") + end + end + + it "needs to verify nil" do + assert_success _(nil).must_be_nil + + assert_triggered "Expected 42 to be nil." do + _(42).must_be_nil + end + + assert_triggered "msg.\nExpected 42 to be nil." do + _(42).must_be_nil "msg" + end + end + + it "needs to verify non-emptyness" do + @assertion_count += 3 # empty is 2 assertions + + assert_success _(["some item"]).wont_be_empty + + assert_triggered "Expected [] to not be empty." do + _([]).wont_be_empty + end + + assert_triggered "msg.\nExpected [] to not be empty." do + _([]).wont_be_empty "msg" + end + end + + it "needs to verify non-identity" do + assert_success _(1).wont_be_same_as(2) + + assert_triggered "Expected 1 (oid=N) to not be the same as 1 (oid=N)." do + _(1).wont_be_same_as 1 + end + + assert_triggered "msg.\nExpected 1 (oid=N) to not be the same as 1 (oid=N)." do + _(1).wont_be_same_as 1, "msg" + end + end + + it "needs to verify non-nil" do + assert_success _(42).wont_be_nil + + assert_triggered "Expected nil to not be nil." do + _(nil).wont_be_nil + end + + assert_triggered "msg.\nExpected nil to not be nil." do + _(nil).wont_be_nil "msg" + end + end + + it "needs to verify objects not responding to a message" do + assert_success _("").wont_respond_to(:woot!) + + assert_triggered "Expected \"\" to not respond to to_s." do + _("").wont_respond_to :to_s + end + + assert_triggered "msg.\nExpected \"\" to not respond to to_s." do + _("").wont_respond_to :to_s, "msg" + end + end + + it "needs to verify output in stderr" do + @assertion_count -= 1 # no msg + + assert_success expect { $stderr.print "blah" }.must_output(nil, "blah") + + assert_triggered "In stderr.\nExpected: \"blah\"\n Actual: \"xxx\"" do + expect { $stderr.print "xxx" }.must_output(nil, "blah") + end + end + + it "needs to verify output in stdout" do + @assertion_count -= 1 # no msg + + assert_success expect { print "blah" }.must_output("blah") + + assert_triggered "In stdout.\nExpected: \"blah\"\n Actual: \"xxx\"" do + expect { print "xxx" }.must_output("blah") + end + end + + it "needs to verify regexp matches" do + @assertion_count += 3 # must_match is 2 assertions + + assert_kind_of MatchData, _("blah").must_match(/\w+/) + + assert_triggered "Expected /\\d+/ to match \"blah\"." do + _("blah").must_match(/\d+/) + end + + assert_triggered "msg.\nExpected /\\d+/ to match \"blah\"." do + _("blah").must_match(/\d+/, "msg") + end + end + + describe "expect" do + before do + @assertion_count -= 3 + end + + it "can use expect" do + _(1 + 1).must_equal 2 + end + + it "can use expect with a lambda" do + _ { raise "blah" }.must_raise RuntimeError + end + + it "can use expect in a thread" do + Thread.new { _(1 + 1).must_equal 2 }.join + end + + it "can NOT use must_equal in a thread. It must use expect in a thread" do + skip "N/A" if ENV["MT_NO_EXPECTATIONS"] + + assert_raises RuntimeError, Minitest::UnexpectedWarning do + capture_io do + Thread.new { (1 + 1).must_equal 2 }.join + end + end + end + + it "fails gracefully when expectation used outside of `it`" do + skip "N/A" if ENV["MT_NO_EXPECTATIONS"] + + @assertion_count += 2 # assert_match is compound + + e = assert_raises RuntimeError, Minitest::UnexpectedWarning do + capture_io do + Thread.new { # forces ctx to be nil + describe "woot" do + (1 + 1).must_equal 2 + end + }.join + end + end + + exp = "Calling #must_equal outside of test." + exp = "DEPRECATED: global use of must_equal from" if error_on_warn? + + assert_match exp, e.message + end + + it "deprecates expectation used without _" do + skip "N/A" if ENV["MT_NO_EXPECTATIONS"] + + @assertion_count += 1 + @assertion_count += 2 unless error_on_warn? + + exp = /DEPRECATED: global use of must_equal from/ + + assert_deprecation exp do + (1 + 1).must_equal 2 + end + end + + # https://github.com/seattlerb/minitest/issues/837 + # https://github.com/rails/rails/pull/39304 + it "deprecates expectation used without _ with empty backtrace_filter" do + skip "N/A" if ENV["MT_NO_EXPECTATIONS"] + + @assertion_count += 1 + @assertion_count += 2 unless error_on_warn? + + exp = /DEPRECATED: global use of must_equal from/ + + with_empty_backtrace_filter do + assert_deprecation exp do + (1 + 1).must_equal 2 + end + end + end + end + + it "needs to verify throw" do + @assertion_count += 4 # 2 extra tests + + assert_nil expect { throw :blah }.must_throw(:blah) + assert_equal 42, expect { throw :blah, 42 }.must_throw(:blah) + + assert_triggered "Expected :blah to have been thrown." do + expect {}.must_throw :blah + end + + assert_triggered "Expected :blah to have been thrown, not :xxx." do + expect { throw :xxx }.must_throw :blah + end + + assert_triggered "msg.\nExpected :blah to have been thrown." do + expect {}.must_throw :blah, "msg" + end + + assert_triggered "msg.\nExpected :blah to have been thrown, not :xxx." do + expect { throw :xxx }.must_throw :blah, "msg" + end + end + + it "needs to verify types of objects" do + assert_success _(6 * 7).must_be_instance_of(Integer) + + exp = "Expected 42 to be an instance of String, not Integer." + + assert_triggered exp do + _(6 * 7).must_be_instance_of String + end + + assert_triggered "msg.\n#{exp}" do + _(6 * 7).must_be_instance_of String, "msg" + end + end + + it "needs to verify using any (negative) predicate" do + @assertion_count -= 1 # doesn"t take a message + + assert_success _("blah").wont_be(:empty?) + + assert_triggered "Expected \"\" to not be empty?." do + _("").wont_be :empty? + end + end + + it "needs to verify using any binary operator" do + @assertion_count -= 1 # no msg + + assert_success _(41).must_be(:<, 42) + + assert_triggered "Expected 42 to be < 41." do + _(42).must_be :<, 41 + end + end + + it "needs to verify using any predicate" do + @assertion_count -= 1 # no msg + + assert_success _("").must_be(:empty?) + + assert_triggered "Expected \"blah\" to be empty?." do + _("blah").must_be :empty? + end + end + + it "needs to verify using respond_to" do + assert_success _(42).must_respond_to(:+) + + assert_triggered "Expected 42 (Integer) to respond to #clear." do + _(42).must_respond_to :clear + end + + assert_triggered "msg.\nExpected 42 (Integer) to respond to #clear." do + _(42).must_respond_to :clear, "msg" + end + end +end + +describe Minitest::Spec, :let do + i_suck_and_my_tests_are_order_dependent! + + def _count + $let_count ||= 0 + end + + let :count do + $let_count += 1 + $let_count + end + + it "is evaluated once per example" do + _(_count).must_equal 0 + + _(count).must_equal 1 + _(count).must_equal 1 + + _(_count).must_equal 1 + end + + it "is REALLY evaluated once per example" do + _(_count).must_equal 1 + + _(count).must_equal 2 + _(count).must_equal 2 + + _(_count).must_equal 2 + end + + it 'raises an error if the name begins with "test"' do + expect { self.class.let(:test_value) { true } }.must_raise ArgumentError + end + + it "raises an error if the name shadows a normal instance method" do + expect { self.class.let(:message) { true } }.must_raise ArgumentError + end + + it "doesn't raise an error if it is just another let" do + v = proc do + describe :outer do + let :bar + describe :inner do + let :bar + end + end + :good + end.call + _(v).must_equal :good + end + + it "procs come after dont_flip" do + p = proc {} + assert_respond_to p, :call + _(p).must_respond_to :call + end +end + +describe Minitest::Spec, :subject do + attr_reader :subject_evaluation_count + + subject do + @subject_evaluation_count ||= 0 + @subject_evaluation_count += 1 + @subject_evaluation_count + end + + it "is evaluated once per example" do + _(subject).must_equal 1 + _(subject).must_equal 1 + _(subject_evaluation_count).must_equal 1 + end +end + +class TestMetaStatic < Minitest::Test + def assert_method_count expected, klass + assert_equal expected, klass.public_instance_methods.grep(/^test_/).count + end + + def test_children + Minitest::Spec.children.clear # prevents parallel run + + y = z = nil + x = describe "top-level thingy" do + y = describe "first thingy" do end + + it "top-level-it" do end + + z = describe "second thingy" do end + end + + assert_equal [x], Minitest::Spec.children + assert_equal [y, z], x.children + assert_equal [], y.children + assert_equal [], z.children + end + + def test_it_wont_remove_existing_child_test_methods + Minitest::Spec.children.clear # prevents parallel run + + inner = nil + outer = describe "outer" do + inner = describe "inner" do + it do + assert true + end + end + it do + assert true + end + end + + assert_method_count 1, outer + assert_method_count 1, inner + end + + def test_it_wont_add_test_methods_to_children + Minitest::Spec.children.clear # prevents parallel run + + inner = nil + outer = describe "outer" do + inner = describe "inner" do end + it do + assert true + end + end + + assert_method_count 1, outer + assert_method_count 0, inner + end +end + +class TestMeta < MetaMetaMetaTestCase + # do not call parallelize_me! here because specs use register_spec_type globally + + def assert_defined_methods expected, klass + assert_equal expected, klass.instance_methods(false).sort.map(&:to_s) + end + + def util_structure + y = z = nil + before_list = [] + after_list = [] + x = describe "top-level thingy" do + before { before_list << 1 } + after { after_list << 1 } + + it "top-level-it" do end + + y = describe "inner thingy" do + before { before_list << 2 } + after { after_list << 2 } + it "inner-it" do end + + z = describe "very inner thingy" do + before { before_list << 3 } + after { after_list << 3 } + it "inner-it" do end + + it { } # ignore me + specify { } # anonymous it + end + end + end + + return x, y, z, before_list, after_list + end + + def test_register_spec_type + original_types = Minitest::Spec::TYPES.dup + + assert_includes Minitest::Spec::TYPES, [//, Minitest::Spec] + + Minitest::Spec.register_spec_type(/woot/, TestMeta) + + p = lambda do |_| true end + Minitest::Spec.register_spec_type TestMeta, &p + + keys = Minitest::Spec::TYPES.map(&:first) + + assert_includes keys, /woot/ + assert_includes keys, p + ensure + Minitest::Spec::TYPES.replace original_types + end + + def test_spec_type + original_types = Minitest::Spec::TYPES.dup + + Minitest::Spec.register_spec_type(/A$/, MiniSpecA) + Minitest::Spec.register_spec_type MiniSpecB do |desc| + desc.superclass == ExampleA + end + Minitest::Spec.register_spec_type MiniSpecC do |_desc, *addl| + addl.include? :woot + end + + assert_equal MiniSpecA, Minitest::Spec.spec_type(ExampleA) + assert_equal MiniSpecB, Minitest::Spec.spec_type(ExampleB) + assert_equal MiniSpecC, Minitest::Spec.spec_type(ExampleB, :woot) + ensure + Minitest::Spec::TYPES.replace original_types + end + + def test_bug_dsl_expectations + spec_class = Class.new MiniSpecB do + it "should work" do + _(0).must_equal 0 + end + end + + test_name = spec_class.instance_methods.sort.grep(/test_/).first + + spec = spec_class.new test_name + + result = spec.run + + assert spec.passed? + assert result.passed? + assert_equal 1, result.assertions + end + + def test_name + spec_a = describe ExampleA do; end + spec_b = describe ExampleB, :random_method do; end + spec_c = describe ExampleB, :random_method, :addl_context do; end + + assert_equal "ExampleA", spec_a.name + assert_equal "ExampleB::random_method", spec_b.name + assert_equal "ExampleB::random_method::addl_context", spec_c.name + end + + def test_name2 + assert_equal "NamedExampleA", NamedExampleA.name + assert_equal "NamedExampleB", NamedExampleB.name + assert_equal "NamedExampleC", NamedExampleC.name + + spec_a = describe ExampleA do; end + spec_b = describe ExampleB, :random_method do; end + + assert_equal "ExampleA", spec_a.name + assert_equal "ExampleB::random_method", spec_b.name + end + + def test_name_inside_class + spec_a = nil + spec_b = nil + inside_class_example = Class.new Minitest::Spec + Object.const_set :InsideClassExample, inside_class_example + inside_class_example.class_eval do + spec_a = describe "a" do + spec_b = describe "b" do; end + end + end + + assert_equal "InsideClassExample::a", spec_a.name + assert_equal "InsideClassExample::a::b", spec_b.name + ensure + Object.send :remove_const, :InsideClassExample + end + + def test_structure + x, y, z, * = util_structure + + assert_equal "top-level thingy", x.to_s + assert_equal "top-level thingy::inner thingy", y.to_s + assert_equal "top-level thingy::inner thingy::very inner thingy", z.to_s + + assert_equal "top-level thingy", x.desc + assert_equal "inner thingy", y.desc + assert_equal "very inner thingy", z.desc + + top_methods = %w[setup teardown test_0001_top-level-it] + inner_methods1 = %w[setup teardown test_0001_inner-it] + inner_methods2 = inner_methods1 + + %w[test_0002_anonymous test_0003_anonymous] + + assert_defined_methods top_methods, x + assert_defined_methods inner_methods1, y + assert_defined_methods inner_methods2, z + end + + def test_structure_postfix_it + z = nil + y = describe "outer" do + # NOT here, below the inner-describe! + # it "inner-it" do end + + z = describe "inner" do + it "inner-it" do end + end + + # defined AFTER inner describe means we'll try to wipe out the inner-it + it "inner-it" do end + end + + assert_defined_methods %w[test_0001_inner-it], y + assert_defined_methods %w[test_0001_inner-it], z + end + + def test_setup_teardown_behavior + _, _, z, before_list, after_list = util_structure + + @tu = z + + run_tu_with_fresh_reporter + + size = z.runnable_methods.size + assert_equal [1, 2, 3] * size, before_list + assert_equal [3, 2, 1] * size, after_list + end + + def test_describe_first_structure + x1 = x2 = y = z = nil + x = describe "top-level thingy" do + y = describe "first thingy" do end + + x1 = it "top level it" do end + x2 = it "не латинские &いった α, β, γ, δ, ε hello!!! world" do end + + z = describe "second thingy" do end + end + + test_methods = [ + "test_0001_top level it", + "test_0002_не латинские &いった α, β, γ, δ, ε hello!!! world", + ].sort + + assert_equal test_methods, [x1, x2] + assert_defined_methods test_methods, x + assert_defined_methods [], y + assert_defined_methods [], z + end + + def test_structure_subclasses + z = nil + x = Class.new Minitest::Spec do + def xyz; end + end + y = Class.new x do + z = describe("inner") { } + end + + assert_respond_to x.new(nil), "xyz" + assert_respond_to y.new(nil), "xyz" + assert_respond_to z.new(nil), "xyz" + end +end + +class TestSpecInTestCase < MetaMetaMetaTestCase + def setup + super + + Thread.current[:current_spec] = self + @tc = self + @assertion_count = 2 + end + + def assert_triggered expected, klass = Minitest::Assertion + @assertion_count += 1 + + e = assert_raises klass do + yield + end + + msg = e.message.sub(/(---Backtrace---).*/m, "\1") + msg.gsub!(/\(oid=[-0-9]+\)/, "(oid=N)") + + assert_equal expected, msg + end + + def teardown + msg = "expected #{@assertion_count} assertions, not #{@tc.assertions}" + assert_equal @assertion_count, @tc.assertions, msg + end + + def test_expectation + @tc.assert_equal true, _(1).must_equal(1) + end + + def test_expectation_triggered + assert_triggered "Expected: 2\n Actual: 1" do + _(1).must_equal 2 + end + end + + include Minitest::Spec::DSL::InstanceMethods + + def test_expectation_with_a_message + assert_triggered "woot.\nExpected: 2\n Actual: 1" do + _(1).must_equal 2, "woot" + end + end +end + +class ValueMonadTest < Minitest::Test + attr_accessor :struct + + def setup + @struct = { :_ => "a", :value => "b", :expect => "c" } + def @struct.method_missing k # think openstruct + self[k] + end + end + + def test_value_monad_method + assert_equal "a", struct._ + end + + def test_value_monad_value_alias + assert_equal "b", struct.value + end + + def test_value_monad_expect_alias + assert_equal "c", struct.expect + end +end + +describe Minitest::Spec, :infect_an_assertion do + class << self + attr_accessor :infect_mock + end + + def assert_infects exp, act, msg = nil, foo: nil, bar: nil + self.class.infect_mock.assert_infects exp, act, msg, foo: foo, bar: bar + end + + infect_an_assertion :assert_infects, :must_infect + infect_an_assertion :assert_infects, :must_infect_without_flipping, :dont_flip + + it "infects assertions with kwargs" do + mock = Minitest::Mock.new + mock.expect :assert_infects, true, [:exp, :act, nil], foo: :foo, bar: :bar + + self.class.infect_mock = mock + + _(:act).must_infect :exp, foo: :foo, bar: :bar + + assert_mock mock + end + + it "infects assertions with kwargs (dont_flip)" do + mock = Minitest::Mock.new + mock.expect :assert_infects, true, [:act, :exp, nil], foo: :foo, bar: :bar + + self.class.infect_mock = mock + + _(:act).must_infect_without_flipping :exp, foo: :foo, bar: :bar + + assert_mock mock + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_test.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_test.rb new file mode 100644 index 00000000..77b5219c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_test.rb @@ -0,0 +1,1374 @@ +require "minitest/metametameta" + +e = Encoding.default_external +if e != Encoding::UTF_8 then + warn "" + warn "" + warn "NOTE: External encoding #{e} is not UTF-8. Tests WILL fail." + warn " Run tests with `RUBYOPT=-Eutf-8 rake` to avoid errors." + warn "" + warn "" +end + +class Minitest::Runnable + attr_reader :gc_stats # only needed if running w/ minitest-gcstats + + def whatever # faked for testing + assert true + end +end + +class TestMinitestUnit < MetaMetaMetaTestCase + parallelize_me! + + MINITEST_BASE_DIR = "./lib/minitest/mini" + BT_MIDDLE = ["#{MINITEST_BASE_DIR}/test.rb:161:in 'each'", + "#{MINITEST_BASE_DIR}/test.rb:158:in 'each'", + "#{MINITEST_BASE_DIR}/test.rb:139:in 'run'", + "#{MINITEST_BASE_DIR}/test.rb:106:in 'run'"] + + def test_filter_backtrace + # this is a semi-lame mix of relative paths. + # I cheated by making the autotest parts not have ./ + bt = (["lib/autotest.rb:571:in 'add_exception'", + "test/test_autotest.rb:62:in 'test_add_exception'", + "#{MINITEST_BASE_DIR}/test.rb:165:in '__send__'"] + + BT_MIDDLE + + ["#{MINITEST_BASE_DIR}/test.rb:29", + "test/test_autotest.rb:422"]) + bt = util_expand_bt bt + + ex = ["lib/autotest.rb:571:in 'add_exception'", + "test/test_autotest.rb:62:in 'test_add_exception'"] + ex = util_expand_bt ex + + Minitest::Test.io_lock.synchronize do # try not to trounce in parallel + fu = Minitest.filter_backtrace bt + + assert_equal ex, fu + end + end + + def test_filter_backtrace_all_unit + bt = (["#{MINITEST_BASE_DIR}/test.rb:165:in '__send__'"] + + BT_MIDDLE + + ["#{MINITEST_BASE_DIR}/test.rb:29"]) + ex = bt.clone + fu = Minitest.filter_backtrace bt + assert_equal ex, fu + end + + def test_filter_backtrace_unit_starts + bt = (["#{MINITEST_BASE_DIR}/test.rb:165:in '__send__'"] + + BT_MIDDLE + + ["#{MINITEST_BASE_DIR}/mini/test.rb:29", + "-e:1"]) + + bt = util_expand_bt bt + + ex = ["-e:1"] + Minitest::Test.io_lock.synchronize do # try not to trounce in parallel + fu = Minitest.filter_backtrace bt + assert_equal ex, fu + end + end + + def test_filter_backtrace__empty + with_empty_backtrace_filter do + bt = %w[first second third] + fu = Minitest.filter_backtrace bt.dup + assert_equal bt, fu + end + end + + def test_infectious_binary_encoding + @tu = Class.new FakeNamedTest do + def test_this_is_not_ascii_assertion + assert_equal "ЁЁЁ", "ёёё" + end + + def test_this_is_non_ascii_failure_message + raise "ЁЁЁ".dup.force_encoding(Encoding::BINARY) + end + end + + expected = <<~EOM + FE + + Finished in 0.00 + + 1) Failure: + FakeNamedTestXX#test_this_is_not_ascii_assertion [FILE:LINE]: + Expected: "ЁЁЁ" + Actual: "ёёё" + + 2) Error: + FakeNamedTestXX#test_this_is_non_ascii_failure_message: + RuntimeError: ЁЁЁ + FILE:LINE:in 'test_this_is_non_ascii_failure_message' + + 2 runs, 1 assertions, 1 failures, 1 errors, 0 skips + EOM + + Minitest::Test.io_lock.synchronize do # try not to trounce in parallel + assert_report expected + end + end + + def test_passed_eh_teardown_good + test_class = Class.new FakeNamedTest do + def teardown; assert true; end + def test_omg; assert true; end + end + + test = test_class.new :test_omg + test.run + + refute_predicate test, :error? + assert_predicate test, :passed? + refute_predicate test, :skipped? + end + + def test_passed_eh_teardown_skipped + test_class = Class.new FakeNamedTest do + def teardown; assert true; end + def test_omg; skip "bork"; end + end + + test = test_class.new :test_omg + test.run + + refute_predicate test, :error? + refute_predicate test, :passed? + assert_predicate test, :skipped? + end + + def test_passed_eh_teardown_flunked + test_class = Class.new FakeNamedTest do + def teardown; flunk; end + def test_omg; assert true; end + end + + test = test_class.new :test_omg + test.run + + refute_predicate test, :error? + refute_predicate test, :passed? + refute_predicate test, :skipped? + end + + def test_skipped_is_reachable + test_class = Class.new FakeNamedTest do + def test_omg + skip + ensure + flunk unless skipped? + end + end + + test = test_class.new :test_omg + test.run + + refute_predicate test, :error? + refute_predicate test, :passed? + + assert_predicate test, :skipped? + end + + def util_expand_bt bt + bt.map { |f| f.start_with?(".") ? File.expand_path(f) : f } + end +end + +class TestMinitestUnitInherited < MetaMetaMetaTestCase + def with_overridden_include + Class.class_eval do + def inherited_with_hacks _klass + throw :inherited_hook + end + + alias inherited_without_hacks inherited + alias inherited inherited_with_hacks + alias IGNORE_ME! inherited # 1.8 bug. god I love venture bros + end + + yield + ensure + Class.class_eval do + alias inherited inherited_without_hacks + + undef_method :inherited_with_hacks + undef_method :inherited_without_hacks + end + + refute_respond_to Class, :inherited_with_hacks + refute_respond_to Class, :inherited_without_hacks + end + + def test_inherited_hook_plays_nice_with_others + with_overridden_include do + assert_throws :inherited_hook do + Class.new FakeNamedTest + end + end + end +end + +class TestMinitestRunner < MetaMetaMetaTestCase + # do not parallelize this suite... it just can't handle it. + + def test_class_runnables + @assertion_count = 0 + + tc = Class.new Minitest::Test + + assert_equal 1, Minitest::Test.runnables.size + assert_equal [tc], Minitest::Test.runnables + end + + def test_run_test + @tu = Class.new FakeNamedTest do + attr_reader :foo + + def run + @foo = "hi mom!" + r = super + @foo = "okay" + + r + end + + def test_something + assert_equal "hi mom!", foo + end + end + + expected = <<~EOM + . + + Finished in 0.00 + + 1 runs, 1 assertions, 0 failures, 0 errors, 0 skips + EOM + + assert_report expected + end + + def test_run_error + @tu = Class.new FakeNamedTest do + def test_something + assert true + end + + def test_error + raise "unhandled exception" + end + end + + expected = <<~EOM + .E + + Finished in 0.00 + + 1) Error: + FakeNamedTestXX#test_error: + RuntimeError: unhandled exception + FILE:LINE:in 'test_error' + + 2 runs, 1 assertions, 0 failures, 1 errors, 0 skips + EOM + + assert_report expected + end + + def test_run_error_teardown + @tu = Class.new FakeNamedTest do + def test_something + assert true + end + + def teardown + raise "unhandled exception" + end + end + + expected = <<~EOM + E + + Finished in 0.00 + + 1) Error: + FakeNamedTestXX#test_something: + RuntimeError: unhandled exception + FILE:LINE:in 'teardown' + + 1 runs, 1 assertions, 0 failures, 1 errors, 0 skips + EOM + + assert_report expected + end + + def test_run_failing + setup_basic_tu + + expected = <<~EOM + .F + + Finished in 0.00 + + 1) Failure: + FakeNamedTestXX#test_failure [FILE:LINE]: + Expected false to be truthy. + + 2 runs, 2 assertions, 1 failures, 0 errors, 0 skips + EOM + + assert_report expected + end + + def setup_basic_tu + @tu = Class.new FakeNamedTest do + def test_something + assert true + end + + def test_failure + assert false + end + end + end + + def test_seed # this is set for THIS run, so I'm not testing it's actual value + assert_instance_of Integer, Minitest.seed + end + + def test_run_failing_filtered + setup_basic_tu + + expected = <<~EOM + . + + Finished in 0.00 + + 1 runs, 1 assertions, 0 failures, 0 errors, 0 skips + EOM + + assert_report expected, %w[--name /some|thing/ --seed 42] + end + + def assert_filtering filter, name, expected, a = false + args = %W[--#{filter} #{name} --seed 42] + + alpha = Class.new FakeNamedTest do + define_method :test_something do + assert a + end + end + Object.const_set :Alpha, alpha + + beta = Class.new FakeNamedTest do + define_method :test_something do + assert true + end + end + Object.const_set :Beta, beta + + @tus = [alpha, beta] + + assert_report expected, args + ensure + Object.send :remove_const, :Alpha + Object.send :remove_const, :Beta + end + + def test_run_filtered_including_suite_name + expected = <<~EOM + . + + Finished in 0.00 + + 1 runs, 1 assertions, 0 failures, 0 errors, 0 skips + EOM + + assert_filtering "name", "/Beta#test_something/", expected + end + + def test_run_filtered_including_suite_name_string + expected = <<~EOM + . + + Finished in 0.00 + + 1 runs, 1 assertions, 0 failures, 0 errors, 0 skips + EOM + + assert_filtering "name", "Beta#test_something", expected + end + + def test_run_filtered_string_method_only + expected = <<~EOM + .. + + Finished in 0.00 + + 2 runs, 2 assertions, 0 failures, 0 errors, 0 skips + EOM + + assert_filtering "name", "test_something", expected, :pass + end + + def test_run_failing_excluded + setup_basic_tu + + expected = <<~EOM + . + + Finished in 0.00 + + 1 runs, 1 assertions, 0 failures, 0 errors, 0 skips + EOM + + assert_report expected, %w[--exclude /failure/ --seed 42] + end + + def test_run_filtered_excluding_suite_name + expected = <<~EOM + . + + Finished in 0.00 + + 1 runs, 1 assertions, 0 failures, 0 errors, 0 skips + EOM + + assert_filtering "exclude", "/Alpha#test_something/", expected + end + + def test_run_filtered_excluding_suite_name_string + expected = <<~EOM + . + + Finished in 0.00 + + 1 runs, 1 assertions, 0 failures, 0 errors, 0 skips + EOM + + assert_filtering "exclude", "Alpha#test_something", expected + end + + def test_run_filtered_excluding_string_method_only + expected = <<~EOM + + + Finished in 0.00 + + 0 runs, 0 assertions, 0 failures, 0 errors, 0 skips + EOM + + assert_filtering "exclude", "test_something", expected, :pass + end + + def test_run_passing + @tu = Class.new FakeNamedTest do + def test_something + assert true + end + end + + expected = <<~EOM + . + + Finished in 0.00 + + 1 runs, 1 assertions, 0 failures, 0 errors, 0 skips + EOM + + assert_report expected + end + + def test_run_skip + @tu = Class.new FakeNamedTest do + def test_something + assert true + end + + def test_skip + skip "not yet" + end + end + + expected = <<~EOM + .S + + Finished in 0.00 + + 2 runs, 1 assertions, 0 failures, 0 errors, 1 skips + + You have skipped tests. Run with --verbose for details. + EOM + + restore_env do + assert_report expected + end + end + + def test_run_skip_verbose + @tu = Class.new FakeNamedTest do + def test_something + assert true + end + + def test_skip + skip "not yet" + end + end + + expected = <<~EOM + FakeNamedTestXX#test_something = 0.00 s = . + FakeNamedTestXX#test_skip = 0.00 s = S + + Finished in 0.00 + + 1) Skipped: + FakeNamedTestXX#test_skip [FILE:LINE]: + not yet + + 2 runs, 1 assertions, 0 failures, 0 errors, 1 skips + EOM + + assert_report expected, %w[--seed 42 --verbose] + end + + def test_run_skip_show_skips + @tu = Class.new FakeNamedTest do + def test_something + assert true + end + + def test_skip + skip "not yet" + end + end + + expected = <<~EOM + .S + + Finished in 0.00 + + 1) Skipped: + FakeNamedTestXX#test_skip [FILE:LINE]: + not yet + + 2 runs, 1 assertions, 0 failures, 0 errors, 1 skips + EOM + + assert_report expected, %w[--seed 42 --show-skips] + end + + def test_run_with_other_runner + @tu = Class.new FakeNamedTest do + def self.run reporter, options = {} + @reporter = reporter + before_my_suite + super + end + + def self.name; "wacky!" end + + def self.before_my_suite + @reporter.io.puts "Running #{self.name} tests" + @@foo = 1 + end + + def test_something + assert_equal 1, @@foo + end + + def test_something_else + assert_equal 1, @@foo + end + end + + expected = <<~EOM + Running wacky! tests + .. + + Finished in 0.00 + + 2 runs, 2 assertions, 0 failures, 0 errors, 0 skips + EOM + + assert_report expected + end + + require "monitor" + + class Latch + def initialize count = 1 + @count = count + @lock = Monitor.new + @cv = @lock.new_cond + end + + def release + @lock.synchronize do + @count -= 1 if @count > 0 + @cv.broadcast if @count == 0 + end + end + + def await + @lock.synchronize { @cv.wait_while { @count > 0 } } + end + end + + def test_run_parallel + test_count = 2 + test_latch = Latch.new test_count + wait_latch = Latch.new test_count + main_latch = Latch.new + + thread = Thread.new { + Thread.current.abort_on_exception = true + + # This latch waits until both test latches have been released. Both + # latches can't be released unless done in separate threads because + # `main_latch` keeps the test method from finishing. + test_latch.await + main_latch.release + } + + @tu = Class.new FakeNamedTest do + parallelize_me! + + test_count.times do |i| + define_method :"test_wait_on_main_thread_#{i}" do + test_latch.release + + # This latch blocks until the "main thread" releases it. The main + # thread can't release this latch until both test latches have + # been released. This forces the latches to be released in separate + # threads. + main_latch.await + assert true + end + end + end + + expected = <<~EOM + .. + + Finished in 0.00 + + 2 runs, 2 assertions, 0 failures, 0 errors, 0 skips + EOM + + skip if Minitest.parallel_executor.size < 2 # locks up test runner if 1 CPU + + assert_report expected do |reporter| + reporter.extend Module.new { + define_method :record do |result| + super(result) + wait_latch.release + end + + define_method :report do + wait_latch.await + super() + end + } + end + assert thread.join + end +end + +class TestMinitestUnitOrder < MetaMetaMetaTestCase + # do not parallelize this suite... it just can't handle it. + + def test_before_setup + call_order = [] + + @tu = Class.new FakeNamedTest do + define_method :setup do + super() + call_order << :setup + end + + define_method :before_setup do + call_order << :before_setup + end + + def test_omg; assert true; end + end + + run_tu_with_fresh_reporter + + expected = %i[before_setup setup] + assert_equal expected, call_order + end + + def test_after_teardown + call_order = [] + @tu = Class.new FakeNamedTest do + define_method :teardown do + super() + call_order << :teardown + end + + define_method :after_teardown do + call_order << :after_teardown + end + + def test_omg; assert true; end + end + + run_tu_with_fresh_reporter + + expected = %i[teardown after_teardown] + assert_equal expected, call_order + end + + def test_all_teardowns_are_guaranteed_to_run + call_order = [] + + @tu = Class.new FakeNamedTest do + define_method :after_teardown do + super() + call_order << :after_teardown + raise + end + + define_method :teardown do + super() + call_order << :teardown + raise + end + + define_method :before_teardown do + super() + call_order << :before_teardown + raise + end + + def test_omg; assert true; end + end + + run_tu_with_fresh_reporter + + expected = %i[before_teardown teardown after_teardown] + assert_equal expected, call_order + end + + def test_setup_and_teardown_survive_inheritance + call_order = [] + + @tu = Class.new FakeNamedTest do + define_method :setup do + call_order << :setup_method + end + + define_method :teardown do + call_order << :teardown_method + end + + define_method :test_something do + call_order << :test + end + end + + run_tu_with_fresh_reporter + + @tu = Class.new @tu + run_tu_with_fresh_reporter + + # Once for the parent class, once for the child + expected = %i[setup_method test teardown_method] * 2 + + assert_equal expected, call_order + end +end + +class BetterError < RuntimeError # like better_error w/o infecting RuntimeError + def set_backtrace bt + super + @bad_ivar = binding + end +end + +class TestMinitestRunnable < Minitest::Test + def setup_marshal klass + tc = klass.new "whatever" + tc.assertions = 42 + tc.failures << "a failure" + + yield tc if block_given? + + def tc.setup + @blah = "blah" + end + tc.setup + + @tc = Minitest::Result.from tc + end + + def assert_marshal expected_ivars + new_tc = Marshal.load Marshal.dump @tc + + ivars = new_tc.instance_variables.map(&:to_s).sort + ivars.delete "@gc_stats" # only needed if running w/ minitest-gcstats + assert_equal expected_ivars, ivars + assert_equal "whatever", new_tc.name + assert_equal 42, new_tc.assertions + assert_equal ["a failure"], new_tc.failures + + yield new_tc if block_given? + end + + def test_marshal + setup_marshal Minitest::Runnable + + assert_marshal %w[@NAME @assertions @failures @klass @source_location @time] + end + + def test_spec_marshal + klass = describe("whatever") { it("passes") { assert true } } + rm = klass.runnable_methods.first + + # Run the test + @tc = klass.new(rm).run + + assert_kind_of Minitest::Result, @tc + + # Pass it over the wire + over_the_wire = Marshal.load Marshal.dump @tc + + assert_equal @tc.time, over_the_wire.time + assert_equal @tc.name, over_the_wire.name + assert_equal @tc.assertions, over_the_wire.assertions + assert_equal @tc.failures, over_the_wire.failures + assert_equal @tc.klass, over_the_wire.klass + end + + def test_spec_marshal_with_exception + klass = describe("whatever") { + it("raises, badly") { + raise Class.new(StandardError), "this is bad!" + } + } + + rm = klass.runnable_methods.first + + # Run the test + @tc = klass.new(rm).run + + assert_kind_of Minitest::Result, @tc + assert_instance_of Minitest::UnexpectedError, @tc.failure + + msg = @tc.failure.error.message + assert_includes msg, "Neutered Exception #: boom", msg + + # Pass it over the wire + over_the_wire = Marshal.load Marshal.dump @tc + + assert_equal @tc.time, over_the_wire.time + assert_equal @tc.name, over_the_wire.name + assert_equal @tc.assertions, over_the_wire.assertions + assert_equal @tc.failures, over_the_wire.failures + assert_equal @tc.klass, over_the_wire.klass + end +end + +class TestMinitestTest < TestMinitestRunnable + def test_dup + setup_marshal Minitest::Test do |tc| + tc.time = 3.14 + end + + assert_marshal %w[@NAME @assertions @failures @klass @source_location @time] do |new_tc| + assert_in_epsilon 3.14, new_tc.time + end + end +end + +class TestMinitestUnitTestCase < Minitest::Test + # do not call parallelize_me! - teardown accesses @tc._assertions + # which is not threadsafe. Nearly every method in here is an + # assertion test so it isn't worth splitting it out further. + + def setup + super + + Minitest::Test.reset + + @tc = Minitest::Test.new "fake tc" + @zomg = "zomg ponies!" + @assertion_count = 1 + end + + def teardown + assert_equal(@assertion_count, @tc.assertions, + "expected #{@assertion_count} assertions to be fired during the test, not #{@tc.assertions}") if @tc.passed? + end + + def non_verbose + orig_verbose = $VERBOSE + $VERBOSE = false + + yield + ensure + $VERBOSE = orig_verbose + end + + def sample_test_case rand + srand rand + Class.new FakeNamedTest do + 100.times do |i| + define_method("test_#{i}") { assert true } + end + end.runnable_methods + end + + # srand varies with OS + def test_runnable_methods_random + @assertion_count = 0 + + random_tests_1 = sample_test_case 42 + random_tests_2 = sample_test_case 42 + random_tests_3 = sample_test_case 1_000 + + assert_equal random_tests_1, random_tests_2 + assert_equal random_tests_1, random_tests_3 + end + + def test_runnable_methods_sorted + @assertion_count = 0 + + sample_test_case = Class.new FakeNamedTest do + def self.test_order; :sorted end + def test_test3; assert "does not matter" end + def test_test2; assert "does not matter" end + def test_test1; assert "does not matter" end + end + + expected = %w[test_test1 test_test2 test_test3] + assert_equal expected, sample_test_case.runnable_methods + end + + def test_i_suck_and_my_tests_are_order_dependent_bang_sets_test_order_alpha + @assertion_count = 0 + + shitty_test_case = Class.new FakeNamedTest + + shitty_test_case.i_suck_and_my_tests_are_order_dependent! + + assert_equal :alpha, shitty_test_case.test_order + end + + def test_i_suck_and_my_tests_are_order_dependent_bang_does_not_warn + @assertion_count = 0 + + shitty_test_case = Class.new FakeNamedTest + + def shitty_test_case.test_order; :lol end + + assert_silent do + shitty_test_case.i_suck_and_my_tests_are_order_dependent! + end + end + + def test_autorun_does_not_affect_fork_success_status + @assertion_count = 0 + skip "windows doesn't have fork" unless Process.respond_to? :fork + Process.waitpid(fork {}) + assert_equal true, $?.success? + end + + def test_autorun_does_not_affect_fork_exit_status + @assertion_count = 0 + skip "windows doesn't have fork" unless Process.respond_to? :fork + Process.waitpid(fork { exit 42 }) + assert_equal 42, $?.exitstatus + end + + def test_autorun_optionally_can_affect_fork_exit_status + @assertion_count = 0 + skip "windows doesn't have fork" unless Process.respond_to? :fork + Minitest.allow_fork = true + Process.waitpid(fork { exit 42 }) + refute_equal 42, $?.exitstatus + ensure + Minitest.allow_fork = false + end +end + +class TestMinitestGuard < Minitest::Test + parallelize_me! + + def test_mri_eh + assert self.class.mri? "ruby blah" + assert self.mri? "ruby blah" + end + + def test_jruby_eh + assert self.class.jruby? "java" + assert self.jruby? "java" + end + + def test_rubinius_eh + assert_deprecation do + assert self.class.rubinius? "rbx" + end + assert_deprecation do + assert self.rubinius? "rbx" + end + end + + def test_maglev_eh + assert_deprecation do + assert self.class.maglev? "maglev" + end + assert_deprecation do + assert self.maglev? "maglev" + end + end + + def test_osx_eh + assert self.class.osx? "darwin" + assert self.osx? "darwin" + end + + def test_windows_eh + assert self.class.windows? "mswin" + assert self.windows? "mswin" + end +end + +class TestMinitestUnitRecording < MetaMetaMetaTestCase + # do not parallelize this suite... it just can't handle it. + + def assert_run_record *expected, &block + @tu = Class.new FakeNamedTest, &block + + run_tu_with_fresh_reporter + + recorded = first_reporter.results.map(&:failures).flatten.map { |f| f.error.class } + + assert_equal expected, recorded + end + + def test_run_with_bogus_reporter + # https://github.com/seattlerb/minitest/issues/659 + # TODO: remove test for minitest 6 + @tu = Class.new FakeNamedTest do + def test_method + assert true + end + end + + bogus_reporter = Class.new do # doesn't subclass AbstractReporter + def start; @success = false; end + # def prerecord klass, name; end # doesn't define full API + def record _result; @success = true; end + def report; end + def passed?; end + def results; end + def success?; @success; end + end.new + + self.reporter = Minitest::CompositeReporter.new + reporter << bogus_reporter + + Minitest::Runnable.runnables.delete @tu + + @tu.run reporter, {} + + assert_predicate bogus_reporter, :success? + end + + def test_record_passing + assert_run_record do + def test_method + assert true + end + end + end + + def test_record_failing + assert_run_record Minitest::Assertion do + def test_method + assert false + end + end + end + + def test_record_error + assert_run_record RuntimeError do + def test_method + raise "unhandled exception" + end + end + end + + def test_record_error_teardown + assert_run_record RuntimeError do + def test_method + assert true + end + + def teardown + raise "unhandled exception" + end + end + end + + def test_record_error_in_test_and_teardown + assert_run_record AnError, RuntimeError do + def test_method + raise AnError + end + + def teardown + raise "unhandled exception" + end + end + end + + def test_to_s_error_in_test_and_teardown + @tu = Class.new FakeNamedTest do + def test_method + raise AnError + end + + def teardown + raise "unhandled exception" + end + end + + run_tu_with_fresh_reporter + + exp = <<~EOM + Error: + FakeNamedTestXX#test_method: + AnError: AnError + FILE:LINE:in 'test_method' + + Error: + FakeNamedTestXX#test_method: + RuntimeError: unhandled exception + FILE:LINE:in 'teardown' + EOM + + assert_equal exp.strip, normalize_output(first_reporter.results.first.to_s).strip + end + + def test_record_skip + assert_run_record Minitest::Skip do + def test_method + skip "not yet" + end + end + end +end + +class TestUnexpectedError < Minitest::Test + def assert_compress exp, input + e = Minitest::UnexpectedError.new RuntimeError.new + + exp = exp.lines.map(&:chomp) if String === exp + act = e.compress input + + assert_equal exp, act + end + + ACT1 = %w[ a b c b c b c b c d ] + + def test_normal + assert_compress <<~EXP, %w[ a b c b c b c b c d ] + a + +->> 4 cycles of 2 lines: + | b + | c + +-<< + d + EXP + end + + def test_normal2 + assert_compress <<~EXP, %w[ a b c b c b c b c ] + a + +->> 4 cycles of 2 lines: + | b + | c + +-<< + EXP + end + + def test_longer_c_than_b + # the extra c in the front makes the overall length longer sorting it first + assert_compress <<~EXP, %w[ c a b c b c b c b c b d ] + c + a + b + +->> 4 cycles of 2 lines: + | c + | b + +-<< + d + EXP + end + + def test_1_line_cycles + assert_compress <<~EXP, %w[ c a b c b c b c b c b b b d ] + c + a + +->> 4 cycles of 2 lines: + | b + | c + +-<< + +->> 3 cycles of 1 lines: + | b + +-<< + d + EXP + end + + def test_sanity3 + pre = ("aa".."am").to_a + mid = ("a".."z").to_a * 67 + post = ("aa".."am").to_a + ary = pre + mid + post + + exp = pre + + [" +->> 67 cycles of 26 lines:"] + + ("a".."z").map { |s| " | #{s}" } + + [" +-<<"] + + post + + assert_compress exp, ary + end + + def test_absurd_patterns + assert_compress <<~EXP, %w[ a b c b c a b c b c a b c ] + +->> 2 cycles of 5 lines: + | a + | +->> 2 cycles of 2 lines: + | | b + | | c + | +-<< + +-<< + a + b + c + EXP + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_test_task.rb b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_test_task.rb new file mode 100644 index 00000000..58902f58 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/minitest-5.25.5/test/minitest/test_minitest_test_task.rb @@ -0,0 +1,57 @@ +require "minitest/autorun" + +begin + require "hoe" +rescue LoadError => e + warn e.message + return +end + +require "minitest/test_task" + +Hoe.load_plugins # make sure Hoe::Test is loaded + +class TestHoeTest < Minitest::Test + PATH = "test/minitest/test_minitest_test_task.rb" + + def util_cmd_string *prelude_framework + mt_path = %w[lib test .].join File::PATH_SEPARATOR + mt_expected = "-I%s -w -e '%srequire %p' -- " + + mt_expected % [mt_path, prelude_framework.join("; "), PATH] + end + + def util_exp_cmd + @tester.make_test_cmd.sub(/ -- .+/, " -- ") + end + + def test_make_test_cmd_for_minitest + skip "Using TESTOPTS... skipping" if ENV["TESTOPTS"] + + require "minitest/test_task" + + framework = %(require "minitest/autorun"; ) + + @tester = Minitest::TestTask.create :test do |t| + t.test_globs = [PATH] + end + + assert_equal util_cmd_string(framework), util_exp_cmd + end + + def test_make_test_cmd_for_minitest_prelude + skip "Using TESTOPTS... skipping" if ENV["TESTOPTS"] + + require "minitest/test_task" + + prelude = %(require "other/file") + framework = %(require "minitest/autorun"; ) + + @tester = Minitest::TestTask.create :test do |t| + t.test_prelude = prelude + t.test_globs = [PATH] + end + + assert_equal util_cmd_string(prelude, framework), util_exp_cmd + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/.rspec b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/.rspec new file mode 100644 index 00000000..09127182 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/.rspec @@ -0,0 +1,2 @@ +--color +--order random diff --git a/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/.rubocop.yml b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/.rubocop.yml new file mode 100644 index 00000000..5014b23f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/.rubocop.yml @@ -0,0 +1,56 @@ +require: + - standard + +plugins: + - rubocop-performance + - rubocop-rake + - rubocop-rspec + - standard-performance + +AllCops: + NewCops: enable + TargetRubyVersion: 3.2 + +Layout/ArgumentAlignment: + EnforcedStyle: with_fixed_indentation + IndentationWidth: 2 + +Layout/CaseIndentation: + EnforcedStyle: end + +Layout/EndAlignment: + EnforcedStyleAlignWith: start_of_line + +Layout/LineLength: + Max: 140 + +Layout/ParameterAlignment: + EnforcedStyle: with_fixed_indentation + IndentationWidth: 2 + +Layout/SpaceInsideHashLiteralBraces: + EnforcedStyle: no_space + +Metrics/ParameterLists: + CountKeywordArgs: false + +Style/Alias: + EnforcedStyle: prefer_alias_method + +Style/Documentation: + Enabled: false + +Style/FrozenStringLiteralComment: + EnforcedStyle: never + +Style/OpenStructUse: + Enabled: false + +Style/StringLiterals: + EnforcedStyle: double_quotes + +Style/StringLiteralsInInterpolation: + EnforcedStyle: double_quotes + +Style/TernaryParentheses: + EnforcedStyle: require_parentheses diff --git a/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/.yardopts b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/.yardopts new file mode 100644 index 00000000..b7f7711c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/.yardopts @@ -0,0 +1,8 @@ +--no-private +--protected +--markup markdown +- +CHANGELOG.md +CONTRIBUTING.md +LICENSE.md +README.md diff --git a/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/CHANGELOG.md b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/CHANGELOG.md new file mode 100644 index 00000000..b71074bb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/CHANGELOG.md @@ -0,0 +1,113 @@ +0.7.2 +----- +* [Drop support for Ruby 3.1](https://github.com/sferik/multi_xml/commit/fab6288edd36c58a2b13e0206d8bed305fcb4a4b) + +0.7.1 +----- +* [Relax required Ruby version constraint to allow installation on Debian stable](https://github.com/sferik/multi_xml/commit/7d18711466a15e158dc71344ca6f6e18838ecc8d) + +0.7.0 +----- +* [Add support for Ruby 3.3](https://github.com/sferik/multi_xml/pull/67) +* [Drop support for Ruby 3.0](https://github.com/sferik/multi_xml/commit/eec72c56307fede3a93f1a61553587cb278b0c8a) [and](https://github.com/sferik/multi_xml/commit/6a6dec80a36c30774a5525b45f71d346fb561e69) [earlier](https://github.com/sferik/multi_xml/commit/e7dad37a0a0be8383a26ffe515c575b5b4d04588) +* [Don't mutate strings](https://github.com/sferik/multi_xml/commit/71be3fff4afb0277a7e1c47c5f1f4b6106a8eb45) + +0.6.0 +----- +* [Duplexed Streams](https://github.com/sferik/multi_xml/pull/45) +* [Support for Oga](https://github.com/sferik/multi_xml/pull/47) +* [Integer unification for Ruby 2.4](https://github.com/sferik/multi_xml/pull/54) + +0.5.5 +----- +* [Fix symbolize_keys function](https://github.com/sferik/multi_xml/commit/a4cae3aeb690999287cd30206399abaa5ce1ae81) +* [Fix Nokogiri parser for the same attr and inner element name](https://github.com/sferik/multi_xml/commit/a28ed86e2d7826b2edeed98552736b4c7ca52726) + +0.5.4 +----- +* [Add option to not cast parsed values](https://github.com/sferik/multi_xml/commit/44fc05fbcfd60cc8b555b75212471fab29fa8cd0) +* [Use message instead of to_s](https://github.com/sferik/multi_xml/commit/b06f0114434ffe1957dd7bc2712cb5b76c1b45fe) + +0.5.3 +----- +* [Add cryptographic signature](https://github.com/sferik/multi_xml/commit/f39f0c74308090737816c622dbb7d7aa28c646c0) + +0.5.2 +----- +* [Remove ability to parse symbols and YAML](https://github.com/sferik/multi_xml/pull/34) + +0.5.1 +----- +* [Revert "Reset @@parser in between specs"](https://github.com/sferik/multi_xml/issues/28) + +0.5.0 +----- +* [Reset @@parser in between specs](https://github.com/sferik/multi_xml/commit/b562bed265918b43ac1c4c638ae3a7ffe95ecd83) +* [Add attributes being passed through on content nodes](https://github.com/sferik/multi_xml/commit/631a8bb3c2253db0024f77f47c16d5a53b8128fd) + +0.4.4 +----- +* [Fix regression in MultiXml.parse](https://github.com/sferik/multi_xml/commit/45ae597d9a35cbd89cc7f5518c85bac30199fc06) + +0.4.3 +----- +* [Make parser a class variable](https://github.com/sferik/multi_xml/commit/6804ffc8680ed6466c66f2472f5e016c412c2c24) +* [Add TYPE_NAMES constant](https://github.com/sferik/multi_xml/commit/72a21f2e86c8e3ac9689cee5f3a62102cfb98028) + +0.4.2 +----- +* [Fix bug in dealing with xml element attributes for both REXML and Ox](https://github.com/sferik/multi_xml/commit/ba3c1ac427ff0268abaf8186fb4bd81100c99559) +* [Make Ox the preferred XML parser](https://github.com/sferik/multi_xml/commit/0a718d740c30fba426f300a929cda9ee8250d238) + +0.4.1 +----- +* [Use the SAX like parser with Ox](https://github.com/sferik/multi_xml/commit/d289d42817a32e48483c00d5361c76fbea62a166) + +0.4.0 +----- +* [Add support for Ox](https://github.com/sferik/multi_xml/pull/14) + +0.3.0 +----- +* [Remove core class monkeypatches](https://github.com/sferik/multi_xml/commit/f7cc3ce4d2924c0e0adc6935d1fba5ec79282938) +* [Sort out some class / singleton class issues](https://github.com/sferik/multi_xml/commit/a5dac06bcf658facaaf7afa295f1291c7be15a44) +* [Have parsers refer to toplevel CONTENT_ROOT instead of defining it](https://github.com/sferik/multi_xml/commit/94e6fa49e69b2a2467a0e6d3558f7d9815cae47e) +* [Move redundant input sanitizing to top-level](https://github.com/sferik/multi_xml/commit/4874148214dbbd2e5a4b877734e2519af42d6132) +* [Refactor libxml and nokogiri parsers to inherit from a common ancestor](https://github.com/sferik/multi_xml/commit/e0fdffcbfe641b6aaa3952ffa0570a893de325c2) + +0.2.2 +----- +* [Respect the global load path](https://github.com/sferik/multi_xml/commit/68eb3011b37f0e0222bb842abd2a78e1285a97c1) + +0.2.1 +----- +* [Add BlueCloth gem as development dependency for Markdown formatting](https://github.com/sferik/multi_xml/commit/18195cd1789176709f68f0d7f8df7fc944fe4d24) +* [Replace BlueCloth with Maruku for JRuby compatibility](https://github.com/sferik/multi_xml/commit/bad5516a5ec5e7ef7fc5a35c411721522357fa19) + +0.2.0 +----- +* [Do not automatically load all library files](https://github.com/sferik/multi_xml/commit/dbd0447e062e8930118573c5453150e9371e5955) + +0.1.4 +----- +* [Preserve backtrace when catching/throwing exceptions](https://github.com/sferik/multi_xml/commit/7475ee90201c2701fddd524082832d16ca62552d) + +0.1.3 +----- +* [Common error handling for all parsers](https://github.com/sferik/multi_xml/commit/5357c28eddc14e921fd1be1f445db602a8dddaf2) + +0.1.2 +----- +* [Make wrap an Array class method](https://github.com/sferik/multi_xml/commit/28307b69bd1d9460353c861466e425c2afadcf56) + +0.1.1 +----- +* [Fix parsing for strings that contain newlines](https://github.com/sferik/multi_xml/commit/68087a4ce50b5d63cfa60d6f1fcbc2f6d689e43f) + +0.1.0 +----- +* [Add support for LibXML and Nokogiri](https://github.com/sferik/multi_xml/commit/856bb17fce66601e0b3d3eb3b64dbeb25aed3bca) + +0.0.1 +----- +* [REXML support](https://github.com/sferik/multi_xml/commit/2a848384a7b90fb3e26b5a8d4dc3fa3e3f2db5fc) diff --git a/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/CONTRIBUTING.md b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/CONTRIBUTING.md new file mode 100644 index 00000000..fdb74164 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/CONTRIBUTING.md @@ -0,0 +1,51 @@ +## Contributing +In the spirit of [free software][free-sw] , **everyone** is encouraged to help +improve this project. + +[free-sw]: http://www.fsf.org/licensing/essays/free-sw.html + +Here are some ways *you* can contribute: + +* by using alpha, beta, and prerelease versions +* by reporting bugs +* by suggesting new features +* by writing or editing documentation +* by writing specifications +* by writing code (**no patch is too small**: fix typos, add comments, clean up + inconsistent whitespace) +* by refactoring code +* by resolving [issues][] +* by reviewing patches +* [financially][gittip] + +[issues]: https://github.com/sferik/multi_xml/issues +[gittip]: https://www.gittip.com/sferik/ + +## Submitting an Issue +We use the [GitHub issue tracker][issues] to track bugs and features. Before +submitting a bug report or feature request, check to make sure it hasn't +already been submitted. When submitting a bug report, please include a [Gist][] +that includes a stack trace and any details that may be necessary to reproduce +the bug, including your gem version, Ruby version, and operating system. +Ideally, a bug report should include a pull request with failing specs. + +[gist]: https://gist.github.com/ + +## Submitting a Pull Request +1. [Fork the repository.][fork] +2. [Create a topic branch.][branch] +3. Add specs for your unimplemented feature or bug fix. +4. Run `bundle exec rake spec`. If your specs pass, return to step 3. +5. Implement your feature or bug fix. +6. Run `bundle exec rake`. If your specs fail, return to step 5. +7. Run `open coverage/index.html`. If your changes are not completely covered + by your tests, return to step 3. +8. Add documentation for your feature or bug fix. +9. Run `bundle exec rake verify_measurements`. If your changes are not 100% + documented, go back to step 8. +10. Add, commit, and push your changes. +11. [Submit a pull request.][pr] + +[fork]: http://help.github.com/fork-a-repo/ +[branch]: http://learn.github.com/p/branching.html +[pr]: http://help.github.com/send-pull-requests/ diff --git a/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/Gemfile b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/Gemfile new file mode 100644 index 00000000..b7164f90 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/Gemfile @@ -0,0 +1,21 @@ +source "https://rubygems.org" + +gem "libxml-ruby", require: nil, platforms: :ruby +gem "nokogiri", require: nil +gem "oga", ">= 2.3", require: nil +gem "ox", require: nil, platforms: :ruby +gem "rexml", require: nil + +gem "rake", ">= 13.2.1" +gem "rspec", ">= 3.12" +gem "rubocop", ">= 1.62.1" +gem "rubocop-performance", ">= 1.20.2" +gem "rubocop-rake", ">= 0.6" +gem "rubocop-rspec", ">= 2.24" +gem "simplecov", ">= 0.22" +gem "standard", ">= 1.35.1" +gem "standard-performance", ">= 1.3.1" +gem "yard", ">= 0.9.36" +gem "yardstick", ">= 0.9.9" + +gemspec diff --git a/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/LICENSE.md b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/LICENSE.md new file mode 100644 index 00000000..7dd92889 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/LICENSE.md @@ -0,0 +1,20 @@ +Copyright (c) 2010-2025 Erik Berlin + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/README.md b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/README.md new file mode 100644 index 00000000..c5b0187c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/README.md @@ -0,0 +1,75 @@ +# MultiXML + +A generic swappable back-end for XML parsing + +## Installation + gem install multi_xml + +## Documentation +[http://rdoc.info/gems/multi_xml][documentation] + +[documentation]: http://rdoc.info/gems/multi_xml + +## Usage Examples +```ruby +require 'multi_xml' + +MultiXml.parser = :ox +MultiXml.parser = MultiXml::Parsers::Ox # Same as above +MultiXml.parse('This is the contents') # Parsed using Ox + +MultiXml.parser = :libxml +MultiXml.parser = MultiXml::Parsers::Libxml # Same as above +MultiXml.parse('This is the contents') # Parsed using LibXML + +MultiXml.parser = :nokogiri +MultiXml.parser = MultiXml::Parsers::Nokogiri # Same as above +MultiXml.parse('This is the contents') # Parsed using Nokogiri + +MultiXml.parser = :rexml +MultiXml.parser = MultiXml::Parsers::Rexml # Same as above +MultiXml.parse('This is the contents') # Parsed using REXML + +MultiXml.parser = :oga +MultiXml.parser = MultiXml::Parsers::Oga # Same as above +MultiXml.parse('This is the contents') # Parsed using Oga +``` +The `parser` setter takes either a symbol or a class (to allow for custom XML +parsers) that responds to `.parse` at the class level. + +MultiXML tries to have intelligent defaulting. That is, if you have any of the +supported parsers already loaded, it will use them before attempting to load +a new one. When loading, libraries are ordered by speed: first Ox, then LibXML, +then Nokogiri, and finally REXML. + +## Supported Ruby Versions +This library aims to support and is tested against the following Ruby +implementations: + +* 3.2 +* 3.3 +* 3.4 +* JRuby 10 + +If something doesn't work on one of these versions, it's a bug. + +This library may inadvertently work (or seem to work) on other Ruby +implementations, however support will only be provided for the versions listed +above. + +If you would like this library to support another Ruby version, you may +volunteer to be a maintainer. Being a maintainer entails making sure all tests +run and pass on that implementation. When something breaks on your +implementation, you will be responsible for providing patches in a timely +fashion. If critical issues for a particular implementation exist at the time +of a major release, support for that Ruby version may be dropped. + +## Inspiration +MultiXML was inspired by [MultiJSON][]. + +[multijson]: https://github.com/intridea/multi_json/ + +## Copyright +Copyright (c) 2010-2025 Erik Berlin. See [LICENSE][] for details. + +[license]: LICENSE.md diff --git a/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/Rakefile b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/Rakefile new file mode 100644 index 00000000..54f21feb --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/Rakefile @@ -0,0 +1,33 @@ +require "bundler" +Bundler::GemHelper.install_tasks + +require "rspec/core/rake_task" +RSpec::Core::RakeTask.new(:spec) + +task test: :spec + +require "rubocop/rake_task" +RuboCop::RakeTask.new + +require "yard" +YARD::Rake::YardocTask.new do |task| + task.files = ["lib/**/*.rb", "-", "LICENSE.md"] + task.options = [ + "--no-private", + "--protected", + "--output-dir", "doc/yard", + "--markup", "markdown" + ] +end + +require "yardstick/rake/measurement" +Yardstick::Rake::Measurement.new do |measurement| + measurement.output = "measurement/report.txt" +end + +require "yardstick/rake/verify" +Yardstick::Rake::Verify.new do |verify| + verify.threshold = 48.8 +end + +task default: %i[spec rubocop verify_measurements] diff --git a/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml.rb b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml.rb new file mode 100644 index 00000000..87e66d05 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml.rb @@ -0,0 +1,311 @@ +require "bigdecimal" +require "date" +require "stringio" +require "time" +require "yaml" + +module MultiXml # rubocop:disable Metrics/ModuleLength + class ParseError < StandardError; end + + class NoParserError < StandardError; end + + class DisallowedTypeError < StandardError + def initialize(type) + super("Disallowed type attribute: #{type.inspect}") + end + end + + unless defined?(REQUIREMENT_MAP) + REQUIREMENT_MAP = [ + ["ox", :ox], + ["libxml", :libxml], + ["nokogiri", :nokogiri], + ["rexml/document", :rexml], + ["oga", :oga] + ].freeze + end + + CONTENT_ROOT = "__content__".freeze unless defined?(CONTENT_ROOT) + + unless defined?(PARSING) + float_proc = proc { |float| float.to_f } + datetime_proc = proc { |time| Time.parse(time).utc rescue DateTime.parse(time).utc } # rubocop:disable Style/RescueModifier + + PARSING = { + "symbol" => proc { |symbol| symbol.to_sym }, + "date" => proc { |date| Date.parse(date) }, + "datetime" => datetime_proc, + "dateTime" => datetime_proc, + "integer" => proc { |integer| integer.to_i }, + "float" => float_proc, + "double" => float_proc, + "decimal" => proc { |number| BigDecimal(number) }, + "boolean" => proc { |boolean| !%w[0 false].include?(boolean.strip) }, + "string" => proc { |string| string.to_s }, + "yaml" => proc { |yaml| YAML.load(yaml) rescue yaml }, # rubocop:disable Style/RescueModifier + "base64Binary" => proc { |binary| base64_decode(binary) }, + "binary" => proc { |binary, entity| parse_binary(binary, entity) }, + "file" => proc { |file, entity| parse_file(file, entity) } + }.freeze + end + + unless defined?(TYPE_NAMES) + TYPE_NAMES = { + "Symbol" => "symbol", + "Integer" => "integer", + "BigDecimal" => "decimal", + "Float" => "float", + "TrueClass" => "boolean", + "FalseClass" => "boolean", + "Date" => "date", + "DateTime" => "datetime", + "Time" => "datetime", + "Array" => "array", + "Hash" => "hash" + }.freeze + end + + DISALLOWED_XML_TYPES = %w[symbol yaml].freeze + + DEFAULT_OPTIONS = { + typecast_xml_value: true, + disallowed_types: DISALLOWED_XML_TYPES, + symbolize_keys: false + }.freeze + + class << self + # Get the current parser class. + def parser + return @parser if defined?(@parser) + + self.parser = default_parser + @parser + end + + # The default parser based on what you currently + # have loaded and installed. First checks to see + # if any parsers are already loaded, then checks + # to see which are installed if none are loaded. + def default_parser + return :ox if defined?(::Ox) + return :libxml if defined?(::LibXML) + return :nokogiri if defined?(::Nokogiri) + return :oga if defined?(::Oga) + + REQUIREMENT_MAP.each do |library, parser| + require library + return parser + rescue LoadError + next + end + raise(NoParserError, + "No XML parser detected. If you're using Rubinius and Bundler, try adding an XML parser to your Gemfile (e.g. libxml-ruby, nokogiri, or rubysl-rexml). For more information, see https://github.com/sferik/multi_xml/issues/42.") + end + + # Set the XML parser utilizing a symbol, string, or class. + # Supported by default are: + # + # * :libxml + # * :nokogiri + # * :ox + # * :rexml + # * :oga + def parser=(new_parser) + case new_parser + when String, Symbol + require "multi_xml/parsers/#{new_parser.to_s.downcase}" + @parser = MultiXml::Parsers.const_get(new_parser.to_s.split("_").collect(&:capitalize).join.to_s) + when Class, Module + @parser = new_parser + else + raise("Did not recognize your parser specification. Please specify either a symbol or a class.") + end + end + + # Parse an XML string or IO into Ruby. + # + # Options + # + # :symbolize_keys :: If true, will use symbols instead of strings for the keys. + # + # :disallowed_types :: Types to disallow from being typecasted. Defaults to `['yaml', 'symbol']`. Use `[]` to allow all types. + # + # :typecast_xml_value :: If true, won't typecast values for parsed document + def parse(xml, options = {}) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity + xml ||= "" + + options = DEFAULT_OPTIONS.merge(options) + + xml = xml.strip if xml.respond_to?(:strip) + begin + xml = StringIO.new(xml) unless xml.respond_to?(:read) + + char = xml.getc + return {} if char.nil? + + xml.ungetc(char) + + hash = undasherize_keys(parser.parse(xml) || {}) + hash = typecast_xml_value(hash, options[:disallowed_types]) if options[:typecast_xml_value] + rescue DisallowedTypeError + raise + rescue parser.parse_error => e + raise(ParseError, e.message, e.backtrace) + end + hash = symbolize_keys(hash) if options[:symbolize_keys] + hash + end + + # This module decorates files with the original_filename + # and content_type methods. + module FileLike # :nodoc: + attr_writer :original_filename, :content_type + + def original_filename + @original_filename || "untitled" + end + + def content_type + @content_type || "application/octet-stream" + end + end + + private + + # TODO: Add support for other encodings + def parse_binary(binary, entity) # :nodoc: + case entity["encoding"] + when "base64" + base64_decode(binary) + else + binary + end + end + + def parse_file(file, entity) + f = StringIO.new(base64_decode(file)) + f.extend(FileLike) + f.original_filename = entity["name"] + f.content_type = entity["content_type"] + f + end + + def base64_decode(input) + input.unpack1("m") + end + + def symbolize_keys(params) + case params + when Hash + params.inject({}) do |result, (key, value)| + result.merge(key.to_sym => symbolize_keys(value)) + end + when Array + params.collect { |value| symbolize_keys(value) } + else + params + end + end + + def undasherize_keys(params) + case params + when Hash + params.each_with_object({}) do |(key, value), hash| + hash[key.to_s.tr("-", "_")] = undasherize_keys(value) + hash + end + when Array + params.collect { |value| undasherize_keys(value) } + else + params + end + end + + def typecast_xml_value(value, disallowed_types = nil) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity + disallowed_types ||= DISALLOWED_XML_TYPES + + case value + when Hash + if value.include?("type") && !value["type"].is_a?(Hash) && disallowed_types.include?(value["type"]) + raise(DisallowedTypeError, value["type"]) + end + + if value["type"] == "array" + + # this commented-out suggestion helps to avoid the multiple attribute + # problem, but it breaks when there is only one item in the array. + # + # from: https://github.com/jnunemaker/httparty/issues/102 + # + # _, entries = value.detect { |k, v| k != 'type' && v.is_a?(Array) } + + # This attempt fails to consider the order that the detect method + # retrieves the entries. + # _, entries = value.detect {|key, _| key != 'type'} + + # This approach ignores attribute entries that are not convertable + # to an Array which allows attributes to be ignored. + _, entries = value.detect { |k, v| k != "type" && (v.is_a?(Array) || v.is_a?(Hash)) } + + case entries + when NilClass + [] + when String + [] if entries.strip.empty? + when Array + entries.collect { |entry| typecast_xml_value(entry, disallowed_types) } + when Hash + [typecast_xml_value(entries, disallowed_types)] + else + raise("can't typecast #{entries.class.name}: #{entries.inspect}") + end + + elsif value.key?(CONTENT_ROOT) + content = value[CONTENT_ROOT] + block = PARSING[value["type"]] + if block + if block.arity == 1 + value.delete("type") if PARSING[value["type"]] + if value.keys.size > 1 + value[CONTENT_ROOT] = block.call(content) + value + else + block.call(content) + end + else + block.call(content, value) + end + else + (value.keys.size > 1) ? value : content + end + elsif value["type"] == "string" && value["nil"] != "true" + "" + # blank or nil parsed values are represented by nil + elsif value.empty? || value["nil"] == "true" + nil + # If the type is the only element which makes it then + # this still makes the value nil, except if type is + # a XML node(where type['value'] is a Hash) + elsif value["type"] && value.size == 1 && !value["type"].is_a?(Hash) + nil + else + xml_value = value.each_with_object({}) do |(k, v), hash| + hash[k] = typecast_xml_value(v, disallowed_types) + hash + end + + # Turn {:files => {:file => #} into {:files => #} so it is compatible with + # how multipart uploaded files from HTML appear + (xml_value["file"].is_a?(StringIO)) ? xml_value["file"] : xml_value + end + when Array + value.map! { |i| typecast_xml_value(i, disallowed_types) } + (value.length > 1) ? value : value.first + when String + value + else + raise("can't typecast #{value.class.name}: #{value.inspect}") + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/parsers/libxml.rb b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/parsers/libxml.rb new file mode 100644 index 00000000..79ddc201 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/parsers/libxml.rb @@ -0,0 +1,33 @@ +require "libxml" unless defined?(LibXML) +require "multi_xml/parsers/libxml2_parser" + +module MultiXml + module Parsers + module Libxml # :nodoc: + include Libxml2Parser + extend self + + def parse_error + ::LibXML::XML::Error + end + + def parse(xml) + node_to_hash(LibXML::XML::Parser.io(xml).parse.root) + end + + private + + def each_child(node, &) + node.each_child(&) + end + + def each_attr(node, &) + node.each_attr(&) + end + + def node_name(node) + node.name + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/parsers/libxml2_parser.rb b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/parsers/libxml2_parser.rb new file mode 100644 index 00000000..d94caed9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/parsers/libxml2_parser.rb @@ -0,0 +1,70 @@ +module MultiXml + module Parsers + module Libxml2Parser # :nodoc: + # Convert XML document to hash + # + # node:: + # The XML node object to convert to a hash. + # + # hash:: + # Hash to merge the converted element into. + def node_to_hash(node, hash = {}) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength + node_hash = {MultiXml::CONTENT_ROOT => ""} + + name = node_name(node) + + # Insert node hash into parent hash correctly. + case hash[name] + when Array + hash[name] << node_hash + when Hash + hash[name] = [hash[name], node_hash] + when NilClass + hash[name] = node_hash + end + + # Handle child elements + each_child(node) do |c| + if c.element? + node_to_hash(c, node_hash) + elsif c.text? || c.cdata? + node_hash[MultiXml::CONTENT_ROOT] += c.content + end + end + + # Remove content node if it is empty + node_hash.delete(MultiXml::CONTENT_ROOT) if node_hash[MultiXml::CONTENT_ROOT].strip.empty? + + # Handle attributes + each_attr(node) do |a| + key = node_name(a) + v = node_hash[key] + node_hash[key] = ((v) ? [a.value, v] : a.value) + end + + hash + end + + # Parse an XML Document IO into a simple hash. + # xml:: + # XML Document IO to parse + def parse(_) + raise(NotImplementedError, "inheritor should define #{__method__}") + end + + private + + def each_child(*) + raise(NotImplementedError, "inheritor should define #{__method__}") + end + + def each_attr(*) + raise(NotImplementedError, "inheritor should define #{__method__}") + end + + def node_name(*) + raise(NotImplementedError, "inheritor should define #{__method__}") + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/parsers/nokogiri.rb b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/parsers/nokogiri.rb new file mode 100644 index 00000000..67a02bc5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/parsers/nokogiri.rb @@ -0,0 +1,36 @@ +require "nokogiri" unless defined?(Nokogiri) +require "multi_xml/parsers/libxml2_parser" + +module MultiXml + module Parsers + module Nokogiri # :nodoc: + include Libxml2Parser + extend self + + def parse_error + ::Nokogiri::XML::SyntaxError + end + + def parse(xml) + doc = ::Nokogiri::XML(xml) + raise(doc.errors.first) unless doc.errors.empty? + + node_to_hash(doc.root) + end + + private + + def each_child(node, &) + node.children.each(&) + end + + def each_attr(node, &) + node.attribute_nodes.each(&) + end + + def node_name(node) + node.node_name + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/parsers/oga.rb b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/parsers/oga.rb new file mode 100644 index 00000000..9d30b2ac --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/parsers/oga.rb @@ -0,0 +1,71 @@ +require "oga" unless defined?(Oga) +require "multi_xml/parsers/libxml2_parser" + +module MultiXml + module Parsers + module Oga # :nodoc: + include Libxml2Parser + extend self + + def parse_error + LL::ParserError + end + + def parse(io) + document = ::Oga.parse_xml(io) + node_to_hash(document.children[0]) + end + + def node_to_hash(node, hash = {}) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength + node_hash = {MultiXml::CONTENT_ROOT => ""} + + name = node_name(node) + + # Insert node hash into parent hash correctly. + case hash[name] + when Array + hash[name] << node_hash + when Hash + hash[name] = [hash[name], node_hash] + when NilClass + hash[name] = node_hash + end + + # Handle child elements + each_child(node) do |c| + if c.is_a?(::Oga::XML::Element) + node_to_hash(c, node_hash) + elsif c.is_a?(::Oga::XML::Text) || c.is_a?(::Oga::XML::Cdata) + node_hash[MultiXml::CONTENT_ROOT] += c.text + end + end + + # Remove content node if it is empty + node_hash.delete(MultiXml::CONTENT_ROOT) if node_hash[MultiXml::CONTENT_ROOT].strip.empty? + + # Handle attributes + each_attr(node) do |a| + key = node_name(a) + v = node_hash[key] + node_hash[key] = ((v) ? [a.value, v] : a.value) + end + + hash + end + + private + + def each_child(node, &) + node.children.each(&) + end + + def each_attr(node, &) + node.attributes.each(&) + end + + def node_name(node) + node.name + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/parsers/ox.rb b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/parsers/ox.rb new file mode 100644 index 00000000..2679a060 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/parsers/ox.rb @@ -0,0 +1,91 @@ +require "ox" unless defined?(Ox) + +# Each MultiXml parser is expected to parse an XML document into a Hash. The +# conversion rules are: +# +# - Each document starts out as an empty Hash. +# +# - Reading an element created an entry in the parent Hash that has a key of +# the element name and a value of a Hash with attributes as key value +# pairs. Children are added as described by this rule. +# +# - Text and CDATE is stored in the parent element Hash with a key of +# MultiXml::CONTENT_ROOT and a value of the text itself. +# +# - If a key already exists in the Hash then the value associated with the key +# is converted to an Array with the old and new value in it. +# +# - Other elements such as the xml prolog, doctype, and comments are ignored. +# + +module MultiXml + module Parsers + module Ox # :nodoc: + module_function + + def parse_error + Exception + end + + def parse(io) + handler = Handler.new + ::Ox.sax_parse(handler, io, convert_special: true, skip: :skip_return) + handler.doc + end + + class Handler + attr_accessor :stack + + def initialize + @stack = [] + end + + def doc + @stack[0] + end + + def attr(name, value) + append(name, value) unless @stack.empty? + end + + def text(value) + append(MultiXml::CONTENT_ROOT, value) + end + + def cdata(value) + append(MultiXml::CONTENT_ROOT, value) + end + + def start_element(name) + @stack.push({}) if @stack.empty? + h = {} + append(name, h) + @stack.push(h) + end + + def end_element(_) + @stack.pop + end + + def error(message, line, column) + raise(StandardError, "#{message} at #{line}:#{column}") + end + + def append(key, value) + key = key.to_s + h = @stack.last + if h.key?(key) + v = h[key] + if v.is_a?(Array) + v << value + else + h[key] = [v, value] + end + else + h[key] = value + end + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/parsers/rexml.rb b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/parsers/rexml.rb new file mode 100644 index 00000000..2c09feaf --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/parsers/rexml.rb @@ -0,0 +1,113 @@ +require "rexml/document" unless defined?(REXML::Document) + +module MultiXml + module Parsers + module Rexml # :nodoc: + extend self + + def parse_error + ::REXML::ParseException + end + + # Parse an XML Document IO into a simple hash using REXML + # + # xml:: + # XML Document IO to parse + def parse(xml) + doc = REXML::Document.new(xml) + raise(REXML::ParseException, "The document #{doc.to_s.inspect} does not have a valid root") unless doc.root + + merge_element!({}, doc.root) + end + + private + + # Convert an XML element and merge into the hash + # + # hash:: + # Hash to merge the converted element into. + # element:: + # XML element to merge into hash + def merge_element!(hash, element) + merge!(hash, element.name, collapse(element)) + end + + # Actually converts an XML document element into a data structure. + # + # element:: + # The document element to be collapsed. + def collapse(element) + hash = get_attributes(element) + + if element.has_elements? + element.each_element { |child| merge_element!(hash, child) } + merge_texts!(hash, element) unless empty_content?(element) + hash + else + merge_texts!(hash, element) + end + end + + # Merge all the texts of an element into the hash + # + # hash:: + # Hash to add the converted element to. + # element:: + # XML element whose texts are to me merged into the hash + def merge_texts!(hash, element) + if element.has_text? + # must use value to prevent double-escaping + texts = element.texts.map(&:value).join + merge!(hash, MultiXml::CONTENT_ROOT, texts) + else + hash + end + end + + # Adds a new key/value pair to an existing Hash. If the key to be added + # already exists and the existing value associated with key is not + # an Array, it will be wrapped in an Array. Then the new value is + # appended to that Array. + # + # hash:: + # Hash to add key/value pair to. + # key:: + # Key to be added. + # value:: + # Value to be associated with key. + def merge!(hash, key, value) + if hash.key?(key) + if hash[key].instance_of?(Array) + hash[key] << value + else + hash[key] = [hash[key], value] + end + elsif value.instance_of?(Array) + hash[key] = [value] + else + hash[key] = value + end + hash + end + + # Converts the attributes array of an XML element into a hash. + # Returns an empty Hash if node has no attributes. + # + # element:: + # XML element to extract attributes from. + def get_attributes(element) + attributes = {} + element.attributes.each { |n, v| attributes[n] = v } + attributes + end + + # Determines if a document element has text content + # + # element:: + # XML element to be checked. + def empty_content?(element) + element.texts.join.strip.empty? + end + end + end +end diff --git a/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/version.rb b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/version.rb new file mode 100644 index 00000000..71d2ab16 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/multi_xml-0.7.2/lib/multi_xml/version.rb @@ -0,0 +1,3 @@ +module MultiXml + VERSION = Gem::Version.create("0.7.2") +end diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/Gemfile b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/Gemfile new file mode 100644 index 00000000..affe3ac2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/Gemfile @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gemspec + +group :development do + # bootstrapping + gem "bundler", "~> 2.3" + gem "rake", "13.2.1" + + # building extensions + gem "rake-compiler", "1.2.8" + gem "rake-compiler-dock", "1.7.0" + + # parser generator + gem "rexical", "1.0.8" + + # tests + gem "minitest", "5.25.4" + gem "minitest-parallel_fork", "2.0.0" + gem "ruby_memcheck", "3.0.0" + gem "rubyzip", "~> 2.3.2" + gem "simplecov", "= 0.21.2" + + # rubocop + unless RUBY_PLATFORM == "java" + gem "standard", "1.43.0" + gem "rubocop-minitest", "0.36.0" + gem "rubocop-packaging", "0.5.2" + gem "rubocop-rake", "0.6.0" + end +end + +# If Psych doesn't build, you can disable this group locally by running +# `bundle config set --local without rdoc` +# Then re-run `bundle install`. +group :rdoc do + gem "rdoc", "6.10.0" unless RUBY_PLATFORM == "java" || ENV["CI"] +end diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/LICENSE-DEPENDENCIES.md b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/LICENSE-DEPENDENCIES.md new file mode 100644 index 00000000..1e950b6e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/LICENSE-DEPENDENCIES.md @@ -0,0 +1,2224 @@ +# Vendored Dependency Licenses + +Nokogiri ships with some third party dependencies, which are listed here along with their licenses. + +Note that this document is broken into multiple sections, each of which describes the dependencies of a different "platform release" of Nokogiri. + + + + + +- [Platform Releases](#platform-releases) + * [Default platform release ("ruby")](#default-platform-release-ruby) + * [Native LinuxⓇ platform releases ("x86_64-linux", "aarch64-linux", and "arm-linux")](#native-linux%E2%93%A1-platform-releases-x86_64-linux-aarch64-linux-and-arm-linux) + * [Native Darwin (macOSⓇ) platform releases ("x86_64-darwin" and "arm64-darwin")](#native-darwin-macos%E2%93%A1-platform-releases-x86_64-darwin-and-arm64-darwin) + * [Native WindowsⓇ platform releases ("x64-mingw-ucrt")](#native-windows%E2%93%A1-platform-releases-x64-mingw-ucrt) + * [JavaⓇ (JRuby) platform release ("java")](#java%E2%93%A1-jruby-platform-release-java) +- [Appendix: Dependencies' License Texts](#appendix-dependencies-license-texts) + * [libgumbo](#libgumbo) + * [libxml2](#libxml2) + * [libxslt](#libxslt) + * [zlib](#zlib) + * [libiconv](#libiconv) + * [isorelax:isorelax](#isorelaxisorelax) + * [net.sf.saxon:Saxon-HE](#netsfsaxonsaxon-he) + * [net.sourceforge.htmlunit:neko-htmlunit](#netsourceforgehtmlunitneko-htmlunit) + * [nu.validator:jing](#nuvalidatorjing) + * [org.nokogiri:nekodtd](#orgnokogirinekodtd) + * [xalan:serializer and xalan:xalan](#xalanserializer-and-xalanxalan) + * [xerces:xercesImpl](#xercesxercesimpl) + * [xml-apis:xml-apis](#xml-apisxml-apis) + + + +Anyone consuming this file via license-tracking software should endeavor to understand which gem file you're downloading and using, so as not to misinterpret the contents of this file and the licenses of the software being distributed. + +You can double-check the dependencies in your gem file by examining the output of `nokogiri -v` after installation, which will emit the complete set of libraries in use (for versions `>= 1.11.0.rc4`). + +In particular, I'm sure somebody's lawyer, somewhere, is going to freak out that the LGPL appears in this file; and so I'd like to take special note that the dependency covered by LGPL, `libiconv`, is only being redistributed in the native Windows and native Darwin platform releases. It's not present in default, JavaⓇ, or native LinuxⓇ releases. + + +## Platform Releases + +### Default platform release ("ruby") + +The default platform release distributes the following dependencies in source form: + +* [libxml2](#libxml2) +* [libxslt](#libxslt) +* [libgumbo](#libgumbo) + +This distribution can be identified by inspecting the included Gem::Specification, which will have the value "ruby" for its "platform" attribute. + + +### Native LinuxⓇ platform releases ("x86_64-linux", "aarch64-linux", and "arm-linux") + +The native LinuxⓇ platform release distributes the following dependencies in source form: + +* [libxml2](#libxml2) +* [libxslt](#libxslt) +* [libgumbo](#libgumbo) +* [zlib](#zlib) + +This distribution can be identified by inspecting the included Gem::Specification, which will have a value similar to "x86_64-linux" or "aarch64-linux" for its "platform.cpu" attribute. + + +### Native Darwin (macOSⓇ) platform releases ("x86_64-darwin" and "arm64-darwin") + +The native Darwin platform release distributes the following dependencies in source form: + +* [libxml2](#libxml2) +* [libxslt](#libxslt) +* [libgumbo](#libgumbo) +* [zlib](#zlib) +* [libiconv](#libiconv) + +This distribution can be identified by inspecting the included Gem::Specification, which will have a value similar to "x86_64-darwin" or "arm64-darwin" for its "platform.cpu" attribute. Darwin is also known more familiarly as "OSX" or "macOSⓇ" and is the operating system for many AppleⓇ computers. + + +### Native WindowsⓇ platform releases ("x64-mingw-ucrt") + +The native WindowsⓇ platform release distributes the following dependencies in source form: + +* [libxml2](#libxml2) +* [libxslt](#libxslt) +* [libgumbo](#libgumbo) +* [zlib](#zlib) +* [libiconv](#libiconv) + +This distribution can be identified by inspecting the included Gem::Specification, which will have a value similar to "x64-mingw-ucrt" for its "platform.cpu" attribute. + + +### JavaⓇ (JRuby) platform release ("java") + +The Java platform release distributes the following dependencies as compiled jar files: + +* [isorelax:isorelax](#isorelaxisorelax) +* [net.sf.saxon:Saxon-HE](#netsfsaxonsaxon-he) +* [net.sourceforge.htmlunit:neko-htmlunit](#netsourceforgehtmlunitneko-htmlunit) +* [nu.validator:jing](#nuvalidatorjing) +* [org.nokogiri:nekodtd](#orgnokogirinekodtd) +* [xalan:serializer and xalan:xalan](#xalanserializer-and-xalanxalan) +* [xerces:xercesImpl](#xercesxercesimpl) +* [xml-apis:xml-apis](#xml-apisxml-apis) + +This distribution can be identified by inspecting the included Gem::Specification, which will have the value "java" for its "platform.os" attribute. + + +## Appendix: Dependencies' License Texts + +This section contains a subsection for each potentially-distributed dependency, which includes the name of the license and the license text. + +Please see previous sections to understand which of these potential dependencies is actually distributed in the gem file you're downloading and using. + + +### libgumbo + +Apache 2.0 + +https://github.com/sparklemotion/nokogiri/blob/main/gumbo-parser/src/README.md + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + +### libxml2 + +MIT + +http://xmlsoft.org/ + + Except where otherwise noted in the source code (e.g. the files hash.c, + list.c and the trio files, which are covered by a similar licence but + with different Copyright notices) all the files are: + + Copyright (C) 1998-2012 Daniel Veillard. All Rights Reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is fur- + nished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIT- + NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + +### libxslt + +MIT + +http://xmlsoft.org/libxslt/ + + Licence for libxslt except libexslt + ---------------------------------------------------------------------- + Copyright (C) 2001-2002 Daniel Veillard. All Rights Reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is fur- + nished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIT- + NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + DANIEL VEILLARD BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CON- + NECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Daniel Veillard shall not + be used in advertising or otherwise to promote the sale, use or other deal- + ings in this Software without prior written authorization from him. + + ---------------------------------------------------------------------- + + Licence for libexslt + ---------------------------------------------------------------------- + Copyright (C) 2001-2002 Thomas Broyer, Charlie Bozeman and Daniel Veillard. + All Rights Reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is fur- + nished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIT- + NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CON- + NECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of the authors shall not + be used in advertising or otherwise to promote the sale, use or other deal- + ings in this Software without prior written authorization from him. + ---------------------------------------------------------------------- + + +### zlib + +zlib license + +http://www.zlib.net/zlib_license.html + + Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + + +### libiconv + +LGPL + +https://www.gnu.org/software/libiconv/ + + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + [This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your + freedom to share and change it. By contrast, the GNU General Public + Licenses are intended to guarantee your freedom to share and change + free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some + specially designated Free Software Foundation software, and to any + other libraries whose authors decide to use it. You can use it for + your libraries, too. + + When we speak of free software, we are referring to freedom, not + price. Our General Public Licenses are designed to make sure that you + have the freedom to distribute copies of free software (and charge for + this service if you wish), that you receive source code or can get it + if you want it, that you can change the software or use pieces of it + in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid + anyone to deny you these rights or to ask you to surrender the rights. + These restrictions translate to certain responsibilities for you if + you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis + or for a fee, you must give the recipients all the rights that we gave + you. You must make sure that they, too, receive or can get the source + code. If you link a program with the library, you must provide + complete object files to the recipients so that they can relink them + with the library, after making changes to the library and recompiling + it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright + the library, and (2) offer you this license which gives you legal + permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain + that everyone understands that there is no warranty for this free + library. If the library is modified by someone else and passed on, we + want its recipients to know that what they have is not the original + version, so that any problems introduced by others will not reflect on + the original authors' reputations. + + Finally, any free program is threatened constantly by software + patents. We wish to avoid the danger that companies distributing free + software will individually obtain patent licenses, thus in effect + transforming the program into proprietary software. To prevent this, + we have made it clear that any patent must be licensed for everyone's + free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary + GNU General Public License, which was designed for utility programs. This + license, the GNU Library General Public License, applies to certain + designated libraries. This license is quite different from the ordinary + one; be sure to read it in full, and don't assume that anything in it is + the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that + they blur the distinction we usually make between modifying or adding to a + program and simply using it. Linking a program with a library, without + changing the library, is in some sense simply using the library, and is + analogous to running a utility program or application program. However, in + a textual and legal sense, the linked executable is a combined work, a + derivative of the original library, and the ordinary General Public License + treats it as such. + + Because of this blurred distinction, using the ordinary General + Public License for libraries did not effectively promote software + sharing, because most developers did not use the libraries. We + concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the + users of those programs of all benefit from the free status of the + libraries themselves. This Library General Public License is intended to + permit developers of non-free programs to use free libraries, while + preserving your freedom as a user of such programs to change the free + libraries that are incorporated in them. (We have not seen how to achieve + this as regards changes in header files, but we have achieved it as regards + changes in the actual functions of the Library.) The hope is that this + will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and + modification follow. Pay close attention to the difference between a + "work based on the library" and a "work that uses the library". The + former contains code derived from the library, while the latter only + works together with the library. + + Note that it is possible for a library to be covered by the ordinary + General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which + contains a notice placed by the copyright holder or other authorized + party saying it may be distributed under the terms of this Library + General Public License (also called "this License"). Each licensee is + addressed as "you". + + A "library" means a collection of software functions and/or data + prepared so as to be conveniently linked with application programs + (which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work + which has been distributed under these terms. A "work based on the + Library" means either the Library or any derivative work under + copyright law: that is to say, a work containing the Library or a + portion of it, either verbatim or with modifications and/or translated + straightforwardly into another language. (Hereinafter, translation is + included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for + making modifications to it. For a library, complete source code means + all the source code for all modules it contains, plus any associated + interface definition files, plus the scripts used to control compilation + and installation of the library. + + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of + running a program using the Library is not restricted, and output from + such a program is covered only if its contents constitute a work based + on the Library (independent of the use of the Library in a tool for + writing it). Whether that is true depends on what the Library does + and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's + complete source code as you receive it, in any medium, provided that + you conspicuously and appropriately publish on each copy an + appropriate copyright notice and disclaimer of warranty; keep intact + all the notices that refer to this License and to the absence of any + warranty; and distribute a copy of this License along with the + Library. + + You may charge a fee for the physical act of transferring a copy, + and you may at your option offer warranty protection in exchange for a + fee. + + 2. You may modify your copy or copies of the Library or any portion + of it, thus forming a work based on the Library, and copy and + distribute such modifications or work under the terms of Section 1 + above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the Library, + and can be reasonably considered independent and separate works in + themselves, then this License, and its terms, do not apply to those + sections when you distribute them as separate works. But when you + distribute the same sections as part of a whole which is a work based + on the Library, the distribution of the whole must be on the terms of + this License, whose permissions for other licensees extend to the + entire whole, and thus to each and every part regardless of who wrote + it. + + Thus, it is not the intent of this section to claim rights or contest + your rights to work written entirely by you; rather, the intent is to + exercise the right to control the distribution of derivative or + collective works based on the Library. + + In addition, mere aggregation of another work not based on the Library + with the Library (or with a work based on the Library) on a volume of + a storage or distribution medium does not bring the other work under + the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public + License instead of this License to a given copy of the Library. To do + this, you must alter all the notices that refer to this License, so + that they refer to the ordinary GNU General Public License, version 2, + instead of to this License. (If a newer version than version 2 of the + ordinary GNU General Public License has appeared, then you can specify + that version instead if you wish.) Do not make any other change in + these notices. + + Once this change is made in a given copy, it is irreversible for + that copy, so the ordinary GNU General Public License applies to all + subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of + the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or + derivative of it, under Section 2) in object code or executable form + under the terms of Sections 1 and 2 above provided that you accompany + it with the complete corresponding machine-readable source code, which + must be distributed under the terms of Sections 1 and 2 above on a + medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy + from a designated place, then offering equivalent access to copy the + source code from the same place satisfies the requirement to + distribute the source code, even though third parties are not + compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the + Library, but is designed to work with the Library by being compiled or + linked with it, is called a "work that uses the Library". Such a + work, in isolation, is not a derivative work of the Library, and + therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library + creates an executable that is a derivative of the Library (because it + contains portions of the Library), rather than a "work that uses the + library". The executable is therefore covered by this License. + Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file + that is part of the Library, the object code for the work may be a + derivative work of the Library even though the source code is not. + Whether this is true is especially significant if the work can be + linked without the Library, or if the work is itself a library. The + threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data + structure layouts and accessors, and small macros and small inline + functions (ten lines or less in length), then the use of the object + file is unrestricted, regardless of whether it is legally a derivative + work. (Executables containing this object code plus portions of the + Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may + distribute the object code for the work under the terms of Section 6. + Any executables containing that work also fall under Section 6, + whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or + link a "work that uses the Library" with the Library to produce a + work containing portions of the Library, and distribute that work + under terms of your choice, provided that the terms permit + modification of the work for the customer's own use and reverse + engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the + Library is used in it and that the Library and its use are covered by + this License. You must supply a copy of this License. If the work + during execution displays copyright notices, you must include the + copyright notice for the Library among them, as well as a reference + directing the user to the copy of this License. Also, you must do one + of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the + Library" must include any data and utility programs needed for + reproducing the executable from it. However, as a special exception, + the source code distributed need not include anything that is normally + distributed (in either source or binary form) with the major + components (compiler, kernel, and so on) of the operating system on + which the executable runs, unless that component itself accompanies + the executable. + + It may happen that this requirement contradicts the license + restrictions of other proprietary libraries that do not normally + accompany the operating system. Such a contradiction means you cannot + use both them and the Library together in an executable that you + distribute. + + 7. You may place library facilities that are a work based on the + Library side-by-side in a single library together with other library + facilities not covered by this License, and distribute such a combined + library, provided that the separate distribution of the work based on + the Library and of the other library facilities is otherwise + permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute + the Library except as expressly provided under this License. Any + attempt otherwise to copy, modify, sublicense, link with, or + distribute the Library is void, and will automatically terminate your + rights under this License. However, parties who have received copies, + or rights, from you under this License will not have their licenses + terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not + signed it. However, nothing else grants you permission to modify or + distribute the Library or its derivative works. These actions are + prohibited by law if you do not accept this License. Therefore, by + modifying or distributing the Library (or any work based on the + Library), you indicate your acceptance of this License to do so, and + all its terms and conditions for copying, distributing or modifying + the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the + Library), the recipient automatically receives a license from the + original licensor to copy, distribute, link with or modify the Library + subject to these terms and conditions. You may not impose any further + restrictions on the recipients' exercise of the rights granted herein. + You are not responsible for enforcing compliance by third parties to + this License. + + 11. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot + distribute so as to satisfy simultaneously your obligations under this + License and any other pertinent obligations, then as a consequence you + may not distribute the Library at all. For example, if a patent + license would not permit royalty-free redistribution of the Library by + all those who receive copies directly or indirectly through you, then + the only way you could satisfy both it and this License would be to + refrain entirely from distribution of the Library. + + If any portion of this section is held invalid or unenforceable under any + particular circumstance, the balance of the section is intended to apply, + and the section as a whole is intended to apply in other circumstances. + + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any + such claims; this section has the sole purpose of protecting the + integrity of the free software distribution system which is + implemented by public license practices. Many people have made + generous contributions to the wide range of software distributed + through that system in reliance on consistent application of that + system; it is up to the author/donor to decide if he or she is willing + to distribute software through any other system and a licensee cannot + impose that choice. + + This section is intended to make thoroughly clear what is believed to + be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in + certain countries either by patents or by copyrighted interfaces, the + original copyright holder who places the Library under this License may add + an explicit geographical distribution limitation excluding those countries, + so that distribution is permitted only in or among countries not thus + excluded. In such case, this License incorporates the limitation as if + written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new + versions of the Library General Public License from time to time. + Such new versions will be similar in spirit to the present version, + but may differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the Library + specifies a version number of this License which applies to it and + "any later version", you have the option of following the terms and + conditions either of that version or of any later version published by + the Free Software Foundation. If the Library does not specify a + license version number, you may choose any version ever published by + the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free + programs whose distribution conditions are incompatible with these, + write to the author to ask for permission. For software which is + copyrighted by the Free Software Foundation, write to the Free + Software Foundation; we sometimes make exceptions for this. Our + decision will be guided by the two goals of preserving the free status + of all derivatives of our free software and of promoting the sharing + and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO + WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. + EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR + OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE + LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME + THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN + WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY + AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU + FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR + CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE + LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING + RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A + FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF + SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + DAMAGES. + + END OF TERMS AND CONDITIONS + + +### isorelax:isorelax + +MIT + +http://iso-relax.sourceforge.net/ + + Copyright (c) 2001-2002, SourceForge ISO-RELAX Project (ASAMI + Tomoharu, Daisuke Okajima, Kohsuke Kawaguchi, and MURATA Makoto) + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +### net.sf.saxon:Saxon-HE + +MPL 2.0 + +http://www.saxonica.com/ + + Mozilla Public License Version 2.0 + ================================== + + 1. Definitions + -------------- + + 1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + + 1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + + 1.3. "Contribution" + means Covered Software of a particular Contributor. + + 1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + + 1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + + 1.6. "Executable Form" + means any form of the work other than Source Code Form. + + 1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + + 1.8. "License" + means this document. + + 1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + + 1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + + 1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + + 1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + + 1.13. "Source Code Form" + means the form of the work preferred for making modifications. + + 1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + + 2. License Grants and Conditions + -------------------------------- + + 2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + (a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + + (b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + + 2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. + + 2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: + + (a) for any code that a Contributor has removed from Covered Software; + or + + (b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + (c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + + 2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). + + 2.5. Representation + + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights + to grant the rights to its Contributions conveyed by this License. + + 2.6. Fair Use + + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. + + 2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted + in Section 2.1. + + 3. Responsibilities + ------------------- + + 3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. + + 3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + (a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + + (b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + + 3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). + + 3.4. Notices + + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, + or limitations of liability) contained within the Source Code Form of + the Covered Software, except that You may alter any license notices to + the extent required to remedy known factual inaccuracies. + + 3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + + 4. Inability to Comply Due to Statute or Regulation + --------------------------------------------------- + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Software due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description must + be placed in a text file included with all distributions of the Covered + Software under this License. Except to the extent prohibited by statute + or regulation, such description must be sufficiently detailed for a + recipient of ordinary skill to be able to understand it. + + 5. Termination + -------------- + + 5.1. The rights granted under this License will terminate automatically + if You fail to comply with any of its terms. However, if You become + compliant, then the rights granted under this License from a particular + Contributor are reinstated (a) provisionally, unless and until such + Contributor explicitly and finally terminates Your grants, and (b) on an + ongoing basis, if such Contributor fails to notify You of the + non-compliance by some reasonable means prior to 60 days after You have + come back into compliance. Moreover, Your grants from a particular + Contributor are reinstated on an ongoing basis if such Contributor + notifies You of the non-compliance by some reasonable means, this is the + first time You have received notice of non-compliance with this License + from such Contributor, and You become compliant prior to 30 days after + Your receipt of the notice. + + 5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. + + 5.3. In the event of termination under Sections 5.1 or 5.2 above, all + end user license agreements (excluding distributors and resellers) which + have been validly granted by You or Your distributors under this License + prior to termination shall survive termination. + + ************************************************************************ + * * + * 6. Disclaimer of Warranty * + * ------------------------- * + * * + * Covered Software is provided under this License on an "as is" * + * basis, without warranty of any kind, either expressed, implied, or * + * statutory, including, without limitation, warranties that the * + * Covered Software is free of defects, merchantable, fit for a * + * particular purpose or non-infringing. The entire risk as to the * + * quality and performance of the Covered Software is with You. * + * Should any Covered Software prove defective in any respect, You * + * (not any Contributor) assume the cost of any necessary servicing, * + * repair, or correction. This disclaimer of warranty constitutes an * + * essential part of this License. No use of any Covered Software is * + * authorized under this License except under this disclaimer. * + * * + ************************************************************************ + + ************************************************************************ + * * + * 7. Limitation of Liability * + * -------------------------- * + * * + * Under no circumstances and under no legal theory, whether tort * + * (including negligence), contract, or otherwise, shall any * + * Contributor, or anyone who distributes Covered Software as * + * permitted above, be liable to You for any direct, indirect, * + * special, incidental, or consequential damages of any character * + * including, without limitation, damages for lost profits, loss of * + * goodwill, work stoppage, computer failure or malfunction, or any * + * and all other commercial damages or losses, even if such party * + * shall have been informed of the possibility of such damages. This * + * limitation of liability shall not apply to liability for death or * + * personal injury resulting from such party's negligence to the * + * extent applicable law prohibits such limitation. Some * + * jurisdictions do not allow the exclusion or limitation of * + * incidental or consequential damages, so this exclusion and * + * limitation may not apply to You. * + * * + ************************************************************************ + + 8. Litigation + ------------- + + Any litigation relating to this License may be brought only in the + courts of a jurisdiction where the defendant maintains its principal + place of business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. + Nothing in this Section shall prevent a party's ability to bring + cross-claims or counter-claims. + + 9. Miscellaneous + ---------------- + + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides + that the language of a contract shall be construed against the drafter + shall not be used to construe this License against a Contributor. + + 10. Versions of the License + --------------------------- + + 10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + + 10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. + + 10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). + + 10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses + + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + + +### net.sourceforge.htmlunit:neko-htmlunit + +Apache 2.0 + +https://github.com/HtmlUnit/htmlunit-neko + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + +### nu.validator:jing + +BSD-3-Clause + +http://www.thaiopensource.com/relaxng/jing.html + + Copyright (c) 2001-2003 Thai Open Source Software Center Ltd + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of the Thai Open Source Software Center Ltd nor + the names of its contributors may be used to endorse or promote + products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + +### org.nokogiri:nekodtd + +Apache 2.0 + +https://github.com/sparklemotion/nekodtd + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + +### xalan:serializer and xalan:xalan + +Apache 2.0 + +https://xml.apache.org/xalan-j/ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + +### xerces:xercesImpl + +Apache 2.0 + +https://xerces.apache.org/xerces2-j/ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + +### xml-apis:xml-apis + +Apache 2.0 + +https://xerces.apache.org/xml-commons/ + + Unless otherwise noted all files in XML Commons are covered under the + Apache License Version 2.0. Please read the LICENSE and NOTICE files. + + XML Commons contains some software and documentation that is covered + under a number of different licenses. This applies particularly to the + xml-commons/java/external/ directory. Most files under + xml-commons/java/external/ are covered under their respective + LICENSE.*.txt files; see the matching README.*.txt files for + descriptions. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/LICENSE.md b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/LICENSE.md new file mode 100644 index 00000000..b649dd87 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/LICENSE.md @@ -0,0 +1,9 @@ +The MIT License + +Copyright 2008 -- 2023 by Mike Dalessio, Aaron Patterson, Yoko Harada, Akinori MUSHA, John Shahid, Karol Bucek, Sam Ruby, Craig Barnes, Stephen Checkoway, Lars Kanis, Sergio Arbeo, Timothy Elliott, Nobuyoshi Nakada, Charles Nutter, Patrick Mahoney. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/README.md b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/README.md new file mode 100644 index 00000000..df466f4a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/README.md @@ -0,0 +1,293 @@ +

    + +# Nokogiri + +Nokogiri (鋸) makes it easy and painless to work with XML and HTML from Ruby. It provides a sensible, easy-to-understand API for [reading](https://nokogiri.org/tutorials/parsing_an_html_xml_document.html), writing, [modifying](https://nokogiri.org/tutorials/modifying_an_html_xml_document.html), and [querying](https://nokogiri.org/tutorials/searching_a_xml_html_document.html) documents. It is fast and standards-compliant by relying on native parsers like libxml2, libgumbo, and xerces. + +## Guiding Principles + +Some guiding principles Nokogiri tries to follow: + +- be secure-by-default by treating all documents as **untrusted** by default +- be a **thin-as-reasonable layer** on top of the underlying parsers, and don't attempt to fix behavioral differences between the parsers + + +## Features Overview + +- DOM Parser for XML, HTML4, and HTML5 +- SAX Parser for XML and HTML4 +- Push Parser for XML and HTML4 +- Document search via XPath 1.0 +- Document search via CSS3 selectors, with some jquery-like extensions +- XSD Schema validation +- XSLT transformation +- "Builder" DSL for XML and HTML documents + + +## Status + +[![Github Actions CI](https://github.com/sparklemotion/nokogiri/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/sparklemotion/nokogiri/actions/workflows/ci.yml) +[![Appveyor CI](https://ci.appveyor.com/api/projects/status/xj2pqwvlxwuwgr06/branch/main?svg=true)](https://ci.appveyor.com/project/flavorjones/nokogiri/branch/main) + +[![Gem Version](https://badge.fury.io/rb/nokogiri.svg)](https://rubygems.org/gems/nokogiri) +[![SemVer compatibility](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=nokogiri&package-manager=bundler&previous-version=1.11.7&new-version=1.12.5)](https://docs.github.com/en/code-security/supply-chain-security/managing-vulnerabilities-in-your-projects-dependencies/about-dependabot-security-updates#about-compatibility-scores) + +[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/5344/badge)](https://bestpractices.coreinfrastructure.org/projects/5344) +[![Tidelift dependencies](https://tidelift.com/badges/package/rubygems/nokogiri)](https://tidelift.com/subscription/pkg/rubygems-nokogiri?utm_source=rubygems-nokogiri&utm_medium=referral&utm_campaign=readme) + + +## Support, Getting Help, and Reporting Issues + +All official documentation is posted at https://nokogiri.org (the source for which is at https://github.com/sparklemotion/nokogiri.org/, and we welcome contributions). + +### Reading + +Your first stops for learning more about Nokogiri should be: + +- [API Documentation](https://nokogiri.org/rdoc/index.html) +- [Tutorials](https://nokogiri.org/tutorials/toc.html) +- An excellent community-maintained [Cheat Sheet](https://github.com/sparklemotion/nokogiri/wiki/Cheat-sheet) + + +### Ask For Help + +There are a few ways to ask exploratory questions: + +- The Nokogiri mailing list is active at https://groups.google.com/group/nokogiri-talk +- Open an issue using the "Help Request" template at https://github.com/sparklemotion/nokogiri/issues +- Open a discussion at https://github.com/sparklemotion/nokogiri/discussions + +Please do not mail the maintainers at their personal addresses. + + +### Report A Bug + +The Nokogiri bug tracker is at https://github.com/sparklemotion/nokogiri/issues + +Please use the "Bug Report" or "Installation Difficulties" templates. + + +### Security and Vulnerability Reporting + +Please report vulnerabilities at https://hackerone.com/nokogiri + +Full information and description of our security policy is in [`SECURITY.md`](SECURITY.md) + + +### Semantic Versioning Policy + +Nokogiri follows [Semantic Versioning](https://semver.org/) (since 2017 or so). [![Dependabot's SemVer compatibility score for Nokogiri](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=nokogiri&package-manager=bundler&previous-version=1.11.7&new-version=1.12.5)](https://docs.github.com/en/code-security/supply-chain-security/managing-vulnerabilities-in-your-projects-dependencies/about-dependabot-security-updates#about-compatibility-scores) + +We bump `Major.Minor.Patch` versions following this guidance: + +`Major`: (we've never done this) + +- Significant backwards-incompatible changes to the public API that would require rewriting existing application code. +- Some examples of backwards-incompatible changes we might someday consider for a Major release are at [`ROADMAP.md`](ROADMAP.md). + +`Minor`: + +- Features and bugfixes. +- Updating packaged libraries for non-security-related reasons. +- Dropping support for EOLed Ruby versions. [Some folks find this objectionable](https://github.com/sparklemotion/nokogiri/issues/1568), but [SemVer says this is OK if the public API hasn't changed](https://semver.org/#what-should-i-do-if-i-update-my-own-dependencies-without-changing-the-public-api). +- Backwards-incompatible changes to internal or private methods and constants. These are detailed in the "Changes" section of each changelog entry. +- Removal of deprecated methods or parameters, after a generous transition period; usually when those methods or parameters are rarely-used or dangerous to the user. Essentially, removals that do not justify a major version bump. + + +`Patch`: + +- Bugfixes. +- Security updates. +- Updating packaged libraries for security-related reasons. + + +### Sponsorship + +You can help sponsor the maintainers of this software through one of these organizations: + +- [github.com/sponsors/flavorjones](https://github.com/sponsors/flavorjones) +- [opencollective.com/nokogiri](https://opencollective.com/nokogiri) +- [tidelift.com/subscription/pkg/rubygems-nokogiri](https://tidelift.com/subscription/pkg/rubygems-nokogiri?utm_source=rubygems-nokogiri&utm_medium=referral&utm_campaign=readme) + + +## Installation + +Requirements: + +- Ruby >= 3.1 +- JRuby >= 9.4.0.0 + +If you are compiling the native extension against a system version of libxml2: + +- libxml2 >= 2.9.2 (recommended >= 2.12.0) + + +### Native Gems: Faster, more reliable installation + +"Native gems" contain pre-compiled libraries for a specific machine architecture. On supported platforms, this removes the need for compiling the C extension and the packaged libraries, or for system dependencies to exist. This results in **much faster installation** and **more reliable installation**, which as you probably know are the biggest headaches for Nokogiri users. + +### Supported Platforms + +Nokogiri ships pre-compiled, "native" gems for the following platforms: + +- Linux: + - `x86_64-linux-gnu`, `aarch64-linux-gnu`, and `arm-linux-gnu` (req: `glibc >= 2.29`) + - `x86_64-linux-musl`, `aarch64-linux-musl`, and `arm-linux-musl` +- Darwin/MacOS: `x86_64-darwin` and `arm64-darwin` +- Windows: `x64-mingw-ucrt` +- Java: any platform running JRuby 9.4 or higher + +To determine whether your system supports one of these gems, look at the output of `bundle platform` or `ruby -e 'puts Gem::Platform.local.to_s'`. + +If you're on a supported platform, either `gem install` or `bundle install` should install a native gem without any additional action on your part. This installation should only take a few seconds, and your output should look something like: + +``` sh +$ gem install nokogiri +Fetching nokogiri-1.11.0-x86_64-linux.gem +Successfully installed nokogiri-1.11.0-x86_64-linux +1 gem installed +``` + + +### Other Installation Options + +Because Nokogiri is a C extension, it requires that you have a C compiler toolchain, Ruby development header files, and some system dependencies installed. + +The following may work for you if you have an appropriately-configured system: + +``` bash +gem install nokogiri +``` + +If you have any issues, please visit [Installing Nokogiri](https://nokogiri.org/tutorials/installing_nokogiri.html) for more complete instructions and troubleshooting. + + +## How To Use Nokogiri + +Nokogiri is a large library, and so it's challenging to briefly summarize it. We've tried to provide long, real-world examples at [Tutorials](https://nokogiri.org/tutorials/toc.html). + +### Parsing and Querying + +Here is example usage for parsing and querying a document: + +```ruby +#! /usr/bin/env ruby + +require 'nokogiri' +require 'open-uri' + +# Fetch and parse HTML document +doc = Nokogiri::HTML(URI.open('https://nokogiri.org/tutorials/installing_nokogiri.html')) + +# Search for nodes by css +doc.css('nav ul.menu li a', 'article h2').each do |link| + puts link.content +end + +# Search for nodes by xpath +doc.xpath('//nav//ul//li/a', '//article//h2').each do |link| + puts link.content +end + +# Or mix and match +doc.search('nav ul.menu li a', '//article//h2').each do |link| + puts link.content +end +``` + + +### Encoding + +Strings are always stored as UTF-8 internally. Methods that return +text values will always return UTF-8 encoded strings. Methods that +return a string containing markup (like `to_xml`, `to_html` and +`inner_html`) will return a string encoded like the source document. + +__WARNING__ + +Some documents declare one encoding, but actually use a different +one. In these cases, which encoding should the parser choose? + +Data is just a stream of bytes. Humans add meaning to that stream. Any +particular set of bytes could be valid characters in multiple +encodings, so detecting encoding with 100% accuracy is not +possible. `libxml2` does its best, but it can't be right all the time. + +If you want Nokogiri to handle the document encoding properly, your +best bet is to explicitly set the encoding. Here is an example of +explicitly setting the encoding to EUC-JP on the parser: + +```ruby + doc = Nokogiri.XML('', nil, 'EUC-JP') +``` + + +## Technical Overview + +### Guiding Principles + +As noted above, two guiding principles of the software are: + +- be secure-by-default by treating all documents as **untrusted** by default +- be a **thin-as-reasonable layer** on top of the underlying parsers, and don't attempt to fix behavioral differences between the parsers + +Notably, despite all parsers being standards-compliant, there are behavioral inconsistencies between the parsers used in the CRuby and JRuby implementations, and Nokogiri does not and should not attempt to remove these inconsistencies. Instead, we surface these differences in the test suite when they are important/semantic; or we intentionally write tests to depend only on the important/semantic bits (omitting whitespace from regex matchers on results, for example). + + +### CRuby + +The Ruby (a.k.a., CRuby, MRI, YARV) implementation is a C extension that depends on libxml2 and libxslt (which in turn depend on zlib and possibly libiconv). + +These dependencies are met by default by Nokogiri's packaged versions of the libxml2 and libxslt source code, but a configuration option `--use-system-libraries` is provided to allow specification of alternative library locations. See [Installing Nokogiri](https://nokogiri.org/tutorials/installing_nokogiri.html) for full documentation. + +We provide native gems by pre-compiling libxml2 and libxslt (and potentially zlib and libiconv) and packaging them into the gem file. In this case, no compilation is necessary at installation time, which leads to faster and more reliable installation. + +See [`LICENSE-DEPENDENCIES.md`](LICENSE-DEPENDENCIES.md) for more information on which dependencies are provided in which native and source gems. + + +### JRuby + +The Java (a.k.a. JRuby) implementation is a Java extension that depends primarily on Xerces and NekoHTML for parsing, though additional dependencies are on `isorelax`, `nekodtd`, `jing`, `serializer`, `xalan-j`, and `xml-apis`. + +These dependencies are provided by pre-compiled jar files packaged in the `java` platform gem. + +See [`LICENSE-DEPENDENCIES.md`](LICENSE-DEPENDENCIES.md) for more information on which dependencies are provided in which native and source gems. + + +## Contributing + +See [`CONTRIBUTING.md`](CONTRIBUTING.md) for an intro guide to developing Nokogiri. + + +## Code of Conduct + +We've adopted the Contributor Covenant code of conduct, which you can read in full in [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md). + + +## License + +This project is licensed under the terms of the MIT license. + +See this license at [`LICENSE.md`](LICENSE.md). + + +### Dependencies + +Some additional libraries may be distributed with your version of Nokogiri. Please see [`LICENSE-DEPENDENCIES.md`](LICENSE-DEPENDENCIES.md) for a discussion of the variations as well as the licenses thereof. + + +## Authors + +- Mike Dalessio +- Aaron Patterson +- Yoko Harada +- Akinori MUSHA +- John Shahid +- Karol Bucek +- Sam Ruby +- Craig Barnes +- Stephen Checkoway +- Lars Kanis +- Sergio Arbeo +- Timothy Elliott +- Nobuyoshi Nakada diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/bin/nokogiri b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/bin/nokogiri new file mode 100755 index 00000000..04a5ceae --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/bin/nokogiri @@ -0,0 +1,131 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "optparse" +require "open-uri" +require "uri" +require "rubygems" +require "nokogiri" +autoload :IRB, "irb" + +parse_class = Nokogiri +encoding = nil + +# This module provides some tunables with the nokogiri CLI for use in +# your ~/.nokogirirc. +module Nokogiri + module CLI + class << self + # Specify the console engine, defaulted to IRB. + # + # call-seq: + # require 'pry' + # Nokogiri::CLI.console = Pry + attr_writer :console + + def console + case @console + when Symbol + Kernel.const_get(@console) + else + @console + end + end + + attr_accessor :rcfile + end + + self.rcfile = File.expand_path("~/.nokogirirc") + self.console = :IRB + end +end + +def safe_read(uri_or_path) + uri = URI.parse(uri_or_path) + case uri + when URI::HTTP + uri.read + when URI::File + File.read(uri.path) + else + File.read(uri_or_path) + end +end + +opts = OptionParser.new do |opts| + opts.banner = "Nokogiri: an HTML, XML, SAX, and Reader parser" + opts.define_head("Usage: nokogiri [options]") + opts.separator("") + opts.separator("Examples:") + opts.separator(" nokogiri https://www.ruby-lang.org/") + opts.separator(" nokogiri ./public/index.html") + opts.separator(" curl -s http://www.nokogiri.org | nokogiri -e'p $_.css(\"h1\").length'") + opts.separator("") + opts.separator("Options:") + + opts.on("--type type", "Parse as type: xml or html (default: auto)", [:xml, :html]) do |v| + parse_class = { xml: Nokogiri::XML, html: Nokogiri::HTML }[v] + end + + opts.on("-C file", "Specifies initialization file to load (default #{Nokogiri::CLI.rcfile})") do |v| + Nokogiri::CLI.rcfile = v + end + + opts.on("-E", "--encoding encoding", "Read as encoding (default: #{encoding || "none"})") do |v| + encoding = v + end + + opts.on("-e command", "Specifies script from command-line.") do |v| + @script = v + end + + opts.on("--rng ", "Validate using this rng file.") do |v| + @rng = Nokogiri::XML::RelaxNG(safe_read(v)) + end + + opts.on_tail("-?", "--help", "Show this message") do + puts opts + exit + end + + opts.on_tail("-v", "--version", "Show version") do + puts Nokogiri::VersionInfo.instance.to_markdown + exit + end +end +opts.parse! + +url = ARGV.shift + +if url.to_s.strip.empty? && $stdin.tty? + puts opts + exit 1 +end + +if File.file?(Nokogiri::CLI.rcfile) + load Nokogiri::CLI.rcfile +end + +@doc = if url || $stdin.tty? + parse_class.parse(safe_read(url), url, encoding) +else + parse_class.parse($stdin, nil, encoding) +end + +$_ = @doc + +if @rng + @rng.validate(@doc).each do |error| + puts error.message + end +elsif @script + begin + eval(@script, binding, "
    ") # rubocop:disable Security/Eval + rescue Exception => e # rubocop:disable Lint/RescueException + warn("ERROR: Exception raised while evaluating '#{@script}'") + raise e + end +else + puts "Your document is stored in @doc..." + Nokogiri::CLI.console.start +end diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/dependencies.yml b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/dependencies.yml new file mode 100644 index 00000000..276d991b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/dependencies.yml @@ -0,0 +1,42 @@ +--- +libxml2: + version: "2.13.8" + sha256: "277294cb33119ab71b2bc81f2f445e9bc9435b893ad15bb2cd2b0e859a0ee84a" + # sha-256 hash provided in https://download.gnome.org/sources/libxml2/2.13/libxml2-2.13.8.sha256sum + +libxslt: + version: "1.1.43" + sha256: "5a3d6b383ca5afc235b171118e90f5ff6aa27e9fea3303065231a6d403f0183a" + # sha-256 hash provided in https://download.gnome.org/sources/libxslt/1.1/libxslt-1.1.43.sha256sum + +zlib: + version: "1.3.1" + sha256: "9a93b2b7dfdac77ceba5a558a580e74667dd6fede4585b91eefb60f03b72df23" + # SHA-256 hash provided on http://zlib.net/ + +libiconv: + version: "1.17" + sha256: "8f74213b56238c85a50a5329f77e06198771e70dd9a739779f4c02f65d971313" + # signature verified by following this path: + # - release announced at https://savannah.gnu.org/forum/forum.php?forum_id=10175 + # - which links to https://savannah.gnu.org/users/haible as the releaser + # - which links to https://savannah.gnu.org/people/viewgpg.php?user_id=1871 as the gpg key + # + # So: + # - wget -q -O - https://savannah.gnu.org/people/viewgpg.php?user_id=1871 | gpg --import + # gpg: key F5BE8B267C6A406D: 1 signature not checked due to a missing key + # gpg: key F5BE8B267C6A406D: public key "Bruno Haible (Open Source Development) " imported + # gpg: Total number processed: 1 + # gpg: imported: 1 + # gpg: marginals needed: 3 completes needed: 1 trust model: pgp + # gpg: depth: 0 valid: 4 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 4u + # gpg: next trustdb check due at 2024-05-09 + # - gpg --verify libiconv-1.17.tar.gz.sig ports/archives/libiconv-1.17.tar.gz + # gpg: Signature made Sun 15 May 2022 11:26:42 AM EDT + # gpg: using RSA key 9001B85AF9E1B83DF1BDA942F5BE8B267C6A406D + # gpg: Good signature from "Bruno Haible (Open Source Development) " [unknown] + # gpg: WARNING: This key is not certified with a trusted signature! + # gpg: There is no indication that the signature belongs to the owner. + # Primary key fingerprint: 9001 B85A F9E1 B83D F1BD A942 F5BE 8B26 7C6A 406D + # + # And this sha256sum is calculated from that verified tarball. diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/depend b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/depend new file mode 100644 index 00000000..24f59088 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/depend @@ -0,0 +1,38 @@ +# -*-makefile-*- +# DO NOT DELETE + +gumbo.o: $(srcdir)/nokogiri.h +html_document.o: $(srcdir)/nokogiri.h +html_element_description.o: $(srcdir)/nokogiri.h +html_entity_lookup.o: $(srcdir)/nokogiri.h +html_sax_parser_context.o: $(srcdir)/nokogiri.h +html_sax_push_parser.o: $(srcdir)/nokogiri.h +libxml2_backwards_compat.o: $(srcdir)/nokogiri.h +nokogiri.o: $(srcdir)/nokogiri.h +test_global_handlers.o: $(srcdir)/nokogiri.h +xml_attr.o: $(srcdir)/nokogiri.h +xml_attribute_decl.o: $(srcdir)/nokogiri.h +xml_cdata.o: $(srcdir)/nokogiri.h +xml_comment.o: $(srcdir)/nokogiri.h +xml_document.o: $(srcdir)/nokogiri.h +xml_document_fragment.o: $(srcdir)/nokogiri.h +xml_dtd.o: $(srcdir)/nokogiri.h +xml_element_content.o: $(srcdir)/nokogiri.h +xml_element_decl.o: $(srcdir)/nokogiri.h +xml_encoding_handler.o: $(srcdir)/nokogiri.h +xml_entity_decl.o: $(srcdir)/nokogiri.h +xml_entity_reference.o: $(srcdir)/nokogiri.h +xml_namespace.o: $(srcdir)/nokogiri.h +xml_node.o: $(srcdir)/nokogiri.h +xml_node_set.o: $(srcdir)/nokogiri.h +xml_processing_instruction.o: $(srcdir)/nokogiri.h +xml_reader.o: $(srcdir)/nokogiri.h +xml_relax_ng.o: $(srcdir)/nokogiri.h +xml_sax_parser.o: $(srcdir)/nokogiri.h +xml_sax_parser_context.o: $(srcdir)/nokogiri.h +xml_sax_push_parser.o: $(srcdir)/nokogiri.h +xml_schema.o: $(srcdir)/nokogiri.h +xml_syntax_error.o: $(srcdir)/nokogiri.h +xml_text.o: $(srcdir)/nokogiri.h +xml_xpath_context.o: $(srcdir)/nokogiri.h +xslt_stylesheet.o: $(srcdir)/nokogiri.h diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/extconf.rb b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/extconf.rb new file mode 100644 index 00000000..844129ed --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/extconf.rb @@ -0,0 +1,1165 @@ +# frozen_string_literal: true + +# rubocop:disable Style/GlobalVars + +ENV["RC_ARCHS"] = "" if RUBY_PLATFORM.include?("darwin") + +require "mkmf" +require "rbconfig" +require "fileutils" +require "shellwords" +require "pathname" + +# helpful constants +PACKAGE_ROOT_DIR = File.expand_path(File.join(File.dirname(__FILE__), "..", "..")) +REQUIRED_LIBXML_VERSION = "2.9.2" +RECOMMENDED_LIBXML_VERSION = "2.12.0" + +REQUIRED_MINI_PORTILE_VERSION = "~> 2.8.2" # keep this version in sync with the one in the gemspec +REQUIRED_PKG_CONFIG_VERSION = "~> 1.1" + +# Keep track of what versions of what libraries we build against +OTHER_LIBRARY_VERSIONS = {} + +NOKOGIRI_HELP_MESSAGE = <<~HELP + USAGE: ruby #{$PROGRAM_NAME} [options] + + Flags that are always valid: + + --use-system-libraries + --enable-system-libraries + Use system libraries instead of building and using the packaged libraries. + + --disable-system-libraries + Use the packaged libraries, and ignore the system libraries. This is the default on most + platforms, and overrides `--use-system-libraries` and the environment variable + `NOKOGIRI_USE_SYSTEM_LIBRARIES`. + + --disable-clean + Do not clean out intermediate files after successful build. + + --prevent-strip + Take steps to prevent stripping the symbol table and debugging info from the shared + library, potentially overriding RbConfig's CFLAGS/LDFLAGS/DLDFLAGS. + + + Flags only used when using system libraries: + + General: + + --with-opt-dir=DIRECTORY + Look for headers and libraries in DIRECTORY. + + --with-opt-lib=DIRECTORY + Look for libraries in DIRECTORY. + + --with-opt-include=DIRECTORY + Look for headers in DIRECTORY. + + + Related to libxml2: + + --with-xml2-dir=DIRECTORY + Look for xml2 headers and library in DIRECTORY. + + --with-xml2-lib=DIRECTORY + Look for xml2 library in DIRECTORY. + + --with-xml2-include=DIRECTORY + Look for xml2 headers in DIRECTORY. + + --with-xml2-source-dir=DIRECTORY + (dev only) Build libxml2 from the source code in DIRECTORY + + --disable-xml2-legacy + Do not build libxml2 with zlib, liblzma, or HTTP support. This will become the default + in a future version of Nokogiri. + + + Related to libxslt: + + --with-xslt-dir=DIRECTORY + Look for xslt headers and library in DIRECTORY. + + --with-xslt-lib=DIRECTORY + Look for xslt library in DIRECTORY. + + --with-xslt-include=DIRECTORY + Look for xslt headers in DIRECTORY. + + --with-xslt-source-dir=DIRECTORY + (dev only) Build libxslt from the source code in DIRECTORY + + + Related to libexslt: + + --with-exslt-dir=DIRECTORY + Look for exslt headers and library in DIRECTORY. + + --with-exslt-lib=DIRECTORY + Look for exslt library in DIRECTORY. + + --with-exslt-include=DIRECTORY + Look for exslt headers in DIRECTORY. + + + Related to iconv: + + --with-iconv-dir=DIRECTORY + Look for iconv headers and library in DIRECTORY. + + --with-iconv-lib=DIRECTORY + Look for iconv library in DIRECTORY. + + --with-iconv-include=DIRECTORY + Look for iconv headers in DIRECTORY. + + + Related to zlib (ignored if `--disable-xml2-legacy` is used): + + --with-zlib-dir=DIRECTORY + Look for zlib headers and library in DIRECTORY. + + --with-zlib-lib=DIRECTORY + Look for zlib library in DIRECTORY. + + --with-zlib-include=DIRECTORY + Look for zlib headers in DIRECTORY. + + + Flags only used when building and using the packaged libraries: + + --disable-static + Do not statically link packaged libraries, instead use shared libraries. + + --enable-cross-build + Enable cross-build mode. (You probably do not want to set this manually.) + + + Environment variables used: + + NOKOGIRI_USE_SYSTEM_LIBRARIES + Equivalent to `--enable-system-libraries` when set, even if nil or blank. + + AR + Use this path to invoke the library archiver instead of `RbConfig::CONFIG['AR']` + + CC + Use this path to invoke the compiler instead of `RbConfig::CONFIG['CC']` + + CPPFLAGS + If this string is accepted by the C preprocessor, add it to the flags passed to the C preprocessor + + CFLAGS + If this string is accepted by the compiler, add it to the flags passed to the compiler + + LD + Use this path to invoke the linker instead of `RbConfig::CONFIG['LD']` + + LDFLAGS + If this string is accepted by the linker, add it to the flags passed to the linker + + LIBS + Add this string to the flags passed to the linker +HELP + +# +# utility functions +# +def config_clean? + enable_config("clean", true) +end + +def config_static? + default_static = !truffle? + enable_config("static", default_static) +end + +def config_cross_build? + enable_config("cross-build") +end + +def config_system_libraries? + enable_config("system-libraries", ENV.key?("NOKOGIRI_USE_SYSTEM_LIBRARIES")) do |_, default| + arg_config("--use-system-libraries", default) + end +end + +def config_with_xml2_legacy? + enable_config("xml2-legacy", true) +end + +def windows? + RbConfig::CONFIG["target_os"].match?(/mingw|mswin/) +end + +def solaris? + RbConfig::CONFIG["target_os"].include?("solaris") +end + +def darwin? + RbConfig::CONFIG["target_os"].include?("darwin") +end + +def openbsd? + RbConfig::CONFIG["target_os"].include?("openbsd") +end + +def aix? + RbConfig::CONFIG["target_os"].include?("aix") +end + +def unix? + !(windows? || solaris? || darwin?) +end + +def nix? + ENV.key?("NIX_CC") +end + +def truffle? + RUBY_ENGINE == "truffleruby" +end + +def concat_flags(*args) + args.compact.join(" ") +end + +def local_have_library(lib, func = nil, headers = nil) + have_library(lib, func, headers) || have_library("lib#{lib}", func, headers) +end + +def zlib_source(version_string) + # As of 2022-12, I'm starting to see failed downloads often enough from zlib.net that I want to + # change the default to github. + if ENV["NOKOGIRI_USE_CANONICAL_ZLIB_SOURCE"] + "https://zlib.net/fossils/zlib-#{version_string}.tar.gz" + else + "https://github.com/madler/zlib/releases/download/v#{version_string}/zlib-#{version_string}.tar.gz" + end +end + +def gnome_source + "https://download.gnome.org" +end + +LOCAL_PACKAGE_RESPONSE = Object.new +def LOCAL_PACKAGE_RESPONSE.%(package) + package ? "yes: #{package}" : "no" +end + +# wrapper around MakeMakefil#pkg_config and the PKGConfig gem +def try_package_configuration(pc) + unless ENV.key?("NOKOGIRI_TEST_PKG_CONFIG_GEM") + # try MakeMakefile#pkg_config, which uses the system utility `pkg-config`. + return if checking_for("#{pc} using `pkg_config`", LOCAL_PACKAGE_RESPONSE) do + pkg_config(pc) + end + end + + # `pkg-config` probably isn't installed, which appears to be the case for lots of freebsd systems. + # let's fall back to the pkg-config gem, which knows how to parse .pc files, and wrap it with the + # same logic as MakeMakefile#pkg_config + begin + require "rubygems" + gem("pkg-config", REQUIRED_PKG_CONFIG_VERSION) + require "pkg-config" + + checking_for("#{pc} using pkg-config gem version #{PKGConfig::VERSION}", LOCAL_PACKAGE_RESPONSE) do + if PKGConfig.have_package(pc) + cflags = PKGConfig.cflags(pc) + ldflags = PKGConfig.libs_only_L(pc) + libs = PKGConfig.libs_only_l(pc) + + Logging.message("pkg-config gem found package configuration for %s\n", pc) + Logging.message("cflags: %s\nldflags: %s\nlibs: %s\n\n", cflags, ldflags, libs) + + [cflags, ldflags, libs] + end + end + rescue LoadError + message("Please install either the `pkg-config` utility or the `pkg-config` rubygem.\n") + end +end + +# set up mkmf to link against the library if we can find it +def have_package_configuration(opt: nil, pc: nil, lib:, func:, headers:) + if opt + dir_config(opt) + dir_config("opt") + end + + # see if we have enough path info to do this without trying any harder + unless ENV.key?("NOKOGIRI_TEST_PKG_CONFIG") + return true if local_have_library(lib, func, headers) + end + + try_package_configuration(pc) if pc + + # verify that we can compile and link against the library + local_have_library(lib, func, headers) +end + +def ensure_package_configuration(opt: nil, pc: nil, lib:, func:, headers:) + have_package_configuration(opt: opt, pc: pc, lib: lib, func: func, headers: headers) || + abort_could_not_find_library(lib) +end + +def ensure_func(func, headers = nil) + have_func(func, headers) || abort_could_not_find_library(func) +end + +def preserving_globals + values = [$arg_config, $INCFLAGS, $CFLAGS, $CPPFLAGS, $LDFLAGS, $DLDFLAGS, $LIBPATH, $libs].map(&:dup) + yield +ensure + $arg_config, $INCFLAGS, $CFLAGS, $CPPFLAGS, $LDFLAGS, $DLDFLAGS, $LIBPATH, $libs = values +end + +def abort_could_not_find_library(lib) + callers = caller(1..2).join("\n") + abort("-----\n#{callers}\n#{lib} is missing. Please locate mkmf.log to investigate how it is failing.\n-----") +end + +def chdir_for_build(&block) + # When using rake-compiler-dock on Windows, the underlying Virtualbox shared + # folders don't support symlinks, but libiconv expects it for a build on + # Linux. We work around this limitation by using the temp dir for cooking. + build_dir = /mingw|mswin|cygwin/.match?(ENV["RCD_HOST_RUBY_PLATFORM"].to_s) ? "/tmp" : "." + Dir.chdir(build_dir, &block) +end + +def sh_export_path(path) + # because libxslt 1.1.29 configure.in uses AC_PATH_TOOL which treats ":" + # as a $PATH separator, we need to convert windows paths from + # + # C:/path/to/foo + # + # to + # + # /C/path/to/foo + # + # which is sh-compatible, in order to find things properly during + # configuration + return path unless windows? + + match = Regexp.new("^([A-Z]):(/.*)").match(path) + if match && match.length == 3 + return File.join("/", match[1], match[2]) + end + + path +end + +def libflag_to_filename(ldflag) + case ldflag + when /\A-l(.+)/ + "lib#{Regexp.last_match(1)}.#{$LIBEXT}" + end +end + +def have_libxml_headers?(version = nil) + source = if version.nil? + <<~SRC + #include + SRC + else + version_int = format("%d%2.2d%2.2d", *version.split(".")) + <<~SRC + #include + #if LIBXML_VERSION < #{version_int} + # error libxml2 is older than #{version} + #endif + SRC + end + + try_cpp(source) +end + +def try_link_iconv(using = nil) + checking_for(using ? "iconv using #{using}" : "iconv") do + ["", "-liconv"].any? do |opt| + preserving_globals do + yield if block_given? + + try_link(<<~SRC, opt) + #include + #include + int main(void) + { + iconv_t cd = iconv_open("", ""); + iconv(cd, NULL, NULL, NULL, NULL); + return EXIT_SUCCESS; + } + SRC + end + end + end +end + +def iconv_configure_flags + # give --with-iconv-dir and --with-opt-dir first priority + ["iconv", "opt"].each do |target| + config = preserving_globals { dir_config(target) } + next unless config.any? && try_link_iconv("--with-#{target}-* flags") { dir_config(target) } + + idirs, ldirs = config.map do |dirs| + Array(dirs).flat_map do |dir| + dir.split(File::PATH_SEPARATOR) + end if dirs + end + + return [ + "--with-iconv=yes", + *("CPPFLAGS=#{idirs.map { |dir| "-I" + dir }.join(" ")}" if idirs), + *("LDFLAGS=#{ldirs.map { |dir| "-L" + dir }.join(" ")}" if ldirs), + ] + end + + if try_link_iconv + return ["--with-iconv=yes"] + end + + config = preserving_globals { pkg_config("libiconv") } + if config && try_link_iconv("pkg-config libiconv") { pkg_config("libiconv") } + cflags, ldflags, libs = config + + return [ + "--with-iconv=yes", + "CPPFLAGS=#{cflags}", + "LDFLAGS=#{ldflags}", + "LIBS=#{libs}", + ] + end + + abort_could_not_find_library("libiconv") +end + +def process_recipe(name, version, static_p, cross_p, cacheable_p = true) + require "rubygems" + gem("mini_portile2", REQUIRED_MINI_PORTILE_VERSION) # gemspec is not respected at install time + require "mini_portile2" + message("Using mini_portile version #{MiniPortile::VERSION}\n") + + unless ["libxml2", "libxslt"].include?(name) + OTHER_LIBRARY_VERSIONS[name] = version + end + + MiniPortile.new(name, version).tap do |recipe| + def recipe.port_path + "#{@target}/#{RUBY_PLATFORM}/#{@name}/#{@version}" + end + + # We use 'host' to set compiler prefix for cross-compiling. Prefer host_alias over host. And + # prefer i686 (what external dev tools use) to i386 (what ruby's configure.ac emits). + recipe.host = RbConfig::CONFIG["host_alias"].empty? ? RbConfig::CONFIG["host"] : RbConfig::CONFIG["host_alias"] + recipe.host = recipe.host.gsub("i386", "i686") + + recipe.target = File.join(PACKAGE_ROOT_DIR, "ports") if cacheable_p + recipe.configure_options << "--libdir=#{File.join(recipe.path, "lib")}" + + yield recipe + + env = Hash.new do |hash, key| + hash[key] = (ENV[key]).to_s + end + + recipe.configure_options.flatten! + + recipe.configure_options.delete_if do |option| + case option + when /\A(\w+)=(.*)\z/ + env[Regexp.last_match(1)] = if env.key?(Regexp.last_match(1)) + concat_flags(env[Regexp.last_match(1)], Regexp.last_match(2)) + else + Regexp.last_match(2) + end + true + else + false + end + end + + if static_p + recipe.configure_options += [ + "--disable-shared", + "--enable-static", + ] + env["CFLAGS"] = concat_flags(env["CFLAGS"], "-fPIC") + else + recipe.configure_options += [ + "--enable-shared", + "--disable-static", + ] + end + + if cross_p + recipe.configure_options += [ + "--target=#{recipe.host}", + "--host=#{recipe.host}", + ] + end + + if RbConfig::CONFIG["target_cpu"] == "universal" + ["CFLAGS", "LDFLAGS"].each do |key| + unless env[key].include?("-arch") + env[key] = concat_flags(env[key], RbConfig::CONFIG["ARCH_FLAG"]) + end + end + end + + recipe.configure_options += env.map do |key, value| + "#{key}=#{value.strip}" + end + + checkpoint = "#{recipe.target}/#{recipe.name}-#{recipe.version}-#{RUBY_PLATFORM}.installed" + if File.exist?(checkpoint) && !recipe.source_directory + message("Building Nokogiri with a packaged version of #{name}-#{version}.\n") + else + message(<<~EOM) + ---------- IMPORTANT NOTICE ---------- + Building Nokogiri with a packaged version of #{name}-#{version}. + Configuration options: #{recipe.configure_options.shelljoin} + EOM + + unless recipe.patch_files.empty? + message("The following patches are being applied:\n") + + recipe.patch_files.each do |patch| + message(format(" - %s\n", File.basename(patch))) + end + end + + message(<<~EOM) if name != "libgumbo" + + The Nokogiri maintainers intend to provide timely security updates, but if + this is a concern for you and want to use your OS/distro system library + instead, then abort this installation process and install nokogiri as + instructed at: + + https://nokogiri.org/tutorials/installing_nokogiri.html#installing-using-standard-system-libraries + + EOM + + message(<<~EOM) if name == "libxml2" + Note, however, that nokogiri cannot guarantee compatibility with every + version of libxml2 that may be provided by OS/package vendors. + + EOM + + chdir_for_build { recipe.cook } + FileUtils.touch(checkpoint) + end + recipe.activate + end +end + +def copy_packaged_libraries_headers(to_path:, from_recipes:) + FileUtils.rm_rf(to_path, secure: true) + FileUtils.mkdir(to_path) + from_recipes.each do |recipe| + FileUtils.cp_r(Dir[File.join(recipe.path, "include/*")], to_path) + end +end + +def do_help + print(NOKOGIRI_HELP_MESSAGE) + exit!(0) +end + +def do_clean + root = Pathname(PACKAGE_ROOT_DIR) + pwd = Pathname(Dir.pwd) + + # Skip if this is a development work tree + unless (root + ".git").exist? + message("Cleaning files only used during build.\n") + + # (root + 'tmp') cannot be removed at this stage because + # nokogiri.so is yet to be copied to lib. + + # clean the ports build directory + Pathname.glob(pwd.join("tmp", "*", "ports")) do |dir| + FileUtils.rm_rf(dir, verbose: true) + end + + if config_static? + # ports installation can be safely removed if statically linked. + FileUtils.rm_rf(root + "ports", verbose: true) + else + FileUtils.rm_rf(root + "ports" + "archives", verbose: true) + end + end + + exit!(0) +end + +# In ruby 3.2, symbol resolution changed on Darwin, to introduce the `-bundle_loader` flag to +# resolve symbols against the ruby binary. +# +# This makes it challenging to build a single extension that works with both a ruby with +# `--enable-shared` and one with `--disable-shared. To work around that, we choose to add +# `-flat_namespace` to the link line (later in this file). +# +# The `-flat_namespace` line introduces its own behavior change, which is that (similar to on +# Linux), any symbols in the extension that are exported may now be resolved by shared libraries +# loaded by the Ruby process. Specifically, that means that libxml2 and libxslt, which are +# statically linked into the nokogiri bundle, will resolve (at runtime) to a system libxml2 loaded +# by Ruby on Darwin. And it appears that often Ruby on Darwin does indeed load the system libxml2, +# and that messes with our assumptions about whether we're running with a patched libxml2 or a +# vanilla libxml2. +# +# We choose to use `-load_hidden` in this case to prevent exporting those symbols from libxml2 and +# libxslt, which ensures that they will be resolved to the static libraries in the bundle. In other +# words, when we use `load_hidden`, what happens in the extension stays in the extension. +# +# See https://github.com/rake-compiler/rake-compiler-dock/issues/87 for more info. +# +# Anyway, this method is the logical bit to tell us when to turn on these workarounds. +def needs_darwin_linker_hack + config_cross_build? && + darwin? && + Gem::Requirement.new("~> 3.2").satisfied_by?(Gem::Version.new(RbConfig::CONFIG["ruby_version"].split("+").first)) +end + +# +# main +# +do_help if arg_config("--help") +do_clean if arg_config("--clean") + +if openbsd? && !config_system_libraries? + unless %x(#{ENV["CC"] || "/usr/bin/cc"} -v 2>&1).include?("clang") + (ENV["CC"] ||= find_executable("egcc")) || + abort("Please install gcc 4.9+ from ports using `pkg_add -v gcc`") + end + append_cppflags "-I/usr/local/include" +end + +if ENV["AR"] + RbConfig::CONFIG["AR"] = RbConfig::MAKEFILE_CONFIG["AR"] = ENV["AR"] +end + +if ENV["CC"] + RbConfig::CONFIG["CC"] = RbConfig::MAKEFILE_CONFIG["CC"] = ENV["CC"] +end + +if ENV["LD"] + RbConfig::CONFIG["LD"] = RbConfig::MAKEFILE_CONFIG["LD"] = ENV["LD"] +end + +# use same toolchain for libxml and libxslt +ENV["AR"] = RbConfig::CONFIG["AR"] +ENV["CC"] = RbConfig::CONFIG["CC"] +ENV["LD"] = RbConfig::CONFIG["LD"] + +if arg_config("--prevent-strip") + old_cflags = $CFLAGS.split.join(" ") + old_ldflags = $LDFLAGS.split.join(" ") + old_dldflags = $DLDFLAGS.split.join(" ") + $CFLAGS = $CFLAGS.split.reject { |flag| flag == "-s" }.join(" ") + $LDFLAGS = $LDFLAGS.split.reject { |flag| flag == "-s" }.join(" ") + $DLDFLAGS = $DLDFLAGS.split.reject { |flag| flag == "-s" }.join(" ") + puts "Prevent stripping by removing '-s' from $CFLAGS" if old_cflags != $CFLAGS + puts "Prevent stripping by removing '-s' from $LDFLAGS" if old_ldflags != $LDFLAGS + puts "Prevent stripping by removing '-s' from $DLDFLAGS" if old_dldflags != $DLDFLAGS +end + +# adopt environment config +append_cflags(ENV["CFLAGS"]) unless ENV["CFLAGS"].nil? +append_cppflags(ENV["CPPFLAGS"]) unless ENV["CPPFLAGS"].nil? +append_ldflags(ENV["LDFLAGS"]) unless ENV["LDFLAGS"].nil? +$LIBS = concat_flags($LIBS, ENV["LIBS"]) + +# libgumbo uses C90/C99 features, see #2302 +append_cflags(["-std=c99", "-Wno-declaration-after-statement"]) + +# gumbo html5 serialization is slower with O3, let's make sure we use O2 +append_cflags("-O2") + +# always include debugging information +append_cflags("-g") + +# we use at least one inline function in the C extension +append_cflags("-Winline") + +# good to have no matter what Ruby was compiled with +append_cflags("-Wmissing-noreturn") + +# check integer loss of precision. this flag won't generally work until Ruby 3.4. +# see https://bugs.ruby-lang.org/issues/20507 +append_cflags("-Wconversion") + +# handle clang variations, see #1101 +if darwin? + append_cflags("-Wno-error=unused-command-line-argument-hard-error-in-future") + append_cflags("-Wno-unknown-warning-option") +end + +# these tend to be noisy, but on occasion useful during development +# append_cflags(["-Wcast-qual", "-Wwrite-strings"]) + +# Add SDK-specific include path for macOS and brew versions before v2.2.12 (2020-04-08) [#1851, #1801] +macos_mojave_sdk_include_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2" +if config_system_libraries? && darwin? && Dir.exist?(macos_mojave_sdk_include_path) && !nix? + append_cppflags("-I#{macos_mojave_sdk_include_path}") +end + +# Work around a character escaping bug in MSYS by passing an arbitrary double-quoted parameter to gcc. +# See https://sourceforge.net/p/mingw/bugs/2142 +append_cppflags(' "-Idummypath"') if windows? + +if config_system_libraries? + message "Building nokogiri using system libraries.\n" + if config_with_xml2_legacy? + ensure_package_configuration( + opt: "zlib", + pc: "zlib", + lib: "z", + headers: "zlib.h", + func: "gzdopen", + ) + end + ensure_package_configuration( + opt: "xml2", + pc: "libxml-2.0", + lib: "xml2", + headers: "libxml/parser.h", + func: "xmlParseDoc", + ) + ensure_package_configuration( + opt: "xslt", + pc: "libxslt", + lib: "xslt", + headers: "libxslt/xslt.h", + func: "xsltParseStylesheetDoc", + ) + ensure_package_configuration( + opt: "exslt", + pc: "libexslt", + lib: "exslt", + headers: "libexslt/exslt.h", + func: "exsltFuncRegister", + ) + + have_libxml_headers?(REQUIRED_LIBXML_VERSION) || + abort("ERROR: libxml2 version #{REQUIRED_LIBXML_VERSION} or later is required!") + have_libxml_headers?(RECOMMENDED_LIBXML_VERSION) || + warn("WARNING: libxml2 version #{RECOMMENDED_LIBXML_VERSION} or later is highly recommended, but proceeding anyway.") + +else + message "Building nokogiri using packaged libraries.\n" + + static_p = config_static? + message "Static linking is #{static_p ? "enabled" : "disabled"}.\n" + + cross_build_p = config_cross_build? + message "Cross build is #{cross_build_p ? "enabled" : "disabled"}.\n" + + if needs_darwin_linker_hack + append_ldflags("-Wl,-flat_namespace") + end + + require "yaml" + dependencies = YAML.load_file(File.join(PACKAGE_ROOT_DIR, "dependencies.yml")) + + dir_config("zlib") if config_with_xml2_legacy? + + if cross_build_p || windows? + if config_with_xml2_legacy? + zlib_recipe = process_recipe("zlib", dependencies["zlib"]["version"], static_p, cross_build_p) do |recipe| + recipe.files = [{ + url: zlib_source(recipe.version), + sha256: dependencies["zlib"]["sha256"], + }] + if windows? + class << recipe + attr_accessor :cross_build_p + + def configure + Dir.chdir(work_path) do + mk = File.read("win32/Makefile.gcc") + File.open("win32/Makefile.gcc", "wb") do |f| + f.puts "BINARY_PATH = #{path}/bin" + f.puts "LIBRARY_PATH = #{path}/lib" + f.puts "INCLUDE_PATH = #{path}/include" + mk.sub!(/^PREFIX\s*=\s*$/, "PREFIX = #{host}-") if cross_build_p + f.puts mk + end + end + end + + def configured? + Dir.chdir(work_path) do + !!(File.read("win32/Makefile.gcc") =~ /^BINARY_PATH/) + end + end + + def compile + execute("compile", "make -f win32/Makefile.gcc") + end + + def install + execute("install", "make -f win32/Makefile.gcc install") + end + end + recipe.cross_build_p = cross_build_p + else + class << recipe + def configure + env = {} + env["CFLAGS"] = concat_flags(ENV["CFLAGS"], "-fPIC", "-g") + env["CHOST"] = host + execute("configure", ["./configure", "--static", configure_prefix], { env: env }) + if darwin? + # needed as of zlib 1.2.13 + Dir.chdir(work_path) do + makefile = File.read("Makefile").gsub(/^AR=.*$/, "AR=#{host}-libtool") + File.open("Makefile", "w") { |m| m.write(makefile) } + end + end + end + end + end + end + end + + unless unix? + libiconv_recipe = process_recipe( + "libiconv", + dependencies["libiconv"]["version"], + static_p, + cross_build_p, + ) do |recipe| + recipe.files = [{ + url: "https://ftp.gnu.org/pub/gnu/libiconv/#{recipe.name}-#{recipe.version}.tar.gz", + sha256: dependencies["libiconv"]["sha256"], + }] + + # The libiconv configure script doesn't accept "arm64" host string but "aarch64" + recipe.host = recipe.host.gsub("arm64-apple-darwin", "aarch64-apple-darwin") + + cflags = concat_flags(ENV["CFLAGS"], "-O2", "-g") + + recipe.configure_options += [ + "--disable-dependency-tracking", + "CPPFLAGS=-Wall", + "CFLAGS=#{cflags}", + "CXXFLAGS=#{cflags}", + "LDFLAGS=", + ] + end + end + elsif darwin? && !have_header("iconv.h") + abort(<<~EOM.chomp) + ----- + The file "iconv.h" is missing in your build environment, + which means you haven't installed Xcode Command Line Tools properly. + + To install Command Line Tools, try running `xcode-select --install` on + terminal and follow the instructions. If it fails, open Xcode.app, + select from the menu "Xcode" - "Open Developer Tool" - "More Developer + Tools" to open the developer site, download the installer for your OS + version and run it. + ----- + EOM + end + + if zlib_recipe + append_cppflags("-I#{zlib_recipe.path}/include") + $LIBPATH = ["#{zlib_recipe.path}/lib"] | $LIBPATH + ensure_package_configuration( + opt: "zlib", + pc: "zlib", + lib: "z", + headers: "zlib.h", + func: "gzdopen", + ) + end + + if libiconv_recipe + append_cppflags("-I#{libiconv_recipe.path}/include") + $LIBPATH = ["#{libiconv_recipe.path}/lib"] | $LIBPATH + ensure_package_configuration( + opt: "iconv", + pc: "iconv", + lib: "iconv", + headers: "iconv.h", + func: "iconv_open", + ) + end + + libxml2_recipe = process_recipe("libxml2", dependencies["libxml2"]["version"], static_p, cross_build_p) do |recipe| + source_dir = arg_config("--with-xml2-source-dir") + if source_dir + recipe.source_directory = source_dir + else + minor_version = Gem::Version.new(recipe.version).segments.take(2).join(".") + recipe.files = [{ + url: "#{gnome_source}/sources/libxml2/#{minor_version}/#{recipe.name}-#{recipe.version}.tar.xz", + sha256: dependencies["libxml2"]["sha256"], + }] + recipe.patch_files = Dir[File.join(PACKAGE_ROOT_DIR, "patches", "libxml2", "*.patch")].sort + end + + cppflags = concat_flags(ENV["CPPFLAGS"]) + cflags = concat_flags(ENV["CFLAGS"], "-O2", "-g") + + if cross_build_p + cppflags = concat_flags(cppflags, "-DNOKOGIRI_PRECOMPILED_LIBRARIES") + end + + if config_with_xml2_legacy? + recipe.configure_options << "--with-legacy" + end + + if zlib_recipe + recipe.configure_options << "--with-zlib=#{zlib_recipe.path}" + end + + if libiconv_recipe + recipe.configure_options << "--with-iconv=#{libiconv_recipe.path}" + else + recipe.configure_options += iconv_configure_flags + end + + if darwin? && !cross_build_p + recipe.configure_options << "RANLIB=/usr/bin/ranlib" unless ENV.key?("RANLIB") + recipe.configure_options << "AR=/usr/bin/ar" unless ENV.key?("AR") + end + + if windows? + cflags = concat_flags(cflags, "-ULIBXML_STATIC", "-DIN_LIBXML") + end + + recipe.configure_options << if source_dir + "--config-cache" + else + "--disable-dependency-tracking" + end + + recipe.configure_options += [ + "--without-python", + "--without-readline", + "--with-c14n", + "--with-debug", + "--with-threads", + "CPPFLAGS=#{cppflags}", + "CFLAGS=#{cflags}", + ] + end + + libxslt_recipe = process_recipe("libxslt", dependencies["libxslt"]["version"], static_p, cross_build_p) do |recipe| + source_dir = arg_config("--with-xslt-source-dir") + if source_dir + recipe.source_directory = source_dir + else + minor_version = Gem::Version.new(recipe.version).segments.take(2).join(".") + recipe.files = [{ + url: "#{gnome_source}/sources/libxslt/#{minor_version}/#{recipe.name}-#{recipe.version}.tar.xz", + sha256: dependencies["libxslt"]["sha256"], + }] + recipe.patch_files = Dir[File.join(PACKAGE_ROOT_DIR, "patches", "libxslt", "*.patch")].sort + end + + cflags = concat_flags(ENV["CFLAGS"], "-O2", "-g") + + if darwin? && !cross_build_p + recipe.configure_options << "RANLIB=/usr/bin/ranlib" unless ENV.key?("RANLIB") + recipe.configure_options << "AR=/usr/bin/ar" unless ENV.key?("AR") + end + + if windows? + cflags = concat_flags(cflags, "-ULIBXSLT_STATIC", "-DIN_LIBXSLT") + cflags = concat_flags(cflags, "-ULIBEXSLT_STATIC", "-DIN_LIBEXSLT") + end + + recipe.configure_options << if source_dir + "--config-cache" + else + "--disable-dependency-tracking" + end + + recipe.configure_options += [ + "--without-python", + "--without-crypto", + "--with-debug", + "--with-libxml-prefix=#{sh_export_path(libxml2_recipe.path)}", + "CFLAGS=#{cflags}", + ] + end + + append_cppflags("-DNOKOGIRI_PACKAGED_LIBRARIES") + append_cppflags("-DNOKOGIRI_PRECOMPILED_LIBRARIES") if cross_build_p + + $libs = $libs.shellsplit.tap do |libs| + [libxml2_recipe, libxslt_recipe].each do |recipe| + libname = recipe.name[/\Alib(.+)\z/, 1] + config_basename = "#{libname}-config" + File.join(recipe.path, "bin", config_basename).tap do |config| + # call config scripts explicit with 'sh' for compat with Windows + cflags = %x(sh #{config} --cflags).strip + message("#{config_basename} cflags: #{cflags}\n") + $CPPFLAGS = concat_flags(cflags, $CPPFLAGS) # prepend + + %x(sh #{config} --libs).strip.shellsplit.each do |arg| + case arg + when /\A-L(.+)\z/ + # Prioritize ports' directories + $LIBPATH = if Regexp.last_match(1).start_with?(PACKAGE_ROOT_DIR + "/") + [Regexp.last_match(1)] | $LIBPATH + else + $LIBPATH | [Regexp.last_match(1)] + end + when /\A-l./ + libs.unshift(arg) + else + $LDFLAGS << " " << arg.shellescape + end + end + end + + patches_string = recipe.patch_files.map { |path| File.basename(path) }.join(" ") + append_cppflags(%[-DNOKOGIRI_#{recipe.name.upcase}_PATCHES="\\"#{patches_string}\\""]) + + case libname + when "xml2" + # xslt-config --libs or pkg-config libxslt --libs does not include + # -llzma, so we need to add it manually when linking statically. + if static_p && preserving_globals { local_have_library("lzma") } + # Add it at the end; GH #988 + libs << "-llzma" + end + when "xslt" + # xslt-config does not have a flag to emit options including + # -lexslt, so add it manually. + libs.unshift("-lexslt") + end + end + end.shelljoin + + if static_p + static_archive_ld_flag = needs_darwin_linker_hack ? ["-load_hidden"] : [] + $libs = $libs.shellsplit.map do |arg| + case arg + when "-lxml2" + static_archive_ld_flag + [File.join(libxml2_recipe.path, "lib", libflag_to_filename(arg))] + when "-lxslt", "-lexslt" + static_archive_ld_flag + [File.join(libxslt_recipe.path, "lib", libflag_to_filename(arg))] + else + arg + end + end.flatten.shelljoin + end + + ensure_func("xmlParseDoc", "libxml/parser.h") + ensure_func("xsltParseStylesheetDoc", "libxslt/xslt.h") + ensure_func("exsltFuncRegister", "libexslt/exslt.h") +end + +if arg_config("--gumbo-dev") + message("DEV MODE ENABLED: build libgumbo as packaged source") + ext_dir = File.dirname(__FILE__) + Dir.chdir(ext_dir) do + $srcs = Dir["*.c", "../../gumbo-parser/src/*.c"] + $hdrs = Dir["*.h", "../../gumbo-parser/src/*.h"] + end + $INCFLAGS << " -I$(srcdir)/../../gumbo-parser/src" + $VPATH << "$(srcdir)/../../gumbo-parser/src" + find_header("nokogiri_gumbo.h") || abort("nokogiri_gumbo.h not found") +else + libgumbo_recipe = process_recipe("libgumbo", "1.0.0-nokogiri", static_p, cross_build_p, false) do |recipe| + recipe.configure_options = [] + + class << recipe + def downloaded? + true + end + + def extract + target = File.join(tmp_path, "gumbo-parser") + output("Copying gumbo-parser files into #{target}...") + FileUtils.mkdir_p(target) + FileUtils.cp(Dir.glob(File.join(PACKAGE_ROOT_DIR, "gumbo-parser/src/*")), target) + end + + def configured? + true + end + + def install + lib_dir = File.join(port_path, "lib") + inc_dir = File.join(port_path, "include") + FileUtils.mkdir_p([lib_dir, inc_dir]) + FileUtils.cp(File.join(work_path, "libgumbo.a"), lib_dir) + FileUtils.cp(Dir.glob(File.join(work_path, "*.h")), inc_dir) + end + + def compile + cflags = concat_flags(ENV["CFLAGS"], "-fPIC", "-O2", "-g") + + env = { "CC" => gcc_cmd, "CFLAGS" => cflags } + if config_cross_build? + if host.include?("darwin") + env["AR"] = "#{host}-libtool" + env["ARFLAGS"] = "-o" + else + env["AR"] = "#{host}-ar" + end + env["RANLIB"] = "#{host}-ranlib" + if windows? + concat_flags(env["CFLAGS"], "-D_RUBY_UCRT") + end + end + + execute("compile", make_cmd, { env: env }) + end + end + end + append_cppflags("-I#{File.join(libgumbo_recipe.path, "include")}") + $libs = $libs + " " + File.join(libgumbo_recipe.path, "lib", "libgumbo.a") + $LIBPATH = $LIBPATH | [File.join(libgumbo_recipe.path, "lib")] + ensure_func("gumbo_parse_with_options", "nokogiri_gumbo.h") +end + +have_func("xmlCtxtSetOptions") # introduced in libxml2 2.13.0 +have_func("xmlCtxtGetOptions") # introduced in libxml2 2.14.0 +have_func("xmlSwitchEncodingName") # introduced in libxml2 2.13.0 +have_func("rb_category_warning") # introduced in Ruby 3.0 but had trouble resolving this symbol in truffleruby + +other_library_versions_string = OTHER_LIBRARY_VERSIONS.map { |k, v| [k, v].join(":") }.join(",") +append_cppflags(%[-DNOKOGIRI_OTHER_LIBRARY_VERSIONS="\\"#{other_library_versions_string}\\""]) + +unless config_system_libraries? + if cross_build_p + # When precompiling native gems, copy packaged libraries' headers to ext/nokogiri/include + # These are packaged up by the cross-compiling callback in the ExtensionTask + copy_packaged_libraries_headers( + to_path: File.join(PACKAGE_ROOT_DIR, "ext/nokogiri/include"), + from_recipes: [libxml2_recipe, libxslt_recipe], + ) + else + # When compiling during installation, install packaged libraries' header files into ext/nokogiri/include + copy_packaged_libraries_headers( + to_path: "include", + from_recipes: [libxml2_recipe, libxslt_recipe], + ) + $INSTALLFILES << ["include/**/*.h", "$(rubylibdir)"] + end +end + +create_makefile("nokogiri/nokogiri") + +if config_clean? + # Do not clean if run in a development work tree. + File.open("Makefile", "at") do |mk| + mk.print(<<~EOF) + + all: clean-ports + clean-ports: $(TARGET_SO) + \t-$(Q)$(RUBY) $(srcdir)/extconf.rb --clean --#{static_p ? "enable" : "disable"}-static + EOF + end +end + +# rubocop:enable Style/GlobalVars diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/gumbo.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/gumbo.c new file mode 100644 index 00000000..fd938f3c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/gumbo.c @@ -0,0 +1,610 @@ +// +// Copyright 2013-2021 Sam Ruby, Stephen Checkoway +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// +// nokogumbo.c defines the following: +// +// class Nokogumbo +// def parse(utf8_string) # returns Nokogiri::HTML5::Document +// end +// +// Processing starts by calling gumbo_parse_with_options. The resulting document tree +// is then walked, a parallel libxml2 tree is constructed, and the final document is +// then wrapped using noko_xml_document_wrap. This approach reduces memory and CPU +// requirements as Ruby objects are only built when necessary. +// + +#include + +#include "nokogiri_gumbo.h" + +VALUE cNokogiriHtml5Document; + +// Interned symbols +static ID internal_subset; +static ID parent; + +#include +#include +#include + +// URI = system id +// external id = public id +static xmlDocPtr +new_html_doc(const char *dtd_name, const char *system, const char *public) +{ + // These two libxml2 functions take the public and system ids in + // opposite orders. + htmlDocPtr doc = htmlNewDocNoDtD(/* URI */ NULL, /* ExternalID */NULL); + assert(doc); + if (dtd_name) { + xmlCreateIntSubset(doc, (const xmlChar *)dtd_name, (const xmlChar *)public, (const xmlChar *)system); + } + return doc; +} + +static xmlNodePtr +get_parent(xmlNodePtr node) +{ + return node->parent; +} + +static GumboOutput * +perform_parse(const GumboOptions *options, VALUE input) +{ + assert(RTEST(input)); + Check_Type(input, T_STRING); + GumboOutput *output = gumbo_parse_with_options( + options, + RSTRING_PTR(input), + (size_t)RSTRING_LEN(input) + ); + + const char *status_string = gumbo_status_to_string(output->status); + switch (output->status) { + case GUMBO_STATUS_OK: + break; + case GUMBO_STATUS_TOO_MANY_ATTRIBUTES: + case GUMBO_STATUS_TREE_TOO_DEEP: + gumbo_destroy_output(output); + rb_raise(rb_eArgError, "%s", status_string); + case GUMBO_STATUS_OUT_OF_MEMORY: + gumbo_destroy_output(output); + rb_raise(rb_eNoMemError, "%s", status_string); + } + return output; +} + +static xmlNsPtr +lookup_or_add_ns( + xmlDocPtr doc, + xmlNodePtr root, + const char *href, + const char *prefix +) +{ + xmlNsPtr ns = xmlSearchNs(doc, root, (const xmlChar *)prefix); + if (ns) { + return ns; + } + return xmlNewNs(root, (const xmlChar *)href, (const xmlChar *)prefix); +} + +static void +set_line(xmlNodePtr node, size_t line) +{ + // libxml2 uses 65535 to mean look elsewhere for the line number on some + // nodes. + if (line < 65535) { + node->line = (unsigned short)line; + } +} + +// Construct an XML tree rooted at xml_output_node from the Gumbo tree rooted +// at gumbo_node. +static void +build_tree( + xmlDocPtr doc, + xmlNodePtr xml_output_node, + const GumboNode *gumbo_node +) +{ + xmlNodePtr xml_root = NULL; + xmlNodePtr xml_node = xml_output_node; + size_t child_index = 0; + + while (true) { + assert(gumbo_node != NULL); + const GumboVector *children = gumbo_node->type == GUMBO_NODE_DOCUMENT ? + &gumbo_node->v.document.children : &gumbo_node->v.element.children; + if (child_index >= children->length) { + // Move up the tree and to the next child. + if (xml_node == xml_output_node) { + // We've built as much of the tree as we can. + return; + } + child_index = gumbo_node->index_within_parent + 1; + gumbo_node = gumbo_node->parent; + xml_node = get_parent(xml_node); + // Children of fragments don't share the same root, so reset it and + // it'll be set below. In the non-fragment case, this will only happen + // after the html element has been finished at which point there are no + // further elements. + if (xml_node == xml_output_node) { + xml_root = NULL; + } + continue; + } + const GumboNode *gumbo_child = children->data[child_index++]; + xmlNodePtr xml_child; + + switch (gumbo_child->type) { + case GUMBO_NODE_DOCUMENT: + abort(); // Bug in Gumbo. + + case GUMBO_NODE_TEXT: + case GUMBO_NODE_WHITESPACE: + xml_child = xmlNewDocText(doc, (const xmlChar *)gumbo_child->v.text.text); + set_line(xml_child, gumbo_child->v.text.start_pos.line); + xmlAddChild(xml_node, xml_child); + break; + + case GUMBO_NODE_CDATA: + xml_child = xmlNewCDataBlock(doc, (const xmlChar *)gumbo_child->v.text.text, + (int) strlen(gumbo_child->v.text.text)); + set_line(xml_child, gumbo_child->v.text.start_pos.line); + xmlAddChild(xml_node, xml_child); + break; + + case GUMBO_NODE_COMMENT: + xml_child = xmlNewDocComment(doc, (const xmlChar *)gumbo_child->v.text.text); + set_line(xml_child, gumbo_child->v.text.start_pos.line); + xmlAddChild(xml_node, xml_child); + break; + + case GUMBO_NODE_TEMPLATE: + // XXX: Should create a template element and a new DocumentFragment + case GUMBO_NODE_ELEMENT: { + xml_child = xmlNewDocNode(doc, NULL, (const xmlChar *)gumbo_child->v.element.name, NULL); + set_line(xml_child, gumbo_child->v.element.start_pos.line); + if (xml_root == NULL) { + xml_root = xml_child; + } + xmlNsPtr ns = NULL; + switch (gumbo_child->v.element.tag_namespace) { + case GUMBO_NAMESPACE_HTML: + break; + case GUMBO_NAMESPACE_SVG: + ns = lookup_or_add_ns(doc, xml_root, "http://www.w3.org/2000/svg", "svg"); + break; + case GUMBO_NAMESPACE_MATHML: + ns = lookup_or_add_ns(doc, xml_root, "http://www.w3.org/1998/Math/MathML", "math"); + break; + } + if (ns != NULL) { + xmlSetNs(xml_child, ns); + } + xmlAddChild(xml_node, xml_child); + + // Add the attributes. + const GumboVector *attrs = &gumbo_child->v.element.attributes; + for (size_t i = 0; i < attrs->length; i++) { + const GumboAttribute *attr = attrs->data[i]; + + switch (attr->attr_namespace) { + case GUMBO_ATTR_NAMESPACE_XLINK: + ns = lookup_or_add_ns(doc, xml_root, "http://www.w3.org/1999/xlink", "xlink"); + break; + + case GUMBO_ATTR_NAMESPACE_XML: + ns = lookup_or_add_ns(doc, xml_root, "http://www.w3.org/XML/1998/namespace", "xml"); + break; + + case GUMBO_ATTR_NAMESPACE_XMLNS: + ns = lookup_or_add_ns(doc, xml_root, "http://www.w3.org/2000/xmlns/", "xmlns"); + break; + + default: + ns = NULL; + } + xmlNewNsProp(xml_child, ns, (const xmlChar *)attr->name, (const xmlChar *)attr->value); + } + + // Add children for this element. + child_index = 0; + gumbo_node = gumbo_child; + xml_node = xml_child; + } + } + } +} + +static void +add_errors(const GumboOutput *output, VALUE rdoc, VALUE input, VALUE url) +{ + const char *input_str = RSTRING_PTR(input); + size_t input_len = (size_t)RSTRING_LEN(input); + + // Add parse errors to rdoc. + if (output->errors.length) { + const GumboVector *errors = &output->errors; + VALUE rerrors = rb_ary_new2(errors->length); + + for (size_t i = 0; i < errors->length; i++) { + GumboError *err = errors->data[i]; + GumboSourcePosition position = gumbo_error_position(err); + char *msg; + size_t size = gumbo_caret_diagnostic_to_string(err, input_str, input_len, &msg); + VALUE err_str = rb_utf8_str_new(msg, (int)size); + free(msg); + VALUE syntax_error = rb_class_new_instance(1, &err_str, cNokogiriXmlSyntaxError); + const char *error_code = gumbo_error_code(err); + VALUE str1 = error_code ? rb_utf8_str_new_static(error_code, (int)strlen(error_code)) : Qnil; + rb_iv_set(syntax_error, "@domain", INT2NUM(1)); // XML_FROM_PARSER + rb_iv_set(syntax_error, "@code", INT2NUM(1)); // XML_ERR_INTERNAL_ERROR + rb_iv_set(syntax_error, "@level", INT2NUM(2)); // XML_ERR_ERROR + rb_iv_set(syntax_error, "@file", url); + rb_iv_set(syntax_error, "@line", SIZET2NUM(position.line)); + rb_iv_set(syntax_error, "@str1", str1); + rb_iv_set(syntax_error, "@str2", Qnil); + rb_iv_set(syntax_error, "@str3", Qnil); + rb_iv_set(syntax_error, "@int1", INT2NUM(0)); + rb_iv_set(syntax_error, "@column", SIZET2NUM(position.column)); + rb_ary_push(rerrors, syntax_error); + } + rb_iv_set(rdoc, "@errors", rerrors); + } +} + +typedef struct { + GumboOutput *output; + VALUE input; + VALUE url_or_frag; + VALUE klass; + xmlDocPtr doc; +} ParseArgs; + +static VALUE +parse_cleanup(VALUE parse_args) +{ + ParseArgs *args = (ParseArgs *)parse_args; + gumbo_destroy_output(args->output); + // Make sure garbage collection doesn't mark the objects as being live based + // on references from the ParseArgs. This may be unnecessary. + args->input = Qnil; + args->url_or_frag = Qnil; + if (args->doc != NULL) { + xmlFreeDoc(args->doc); + } + return Qnil; +} + +// Scan the keyword arguments for options common to the document and fragment +// parse. +static GumboOptions +common_options(VALUE kwargs) +{ + // The order of the keywords determines the order of the values below. + // If this order is changed, then setting the options below must change as + // well. + ID keywords[] = { + // Required keywords. + rb_intern_const("max_attributes"), + rb_intern_const("max_errors"), + rb_intern_const("max_tree_depth"), + + // Optional keywords. + rb_intern_const("parse_noscript_content_as_text"), + }; + VALUE values[sizeof keywords / sizeof keywords[0]]; + + // Extract the values coresponding to the required keywords. Raise an error + // if required arguments are missing. + rb_get_kwargs(kwargs, keywords, 3, 1, values); + + GumboOptions options = kGumboDefaultOptions; + options.max_attributes = NUM2INT(values[0]); + options.max_errors = NUM2INT(values[1]); + + // handle negative values + int depth = NUM2INT(values[2]); + options.max_tree_depth = depth < 0 ? UINT_MAX : (unsigned int)depth; + + options.parse_noscript_content_as_text = values[3] != Qundef && RTEST(values[3]); + + return options; +} + +static VALUE parse_continue(VALUE parse_args); + +/* + * @!visibility protected + */ +static VALUE +noko_gumbo_s_parse(int argc, VALUE *argv, VALUE _self) +{ + VALUE input, url, klass, kwargs; + + rb_scan_args(argc, argv, "3:", &input, &url, &klass, &kwargs); + if (NIL_P(kwargs)) { + kwargs = rb_hash_new(); + } + + GumboOptions options = common_options(kwargs); + + GumboOutput *output = perform_parse(&options, input); + ParseArgs args = { + .output = output, + .input = input, + .url_or_frag = url, + .klass = klass, + .doc = NULL, + }; + + return rb_ensure(parse_continue, (VALUE)(&args), parse_cleanup, (VALUE)(&args)); +} + +static VALUE +parse_continue(VALUE parse_args) +{ + ParseArgs *args = (ParseArgs *)parse_args; + GumboOutput *output = args->output; + xmlDocPtr doc; + if (output->document->v.document.has_doctype) { + const char *name = output->document->v.document.name; + const char *public = output->document->v.document.public_identifier; + const char *system = output->document->v.document.system_identifier; + public = public[0] ? public : NULL; + system = system[0] ? system : NULL; + doc = new_html_doc(name, system, public); + } else { + doc = new_html_doc(NULL, NULL, NULL); + } + args->doc = doc; // Make sure doc gets cleaned up if an error is thrown. + build_tree(doc, (xmlNodePtr)doc, output->document); + VALUE rdoc = noko_xml_document_wrap(args->klass, doc); + rb_iv_set(rdoc, "@url", args->url_or_frag); + rb_iv_set(rdoc, "@quirks_mode", INT2NUM(output->document->v.document.doc_type_quirks_mode)); + args->doc = NULL; // The Ruby runtime now owns doc so don't delete it. + add_errors(output, rdoc, args->input, args->url_or_frag); + return rdoc; +} + +static int +lookup_namespace(VALUE node, bool require_known_ns) +{ + ID namespace, href; + CONST_ID(namespace, "namespace"); + CONST_ID(href, "href"); + VALUE ns = rb_funcall(node, namespace, 0); + + if (NIL_P(ns)) { + return GUMBO_NAMESPACE_HTML; + } + ns = rb_funcall(ns, href, 0); + assert(RTEST(ns)); + Check_Type(ns, T_STRING); + + const char *href_ptr = RSTRING_PTR(ns); + size_t href_len = (size_t)RSTRING_LEN(ns); +#define NAMESPACE_P(uri) (href_len == sizeof uri - 1 && !memcmp(href_ptr, uri, href_len)) + if (NAMESPACE_P("http://www.w3.org/1999/xhtml")) { + return GUMBO_NAMESPACE_HTML; + } + if (NAMESPACE_P("http://www.w3.org/1998/Math/MathML")) { + return GUMBO_NAMESPACE_MATHML; + } + if (NAMESPACE_P("http://www.w3.org/2000/svg")) { + return GUMBO_NAMESPACE_SVG; + } +#undef NAMESPACE_P + if (require_known_ns) { + rb_raise(rb_eArgError, "Unexpected namespace URI \"%*s\"", (int)href_len, href_ptr); + } + return -1; +} + +static xmlNodePtr +extract_xml_node(VALUE node) +{ + xmlNodePtr xml_node; + Noko_Node_Get_Struct(node, xmlNode, xml_node); + return xml_node; +} + +static VALUE fragment_continue(VALUE parse_args); + +/* + * @!visibility protected + */ +static VALUE +noko_gumbo_s_fragment(int argc, VALUE *argv, VALUE _self) +{ + VALUE doc_fragment; + VALUE tags; + VALUE ctx; + VALUE kwargs; + ID name = rb_intern_const("name"); + const char *ctx_tag; + GumboNamespaceEnum ctx_ns; + GumboQuirksModeEnum quirks_mode; + bool form = false; + const char *encoding = NULL; + + rb_scan_args(argc, argv, "3:", &doc_fragment, &tags, &ctx, &kwargs); + if (NIL_P(kwargs)) { + kwargs = rb_hash_new(); + } + + GumboOptions options = common_options(kwargs); + + if (NIL_P(ctx)) { + ctx_tag = "body"; + ctx_ns = GUMBO_NAMESPACE_HTML; + } else if (TYPE(ctx) == T_STRING) { + ctx_tag = StringValueCStr(ctx); + ctx_ns = GUMBO_NAMESPACE_HTML; + size_t len = (size_t)RSTRING_LEN(ctx); + const char *colon = memchr(ctx_tag, ':', len); + if (colon) { + switch (colon - ctx_tag) { + case 3: + if (st_strncasecmp(ctx_tag, "svg", 3) != 0) { + goto error; + } + ctx_ns = GUMBO_NAMESPACE_SVG; + break; + case 4: + if (st_strncasecmp(ctx_tag, "html", 4) == 0) { + ctx_ns = GUMBO_NAMESPACE_HTML; + } else if (st_strncasecmp(ctx_tag, "math", 4) == 0) { + ctx_ns = GUMBO_NAMESPACE_MATHML; + } else { + goto error; + } + break; + default: +error: + rb_raise(rb_eArgError, "Invalid context namespace '%*s'", (int)(colon - ctx_tag), ctx_tag); + } + ctx_tag = colon + 1; + } else { + // For convenience, put 'svg' and 'math' in their namespaces. + if (len == 3 && st_strncasecmp(ctx_tag, "svg", 3) == 0) { + ctx_ns = GUMBO_NAMESPACE_SVG; + } else if (len == 4 && st_strncasecmp(ctx_tag, "math", 4) == 0) { + ctx_ns = GUMBO_NAMESPACE_MATHML; + } + } + + // Check if it's a form. + form = ctx_ns == GUMBO_NAMESPACE_HTML && st_strcasecmp(ctx_tag, "form") == 0; + } else { + ID element_ = rb_intern_const("element?"); + + // Context fragment name. + VALUE tag_name = rb_funcall(ctx, name, 0); + assert(RTEST(tag_name)); + Check_Type(tag_name, T_STRING); + ctx_tag = StringValueCStr(tag_name); + + // Context fragment namespace. + ctx_ns = lookup_namespace(ctx, true); + + // Check for a form ancestor, including self. + for (VALUE node = ctx; + !NIL_P(node); + node = rb_respond_to(node, parent) ? rb_funcall(node, parent, 0) : Qnil) { + if (!RTEST(rb_funcall(node, element_, 0))) { + continue; + } + VALUE element_name = rb_funcall(node, name, 0); + if (RSTRING_LEN(element_name) == 4 + && !st_strcasecmp(RSTRING_PTR(element_name), "form") + && lookup_namespace(node, false) == GUMBO_NAMESPACE_HTML) { + form = true; + break; + } + } + + // Encoding. + if (ctx_ns == GUMBO_NAMESPACE_MATHML + && RSTRING_LEN(tag_name) == 14 + && !st_strcasecmp(ctx_tag, "annotation-xml")) { + VALUE enc = rb_funcall(ctx, rb_intern_const("[]"), + 1, + rb_utf8_str_new_static("encoding", 8)); + if (RTEST(enc)) { + Check_Type(enc, T_STRING); + encoding = StringValueCStr(enc); + } + } + } + + // Quirks mode. + VALUE doc = rb_funcall(doc_fragment, rb_intern_const("document"), 0); + VALUE dtd = rb_funcall(doc, internal_subset, 0); + VALUE doc_quirks_mode = rb_iv_get(doc, "@quirks_mode"); + if (NIL_P(ctx) || (TYPE(ctx) == T_STRING) || NIL_P(doc_quirks_mode)) { + quirks_mode = GUMBO_DOCTYPE_NO_QUIRKS; + } else if (NIL_P(dtd)) { + quirks_mode = GUMBO_DOCTYPE_QUIRKS; + } else { + VALUE dtd_name = rb_funcall(dtd, name, 0); + VALUE pubid = rb_funcall(dtd, rb_intern_const("external_id"), 0); + VALUE sysid = rb_funcall(dtd, rb_intern_const("system_id"), 0); + quirks_mode = gumbo_compute_quirks_mode( + NIL_P(dtd_name) ? NULL : StringValueCStr(dtd_name), + NIL_P(pubid) ? NULL : StringValueCStr(pubid), + NIL_P(sysid) ? NULL : StringValueCStr(sysid) + ); + } + + // Perform a fragment parse. + options.fragment_context = ctx_tag; + options.fragment_namespace = ctx_ns; + options.fragment_encoding = encoding; + options.quirks_mode = quirks_mode; + options.fragment_context_has_form_ancestor = form; + + // Add one to the max tree depth to account for the HTML element. + if (options.max_tree_depth < UINT_MAX) { options.max_tree_depth++; } + + GumboOutput *output = perform_parse(&options, tags); + ParseArgs args = { + .output = output, + .input = tags, + .url_or_frag = doc_fragment, + .doc = (xmlDocPtr)extract_xml_node(doc), + }; + rb_ensure(fragment_continue, (VALUE)(&args), parse_cleanup, (VALUE)(&args)); + return Qnil; +} + +static VALUE +fragment_continue(VALUE parse_args) +{ + ParseArgs *args = (ParseArgs *)parse_args; + GumboOutput *output = args->output; + VALUE doc_fragment = args->url_or_frag; + xmlDocPtr xml_doc = args->doc; + + args->doc = NULL; // The Ruby runtime owns doc so make sure we don't delete it. + xmlNodePtr xml_frag = extract_xml_node(doc_fragment); + build_tree(xml_doc, xml_frag, output->root); + rb_iv_set(doc_fragment, "@quirks_mode", INT2NUM(output->document->v.document.doc_type_quirks_mode)); + add_errors(output, doc_fragment, args->input, rb_utf8_str_new_static("#fragment", 9)); + return Qnil; +} + +// Initialize the Nokogumbo class and fetch constants we will use later. +void +noko_init_gumbo(void) +{ + // Class constants. + cNokogiriHtml5Document = rb_define_class_under(mNokogiriHtml5, "Document", cNokogiriHtml4Document); + rb_gc_register_mark_object(cNokogiriHtml5Document); + + // Interned symbols. + internal_subset = rb_intern_const("internal_subset"); + parent = rb_intern_const("parent"); + + // Define Nokogumbo module with parse and fragment methods. + rb_define_singleton_method(mNokogiriGumbo, "parse", noko_gumbo_s_parse, -1); + rb_define_singleton_method(mNokogiriGumbo, "fragment", noko_gumbo_s_fragment, -1); +} + +// vim: set shiftwidth=2 softtabstop=2 tabstop=8 expandtab: diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/html4_document.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/html4_document.c new file mode 100644 index 00000000..e3e0ee08 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/html4_document.c @@ -0,0 +1,171 @@ +#include + +VALUE cNokogiriHtml4Document ; + +static ID id_encoding_found; +static ID id_to_s; + +/* + * call-seq: + * new(uri=nil, external_id=nil) → HTML4::Document + * + * Create a new empty document with base URI +uri+ and external ID +external_id+. + */ +static VALUE +rb_html_document_s_new(int argc, VALUE *argv, VALUE klass) +{ + VALUE uri, external_id, rest, rb_doc; + htmlDocPtr doc; + + rb_scan_args(argc, argv, "0*", &rest); + uri = rb_ary_entry(rest, (long)0); + external_id = rb_ary_entry(rest, (long)1); + + doc = htmlNewDoc( + RTEST(uri) ? (const xmlChar *)StringValueCStr(uri) : NULL, + RTEST(external_id) ? (const xmlChar *)StringValueCStr(external_id) : NULL + ); + rb_doc = noko_xml_document_wrap_with_init_args(klass, doc, argc, argv); + return rb_doc ; +} + +/* + * call-seq: + * read_io(io, url, encoding, options) + * + * Read the HTML document from +io+ with given +url+, +encoding+, + * and +options+. See Nokogiri::HTML4.parse + */ +static VALUE +rb_html_document_s_read_io(VALUE klass, VALUE rb_io, VALUE rb_url, VALUE rb_encoding, VALUE rb_options) +{ + VALUE rb_doc; + VALUE rb_error_list = rb_ary_new(); + htmlDocPtr c_doc; + const char *c_url = NIL_P(rb_url) ? NULL : StringValueCStr(rb_url); + const char *c_encoding = NIL_P(rb_encoding) ? NULL : StringValueCStr(rb_encoding); + int options = NUM2INT(rb_options); + + xmlSetStructuredErrorFunc((void *)rb_error_list, noko__error_array_pusher); + + c_doc = htmlReadIO(noko_io_read, noko_io_close, (void *)rb_io, c_url, c_encoding, options); + + xmlSetStructuredErrorFunc(NULL, NULL); + + /* + * If EncodingFound has occurred in EncodingReader, make sure to do + * a cleanup and propagate the error. + */ + if (rb_respond_to(rb_io, id_encoding_found)) { + VALUE encoding_found = rb_funcall(rb_io, id_encoding_found, 0); + if (!NIL_P(encoding_found)) { + xmlFreeDoc(c_doc); + rb_exc_raise(encoding_found); + } + } + + if ((c_doc == NULL) || (!(options & XML_PARSE_RECOVER) && (RARRAY_LEN(rb_error_list) > 0))) { + VALUE rb_error ; + + xmlFreeDoc(c_doc); + + rb_error = rb_ary_entry(rb_error_list, 0); + if (rb_error == Qnil) { + rb_raise(rb_eRuntimeError, "Could not parse document"); + } else { + VALUE exception_message = rb_funcall(rb_error, id_to_s, 0); + exception_message = rb_str_concat(rb_str_new2("Parser without recover option encountered error or warning: "), + exception_message); + rb_exc_raise(rb_class_new_instance(1, &exception_message, cNokogiriXmlSyntaxError)); + } + + return Qnil; + } + + rb_doc = noko_xml_document_wrap(klass, c_doc); + rb_iv_set(rb_doc, "@errors", rb_error_list); + return rb_doc; +} + +/* + * call-seq: + * read_memory(string, url, encoding, options) + * + * Read the HTML document contained in +string+ with given +url+, +encoding+, + * and +options+. See Nokogiri::HTML4.parse + */ +static VALUE +rb_html_document_s_read_memory(VALUE klass, VALUE rb_html, VALUE rb_url, VALUE rb_encoding, VALUE rb_options) +{ + VALUE rb_doc; + VALUE rb_error_list = rb_ary_new(); + htmlDocPtr c_doc; + const char *c_buffer = StringValuePtr(rb_html); + const char *c_url = NIL_P(rb_url) ? NULL : StringValueCStr(rb_url); + const char *c_encoding = NIL_P(rb_encoding) ? NULL : StringValueCStr(rb_encoding); + int html_len = (int)RSTRING_LEN(rb_html); + int options = NUM2INT(rb_options); + + xmlSetStructuredErrorFunc((void *)rb_error_list, noko__error_array_pusher); + + c_doc = htmlReadMemory(c_buffer, html_len, c_url, c_encoding, options); + + xmlSetStructuredErrorFunc(NULL, NULL); + + if ((c_doc == NULL) || (!(options & XML_PARSE_RECOVER) && (RARRAY_LEN(rb_error_list) > 0))) { + VALUE rb_error ; + + xmlFreeDoc(c_doc); + + rb_error = rb_ary_entry(rb_error_list, 0); + if (rb_error == Qnil) { + rb_raise(rb_eRuntimeError, "Could not parse document"); + } else { + VALUE exception_message = rb_funcall(rb_error, id_to_s, 0); + exception_message = rb_str_concat(rb_str_new2("Parser without recover option encountered error or warning: "), + exception_message); + rb_exc_raise(rb_class_new_instance(1, &exception_message, cNokogiriXmlSyntaxError)); + } + + return Qnil; + } + + rb_doc = noko_xml_document_wrap(klass, c_doc); + rb_iv_set(rb_doc, "@errors", rb_error_list); + return rb_doc; +} + +/* + * call-seq: + * type + * + * The type for this document + */ +static VALUE +rb_html_document_type(VALUE self) +{ + htmlDocPtr doc = noko_xml_document_unwrap(self); + return INT2NUM(doc->type); +} + +void +noko_init_html_document(void) +{ + /* this is here so that rdoc doesn't ignore this file. */ + /* + mNokogiri = rb_define_module("Nokogiri"); + mNokogiriHtml4 = rb_define_module_under(mNokogiri, "HTML4"); + */ + + assert(cNokogiriXmlDocument); + cNokogiriHtml4Document = rb_define_class_under(mNokogiriHtml4, "Document", cNokogiriXmlDocument); + + rb_define_singleton_method(cNokogiriHtml4Document, "read_memory", rb_html_document_s_read_memory, 4); + rb_define_singleton_method(cNokogiriHtml4Document, "read_io", rb_html_document_s_read_io, 4); + rb_define_singleton_method(cNokogiriHtml4Document, "new", rb_html_document_s_new, -1); + + rb_define_method(cNokogiriHtml4Document, "type", rb_html_document_type, 0); + + id_encoding_found = rb_intern("encoding_found"); + id_to_s = rb_intern("to_s"); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/html4_element_description.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/html4_element_description.c new file mode 100644 index 00000000..bd345d1a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/html4_element_description.c @@ -0,0 +1,299 @@ +#include + +static const rb_data_type_t html_elem_desc_type = { + .wrap_struct_name = "htmlElemDesc", + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; + +VALUE cNokogiriHtml4ElementDescription ; + +/* + * call-seq: + * required_attributes + * + * A list of required attributes for this element + */ +static VALUE +required_attributes(VALUE self) +{ + const htmlElemDesc *description; + VALUE list; + int i; + + TypedData_Get_Struct(self, htmlElemDesc, &html_elem_desc_type, description); + + list = rb_ary_new(); + + if (NULL == description->attrs_req) { return list; } + + for (i = 0; description->attrs_depr[i]; i++) { + rb_ary_push(list, NOKOGIRI_STR_NEW2(description->attrs_req[i])); + } + + return list; +} + +/* + * call-seq: + * deprecated_attributes + * + * A list of deprecated attributes for this element + */ +static VALUE +deprecated_attributes(VALUE self) +{ + const htmlElemDesc *description; + VALUE list; + int i; + + TypedData_Get_Struct(self, htmlElemDesc, &html_elem_desc_type, description); + + list = rb_ary_new(); + + if (NULL == description->attrs_depr) { return list; } + + for (i = 0; description->attrs_depr[i]; i++) { + rb_ary_push(list, NOKOGIRI_STR_NEW2(description->attrs_depr[i])); + } + + return list; +} + +/* + * call-seq: + * optional_attributes + * + * A list of optional attributes for this element + */ +static VALUE +optional_attributes(VALUE self) +{ + const htmlElemDesc *description; + VALUE list; + int i; + + TypedData_Get_Struct(self, htmlElemDesc, &html_elem_desc_type, description); + + list = rb_ary_new(); + + if (NULL == description->attrs_opt) { return list; } + + for (i = 0; description->attrs_opt[i]; i++) { + rb_ary_push(list, NOKOGIRI_STR_NEW2(description->attrs_opt[i])); + } + + return list; +} + +/* + * call-seq: + * default_sub_element + * + * The default sub element for this element + */ +static VALUE +default_sub_element(VALUE self) +{ + const htmlElemDesc *description; + TypedData_Get_Struct(self, htmlElemDesc, &html_elem_desc_type, description); + + if (description->defaultsubelt) { + return NOKOGIRI_STR_NEW2(description->defaultsubelt); + } + + return Qnil; +} + +/* + * call-seq: + * sub_elements + * + * A list of allowed sub elements for this element. + */ +static VALUE +sub_elements(VALUE self) +{ + const htmlElemDesc *description; + VALUE list; + int i; + + TypedData_Get_Struct(self, htmlElemDesc, &html_elem_desc_type, description); + + list = rb_ary_new(); + + if (NULL == description->subelts) { return list; } + + for (i = 0; description->subelts[i]; i++) { + rb_ary_push(list, NOKOGIRI_STR_NEW2(description->subelts[i])); + } + + return list; +} + +/* + * call-seq: + * description + * + * The description for this element + */ +static VALUE +description(VALUE self) +{ + const htmlElemDesc *description; + TypedData_Get_Struct(self, htmlElemDesc, &html_elem_desc_type, description); + + return NOKOGIRI_STR_NEW2(description->desc); +} + +/* + * call-seq: + * inline? + * + * Is this element an inline element? + */ +static VALUE +inline_eh(VALUE self) +{ + const htmlElemDesc *description; + TypedData_Get_Struct(self, htmlElemDesc, &html_elem_desc_type, description); + + if (description->isinline) { return Qtrue; } + return Qfalse; +} + +/* + * call-seq: + * deprecated? + * + * Is this element deprecated? + */ +static VALUE +deprecated_eh(VALUE self) +{ + const htmlElemDesc *description; + TypedData_Get_Struct(self, htmlElemDesc, &html_elem_desc_type, description); + + if (description->depr) { return Qtrue; } + return Qfalse; +} + +/* + * call-seq: + * empty? + * + * Is this an empty element? + */ +static VALUE +empty_eh(VALUE self) +{ + const htmlElemDesc *description; + TypedData_Get_Struct(self, htmlElemDesc, &html_elem_desc_type, description); + + if (description->empty) { return Qtrue; } + return Qfalse; +} + +/* + * call-seq: + * save_end_tag? + * + * Should the end tag be saved? + */ +static VALUE +save_end_tag_eh(VALUE self) +{ + const htmlElemDesc *description; + TypedData_Get_Struct(self, htmlElemDesc, &html_elem_desc_type, description); + + if (description->saveEndTag) { return Qtrue; } + return Qfalse; +} + +/* + * call-seq: + * implied_end_tag? + * + * Can the end tag be implied for this tag? + */ +static VALUE +implied_end_tag_eh(VALUE self) +{ + const htmlElemDesc *description; + TypedData_Get_Struct(self, htmlElemDesc, &html_elem_desc_type, description); + + if (description->endTag) { return Qtrue; } + return Qfalse; +} + +/* + * call-seq: + * implied_start_tag? + * + * Can the start tag be implied for this tag? + */ +static VALUE +implied_start_tag_eh(VALUE self) +{ + const htmlElemDesc *description; + TypedData_Get_Struct(self, htmlElemDesc, &html_elem_desc_type, description); + + if (description->startTag) { return Qtrue; } + return Qfalse; +} + +/* + * call-seq: + * name + * + * Get the tag name for this ElementDescription + */ +static VALUE +name(VALUE self) +{ + const htmlElemDesc *description; + TypedData_Get_Struct(self, htmlElemDesc, &html_elem_desc_type, description); + + if (NULL == description->name) { return Qnil; } + return NOKOGIRI_STR_NEW2(description->name); +} + +/* + * call-seq: + * [](tag_name) + * + * Get ElementDescription for +tag_name+ + */ +static VALUE +get_description(VALUE klass, VALUE tag_name) +{ + const htmlElemDesc *description = htmlTagLookup( + (const xmlChar *)StringValueCStr(tag_name) + ); + + if (NULL == description) { return Qnil; } + return TypedData_Wrap_Struct(klass, &html_elem_desc_type, DISCARD_CONST_QUAL(void *, description)); +} + +void +noko_init_html_element_description(void) +{ + cNokogiriHtml4ElementDescription = rb_define_class_under(mNokogiriHtml4, "ElementDescription", rb_cObject); + + rb_undef_alloc_func(cNokogiriHtml4ElementDescription); + + rb_define_singleton_method(cNokogiriHtml4ElementDescription, "[]", get_description, 1); + + rb_define_method(cNokogiriHtml4ElementDescription, "name", name, 0); + rb_define_method(cNokogiriHtml4ElementDescription, "implied_start_tag?", implied_start_tag_eh, 0); + rb_define_method(cNokogiriHtml4ElementDescription, "implied_end_tag?", implied_end_tag_eh, 0); + rb_define_method(cNokogiriHtml4ElementDescription, "save_end_tag?", save_end_tag_eh, 0); + rb_define_method(cNokogiriHtml4ElementDescription, "empty?", empty_eh, 0); + rb_define_method(cNokogiriHtml4ElementDescription, "deprecated?", deprecated_eh, 0); + rb_define_method(cNokogiriHtml4ElementDescription, "inline?", inline_eh, 0); + rb_define_method(cNokogiriHtml4ElementDescription, "description", description, 0); + rb_define_method(cNokogiriHtml4ElementDescription, "sub_elements", sub_elements, 0); + rb_define_method(cNokogiriHtml4ElementDescription, "default_sub_element", default_sub_element, 0); + rb_define_method(cNokogiriHtml4ElementDescription, "optional_attributes", optional_attributes, 0); + rb_define_method(cNokogiriHtml4ElementDescription, "deprecated_attributes", deprecated_attributes, 0); + rb_define_method(cNokogiriHtml4ElementDescription, "required_attributes", required_attributes, 0); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/html4_entity_lookup.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/html4_entity_lookup.c new file mode 100644 index 00000000..85ad3842 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/html4_entity_lookup.c @@ -0,0 +1,37 @@ +#include + +static VALUE cNokogiriHtml4EntityLookup; + +/* + * call-seq: + * get(key) + * + * Get the HTML4::EntityDescription for +key+ + */ +static VALUE +get(VALUE _, VALUE rb_entity_name) +{ + VALUE cNokogiriHtml4EntityDescription; + const htmlEntityDesc *c_entity_desc; + VALUE rb_constructor_args[3]; + + c_entity_desc = htmlEntityLookup((const xmlChar *)StringValueCStr(rb_entity_name)); + if (NULL == c_entity_desc) { + return Qnil; + } + + rb_constructor_args[0] = UINT2NUM(c_entity_desc->value); + rb_constructor_args[1] = NOKOGIRI_STR_NEW2(c_entity_desc->name); + rb_constructor_args[2] = NOKOGIRI_STR_NEW2(c_entity_desc->desc); + + cNokogiriHtml4EntityDescription = rb_const_get_at(mNokogiriHtml4, rb_intern("EntityDescription")); + return rb_class_new_instance(3, rb_constructor_args, cNokogiriHtml4EntityDescription); +} + +void +noko_init_html_entity_lookup(void) +{ + cNokogiriHtml4EntityLookup = rb_define_class_under(mNokogiriHtml4, "EntityLookup", rb_cObject); + + rb_define_method(cNokogiriHtml4EntityLookup, "get", get, 1); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/html4_sax_parser.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/html4_sax_parser.c new file mode 100644 index 00000000..2316ec2b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/html4_sax_parser.c @@ -0,0 +1,40 @@ +#include + +VALUE cNokogiriHtml4SaxParser; + +static ID id_start_document; + +static void +noko_html4_sax_parser_start_document(void *ctx) +{ + xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr)ctx; + VALUE self = (VALUE)ctxt->_private; + VALUE doc = rb_iv_get(self, "@document"); + + xmlSAX2StartDocument(ctx); + + rb_funcall(doc, id_start_document, 0); +} + +static VALUE +noko_html4_sax_parser_initialize(VALUE self) +{ + xmlSAXHandlerPtr handler = noko_xml_sax_parser_unwrap(self); + + rb_call_super(0, NULL); + + handler->startDocument = noko_html4_sax_parser_start_document; + + return self; +} + +void +noko_init_html4_sax_parser(void) +{ + cNokogiriHtml4SaxParser = rb_define_class_under(mNokogiriHtml4Sax, "Parser", cNokogiriXmlSaxParser); + + rb_define_private_method(cNokogiriHtml4SaxParser, "initialize_native", + noko_html4_sax_parser_initialize, 0); + + id_start_document = rb_intern("start_document"); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/html4_sax_parser_context.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/html4_sax_parser_context.c new file mode 100644 index 00000000..6f971d1f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/html4_sax_parser_context.c @@ -0,0 +1,98 @@ +#include + +VALUE cNokogiriHtml4SaxParserContext ; + +/* :nodoc: */ +static VALUE +noko_html4_sax_parser_context_s_native_memory(VALUE rb_class, VALUE rb_input, VALUE rb_encoding) +{ + Check_Type(rb_input, T_STRING); + if (!(int)RSTRING_LEN(rb_input)) { + rb_raise(rb_eRuntimeError, "input string cannot be empty"); + } + + if (!NIL_P(rb_encoding) && !rb_obj_is_kind_of(rb_encoding, rb_cEncoding)) { + rb_raise(rb_eTypeError, "argument must be an Encoding object"); + } + + htmlParserCtxtPtr c_context = + htmlCreateMemoryParserCtxt(StringValuePtr(rb_input), (int)RSTRING_LEN(rb_input)); + if (!c_context) { + rb_raise(rb_eRuntimeError, "failed to create xml sax parser context"); + } + + noko_xml_sax_parser_context_set_encoding(c_context, rb_encoding); + + if (c_context->sax) { + xmlFree(c_context->sax); + c_context->sax = NULL; + } + + return noko_xml_sax_parser_context_wrap(rb_class, c_context); +} + +/* :nodoc: */ +static VALUE +noko_html4_sax_parser_context_s_native_file(VALUE rb_class, VALUE rb_filename, VALUE rb_encoding) +{ + if (!NIL_P(rb_encoding) && !rb_obj_is_kind_of(rb_encoding, rb_cEncoding)) { + rb_raise(rb_eTypeError, "argument must be an Encoding object"); + } + + htmlParserCtxtPtr c_context = htmlCreateFileParserCtxt(StringValueCStr(rb_filename), NULL); + if (!c_context) { + rb_raise(rb_eRuntimeError, "failed to create xml sax parser context"); + } + + noko_xml_sax_parser_context_set_encoding(c_context, rb_encoding); + + if (c_context->sax) { + xmlFree(c_context->sax); + c_context->sax = NULL; + } + + return noko_xml_sax_parser_context_wrap(rb_class, c_context); +} + +static VALUE +noko_html4_sax_parser_context__parse_with(VALUE rb_context, VALUE rb_sax_parser) +{ + htmlParserCtxtPtr ctxt; + htmlSAXHandlerPtr sax; + + if (!rb_obj_is_kind_of(rb_sax_parser, cNokogiriXmlSaxParser)) { + rb_raise(rb_eArgError, "argument must be a Nokogiri::XML::SAX::Parser"); + } + + ctxt = noko_xml_sax_parser_context_unwrap(rb_context); + sax = noko_xml_sax_parser_unwrap(rb_sax_parser); + + ctxt->sax = sax; + ctxt->userData = ctxt; /* so we can use libxml2/SAX2.c handlers if we want to */ + ctxt->_private = (void *)rb_sax_parser; + + xmlSetStructuredErrorFunc(NULL, NULL); + + /* although we're calling back into Ruby here, we don't need to worry about exceptions, because we + * don't have any cleanup to do. The only memory we need to free is handled by + * xml_sax_parser_context_type_free */ + htmlParseDocument(ctxt); + + return Qnil; +} + +void +noko_init_html_sax_parser_context(void) +{ + assert(cNokogiriXmlSaxParserContext); + cNokogiriHtml4SaxParserContext = rb_define_class_under(mNokogiriHtml4Sax, "ParserContext", + cNokogiriXmlSaxParserContext); + + rb_define_singleton_method(cNokogiriHtml4SaxParserContext, "native_memory", + noko_html4_sax_parser_context_s_native_memory, 2); + rb_define_singleton_method(cNokogiriHtml4SaxParserContext, "native_file", + noko_html4_sax_parser_context_s_native_file, 2); + + rb_define_method(cNokogiriHtml4SaxParserContext, "parse_with", + noko_html4_sax_parser_context__parse_with, 1); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/html4_sax_push_parser.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/html4_sax_push_parser.c new file mode 100644 index 00000000..845baf0b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/html4_sax_push_parser.c @@ -0,0 +1,96 @@ +#include + +VALUE cNokogiriHtml4SaxPushParser; + +/* + * Write +chunk+ to PushParser. +last_chunk+ triggers the end_document handle + */ +static VALUE +noko_html4_sax_push_parser__native_write(VALUE self, VALUE rb_chunk, VALUE rb_last_chunk) +{ + xmlParserCtxtPtr ctx; + const char *chunk = NULL; + int size = 0; + int status = 0; + libxmlStructuredErrorHandlerState handler_state; + + ctx = noko_xml_sax_push_parser_unwrap(self); + + if (Qnil != rb_chunk) { + chunk = StringValuePtr(rb_chunk); + size = (int)RSTRING_LEN(rb_chunk); + } + + noko__structured_error_func_save_and_set(&handler_state, NULL, NULL); + + status = htmlParseChunk(ctx, chunk, size, Qtrue == rb_last_chunk ? 1 : 0); + + noko__structured_error_func_restore(&handler_state); + + if ((status != 0) && !(xmlCtxtGetOptions(ctx) & XML_PARSE_RECOVER)) { + // TODO: there appear to be no tests for this block + xmlErrorConstPtr e = xmlCtxtGetLastError(ctx); + noko__error_raise(NULL, e); + } + + return self; +} + +/* + * Initialize the push parser with +xml_sax+ using +filename+ + */ +static VALUE +noko_html4_sax_push_parser__initialize_native( + VALUE self, + VALUE rb_xml_sax, + VALUE rb_filename, + VALUE encoding +) +{ + htmlSAXHandlerPtr sax; + const char *filename = NULL; + htmlParserCtxtPtr ctx; + xmlCharEncoding enc = XML_CHAR_ENCODING_NONE; + + sax = noko_xml_sax_parser_unwrap(rb_xml_sax); + + if (rb_filename != Qnil) { filename = StringValueCStr(rb_filename); } + + if (!NIL_P(encoding)) { + enc = xmlParseCharEncoding(StringValueCStr(encoding)); + if (enc == XML_CHAR_ENCODING_ERROR) { + rb_raise(rb_eArgError, "Unsupported Encoding"); + } + } + + ctx = htmlCreatePushParserCtxt( + sax, + NULL, + NULL, + 0, + filename, + enc + ); + if (ctx == NULL) { + rb_raise(rb_eRuntimeError, "Could not create a parser context"); + } + + ctx->userData = ctx; + ctx->_private = (void *)rb_xml_sax; + + DATA_PTR(self) = ctx; + return self; +} + +void +noko_init_html_sax_push_parser(void) +{ + assert(cNokogiriXmlSaxPushParser); + cNokogiriHtml4SaxPushParser = + rb_define_class_under(mNokogiriHtml4Sax, "PushParser", cNokogiriXmlSaxPushParser); + + rb_define_private_method(cNokogiriHtml4SaxPushParser, "initialize_native", + noko_html4_sax_push_parser__initialize_native, 3); + rb_define_private_method(cNokogiriHtml4SaxPushParser, "native_write", + noko_html4_sax_push_parser__native_write, 2); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libexslt/exslt.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libexslt/exslt.h new file mode 100644 index 00000000..dfbd09be --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libexslt/exslt.h @@ -0,0 +1,108 @@ +/* + * Summary: main header file + * + * Copy: See Copyright for the status of this software. + */ + + +#ifndef __EXSLT_H__ +#define __EXSLT_H__ + +#include +#include +#include "exsltexports.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +EXSLTPUBVAR const char *exsltLibraryVersion; +EXSLTPUBVAR const int exsltLibexsltVersion; +EXSLTPUBVAR const int exsltLibxsltVersion; +EXSLTPUBVAR const int exsltLibxmlVersion; + +/** + * EXSLT_COMMON_NAMESPACE: + * + * Namespace for EXSLT common functions + */ +#define EXSLT_COMMON_NAMESPACE ((const xmlChar *) "http://exslt.org/common") +/** + * EXSLT_CRYPTO_NAMESPACE: + * + * Namespace for EXSLT crypto functions + */ +#define EXSLT_CRYPTO_NAMESPACE ((const xmlChar *) "http://exslt.org/crypto") +/** + * EXSLT_MATH_NAMESPACE: + * + * Namespace for EXSLT math functions + */ +#define EXSLT_MATH_NAMESPACE ((const xmlChar *) "http://exslt.org/math") +/** + * EXSLT_SETS_NAMESPACE: + * + * Namespace for EXSLT set functions + */ +#define EXSLT_SETS_NAMESPACE ((const xmlChar *) "http://exslt.org/sets") +/** + * EXSLT_FUNCTIONS_NAMESPACE: + * + * Namespace for EXSLT functions extension functions + */ +#define EXSLT_FUNCTIONS_NAMESPACE ((const xmlChar *) "http://exslt.org/functions") +/** + * EXSLT_STRINGS_NAMESPACE: + * + * Namespace for EXSLT strings functions + */ +#define EXSLT_STRINGS_NAMESPACE ((const xmlChar *) "http://exslt.org/strings") +/** + * EXSLT_DATE_NAMESPACE: + * + * Namespace for EXSLT date functions + */ +#define EXSLT_DATE_NAMESPACE ((const xmlChar *) "http://exslt.org/dates-and-times") +/** + * EXSLT_DYNAMIC_NAMESPACE: + * + * Namespace for EXSLT dynamic functions + */ +#define EXSLT_DYNAMIC_NAMESPACE ((const xmlChar *) "http://exslt.org/dynamic") + +/** + * SAXON_NAMESPACE: + * + * Namespace for SAXON extensions functions + */ +#define SAXON_NAMESPACE ((const xmlChar *) "http://icl.com/saxon") + +EXSLTPUBFUN void EXSLTCALL exsltCommonRegister (void); +#ifdef EXSLT_CRYPTO_ENABLED +EXSLTPUBFUN void EXSLTCALL exsltCryptoRegister (void); +#endif +EXSLTPUBFUN void EXSLTCALL exsltMathRegister (void); +EXSLTPUBFUN void EXSLTCALL exsltSetsRegister (void); +EXSLTPUBFUN void EXSLTCALL exsltFuncRegister (void); +EXSLTPUBFUN void EXSLTCALL exsltStrRegister (void); +EXSLTPUBFUN void EXSLTCALL exsltDateRegister (void); +EXSLTPUBFUN void EXSLTCALL exsltSaxonRegister (void); +EXSLTPUBFUN void EXSLTCALL exsltDynRegister(void); + +EXSLTPUBFUN void EXSLTCALL exsltRegisterAll (void); + +EXSLTPUBFUN int EXSLTCALL exsltDateXpathCtxtRegister (xmlXPathContextPtr ctxt, + const xmlChar *prefix); +EXSLTPUBFUN int EXSLTCALL exsltMathXpathCtxtRegister (xmlXPathContextPtr ctxt, + const xmlChar *prefix); +EXSLTPUBFUN int EXSLTCALL exsltSetsXpathCtxtRegister (xmlXPathContextPtr ctxt, + const xmlChar *prefix); +EXSLTPUBFUN int EXSLTCALL exsltStrXpathCtxtRegister (xmlXPathContextPtr ctxt, + const xmlChar *prefix); + +#ifdef __cplusplus +} +#endif +#endif /* __EXSLT_H__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libexslt/exsltconfig.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libexslt/exsltconfig.h new file mode 100644 index 00000000..10e43b0f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libexslt/exsltconfig.h @@ -0,0 +1,70 @@ +/* + * exsltconfig.h: compile-time version information for the EXSLT library + * + * See Copyright for the status of this software. + * + * daniel@veillard.com + */ + +#ifndef __XML_EXSLTCONFIG_H__ +#define __XML_EXSLTCONFIG_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * LIBEXSLT_DOTTED_VERSION: + * + * the version string like "1.2.3" + */ +#define LIBEXSLT_DOTTED_VERSION "0.8.24" + +/** + * LIBEXSLT_VERSION: + * + * the version number: 1.2.3 value is 10203 + */ +#define LIBEXSLT_VERSION 824 + +/** + * LIBEXSLT_VERSION_STRING: + * + * the version number string, 1.2.3 value is "10203" + */ +#define LIBEXSLT_VERSION_STRING "824" + +/** + * LIBEXSLT_VERSION_EXTRA: + * + * extra version information, used to show a Git commit description + */ +#define LIBEXSLT_VERSION_EXTRA "" + +/** + * WITH_CRYPTO: + * + * Whether crypto support is configured into exslt + */ +#if 0 +#define EXSLT_CRYPTO_ENABLED +#endif + +/** + * ATTRIBUTE_UNUSED: + * + * This macro is used to flag unused function parameters to GCC + */ +#ifdef __GNUC__ +#ifndef ATTRIBUTE_UNUSED +#define ATTRIBUTE_UNUSED __attribute__((unused)) +#endif +#else +#define ATTRIBUTE_UNUSED +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_EXSLTCONFIG_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libexslt/exsltexports.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libexslt/exsltexports.h new file mode 100644 index 00000000..ee79ec7a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libexslt/exsltexports.h @@ -0,0 +1,63 @@ +/* + * Summary: macros for marking symbols as exportable/importable. + * + * Copy: See Copyright for the status of this software. + */ + +#ifndef __EXSLT_EXPORTS_H__ +#define __EXSLT_EXPORTS_H__ + +#if defined(_WIN32) || defined(__CYGWIN__) +/** DOC_DISABLE */ + +#ifdef LIBEXSLT_STATIC + #define EXSLTPUBLIC +#elif defined(IN_LIBEXSLT) + #define EXSLTPUBLIC __declspec(dllexport) +#else + #define EXSLTPUBLIC __declspec(dllimport) +#endif + +#define EXSLTCALL __cdecl + +/** DOC_ENABLE */ +#else /* not Windows */ + +/** + * EXSLTPUBLIC: + * + * Macro which declares a public symbol + */ +#define EXSLTPUBLIC + +/** + * EXSLTCALL: + * + * Macro which declares the calling convention for exported functions + */ +#define EXSLTCALL + +#endif /* platform switch */ + +/* + * EXSLTPUBFUN: + * + * Macro which declares an exportable function + */ +#define EXSLTPUBFUN EXSLTPUBLIC + +/** + * EXSLTPUBVAR: + * + * Macro which declares an exportable variable + */ +#define EXSLTPUBVAR EXSLTPUBLIC extern + +/* Compatibility */ +#if !defined(LIBEXSLT_PUBLIC) +#define LIBEXSLT_PUBLIC EXSLTPUBVAR +#endif + +#endif /* __EXSLT_EXPORTS_H__ */ + + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/HTMLparser.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/HTMLparser.h new file mode 100644 index 00000000..7be3d2b8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/HTMLparser.h @@ -0,0 +1,336 @@ +/* + * Summary: interface for an HTML 4.0 non-verifying parser + * Description: this module implements an HTML 4.0 non-verifying parser + * with API compatible with the XML parser ones. It should + * be able to parse "real world" HTML, even if severely + * broken from a specification point of view. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __HTML_PARSER_H__ +#define __HTML_PARSER_H__ +#include +#include + +#ifdef LIBXML_HTML_ENABLED + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Most of the back-end structures from XML and HTML are shared. + */ +typedef xmlParserCtxt htmlParserCtxt; +typedef xmlParserCtxtPtr htmlParserCtxtPtr; +typedef xmlParserNodeInfo htmlParserNodeInfo; +typedef xmlSAXHandler htmlSAXHandler; +typedef xmlSAXHandlerPtr htmlSAXHandlerPtr; +typedef xmlParserInput htmlParserInput; +typedef xmlParserInputPtr htmlParserInputPtr; +typedef xmlDocPtr htmlDocPtr; +typedef xmlNodePtr htmlNodePtr; + +/* + * Internal description of an HTML element, representing HTML 4.01 + * and XHTML 1.0 (which share the same structure). + */ +typedef struct _htmlElemDesc htmlElemDesc; +typedef htmlElemDesc *htmlElemDescPtr; +struct _htmlElemDesc { + const char *name; /* The tag name */ + char startTag; /* Whether the start tag can be implied */ + char endTag; /* Whether the end tag can be implied */ + char saveEndTag; /* Whether the end tag should be saved */ + char empty; /* Is this an empty element ? */ + char depr; /* Is this a deprecated element ? */ + char dtd; /* 1: only in Loose DTD, 2: only Frameset one */ + char isinline; /* is this a block 0 or inline 1 element */ + const char *desc; /* the description */ + +/* NRK Jan.2003 + * New fields encapsulating HTML structure + * + * Bugs: + * This is a very limited representation. It fails to tell us when + * an element *requires* subelements (we only have whether they're + * allowed or not), and it doesn't tell us where CDATA and PCDATA + * are allowed. Some element relationships are not fully represented: + * these are flagged with the word MODIFIER + */ + const char** subelts; /* allowed sub-elements of this element */ + const char* defaultsubelt; /* subelement for suggested auto-repair + if necessary or NULL */ + const char** attrs_opt; /* Optional Attributes */ + const char** attrs_depr; /* Additional deprecated attributes */ + const char** attrs_req; /* Required attributes */ +}; + +/* + * Internal description of an HTML entity. + */ +typedef struct _htmlEntityDesc htmlEntityDesc; +typedef htmlEntityDesc *htmlEntityDescPtr; +struct _htmlEntityDesc { + unsigned int value; /* the UNICODE value for the character */ + const char *name; /* The entity name */ + const char *desc; /* the description */ +}; + +#ifdef LIBXML_SAX1_ENABLED + +XML_DEPRECATED +XMLPUBVAR const xmlSAXHandlerV1 htmlDefaultSAXHandler; + +#ifdef LIBXML_THREAD_ENABLED +XML_DEPRECATED +XMLPUBFUN const xmlSAXHandlerV1 *__htmlDefaultSAXHandler(void); +#endif + +#endif /* LIBXML_SAX1_ENABLED */ + +/* + * There is only few public functions. + */ +XML_DEPRECATED +XMLPUBFUN void + htmlInitAutoClose (void); +XMLPUBFUN const htmlElemDesc * + htmlTagLookup (const xmlChar *tag); +XMLPUBFUN const htmlEntityDesc * + htmlEntityLookup(const xmlChar *name); +XMLPUBFUN const htmlEntityDesc * + htmlEntityValueLookup(unsigned int value); + +XMLPUBFUN int + htmlIsAutoClosed(htmlDocPtr doc, + htmlNodePtr elem); +XMLPUBFUN int + htmlAutoCloseTag(htmlDocPtr doc, + const xmlChar *name, + htmlNodePtr elem); +XML_DEPRECATED +XMLPUBFUN const htmlEntityDesc * + htmlParseEntityRef(htmlParserCtxtPtr ctxt, + const xmlChar **str); +XML_DEPRECATED +XMLPUBFUN int + htmlParseCharRef(htmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN void + htmlParseElement(htmlParserCtxtPtr ctxt); + +XMLPUBFUN htmlParserCtxtPtr + htmlNewParserCtxt(void); +XMLPUBFUN htmlParserCtxtPtr + htmlNewSAXParserCtxt(const htmlSAXHandler *sax, + void *userData); + +XMLPUBFUN htmlParserCtxtPtr + htmlCreateMemoryParserCtxt(const char *buffer, + int size); + +XMLPUBFUN int + htmlParseDocument(htmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN htmlDocPtr + htmlSAXParseDoc (const xmlChar *cur, + const char *encoding, + htmlSAXHandlerPtr sax, + void *userData); +XMLPUBFUN htmlDocPtr + htmlParseDoc (const xmlChar *cur, + const char *encoding); +XMLPUBFUN htmlParserCtxtPtr + htmlCreateFileParserCtxt(const char *filename, + const char *encoding); +XML_DEPRECATED +XMLPUBFUN htmlDocPtr + htmlSAXParseFile(const char *filename, + const char *encoding, + htmlSAXHandlerPtr sax, + void *userData); +XMLPUBFUN htmlDocPtr + htmlParseFile (const char *filename, + const char *encoding); +XMLPUBFUN int + UTF8ToHtml (unsigned char *out, + int *outlen, + const unsigned char *in, + int *inlen); +XMLPUBFUN int + htmlEncodeEntities(unsigned char *out, + int *outlen, + const unsigned char *in, + int *inlen, int quoteChar); +XMLPUBFUN int + htmlIsScriptAttribute(const xmlChar *name); +XML_DEPRECATED +XMLPUBFUN int + htmlHandleOmittedElem(int val); + +#ifdef LIBXML_PUSH_ENABLED +/** + * Interfaces for the Push mode. + */ +XMLPUBFUN htmlParserCtxtPtr + htmlCreatePushParserCtxt(htmlSAXHandlerPtr sax, + void *user_data, + const char *chunk, + int size, + const char *filename, + xmlCharEncoding enc); +XMLPUBFUN int + htmlParseChunk (htmlParserCtxtPtr ctxt, + const char *chunk, + int size, + int terminate); +#endif /* LIBXML_PUSH_ENABLED */ + +XMLPUBFUN void + htmlFreeParserCtxt (htmlParserCtxtPtr ctxt); + +/* + * New set of simpler/more flexible APIs + */ +/** + * xmlParserOption: + * + * This is the set of XML parser options that can be passed down + * to the xmlReadDoc() and similar calls. + */ +typedef enum { + HTML_PARSE_RECOVER = 1<<0, /* Relaxed parsing */ + HTML_PARSE_NODEFDTD = 1<<2, /* do not default a doctype if not found */ + HTML_PARSE_NOERROR = 1<<5, /* suppress error reports */ + HTML_PARSE_NOWARNING= 1<<6, /* suppress warning reports */ + HTML_PARSE_PEDANTIC = 1<<7, /* pedantic error reporting */ + HTML_PARSE_NOBLANKS = 1<<8, /* remove blank nodes */ + HTML_PARSE_NONET = 1<<11,/* Forbid network access */ + HTML_PARSE_NOIMPLIED= 1<<13,/* Do not add implied html/body... elements */ + HTML_PARSE_COMPACT = 1<<16,/* compact small text nodes */ + HTML_PARSE_IGNORE_ENC=1<<21 /* ignore internal document encoding hint */ +} htmlParserOption; + +XMLPUBFUN void + htmlCtxtReset (htmlParserCtxtPtr ctxt); +XMLPUBFUN int + htmlCtxtUseOptions (htmlParserCtxtPtr ctxt, + int options); +XMLPUBFUN htmlDocPtr + htmlReadDoc (const xmlChar *cur, + const char *URL, + const char *encoding, + int options); +XMLPUBFUN htmlDocPtr + htmlReadFile (const char *URL, + const char *encoding, + int options); +XMLPUBFUN htmlDocPtr + htmlReadMemory (const char *buffer, + int size, + const char *URL, + const char *encoding, + int options); +XMLPUBFUN htmlDocPtr + htmlReadFd (int fd, + const char *URL, + const char *encoding, + int options); +XMLPUBFUN htmlDocPtr + htmlReadIO (xmlInputReadCallback ioread, + xmlInputCloseCallback ioclose, + void *ioctx, + const char *URL, + const char *encoding, + int options); +XMLPUBFUN htmlDocPtr + htmlCtxtParseDocument (htmlParserCtxtPtr ctxt, + xmlParserInputPtr input); +XMLPUBFUN htmlDocPtr + htmlCtxtReadDoc (xmlParserCtxtPtr ctxt, + const xmlChar *cur, + const char *URL, + const char *encoding, + int options); +XMLPUBFUN htmlDocPtr + htmlCtxtReadFile (xmlParserCtxtPtr ctxt, + const char *filename, + const char *encoding, + int options); +XMLPUBFUN htmlDocPtr + htmlCtxtReadMemory (xmlParserCtxtPtr ctxt, + const char *buffer, + int size, + const char *URL, + const char *encoding, + int options); +XMLPUBFUN htmlDocPtr + htmlCtxtReadFd (xmlParserCtxtPtr ctxt, + int fd, + const char *URL, + const char *encoding, + int options); +XMLPUBFUN htmlDocPtr + htmlCtxtReadIO (xmlParserCtxtPtr ctxt, + xmlInputReadCallback ioread, + xmlInputCloseCallback ioclose, + void *ioctx, + const char *URL, + const char *encoding, + int options); + +/* NRK/Jan2003: further knowledge of HTML structure + */ +typedef enum { + HTML_NA = 0 , /* something we don't check at all */ + HTML_INVALID = 0x1 , + HTML_DEPRECATED = 0x2 , + HTML_VALID = 0x4 , + HTML_REQUIRED = 0xc /* VALID bit set so ( & HTML_VALID ) is TRUE */ +} htmlStatus ; + +/* Using htmlElemDesc rather than name here, to emphasise the fact + that otherwise there's a lookup overhead +*/ +XMLPUBFUN htmlStatus htmlAttrAllowed(const htmlElemDesc*, const xmlChar*, int) ; +XMLPUBFUN int htmlElementAllowedHere(const htmlElemDesc*, const xmlChar*) ; +XMLPUBFUN htmlStatus htmlElementStatusHere(const htmlElemDesc*, const htmlElemDesc*) ; +XMLPUBFUN htmlStatus htmlNodeStatus(htmlNodePtr, int) ; +/** + * htmlDefaultSubelement: + * @elt: HTML element + * + * Returns the default subelement for this element + */ +#define htmlDefaultSubelement(elt) elt->defaultsubelt +/** + * htmlElementAllowedHereDesc: + * @parent: HTML parent element + * @elt: HTML element + * + * Checks whether an HTML element description may be a + * direct child of the specified element. + * + * Returns 1 if allowed; 0 otherwise. + */ +#define htmlElementAllowedHereDesc(parent,elt) \ + htmlElementAllowedHere((parent), (elt)->name) +/** + * htmlRequiredAttrs: + * @elt: HTML element + * + * Returns the attributes required for the specified element. + */ +#define htmlRequiredAttrs(elt) (elt)->attrs_req + + +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_HTML_ENABLED */ +#endif /* __HTML_PARSER_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/HTMLtree.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/HTMLtree.h new file mode 100644 index 00000000..8e1ba90e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/HTMLtree.h @@ -0,0 +1,147 @@ +/* + * Summary: specific APIs to process HTML tree, especially serialization + * Description: this module implements a few function needed to process + * tree in an HTML specific way. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __HTML_TREE_H__ +#define __HTML_TREE_H__ + +#include +#include +#include +#include + +#ifdef LIBXML_HTML_ENABLED + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * HTML_TEXT_NODE: + * + * Macro. A text node in a HTML document is really implemented + * the same way as a text node in an XML document. + */ +#define HTML_TEXT_NODE XML_TEXT_NODE +/** + * HTML_ENTITY_REF_NODE: + * + * Macro. An entity reference in a HTML document is really implemented + * the same way as an entity reference in an XML document. + */ +#define HTML_ENTITY_REF_NODE XML_ENTITY_REF_NODE +/** + * HTML_COMMENT_NODE: + * + * Macro. A comment in a HTML document is really implemented + * the same way as a comment in an XML document. + */ +#define HTML_COMMENT_NODE XML_COMMENT_NODE +/** + * HTML_PRESERVE_NODE: + * + * Macro. A preserved node in a HTML document is really implemented + * the same way as a CDATA section in an XML document. + */ +#define HTML_PRESERVE_NODE XML_CDATA_SECTION_NODE +/** + * HTML_PI_NODE: + * + * Macro. A processing instruction in a HTML document is really implemented + * the same way as a processing instruction in an XML document. + */ +#define HTML_PI_NODE XML_PI_NODE + +XMLPUBFUN htmlDocPtr + htmlNewDoc (const xmlChar *URI, + const xmlChar *ExternalID); +XMLPUBFUN htmlDocPtr + htmlNewDocNoDtD (const xmlChar *URI, + const xmlChar *ExternalID); +XMLPUBFUN const xmlChar * + htmlGetMetaEncoding (htmlDocPtr doc); +XMLPUBFUN int + htmlSetMetaEncoding (htmlDocPtr doc, + const xmlChar *encoding); +#ifdef LIBXML_OUTPUT_ENABLED +XMLPUBFUN void + htmlDocDumpMemory (xmlDocPtr cur, + xmlChar **mem, + int *size); +XMLPUBFUN void + htmlDocDumpMemoryFormat (xmlDocPtr cur, + xmlChar **mem, + int *size, + int format); +XMLPUBFUN int + htmlDocDump (FILE *f, + xmlDocPtr cur); +XMLPUBFUN int + htmlSaveFile (const char *filename, + xmlDocPtr cur); +XMLPUBFUN int + htmlNodeDump (xmlBufferPtr buf, + xmlDocPtr doc, + xmlNodePtr cur); +XMLPUBFUN void + htmlNodeDumpFile (FILE *out, + xmlDocPtr doc, + xmlNodePtr cur); +XMLPUBFUN int + htmlNodeDumpFileFormat (FILE *out, + xmlDocPtr doc, + xmlNodePtr cur, + const char *encoding, + int format); +XMLPUBFUN int + htmlSaveFileEnc (const char *filename, + xmlDocPtr cur, + const char *encoding); +XMLPUBFUN int + htmlSaveFileFormat (const char *filename, + xmlDocPtr cur, + const char *encoding, + int format); + +XMLPUBFUN void + htmlNodeDumpFormatOutput(xmlOutputBufferPtr buf, + xmlDocPtr doc, + xmlNodePtr cur, + const char *encoding, + int format); +XMLPUBFUN void + htmlDocContentDumpOutput(xmlOutputBufferPtr buf, + xmlDocPtr cur, + const char *encoding); +XMLPUBFUN void + htmlDocContentDumpFormatOutput(xmlOutputBufferPtr buf, + xmlDocPtr cur, + const char *encoding, + int format); +XMLPUBFUN void + htmlNodeDumpOutput (xmlOutputBufferPtr buf, + xmlDocPtr doc, + xmlNodePtr cur, + const char *encoding); + +#endif /* LIBXML_OUTPUT_ENABLED */ + +XMLPUBFUN int + htmlIsBooleanAttr (const xmlChar *name); + + +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_HTML_ENABLED */ + +#endif /* __HTML_TREE_H__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/SAX.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/SAX.h new file mode 100644 index 00000000..eea1057b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/SAX.h @@ -0,0 +1,202 @@ +/* + * Summary: Old SAX version 1 handler, deprecated + * Description: DEPRECATED set of SAX version 1 interfaces used to + * build the DOM tree. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + + +#ifndef __XML_SAX_H__ +#define __XML_SAX_H__ + +#include +#include + +#ifdef LIBXML_LEGACY_ENABLED + +#ifdef __cplusplus +extern "C" { +#endif +XML_DEPRECATED +XMLPUBFUN const xmlChar * + getPublicId (void *ctx); +XML_DEPRECATED +XMLPUBFUN const xmlChar * + getSystemId (void *ctx); +XML_DEPRECATED +XMLPUBFUN void + setDocumentLocator (void *ctx, + xmlSAXLocatorPtr loc); + +XML_DEPRECATED +XMLPUBFUN int + getLineNumber (void *ctx); +XML_DEPRECATED +XMLPUBFUN int + getColumnNumber (void *ctx); + +XML_DEPRECATED +XMLPUBFUN int + isStandalone (void *ctx); +XML_DEPRECATED +XMLPUBFUN int + hasInternalSubset (void *ctx); +XML_DEPRECATED +XMLPUBFUN int + hasExternalSubset (void *ctx); + +XML_DEPRECATED +XMLPUBFUN void + internalSubset (void *ctx, + const xmlChar *name, + const xmlChar *ExternalID, + const xmlChar *SystemID); +XML_DEPRECATED +XMLPUBFUN void + externalSubset (void *ctx, + const xmlChar *name, + const xmlChar *ExternalID, + const xmlChar *SystemID); +XML_DEPRECATED +XMLPUBFUN xmlEntityPtr + getEntity (void *ctx, + const xmlChar *name); +XML_DEPRECATED +XMLPUBFUN xmlEntityPtr + getParameterEntity (void *ctx, + const xmlChar *name); +XML_DEPRECATED +XMLPUBFUN xmlParserInputPtr + resolveEntity (void *ctx, + const xmlChar *publicId, + const xmlChar *systemId); + +XML_DEPRECATED +XMLPUBFUN void + entityDecl (void *ctx, + const xmlChar *name, + int type, + const xmlChar *publicId, + const xmlChar *systemId, + xmlChar *content); +XML_DEPRECATED +XMLPUBFUN void + attributeDecl (void *ctx, + const xmlChar *elem, + const xmlChar *fullname, + int type, + int def, + const xmlChar *defaultValue, + xmlEnumerationPtr tree); +XML_DEPRECATED +XMLPUBFUN void + elementDecl (void *ctx, + const xmlChar *name, + int type, + xmlElementContentPtr content); +XML_DEPRECATED +XMLPUBFUN void + notationDecl (void *ctx, + const xmlChar *name, + const xmlChar *publicId, + const xmlChar *systemId); +XML_DEPRECATED +XMLPUBFUN void + unparsedEntityDecl (void *ctx, + const xmlChar *name, + const xmlChar *publicId, + const xmlChar *systemId, + const xmlChar *notationName); + +XML_DEPRECATED +XMLPUBFUN void + startDocument (void *ctx); +XML_DEPRECATED +XMLPUBFUN void + endDocument (void *ctx); +XML_DEPRECATED +XMLPUBFUN void + attribute (void *ctx, + const xmlChar *fullname, + const xmlChar *value); +XML_DEPRECATED +XMLPUBFUN void + startElement (void *ctx, + const xmlChar *fullname, + const xmlChar **atts); +XML_DEPRECATED +XMLPUBFUN void + endElement (void *ctx, + const xmlChar *name); +XML_DEPRECATED +XMLPUBFUN void + reference (void *ctx, + const xmlChar *name); +XML_DEPRECATED +XMLPUBFUN void + characters (void *ctx, + const xmlChar *ch, + int len); +XML_DEPRECATED +XMLPUBFUN void + ignorableWhitespace (void *ctx, + const xmlChar *ch, + int len); +XML_DEPRECATED +XMLPUBFUN void + processingInstruction (void *ctx, + const xmlChar *target, + const xmlChar *data); +XML_DEPRECATED +XMLPUBFUN void + globalNamespace (void *ctx, + const xmlChar *href, + const xmlChar *prefix); +XML_DEPRECATED +XMLPUBFUN void + setNamespace (void *ctx, + const xmlChar *name); +XML_DEPRECATED +XMLPUBFUN xmlNsPtr + getNamespace (void *ctx); +XML_DEPRECATED +XMLPUBFUN int + checkNamespace (void *ctx, + xmlChar *nameSpace); +XML_DEPRECATED +XMLPUBFUN void + namespaceDecl (void *ctx, + const xmlChar *href, + const xmlChar *prefix); +XML_DEPRECATED +XMLPUBFUN void + comment (void *ctx, + const xmlChar *value); +XML_DEPRECATED +XMLPUBFUN void + cdataBlock (void *ctx, + const xmlChar *value, + int len); + +#ifdef LIBXML_SAX1_ENABLED +XML_DEPRECATED +XMLPUBFUN void + initxmlDefaultSAXHandler (xmlSAXHandlerV1 *hdlr, + int warning); +#ifdef LIBXML_HTML_ENABLED +XML_DEPRECATED +XMLPUBFUN void + inithtmlDefaultSAXHandler (xmlSAXHandlerV1 *hdlr); +#endif +#endif /* LIBXML_SAX1_ENABLED */ + +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_LEGACY_ENABLED */ + +#endif /* __XML_SAX_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/SAX2.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/SAX2.h new file mode 100644 index 00000000..4c4ecce8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/SAX2.h @@ -0,0 +1,171 @@ +/* + * Summary: SAX2 parser interface used to build the DOM tree + * Description: those are the default SAX2 interfaces used by + * the library when building DOM tree. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + + +#ifndef __XML_SAX2_H__ +#define __XML_SAX2_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif +XMLPUBFUN const xmlChar * + xmlSAX2GetPublicId (void *ctx); +XMLPUBFUN const xmlChar * + xmlSAX2GetSystemId (void *ctx); +XMLPUBFUN void + xmlSAX2SetDocumentLocator (void *ctx, + xmlSAXLocatorPtr loc); + +XMLPUBFUN int + xmlSAX2GetLineNumber (void *ctx); +XMLPUBFUN int + xmlSAX2GetColumnNumber (void *ctx); + +XMLPUBFUN int + xmlSAX2IsStandalone (void *ctx); +XMLPUBFUN int + xmlSAX2HasInternalSubset (void *ctx); +XMLPUBFUN int + xmlSAX2HasExternalSubset (void *ctx); + +XMLPUBFUN void + xmlSAX2InternalSubset (void *ctx, + const xmlChar *name, + const xmlChar *ExternalID, + const xmlChar *SystemID); +XMLPUBFUN void + xmlSAX2ExternalSubset (void *ctx, + const xmlChar *name, + const xmlChar *ExternalID, + const xmlChar *SystemID); +XMLPUBFUN xmlEntityPtr + xmlSAX2GetEntity (void *ctx, + const xmlChar *name); +XMLPUBFUN xmlEntityPtr + xmlSAX2GetParameterEntity (void *ctx, + const xmlChar *name); +XMLPUBFUN xmlParserInputPtr + xmlSAX2ResolveEntity (void *ctx, + const xmlChar *publicId, + const xmlChar *systemId); + +XMLPUBFUN void + xmlSAX2EntityDecl (void *ctx, + const xmlChar *name, + int type, + const xmlChar *publicId, + const xmlChar *systemId, + xmlChar *content); +XMLPUBFUN void + xmlSAX2AttributeDecl (void *ctx, + const xmlChar *elem, + const xmlChar *fullname, + int type, + int def, + const xmlChar *defaultValue, + xmlEnumerationPtr tree); +XMLPUBFUN void + xmlSAX2ElementDecl (void *ctx, + const xmlChar *name, + int type, + xmlElementContentPtr content); +XMLPUBFUN void + xmlSAX2NotationDecl (void *ctx, + const xmlChar *name, + const xmlChar *publicId, + const xmlChar *systemId); +XMLPUBFUN void + xmlSAX2UnparsedEntityDecl (void *ctx, + const xmlChar *name, + const xmlChar *publicId, + const xmlChar *systemId, + const xmlChar *notationName); + +XMLPUBFUN void + xmlSAX2StartDocument (void *ctx); +XMLPUBFUN void + xmlSAX2EndDocument (void *ctx); +#if defined(LIBXML_SAX1_ENABLED) || defined(LIBXML_HTML_ENABLED) || \ + defined(LIBXML_WRITER_ENABLED) || defined(LIBXML_LEGACY_ENABLED) +XMLPUBFUN void + xmlSAX2StartElement (void *ctx, + const xmlChar *fullname, + const xmlChar **atts); +XMLPUBFUN void + xmlSAX2EndElement (void *ctx, + const xmlChar *name); +#endif /* LIBXML_SAX1_ENABLED or LIBXML_HTML_ENABLED or LIBXML_LEGACY_ENABLED */ +XMLPUBFUN void + xmlSAX2StartElementNs (void *ctx, + const xmlChar *localname, + const xmlChar *prefix, + const xmlChar *URI, + int nb_namespaces, + const xmlChar **namespaces, + int nb_attributes, + int nb_defaulted, + const xmlChar **attributes); +XMLPUBFUN void + xmlSAX2EndElementNs (void *ctx, + const xmlChar *localname, + const xmlChar *prefix, + const xmlChar *URI); +XMLPUBFUN void + xmlSAX2Reference (void *ctx, + const xmlChar *name); +XMLPUBFUN void + xmlSAX2Characters (void *ctx, + const xmlChar *ch, + int len); +XMLPUBFUN void + xmlSAX2IgnorableWhitespace (void *ctx, + const xmlChar *ch, + int len); +XMLPUBFUN void + xmlSAX2ProcessingInstruction (void *ctx, + const xmlChar *target, + const xmlChar *data); +XMLPUBFUN void + xmlSAX2Comment (void *ctx, + const xmlChar *value); +XMLPUBFUN void + xmlSAX2CDataBlock (void *ctx, + const xmlChar *value, + int len); + +#ifdef LIBXML_SAX1_ENABLED +XML_DEPRECATED +XMLPUBFUN int + xmlSAXDefaultVersion (int version); +#endif /* LIBXML_SAX1_ENABLED */ + +XMLPUBFUN int + xmlSAXVersion (xmlSAXHandler *hdlr, + int version); +XMLPUBFUN void + xmlSAX2InitDefaultSAXHandler (xmlSAXHandler *hdlr, + int warning); +#ifdef LIBXML_HTML_ENABLED +XMLPUBFUN void + xmlSAX2InitHtmlDefaultSAXHandler(xmlSAXHandler *hdlr); +XML_DEPRECATED +XMLPUBFUN void + htmlDefaultSAXHandlerInit (void); +#endif +XML_DEPRECATED +XMLPUBFUN void + xmlDefaultSAXHandlerInit (void); +#ifdef __cplusplus +} +#endif +#endif /* __XML_SAX2_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/c14n.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/c14n.h new file mode 100644 index 00000000..8ccd1cef --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/c14n.h @@ -0,0 +1,115 @@ +/* + * Summary: Provide Canonical XML and Exclusive XML Canonicalization + * Description: the c14n modules provides a + * + * "Canonical XML" implementation + * http://www.w3.org/TR/xml-c14n + * + * and an + * + * "Exclusive XML Canonicalization" implementation + * http://www.w3.org/TR/xml-exc-c14n + + * Copy: See Copyright for the status of this software. + * + * Author: Aleksey Sanin + */ +#ifndef __XML_C14N_H__ +#define __XML_C14N_H__ + +#include + +#ifdef LIBXML_C14N_ENABLED + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * XML Canonicalization + * http://www.w3.org/TR/xml-c14n + * + * Exclusive XML Canonicalization + * http://www.w3.org/TR/xml-exc-c14n + * + * Canonical form of an XML document could be created if and only if + * a) default attributes (if any) are added to all nodes + * b) all character and parsed entity references are resolved + * In order to achieve this in libxml2 the document MUST be loaded with + * following options: XML_PARSE_DTDATTR | XML_PARSE_NOENT + */ + +/* + * xmlC14NMode: + * + * Predefined values for C14N modes + * + */ +typedef enum { + XML_C14N_1_0 = 0, /* Original C14N 1.0 spec */ + XML_C14N_EXCLUSIVE_1_0 = 1, /* Exclusive C14N 1.0 spec */ + XML_C14N_1_1 = 2 /* C14N 1.1 spec */ +} xmlC14NMode; + +XMLPUBFUN int + xmlC14NDocSaveTo (xmlDocPtr doc, + xmlNodeSetPtr nodes, + int mode, /* a xmlC14NMode */ + xmlChar **inclusive_ns_prefixes, + int with_comments, + xmlOutputBufferPtr buf); + +XMLPUBFUN int + xmlC14NDocDumpMemory (xmlDocPtr doc, + xmlNodeSetPtr nodes, + int mode, /* a xmlC14NMode */ + xmlChar **inclusive_ns_prefixes, + int with_comments, + xmlChar **doc_txt_ptr); + +XMLPUBFUN int + xmlC14NDocSave (xmlDocPtr doc, + xmlNodeSetPtr nodes, + int mode, /* a xmlC14NMode */ + xmlChar **inclusive_ns_prefixes, + int with_comments, + const char* filename, + int compression); + + +/** + * This is the core C14N function + */ +/** + * xmlC14NIsVisibleCallback: + * @user_data: user data + * @node: the current node + * @parent: the parent node + * + * Signature for a C14N callback on visible nodes + * + * Returns 1 if the node should be included + */ +typedef int (*xmlC14NIsVisibleCallback) (void* user_data, + xmlNodePtr node, + xmlNodePtr parent); + +XMLPUBFUN int + xmlC14NExecute (xmlDocPtr doc, + xmlC14NIsVisibleCallback is_visible_callback, + void* user_data, + int mode, /* a xmlC14NMode */ + xmlChar **inclusive_ns_prefixes, + int with_comments, + xmlOutputBufferPtr buf); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* LIBXML_C14N_ENABLED */ +#endif /* __XML_C14N_H__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/catalog.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/catalog.h new file mode 100644 index 00000000..02fa7ab2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/catalog.h @@ -0,0 +1,182 @@ +/** + * Summary: interfaces to the Catalog handling system + * Description: the catalog module implements the support for + * XML Catalogs and SGML catalogs + * + * SGML Open Technical Resolution TR9401:1997. + * http://www.jclark.com/sp/catalog.htm + * + * XML Catalogs Working Draft 06 August 2001 + * http://www.oasis-open.org/committees/entity/spec-2001-08-06.html + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_CATALOG_H__ +#define __XML_CATALOG_H__ + +#include + +#include +#include +#include + +#ifdef LIBXML_CATALOG_ENABLED + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * XML_CATALOGS_NAMESPACE: + * + * The namespace for the XML Catalogs elements. + */ +#define XML_CATALOGS_NAMESPACE \ + (const xmlChar *) "urn:oasis:names:tc:entity:xmlns:xml:catalog" +/** + * XML_CATALOG_PI: + * + * The specific XML Catalog Processing Instruction name. + */ +#define XML_CATALOG_PI \ + (const xmlChar *) "oasis-xml-catalog" + +/* + * The API is voluntarily limited to general cataloging. + */ +typedef enum { + XML_CATA_PREFER_NONE = 0, + XML_CATA_PREFER_PUBLIC = 1, + XML_CATA_PREFER_SYSTEM +} xmlCatalogPrefer; + +typedef enum { + XML_CATA_ALLOW_NONE = 0, + XML_CATA_ALLOW_GLOBAL = 1, + XML_CATA_ALLOW_DOCUMENT = 2, + XML_CATA_ALLOW_ALL = 3 +} xmlCatalogAllow; + +typedef struct _xmlCatalog xmlCatalog; +typedef xmlCatalog *xmlCatalogPtr; + +/* + * Operations on a given catalog. + */ +XMLPUBFUN xmlCatalogPtr + xmlNewCatalog (int sgml); +XMLPUBFUN xmlCatalogPtr + xmlLoadACatalog (const char *filename); +XMLPUBFUN xmlCatalogPtr + xmlLoadSGMLSuperCatalog (const char *filename); +XMLPUBFUN int + xmlConvertSGMLCatalog (xmlCatalogPtr catal); +XMLPUBFUN int + xmlACatalogAdd (xmlCatalogPtr catal, + const xmlChar *type, + const xmlChar *orig, + const xmlChar *replace); +XMLPUBFUN int + xmlACatalogRemove (xmlCatalogPtr catal, + const xmlChar *value); +XMLPUBFUN xmlChar * + xmlACatalogResolve (xmlCatalogPtr catal, + const xmlChar *pubID, + const xmlChar *sysID); +XMLPUBFUN xmlChar * + xmlACatalogResolveSystem(xmlCatalogPtr catal, + const xmlChar *sysID); +XMLPUBFUN xmlChar * + xmlACatalogResolvePublic(xmlCatalogPtr catal, + const xmlChar *pubID); +XMLPUBFUN xmlChar * + xmlACatalogResolveURI (xmlCatalogPtr catal, + const xmlChar *URI); +#ifdef LIBXML_OUTPUT_ENABLED +XMLPUBFUN void + xmlACatalogDump (xmlCatalogPtr catal, + FILE *out); +#endif /* LIBXML_OUTPUT_ENABLED */ +XMLPUBFUN void + xmlFreeCatalog (xmlCatalogPtr catal); +XMLPUBFUN int + xmlCatalogIsEmpty (xmlCatalogPtr catal); + +/* + * Global operations. + */ +XMLPUBFUN void + xmlInitializeCatalog (void); +XMLPUBFUN int + xmlLoadCatalog (const char *filename); +XMLPUBFUN void + xmlLoadCatalogs (const char *paths); +XMLPUBFUN void + xmlCatalogCleanup (void); +#ifdef LIBXML_OUTPUT_ENABLED +XMLPUBFUN void + xmlCatalogDump (FILE *out); +#endif /* LIBXML_OUTPUT_ENABLED */ +XMLPUBFUN xmlChar * + xmlCatalogResolve (const xmlChar *pubID, + const xmlChar *sysID); +XMLPUBFUN xmlChar * + xmlCatalogResolveSystem (const xmlChar *sysID); +XMLPUBFUN xmlChar * + xmlCatalogResolvePublic (const xmlChar *pubID); +XMLPUBFUN xmlChar * + xmlCatalogResolveURI (const xmlChar *URI); +XMLPUBFUN int + xmlCatalogAdd (const xmlChar *type, + const xmlChar *orig, + const xmlChar *replace); +XMLPUBFUN int + xmlCatalogRemove (const xmlChar *value); +XMLPUBFUN xmlDocPtr + xmlParseCatalogFile (const char *filename); +XMLPUBFUN int + xmlCatalogConvert (void); + +/* + * Strictly minimal interfaces for per-document catalogs used + * by the parser. + */ +XMLPUBFUN void + xmlCatalogFreeLocal (void *catalogs); +XMLPUBFUN void * + xmlCatalogAddLocal (void *catalogs, + const xmlChar *URL); +XMLPUBFUN xmlChar * + xmlCatalogLocalResolve (void *catalogs, + const xmlChar *pubID, + const xmlChar *sysID); +XMLPUBFUN xmlChar * + xmlCatalogLocalResolveURI(void *catalogs, + const xmlChar *URI); +/* + * Preference settings. + */ +XMLPUBFUN int + xmlCatalogSetDebug (int level); +XMLPUBFUN xmlCatalogPrefer + xmlCatalogSetDefaultPrefer(xmlCatalogPrefer prefer); +XMLPUBFUN void + xmlCatalogSetDefaults (xmlCatalogAllow allow); +XMLPUBFUN xmlCatalogAllow + xmlCatalogGetDefaults (void); + + +/* DEPRECATED interfaces */ +XMLPUBFUN const xmlChar * + xmlCatalogGetSystem (const xmlChar *sysID); +XMLPUBFUN const xmlChar * + xmlCatalogGetPublic (const xmlChar *pubID); + +#ifdef __cplusplus +} +#endif +#endif /* LIBXML_CATALOG_ENABLED */ +#endif /* __XML_CATALOG_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/chvalid.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/chvalid.h new file mode 100644 index 00000000..8225c95e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/chvalid.h @@ -0,0 +1,230 @@ +/* + * Summary: Unicode character range checking + * Description: this module exports interfaces for the character + * range validation APIs + * + * This file is automatically generated from the cvs source + * definition files using the genChRanges.py Python script + * + * Generation date: Mon Mar 27 11:09:48 2006 + * Sources: chvalid.def + * Author: William Brack + */ + +#ifndef __XML_CHVALID_H__ +#define __XML_CHVALID_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Define our typedefs and structures + * + */ +typedef struct _xmlChSRange xmlChSRange; +typedef xmlChSRange *xmlChSRangePtr; +struct _xmlChSRange { + unsigned short low; + unsigned short high; +}; + +typedef struct _xmlChLRange xmlChLRange; +typedef xmlChLRange *xmlChLRangePtr; +struct _xmlChLRange { + unsigned int low; + unsigned int high; +}; + +typedef struct _xmlChRangeGroup xmlChRangeGroup; +typedef xmlChRangeGroup *xmlChRangeGroupPtr; +struct _xmlChRangeGroup { + int nbShortRange; + int nbLongRange; + const xmlChSRange *shortRange; /* points to an array of ranges */ + const xmlChLRange *longRange; +}; + +/** + * Range checking routine + */ +XMLPUBFUN int + xmlCharInRange(unsigned int val, const xmlChRangeGroup *group); + + +/** + * xmlIsBaseChar_ch: + * @c: char to validate + * + * Automatically generated by genChRanges.py + */ +#define xmlIsBaseChar_ch(c) (((0x41 <= (c)) && ((c) <= 0x5a)) || \ + ((0x61 <= (c)) && ((c) <= 0x7a)) || \ + ((0xc0 <= (c)) && ((c) <= 0xd6)) || \ + ((0xd8 <= (c)) && ((c) <= 0xf6)) || \ + (0xf8 <= (c))) + +/** + * xmlIsBaseCharQ: + * @c: char to validate + * + * Automatically generated by genChRanges.py + */ +#define xmlIsBaseCharQ(c) (((c) < 0x100) ? \ + xmlIsBaseChar_ch((c)) : \ + xmlCharInRange((c), &xmlIsBaseCharGroup)) + +XMLPUBVAR const xmlChRangeGroup xmlIsBaseCharGroup; + +/** + * xmlIsBlank_ch: + * @c: char to validate + * + * Automatically generated by genChRanges.py + */ +#define xmlIsBlank_ch(c) (((c) == 0x20) || \ + ((0x9 <= (c)) && ((c) <= 0xa)) || \ + ((c) == 0xd)) + +/** + * xmlIsBlankQ: + * @c: char to validate + * + * Automatically generated by genChRanges.py + */ +#define xmlIsBlankQ(c) (((c) < 0x100) ? \ + xmlIsBlank_ch((c)) : 0) + + +/** + * xmlIsChar_ch: + * @c: char to validate + * + * Automatically generated by genChRanges.py + */ +#define xmlIsChar_ch(c) (((0x9 <= (c)) && ((c) <= 0xa)) || \ + ((c) == 0xd) || \ + (0x20 <= (c))) + +/** + * xmlIsCharQ: + * @c: char to validate + * + * Automatically generated by genChRanges.py + */ +#define xmlIsCharQ(c) (((c) < 0x100) ? \ + xmlIsChar_ch((c)) :\ + (((0x100 <= (c)) && ((c) <= 0xd7ff)) || \ + ((0xe000 <= (c)) && ((c) <= 0xfffd)) || \ + ((0x10000 <= (c)) && ((c) <= 0x10ffff)))) + +XMLPUBVAR const xmlChRangeGroup xmlIsCharGroup; + +/** + * xmlIsCombiningQ: + * @c: char to validate + * + * Automatically generated by genChRanges.py + */ +#define xmlIsCombiningQ(c) (((c) < 0x100) ? \ + 0 : \ + xmlCharInRange((c), &xmlIsCombiningGroup)) + +XMLPUBVAR const xmlChRangeGroup xmlIsCombiningGroup; + +/** + * xmlIsDigit_ch: + * @c: char to validate + * + * Automatically generated by genChRanges.py + */ +#define xmlIsDigit_ch(c) (((0x30 <= (c)) && ((c) <= 0x39))) + +/** + * xmlIsDigitQ: + * @c: char to validate + * + * Automatically generated by genChRanges.py + */ +#define xmlIsDigitQ(c) (((c) < 0x100) ? \ + xmlIsDigit_ch((c)) : \ + xmlCharInRange((c), &xmlIsDigitGroup)) + +XMLPUBVAR const xmlChRangeGroup xmlIsDigitGroup; + +/** + * xmlIsExtender_ch: + * @c: char to validate + * + * Automatically generated by genChRanges.py + */ +#define xmlIsExtender_ch(c) (((c) == 0xb7)) + +/** + * xmlIsExtenderQ: + * @c: char to validate + * + * Automatically generated by genChRanges.py + */ +#define xmlIsExtenderQ(c) (((c) < 0x100) ? \ + xmlIsExtender_ch((c)) : \ + xmlCharInRange((c), &xmlIsExtenderGroup)) + +XMLPUBVAR const xmlChRangeGroup xmlIsExtenderGroup; + +/** + * xmlIsIdeographicQ: + * @c: char to validate + * + * Automatically generated by genChRanges.py + */ +#define xmlIsIdeographicQ(c) (((c) < 0x100) ? \ + 0 :\ + (((0x4e00 <= (c)) && ((c) <= 0x9fa5)) || \ + ((c) == 0x3007) || \ + ((0x3021 <= (c)) && ((c) <= 0x3029)))) + +XMLPUBVAR const xmlChRangeGroup xmlIsIdeographicGroup; +XMLPUBVAR const unsigned char xmlIsPubidChar_tab[256]; + +/** + * xmlIsPubidChar_ch: + * @c: char to validate + * + * Automatically generated by genChRanges.py + */ +#define xmlIsPubidChar_ch(c) (xmlIsPubidChar_tab[(c)]) + +/** + * xmlIsPubidCharQ: + * @c: char to validate + * + * Automatically generated by genChRanges.py + */ +#define xmlIsPubidCharQ(c) (((c) < 0x100) ? \ + xmlIsPubidChar_ch((c)) : 0) + +XMLPUBFUN int + xmlIsBaseChar(unsigned int ch); +XMLPUBFUN int + xmlIsBlank(unsigned int ch); +XMLPUBFUN int + xmlIsChar(unsigned int ch); +XMLPUBFUN int + xmlIsCombining(unsigned int ch); +XMLPUBFUN int + xmlIsDigit(unsigned int ch); +XMLPUBFUN int + xmlIsExtender(unsigned int ch); +XMLPUBFUN int + xmlIsIdeographic(unsigned int ch); +XMLPUBFUN int + xmlIsPubidChar(unsigned int ch); + +#ifdef __cplusplus +} +#endif +#endif /* __XML_CHVALID_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/debugXML.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/debugXML.h new file mode 100644 index 00000000..1332dd73 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/debugXML.h @@ -0,0 +1,217 @@ +/* + * Summary: Tree debugging APIs + * Description: Interfaces to a set of routines used for debugging the tree + * produced by the XML parser. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __DEBUG_XML__ +#define __DEBUG_XML__ +#include +#include +#include + +#ifdef LIBXML_DEBUG_ENABLED + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The standard Dump routines. + */ +XMLPUBFUN void + xmlDebugDumpString (FILE *output, + const xmlChar *str); +XMLPUBFUN void + xmlDebugDumpAttr (FILE *output, + xmlAttrPtr attr, + int depth); +XMLPUBFUN void + xmlDebugDumpAttrList (FILE *output, + xmlAttrPtr attr, + int depth); +XMLPUBFUN void + xmlDebugDumpOneNode (FILE *output, + xmlNodePtr node, + int depth); +XMLPUBFUN void + xmlDebugDumpNode (FILE *output, + xmlNodePtr node, + int depth); +XMLPUBFUN void + xmlDebugDumpNodeList (FILE *output, + xmlNodePtr node, + int depth); +XMLPUBFUN void + xmlDebugDumpDocumentHead(FILE *output, + xmlDocPtr doc); +XMLPUBFUN void + xmlDebugDumpDocument (FILE *output, + xmlDocPtr doc); +XMLPUBFUN void + xmlDebugDumpDTD (FILE *output, + xmlDtdPtr dtd); +XMLPUBFUN void + xmlDebugDumpEntities (FILE *output, + xmlDocPtr doc); + +/**************************************************************** + * * + * Checking routines * + * * + ****************************************************************/ + +XMLPUBFUN int + xmlDebugCheckDocument (FILE * output, + xmlDocPtr doc); + +/**************************************************************** + * * + * XML shell helpers * + * * + ****************************************************************/ + +XMLPUBFUN void + xmlLsOneNode (FILE *output, xmlNodePtr node); +XMLPUBFUN int + xmlLsCountNode (xmlNodePtr node); + +XMLPUBFUN const char * + xmlBoolToText (int boolval); + +/**************************************************************** + * * + * The XML shell related structures and functions * + * * + ****************************************************************/ + +#ifdef LIBXML_XPATH_ENABLED +/** + * xmlShellReadlineFunc: + * @prompt: a string prompt + * + * This is a generic signature for the XML shell input function. + * + * Returns a string which will be freed by the Shell. + */ +typedef char * (* xmlShellReadlineFunc)(char *prompt); + +/** + * xmlShellCtxt: + * + * A debugging shell context. + * TODO: add the defined function tables. + */ +typedef struct _xmlShellCtxt xmlShellCtxt; +typedef xmlShellCtxt *xmlShellCtxtPtr; +struct _xmlShellCtxt { + char *filename; + xmlDocPtr doc; + xmlNodePtr node; + xmlXPathContextPtr pctxt; + int loaded; + FILE *output; + xmlShellReadlineFunc input; +}; + +/** + * xmlShellCmd: + * @ctxt: a shell context + * @arg: a string argument + * @node: a first node + * @node2: a second node + * + * This is a generic signature for the XML shell functions. + * + * Returns an int, negative returns indicating errors. + */ +typedef int (* xmlShellCmd) (xmlShellCtxtPtr ctxt, + char *arg, + xmlNodePtr node, + xmlNodePtr node2); + +XMLPUBFUN void + xmlShellPrintXPathError (int errorType, + const char *arg); +XMLPUBFUN void + xmlShellPrintXPathResult(xmlXPathObjectPtr list); +XMLPUBFUN int + xmlShellList (xmlShellCtxtPtr ctxt, + char *arg, + xmlNodePtr node, + xmlNodePtr node2); +XMLPUBFUN int + xmlShellBase (xmlShellCtxtPtr ctxt, + char *arg, + xmlNodePtr node, + xmlNodePtr node2); +XMLPUBFUN int + xmlShellDir (xmlShellCtxtPtr ctxt, + char *arg, + xmlNodePtr node, + xmlNodePtr node2); +XMLPUBFUN int + xmlShellLoad (xmlShellCtxtPtr ctxt, + char *filename, + xmlNodePtr node, + xmlNodePtr node2); +#ifdef LIBXML_OUTPUT_ENABLED +XMLPUBFUN void + xmlShellPrintNode (xmlNodePtr node); +XMLPUBFUN int + xmlShellCat (xmlShellCtxtPtr ctxt, + char *arg, + xmlNodePtr node, + xmlNodePtr node2); +XMLPUBFUN int + xmlShellWrite (xmlShellCtxtPtr ctxt, + char *filename, + xmlNodePtr node, + xmlNodePtr node2); +XMLPUBFUN int + xmlShellSave (xmlShellCtxtPtr ctxt, + char *filename, + xmlNodePtr node, + xmlNodePtr node2); +#endif /* LIBXML_OUTPUT_ENABLED */ +#ifdef LIBXML_VALID_ENABLED +XMLPUBFUN int + xmlShellValidate (xmlShellCtxtPtr ctxt, + char *dtd, + xmlNodePtr node, + xmlNodePtr node2); +#endif /* LIBXML_VALID_ENABLED */ +XMLPUBFUN int + xmlShellDu (xmlShellCtxtPtr ctxt, + char *arg, + xmlNodePtr tree, + xmlNodePtr node2); +XMLPUBFUN int + xmlShellPwd (xmlShellCtxtPtr ctxt, + char *buffer, + xmlNodePtr node, + xmlNodePtr node2); + +/* + * The Shell interface. + */ +XMLPUBFUN void + xmlShell (xmlDocPtr doc, + const char *filename, + xmlShellReadlineFunc input, + FILE *output); + +#endif /* LIBXML_XPATH_ENABLED */ + +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_DEBUG_ENABLED */ +#endif /* __DEBUG_XML__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/dict.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/dict.h new file mode 100644 index 00000000..22aa3d9d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/dict.h @@ -0,0 +1,82 @@ +/* + * Summary: string dictionary + * Description: dictionary of reusable strings, just used to avoid allocation + * and freeing operations. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_DICT_H__ +#define __XML_DICT_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The dictionary. + */ +typedef struct _xmlDict xmlDict; +typedef xmlDict *xmlDictPtr; + +/* + * Initializer + */ +XML_DEPRECATED +XMLPUBFUN int xmlInitializeDict(void); + +/* + * Constructor and destructor. + */ +XMLPUBFUN xmlDictPtr + xmlDictCreate (void); +XMLPUBFUN size_t + xmlDictSetLimit (xmlDictPtr dict, + size_t limit); +XMLPUBFUN size_t + xmlDictGetUsage (xmlDictPtr dict); +XMLPUBFUN xmlDictPtr + xmlDictCreateSub(xmlDictPtr sub); +XMLPUBFUN int + xmlDictReference(xmlDictPtr dict); +XMLPUBFUN void + xmlDictFree (xmlDictPtr dict); + +/* + * Lookup of entry in the dictionary. + */ +XMLPUBFUN const xmlChar * + xmlDictLookup (xmlDictPtr dict, + const xmlChar *name, + int len); +XMLPUBFUN const xmlChar * + xmlDictExists (xmlDictPtr dict, + const xmlChar *name, + int len); +XMLPUBFUN const xmlChar * + xmlDictQLookup (xmlDictPtr dict, + const xmlChar *prefix, + const xmlChar *name); +XMLPUBFUN int + xmlDictOwns (xmlDictPtr dict, + const xmlChar *str); +XMLPUBFUN int + xmlDictSize (xmlDictPtr dict); + +/* + * Cleanup function + */ +XML_DEPRECATED +XMLPUBFUN void + xmlDictCleanup (void); + +#ifdef __cplusplus +} +#endif +#endif /* ! __XML_DICT_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/encoding.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/encoding.h new file mode 100644 index 00000000..599a03e1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/encoding.h @@ -0,0 +1,244 @@ +/* + * Summary: interface for the encoding conversion functions + * Description: interface for the encoding conversion functions needed for + * XML basic encoding and iconv() support. + * + * Related specs are + * rfc2044 (UTF-8 and UTF-16) F. Yergeau Alis Technologies + * [ISO-10646] UTF-8 and UTF-16 in Annexes + * [ISO-8859-1] ISO Latin-1 characters codes. + * [UNICODE] The Unicode Consortium, "The Unicode Standard -- + * Worldwide Character Encoding -- Version 1.0", Addison- + * Wesley, Volume 1, 1991, Volume 2, 1992. UTF-8 is + * described in Unicode Technical Report #4. + * [US-ASCII] Coded Character Set--7-bit American Standard Code for + * Information Interchange, ANSI X3.4-1986. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_CHAR_ENCODING_H__ +#define __XML_CHAR_ENCODING_H__ + +#include + +#ifdef LIBXML_ICONV_ENABLED +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + XML_ENC_ERR_SUCCESS = 0, + XML_ENC_ERR_SPACE = -1, + XML_ENC_ERR_INPUT = -2, + XML_ENC_ERR_PARTIAL = -3, + XML_ENC_ERR_INTERNAL = -4, + XML_ENC_ERR_MEMORY = -5 +} xmlCharEncError; + +/* + * xmlCharEncoding: + * + * Predefined values for some standard encodings. + * Libxml does not do beforehand translation on UTF8 and ISOLatinX. + * It also supports ASCII, ISO-8859-1, and UTF16 (LE and BE) by default. + * + * Anything else would have to be translated to UTF8 before being + * given to the parser itself. The BOM for UTF16 and the encoding + * declaration are looked at and a converter is looked for at that + * point. If not found the parser stops here as asked by the XML REC. A + * converter can be registered by the user using xmlRegisterCharEncodingHandler + * but the current form doesn't allow stateful transcoding (a serious + * problem agreed !). If iconv has been found it will be used + * automatically and allow stateful transcoding, the simplest is then + * to be sure to enable iconv and to provide iconv libs for the encoding + * support needed. + * + * Note that the generic "UTF-16" is not a predefined value. Instead, only + * the specific UTF-16LE and UTF-16BE are present. + */ +typedef enum { + XML_CHAR_ENCODING_ERROR= -1, /* No char encoding detected */ + XML_CHAR_ENCODING_NONE= 0, /* No char encoding detected */ + XML_CHAR_ENCODING_UTF8= 1, /* UTF-8 */ + XML_CHAR_ENCODING_UTF16LE= 2, /* UTF-16 little endian */ + XML_CHAR_ENCODING_UTF16BE= 3, /* UTF-16 big endian */ + XML_CHAR_ENCODING_UCS4LE= 4, /* UCS-4 little endian */ + XML_CHAR_ENCODING_UCS4BE= 5, /* UCS-4 big endian */ + XML_CHAR_ENCODING_EBCDIC= 6, /* EBCDIC uh! */ + XML_CHAR_ENCODING_UCS4_2143=7, /* UCS-4 unusual ordering */ + XML_CHAR_ENCODING_UCS4_3412=8, /* UCS-4 unusual ordering */ + XML_CHAR_ENCODING_UCS2= 9, /* UCS-2 */ + XML_CHAR_ENCODING_8859_1= 10,/* ISO-8859-1 ISO Latin 1 */ + XML_CHAR_ENCODING_8859_2= 11,/* ISO-8859-2 ISO Latin 2 */ + XML_CHAR_ENCODING_8859_3= 12,/* ISO-8859-3 */ + XML_CHAR_ENCODING_8859_4= 13,/* ISO-8859-4 */ + XML_CHAR_ENCODING_8859_5= 14,/* ISO-8859-5 */ + XML_CHAR_ENCODING_8859_6= 15,/* ISO-8859-6 */ + XML_CHAR_ENCODING_8859_7= 16,/* ISO-8859-7 */ + XML_CHAR_ENCODING_8859_8= 17,/* ISO-8859-8 */ + XML_CHAR_ENCODING_8859_9= 18,/* ISO-8859-9 */ + XML_CHAR_ENCODING_2022_JP= 19,/* ISO-2022-JP */ + XML_CHAR_ENCODING_SHIFT_JIS=20,/* Shift_JIS */ + XML_CHAR_ENCODING_EUC_JP= 21,/* EUC-JP */ + XML_CHAR_ENCODING_ASCII= 22 /* pure ASCII */ +} xmlCharEncoding; + +/** + * xmlCharEncodingInputFunc: + * @out: a pointer to an array of bytes to store the UTF-8 result + * @outlen: the length of @out + * @in: a pointer to an array of chars in the original encoding + * @inlen: the length of @in + * + * Take a block of chars in the original encoding and try to convert + * it to an UTF-8 block of chars out. + * + * Returns the number of bytes written, -1 if lack of space, or -2 + * if the transcoding failed. + * The value of @inlen after return is the number of octets consumed + * if the return value is positive, else unpredictiable. + * The value of @outlen after return is the number of octets consumed. + */ +typedef int (* xmlCharEncodingInputFunc)(unsigned char *out, int *outlen, + const unsigned char *in, int *inlen); + + +/** + * xmlCharEncodingOutputFunc: + * @out: a pointer to an array of bytes to store the result + * @outlen: the length of @out + * @in: a pointer to an array of UTF-8 chars + * @inlen: the length of @in + * + * Take a block of UTF-8 chars in and try to convert it to another + * encoding. + * Note: a first call designed to produce heading info is called with + * in = NULL. If stateful this should also initialize the encoder state. + * + * Returns the number of bytes written, -1 if lack of space, or -2 + * if the transcoding failed. + * The value of @inlen after return is the number of octets consumed + * if the return value is positive, else unpredictiable. + * The value of @outlen after return is the number of octets produced. + */ +typedef int (* xmlCharEncodingOutputFunc)(unsigned char *out, int *outlen, + const unsigned char *in, int *inlen); + + +/* + * Block defining the handlers for non UTF-8 encodings. + * If iconv is supported, there are two extra fields. + */ +typedef struct _xmlCharEncodingHandler xmlCharEncodingHandler; +typedef xmlCharEncodingHandler *xmlCharEncodingHandlerPtr; +struct _xmlCharEncodingHandler { + char *name; + xmlCharEncodingInputFunc input; + xmlCharEncodingOutputFunc output; +#ifdef LIBXML_ICONV_ENABLED + iconv_t iconv_in; + iconv_t iconv_out; +#endif /* LIBXML_ICONV_ENABLED */ +#ifdef LIBXML_ICU_ENABLED + struct _uconv_t *uconv_in; + struct _uconv_t *uconv_out; +#endif /* LIBXML_ICU_ENABLED */ +}; + +/* + * Interfaces for encoding handlers. + */ +XML_DEPRECATED +XMLPUBFUN void + xmlInitCharEncodingHandlers (void); +XML_DEPRECATED +XMLPUBFUN void + xmlCleanupCharEncodingHandlers (void); +XMLPUBFUN void + xmlRegisterCharEncodingHandler (xmlCharEncodingHandlerPtr handler); +XMLPUBFUN int + xmlLookupCharEncodingHandler (xmlCharEncoding enc, + xmlCharEncodingHandlerPtr *out); +XMLPUBFUN int + xmlOpenCharEncodingHandler (const char *name, + int output, + xmlCharEncodingHandlerPtr *out); +XMLPUBFUN xmlCharEncodingHandlerPtr + xmlGetCharEncodingHandler (xmlCharEncoding enc); +XMLPUBFUN xmlCharEncodingHandlerPtr + xmlFindCharEncodingHandler (const char *name); +XMLPUBFUN xmlCharEncodingHandlerPtr + xmlNewCharEncodingHandler (const char *name, + xmlCharEncodingInputFunc input, + xmlCharEncodingOutputFunc output); + +/* + * Interfaces for encoding names and aliases. + */ +XMLPUBFUN int + xmlAddEncodingAlias (const char *name, + const char *alias); +XMLPUBFUN int + xmlDelEncodingAlias (const char *alias); +XMLPUBFUN const char * + xmlGetEncodingAlias (const char *alias); +XMLPUBFUN void + xmlCleanupEncodingAliases (void); +XMLPUBFUN xmlCharEncoding + xmlParseCharEncoding (const char *name); +XMLPUBFUN const char * + xmlGetCharEncodingName (xmlCharEncoding enc); + +/* + * Interfaces directly used by the parsers. + */ +XMLPUBFUN xmlCharEncoding + xmlDetectCharEncoding (const unsigned char *in, + int len); + +/** DOC_DISABLE */ +struct _xmlBuffer; +/** DOC_ENABLE */ +XMLPUBFUN int + xmlCharEncOutFunc (xmlCharEncodingHandler *handler, + struct _xmlBuffer *out, + struct _xmlBuffer *in); + +XMLPUBFUN int + xmlCharEncInFunc (xmlCharEncodingHandler *handler, + struct _xmlBuffer *out, + struct _xmlBuffer *in); +XML_DEPRECATED +XMLPUBFUN int + xmlCharEncFirstLine (xmlCharEncodingHandler *handler, + struct _xmlBuffer *out, + struct _xmlBuffer *in); +XMLPUBFUN int + xmlCharEncCloseFunc (xmlCharEncodingHandler *handler); + +/* + * Export a few useful functions + */ +#ifdef LIBXML_OUTPUT_ENABLED +XMLPUBFUN int + UTF8Toisolat1 (unsigned char *out, + int *outlen, + const unsigned char *in, + int *inlen); +#endif /* LIBXML_OUTPUT_ENABLED */ +XMLPUBFUN int + isolat1ToUTF8 (unsigned char *out, + int *outlen, + const unsigned char *in, + int *inlen); +#ifdef __cplusplus +} +#endif + +#endif /* __XML_CHAR_ENCODING_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/entities.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/entities.h new file mode 100644 index 00000000..a0cfca81 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/entities.h @@ -0,0 +1,166 @@ +/* + * Summary: interface for the XML entities handling + * Description: this module provides some of the entity API needed + * for the parser and applications. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_ENTITIES_H__ +#define __XML_ENTITIES_H__ + +/** DOC_DISABLE */ +#include +#define XML_TREE_INTERNALS +#include +#undef XML_TREE_INTERNALS +/** DOC_ENABLE */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The different valid entity types. + */ +typedef enum { + XML_INTERNAL_GENERAL_ENTITY = 1, + XML_EXTERNAL_GENERAL_PARSED_ENTITY = 2, + XML_EXTERNAL_GENERAL_UNPARSED_ENTITY = 3, + XML_INTERNAL_PARAMETER_ENTITY = 4, + XML_EXTERNAL_PARAMETER_ENTITY = 5, + XML_INTERNAL_PREDEFINED_ENTITY = 6 +} xmlEntityType; + +/* + * An unit of storage for an entity, contains the string, the value + * and the linkind data needed for the linking in the hash table. + */ + +struct _xmlEntity { + void *_private; /* application data */ + xmlElementType type; /* XML_ENTITY_DECL, must be second ! */ + const xmlChar *name; /* Entity name */ + struct _xmlNode *children; /* First child link */ + struct _xmlNode *last; /* Last child link */ + struct _xmlDtd *parent; /* -> DTD */ + struct _xmlNode *next; /* next sibling link */ + struct _xmlNode *prev; /* previous sibling link */ + struct _xmlDoc *doc; /* the containing document */ + + xmlChar *orig; /* content without ref substitution */ + xmlChar *content; /* content or ndata if unparsed */ + int length; /* the content length */ + xmlEntityType etype; /* The entity type */ + const xmlChar *ExternalID; /* External identifier for PUBLIC */ + const xmlChar *SystemID; /* URI for a SYSTEM or PUBLIC Entity */ + + struct _xmlEntity *nexte; /* unused */ + const xmlChar *URI; /* the full URI as computed */ + int owner; /* unused */ + int flags; /* various flags */ + unsigned long expandedSize; /* expanded size */ +}; + +/* + * All entities are stored in an hash table. + * There is 2 separate hash tables for global and parameter entities. + */ + +typedef struct _xmlHashTable xmlEntitiesTable; +typedef xmlEntitiesTable *xmlEntitiesTablePtr; + +/* + * External functions: + */ + +#ifdef LIBXML_LEGACY_ENABLED +XML_DEPRECATED +XMLPUBFUN void + xmlInitializePredefinedEntities (void); +#endif /* LIBXML_LEGACY_ENABLED */ + +XMLPUBFUN xmlEntityPtr + xmlNewEntity (xmlDocPtr doc, + const xmlChar *name, + int type, + const xmlChar *ExternalID, + const xmlChar *SystemID, + const xmlChar *content); +XMLPUBFUN void + xmlFreeEntity (xmlEntityPtr entity); +XMLPUBFUN int + xmlAddEntity (xmlDocPtr doc, + int extSubset, + const xmlChar *name, + int type, + const xmlChar *ExternalID, + const xmlChar *SystemID, + const xmlChar *content, + xmlEntityPtr *out); +XMLPUBFUN xmlEntityPtr + xmlAddDocEntity (xmlDocPtr doc, + const xmlChar *name, + int type, + const xmlChar *ExternalID, + const xmlChar *SystemID, + const xmlChar *content); +XMLPUBFUN xmlEntityPtr + xmlAddDtdEntity (xmlDocPtr doc, + const xmlChar *name, + int type, + const xmlChar *ExternalID, + const xmlChar *SystemID, + const xmlChar *content); +XMLPUBFUN xmlEntityPtr + xmlGetPredefinedEntity (const xmlChar *name); +XMLPUBFUN xmlEntityPtr + xmlGetDocEntity (const xmlDoc *doc, + const xmlChar *name); +XMLPUBFUN xmlEntityPtr + xmlGetDtdEntity (xmlDocPtr doc, + const xmlChar *name); +XMLPUBFUN xmlEntityPtr + xmlGetParameterEntity (xmlDocPtr doc, + const xmlChar *name); +#ifdef LIBXML_LEGACY_ENABLED +XML_DEPRECATED +XMLPUBFUN const xmlChar * + xmlEncodeEntities (xmlDocPtr doc, + const xmlChar *input); +#endif /* LIBXML_LEGACY_ENABLED */ +XMLPUBFUN xmlChar * + xmlEncodeEntitiesReentrant(xmlDocPtr doc, + const xmlChar *input); +XMLPUBFUN xmlChar * + xmlEncodeSpecialChars (const xmlDoc *doc, + const xmlChar *input); +XMLPUBFUN xmlEntitiesTablePtr + xmlCreateEntitiesTable (void); +#ifdef LIBXML_TREE_ENABLED +XMLPUBFUN xmlEntitiesTablePtr + xmlCopyEntitiesTable (xmlEntitiesTablePtr table); +#endif /* LIBXML_TREE_ENABLED */ +XMLPUBFUN void + xmlFreeEntitiesTable (xmlEntitiesTablePtr table); +#ifdef LIBXML_OUTPUT_ENABLED +XMLPUBFUN void + xmlDumpEntitiesTable (xmlBufferPtr buf, + xmlEntitiesTablePtr table); +XMLPUBFUN void + xmlDumpEntityDecl (xmlBufferPtr buf, + xmlEntityPtr ent); +#endif /* LIBXML_OUTPUT_ENABLED */ +#ifdef LIBXML_LEGACY_ENABLED +XMLPUBFUN void + xmlCleanupPredefinedEntities(void); +#endif /* LIBXML_LEGACY_ENABLED */ + + +#ifdef __cplusplus +} +#endif + +# endif /* __XML_ENTITIES_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/globals.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/globals.h new file mode 100644 index 00000000..92f41312 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/globals.h @@ -0,0 +1,41 @@ +/* + * Summary: interface for all global variables of the library + * Description: Deprecated, don't use + * + * Copy: See Copyright for the status of this software. + */ + +#ifndef __XML_GLOBALS_H +#define __XML_GLOBALS_H + +#include + +/* + * This file was required to access global variables until version v2.12.0. + * + * These includes are for backward compatibility. + */ +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _xmlGlobalState xmlGlobalState; +typedef xmlGlobalState *xmlGlobalStatePtr; + +XML_DEPRECATED XMLPUBFUN void +xmlInitializeGlobalState(xmlGlobalStatePtr gs); +XML_DEPRECATED XMLPUBFUN +xmlGlobalStatePtr xmlGetGlobalState(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_GLOBALS_H */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/hash.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/hash.h new file mode 100644 index 00000000..135b6966 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/hash.h @@ -0,0 +1,251 @@ +/* + * Summary: Chained hash tables + * Description: This module implements the hash table support used in + * various places in the library. + * + * Copy: See Copyright for the status of this software. + * + * Author: Bjorn Reese + */ + +#ifndef __XML_HASH_H__ +#define __XML_HASH_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The hash table. + */ +typedef struct _xmlHashTable xmlHashTable; +typedef xmlHashTable *xmlHashTablePtr; + +/* + * Recent version of gcc produce a warning when a function pointer is assigned + * to an object pointer, or vice versa. The following macro is a dirty hack + * to allow suppression of the warning. If your architecture has function + * pointers which are a different size than a void pointer, there may be some + * serious trouble within the library. + */ +/** + * XML_CAST_FPTR: + * @fptr: pointer to a function + * + * Macro to do a casting from an object pointer to a + * function pointer without encountering a warning from + * gcc + * + * #define XML_CAST_FPTR(fptr) (*(void **)(&fptr)) + * This macro violated ISO C aliasing rules (gcc4 on s390 broke) + * so it is disabled now + */ + +#define XML_CAST_FPTR(fptr) fptr + +/* + * function types: + */ +/** + * xmlHashDeallocator: + * @payload: the data in the hash + * @name: the name associated + * + * Callback to free data from a hash. + */ +typedef void (*xmlHashDeallocator)(void *payload, const xmlChar *name); +/** + * xmlHashCopier: + * @payload: the data in the hash + * @name: the name associated + * + * Callback to copy data from a hash. + * + * Returns a copy of the data or NULL in case of error. + */ +typedef void *(*xmlHashCopier)(void *payload, const xmlChar *name); +/** + * xmlHashScanner: + * @payload: the data in the hash + * @data: extra scanner data + * @name: the name associated + * + * Callback when scanning data in a hash with the simple scanner. + */ +typedef void (*xmlHashScanner)(void *payload, void *data, const xmlChar *name); +/** + * xmlHashScannerFull: + * @payload: the data in the hash + * @data: extra scanner data + * @name: the name associated + * @name2: the second name associated + * @name3: the third name associated + * + * Callback when scanning data in a hash with the full scanner. + */ +typedef void (*xmlHashScannerFull)(void *payload, void *data, + const xmlChar *name, const xmlChar *name2, + const xmlChar *name3); + +/* + * Constructor and destructor. + */ +XMLPUBFUN xmlHashTablePtr + xmlHashCreate (int size); +XMLPUBFUN xmlHashTablePtr + xmlHashCreateDict (int size, + xmlDictPtr dict); +XMLPUBFUN void + xmlHashFree (xmlHashTablePtr hash, + xmlHashDeallocator dealloc); +XMLPUBFUN void + xmlHashDefaultDeallocator(void *entry, + const xmlChar *name); + +/* + * Add a new entry to the hash table. + */ +XMLPUBFUN int + xmlHashAdd (xmlHashTablePtr hash, + const xmlChar *name, + void *userdata); +XMLPUBFUN int + xmlHashAddEntry (xmlHashTablePtr hash, + const xmlChar *name, + void *userdata); +XMLPUBFUN int + xmlHashUpdateEntry (xmlHashTablePtr hash, + const xmlChar *name, + void *userdata, + xmlHashDeallocator dealloc); +XMLPUBFUN int + xmlHashAdd2 (xmlHashTablePtr hash, + const xmlChar *name, + const xmlChar *name2, + void *userdata); +XMLPUBFUN int + xmlHashAddEntry2 (xmlHashTablePtr hash, + const xmlChar *name, + const xmlChar *name2, + void *userdata); +XMLPUBFUN int + xmlHashUpdateEntry2 (xmlHashTablePtr hash, + const xmlChar *name, + const xmlChar *name2, + void *userdata, + xmlHashDeallocator dealloc); +XMLPUBFUN int + xmlHashAdd3 (xmlHashTablePtr hash, + const xmlChar *name, + const xmlChar *name2, + const xmlChar *name3, + void *userdata); +XMLPUBFUN int + xmlHashAddEntry3 (xmlHashTablePtr hash, + const xmlChar *name, + const xmlChar *name2, + const xmlChar *name3, + void *userdata); +XMLPUBFUN int + xmlHashUpdateEntry3 (xmlHashTablePtr hash, + const xmlChar *name, + const xmlChar *name2, + const xmlChar *name3, + void *userdata, + xmlHashDeallocator dealloc); + +/* + * Remove an entry from the hash table. + */ +XMLPUBFUN int + xmlHashRemoveEntry (xmlHashTablePtr hash, + const xmlChar *name, + xmlHashDeallocator dealloc); +XMLPUBFUN int + xmlHashRemoveEntry2 (xmlHashTablePtr hash, + const xmlChar *name, + const xmlChar *name2, + xmlHashDeallocator dealloc); +XMLPUBFUN int + xmlHashRemoveEntry3 (xmlHashTablePtr hash, + const xmlChar *name, + const xmlChar *name2, + const xmlChar *name3, + xmlHashDeallocator dealloc); + +/* + * Retrieve the payload. + */ +XMLPUBFUN void * + xmlHashLookup (xmlHashTablePtr hash, + const xmlChar *name); +XMLPUBFUN void * + xmlHashLookup2 (xmlHashTablePtr hash, + const xmlChar *name, + const xmlChar *name2); +XMLPUBFUN void * + xmlHashLookup3 (xmlHashTablePtr hash, + const xmlChar *name, + const xmlChar *name2, + const xmlChar *name3); +XMLPUBFUN void * + xmlHashQLookup (xmlHashTablePtr hash, + const xmlChar *prefix, + const xmlChar *name); +XMLPUBFUN void * + xmlHashQLookup2 (xmlHashTablePtr hash, + const xmlChar *prefix, + const xmlChar *name, + const xmlChar *prefix2, + const xmlChar *name2); +XMLPUBFUN void * + xmlHashQLookup3 (xmlHashTablePtr hash, + const xmlChar *prefix, + const xmlChar *name, + const xmlChar *prefix2, + const xmlChar *name2, + const xmlChar *prefix3, + const xmlChar *name3); + +/* + * Helpers. + */ +XMLPUBFUN xmlHashTablePtr + xmlHashCopySafe (xmlHashTablePtr hash, + xmlHashCopier copy, + xmlHashDeallocator dealloc); +XMLPUBFUN xmlHashTablePtr + xmlHashCopy (xmlHashTablePtr hash, + xmlHashCopier copy); +XMLPUBFUN int + xmlHashSize (xmlHashTablePtr hash); +XMLPUBFUN void + xmlHashScan (xmlHashTablePtr hash, + xmlHashScanner scan, + void *data); +XMLPUBFUN void + xmlHashScan3 (xmlHashTablePtr hash, + const xmlChar *name, + const xmlChar *name2, + const xmlChar *name3, + xmlHashScanner scan, + void *data); +XMLPUBFUN void + xmlHashScanFull (xmlHashTablePtr hash, + xmlHashScannerFull scan, + void *data); +XMLPUBFUN void + xmlHashScanFull3 (xmlHashTablePtr hash, + const xmlChar *name, + const xmlChar *name2, + const xmlChar *name3, + xmlHashScannerFull scan, + void *data); +#ifdef __cplusplus +} +#endif +#endif /* ! __XML_HASH_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/list.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/list.h new file mode 100644 index 00000000..1fa76aff --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/list.h @@ -0,0 +1,137 @@ +/* + * Summary: lists interfaces + * Description: this module implement the list support used in + * various place in the library. + * + * Copy: See Copyright for the status of this software. + * + * Author: Gary Pennington + */ + +#ifndef __XML_LINK_INCLUDE__ +#define __XML_LINK_INCLUDE__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _xmlLink xmlLink; +typedef xmlLink *xmlLinkPtr; + +typedef struct _xmlList xmlList; +typedef xmlList *xmlListPtr; + +/** + * xmlListDeallocator: + * @lk: the data to deallocate + * + * Callback function used to free data from a list. + */ +typedef void (*xmlListDeallocator) (xmlLinkPtr lk); +/** + * xmlListDataCompare: + * @data0: the first data + * @data1: the second data + * + * Callback function used to compare 2 data. + * + * Returns 0 is equality, -1 or 1 otherwise depending on the ordering. + */ +typedef int (*xmlListDataCompare) (const void *data0, const void *data1); +/** + * xmlListWalker: + * @data: the data found in the list + * @user: extra user provided data to the walker + * + * Callback function used when walking a list with xmlListWalk(). + * + * Returns 0 to stop walking the list, 1 otherwise. + */ +typedef int (*xmlListWalker) (const void *data, void *user); + +/* Creation/Deletion */ +XMLPUBFUN xmlListPtr + xmlListCreate (xmlListDeallocator deallocator, + xmlListDataCompare compare); +XMLPUBFUN void + xmlListDelete (xmlListPtr l); + +/* Basic Operators */ +XMLPUBFUN void * + xmlListSearch (xmlListPtr l, + void *data); +XMLPUBFUN void * + xmlListReverseSearch (xmlListPtr l, + void *data); +XMLPUBFUN int + xmlListInsert (xmlListPtr l, + void *data) ; +XMLPUBFUN int + xmlListAppend (xmlListPtr l, + void *data) ; +XMLPUBFUN int + xmlListRemoveFirst (xmlListPtr l, + void *data); +XMLPUBFUN int + xmlListRemoveLast (xmlListPtr l, + void *data); +XMLPUBFUN int + xmlListRemoveAll (xmlListPtr l, + void *data); +XMLPUBFUN void + xmlListClear (xmlListPtr l); +XMLPUBFUN int + xmlListEmpty (xmlListPtr l); +XMLPUBFUN xmlLinkPtr + xmlListFront (xmlListPtr l); +XMLPUBFUN xmlLinkPtr + xmlListEnd (xmlListPtr l); +XMLPUBFUN int + xmlListSize (xmlListPtr l); + +XMLPUBFUN void + xmlListPopFront (xmlListPtr l); +XMLPUBFUN void + xmlListPopBack (xmlListPtr l); +XMLPUBFUN int + xmlListPushFront (xmlListPtr l, + void *data); +XMLPUBFUN int + xmlListPushBack (xmlListPtr l, + void *data); + +/* Advanced Operators */ +XMLPUBFUN void + xmlListReverse (xmlListPtr l); +XMLPUBFUN void + xmlListSort (xmlListPtr l); +XMLPUBFUN void + xmlListWalk (xmlListPtr l, + xmlListWalker walker, + void *user); +XMLPUBFUN void + xmlListReverseWalk (xmlListPtr l, + xmlListWalker walker, + void *user); +XMLPUBFUN void + xmlListMerge (xmlListPtr l1, + xmlListPtr l2); +XMLPUBFUN xmlListPtr + xmlListDup (xmlListPtr old); +XMLPUBFUN int + xmlListCopy (xmlListPtr cur, + xmlListPtr old); +/* Link operators */ +XMLPUBFUN void * + xmlLinkGetData (xmlLinkPtr lk); + +/* xmlListUnique() */ +/* xmlListSwap */ + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_LINK_INCLUDE__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/nanoftp.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/nanoftp.h new file mode 100644 index 00000000..ed3ac4f1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/nanoftp.h @@ -0,0 +1,186 @@ +/* + * Summary: minimal FTP implementation + * Description: minimal FTP implementation allowing to fetch resources + * like external subset. This module is DEPRECATED, do not + * use any of its functions. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __NANO_FTP_H__ +#define __NANO_FTP_H__ + +#include + +#if defined(LIBXML_FTP_ENABLED) + +/* Needed for portability to Windows 64 bits */ +#if defined(_WIN32) +#include +#else +/** + * SOCKET: + * + * macro used to provide portability of code to windows sockets + */ +#define SOCKET int +/** + * INVALID_SOCKET: + * + * macro used to provide portability of code to windows sockets + * the value to be used when the socket is not valid + */ +#undef INVALID_SOCKET +#define INVALID_SOCKET (-1) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * ftpListCallback: + * @userData: user provided data for the callback + * @filename: the file name (including "->" when links are shown) + * @attrib: the attribute string + * @owner: the owner string + * @group: the group string + * @size: the file size + * @links: the link count + * @year: the year + * @month: the month + * @day: the day + * @hour: the hour + * @minute: the minute + * + * A callback for the xmlNanoFTPList command. + * Note that only one of year and day:minute are specified. + */ +typedef void (*ftpListCallback) (void *userData, + const char *filename, const char *attrib, + const char *owner, const char *group, + unsigned long size, int links, int year, + const char *month, int day, int hour, + int minute); +/** + * ftpDataCallback: + * @userData: the user provided context + * @data: the data received + * @len: its size in bytes + * + * A callback for the xmlNanoFTPGet command. + */ +typedef void (*ftpDataCallback) (void *userData, + const char *data, + int len); + +/* + * Init + */ +XML_DEPRECATED +XMLPUBFUN void + xmlNanoFTPInit (void); +XML_DEPRECATED +XMLPUBFUN void + xmlNanoFTPCleanup (void); + +/* + * Creating/freeing contexts. + */ +XML_DEPRECATED +XMLPUBFUN void * + xmlNanoFTPNewCtxt (const char *URL); +XML_DEPRECATED +XMLPUBFUN void + xmlNanoFTPFreeCtxt (void * ctx); +XML_DEPRECATED +XMLPUBFUN void * + xmlNanoFTPConnectTo (const char *server, + int port); +/* + * Opening/closing session connections. + */ +XML_DEPRECATED +XMLPUBFUN void * + xmlNanoFTPOpen (const char *URL); +XML_DEPRECATED +XMLPUBFUN int + xmlNanoFTPConnect (void *ctx); +XML_DEPRECATED +XMLPUBFUN int + xmlNanoFTPClose (void *ctx); +XML_DEPRECATED +XMLPUBFUN int + xmlNanoFTPQuit (void *ctx); +XML_DEPRECATED +XMLPUBFUN void + xmlNanoFTPScanProxy (const char *URL); +XML_DEPRECATED +XMLPUBFUN void + xmlNanoFTPProxy (const char *host, + int port, + const char *user, + const char *passwd, + int type); +XML_DEPRECATED +XMLPUBFUN int + xmlNanoFTPUpdateURL (void *ctx, + const char *URL); + +/* + * Rather internal commands. + */ +XML_DEPRECATED +XMLPUBFUN int + xmlNanoFTPGetResponse (void *ctx); +XML_DEPRECATED +XMLPUBFUN int + xmlNanoFTPCheckResponse (void *ctx); + +/* + * CD/DIR/GET handlers. + */ +XML_DEPRECATED +XMLPUBFUN int + xmlNanoFTPCwd (void *ctx, + const char *directory); +XML_DEPRECATED +XMLPUBFUN int + xmlNanoFTPDele (void *ctx, + const char *file); + +XML_DEPRECATED +XMLPUBFUN SOCKET + xmlNanoFTPGetConnection (void *ctx); +XML_DEPRECATED +XMLPUBFUN int + xmlNanoFTPCloseConnection(void *ctx); +XML_DEPRECATED +XMLPUBFUN int + xmlNanoFTPList (void *ctx, + ftpListCallback callback, + void *userData, + const char *filename); +XML_DEPRECATED +XMLPUBFUN SOCKET + xmlNanoFTPGetSocket (void *ctx, + const char *filename); +XML_DEPRECATED +XMLPUBFUN int + xmlNanoFTPGet (void *ctx, + ftpDataCallback callback, + void *userData, + const char *filename); +XML_DEPRECATED +XMLPUBFUN int + xmlNanoFTPRead (void *ctx, + void *dest, + int len); + +#ifdef __cplusplus +} +#endif +#endif /* defined(LIBXML_FTP_ENABLED) || defined(LIBXML_LEGACY_ENABLED) */ +#endif /* __NANO_FTP_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/nanohttp.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/nanohttp.h new file mode 100644 index 00000000..c70d1c26 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/nanohttp.h @@ -0,0 +1,98 @@ +/* + * Summary: minimal HTTP implementation + * Description: minimal HTTP implementation allowing to fetch resources + * like external subset. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __NANO_HTTP_H__ +#define __NANO_HTTP_H__ + +#include + +#ifdef LIBXML_HTTP_ENABLED + +#ifdef __cplusplus +extern "C" { +#endif +XML_DEPRECATED +XMLPUBFUN void + xmlNanoHTTPInit (void); +XML_DEPRECATED +XMLPUBFUN void + xmlNanoHTTPCleanup (void); +XML_DEPRECATED +XMLPUBFUN void + xmlNanoHTTPScanProxy (const char *URL); +XML_DEPRECATED +XMLPUBFUN int + xmlNanoHTTPFetch (const char *URL, + const char *filename, + char **contentType); +XML_DEPRECATED +XMLPUBFUN void * + xmlNanoHTTPMethod (const char *URL, + const char *method, + const char *input, + char **contentType, + const char *headers, + int ilen); +XML_DEPRECATED +XMLPUBFUN void * + xmlNanoHTTPMethodRedir (const char *URL, + const char *method, + const char *input, + char **contentType, + char **redir, + const char *headers, + int ilen); +XML_DEPRECATED +XMLPUBFUN void * + xmlNanoHTTPOpen (const char *URL, + char **contentType); +XML_DEPRECATED +XMLPUBFUN void * + xmlNanoHTTPOpenRedir (const char *URL, + char **contentType, + char **redir); +XML_DEPRECATED +XMLPUBFUN int + xmlNanoHTTPReturnCode (void *ctx); +XML_DEPRECATED +XMLPUBFUN const char * + xmlNanoHTTPAuthHeader (void *ctx); +XML_DEPRECATED +XMLPUBFUN const char * + xmlNanoHTTPRedir (void *ctx); +XML_DEPRECATED +XMLPUBFUN int + xmlNanoHTTPContentLength( void * ctx ); +XML_DEPRECATED +XMLPUBFUN const char * + xmlNanoHTTPEncoding (void *ctx); +XML_DEPRECATED +XMLPUBFUN const char * + xmlNanoHTTPMimeType (void *ctx); +XML_DEPRECATED +XMLPUBFUN int + xmlNanoHTTPRead (void *ctx, + void *dest, + int len); +#ifdef LIBXML_OUTPUT_ENABLED +XML_DEPRECATED +XMLPUBFUN int + xmlNanoHTTPSave (void *ctxt, + const char *filename); +#endif /* LIBXML_OUTPUT_ENABLED */ +XML_DEPRECATED +XMLPUBFUN void + xmlNanoHTTPClose (void *ctx); +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_HTTP_ENABLED */ +#endif /* __NANO_HTTP_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/parser.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/parser.h new file mode 100644 index 00000000..78d29cad --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/parser.h @@ -0,0 +1,1390 @@ +/* + * Summary: the core parser module + * Description: Interfaces, constants and types related to the XML parser + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_PARSER_H__ +#define __XML_PARSER_H__ + +/** DOC_DISABLE */ +#include +#define XML_TREE_INTERNALS +#include +#undef XML_TREE_INTERNALS +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* for compatibility */ +#include +#include +/** DOC_ENABLE */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * XML_DEFAULT_VERSION: + * + * The default version of XML used: 1.0 + */ +#define XML_DEFAULT_VERSION "1.0" + +/** + * xmlParserInput: + * + * An xmlParserInput is an input flow for the XML processor. + * Each entity parsed is associated an xmlParserInput (except the + * few predefined ones). This is the case both for internal entities + * - in which case the flow is already completely in memory - or + * external entities - in which case we use the buf structure for + * progressive reading and I18N conversions to the internal UTF-8 format. + */ + +/** + * xmlParserInputDeallocate: + * @str: the string to deallocate + * + * Callback for freeing some parser input allocations. + */ +typedef void (* xmlParserInputDeallocate)(xmlChar *str); + +struct _xmlParserInput { + /* Input buffer */ + xmlParserInputBufferPtr buf; /* UTF-8 encoded buffer */ + + const char *filename; /* The file analyzed, if any */ + const char *directory; /* unused */ + const xmlChar *base; /* Base of the array to parse */ + const xmlChar *cur; /* Current char being parsed */ + const xmlChar *end; /* end of the array to parse */ + int length; /* unused */ + int line; /* Current line */ + int col; /* Current column */ + unsigned long consumed; /* How many xmlChars already consumed */ + xmlParserInputDeallocate free; /* function to deallocate the base */ + const xmlChar *encoding; /* unused */ + const xmlChar *version; /* the version string for entity */ + int flags; /* Flags */ + int id; /* an unique identifier for the entity */ + unsigned long parentConsumed; /* unused */ + xmlEntityPtr entity; /* entity, if any */ +}; + +/** + * xmlParserNodeInfo: + * + * The parser can be asked to collect Node information, i.e. at what + * place in the file they were detected. + * NOTE: This is off by default and not very well tested. + */ +typedef struct _xmlParserNodeInfo xmlParserNodeInfo; +typedef xmlParserNodeInfo *xmlParserNodeInfoPtr; + +struct _xmlParserNodeInfo { + const struct _xmlNode* node; + /* Position & line # that text that created the node begins & ends on */ + unsigned long begin_pos; + unsigned long begin_line; + unsigned long end_pos; + unsigned long end_line; +}; + +typedef struct _xmlParserNodeInfoSeq xmlParserNodeInfoSeq; +typedef xmlParserNodeInfoSeq *xmlParserNodeInfoSeqPtr; +struct _xmlParserNodeInfoSeq { + unsigned long maximum; + unsigned long length; + xmlParserNodeInfo* buffer; +}; + +/** + * xmlParserInputState: + * + * The parser is now working also as a state based parser. + * The recursive one use the state info for entities processing. + */ +typedef enum { + XML_PARSER_EOF = -1, /* nothing is to be parsed */ + XML_PARSER_START = 0, /* nothing has been parsed */ + XML_PARSER_MISC, /* Misc* before int subset */ + XML_PARSER_PI, /* Within a processing instruction */ + XML_PARSER_DTD, /* within some DTD content */ + XML_PARSER_PROLOG, /* Misc* after internal subset */ + XML_PARSER_COMMENT, /* within a comment */ + XML_PARSER_START_TAG, /* within a start tag */ + XML_PARSER_CONTENT, /* within the content */ + XML_PARSER_CDATA_SECTION, /* within a CDATA section */ + XML_PARSER_END_TAG, /* within a closing tag */ + XML_PARSER_ENTITY_DECL, /* within an entity declaration */ + XML_PARSER_ENTITY_VALUE, /* within an entity value in a decl */ + XML_PARSER_ATTRIBUTE_VALUE, /* within an attribute value */ + XML_PARSER_SYSTEM_LITERAL, /* within a SYSTEM value */ + XML_PARSER_EPILOG, /* the Misc* after the last end tag */ + XML_PARSER_IGNORE, /* within an IGNORED section */ + XML_PARSER_PUBLIC_LITERAL, /* within a PUBLIC value */ + XML_PARSER_XML_DECL /* before XML decl (but after BOM) */ +} xmlParserInputState; + +/** DOC_DISABLE */ +/* + * Internal bits in the 'loadsubset' context member + */ +#define XML_DETECT_IDS 2 +#define XML_COMPLETE_ATTRS 4 +#define XML_SKIP_IDS 8 +/** DOC_ENABLE */ + +/** + * xmlParserMode: + * + * A parser can operate in various modes + */ +typedef enum { + XML_PARSE_UNKNOWN = 0, + XML_PARSE_DOM = 1, + XML_PARSE_SAX = 2, + XML_PARSE_PUSH_DOM = 3, + XML_PARSE_PUSH_SAX = 4, + XML_PARSE_READER = 5 +} xmlParserMode; + +typedef struct _xmlStartTag xmlStartTag; +typedef struct _xmlParserNsData xmlParserNsData; +typedef struct _xmlAttrHashBucket xmlAttrHashBucket; + +/** + * xmlParserCtxt: + * + * The parser context. + * NOTE This doesn't completely define the parser state, the (current ?) + * design of the parser uses recursive function calls since this allow + * and easy mapping from the production rules of the specification + * to the actual code. The drawback is that the actual function call + * also reflect the parser state. However most of the parsing routines + * takes as the only argument the parser context pointer, so migrating + * to a state based parser for progressive parsing shouldn't be too hard. + */ +struct _xmlParserCtxt { + struct _xmlSAXHandler *sax; /* The SAX handler */ + void *userData; /* For SAX interface only, used by DOM build */ + xmlDocPtr myDoc; /* the document being built */ + int wellFormed; /* is the document well formed */ + int replaceEntities; /* shall we replace entities ? */ + const xmlChar *version; /* the XML version string */ + const xmlChar *encoding; /* the declared encoding, if any */ + int standalone; /* standalone document */ + int html; /* an HTML(1) document + * 3 is HTML after + * 10 is HTML after + */ + + /* Input stream stack */ + xmlParserInputPtr input; /* Current input stream */ + int inputNr; /* Number of current input streams */ + int inputMax; /* Max number of input streams */ + xmlParserInputPtr *inputTab; /* stack of inputs */ + + /* Node analysis stack only used for DOM building */ + xmlNodePtr node; /* Current parsed Node */ + int nodeNr; /* Depth of the parsing stack */ + int nodeMax; /* Max depth of the parsing stack */ + xmlNodePtr *nodeTab; /* array of nodes */ + + int record_info; /* Whether node info should be kept */ + xmlParserNodeInfoSeq node_seq; /* info about each node parsed */ + + int errNo; /* error code */ + + int hasExternalSubset; /* reference and external subset */ + int hasPErefs; /* the internal subset has PE refs */ + int external; /* unused */ + + int valid; /* is the document valid */ + int validate; /* shall we try to validate ? */ + xmlValidCtxt vctxt; /* The validity context */ + + xmlParserInputState instate; /* push parser state */ + int token; /* unused */ + + char *directory; /* unused */ + + /* Node name stack */ + const xmlChar *name; /* Current parsed Node */ + int nameNr; /* Depth of the parsing stack */ + int nameMax; /* Max depth of the parsing stack */ + const xmlChar * *nameTab; /* array of nodes */ + + long nbChars; /* unused */ + long checkIndex; /* used by progressive parsing lookup */ + int keepBlanks; /* ugly but ... */ + int disableSAX; /* SAX callbacks are disabled */ + int inSubset; /* Parsing is in int 1/ext 2 subset */ + const xmlChar * intSubName; /* name of subset */ + xmlChar * extSubURI; /* URI of external subset */ + xmlChar * extSubSystem; /* SYSTEM ID of external subset */ + + /* xml:space values */ + int * space; /* Should the parser preserve spaces */ + int spaceNr; /* Depth of the parsing stack */ + int spaceMax; /* Max depth of the parsing stack */ + int * spaceTab; /* array of space infos */ + + int depth; /* to prevent entity substitution loops */ + xmlParserInputPtr entity; /* unused */ + int charset; /* unused */ + int nodelen; /* Those two fields are there to */ + int nodemem; /* Speed up large node parsing */ + int pedantic; /* signal pedantic warnings */ + void *_private; /* For user data, libxml won't touch it */ + + int loadsubset; /* should the external subset be loaded */ + int linenumbers; /* set line number in element content */ + void *catalogs; /* document's own catalog */ + int recovery; /* run in recovery mode */ + int progressive; /* unused */ + xmlDictPtr dict; /* dictionary for the parser */ + const xmlChar * *atts; /* array for the attributes callbacks */ + int maxatts; /* the size of the array */ + int docdict; /* unused */ + + /* + * pre-interned strings + */ + const xmlChar *str_xml; + const xmlChar *str_xmlns; + const xmlChar *str_xml_ns; + + /* + * Everything below is used only by the new SAX mode + */ + int sax2; /* operating in the new SAX mode */ + int nsNr; /* the number of inherited namespaces */ + int nsMax; /* the size of the arrays */ + const xmlChar * *nsTab; /* the array of prefix/namespace name */ + unsigned *attallocs; /* which attribute were allocated */ + xmlStartTag *pushTab; /* array of data for push */ + xmlHashTablePtr attsDefault; /* defaulted attributes if any */ + xmlHashTablePtr attsSpecial; /* non-CDATA attributes if any */ + int nsWellFormed; /* is the document XML Namespace okay */ + int options; /* Extra options */ + + /* + * Those fields are needed only for streaming parsing so far + */ + int dictNames; /* Use dictionary names for the tree */ + int freeElemsNr; /* number of freed element nodes */ + xmlNodePtr freeElems; /* List of freed element nodes */ + int freeAttrsNr; /* number of freed attributes nodes */ + xmlAttrPtr freeAttrs; /* List of freed attributes nodes */ + + /* + * the complete error information for the last error. + */ + xmlError lastError; + xmlParserMode parseMode; /* the parser mode */ + unsigned long nbentities; /* unused */ + unsigned long sizeentities; /* size of external entities */ + + /* for use by HTML non-recursive parser */ + xmlParserNodeInfo *nodeInfo; /* Current NodeInfo */ + int nodeInfoNr; /* Depth of the parsing stack */ + int nodeInfoMax; /* Max depth of the parsing stack */ + xmlParserNodeInfo *nodeInfoTab; /* array of nodeInfos */ + + int input_id; /* we need to label inputs */ + unsigned long sizeentcopy; /* volume of entity copy */ + + int endCheckState; /* quote state for push parser */ + unsigned short nbErrors; /* number of errors */ + unsigned short nbWarnings; /* number of warnings */ + unsigned maxAmpl; /* maximum amplification factor */ + + xmlParserNsData *nsdb; /* namespace database */ + unsigned attrHashMax; /* allocated size */ + xmlAttrHashBucket *attrHash; /* atttribute hash table */ + + xmlStructuredErrorFunc errorHandler; + void *errorCtxt; +}; + +/** + * xmlSAXLocator: + * + * A SAX Locator. + */ +struct _xmlSAXLocator { + const xmlChar *(*getPublicId)(void *ctx); + const xmlChar *(*getSystemId)(void *ctx); + int (*getLineNumber)(void *ctx); + int (*getColumnNumber)(void *ctx); +}; + +/** + * xmlSAXHandler: + * + * A SAX handler is bunch of callbacks called by the parser when processing + * of the input generate data or structure information. + */ + +/** + * resolveEntitySAXFunc: + * @ctx: the user data (XML parser context) + * @publicId: The public ID of the entity + * @systemId: The system ID of the entity + * + * Callback: + * The entity loader, to control the loading of external entities, + * the application can either: + * - override this resolveEntity() callback in the SAX block + * - or better use the xmlSetExternalEntityLoader() function to + * set up it's own entity resolution routine + * + * Returns the xmlParserInputPtr if inlined or NULL for DOM behaviour. + */ +typedef xmlParserInputPtr (*resolveEntitySAXFunc) (void *ctx, + const xmlChar *publicId, + const xmlChar *systemId); +/** + * internalSubsetSAXFunc: + * @ctx: the user data (XML parser context) + * @name: the root element name + * @ExternalID: the external ID + * @SystemID: the SYSTEM ID (e.g. filename or URL) + * + * Callback on internal subset declaration. + */ +typedef void (*internalSubsetSAXFunc) (void *ctx, + const xmlChar *name, + const xmlChar *ExternalID, + const xmlChar *SystemID); +/** + * externalSubsetSAXFunc: + * @ctx: the user data (XML parser context) + * @name: the root element name + * @ExternalID: the external ID + * @SystemID: the SYSTEM ID (e.g. filename or URL) + * + * Callback on external subset declaration. + */ +typedef void (*externalSubsetSAXFunc) (void *ctx, + const xmlChar *name, + const xmlChar *ExternalID, + const xmlChar *SystemID); +/** + * getEntitySAXFunc: + * @ctx: the user data (XML parser context) + * @name: The entity name + * + * Get an entity by name. + * + * Returns the xmlEntityPtr if found. + */ +typedef xmlEntityPtr (*getEntitySAXFunc) (void *ctx, + const xmlChar *name); +/** + * getParameterEntitySAXFunc: + * @ctx: the user data (XML parser context) + * @name: The entity name + * + * Get a parameter entity by name. + * + * Returns the xmlEntityPtr if found. + */ +typedef xmlEntityPtr (*getParameterEntitySAXFunc) (void *ctx, + const xmlChar *name); +/** + * entityDeclSAXFunc: + * @ctx: the user data (XML parser context) + * @name: the entity name + * @type: the entity type + * @publicId: The public ID of the entity + * @systemId: The system ID of the entity + * @content: the entity value (without processing). + * + * An entity definition has been parsed. + */ +typedef void (*entityDeclSAXFunc) (void *ctx, + const xmlChar *name, + int type, + const xmlChar *publicId, + const xmlChar *systemId, + xmlChar *content); +/** + * notationDeclSAXFunc: + * @ctx: the user data (XML parser context) + * @name: The name of the notation + * @publicId: The public ID of the entity + * @systemId: The system ID of the entity + * + * What to do when a notation declaration has been parsed. + */ +typedef void (*notationDeclSAXFunc)(void *ctx, + const xmlChar *name, + const xmlChar *publicId, + const xmlChar *systemId); +/** + * attributeDeclSAXFunc: + * @ctx: the user data (XML parser context) + * @elem: the name of the element + * @fullname: the attribute name + * @type: the attribute type + * @def: the type of default value + * @defaultValue: the attribute default value + * @tree: the tree of enumerated value set + * + * An attribute definition has been parsed. + */ +typedef void (*attributeDeclSAXFunc)(void *ctx, + const xmlChar *elem, + const xmlChar *fullname, + int type, + int def, + const xmlChar *defaultValue, + xmlEnumerationPtr tree); +/** + * elementDeclSAXFunc: + * @ctx: the user data (XML parser context) + * @name: the element name + * @type: the element type + * @content: the element value tree + * + * An element definition has been parsed. + */ +typedef void (*elementDeclSAXFunc)(void *ctx, + const xmlChar *name, + int type, + xmlElementContentPtr content); +/** + * unparsedEntityDeclSAXFunc: + * @ctx: the user data (XML parser context) + * @name: The name of the entity + * @publicId: The public ID of the entity + * @systemId: The system ID of the entity + * @notationName: the name of the notation + * + * What to do when an unparsed entity declaration is parsed. + */ +typedef void (*unparsedEntityDeclSAXFunc)(void *ctx, + const xmlChar *name, + const xmlChar *publicId, + const xmlChar *systemId, + const xmlChar *notationName); +/** + * setDocumentLocatorSAXFunc: + * @ctx: the user data (XML parser context) + * @loc: A SAX Locator + * + * Receive the document locator at startup, actually xmlDefaultSAXLocator. + * Everything is available on the context, so this is useless in our case. + */ +typedef void (*setDocumentLocatorSAXFunc) (void *ctx, + xmlSAXLocatorPtr loc); +/** + * startDocumentSAXFunc: + * @ctx: the user data (XML parser context) + * + * Called when the document start being processed. + */ +typedef void (*startDocumentSAXFunc) (void *ctx); +/** + * endDocumentSAXFunc: + * @ctx: the user data (XML parser context) + * + * Called when the document end has been detected. + */ +typedef void (*endDocumentSAXFunc) (void *ctx); +/** + * startElementSAXFunc: + * @ctx: the user data (XML parser context) + * @name: The element name, including namespace prefix + * @atts: An array of name/value attributes pairs, NULL terminated + * + * Called when an opening tag has been processed. + */ +typedef void (*startElementSAXFunc) (void *ctx, + const xmlChar *name, + const xmlChar **atts); +/** + * endElementSAXFunc: + * @ctx: the user data (XML parser context) + * @name: The element name + * + * Called when the end of an element has been detected. + */ +typedef void (*endElementSAXFunc) (void *ctx, + const xmlChar *name); +/** + * attributeSAXFunc: + * @ctx: the user data (XML parser context) + * @name: The attribute name, including namespace prefix + * @value: The attribute value + * + * Handle an attribute that has been read by the parser. + * The default handling is to convert the attribute into an + * DOM subtree and past it in a new xmlAttr element added to + * the element. + */ +typedef void (*attributeSAXFunc) (void *ctx, + const xmlChar *name, + const xmlChar *value); +/** + * referenceSAXFunc: + * @ctx: the user data (XML parser context) + * @name: The entity name + * + * Called when an entity reference is detected. + */ +typedef void (*referenceSAXFunc) (void *ctx, + const xmlChar *name); +/** + * charactersSAXFunc: + * @ctx: the user data (XML parser context) + * @ch: a xmlChar string + * @len: the number of xmlChar + * + * Receiving some chars from the parser. + */ +typedef void (*charactersSAXFunc) (void *ctx, + const xmlChar *ch, + int len); +/** + * ignorableWhitespaceSAXFunc: + * @ctx: the user data (XML parser context) + * @ch: a xmlChar string + * @len: the number of xmlChar + * + * Receiving some ignorable whitespaces from the parser. + * UNUSED: by default the DOM building will use characters. + */ +typedef void (*ignorableWhitespaceSAXFunc) (void *ctx, + const xmlChar *ch, + int len); +/** + * processingInstructionSAXFunc: + * @ctx: the user data (XML parser context) + * @target: the target name + * @data: the PI data's + * + * A processing instruction has been parsed. + */ +typedef void (*processingInstructionSAXFunc) (void *ctx, + const xmlChar *target, + const xmlChar *data); +/** + * commentSAXFunc: + * @ctx: the user data (XML parser context) + * @value: the comment content + * + * A comment has been parsed. + */ +typedef void (*commentSAXFunc) (void *ctx, + const xmlChar *value); +/** + * cdataBlockSAXFunc: + * @ctx: the user data (XML parser context) + * @value: The pcdata content + * @len: the block length + * + * Called when a pcdata block has been parsed. + */ +typedef void (*cdataBlockSAXFunc) ( + void *ctx, + const xmlChar *value, + int len); +/** + * warningSAXFunc: + * @ctx: an XML parser context + * @msg: the message to display/transmit + * @...: extra parameters for the message display + * + * Display and format a warning messages, callback. + */ +typedef void (*warningSAXFunc) (void *ctx, + const char *msg, ...) LIBXML_ATTR_FORMAT(2,3); +/** + * errorSAXFunc: + * @ctx: an XML parser context + * @msg: the message to display/transmit + * @...: extra parameters for the message display + * + * Display and format an error messages, callback. + */ +typedef void (*errorSAXFunc) (void *ctx, + const char *msg, ...) LIBXML_ATTR_FORMAT(2,3); +/** + * fatalErrorSAXFunc: + * @ctx: an XML parser context + * @msg: the message to display/transmit + * @...: extra parameters for the message display + * + * Display and format fatal error messages, callback. + * Note: so far fatalError() SAX callbacks are not used, error() + * get all the callbacks for errors. + */ +typedef void (*fatalErrorSAXFunc) (void *ctx, + const char *msg, ...) LIBXML_ATTR_FORMAT(2,3); +/** + * isStandaloneSAXFunc: + * @ctx: the user data (XML parser context) + * + * Is this document tagged standalone? + * + * Returns 1 if true + */ +typedef int (*isStandaloneSAXFunc) (void *ctx); +/** + * hasInternalSubsetSAXFunc: + * @ctx: the user data (XML parser context) + * + * Does this document has an internal subset. + * + * Returns 1 if true + */ +typedef int (*hasInternalSubsetSAXFunc) (void *ctx); + +/** + * hasExternalSubsetSAXFunc: + * @ctx: the user data (XML parser context) + * + * Does this document has an external subset? + * + * Returns 1 if true + */ +typedef int (*hasExternalSubsetSAXFunc) (void *ctx); + +/************************************************************************ + * * + * The SAX version 2 API extensions * + * * + ************************************************************************/ +/** + * XML_SAX2_MAGIC: + * + * Special constant found in SAX2 blocks initialized fields + */ +#define XML_SAX2_MAGIC 0xDEEDBEAF + +/** + * startElementNsSAX2Func: + * @ctx: the user data (XML parser context) + * @localname: the local name of the element + * @prefix: the element namespace prefix if available + * @URI: the element namespace name if available + * @nb_namespaces: number of namespace definitions on that node + * @namespaces: pointer to the array of prefix/URI pairs namespace definitions + * @nb_attributes: the number of attributes on that node + * @nb_defaulted: the number of defaulted attributes. The defaulted + * ones are at the end of the array + * @attributes: pointer to the array of (localname/prefix/URI/value/end) + * attribute values. + * + * SAX2 callback when an element start has been detected by the parser. + * It provides the namespace information for the element, as well as + * the new namespace declarations on the element. + */ + +typedef void (*startElementNsSAX2Func) (void *ctx, + const xmlChar *localname, + const xmlChar *prefix, + const xmlChar *URI, + int nb_namespaces, + const xmlChar **namespaces, + int nb_attributes, + int nb_defaulted, + const xmlChar **attributes); + +/** + * endElementNsSAX2Func: + * @ctx: the user data (XML parser context) + * @localname: the local name of the element + * @prefix: the element namespace prefix if available + * @URI: the element namespace name if available + * + * SAX2 callback when an element end has been detected by the parser. + * It provides the namespace information for the element. + */ + +typedef void (*endElementNsSAX2Func) (void *ctx, + const xmlChar *localname, + const xmlChar *prefix, + const xmlChar *URI); + + +struct _xmlSAXHandler { + internalSubsetSAXFunc internalSubset; + isStandaloneSAXFunc isStandalone; + hasInternalSubsetSAXFunc hasInternalSubset; + hasExternalSubsetSAXFunc hasExternalSubset; + resolveEntitySAXFunc resolveEntity; + getEntitySAXFunc getEntity; + entityDeclSAXFunc entityDecl; + notationDeclSAXFunc notationDecl; + attributeDeclSAXFunc attributeDecl; + elementDeclSAXFunc elementDecl; + unparsedEntityDeclSAXFunc unparsedEntityDecl; + setDocumentLocatorSAXFunc setDocumentLocator; + startDocumentSAXFunc startDocument; + endDocumentSAXFunc endDocument; + /* + * `startElement` and `endElement` are only used by the legacy SAX1 + * interface and should not be used in new software. If you really + * have to enable SAX1, the preferred way is set the `initialized` + * member to 1 instead of XML_SAX2_MAGIC. + * + * For backward compatibility, it's also possible to set the + * `startElementNs` and `endElementNs` handlers to NULL. + * + * You can also set the XML_PARSE_SAX1 parser option, but versions + * older than 2.12.0 will probably crash if this option is provided + * together with custom SAX callbacks. + */ + startElementSAXFunc startElement; + endElementSAXFunc endElement; + referenceSAXFunc reference; + charactersSAXFunc characters; + ignorableWhitespaceSAXFunc ignorableWhitespace; + processingInstructionSAXFunc processingInstruction; + commentSAXFunc comment; + warningSAXFunc warning; + errorSAXFunc error; + fatalErrorSAXFunc fatalError; /* unused error() get all the errors */ + getParameterEntitySAXFunc getParameterEntity; + cdataBlockSAXFunc cdataBlock; + externalSubsetSAXFunc externalSubset; + /* + * `initialized` should always be set to XML_SAX2_MAGIC to enable the + * modern SAX2 interface. + */ + unsigned int initialized; + /* + * The following members are only used by the SAX2 interface. + */ + void *_private; + startElementNsSAX2Func startElementNs; + endElementNsSAX2Func endElementNs; + xmlStructuredErrorFunc serror; +}; + +/* + * SAX Version 1 + */ +typedef struct _xmlSAXHandlerV1 xmlSAXHandlerV1; +typedef xmlSAXHandlerV1 *xmlSAXHandlerV1Ptr; +struct _xmlSAXHandlerV1 { + internalSubsetSAXFunc internalSubset; + isStandaloneSAXFunc isStandalone; + hasInternalSubsetSAXFunc hasInternalSubset; + hasExternalSubsetSAXFunc hasExternalSubset; + resolveEntitySAXFunc resolveEntity; + getEntitySAXFunc getEntity; + entityDeclSAXFunc entityDecl; + notationDeclSAXFunc notationDecl; + attributeDeclSAXFunc attributeDecl; + elementDeclSAXFunc elementDecl; + unparsedEntityDeclSAXFunc unparsedEntityDecl; + setDocumentLocatorSAXFunc setDocumentLocator; + startDocumentSAXFunc startDocument; + endDocumentSAXFunc endDocument; + startElementSAXFunc startElement; + endElementSAXFunc endElement; + referenceSAXFunc reference; + charactersSAXFunc characters; + ignorableWhitespaceSAXFunc ignorableWhitespace; + processingInstructionSAXFunc processingInstruction; + commentSAXFunc comment; + warningSAXFunc warning; + errorSAXFunc error; + fatalErrorSAXFunc fatalError; /* unused error() get all the errors */ + getParameterEntitySAXFunc getParameterEntity; + cdataBlockSAXFunc cdataBlock; + externalSubsetSAXFunc externalSubset; + unsigned int initialized; +}; + + +/** + * xmlExternalEntityLoader: + * @URL: The System ID of the resource requested + * @ID: The Public ID of the resource requested + * @context: the XML parser context + * + * External entity loaders types. + * + * Returns the entity input parser. + */ +typedef xmlParserInputPtr (*xmlExternalEntityLoader) (const char *URL, + const char *ID, + xmlParserCtxtPtr context); + +/* + * Variables + */ + +XMLPUBVAR const char *const xmlParserVersion; +XML_DEPRECATED +XMLPUBVAR const int oldXMLWDcompatibility; +XML_DEPRECATED +XMLPUBVAR const int xmlParserDebugEntities; +XML_DEPRECATED +XMLPUBVAR const xmlSAXLocator xmlDefaultSAXLocator; +#ifdef LIBXML_SAX1_ENABLED +XML_DEPRECATED +XMLPUBVAR const xmlSAXHandlerV1 xmlDefaultSAXHandler; +#endif + +#ifdef LIBXML_THREAD_ENABLED +/* backward compatibility */ +XMLPUBFUN const char *const *__xmlParserVersion(void); +XML_DEPRECATED +XMLPUBFUN const int *__oldXMLWDcompatibility(void); +XML_DEPRECATED +XMLPUBFUN const int *__xmlParserDebugEntities(void); +XML_DEPRECATED +XMLPUBFUN const xmlSAXLocator *__xmlDefaultSAXLocator(void); +#ifdef LIBXML_SAX1_ENABLED +XML_DEPRECATED +XMLPUBFUN const xmlSAXHandlerV1 *__xmlDefaultSAXHandler(void); +#endif +#endif + +/** DOC_DISABLE */ +#define XML_GLOBALS_PARSER_CORE \ + XML_OP(xmlDoValidityCheckingDefaultValue, int, XML_DEPRECATED) \ + XML_OP(xmlGetWarningsDefaultValue, int, XML_DEPRECATED) \ + XML_OP(xmlKeepBlanksDefaultValue, int, XML_DEPRECATED) \ + XML_OP(xmlLineNumbersDefaultValue, int, XML_DEPRECATED) \ + XML_OP(xmlLoadExtDtdDefaultValue, int, XML_DEPRECATED) \ + XML_OP(xmlPedanticParserDefaultValue, int, XML_DEPRECATED) \ + XML_OP(xmlSubstituteEntitiesDefaultValue, int, XML_DEPRECATED) + +#ifdef LIBXML_OUTPUT_ENABLED + #define XML_GLOBALS_PARSER_OUTPUT \ + XML_OP(xmlIndentTreeOutput, int, XML_NO_ATTR) \ + XML_OP(xmlTreeIndentString, const char *, XML_NO_ATTR) \ + XML_OP(xmlSaveNoEmptyTags, int, XML_NO_ATTR) +#else + #define XML_GLOBALS_PARSER_OUTPUT +#endif + +#define XML_GLOBALS_PARSER \ + XML_GLOBALS_PARSER_CORE \ + XML_GLOBALS_PARSER_OUTPUT + +#define XML_OP XML_DECLARE_GLOBAL +XML_GLOBALS_PARSER +#undef XML_OP + +#if defined(LIBXML_THREAD_ENABLED) && !defined(XML_GLOBALS_NO_REDEFINITION) + #define xmlDoValidityCheckingDefaultValue \ + XML_GLOBAL_MACRO(xmlDoValidityCheckingDefaultValue) + #define xmlGetWarningsDefaultValue \ + XML_GLOBAL_MACRO(xmlGetWarningsDefaultValue) + #define xmlKeepBlanksDefaultValue XML_GLOBAL_MACRO(xmlKeepBlanksDefaultValue) + #define xmlLineNumbersDefaultValue \ + XML_GLOBAL_MACRO(xmlLineNumbersDefaultValue) + #define xmlLoadExtDtdDefaultValue XML_GLOBAL_MACRO(xmlLoadExtDtdDefaultValue) + #define xmlPedanticParserDefaultValue \ + XML_GLOBAL_MACRO(xmlPedanticParserDefaultValue) + #define xmlSubstituteEntitiesDefaultValue \ + XML_GLOBAL_MACRO(xmlSubstituteEntitiesDefaultValue) + #ifdef LIBXML_OUTPUT_ENABLED + #define xmlIndentTreeOutput XML_GLOBAL_MACRO(xmlIndentTreeOutput) + #define xmlTreeIndentString XML_GLOBAL_MACRO(xmlTreeIndentString) + #define xmlSaveNoEmptyTags XML_GLOBAL_MACRO(xmlSaveNoEmptyTags) + #endif +#endif +/** DOC_ENABLE */ + +/* + * Init/Cleanup + */ +XMLPUBFUN void + xmlInitParser (void); +XMLPUBFUN void + xmlCleanupParser (void); +XML_DEPRECATED +XMLPUBFUN void + xmlInitGlobals (void); +XML_DEPRECATED +XMLPUBFUN void + xmlCleanupGlobals (void); + +/* + * Input functions + */ +XML_DEPRECATED +XMLPUBFUN int + xmlParserInputRead (xmlParserInputPtr in, + int len); +XML_DEPRECATED +XMLPUBFUN int + xmlParserInputGrow (xmlParserInputPtr in, + int len); + +/* + * Basic parsing Interfaces + */ +#ifdef LIBXML_SAX1_ENABLED +XMLPUBFUN xmlDocPtr + xmlParseDoc (const xmlChar *cur); +XMLPUBFUN xmlDocPtr + xmlParseFile (const char *filename); +XMLPUBFUN xmlDocPtr + xmlParseMemory (const char *buffer, + int size); +#endif /* LIBXML_SAX1_ENABLED */ +XML_DEPRECATED XMLPUBFUN int + xmlSubstituteEntitiesDefault(int val); +XML_DEPRECATED XMLPUBFUN int + xmlThrDefSubstituteEntitiesDefaultValue(int v); +XMLPUBFUN int + xmlKeepBlanksDefault (int val); +XML_DEPRECATED XMLPUBFUN int + xmlThrDefKeepBlanksDefaultValue(int v); +XMLPUBFUN void + xmlStopParser (xmlParserCtxtPtr ctxt); +XML_DEPRECATED XMLPUBFUN int + xmlPedanticParserDefault(int val); +XML_DEPRECATED XMLPUBFUN int + xmlThrDefPedanticParserDefaultValue(int v); +XML_DEPRECATED XMLPUBFUN int + xmlLineNumbersDefault (int val); +XML_DEPRECATED XMLPUBFUN int + xmlThrDefLineNumbersDefaultValue(int v); +XML_DEPRECATED XMLPUBFUN int + xmlThrDefDoValidityCheckingDefaultValue(int v); +XML_DEPRECATED XMLPUBFUN int + xmlThrDefGetWarningsDefaultValue(int v); +XML_DEPRECATED XMLPUBFUN int + xmlThrDefLoadExtDtdDefaultValue(int v); +XML_DEPRECATED XMLPUBFUN int + xmlThrDefParserDebugEntities(int v); + +#ifdef LIBXML_SAX1_ENABLED +/* + * Recovery mode + */ +XML_DEPRECATED +XMLPUBFUN xmlDocPtr + xmlRecoverDoc (const xmlChar *cur); +XML_DEPRECATED +XMLPUBFUN xmlDocPtr + xmlRecoverMemory (const char *buffer, + int size); +XML_DEPRECATED +XMLPUBFUN xmlDocPtr + xmlRecoverFile (const char *filename); +#endif /* LIBXML_SAX1_ENABLED */ + +/* + * Less common routines and SAX interfaces + */ +XMLPUBFUN int + xmlParseDocument (xmlParserCtxtPtr ctxt); +XMLPUBFUN int + xmlParseExtParsedEnt (xmlParserCtxtPtr ctxt); +#ifdef LIBXML_SAX1_ENABLED +XML_DEPRECATED +XMLPUBFUN int + xmlSAXUserParseFile (xmlSAXHandlerPtr sax, + void *user_data, + const char *filename); +XML_DEPRECATED +XMLPUBFUN int + xmlSAXUserParseMemory (xmlSAXHandlerPtr sax, + void *user_data, + const char *buffer, + int size); +XML_DEPRECATED +XMLPUBFUN xmlDocPtr + xmlSAXParseDoc (xmlSAXHandlerPtr sax, + const xmlChar *cur, + int recovery); +XML_DEPRECATED +XMLPUBFUN xmlDocPtr + xmlSAXParseMemory (xmlSAXHandlerPtr sax, + const char *buffer, + int size, + int recovery); +XML_DEPRECATED +XMLPUBFUN xmlDocPtr + xmlSAXParseMemoryWithData (xmlSAXHandlerPtr sax, + const char *buffer, + int size, + int recovery, + void *data); +XML_DEPRECATED +XMLPUBFUN xmlDocPtr + xmlSAXParseFile (xmlSAXHandlerPtr sax, + const char *filename, + int recovery); +XML_DEPRECATED +XMLPUBFUN xmlDocPtr + xmlSAXParseFileWithData (xmlSAXHandlerPtr sax, + const char *filename, + int recovery, + void *data); +XML_DEPRECATED +XMLPUBFUN xmlDocPtr + xmlSAXParseEntity (xmlSAXHandlerPtr sax, + const char *filename); +XML_DEPRECATED +XMLPUBFUN xmlDocPtr + xmlParseEntity (const char *filename); +#endif /* LIBXML_SAX1_ENABLED */ + +#ifdef LIBXML_VALID_ENABLED +XML_DEPRECATED +XMLPUBFUN xmlDtdPtr + xmlSAXParseDTD (xmlSAXHandlerPtr sax, + const xmlChar *ExternalID, + const xmlChar *SystemID); +XMLPUBFUN xmlDtdPtr + xmlParseDTD (const xmlChar *ExternalID, + const xmlChar *SystemID); +XMLPUBFUN xmlDtdPtr + xmlIOParseDTD (xmlSAXHandlerPtr sax, + xmlParserInputBufferPtr input, + xmlCharEncoding enc); +#endif /* LIBXML_VALID_ENABLE */ +#ifdef LIBXML_SAX1_ENABLED +XMLPUBFUN int + xmlParseBalancedChunkMemory(xmlDocPtr doc, + xmlSAXHandlerPtr sax, + void *user_data, + int depth, + const xmlChar *string, + xmlNodePtr *lst); +#endif /* LIBXML_SAX1_ENABLED */ +XMLPUBFUN xmlParserErrors + xmlParseInNodeContext (xmlNodePtr node, + const char *data, + int datalen, + int options, + xmlNodePtr *lst); +#ifdef LIBXML_SAX1_ENABLED +XMLPUBFUN int + xmlParseBalancedChunkMemoryRecover(xmlDocPtr doc, + xmlSAXHandlerPtr sax, + void *user_data, + int depth, + const xmlChar *string, + xmlNodePtr *lst, + int recover); +XML_DEPRECATED +XMLPUBFUN int + xmlParseExternalEntity (xmlDocPtr doc, + xmlSAXHandlerPtr sax, + void *user_data, + int depth, + const xmlChar *URL, + const xmlChar *ID, + xmlNodePtr *lst); +#endif /* LIBXML_SAX1_ENABLED */ +XMLPUBFUN int + xmlParseCtxtExternalEntity(xmlParserCtxtPtr ctx, + const xmlChar *URL, + const xmlChar *ID, + xmlNodePtr *lst); + +/* + * Parser contexts handling. + */ +XMLPUBFUN xmlParserCtxtPtr + xmlNewParserCtxt (void); +XMLPUBFUN xmlParserCtxtPtr + xmlNewSAXParserCtxt (const xmlSAXHandler *sax, + void *userData); +XMLPUBFUN int + xmlInitParserCtxt (xmlParserCtxtPtr ctxt); +XMLPUBFUN void + xmlClearParserCtxt (xmlParserCtxtPtr ctxt); +XMLPUBFUN void + xmlFreeParserCtxt (xmlParserCtxtPtr ctxt); +#ifdef LIBXML_SAX1_ENABLED +XML_DEPRECATED +XMLPUBFUN void + xmlSetupParserForBuffer (xmlParserCtxtPtr ctxt, + const xmlChar* buffer, + const char *filename); +#endif /* LIBXML_SAX1_ENABLED */ +XMLPUBFUN xmlParserCtxtPtr + xmlCreateDocParserCtxt (const xmlChar *cur); + +#ifdef LIBXML_LEGACY_ENABLED +/* + * Reading/setting optional parsing features. + */ +XML_DEPRECATED +XMLPUBFUN int + xmlGetFeaturesList (int *len, + const char **result); +XML_DEPRECATED +XMLPUBFUN int + xmlGetFeature (xmlParserCtxtPtr ctxt, + const char *name, + void *result); +XML_DEPRECATED +XMLPUBFUN int + xmlSetFeature (xmlParserCtxtPtr ctxt, + const char *name, + void *value); +#endif /* LIBXML_LEGACY_ENABLED */ + +#ifdef LIBXML_PUSH_ENABLED +/* + * Interfaces for the Push mode. + */ +XMLPUBFUN xmlParserCtxtPtr + xmlCreatePushParserCtxt(xmlSAXHandlerPtr sax, + void *user_data, + const char *chunk, + int size, + const char *filename); +XMLPUBFUN int + xmlParseChunk (xmlParserCtxtPtr ctxt, + const char *chunk, + int size, + int terminate); +#endif /* LIBXML_PUSH_ENABLED */ + +/* + * Special I/O mode. + */ + +XMLPUBFUN xmlParserCtxtPtr + xmlCreateIOParserCtxt (xmlSAXHandlerPtr sax, + void *user_data, + xmlInputReadCallback ioread, + xmlInputCloseCallback ioclose, + void *ioctx, + xmlCharEncoding enc); + +XMLPUBFUN xmlParserInputPtr + xmlNewIOInputStream (xmlParserCtxtPtr ctxt, + xmlParserInputBufferPtr input, + xmlCharEncoding enc); + +/* + * Node infos. + */ +XMLPUBFUN const xmlParserNodeInfo* + xmlParserFindNodeInfo (xmlParserCtxtPtr ctxt, + xmlNodePtr node); +XMLPUBFUN void + xmlInitNodeInfoSeq (xmlParserNodeInfoSeqPtr seq); +XMLPUBFUN void + xmlClearNodeInfoSeq (xmlParserNodeInfoSeqPtr seq); +XMLPUBFUN unsigned long + xmlParserFindNodeInfoIndex(xmlParserNodeInfoSeqPtr seq, + xmlNodePtr node); +XMLPUBFUN void + xmlParserAddNodeInfo (xmlParserCtxtPtr ctxt, + xmlParserNodeInfoPtr info); + +/* + * External entities handling actually implemented in xmlIO. + */ + +XMLPUBFUN void + xmlSetExternalEntityLoader(xmlExternalEntityLoader f); +XMLPUBFUN xmlExternalEntityLoader + xmlGetExternalEntityLoader(void); +XMLPUBFUN xmlParserInputPtr + xmlLoadExternalEntity (const char *URL, + const char *ID, + xmlParserCtxtPtr ctxt); + +/* + * Index lookup, actually implemented in the encoding module + */ +XMLPUBFUN long + xmlByteConsumed (xmlParserCtxtPtr ctxt); + +/* + * New set of simpler/more flexible APIs + */ +/** + * xmlParserOption: + * + * This is the set of XML parser options that can be passed down + * to the xmlReadDoc() and similar calls. + */ +typedef enum { + XML_PARSE_RECOVER = 1<<0, /* recover on errors */ + XML_PARSE_NOENT = 1<<1, /* substitute entities */ + XML_PARSE_DTDLOAD = 1<<2, /* load the external subset */ + XML_PARSE_DTDATTR = 1<<3, /* default DTD attributes */ + XML_PARSE_DTDVALID = 1<<4, /* validate with the DTD */ + XML_PARSE_NOERROR = 1<<5, /* suppress error reports */ + XML_PARSE_NOWARNING = 1<<6, /* suppress warning reports */ + XML_PARSE_PEDANTIC = 1<<7, /* pedantic error reporting */ + XML_PARSE_NOBLANKS = 1<<8, /* remove blank nodes */ + XML_PARSE_SAX1 = 1<<9, /* use the SAX1 interface internally */ + XML_PARSE_XINCLUDE = 1<<10,/* Implement XInclude substitution */ + XML_PARSE_NONET = 1<<11,/* Forbid network access */ + XML_PARSE_NODICT = 1<<12,/* Do not reuse the context dictionary */ + XML_PARSE_NSCLEAN = 1<<13,/* remove redundant namespaces declarations */ + XML_PARSE_NOCDATA = 1<<14,/* merge CDATA as text nodes */ + XML_PARSE_NOXINCNODE= 1<<15,/* do not generate XINCLUDE START/END nodes */ + XML_PARSE_COMPACT = 1<<16,/* compact small text nodes; no modification of + the tree allowed afterwards (will possibly + crash if you try to modify the tree) */ + XML_PARSE_OLD10 = 1<<17,/* parse using XML-1.0 before update 5 */ + XML_PARSE_NOBASEFIX = 1<<18,/* do not fixup XINCLUDE xml:base uris */ + XML_PARSE_HUGE = 1<<19,/* relax any hardcoded limit from the parser */ + XML_PARSE_OLDSAX = 1<<20,/* parse using SAX2 interface before 2.7.0 */ + XML_PARSE_IGNORE_ENC= 1<<21,/* ignore internal document encoding hint */ + XML_PARSE_BIG_LINES = 1<<22,/* Store big lines numbers in text PSVI field */ + XML_PARSE_NO_XXE = 1<<23 /* disable loading of external content */ +} xmlParserOption; + +XMLPUBFUN void + xmlCtxtReset (xmlParserCtxtPtr ctxt); +XMLPUBFUN int + xmlCtxtResetPush (xmlParserCtxtPtr ctxt, + const char *chunk, + int size, + const char *filename, + const char *encoding); +XMLPUBFUN int + xmlCtxtSetOptions (xmlParserCtxtPtr ctxt, + int options); +XMLPUBFUN int + xmlCtxtUseOptions (xmlParserCtxtPtr ctxt, + int options); +XMLPUBFUN void + xmlCtxtSetErrorHandler (xmlParserCtxtPtr ctxt, + xmlStructuredErrorFunc handler, + void *data); +XMLPUBFUN void + xmlCtxtSetMaxAmplification(xmlParserCtxtPtr ctxt, + unsigned maxAmpl); +XMLPUBFUN xmlDocPtr + xmlReadDoc (const xmlChar *cur, + const char *URL, + const char *encoding, + int options); +XMLPUBFUN xmlDocPtr + xmlReadFile (const char *URL, + const char *encoding, + int options); +XMLPUBFUN xmlDocPtr + xmlReadMemory (const char *buffer, + int size, + const char *URL, + const char *encoding, + int options); +XMLPUBFUN xmlDocPtr + xmlReadFd (int fd, + const char *URL, + const char *encoding, + int options); +XMLPUBFUN xmlDocPtr + xmlReadIO (xmlInputReadCallback ioread, + xmlInputCloseCallback ioclose, + void *ioctx, + const char *URL, + const char *encoding, + int options); +XMLPUBFUN xmlDocPtr + xmlCtxtParseDocument (xmlParserCtxtPtr ctxt, + xmlParserInputPtr input); +XMLPUBFUN xmlDocPtr + xmlCtxtReadDoc (xmlParserCtxtPtr ctxt, + const xmlChar *cur, + const char *URL, + const char *encoding, + int options); +XMLPUBFUN xmlDocPtr + xmlCtxtReadFile (xmlParserCtxtPtr ctxt, + const char *filename, + const char *encoding, + int options); +XMLPUBFUN xmlDocPtr + xmlCtxtReadMemory (xmlParserCtxtPtr ctxt, + const char *buffer, + int size, + const char *URL, + const char *encoding, + int options); +XMLPUBFUN xmlDocPtr + xmlCtxtReadFd (xmlParserCtxtPtr ctxt, + int fd, + const char *URL, + const char *encoding, + int options); +XMLPUBFUN xmlDocPtr + xmlCtxtReadIO (xmlParserCtxtPtr ctxt, + xmlInputReadCallback ioread, + xmlInputCloseCallback ioclose, + void *ioctx, + const char *URL, + const char *encoding, + int options); + +/* + * Library wide options + */ +/** + * xmlFeature: + * + * Used to examine the existence of features that can be enabled + * or disabled at compile-time. + * They used to be called XML_FEATURE_xxx but this clashed with Expat + */ +typedef enum { + XML_WITH_THREAD = 1, + XML_WITH_TREE = 2, + XML_WITH_OUTPUT = 3, + XML_WITH_PUSH = 4, + XML_WITH_READER = 5, + XML_WITH_PATTERN = 6, + XML_WITH_WRITER = 7, + XML_WITH_SAX1 = 8, + XML_WITH_FTP = 9, + XML_WITH_HTTP = 10, + XML_WITH_VALID = 11, + XML_WITH_HTML = 12, + XML_WITH_LEGACY = 13, + XML_WITH_C14N = 14, + XML_WITH_CATALOG = 15, + XML_WITH_XPATH = 16, + XML_WITH_XPTR = 17, + XML_WITH_XINCLUDE = 18, + XML_WITH_ICONV = 19, + XML_WITH_ISO8859X = 20, + XML_WITH_UNICODE = 21, + XML_WITH_REGEXP = 22, + XML_WITH_AUTOMATA = 23, + XML_WITH_EXPR = 24, + XML_WITH_SCHEMAS = 25, + XML_WITH_SCHEMATRON = 26, + XML_WITH_MODULES = 27, + XML_WITH_DEBUG = 28, + XML_WITH_DEBUG_MEM = 29, + XML_WITH_DEBUG_RUN = 30, /* unused */ + XML_WITH_ZLIB = 31, + XML_WITH_ICU = 32, + XML_WITH_LZMA = 33, + XML_WITH_NONE = 99999 /* just to be sure of allocation size */ +} xmlFeature; + +XMLPUBFUN int + xmlHasFeature (xmlFeature feature); + +#ifdef __cplusplus +} +#endif +#endif /* __XML_PARSER_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/parserInternals.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/parserInternals.h new file mode 100644 index 00000000..c4d4363b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/parserInternals.h @@ -0,0 +1,671 @@ +/* + * Summary: internals routines and limits exported by the parser. + * Description: this module exports a number of internal parsing routines + * they are not really all intended for applications but + * can prove useful doing low level processing. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_PARSER_INTERNALS_H__ +#define __XML_PARSER_INTERNALS_H__ + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * xmlParserMaxDepth: + * + * DEPRECATED: has no effect + * + * arbitrary depth limit for the XML documents that we allow to + * process. This is not a limitation of the parser but a safety + * boundary feature, use XML_PARSE_HUGE option to override it. + */ +XML_DEPRECATED +XMLPUBVAR const unsigned int xmlParserMaxDepth; + +/** + * XML_MAX_TEXT_LENGTH: + * + * Maximum size allowed for a single text node when building a tree. + * This is not a limitation of the parser but a safety boundary feature, + * use XML_PARSE_HUGE option to override it. + * Introduced in 2.9.0 + */ +#define XML_MAX_TEXT_LENGTH 10000000 + +/** + * XML_MAX_HUGE_LENGTH: + * + * Maximum size allowed when XML_PARSE_HUGE is set. + */ +#define XML_MAX_HUGE_LENGTH 1000000000 + +/** + * XML_MAX_NAME_LENGTH: + * + * Maximum size allowed for a markup identifier. + * This is not a limitation of the parser but a safety boundary feature, + * use XML_PARSE_HUGE option to override it. + * Note that with the use of parsing dictionaries overriding the limit + * may result in more runtime memory usage in face of "unfriendly' content + * Introduced in 2.9.0 + */ +#define XML_MAX_NAME_LENGTH 50000 + +/** + * XML_MAX_DICTIONARY_LIMIT: + * + * Maximum size allowed by the parser for a dictionary by default + * This is not a limitation of the parser but a safety boundary feature, + * use XML_PARSE_HUGE option to override it. + * Introduced in 2.9.0 + */ +#define XML_MAX_DICTIONARY_LIMIT 10000000 + +/** + * XML_MAX_LOOKUP_LIMIT: + * + * Maximum size allowed by the parser for ahead lookup + * This is an upper boundary enforced by the parser to avoid bad + * behaviour on "unfriendly' content + * Introduced in 2.9.0 + */ +#define XML_MAX_LOOKUP_LIMIT 10000000 + +/** + * XML_MAX_NAMELEN: + * + * Identifiers can be longer, but this will be more costly + * at runtime. + */ +#define XML_MAX_NAMELEN 100 + +/** + * INPUT_CHUNK: + * + * The parser tries to always have that amount of input ready. + * One of the point is providing context when reporting errors. + */ +#define INPUT_CHUNK 250 + +/************************************************************************ + * * + * UNICODE version of the macros. * + * * + ************************************************************************/ +/** + * IS_BYTE_CHAR: + * @c: an byte value (int) + * + * Macro to check the following production in the XML spec: + * + * [2] Char ::= #x9 | #xA | #xD | [#x20...] + * any byte character in the accepted range + */ +#define IS_BYTE_CHAR(c) xmlIsChar_ch(c) + +/** + * IS_CHAR: + * @c: an UNICODE value (int) + * + * Macro to check the following production in the XML spec: + * + * [2] Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] + * | [#x10000-#x10FFFF] + * any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. + */ +#define IS_CHAR(c) xmlIsCharQ(c) + +/** + * IS_CHAR_CH: + * @c: an xmlChar (usually an unsigned char) + * + * Behaves like IS_CHAR on single-byte value + */ +#define IS_CHAR_CH(c) xmlIsChar_ch(c) + +/** + * IS_BLANK: + * @c: an UNICODE value (int) + * + * Macro to check the following production in the XML spec: + * + * [3] S ::= (#x20 | #x9 | #xD | #xA)+ + */ +#define IS_BLANK(c) xmlIsBlankQ(c) + +/** + * IS_BLANK_CH: + * @c: an xmlChar value (normally unsigned char) + * + * Behaviour same as IS_BLANK + */ +#define IS_BLANK_CH(c) xmlIsBlank_ch(c) + +/** + * IS_BASECHAR: + * @c: an UNICODE value (int) + * + * Macro to check the following production in the XML spec: + * + * [85] BaseChar ::= ... long list see REC ... + */ +#define IS_BASECHAR(c) xmlIsBaseCharQ(c) + +/** + * IS_DIGIT: + * @c: an UNICODE value (int) + * + * Macro to check the following production in the XML spec: + * + * [88] Digit ::= ... long list see REC ... + */ +#define IS_DIGIT(c) xmlIsDigitQ(c) + +/** + * IS_DIGIT_CH: + * @c: an xmlChar value (usually an unsigned char) + * + * Behaves like IS_DIGIT but with a single byte argument + */ +#define IS_DIGIT_CH(c) xmlIsDigit_ch(c) + +/** + * IS_COMBINING: + * @c: an UNICODE value (int) + * + * Macro to check the following production in the XML spec: + * + * [87] CombiningChar ::= ... long list see REC ... + */ +#define IS_COMBINING(c) xmlIsCombiningQ(c) + +/** + * IS_COMBINING_CH: + * @c: an xmlChar (usually an unsigned char) + * + * Always false (all combining chars > 0xff) + */ +#define IS_COMBINING_CH(c) 0 + +/** + * IS_EXTENDER: + * @c: an UNICODE value (int) + * + * Macro to check the following production in the XML spec: + * + * + * [89] Extender ::= #x00B7 | #x02D0 | #x02D1 | #x0387 | #x0640 | + * #x0E46 | #x0EC6 | #x3005 | [#x3031-#x3035] | + * [#x309D-#x309E] | [#x30FC-#x30FE] + */ +#define IS_EXTENDER(c) xmlIsExtenderQ(c) + +/** + * IS_EXTENDER_CH: + * @c: an xmlChar value (usually an unsigned char) + * + * Behaves like IS_EXTENDER but with a single-byte argument + */ +#define IS_EXTENDER_CH(c) xmlIsExtender_ch(c) + +/** + * IS_IDEOGRAPHIC: + * @c: an UNICODE value (int) + * + * Macro to check the following production in the XML spec: + * + * + * [86] Ideographic ::= [#x4E00-#x9FA5] | #x3007 | [#x3021-#x3029] + */ +#define IS_IDEOGRAPHIC(c) xmlIsIdeographicQ(c) + +/** + * IS_LETTER: + * @c: an UNICODE value (int) + * + * Macro to check the following production in the XML spec: + * + * + * [84] Letter ::= BaseChar | Ideographic + */ +#define IS_LETTER(c) (IS_BASECHAR(c) || IS_IDEOGRAPHIC(c)) + +/** + * IS_LETTER_CH: + * @c: an xmlChar value (normally unsigned char) + * + * Macro behaves like IS_LETTER, but only check base chars + * + */ +#define IS_LETTER_CH(c) xmlIsBaseChar_ch(c) + +/** + * IS_ASCII_LETTER: + * @c: an xmlChar value + * + * Macro to check [a-zA-Z] + * + */ +#define IS_ASCII_LETTER(c) (((0x41 <= (c)) && ((c) <= 0x5a)) || \ + ((0x61 <= (c)) && ((c) <= 0x7a))) + +/** + * IS_ASCII_DIGIT: + * @c: an xmlChar value + * + * Macro to check [0-9] + * + */ +#define IS_ASCII_DIGIT(c) ((0x30 <= (c)) && ((c) <= 0x39)) + +/** + * IS_PUBIDCHAR: + * @c: an UNICODE value (int) + * + * Macro to check the following production in the XML spec: + * + * + * [13] PubidChar ::= #x20 | #xD | #xA | [a-zA-Z0-9] | [-'()+,./:=?;!*#@$_%] + */ +#define IS_PUBIDCHAR(c) xmlIsPubidCharQ(c) + +/** + * IS_PUBIDCHAR_CH: + * @c: an xmlChar value (normally unsigned char) + * + * Same as IS_PUBIDCHAR but for single-byte value + */ +#define IS_PUBIDCHAR_CH(c) xmlIsPubidChar_ch(c) + +/** + * Global variables used for predefined strings. + */ +XMLPUBVAR const xmlChar xmlStringText[]; +XMLPUBVAR const xmlChar xmlStringTextNoenc[]; +XMLPUBVAR const xmlChar xmlStringComment[]; + +/* + * Function to finish the work of the macros where needed. + */ +XMLPUBFUN int xmlIsLetter (int c); + +/** + * Parser context. + */ +XMLPUBFUN xmlParserCtxtPtr + xmlCreateFileParserCtxt (const char *filename); +XMLPUBFUN xmlParserCtxtPtr + xmlCreateURLParserCtxt (const char *filename, + int options); +XMLPUBFUN xmlParserCtxtPtr + xmlCreateMemoryParserCtxt(const char *buffer, + int size); +XMLPUBFUN xmlParserCtxtPtr + xmlCreateEntityParserCtxt(const xmlChar *URL, + const xmlChar *ID, + const xmlChar *base); +XMLPUBFUN void + xmlCtxtErrMemory (xmlParserCtxtPtr ctxt); +XMLPUBFUN int + xmlSwitchEncoding (xmlParserCtxtPtr ctxt, + xmlCharEncoding enc); +XMLPUBFUN int + xmlSwitchEncodingName (xmlParserCtxtPtr ctxt, + const char *encoding); +XMLPUBFUN int + xmlSwitchToEncoding (xmlParserCtxtPtr ctxt, + xmlCharEncodingHandlerPtr handler); +XML_DEPRECATED +XMLPUBFUN int + xmlSwitchInputEncoding (xmlParserCtxtPtr ctxt, + xmlParserInputPtr input, + xmlCharEncodingHandlerPtr handler); + +/** + * Input Streams. + */ +XMLPUBFUN xmlParserInputPtr + xmlNewStringInputStream (xmlParserCtxtPtr ctxt, + const xmlChar *buffer); +XML_DEPRECATED +XMLPUBFUN xmlParserInputPtr + xmlNewEntityInputStream (xmlParserCtxtPtr ctxt, + xmlEntityPtr entity); +XMLPUBFUN int + xmlPushInput (xmlParserCtxtPtr ctxt, + xmlParserInputPtr input); +XMLPUBFUN xmlChar + xmlPopInput (xmlParserCtxtPtr ctxt); +XMLPUBFUN void + xmlFreeInputStream (xmlParserInputPtr input); +XMLPUBFUN xmlParserInputPtr + xmlNewInputFromFile (xmlParserCtxtPtr ctxt, + const char *filename); +XMLPUBFUN xmlParserInputPtr + xmlNewInputStream (xmlParserCtxtPtr ctxt); + +/** + * Namespaces. + */ +XMLPUBFUN xmlChar * + xmlSplitQName (xmlParserCtxtPtr ctxt, + const xmlChar *name, + xmlChar **prefix); + +/** + * Generic production rules. + */ +XML_DEPRECATED +XMLPUBFUN const xmlChar * + xmlParseName (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN xmlChar * + xmlParseNmtoken (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN xmlChar * + xmlParseEntityValue (xmlParserCtxtPtr ctxt, + xmlChar **orig); +XML_DEPRECATED +XMLPUBFUN xmlChar * + xmlParseAttValue (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN xmlChar * + xmlParseSystemLiteral (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN xmlChar * + xmlParsePubidLiteral (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN void + xmlParseCharData (xmlParserCtxtPtr ctxt, + int cdata); +XML_DEPRECATED +XMLPUBFUN xmlChar * + xmlParseExternalID (xmlParserCtxtPtr ctxt, + xmlChar **publicID, + int strict); +XML_DEPRECATED +XMLPUBFUN void + xmlParseComment (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN const xmlChar * + xmlParsePITarget (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN void + xmlParsePI (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN void + xmlParseNotationDecl (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN void + xmlParseEntityDecl (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN int + xmlParseDefaultDecl (xmlParserCtxtPtr ctxt, + xmlChar **value); +XML_DEPRECATED +XMLPUBFUN xmlEnumerationPtr + xmlParseNotationType (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN xmlEnumerationPtr + xmlParseEnumerationType (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN int + xmlParseEnumeratedType (xmlParserCtxtPtr ctxt, + xmlEnumerationPtr *tree); +XML_DEPRECATED +XMLPUBFUN int + xmlParseAttributeType (xmlParserCtxtPtr ctxt, + xmlEnumerationPtr *tree); +XML_DEPRECATED +XMLPUBFUN void + xmlParseAttributeListDecl(xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN xmlElementContentPtr + xmlParseElementMixedContentDecl + (xmlParserCtxtPtr ctxt, + int inputchk); +XML_DEPRECATED +XMLPUBFUN xmlElementContentPtr + xmlParseElementChildrenContentDecl + (xmlParserCtxtPtr ctxt, + int inputchk); +XML_DEPRECATED +XMLPUBFUN int + xmlParseElementContentDecl(xmlParserCtxtPtr ctxt, + const xmlChar *name, + xmlElementContentPtr *result); +XML_DEPRECATED +XMLPUBFUN int + xmlParseElementDecl (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN void + xmlParseMarkupDecl (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN int + xmlParseCharRef (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN xmlEntityPtr + xmlParseEntityRef (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN void + xmlParseReference (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN void + xmlParsePEReference (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN void + xmlParseDocTypeDecl (xmlParserCtxtPtr ctxt); +#ifdef LIBXML_SAX1_ENABLED +XML_DEPRECATED +XMLPUBFUN const xmlChar * + xmlParseAttribute (xmlParserCtxtPtr ctxt, + xmlChar **value); +XML_DEPRECATED +XMLPUBFUN const xmlChar * + xmlParseStartTag (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN void + xmlParseEndTag (xmlParserCtxtPtr ctxt); +#endif /* LIBXML_SAX1_ENABLED */ +XML_DEPRECATED +XMLPUBFUN void + xmlParseCDSect (xmlParserCtxtPtr ctxt); +XMLPUBFUN void + xmlParseContent (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN void + xmlParseElement (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN xmlChar * + xmlParseVersionNum (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN xmlChar * + xmlParseVersionInfo (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN xmlChar * + xmlParseEncName (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN const xmlChar * + xmlParseEncodingDecl (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN int + xmlParseSDDecl (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN void + xmlParseXMLDecl (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN void + xmlParseTextDecl (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN void + xmlParseMisc (xmlParserCtxtPtr ctxt); +XMLPUBFUN void + xmlParseExternalSubset (xmlParserCtxtPtr ctxt, + const xmlChar *ExternalID, + const xmlChar *SystemID); +/** + * XML_SUBSTITUTE_NONE: + * + * If no entities need to be substituted. + */ +#define XML_SUBSTITUTE_NONE 0 +/** + * XML_SUBSTITUTE_REF: + * + * Whether general entities need to be substituted. + */ +#define XML_SUBSTITUTE_REF 1 +/** + * XML_SUBSTITUTE_PEREF: + * + * Whether parameter entities need to be substituted. + */ +#define XML_SUBSTITUTE_PEREF 2 +/** + * XML_SUBSTITUTE_BOTH: + * + * Both general and parameter entities need to be substituted. + */ +#define XML_SUBSTITUTE_BOTH 3 + +XML_DEPRECATED +XMLPUBFUN xmlChar * + xmlStringDecodeEntities (xmlParserCtxtPtr ctxt, + const xmlChar *str, + int what, + xmlChar end, + xmlChar end2, + xmlChar end3); +XML_DEPRECATED +XMLPUBFUN xmlChar * + xmlStringLenDecodeEntities (xmlParserCtxtPtr ctxt, + const xmlChar *str, + int len, + int what, + xmlChar end, + xmlChar end2, + xmlChar end3); + +/* + * Generated by MACROS on top of parser.c c.f. PUSH_AND_POP. + */ +XML_DEPRECATED +XMLPUBFUN int nodePush (xmlParserCtxtPtr ctxt, + xmlNodePtr value); +XML_DEPRECATED +XMLPUBFUN xmlNodePtr nodePop (xmlParserCtxtPtr ctxt); +XMLPUBFUN int inputPush (xmlParserCtxtPtr ctxt, + xmlParserInputPtr value); +XMLPUBFUN xmlParserInputPtr inputPop (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN const xmlChar * namePop (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN int namePush (xmlParserCtxtPtr ctxt, + const xmlChar *value); + +/* + * other commodities shared between parser.c and parserInternals. + */ +XML_DEPRECATED +XMLPUBFUN int xmlSkipBlankChars (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN int xmlStringCurrentChar (xmlParserCtxtPtr ctxt, + const xmlChar *cur, + int *len); +XML_DEPRECATED +XMLPUBFUN void xmlParserHandlePEReference(xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN int xmlCheckLanguageID (const xmlChar *lang); + +/* + * Really core function shared with HTML parser. + */ +XML_DEPRECATED +XMLPUBFUN int xmlCurrentChar (xmlParserCtxtPtr ctxt, + int *len); +XMLPUBFUN int xmlCopyCharMultiByte (xmlChar *out, + int val); +XMLPUBFUN int xmlCopyChar (int len, + xmlChar *out, + int val); +XML_DEPRECATED +XMLPUBFUN void xmlNextChar (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN void xmlParserInputShrink (xmlParserInputPtr in); + +/* + * Specific function to keep track of entities references + * and used by the XSLT debugger. + */ +#ifdef LIBXML_LEGACY_ENABLED +/** + * xmlEntityReferenceFunc: + * @ent: the entity + * @firstNode: the fist node in the chunk + * @lastNode: the last nod in the chunk + * + * Callback function used when one needs to be able to track back the + * provenance of a chunk of nodes inherited from an entity replacement. + */ +typedef void (*xmlEntityReferenceFunc) (xmlEntityPtr ent, + xmlNodePtr firstNode, + xmlNodePtr lastNode); + +XML_DEPRECATED +XMLPUBFUN void xmlSetEntityReferenceFunc (xmlEntityReferenceFunc func); + +XML_DEPRECATED +XMLPUBFUN xmlChar * + xmlParseQuotedString (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN void + xmlParseNamespace (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN xmlChar * + xmlNamespaceParseNSDef (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN xmlChar * + xmlScanName (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN xmlChar * + xmlNamespaceParseNCName (xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN void xmlParserHandleReference(xmlParserCtxtPtr ctxt); +XML_DEPRECATED +XMLPUBFUN xmlChar * + xmlNamespaceParseQName (xmlParserCtxtPtr ctxt, + xmlChar **prefix); +/** + * Entities + */ +XML_DEPRECATED +XMLPUBFUN xmlChar * + xmlDecodeEntities (xmlParserCtxtPtr ctxt, + int len, + int what, + xmlChar end, + xmlChar end2, + xmlChar end3); +XML_DEPRECATED +XMLPUBFUN void + xmlHandleEntity (xmlParserCtxtPtr ctxt, + xmlEntityPtr entity); + +#endif /* LIBXML_LEGACY_ENABLED */ + +#ifdef __cplusplus +} +#endif +#endif /* __XML_PARSER_INTERNALS_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/pattern.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/pattern.h new file mode 100644 index 00000000..947f0900 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/pattern.h @@ -0,0 +1,106 @@ +/* + * Summary: pattern expression handling + * Description: allows to compile and test pattern expressions for nodes + * either in a tree or based on a parser state. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_PATTERN_H__ +#define __XML_PATTERN_H__ + +#include +#include +#include + +#ifdef LIBXML_PATTERN_ENABLED + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * xmlPattern: + * + * A compiled (XPath based) pattern to select nodes + */ +typedef struct _xmlPattern xmlPattern; +typedef xmlPattern *xmlPatternPtr; + +/** + * xmlPatternFlags: + * + * This is the set of options affecting the behaviour of pattern + * matching with this module + * + */ +typedef enum { + XML_PATTERN_DEFAULT = 0, /* simple pattern match */ + XML_PATTERN_XPATH = 1<<0, /* standard XPath pattern */ + XML_PATTERN_XSSEL = 1<<1, /* XPath subset for schema selector */ + XML_PATTERN_XSFIELD = 1<<2 /* XPath subset for schema field */ +} xmlPatternFlags; + +XMLPUBFUN void + xmlFreePattern (xmlPatternPtr comp); + +XMLPUBFUN void + xmlFreePatternList (xmlPatternPtr comp); + +XMLPUBFUN xmlPatternPtr + xmlPatterncompile (const xmlChar *pattern, + xmlDict *dict, + int flags, + const xmlChar **namespaces); +XMLPUBFUN int + xmlPatternCompileSafe (const xmlChar *pattern, + xmlDict *dict, + int flags, + const xmlChar **namespaces, + xmlPatternPtr *patternOut); +XMLPUBFUN int + xmlPatternMatch (xmlPatternPtr comp, + xmlNodePtr node); + +/* streaming interfaces */ +typedef struct _xmlStreamCtxt xmlStreamCtxt; +typedef xmlStreamCtxt *xmlStreamCtxtPtr; + +XMLPUBFUN int + xmlPatternStreamable (xmlPatternPtr comp); +XMLPUBFUN int + xmlPatternMaxDepth (xmlPatternPtr comp); +XMLPUBFUN int + xmlPatternMinDepth (xmlPatternPtr comp); +XMLPUBFUN int + xmlPatternFromRoot (xmlPatternPtr comp); +XMLPUBFUN xmlStreamCtxtPtr + xmlPatternGetStreamCtxt (xmlPatternPtr comp); +XMLPUBFUN void + xmlFreeStreamCtxt (xmlStreamCtxtPtr stream); +XMLPUBFUN int + xmlStreamPushNode (xmlStreamCtxtPtr stream, + const xmlChar *name, + const xmlChar *ns, + int nodeType); +XMLPUBFUN int + xmlStreamPush (xmlStreamCtxtPtr stream, + const xmlChar *name, + const xmlChar *ns); +XMLPUBFUN int + xmlStreamPushAttr (xmlStreamCtxtPtr stream, + const xmlChar *name, + const xmlChar *ns); +XMLPUBFUN int + xmlStreamPop (xmlStreamCtxtPtr stream); +XMLPUBFUN int + xmlStreamWantsAnyNode (xmlStreamCtxtPtr stream); +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_PATTERN_ENABLED */ + +#endif /* __XML_PATTERN_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/relaxng.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/relaxng.h new file mode 100644 index 00000000..079b7f12 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/relaxng.h @@ -0,0 +1,219 @@ +/* + * Summary: implementation of the Relax-NG validation + * Description: implementation of the Relax-NG validation + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_RELAX_NG__ +#define __XML_RELAX_NG__ + +#include +#include +#include +#include + +#ifdef LIBXML_SCHEMAS_ENABLED + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _xmlRelaxNG xmlRelaxNG; +typedef xmlRelaxNG *xmlRelaxNGPtr; + + +/** + * xmlRelaxNGValidityErrorFunc: + * @ctx: the validation context + * @msg: the message + * @...: extra arguments + * + * Signature of an error callback from a Relax-NG validation + */ +typedef void (*xmlRelaxNGValidityErrorFunc) (void *ctx, + const char *msg, + ...) LIBXML_ATTR_FORMAT(2,3); + +/** + * xmlRelaxNGValidityWarningFunc: + * @ctx: the validation context + * @msg: the message + * @...: extra arguments + * + * Signature of a warning callback from a Relax-NG validation + */ +typedef void (*xmlRelaxNGValidityWarningFunc) (void *ctx, + const char *msg, + ...) LIBXML_ATTR_FORMAT(2,3); + +/** + * A schemas validation context + */ +typedef struct _xmlRelaxNGParserCtxt xmlRelaxNGParserCtxt; +typedef xmlRelaxNGParserCtxt *xmlRelaxNGParserCtxtPtr; + +typedef struct _xmlRelaxNGValidCtxt xmlRelaxNGValidCtxt; +typedef xmlRelaxNGValidCtxt *xmlRelaxNGValidCtxtPtr; + +/* + * xmlRelaxNGValidErr: + * + * List of possible Relax NG validation errors + */ +typedef enum { + XML_RELAXNG_OK = 0, + XML_RELAXNG_ERR_MEMORY, + XML_RELAXNG_ERR_TYPE, + XML_RELAXNG_ERR_TYPEVAL, + XML_RELAXNG_ERR_DUPID, + XML_RELAXNG_ERR_TYPECMP, + XML_RELAXNG_ERR_NOSTATE, + XML_RELAXNG_ERR_NODEFINE, + XML_RELAXNG_ERR_LISTEXTRA, + XML_RELAXNG_ERR_LISTEMPTY, + XML_RELAXNG_ERR_INTERNODATA, + XML_RELAXNG_ERR_INTERSEQ, + XML_RELAXNG_ERR_INTEREXTRA, + XML_RELAXNG_ERR_ELEMNAME, + XML_RELAXNG_ERR_ATTRNAME, + XML_RELAXNG_ERR_ELEMNONS, + XML_RELAXNG_ERR_ATTRNONS, + XML_RELAXNG_ERR_ELEMWRONGNS, + XML_RELAXNG_ERR_ATTRWRONGNS, + XML_RELAXNG_ERR_ELEMEXTRANS, + XML_RELAXNG_ERR_ATTREXTRANS, + XML_RELAXNG_ERR_ELEMNOTEMPTY, + XML_RELAXNG_ERR_NOELEM, + XML_RELAXNG_ERR_NOTELEM, + XML_RELAXNG_ERR_ATTRVALID, + XML_RELAXNG_ERR_CONTENTVALID, + XML_RELAXNG_ERR_EXTRACONTENT, + XML_RELAXNG_ERR_INVALIDATTR, + XML_RELAXNG_ERR_DATAELEM, + XML_RELAXNG_ERR_VALELEM, + XML_RELAXNG_ERR_LISTELEM, + XML_RELAXNG_ERR_DATATYPE, + XML_RELAXNG_ERR_VALUE, + XML_RELAXNG_ERR_LIST, + XML_RELAXNG_ERR_NOGRAMMAR, + XML_RELAXNG_ERR_EXTRADATA, + XML_RELAXNG_ERR_LACKDATA, + XML_RELAXNG_ERR_INTERNAL, + XML_RELAXNG_ERR_ELEMWRONG, + XML_RELAXNG_ERR_TEXTWRONG +} xmlRelaxNGValidErr; + +/* + * xmlRelaxNGParserFlags: + * + * List of possible Relax NG Parser flags + */ +typedef enum { + XML_RELAXNGP_NONE = 0, + XML_RELAXNGP_FREE_DOC = 1, + XML_RELAXNGP_CRNG = 2 +} xmlRelaxNGParserFlag; + +XMLPUBFUN int + xmlRelaxNGInitTypes (void); +XML_DEPRECATED +XMLPUBFUN void + xmlRelaxNGCleanupTypes (void); + +/* + * Interfaces for parsing. + */ +XMLPUBFUN xmlRelaxNGParserCtxtPtr + xmlRelaxNGNewParserCtxt (const char *URL); +XMLPUBFUN xmlRelaxNGParserCtxtPtr + xmlRelaxNGNewMemParserCtxt (const char *buffer, + int size); +XMLPUBFUN xmlRelaxNGParserCtxtPtr + xmlRelaxNGNewDocParserCtxt (xmlDocPtr doc); + +XMLPUBFUN int + xmlRelaxParserSetFlag (xmlRelaxNGParserCtxtPtr ctxt, + int flag); + +XMLPUBFUN void + xmlRelaxNGFreeParserCtxt (xmlRelaxNGParserCtxtPtr ctxt); +XMLPUBFUN void + xmlRelaxNGSetParserErrors(xmlRelaxNGParserCtxtPtr ctxt, + xmlRelaxNGValidityErrorFunc err, + xmlRelaxNGValidityWarningFunc warn, + void *ctx); +XMLPUBFUN int + xmlRelaxNGGetParserErrors(xmlRelaxNGParserCtxtPtr ctxt, + xmlRelaxNGValidityErrorFunc *err, + xmlRelaxNGValidityWarningFunc *warn, + void **ctx); +XMLPUBFUN void + xmlRelaxNGSetParserStructuredErrors( + xmlRelaxNGParserCtxtPtr ctxt, + xmlStructuredErrorFunc serror, + void *ctx); +XMLPUBFUN xmlRelaxNGPtr + xmlRelaxNGParse (xmlRelaxNGParserCtxtPtr ctxt); +XMLPUBFUN void + xmlRelaxNGFree (xmlRelaxNGPtr schema); +#ifdef LIBXML_OUTPUT_ENABLED +XMLPUBFUN void + xmlRelaxNGDump (FILE *output, + xmlRelaxNGPtr schema); +XMLPUBFUN void + xmlRelaxNGDumpTree (FILE * output, + xmlRelaxNGPtr schema); +#endif /* LIBXML_OUTPUT_ENABLED */ +/* + * Interfaces for validating + */ +XMLPUBFUN void + xmlRelaxNGSetValidErrors(xmlRelaxNGValidCtxtPtr ctxt, + xmlRelaxNGValidityErrorFunc err, + xmlRelaxNGValidityWarningFunc warn, + void *ctx); +XMLPUBFUN int + xmlRelaxNGGetValidErrors(xmlRelaxNGValidCtxtPtr ctxt, + xmlRelaxNGValidityErrorFunc *err, + xmlRelaxNGValidityWarningFunc *warn, + void **ctx); +XMLPUBFUN void + xmlRelaxNGSetValidStructuredErrors(xmlRelaxNGValidCtxtPtr ctxt, + xmlStructuredErrorFunc serror, void *ctx); +XMLPUBFUN xmlRelaxNGValidCtxtPtr + xmlRelaxNGNewValidCtxt (xmlRelaxNGPtr schema); +XMLPUBFUN void + xmlRelaxNGFreeValidCtxt (xmlRelaxNGValidCtxtPtr ctxt); +XMLPUBFUN int + xmlRelaxNGValidateDoc (xmlRelaxNGValidCtxtPtr ctxt, + xmlDocPtr doc); +/* + * Interfaces for progressive validation when possible + */ +XMLPUBFUN int + xmlRelaxNGValidatePushElement (xmlRelaxNGValidCtxtPtr ctxt, + xmlDocPtr doc, + xmlNodePtr elem); +XMLPUBFUN int + xmlRelaxNGValidatePushCData (xmlRelaxNGValidCtxtPtr ctxt, + const xmlChar *data, + int len); +XMLPUBFUN int + xmlRelaxNGValidatePopElement (xmlRelaxNGValidCtxtPtr ctxt, + xmlDocPtr doc, + xmlNodePtr elem); +XMLPUBFUN int + xmlRelaxNGValidateFullElement (xmlRelaxNGValidCtxtPtr ctxt, + xmlDocPtr doc, + xmlNodePtr elem); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_SCHEMAS_ENABLED */ + +#endif /* __XML_RELAX_NG__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/schemasInternals.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/schemasInternals.h new file mode 100644 index 00000000..e9d3b3c7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/schemasInternals.h @@ -0,0 +1,959 @@ +/* + * Summary: internal interfaces for XML Schemas + * Description: internal interfaces for the XML Schemas handling + * and schema validity checking + * The Schemas development is a Work In Progress. + * Some of those interfaces are not guaranteed to be API or ABI stable ! + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + + +#ifndef __XML_SCHEMA_INTERNALS_H__ +#define __XML_SCHEMA_INTERNALS_H__ + +#include + +#ifdef LIBXML_SCHEMAS_ENABLED + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + XML_SCHEMAS_UNKNOWN = 0, + XML_SCHEMAS_STRING = 1, + XML_SCHEMAS_NORMSTRING = 2, + XML_SCHEMAS_DECIMAL = 3, + XML_SCHEMAS_TIME = 4, + XML_SCHEMAS_GDAY = 5, + XML_SCHEMAS_GMONTH = 6, + XML_SCHEMAS_GMONTHDAY = 7, + XML_SCHEMAS_GYEAR = 8, + XML_SCHEMAS_GYEARMONTH = 9, + XML_SCHEMAS_DATE = 10, + XML_SCHEMAS_DATETIME = 11, + XML_SCHEMAS_DURATION = 12, + XML_SCHEMAS_FLOAT = 13, + XML_SCHEMAS_DOUBLE = 14, + XML_SCHEMAS_BOOLEAN = 15, + XML_SCHEMAS_TOKEN = 16, + XML_SCHEMAS_LANGUAGE = 17, + XML_SCHEMAS_NMTOKEN = 18, + XML_SCHEMAS_NMTOKENS = 19, + XML_SCHEMAS_NAME = 20, + XML_SCHEMAS_QNAME = 21, + XML_SCHEMAS_NCNAME = 22, + XML_SCHEMAS_ID = 23, + XML_SCHEMAS_IDREF = 24, + XML_SCHEMAS_IDREFS = 25, + XML_SCHEMAS_ENTITY = 26, + XML_SCHEMAS_ENTITIES = 27, + XML_SCHEMAS_NOTATION = 28, + XML_SCHEMAS_ANYURI = 29, + XML_SCHEMAS_INTEGER = 30, + XML_SCHEMAS_NPINTEGER = 31, + XML_SCHEMAS_NINTEGER = 32, + XML_SCHEMAS_NNINTEGER = 33, + XML_SCHEMAS_PINTEGER = 34, + XML_SCHEMAS_INT = 35, + XML_SCHEMAS_UINT = 36, + XML_SCHEMAS_LONG = 37, + XML_SCHEMAS_ULONG = 38, + XML_SCHEMAS_SHORT = 39, + XML_SCHEMAS_USHORT = 40, + XML_SCHEMAS_BYTE = 41, + XML_SCHEMAS_UBYTE = 42, + XML_SCHEMAS_HEXBINARY = 43, + XML_SCHEMAS_BASE64BINARY = 44, + XML_SCHEMAS_ANYTYPE = 45, + XML_SCHEMAS_ANYSIMPLETYPE = 46 +} xmlSchemaValType; + +/* + * XML Schemas defines multiple type of types. + */ +typedef enum { + XML_SCHEMA_TYPE_BASIC = 1, /* A built-in datatype */ + XML_SCHEMA_TYPE_ANY, + XML_SCHEMA_TYPE_FACET, + XML_SCHEMA_TYPE_SIMPLE, + XML_SCHEMA_TYPE_COMPLEX, + XML_SCHEMA_TYPE_SEQUENCE = 6, + XML_SCHEMA_TYPE_CHOICE, + XML_SCHEMA_TYPE_ALL, + XML_SCHEMA_TYPE_SIMPLE_CONTENT, + XML_SCHEMA_TYPE_COMPLEX_CONTENT, + XML_SCHEMA_TYPE_UR, + XML_SCHEMA_TYPE_RESTRICTION, + XML_SCHEMA_TYPE_EXTENSION, + XML_SCHEMA_TYPE_ELEMENT, + XML_SCHEMA_TYPE_ATTRIBUTE, + XML_SCHEMA_TYPE_ATTRIBUTEGROUP, + XML_SCHEMA_TYPE_GROUP, + XML_SCHEMA_TYPE_NOTATION, + XML_SCHEMA_TYPE_LIST, + XML_SCHEMA_TYPE_UNION, + XML_SCHEMA_TYPE_ANY_ATTRIBUTE, + XML_SCHEMA_TYPE_IDC_UNIQUE, + XML_SCHEMA_TYPE_IDC_KEY, + XML_SCHEMA_TYPE_IDC_KEYREF, + XML_SCHEMA_TYPE_PARTICLE = 25, + XML_SCHEMA_TYPE_ATTRIBUTE_USE, + XML_SCHEMA_FACET_MININCLUSIVE = 1000, + XML_SCHEMA_FACET_MINEXCLUSIVE, + XML_SCHEMA_FACET_MAXINCLUSIVE, + XML_SCHEMA_FACET_MAXEXCLUSIVE, + XML_SCHEMA_FACET_TOTALDIGITS, + XML_SCHEMA_FACET_FRACTIONDIGITS, + XML_SCHEMA_FACET_PATTERN, + XML_SCHEMA_FACET_ENUMERATION, + XML_SCHEMA_FACET_WHITESPACE, + XML_SCHEMA_FACET_LENGTH, + XML_SCHEMA_FACET_MAXLENGTH, + XML_SCHEMA_FACET_MINLENGTH, + XML_SCHEMA_EXTRA_QNAMEREF = 2000, + XML_SCHEMA_EXTRA_ATTR_USE_PROHIB +} xmlSchemaTypeType; + +typedef enum { + XML_SCHEMA_CONTENT_UNKNOWN = 0, + XML_SCHEMA_CONTENT_EMPTY = 1, + XML_SCHEMA_CONTENT_ELEMENTS, + XML_SCHEMA_CONTENT_MIXED, + XML_SCHEMA_CONTENT_SIMPLE, + XML_SCHEMA_CONTENT_MIXED_OR_ELEMENTS, /* Obsolete */ + XML_SCHEMA_CONTENT_BASIC, + XML_SCHEMA_CONTENT_ANY +} xmlSchemaContentType; + +typedef struct _xmlSchemaVal xmlSchemaVal; +typedef xmlSchemaVal *xmlSchemaValPtr; + +typedef struct _xmlSchemaType xmlSchemaType; +typedef xmlSchemaType *xmlSchemaTypePtr; + +typedef struct _xmlSchemaFacet xmlSchemaFacet; +typedef xmlSchemaFacet *xmlSchemaFacetPtr; + +/** + * Annotation + */ +typedef struct _xmlSchemaAnnot xmlSchemaAnnot; +typedef xmlSchemaAnnot *xmlSchemaAnnotPtr; +struct _xmlSchemaAnnot { + struct _xmlSchemaAnnot *next; + xmlNodePtr content; /* the annotation */ +}; + +/** + * XML_SCHEMAS_ANYATTR_SKIP: + * + * Skip unknown attribute from validation + * Obsolete, not used anymore. + */ +#define XML_SCHEMAS_ANYATTR_SKIP 1 +/** + * XML_SCHEMAS_ANYATTR_LAX: + * + * Ignore validation non definition on attributes + * Obsolete, not used anymore. + */ +#define XML_SCHEMAS_ANYATTR_LAX 2 +/** + * XML_SCHEMAS_ANYATTR_STRICT: + * + * Apply strict validation rules on attributes + * Obsolete, not used anymore. + */ +#define XML_SCHEMAS_ANYATTR_STRICT 3 +/** + * XML_SCHEMAS_ANY_SKIP: + * + * Skip unknown attribute from validation + */ +#define XML_SCHEMAS_ANY_SKIP 1 +/** + * XML_SCHEMAS_ANY_LAX: + * + * Used by wildcards. + * Validate if type found, don't worry if not found + */ +#define XML_SCHEMAS_ANY_LAX 2 +/** + * XML_SCHEMAS_ANY_STRICT: + * + * Used by wildcards. + * Apply strict validation rules + */ +#define XML_SCHEMAS_ANY_STRICT 3 +/** + * XML_SCHEMAS_ATTR_USE_PROHIBITED: + * + * Used by wildcards. + * The attribute is prohibited. + */ +#define XML_SCHEMAS_ATTR_USE_PROHIBITED 0 +/** + * XML_SCHEMAS_ATTR_USE_REQUIRED: + * + * The attribute is required. + */ +#define XML_SCHEMAS_ATTR_USE_REQUIRED 1 +/** + * XML_SCHEMAS_ATTR_USE_OPTIONAL: + * + * The attribute is optional. + */ +#define XML_SCHEMAS_ATTR_USE_OPTIONAL 2 +/** + * XML_SCHEMAS_ATTR_GLOBAL: + * + * allow elements in no namespace + */ +#define XML_SCHEMAS_ATTR_GLOBAL 1 << 0 +/** + * XML_SCHEMAS_ATTR_NSDEFAULT: + * + * allow elements in no namespace + */ +#define XML_SCHEMAS_ATTR_NSDEFAULT 1 << 7 +/** + * XML_SCHEMAS_ATTR_INTERNAL_RESOLVED: + * + * this is set when the "type" and "ref" references + * have been resolved. + */ +#define XML_SCHEMAS_ATTR_INTERNAL_RESOLVED 1 << 8 +/** + * XML_SCHEMAS_ATTR_FIXED: + * + * the attribute has a fixed value + */ +#define XML_SCHEMAS_ATTR_FIXED 1 << 9 + +/** + * xmlSchemaAttribute: + * An attribute definition. + */ + +typedef struct _xmlSchemaAttribute xmlSchemaAttribute; +typedef xmlSchemaAttribute *xmlSchemaAttributePtr; +struct _xmlSchemaAttribute { + xmlSchemaTypeType type; + struct _xmlSchemaAttribute *next; /* the next attribute (not used?) */ + const xmlChar *name; /* the name of the declaration */ + const xmlChar *id; /* Deprecated; not used */ + const xmlChar *ref; /* Deprecated; not used */ + const xmlChar *refNs; /* Deprecated; not used */ + const xmlChar *typeName; /* the local name of the type definition */ + const xmlChar *typeNs; /* the ns URI of the type definition */ + xmlSchemaAnnotPtr annot; + + xmlSchemaTypePtr base; /* Deprecated; not used */ + int occurs; /* Deprecated; not used */ + const xmlChar *defValue; /* The initial value of the value constraint */ + xmlSchemaTypePtr subtypes; /* the type definition */ + xmlNodePtr node; + const xmlChar *targetNamespace; + int flags; + const xmlChar *refPrefix; /* Deprecated; not used */ + xmlSchemaValPtr defVal; /* The compiled value constraint */ + xmlSchemaAttributePtr refDecl; /* Deprecated; not used */ +}; + +/** + * xmlSchemaAttributeLink: + * Used to build a list of attribute uses on complexType definitions. + * WARNING: Deprecated; not used. + */ +typedef struct _xmlSchemaAttributeLink xmlSchemaAttributeLink; +typedef xmlSchemaAttributeLink *xmlSchemaAttributeLinkPtr; +struct _xmlSchemaAttributeLink { + struct _xmlSchemaAttributeLink *next;/* the next attribute link ... */ + struct _xmlSchemaAttribute *attr;/* the linked attribute */ +}; + +/** + * XML_SCHEMAS_WILDCARD_COMPLETE: + * + * If the wildcard is complete. + */ +#define XML_SCHEMAS_WILDCARD_COMPLETE 1 << 0 + +/** + * xmlSchemaCharValueLink: + * Used to build a list of namespaces on wildcards. + */ +typedef struct _xmlSchemaWildcardNs xmlSchemaWildcardNs; +typedef xmlSchemaWildcardNs *xmlSchemaWildcardNsPtr; +struct _xmlSchemaWildcardNs { + struct _xmlSchemaWildcardNs *next;/* the next constraint link ... */ + const xmlChar *value;/* the value */ +}; + +/** + * xmlSchemaWildcard. + * A wildcard. + */ +typedef struct _xmlSchemaWildcard xmlSchemaWildcard; +typedef xmlSchemaWildcard *xmlSchemaWildcardPtr; +struct _xmlSchemaWildcard { + xmlSchemaTypeType type; /* The kind of type */ + const xmlChar *id; /* Deprecated; not used */ + xmlSchemaAnnotPtr annot; + xmlNodePtr node; + int minOccurs; /* Deprecated; not used */ + int maxOccurs; /* Deprecated; not used */ + int processContents; + int any; /* Indicates if the ns constraint is of ##any */ + xmlSchemaWildcardNsPtr nsSet; /* The list of allowed namespaces */ + xmlSchemaWildcardNsPtr negNsSet; /* The negated namespace */ + int flags; +}; + +/** + * XML_SCHEMAS_ATTRGROUP_WILDCARD_BUILDED: + * + * The attribute wildcard has been built. + */ +#define XML_SCHEMAS_ATTRGROUP_WILDCARD_BUILDED 1 << 0 +/** + * XML_SCHEMAS_ATTRGROUP_GLOBAL: + * + * The attribute group has been defined. + */ +#define XML_SCHEMAS_ATTRGROUP_GLOBAL 1 << 1 +/** + * XML_SCHEMAS_ATTRGROUP_MARKED: + * + * Marks the attr group as marked; used for circular checks. + */ +#define XML_SCHEMAS_ATTRGROUP_MARKED 1 << 2 + +/** + * XML_SCHEMAS_ATTRGROUP_REDEFINED: + * + * The attr group was redefined. + */ +#define XML_SCHEMAS_ATTRGROUP_REDEFINED 1 << 3 +/** + * XML_SCHEMAS_ATTRGROUP_HAS_REFS: + * + * Whether this attr. group contains attr. group references. + */ +#define XML_SCHEMAS_ATTRGROUP_HAS_REFS 1 << 4 + +/** + * An attribute group definition. + * + * xmlSchemaAttribute and xmlSchemaAttributeGroup start of structures + * must be kept similar + */ +typedef struct _xmlSchemaAttributeGroup xmlSchemaAttributeGroup; +typedef xmlSchemaAttributeGroup *xmlSchemaAttributeGroupPtr; +struct _xmlSchemaAttributeGroup { + xmlSchemaTypeType type; /* The kind of type */ + struct _xmlSchemaAttribute *next;/* the next attribute if in a group ... */ + const xmlChar *name; + const xmlChar *id; + const xmlChar *ref; /* Deprecated; not used */ + const xmlChar *refNs; /* Deprecated; not used */ + xmlSchemaAnnotPtr annot; + + xmlSchemaAttributePtr attributes; /* Deprecated; not used */ + xmlNodePtr node; + int flags; + xmlSchemaWildcardPtr attributeWildcard; + const xmlChar *refPrefix; /* Deprecated; not used */ + xmlSchemaAttributeGroupPtr refItem; /* Deprecated; not used */ + const xmlChar *targetNamespace; + void *attrUses; +}; + +/** + * xmlSchemaTypeLink: + * Used to build a list of types (e.g. member types of + * simpleType with variety "union"). + */ +typedef struct _xmlSchemaTypeLink xmlSchemaTypeLink; +typedef xmlSchemaTypeLink *xmlSchemaTypeLinkPtr; +struct _xmlSchemaTypeLink { + struct _xmlSchemaTypeLink *next;/* the next type link ... */ + xmlSchemaTypePtr type;/* the linked type */ +}; + +/** + * xmlSchemaFacetLink: + * Used to build a list of facets. + */ +typedef struct _xmlSchemaFacetLink xmlSchemaFacetLink; +typedef xmlSchemaFacetLink *xmlSchemaFacetLinkPtr; +struct _xmlSchemaFacetLink { + struct _xmlSchemaFacetLink *next;/* the next facet link ... */ + xmlSchemaFacetPtr facet;/* the linked facet */ +}; + +/** + * XML_SCHEMAS_TYPE_MIXED: + * + * the element content type is mixed + */ +#define XML_SCHEMAS_TYPE_MIXED 1 << 0 +/** + * XML_SCHEMAS_TYPE_DERIVATION_METHOD_EXTENSION: + * + * the simple or complex type has a derivation method of "extension". + */ +#define XML_SCHEMAS_TYPE_DERIVATION_METHOD_EXTENSION 1 << 1 +/** + * XML_SCHEMAS_TYPE_DERIVATION_METHOD_RESTRICTION: + * + * the simple or complex type has a derivation method of "restriction". + */ +#define XML_SCHEMAS_TYPE_DERIVATION_METHOD_RESTRICTION 1 << 2 +/** + * XML_SCHEMAS_TYPE_GLOBAL: + * + * the type is global + */ +#define XML_SCHEMAS_TYPE_GLOBAL 1 << 3 +/** + * XML_SCHEMAS_TYPE_OWNED_ATTR_WILDCARD: + * + * the complexType owns an attribute wildcard, i.e. + * it can be freed by the complexType + */ +#define XML_SCHEMAS_TYPE_OWNED_ATTR_WILDCARD 1 << 4 /* Obsolete. */ +/** + * XML_SCHEMAS_TYPE_VARIETY_ABSENT: + * + * the simpleType has a variety of "absent". + * TODO: Actually not necessary :-/, since if + * none of the variety flags occur then it's + * automatically absent. + */ +#define XML_SCHEMAS_TYPE_VARIETY_ABSENT 1 << 5 +/** + * XML_SCHEMAS_TYPE_VARIETY_LIST: + * + * the simpleType has a variety of "list". + */ +#define XML_SCHEMAS_TYPE_VARIETY_LIST 1 << 6 +/** + * XML_SCHEMAS_TYPE_VARIETY_UNION: + * + * the simpleType has a variety of "union". + */ +#define XML_SCHEMAS_TYPE_VARIETY_UNION 1 << 7 +/** + * XML_SCHEMAS_TYPE_VARIETY_ATOMIC: + * + * the simpleType has a variety of "union". + */ +#define XML_SCHEMAS_TYPE_VARIETY_ATOMIC 1 << 8 +/** + * XML_SCHEMAS_TYPE_FINAL_EXTENSION: + * + * the complexType has a final of "extension". + */ +#define XML_SCHEMAS_TYPE_FINAL_EXTENSION 1 << 9 +/** + * XML_SCHEMAS_TYPE_FINAL_RESTRICTION: + * + * the simpleType/complexType has a final of "restriction". + */ +#define XML_SCHEMAS_TYPE_FINAL_RESTRICTION 1 << 10 +/** + * XML_SCHEMAS_TYPE_FINAL_LIST: + * + * the simpleType has a final of "list". + */ +#define XML_SCHEMAS_TYPE_FINAL_LIST 1 << 11 +/** + * XML_SCHEMAS_TYPE_FINAL_UNION: + * + * the simpleType has a final of "union". + */ +#define XML_SCHEMAS_TYPE_FINAL_UNION 1 << 12 +/** + * XML_SCHEMAS_TYPE_FINAL_DEFAULT: + * + * the simpleType has a final of "default". + */ +#define XML_SCHEMAS_TYPE_FINAL_DEFAULT 1 << 13 +/** + * XML_SCHEMAS_TYPE_BUILTIN_PRIMITIVE: + * + * Marks the item as a builtin primitive. + */ +#define XML_SCHEMAS_TYPE_BUILTIN_PRIMITIVE 1 << 14 +/** + * XML_SCHEMAS_TYPE_MARKED: + * + * Marks the item as marked; used for circular checks. + */ +#define XML_SCHEMAS_TYPE_MARKED 1 << 16 +/** + * XML_SCHEMAS_TYPE_BLOCK_DEFAULT: + * + * the complexType did not specify 'block' so use the default of the + * item. + */ +#define XML_SCHEMAS_TYPE_BLOCK_DEFAULT 1 << 17 +/** + * XML_SCHEMAS_TYPE_BLOCK_EXTENSION: + * + * the complexType has a 'block' of "extension". + */ +#define XML_SCHEMAS_TYPE_BLOCK_EXTENSION 1 << 18 +/** + * XML_SCHEMAS_TYPE_BLOCK_RESTRICTION: + * + * the complexType has a 'block' of "restriction". + */ +#define XML_SCHEMAS_TYPE_BLOCK_RESTRICTION 1 << 19 +/** + * XML_SCHEMAS_TYPE_ABSTRACT: + * + * the simple/complexType is abstract. + */ +#define XML_SCHEMAS_TYPE_ABSTRACT 1 << 20 +/** + * XML_SCHEMAS_TYPE_FACETSNEEDVALUE: + * + * indicates if the facets need a computed value + */ +#define XML_SCHEMAS_TYPE_FACETSNEEDVALUE 1 << 21 +/** + * XML_SCHEMAS_TYPE_INTERNAL_RESOLVED: + * + * indicates that the type was typefixed + */ +#define XML_SCHEMAS_TYPE_INTERNAL_RESOLVED 1 << 22 +/** + * XML_SCHEMAS_TYPE_INTERNAL_INVALID: + * + * indicates that the type is invalid + */ +#define XML_SCHEMAS_TYPE_INTERNAL_INVALID 1 << 23 +/** + * XML_SCHEMAS_TYPE_WHITESPACE_PRESERVE: + * + * a whitespace-facet value of "preserve" + */ +#define XML_SCHEMAS_TYPE_WHITESPACE_PRESERVE 1 << 24 +/** + * XML_SCHEMAS_TYPE_WHITESPACE_REPLACE: + * + * a whitespace-facet value of "replace" + */ +#define XML_SCHEMAS_TYPE_WHITESPACE_REPLACE 1 << 25 +/** + * XML_SCHEMAS_TYPE_WHITESPACE_COLLAPSE: + * + * a whitespace-facet value of "collapse" + */ +#define XML_SCHEMAS_TYPE_WHITESPACE_COLLAPSE 1 << 26 +/** + * XML_SCHEMAS_TYPE_HAS_FACETS: + * + * has facets + */ +#define XML_SCHEMAS_TYPE_HAS_FACETS 1 << 27 +/** + * XML_SCHEMAS_TYPE_NORMVALUENEEDED: + * + * indicates if the facets (pattern) need a normalized value + */ +#define XML_SCHEMAS_TYPE_NORMVALUENEEDED 1 << 28 + +/** + * XML_SCHEMAS_TYPE_FIXUP_1: + * + * First stage of fixup was done. + */ +#define XML_SCHEMAS_TYPE_FIXUP_1 1 << 29 + +/** + * XML_SCHEMAS_TYPE_REDEFINED: + * + * The type was redefined. + */ +#define XML_SCHEMAS_TYPE_REDEFINED 1 << 30 +/** + * XML_SCHEMAS_TYPE_REDEFINING: + * + * The type redefines an other type. + */ +/* #define XML_SCHEMAS_TYPE_REDEFINING 1 << 31 */ + +/** + * _xmlSchemaType: + * + * Schemas type definition. + */ +struct _xmlSchemaType { + xmlSchemaTypeType type; /* The kind of type */ + struct _xmlSchemaType *next; /* the next type if in a sequence ... */ + const xmlChar *name; + const xmlChar *id ; /* Deprecated; not used */ + const xmlChar *ref; /* Deprecated; not used */ + const xmlChar *refNs; /* Deprecated; not used */ + xmlSchemaAnnotPtr annot; + xmlSchemaTypePtr subtypes; + xmlSchemaAttributePtr attributes; /* Deprecated; not used */ + xmlNodePtr node; + int minOccurs; /* Deprecated; not used */ + int maxOccurs; /* Deprecated; not used */ + + int flags; + xmlSchemaContentType contentType; + const xmlChar *base; /* Base type's local name */ + const xmlChar *baseNs; /* Base type's target namespace */ + xmlSchemaTypePtr baseType; /* The base type component */ + xmlSchemaFacetPtr facets; /* Local facets */ + struct _xmlSchemaType *redef; /* Deprecated; not used */ + int recurse; /* Obsolete */ + xmlSchemaAttributeLinkPtr *attributeUses; /* Deprecated; not used */ + xmlSchemaWildcardPtr attributeWildcard; + int builtInType; /* Type of built-in types. */ + xmlSchemaTypeLinkPtr memberTypes; /* member-types if a union type. */ + xmlSchemaFacetLinkPtr facetSet; /* All facets (incl. inherited) */ + const xmlChar *refPrefix; /* Deprecated; not used */ + xmlSchemaTypePtr contentTypeDef; /* Used for the simple content of complex types. + Could we use @subtypes for this? */ + xmlRegexpPtr contModel; /* Holds the automaton of the content model */ + const xmlChar *targetNamespace; + void *attrUses; +}; + +/* + * xmlSchemaElement: + * An element definition. + * + * xmlSchemaType, xmlSchemaFacet and xmlSchemaElement start of + * structures must be kept similar + */ +/** + * XML_SCHEMAS_ELEM_NILLABLE: + * + * the element is nillable + */ +#define XML_SCHEMAS_ELEM_NILLABLE 1 << 0 +/** + * XML_SCHEMAS_ELEM_GLOBAL: + * + * the element is global + */ +#define XML_SCHEMAS_ELEM_GLOBAL 1 << 1 +/** + * XML_SCHEMAS_ELEM_DEFAULT: + * + * the element has a default value + */ +#define XML_SCHEMAS_ELEM_DEFAULT 1 << 2 +/** + * XML_SCHEMAS_ELEM_FIXED: + * + * the element has a fixed value + */ +#define XML_SCHEMAS_ELEM_FIXED 1 << 3 +/** + * XML_SCHEMAS_ELEM_ABSTRACT: + * + * the element is abstract + */ +#define XML_SCHEMAS_ELEM_ABSTRACT 1 << 4 +/** + * XML_SCHEMAS_ELEM_TOPLEVEL: + * + * the element is top level + * obsolete: use XML_SCHEMAS_ELEM_GLOBAL instead + */ +#define XML_SCHEMAS_ELEM_TOPLEVEL 1 << 5 +/** + * XML_SCHEMAS_ELEM_REF: + * + * the element is a reference to a type + */ +#define XML_SCHEMAS_ELEM_REF 1 << 6 +/** + * XML_SCHEMAS_ELEM_NSDEFAULT: + * + * allow elements in no namespace + * Obsolete, not used anymore. + */ +#define XML_SCHEMAS_ELEM_NSDEFAULT 1 << 7 +/** + * XML_SCHEMAS_ELEM_INTERNAL_RESOLVED: + * + * this is set when "type", "ref", "substitutionGroup" + * references have been resolved. + */ +#define XML_SCHEMAS_ELEM_INTERNAL_RESOLVED 1 << 8 + /** + * XML_SCHEMAS_ELEM_CIRCULAR: + * + * a helper flag for the search of circular references. + */ +#define XML_SCHEMAS_ELEM_CIRCULAR 1 << 9 +/** + * XML_SCHEMAS_ELEM_BLOCK_ABSENT: + * + * the "block" attribute is absent + */ +#define XML_SCHEMAS_ELEM_BLOCK_ABSENT 1 << 10 +/** + * XML_SCHEMAS_ELEM_BLOCK_EXTENSION: + * + * disallowed substitutions are absent + */ +#define XML_SCHEMAS_ELEM_BLOCK_EXTENSION 1 << 11 +/** + * XML_SCHEMAS_ELEM_BLOCK_RESTRICTION: + * + * disallowed substitutions: "restriction" + */ +#define XML_SCHEMAS_ELEM_BLOCK_RESTRICTION 1 << 12 +/** + * XML_SCHEMAS_ELEM_BLOCK_SUBSTITUTION: + * + * disallowed substitutions: "substitution" + */ +#define XML_SCHEMAS_ELEM_BLOCK_SUBSTITUTION 1 << 13 +/** + * XML_SCHEMAS_ELEM_FINAL_ABSENT: + * + * substitution group exclusions are absent + */ +#define XML_SCHEMAS_ELEM_FINAL_ABSENT 1 << 14 +/** + * XML_SCHEMAS_ELEM_FINAL_EXTENSION: + * + * substitution group exclusions: "extension" + */ +#define XML_SCHEMAS_ELEM_FINAL_EXTENSION 1 << 15 +/** + * XML_SCHEMAS_ELEM_FINAL_RESTRICTION: + * + * substitution group exclusions: "restriction" + */ +#define XML_SCHEMAS_ELEM_FINAL_RESTRICTION 1 << 16 +/** + * XML_SCHEMAS_ELEM_SUBST_GROUP_HEAD: + * + * the declaration is a substitution group head + */ +#define XML_SCHEMAS_ELEM_SUBST_GROUP_HEAD 1 << 17 +/** + * XML_SCHEMAS_ELEM_INTERNAL_CHECKED: + * + * this is set when the elem decl has been checked against + * all constraints + */ +#define XML_SCHEMAS_ELEM_INTERNAL_CHECKED 1 << 18 + +typedef struct _xmlSchemaElement xmlSchemaElement; +typedef xmlSchemaElement *xmlSchemaElementPtr; +struct _xmlSchemaElement { + xmlSchemaTypeType type; /* The kind of type */ + struct _xmlSchemaType *next; /* Not used? */ + const xmlChar *name; + const xmlChar *id; /* Deprecated; not used */ + const xmlChar *ref; /* Deprecated; not used */ + const xmlChar *refNs; /* Deprecated; not used */ + xmlSchemaAnnotPtr annot; + xmlSchemaTypePtr subtypes; /* the type definition */ + xmlSchemaAttributePtr attributes; + xmlNodePtr node; + int minOccurs; /* Deprecated; not used */ + int maxOccurs; /* Deprecated; not used */ + + int flags; + const xmlChar *targetNamespace; + const xmlChar *namedType; + const xmlChar *namedTypeNs; + const xmlChar *substGroup; + const xmlChar *substGroupNs; + const xmlChar *scope; + const xmlChar *value; /* The original value of the value constraint. */ + struct _xmlSchemaElement *refDecl; /* This will now be used for the + substitution group affiliation */ + xmlRegexpPtr contModel; /* Obsolete for WXS, maybe used for RelaxNG */ + xmlSchemaContentType contentType; + const xmlChar *refPrefix; /* Deprecated; not used */ + xmlSchemaValPtr defVal; /* The compiled value constraint. */ + void *idcs; /* The identity-constraint defs */ +}; + +/* + * XML_SCHEMAS_FACET_UNKNOWN: + * + * unknown facet handling + */ +#define XML_SCHEMAS_FACET_UNKNOWN 0 +/* + * XML_SCHEMAS_FACET_PRESERVE: + * + * preserve the type of the facet + */ +#define XML_SCHEMAS_FACET_PRESERVE 1 +/* + * XML_SCHEMAS_FACET_REPLACE: + * + * replace the type of the facet + */ +#define XML_SCHEMAS_FACET_REPLACE 2 +/* + * XML_SCHEMAS_FACET_COLLAPSE: + * + * collapse the types of the facet + */ +#define XML_SCHEMAS_FACET_COLLAPSE 3 +/** + * A facet definition. + */ +struct _xmlSchemaFacet { + xmlSchemaTypeType type; /* The kind of type */ + struct _xmlSchemaFacet *next;/* the next type if in a sequence ... */ + const xmlChar *value; /* The original value */ + const xmlChar *id; /* Obsolete */ + xmlSchemaAnnotPtr annot; + xmlNodePtr node; + int fixed; /* XML_SCHEMAS_FACET_PRESERVE, etc. */ + int whitespace; + xmlSchemaValPtr val; /* The compiled value */ + xmlRegexpPtr regexp; /* The regex for patterns */ +}; + +/** + * A notation definition. + */ +typedef struct _xmlSchemaNotation xmlSchemaNotation; +typedef xmlSchemaNotation *xmlSchemaNotationPtr; +struct _xmlSchemaNotation { + xmlSchemaTypeType type; /* The kind of type */ + const xmlChar *name; + xmlSchemaAnnotPtr annot; + const xmlChar *identifier; + const xmlChar *targetNamespace; +}; + +/* +* TODO: Actually all those flags used for the schema should sit +* on the schema parser context, since they are used only +* during parsing an XML schema document, and not available +* on the component level as per spec. +*/ +/** + * XML_SCHEMAS_QUALIF_ELEM: + * + * Reflects elementFormDefault == qualified in + * an XML schema document. + */ +#define XML_SCHEMAS_QUALIF_ELEM 1 << 0 +/** + * XML_SCHEMAS_QUALIF_ATTR: + * + * Reflects attributeFormDefault == qualified in + * an XML schema document. + */ +#define XML_SCHEMAS_QUALIF_ATTR 1 << 1 +/** + * XML_SCHEMAS_FINAL_DEFAULT_EXTENSION: + * + * the schema has "extension" in the set of finalDefault. + */ +#define XML_SCHEMAS_FINAL_DEFAULT_EXTENSION 1 << 2 +/** + * XML_SCHEMAS_FINAL_DEFAULT_RESTRICTION: + * + * the schema has "restriction" in the set of finalDefault. + */ +#define XML_SCHEMAS_FINAL_DEFAULT_RESTRICTION 1 << 3 +/** + * XML_SCHEMAS_FINAL_DEFAULT_LIST: + * + * the schema has "list" in the set of finalDefault. + */ +#define XML_SCHEMAS_FINAL_DEFAULT_LIST 1 << 4 +/** + * XML_SCHEMAS_FINAL_DEFAULT_UNION: + * + * the schema has "union" in the set of finalDefault. + */ +#define XML_SCHEMAS_FINAL_DEFAULT_UNION 1 << 5 +/** + * XML_SCHEMAS_BLOCK_DEFAULT_EXTENSION: + * + * the schema has "extension" in the set of blockDefault. + */ +#define XML_SCHEMAS_BLOCK_DEFAULT_EXTENSION 1 << 6 +/** + * XML_SCHEMAS_BLOCK_DEFAULT_RESTRICTION: + * + * the schema has "restriction" in the set of blockDefault. + */ +#define XML_SCHEMAS_BLOCK_DEFAULT_RESTRICTION 1 << 7 +/** + * XML_SCHEMAS_BLOCK_DEFAULT_SUBSTITUTION: + * + * the schema has "substitution" in the set of blockDefault. + */ +#define XML_SCHEMAS_BLOCK_DEFAULT_SUBSTITUTION 1 << 8 +/** + * XML_SCHEMAS_INCLUDING_CONVERT_NS: + * + * the schema is currently including an other schema with + * no target namespace. + */ +#define XML_SCHEMAS_INCLUDING_CONVERT_NS 1 << 9 +/** + * _xmlSchema: + * + * A Schemas definition + */ +struct _xmlSchema { + const xmlChar *name; /* schema name */ + const xmlChar *targetNamespace; /* the target namespace */ + const xmlChar *version; + const xmlChar *id; /* Obsolete */ + xmlDocPtr doc; + xmlSchemaAnnotPtr annot; + int flags; + + xmlHashTablePtr typeDecl; + xmlHashTablePtr attrDecl; + xmlHashTablePtr attrgrpDecl; + xmlHashTablePtr elemDecl; + xmlHashTablePtr notaDecl; + + xmlHashTablePtr schemasImports; + + void *_private; /* unused by the library for users or bindings */ + xmlHashTablePtr groupDecl; + xmlDictPtr dict; + void *includes; /* the includes, this is opaque for now */ + int preserve; /* whether to free the document */ + int counter; /* used to give anonymous components unique names */ + xmlHashTablePtr idcDef; /* All identity-constraint defs. */ + void *volatiles; /* Obsolete */ +}; + +XMLPUBFUN void xmlSchemaFreeType (xmlSchemaTypePtr type); +XMLPUBFUN void xmlSchemaFreeWildcard(xmlSchemaWildcardPtr wildcard); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_SCHEMAS_ENABLED */ +#endif /* __XML_SCHEMA_INTERNALS_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/schematron.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/schematron.h new file mode 100644 index 00000000..8dd8d25c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/schematron.h @@ -0,0 +1,143 @@ +/* + * Summary: XML Schematron implementation + * Description: interface to the XML Schematron validity checking. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + + +#ifndef __XML_SCHEMATRON_H__ +#define __XML_SCHEMATRON_H__ + +#include + +#ifdef LIBXML_SCHEMATRON_ENABLED + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + XML_SCHEMATRON_OUT_QUIET = 1 << 0, /* quiet no report */ + XML_SCHEMATRON_OUT_TEXT = 1 << 1, /* build a textual report */ + XML_SCHEMATRON_OUT_XML = 1 << 2, /* output SVRL */ + XML_SCHEMATRON_OUT_ERROR = 1 << 3, /* output via xmlStructuredErrorFunc */ + XML_SCHEMATRON_OUT_FILE = 1 << 8, /* output to a file descriptor */ + XML_SCHEMATRON_OUT_BUFFER = 1 << 9, /* output to a buffer */ + XML_SCHEMATRON_OUT_IO = 1 << 10 /* output to I/O mechanism */ +} xmlSchematronValidOptions; + +/** + * The schemas related types are kept internal + */ +typedef struct _xmlSchematron xmlSchematron; +typedef xmlSchematron *xmlSchematronPtr; + +/** + * xmlSchematronValidityErrorFunc: + * @ctx: the validation context + * @msg: the message + * @...: extra arguments + * + * Signature of an error callback from a Schematron validation + */ +typedef void (*xmlSchematronValidityErrorFunc) (void *ctx, const char *msg, ...); + +/** + * xmlSchematronValidityWarningFunc: + * @ctx: the validation context + * @msg: the message + * @...: extra arguments + * + * Signature of a warning callback from a Schematron validation + */ +typedef void (*xmlSchematronValidityWarningFunc) (void *ctx, const char *msg, ...); + +/** + * A schemas validation context + */ +typedef struct _xmlSchematronParserCtxt xmlSchematronParserCtxt; +typedef xmlSchematronParserCtxt *xmlSchematronParserCtxtPtr; + +typedef struct _xmlSchematronValidCtxt xmlSchematronValidCtxt; +typedef xmlSchematronValidCtxt *xmlSchematronValidCtxtPtr; + +/* + * Interfaces for parsing. + */ +XMLPUBFUN xmlSchematronParserCtxtPtr + xmlSchematronNewParserCtxt (const char *URL); +XMLPUBFUN xmlSchematronParserCtxtPtr + xmlSchematronNewMemParserCtxt(const char *buffer, + int size); +XMLPUBFUN xmlSchematronParserCtxtPtr + xmlSchematronNewDocParserCtxt(xmlDocPtr doc); +XMLPUBFUN void + xmlSchematronFreeParserCtxt (xmlSchematronParserCtxtPtr ctxt); +/***** +XMLPUBFUN void + xmlSchematronSetParserErrors(xmlSchematronParserCtxtPtr ctxt, + xmlSchematronValidityErrorFunc err, + xmlSchematronValidityWarningFunc warn, + void *ctx); +XMLPUBFUN int + xmlSchematronGetParserErrors(xmlSchematronParserCtxtPtr ctxt, + xmlSchematronValidityErrorFunc * err, + xmlSchematronValidityWarningFunc * warn, + void **ctx); +XMLPUBFUN int + xmlSchematronIsValid (xmlSchematronValidCtxtPtr ctxt); + *****/ +XMLPUBFUN xmlSchematronPtr + xmlSchematronParse (xmlSchematronParserCtxtPtr ctxt); +XMLPUBFUN void + xmlSchematronFree (xmlSchematronPtr schema); +/* + * Interfaces for validating + */ +XMLPUBFUN void + xmlSchematronSetValidStructuredErrors( + xmlSchematronValidCtxtPtr ctxt, + xmlStructuredErrorFunc serror, + void *ctx); +/****** +XMLPUBFUN void + xmlSchematronSetValidErrors (xmlSchematronValidCtxtPtr ctxt, + xmlSchematronValidityErrorFunc err, + xmlSchematronValidityWarningFunc warn, + void *ctx); +XMLPUBFUN int + xmlSchematronGetValidErrors (xmlSchematronValidCtxtPtr ctxt, + xmlSchematronValidityErrorFunc *err, + xmlSchematronValidityWarningFunc *warn, + void **ctx); +XMLPUBFUN int + xmlSchematronSetValidOptions(xmlSchematronValidCtxtPtr ctxt, + int options); +XMLPUBFUN int + xmlSchematronValidCtxtGetOptions(xmlSchematronValidCtxtPtr ctxt); +XMLPUBFUN int + xmlSchematronValidateOneElement (xmlSchematronValidCtxtPtr ctxt, + xmlNodePtr elem); + *******/ + +XMLPUBFUN xmlSchematronValidCtxtPtr + xmlSchematronNewValidCtxt (xmlSchematronPtr schema, + int options); +XMLPUBFUN void + xmlSchematronFreeValidCtxt (xmlSchematronValidCtxtPtr ctxt); +XMLPUBFUN int + xmlSchematronValidateDoc (xmlSchematronValidCtxtPtr ctxt, + xmlDocPtr instance); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_SCHEMATRON_ENABLED */ +#endif /* __XML_SCHEMATRON_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/threads.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/threads.h new file mode 100644 index 00000000..8f4b6e17 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/threads.h @@ -0,0 +1,87 @@ +/** + * Summary: interfaces for thread handling + * Description: set of generic threading related routines + * should work with pthreads, Windows native or TLS threads + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_THREADS_H__ +#define __XML_THREADS_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * xmlMutex are a simple mutual exception locks. + */ +typedef struct _xmlMutex xmlMutex; +typedef xmlMutex *xmlMutexPtr; + +/* + * xmlRMutex are reentrant mutual exception locks. + */ +typedef struct _xmlRMutex xmlRMutex; +typedef xmlRMutex *xmlRMutexPtr; + +XMLPUBFUN int + xmlCheckThreadLocalStorage(void); + +XMLPUBFUN xmlMutexPtr + xmlNewMutex (void); +XMLPUBFUN void + xmlMutexLock (xmlMutexPtr tok); +XMLPUBFUN void + xmlMutexUnlock (xmlMutexPtr tok); +XMLPUBFUN void + xmlFreeMutex (xmlMutexPtr tok); + +XMLPUBFUN xmlRMutexPtr + xmlNewRMutex (void); +XMLPUBFUN void + xmlRMutexLock (xmlRMutexPtr tok); +XMLPUBFUN void + xmlRMutexUnlock (xmlRMutexPtr tok); +XMLPUBFUN void + xmlFreeRMutex (xmlRMutexPtr tok); + +/* + * Library wide APIs. + */ +XML_DEPRECATED +XMLPUBFUN void + xmlInitThreads (void); +XMLPUBFUN void + xmlLockLibrary (void); +XMLPUBFUN void + xmlUnlockLibrary(void); +XML_DEPRECATED +XMLPUBFUN int + xmlGetThreadId (void); +XML_DEPRECATED +XMLPUBFUN int + xmlIsMainThread (void); +XML_DEPRECATED +XMLPUBFUN void + xmlCleanupThreads(void); + +/** DOC_DISABLE */ +#if defined(LIBXML_THREAD_ENABLED) && defined(_WIN32) && \ + defined(LIBXML_STATIC_FOR_DLL) +int +xmlDllMain(void *hinstDLL, unsigned long fdwReason, + void *lpvReserved); +#endif +/** DOC_ENABLE */ + +#ifdef __cplusplus +} +#endif + + +#endif /* __XML_THREADS_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/tree.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/tree.h new file mode 100644 index 00000000..4070375b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/tree.h @@ -0,0 +1,1382 @@ +/* + * Summary: interfaces for tree manipulation + * Description: this module describes the structures found in an tree resulting + * from an XML or HTML parsing, as well as the API provided for + * various processing on that tree + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef XML_TREE_INTERNALS + +/* + * Emulate circular dependency for backward compatibility + */ +#include + +#else /* XML_TREE_INTERNALS */ + +#ifndef __XML_TREE_H__ +#define __XML_TREE_H__ + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Some of the basic types pointer to structures: + */ +/* xmlIO.h */ +typedef struct _xmlParserInputBuffer xmlParserInputBuffer; +typedef xmlParserInputBuffer *xmlParserInputBufferPtr; + +typedef struct _xmlOutputBuffer xmlOutputBuffer; +typedef xmlOutputBuffer *xmlOutputBufferPtr; + +/* parser.h */ +typedef struct _xmlParserInput xmlParserInput; +typedef xmlParserInput *xmlParserInputPtr; + +typedef struct _xmlParserCtxt xmlParserCtxt; +typedef xmlParserCtxt *xmlParserCtxtPtr; + +typedef struct _xmlSAXLocator xmlSAXLocator; +typedef xmlSAXLocator *xmlSAXLocatorPtr; + +typedef struct _xmlSAXHandler xmlSAXHandler; +typedef xmlSAXHandler *xmlSAXHandlerPtr; + +/* entities.h */ +typedef struct _xmlEntity xmlEntity; +typedef xmlEntity *xmlEntityPtr; + +/** + * BASE_BUFFER_SIZE: + * + * default buffer size 4000. + */ +#define BASE_BUFFER_SIZE 4096 + +/** + * LIBXML_NAMESPACE_DICT: + * + * Defines experimental behaviour: + * 1) xmlNs gets an additional field @context (a xmlDoc) + * 2) when creating a tree, xmlNs->href is stored in the dict of xmlDoc. + */ +/* #define LIBXML_NAMESPACE_DICT */ + +/** + * xmlBufferAllocationScheme: + * + * A buffer allocation scheme can be defined to either match exactly the + * need or double it's allocated size each time it is found too small. + */ + +typedef enum { + XML_BUFFER_ALLOC_DOUBLEIT, /* double each time one need to grow */ + XML_BUFFER_ALLOC_EXACT, /* grow only to the minimal size */ + XML_BUFFER_ALLOC_IMMUTABLE, /* immutable buffer, deprecated */ + XML_BUFFER_ALLOC_IO, /* special allocation scheme used for I/O */ + XML_BUFFER_ALLOC_HYBRID, /* exact up to a threshold, and doubleit thereafter */ + XML_BUFFER_ALLOC_BOUNDED /* limit the upper size of the buffer */ +} xmlBufferAllocationScheme; + +/** + * xmlBuffer: + * + * A buffer structure, this old construct is limited to 2GB and + * is being deprecated, use API with xmlBuf instead + */ +typedef struct _xmlBuffer xmlBuffer; +typedef xmlBuffer *xmlBufferPtr; +struct _xmlBuffer { + xmlChar *content; /* The buffer content UTF8 */ + unsigned int use; /* The buffer size used */ + unsigned int size; /* The buffer size */ + xmlBufferAllocationScheme alloc; /* The realloc method */ + xmlChar *contentIO; /* in IO mode we may have a different base */ +}; + +/** + * xmlBuf: + * + * A buffer structure, new one, the actual structure internals are not public + */ + +typedef struct _xmlBuf xmlBuf; + +/** + * xmlBufPtr: + * + * A pointer to a buffer structure, the actual structure internals are not + * public + */ + +typedef xmlBuf *xmlBufPtr; + +/* + * A few public routines for xmlBuf. As those are expected to be used + * mostly internally the bulk of the routines are internal in buf.h + */ +XMLPUBFUN xmlChar* xmlBufContent (const xmlBuf* buf); +XMLPUBFUN xmlChar* xmlBufEnd (xmlBufPtr buf); +XMLPUBFUN size_t xmlBufUse (const xmlBufPtr buf); +XMLPUBFUN size_t xmlBufShrink (xmlBufPtr buf, size_t len); + +/* + * LIBXML2_NEW_BUFFER: + * + * Macro used to express that the API use the new buffers for + * xmlParserInputBuffer and xmlOutputBuffer. The change was + * introduced in 2.9.0. + */ +#define LIBXML2_NEW_BUFFER + +/** + * XML_XML_NAMESPACE: + * + * This is the namespace for the special xml: prefix predefined in the + * XML Namespace specification. + */ +#define XML_XML_NAMESPACE \ + (const xmlChar *) "http://www.w3.org/XML/1998/namespace" + +/** + * XML_XML_ID: + * + * This is the name for the special xml:id attribute + */ +#define XML_XML_ID (const xmlChar *) "xml:id" + +/* + * The different element types carried by an XML tree. + * + * NOTE: This is synchronized with DOM Level1 values + * See http://www.w3.org/TR/REC-DOM-Level-1/ + * + * Actually this had diverged a bit, and now XML_DOCUMENT_TYPE_NODE should + * be deprecated to use an XML_DTD_NODE. + */ +typedef enum { + XML_ELEMENT_NODE= 1, + XML_ATTRIBUTE_NODE= 2, + XML_TEXT_NODE= 3, + XML_CDATA_SECTION_NODE= 4, + XML_ENTITY_REF_NODE= 5, + XML_ENTITY_NODE= 6, /* unused */ + XML_PI_NODE= 7, + XML_COMMENT_NODE= 8, + XML_DOCUMENT_NODE= 9, + XML_DOCUMENT_TYPE_NODE= 10, /* unused */ + XML_DOCUMENT_FRAG_NODE= 11, + XML_NOTATION_NODE= 12, /* unused */ + XML_HTML_DOCUMENT_NODE= 13, + XML_DTD_NODE= 14, + XML_ELEMENT_DECL= 15, + XML_ATTRIBUTE_DECL= 16, + XML_ENTITY_DECL= 17, + XML_NAMESPACE_DECL= 18, + XML_XINCLUDE_START= 19, + XML_XINCLUDE_END= 20 + /* XML_DOCB_DOCUMENT_NODE= 21 */ /* removed */ +} xmlElementType; + +/** DOC_DISABLE */ +/* For backward compatibility */ +#define XML_DOCB_DOCUMENT_NODE 21 +/** DOC_ENABLE */ + +/** + * xmlNotation: + * + * A DTD Notation definition. + */ + +typedef struct _xmlNotation xmlNotation; +typedef xmlNotation *xmlNotationPtr; +struct _xmlNotation { + const xmlChar *name; /* Notation name */ + const xmlChar *PublicID; /* Public identifier, if any */ + const xmlChar *SystemID; /* System identifier, if any */ +}; + +/** + * xmlAttributeType: + * + * A DTD Attribute type definition. + */ + +typedef enum { + XML_ATTRIBUTE_CDATA = 1, + XML_ATTRIBUTE_ID, + XML_ATTRIBUTE_IDREF , + XML_ATTRIBUTE_IDREFS, + XML_ATTRIBUTE_ENTITY, + XML_ATTRIBUTE_ENTITIES, + XML_ATTRIBUTE_NMTOKEN, + XML_ATTRIBUTE_NMTOKENS, + XML_ATTRIBUTE_ENUMERATION, + XML_ATTRIBUTE_NOTATION +} xmlAttributeType; + +/** + * xmlAttributeDefault: + * + * A DTD Attribute default definition. + */ + +typedef enum { + XML_ATTRIBUTE_NONE = 1, + XML_ATTRIBUTE_REQUIRED, + XML_ATTRIBUTE_IMPLIED, + XML_ATTRIBUTE_FIXED +} xmlAttributeDefault; + +/** + * xmlEnumeration: + * + * List structure used when there is an enumeration in DTDs. + */ + +typedef struct _xmlEnumeration xmlEnumeration; +typedef xmlEnumeration *xmlEnumerationPtr; +struct _xmlEnumeration { + struct _xmlEnumeration *next; /* next one */ + const xmlChar *name; /* Enumeration name */ +}; + +/** + * xmlAttribute: + * + * An Attribute declaration in a DTD. + */ + +typedef struct _xmlAttribute xmlAttribute; +typedef xmlAttribute *xmlAttributePtr; +struct _xmlAttribute { + void *_private; /* application data */ + xmlElementType type; /* XML_ATTRIBUTE_DECL, must be second ! */ + const xmlChar *name; /* Attribute name */ + struct _xmlNode *children; /* NULL */ + struct _xmlNode *last; /* NULL */ + struct _xmlDtd *parent; /* -> DTD */ + struct _xmlNode *next; /* next sibling link */ + struct _xmlNode *prev; /* previous sibling link */ + struct _xmlDoc *doc; /* the containing document */ + + struct _xmlAttribute *nexth; /* next in hash table */ + xmlAttributeType atype; /* The attribute type */ + xmlAttributeDefault def; /* the default */ + const xmlChar *defaultValue; /* or the default value */ + xmlEnumerationPtr tree; /* or the enumeration tree if any */ + const xmlChar *prefix; /* the namespace prefix if any */ + const xmlChar *elem; /* Element holding the attribute */ +}; + +/** + * xmlElementContentType: + * + * Possible definitions of element content types. + */ +typedef enum { + XML_ELEMENT_CONTENT_PCDATA = 1, + XML_ELEMENT_CONTENT_ELEMENT, + XML_ELEMENT_CONTENT_SEQ, + XML_ELEMENT_CONTENT_OR +} xmlElementContentType; + +/** + * xmlElementContentOccur: + * + * Possible definitions of element content occurrences. + */ +typedef enum { + XML_ELEMENT_CONTENT_ONCE = 1, + XML_ELEMENT_CONTENT_OPT, + XML_ELEMENT_CONTENT_MULT, + XML_ELEMENT_CONTENT_PLUS +} xmlElementContentOccur; + +/** + * xmlElementContent: + * + * An XML Element content as stored after parsing an element definition + * in a DTD. + */ + +typedef struct _xmlElementContent xmlElementContent; +typedef xmlElementContent *xmlElementContentPtr; +struct _xmlElementContent { + xmlElementContentType type; /* PCDATA, ELEMENT, SEQ or OR */ + xmlElementContentOccur ocur; /* ONCE, OPT, MULT or PLUS */ + const xmlChar *name; /* Element name */ + struct _xmlElementContent *c1; /* first child */ + struct _xmlElementContent *c2; /* second child */ + struct _xmlElementContent *parent; /* parent */ + const xmlChar *prefix; /* Namespace prefix */ +}; + +/** + * xmlElementTypeVal: + * + * The different possibilities for an element content type. + */ + +typedef enum { + XML_ELEMENT_TYPE_UNDEFINED = 0, + XML_ELEMENT_TYPE_EMPTY = 1, + XML_ELEMENT_TYPE_ANY, + XML_ELEMENT_TYPE_MIXED, + XML_ELEMENT_TYPE_ELEMENT +} xmlElementTypeVal; + +/** + * xmlElement: + * + * An XML Element declaration from a DTD. + */ + +typedef struct _xmlElement xmlElement; +typedef xmlElement *xmlElementPtr; +struct _xmlElement { + void *_private; /* application data */ + xmlElementType type; /* XML_ELEMENT_DECL, must be second ! */ + const xmlChar *name; /* Element name */ + struct _xmlNode *children; /* NULL */ + struct _xmlNode *last; /* NULL */ + struct _xmlDtd *parent; /* -> DTD */ + struct _xmlNode *next; /* next sibling link */ + struct _xmlNode *prev; /* previous sibling link */ + struct _xmlDoc *doc; /* the containing document */ + + xmlElementTypeVal etype; /* The type */ + xmlElementContentPtr content; /* the allowed element content */ + xmlAttributePtr attributes; /* List of the declared attributes */ + const xmlChar *prefix; /* the namespace prefix if any */ +#ifdef LIBXML_REGEXP_ENABLED + xmlRegexpPtr contModel; /* the validating regexp */ +#else + void *contModel; +#endif +}; + + +/** + * XML_LOCAL_NAMESPACE: + * + * A namespace declaration node. + */ +#define XML_LOCAL_NAMESPACE XML_NAMESPACE_DECL +typedef xmlElementType xmlNsType; + +/** + * xmlNs: + * + * An XML namespace. + * Note that prefix == NULL is valid, it defines the default namespace + * within the subtree (until overridden). + * + * xmlNsType is unified with xmlElementType. + */ + +typedef struct _xmlNs xmlNs; +typedef xmlNs *xmlNsPtr; +struct _xmlNs { + struct _xmlNs *next; /* next Ns link for this node */ + xmlNsType type; /* global or local */ + const xmlChar *href; /* URL for the namespace */ + const xmlChar *prefix; /* prefix for the namespace */ + void *_private; /* application data */ + struct _xmlDoc *context; /* normally an xmlDoc */ +}; + +/** + * xmlDtd: + * + * An XML DTD, as defined by parent link */ + struct _xmlNode *next; /* next sibling link */ + struct _xmlNode *prev; /* previous sibling link */ + struct _xmlDoc *doc; /* the containing document */ + + /* End of common part */ + void *notations; /* Hash table for notations if any */ + void *elements; /* Hash table for elements if any */ + void *attributes; /* Hash table for attributes if any */ + void *entities; /* Hash table for entities if any */ + const xmlChar *ExternalID; /* External identifier for PUBLIC DTD */ + const xmlChar *SystemID; /* URI for a SYSTEM or PUBLIC DTD */ + void *pentities; /* Hash table for param entities if any */ +}; + +/** + * xmlAttr: + * + * An attribute on an XML node. + */ +typedef struct _xmlAttr xmlAttr; +typedef xmlAttr *xmlAttrPtr; +struct _xmlAttr { + void *_private; /* application data */ + xmlElementType type; /* XML_ATTRIBUTE_NODE, must be second ! */ + const xmlChar *name; /* the name of the property */ + struct _xmlNode *children; /* the value of the property */ + struct _xmlNode *last; /* NULL */ + struct _xmlNode *parent; /* child->parent link */ + struct _xmlAttr *next; /* next sibling link */ + struct _xmlAttr *prev; /* previous sibling link */ + struct _xmlDoc *doc; /* the containing document */ + xmlNs *ns; /* pointer to the associated namespace */ + xmlAttributeType atype; /* the attribute type if validating */ + void *psvi; /* for type/PSVI information */ + struct _xmlID *id; /* the ID struct */ +}; + +/** + * xmlID: + * + * An XML ID instance. + */ + +typedef struct _xmlID xmlID; +typedef xmlID *xmlIDPtr; +struct _xmlID { + struct _xmlID *next; /* next ID */ + const xmlChar *value; /* The ID name */ + xmlAttrPtr attr; /* The attribute holding it */ + const xmlChar *name; /* The attribute if attr is not available */ + int lineno; /* The line number if attr is not available */ + struct _xmlDoc *doc; /* The document holding the ID */ +}; + +/** + * xmlRef: + * + * An XML IDREF instance. + */ + +typedef struct _xmlRef xmlRef; +typedef xmlRef *xmlRefPtr; +struct _xmlRef { + struct _xmlRef *next; /* next Ref */ + const xmlChar *value; /* The Ref name */ + xmlAttrPtr attr; /* The attribute holding it */ + const xmlChar *name; /* The attribute if attr is not available */ + int lineno; /* The line number if attr is not available */ +}; + +/** + * xmlNode: + * + * A node in an XML tree. + */ +typedef struct _xmlNode xmlNode; +typedef xmlNode *xmlNodePtr; +struct _xmlNode { + void *_private; /* application data */ + xmlElementType type; /* type number, must be second ! */ + const xmlChar *name; /* the name of the node, or the entity */ + struct _xmlNode *children; /* parent->childs link */ + struct _xmlNode *last; /* last child link */ + struct _xmlNode *parent; /* child->parent link */ + struct _xmlNode *next; /* next sibling link */ + struct _xmlNode *prev; /* previous sibling link */ + struct _xmlDoc *doc; /* the containing document */ + + /* End of common part */ + xmlNs *ns; /* pointer to the associated namespace */ + xmlChar *content; /* the content */ + struct _xmlAttr *properties;/* properties list */ + xmlNs *nsDef; /* namespace definitions on this node */ + void *psvi; /* for type/PSVI information */ + unsigned short line; /* line number */ + unsigned short extra; /* extra data for XPath/XSLT */ +}; + +/** + * XML_GET_CONTENT: + * + * Macro to extract the content pointer of a node. + */ +#define XML_GET_CONTENT(n) \ + ((n)->type == XML_ELEMENT_NODE ? NULL : (n)->content) + +/** + * XML_GET_LINE: + * + * Macro to extract the line number of an element node. + */ +#define XML_GET_LINE(n) \ + (xmlGetLineNo(n)) + +/** + * xmlDocProperty + * + * Set of properties of the document as found by the parser + * Some of them are linked to similarly named xmlParserOption + */ +typedef enum { + XML_DOC_WELLFORMED = 1<<0, /* document is XML well formed */ + XML_DOC_NSVALID = 1<<1, /* document is Namespace valid */ + XML_DOC_OLD10 = 1<<2, /* parsed with old XML-1.0 parser */ + XML_DOC_DTDVALID = 1<<3, /* DTD validation was successful */ + XML_DOC_XINCLUDE = 1<<4, /* XInclude substitution was done */ + XML_DOC_USERBUILT = 1<<5, /* Document was built using the API + and not by parsing an instance */ + XML_DOC_INTERNAL = 1<<6, /* built for internal processing */ + XML_DOC_HTML = 1<<7 /* parsed or built HTML document */ +} xmlDocProperties; + +/** + * xmlDoc: + * + * An XML document. + */ +typedef struct _xmlDoc xmlDoc; +typedef xmlDoc *xmlDocPtr; +struct _xmlDoc { + void *_private; /* application data */ + xmlElementType type; /* XML_DOCUMENT_NODE, must be second ! */ + char *name; /* name/filename/URI of the document */ + struct _xmlNode *children; /* the document tree */ + struct _xmlNode *last; /* last child link */ + struct _xmlNode *parent; /* child->parent link */ + struct _xmlNode *next; /* next sibling link */ + struct _xmlNode *prev; /* previous sibling link */ + struct _xmlDoc *doc; /* autoreference to itself */ + + /* End of common part */ + int compression;/* level of zlib compression */ + int standalone; /* standalone document (no external refs) + 1 if standalone="yes" + 0 if standalone="no" + -1 if there is no XML declaration + -2 if there is an XML declaration, but no + standalone attribute was specified */ + struct _xmlDtd *intSubset; /* the document internal subset */ + struct _xmlDtd *extSubset; /* the document external subset */ + struct _xmlNs *oldNs; /* Global namespace, the old way */ + const xmlChar *version; /* the XML version string */ + const xmlChar *encoding; /* actual encoding, if any */ + void *ids; /* Hash table for ID attributes if any */ + void *refs; /* Hash table for IDREFs attributes if any */ + const xmlChar *URL; /* The URI for that document */ + int charset; /* unused */ + struct _xmlDict *dict; /* dict used to allocate names or NULL */ + void *psvi; /* for type/PSVI information */ + int parseFlags; /* set of xmlParserOption used to parse the + document */ + int properties; /* set of xmlDocProperties for this document + set at the end of parsing */ +}; + + +typedef struct _xmlDOMWrapCtxt xmlDOMWrapCtxt; +typedef xmlDOMWrapCtxt *xmlDOMWrapCtxtPtr; + +/** + * xmlDOMWrapAcquireNsFunction: + * @ctxt: a DOM wrapper context + * @node: the context node (element or attribute) + * @nsName: the requested namespace name + * @nsPrefix: the requested namespace prefix + * + * A function called to acquire namespaces (xmlNs) from the wrapper. + * + * Returns an xmlNsPtr or NULL in case of an error. + */ +typedef xmlNsPtr (*xmlDOMWrapAcquireNsFunction) (xmlDOMWrapCtxtPtr ctxt, + xmlNodePtr node, + const xmlChar *nsName, + const xmlChar *nsPrefix); + +/** + * xmlDOMWrapCtxt: + * + * Context for DOM wrapper-operations. + */ +struct _xmlDOMWrapCtxt { + void * _private; + /* + * The type of this context, just in case we need specialized + * contexts in the future. + */ + int type; + /* + * Internal namespace map used for various operations. + */ + void * namespaceMap; + /* + * Use this one to acquire an xmlNsPtr intended for node->ns. + * (Note that this is not intended for elem->nsDef). + */ + xmlDOMWrapAcquireNsFunction getNsForNodeFunc; +}; + +/** + * xmlRegisterNodeFunc: + * @node: the current node + * + * Signature for the registration callback of a created node + */ +typedef void (*xmlRegisterNodeFunc) (xmlNodePtr node); + +/** + * xmlDeregisterNodeFunc: + * @node: the current node + * + * Signature for the deregistration callback of a discarded node + */ +typedef void (*xmlDeregisterNodeFunc) (xmlNodePtr node); + +/** + * xmlChildrenNode: + * + * Macro for compatibility naming layer with libxml1. Maps + * to "children." + */ +#ifndef xmlChildrenNode +#define xmlChildrenNode children +#endif + +/** + * xmlRootNode: + * + * Macro for compatibility naming layer with libxml1. Maps + * to "children". + */ +#ifndef xmlRootNode +#define xmlRootNode children +#endif + +/* + * Variables. + */ + +/** DOC_DISABLE */ +#define XML_GLOBALS_TREE \ + XML_OP(xmlBufferAllocScheme, xmlBufferAllocationScheme, XML_DEPRECATED) \ + XML_OP(xmlDefaultBufferSize, int, XML_DEPRECATED) \ + XML_OP(xmlRegisterNodeDefaultValue, xmlRegisterNodeFunc, XML_DEPRECATED) \ + XML_OP(xmlDeregisterNodeDefaultValue, xmlDeregisterNodeFunc, \ + XML_DEPRECATED) + +#define XML_OP XML_DECLARE_GLOBAL +XML_GLOBALS_TREE +#undef XML_OP + +#if defined(LIBXML_THREAD_ENABLED) && !defined(XML_GLOBALS_NO_REDEFINITION) + #define xmlBufferAllocScheme XML_GLOBAL_MACRO(xmlBufferAllocScheme) + #define xmlDefaultBufferSize XML_GLOBAL_MACRO(xmlDefaultBufferSize) + #define xmlRegisterNodeDefaultValue \ + XML_GLOBAL_MACRO(xmlRegisterNodeDefaultValue) + #define xmlDeregisterNodeDefaultValue \ + XML_GLOBAL_MACRO(xmlDeregisterNodeDefaultValue) +#endif +/** DOC_ENABLE */ + +/* + * Some helper functions + */ +XMLPUBFUN int + xmlValidateNCName (const xmlChar *value, + int space); + +#if defined(LIBXML_TREE_ENABLED) || defined(LIBXML_SCHEMAS_ENABLED) +XMLPUBFUN int + xmlValidateQName (const xmlChar *value, + int space); +XMLPUBFUN int + xmlValidateName (const xmlChar *value, + int space); +XMLPUBFUN int + xmlValidateNMToken (const xmlChar *value, + int space); +#endif + +XMLPUBFUN xmlChar * + xmlBuildQName (const xmlChar *ncname, + const xmlChar *prefix, + xmlChar *memory, + int len); +XMLPUBFUN xmlChar * + xmlSplitQName2 (const xmlChar *name, + xmlChar **prefix); +XMLPUBFUN const xmlChar * + xmlSplitQName3 (const xmlChar *name, + int *len); + +/* + * Handling Buffers, the old ones see @xmlBuf for the new ones. + */ + +XMLPUBFUN void + xmlSetBufferAllocationScheme(xmlBufferAllocationScheme scheme); +XMLPUBFUN xmlBufferAllocationScheme + xmlGetBufferAllocationScheme(void); + +XMLPUBFUN xmlBufferPtr + xmlBufferCreate (void); +XMLPUBFUN xmlBufferPtr + xmlBufferCreateSize (size_t size); +XMLPUBFUN xmlBufferPtr + xmlBufferCreateStatic (void *mem, + size_t size); +XMLPUBFUN int + xmlBufferResize (xmlBufferPtr buf, + unsigned int size); +XMLPUBFUN void + xmlBufferFree (xmlBufferPtr buf); +XMLPUBFUN int + xmlBufferDump (FILE *file, + xmlBufferPtr buf); +XMLPUBFUN int + xmlBufferAdd (xmlBufferPtr buf, + const xmlChar *str, + int len); +XMLPUBFUN int + xmlBufferAddHead (xmlBufferPtr buf, + const xmlChar *str, + int len); +XMLPUBFUN int + xmlBufferCat (xmlBufferPtr buf, + const xmlChar *str); +XMLPUBFUN int + xmlBufferCCat (xmlBufferPtr buf, + const char *str); +XMLPUBFUN int + xmlBufferShrink (xmlBufferPtr buf, + unsigned int len); +XMLPUBFUN int + xmlBufferGrow (xmlBufferPtr buf, + unsigned int len); +XMLPUBFUN void + xmlBufferEmpty (xmlBufferPtr buf); +XMLPUBFUN const xmlChar* + xmlBufferContent (const xmlBuffer *buf); +XMLPUBFUN xmlChar* + xmlBufferDetach (xmlBufferPtr buf); +XMLPUBFUN void + xmlBufferSetAllocationScheme(xmlBufferPtr buf, + xmlBufferAllocationScheme scheme); +XMLPUBFUN int + xmlBufferLength (const xmlBuffer *buf); + +/* + * Creating/freeing new structures. + */ +XMLPUBFUN xmlDtdPtr + xmlCreateIntSubset (xmlDocPtr doc, + const xmlChar *name, + const xmlChar *ExternalID, + const xmlChar *SystemID); +XMLPUBFUN xmlDtdPtr + xmlNewDtd (xmlDocPtr doc, + const xmlChar *name, + const xmlChar *ExternalID, + const xmlChar *SystemID); +XMLPUBFUN xmlDtdPtr + xmlGetIntSubset (const xmlDoc *doc); +XMLPUBFUN void + xmlFreeDtd (xmlDtdPtr cur); +#ifdef LIBXML_LEGACY_ENABLED +XML_DEPRECATED +XMLPUBFUN xmlNsPtr + xmlNewGlobalNs (xmlDocPtr doc, + const xmlChar *href, + const xmlChar *prefix); +#endif /* LIBXML_LEGACY_ENABLED */ +XMLPUBFUN xmlNsPtr + xmlNewNs (xmlNodePtr node, + const xmlChar *href, + const xmlChar *prefix); +XMLPUBFUN void + xmlFreeNs (xmlNsPtr cur); +XMLPUBFUN void + xmlFreeNsList (xmlNsPtr cur); +XMLPUBFUN xmlDocPtr + xmlNewDoc (const xmlChar *version); +XMLPUBFUN void + xmlFreeDoc (xmlDocPtr cur); +XMLPUBFUN xmlAttrPtr + xmlNewDocProp (xmlDocPtr doc, + const xmlChar *name, + const xmlChar *value); +#if defined(LIBXML_TREE_ENABLED) || defined(LIBXML_HTML_ENABLED) || \ + defined(LIBXML_SCHEMAS_ENABLED) +XMLPUBFUN xmlAttrPtr + xmlNewProp (xmlNodePtr node, + const xmlChar *name, + const xmlChar *value); +#endif +XMLPUBFUN xmlAttrPtr + xmlNewNsProp (xmlNodePtr node, + xmlNsPtr ns, + const xmlChar *name, + const xmlChar *value); +XMLPUBFUN xmlAttrPtr + xmlNewNsPropEatName (xmlNodePtr node, + xmlNsPtr ns, + xmlChar *name, + const xmlChar *value); +XMLPUBFUN void + xmlFreePropList (xmlAttrPtr cur); +XMLPUBFUN void + xmlFreeProp (xmlAttrPtr cur); +XMLPUBFUN xmlAttrPtr + xmlCopyProp (xmlNodePtr target, + xmlAttrPtr cur); +XMLPUBFUN xmlAttrPtr + xmlCopyPropList (xmlNodePtr target, + xmlAttrPtr cur); +#ifdef LIBXML_TREE_ENABLED +XMLPUBFUN xmlDtdPtr + xmlCopyDtd (xmlDtdPtr dtd); +#endif /* LIBXML_TREE_ENABLED */ +#if defined(LIBXML_TREE_ENABLED) || defined(LIBXML_SCHEMAS_ENABLED) +XMLPUBFUN xmlDocPtr + xmlCopyDoc (xmlDocPtr doc, + int recursive); +#endif /* defined(LIBXML_TREE_ENABLED) || defined(LIBXML_SCHEMAS_ENABLED) */ +/* + * Creating new nodes. + */ +XMLPUBFUN xmlNodePtr + xmlNewDocNode (xmlDocPtr doc, + xmlNsPtr ns, + const xmlChar *name, + const xmlChar *content); +XMLPUBFUN xmlNodePtr + xmlNewDocNodeEatName (xmlDocPtr doc, + xmlNsPtr ns, + xmlChar *name, + const xmlChar *content); +XMLPUBFUN xmlNodePtr + xmlNewNode (xmlNsPtr ns, + const xmlChar *name); +XMLPUBFUN xmlNodePtr + xmlNewNodeEatName (xmlNsPtr ns, + xmlChar *name); +#if defined(LIBXML_TREE_ENABLED) || defined(LIBXML_SCHEMAS_ENABLED) +XMLPUBFUN xmlNodePtr + xmlNewChild (xmlNodePtr parent, + xmlNsPtr ns, + const xmlChar *name, + const xmlChar *content); +#endif +XMLPUBFUN xmlNodePtr + xmlNewDocText (const xmlDoc *doc, + const xmlChar *content); +XMLPUBFUN xmlNodePtr + xmlNewText (const xmlChar *content); +XMLPUBFUN xmlNodePtr + xmlNewDocPI (xmlDocPtr doc, + const xmlChar *name, + const xmlChar *content); +XMLPUBFUN xmlNodePtr + xmlNewPI (const xmlChar *name, + const xmlChar *content); +XMLPUBFUN xmlNodePtr + xmlNewDocTextLen (xmlDocPtr doc, + const xmlChar *content, + int len); +XMLPUBFUN xmlNodePtr + xmlNewTextLen (const xmlChar *content, + int len); +XMLPUBFUN xmlNodePtr + xmlNewDocComment (xmlDocPtr doc, + const xmlChar *content); +XMLPUBFUN xmlNodePtr + xmlNewComment (const xmlChar *content); +XMLPUBFUN xmlNodePtr + xmlNewCDataBlock (xmlDocPtr doc, + const xmlChar *content, + int len); +XMLPUBFUN xmlNodePtr + xmlNewCharRef (xmlDocPtr doc, + const xmlChar *name); +XMLPUBFUN xmlNodePtr + xmlNewReference (const xmlDoc *doc, + const xmlChar *name); +XMLPUBFUN xmlNodePtr + xmlCopyNode (xmlNodePtr node, + int recursive); +XMLPUBFUN xmlNodePtr + xmlDocCopyNode (xmlNodePtr node, + xmlDocPtr doc, + int recursive); +XMLPUBFUN xmlNodePtr + xmlDocCopyNodeList (xmlDocPtr doc, + xmlNodePtr node); +XMLPUBFUN xmlNodePtr + xmlCopyNodeList (xmlNodePtr node); +#ifdef LIBXML_TREE_ENABLED +XMLPUBFUN xmlNodePtr + xmlNewTextChild (xmlNodePtr parent, + xmlNsPtr ns, + const xmlChar *name, + const xmlChar *content); +XMLPUBFUN xmlNodePtr + xmlNewDocRawNode (xmlDocPtr doc, + xmlNsPtr ns, + const xmlChar *name, + const xmlChar *content); +XMLPUBFUN xmlNodePtr + xmlNewDocFragment (xmlDocPtr doc); +#endif /* LIBXML_TREE_ENABLED */ + +/* + * Navigating. + */ +XMLPUBFUN long + xmlGetLineNo (const xmlNode *node); +#if defined(LIBXML_TREE_ENABLED) || defined(LIBXML_DEBUG_ENABLED) +XMLPUBFUN xmlChar * + xmlGetNodePath (const xmlNode *node); +#endif /* defined(LIBXML_TREE_ENABLED) || defined(LIBXML_DEBUG_ENABLED) */ +XMLPUBFUN xmlNodePtr + xmlDocGetRootElement (const xmlDoc *doc); +XMLPUBFUN xmlNodePtr + xmlGetLastChild (const xmlNode *parent); +XMLPUBFUN int + xmlNodeIsText (const xmlNode *node); +XMLPUBFUN int + xmlIsBlankNode (const xmlNode *node); + +/* + * Changing the structure. + */ +#if defined(LIBXML_TREE_ENABLED) || defined(LIBXML_WRITER_ENABLED) +XMLPUBFUN xmlNodePtr + xmlDocSetRootElement (xmlDocPtr doc, + xmlNodePtr root); +#endif /* defined(LIBXML_TREE_ENABLED) || defined(LIBXML_WRITER_ENABLED) */ +#ifdef LIBXML_TREE_ENABLED +XMLPUBFUN void + xmlNodeSetName (xmlNodePtr cur, + const xmlChar *name); +#endif /* LIBXML_TREE_ENABLED */ +XMLPUBFUN xmlNodePtr + xmlAddChild (xmlNodePtr parent, + xmlNodePtr cur); +XMLPUBFUN xmlNodePtr + xmlAddChildList (xmlNodePtr parent, + xmlNodePtr cur); +#if defined(LIBXML_TREE_ENABLED) || defined(LIBXML_WRITER_ENABLED) +XMLPUBFUN xmlNodePtr + xmlReplaceNode (xmlNodePtr old, + xmlNodePtr cur); +#endif /* defined(LIBXML_TREE_ENABLED) || defined(LIBXML_WRITER_ENABLED) */ +#if defined(LIBXML_TREE_ENABLED) || defined(LIBXML_HTML_ENABLED) || \ + defined(LIBXML_SCHEMAS_ENABLED) || defined(LIBXML_XINCLUDE_ENABLED) +XMLPUBFUN xmlNodePtr + xmlAddPrevSibling (xmlNodePtr cur, + xmlNodePtr elem); +#endif /* LIBXML_TREE_ENABLED || LIBXML_HTML_ENABLED || LIBXML_SCHEMAS_ENABLED */ +XMLPUBFUN xmlNodePtr + xmlAddSibling (xmlNodePtr cur, + xmlNodePtr elem); +XMLPUBFUN xmlNodePtr + xmlAddNextSibling (xmlNodePtr cur, + xmlNodePtr elem); +XMLPUBFUN void + xmlUnlinkNode (xmlNodePtr cur); +XMLPUBFUN xmlNodePtr + xmlTextMerge (xmlNodePtr first, + xmlNodePtr second); +XMLPUBFUN int + xmlTextConcat (xmlNodePtr node, + const xmlChar *content, + int len); +XMLPUBFUN void + xmlFreeNodeList (xmlNodePtr cur); +XMLPUBFUN void + xmlFreeNode (xmlNodePtr cur); +XMLPUBFUN int + xmlSetTreeDoc (xmlNodePtr tree, + xmlDocPtr doc); +XMLPUBFUN int + xmlSetListDoc (xmlNodePtr list, + xmlDocPtr doc); +/* + * Namespaces. + */ +XMLPUBFUN xmlNsPtr + xmlSearchNs (xmlDocPtr doc, + xmlNodePtr node, + const xmlChar *nameSpace); +XMLPUBFUN xmlNsPtr + xmlSearchNsByHref (xmlDocPtr doc, + xmlNodePtr node, + const xmlChar *href); +#if defined(LIBXML_TREE_ENABLED) || defined(LIBXML_XPATH_ENABLED) || \ + defined(LIBXML_SCHEMAS_ENABLED) +XMLPUBFUN int + xmlGetNsListSafe (const xmlDoc *doc, + const xmlNode *node, + xmlNsPtr **out); +XMLPUBFUN xmlNsPtr * + xmlGetNsList (const xmlDoc *doc, + const xmlNode *node); +#endif /* defined(LIBXML_TREE_ENABLED) || defined(LIBXML_XPATH_ENABLED) */ + +XMLPUBFUN void + xmlSetNs (xmlNodePtr node, + xmlNsPtr ns); +XMLPUBFUN xmlNsPtr + xmlCopyNamespace (xmlNsPtr cur); +XMLPUBFUN xmlNsPtr + xmlCopyNamespaceList (xmlNsPtr cur); + +/* + * Changing the content. + */ +#if defined(LIBXML_TREE_ENABLED) || defined(LIBXML_XINCLUDE_ENABLED) || \ + defined(LIBXML_SCHEMAS_ENABLED) || defined(LIBXML_HTML_ENABLED) +XMLPUBFUN xmlAttrPtr + xmlSetProp (xmlNodePtr node, + const xmlChar *name, + const xmlChar *value); +XMLPUBFUN xmlAttrPtr + xmlSetNsProp (xmlNodePtr node, + xmlNsPtr ns, + const xmlChar *name, + const xmlChar *value); +#endif /* defined(LIBXML_TREE_ENABLED) || defined(LIBXML_XINCLUDE_ENABLED) || \ + defined(LIBXML_SCHEMAS_ENABLED) || defined(LIBXML_HTML_ENABLED) */ +XMLPUBFUN int + xmlNodeGetAttrValue (const xmlNode *node, + const xmlChar *name, + const xmlChar *nsUri, + xmlChar **out); +XMLPUBFUN xmlChar * + xmlGetNoNsProp (const xmlNode *node, + const xmlChar *name); +XMLPUBFUN xmlChar * + xmlGetProp (const xmlNode *node, + const xmlChar *name); +XMLPUBFUN xmlAttrPtr + xmlHasProp (const xmlNode *node, + const xmlChar *name); +XMLPUBFUN xmlAttrPtr + xmlHasNsProp (const xmlNode *node, + const xmlChar *name, + const xmlChar *nameSpace); +XMLPUBFUN xmlChar * + xmlGetNsProp (const xmlNode *node, + const xmlChar *name, + const xmlChar *nameSpace); +XMLPUBFUN xmlNodePtr + xmlStringGetNodeList (const xmlDoc *doc, + const xmlChar *value); +XMLPUBFUN xmlNodePtr + xmlStringLenGetNodeList (const xmlDoc *doc, + const xmlChar *value, + int len); +XMLPUBFUN xmlChar * + xmlNodeListGetString (xmlDocPtr doc, + const xmlNode *list, + int inLine); +#ifdef LIBXML_TREE_ENABLED +XMLPUBFUN xmlChar * + xmlNodeListGetRawString (const xmlDoc *doc, + const xmlNode *list, + int inLine); +#endif /* LIBXML_TREE_ENABLED */ +XMLPUBFUN int + xmlNodeSetContent (xmlNodePtr cur, + const xmlChar *content); +#ifdef LIBXML_TREE_ENABLED +XMLPUBFUN int + xmlNodeSetContentLen (xmlNodePtr cur, + const xmlChar *content, + int len); +#endif /* LIBXML_TREE_ENABLED */ +XMLPUBFUN int + xmlNodeAddContent (xmlNodePtr cur, + const xmlChar *content); +XMLPUBFUN int + xmlNodeAddContentLen (xmlNodePtr cur, + const xmlChar *content, + int len); +XMLPUBFUN xmlChar * + xmlNodeGetContent (const xmlNode *cur); + +XMLPUBFUN int + xmlNodeBufGetContent (xmlBufferPtr buffer, + const xmlNode *cur); +XMLPUBFUN int + xmlBufGetNodeContent (xmlBufPtr buf, + const xmlNode *cur); + +XMLPUBFUN xmlChar * + xmlNodeGetLang (const xmlNode *cur); +XMLPUBFUN int + xmlNodeGetSpacePreserve (const xmlNode *cur); +#ifdef LIBXML_TREE_ENABLED +XMLPUBFUN int + xmlNodeSetLang (xmlNodePtr cur, + const xmlChar *lang); +XMLPUBFUN int + xmlNodeSetSpacePreserve (xmlNodePtr cur, + int val); +#endif /* LIBXML_TREE_ENABLED */ +XMLPUBFUN int + xmlNodeGetBaseSafe (const xmlDoc *doc, + const xmlNode *cur, + xmlChar **baseOut); +XMLPUBFUN xmlChar * + xmlNodeGetBase (const xmlDoc *doc, + const xmlNode *cur); +#if defined(LIBXML_TREE_ENABLED) || defined(LIBXML_XINCLUDE_ENABLED) +XMLPUBFUN int + xmlNodeSetBase (xmlNodePtr cur, + const xmlChar *uri); +#endif + +/* + * Removing content. + */ +XMLPUBFUN int + xmlRemoveProp (xmlAttrPtr cur); +#if defined(LIBXML_TREE_ENABLED) || defined(LIBXML_SCHEMAS_ENABLED) +XMLPUBFUN int + xmlUnsetNsProp (xmlNodePtr node, + xmlNsPtr ns, + const xmlChar *name); +XMLPUBFUN int + xmlUnsetProp (xmlNodePtr node, + const xmlChar *name); +#endif /* defined(LIBXML_TREE_ENABLED) || defined(LIBXML_SCHEMAS_ENABLED) */ + +/* + * Internal, don't use. + */ +XMLPUBFUN void + xmlBufferWriteCHAR (xmlBufferPtr buf, + const xmlChar *string); +XMLPUBFUN void + xmlBufferWriteChar (xmlBufferPtr buf, + const char *string); +XMLPUBFUN void + xmlBufferWriteQuotedString(xmlBufferPtr buf, + const xmlChar *string); + +#ifdef LIBXML_OUTPUT_ENABLED +XMLPUBFUN void xmlAttrSerializeTxtContent(xmlBufferPtr buf, + xmlDocPtr doc, + xmlAttrPtr attr, + const xmlChar *string); +#endif /* LIBXML_OUTPUT_ENABLED */ + +#ifdef LIBXML_TREE_ENABLED +/* + * Namespace handling. + */ +XMLPUBFUN int + xmlReconciliateNs (xmlDocPtr doc, + xmlNodePtr tree); +#endif + +#ifdef LIBXML_OUTPUT_ENABLED +/* + * Saving. + */ +XMLPUBFUN void + xmlDocDumpFormatMemory (xmlDocPtr cur, + xmlChar **mem, + int *size, + int format); +XMLPUBFUN void + xmlDocDumpMemory (xmlDocPtr cur, + xmlChar **mem, + int *size); +XMLPUBFUN void + xmlDocDumpMemoryEnc (xmlDocPtr out_doc, + xmlChar **doc_txt_ptr, + int * doc_txt_len, + const char *txt_encoding); +XMLPUBFUN void + xmlDocDumpFormatMemoryEnc(xmlDocPtr out_doc, + xmlChar **doc_txt_ptr, + int * doc_txt_len, + const char *txt_encoding, + int format); +XMLPUBFUN int + xmlDocFormatDump (FILE *f, + xmlDocPtr cur, + int format); +XMLPUBFUN int + xmlDocDump (FILE *f, + xmlDocPtr cur); +XMLPUBFUN void + xmlElemDump (FILE *f, + xmlDocPtr doc, + xmlNodePtr cur); +XMLPUBFUN int + xmlSaveFile (const char *filename, + xmlDocPtr cur); +XMLPUBFUN int + xmlSaveFormatFile (const char *filename, + xmlDocPtr cur, + int format); +XMLPUBFUN size_t + xmlBufNodeDump (xmlBufPtr buf, + xmlDocPtr doc, + xmlNodePtr cur, + int level, + int format); +XMLPUBFUN int + xmlNodeDump (xmlBufferPtr buf, + xmlDocPtr doc, + xmlNodePtr cur, + int level, + int format); + +XMLPUBFUN int + xmlSaveFileTo (xmlOutputBufferPtr buf, + xmlDocPtr cur, + const char *encoding); +XMLPUBFUN int + xmlSaveFormatFileTo (xmlOutputBufferPtr buf, + xmlDocPtr cur, + const char *encoding, + int format); +XMLPUBFUN void + xmlNodeDumpOutput (xmlOutputBufferPtr buf, + xmlDocPtr doc, + xmlNodePtr cur, + int level, + int format, + const char *encoding); + +XMLPUBFUN int + xmlSaveFormatFileEnc (const char *filename, + xmlDocPtr cur, + const char *encoding, + int format); + +XMLPUBFUN int + xmlSaveFileEnc (const char *filename, + xmlDocPtr cur, + const char *encoding); + +#endif /* LIBXML_OUTPUT_ENABLED */ +/* + * XHTML + */ +XMLPUBFUN int + xmlIsXHTML (const xmlChar *systemID, + const xmlChar *publicID); + +/* + * Compression. + */ +XMLPUBFUN int + xmlGetDocCompressMode (const xmlDoc *doc); +XMLPUBFUN void + xmlSetDocCompressMode (xmlDocPtr doc, + int mode); +XML_DEPRECATED +XMLPUBFUN int + xmlGetCompressMode (void); +XML_DEPRECATED +XMLPUBFUN void + xmlSetCompressMode (int mode); + +/* +* DOM-wrapper helper functions. +*/ +XMLPUBFUN xmlDOMWrapCtxtPtr + xmlDOMWrapNewCtxt (void); +XMLPUBFUN void + xmlDOMWrapFreeCtxt (xmlDOMWrapCtxtPtr ctxt); +XMLPUBFUN int + xmlDOMWrapReconcileNamespaces(xmlDOMWrapCtxtPtr ctxt, + xmlNodePtr elem, + int options); +XMLPUBFUN int + xmlDOMWrapAdoptNode (xmlDOMWrapCtxtPtr ctxt, + xmlDocPtr sourceDoc, + xmlNodePtr node, + xmlDocPtr destDoc, + xmlNodePtr destParent, + int options); +XMLPUBFUN int + xmlDOMWrapRemoveNode (xmlDOMWrapCtxtPtr ctxt, + xmlDocPtr doc, + xmlNodePtr node, + int options); +XMLPUBFUN int + xmlDOMWrapCloneNode (xmlDOMWrapCtxtPtr ctxt, + xmlDocPtr sourceDoc, + xmlNodePtr node, + xmlNodePtr *clonedNode, + xmlDocPtr destDoc, + xmlNodePtr destParent, + int deep, + int options); + +#ifdef LIBXML_TREE_ENABLED +/* + * 5 interfaces from DOM ElementTraversal, but different in entities + * traversal. + */ +XMLPUBFUN unsigned long + xmlChildElementCount (xmlNodePtr parent); +XMLPUBFUN xmlNodePtr + xmlNextElementSibling (xmlNodePtr node); +XMLPUBFUN xmlNodePtr + xmlFirstElementChild (xmlNodePtr parent); +XMLPUBFUN xmlNodePtr + xmlLastElementChild (xmlNodePtr parent); +XMLPUBFUN xmlNodePtr + xmlPreviousElementSibling (xmlNodePtr node); +#endif + +XML_DEPRECATED +XMLPUBFUN xmlRegisterNodeFunc + xmlRegisterNodeDefault (xmlRegisterNodeFunc func); +XML_DEPRECATED +XMLPUBFUN xmlDeregisterNodeFunc + xmlDeregisterNodeDefault (xmlDeregisterNodeFunc func); +XML_DEPRECATED +XMLPUBFUN xmlRegisterNodeFunc + xmlThrDefRegisterNodeDefault(xmlRegisterNodeFunc func); +XML_DEPRECATED +XMLPUBFUN xmlDeregisterNodeFunc + xmlThrDefDeregisterNodeDefault(xmlDeregisterNodeFunc func); + +XML_DEPRECATED XMLPUBFUN xmlBufferAllocationScheme + xmlThrDefBufferAllocScheme (xmlBufferAllocationScheme v); +XML_DEPRECATED XMLPUBFUN int + xmlThrDefDefaultBufferSize (int v); + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_TREE_H__ */ + +#endif /* XML_TREE_INTERNALS */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/uri.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/uri.h new file mode 100644 index 00000000..19980b71 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/uri.h @@ -0,0 +1,106 @@ +/** + * Summary: library of generic URI related routines + * Description: library of generic URI related routines + * Implements RFC 2396 + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_URI_H__ +#define __XML_URI_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * xmlURI: + * + * A parsed URI reference. This is a struct containing the various fields + * as described in RFC 2396 but separated for further processing. + * + * Note: query is a deprecated field which is incorrectly unescaped. + * query_raw takes precedence over query if the former is set. + * See: http://mail.gnome.org/archives/xml/2007-April/thread.html#00127 + */ +typedef struct _xmlURI xmlURI; +typedef xmlURI *xmlURIPtr; +struct _xmlURI { + char *scheme; /* the URI scheme */ + char *opaque; /* opaque part */ + char *authority; /* the authority part */ + char *server; /* the server part */ + char *user; /* the user part */ + int port; /* the port number */ + char *path; /* the path string */ + char *query; /* the query string (deprecated - use with caution) */ + char *fragment; /* the fragment identifier */ + int cleanup; /* parsing potentially unclean URI */ + char *query_raw; /* the query string (as it appears in the URI) */ +}; + +/* + * This function is in tree.h: + * xmlChar * xmlNodeGetBase (xmlDocPtr doc, + * xmlNodePtr cur); + */ +XMLPUBFUN xmlURIPtr + xmlCreateURI (void); +XMLPUBFUN int + xmlBuildURISafe (const xmlChar *URI, + const xmlChar *base, + xmlChar **out); +XMLPUBFUN xmlChar * + xmlBuildURI (const xmlChar *URI, + const xmlChar *base); +XMLPUBFUN int + xmlBuildRelativeURISafe (const xmlChar *URI, + const xmlChar *base, + xmlChar **out); +XMLPUBFUN xmlChar * + xmlBuildRelativeURI (const xmlChar *URI, + const xmlChar *base); +XMLPUBFUN xmlURIPtr + xmlParseURI (const char *str); +XMLPUBFUN int + xmlParseURISafe (const char *str, + xmlURIPtr *uri); +XMLPUBFUN xmlURIPtr + xmlParseURIRaw (const char *str, + int raw); +XMLPUBFUN int + xmlParseURIReference (xmlURIPtr uri, + const char *str); +XMLPUBFUN xmlChar * + xmlSaveUri (xmlURIPtr uri); +XMLPUBFUN void + xmlPrintURI (FILE *stream, + xmlURIPtr uri); +XMLPUBFUN xmlChar * + xmlURIEscapeStr (const xmlChar *str, + const xmlChar *list); +XMLPUBFUN char * + xmlURIUnescapeString (const char *str, + int len, + char *target); +XMLPUBFUN int + xmlNormalizeURIPath (char *path); +XMLPUBFUN xmlChar * + xmlURIEscape (const xmlChar *str); +XMLPUBFUN void + xmlFreeURI (xmlURIPtr uri); +XMLPUBFUN xmlChar* + xmlCanonicPath (const xmlChar *path); +XMLPUBFUN xmlChar* + xmlPathToURI (const xmlChar *path); + +#ifdef __cplusplus +} +#endif +#endif /* __XML_URI_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/valid.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/valid.h new file mode 100644 index 00000000..e1698d7a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/valid.h @@ -0,0 +1,477 @@ +/* + * Summary: The DTD validation + * Description: API for the DTD handling and the validity checking + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + + +#ifndef __XML_VALID_H__ +#define __XML_VALID_H__ + +/** DOC_DISABLE */ +#include +#include +#define XML_TREE_INTERNALS +#include +#undef XML_TREE_INTERNALS +#include +#include +#include +/** DOC_ENABLE */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Validation state added for non-determinist content model. + */ +typedef struct _xmlValidState xmlValidState; +typedef xmlValidState *xmlValidStatePtr; + +/** + * xmlValidityErrorFunc: + * @ctx: usually an xmlValidCtxtPtr to a validity error context, + * but comes from ctxt->userData (which normally contains such + * a pointer); ctxt->userData can be changed by the user. + * @msg: the string to format *printf like vararg + * @...: remaining arguments to the format + * + * Callback called when a validity error is found. This is a message + * oriented function similar to an *printf function. + */ +typedef void (*xmlValidityErrorFunc) (void *ctx, + const char *msg, + ...) LIBXML_ATTR_FORMAT(2,3); + +/** + * xmlValidityWarningFunc: + * @ctx: usually an xmlValidCtxtPtr to a validity error context, + * but comes from ctxt->userData (which normally contains such + * a pointer); ctxt->userData can be changed by the user. + * @msg: the string to format *printf like vararg + * @...: remaining arguments to the format + * + * Callback called when a validity warning is found. This is a message + * oriented function similar to an *printf function. + */ +typedef void (*xmlValidityWarningFunc) (void *ctx, + const char *msg, + ...) LIBXML_ATTR_FORMAT(2,3); + +/* + * xmlValidCtxt: + * An xmlValidCtxt is used for error reporting when validating. + */ +typedef struct _xmlValidCtxt xmlValidCtxt; +typedef xmlValidCtxt *xmlValidCtxtPtr; +struct _xmlValidCtxt { + void *userData; /* user specific data block */ + xmlValidityErrorFunc error; /* the callback in case of errors */ + xmlValidityWarningFunc warning; /* the callback in case of warning */ + + /* Node analysis stack used when validating within entities */ + xmlNodePtr node; /* Current parsed Node */ + int nodeNr; /* Depth of the parsing stack */ + int nodeMax; /* Max depth of the parsing stack */ + xmlNodePtr *nodeTab; /* array of nodes */ + + unsigned int flags; /* internal flags */ + xmlDocPtr doc; /* the document */ + int valid; /* temporary validity check result */ + + /* state state used for non-determinist content validation */ + xmlValidState *vstate; /* current state */ + int vstateNr; /* Depth of the validation stack */ + int vstateMax; /* Max depth of the validation stack */ + xmlValidState *vstateTab; /* array of validation states */ + +#ifdef LIBXML_REGEXP_ENABLED + xmlAutomataPtr am; /* the automata */ + xmlAutomataStatePtr state; /* used to build the automata */ +#else + void *am; + void *state; +#endif +}; + +/* + * ALL notation declarations are stored in a table. + * There is one table per DTD. + */ + +typedef struct _xmlHashTable xmlNotationTable; +typedef xmlNotationTable *xmlNotationTablePtr; + +/* + * ALL element declarations are stored in a table. + * There is one table per DTD. + */ + +typedef struct _xmlHashTable xmlElementTable; +typedef xmlElementTable *xmlElementTablePtr; + +/* + * ALL attribute declarations are stored in a table. + * There is one table per DTD. + */ + +typedef struct _xmlHashTable xmlAttributeTable; +typedef xmlAttributeTable *xmlAttributeTablePtr; + +/* + * ALL IDs attributes are stored in a table. + * There is one table per document. + */ + +typedef struct _xmlHashTable xmlIDTable; +typedef xmlIDTable *xmlIDTablePtr; + +/* + * ALL Refs attributes are stored in a table. + * There is one table per document. + */ + +typedef struct _xmlHashTable xmlRefTable; +typedef xmlRefTable *xmlRefTablePtr; + +/* Notation */ +XMLPUBFUN xmlNotationPtr + xmlAddNotationDecl (xmlValidCtxtPtr ctxt, + xmlDtdPtr dtd, + const xmlChar *name, + const xmlChar *PublicID, + const xmlChar *SystemID); +#ifdef LIBXML_TREE_ENABLED +XMLPUBFUN xmlNotationTablePtr + xmlCopyNotationTable (xmlNotationTablePtr table); +#endif /* LIBXML_TREE_ENABLED */ +XMLPUBFUN void + xmlFreeNotationTable (xmlNotationTablePtr table); +#ifdef LIBXML_OUTPUT_ENABLED +XML_DEPRECATED +XMLPUBFUN void + xmlDumpNotationDecl (xmlBufferPtr buf, + xmlNotationPtr nota); +/* XML_DEPRECATED, still used in lxml */ +XMLPUBFUN void + xmlDumpNotationTable (xmlBufferPtr buf, + xmlNotationTablePtr table); +#endif /* LIBXML_OUTPUT_ENABLED */ + +/* Element Content */ +/* the non Doc version are being deprecated */ +XMLPUBFUN xmlElementContentPtr + xmlNewElementContent (const xmlChar *name, + xmlElementContentType type); +XMLPUBFUN xmlElementContentPtr + xmlCopyElementContent (xmlElementContentPtr content); +XMLPUBFUN void + xmlFreeElementContent (xmlElementContentPtr cur); +/* the new versions with doc argument */ +XMLPUBFUN xmlElementContentPtr + xmlNewDocElementContent (xmlDocPtr doc, + const xmlChar *name, + xmlElementContentType type); +XMLPUBFUN xmlElementContentPtr + xmlCopyDocElementContent(xmlDocPtr doc, + xmlElementContentPtr content); +XMLPUBFUN void + xmlFreeDocElementContent(xmlDocPtr doc, + xmlElementContentPtr cur); +XMLPUBFUN void + xmlSnprintfElementContent(char *buf, + int size, + xmlElementContentPtr content, + int englob); +#ifdef LIBXML_OUTPUT_ENABLED +XML_DEPRECATED +XMLPUBFUN void + xmlSprintfElementContent(char *buf, + xmlElementContentPtr content, + int englob); +#endif /* LIBXML_OUTPUT_ENABLED */ + +/* Element */ +XMLPUBFUN xmlElementPtr + xmlAddElementDecl (xmlValidCtxtPtr ctxt, + xmlDtdPtr dtd, + const xmlChar *name, + xmlElementTypeVal type, + xmlElementContentPtr content); +#ifdef LIBXML_TREE_ENABLED +XMLPUBFUN xmlElementTablePtr + xmlCopyElementTable (xmlElementTablePtr table); +#endif /* LIBXML_TREE_ENABLED */ +XMLPUBFUN void + xmlFreeElementTable (xmlElementTablePtr table); +#ifdef LIBXML_OUTPUT_ENABLED +XML_DEPRECATED +XMLPUBFUN void + xmlDumpElementTable (xmlBufferPtr buf, + xmlElementTablePtr table); +XML_DEPRECATED +XMLPUBFUN void + xmlDumpElementDecl (xmlBufferPtr buf, + xmlElementPtr elem); +#endif /* LIBXML_OUTPUT_ENABLED */ + +/* Enumeration */ +XMLPUBFUN xmlEnumerationPtr + xmlCreateEnumeration (const xmlChar *name); +XMLPUBFUN void + xmlFreeEnumeration (xmlEnumerationPtr cur); +#ifdef LIBXML_TREE_ENABLED +XMLPUBFUN xmlEnumerationPtr + xmlCopyEnumeration (xmlEnumerationPtr cur); +#endif /* LIBXML_TREE_ENABLED */ + +/* Attribute */ +XMLPUBFUN xmlAttributePtr + xmlAddAttributeDecl (xmlValidCtxtPtr ctxt, + xmlDtdPtr dtd, + const xmlChar *elem, + const xmlChar *name, + const xmlChar *ns, + xmlAttributeType type, + xmlAttributeDefault def, + const xmlChar *defaultValue, + xmlEnumerationPtr tree); +#ifdef LIBXML_TREE_ENABLED +XMLPUBFUN xmlAttributeTablePtr + xmlCopyAttributeTable (xmlAttributeTablePtr table); +#endif /* LIBXML_TREE_ENABLED */ +XMLPUBFUN void + xmlFreeAttributeTable (xmlAttributeTablePtr table); +#ifdef LIBXML_OUTPUT_ENABLED +XML_DEPRECATED +XMLPUBFUN void + xmlDumpAttributeTable (xmlBufferPtr buf, + xmlAttributeTablePtr table); +XML_DEPRECATED +XMLPUBFUN void + xmlDumpAttributeDecl (xmlBufferPtr buf, + xmlAttributePtr attr); +#endif /* LIBXML_OUTPUT_ENABLED */ + +/* IDs */ +XMLPUBFUN int + xmlAddIDSafe (xmlAttrPtr attr, + const xmlChar *value); +XMLPUBFUN xmlIDPtr + xmlAddID (xmlValidCtxtPtr ctxt, + xmlDocPtr doc, + const xmlChar *value, + xmlAttrPtr attr); +XMLPUBFUN void + xmlFreeIDTable (xmlIDTablePtr table); +XMLPUBFUN xmlAttrPtr + xmlGetID (xmlDocPtr doc, + const xmlChar *ID); +XMLPUBFUN int + xmlIsID (xmlDocPtr doc, + xmlNodePtr elem, + xmlAttrPtr attr); +XMLPUBFUN int + xmlRemoveID (xmlDocPtr doc, + xmlAttrPtr attr); + +/* IDREFs */ +XML_DEPRECATED +XMLPUBFUN xmlRefPtr + xmlAddRef (xmlValidCtxtPtr ctxt, + xmlDocPtr doc, + const xmlChar *value, + xmlAttrPtr attr); +XML_DEPRECATED +XMLPUBFUN void + xmlFreeRefTable (xmlRefTablePtr table); +XML_DEPRECATED +XMLPUBFUN int + xmlIsRef (xmlDocPtr doc, + xmlNodePtr elem, + xmlAttrPtr attr); +XML_DEPRECATED +XMLPUBFUN int + xmlRemoveRef (xmlDocPtr doc, + xmlAttrPtr attr); +XML_DEPRECATED +XMLPUBFUN xmlListPtr + xmlGetRefs (xmlDocPtr doc, + const xmlChar *ID); + +/** + * The public function calls related to validity checking. + */ +#ifdef LIBXML_VALID_ENABLED +/* Allocate/Release Validation Contexts */ +XMLPUBFUN xmlValidCtxtPtr + xmlNewValidCtxt(void); +XMLPUBFUN void + xmlFreeValidCtxt(xmlValidCtxtPtr); + +XML_DEPRECATED +XMLPUBFUN int + xmlValidateRoot (xmlValidCtxtPtr ctxt, + xmlDocPtr doc); +XML_DEPRECATED +XMLPUBFUN int + xmlValidateElementDecl (xmlValidCtxtPtr ctxt, + xmlDocPtr doc, + xmlElementPtr elem); +XML_DEPRECATED +XMLPUBFUN xmlChar * + xmlValidNormalizeAttributeValue(xmlDocPtr doc, + xmlNodePtr elem, + const xmlChar *name, + const xmlChar *value); +XML_DEPRECATED +XMLPUBFUN xmlChar * + xmlValidCtxtNormalizeAttributeValue(xmlValidCtxtPtr ctxt, + xmlDocPtr doc, + xmlNodePtr elem, + const xmlChar *name, + const xmlChar *value); +XML_DEPRECATED +XMLPUBFUN int + xmlValidateAttributeDecl(xmlValidCtxtPtr ctxt, + xmlDocPtr doc, + xmlAttributePtr attr); +XML_DEPRECATED +XMLPUBFUN int + xmlValidateAttributeValue(xmlAttributeType type, + const xmlChar *value); +XML_DEPRECATED +XMLPUBFUN int + xmlValidateNotationDecl (xmlValidCtxtPtr ctxt, + xmlDocPtr doc, + xmlNotationPtr nota); +XMLPUBFUN int + xmlValidateDtd (xmlValidCtxtPtr ctxt, + xmlDocPtr doc, + xmlDtdPtr dtd); +XML_DEPRECATED +XMLPUBFUN int + xmlValidateDtdFinal (xmlValidCtxtPtr ctxt, + xmlDocPtr doc); +XMLPUBFUN int + xmlValidateDocument (xmlValidCtxtPtr ctxt, + xmlDocPtr doc); +XMLPUBFUN int + xmlValidateElement (xmlValidCtxtPtr ctxt, + xmlDocPtr doc, + xmlNodePtr elem); +XML_DEPRECATED +XMLPUBFUN int + xmlValidateOneElement (xmlValidCtxtPtr ctxt, + xmlDocPtr doc, + xmlNodePtr elem); +XML_DEPRECATED +XMLPUBFUN int + xmlValidateOneAttribute (xmlValidCtxtPtr ctxt, + xmlDocPtr doc, + xmlNodePtr elem, + xmlAttrPtr attr, + const xmlChar *value); +XML_DEPRECATED +XMLPUBFUN int + xmlValidateOneNamespace (xmlValidCtxtPtr ctxt, + xmlDocPtr doc, + xmlNodePtr elem, + const xmlChar *prefix, + xmlNsPtr ns, + const xmlChar *value); +XML_DEPRECATED +XMLPUBFUN int + xmlValidateDocumentFinal(xmlValidCtxtPtr ctxt, + xmlDocPtr doc); +#endif /* LIBXML_VALID_ENABLED */ + +#if defined(LIBXML_VALID_ENABLED) || defined(LIBXML_SCHEMAS_ENABLED) +XML_DEPRECATED +XMLPUBFUN int + xmlValidateNotationUse (xmlValidCtxtPtr ctxt, + xmlDocPtr doc, + const xmlChar *notationName); +#endif /* LIBXML_VALID_ENABLED or LIBXML_SCHEMAS_ENABLED */ + +XMLPUBFUN int + xmlIsMixedElement (xmlDocPtr doc, + const xmlChar *name); +XMLPUBFUN xmlAttributePtr + xmlGetDtdAttrDesc (xmlDtdPtr dtd, + const xmlChar *elem, + const xmlChar *name); +XMLPUBFUN xmlAttributePtr + xmlGetDtdQAttrDesc (xmlDtdPtr dtd, + const xmlChar *elem, + const xmlChar *name, + const xmlChar *prefix); +XMLPUBFUN xmlNotationPtr + xmlGetDtdNotationDesc (xmlDtdPtr dtd, + const xmlChar *name); +XMLPUBFUN xmlElementPtr + xmlGetDtdQElementDesc (xmlDtdPtr dtd, + const xmlChar *name, + const xmlChar *prefix); +XMLPUBFUN xmlElementPtr + xmlGetDtdElementDesc (xmlDtdPtr dtd, + const xmlChar *name); + +#ifdef LIBXML_VALID_ENABLED + +XMLPUBFUN int + xmlValidGetPotentialChildren(xmlElementContent *ctree, + const xmlChar **names, + int *len, + int max); + +XMLPUBFUN int + xmlValidGetValidElements(xmlNode *prev, + xmlNode *next, + const xmlChar **names, + int max); +XMLPUBFUN int + xmlValidateNameValue (const xmlChar *value); +XMLPUBFUN int + xmlValidateNamesValue (const xmlChar *value); +XMLPUBFUN int + xmlValidateNmtokenValue (const xmlChar *value); +XMLPUBFUN int + xmlValidateNmtokensValue(const xmlChar *value); + +#ifdef LIBXML_REGEXP_ENABLED +/* + * Validation based on the regexp support + */ +XML_DEPRECATED +XMLPUBFUN int + xmlValidBuildContentModel(xmlValidCtxtPtr ctxt, + xmlElementPtr elem); + +XML_DEPRECATED +XMLPUBFUN int + xmlValidatePushElement (xmlValidCtxtPtr ctxt, + xmlDocPtr doc, + xmlNodePtr elem, + const xmlChar *qname); +XML_DEPRECATED +XMLPUBFUN int + xmlValidatePushCData (xmlValidCtxtPtr ctxt, + const xmlChar *data, + int len); +XML_DEPRECATED +XMLPUBFUN int + xmlValidatePopElement (xmlValidCtxtPtr ctxt, + xmlDocPtr doc, + xmlNodePtr elem, + const xmlChar *qname); +#endif /* LIBXML_REGEXP_ENABLED */ +#endif /* LIBXML_VALID_ENABLED */ +#ifdef __cplusplus +} +#endif +#endif /* __XML_VALID_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xinclude.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xinclude.h new file mode 100644 index 00000000..71fa4c20 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xinclude.h @@ -0,0 +1,136 @@ +/* + * Summary: implementation of XInclude + * Description: API to handle XInclude processing, + * implements the + * World Wide Web Consortium Last Call Working Draft 10 November 2003 + * http://www.w3.org/TR/2003/WD-xinclude-20031110 + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XINCLUDE_H__ +#define __XML_XINCLUDE_H__ + +#include +#include +#include + +#ifdef LIBXML_XINCLUDE_ENABLED + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * XINCLUDE_NS: + * + * Macro defining the Xinclude namespace: http://www.w3.org/2003/XInclude + */ +#define XINCLUDE_NS (const xmlChar *) "http://www.w3.org/2003/XInclude" +/** + * XINCLUDE_OLD_NS: + * + * Macro defining the draft Xinclude namespace: http://www.w3.org/2001/XInclude + */ +#define XINCLUDE_OLD_NS (const xmlChar *) "http://www.w3.org/2001/XInclude" +/** + * XINCLUDE_NODE: + * + * Macro defining "include" + */ +#define XINCLUDE_NODE (const xmlChar *) "include" +/** + * XINCLUDE_FALLBACK: + * + * Macro defining "fallback" + */ +#define XINCLUDE_FALLBACK (const xmlChar *) "fallback" +/** + * XINCLUDE_HREF: + * + * Macro defining "href" + */ +#define XINCLUDE_HREF (const xmlChar *) "href" +/** + * XINCLUDE_PARSE: + * + * Macro defining "parse" + */ +#define XINCLUDE_PARSE (const xmlChar *) "parse" +/** + * XINCLUDE_PARSE_XML: + * + * Macro defining "xml" + */ +#define XINCLUDE_PARSE_XML (const xmlChar *) "xml" +/** + * XINCLUDE_PARSE_TEXT: + * + * Macro defining "text" + */ +#define XINCLUDE_PARSE_TEXT (const xmlChar *) "text" +/** + * XINCLUDE_PARSE_ENCODING: + * + * Macro defining "encoding" + */ +#define XINCLUDE_PARSE_ENCODING (const xmlChar *) "encoding" +/** + * XINCLUDE_PARSE_XPOINTER: + * + * Macro defining "xpointer" + */ +#define XINCLUDE_PARSE_XPOINTER (const xmlChar *) "xpointer" + +typedef struct _xmlXIncludeCtxt xmlXIncludeCtxt; +typedef xmlXIncludeCtxt *xmlXIncludeCtxtPtr; + +/* + * standalone processing + */ +XMLPUBFUN int + xmlXIncludeProcess (xmlDocPtr doc); +XMLPUBFUN int + xmlXIncludeProcessFlags (xmlDocPtr doc, + int flags); +XMLPUBFUN int + xmlXIncludeProcessFlagsData(xmlDocPtr doc, + int flags, + void *data); +XMLPUBFUN int + xmlXIncludeProcessTreeFlagsData(xmlNodePtr tree, + int flags, + void *data); +XMLPUBFUN int + xmlXIncludeProcessTree (xmlNodePtr tree); +XMLPUBFUN int + xmlXIncludeProcessTreeFlags(xmlNodePtr tree, + int flags); +/* + * contextual processing + */ +XMLPUBFUN xmlXIncludeCtxtPtr + xmlXIncludeNewContext (xmlDocPtr doc); +XMLPUBFUN int + xmlXIncludeSetFlags (xmlXIncludeCtxtPtr ctxt, + int flags); +XMLPUBFUN void + xmlXIncludeSetErrorHandler(xmlXIncludeCtxtPtr ctxt, + xmlStructuredErrorFunc handler, + void *data); +XMLPUBFUN int + xmlXIncludeGetLastError (xmlXIncludeCtxtPtr ctxt); +XMLPUBFUN void + xmlXIncludeFreeContext (xmlXIncludeCtxtPtr ctxt); +XMLPUBFUN int + xmlXIncludeProcessNode (xmlXIncludeCtxtPtr ctxt, + xmlNodePtr tree); +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_XINCLUDE_ENABLED */ + +#endif /* __XML_XINCLUDE_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xlink.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xlink.h new file mode 100644 index 00000000..10657366 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xlink.h @@ -0,0 +1,189 @@ +/* + * Summary: unfinished XLink detection module + * Description: unfinished XLink detection module + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XLINK_H__ +#define __XML_XLINK_H__ + +#include +#include + +#ifdef LIBXML_XPTR_ENABLED + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Various defines for the various Link properties. + * + * NOTE: the link detection layer will try to resolve QName expansion + * of namespaces. If "foo" is the prefix for "http://foo.com/" + * then the link detection layer will expand role="foo:myrole" + * to "http://foo.com/:myrole". + * NOTE: the link detection layer will expand URI-References found on + * href attributes by using the base mechanism if found. + */ +typedef xmlChar *xlinkHRef; +typedef xmlChar *xlinkRole; +typedef xmlChar *xlinkTitle; + +typedef enum { + XLINK_TYPE_NONE = 0, + XLINK_TYPE_SIMPLE, + XLINK_TYPE_EXTENDED, + XLINK_TYPE_EXTENDED_SET +} xlinkType; + +typedef enum { + XLINK_SHOW_NONE = 0, + XLINK_SHOW_NEW, + XLINK_SHOW_EMBED, + XLINK_SHOW_REPLACE +} xlinkShow; + +typedef enum { + XLINK_ACTUATE_NONE = 0, + XLINK_ACTUATE_AUTO, + XLINK_ACTUATE_ONREQUEST +} xlinkActuate; + +/** + * xlinkNodeDetectFunc: + * @ctx: user data pointer + * @node: the node to check + * + * This is the prototype for the link detection routine. + * It calls the default link detection callbacks upon link detection. + */ +typedef void (*xlinkNodeDetectFunc) (void *ctx, xmlNodePtr node); + +/* + * The link detection module interact with the upper layers using + * a set of callback registered at parsing time. + */ + +/** + * xlinkSimpleLinkFunk: + * @ctx: user data pointer + * @node: the node carrying the link + * @href: the target of the link + * @role: the role string + * @title: the link title + * + * This is the prototype for a simple link detection callback. + */ +typedef void +(*xlinkSimpleLinkFunk) (void *ctx, + xmlNodePtr node, + const xlinkHRef href, + const xlinkRole role, + const xlinkTitle title); + +/** + * xlinkExtendedLinkFunk: + * @ctx: user data pointer + * @node: the node carrying the link + * @nbLocators: the number of locators detected on the link + * @hrefs: pointer to the array of locator hrefs + * @roles: pointer to the array of locator roles + * @nbArcs: the number of arcs detected on the link + * @from: pointer to the array of source roles found on the arcs + * @to: pointer to the array of target roles found on the arcs + * @show: array of values for the show attributes found on the arcs + * @actuate: array of values for the actuate attributes found on the arcs + * @nbTitles: the number of titles detected on the link + * @title: array of titles detected on the link + * @langs: array of xml:lang values for the titles + * + * This is the prototype for a extended link detection callback. + */ +typedef void +(*xlinkExtendedLinkFunk)(void *ctx, + xmlNodePtr node, + int nbLocators, + const xlinkHRef *hrefs, + const xlinkRole *roles, + int nbArcs, + const xlinkRole *from, + const xlinkRole *to, + xlinkShow *show, + xlinkActuate *actuate, + int nbTitles, + const xlinkTitle *titles, + const xmlChar **langs); + +/** + * xlinkExtendedLinkSetFunk: + * @ctx: user data pointer + * @node: the node carrying the link + * @nbLocators: the number of locators detected on the link + * @hrefs: pointer to the array of locator hrefs + * @roles: pointer to the array of locator roles + * @nbTitles: the number of titles detected on the link + * @title: array of titles detected on the link + * @langs: array of xml:lang values for the titles + * + * This is the prototype for a extended link set detection callback. + */ +typedef void +(*xlinkExtendedLinkSetFunk) (void *ctx, + xmlNodePtr node, + int nbLocators, + const xlinkHRef *hrefs, + const xlinkRole *roles, + int nbTitles, + const xlinkTitle *titles, + const xmlChar **langs); + +/** + * This is the structure containing a set of Links detection callbacks. + * + * There is no default xlink callbacks, if one want to get link + * recognition activated, those call backs must be provided before parsing. + */ +typedef struct _xlinkHandler xlinkHandler; +typedef xlinkHandler *xlinkHandlerPtr; +struct _xlinkHandler { + xlinkSimpleLinkFunk simple; + xlinkExtendedLinkFunk extended; + xlinkExtendedLinkSetFunk set; +}; + +/* + * The default detection routine, can be overridden, they call the default + * detection callbacks. + */ + +XMLPUBFUN xlinkNodeDetectFunc + xlinkGetDefaultDetect (void); +XMLPUBFUN void + xlinkSetDefaultDetect (xlinkNodeDetectFunc func); + +/* + * Routines to set/get the default handlers. + */ +XMLPUBFUN xlinkHandlerPtr + xlinkGetDefaultHandler (void); +XMLPUBFUN void + xlinkSetDefaultHandler (xlinkHandlerPtr handler); + +/* + * Link detection module itself. + */ +XMLPUBFUN xlinkType + xlinkIsLink (xmlDocPtr doc, + xmlNodePtr node); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_XPTR_ENABLED */ + +#endif /* __XML_XLINK_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlIO.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlIO.h new file mode 100644 index 00000000..5a35dc64 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlIO.h @@ -0,0 +1,438 @@ +/* + * Summary: interface for the I/O interfaces used by the parser + * Description: interface for the I/O interfaces used by the parser + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_IO_H__ +#define __XML_IO_H__ + +/** DOC_DISABLE */ +#include +#include +#include +#define XML_TREE_INTERNALS +#include +#undef XML_TREE_INTERNALS +/** DOC_ENABLE */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Those are the functions and datatypes for the parser input + * I/O structures. + */ + +/** + * xmlInputMatchCallback: + * @filename: the filename or URI + * + * Callback used in the I/O Input API to detect if the current handler + * can provide input functionality for this resource. + * + * Returns 1 if yes and 0 if another Input module should be used + */ +typedef int (*xmlInputMatchCallback) (char const *filename); +/** + * xmlInputOpenCallback: + * @filename: the filename or URI + * + * Callback used in the I/O Input API to open the resource + * + * Returns an Input context or NULL in case or error + */ +typedef void * (*xmlInputOpenCallback) (char const *filename); +/** + * xmlInputReadCallback: + * @context: an Input context + * @buffer: the buffer to store data read + * @len: the length of the buffer in bytes + * + * Callback used in the I/O Input API to read the resource + * + * Returns the number of bytes read or -1 in case of error + */ +typedef int (*xmlInputReadCallback) (void * context, char * buffer, int len); +/** + * xmlInputCloseCallback: + * @context: an Input context + * + * Callback used in the I/O Input API to close the resource + * + * Returns 0 or -1 in case of error + */ +typedef int (*xmlInputCloseCallback) (void * context); + +#ifdef LIBXML_OUTPUT_ENABLED +/* + * Those are the functions and datatypes for the library output + * I/O structures. + */ + +/** + * xmlOutputMatchCallback: + * @filename: the filename or URI + * + * Callback used in the I/O Output API to detect if the current handler + * can provide output functionality for this resource. + * + * Returns 1 if yes and 0 if another Output module should be used + */ +typedef int (*xmlOutputMatchCallback) (char const *filename); +/** + * xmlOutputOpenCallback: + * @filename: the filename or URI + * + * Callback used in the I/O Output API to open the resource + * + * Returns an Output context or NULL in case or error + */ +typedef void * (*xmlOutputOpenCallback) (char const *filename); +/** + * xmlOutputWriteCallback: + * @context: an Output context + * @buffer: the buffer of data to write + * @len: the length of the buffer in bytes + * + * Callback used in the I/O Output API to write to the resource + * + * Returns the number of bytes written or -1 in case of error + */ +typedef int (*xmlOutputWriteCallback) (void * context, const char * buffer, + int len); +/** + * xmlOutputCloseCallback: + * @context: an Output context + * + * Callback used in the I/O Output API to close the resource + * + * Returns 0 or -1 in case of error + */ +typedef int (*xmlOutputCloseCallback) (void * context); +#endif /* LIBXML_OUTPUT_ENABLED */ + +/** + * xmlParserInputBufferCreateFilenameFunc: + * @URI: the URI to read from + * @enc: the requested source encoding + * + * Signature for the function doing the lookup for a suitable input method + * corresponding to an URI. + * + * Returns the new xmlParserInputBufferPtr in case of success or NULL if no + * method was found. + */ +typedef xmlParserInputBufferPtr +(*xmlParserInputBufferCreateFilenameFunc)(const char *URI, xmlCharEncoding enc); + +/** + * xmlOutputBufferCreateFilenameFunc: + * @URI: the URI to write to + * @enc: the requested target encoding + * + * Signature for the function doing the lookup for a suitable output method + * corresponding to an URI. + * + * Returns the new xmlOutputBufferPtr in case of success or NULL if no + * method was found. + */ +typedef xmlOutputBufferPtr +(*xmlOutputBufferCreateFilenameFunc)(const char *URI, + xmlCharEncodingHandlerPtr encoder, int compression); + +struct _xmlParserInputBuffer { + void* context; + xmlInputReadCallback readcallback; + xmlInputCloseCallback closecallback; + + xmlCharEncodingHandlerPtr encoder; /* I18N conversions to UTF-8 */ + + xmlBufPtr buffer; /* Local buffer encoded in UTF-8 */ + xmlBufPtr raw; /* if encoder != NULL buffer for raw input */ + int compressed; /* -1=unknown, 0=not compressed, 1=compressed */ + int error; + unsigned long rawconsumed;/* amount consumed from raw */ +}; + + +#ifdef LIBXML_OUTPUT_ENABLED +struct _xmlOutputBuffer { + void* context; + xmlOutputWriteCallback writecallback; + xmlOutputCloseCallback closecallback; + + xmlCharEncodingHandlerPtr encoder; /* I18N conversions to UTF-8 */ + + xmlBufPtr buffer; /* Local buffer encoded in UTF-8 or ISOLatin */ + xmlBufPtr conv; /* if encoder != NULL buffer for output */ + int written; /* total number of byte written */ + int error; +}; +#endif /* LIBXML_OUTPUT_ENABLED */ + +/** DOC_DISABLE */ +#define XML_GLOBALS_IO \ + XML_OP(xmlParserInputBufferCreateFilenameValue, \ + xmlParserInputBufferCreateFilenameFunc, XML_DEPRECATED) \ + XML_OP(xmlOutputBufferCreateFilenameValue, \ + xmlOutputBufferCreateFilenameFunc, XML_DEPRECATED) + +#define XML_OP XML_DECLARE_GLOBAL +XML_GLOBALS_IO +#undef XML_OP + +#if defined(LIBXML_THREAD_ENABLED) && !defined(XML_GLOBALS_NO_REDEFINITION) + #define xmlParserInputBufferCreateFilenameValue \ + XML_GLOBAL_MACRO(xmlParserInputBufferCreateFilenameValue) + #define xmlOutputBufferCreateFilenameValue \ + XML_GLOBAL_MACRO(xmlOutputBufferCreateFilenameValue) +#endif +/** DOC_ENABLE */ + +/* + * Interfaces for input + */ +XMLPUBFUN void + xmlCleanupInputCallbacks (void); + +XMLPUBFUN int + xmlPopInputCallbacks (void); + +XMLPUBFUN void + xmlRegisterDefaultInputCallbacks (void); +XMLPUBFUN xmlParserInputBufferPtr + xmlAllocParserInputBuffer (xmlCharEncoding enc); + +XMLPUBFUN xmlParserInputBufferPtr + xmlParserInputBufferCreateFilename (const char *URI, + xmlCharEncoding enc); +XMLPUBFUN xmlParserInputBufferPtr + xmlParserInputBufferCreateFile (FILE *file, + xmlCharEncoding enc); +XMLPUBFUN xmlParserInputBufferPtr + xmlParserInputBufferCreateFd (int fd, + xmlCharEncoding enc); +XMLPUBFUN xmlParserInputBufferPtr + xmlParserInputBufferCreateMem (const char *mem, int size, + xmlCharEncoding enc); +XMLPUBFUN xmlParserInputBufferPtr + xmlParserInputBufferCreateStatic (const char *mem, int size, + xmlCharEncoding enc); +XMLPUBFUN xmlParserInputBufferPtr + xmlParserInputBufferCreateIO (xmlInputReadCallback ioread, + xmlInputCloseCallback ioclose, + void *ioctx, + xmlCharEncoding enc); +XMLPUBFUN int + xmlParserInputBufferRead (xmlParserInputBufferPtr in, + int len); +XMLPUBFUN int + xmlParserInputBufferGrow (xmlParserInputBufferPtr in, + int len); +XMLPUBFUN int + xmlParserInputBufferPush (xmlParserInputBufferPtr in, + int len, + const char *buf); +XMLPUBFUN void + xmlFreeParserInputBuffer (xmlParserInputBufferPtr in); +XMLPUBFUN char * + xmlParserGetDirectory (const char *filename); + +XMLPUBFUN int + xmlRegisterInputCallbacks (xmlInputMatchCallback matchFunc, + xmlInputOpenCallback openFunc, + xmlInputReadCallback readFunc, + xmlInputCloseCallback closeFunc); + +xmlParserInputBufferPtr + __xmlParserInputBufferCreateFilename(const char *URI, + xmlCharEncoding enc); + +#ifdef LIBXML_OUTPUT_ENABLED +/* + * Interfaces for output + */ +XMLPUBFUN void + xmlCleanupOutputCallbacks (void); +XMLPUBFUN int + xmlPopOutputCallbacks (void); +XMLPUBFUN void + xmlRegisterDefaultOutputCallbacks(void); +XMLPUBFUN xmlOutputBufferPtr + xmlAllocOutputBuffer (xmlCharEncodingHandlerPtr encoder); + +XMLPUBFUN xmlOutputBufferPtr + xmlOutputBufferCreateFilename (const char *URI, + xmlCharEncodingHandlerPtr encoder, + int compression); + +XMLPUBFUN xmlOutputBufferPtr + xmlOutputBufferCreateFile (FILE *file, + xmlCharEncodingHandlerPtr encoder); + +XMLPUBFUN xmlOutputBufferPtr + xmlOutputBufferCreateBuffer (xmlBufferPtr buffer, + xmlCharEncodingHandlerPtr encoder); + +XMLPUBFUN xmlOutputBufferPtr + xmlOutputBufferCreateFd (int fd, + xmlCharEncodingHandlerPtr encoder); + +XMLPUBFUN xmlOutputBufferPtr + xmlOutputBufferCreateIO (xmlOutputWriteCallback iowrite, + xmlOutputCloseCallback ioclose, + void *ioctx, + xmlCharEncodingHandlerPtr encoder); + +/* Couple of APIs to get the output without digging into the buffers */ +XMLPUBFUN const xmlChar * + xmlOutputBufferGetContent (xmlOutputBufferPtr out); +XMLPUBFUN size_t + xmlOutputBufferGetSize (xmlOutputBufferPtr out); + +XMLPUBFUN int + xmlOutputBufferWrite (xmlOutputBufferPtr out, + int len, + const char *buf); +XMLPUBFUN int + xmlOutputBufferWriteString (xmlOutputBufferPtr out, + const char *str); +XMLPUBFUN int + xmlOutputBufferWriteEscape (xmlOutputBufferPtr out, + const xmlChar *str, + xmlCharEncodingOutputFunc escaping); + +XMLPUBFUN int + xmlOutputBufferFlush (xmlOutputBufferPtr out); +XMLPUBFUN int + xmlOutputBufferClose (xmlOutputBufferPtr out); + +XMLPUBFUN int + xmlRegisterOutputCallbacks (xmlOutputMatchCallback matchFunc, + xmlOutputOpenCallback openFunc, + xmlOutputWriteCallback writeFunc, + xmlOutputCloseCallback closeFunc); + +xmlOutputBufferPtr + __xmlOutputBufferCreateFilename(const char *URI, + xmlCharEncodingHandlerPtr encoder, + int compression); + +#ifdef LIBXML_HTTP_ENABLED +/* This function only exists if HTTP support built into the library */ +XML_DEPRECATED +XMLPUBFUN void + xmlRegisterHTTPPostCallbacks (void ); +#endif /* LIBXML_HTTP_ENABLED */ + +#endif /* LIBXML_OUTPUT_ENABLED */ + +XML_DEPRECATED +XMLPUBFUN xmlParserInputPtr + xmlCheckHTTPInput (xmlParserCtxtPtr ctxt, + xmlParserInputPtr ret); + +/* + * A predefined entity loader disabling network accesses + */ +XMLPUBFUN xmlParserInputPtr + xmlNoNetExternalEntityLoader (const char *URL, + const char *ID, + xmlParserCtxtPtr ctxt); + +XML_DEPRECATED +XMLPUBFUN xmlChar * + xmlNormalizeWindowsPath (const xmlChar *path); + +XML_DEPRECATED +XMLPUBFUN int + xmlCheckFilename (const char *path); +/** + * Default 'file://' protocol callbacks + */ +XML_DEPRECATED +XMLPUBFUN int + xmlFileMatch (const char *filename); +XML_DEPRECATED +XMLPUBFUN void * + xmlFileOpen (const char *filename); +XML_DEPRECATED +XMLPUBFUN int + xmlFileRead (void * context, + char * buffer, + int len); +XML_DEPRECATED +XMLPUBFUN int + xmlFileClose (void * context); + +/** + * Default 'http://' protocol callbacks + */ +#ifdef LIBXML_HTTP_ENABLED +XML_DEPRECATED +XMLPUBFUN int + xmlIOHTTPMatch (const char *filename); +XML_DEPRECATED +XMLPUBFUN void * + xmlIOHTTPOpen (const char *filename); +#ifdef LIBXML_OUTPUT_ENABLED +XML_DEPRECATED +XMLPUBFUN void * + xmlIOHTTPOpenW (const char * post_uri, + int compression ); +#endif /* LIBXML_OUTPUT_ENABLED */ +XML_DEPRECATED +XMLPUBFUN int + xmlIOHTTPRead (void * context, + char * buffer, + int len); +XML_DEPRECATED +XMLPUBFUN int + xmlIOHTTPClose (void * context); +#endif /* LIBXML_HTTP_ENABLED */ + +/** + * Default 'ftp://' protocol callbacks + */ +#if defined(LIBXML_FTP_ENABLED) +XML_DEPRECATED +XMLPUBFUN int + xmlIOFTPMatch (const char *filename); +XML_DEPRECATED +XMLPUBFUN void * + xmlIOFTPOpen (const char *filename); +XML_DEPRECATED +XMLPUBFUN int + xmlIOFTPRead (void * context, + char * buffer, + int len); +XML_DEPRECATED +XMLPUBFUN int + xmlIOFTPClose (void * context); +#endif /* defined(LIBXML_FTP_ENABLED) */ + +XMLPUBFUN xmlParserInputBufferCreateFilenameFunc + xmlParserInputBufferCreateFilenameDefault( + xmlParserInputBufferCreateFilenameFunc func); +XMLPUBFUN xmlOutputBufferCreateFilenameFunc + xmlOutputBufferCreateFilenameDefault( + xmlOutputBufferCreateFilenameFunc func); +XML_DEPRECATED +XMLPUBFUN xmlOutputBufferCreateFilenameFunc + xmlThrDefOutputBufferCreateFilenameDefault( + xmlOutputBufferCreateFilenameFunc func); +XML_DEPRECATED +XMLPUBFUN xmlParserInputBufferCreateFilenameFunc + xmlThrDefParserInputBufferCreateFilenameDefault( + xmlParserInputBufferCreateFilenameFunc func); + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_IO_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlautomata.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlautomata.h new file mode 100644 index 00000000..ea38eb37 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlautomata.h @@ -0,0 +1,146 @@ +/* + * Summary: API to build regexp automata + * Description: the API to build regexp automata + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_AUTOMATA_H__ +#define __XML_AUTOMATA_H__ + +#include + +#ifdef LIBXML_REGEXP_ENABLED +#ifdef LIBXML_AUTOMATA_ENABLED + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * xmlAutomataPtr: + * + * A libxml automata description, It can be compiled into a regexp + */ +typedef struct _xmlAutomata xmlAutomata; +typedef xmlAutomata *xmlAutomataPtr; + +/** + * xmlAutomataStatePtr: + * + * A state int the automata description, + */ +typedef struct _xmlAutomataState xmlAutomataState; +typedef xmlAutomataState *xmlAutomataStatePtr; + +/* + * Building API + */ +XMLPUBFUN xmlAutomataPtr + xmlNewAutomata (void); +XMLPUBFUN void + xmlFreeAutomata (xmlAutomataPtr am); + +XMLPUBFUN xmlAutomataStatePtr + xmlAutomataGetInitState (xmlAutomataPtr am); +XMLPUBFUN int + xmlAutomataSetFinalState (xmlAutomataPtr am, + xmlAutomataStatePtr state); +XMLPUBFUN xmlAutomataStatePtr + xmlAutomataNewState (xmlAutomataPtr am); +XMLPUBFUN xmlAutomataStatePtr + xmlAutomataNewTransition (xmlAutomataPtr am, + xmlAutomataStatePtr from, + xmlAutomataStatePtr to, + const xmlChar *token, + void *data); +XMLPUBFUN xmlAutomataStatePtr + xmlAutomataNewTransition2 (xmlAutomataPtr am, + xmlAutomataStatePtr from, + xmlAutomataStatePtr to, + const xmlChar *token, + const xmlChar *token2, + void *data); +XMLPUBFUN xmlAutomataStatePtr + xmlAutomataNewNegTrans (xmlAutomataPtr am, + xmlAutomataStatePtr from, + xmlAutomataStatePtr to, + const xmlChar *token, + const xmlChar *token2, + void *data); + +XMLPUBFUN xmlAutomataStatePtr + xmlAutomataNewCountTrans (xmlAutomataPtr am, + xmlAutomataStatePtr from, + xmlAutomataStatePtr to, + const xmlChar *token, + int min, + int max, + void *data); +XMLPUBFUN xmlAutomataStatePtr + xmlAutomataNewCountTrans2 (xmlAutomataPtr am, + xmlAutomataStatePtr from, + xmlAutomataStatePtr to, + const xmlChar *token, + const xmlChar *token2, + int min, + int max, + void *data); +XMLPUBFUN xmlAutomataStatePtr + xmlAutomataNewOnceTrans (xmlAutomataPtr am, + xmlAutomataStatePtr from, + xmlAutomataStatePtr to, + const xmlChar *token, + int min, + int max, + void *data); +XMLPUBFUN xmlAutomataStatePtr + xmlAutomataNewOnceTrans2 (xmlAutomataPtr am, + xmlAutomataStatePtr from, + xmlAutomataStatePtr to, + const xmlChar *token, + const xmlChar *token2, + int min, + int max, + void *data); +XMLPUBFUN xmlAutomataStatePtr + xmlAutomataNewAllTrans (xmlAutomataPtr am, + xmlAutomataStatePtr from, + xmlAutomataStatePtr to, + int lax); +XMLPUBFUN xmlAutomataStatePtr + xmlAutomataNewEpsilon (xmlAutomataPtr am, + xmlAutomataStatePtr from, + xmlAutomataStatePtr to); +XMLPUBFUN xmlAutomataStatePtr + xmlAutomataNewCountedTrans (xmlAutomataPtr am, + xmlAutomataStatePtr from, + xmlAutomataStatePtr to, + int counter); +XMLPUBFUN xmlAutomataStatePtr + xmlAutomataNewCounterTrans (xmlAutomataPtr am, + xmlAutomataStatePtr from, + xmlAutomataStatePtr to, + int counter); +XMLPUBFUN int + xmlAutomataNewCounter (xmlAutomataPtr am, + int min, + int max); + +XMLPUBFUN struct _xmlRegexp * + xmlAutomataCompile (xmlAutomataPtr am); +XMLPUBFUN int + xmlAutomataIsDeterminist (xmlAutomataPtr am); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_AUTOMATA_ENABLED */ +#endif /* LIBXML_REGEXP_ENABLED */ + +#endif /* __XML_AUTOMATA_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlerror.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlerror.h new file mode 100644 index 00000000..36381bec --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlerror.h @@ -0,0 +1,962 @@ +/* + * Summary: error handling + * Description: the API used to report errors + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_ERROR_H__ +#define __XML_ERROR_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * xmlErrorLevel: + * + * Indicates the level of an error + */ +typedef enum { + XML_ERR_NONE = 0, + XML_ERR_WARNING = 1, /* A simple warning */ + XML_ERR_ERROR = 2, /* A recoverable error */ + XML_ERR_FATAL = 3 /* A fatal error */ +} xmlErrorLevel; + +/** + * xmlErrorDomain: + * + * Indicates where an error may have come from + */ +typedef enum { + XML_FROM_NONE = 0, + XML_FROM_PARSER, /* The XML parser */ + XML_FROM_TREE, /* The tree module */ + XML_FROM_NAMESPACE, /* The XML Namespace module */ + XML_FROM_DTD, /* The XML DTD validation with parser context*/ + XML_FROM_HTML, /* The HTML parser */ + XML_FROM_MEMORY, /* The memory allocator */ + XML_FROM_OUTPUT, /* The serialization code */ + XML_FROM_IO, /* The Input/Output stack */ + XML_FROM_FTP, /* The FTP module */ + XML_FROM_HTTP, /* The HTTP module */ + XML_FROM_XINCLUDE, /* The XInclude processing */ + XML_FROM_XPATH, /* The XPath module */ + XML_FROM_XPOINTER, /* The XPointer module */ + XML_FROM_REGEXP, /* The regular expressions module */ + XML_FROM_DATATYPE, /* The W3C XML Schemas Datatype module */ + XML_FROM_SCHEMASP, /* The W3C XML Schemas parser module */ + XML_FROM_SCHEMASV, /* The W3C XML Schemas validation module */ + XML_FROM_RELAXNGP, /* The Relax-NG parser module */ + XML_FROM_RELAXNGV, /* The Relax-NG validator module */ + XML_FROM_CATALOG, /* The Catalog module */ + XML_FROM_C14N, /* The Canonicalization module */ + XML_FROM_XSLT, /* The XSLT engine from libxslt */ + XML_FROM_VALID, /* The XML DTD validation with valid context */ + XML_FROM_CHECK, /* The error checking module */ + XML_FROM_WRITER, /* The xmlwriter module */ + XML_FROM_MODULE, /* The dynamically loaded module module*/ + XML_FROM_I18N, /* The module handling character conversion */ + XML_FROM_SCHEMATRONV,/* The Schematron validator module */ + XML_FROM_BUFFER, /* The buffers module */ + XML_FROM_URI /* The URI module */ +} xmlErrorDomain; + +/** + * xmlError: + * + * An XML Error instance. + */ + +typedef struct _xmlError xmlError; +typedef xmlError *xmlErrorPtr; +struct _xmlError { + int domain; /* What part of the library raised this error */ + int code; /* The error code, e.g. an xmlParserError */ + char *message;/* human-readable informative error message */ + xmlErrorLevel level;/* how consequent is the error */ + char *file; /* the filename */ + int line; /* the line number if available */ + char *str1; /* extra string information */ + char *str2; /* extra string information */ + char *str3; /* extra string information */ + int int1; /* extra number information */ + int int2; /* error column # or 0 if N/A (todo: rename field when we would brk ABI) */ + void *ctxt; /* the parser context if available */ + void *node; /* the node in the tree */ +}; + +/** + * xmlParserError: + * + * This is an error that the XML (or HTML) parser can generate + */ +typedef enum { + XML_ERR_OK = 0, + XML_ERR_INTERNAL_ERROR, /* 1 */ + XML_ERR_NO_MEMORY, /* 2 */ + XML_ERR_DOCUMENT_START, /* 3 */ + XML_ERR_DOCUMENT_EMPTY, /* 4 */ + XML_ERR_DOCUMENT_END, /* 5 */ + XML_ERR_INVALID_HEX_CHARREF, /* 6 */ + XML_ERR_INVALID_DEC_CHARREF, /* 7 */ + XML_ERR_INVALID_CHARREF, /* 8 */ + XML_ERR_INVALID_CHAR, /* 9 */ + XML_ERR_CHARREF_AT_EOF, /* 10 */ + XML_ERR_CHARREF_IN_PROLOG, /* 11 */ + XML_ERR_CHARREF_IN_EPILOG, /* 12 */ + XML_ERR_CHARREF_IN_DTD, /* 13 */ + XML_ERR_ENTITYREF_AT_EOF, /* 14 */ + XML_ERR_ENTITYREF_IN_PROLOG, /* 15 */ + XML_ERR_ENTITYREF_IN_EPILOG, /* 16 */ + XML_ERR_ENTITYREF_IN_DTD, /* 17 */ + XML_ERR_PEREF_AT_EOF, /* 18 */ + XML_ERR_PEREF_IN_PROLOG, /* 19 */ + XML_ERR_PEREF_IN_EPILOG, /* 20 */ + XML_ERR_PEREF_IN_INT_SUBSET, /* 21 */ + XML_ERR_ENTITYREF_NO_NAME, /* 22 */ + XML_ERR_ENTITYREF_SEMICOL_MISSING, /* 23 */ + XML_ERR_PEREF_NO_NAME, /* 24 */ + XML_ERR_PEREF_SEMICOL_MISSING, /* 25 */ + XML_ERR_UNDECLARED_ENTITY, /* 26 */ + XML_WAR_UNDECLARED_ENTITY, /* 27 */ + XML_ERR_UNPARSED_ENTITY, /* 28 */ + XML_ERR_ENTITY_IS_EXTERNAL, /* 29 */ + XML_ERR_ENTITY_IS_PARAMETER, /* 30 */ + XML_ERR_UNKNOWN_ENCODING, /* 31 */ + XML_ERR_UNSUPPORTED_ENCODING, /* 32 */ + XML_ERR_STRING_NOT_STARTED, /* 33 */ + XML_ERR_STRING_NOT_CLOSED, /* 34 */ + XML_ERR_NS_DECL_ERROR, /* 35 */ + XML_ERR_ENTITY_NOT_STARTED, /* 36 */ + XML_ERR_ENTITY_NOT_FINISHED, /* 37 */ + XML_ERR_LT_IN_ATTRIBUTE, /* 38 */ + XML_ERR_ATTRIBUTE_NOT_STARTED, /* 39 */ + XML_ERR_ATTRIBUTE_NOT_FINISHED, /* 40 */ + XML_ERR_ATTRIBUTE_WITHOUT_VALUE, /* 41 */ + XML_ERR_ATTRIBUTE_REDEFINED, /* 42 */ + XML_ERR_LITERAL_NOT_STARTED, /* 43 */ + XML_ERR_LITERAL_NOT_FINISHED, /* 44 */ + XML_ERR_COMMENT_NOT_FINISHED, /* 45 */ + XML_ERR_PI_NOT_STARTED, /* 46 */ + XML_ERR_PI_NOT_FINISHED, /* 47 */ + XML_ERR_NOTATION_NOT_STARTED, /* 48 */ + XML_ERR_NOTATION_NOT_FINISHED, /* 49 */ + XML_ERR_ATTLIST_NOT_STARTED, /* 50 */ + XML_ERR_ATTLIST_NOT_FINISHED, /* 51 */ + XML_ERR_MIXED_NOT_STARTED, /* 52 */ + XML_ERR_MIXED_NOT_FINISHED, /* 53 */ + XML_ERR_ELEMCONTENT_NOT_STARTED, /* 54 */ + XML_ERR_ELEMCONTENT_NOT_FINISHED, /* 55 */ + XML_ERR_XMLDECL_NOT_STARTED, /* 56 */ + XML_ERR_XMLDECL_NOT_FINISHED, /* 57 */ + XML_ERR_CONDSEC_NOT_STARTED, /* 58 */ + XML_ERR_CONDSEC_NOT_FINISHED, /* 59 */ + XML_ERR_EXT_SUBSET_NOT_FINISHED, /* 60 */ + XML_ERR_DOCTYPE_NOT_FINISHED, /* 61 */ + XML_ERR_MISPLACED_CDATA_END, /* 62 */ + XML_ERR_CDATA_NOT_FINISHED, /* 63 */ + XML_ERR_RESERVED_XML_NAME, /* 64 */ + XML_ERR_SPACE_REQUIRED, /* 65 */ + XML_ERR_SEPARATOR_REQUIRED, /* 66 */ + XML_ERR_NMTOKEN_REQUIRED, /* 67 */ + XML_ERR_NAME_REQUIRED, /* 68 */ + XML_ERR_PCDATA_REQUIRED, /* 69 */ + XML_ERR_URI_REQUIRED, /* 70 */ + XML_ERR_PUBID_REQUIRED, /* 71 */ + XML_ERR_LT_REQUIRED, /* 72 */ + XML_ERR_GT_REQUIRED, /* 73 */ + XML_ERR_LTSLASH_REQUIRED, /* 74 */ + XML_ERR_EQUAL_REQUIRED, /* 75 */ + XML_ERR_TAG_NAME_MISMATCH, /* 76 */ + XML_ERR_TAG_NOT_FINISHED, /* 77 */ + XML_ERR_STANDALONE_VALUE, /* 78 */ + XML_ERR_ENCODING_NAME, /* 79 */ + XML_ERR_HYPHEN_IN_COMMENT, /* 80 */ + XML_ERR_INVALID_ENCODING, /* 81 */ + XML_ERR_EXT_ENTITY_STANDALONE, /* 82 */ + XML_ERR_CONDSEC_INVALID, /* 83 */ + XML_ERR_VALUE_REQUIRED, /* 84 */ + XML_ERR_NOT_WELL_BALANCED, /* 85 */ + XML_ERR_EXTRA_CONTENT, /* 86 */ + XML_ERR_ENTITY_CHAR_ERROR, /* 87 */ + XML_ERR_ENTITY_PE_INTERNAL, /* 88 */ + XML_ERR_ENTITY_LOOP, /* 89 */ + XML_ERR_ENTITY_BOUNDARY, /* 90 */ + XML_ERR_INVALID_URI, /* 91 */ + XML_ERR_URI_FRAGMENT, /* 92 */ + XML_WAR_CATALOG_PI, /* 93 */ + XML_ERR_NO_DTD, /* 94 */ + XML_ERR_CONDSEC_INVALID_KEYWORD, /* 95 */ + XML_ERR_VERSION_MISSING, /* 96 */ + XML_WAR_UNKNOWN_VERSION, /* 97 */ + XML_WAR_LANG_VALUE, /* 98 */ + XML_WAR_NS_URI, /* 99 */ + XML_WAR_NS_URI_RELATIVE, /* 100 */ + XML_ERR_MISSING_ENCODING, /* 101 */ + XML_WAR_SPACE_VALUE, /* 102 */ + XML_ERR_NOT_STANDALONE, /* 103 */ + XML_ERR_ENTITY_PROCESSING, /* 104 */ + XML_ERR_NOTATION_PROCESSING, /* 105 */ + XML_WAR_NS_COLUMN, /* 106 */ + XML_WAR_ENTITY_REDEFINED, /* 107 */ + XML_ERR_UNKNOWN_VERSION, /* 108 */ + XML_ERR_VERSION_MISMATCH, /* 109 */ + XML_ERR_NAME_TOO_LONG, /* 110 */ + XML_ERR_USER_STOP, /* 111 */ + XML_ERR_COMMENT_ABRUPTLY_ENDED, /* 112 */ + XML_WAR_ENCODING_MISMATCH, /* 113 */ + XML_ERR_RESOURCE_LIMIT, /* 114 */ + XML_ERR_ARGUMENT, /* 115 */ + XML_ERR_SYSTEM, /* 116 */ + XML_ERR_REDECL_PREDEF_ENTITY, /* 117 */ + XML_ERR_INT_SUBSET_NOT_FINISHED, /* 118 */ + XML_NS_ERR_XML_NAMESPACE = 200, + XML_NS_ERR_UNDEFINED_NAMESPACE, /* 201 */ + XML_NS_ERR_QNAME, /* 202 */ + XML_NS_ERR_ATTRIBUTE_REDEFINED, /* 203 */ + XML_NS_ERR_EMPTY, /* 204 */ + XML_NS_ERR_COLON, /* 205 */ + XML_DTD_ATTRIBUTE_DEFAULT = 500, + XML_DTD_ATTRIBUTE_REDEFINED, /* 501 */ + XML_DTD_ATTRIBUTE_VALUE, /* 502 */ + XML_DTD_CONTENT_ERROR, /* 503 */ + XML_DTD_CONTENT_MODEL, /* 504 */ + XML_DTD_CONTENT_NOT_DETERMINIST, /* 505 */ + XML_DTD_DIFFERENT_PREFIX, /* 506 */ + XML_DTD_ELEM_DEFAULT_NAMESPACE, /* 507 */ + XML_DTD_ELEM_NAMESPACE, /* 508 */ + XML_DTD_ELEM_REDEFINED, /* 509 */ + XML_DTD_EMPTY_NOTATION, /* 510 */ + XML_DTD_ENTITY_TYPE, /* 511 */ + XML_DTD_ID_FIXED, /* 512 */ + XML_DTD_ID_REDEFINED, /* 513 */ + XML_DTD_ID_SUBSET, /* 514 */ + XML_DTD_INVALID_CHILD, /* 515 */ + XML_DTD_INVALID_DEFAULT, /* 516 */ + XML_DTD_LOAD_ERROR, /* 517 */ + XML_DTD_MISSING_ATTRIBUTE, /* 518 */ + XML_DTD_MIXED_CORRUPT, /* 519 */ + XML_DTD_MULTIPLE_ID, /* 520 */ + XML_DTD_NO_DOC, /* 521 */ + XML_DTD_NO_DTD, /* 522 */ + XML_DTD_NO_ELEM_NAME, /* 523 */ + XML_DTD_NO_PREFIX, /* 524 */ + XML_DTD_NO_ROOT, /* 525 */ + XML_DTD_NOTATION_REDEFINED, /* 526 */ + XML_DTD_NOTATION_VALUE, /* 527 */ + XML_DTD_NOT_EMPTY, /* 528 */ + XML_DTD_NOT_PCDATA, /* 529 */ + XML_DTD_NOT_STANDALONE, /* 530 */ + XML_DTD_ROOT_NAME, /* 531 */ + XML_DTD_STANDALONE_WHITE_SPACE, /* 532 */ + XML_DTD_UNKNOWN_ATTRIBUTE, /* 533 */ + XML_DTD_UNKNOWN_ELEM, /* 534 */ + XML_DTD_UNKNOWN_ENTITY, /* 535 */ + XML_DTD_UNKNOWN_ID, /* 536 */ + XML_DTD_UNKNOWN_NOTATION, /* 537 */ + XML_DTD_STANDALONE_DEFAULTED, /* 538 */ + XML_DTD_XMLID_VALUE, /* 539 */ + XML_DTD_XMLID_TYPE, /* 540 */ + XML_DTD_DUP_TOKEN, /* 541 */ + XML_HTML_STRUCURE_ERROR = 800, + XML_HTML_UNKNOWN_TAG, /* 801 */ + XML_HTML_INCORRECTLY_OPENED_COMMENT, /* 802 */ + XML_RNGP_ANYNAME_ATTR_ANCESTOR = 1000, + XML_RNGP_ATTR_CONFLICT, /* 1001 */ + XML_RNGP_ATTRIBUTE_CHILDREN, /* 1002 */ + XML_RNGP_ATTRIBUTE_CONTENT, /* 1003 */ + XML_RNGP_ATTRIBUTE_EMPTY, /* 1004 */ + XML_RNGP_ATTRIBUTE_NOOP, /* 1005 */ + XML_RNGP_CHOICE_CONTENT, /* 1006 */ + XML_RNGP_CHOICE_EMPTY, /* 1007 */ + XML_RNGP_CREATE_FAILURE, /* 1008 */ + XML_RNGP_DATA_CONTENT, /* 1009 */ + XML_RNGP_DEF_CHOICE_AND_INTERLEAVE, /* 1010 */ + XML_RNGP_DEFINE_CREATE_FAILED, /* 1011 */ + XML_RNGP_DEFINE_EMPTY, /* 1012 */ + XML_RNGP_DEFINE_MISSING, /* 1013 */ + XML_RNGP_DEFINE_NAME_MISSING, /* 1014 */ + XML_RNGP_ELEM_CONTENT_EMPTY, /* 1015 */ + XML_RNGP_ELEM_CONTENT_ERROR, /* 1016 */ + XML_RNGP_ELEMENT_EMPTY, /* 1017 */ + XML_RNGP_ELEMENT_CONTENT, /* 1018 */ + XML_RNGP_ELEMENT_NAME, /* 1019 */ + XML_RNGP_ELEMENT_NO_CONTENT, /* 1020 */ + XML_RNGP_ELEM_TEXT_CONFLICT, /* 1021 */ + XML_RNGP_EMPTY, /* 1022 */ + XML_RNGP_EMPTY_CONSTRUCT, /* 1023 */ + XML_RNGP_EMPTY_CONTENT, /* 1024 */ + XML_RNGP_EMPTY_NOT_EMPTY, /* 1025 */ + XML_RNGP_ERROR_TYPE_LIB, /* 1026 */ + XML_RNGP_EXCEPT_EMPTY, /* 1027 */ + XML_RNGP_EXCEPT_MISSING, /* 1028 */ + XML_RNGP_EXCEPT_MULTIPLE, /* 1029 */ + XML_RNGP_EXCEPT_NO_CONTENT, /* 1030 */ + XML_RNGP_EXTERNALREF_EMTPY, /* 1031 */ + XML_RNGP_EXTERNAL_REF_FAILURE, /* 1032 */ + XML_RNGP_EXTERNALREF_RECURSE, /* 1033 */ + XML_RNGP_FORBIDDEN_ATTRIBUTE, /* 1034 */ + XML_RNGP_FOREIGN_ELEMENT, /* 1035 */ + XML_RNGP_GRAMMAR_CONTENT, /* 1036 */ + XML_RNGP_GRAMMAR_EMPTY, /* 1037 */ + XML_RNGP_GRAMMAR_MISSING, /* 1038 */ + XML_RNGP_GRAMMAR_NO_START, /* 1039 */ + XML_RNGP_GROUP_ATTR_CONFLICT, /* 1040 */ + XML_RNGP_HREF_ERROR, /* 1041 */ + XML_RNGP_INCLUDE_EMPTY, /* 1042 */ + XML_RNGP_INCLUDE_FAILURE, /* 1043 */ + XML_RNGP_INCLUDE_RECURSE, /* 1044 */ + XML_RNGP_INTERLEAVE_ADD, /* 1045 */ + XML_RNGP_INTERLEAVE_CREATE_FAILED, /* 1046 */ + XML_RNGP_INTERLEAVE_EMPTY, /* 1047 */ + XML_RNGP_INTERLEAVE_NO_CONTENT, /* 1048 */ + XML_RNGP_INVALID_DEFINE_NAME, /* 1049 */ + XML_RNGP_INVALID_URI, /* 1050 */ + XML_RNGP_INVALID_VALUE, /* 1051 */ + XML_RNGP_MISSING_HREF, /* 1052 */ + XML_RNGP_NAME_MISSING, /* 1053 */ + XML_RNGP_NEED_COMBINE, /* 1054 */ + XML_RNGP_NOTALLOWED_NOT_EMPTY, /* 1055 */ + XML_RNGP_NSNAME_ATTR_ANCESTOR, /* 1056 */ + XML_RNGP_NSNAME_NO_NS, /* 1057 */ + XML_RNGP_PARAM_FORBIDDEN, /* 1058 */ + XML_RNGP_PARAM_NAME_MISSING, /* 1059 */ + XML_RNGP_PARENTREF_CREATE_FAILED, /* 1060 */ + XML_RNGP_PARENTREF_NAME_INVALID, /* 1061 */ + XML_RNGP_PARENTREF_NO_NAME, /* 1062 */ + XML_RNGP_PARENTREF_NO_PARENT, /* 1063 */ + XML_RNGP_PARENTREF_NOT_EMPTY, /* 1064 */ + XML_RNGP_PARSE_ERROR, /* 1065 */ + XML_RNGP_PAT_ANYNAME_EXCEPT_ANYNAME, /* 1066 */ + XML_RNGP_PAT_ATTR_ATTR, /* 1067 */ + XML_RNGP_PAT_ATTR_ELEM, /* 1068 */ + XML_RNGP_PAT_DATA_EXCEPT_ATTR, /* 1069 */ + XML_RNGP_PAT_DATA_EXCEPT_ELEM, /* 1070 */ + XML_RNGP_PAT_DATA_EXCEPT_EMPTY, /* 1071 */ + XML_RNGP_PAT_DATA_EXCEPT_GROUP, /* 1072 */ + XML_RNGP_PAT_DATA_EXCEPT_INTERLEAVE, /* 1073 */ + XML_RNGP_PAT_DATA_EXCEPT_LIST, /* 1074 */ + XML_RNGP_PAT_DATA_EXCEPT_ONEMORE, /* 1075 */ + XML_RNGP_PAT_DATA_EXCEPT_REF, /* 1076 */ + XML_RNGP_PAT_DATA_EXCEPT_TEXT, /* 1077 */ + XML_RNGP_PAT_LIST_ATTR, /* 1078 */ + XML_RNGP_PAT_LIST_ELEM, /* 1079 */ + XML_RNGP_PAT_LIST_INTERLEAVE, /* 1080 */ + XML_RNGP_PAT_LIST_LIST, /* 1081 */ + XML_RNGP_PAT_LIST_REF, /* 1082 */ + XML_RNGP_PAT_LIST_TEXT, /* 1083 */ + XML_RNGP_PAT_NSNAME_EXCEPT_ANYNAME, /* 1084 */ + XML_RNGP_PAT_NSNAME_EXCEPT_NSNAME, /* 1085 */ + XML_RNGP_PAT_ONEMORE_GROUP_ATTR, /* 1086 */ + XML_RNGP_PAT_ONEMORE_INTERLEAVE_ATTR, /* 1087 */ + XML_RNGP_PAT_START_ATTR, /* 1088 */ + XML_RNGP_PAT_START_DATA, /* 1089 */ + XML_RNGP_PAT_START_EMPTY, /* 1090 */ + XML_RNGP_PAT_START_GROUP, /* 1091 */ + XML_RNGP_PAT_START_INTERLEAVE, /* 1092 */ + XML_RNGP_PAT_START_LIST, /* 1093 */ + XML_RNGP_PAT_START_ONEMORE, /* 1094 */ + XML_RNGP_PAT_START_TEXT, /* 1095 */ + XML_RNGP_PAT_START_VALUE, /* 1096 */ + XML_RNGP_PREFIX_UNDEFINED, /* 1097 */ + XML_RNGP_REF_CREATE_FAILED, /* 1098 */ + XML_RNGP_REF_CYCLE, /* 1099 */ + XML_RNGP_REF_NAME_INVALID, /* 1100 */ + XML_RNGP_REF_NO_DEF, /* 1101 */ + XML_RNGP_REF_NO_NAME, /* 1102 */ + XML_RNGP_REF_NOT_EMPTY, /* 1103 */ + XML_RNGP_START_CHOICE_AND_INTERLEAVE, /* 1104 */ + XML_RNGP_START_CONTENT, /* 1105 */ + XML_RNGP_START_EMPTY, /* 1106 */ + XML_RNGP_START_MISSING, /* 1107 */ + XML_RNGP_TEXT_EXPECTED, /* 1108 */ + XML_RNGP_TEXT_HAS_CHILD, /* 1109 */ + XML_RNGP_TYPE_MISSING, /* 1110 */ + XML_RNGP_TYPE_NOT_FOUND, /* 1111 */ + XML_RNGP_TYPE_VALUE, /* 1112 */ + XML_RNGP_UNKNOWN_ATTRIBUTE, /* 1113 */ + XML_RNGP_UNKNOWN_COMBINE, /* 1114 */ + XML_RNGP_UNKNOWN_CONSTRUCT, /* 1115 */ + XML_RNGP_UNKNOWN_TYPE_LIB, /* 1116 */ + XML_RNGP_URI_FRAGMENT, /* 1117 */ + XML_RNGP_URI_NOT_ABSOLUTE, /* 1118 */ + XML_RNGP_VALUE_EMPTY, /* 1119 */ + XML_RNGP_VALUE_NO_CONTENT, /* 1120 */ + XML_RNGP_XMLNS_NAME, /* 1121 */ + XML_RNGP_XML_NS, /* 1122 */ + XML_XPATH_EXPRESSION_OK = 1200, + XML_XPATH_NUMBER_ERROR, /* 1201 */ + XML_XPATH_UNFINISHED_LITERAL_ERROR, /* 1202 */ + XML_XPATH_START_LITERAL_ERROR, /* 1203 */ + XML_XPATH_VARIABLE_REF_ERROR, /* 1204 */ + XML_XPATH_UNDEF_VARIABLE_ERROR, /* 1205 */ + XML_XPATH_INVALID_PREDICATE_ERROR, /* 1206 */ + XML_XPATH_EXPR_ERROR, /* 1207 */ + XML_XPATH_UNCLOSED_ERROR, /* 1208 */ + XML_XPATH_UNKNOWN_FUNC_ERROR, /* 1209 */ + XML_XPATH_INVALID_OPERAND, /* 1210 */ + XML_XPATH_INVALID_TYPE, /* 1211 */ + XML_XPATH_INVALID_ARITY, /* 1212 */ + XML_XPATH_INVALID_CTXT_SIZE, /* 1213 */ + XML_XPATH_INVALID_CTXT_POSITION, /* 1214 */ + XML_XPATH_MEMORY_ERROR, /* 1215 */ + XML_XPTR_SYNTAX_ERROR, /* 1216 */ + XML_XPTR_RESOURCE_ERROR, /* 1217 */ + XML_XPTR_SUB_RESOURCE_ERROR, /* 1218 */ + XML_XPATH_UNDEF_PREFIX_ERROR, /* 1219 */ + XML_XPATH_ENCODING_ERROR, /* 1220 */ + XML_XPATH_INVALID_CHAR_ERROR, /* 1221 */ + XML_TREE_INVALID_HEX = 1300, + XML_TREE_INVALID_DEC, /* 1301 */ + XML_TREE_UNTERMINATED_ENTITY, /* 1302 */ + XML_TREE_NOT_UTF8, /* 1303 */ + XML_SAVE_NOT_UTF8 = 1400, + XML_SAVE_CHAR_INVALID, /* 1401 */ + XML_SAVE_NO_DOCTYPE, /* 1402 */ + XML_SAVE_UNKNOWN_ENCODING, /* 1403 */ + XML_REGEXP_COMPILE_ERROR = 1450, + XML_IO_UNKNOWN = 1500, + XML_IO_EACCES, /* 1501 */ + XML_IO_EAGAIN, /* 1502 */ + XML_IO_EBADF, /* 1503 */ + XML_IO_EBADMSG, /* 1504 */ + XML_IO_EBUSY, /* 1505 */ + XML_IO_ECANCELED, /* 1506 */ + XML_IO_ECHILD, /* 1507 */ + XML_IO_EDEADLK, /* 1508 */ + XML_IO_EDOM, /* 1509 */ + XML_IO_EEXIST, /* 1510 */ + XML_IO_EFAULT, /* 1511 */ + XML_IO_EFBIG, /* 1512 */ + XML_IO_EINPROGRESS, /* 1513 */ + XML_IO_EINTR, /* 1514 */ + XML_IO_EINVAL, /* 1515 */ + XML_IO_EIO, /* 1516 */ + XML_IO_EISDIR, /* 1517 */ + XML_IO_EMFILE, /* 1518 */ + XML_IO_EMLINK, /* 1519 */ + XML_IO_EMSGSIZE, /* 1520 */ + XML_IO_ENAMETOOLONG, /* 1521 */ + XML_IO_ENFILE, /* 1522 */ + XML_IO_ENODEV, /* 1523 */ + XML_IO_ENOENT, /* 1524 */ + XML_IO_ENOEXEC, /* 1525 */ + XML_IO_ENOLCK, /* 1526 */ + XML_IO_ENOMEM, /* 1527 */ + XML_IO_ENOSPC, /* 1528 */ + XML_IO_ENOSYS, /* 1529 */ + XML_IO_ENOTDIR, /* 1530 */ + XML_IO_ENOTEMPTY, /* 1531 */ + XML_IO_ENOTSUP, /* 1532 */ + XML_IO_ENOTTY, /* 1533 */ + XML_IO_ENXIO, /* 1534 */ + XML_IO_EPERM, /* 1535 */ + XML_IO_EPIPE, /* 1536 */ + XML_IO_ERANGE, /* 1537 */ + XML_IO_EROFS, /* 1538 */ + XML_IO_ESPIPE, /* 1539 */ + XML_IO_ESRCH, /* 1540 */ + XML_IO_ETIMEDOUT, /* 1541 */ + XML_IO_EXDEV, /* 1542 */ + XML_IO_NETWORK_ATTEMPT, /* 1543 */ + XML_IO_ENCODER, /* 1544 */ + XML_IO_FLUSH, /* 1545 */ + XML_IO_WRITE, /* 1546 */ + XML_IO_NO_INPUT, /* 1547 */ + XML_IO_BUFFER_FULL, /* 1548 */ + XML_IO_LOAD_ERROR, /* 1549 */ + XML_IO_ENOTSOCK, /* 1550 */ + XML_IO_EISCONN, /* 1551 */ + XML_IO_ECONNREFUSED, /* 1552 */ + XML_IO_ENETUNREACH, /* 1553 */ + XML_IO_EADDRINUSE, /* 1554 */ + XML_IO_EALREADY, /* 1555 */ + XML_IO_EAFNOSUPPORT, /* 1556 */ + XML_IO_UNSUPPORTED_PROTOCOL, /* 1557 */ + XML_XINCLUDE_RECURSION=1600, + XML_XINCLUDE_PARSE_VALUE, /* 1601 */ + XML_XINCLUDE_ENTITY_DEF_MISMATCH, /* 1602 */ + XML_XINCLUDE_NO_HREF, /* 1603 */ + XML_XINCLUDE_NO_FALLBACK, /* 1604 */ + XML_XINCLUDE_HREF_URI, /* 1605 */ + XML_XINCLUDE_TEXT_FRAGMENT, /* 1606 */ + XML_XINCLUDE_TEXT_DOCUMENT, /* 1607 */ + XML_XINCLUDE_INVALID_CHAR, /* 1608 */ + XML_XINCLUDE_BUILD_FAILED, /* 1609 */ + XML_XINCLUDE_UNKNOWN_ENCODING, /* 1610 */ + XML_XINCLUDE_MULTIPLE_ROOT, /* 1611 */ + XML_XINCLUDE_XPTR_FAILED, /* 1612 */ + XML_XINCLUDE_XPTR_RESULT, /* 1613 */ + XML_XINCLUDE_INCLUDE_IN_INCLUDE, /* 1614 */ + XML_XINCLUDE_FALLBACKS_IN_INCLUDE, /* 1615 */ + XML_XINCLUDE_FALLBACK_NOT_IN_INCLUDE, /* 1616 */ + XML_XINCLUDE_DEPRECATED_NS, /* 1617 */ + XML_XINCLUDE_FRAGMENT_ID, /* 1618 */ + XML_CATALOG_MISSING_ATTR = 1650, + XML_CATALOG_ENTRY_BROKEN, /* 1651 */ + XML_CATALOG_PREFER_VALUE, /* 1652 */ + XML_CATALOG_NOT_CATALOG, /* 1653 */ + XML_CATALOG_RECURSION, /* 1654 */ + XML_SCHEMAP_PREFIX_UNDEFINED = 1700, + XML_SCHEMAP_ATTRFORMDEFAULT_VALUE, /* 1701 */ + XML_SCHEMAP_ATTRGRP_NONAME_NOREF, /* 1702 */ + XML_SCHEMAP_ATTR_NONAME_NOREF, /* 1703 */ + XML_SCHEMAP_COMPLEXTYPE_NONAME_NOREF, /* 1704 */ + XML_SCHEMAP_ELEMFORMDEFAULT_VALUE, /* 1705 */ + XML_SCHEMAP_ELEM_NONAME_NOREF, /* 1706 */ + XML_SCHEMAP_EXTENSION_NO_BASE, /* 1707 */ + XML_SCHEMAP_FACET_NO_VALUE, /* 1708 */ + XML_SCHEMAP_FAILED_BUILD_IMPORT, /* 1709 */ + XML_SCHEMAP_GROUP_NONAME_NOREF, /* 1710 */ + XML_SCHEMAP_IMPORT_NAMESPACE_NOT_URI, /* 1711 */ + XML_SCHEMAP_IMPORT_REDEFINE_NSNAME, /* 1712 */ + XML_SCHEMAP_IMPORT_SCHEMA_NOT_URI, /* 1713 */ + XML_SCHEMAP_INVALID_BOOLEAN, /* 1714 */ + XML_SCHEMAP_INVALID_ENUM, /* 1715 */ + XML_SCHEMAP_INVALID_FACET, /* 1716 */ + XML_SCHEMAP_INVALID_FACET_VALUE, /* 1717 */ + XML_SCHEMAP_INVALID_MAXOCCURS, /* 1718 */ + XML_SCHEMAP_INVALID_MINOCCURS, /* 1719 */ + XML_SCHEMAP_INVALID_REF_AND_SUBTYPE, /* 1720 */ + XML_SCHEMAP_INVALID_WHITE_SPACE, /* 1721 */ + XML_SCHEMAP_NOATTR_NOREF, /* 1722 */ + XML_SCHEMAP_NOTATION_NO_NAME, /* 1723 */ + XML_SCHEMAP_NOTYPE_NOREF, /* 1724 */ + XML_SCHEMAP_REF_AND_SUBTYPE, /* 1725 */ + XML_SCHEMAP_RESTRICTION_NONAME_NOREF, /* 1726 */ + XML_SCHEMAP_SIMPLETYPE_NONAME, /* 1727 */ + XML_SCHEMAP_TYPE_AND_SUBTYPE, /* 1728 */ + XML_SCHEMAP_UNKNOWN_ALL_CHILD, /* 1729 */ + XML_SCHEMAP_UNKNOWN_ANYATTRIBUTE_CHILD, /* 1730 */ + XML_SCHEMAP_UNKNOWN_ATTR_CHILD, /* 1731 */ + XML_SCHEMAP_UNKNOWN_ATTRGRP_CHILD, /* 1732 */ + XML_SCHEMAP_UNKNOWN_ATTRIBUTE_GROUP, /* 1733 */ + XML_SCHEMAP_UNKNOWN_BASE_TYPE, /* 1734 */ + XML_SCHEMAP_UNKNOWN_CHOICE_CHILD, /* 1735 */ + XML_SCHEMAP_UNKNOWN_COMPLEXCONTENT_CHILD, /* 1736 */ + XML_SCHEMAP_UNKNOWN_COMPLEXTYPE_CHILD, /* 1737 */ + XML_SCHEMAP_UNKNOWN_ELEM_CHILD, /* 1738 */ + XML_SCHEMAP_UNKNOWN_EXTENSION_CHILD, /* 1739 */ + XML_SCHEMAP_UNKNOWN_FACET_CHILD, /* 1740 */ + XML_SCHEMAP_UNKNOWN_FACET_TYPE, /* 1741 */ + XML_SCHEMAP_UNKNOWN_GROUP_CHILD, /* 1742 */ + XML_SCHEMAP_UNKNOWN_IMPORT_CHILD, /* 1743 */ + XML_SCHEMAP_UNKNOWN_LIST_CHILD, /* 1744 */ + XML_SCHEMAP_UNKNOWN_NOTATION_CHILD, /* 1745 */ + XML_SCHEMAP_UNKNOWN_PROCESSCONTENT_CHILD, /* 1746 */ + XML_SCHEMAP_UNKNOWN_REF, /* 1747 */ + XML_SCHEMAP_UNKNOWN_RESTRICTION_CHILD, /* 1748 */ + XML_SCHEMAP_UNKNOWN_SCHEMAS_CHILD, /* 1749 */ + XML_SCHEMAP_UNKNOWN_SEQUENCE_CHILD, /* 1750 */ + XML_SCHEMAP_UNKNOWN_SIMPLECONTENT_CHILD, /* 1751 */ + XML_SCHEMAP_UNKNOWN_SIMPLETYPE_CHILD, /* 1752 */ + XML_SCHEMAP_UNKNOWN_TYPE, /* 1753 */ + XML_SCHEMAP_UNKNOWN_UNION_CHILD, /* 1754 */ + XML_SCHEMAP_ELEM_DEFAULT_FIXED, /* 1755 */ + XML_SCHEMAP_REGEXP_INVALID, /* 1756 */ + XML_SCHEMAP_FAILED_LOAD, /* 1757 */ + XML_SCHEMAP_NOTHING_TO_PARSE, /* 1758 */ + XML_SCHEMAP_NOROOT, /* 1759 */ + XML_SCHEMAP_REDEFINED_GROUP, /* 1760 */ + XML_SCHEMAP_REDEFINED_TYPE, /* 1761 */ + XML_SCHEMAP_REDEFINED_ELEMENT, /* 1762 */ + XML_SCHEMAP_REDEFINED_ATTRGROUP, /* 1763 */ + XML_SCHEMAP_REDEFINED_ATTR, /* 1764 */ + XML_SCHEMAP_REDEFINED_NOTATION, /* 1765 */ + XML_SCHEMAP_FAILED_PARSE, /* 1766 */ + XML_SCHEMAP_UNKNOWN_PREFIX, /* 1767 */ + XML_SCHEMAP_DEF_AND_PREFIX, /* 1768 */ + XML_SCHEMAP_UNKNOWN_INCLUDE_CHILD, /* 1769 */ + XML_SCHEMAP_INCLUDE_SCHEMA_NOT_URI, /* 1770 */ + XML_SCHEMAP_INCLUDE_SCHEMA_NO_URI, /* 1771 */ + XML_SCHEMAP_NOT_SCHEMA, /* 1772 */ + XML_SCHEMAP_UNKNOWN_MEMBER_TYPE, /* 1773 */ + XML_SCHEMAP_INVALID_ATTR_USE, /* 1774 */ + XML_SCHEMAP_RECURSIVE, /* 1775 */ + XML_SCHEMAP_SUPERNUMEROUS_LIST_ITEM_TYPE, /* 1776 */ + XML_SCHEMAP_INVALID_ATTR_COMBINATION, /* 1777 */ + XML_SCHEMAP_INVALID_ATTR_INLINE_COMBINATION, /* 1778 */ + XML_SCHEMAP_MISSING_SIMPLETYPE_CHILD, /* 1779 */ + XML_SCHEMAP_INVALID_ATTR_NAME, /* 1780 */ + XML_SCHEMAP_REF_AND_CONTENT, /* 1781 */ + XML_SCHEMAP_CT_PROPS_CORRECT_1, /* 1782 */ + XML_SCHEMAP_CT_PROPS_CORRECT_2, /* 1783 */ + XML_SCHEMAP_CT_PROPS_CORRECT_3, /* 1784 */ + XML_SCHEMAP_CT_PROPS_CORRECT_4, /* 1785 */ + XML_SCHEMAP_CT_PROPS_CORRECT_5, /* 1786 */ + XML_SCHEMAP_DERIVATION_OK_RESTRICTION_1, /* 1787 */ + XML_SCHEMAP_DERIVATION_OK_RESTRICTION_2_1_1, /* 1788 */ + XML_SCHEMAP_DERIVATION_OK_RESTRICTION_2_1_2, /* 1789 */ + XML_SCHEMAP_DERIVATION_OK_RESTRICTION_2_2, /* 1790 */ + XML_SCHEMAP_DERIVATION_OK_RESTRICTION_3, /* 1791 */ + XML_SCHEMAP_WILDCARD_INVALID_NS_MEMBER, /* 1792 */ + XML_SCHEMAP_INTERSECTION_NOT_EXPRESSIBLE, /* 1793 */ + XML_SCHEMAP_UNION_NOT_EXPRESSIBLE, /* 1794 */ + XML_SCHEMAP_SRC_IMPORT_3_1, /* 1795 */ + XML_SCHEMAP_SRC_IMPORT_3_2, /* 1796 */ + XML_SCHEMAP_DERIVATION_OK_RESTRICTION_4_1, /* 1797 */ + XML_SCHEMAP_DERIVATION_OK_RESTRICTION_4_2, /* 1798 */ + XML_SCHEMAP_DERIVATION_OK_RESTRICTION_4_3, /* 1799 */ + XML_SCHEMAP_COS_CT_EXTENDS_1_3, /* 1800 */ + XML_SCHEMAV_NOROOT = 1801, + XML_SCHEMAV_UNDECLAREDELEM, /* 1802 */ + XML_SCHEMAV_NOTTOPLEVEL, /* 1803 */ + XML_SCHEMAV_MISSING, /* 1804 */ + XML_SCHEMAV_WRONGELEM, /* 1805 */ + XML_SCHEMAV_NOTYPE, /* 1806 */ + XML_SCHEMAV_NOROLLBACK, /* 1807 */ + XML_SCHEMAV_ISABSTRACT, /* 1808 */ + XML_SCHEMAV_NOTEMPTY, /* 1809 */ + XML_SCHEMAV_ELEMCONT, /* 1810 */ + XML_SCHEMAV_HAVEDEFAULT, /* 1811 */ + XML_SCHEMAV_NOTNILLABLE, /* 1812 */ + XML_SCHEMAV_EXTRACONTENT, /* 1813 */ + XML_SCHEMAV_INVALIDATTR, /* 1814 */ + XML_SCHEMAV_INVALIDELEM, /* 1815 */ + XML_SCHEMAV_NOTDETERMINIST, /* 1816 */ + XML_SCHEMAV_CONSTRUCT, /* 1817 */ + XML_SCHEMAV_INTERNAL, /* 1818 */ + XML_SCHEMAV_NOTSIMPLE, /* 1819 */ + XML_SCHEMAV_ATTRUNKNOWN, /* 1820 */ + XML_SCHEMAV_ATTRINVALID, /* 1821 */ + XML_SCHEMAV_VALUE, /* 1822 */ + XML_SCHEMAV_FACET, /* 1823 */ + XML_SCHEMAV_CVC_DATATYPE_VALID_1_2_1, /* 1824 */ + XML_SCHEMAV_CVC_DATATYPE_VALID_1_2_2, /* 1825 */ + XML_SCHEMAV_CVC_DATATYPE_VALID_1_2_3, /* 1826 */ + XML_SCHEMAV_CVC_TYPE_3_1_1, /* 1827 */ + XML_SCHEMAV_CVC_TYPE_3_1_2, /* 1828 */ + XML_SCHEMAV_CVC_FACET_VALID, /* 1829 */ + XML_SCHEMAV_CVC_LENGTH_VALID, /* 1830 */ + XML_SCHEMAV_CVC_MINLENGTH_VALID, /* 1831 */ + XML_SCHEMAV_CVC_MAXLENGTH_VALID, /* 1832 */ + XML_SCHEMAV_CVC_MININCLUSIVE_VALID, /* 1833 */ + XML_SCHEMAV_CVC_MAXINCLUSIVE_VALID, /* 1834 */ + XML_SCHEMAV_CVC_MINEXCLUSIVE_VALID, /* 1835 */ + XML_SCHEMAV_CVC_MAXEXCLUSIVE_VALID, /* 1836 */ + XML_SCHEMAV_CVC_TOTALDIGITS_VALID, /* 1837 */ + XML_SCHEMAV_CVC_FRACTIONDIGITS_VALID, /* 1838 */ + XML_SCHEMAV_CVC_PATTERN_VALID, /* 1839 */ + XML_SCHEMAV_CVC_ENUMERATION_VALID, /* 1840 */ + XML_SCHEMAV_CVC_COMPLEX_TYPE_2_1, /* 1841 */ + XML_SCHEMAV_CVC_COMPLEX_TYPE_2_2, /* 1842 */ + XML_SCHEMAV_CVC_COMPLEX_TYPE_2_3, /* 1843 */ + XML_SCHEMAV_CVC_COMPLEX_TYPE_2_4, /* 1844 */ + XML_SCHEMAV_CVC_ELT_1, /* 1845 */ + XML_SCHEMAV_CVC_ELT_2, /* 1846 */ + XML_SCHEMAV_CVC_ELT_3_1, /* 1847 */ + XML_SCHEMAV_CVC_ELT_3_2_1, /* 1848 */ + XML_SCHEMAV_CVC_ELT_3_2_2, /* 1849 */ + XML_SCHEMAV_CVC_ELT_4_1, /* 1850 */ + XML_SCHEMAV_CVC_ELT_4_2, /* 1851 */ + XML_SCHEMAV_CVC_ELT_4_3, /* 1852 */ + XML_SCHEMAV_CVC_ELT_5_1_1, /* 1853 */ + XML_SCHEMAV_CVC_ELT_5_1_2, /* 1854 */ + XML_SCHEMAV_CVC_ELT_5_2_1, /* 1855 */ + XML_SCHEMAV_CVC_ELT_5_2_2_1, /* 1856 */ + XML_SCHEMAV_CVC_ELT_5_2_2_2_1, /* 1857 */ + XML_SCHEMAV_CVC_ELT_5_2_2_2_2, /* 1858 */ + XML_SCHEMAV_CVC_ELT_6, /* 1859 */ + XML_SCHEMAV_CVC_ELT_7, /* 1860 */ + XML_SCHEMAV_CVC_ATTRIBUTE_1, /* 1861 */ + XML_SCHEMAV_CVC_ATTRIBUTE_2, /* 1862 */ + XML_SCHEMAV_CVC_ATTRIBUTE_3, /* 1863 */ + XML_SCHEMAV_CVC_ATTRIBUTE_4, /* 1864 */ + XML_SCHEMAV_CVC_COMPLEX_TYPE_3_1, /* 1865 */ + XML_SCHEMAV_CVC_COMPLEX_TYPE_3_2_1, /* 1866 */ + XML_SCHEMAV_CVC_COMPLEX_TYPE_3_2_2, /* 1867 */ + XML_SCHEMAV_CVC_COMPLEX_TYPE_4, /* 1868 */ + XML_SCHEMAV_CVC_COMPLEX_TYPE_5_1, /* 1869 */ + XML_SCHEMAV_CVC_COMPLEX_TYPE_5_2, /* 1870 */ + XML_SCHEMAV_ELEMENT_CONTENT, /* 1871 */ + XML_SCHEMAV_DOCUMENT_ELEMENT_MISSING, /* 1872 */ + XML_SCHEMAV_CVC_COMPLEX_TYPE_1, /* 1873 */ + XML_SCHEMAV_CVC_AU, /* 1874 */ + XML_SCHEMAV_CVC_TYPE_1, /* 1875 */ + XML_SCHEMAV_CVC_TYPE_2, /* 1876 */ + XML_SCHEMAV_CVC_IDC, /* 1877 */ + XML_SCHEMAV_CVC_WILDCARD, /* 1878 */ + XML_SCHEMAV_MISC, /* 1879 */ + XML_XPTR_UNKNOWN_SCHEME = 1900, + XML_XPTR_CHILDSEQ_START, /* 1901 */ + XML_XPTR_EVAL_FAILED, /* 1902 */ + XML_XPTR_EXTRA_OBJECTS, /* 1903 */ + XML_C14N_CREATE_CTXT = 1950, + XML_C14N_REQUIRES_UTF8, /* 1951 */ + XML_C14N_CREATE_STACK, /* 1952 */ + XML_C14N_INVALID_NODE, /* 1953 */ + XML_C14N_UNKNOW_NODE, /* 1954 */ + XML_C14N_RELATIVE_NAMESPACE, /* 1955 */ + XML_FTP_PASV_ANSWER = 2000, + XML_FTP_EPSV_ANSWER, /* 2001 */ + XML_FTP_ACCNT, /* 2002 */ + XML_FTP_URL_SYNTAX, /* 2003 */ + XML_HTTP_URL_SYNTAX = 2020, + XML_HTTP_USE_IP, /* 2021 */ + XML_HTTP_UNKNOWN_HOST, /* 2022 */ + XML_SCHEMAP_SRC_SIMPLE_TYPE_1 = 3000, + XML_SCHEMAP_SRC_SIMPLE_TYPE_2, /* 3001 */ + XML_SCHEMAP_SRC_SIMPLE_TYPE_3, /* 3002 */ + XML_SCHEMAP_SRC_SIMPLE_TYPE_4, /* 3003 */ + XML_SCHEMAP_SRC_RESOLVE, /* 3004 */ + XML_SCHEMAP_SRC_RESTRICTION_BASE_OR_SIMPLETYPE, /* 3005 */ + XML_SCHEMAP_SRC_LIST_ITEMTYPE_OR_SIMPLETYPE, /* 3006 */ + XML_SCHEMAP_SRC_UNION_MEMBERTYPES_OR_SIMPLETYPES, /* 3007 */ + XML_SCHEMAP_ST_PROPS_CORRECT_1, /* 3008 */ + XML_SCHEMAP_ST_PROPS_CORRECT_2, /* 3009 */ + XML_SCHEMAP_ST_PROPS_CORRECT_3, /* 3010 */ + XML_SCHEMAP_COS_ST_RESTRICTS_1_1, /* 3011 */ + XML_SCHEMAP_COS_ST_RESTRICTS_1_2, /* 3012 */ + XML_SCHEMAP_COS_ST_RESTRICTS_1_3_1, /* 3013 */ + XML_SCHEMAP_COS_ST_RESTRICTS_1_3_2, /* 3014 */ + XML_SCHEMAP_COS_ST_RESTRICTS_2_1, /* 3015 */ + XML_SCHEMAP_COS_ST_RESTRICTS_2_3_1_1, /* 3016 */ + XML_SCHEMAP_COS_ST_RESTRICTS_2_3_1_2, /* 3017 */ + XML_SCHEMAP_COS_ST_RESTRICTS_2_3_2_1, /* 3018 */ + XML_SCHEMAP_COS_ST_RESTRICTS_2_3_2_2, /* 3019 */ + XML_SCHEMAP_COS_ST_RESTRICTS_2_3_2_3, /* 3020 */ + XML_SCHEMAP_COS_ST_RESTRICTS_2_3_2_4, /* 3021 */ + XML_SCHEMAP_COS_ST_RESTRICTS_2_3_2_5, /* 3022 */ + XML_SCHEMAP_COS_ST_RESTRICTS_3_1, /* 3023 */ + XML_SCHEMAP_COS_ST_RESTRICTS_3_3_1, /* 3024 */ + XML_SCHEMAP_COS_ST_RESTRICTS_3_3_1_2, /* 3025 */ + XML_SCHEMAP_COS_ST_RESTRICTS_3_3_2_2, /* 3026 */ + XML_SCHEMAP_COS_ST_RESTRICTS_3_3_2_1, /* 3027 */ + XML_SCHEMAP_COS_ST_RESTRICTS_3_3_2_3, /* 3028 */ + XML_SCHEMAP_COS_ST_RESTRICTS_3_3_2_4, /* 3029 */ + XML_SCHEMAP_COS_ST_RESTRICTS_3_3_2_5, /* 3030 */ + XML_SCHEMAP_COS_ST_DERIVED_OK_2_1, /* 3031 */ + XML_SCHEMAP_COS_ST_DERIVED_OK_2_2, /* 3032 */ + XML_SCHEMAP_S4S_ELEM_NOT_ALLOWED, /* 3033 */ + XML_SCHEMAP_S4S_ELEM_MISSING, /* 3034 */ + XML_SCHEMAP_S4S_ATTR_NOT_ALLOWED, /* 3035 */ + XML_SCHEMAP_S4S_ATTR_MISSING, /* 3036 */ + XML_SCHEMAP_S4S_ATTR_INVALID_VALUE, /* 3037 */ + XML_SCHEMAP_SRC_ELEMENT_1, /* 3038 */ + XML_SCHEMAP_SRC_ELEMENT_2_1, /* 3039 */ + XML_SCHEMAP_SRC_ELEMENT_2_2, /* 3040 */ + XML_SCHEMAP_SRC_ELEMENT_3, /* 3041 */ + XML_SCHEMAP_P_PROPS_CORRECT_1, /* 3042 */ + XML_SCHEMAP_P_PROPS_CORRECT_2_1, /* 3043 */ + XML_SCHEMAP_P_PROPS_CORRECT_2_2, /* 3044 */ + XML_SCHEMAP_E_PROPS_CORRECT_2, /* 3045 */ + XML_SCHEMAP_E_PROPS_CORRECT_3, /* 3046 */ + XML_SCHEMAP_E_PROPS_CORRECT_4, /* 3047 */ + XML_SCHEMAP_E_PROPS_CORRECT_5, /* 3048 */ + XML_SCHEMAP_E_PROPS_CORRECT_6, /* 3049 */ + XML_SCHEMAP_SRC_INCLUDE, /* 3050 */ + XML_SCHEMAP_SRC_ATTRIBUTE_1, /* 3051 */ + XML_SCHEMAP_SRC_ATTRIBUTE_2, /* 3052 */ + XML_SCHEMAP_SRC_ATTRIBUTE_3_1, /* 3053 */ + XML_SCHEMAP_SRC_ATTRIBUTE_3_2, /* 3054 */ + XML_SCHEMAP_SRC_ATTRIBUTE_4, /* 3055 */ + XML_SCHEMAP_NO_XMLNS, /* 3056 */ + XML_SCHEMAP_NO_XSI, /* 3057 */ + XML_SCHEMAP_COS_VALID_DEFAULT_1, /* 3058 */ + XML_SCHEMAP_COS_VALID_DEFAULT_2_1, /* 3059 */ + XML_SCHEMAP_COS_VALID_DEFAULT_2_2_1, /* 3060 */ + XML_SCHEMAP_COS_VALID_DEFAULT_2_2_2, /* 3061 */ + XML_SCHEMAP_CVC_SIMPLE_TYPE, /* 3062 */ + XML_SCHEMAP_COS_CT_EXTENDS_1_1, /* 3063 */ + XML_SCHEMAP_SRC_IMPORT_1_1, /* 3064 */ + XML_SCHEMAP_SRC_IMPORT_1_2, /* 3065 */ + XML_SCHEMAP_SRC_IMPORT_2, /* 3066 */ + XML_SCHEMAP_SRC_IMPORT_2_1, /* 3067 */ + XML_SCHEMAP_SRC_IMPORT_2_2, /* 3068 */ + XML_SCHEMAP_INTERNAL, /* 3069 non-W3C */ + XML_SCHEMAP_NOT_DETERMINISTIC, /* 3070 non-W3C */ + XML_SCHEMAP_SRC_ATTRIBUTE_GROUP_1, /* 3071 */ + XML_SCHEMAP_SRC_ATTRIBUTE_GROUP_2, /* 3072 */ + XML_SCHEMAP_SRC_ATTRIBUTE_GROUP_3, /* 3073 */ + XML_SCHEMAP_MG_PROPS_CORRECT_1, /* 3074 */ + XML_SCHEMAP_MG_PROPS_CORRECT_2, /* 3075 */ + XML_SCHEMAP_SRC_CT_1, /* 3076 */ + XML_SCHEMAP_DERIVATION_OK_RESTRICTION_2_1_3, /* 3077 */ + XML_SCHEMAP_AU_PROPS_CORRECT_2, /* 3078 */ + XML_SCHEMAP_A_PROPS_CORRECT_2, /* 3079 */ + XML_SCHEMAP_C_PROPS_CORRECT, /* 3080 */ + XML_SCHEMAP_SRC_REDEFINE, /* 3081 */ + XML_SCHEMAP_SRC_IMPORT, /* 3082 */ + XML_SCHEMAP_WARN_SKIP_SCHEMA, /* 3083 */ + XML_SCHEMAP_WARN_UNLOCATED_SCHEMA, /* 3084 */ + XML_SCHEMAP_WARN_ATTR_REDECL_PROH, /* 3085 */ + XML_SCHEMAP_WARN_ATTR_POINTLESS_PROH, /* 3085 */ + XML_SCHEMAP_AG_PROPS_CORRECT, /* 3086 */ + XML_SCHEMAP_COS_CT_EXTENDS_1_2, /* 3087 */ + XML_SCHEMAP_AU_PROPS_CORRECT, /* 3088 */ + XML_SCHEMAP_A_PROPS_CORRECT_3, /* 3089 */ + XML_SCHEMAP_COS_ALL_LIMITED, /* 3090 */ + XML_SCHEMATRONV_ASSERT = 4000, /* 4000 */ + XML_SCHEMATRONV_REPORT, + XML_MODULE_OPEN = 4900, /* 4900 */ + XML_MODULE_CLOSE, /* 4901 */ + XML_CHECK_FOUND_ELEMENT = 5000, + XML_CHECK_FOUND_ATTRIBUTE, /* 5001 */ + XML_CHECK_FOUND_TEXT, /* 5002 */ + XML_CHECK_FOUND_CDATA, /* 5003 */ + XML_CHECK_FOUND_ENTITYREF, /* 5004 */ + XML_CHECK_FOUND_ENTITY, /* 5005 */ + XML_CHECK_FOUND_PI, /* 5006 */ + XML_CHECK_FOUND_COMMENT, /* 5007 */ + XML_CHECK_FOUND_DOCTYPE, /* 5008 */ + XML_CHECK_FOUND_FRAGMENT, /* 5009 */ + XML_CHECK_FOUND_NOTATION, /* 5010 */ + XML_CHECK_UNKNOWN_NODE, /* 5011 */ + XML_CHECK_ENTITY_TYPE, /* 5012 */ + XML_CHECK_NO_PARENT, /* 5013 */ + XML_CHECK_NO_DOC, /* 5014 */ + XML_CHECK_NO_NAME, /* 5015 */ + XML_CHECK_NO_ELEM, /* 5016 */ + XML_CHECK_WRONG_DOC, /* 5017 */ + XML_CHECK_NO_PREV, /* 5018 */ + XML_CHECK_WRONG_PREV, /* 5019 */ + XML_CHECK_NO_NEXT, /* 5020 */ + XML_CHECK_WRONG_NEXT, /* 5021 */ + XML_CHECK_NOT_DTD, /* 5022 */ + XML_CHECK_NOT_ATTR, /* 5023 */ + XML_CHECK_NOT_ATTR_DECL, /* 5024 */ + XML_CHECK_NOT_ELEM_DECL, /* 5025 */ + XML_CHECK_NOT_ENTITY_DECL, /* 5026 */ + XML_CHECK_NOT_NS_DECL, /* 5027 */ + XML_CHECK_NO_HREF, /* 5028 */ + XML_CHECK_WRONG_PARENT,/* 5029 */ + XML_CHECK_NS_SCOPE, /* 5030 */ + XML_CHECK_NS_ANCESTOR, /* 5031 */ + XML_CHECK_NOT_UTF8, /* 5032 */ + XML_CHECK_NO_DICT, /* 5033 */ + XML_CHECK_NOT_NCNAME, /* 5034 */ + XML_CHECK_OUTSIDE_DICT, /* 5035 */ + XML_CHECK_WRONG_NAME, /* 5036 */ + XML_CHECK_NAME_NOT_NULL, /* 5037 */ + XML_I18N_NO_NAME = 6000, + XML_I18N_NO_HANDLER, /* 6001 */ + XML_I18N_EXCESS_HANDLER, /* 6002 */ + XML_I18N_CONV_FAILED, /* 6003 */ + XML_I18N_NO_OUTPUT, /* 6004 */ + XML_BUF_OVERFLOW = 7000 +} xmlParserErrors; + +/** + * xmlGenericErrorFunc: + * @ctx: a parsing context + * @msg: the message + * @...: the extra arguments of the varargs to format the message + * + * Signature of the function to use when there is an error and + * no parsing or validity context available . + */ +typedef void (*xmlGenericErrorFunc) (void *ctx, + const char *msg, + ...) LIBXML_ATTR_FORMAT(2,3); +/** + * xmlStructuredErrorFunc: + * @userData: user provided data for the error callback + * @error: the error being raised. + * + * Signature of the function to use when there is an error and + * the module handles the new error reporting mechanism. + */ +typedef void (*xmlStructuredErrorFunc) (void *userData, const xmlError *error); + +/** DOC_DISABLE */ +#define XML_GLOBALS_ERROR \ + XML_OP(xmlLastError, xmlError, XML_DEPRECATED) \ + XML_OP(xmlGenericError, xmlGenericErrorFunc, XML_NO_ATTR) \ + XML_OP(xmlGenericErrorContext, void *, XML_NO_ATTR) \ + XML_OP(xmlStructuredError, xmlStructuredErrorFunc, XML_NO_ATTR) \ + XML_OP(xmlStructuredErrorContext, void *, XML_NO_ATTR) + +#define XML_OP XML_DECLARE_GLOBAL +XML_GLOBALS_ERROR +#undef XML_OP + +#if defined(LIBXML_THREAD_ENABLED) && !defined(XML_GLOBALS_NO_REDEFINITION) + #define xmlLastError XML_GLOBAL_MACRO(xmlLastError) + #define xmlGenericError XML_GLOBAL_MACRO(xmlGenericError) + #define xmlGenericErrorContext XML_GLOBAL_MACRO(xmlGenericErrorContext) + #define xmlStructuredError XML_GLOBAL_MACRO(xmlStructuredError) + #define xmlStructuredErrorContext XML_GLOBAL_MACRO(xmlStructuredErrorContext) +#endif +/** DOC_ENABLE */ + +/* + * Use the following function to reset the two global variables + * xmlGenericError and xmlGenericErrorContext. + */ +XMLPUBFUN void + xmlSetGenericErrorFunc (void *ctx, + xmlGenericErrorFunc handler); +XML_DEPRECATED +XMLPUBFUN void + xmlThrDefSetGenericErrorFunc(void *ctx, + xmlGenericErrorFunc handler); +XML_DEPRECATED +XMLPUBFUN void + initGenericErrorDefaultFunc (xmlGenericErrorFunc *handler); + +XMLPUBFUN void + xmlSetStructuredErrorFunc (void *ctx, + xmlStructuredErrorFunc handler); +XML_DEPRECATED +XMLPUBFUN void + xmlThrDefSetStructuredErrorFunc(void *ctx, + xmlStructuredErrorFunc handler); +/* + * Default message routines used by SAX and Valid context for error + * and warning reporting. + */ +XMLPUBFUN void + xmlParserError (void *ctx, + const char *msg, + ...) LIBXML_ATTR_FORMAT(2,3); +XMLPUBFUN void + xmlParserWarning (void *ctx, + const char *msg, + ...) LIBXML_ATTR_FORMAT(2,3); +XMLPUBFUN void + xmlParserValidityError (void *ctx, + const char *msg, + ...) LIBXML_ATTR_FORMAT(2,3); +XMLPUBFUN void + xmlParserValidityWarning (void *ctx, + const char *msg, + ...) LIBXML_ATTR_FORMAT(2,3); +/** DOC_DISABLE */ +struct _xmlParserInput; +/** DOC_ENABLE */ +XMLPUBFUN void + xmlParserPrintFileInfo (struct _xmlParserInput *input); +XMLPUBFUN void + xmlParserPrintFileContext (struct _xmlParserInput *input); +XMLPUBFUN void +xmlFormatError (const xmlError *err, + xmlGenericErrorFunc channel, + void *data); + +/* + * Extended error information routines + */ +XMLPUBFUN const xmlError * + xmlGetLastError (void); +XMLPUBFUN void + xmlResetLastError (void); +XMLPUBFUN const xmlError * + xmlCtxtGetLastError (void *ctx); +XMLPUBFUN void + xmlCtxtResetLastError (void *ctx); +XMLPUBFUN void + xmlResetError (xmlErrorPtr err); +XMLPUBFUN int + xmlCopyError (const xmlError *from, + xmlErrorPtr to); + +#ifdef __cplusplus +} +#endif +#endif /* __XML_ERROR_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlexports.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlexports.h new file mode 100644 index 00000000..3c1d83f4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlexports.h @@ -0,0 +1,146 @@ +/* + * Summary: macros for marking symbols as exportable/importable. + * Description: macros for marking symbols as exportable/importable. + * + * Copy: See Copyright for the status of this software. + */ + +#ifndef __XML_EXPORTS_H__ +#define __XML_EXPORTS_H__ + +/** DOC_DISABLE */ + +/* + * Symbol visibility + */ + +#if defined(_WIN32) || defined(__CYGWIN__) + #ifdef LIBXML_STATIC + #define XMLPUBLIC + #elif defined(IN_LIBXML) + #define XMLPUBLIC __declspec(dllexport) + #else + #define XMLPUBLIC __declspec(dllimport) + #endif +#else /* not Windows */ + #define XMLPUBLIC +#endif /* platform switch */ + +#define XMLPUBFUN XMLPUBLIC + +#define XMLPUBVAR XMLPUBLIC extern + +/* Compatibility */ +#define XMLCALL +#define XMLCDECL +#ifndef LIBXML_DLL_IMPORT + #define LIBXML_DLL_IMPORT XMLPUBVAR +#endif + +/* + * Attributes + */ + +#ifndef ATTRIBUTE_UNUSED + #if __GNUC__ * 100 + __GNUC_MINOR__ >= 207 || defined(__clang__) + #define ATTRIBUTE_UNUSED __attribute__((unused)) + #else + #define ATTRIBUTE_UNUSED + #endif +#endif + +#if !defined(__clang__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 403) + #define LIBXML_ATTR_ALLOC_SIZE(x) __attribute__((alloc_size(x))) +#else + #define LIBXML_ATTR_ALLOC_SIZE(x) +#endif + +#if __GNUC__ * 100 + __GNUC_MINOR__ >= 303 + #define LIBXML_ATTR_FORMAT(fmt,args) \ + __attribute__((__format__(__printf__,fmt,args))) +#else + #define LIBXML_ATTR_FORMAT(fmt,args) +#endif + +#ifndef XML_DEPRECATED + #if defined(IN_LIBXML) + #define XML_DEPRECATED + #elif __GNUC__ * 100 + __GNUC_MINOR__ >= 301 + #define XML_DEPRECATED __attribute__((deprecated)) + #elif defined(_MSC_VER) && _MSC_VER >= 1400 + /* Available since Visual Studio 2005 */ + #define XML_DEPRECATED __declspec(deprecated) + #else + #define XML_DEPRECATED + #endif +#endif + +/* + * Warnings pragmas, should be moved from public headers + */ + +#if defined(__LCC__) + + #define XML_IGNORE_FPTR_CAST_WARNINGS + #define XML_POP_WARNINGS \ + _Pragma("diag_default 1215") + +#elif defined(__clang__) || (__GNUC__ * 100 + __GNUC_MINOR__ >= 406) + + #if defined(__clang__) || (__GNUC__ * 100 + __GNUC_MINOR__ >= 800) + #define XML_IGNORE_FPTR_CAST_WARNINGS \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wpedantic\"") \ + _Pragma("GCC diagnostic ignored \"-Wcast-function-type\"") + #else + #define XML_IGNORE_FPTR_CAST_WARNINGS \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wpedantic\"") + #endif + #define XML_POP_WARNINGS \ + _Pragma("GCC diagnostic pop") + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + + #define XML_IGNORE_FPTR_CAST_WARNINGS __pragma(warning(push)) + #define XML_POP_WARNINGS __pragma(warning(pop)) + +#else + + #define XML_IGNORE_FPTR_CAST_WARNINGS + #define XML_POP_WARNINGS + +#endif + +/* + * Accessors for globals + */ + +#define XML_NO_ATTR + +#ifdef LIBXML_THREAD_ENABLED + #define XML_DECLARE_GLOBAL(name, type, attrs) \ + attrs XMLPUBFUN type *__##name(void); + #define XML_GLOBAL_MACRO(name) (*__##name()) +#else + #define XML_DECLARE_GLOBAL(name, type, attrs) \ + attrs XMLPUBVAR type name; +#endif + +/* + * Originally declared in xmlversion.h which is generated + */ + +#ifdef __cplusplus +extern "C" { +#endif + +XMLPUBFUN void xmlCheckVersion(int version); + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_EXPORTS_H__ */ + + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlmemory.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlmemory.h new file mode 100644 index 00000000..1de3e9fc --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlmemory.h @@ -0,0 +1,188 @@ +/* + * Summary: interface for the memory allocator + * Description: provides interfaces for the memory allocator, + * including debugging capabilities. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + + +#ifndef __DEBUG_MEMORY_ALLOC__ +#define __DEBUG_MEMORY_ALLOC__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The XML memory wrapper support 4 basic overloadable functions. + */ +/** + * xmlFreeFunc: + * @mem: an already allocated block of memory + * + * Signature for a free() implementation. + */ +typedef void (*xmlFreeFunc)(void *mem); +/** + * xmlMallocFunc: + * @size: the size requested in bytes + * + * Signature for a malloc() implementation. + * + * Returns a pointer to the newly allocated block or NULL in case of error. + */ +typedef void *(LIBXML_ATTR_ALLOC_SIZE(1) *xmlMallocFunc)(size_t size); + +/** + * xmlReallocFunc: + * @mem: an already allocated block of memory + * @size: the new size requested in bytes + * + * Signature for a realloc() implementation. + * + * Returns a pointer to the newly reallocated block or NULL in case of error. + */ +typedef void *(*xmlReallocFunc)(void *mem, size_t size); + +/** + * xmlStrdupFunc: + * @str: a zero terminated string + * + * Signature for an strdup() implementation. + * + * Returns the copy of the string or NULL in case of error. + */ +typedef char *(*xmlStrdupFunc)(const char *str); + +/* + * In general the memory allocation entry points are not kept + * thread specific but this can be overridden by LIBXML_THREAD_ALLOC_ENABLED + * - xmlMalloc + * - xmlMallocAtomic + * - xmlRealloc + * - xmlMemStrdup + * - xmlFree + */ +/** DOC_DISABLE */ +#ifdef LIBXML_THREAD_ALLOC_ENABLED + #define XML_GLOBALS_ALLOC \ + XML_OP(xmlMalloc, xmlMallocFunc, XML_NO_ATTR) \ + XML_OP(xmlMallocAtomic, xmlMallocFunc, XML_NO_ATTR) \ + XML_OP(xmlRealloc, xmlReallocFunc, XML_NO_ATTR) \ + XML_OP(xmlFree, xmlFreeFunc, XML_NO_ATTR) \ + XML_OP(xmlMemStrdup, xmlStrdupFunc, XML_NO_ATTR) + #define XML_OP XML_DECLARE_GLOBAL + XML_GLOBALS_ALLOC + #undef XML_OP + #if defined(LIBXML_THREAD_ENABLED) && !defined(XML_GLOBALS_NO_REDEFINITION) + #define xmlMalloc XML_GLOBAL_MACRO(xmlMalloc) + #define xmlMallocAtomic XML_GLOBAL_MACRO(xmlMallocAtomic) + #define xmlRealloc XML_GLOBAL_MACRO(xmlRealloc) + #define xmlFree XML_GLOBAL_MACRO(xmlFree) + #define xmlMemStrdup XML_GLOBAL_MACRO(xmlMemStrdup) + #endif +#else + #define XML_GLOBALS_ALLOC +/** DOC_ENABLE */ + XMLPUBVAR xmlMallocFunc xmlMalloc; + XMLPUBVAR xmlMallocFunc xmlMallocAtomic; + XMLPUBVAR xmlReallocFunc xmlRealloc; + XMLPUBVAR xmlFreeFunc xmlFree; + XMLPUBVAR xmlStrdupFunc xmlMemStrdup; +#endif + +/* + * The way to overload the existing functions. + * The xmlGc function have an extra entry for atomic block + * allocations useful for garbage collected memory allocators + */ +XMLPUBFUN int + xmlMemSetup (xmlFreeFunc freeFunc, + xmlMallocFunc mallocFunc, + xmlReallocFunc reallocFunc, + xmlStrdupFunc strdupFunc); +XMLPUBFUN int + xmlMemGet (xmlFreeFunc *freeFunc, + xmlMallocFunc *mallocFunc, + xmlReallocFunc *reallocFunc, + xmlStrdupFunc *strdupFunc); +XMLPUBFUN int + xmlGcMemSetup (xmlFreeFunc freeFunc, + xmlMallocFunc mallocFunc, + xmlMallocFunc mallocAtomicFunc, + xmlReallocFunc reallocFunc, + xmlStrdupFunc strdupFunc); +XMLPUBFUN int + xmlGcMemGet (xmlFreeFunc *freeFunc, + xmlMallocFunc *mallocFunc, + xmlMallocFunc *mallocAtomicFunc, + xmlReallocFunc *reallocFunc, + xmlStrdupFunc *strdupFunc); + +/* + * Initialization of the memory layer. + */ +XML_DEPRECATED +XMLPUBFUN int + xmlInitMemory (void); + +/* + * Cleanup of the memory layer. + */ +XML_DEPRECATED +XMLPUBFUN void + xmlCleanupMemory (void); +/* + * These are specific to the XML debug memory wrapper. + */ +XMLPUBFUN size_t + xmlMemSize (void *ptr); +XMLPUBFUN int + xmlMemUsed (void); +XMLPUBFUN int + xmlMemBlocks (void); +XML_DEPRECATED +XMLPUBFUN void + xmlMemDisplay (FILE *fp); +XML_DEPRECATED +XMLPUBFUN void + xmlMemDisplayLast(FILE *fp, long nbBytes); +XML_DEPRECATED +XMLPUBFUN void + xmlMemShow (FILE *fp, int nr); +XML_DEPRECATED +XMLPUBFUN void + xmlMemoryDump (void); +XMLPUBFUN void * + xmlMemMalloc (size_t size) LIBXML_ATTR_ALLOC_SIZE(1); +XMLPUBFUN void * + xmlMemRealloc (void *ptr,size_t size); +XMLPUBFUN void + xmlMemFree (void *ptr); +XMLPUBFUN char * + xmlMemoryStrdup (const char *str); +XML_DEPRECATED +XMLPUBFUN void * + xmlMallocLoc (size_t size, const char *file, int line) LIBXML_ATTR_ALLOC_SIZE(1); +XML_DEPRECATED +XMLPUBFUN void * + xmlReallocLoc (void *ptr, size_t size, const char *file, int line); +XML_DEPRECATED +XMLPUBFUN void * + xmlMallocAtomicLoc (size_t size, const char *file, int line) LIBXML_ATTR_ALLOC_SIZE(1); +XML_DEPRECATED +XMLPUBFUN char * + xmlMemStrdupLoc (const char *str, const char *file, int line); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __DEBUG_MEMORY_ALLOC__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlmodule.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlmodule.h new file mode 100644 index 00000000..279986c1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlmodule.h @@ -0,0 +1,57 @@ +/* + * Summary: dynamic module loading + * Description: basic API for dynamic module loading, used by + * libexslt added in 2.6.17 + * + * Copy: See Copyright for the status of this software. + * + * Author: Joel W. Reed + */ + +#ifndef __XML_MODULE_H__ +#define __XML_MODULE_H__ + +#include + +#ifdef LIBXML_MODULES_ENABLED + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * xmlModulePtr: + * + * A handle to a dynamically loaded module + */ +typedef struct _xmlModule xmlModule; +typedef xmlModule *xmlModulePtr; + +/** + * xmlModuleOption: + * + * enumeration of options that can be passed down to xmlModuleOpen() + */ +typedef enum { + XML_MODULE_LAZY = 1, /* lazy binding */ + XML_MODULE_LOCAL= 2 /* local binding */ +} xmlModuleOption; + +XMLPUBFUN xmlModulePtr xmlModuleOpen (const char *filename, + int options); + +XMLPUBFUN int xmlModuleSymbol (xmlModulePtr module, + const char* name, + void **result); + +XMLPUBFUN int xmlModuleClose (xmlModulePtr module); + +XMLPUBFUN int xmlModuleFree (xmlModulePtr module); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_MODULES_ENABLED */ + +#endif /*__XML_MODULE_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlreader.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlreader.h new file mode 100644 index 00000000..5d4fc5d5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlreader.h @@ -0,0 +1,436 @@ +/* + * Summary: the XMLReader implementation + * Description: API of the XML streaming API based on C# interfaces. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XMLREADER_H__ +#define __XML_XMLREADER_H__ + +#include +#include +#include +#include +#ifdef LIBXML_SCHEMAS_ENABLED +#include +#include +#endif +/* for compatibility */ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * xmlParserSeverities: + * + * How severe an error callback is when the per-reader error callback API + * is used. + */ +typedef enum { + XML_PARSER_SEVERITY_VALIDITY_WARNING = 1, + XML_PARSER_SEVERITY_VALIDITY_ERROR = 2, + XML_PARSER_SEVERITY_WARNING = 3, + XML_PARSER_SEVERITY_ERROR = 4 +} xmlParserSeverities; + +#ifdef LIBXML_READER_ENABLED + +/** + * xmlTextReaderMode: + * + * Internal state values for the reader. + */ +typedef enum { + XML_TEXTREADER_MODE_INITIAL = 0, + XML_TEXTREADER_MODE_INTERACTIVE = 1, + XML_TEXTREADER_MODE_ERROR = 2, + XML_TEXTREADER_MODE_EOF =3, + XML_TEXTREADER_MODE_CLOSED = 4, + XML_TEXTREADER_MODE_READING = 5 +} xmlTextReaderMode; + +/** + * xmlParserProperties: + * + * Some common options to use with xmlTextReaderSetParserProp, but it + * is better to use xmlParserOption and the xmlReaderNewxxx and + * xmlReaderForxxx APIs now. + */ +typedef enum { + XML_PARSER_LOADDTD = 1, + XML_PARSER_DEFAULTATTRS = 2, + XML_PARSER_VALIDATE = 3, + XML_PARSER_SUBST_ENTITIES = 4 +} xmlParserProperties; + +/** + * xmlReaderTypes: + * + * Predefined constants for the different types of nodes. + */ +typedef enum { + XML_READER_TYPE_NONE = 0, + XML_READER_TYPE_ELEMENT = 1, + XML_READER_TYPE_ATTRIBUTE = 2, + XML_READER_TYPE_TEXT = 3, + XML_READER_TYPE_CDATA = 4, + XML_READER_TYPE_ENTITY_REFERENCE = 5, + XML_READER_TYPE_ENTITY = 6, + XML_READER_TYPE_PROCESSING_INSTRUCTION = 7, + XML_READER_TYPE_COMMENT = 8, + XML_READER_TYPE_DOCUMENT = 9, + XML_READER_TYPE_DOCUMENT_TYPE = 10, + XML_READER_TYPE_DOCUMENT_FRAGMENT = 11, + XML_READER_TYPE_NOTATION = 12, + XML_READER_TYPE_WHITESPACE = 13, + XML_READER_TYPE_SIGNIFICANT_WHITESPACE = 14, + XML_READER_TYPE_END_ELEMENT = 15, + XML_READER_TYPE_END_ENTITY = 16, + XML_READER_TYPE_XML_DECLARATION = 17 +} xmlReaderTypes; + +/** + * xmlTextReader: + * + * Structure for an xmlReader context. + */ +typedef struct _xmlTextReader xmlTextReader; + +/** + * xmlTextReaderPtr: + * + * Pointer to an xmlReader context. + */ +typedef xmlTextReader *xmlTextReaderPtr; + +/* + * Constructors & Destructor + */ +XMLPUBFUN xmlTextReaderPtr + xmlNewTextReader (xmlParserInputBufferPtr input, + const char *URI); +XMLPUBFUN xmlTextReaderPtr + xmlNewTextReaderFilename(const char *URI); + +XMLPUBFUN void + xmlFreeTextReader (xmlTextReaderPtr reader); + +XMLPUBFUN int + xmlTextReaderSetup(xmlTextReaderPtr reader, + xmlParserInputBufferPtr input, const char *URL, + const char *encoding, int options); +XMLPUBFUN void + xmlTextReaderSetMaxAmplification(xmlTextReaderPtr reader, + unsigned maxAmpl); +XMLPUBFUN const xmlError * + xmlTextReaderGetLastError(xmlTextReaderPtr reader); + +/* + * Iterators + */ +XMLPUBFUN int + xmlTextReaderRead (xmlTextReaderPtr reader); + +#ifdef LIBXML_WRITER_ENABLED +XMLPUBFUN xmlChar * + xmlTextReaderReadInnerXml(xmlTextReaderPtr reader); + +XMLPUBFUN xmlChar * + xmlTextReaderReadOuterXml(xmlTextReaderPtr reader); +#endif + +XMLPUBFUN xmlChar * + xmlTextReaderReadString (xmlTextReaderPtr reader); +XMLPUBFUN int + xmlTextReaderReadAttributeValue(xmlTextReaderPtr reader); + +/* + * Attributes of the node + */ +XMLPUBFUN int + xmlTextReaderAttributeCount(xmlTextReaderPtr reader); +XMLPUBFUN int + xmlTextReaderDepth (xmlTextReaderPtr reader); +XMLPUBFUN int + xmlTextReaderHasAttributes(xmlTextReaderPtr reader); +XMLPUBFUN int + xmlTextReaderHasValue(xmlTextReaderPtr reader); +XMLPUBFUN int + xmlTextReaderIsDefault (xmlTextReaderPtr reader); +XMLPUBFUN int + xmlTextReaderIsEmptyElement(xmlTextReaderPtr reader); +XMLPUBFUN int + xmlTextReaderNodeType (xmlTextReaderPtr reader); +XMLPUBFUN int + xmlTextReaderQuoteChar (xmlTextReaderPtr reader); +XMLPUBFUN int + xmlTextReaderReadState (xmlTextReaderPtr reader); +XMLPUBFUN int + xmlTextReaderIsNamespaceDecl(xmlTextReaderPtr reader); + +XMLPUBFUN const xmlChar * + xmlTextReaderConstBaseUri (xmlTextReaderPtr reader); +XMLPUBFUN const xmlChar * + xmlTextReaderConstLocalName (xmlTextReaderPtr reader); +XMLPUBFUN const xmlChar * + xmlTextReaderConstName (xmlTextReaderPtr reader); +XMLPUBFUN const xmlChar * + xmlTextReaderConstNamespaceUri(xmlTextReaderPtr reader); +XMLPUBFUN const xmlChar * + xmlTextReaderConstPrefix (xmlTextReaderPtr reader); +XMLPUBFUN const xmlChar * + xmlTextReaderConstXmlLang (xmlTextReaderPtr reader); +XMLPUBFUN const xmlChar * + xmlTextReaderConstString (xmlTextReaderPtr reader, + const xmlChar *str); +XMLPUBFUN const xmlChar * + xmlTextReaderConstValue (xmlTextReaderPtr reader); + +/* + * use the Const version of the routine for + * better performance and simpler code + */ +XMLPUBFUN xmlChar * + xmlTextReaderBaseUri (xmlTextReaderPtr reader); +XMLPUBFUN xmlChar * + xmlTextReaderLocalName (xmlTextReaderPtr reader); +XMLPUBFUN xmlChar * + xmlTextReaderName (xmlTextReaderPtr reader); +XMLPUBFUN xmlChar * + xmlTextReaderNamespaceUri(xmlTextReaderPtr reader); +XMLPUBFUN xmlChar * + xmlTextReaderPrefix (xmlTextReaderPtr reader); +XMLPUBFUN xmlChar * + xmlTextReaderXmlLang (xmlTextReaderPtr reader); +XMLPUBFUN xmlChar * + xmlTextReaderValue (xmlTextReaderPtr reader); + +/* + * Methods of the XmlTextReader + */ +XMLPUBFUN int + xmlTextReaderClose (xmlTextReaderPtr reader); +XMLPUBFUN xmlChar * + xmlTextReaderGetAttributeNo (xmlTextReaderPtr reader, + int no); +XMLPUBFUN xmlChar * + xmlTextReaderGetAttribute (xmlTextReaderPtr reader, + const xmlChar *name); +XMLPUBFUN xmlChar * + xmlTextReaderGetAttributeNs (xmlTextReaderPtr reader, + const xmlChar *localName, + const xmlChar *namespaceURI); +XMLPUBFUN xmlParserInputBufferPtr + xmlTextReaderGetRemainder (xmlTextReaderPtr reader); +XMLPUBFUN xmlChar * + xmlTextReaderLookupNamespace(xmlTextReaderPtr reader, + const xmlChar *prefix); +XMLPUBFUN int + xmlTextReaderMoveToAttributeNo(xmlTextReaderPtr reader, + int no); +XMLPUBFUN int + xmlTextReaderMoveToAttribute(xmlTextReaderPtr reader, + const xmlChar *name); +XMLPUBFUN int + xmlTextReaderMoveToAttributeNs(xmlTextReaderPtr reader, + const xmlChar *localName, + const xmlChar *namespaceURI); +XMLPUBFUN int + xmlTextReaderMoveToFirstAttribute(xmlTextReaderPtr reader); +XMLPUBFUN int + xmlTextReaderMoveToNextAttribute(xmlTextReaderPtr reader); +XMLPUBFUN int + xmlTextReaderMoveToElement (xmlTextReaderPtr reader); +XMLPUBFUN int + xmlTextReaderNormalization (xmlTextReaderPtr reader); +XMLPUBFUN const xmlChar * + xmlTextReaderConstEncoding (xmlTextReaderPtr reader); + +/* + * Extensions + */ +XMLPUBFUN int + xmlTextReaderSetParserProp (xmlTextReaderPtr reader, + int prop, + int value); +XMLPUBFUN int + xmlTextReaderGetParserProp (xmlTextReaderPtr reader, + int prop); +XMLPUBFUN xmlNodePtr + xmlTextReaderCurrentNode (xmlTextReaderPtr reader); + +XMLPUBFUN int + xmlTextReaderGetParserLineNumber(xmlTextReaderPtr reader); + +XMLPUBFUN int + xmlTextReaderGetParserColumnNumber(xmlTextReaderPtr reader); + +XMLPUBFUN xmlNodePtr + xmlTextReaderPreserve (xmlTextReaderPtr reader); +#ifdef LIBXML_PATTERN_ENABLED +XMLPUBFUN int + xmlTextReaderPreservePattern(xmlTextReaderPtr reader, + const xmlChar *pattern, + const xmlChar **namespaces); +#endif /* LIBXML_PATTERN_ENABLED */ +XMLPUBFUN xmlDocPtr + xmlTextReaderCurrentDoc (xmlTextReaderPtr reader); +XMLPUBFUN xmlNodePtr + xmlTextReaderExpand (xmlTextReaderPtr reader); +XMLPUBFUN int + xmlTextReaderNext (xmlTextReaderPtr reader); +XMLPUBFUN int + xmlTextReaderNextSibling (xmlTextReaderPtr reader); +XMLPUBFUN int + xmlTextReaderIsValid (xmlTextReaderPtr reader); +#ifdef LIBXML_SCHEMAS_ENABLED +XMLPUBFUN int + xmlTextReaderRelaxNGValidate(xmlTextReaderPtr reader, + const char *rng); +XMLPUBFUN int + xmlTextReaderRelaxNGValidateCtxt(xmlTextReaderPtr reader, + xmlRelaxNGValidCtxtPtr ctxt, + int options); + +XMLPUBFUN int + xmlTextReaderRelaxNGSetSchema(xmlTextReaderPtr reader, + xmlRelaxNGPtr schema); +XMLPUBFUN int + xmlTextReaderSchemaValidate (xmlTextReaderPtr reader, + const char *xsd); +XMLPUBFUN int + xmlTextReaderSchemaValidateCtxt(xmlTextReaderPtr reader, + xmlSchemaValidCtxtPtr ctxt, + int options); +XMLPUBFUN int + xmlTextReaderSetSchema (xmlTextReaderPtr reader, + xmlSchemaPtr schema); +#endif +XMLPUBFUN const xmlChar * + xmlTextReaderConstXmlVersion(xmlTextReaderPtr reader); +XMLPUBFUN int + xmlTextReaderStandalone (xmlTextReaderPtr reader); + + +/* + * Index lookup + */ +XMLPUBFUN long + xmlTextReaderByteConsumed (xmlTextReaderPtr reader); + +/* + * New more complete APIs for simpler creation and reuse of readers + */ +XMLPUBFUN xmlTextReaderPtr + xmlReaderWalker (xmlDocPtr doc); +XMLPUBFUN xmlTextReaderPtr + xmlReaderForDoc (const xmlChar * cur, + const char *URL, + const char *encoding, + int options); +XMLPUBFUN xmlTextReaderPtr + xmlReaderForFile (const char *filename, + const char *encoding, + int options); +XMLPUBFUN xmlTextReaderPtr + xmlReaderForMemory (const char *buffer, + int size, + const char *URL, + const char *encoding, + int options); +XMLPUBFUN xmlTextReaderPtr + xmlReaderForFd (int fd, + const char *URL, + const char *encoding, + int options); +XMLPUBFUN xmlTextReaderPtr + xmlReaderForIO (xmlInputReadCallback ioread, + xmlInputCloseCallback ioclose, + void *ioctx, + const char *URL, + const char *encoding, + int options); + +XMLPUBFUN int + xmlReaderNewWalker (xmlTextReaderPtr reader, + xmlDocPtr doc); +XMLPUBFUN int + xmlReaderNewDoc (xmlTextReaderPtr reader, + const xmlChar * cur, + const char *URL, + const char *encoding, + int options); +XMLPUBFUN int + xmlReaderNewFile (xmlTextReaderPtr reader, + const char *filename, + const char *encoding, + int options); +XMLPUBFUN int + xmlReaderNewMemory (xmlTextReaderPtr reader, + const char *buffer, + int size, + const char *URL, + const char *encoding, + int options); +XMLPUBFUN int + xmlReaderNewFd (xmlTextReaderPtr reader, + int fd, + const char *URL, + const char *encoding, + int options); +XMLPUBFUN int + xmlReaderNewIO (xmlTextReaderPtr reader, + xmlInputReadCallback ioread, + xmlInputCloseCallback ioclose, + void *ioctx, + const char *URL, + const char *encoding, + int options); +/* + * Error handling extensions + */ +typedef void * xmlTextReaderLocatorPtr; + +/** + * xmlTextReaderErrorFunc: + * @arg: the user argument + * @msg: the message + * @severity: the severity of the error + * @locator: a locator indicating where the error occurred + * + * Signature of an error callback from a reader parser + */ +typedef void (*xmlTextReaderErrorFunc)(void *arg, + const char *msg, + xmlParserSeverities severity, + xmlTextReaderLocatorPtr locator); +XMLPUBFUN int + xmlTextReaderLocatorLineNumber(xmlTextReaderLocatorPtr locator); +XMLPUBFUN xmlChar * + xmlTextReaderLocatorBaseURI (xmlTextReaderLocatorPtr locator); +XMLPUBFUN void + xmlTextReaderSetErrorHandler(xmlTextReaderPtr reader, + xmlTextReaderErrorFunc f, + void *arg); +XMLPUBFUN void + xmlTextReaderSetStructuredErrorHandler(xmlTextReaderPtr reader, + xmlStructuredErrorFunc f, + void *arg); +XMLPUBFUN void + xmlTextReaderGetErrorHandler(xmlTextReaderPtr reader, + xmlTextReaderErrorFunc *f, + void **arg); + +#endif /* LIBXML_READER_ENABLED */ + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_XMLREADER_H__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlregexp.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlregexp.h new file mode 100644 index 00000000..2d66437a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlregexp.h @@ -0,0 +1,215 @@ +/* + * Summary: regular expressions handling + * Description: basic API for libxml regular expressions handling used + * for XML Schemas and validation. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_REGEXP_H__ +#define __XML_REGEXP_H__ + +#include +#include +#include + +#ifdef LIBXML_REGEXP_ENABLED + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * xmlRegexpPtr: + * + * A libxml regular expression, they can actually be far more complex + * thank the POSIX regex expressions. + */ +typedef struct _xmlRegexp xmlRegexp; +typedef xmlRegexp *xmlRegexpPtr; + +/** + * xmlRegExecCtxtPtr: + * + * A libxml progressive regular expression evaluation context + */ +typedef struct _xmlRegExecCtxt xmlRegExecCtxt; +typedef xmlRegExecCtxt *xmlRegExecCtxtPtr; + +/* + * The POSIX like API + */ +XMLPUBFUN xmlRegexpPtr + xmlRegexpCompile (const xmlChar *regexp); +XMLPUBFUN void xmlRegFreeRegexp(xmlRegexpPtr regexp); +XMLPUBFUN int + xmlRegexpExec (xmlRegexpPtr comp, + const xmlChar *value); +XMLPUBFUN void + xmlRegexpPrint (FILE *output, + xmlRegexpPtr regexp); +XMLPUBFUN int + xmlRegexpIsDeterminist(xmlRegexpPtr comp); + +/** + * xmlRegExecCallbacks: + * @exec: the regular expression context + * @token: the current token string + * @transdata: transition data + * @inputdata: input data + * + * Callback function when doing a transition in the automata + */ +typedef void (*xmlRegExecCallbacks) (xmlRegExecCtxtPtr exec, + const xmlChar *token, + void *transdata, + void *inputdata); + +/* + * The progressive API + */ +XMLPUBFUN xmlRegExecCtxtPtr + xmlRegNewExecCtxt (xmlRegexpPtr comp, + xmlRegExecCallbacks callback, + void *data); +XMLPUBFUN void + xmlRegFreeExecCtxt (xmlRegExecCtxtPtr exec); +XMLPUBFUN int + xmlRegExecPushString(xmlRegExecCtxtPtr exec, + const xmlChar *value, + void *data); +XMLPUBFUN int + xmlRegExecPushString2(xmlRegExecCtxtPtr exec, + const xmlChar *value, + const xmlChar *value2, + void *data); + +XMLPUBFUN int + xmlRegExecNextValues(xmlRegExecCtxtPtr exec, + int *nbval, + int *nbneg, + xmlChar **values, + int *terminal); +XMLPUBFUN int + xmlRegExecErrInfo (xmlRegExecCtxtPtr exec, + const xmlChar **string, + int *nbval, + int *nbneg, + xmlChar **values, + int *terminal); +#ifdef LIBXML_EXPR_ENABLED +/* + * Formal regular expression handling + * Its goal is to do some formal work on content models + */ + +/* expressions are used within a context */ +typedef struct _xmlExpCtxt xmlExpCtxt; +typedef xmlExpCtxt *xmlExpCtxtPtr; + +XMLPUBFUN void + xmlExpFreeCtxt (xmlExpCtxtPtr ctxt); +XMLPUBFUN xmlExpCtxtPtr + xmlExpNewCtxt (int maxNodes, + xmlDictPtr dict); + +XMLPUBFUN int + xmlExpCtxtNbNodes(xmlExpCtxtPtr ctxt); +XMLPUBFUN int + xmlExpCtxtNbCons(xmlExpCtxtPtr ctxt); + +/* Expressions are trees but the tree is opaque */ +typedef struct _xmlExpNode xmlExpNode; +typedef xmlExpNode *xmlExpNodePtr; + +typedef enum { + XML_EXP_EMPTY = 0, + XML_EXP_FORBID = 1, + XML_EXP_ATOM = 2, + XML_EXP_SEQ = 3, + XML_EXP_OR = 4, + XML_EXP_COUNT = 5 +} xmlExpNodeType; + +/* + * 2 core expressions shared by all for the empty language set + * and for the set with just the empty token + */ +XMLPUBVAR xmlExpNodePtr forbiddenExp; +XMLPUBVAR xmlExpNodePtr emptyExp; + +/* + * Expressions are reference counted internally + */ +XMLPUBFUN void + xmlExpFree (xmlExpCtxtPtr ctxt, + xmlExpNodePtr expr); +XMLPUBFUN void + xmlExpRef (xmlExpNodePtr expr); + +/* + * constructors can be either manual or from a string + */ +XMLPUBFUN xmlExpNodePtr + xmlExpParse (xmlExpCtxtPtr ctxt, + const char *expr); +XMLPUBFUN xmlExpNodePtr + xmlExpNewAtom (xmlExpCtxtPtr ctxt, + const xmlChar *name, + int len); +XMLPUBFUN xmlExpNodePtr + xmlExpNewOr (xmlExpCtxtPtr ctxt, + xmlExpNodePtr left, + xmlExpNodePtr right); +XMLPUBFUN xmlExpNodePtr + xmlExpNewSeq (xmlExpCtxtPtr ctxt, + xmlExpNodePtr left, + xmlExpNodePtr right); +XMLPUBFUN xmlExpNodePtr + xmlExpNewRange (xmlExpCtxtPtr ctxt, + xmlExpNodePtr subset, + int min, + int max); +/* + * The really interesting APIs + */ +XMLPUBFUN int + xmlExpIsNillable(xmlExpNodePtr expr); +XMLPUBFUN int + xmlExpMaxToken (xmlExpNodePtr expr); +XMLPUBFUN int + xmlExpGetLanguage(xmlExpCtxtPtr ctxt, + xmlExpNodePtr expr, + const xmlChar**langList, + int len); +XMLPUBFUN int + xmlExpGetStart (xmlExpCtxtPtr ctxt, + xmlExpNodePtr expr, + const xmlChar**tokList, + int len); +XMLPUBFUN xmlExpNodePtr + xmlExpStringDerive(xmlExpCtxtPtr ctxt, + xmlExpNodePtr expr, + const xmlChar *str, + int len); +XMLPUBFUN xmlExpNodePtr + xmlExpExpDerive (xmlExpCtxtPtr ctxt, + xmlExpNodePtr expr, + xmlExpNodePtr sub); +XMLPUBFUN int + xmlExpSubsume (xmlExpCtxtPtr ctxt, + xmlExpNodePtr expr, + xmlExpNodePtr sub); +XMLPUBFUN void + xmlExpDump (xmlBufferPtr buf, + xmlExpNodePtr expr); +#endif /* LIBXML_EXPR_ENABLED */ +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_REGEXP_ENABLED */ + +#endif /*__XML_REGEXP_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlsave.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlsave.h new file mode 100644 index 00000000..e266e467 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlsave.h @@ -0,0 +1,102 @@ +/* + * Summary: the XML document serializer + * Description: API to save document or subtree of document + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XMLSAVE_H__ +#define __XML_XMLSAVE_H__ + +#include +#include +#include +#include + +#ifdef LIBXML_OUTPUT_ENABLED +#ifdef __cplusplus +extern "C" { +#endif + +/** + * xmlSaveOption: + * + * This is the set of XML save options that can be passed down + * to the xmlSaveToFd() and similar calls. + */ +typedef enum { + XML_SAVE_FORMAT = 1<<0, /* format save output */ + XML_SAVE_NO_DECL = 1<<1, /* drop the xml declaration */ + XML_SAVE_NO_EMPTY = 1<<2, /* no empty tags */ + XML_SAVE_NO_XHTML = 1<<3, /* disable XHTML1 specific rules */ + XML_SAVE_XHTML = 1<<4, /* force XHTML1 specific rules */ + XML_SAVE_AS_XML = 1<<5, /* force XML serialization on HTML doc */ + XML_SAVE_AS_HTML = 1<<6, /* force HTML serialization on XML doc */ + XML_SAVE_WSNONSIG = 1<<7 /* format with non-significant whitespace */ +} xmlSaveOption; + + +typedef struct _xmlSaveCtxt xmlSaveCtxt; +typedef xmlSaveCtxt *xmlSaveCtxtPtr; + +XMLPUBFUN xmlSaveCtxtPtr + xmlSaveToFd (int fd, + const char *encoding, + int options); +XMLPUBFUN xmlSaveCtxtPtr + xmlSaveToFilename (const char *filename, + const char *encoding, + int options); + +XMLPUBFUN xmlSaveCtxtPtr + xmlSaveToBuffer (xmlBufferPtr buffer, + const char *encoding, + int options); + +XMLPUBFUN xmlSaveCtxtPtr + xmlSaveToIO (xmlOutputWriteCallback iowrite, + xmlOutputCloseCallback ioclose, + void *ioctx, + const char *encoding, + int options); + +XMLPUBFUN long + xmlSaveDoc (xmlSaveCtxtPtr ctxt, + xmlDocPtr doc); +XMLPUBFUN long + xmlSaveTree (xmlSaveCtxtPtr ctxt, + xmlNodePtr node); + +XMLPUBFUN int + xmlSaveFlush (xmlSaveCtxtPtr ctxt); +XMLPUBFUN int + xmlSaveClose (xmlSaveCtxtPtr ctxt); +XMLPUBFUN int + xmlSaveFinish (xmlSaveCtxtPtr ctxt); +XMLPUBFUN int + xmlSaveSetEscape (xmlSaveCtxtPtr ctxt, + xmlCharEncodingOutputFunc escape); +XMLPUBFUN int + xmlSaveSetAttrEscape (xmlSaveCtxtPtr ctxt, + xmlCharEncodingOutputFunc escape); + +XML_DEPRECATED +XMLPUBFUN int + xmlThrDefIndentTreeOutput(int v); +XML_DEPRECATED +XMLPUBFUN const char * + xmlThrDefTreeIndentString(const char * v); +XML_DEPRECATED +XMLPUBFUN int + xmlThrDefSaveNoEmptyTags(int v); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_OUTPUT_ENABLED */ +#endif /* __XML_XMLSAVE_H__ */ + + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlschemas.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlschemas.h new file mode 100644 index 00000000..c2af3d70 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlschemas.h @@ -0,0 +1,249 @@ +/* + * Summary: incomplete XML Schemas structure implementation + * Description: interface to the XML Schemas handling and schema validity + * checking, it is incomplete right now. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + + +#ifndef __XML_SCHEMA_H__ +#define __XML_SCHEMA_H__ + +#include + +#ifdef LIBXML_SCHEMAS_ENABLED + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * This error codes are obsolete; not used any more. + */ +typedef enum { + XML_SCHEMAS_ERR_OK = 0, + XML_SCHEMAS_ERR_NOROOT = 1, + XML_SCHEMAS_ERR_UNDECLAREDELEM, + XML_SCHEMAS_ERR_NOTTOPLEVEL, + XML_SCHEMAS_ERR_MISSING, + XML_SCHEMAS_ERR_WRONGELEM, + XML_SCHEMAS_ERR_NOTYPE, + XML_SCHEMAS_ERR_NOROLLBACK, + XML_SCHEMAS_ERR_ISABSTRACT, + XML_SCHEMAS_ERR_NOTEMPTY, + XML_SCHEMAS_ERR_ELEMCONT, + XML_SCHEMAS_ERR_HAVEDEFAULT, + XML_SCHEMAS_ERR_NOTNILLABLE, + XML_SCHEMAS_ERR_EXTRACONTENT, + XML_SCHEMAS_ERR_INVALIDATTR, + XML_SCHEMAS_ERR_INVALIDELEM, + XML_SCHEMAS_ERR_NOTDETERMINIST, + XML_SCHEMAS_ERR_CONSTRUCT, + XML_SCHEMAS_ERR_INTERNAL, + XML_SCHEMAS_ERR_NOTSIMPLE, + XML_SCHEMAS_ERR_ATTRUNKNOWN, + XML_SCHEMAS_ERR_ATTRINVALID, + XML_SCHEMAS_ERR_VALUE, + XML_SCHEMAS_ERR_FACET, + XML_SCHEMAS_ERR_, + XML_SCHEMAS_ERR_XXX +} xmlSchemaValidError; + +/* +* ATTENTION: Change xmlSchemaSetValidOptions's check +* for invalid values, if adding to the validation +* options below. +*/ +/** + * xmlSchemaValidOption: + * + * This is the set of XML Schema validation options. + */ +typedef enum { + XML_SCHEMA_VAL_VC_I_CREATE = 1<<0 + /* Default/fixed: create an attribute node + * or an element's text node on the instance. + */ +} xmlSchemaValidOption; + +/* + XML_SCHEMA_VAL_XSI_ASSEMBLE = 1<<1, + * assemble schemata using + * xsi:schemaLocation and + * xsi:noNamespaceSchemaLocation +*/ + +/** + * The schemas related types are kept internal + */ +typedef struct _xmlSchema xmlSchema; +typedef xmlSchema *xmlSchemaPtr; + +/** + * xmlSchemaValidityErrorFunc: + * @ctx: the validation context + * @msg: the message + * @...: extra arguments + * + * Signature of an error callback from an XSD validation + */ +typedef void (*xmlSchemaValidityErrorFunc) + (void *ctx, const char *msg, ...) LIBXML_ATTR_FORMAT(2,3); + +/** + * xmlSchemaValidityWarningFunc: + * @ctx: the validation context + * @msg: the message + * @...: extra arguments + * + * Signature of a warning callback from an XSD validation + */ +typedef void (*xmlSchemaValidityWarningFunc) + (void *ctx, const char *msg, ...) LIBXML_ATTR_FORMAT(2,3); + +/** + * A schemas validation context + */ +typedef struct _xmlSchemaParserCtxt xmlSchemaParserCtxt; +typedef xmlSchemaParserCtxt *xmlSchemaParserCtxtPtr; + +typedef struct _xmlSchemaValidCtxt xmlSchemaValidCtxt; +typedef xmlSchemaValidCtxt *xmlSchemaValidCtxtPtr; + +/** + * xmlSchemaValidityLocatorFunc: + * @ctx: user provided context + * @file: returned file information + * @line: returned line information + * + * A schemas validation locator, a callback called by the validator. + * This is used when file or node information are not available + * to find out what file and line number are affected + * + * Returns: 0 in case of success and -1 in case of error + */ + +typedef int (*xmlSchemaValidityLocatorFunc) (void *ctx, + const char **file, unsigned long *line); + +/* + * Interfaces for parsing. + */ +XMLPUBFUN xmlSchemaParserCtxtPtr + xmlSchemaNewParserCtxt (const char *URL); +XMLPUBFUN xmlSchemaParserCtxtPtr + xmlSchemaNewMemParserCtxt (const char *buffer, + int size); +XMLPUBFUN xmlSchemaParserCtxtPtr + xmlSchemaNewDocParserCtxt (xmlDocPtr doc); +XMLPUBFUN void + xmlSchemaFreeParserCtxt (xmlSchemaParserCtxtPtr ctxt); +XMLPUBFUN void + xmlSchemaSetParserErrors (xmlSchemaParserCtxtPtr ctxt, + xmlSchemaValidityErrorFunc err, + xmlSchemaValidityWarningFunc warn, + void *ctx); +XMLPUBFUN void + xmlSchemaSetParserStructuredErrors(xmlSchemaParserCtxtPtr ctxt, + xmlStructuredErrorFunc serror, + void *ctx); +XMLPUBFUN int + xmlSchemaGetParserErrors(xmlSchemaParserCtxtPtr ctxt, + xmlSchemaValidityErrorFunc * err, + xmlSchemaValidityWarningFunc * warn, + void **ctx); +XMLPUBFUN int + xmlSchemaIsValid (xmlSchemaValidCtxtPtr ctxt); + +XMLPUBFUN xmlSchemaPtr + xmlSchemaParse (xmlSchemaParserCtxtPtr ctxt); +XMLPUBFUN void + xmlSchemaFree (xmlSchemaPtr schema); +#ifdef LIBXML_OUTPUT_ENABLED +XMLPUBFUN void + xmlSchemaDump (FILE *output, + xmlSchemaPtr schema); +#endif /* LIBXML_OUTPUT_ENABLED */ +/* + * Interfaces for validating + */ +XMLPUBFUN void + xmlSchemaSetValidErrors (xmlSchemaValidCtxtPtr ctxt, + xmlSchemaValidityErrorFunc err, + xmlSchemaValidityWarningFunc warn, + void *ctx); +XMLPUBFUN void + xmlSchemaSetValidStructuredErrors(xmlSchemaValidCtxtPtr ctxt, + xmlStructuredErrorFunc serror, + void *ctx); +XMLPUBFUN int + xmlSchemaGetValidErrors (xmlSchemaValidCtxtPtr ctxt, + xmlSchemaValidityErrorFunc *err, + xmlSchemaValidityWarningFunc *warn, + void **ctx); +XMLPUBFUN int + xmlSchemaSetValidOptions (xmlSchemaValidCtxtPtr ctxt, + int options); +XMLPUBFUN void + xmlSchemaValidateSetFilename(xmlSchemaValidCtxtPtr vctxt, + const char *filename); +XMLPUBFUN int + xmlSchemaValidCtxtGetOptions(xmlSchemaValidCtxtPtr ctxt); + +XMLPUBFUN xmlSchemaValidCtxtPtr + xmlSchemaNewValidCtxt (xmlSchemaPtr schema); +XMLPUBFUN void + xmlSchemaFreeValidCtxt (xmlSchemaValidCtxtPtr ctxt); +XMLPUBFUN int + xmlSchemaValidateDoc (xmlSchemaValidCtxtPtr ctxt, + xmlDocPtr instance); +XMLPUBFUN int + xmlSchemaValidateOneElement (xmlSchemaValidCtxtPtr ctxt, + xmlNodePtr elem); +XMLPUBFUN int + xmlSchemaValidateStream (xmlSchemaValidCtxtPtr ctxt, + xmlParserInputBufferPtr input, + xmlCharEncoding enc, + xmlSAXHandlerPtr sax, + void *user_data); +XMLPUBFUN int + xmlSchemaValidateFile (xmlSchemaValidCtxtPtr ctxt, + const char * filename, + int options); + +XMLPUBFUN xmlParserCtxtPtr + xmlSchemaValidCtxtGetParserCtxt(xmlSchemaValidCtxtPtr ctxt); + +/* + * Interface to insert Schemas SAX validation in a SAX stream + */ +typedef struct _xmlSchemaSAXPlug xmlSchemaSAXPlugStruct; +typedef xmlSchemaSAXPlugStruct *xmlSchemaSAXPlugPtr; + +XMLPUBFUN xmlSchemaSAXPlugPtr + xmlSchemaSAXPlug (xmlSchemaValidCtxtPtr ctxt, + xmlSAXHandlerPtr *sax, + void **user_data); +XMLPUBFUN int + xmlSchemaSAXUnplug (xmlSchemaSAXPlugPtr plug); + + +XMLPUBFUN void + xmlSchemaValidateSetLocator (xmlSchemaValidCtxtPtr vctxt, + xmlSchemaValidityLocatorFunc f, + void *ctxt); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_SCHEMAS_ENABLED */ +#endif /* __XML_SCHEMA_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlschemastypes.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlschemastypes.h new file mode 100644 index 00000000..e2cde357 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlschemastypes.h @@ -0,0 +1,152 @@ +/* + * Summary: implementation of XML Schema Datatypes + * Description: module providing the XML Schema Datatypes implementation + * both definition and validity checking + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + + +#ifndef __XML_SCHEMA_TYPES_H__ +#define __XML_SCHEMA_TYPES_H__ + +#include + +#ifdef LIBXML_SCHEMAS_ENABLED + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + XML_SCHEMA_WHITESPACE_UNKNOWN = 0, + XML_SCHEMA_WHITESPACE_PRESERVE = 1, + XML_SCHEMA_WHITESPACE_REPLACE = 2, + XML_SCHEMA_WHITESPACE_COLLAPSE = 3 +} xmlSchemaWhitespaceValueType; + +XMLPUBFUN int + xmlSchemaInitTypes (void); +XML_DEPRECATED +XMLPUBFUN void + xmlSchemaCleanupTypes (void); +XMLPUBFUN xmlSchemaTypePtr + xmlSchemaGetPredefinedType (const xmlChar *name, + const xmlChar *ns); +XMLPUBFUN int + xmlSchemaValidatePredefinedType (xmlSchemaTypePtr type, + const xmlChar *value, + xmlSchemaValPtr *val); +XMLPUBFUN int + xmlSchemaValPredefTypeNode (xmlSchemaTypePtr type, + const xmlChar *value, + xmlSchemaValPtr *val, + xmlNodePtr node); +XMLPUBFUN int + xmlSchemaValidateFacet (xmlSchemaTypePtr base, + xmlSchemaFacetPtr facet, + const xmlChar *value, + xmlSchemaValPtr val); +XMLPUBFUN int + xmlSchemaValidateFacetWhtsp (xmlSchemaFacetPtr facet, + xmlSchemaWhitespaceValueType fws, + xmlSchemaValType valType, + const xmlChar *value, + xmlSchemaValPtr val, + xmlSchemaWhitespaceValueType ws); +XMLPUBFUN void + xmlSchemaFreeValue (xmlSchemaValPtr val); +XMLPUBFUN xmlSchemaFacetPtr + xmlSchemaNewFacet (void); +XMLPUBFUN int + xmlSchemaCheckFacet (xmlSchemaFacetPtr facet, + xmlSchemaTypePtr typeDecl, + xmlSchemaParserCtxtPtr ctxt, + const xmlChar *name); +XMLPUBFUN void + xmlSchemaFreeFacet (xmlSchemaFacetPtr facet); +XMLPUBFUN int + xmlSchemaCompareValues (xmlSchemaValPtr x, + xmlSchemaValPtr y); +XMLPUBFUN xmlSchemaTypePtr + xmlSchemaGetBuiltInListSimpleTypeItemType (xmlSchemaTypePtr type); +XMLPUBFUN int + xmlSchemaValidateListSimpleTypeFacet (xmlSchemaFacetPtr facet, + const xmlChar *value, + unsigned long actualLen, + unsigned long *expectedLen); +XMLPUBFUN xmlSchemaTypePtr + xmlSchemaGetBuiltInType (xmlSchemaValType type); +XMLPUBFUN int + xmlSchemaIsBuiltInTypeFacet (xmlSchemaTypePtr type, + int facetType); +XMLPUBFUN xmlChar * + xmlSchemaCollapseString (const xmlChar *value); +XMLPUBFUN xmlChar * + xmlSchemaWhiteSpaceReplace (const xmlChar *value); +XMLPUBFUN unsigned long + xmlSchemaGetFacetValueAsULong (xmlSchemaFacetPtr facet); +XMLPUBFUN int + xmlSchemaValidateLengthFacet (xmlSchemaTypePtr type, + xmlSchemaFacetPtr facet, + const xmlChar *value, + xmlSchemaValPtr val, + unsigned long *length); +XMLPUBFUN int + xmlSchemaValidateLengthFacetWhtsp(xmlSchemaFacetPtr facet, + xmlSchemaValType valType, + const xmlChar *value, + xmlSchemaValPtr val, + unsigned long *length, + xmlSchemaWhitespaceValueType ws); +XMLPUBFUN int + xmlSchemaValPredefTypeNodeNoNorm(xmlSchemaTypePtr type, + const xmlChar *value, + xmlSchemaValPtr *val, + xmlNodePtr node); +XMLPUBFUN int + xmlSchemaGetCanonValue (xmlSchemaValPtr val, + const xmlChar **retValue); +XMLPUBFUN int + xmlSchemaGetCanonValueWhtsp (xmlSchemaValPtr val, + const xmlChar **retValue, + xmlSchemaWhitespaceValueType ws); +XMLPUBFUN int + xmlSchemaValueAppend (xmlSchemaValPtr prev, + xmlSchemaValPtr cur); +XMLPUBFUN xmlSchemaValPtr + xmlSchemaValueGetNext (xmlSchemaValPtr cur); +XMLPUBFUN const xmlChar * + xmlSchemaValueGetAsString (xmlSchemaValPtr val); +XMLPUBFUN int + xmlSchemaValueGetAsBoolean (xmlSchemaValPtr val); +XMLPUBFUN xmlSchemaValPtr + xmlSchemaNewStringValue (xmlSchemaValType type, + const xmlChar *value); +XMLPUBFUN xmlSchemaValPtr + xmlSchemaNewNOTATIONValue (const xmlChar *name, + const xmlChar *ns); +XMLPUBFUN xmlSchemaValPtr + xmlSchemaNewQNameValue (const xmlChar *namespaceName, + const xmlChar *localName); +XMLPUBFUN int + xmlSchemaCompareValuesWhtsp (xmlSchemaValPtr x, + xmlSchemaWhitespaceValueType xws, + xmlSchemaValPtr y, + xmlSchemaWhitespaceValueType yws); +XMLPUBFUN xmlSchemaValPtr + xmlSchemaCopyValue (xmlSchemaValPtr val); +XMLPUBFUN xmlSchemaValType + xmlSchemaGetValType (xmlSchemaValPtr val); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_SCHEMAS_ENABLED */ +#endif /* __XML_SCHEMA_TYPES_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlstring.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlstring.h new file mode 100644 index 00000000..db11a0b0 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlstring.h @@ -0,0 +1,140 @@ +/* + * Summary: set of routines to process strings + * Description: type and interfaces needed for the internal string handling + * of the library, especially UTF8 processing. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_STRING_H__ +#define __XML_STRING_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * xmlChar: + * + * This is a basic byte in an UTF-8 encoded string. + * It's unsigned allowing to pinpoint case where char * are assigned + * to xmlChar * (possibly making serialization back impossible). + */ +typedef unsigned char xmlChar; + +/** + * BAD_CAST: + * + * Macro to cast a string to an xmlChar * when one know its safe. + */ +#define BAD_CAST (xmlChar *) + +/* + * xmlChar handling + */ +XMLPUBFUN xmlChar * + xmlStrdup (const xmlChar *cur); +XMLPUBFUN xmlChar * + xmlStrndup (const xmlChar *cur, + int len); +XMLPUBFUN xmlChar * + xmlCharStrndup (const char *cur, + int len); +XMLPUBFUN xmlChar * + xmlCharStrdup (const char *cur); +XMLPUBFUN xmlChar * + xmlStrsub (const xmlChar *str, + int start, + int len); +XMLPUBFUN const xmlChar * + xmlStrchr (const xmlChar *str, + xmlChar val); +XMLPUBFUN const xmlChar * + xmlStrstr (const xmlChar *str, + const xmlChar *val); +XMLPUBFUN const xmlChar * + xmlStrcasestr (const xmlChar *str, + const xmlChar *val); +XMLPUBFUN int + xmlStrcmp (const xmlChar *str1, + const xmlChar *str2); +XMLPUBFUN int + xmlStrncmp (const xmlChar *str1, + const xmlChar *str2, + int len); +XMLPUBFUN int + xmlStrcasecmp (const xmlChar *str1, + const xmlChar *str2); +XMLPUBFUN int + xmlStrncasecmp (const xmlChar *str1, + const xmlChar *str2, + int len); +XMLPUBFUN int + xmlStrEqual (const xmlChar *str1, + const xmlChar *str2); +XMLPUBFUN int + xmlStrQEqual (const xmlChar *pref, + const xmlChar *name, + const xmlChar *str); +XMLPUBFUN int + xmlStrlen (const xmlChar *str); +XMLPUBFUN xmlChar * + xmlStrcat (xmlChar *cur, + const xmlChar *add); +XMLPUBFUN xmlChar * + xmlStrncat (xmlChar *cur, + const xmlChar *add, + int len); +XMLPUBFUN xmlChar * + xmlStrncatNew (const xmlChar *str1, + const xmlChar *str2, + int len); +XMLPUBFUN int + xmlStrPrintf (xmlChar *buf, + int len, + const char *msg, + ...) LIBXML_ATTR_FORMAT(3,4); +XMLPUBFUN int + xmlStrVPrintf (xmlChar *buf, + int len, + const char *msg, + va_list ap) LIBXML_ATTR_FORMAT(3,0); + +XMLPUBFUN int + xmlGetUTF8Char (const unsigned char *utf, + int *len); +XMLPUBFUN int + xmlCheckUTF8 (const unsigned char *utf); +XMLPUBFUN int + xmlUTF8Strsize (const xmlChar *utf, + int len); +XMLPUBFUN xmlChar * + xmlUTF8Strndup (const xmlChar *utf, + int len); +XMLPUBFUN const xmlChar * + xmlUTF8Strpos (const xmlChar *utf, + int pos); +XMLPUBFUN int + xmlUTF8Strloc (const xmlChar *utf, + const xmlChar *utfchar); +XMLPUBFUN xmlChar * + xmlUTF8Strsub (const xmlChar *utf, + int start, + int len); +XMLPUBFUN int + xmlUTF8Strlen (const xmlChar *utf); +XMLPUBFUN int + xmlUTF8Size (const xmlChar *utf); +XMLPUBFUN int + xmlUTF8Charcmp (const xmlChar *utf1, + const xmlChar *utf2); + +#ifdef __cplusplus +} +#endif +#endif /* __XML_STRING_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlunicode.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlunicode.h new file mode 100644 index 00000000..b6d795b2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlunicode.h @@ -0,0 +1,366 @@ +/* + * Summary: Unicode character APIs + * Description: API for the Unicode character APIs + * + * This file is automatically generated from the + * UCS description files of the Unicode Character Database + * http://www.unicode.org/Public/4.0-Update1/UCD-4.0.1.html + * using the genUnicode.py Python script. + * + * Generation date: Tue Apr 30 17:30:38 2024 + * Sources: Blocks-4.0.1.txt UnicodeData-4.0.1.txt + * Author: Daniel Veillard + */ + +#ifndef __XML_UNICODE_H__ +#define __XML_UNICODE_H__ + +#include + +#ifdef LIBXML_UNICODE_ENABLED + +#ifdef __cplusplus +extern "C" { +#endif + +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsAegeanNumbers (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsAlphabeticPresentationForms (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsArabic (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsArabicPresentationFormsA (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsArabicPresentationFormsB (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsArmenian (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsArrows (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsBasicLatin (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsBengali (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsBlockElements (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsBopomofo (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsBopomofoExtended (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsBoxDrawing (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsBraillePatterns (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsBuhid (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsByzantineMusicalSymbols (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCJKCompatibility (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCJKCompatibilityForms (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCJKCompatibilityIdeographs (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCJKCompatibilityIdeographsSupplement (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCJKRadicalsSupplement (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCJKSymbolsandPunctuation (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCJKUnifiedIdeographs (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCJKUnifiedIdeographsExtensionA (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCJKUnifiedIdeographsExtensionB (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCherokee (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCombiningDiacriticalMarks (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCombiningDiacriticalMarksforSymbols (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCombiningHalfMarks (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCombiningMarksforSymbols (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsControlPictures (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCurrencySymbols (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCypriotSyllabary (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCyrillic (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCyrillicSupplement (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsDeseret (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsDevanagari (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsDingbats (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsEnclosedAlphanumerics (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsEnclosedCJKLettersandMonths (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsEthiopic (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsGeneralPunctuation (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsGeometricShapes (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsGeorgian (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsGothic (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsGreek (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsGreekExtended (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsGreekandCoptic (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsGujarati (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsGurmukhi (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsHalfwidthandFullwidthForms (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsHangulCompatibilityJamo (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsHangulJamo (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsHangulSyllables (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsHanunoo (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsHebrew (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsHighPrivateUseSurrogates (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsHighSurrogates (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsHiragana (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsIPAExtensions (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsIdeographicDescriptionCharacters (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsKanbun (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsKangxiRadicals (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsKannada (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsKatakana (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsKatakanaPhoneticExtensions (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsKhmer (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsKhmerSymbols (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsLao (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsLatin1Supplement (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsLatinExtendedA (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsLatinExtendedB (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsLatinExtendedAdditional (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsLetterlikeSymbols (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsLimbu (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsLinearBIdeograms (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsLinearBSyllabary (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsLowSurrogates (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsMalayalam (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsMathematicalAlphanumericSymbols (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsMathematicalOperators (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsMiscellaneousMathematicalSymbolsA (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsMiscellaneousMathematicalSymbolsB (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsMiscellaneousSymbols (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsMiscellaneousSymbolsandArrows (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsMiscellaneousTechnical (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsMongolian (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsMusicalSymbols (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsMyanmar (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsNumberForms (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsOgham (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsOldItalic (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsOpticalCharacterRecognition (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsOriya (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsOsmanya (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsPhoneticExtensions (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsPrivateUse (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsPrivateUseArea (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsRunic (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsShavian (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsSinhala (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsSmallFormVariants (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsSpacingModifierLetters (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsSpecials (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsSuperscriptsandSubscripts (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsSupplementalArrowsA (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsSupplementalArrowsB (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsSupplementalMathematicalOperators (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsSupplementaryPrivateUseAreaA (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsSupplementaryPrivateUseAreaB (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsSyriac (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsTagalog (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsTagbanwa (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsTags (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsTaiLe (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsTaiXuanJingSymbols (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsTamil (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsTelugu (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsThaana (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsThai (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsTibetan (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsUgaritic (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsUnifiedCanadianAboriginalSyllabics (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsVariationSelectors (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsVariationSelectorsSupplement (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsYiRadicals (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsYiSyllables (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsYijingHexagramSymbols (int code); + +XMLPUBFUN int xmlUCSIsBlock (int code, const char *block); + +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatC (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatCc (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatCf (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatCo (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatCs (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatL (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatLl (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatLm (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatLo (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatLt (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatLu (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatM (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatMc (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatMe (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatMn (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatN (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatNd (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatNl (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatNo (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatP (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatPc (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatPd (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatPe (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatPf (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatPi (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatPo (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatPs (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatS (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatSc (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatSk (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatSm (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatSo (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatZ (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatZl (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatZp (int code); +XML_DEPRECATED +XMLPUBFUN int xmlUCSIsCatZs (int code); + +XMLPUBFUN int xmlUCSIsCat (int code, const char *cat); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_UNICODE_ENABLED */ + +#endif /* __XML_UNICODE_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlversion.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlversion.h new file mode 100644 index 00000000..4351478d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlversion.h @@ -0,0 +1,347 @@ +/* + * Summary: compile-time version information + * Description: compile-time version information for the XML library + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_VERSION_H__ +#define __XML_VERSION_H__ + +/** + * LIBXML_DOTTED_VERSION: + * + * the version string like "1.2.3" + */ +#define LIBXML_DOTTED_VERSION "2.13.8" + +/** + * LIBXML_VERSION: + * + * the version number: 1.2.3 value is 10203 + */ +#define LIBXML_VERSION 21308 + +/** + * LIBXML_VERSION_STRING: + * + * the version number string, 1.2.3 value is "10203" + */ +#define LIBXML_VERSION_STRING "21308" + +/** + * LIBXML_VERSION_EXTRA: + * + * extra version information, used to show a git commit description + */ +#define LIBXML_VERSION_EXTRA "" + +/** + * LIBXML_TEST_VERSION: + * + * Macro to check that the libxml version in use is compatible with + * the version the software has been compiled against + */ +#define LIBXML_TEST_VERSION xmlCheckVersion(21308); + +/** + * LIBXML_THREAD_ENABLED: + * + * Whether the thread support is configured in + */ +#if 1 +#define LIBXML_THREAD_ENABLED +#endif + +/** + * LIBXML_THREAD_ALLOC_ENABLED: + * + * Whether the allocation hooks are per-thread + */ +#if 0 +#define LIBXML_THREAD_ALLOC_ENABLED +#endif + +/** + * LIBXML_TREE_ENABLED: + * + * Whether the DOM like tree manipulation API support is configured in + */ +#if 1 +#define LIBXML_TREE_ENABLED +#endif + +/** + * LIBXML_OUTPUT_ENABLED: + * + * Whether the serialization/saving support is configured in + */ +#if 1 +#define LIBXML_OUTPUT_ENABLED +#endif + +/** + * LIBXML_PUSH_ENABLED: + * + * Whether the push parsing interfaces are configured in + */ +#if 1 +#define LIBXML_PUSH_ENABLED +#endif + +/** + * LIBXML_READER_ENABLED: + * + * Whether the xmlReader parsing interface is configured in + */ +#if 1 +#define LIBXML_READER_ENABLED +#endif + +/** + * LIBXML_PATTERN_ENABLED: + * + * Whether the xmlPattern node selection interface is configured in + */ +#if 1 +#define LIBXML_PATTERN_ENABLED +#endif + +/** + * LIBXML_WRITER_ENABLED: + * + * Whether the xmlWriter saving interface is configured in + */ +#if 1 +#define LIBXML_WRITER_ENABLED +#endif + +/** + * LIBXML_SAX1_ENABLED: + * + * Whether the older SAX1 interface is configured in + */ +#if 1 +#define LIBXML_SAX1_ENABLED +#endif + +/** + * LIBXML_FTP_ENABLED: + * + * Whether the FTP support is configured in + */ +#if 0 +#define LIBXML_FTP_ENABLED +#endif + +/** + * LIBXML_HTTP_ENABLED: + * + * Whether the HTTP support is configured in + */ +#if 1 +#define LIBXML_HTTP_ENABLED +#endif + +/** + * LIBXML_VALID_ENABLED: + * + * Whether the DTD validation support is configured in + */ +#if 1 +#define LIBXML_VALID_ENABLED +#endif + +/** + * LIBXML_HTML_ENABLED: + * + * Whether the HTML support is configured in + */ +#if 1 +#define LIBXML_HTML_ENABLED +#endif + +/** + * LIBXML_LEGACY_ENABLED: + * + * Whether the deprecated APIs are compiled in for compatibility + */ +#if 1 +#define LIBXML_LEGACY_ENABLED +#endif + +/** + * LIBXML_C14N_ENABLED: + * + * Whether the Canonicalization support is configured in + */ +#if 1 +#define LIBXML_C14N_ENABLED +#endif + +/** + * LIBXML_CATALOG_ENABLED: + * + * Whether the Catalog support is configured in + */ +#if 1 +#define LIBXML_CATALOG_ENABLED +#endif + +/** + * LIBXML_XPATH_ENABLED: + * + * Whether XPath is configured in + */ +#if 1 +#define LIBXML_XPATH_ENABLED +#endif + +/** + * LIBXML_XPTR_ENABLED: + * + * Whether XPointer is configured in + */ +#if 1 +#define LIBXML_XPTR_ENABLED +#endif + +/** + * LIBXML_XPTR_LOCS_ENABLED: + * + * Whether support for XPointer locations is configured in + */ +#if 0 +#define LIBXML_XPTR_LOCS_ENABLED +#endif + +/** + * LIBXML_XINCLUDE_ENABLED: + * + * Whether XInclude is configured in + */ +#if 1 +#define LIBXML_XINCLUDE_ENABLED +#endif + +/** + * LIBXML_ICONV_ENABLED: + * + * Whether iconv support is available + */ +#if 1 +#define LIBXML_ICONV_ENABLED +#endif + +/** + * LIBXML_ICU_ENABLED: + * + * Whether icu support is available + */ +#if 0 +#define LIBXML_ICU_ENABLED +#endif + +/** + * LIBXML_ISO8859X_ENABLED: + * + * Whether ISO-8859-* support is made available in case iconv is not + */ +#if 1 +#define LIBXML_ISO8859X_ENABLED +#endif + +/** + * LIBXML_DEBUG_ENABLED: + * + * Whether Debugging module is configured in + */ +#if 1 +#define LIBXML_DEBUG_ENABLED +#endif + +/** + * LIBXML_UNICODE_ENABLED: + * + * Whether the Unicode related interfaces are compiled in + */ +#if 1 +#define LIBXML_UNICODE_ENABLED +#endif + +/** + * LIBXML_REGEXP_ENABLED: + * + * Whether the regular expressions interfaces are compiled in + */ +#if 1 +#define LIBXML_REGEXP_ENABLED +#endif + +/** + * LIBXML_AUTOMATA_ENABLED: + * + * Whether the automata interfaces are compiled in + */ +#if 1 +#define LIBXML_AUTOMATA_ENABLED +#endif + +/** + * LIBXML_SCHEMAS_ENABLED: + * + * Whether the Schemas validation interfaces are compiled in + */ +#if 1 +#define LIBXML_SCHEMAS_ENABLED +#endif + +/** + * LIBXML_SCHEMATRON_ENABLED: + * + * Whether the Schematron validation interfaces are compiled in + */ +#if 1 +#define LIBXML_SCHEMATRON_ENABLED +#endif + +/** + * LIBXML_MODULES_ENABLED: + * + * Whether the module interfaces are compiled in + */ +#if 1 +#define LIBXML_MODULES_ENABLED +/** + * LIBXML_MODULE_EXTENSION: + * + * the string suffix used by dynamic modules (usually shared libraries) + */ +#define LIBXML_MODULE_EXTENSION ".so" +#endif + +/** + * LIBXML_ZLIB_ENABLED: + * + * Whether the Zlib support is compiled in + */ +#if 1 +#define LIBXML_ZLIB_ENABLED +#endif + +/** + * LIBXML_LZMA_ENABLED: + * + * Whether the Lzma support is compiled in + */ +#if 0 +#define LIBXML_LZMA_ENABLED +#endif + +#include + +#endif + + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlwriter.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlwriter.h new file mode 100644 index 00000000..55f88bc7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xmlwriter.h @@ -0,0 +1,489 @@ +/* + * Summary: text writing API for XML + * Description: text writing API for XML + * + * Copy: See Copyright for the status of this software. + * + * Author: Alfred Mickautsch + */ + +#ifndef __XML_XMLWRITER_H__ +#define __XML_XMLWRITER_H__ + +#include + +#ifdef LIBXML_WRITER_ENABLED + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + typedef struct _xmlTextWriter xmlTextWriter; + typedef xmlTextWriter *xmlTextWriterPtr; + +/* + * Constructors & Destructor + */ + XMLPUBFUN xmlTextWriterPtr + xmlNewTextWriter(xmlOutputBufferPtr out); + XMLPUBFUN xmlTextWriterPtr + xmlNewTextWriterFilename(const char *uri, int compression); + XMLPUBFUN xmlTextWriterPtr + xmlNewTextWriterMemory(xmlBufferPtr buf, int compression); + XMLPUBFUN xmlTextWriterPtr + xmlNewTextWriterPushParser(xmlParserCtxtPtr ctxt, int compression); + XMLPUBFUN xmlTextWriterPtr + xmlNewTextWriterDoc(xmlDocPtr * doc, int compression); + XMLPUBFUN xmlTextWriterPtr + xmlNewTextWriterTree(xmlDocPtr doc, xmlNodePtr node, + int compression); + XMLPUBFUN void xmlFreeTextWriter(xmlTextWriterPtr writer); + +/* + * Functions + */ + + +/* + * Document + */ + XMLPUBFUN int + xmlTextWriterStartDocument(xmlTextWriterPtr writer, + const char *version, + const char *encoding, + const char *standalone); + XMLPUBFUN int xmlTextWriterEndDocument(xmlTextWriterPtr + writer); + +/* + * Comments + */ + XMLPUBFUN int xmlTextWriterStartComment(xmlTextWriterPtr + writer); + XMLPUBFUN int xmlTextWriterEndComment(xmlTextWriterPtr writer); + XMLPUBFUN int + xmlTextWriterWriteFormatComment(xmlTextWriterPtr writer, + const char *format, ...) + LIBXML_ATTR_FORMAT(2,3); + XMLPUBFUN int + xmlTextWriterWriteVFormatComment(xmlTextWriterPtr writer, + const char *format, + va_list argptr) + LIBXML_ATTR_FORMAT(2,0); + XMLPUBFUN int xmlTextWriterWriteComment(xmlTextWriterPtr + writer, + const xmlChar * + content); + +/* + * Elements + */ + XMLPUBFUN int + xmlTextWriterStartElement(xmlTextWriterPtr writer, + const xmlChar * name); + XMLPUBFUN int xmlTextWriterStartElementNS(xmlTextWriterPtr + writer, + const xmlChar * + prefix, + const xmlChar * name, + const xmlChar * + namespaceURI); + XMLPUBFUN int xmlTextWriterEndElement(xmlTextWriterPtr writer); + XMLPUBFUN int xmlTextWriterFullEndElement(xmlTextWriterPtr + writer); + +/* + * Elements conveniency functions + */ + XMLPUBFUN int + xmlTextWriterWriteFormatElement(xmlTextWriterPtr writer, + const xmlChar * name, + const char *format, ...) + LIBXML_ATTR_FORMAT(3,4); + XMLPUBFUN int + xmlTextWriterWriteVFormatElement(xmlTextWriterPtr writer, + const xmlChar * name, + const char *format, + va_list argptr) + LIBXML_ATTR_FORMAT(3,0); + XMLPUBFUN int xmlTextWriterWriteElement(xmlTextWriterPtr + writer, + const xmlChar * name, + const xmlChar * + content); + XMLPUBFUN int + xmlTextWriterWriteFormatElementNS(xmlTextWriterPtr writer, + const xmlChar * prefix, + const xmlChar * name, + const xmlChar * namespaceURI, + const char *format, ...) + LIBXML_ATTR_FORMAT(5,6); + XMLPUBFUN int + xmlTextWriterWriteVFormatElementNS(xmlTextWriterPtr writer, + const xmlChar * prefix, + const xmlChar * name, + const xmlChar * namespaceURI, + const char *format, + va_list argptr) + LIBXML_ATTR_FORMAT(5,0); + XMLPUBFUN int xmlTextWriterWriteElementNS(xmlTextWriterPtr + writer, + const xmlChar * + prefix, + const xmlChar * name, + const xmlChar * + namespaceURI, + const xmlChar * + content); + +/* + * Text + */ + XMLPUBFUN int + xmlTextWriterWriteFormatRaw(xmlTextWriterPtr writer, + const char *format, ...) + LIBXML_ATTR_FORMAT(2,3); + XMLPUBFUN int + xmlTextWriterWriteVFormatRaw(xmlTextWriterPtr writer, + const char *format, va_list argptr) + LIBXML_ATTR_FORMAT(2,0); + XMLPUBFUN int + xmlTextWriterWriteRawLen(xmlTextWriterPtr writer, + const xmlChar * content, int len); + XMLPUBFUN int + xmlTextWriterWriteRaw(xmlTextWriterPtr writer, + const xmlChar * content); + XMLPUBFUN int xmlTextWriterWriteFormatString(xmlTextWriterPtr + writer, + const char + *format, ...) + LIBXML_ATTR_FORMAT(2,3); + XMLPUBFUN int xmlTextWriterWriteVFormatString(xmlTextWriterPtr + writer, + const char + *format, + va_list argptr) + LIBXML_ATTR_FORMAT(2,0); + XMLPUBFUN int xmlTextWriterWriteString(xmlTextWriterPtr writer, + const xmlChar * + content); + XMLPUBFUN int xmlTextWriterWriteBase64(xmlTextWriterPtr writer, + const char *data, + int start, int len); + XMLPUBFUN int xmlTextWriterWriteBinHex(xmlTextWriterPtr writer, + const char *data, + int start, int len); + +/* + * Attributes + */ + XMLPUBFUN int + xmlTextWriterStartAttribute(xmlTextWriterPtr writer, + const xmlChar * name); + XMLPUBFUN int xmlTextWriterStartAttributeNS(xmlTextWriterPtr + writer, + const xmlChar * + prefix, + const xmlChar * + name, + const xmlChar * + namespaceURI); + XMLPUBFUN int xmlTextWriterEndAttribute(xmlTextWriterPtr + writer); + +/* + * Attributes conveniency functions + */ + XMLPUBFUN int + xmlTextWriterWriteFormatAttribute(xmlTextWriterPtr writer, + const xmlChar * name, + const char *format, ...) + LIBXML_ATTR_FORMAT(3,4); + XMLPUBFUN int + xmlTextWriterWriteVFormatAttribute(xmlTextWriterPtr writer, + const xmlChar * name, + const char *format, + va_list argptr) + LIBXML_ATTR_FORMAT(3,0); + XMLPUBFUN int xmlTextWriterWriteAttribute(xmlTextWriterPtr + writer, + const xmlChar * name, + const xmlChar * + content); + XMLPUBFUN int + xmlTextWriterWriteFormatAttributeNS(xmlTextWriterPtr writer, + const xmlChar * prefix, + const xmlChar * name, + const xmlChar * namespaceURI, + const char *format, ...) + LIBXML_ATTR_FORMAT(5,6); + XMLPUBFUN int + xmlTextWriterWriteVFormatAttributeNS(xmlTextWriterPtr writer, + const xmlChar * prefix, + const xmlChar * name, + const xmlChar * namespaceURI, + const char *format, + va_list argptr) + LIBXML_ATTR_FORMAT(5,0); + XMLPUBFUN int xmlTextWriterWriteAttributeNS(xmlTextWriterPtr + writer, + const xmlChar * + prefix, + const xmlChar * + name, + const xmlChar * + namespaceURI, + const xmlChar * + content); + +/* + * PI's + */ + XMLPUBFUN int + xmlTextWriterStartPI(xmlTextWriterPtr writer, + const xmlChar * target); + XMLPUBFUN int xmlTextWriterEndPI(xmlTextWriterPtr writer); + +/* + * PI conveniency functions + */ + XMLPUBFUN int + xmlTextWriterWriteFormatPI(xmlTextWriterPtr writer, + const xmlChar * target, + const char *format, ...) + LIBXML_ATTR_FORMAT(3,4); + XMLPUBFUN int + xmlTextWriterWriteVFormatPI(xmlTextWriterPtr writer, + const xmlChar * target, + const char *format, va_list argptr) + LIBXML_ATTR_FORMAT(3,0); + XMLPUBFUN int + xmlTextWriterWritePI(xmlTextWriterPtr writer, + const xmlChar * target, + const xmlChar * content); + +/** + * xmlTextWriterWriteProcessingInstruction: + * + * This macro maps to xmlTextWriterWritePI + */ +#define xmlTextWriterWriteProcessingInstruction xmlTextWriterWritePI + +/* + * CDATA + */ + XMLPUBFUN int xmlTextWriterStartCDATA(xmlTextWriterPtr writer); + XMLPUBFUN int xmlTextWriterEndCDATA(xmlTextWriterPtr writer); + +/* + * CDATA conveniency functions + */ + XMLPUBFUN int + xmlTextWriterWriteFormatCDATA(xmlTextWriterPtr writer, + const char *format, ...) + LIBXML_ATTR_FORMAT(2,3); + XMLPUBFUN int + xmlTextWriterWriteVFormatCDATA(xmlTextWriterPtr writer, + const char *format, va_list argptr) + LIBXML_ATTR_FORMAT(2,0); + XMLPUBFUN int + xmlTextWriterWriteCDATA(xmlTextWriterPtr writer, + const xmlChar * content); + +/* + * DTD + */ + XMLPUBFUN int + xmlTextWriterStartDTD(xmlTextWriterPtr writer, + const xmlChar * name, + const xmlChar * pubid, + const xmlChar * sysid); + XMLPUBFUN int xmlTextWriterEndDTD(xmlTextWriterPtr writer); + +/* + * DTD conveniency functions + */ + XMLPUBFUN int + xmlTextWriterWriteFormatDTD(xmlTextWriterPtr writer, + const xmlChar * name, + const xmlChar * pubid, + const xmlChar * sysid, + const char *format, ...) + LIBXML_ATTR_FORMAT(5,6); + XMLPUBFUN int + xmlTextWriterWriteVFormatDTD(xmlTextWriterPtr writer, + const xmlChar * name, + const xmlChar * pubid, + const xmlChar * sysid, + const char *format, va_list argptr) + LIBXML_ATTR_FORMAT(5,0); + XMLPUBFUN int + xmlTextWriterWriteDTD(xmlTextWriterPtr writer, + const xmlChar * name, + const xmlChar * pubid, + const xmlChar * sysid, + const xmlChar * subset); + +/** + * xmlTextWriterWriteDocType: + * + * this macro maps to xmlTextWriterWriteDTD + */ +#define xmlTextWriterWriteDocType xmlTextWriterWriteDTD + +/* + * DTD element definition + */ + XMLPUBFUN int + xmlTextWriterStartDTDElement(xmlTextWriterPtr writer, + const xmlChar * name); + XMLPUBFUN int xmlTextWriterEndDTDElement(xmlTextWriterPtr + writer); + +/* + * DTD element definition conveniency functions + */ + XMLPUBFUN int + xmlTextWriterWriteFormatDTDElement(xmlTextWriterPtr writer, + const xmlChar * name, + const char *format, ...) + LIBXML_ATTR_FORMAT(3,4); + XMLPUBFUN int + xmlTextWriterWriteVFormatDTDElement(xmlTextWriterPtr writer, + const xmlChar * name, + const char *format, + va_list argptr) + LIBXML_ATTR_FORMAT(3,0); + XMLPUBFUN int xmlTextWriterWriteDTDElement(xmlTextWriterPtr + writer, + const xmlChar * + name, + const xmlChar * + content); + +/* + * DTD attribute list definition + */ + XMLPUBFUN int + xmlTextWriterStartDTDAttlist(xmlTextWriterPtr writer, + const xmlChar * name); + XMLPUBFUN int xmlTextWriterEndDTDAttlist(xmlTextWriterPtr + writer); + +/* + * DTD attribute list definition conveniency functions + */ + XMLPUBFUN int + xmlTextWriterWriteFormatDTDAttlist(xmlTextWriterPtr writer, + const xmlChar * name, + const char *format, ...) + LIBXML_ATTR_FORMAT(3,4); + XMLPUBFUN int + xmlTextWriterWriteVFormatDTDAttlist(xmlTextWriterPtr writer, + const xmlChar * name, + const char *format, + va_list argptr) + LIBXML_ATTR_FORMAT(3,0); + XMLPUBFUN int xmlTextWriterWriteDTDAttlist(xmlTextWriterPtr + writer, + const xmlChar * + name, + const xmlChar * + content); + +/* + * DTD entity definition + */ + XMLPUBFUN int + xmlTextWriterStartDTDEntity(xmlTextWriterPtr writer, + int pe, const xmlChar * name); + XMLPUBFUN int xmlTextWriterEndDTDEntity(xmlTextWriterPtr + writer); + +/* + * DTD entity definition conveniency functions + */ + XMLPUBFUN int + xmlTextWriterWriteFormatDTDInternalEntity(xmlTextWriterPtr writer, + int pe, + const xmlChar * name, + const char *format, ...) + LIBXML_ATTR_FORMAT(4,5); + XMLPUBFUN int + xmlTextWriterWriteVFormatDTDInternalEntity(xmlTextWriterPtr writer, + int pe, + const xmlChar * name, + const char *format, + va_list argptr) + LIBXML_ATTR_FORMAT(4,0); + XMLPUBFUN int + xmlTextWriterWriteDTDInternalEntity(xmlTextWriterPtr writer, + int pe, + const xmlChar * name, + const xmlChar * content); + XMLPUBFUN int + xmlTextWriterWriteDTDExternalEntity(xmlTextWriterPtr writer, + int pe, + const xmlChar * name, + const xmlChar * pubid, + const xmlChar * sysid, + const xmlChar * ndataid); + XMLPUBFUN int + xmlTextWriterWriteDTDExternalEntityContents(xmlTextWriterPtr + writer, + const xmlChar * pubid, + const xmlChar * sysid, + const xmlChar * + ndataid); + XMLPUBFUN int xmlTextWriterWriteDTDEntity(xmlTextWriterPtr + writer, int pe, + const xmlChar * name, + const xmlChar * + pubid, + const xmlChar * + sysid, + const xmlChar * + ndataid, + const xmlChar * + content); + +/* + * DTD notation definition + */ + XMLPUBFUN int + xmlTextWriterWriteDTDNotation(xmlTextWriterPtr writer, + const xmlChar * name, + const xmlChar * pubid, + const xmlChar * sysid); + +/* + * Indentation + */ + XMLPUBFUN int + xmlTextWriterSetIndent(xmlTextWriterPtr writer, int indent); + XMLPUBFUN int + xmlTextWriterSetIndentString(xmlTextWriterPtr writer, + const xmlChar * str); + + XMLPUBFUN int + xmlTextWriterSetQuoteChar(xmlTextWriterPtr writer, xmlChar quotechar); + + +/* + * misc + */ + XMLPUBFUN int xmlTextWriterFlush(xmlTextWriterPtr writer); + XMLPUBFUN int xmlTextWriterClose(xmlTextWriterPtr writer); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_WRITER_ENABLED */ + +#endif /* __XML_XMLWRITER_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xpath.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xpath.h new file mode 100644 index 00000000..b89e105c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xpath.h @@ -0,0 +1,579 @@ +/* + * Summary: XML Path Language implementation + * Description: API for the XML Path Language implementation + * + * XML Path Language implementation + * XPath is a language for addressing parts of an XML document, + * designed to be used by both XSLT and XPointer + * http://www.w3.org/TR/xpath + * + * Implements + * W3C Recommendation 16 November 1999 + * http://www.w3.org/TR/1999/REC-xpath-19991116 + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XPATH_H__ +#define __XML_XPATH_H__ + +#include + +#ifdef LIBXML_XPATH_ENABLED + +#include +#include +#include +#endif /* LIBXML_XPATH_ENABLED */ + +#if defined(LIBXML_XPATH_ENABLED) || defined(LIBXML_SCHEMAS_ENABLED) +#ifdef __cplusplus +extern "C" { +#endif +#endif /* LIBXML_XPATH_ENABLED or LIBXML_SCHEMAS_ENABLED */ + +#ifdef LIBXML_XPATH_ENABLED + +typedef struct _xmlXPathContext xmlXPathContext; +typedef xmlXPathContext *xmlXPathContextPtr; +typedef struct _xmlXPathParserContext xmlXPathParserContext; +typedef xmlXPathParserContext *xmlXPathParserContextPtr; + +/** + * The set of XPath error codes. + */ + +typedef enum { + XPATH_EXPRESSION_OK = 0, + XPATH_NUMBER_ERROR, + XPATH_UNFINISHED_LITERAL_ERROR, + XPATH_START_LITERAL_ERROR, + XPATH_VARIABLE_REF_ERROR, + XPATH_UNDEF_VARIABLE_ERROR, + XPATH_INVALID_PREDICATE_ERROR, + XPATH_EXPR_ERROR, + XPATH_UNCLOSED_ERROR, + XPATH_UNKNOWN_FUNC_ERROR, + XPATH_INVALID_OPERAND, + XPATH_INVALID_TYPE, + XPATH_INVALID_ARITY, + XPATH_INVALID_CTXT_SIZE, + XPATH_INVALID_CTXT_POSITION, + XPATH_MEMORY_ERROR, + XPTR_SYNTAX_ERROR, + XPTR_RESOURCE_ERROR, + XPTR_SUB_RESOURCE_ERROR, + XPATH_UNDEF_PREFIX_ERROR, + XPATH_ENCODING_ERROR, + XPATH_INVALID_CHAR_ERROR, + XPATH_INVALID_CTXT, + XPATH_STACK_ERROR, + XPATH_FORBID_VARIABLE_ERROR, + XPATH_OP_LIMIT_EXCEEDED, + XPATH_RECURSION_LIMIT_EXCEEDED +} xmlXPathError; + +/* + * A node-set (an unordered collection of nodes without duplicates). + */ +typedef struct _xmlNodeSet xmlNodeSet; +typedef xmlNodeSet *xmlNodeSetPtr; +struct _xmlNodeSet { + int nodeNr; /* number of nodes in the set */ + int nodeMax; /* size of the array as allocated */ + xmlNodePtr *nodeTab; /* array of nodes in no particular order */ + /* @@ with_ns to check whether namespace nodes should be looked at @@ */ +}; + +/* + * An expression is evaluated to yield an object, which + * has one of the following four basic types: + * - node-set + * - boolean + * - number + * - string + * + * @@ XPointer will add more types ! + */ + +typedef enum { + XPATH_UNDEFINED = 0, + XPATH_NODESET = 1, + XPATH_BOOLEAN = 2, + XPATH_NUMBER = 3, + XPATH_STRING = 4, +#ifdef LIBXML_XPTR_LOCS_ENABLED + XPATH_POINT = 5, + XPATH_RANGE = 6, + XPATH_LOCATIONSET = 7, +#endif + XPATH_USERS = 8, + XPATH_XSLT_TREE = 9 /* An XSLT value tree, non modifiable */ +} xmlXPathObjectType; + +#ifndef LIBXML_XPTR_LOCS_ENABLED +/** DOC_DISABLE */ +#define XPATH_POINT 5 +#define XPATH_RANGE 6 +#define XPATH_LOCATIONSET 7 +/** DOC_ENABLE */ +#endif + +typedef struct _xmlXPathObject xmlXPathObject; +typedef xmlXPathObject *xmlXPathObjectPtr; +struct _xmlXPathObject { + xmlXPathObjectType type; + xmlNodeSetPtr nodesetval; + int boolval; + double floatval; + xmlChar *stringval; + void *user; + int index; + void *user2; + int index2; +}; + +/** + * xmlXPathConvertFunc: + * @obj: an XPath object + * @type: the number of the target type + * + * A conversion function is associated to a type and used to cast + * the new type to primitive values. + * + * Returns -1 in case of error, 0 otherwise + */ +typedef int (*xmlXPathConvertFunc) (xmlXPathObjectPtr obj, int type); + +/* + * Extra type: a name and a conversion function. + */ + +typedef struct _xmlXPathType xmlXPathType; +typedef xmlXPathType *xmlXPathTypePtr; +struct _xmlXPathType { + const xmlChar *name; /* the type name */ + xmlXPathConvertFunc func; /* the conversion function */ +}; + +/* + * Extra variable: a name and a value. + */ + +typedef struct _xmlXPathVariable xmlXPathVariable; +typedef xmlXPathVariable *xmlXPathVariablePtr; +struct _xmlXPathVariable { + const xmlChar *name; /* the variable name */ + xmlXPathObjectPtr value; /* the value */ +}; + +/** + * xmlXPathEvalFunc: + * @ctxt: an XPath parser context + * @nargs: the number of arguments passed to the function + * + * An XPath evaluation function, the parameters are on the XPath context stack. + */ + +typedef void (*xmlXPathEvalFunc)(xmlXPathParserContextPtr ctxt, + int nargs); + +/* + * Extra function: a name and a evaluation function. + */ + +typedef struct _xmlXPathFunct xmlXPathFunct; +typedef xmlXPathFunct *xmlXPathFuncPtr; +struct _xmlXPathFunct { + const xmlChar *name; /* the function name */ + xmlXPathEvalFunc func; /* the evaluation function */ +}; + +/** + * xmlXPathAxisFunc: + * @ctxt: the XPath interpreter context + * @cur: the previous node being explored on that axis + * + * An axis traversal function. To traverse an axis, the engine calls + * the first time with cur == NULL and repeat until the function returns + * NULL indicating the end of the axis traversal. + * + * Returns the next node in that axis or NULL if at the end of the axis. + */ + +typedef xmlXPathObjectPtr (*xmlXPathAxisFunc) (xmlXPathParserContextPtr ctxt, + xmlXPathObjectPtr cur); + +/* + * Extra axis: a name and an axis function. + */ + +typedef struct _xmlXPathAxis xmlXPathAxis; +typedef xmlXPathAxis *xmlXPathAxisPtr; +struct _xmlXPathAxis { + const xmlChar *name; /* the axis name */ + xmlXPathAxisFunc func; /* the search function */ +}; + +/** + * xmlXPathFunction: + * @ctxt: the XPath interprestation context + * @nargs: the number of arguments + * + * An XPath function. + * The arguments (if any) are popped out from the context stack + * and the result is pushed on the stack. + */ + +typedef void (*xmlXPathFunction) (xmlXPathParserContextPtr ctxt, int nargs); + +/* + * Function and Variable Lookup. + */ + +/** + * xmlXPathVariableLookupFunc: + * @ctxt: an XPath context + * @name: name of the variable + * @ns_uri: the namespace name hosting this variable + * + * Prototype for callbacks used to plug variable lookup in the XPath + * engine. + * + * Returns the XPath object value or NULL if not found. + */ +typedef xmlXPathObjectPtr (*xmlXPathVariableLookupFunc) (void *ctxt, + const xmlChar *name, + const xmlChar *ns_uri); + +/** + * xmlXPathFuncLookupFunc: + * @ctxt: an XPath context + * @name: name of the function + * @ns_uri: the namespace name hosting this function + * + * Prototype for callbacks used to plug function lookup in the XPath + * engine. + * + * Returns the XPath function or NULL if not found. + */ +typedef xmlXPathFunction (*xmlXPathFuncLookupFunc) (void *ctxt, + const xmlChar *name, + const xmlChar *ns_uri); + +/** + * xmlXPathFlags: + * Flags for XPath engine compilation and runtime + */ +/** + * XML_XPATH_CHECKNS: + * + * check namespaces at compilation + */ +#define XML_XPATH_CHECKNS (1<<0) +/** + * XML_XPATH_NOVAR: + * + * forbid variables in expression + */ +#define XML_XPATH_NOVAR (1<<1) + +/** + * xmlXPathContext: + * + * Expression evaluation occurs with respect to a context. + * he context consists of: + * - a node (the context node) + * - a node list (the context node list) + * - a set of variable bindings + * - a function library + * - the set of namespace declarations in scope for the expression + * Following the switch to hash tables, this need to be trimmed up at + * the next binary incompatible release. + * The node may be modified when the context is passed to libxml2 + * for an XPath evaluation so you may need to initialize it again + * before the next call. + */ + +struct _xmlXPathContext { + xmlDocPtr doc; /* The current document */ + xmlNodePtr node; /* The current node */ + + int nb_variables_unused; /* unused (hash table) */ + int max_variables_unused; /* unused (hash table) */ + xmlHashTablePtr varHash; /* Hash table of defined variables */ + + int nb_types; /* number of defined types */ + int max_types; /* max number of types */ + xmlXPathTypePtr types; /* Array of defined types */ + + int nb_funcs_unused; /* unused (hash table) */ + int max_funcs_unused; /* unused (hash table) */ + xmlHashTablePtr funcHash; /* Hash table of defined funcs */ + + int nb_axis; /* number of defined axis */ + int max_axis; /* max number of axis */ + xmlXPathAxisPtr axis; /* Array of defined axis */ + + /* the namespace nodes of the context node */ + xmlNsPtr *namespaces; /* Array of namespaces */ + int nsNr; /* number of namespace in scope */ + void *user; /* function to free */ + + /* extra variables */ + int contextSize; /* the context size */ + int proximityPosition; /* the proximity position */ + + /* extra stuff for XPointer */ + int xptr; /* is this an XPointer context? */ + xmlNodePtr here; /* for here() */ + xmlNodePtr origin; /* for origin() */ + + /* the set of namespace declarations in scope for the expression */ + xmlHashTablePtr nsHash; /* The namespaces hash table */ + xmlXPathVariableLookupFunc varLookupFunc;/* variable lookup func */ + void *varLookupData; /* variable lookup data */ + + /* Possibility to link in an extra item */ + void *extra; /* needed for XSLT */ + + /* The function name and URI when calling a function */ + const xmlChar *function; + const xmlChar *functionURI; + + /* function lookup function and data */ + xmlXPathFuncLookupFunc funcLookupFunc;/* function lookup func */ + void *funcLookupData; /* function lookup data */ + + /* temporary namespace lists kept for walking the namespace axis */ + xmlNsPtr *tmpNsList; /* Array of namespaces */ + int tmpNsNr; /* number of namespaces in scope */ + + /* error reporting mechanism */ + void *userData; /* user specific data block */ + xmlStructuredErrorFunc error; /* the callback in case of errors */ + xmlError lastError; /* the last error */ + xmlNodePtr debugNode; /* the source node XSLT */ + + /* dictionary */ + xmlDictPtr dict; /* dictionary if any */ + + int flags; /* flags to control compilation */ + + /* Cache for reusal of XPath objects */ + void *cache; + + /* Resource limits */ + unsigned long opLimit; + unsigned long opCount; + int depth; +}; + +/* + * The structure of a compiled expression form is not public. + */ + +typedef struct _xmlXPathCompExpr xmlXPathCompExpr; +typedef xmlXPathCompExpr *xmlXPathCompExprPtr; + +/** + * xmlXPathParserContext: + * + * An XPath parser context. It contains pure parsing information, + * an xmlXPathContext, and the stack of objects. + */ +struct _xmlXPathParserContext { + const xmlChar *cur; /* the current char being parsed */ + const xmlChar *base; /* the full expression */ + + int error; /* error code */ + + xmlXPathContextPtr context; /* the evaluation context */ + xmlXPathObjectPtr value; /* the current value */ + int valueNr; /* number of values stacked */ + int valueMax; /* max number of values stacked */ + xmlXPathObjectPtr *valueTab; /* stack of values */ + + xmlXPathCompExprPtr comp; /* the precompiled expression */ + int xptr; /* it this an XPointer expression */ + xmlNodePtr ancestor; /* used for walking preceding axis */ + + int valueFrame; /* always zero for compatibility */ +}; + +/************************************************************************ + * * + * Public API * + * * + ************************************************************************/ + +/** + * Objects and Nodesets handling + */ + +XMLPUBVAR double xmlXPathNAN; +XMLPUBVAR double xmlXPathPINF; +XMLPUBVAR double xmlXPathNINF; + +/* These macros may later turn into functions */ +/** + * xmlXPathNodeSetGetLength: + * @ns: a node-set + * + * Implement a functionality similar to the DOM NodeList.length. + * + * Returns the number of nodes in the node-set. + */ +#define xmlXPathNodeSetGetLength(ns) ((ns) ? (ns)->nodeNr : 0) +/** + * xmlXPathNodeSetItem: + * @ns: a node-set + * @index: index of a node in the set + * + * Implements a functionality similar to the DOM NodeList.item(). + * + * Returns the xmlNodePtr at the given @index in @ns or NULL if + * @index is out of range (0 to length-1) + */ +#define xmlXPathNodeSetItem(ns, index) \ + ((((ns) != NULL) && \ + ((index) >= 0) && ((index) < (ns)->nodeNr)) ? \ + (ns)->nodeTab[(index)] \ + : NULL) +/** + * xmlXPathNodeSetIsEmpty: + * @ns: a node-set + * + * Checks whether @ns is empty or not. + * + * Returns %TRUE if @ns is an empty node-set. + */ +#define xmlXPathNodeSetIsEmpty(ns) \ + (((ns) == NULL) || ((ns)->nodeNr == 0) || ((ns)->nodeTab == NULL)) + + +XMLPUBFUN void + xmlXPathFreeObject (xmlXPathObjectPtr obj); +XMLPUBFUN xmlNodeSetPtr + xmlXPathNodeSetCreate (xmlNodePtr val); +XMLPUBFUN void + xmlXPathFreeNodeSetList (xmlXPathObjectPtr obj); +XMLPUBFUN void + xmlXPathFreeNodeSet (xmlNodeSetPtr obj); +XMLPUBFUN xmlXPathObjectPtr + xmlXPathObjectCopy (xmlXPathObjectPtr val); +XMLPUBFUN int + xmlXPathCmpNodes (xmlNodePtr node1, + xmlNodePtr node2); +/** + * Conversion functions to basic types. + */ +XMLPUBFUN int + xmlXPathCastNumberToBoolean (double val); +XMLPUBFUN int + xmlXPathCastStringToBoolean (const xmlChar * val); +XMLPUBFUN int + xmlXPathCastNodeSetToBoolean(xmlNodeSetPtr ns); +XMLPUBFUN int + xmlXPathCastToBoolean (xmlXPathObjectPtr val); + +XMLPUBFUN double + xmlXPathCastBooleanToNumber (int val); +XMLPUBFUN double + xmlXPathCastStringToNumber (const xmlChar * val); +XMLPUBFUN double + xmlXPathCastNodeToNumber (xmlNodePtr node); +XMLPUBFUN double + xmlXPathCastNodeSetToNumber (xmlNodeSetPtr ns); +XMLPUBFUN double + xmlXPathCastToNumber (xmlXPathObjectPtr val); + +XMLPUBFUN xmlChar * + xmlXPathCastBooleanToString (int val); +XMLPUBFUN xmlChar * + xmlXPathCastNumberToString (double val); +XMLPUBFUN xmlChar * + xmlXPathCastNodeToString (xmlNodePtr node); +XMLPUBFUN xmlChar * + xmlXPathCastNodeSetToString (xmlNodeSetPtr ns); +XMLPUBFUN xmlChar * + xmlXPathCastToString (xmlXPathObjectPtr val); + +XMLPUBFUN xmlXPathObjectPtr + xmlXPathConvertBoolean (xmlXPathObjectPtr val); +XMLPUBFUN xmlXPathObjectPtr + xmlXPathConvertNumber (xmlXPathObjectPtr val); +XMLPUBFUN xmlXPathObjectPtr + xmlXPathConvertString (xmlXPathObjectPtr val); + +/** + * Context handling. + */ +XMLPUBFUN xmlXPathContextPtr + xmlXPathNewContext (xmlDocPtr doc); +XMLPUBFUN void + xmlXPathFreeContext (xmlXPathContextPtr ctxt); +XMLPUBFUN void + xmlXPathSetErrorHandler(xmlXPathContextPtr ctxt, + xmlStructuredErrorFunc handler, + void *context); +XMLPUBFUN int + xmlXPathContextSetCache(xmlXPathContextPtr ctxt, + int active, + int value, + int options); +/** + * Evaluation functions. + */ +XMLPUBFUN long + xmlXPathOrderDocElems (xmlDocPtr doc); +XMLPUBFUN int + xmlXPathSetContextNode (xmlNodePtr node, + xmlXPathContextPtr ctx); +XMLPUBFUN xmlXPathObjectPtr + xmlXPathNodeEval (xmlNodePtr node, + const xmlChar *str, + xmlXPathContextPtr ctx); +XMLPUBFUN xmlXPathObjectPtr + xmlXPathEval (const xmlChar *str, + xmlXPathContextPtr ctx); +XMLPUBFUN xmlXPathObjectPtr + xmlXPathEvalExpression (const xmlChar *str, + xmlXPathContextPtr ctxt); +XMLPUBFUN int + xmlXPathEvalPredicate (xmlXPathContextPtr ctxt, + xmlXPathObjectPtr res); +/** + * Separate compilation/evaluation entry points. + */ +XMLPUBFUN xmlXPathCompExprPtr + xmlXPathCompile (const xmlChar *str); +XMLPUBFUN xmlXPathCompExprPtr + xmlXPathCtxtCompile (xmlXPathContextPtr ctxt, + const xmlChar *str); +XMLPUBFUN xmlXPathObjectPtr + xmlXPathCompiledEval (xmlXPathCompExprPtr comp, + xmlXPathContextPtr ctx); +XMLPUBFUN int + xmlXPathCompiledEvalToBoolean(xmlXPathCompExprPtr comp, + xmlXPathContextPtr ctxt); +XMLPUBFUN void + xmlXPathFreeCompExpr (xmlXPathCompExprPtr comp); +#endif /* LIBXML_XPATH_ENABLED */ +#if defined(LIBXML_XPATH_ENABLED) || defined(LIBXML_SCHEMAS_ENABLED) +XML_DEPRECATED +XMLPUBFUN void + xmlXPathInit (void); +XMLPUBFUN int + xmlXPathIsNaN (double val); +XMLPUBFUN int + xmlXPathIsInf (double val); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_XPATH_ENABLED or LIBXML_SCHEMAS_ENABLED*/ +#endif /* ! __XML_XPATH_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xpathInternals.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xpathInternals.h new file mode 100644 index 00000000..d1c90dff --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xpathInternals.h @@ -0,0 +1,633 @@ +/* + * Summary: internal interfaces for XML Path Language implementation + * Description: internal interfaces for XML Path Language implementation + * used to build new modules on top of XPath like XPointer and + * XSLT + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XPATH_INTERNALS_H__ +#define __XML_XPATH_INTERNALS_H__ + +#include +#include +#include + +#ifdef LIBXML_XPATH_ENABLED + +#ifdef __cplusplus +extern "C" { +#endif + +/************************************************************************ + * * + * Helpers * + * * + ************************************************************************/ + +/* + * Many of these macros may later turn into functions. They + * shouldn't be used in #ifdef's preprocessor instructions. + */ +/** + * xmlXPathSetError: + * @ctxt: an XPath parser context + * @err: an xmlXPathError code + * + * Raises an error. + */ +#define xmlXPathSetError(ctxt, err) \ + { xmlXPatherror((ctxt), __FILE__, __LINE__, (err)); \ + if ((ctxt) != NULL) (ctxt)->error = (err); } + +/** + * xmlXPathSetArityError: + * @ctxt: an XPath parser context + * + * Raises an XPATH_INVALID_ARITY error. + */ +#define xmlXPathSetArityError(ctxt) \ + xmlXPathSetError((ctxt), XPATH_INVALID_ARITY) + +/** + * xmlXPathSetTypeError: + * @ctxt: an XPath parser context + * + * Raises an XPATH_INVALID_TYPE error. + */ +#define xmlXPathSetTypeError(ctxt) \ + xmlXPathSetError((ctxt), XPATH_INVALID_TYPE) + +/** + * xmlXPathGetError: + * @ctxt: an XPath parser context + * + * Get the error code of an XPath context. + * + * Returns the context error. + */ +#define xmlXPathGetError(ctxt) ((ctxt)->error) + +/** + * xmlXPathCheckError: + * @ctxt: an XPath parser context + * + * Check if an XPath error was raised. + * + * Returns true if an error has been raised, false otherwise. + */ +#define xmlXPathCheckError(ctxt) ((ctxt)->error != XPATH_EXPRESSION_OK) + +/** + * xmlXPathGetDocument: + * @ctxt: an XPath parser context + * + * Get the document of an XPath context. + * + * Returns the context document. + */ +#define xmlXPathGetDocument(ctxt) ((ctxt)->context->doc) + +/** + * xmlXPathGetContextNode: + * @ctxt: an XPath parser context + * + * Get the context node of an XPath context. + * + * Returns the context node. + */ +#define xmlXPathGetContextNode(ctxt) ((ctxt)->context->node) + +XMLPUBFUN int + xmlXPathPopBoolean (xmlXPathParserContextPtr ctxt); +XMLPUBFUN double + xmlXPathPopNumber (xmlXPathParserContextPtr ctxt); +XMLPUBFUN xmlChar * + xmlXPathPopString (xmlXPathParserContextPtr ctxt); +XMLPUBFUN xmlNodeSetPtr + xmlXPathPopNodeSet (xmlXPathParserContextPtr ctxt); +XMLPUBFUN void * + xmlXPathPopExternal (xmlXPathParserContextPtr ctxt); + +/** + * xmlXPathReturnBoolean: + * @ctxt: an XPath parser context + * @val: a boolean + * + * Pushes the boolean @val on the context stack. + */ +#define xmlXPathReturnBoolean(ctxt, val) \ + valuePush((ctxt), xmlXPathNewBoolean(val)) + +/** + * xmlXPathReturnTrue: + * @ctxt: an XPath parser context + * + * Pushes true on the context stack. + */ +#define xmlXPathReturnTrue(ctxt) xmlXPathReturnBoolean((ctxt), 1) + +/** + * xmlXPathReturnFalse: + * @ctxt: an XPath parser context + * + * Pushes false on the context stack. + */ +#define xmlXPathReturnFalse(ctxt) xmlXPathReturnBoolean((ctxt), 0) + +/** + * xmlXPathReturnNumber: + * @ctxt: an XPath parser context + * @val: a double + * + * Pushes the double @val on the context stack. + */ +#define xmlXPathReturnNumber(ctxt, val) \ + valuePush((ctxt), xmlXPathNewFloat(val)) + +/** + * xmlXPathReturnString: + * @ctxt: an XPath parser context + * @str: a string + * + * Pushes the string @str on the context stack. + */ +#define xmlXPathReturnString(ctxt, str) \ + valuePush((ctxt), xmlXPathWrapString(str)) + +/** + * xmlXPathReturnEmptyString: + * @ctxt: an XPath parser context + * + * Pushes an empty string on the stack. + */ +#define xmlXPathReturnEmptyString(ctxt) \ + valuePush((ctxt), xmlXPathNewCString("")) + +/** + * xmlXPathReturnNodeSet: + * @ctxt: an XPath parser context + * @ns: a node-set + * + * Pushes the node-set @ns on the context stack. + */ +#define xmlXPathReturnNodeSet(ctxt, ns) \ + valuePush((ctxt), xmlXPathWrapNodeSet(ns)) + +/** + * xmlXPathReturnEmptyNodeSet: + * @ctxt: an XPath parser context + * + * Pushes an empty node-set on the context stack. + */ +#define xmlXPathReturnEmptyNodeSet(ctxt) \ + valuePush((ctxt), xmlXPathNewNodeSet(NULL)) + +/** + * xmlXPathReturnExternal: + * @ctxt: an XPath parser context + * @val: user data + * + * Pushes user data on the context stack. + */ +#define xmlXPathReturnExternal(ctxt, val) \ + valuePush((ctxt), xmlXPathWrapExternal(val)) + +/** + * xmlXPathStackIsNodeSet: + * @ctxt: an XPath parser context + * + * Check if the current value on the XPath stack is a node set or + * an XSLT value tree. + * + * Returns true if the current object on the stack is a node-set. + */ +#define xmlXPathStackIsNodeSet(ctxt) \ + (((ctxt)->value != NULL) \ + && (((ctxt)->value->type == XPATH_NODESET) \ + || ((ctxt)->value->type == XPATH_XSLT_TREE))) + +/** + * xmlXPathStackIsExternal: + * @ctxt: an XPath parser context + * + * Checks if the current value on the XPath stack is an external + * object. + * + * Returns true if the current object on the stack is an external + * object. + */ +#define xmlXPathStackIsExternal(ctxt) \ + ((ctxt->value != NULL) && (ctxt->value->type == XPATH_USERS)) + +/** + * xmlXPathEmptyNodeSet: + * @ns: a node-set + * + * Empties a node-set. + */ +#define xmlXPathEmptyNodeSet(ns) \ + { while ((ns)->nodeNr > 0) (ns)->nodeTab[--(ns)->nodeNr] = NULL; } + +/** + * CHECK_ERROR: + * + * Macro to return from the function if an XPath error was detected. + */ +#define CHECK_ERROR \ + if (ctxt->error != XPATH_EXPRESSION_OK) return + +/** + * CHECK_ERROR0: + * + * Macro to return 0 from the function if an XPath error was detected. + */ +#define CHECK_ERROR0 \ + if (ctxt->error != XPATH_EXPRESSION_OK) return(0) + +/** + * XP_ERROR: + * @X: the error code + * + * Macro to raise an XPath error and return. + */ +#define XP_ERROR(X) \ + { xmlXPathErr(ctxt, X); return; } + +/** + * XP_ERROR0: + * @X: the error code + * + * Macro to raise an XPath error and return 0. + */ +#define XP_ERROR0(X) \ + { xmlXPathErr(ctxt, X); return(0); } + +/** + * CHECK_TYPE: + * @typeval: the XPath type + * + * Macro to check that the value on top of the XPath stack is of a given + * type. + */ +#define CHECK_TYPE(typeval) \ + if ((ctxt->value == NULL) || (ctxt->value->type != typeval)) \ + XP_ERROR(XPATH_INVALID_TYPE) + +/** + * CHECK_TYPE0: + * @typeval: the XPath type + * + * Macro to check that the value on top of the XPath stack is of a given + * type. Return(0) in case of failure + */ +#define CHECK_TYPE0(typeval) \ + if ((ctxt->value == NULL) || (ctxt->value->type != typeval)) \ + XP_ERROR0(XPATH_INVALID_TYPE) + +/** + * CHECK_ARITY: + * @x: the number of expected args + * + * Macro to check that the number of args passed to an XPath function matches. + */ +#define CHECK_ARITY(x) \ + if (ctxt == NULL) return; \ + if (nargs != (x)) \ + XP_ERROR(XPATH_INVALID_ARITY); \ + if (ctxt->valueNr < (x)) \ + XP_ERROR(XPATH_STACK_ERROR); + +/** + * CAST_TO_STRING: + * + * Macro to try to cast the value on the top of the XPath stack to a string. + */ +#define CAST_TO_STRING \ + if ((ctxt->value != NULL) && (ctxt->value->type != XPATH_STRING)) \ + xmlXPathStringFunction(ctxt, 1); + +/** + * CAST_TO_NUMBER: + * + * Macro to try to cast the value on the top of the XPath stack to a number. + */ +#define CAST_TO_NUMBER \ + if ((ctxt->value != NULL) && (ctxt->value->type != XPATH_NUMBER)) \ + xmlXPathNumberFunction(ctxt, 1); + +/** + * CAST_TO_BOOLEAN: + * + * Macro to try to cast the value on the top of the XPath stack to a boolean. + */ +#define CAST_TO_BOOLEAN \ + if ((ctxt->value != NULL) && (ctxt->value->type != XPATH_BOOLEAN)) \ + xmlXPathBooleanFunction(ctxt, 1); + +/* + * Variable Lookup forwarding. + */ + +XMLPUBFUN void + xmlXPathRegisterVariableLookup (xmlXPathContextPtr ctxt, + xmlXPathVariableLookupFunc f, + void *data); + +/* + * Function Lookup forwarding. + */ + +XMLPUBFUN void + xmlXPathRegisterFuncLookup (xmlXPathContextPtr ctxt, + xmlXPathFuncLookupFunc f, + void *funcCtxt); + +/* + * Error reporting. + */ +XMLPUBFUN void + xmlXPatherror (xmlXPathParserContextPtr ctxt, + const char *file, + int line, + int no); + +XMLPUBFUN void + xmlXPathErr (xmlXPathParserContextPtr ctxt, + int error); + +#ifdef LIBXML_DEBUG_ENABLED +XMLPUBFUN void + xmlXPathDebugDumpObject (FILE *output, + xmlXPathObjectPtr cur, + int depth); +XMLPUBFUN void + xmlXPathDebugDumpCompExpr(FILE *output, + xmlXPathCompExprPtr comp, + int depth); +#endif +/** + * NodeSet handling. + */ +XMLPUBFUN int + xmlXPathNodeSetContains (xmlNodeSetPtr cur, + xmlNodePtr val); +XMLPUBFUN xmlNodeSetPtr + xmlXPathDifference (xmlNodeSetPtr nodes1, + xmlNodeSetPtr nodes2); +XMLPUBFUN xmlNodeSetPtr + xmlXPathIntersection (xmlNodeSetPtr nodes1, + xmlNodeSetPtr nodes2); + +XMLPUBFUN xmlNodeSetPtr + xmlXPathDistinctSorted (xmlNodeSetPtr nodes); +XMLPUBFUN xmlNodeSetPtr + xmlXPathDistinct (xmlNodeSetPtr nodes); + +XMLPUBFUN int + xmlXPathHasSameNodes (xmlNodeSetPtr nodes1, + xmlNodeSetPtr nodes2); + +XMLPUBFUN xmlNodeSetPtr + xmlXPathNodeLeadingSorted (xmlNodeSetPtr nodes, + xmlNodePtr node); +XMLPUBFUN xmlNodeSetPtr + xmlXPathLeadingSorted (xmlNodeSetPtr nodes1, + xmlNodeSetPtr nodes2); +XMLPUBFUN xmlNodeSetPtr + xmlXPathNodeLeading (xmlNodeSetPtr nodes, + xmlNodePtr node); +XMLPUBFUN xmlNodeSetPtr + xmlXPathLeading (xmlNodeSetPtr nodes1, + xmlNodeSetPtr nodes2); + +XMLPUBFUN xmlNodeSetPtr + xmlXPathNodeTrailingSorted (xmlNodeSetPtr nodes, + xmlNodePtr node); +XMLPUBFUN xmlNodeSetPtr + xmlXPathTrailingSorted (xmlNodeSetPtr nodes1, + xmlNodeSetPtr nodes2); +XMLPUBFUN xmlNodeSetPtr + xmlXPathNodeTrailing (xmlNodeSetPtr nodes, + xmlNodePtr node); +XMLPUBFUN xmlNodeSetPtr + xmlXPathTrailing (xmlNodeSetPtr nodes1, + xmlNodeSetPtr nodes2); + + +/** + * Extending a context. + */ + +XMLPUBFUN int + xmlXPathRegisterNs (xmlXPathContextPtr ctxt, + const xmlChar *prefix, + const xmlChar *ns_uri); +XMLPUBFUN const xmlChar * + xmlXPathNsLookup (xmlXPathContextPtr ctxt, + const xmlChar *prefix); +XMLPUBFUN void + xmlXPathRegisteredNsCleanup (xmlXPathContextPtr ctxt); + +XMLPUBFUN int + xmlXPathRegisterFunc (xmlXPathContextPtr ctxt, + const xmlChar *name, + xmlXPathFunction f); +XMLPUBFUN int + xmlXPathRegisterFuncNS (xmlXPathContextPtr ctxt, + const xmlChar *name, + const xmlChar *ns_uri, + xmlXPathFunction f); +XMLPUBFUN int + xmlXPathRegisterVariable (xmlXPathContextPtr ctxt, + const xmlChar *name, + xmlXPathObjectPtr value); +XMLPUBFUN int + xmlXPathRegisterVariableNS (xmlXPathContextPtr ctxt, + const xmlChar *name, + const xmlChar *ns_uri, + xmlXPathObjectPtr value); +XMLPUBFUN xmlXPathFunction + xmlXPathFunctionLookup (xmlXPathContextPtr ctxt, + const xmlChar *name); +XMLPUBFUN xmlXPathFunction + xmlXPathFunctionLookupNS (xmlXPathContextPtr ctxt, + const xmlChar *name, + const xmlChar *ns_uri); +XMLPUBFUN void + xmlXPathRegisteredFuncsCleanup (xmlXPathContextPtr ctxt); +XMLPUBFUN xmlXPathObjectPtr + xmlXPathVariableLookup (xmlXPathContextPtr ctxt, + const xmlChar *name); +XMLPUBFUN xmlXPathObjectPtr + xmlXPathVariableLookupNS (xmlXPathContextPtr ctxt, + const xmlChar *name, + const xmlChar *ns_uri); +XMLPUBFUN void + xmlXPathRegisteredVariablesCleanup(xmlXPathContextPtr ctxt); + +/** + * Utilities to extend XPath. + */ +XMLPUBFUN xmlXPathParserContextPtr + xmlXPathNewParserContext (const xmlChar *str, + xmlXPathContextPtr ctxt); +XMLPUBFUN void + xmlXPathFreeParserContext (xmlXPathParserContextPtr ctxt); + +/* TODO: remap to xmlXPathValuePop and Push. */ +XMLPUBFUN xmlXPathObjectPtr + valuePop (xmlXPathParserContextPtr ctxt); +XMLPUBFUN int + valuePush (xmlXPathParserContextPtr ctxt, + xmlXPathObjectPtr value); + +XMLPUBFUN xmlXPathObjectPtr + xmlXPathNewString (const xmlChar *val); +XMLPUBFUN xmlXPathObjectPtr + xmlXPathNewCString (const char *val); +XMLPUBFUN xmlXPathObjectPtr + xmlXPathWrapString (xmlChar *val); +XMLPUBFUN xmlXPathObjectPtr + xmlXPathWrapCString (char * val); +XMLPUBFUN xmlXPathObjectPtr + xmlXPathNewFloat (double val); +XMLPUBFUN xmlXPathObjectPtr + xmlXPathNewBoolean (int val); +XMLPUBFUN xmlXPathObjectPtr + xmlXPathNewNodeSet (xmlNodePtr val); +XMLPUBFUN xmlXPathObjectPtr + xmlXPathNewValueTree (xmlNodePtr val); +XMLPUBFUN int + xmlXPathNodeSetAdd (xmlNodeSetPtr cur, + xmlNodePtr val); +XMLPUBFUN int + xmlXPathNodeSetAddUnique (xmlNodeSetPtr cur, + xmlNodePtr val); +XMLPUBFUN int + xmlXPathNodeSetAddNs (xmlNodeSetPtr cur, + xmlNodePtr node, + xmlNsPtr ns); +XMLPUBFUN void + xmlXPathNodeSetSort (xmlNodeSetPtr set); + +XMLPUBFUN void + xmlXPathRoot (xmlXPathParserContextPtr ctxt); +XMLPUBFUN void + xmlXPathEvalExpr (xmlXPathParserContextPtr ctxt); +XMLPUBFUN xmlChar * + xmlXPathParseName (xmlXPathParserContextPtr ctxt); +XMLPUBFUN xmlChar * + xmlXPathParseNCName (xmlXPathParserContextPtr ctxt); + +/* + * Existing functions. + */ +XMLPUBFUN double + xmlXPathStringEvalNumber (const xmlChar *str); +XMLPUBFUN int + xmlXPathEvaluatePredicateResult (xmlXPathParserContextPtr ctxt, + xmlXPathObjectPtr res); +XMLPUBFUN void + xmlXPathRegisterAllFunctions (xmlXPathContextPtr ctxt); +XMLPUBFUN xmlNodeSetPtr + xmlXPathNodeSetMerge (xmlNodeSetPtr val1, + xmlNodeSetPtr val2); +XMLPUBFUN void + xmlXPathNodeSetDel (xmlNodeSetPtr cur, + xmlNodePtr val); +XMLPUBFUN void + xmlXPathNodeSetRemove (xmlNodeSetPtr cur, + int val); +XMLPUBFUN xmlXPathObjectPtr + xmlXPathNewNodeSetList (xmlNodeSetPtr val); +XMLPUBFUN xmlXPathObjectPtr + xmlXPathWrapNodeSet (xmlNodeSetPtr val); +XMLPUBFUN xmlXPathObjectPtr + xmlXPathWrapExternal (void *val); + +XMLPUBFUN int xmlXPathEqualValues(xmlXPathParserContextPtr ctxt); +XMLPUBFUN int xmlXPathNotEqualValues(xmlXPathParserContextPtr ctxt); +XMLPUBFUN int xmlXPathCompareValues(xmlXPathParserContextPtr ctxt, int inf, int strict); +XMLPUBFUN void xmlXPathValueFlipSign(xmlXPathParserContextPtr ctxt); +XMLPUBFUN void xmlXPathAddValues(xmlXPathParserContextPtr ctxt); +XMLPUBFUN void xmlXPathSubValues(xmlXPathParserContextPtr ctxt); +XMLPUBFUN void xmlXPathMultValues(xmlXPathParserContextPtr ctxt); +XMLPUBFUN void xmlXPathDivValues(xmlXPathParserContextPtr ctxt); +XMLPUBFUN void xmlXPathModValues(xmlXPathParserContextPtr ctxt); + +XMLPUBFUN int xmlXPathIsNodeType(const xmlChar *name); + +/* + * Some of the axis navigation routines. + */ +XMLPUBFUN xmlNodePtr xmlXPathNextSelf(xmlXPathParserContextPtr ctxt, + xmlNodePtr cur); +XMLPUBFUN xmlNodePtr xmlXPathNextChild(xmlXPathParserContextPtr ctxt, + xmlNodePtr cur); +XMLPUBFUN xmlNodePtr xmlXPathNextDescendant(xmlXPathParserContextPtr ctxt, + xmlNodePtr cur); +XMLPUBFUN xmlNodePtr xmlXPathNextDescendantOrSelf(xmlXPathParserContextPtr ctxt, + xmlNodePtr cur); +XMLPUBFUN xmlNodePtr xmlXPathNextParent(xmlXPathParserContextPtr ctxt, + xmlNodePtr cur); +XMLPUBFUN xmlNodePtr xmlXPathNextAncestorOrSelf(xmlXPathParserContextPtr ctxt, + xmlNodePtr cur); +XMLPUBFUN xmlNodePtr xmlXPathNextFollowingSibling(xmlXPathParserContextPtr ctxt, + xmlNodePtr cur); +XMLPUBFUN xmlNodePtr xmlXPathNextFollowing(xmlXPathParserContextPtr ctxt, + xmlNodePtr cur); +XMLPUBFUN xmlNodePtr xmlXPathNextNamespace(xmlXPathParserContextPtr ctxt, + xmlNodePtr cur); +XMLPUBFUN xmlNodePtr xmlXPathNextAttribute(xmlXPathParserContextPtr ctxt, + xmlNodePtr cur); +XMLPUBFUN xmlNodePtr xmlXPathNextPreceding(xmlXPathParserContextPtr ctxt, + xmlNodePtr cur); +XMLPUBFUN xmlNodePtr xmlXPathNextAncestor(xmlXPathParserContextPtr ctxt, + xmlNodePtr cur); +XMLPUBFUN xmlNodePtr xmlXPathNextPrecedingSibling(xmlXPathParserContextPtr ctxt, + xmlNodePtr cur); +/* + * The official core of XPath functions. + */ +XMLPUBFUN void xmlXPathLastFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathPositionFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathCountFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathIdFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathLocalNameFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathNamespaceURIFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathStringFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathStringLengthFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathConcatFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathContainsFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathStartsWithFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathSubstringFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathSubstringBeforeFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathSubstringAfterFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathNormalizeFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathTranslateFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathNotFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathTrueFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathFalseFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathLangFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathNumberFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathSumFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathFloorFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathCeilingFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathRoundFunction(xmlXPathParserContextPtr ctxt, int nargs); +XMLPUBFUN void xmlXPathBooleanFunction(xmlXPathParserContextPtr ctxt, int nargs); + +/** + * Really internal functions + */ +XMLPUBFUN void xmlXPathNodeSetFreeNs(xmlNsPtr ns); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_XPATH_ENABLED */ +#endif /* ! __XML_XPATH_INTERNALS_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xpointer.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xpointer.h new file mode 100644 index 00000000..a5260008 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxml2/libxml/xpointer.h @@ -0,0 +1,138 @@ +/* + * Summary: API to handle XML Pointers + * Description: API to handle XML Pointers + * Base implementation was made accordingly to + * W3C Candidate Recommendation 7 June 2000 + * http://www.w3.org/TR/2000/CR-xptr-20000607 + * + * Added support for the element() scheme described in: + * W3C Proposed Recommendation 13 November 2002 + * http://www.w3.org/TR/2002/PR-xptr-element-20021113/ + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XPTR_H__ +#define __XML_XPTR_H__ + +#include + +#ifdef LIBXML_XPTR_ENABLED + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(LIBXML_XPTR_LOCS_ENABLED) +/* + * A Location Set + */ +typedef struct _xmlLocationSet xmlLocationSet; +typedef xmlLocationSet *xmlLocationSetPtr; +struct _xmlLocationSet { + int locNr; /* number of locations in the set */ + int locMax; /* size of the array as allocated */ + xmlXPathObjectPtr *locTab;/* array of locations */ +}; + +/* + * Handling of location sets. + */ + +XML_DEPRECATED +XMLPUBFUN xmlLocationSetPtr + xmlXPtrLocationSetCreate (xmlXPathObjectPtr val); +XML_DEPRECATED +XMLPUBFUN void + xmlXPtrFreeLocationSet (xmlLocationSetPtr obj); +XML_DEPRECATED +XMLPUBFUN xmlLocationSetPtr + xmlXPtrLocationSetMerge (xmlLocationSetPtr val1, + xmlLocationSetPtr val2); +XML_DEPRECATED +XMLPUBFUN xmlXPathObjectPtr + xmlXPtrNewRange (xmlNodePtr start, + int startindex, + xmlNodePtr end, + int endindex); +XML_DEPRECATED +XMLPUBFUN xmlXPathObjectPtr + xmlXPtrNewRangePoints (xmlXPathObjectPtr start, + xmlXPathObjectPtr end); +XML_DEPRECATED +XMLPUBFUN xmlXPathObjectPtr + xmlXPtrNewRangeNodePoint (xmlNodePtr start, + xmlXPathObjectPtr end); +XML_DEPRECATED +XMLPUBFUN xmlXPathObjectPtr + xmlXPtrNewRangePointNode (xmlXPathObjectPtr start, + xmlNodePtr end); +XML_DEPRECATED +XMLPUBFUN xmlXPathObjectPtr + xmlXPtrNewRangeNodes (xmlNodePtr start, + xmlNodePtr end); +XML_DEPRECATED +XMLPUBFUN xmlXPathObjectPtr + xmlXPtrNewLocationSetNodes (xmlNodePtr start, + xmlNodePtr end); +XML_DEPRECATED +XMLPUBFUN xmlXPathObjectPtr + xmlXPtrNewLocationSetNodeSet(xmlNodeSetPtr set); +XML_DEPRECATED +XMLPUBFUN xmlXPathObjectPtr + xmlXPtrNewRangeNodeObject (xmlNodePtr start, + xmlXPathObjectPtr end); +XML_DEPRECATED +XMLPUBFUN xmlXPathObjectPtr + xmlXPtrNewCollapsedRange (xmlNodePtr start); +XML_DEPRECATED +XMLPUBFUN void + xmlXPtrLocationSetAdd (xmlLocationSetPtr cur, + xmlXPathObjectPtr val); +XML_DEPRECATED +XMLPUBFUN xmlXPathObjectPtr + xmlXPtrWrapLocationSet (xmlLocationSetPtr val); +XML_DEPRECATED +XMLPUBFUN void + xmlXPtrLocationSetDel (xmlLocationSetPtr cur, + xmlXPathObjectPtr val); +XML_DEPRECATED +XMLPUBFUN void + xmlXPtrLocationSetRemove (xmlLocationSetPtr cur, + int val); +#endif /* defined(LIBXML_XPTR_LOCS_ENABLED) */ + +/* + * Functions. + */ +XMLPUBFUN xmlXPathContextPtr + xmlXPtrNewContext (xmlDocPtr doc, + xmlNodePtr here, + xmlNodePtr origin); +XMLPUBFUN xmlXPathObjectPtr + xmlXPtrEval (const xmlChar *str, + xmlXPathContextPtr ctx); + +#if defined(LIBXML_XPTR_LOCS_ENABLED) +XML_DEPRECATED +XMLPUBFUN void + xmlXPtrRangeToFunction (xmlXPathParserContextPtr ctxt, + int nargs); +XML_DEPRECATED +XMLPUBFUN xmlNodePtr + xmlXPtrBuildNodeList (xmlXPathObjectPtr obj); +XML_DEPRECATED +XMLPUBFUN void + xmlXPtrEvalRangePredicate (xmlXPathParserContextPtr ctxt); +#endif /* defined(LIBXML_XPTR_LOCS_ENABLED) */ +#ifdef __cplusplus +} +#endif + +#endif /* LIBXML_XPTR_ENABLED */ +#endif /* __XML_XPTR_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/attributes.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/attributes.h new file mode 100644 index 00000000..d9b99a74 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/attributes.h @@ -0,0 +1,39 @@ +/* + * Summary: interface for the XSLT attribute handling + * Description: this module handles the specificities of attribute + * and attribute groups processing. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XSLT_ATTRIBUTES_H__ +#define __XML_XSLT_ATTRIBUTES_H__ + +#include +#include "xsltexports.h" +#include "xsltInternals.h" + +#ifdef __cplusplus +extern "C" { +#endif + +XSLTPUBFUN void XSLTCALL + xsltParseStylesheetAttributeSet (xsltStylesheetPtr style, + xmlNodePtr cur); +XSLTPUBFUN void XSLTCALL + xsltFreeAttributeSetsHashes (xsltStylesheetPtr style); +XSLTPUBFUN void XSLTCALL + xsltApplyAttributeSet (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst, + const xmlChar *attributes); +XSLTPUBFUN void XSLTCALL + xsltResolveStylesheetAttributeSet(xsltStylesheetPtr style); +#ifdef __cplusplus +} +#endif + +#endif /* __XML_XSLT_ATTRIBUTES_H__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/documents.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/documents.h new file mode 100644 index 00000000..ae7c0ca2 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/documents.h @@ -0,0 +1,93 @@ +/* + * Summary: interface for the document handling + * Description: implements document loading and cache (multiple + * document() reference for the same resources must + * be equal. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XSLT_DOCUMENTS_H__ +#define __XML_XSLT_DOCUMENTS_H__ + +#include +#include "xsltexports.h" +#include "xsltInternals.h" + +#ifdef __cplusplus +extern "C" { +#endif + +XSLTPUBFUN xsltDocumentPtr XSLTCALL + xsltNewDocument (xsltTransformContextPtr ctxt, + xmlDocPtr doc); +XSLTPUBFUN xsltDocumentPtr XSLTCALL + xsltLoadDocument (xsltTransformContextPtr ctxt, + const xmlChar *URI); +XSLTPUBFUN xsltDocumentPtr XSLTCALL + xsltFindDocument (xsltTransformContextPtr ctxt, + xmlDocPtr doc); +XSLTPUBFUN void XSLTCALL + xsltFreeDocuments (xsltTransformContextPtr ctxt); + +XSLTPUBFUN xsltDocumentPtr XSLTCALL + xsltLoadStyleDocument (xsltStylesheetPtr style, + const xmlChar *URI); +XSLTPUBFUN xsltDocumentPtr XSLTCALL + xsltNewStyleDocument (xsltStylesheetPtr style, + xmlDocPtr doc); +XSLTPUBFUN void XSLTCALL + xsltFreeStyleDocuments (xsltStylesheetPtr style); + +/* + * Hooks for document loading + */ + +/** + * xsltLoadType: + * + * Enum defining the kind of loader requirement. + */ +typedef enum { + XSLT_LOAD_START = 0, /* loading for a top stylesheet */ + XSLT_LOAD_STYLESHEET = 1, /* loading for a stylesheet include/import */ + XSLT_LOAD_DOCUMENT = 2 /* loading document at transformation time */ +} xsltLoadType; + +/** + * xsltDocLoaderFunc: + * @URI: the URI of the document to load + * @dict: the dictionary to use when parsing that document + * @options: parsing options, a set of xmlParserOption + * @ctxt: the context, either a stylesheet or a transformation context + * @type: the xsltLoadType indicating the kind of loading required + * + * An xsltDocLoaderFunc is a signature for a function which can be + * registered to load document not provided by the compilation or + * transformation API themselve, for example when an xsl:import, + * xsl:include is found at compilation time or when a document() + * call is made at runtime. + * + * Returns the pointer to the document (which will be modified and + * freed by the engine later), or NULL in case of error. + */ +typedef xmlDocPtr (*xsltDocLoaderFunc) (const xmlChar *URI, + xmlDictPtr dict, + int options, + void *ctxt, + xsltLoadType type); + +XSLTPUBFUN void XSLTCALL + xsltSetLoaderFunc (xsltDocLoaderFunc f); + +/* the loader may be needed by extension libraries so it is exported */ +XSLTPUBVAR xsltDocLoaderFunc xsltDocDefaultLoader; + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_XSLT_DOCUMENTS_H__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/extensions.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/extensions.h new file mode 100644 index 00000000..84d6aa44 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/extensions.h @@ -0,0 +1,262 @@ +/* + * Summary: interface for the extension support + * Description: This provide the API needed for simple and module + * extension support. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XSLT_EXTENSION_H__ +#define __XML_XSLT_EXTENSION_H__ + +#include +#include "xsltexports.h" +#include "xsltInternals.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Extension Modules API. + */ + +/** + * xsltInitGlobals: + * + * Initialize the global variables for extensions + * + */ + +XSLTPUBFUN void XSLTCALL + xsltInitGlobals (void); + +/** + * xsltStyleExtInitFunction: + * @ctxt: an XSLT stylesheet + * @URI: the namespace URI for the extension + * + * A function called at initialization time of an XSLT extension module. + * + * Returns a pointer to the module specific data for this transformation. + */ +typedef void * (*xsltStyleExtInitFunction) (xsltStylesheetPtr style, + const xmlChar *URI); + +/** + * xsltStyleExtShutdownFunction: + * @ctxt: an XSLT stylesheet + * @URI: the namespace URI for the extension + * @data: the data associated to this module + * + * A function called at shutdown time of an XSLT extension module. + */ +typedef void (*xsltStyleExtShutdownFunction) (xsltStylesheetPtr style, + const xmlChar *URI, + void *data); + +/** + * xsltExtInitFunction: + * @ctxt: an XSLT transformation context + * @URI: the namespace URI for the extension + * + * A function called at initialization time of an XSLT extension module. + * + * Returns a pointer to the module specific data for this transformation. + */ +typedef void * (*xsltExtInitFunction) (xsltTransformContextPtr ctxt, + const xmlChar *URI); + +/** + * xsltExtShutdownFunction: + * @ctxt: an XSLT transformation context + * @URI: the namespace URI for the extension + * @data: the data associated to this module + * + * A function called at shutdown time of an XSLT extension module. + */ +typedef void (*xsltExtShutdownFunction) (xsltTransformContextPtr ctxt, + const xmlChar *URI, + void *data); + +XSLTPUBFUN int XSLTCALL + xsltRegisterExtModule (const xmlChar *URI, + xsltExtInitFunction initFunc, + xsltExtShutdownFunction shutdownFunc); +XSLTPUBFUN int XSLTCALL + xsltRegisterExtModuleFull + (const xmlChar * URI, + xsltExtInitFunction initFunc, + xsltExtShutdownFunction shutdownFunc, + xsltStyleExtInitFunction styleInitFunc, + xsltStyleExtShutdownFunction styleShutdownFunc); + +XSLTPUBFUN int XSLTCALL + xsltUnregisterExtModule (const xmlChar * URI); + +XSLTPUBFUN void * XSLTCALL + xsltGetExtData (xsltTransformContextPtr ctxt, + const xmlChar *URI); + +XSLTPUBFUN void * XSLTCALL + xsltStyleGetExtData (xsltStylesheetPtr style, + const xmlChar *URI); +#ifdef XSLT_REFACTORED +XSLTPUBFUN void * XSLTCALL + xsltStyleStylesheetLevelGetExtData( + xsltStylesheetPtr style, + const xmlChar * URI); +#endif +XSLTPUBFUN void XSLTCALL + xsltShutdownCtxtExts (xsltTransformContextPtr ctxt); + +XSLTPUBFUN void XSLTCALL + xsltShutdownExts (xsltStylesheetPtr style); + +XSLTPUBFUN xsltTransformContextPtr XSLTCALL + xsltXPathGetTransformContext + (xmlXPathParserContextPtr ctxt); + +/* + * extension functions +*/ +XSLTPUBFUN int XSLTCALL + xsltRegisterExtModuleFunction + (const xmlChar *name, + const xmlChar *URI, + xmlXPathFunction function); +XSLTPUBFUN xmlXPathFunction XSLTCALL + xsltExtModuleFunctionLookup (const xmlChar *name, + const xmlChar *URI); +XSLTPUBFUN int XSLTCALL + xsltUnregisterExtModuleFunction + (const xmlChar *name, + const xmlChar *URI); + +/* + * extension elements + */ +typedef xsltElemPreCompPtr (*xsltPreComputeFunction) + (xsltStylesheetPtr style, + xmlNodePtr inst, + xsltTransformFunction function); + +XSLTPUBFUN xsltElemPreCompPtr XSLTCALL + xsltNewElemPreComp (xsltStylesheetPtr style, + xmlNodePtr inst, + xsltTransformFunction function); +XSLTPUBFUN void XSLTCALL + xsltInitElemPreComp (xsltElemPreCompPtr comp, + xsltStylesheetPtr style, + xmlNodePtr inst, + xsltTransformFunction function, + xsltElemPreCompDeallocator freeFunc); + +XSLTPUBFUN int XSLTCALL + xsltRegisterExtModuleElement + (const xmlChar *name, + const xmlChar *URI, + xsltPreComputeFunction precomp, + xsltTransformFunction transform); +XSLTPUBFUN xsltTransformFunction XSLTCALL + xsltExtElementLookup (xsltTransformContextPtr ctxt, + const xmlChar *name, + const xmlChar *URI); +XSLTPUBFUN xsltTransformFunction XSLTCALL + xsltExtModuleElementLookup + (const xmlChar *name, + const xmlChar *URI); +XSLTPUBFUN xsltPreComputeFunction XSLTCALL + xsltExtModuleElementPreComputeLookup + (const xmlChar *name, + const xmlChar *URI); +XSLTPUBFUN int XSLTCALL + xsltUnregisterExtModuleElement + (const xmlChar *name, + const xmlChar *URI); + +/* + * top-level elements + */ +typedef void (*xsltTopLevelFunction) (xsltStylesheetPtr style, + xmlNodePtr inst); + +XSLTPUBFUN int XSLTCALL + xsltRegisterExtModuleTopLevel + (const xmlChar *name, + const xmlChar *URI, + xsltTopLevelFunction function); +XSLTPUBFUN xsltTopLevelFunction XSLTCALL + xsltExtModuleTopLevelLookup + (const xmlChar *name, + const xmlChar *URI); +XSLTPUBFUN int XSLTCALL + xsltUnregisterExtModuleTopLevel + (const xmlChar *name, + const xmlChar *URI); + + +/* These 2 functions are deprecated for use within modules. */ +XSLTPUBFUN int XSLTCALL + xsltRegisterExtFunction (xsltTransformContextPtr ctxt, + const xmlChar *name, + const xmlChar *URI, + xmlXPathFunction function); +XSLTPUBFUN int XSLTCALL + xsltRegisterExtElement (xsltTransformContextPtr ctxt, + const xmlChar *name, + const xmlChar *URI, + xsltTransformFunction function); + +/* + * Extension Prefix handling API. + * Those are used by the XSLT (pre)processor. + */ + +XSLTPUBFUN int XSLTCALL + xsltRegisterExtPrefix (xsltStylesheetPtr style, + const xmlChar *prefix, + const xmlChar *URI); +XSLTPUBFUN int XSLTCALL + xsltCheckExtPrefix (xsltStylesheetPtr style, + const xmlChar *URI); +XSLTPUBFUN int XSLTCALL + xsltCheckExtURI (xsltStylesheetPtr style, + const xmlChar *URI); +XSLTPUBFUN int XSLTCALL + xsltInitCtxtExts (xsltTransformContextPtr ctxt); +XSLTPUBFUN void XSLTCALL + xsltFreeCtxtExts (xsltTransformContextPtr ctxt); +XSLTPUBFUN void XSLTCALL + xsltFreeExts (xsltStylesheetPtr style); + +XSLTPUBFUN xsltElemPreCompPtr XSLTCALL + xsltPreComputeExtModuleElement + (xsltStylesheetPtr style, + xmlNodePtr inst); +/* + * Extension Infos access. + * Used by exslt initialisation + */ + +XSLTPUBFUN xmlHashTablePtr XSLTCALL + xsltGetExtInfo (xsltStylesheetPtr style, + const xmlChar *URI); + +/** + * Test of the extension module API + */ +XSLTPUBFUN void XSLTCALL + xsltRegisterTestModule (void); +XSLTPUBFUN void XSLTCALL + xsltDebugDumpExtensions (FILE * output); + + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_XSLT_EXTENSION_H__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/extra.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/extra.h new file mode 100644 index 00000000..e512fd03 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/extra.h @@ -0,0 +1,72 @@ +/* + * Summary: interface for the non-standard features + * Description: implement some extension outside the XSLT namespace + * but not EXSLT with is in a different library. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XSLT_EXTRA_H__ +#define __XML_XSLT_EXTRA_H__ + +#include +#include "xsltexports.h" +#include "xsltInternals.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * XSLT_LIBXSLT_NAMESPACE: + * + * This is the libxslt namespace for specific extensions. + */ +#define XSLT_LIBXSLT_NAMESPACE ((xmlChar *) "http://xmlsoft.org/XSLT/namespace") + +/** + * XSLT_SAXON_NAMESPACE: + * + * This is Michael Kay's Saxon processor namespace for extensions. + */ +#define XSLT_SAXON_NAMESPACE ((xmlChar *) "http://icl.com/saxon") + +/** + * XSLT_XT_NAMESPACE: + * + * This is James Clark's XT processor namespace for extensions. + */ +#define XSLT_XT_NAMESPACE ((xmlChar *) "http://www.jclark.com/xt") + +/** + * XSLT_XALAN_NAMESPACE: + * + * This is the Apache project XALAN processor namespace for extensions. + */ +#define XSLT_XALAN_NAMESPACE ((xmlChar *) \ + "org.apache.xalan.xslt.extensions.Redirect") + + +XSLTPUBFUN void XSLTCALL + xsltFunctionNodeSet (xmlXPathParserContextPtr ctxt, + int nargs); +XSLTPUBFUN void XSLTCALL + xsltDebug (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst, + xsltElemPreCompPtr comp); + + +XSLTPUBFUN void XSLTCALL + xsltRegisterExtras (xsltTransformContextPtr ctxt); +XSLTPUBFUN void XSLTCALL + xsltRegisterAllExtras (void); + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_XSLT_EXTRA_H__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/functions.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/functions.h new file mode 100644 index 00000000..5455b7f4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/functions.h @@ -0,0 +1,78 @@ +/* + * Summary: interface for the XSLT functions not from XPath + * Description: a set of extra functions coming from XSLT but not in XPath + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard and Bjorn Reese + */ + +#ifndef __XML_XSLT_FUNCTIONS_H__ +#define __XML_XSLT_FUNCTIONS_H__ + +#include +#include +#include "xsltexports.h" +#include "xsltInternals.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * XSLT_REGISTER_FUNCTION_LOOKUP: + * + * Registering macro, not general purpose at all but used in different modules. + */ +#define XSLT_REGISTER_FUNCTION_LOOKUP(ctxt) \ + xmlXPathRegisterFuncLookup((ctxt)->xpathCtxt, \ + xsltXPathFunctionLookup, \ + (void *)(ctxt->xpathCtxt)); + +XSLTPUBFUN xmlXPathFunction XSLTCALL + xsltXPathFunctionLookup (void *vctxt, + const xmlChar *name, + const xmlChar *ns_uri); + +/* + * Interfaces for the functions implementations. + */ + +XSLTPUBFUN void XSLTCALL + xsltDocumentFunction (xmlXPathParserContextPtr ctxt, + int nargs); +XSLTPUBFUN void XSLTCALL + xsltKeyFunction (xmlXPathParserContextPtr ctxt, + int nargs); +XSLTPUBFUN void XSLTCALL + xsltUnparsedEntityURIFunction (xmlXPathParserContextPtr ctxt, + int nargs); +XSLTPUBFUN void XSLTCALL + xsltFormatNumberFunction (xmlXPathParserContextPtr ctxt, + int nargs); +XSLTPUBFUN void XSLTCALL + xsltGenerateIdFunction (xmlXPathParserContextPtr ctxt, + int nargs); +XSLTPUBFUN void XSLTCALL + xsltSystemPropertyFunction (xmlXPathParserContextPtr ctxt, + int nargs); +XSLTPUBFUN void XSLTCALL + xsltElementAvailableFunction (xmlXPathParserContextPtr ctxt, + int nargs); +XSLTPUBFUN void XSLTCALL + xsltFunctionAvailableFunction (xmlXPathParserContextPtr ctxt, + int nargs); + +/* + * And the registration + */ + +XSLTPUBFUN void XSLTCALL + xsltRegisterAllFunctions (xmlXPathContextPtr ctxt); + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_XSLT_FUNCTIONS_H__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/imports.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/imports.h new file mode 100644 index 00000000..95e44e51 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/imports.h @@ -0,0 +1,75 @@ +/* + * Summary: interface for the XSLT import support + * Description: macros and fuctions needed to implement and + * access the import tree + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XSLT_IMPORTS_H__ +#define __XML_XSLT_IMPORTS_H__ + +#include +#include "xsltexports.h" +#include "xsltInternals.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * XSLT_GET_IMPORT_PTR: + * + * A macro to import pointers from the stylesheet cascading order. + */ +#define XSLT_GET_IMPORT_PTR(res, style, name) { \ + xsltStylesheetPtr st = style; \ + res = NULL; \ + while (st != NULL) { \ + if (st->name != NULL) { res = st->name; break; } \ + st = xsltNextImport(st); \ + }} + +/** + * XSLT_GET_IMPORT_INT: + * + * A macro to import intergers from the stylesheet cascading order. + */ +#define XSLT_GET_IMPORT_INT(res, style, name) { \ + xsltStylesheetPtr st = style; \ + res = -1; \ + while (st != NULL) { \ + if (st->name != -1) { res = st->name; break; } \ + st = xsltNextImport(st); \ + }} + +/* + * Module interfaces + */ +XSLTPUBFUN int XSLTCALL + xsltParseStylesheetImport(xsltStylesheetPtr style, + xmlNodePtr cur); +XSLTPUBFUN int XSLTCALL + xsltParseStylesheetInclude + (xsltStylesheetPtr style, + xmlNodePtr cur); +XSLTPUBFUN xsltStylesheetPtr XSLTCALL + xsltNextImport (xsltStylesheetPtr style); +XSLTPUBFUN int XSLTCALL + xsltNeedElemSpaceHandling(xsltTransformContextPtr ctxt); +XSLTPUBFUN int XSLTCALL + xsltFindElemSpaceHandling(xsltTransformContextPtr ctxt, + xmlNodePtr node); +XSLTPUBFUN xsltTemplatePtr XSLTCALL + xsltFindTemplate (xsltTransformContextPtr ctxt, + const xmlChar *name, + const xmlChar *nameURI); + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_XSLT_IMPORTS_H__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/keys.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/keys.h new file mode 100644 index 00000000..757d1224 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/keys.h @@ -0,0 +1,53 @@ +/* + * Summary: interface for the key matching used in key() and template matches. + * Description: implementation of the key mechanims. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XSLT_KEY_H__ +#define __XML_XSLT_KEY_H__ + +#include +#include "xsltexports.h" +#include "xsltInternals.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * NODE_IS_KEYED: + * + * check for bit 15 set + */ +#define NODE_IS_KEYED (1 >> 15) + +XSLTPUBFUN int XSLTCALL + xsltAddKey (xsltStylesheetPtr style, + const xmlChar *name, + const xmlChar *nameURI, + const xmlChar *match, + const xmlChar *use, + xmlNodePtr inst); +XSLTPUBFUN xmlNodeSetPtr XSLTCALL + xsltGetKey (xsltTransformContextPtr ctxt, + const xmlChar *name, + const xmlChar *nameURI, + const xmlChar *value); +XSLTPUBFUN void XSLTCALL + xsltInitCtxtKeys (xsltTransformContextPtr ctxt, + xsltDocumentPtr doc); +XSLTPUBFUN void XSLTCALL + xsltFreeKeys (xsltStylesheetPtr style); +XSLTPUBFUN void XSLTCALL + xsltFreeDocumentKeys (xsltDocumentPtr doc); + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_XSLT_H__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/namespaces.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/namespaces.h new file mode 100644 index 00000000..fa2d3b4c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/namespaces.h @@ -0,0 +1,68 @@ +/* + * Summary: interface for the XSLT namespace handling + * Description: set of function easing the processing and generation + * of namespace nodes in XSLT. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XSLT_NAMESPACES_H__ +#define __XML_XSLT_NAMESPACES_H__ + +#include +#include "xsltexports.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Used within nsAliases hashtable when the default namespace is required + * but it's not been explicitly defined + */ +/** + * UNDEFINED_DEFAULT_NS: + * + * Special value for undefined namespace, internal + */ +#define UNDEFINED_DEFAULT_NS (const xmlChar *) -1L + +XSLTPUBFUN void XSLTCALL + xsltNamespaceAlias (xsltStylesheetPtr style, + xmlNodePtr node); +XSLTPUBFUN xmlNsPtr XSLTCALL + xsltGetNamespace (xsltTransformContextPtr ctxt, + xmlNodePtr cur, + xmlNsPtr ns, + xmlNodePtr out); +XSLTPUBFUN xmlNsPtr XSLTCALL + xsltGetPlainNamespace (xsltTransformContextPtr ctxt, + xmlNodePtr cur, + xmlNsPtr ns, + xmlNodePtr out); +XSLTPUBFUN xmlNsPtr XSLTCALL + xsltGetSpecialNamespace (xsltTransformContextPtr ctxt, + xmlNodePtr cur, + const xmlChar *URI, + const xmlChar *prefix, + xmlNodePtr out); +XSLTPUBFUN xmlNsPtr XSLTCALL + xsltCopyNamespace (xsltTransformContextPtr ctxt, + xmlNodePtr elem, + xmlNsPtr ns); +XSLTPUBFUN xmlNsPtr XSLTCALL + xsltCopyNamespaceList (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNsPtr cur); +XSLTPUBFUN void XSLTCALL + xsltFreeNamespaceAliasHashes + (xsltStylesheetPtr style); + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_XSLT_NAMESPACES_H__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/numbersInternals.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/numbersInternals.h new file mode 100644 index 00000000..85245928 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/numbersInternals.h @@ -0,0 +1,73 @@ +/* + * Summary: Implementation of the XSLT number functions + * Description: Implementation of the XSLT number functions + * + * Copy: See Copyright for the status of this software. + * + * Author: Bjorn Reese and Daniel Veillard + */ + +#ifndef __XML_XSLT_NUMBERSINTERNALS_H__ +#define __XML_XSLT_NUMBERSINTERNALS_H__ + +#include +#include "xsltexports.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct _xsltCompMatch; + +/** + * xsltNumberData: + * + * This data structure is just a wrapper to pass xsl:number data in. + */ +typedef struct _xsltNumberData xsltNumberData; +typedef xsltNumberData *xsltNumberDataPtr; + +struct _xsltNumberData { + const xmlChar *level; + const xmlChar *count; + const xmlChar *from; + const xmlChar *value; + const xmlChar *format; + int has_format; + int digitsPerGroup; + int groupingCharacter; + int groupingCharacterLen; + xmlDocPtr doc; + xmlNodePtr node; + struct _xsltCompMatch *countPat; + struct _xsltCompMatch *fromPat; + + /* + * accelerators + */ +}; + +/** + * xsltFormatNumberInfo,: + * + * This data structure lists the various parameters needed to format numbers. + */ +typedef struct _xsltFormatNumberInfo xsltFormatNumberInfo; +typedef xsltFormatNumberInfo *xsltFormatNumberInfoPtr; + +struct _xsltFormatNumberInfo { + int integer_hash; /* Number of '#' in integer part */ + int integer_digits; /* Number of '0' in integer part */ + int frac_digits; /* Number of '0' in fractional part */ + int frac_hash; /* Number of '#' in fractional part */ + int group; /* Number of chars per display 'group' */ + int multiplier; /* Scaling for percent or permille */ + char add_decimal; /* Flag for whether decimal point appears in pattern */ + char is_multiplier_set; /* Flag to catch multiple occurences of percent/permille */ + char is_negative_pattern;/* Flag for processing -ve prefix/suffix */ +}; + +#ifdef __cplusplus +} +#endif +#endif /* __XML_XSLT_NUMBERSINTERNALS_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/pattern.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/pattern.h new file mode 100644 index 00000000..a0991c0c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/pattern.h @@ -0,0 +1,84 @@ +/* + * Summary: interface for the pattern matching used in template matches. + * Description: the implementation of the lookup of the right template + * for a given node must be really fast in order to keep + * decent performances. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XSLT_PATTERN_H__ +#define __XML_XSLT_PATTERN_H__ + +#include "xsltInternals.h" +#include "xsltexports.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * xsltCompMatch: + * + * Data structure used for the implementation of patterns. + * It is kept private (in pattern.c). + */ +typedef struct _xsltCompMatch xsltCompMatch; +typedef xsltCompMatch *xsltCompMatchPtr; + +/* + * Pattern related interfaces. + */ + +XSLTPUBFUN xsltCompMatchPtr XSLTCALL + xsltCompilePattern (const xmlChar *pattern, + xmlDocPtr doc, + xmlNodePtr node, + xsltStylesheetPtr style, + xsltTransformContextPtr runtime); +XSLTPUBFUN void XSLTCALL + xsltFreeCompMatchList (xsltCompMatchPtr comp); +XSLTPUBFUN int XSLTCALL + xsltTestCompMatchList (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xsltCompMatchPtr comp); +XSLTPUBFUN void XSLTCALL + xsltCompMatchClearCache (xsltTransformContextPtr ctxt, + xsltCompMatchPtr comp); +XSLTPUBFUN void XSLTCALL + xsltNormalizeCompSteps (void *payload, + void *data, + const xmlChar *name); + +/* + * Template related interfaces. + */ +XSLTPUBFUN int XSLTCALL + xsltAddTemplate (xsltStylesheetPtr style, + xsltTemplatePtr cur, + const xmlChar *mode, + const xmlChar *modeURI); +XSLTPUBFUN xsltTemplatePtr XSLTCALL + xsltGetTemplate (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xsltStylesheetPtr style); +XSLTPUBFUN void XSLTCALL + xsltFreeTemplateHashes (xsltStylesheetPtr style); +XSLTPUBFUN void XSLTCALL + xsltCleanupTemplates (xsltStylesheetPtr style); + +#if 0 +int xsltMatchPattern (xsltTransformContextPtr ctxt, + xmlNodePtr node, + const xmlChar *pattern, + xmlDocPtr ctxtdoc, + xmlNodePtr ctxtnode); +#endif +#ifdef __cplusplus +} +#endif + +#endif /* __XML_XSLT_PATTERN_H__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/preproc.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/preproc.h new file mode 100644 index 00000000..2a2fc7e4 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/preproc.h @@ -0,0 +1,43 @@ +/* + * Summary: precomputing stylesheets + * Description: this is the compilation phase, where most of the + * stylesheet is "compiled" into faster to use data. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XSLT_PRECOMP_H__ +#define __XML_XSLT_PRECOMP_H__ + +#include +#include "xsltexports.h" +#include "xsltInternals.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Interfaces + */ +XSLTPUBVAR const xmlChar *xsltExtMarker; + +XSLTPUBFUN xsltElemPreCompPtr XSLTCALL + xsltDocumentComp (xsltStylesheetPtr style, + xmlNodePtr inst, + xsltTransformFunction function); + +XSLTPUBFUN void XSLTCALL + xsltStylePreCompute (xsltStylesheetPtr style, + xmlNodePtr inst); +XSLTPUBFUN void XSLTCALL + xsltFreeStylePreComps (xsltStylesheetPtr style); + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_XSLT_PRECOMP_H__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/security.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/security.h new file mode 100644 index 00000000..bab5c8c6 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/security.h @@ -0,0 +1,104 @@ +/* + * Summary: interface for the libxslt security framework + * Description: the libxslt security framework allow to restrict + * the access to new resources (file or URL) from + * the stylesheet at runtime. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XSLT_SECURITY_H__ +#define __XML_XSLT_SECURITY_H__ + +#include +#include "xsltexports.h" +#include "xsltInternals.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * xsltSecurityPref: + * + * structure to indicate the preferences for security in the XSLT + * transformation. + */ +typedef struct _xsltSecurityPrefs xsltSecurityPrefs; +typedef xsltSecurityPrefs *xsltSecurityPrefsPtr; + +/** + * xsltSecurityOption: + * + * the set of option that can be configured + */ +typedef enum { + XSLT_SECPREF_READ_FILE = 1, + XSLT_SECPREF_WRITE_FILE, + XSLT_SECPREF_CREATE_DIRECTORY, + XSLT_SECPREF_READ_NETWORK, + XSLT_SECPREF_WRITE_NETWORK +} xsltSecurityOption; + +/** + * xsltSecurityCheck: + * + * User provided function to check the value of a string like a file + * path or an URL ... + */ +typedef int (*xsltSecurityCheck) (xsltSecurityPrefsPtr sec, + xsltTransformContextPtr ctxt, + const char *value); + +/* + * Module interfaces + */ +XSLTPUBFUN xsltSecurityPrefsPtr XSLTCALL + xsltNewSecurityPrefs (void); +XSLTPUBFUN void XSLTCALL + xsltFreeSecurityPrefs (xsltSecurityPrefsPtr sec); +XSLTPUBFUN int XSLTCALL + xsltSetSecurityPrefs (xsltSecurityPrefsPtr sec, + xsltSecurityOption option, + xsltSecurityCheck func); +XSLTPUBFUN xsltSecurityCheck XSLTCALL + xsltGetSecurityPrefs (xsltSecurityPrefsPtr sec, + xsltSecurityOption option); + +XSLTPUBFUN void XSLTCALL + xsltSetDefaultSecurityPrefs (xsltSecurityPrefsPtr sec); +XSLTPUBFUN xsltSecurityPrefsPtr XSLTCALL + xsltGetDefaultSecurityPrefs (void); + +XSLTPUBFUN int XSLTCALL + xsltSetCtxtSecurityPrefs (xsltSecurityPrefsPtr sec, + xsltTransformContextPtr ctxt); + +XSLTPUBFUN int XSLTCALL + xsltSecurityAllow (xsltSecurityPrefsPtr sec, + xsltTransformContextPtr ctxt, + const char *value); +XSLTPUBFUN int XSLTCALL + xsltSecurityForbid (xsltSecurityPrefsPtr sec, + xsltTransformContextPtr ctxt, + const char *value); +/* + * internal interfaces + */ +XSLTPUBFUN int XSLTCALL + xsltCheckWrite (xsltSecurityPrefsPtr sec, + xsltTransformContextPtr ctxt, + const xmlChar *URL); +XSLTPUBFUN int XSLTCALL + xsltCheckRead (xsltSecurityPrefsPtr sec, + xsltTransformContextPtr ctxt, + const xmlChar *URL); + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_XSLT_SECURITY_H__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/templates.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/templates.h new file mode 100644 index 00000000..84a9de4d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/templates.h @@ -0,0 +1,77 @@ +/* + * Summary: interface for the template processing + * Description: This set of routine encapsulates XPath calls + * and Attribute Value Templates evaluation. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XSLT_TEMPLATES_H__ +#define __XML_XSLT_TEMPLATES_H__ + +#include +#include +#include "xsltexports.h" +#include "xsltInternals.h" + +#ifdef __cplusplus +extern "C" { +#endif + +XSLTPUBFUN int XSLTCALL + xsltEvalXPathPredicate (xsltTransformContextPtr ctxt, + xmlXPathCompExprPtr comp, + xmlNsPtr *nsList, + int nsNr); +XSLTPUBFUN xmlChar * XSLTCALL + xsltEvalTemplateString (xsltTransformContextPtr ctxt, + xmlNodePtr contextNode, + xmlNodePtr inst); +XSLTPUBFUN xmlChar * XSLTCALL + xsltEvalAttrValueTemplate (xsltTransformContextPtr ctxt, + xmlNodePtr node, + const xmlChar *name, + const xmlChar *ns); +XSLTPUBFUN const xmlChar * XSLTCALL + xsltEvalStaticAttrValueTemplate (xsltStylesheetPtr style, + xmlNodePtr node, + const xmlChar *name, + const xmlChar *ns, + int *found); + +/* TODO: this is obviously broken ... the namespaces should be passed too ! */ +XSLTPUBFUN xmlChar * XSLTCALL + xsltEvalXPathString (xsltTransformContextPtr ctxt, + xmlXPathCompExprPtr comp); +XSLTPUBFUN xmlChar * XSLTCALL + xsltEvalXPathStringNs (xsltTransformContextPtr ctxt, + xmlXPathCompExprPtr comp, + int nsNr, + xmlNsPtr *nsList); + +XSLTPUBFUN xmlNodePtr * XSLTCALL + xsltTemplateProcess (xsltTransformContextPtr ctxt, + xmlNodePtr node); +XSLTPUBFUN xmlAttrPtr XSLTCALL + xsltAttrListTemplateProcess (xsltTransformContextPtr ctxt, + xmlNodePtr target, + xmlAttrPtr cur); +XSLTPUBFUN xmlAttrPtr XSLTCALL + xsltAttrTemplateProcess (xsltTransformContextPtr ctxt, + xmlNodePtr target, + xmlAttrPtr attr); +XSLTPUBFUN xmlChar * XSLTCALL + xsltAttrTemplateValueProcess (xsltTransformContextPtr ctxt, + const xmlChar* attr); +XSLTPUBFUN xmlChar * XSLTCALL + xsltAttrTemplateValueProcessNode(xsltTransformContextPtr ctxt, + const xmlChar* str, + xmlNodePtr node); +#ifdef __cplusplus +} +#endif + +#endif /* __XML_XSLT_TEMPLATES_H__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/transform.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/transform.h new file mode 100644 index 00000000..5a6f7959 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/transform.h @@ -0,0 +1,207 @@ +/* + * Summary: the XSLT engine transformation part. + * Description: This module implements the bulk of the actual + * transformation processing. Most of the xsl: element + * constructs are implemented in this module. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XSLT_TRANSFORM_H__ +#define __XML_XSLT_TRANSFORM_H__ + +#include +#include +#include "xsltexports.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * XInclude default processing. + */ +XSLTPUBFUN void XSLTCALL + xsltSetXIncludeDefault (int xinclude); +XSLTPUBFUN int XSLTCALL + xsltGetXIncludeDefault (void); + +/** + * Export context to users. + */ +XSLTPUBFUN xsltTransformContextPtr XSLTCALL + xsltNewTransformContext (xsltStylesheetPtr style, + xmlDocPtr doc); + +XSLTPUBFUN void XSLTCALL + xsltFreeTransformContext(xsltTransformContextPtr ctxt); + +XSLTPUBFUN xmlDocPtr XSLTCALL + xsltApplyStylesheetUser (xsltStylesheetPtr style, + xmlDocPtr doc, + const char **params, + const char *output, + FILE * profile, + xsltTransformContextPtr userCtxt); +XSLTPUBFUN void XSLTCALL + xsltProcessOneNode (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xsltStackElemPtr params); +/** + * Private Interfaces. + */ +XSLTPUBFUN void XSLTCALL + xsltApplyStripSpaces (xsltTransformContextPtr ctxt, + xmlNodePtr node); +XSLTPUBFUN xmlDocPtr XSLTCALL + xsltApplyStylesheet (xsltStylesheetPtr style, + xmlDocPtr doc, + const char **params); +XSLTPUBFUN xmlDocPtr XSLTCALL + xsltProfileStylesheet (xsltStylesheetPtr style, + xmlDocPtr doc, + const char **params, + FILE * output); +XSLTPUBFUN int XSLTCALL + xsltRunStylesheet (xsltStylesheetPtr style, + xmlDocPtr doc, + const char **params, + const char *output, + xmlSAXHandlerPtr SAX, + xmlOutputBufferPtr IObuf); +XSLTPUBFUN int XSLTCALL + xsltRunStylesheetUser (xsltStylesheetPtr style, + xmlDocPtr doc, + const char **params, + const char *output, + xmlSAXHandlerPtr SAX, + xmlOutputBufferPtr IObuf, + FILE * profile, + xsltTransformContextPtr userCtxt); +XSLTPUBFUN void XSLTCALL + xsltApplyOneTemplate (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr list, + xsltTemplatePtr templ, + xsltStackElemPtr params); +XSLTPUBFUN void XSLTCALL + xsltDocumentElem (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst, + xsltElemPreCompPtr comp); +XSLTPUBFUN void XSLTCALL + xsltSort (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst, + xsltElemPreCompPtr comp); +XSLTPUBFUN void XSLTCALL + xsltCopy (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst, + xsltElemPreCompPtr comp); +XSLTPUBFUN void XSLTCALL + xsltText (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst, + xsltElemPreCompPtr comp); +XSLTPUBFUN void XSLTCALL + xsltElement (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst, + xsltElemPreCompPtr comp); +XSLTPUBFUN void XSLTCALL + xsltComment (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst, + xsltElemPreCompPtr comp); +XSLTPUBFUN void XSLTCALL + xsltAttribute (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst, + xsltElemPreCompPtr comp); +XSLTPUBFUN void XSLTCALL + xsltProcessingInstruction(xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst, + xsltElemPreCompPtr comp); +XSLTPUBFUN void XSLTCALL + xsltCopyOf (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst, + xsltElemPreCompPtr comp); +XSLTPUBFUN void XSLTCALL + xsltValueOf (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst, + xsltElemPreCompPtr comp); +XSLTPUBFUN void XSLTCALL + xsltNumber (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst, + xsltElemPreCompPtr comp); +XSLTPUBFUN void XSLTCALL + xsltApplyImports (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst, + xsltElemPreCompPtr comp); +XSLTPUBFUN void XSLTCALL + xsltCallTemplate (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst, + xsltElemPreCompPtr comp); +XSLTPUBFUN void XSLTCALL + xsltApplyTemplates (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst, + xsltElemPreCompPtr comp); +XSLTPUBFUN void XSLTCALL + xsltChoose (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst, + xsltElemPreCompPtr comp); +XSLTPUBFUN void XSLTCALL + xsltIf (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst, + xsltElemPreCompPtr comp); +XSLTPUBFUN void XSLTCALL + xsltForEach (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst, + xsltElemPreCompPtr comp); +XSLTPUBFUN void XSLTCALL + xsltRegisterAllElement (xsltTransformContextPtr ctxt); + +XSLTPUBFUN xmlNodePtr XSLTCALL + xsltCopyTextString (xsltTransformContextPtr ctxt, + xmlNodePtr target, + const xmlChar *string, + int noescape); + +/* Following 2 functions needed for libexslt/functions.c */ +XSLTPUBFUN void XSLTCALL + xsltLocalVariablePop (xsltTransformContextPtr ctxt, + int limitNr, + int level); +XSLTPUBFUN int XSLTCALL + xsltLocalVariablePush (xsltTransformContextPtr ctxt, + xsltStackElemPtr variable, + int level); +/* + * Hook for the debugger if activated. + */ +XSLTPUBFUN void XSLTCALL + xslHandleDebugger (xmlNodePtr cur, + xmlNodePtr node, + xsltTemplatePtr templ, + xsltTransformContextPtr ctxt); + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_XSLT_TRANSFORM_H__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/variables.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/variables.h new file mode 100644 index 00000000..e2adee0f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/variables.h @@ -0,0 +1,118 @@ +/* + * Summary: interface for the variable matching and lookup. + * Description: interface for the variable matching and lookup. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XSLT_VARIABLES_H__ +#define __XML_XSLT_VARIABLES_H__ + +#include +#include +#include "xsltexports.h" +#include "xsltInternals.h" +#include "functions.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * XSLT_REGISTER_VARIABLE_LOOKUP: + * + * Registering macro, not general purpose at all but used in different modules. + */ + +#define XSLT_REGISTER_VARIABLE_LOOKUP(ctxt) \ + xmlXPathRegisterVariableLookup((ctxt)->xpathCtxt, \ + xsltXPathVariableLookup, (void *)(ctxt)); \ + xsltRegisterAllFunctions((ctxt)->xpathCtxt); \ + xsltRegisterAllElement(ctxt); \ + (ctxt)->xpathCtxt->extra = ctxt + +/* + * Flags for memory management of RVTs + */ + +/** + * XSLT_RVT_LOCAL: + * + * RVT is destroyed after the current instructions ends. + */ +#define XSLT_RVT_LOCAL 1 + +/** + * XSLT_RVT_FUNC_RESULT: + * + * RVT is part of results returned with func:result. The RVT won't be + * destroyed after exiting a template and will be reset to XSLT_RVT_LOCAL or + * XSLT_RVT_VARIABLE in the template that receives the return value. + */ +#define XSLT_RVT_FUNC_RESULT 2 + +/** + * XSLT_RVT_GLOBAL: + * + * RVT is part of a global variable. + */ +#define XSLT_RVT_GLOBAL 3 + +/* + * Interfaces for the variable module. + */ + +XSLTPUBFUN int XSLTCALL + xsltEvalGlobalVariables (xsltTransformContextPtr ctxt); +XSLTPUBFUN int XSLTCALL + xsltEvalUserParams (xsltTransformContextPtr ctxt, + const char **params); +XSLTPUBFUN int XSLTCALL + xsltQuoteUserParams (xsltTransformContextPtr ctxt, + const char **params); +XSLTPUBFUN int XSLTCALL + xsltEvalOneUserParam (xsltTransformContextPtr ctxt, + const xmlChar * name, + const xmlChar * value); +XSLTPUBFUN int XSLTCALL + xsltQuoteOneUserParam (xsltTransformContextPtr ctxt, + const xmlChar * name, + const xmlChar * value); + +XSLTPUBFUN void XSLTCALL + xsltParseGlobalVariable (xsltStylesheetPtr style, + xmlNodePtr cur); +XSLTPUBFUN void XSLTCALL + xsltParseGlobalParam (xsltStylesheetPtr style, + xmlNodePtr cur); +XSLTPUBFUN void XSLTCALL + xsltParseStylesheetVariable (xsltTransformContextPtr ctxt, + xmlNodePtr cur); +XSLTPUBFUN void XSLTCALL + xsltParseStylesheetParam (xsltTransformContextPtr ctxt, + xmlNodePtr cur); +XSLTPUBFUN xsltStackElemPtr XSLTCALL + xsltParseStylesheetCallerParam (xsltTransformContextPtr ctxt, + xmlNodePtr cur); +XSLTPUBFUN int XSLTCALL + xsltAddStackElemList (xsltTransformContextPtr ctxt, + xsltStackElemPtr elems); +XSLTPUBFUN void XSLTCALL + xsltFreeGlobalVariables (xsltTransformContextPtr ctxt); +XSLTPUBFUN xmlXPathObjectPtr XSLTCALL + xsltVariableLookup (xsltTransformContextPtr ctxt, + const xmlChar *name, + const xmlChar *ns_uri); +XSLTPUBFUN xmlXPathObjectPtr XSLTCALL + xsltXPathVariableLookup (void *ctxt, + const xmlChar *name, + const xmlChar *ns_uri); +#ifdef __cplusplus +} +#endif + +#endif /* __XML_XSLT_VARIABLES_H__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/xslt.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/xslt.h new file mode 100644 index 00000000..02f491a5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/xslt.h @@ -0,0 +1,110 @@ +/* + * Summary: Interfaces, constants and types related to the XSLT engine + * Description: Interfaces, constants and types related to the XSLT engine + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XSLT_H__ +#define __XML_XSLT_H__ + +#include +#include "xsltexports.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * XSLT_DEFAULT_VERSION: + * + * The default version of XSLT supported. + */ +#define XSLT_DEFAULT_VERSION "1.0" + +/** + * XSLT_DEFAULT_VENDOR: + * + * The XSLT "vendor" string for this processor. + */ +#define XSLT_DEFAULT_VENDOR "libxslt" + +/** + * XSLT_DEFAULT_URL: + * + * The XSLT "vendor" URL for this processor. + */ +#define XSLT_DEFAULT_URL "http://xmlsoft.org/XSLT/" + +/** + * XSLT_NAMESPACE: + * + * The XSLT specification namespace. + */ +#define XSLT_NAMESPACE ((const xmlChar *)"http://www.w3.org/1999/XSL/Transform") + +/** + * XSLT_PARSE_OPTIONS: + * + * The set of options to pass to an xmlReadxxx when loading files for + * XSLT consumption. + */ +#define XSLT_PARSE_OPTIONS \ + XML_PARSE_NOENT | XML_PARSE_DTDLOAD | XML_PARSE_DTDATTR | XML_PARSE_NOCDATA + +/** + * xsltMaxDepth: + * + * This value is used to detect templates loops. + */ +XSLTPUBVAR int xsltMaxDepth; + +/** + * * xsltMaxVars: + * * + * * This value is used to detect templates loops. + * */ +XSLTPUBVAR int xsltMaxVars; + +/** + * xsltEngineVersion: + * + * The version string for libxslt. + */ +XSLTPUBVAR const char *xsltEngineVersion; + +/** + * xsltLibxsltVersion: + * + * The version of libxslt compiled. + */ +XSLTPUBVAR const int xsltLibxsltVersion; + +/** + * xsltLibxmlVersion: + * + * The version of libxml libxslt was compiled against. + */ +XSLTPUBVAR const int xsltLibxmlVersion; + +/* + * Global initialization function. + */ + +XSLTPUBFUN void XSLTCALL + xsltInit (void); + +/* + * Global cleanup function. + */ +XSLTPUBFUN void XSLTCALL + xsltCleanupGlobals (void); + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_XSLT_H__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/xsltInternals.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/xsltInternals.h new file mode 100644 index 00000000..6faa07db --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/xsltInternals.h @@ -0,0 +1,1995 @@ +/* + * Summary: internal data structures, constants and functions + * Description: Internal data structures, constants and functions used + * by the XSLT engine. + * They are not part of the API or ABI, i.e. they can change + * without prior notice, use carefully. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XSLT_INTERNALS_H__ +#define __XML_XSLT_INTERNALS_H__ + +#include +#include +#include +#include +#include +#include +#include +#include "xsltexports.h" +#include "numbersInternals.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* #define XSLT_DEBUG_PROFILE_CACHE */ + +/** + * XSLT_IS_TEXT_NODE: + * + * check if the argument is a text node + */ +#define XSLT_IS_TEXT_NODE(n) ((n != NULL) && \ + (((n)->type == XML_TEXT_NODE) || \ + ((n)->type == XML_CDATA_SECTION_NODE))) + + +/** + * XSLT_MARK_RES_TREE_FRAG: + * + * internal macro to set up tree fragments + */ +#define XSLT_MARK_RES_TREE_FRAG(n) \ + (n)->name = (char *) xmlStrdup(BAD_CAST " fake node libxslt"); + +/** + * XSLT_IS_RES_TREE_FRAG: + * + * internal macro to test tree fragments + */ +#define XSLT_IS_RES_TREE_FRAG(n) \ + ((n != NULL) && ((n)->type == XML_DOCUMENT_NODE) && \ + ((n)->name != NULL) && ((n)->name[0] == ' ')) + +/** + * XSLT_REFACTORED_KEYCOMP: + * + * Internal define to enable on-demand xsl:key computation. + * That's the only mode now but the define is kept for compatibility + */ +#define XSLT_REFACTORED_KEYCOMP + +/** + * XSLT_FAST_IF: + * + * Internal define to enable usage of xmlXPathCompiledEvalToBoolean() + * for XSLT "tests"; e.g. in + */ +#define XSLT_FAST_IF + +/** + * XSLT_REFACTORED: + * + * Internal define to enable the refactored parts of Libxslt. + */ +/* #define XSLT_REFACTORED */ +/* ==================================================================== */ + +/** + * XSLT_REFACTORED_VARS: + * + * Internal define to enable the refactored variable part of libxslt + */ +#define XSLT_REFACTORED_VARS + +#ifdef XSLT_REFACTORED + +extern const xmlChar *xsltXSLTAttrMarker; + + +/* TODO: REMOVE: #define XSLT_REFACTORED_EXCLRESNS */ + +/* TODO: REMOVE: #define XSLT_REFACTORED_NSALIAS */ + +/** + * XSLT_REFACTORED_XSLT_NSCOMP + * + * Internal define to enable the pointer-comparison of + * namespaces of XSLT elements. + */ +/* #define XSLT_REFACTORED_XSLT_NSCOMP */ + +#ifdef XSLT_REFACTORED_XSLT_NSCOMP + +extern const xmlChar *xsltConstNamespaceNameXSLT; + +/** + * IS_XSLT_ELEM_FAST: + * + * quick test to detect XSLT elements + */ +#define IS_XSLT_ELEM_FAST(n) \ + (((n) != NULL) && ((n)->ns != NULL) && \ + ((n)->ns->href == xsltConstNamespaceNameXSLT)) + +/** + * IS_XSLT_ATTR_FAST: + * + * quick test to detect XSLT attributes + */ +#define IS_XSLT_ATTR_FAST(a) \ + (((a) != NULL) && ((a)->ns != NULL) && \ + ((a)->ns->href == xsltConstNamespaceNameXSLT)) + +/** + * XSLT_HAS_INTERNAL_NSMAP: + * + * check for namespace mapping + */ +#define XSLT_HAS_INTERNAL_NSMAP(s) \ + (((s) != NULL) && ((s)->principal) && \ + ((s)->principal->principalData) && \ + ((s)->principal->principalData->nsMap)) + +/** + * XSLT_GET_INTERNAL_NSMAP: + * + * get pointer to namespace map + */ +#define XSLT_GET_INTERNAL_NSMAP(s) ((s)->principal->principalData->nsMap) + +#else /* XSLT_REFACTORED_XSLT_NSCOMP */ + +/** + * IS_XSLT_ELEM_FAST: + * + * quick check whether this is an xslt element + */ +#define IS_XSLT_ELEM_FAST(n) \ + (((n) != NULL) && ((n)->ns != NULL) && \ + (xmlStrEqual((n)->ns->href, XSLT_NAMESPACE))) + +/** + * IS_XSLT_ATTR_FAST: + * + * quick check for xslt namespace attribute + */ +#define IS_XSLT_ATTR_FAST(a) \ + (((a) != NULL) && ((a)->ns != NULL) && \ + (xmlStrEqual((a)->ns->href, XSLT_NAMESPACE))) + + +#endif /* XSLT_REFACTORED_XSLT_NSCOMP */ + + +/** + * XSLT_REFACTORED_MANDATORY_VERSION: + * + * TODO: Currently disabled to surpress regression test failures, since + * the old behaviour was that a missing version attribute + * produced a only a warning and not an error, which was incerrect. + * So the regression tests need to be fixed if this is enabled. + */ +/* #define XSLT_REFACTORED_MANDATORY_VERSION */ + +/** + * xsltPointerList: + * + * Pointer-list for various purposes. + */ +typedef struct _xsltPointerList xsltPointerList; +typedef xsltPointerList *xsltPointerListPtr; +struct _xsltPointerList { + void **items; + int number; + int size; +}; + +#endif + +/** + * XSLT_REFACTORED_PARSING: + * + * Internal define to enable the refactored parts of Libxslt + * related to parsing. + */ +/* #define XSLT_REFACTORED_PARSING */ + +/** + * XSLT_MAX_SORT: + * + * Max number of specified xsl:sort on an element. + */ +#define XSLT_MAX_SORT 15 + +/** + * XSLT_PAT_NO_PRIORITY: + * + * Specific value for pattern without priority expressed. + */ +#define XSLT_PAT_NO_PRIORITY -12345789 + +/** + * xsltRuntimeExtra: + * + * Extra information added to the transformation context. + */ +typedef struct _xsltRuntimeExtra xsltRuntimeExtra; +typedef xsltRuntimeExtra *xsltRuntimeExtraPtr; +struct _xsltRuntimeExtra { + void *info; /* pointer to the extra data */ + xmlFreeFunc deallocate; /* pointer to the deallocation routine */ + union { /* dual-purpose field */ + void *ptr; /* data not needing deallocation */ + int ival; /* integer value storage */ + } val; +}; + +/** + * XSLT_RUNTIME_EXTRA_LST: + * @ctxt: the transformation context + * @nr: the index + * + * Macro used to access extra information stored in the context + */ +#define XSLT_RUNTIME_EXTRA_LST(ctxt, nr) (ctxt)->extras[(nr)].info +/** + * XSLT_RUNTIME_EXTRA_FREE: + * @ctxt: the transformation context + * @nr: the index + * + * Macro used to free extra information stored in the context + */ +#define XSLT_RUNTIME_EXTRA_FREE(ctxt, nr) (ctxt)->extras[(nr)].deallocate +/** + * XSLT_RUNTIME_EXTRA: + * @ctxt: the transformation context + * @nr: the index + * + * Macro used to define extra information stored in the context + */ +#define XSLT_RUNTIME_EXTRA(ctxt, nr, typ) (ctxt)->extras[(nr)].val.typ + +/** + * xsltTemplate: + * + * The in-memory structure corresponding to an XSLT Template. + */ +typedef struct _xsltTemplate xsltTemplate; +typedef xsltTemplate *xsltTemplatePtr; +struct _xsltTemplate { + struct _xsltTemplate *next;/* chained list sorted by priority */ + struct _xsltStylesheet *style;/* the containing stylesheet */ + xmlChar *match; /* the matching string */ + float priority; /* as given from the stylesheet, not computed */ + const xmlChar *name; /* the local part of the name QName */ + const xmlChar *nameURI; /* the URI part of the name QName */ + const xmlChar *mode;/* the local part of the mode QName */ + const xmlChar *modeURI;/* the URI part of the mode QName */ + xmlNodePtr content; /* the template replacement value */ + xmlNodePtr elem; /* the source element */ + + /* + * TODO: @inheritedNsNr and @inheritedNs won't be used in the + * refactored code. + */ + int inheritedNsNr; /* number of inherited namespaces */ + xmlNsPtr *inheritedNs;/* inherited non-excluded namespaces */ + + /* Profiling information */ + int nbCalls; /* the number of time the template was called */ + unsigned long time; /* the time spent in this template */ + void *params; /* xsl:param instructions */ + + int templNr; /* Nb of templates in the stack */ + int templMax; /* Size of the templtes stack */ + xsltTemplatePtr *templCalledTab; /* templates called */ + int *templCountTab; /* .. and how often */ + + /* Conflict resolution */ + int position; +}; + +/** + * xsltDecimalFormat: + * + * Data structure of decimal-format. + */ +typedef struct _xsltDecimalFormat xsltDecimalFormat; +typedef xsltDecimalFormat *xsltDecimalFormatPtr; +struct _xsltDecimalFormat { + struct _xsltDecimalFormat *next; /* chained list */ + xmlChar *name; + /* Used for interpretation of pattern */ + xmlChar *digit; + xmlChar *patternSeparator; + /* May appear in result */ + xmlChar *minusSign; + xmlChar *infinity; + xmlChar *noNumber; /* Not-a-number */ + /* Used for interpretation of pattern and may appear in result */ + xmlChar *decimalPoint; + xmlChar *grouping; + xmlChar *percent; + xmlChar *permille; + xmlChar *zeroDigit; + const xmlChar *nsUri; +}; + +/** + * xsltDocument: + * + * Data structure associated to a parsed document. + */ +typedef struct _xsltDocument xsltDocument; +typedef xsltDocument *xsltDocumentPtr; +struct _xsltDocument { + struct _xsltDocument *next; /* documents are kept in a chained list */ + int main; /* is this the main document */ + xmlDocPtr doc; /* the parsed document */ + void *keys; /* key tables storage */ + struct _xsltDocument *includes; /* subsidiary includes */ + int preproc; /* pre-processing already done */ + int nbKeysComputed; +}; + +/** + * xsltKeyDef: + * + * Representation of an xsl:key. + */ +typedef struct _xsltKeyDef xsltKeyDef; +typedef xsltKeyDef *xsltKeyDefPtr; +struct _xsltKeyDef { + struct _xsltKeyDef *next; + xmlNodePtr inst; + xmlChar *name; + xmlChar *nameURI; + xmlChar *match; + xmlChar *use; + xmlXPathCompExprPtr comp; + xmlXPathCompExprPtr usecomp; + xmlNsPtr *nsList; /* the namespaces in scope */ + int nsNr; /* the number of namespaces in scope */ +}; + +/** + * xsltKeyTable: + * + * Holds the computed keys for key definitions of the same QName. + * Is owned by an xsltDocument. + */ +typedef struct _xsltKeyTable xsltKeyTable; +typedef xsltKeyTable *xsltKeyTablePtr; +struct _xsltKeyTable { + struct _xsltKeyTable *next; + xmlChar *name; + xmlChar *nameURI; + xmlHashTablePtr keys; +}; + +/* + * The in-memory structure corresponding to an XSLT Stylesheet. + * NOTE: most of the content is simply linked from the doc tree + * structure, no specific allocation is made. + */ +typedef struct _xsltStylesheet xsltStylesheet; +typedef xsltStylesheet *xsltStylesheetPtr; + +typedef struct _xsltTransformContext xsltTransformContext; +typedef xsltTransformContext *xsltTransformContextPtr; + +/** + * xsltElemPreComp: + * + * The in-memory structure corresponding to element precomputed data, + * designed to be extended by extension implementors. + */ +typedef struct _xsltElemPreComp xsltElemPreComp; +typedef xsltElemPreComp *xsltElemPreCompPtr; + +/** + * xsltTransformFunction: + * @ctxt: the XSLT transformation context + * @node: the input node + * @inst: the stylesheet node + * @comp: the compiled information from the stylesheet + * + * Signature of the function associated to elements part of the + * stylesheet language like xsl:if or xsl:apply-templates. + */ +typedef void (*xsltTransformFunction) (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst, + xsltElemPreCompPtr comp); + +/** + * xsltSortFunc: + * @ctxt: a transformation context + * @sorts: the node-set to sort + * @nbsorts: the number of sorts + * + * Signature of the function to use during sorting + */ +typedef void (*xsltSortFunc) (xsltTransformContextPtr ctxt, xmlNodePtr *sorts, + int nbsorts); + +typedef enum { + XSLT_FUNC_COPY=1, + XSLT_FUNC_SORT, + XSLT_FUNC_TEXT, + XSLT_FUNC_ELEMENT, + XSLT_FUNC_ATTRIBUTE, + XSLT_FUNC_COMMENT, + XSLT_FUNC_PI, + XSLT_FUNC_COPYOF, + XSLT_FUNC_VALUEOF, + XSLT_FUNC_NUMBER, + XSLT_FUNC_APPLYIMPORTS, + XSLT_FUNC_CALLTEMPLATE, + XSLT_FUNC_APPLYTEMPLATES, + XSLT_FUNC_CHOOSE, + XSLT_FUNC_IF, + XSLT_FUNC_FOREACH, + XSLT_FUNC_DOCUMENT, + XSLT_FUNC_WITHPARAM, + XSLT_FUNC_PARAM, + XSLT_FUNC_VARIABLE, + XSLT_FUNC_WHEN, + XSLT_FUNC_EXTENSION +#ifdef XSLT_REFACTORED + , + XSLT_FUNC_OTHERWISE, + XSLT_FUNC_FALLBACK, + XSLT_FUNC_MESSAGE, + XSLT_FUNC_INCLUDE, + XSLT_FUNC_ATTRSET, + XSLT_FUNC_LITERAL_RESULT_ELEMENT, + XSLT_FUNC_UNKOWN_FORWARDS_COMPAT +#endif +} xsltStyleType; + +/** + * xsltElemPreCompDeallocator: + * @comp: the #xsltElemPreComp to free up + * + * Deallocates an #xsltElemPreComp structure. + */ +typedef void (*xsltElemPreCompDeallocator) (xsltElemPreCompPtr comp); + +/** + * xsltElemPreComp: + * + * The basic structure for compiled items of the AST of the XSLT processor. + * This structure is also intended to be extended by extension implementors. + * TODO: This is somehow not nice, since it has a "free" field, which + * derived stylesheet-structs do not have. + */ +struct _xsltElemPreComp { + xsltElemPreCompPtr next; /* next item in the global chained + list held by xsltStylesheet. */ + xsltStyleType type; /* type of the element */ + xsltTransformFunction func; /* handling function */ + xmlNodePtr inst; /* the node in the stylesheet's tree + corresponding to this item */ + + /* end of common part */ + xsltElemPreCompDeallocator free; /* the deallocator */ +}; + +/** + * xsltStylePreComp: + * + * The abstract basic structure for items of the XSLT processor. + * This includes: + * 1) compiled forms of XSLT instructions (xsl:if, xsl:attribute, etc.) + * 2) compiled forms of literal result elements + * 3) compiled forms of extension elements + */ +typedef struct _xsltStylePreComp xsltStylePreComp; +typedef xsltStylePreComp *xsltStylePreCompPtr; + +#ifdef XSLT_REFACTORED + +/* +* Some pointer-list utility functions. +*/ +XSLTPUBFUN xsltPointerListPtr XSLTCALL + xsltPointerListCreate (int initialSize); +XSLTPUBFUN void XSLTCALL + xsltPointerListFree (xsltPointerListPtr list); +XSLTPUBFUN void XSLTCALL + xsltPointerListClear (xsltPointerListPtr list); +XSLTPUBFUN int XSLTCALL + xsltPointerListAddSize (xsltPointerListPtr list, + void *item, + int initialSize); + +/************************************************************************ + * * + * Refactored structures * + * * + ************************************************************************/ + +typedef struct _xsltNsListContainer xsltNsListContainer; +typedef xsltNsListContainer *xsltNsListContainerPtr; +struct _xsltNsListContainer { + xmlNsPtr *list; + int totalNumber; + int xpathNumber; +}; + +/** + * XSLT_ITEM_COMPATIBILITY_FIELDS: + * + * Fields for API compatibility to the structure + * _xsltElemPreComp which is used for extension functions. + * Note that @next is used for storage; it does not reflect a next + * sibling in the tree. + * TODO: Evaluate if we really need such a compatibility. + */ +#define XSLT_ITEM_COMPATIBILITY_FIELDS \ + xsltElemPreCompPtr next;\ + xsltStyleType type;\ + xsltTransformFunction func;\ + xmlNodePtr inst; + +/** + * XSLT_ITEM_NAVIGATION_FIELDS: + * + * Currently empty. + * TODO: It is intended to hold navigational fields in the future. + */ +#define XSLT_ITEM_NAVIGATION_FIELDS +/* + xsltStylePreCompPtr parent;\ + xsltStylePreCompPtr children;\ + xsltStylePreCompPtr nextItem; +*/ + +/** + * XSLT_ITEM_NSINSCOPE_FIELDS: + * + * The in-scope namespaces. + */ +#define XSLT_ITEM_NSINSCOPE_FIELDS xsltNsListContainerPtr inScopeNs; + +/** + * XSLT_ITEM_COMMON_FIELDS: + * + * Common fields used for all items. + */ +#define XSLT_ITEM_COMMON_FIELDS \ + XSLT_ITEM_COMPATIBILITY_FIELDS \ + XSLT_ITEM_NAVIGATION_FIELDS \ + XSLT_ITEM_NSINSCOPE_FIELDS + +/** + * _xsltStylePreComp: + * + * The abstract basic structure for items of the XSLT processor. + * This includes: + * 1) compiled forms of XSLT instructions (e.g. xsl:if, xsl:attribute, etc.) + * 2) compiled forms of literal result elements + * 3) various properties for XSLT instructions (e.g. xsl:when, + * xsl:with-param) + * + * REVISIT TODO: Keep this structure equal to the fields + * defined by XSLT_ITEM_COMMON_FIELDS + */ +struct _xsltStylePreComp { + xsltElemPreCompPtr next; /* next item in the global chained + list held by xsltStylesheet */ + xsltStyleType type; /* type of the item */ + xsltTransformFunction func; /* handling function */ + xmlNodePtr inst; /* the node in the stylesheet's tree + corresponding to this item. */ + /* Currently no navigational fields. */ + xsltNsListContainerPtr inScopeNs; +}; + +/** + * xsltStyleBasicEmptyItem: + * + * Abstract structure only used as a short-cut for + * XSLT items with no extra fields. + * NOTE that it is intended that this structure looks the same as + * _xsltStylePreComp. + */ +typedef struct _xsltStyleBasicEmptyItem xsltStyleBasicEmptyItem; +typedef xsltStyleBasicEmptyItem *xsltStyleBasicEmptyItemPtr; + +struct _xsltStyleBasicEmptyItem { + XSLT_ITEM_COMMON_FIELDS +}; + +/** + * xsltStyleBasicExpressionItem: + * + * Abstract structure only used as a short-cut for + * XSLT items with just an expression. + */ +typedef struct _xsltStyleBasicExpressionItem xsltStyleBasicExpressionItem; +typedef xsltStyleBasicExpressionItem *xsltStyleBasicExpressionItemPtr; + +struct _xsltStyleBasicExpressionItem { + XSLT_ITEM_COMMON_FIELDS + + const xmlChar *select; /* TODO: Change this to "expression". */ + xmlXPathCompExprPtr comp; /* TODO: Change this to compExpr. */ +}; + +/************************************************************************ + * * + * XSLT-instructions/declarations * + * * + ************************************************************************/ + +/** + * xsltStyleItemElement: + * + * + * + * + * + */ +typedef struct _xsltStyleItemElement xsltStyleItemElement; +typedef xsltStyleItemElement *xsltStyleItemElementPtr; + +struct _xsltStyleItemElement { + XSLT_ITEM_COMMON_FIELDS + + const xmlChar *use; + int has_use; + const xmlChar *name; + int has_name; + const xmlChar *ns; + const xmlChar *nsPrefix; + int has_ns; +}; + +/** + * xsltStyleItemAttribute: + * + * + * + * + * + */ +typedef struct _xsltStyleItemAttribute xsltStyleItemAttribute; +typedef xsltStyleItemAttribute *xsltStyleItemAttributePtr; + +struct _xsltStyleItemAttribute { + XSLT_ITEM_COMMON_FIELDS + const xmlChar *name; + int has_name; + const xmlChar *ns; + const xmlChar *nsPrefix; + int has_ns; +}; + +/** + * xsltStyleItemText: + * + * + * + * + * + */ +typedef struct _xsltStyleItemText xsltStyleItemText; +typedef xsltStyleItemText *xsltStyleItemTextPtr; + +struct _xsltStyleItemText { + XSLT_ITEM_COMMON_FIELDS + int noescape; /* text */ +}; + +/** + * xsltStyleItemComment: + * + * + * + * + * + */ +typedef xsltStyleBasicEmptyItem xsltStyleItemComment; +typedef xsltStyleItemComment *xsltStyleItemCommentPtr; + +/** + * xsltStyleItemPI: + * + * + * + * + * + */ +typedef struct _xsltStyleItemPI xsltStyleItemPI; +typedef xsltStyleItemPI *xsltStyleItemPIPtr; + +struct _xsltStyleItemPI { + XSLT_ITEM_COMMON_FIELDS + const xmlChar *name; + int has_name; +}; + +/** + * xsltStyleItemApplyImports: + * + * + * + */ +typedef xsltStyleBasicEmptyItem xsltStyleItemApplyImports; +typedef xsltStyleItemApplyImports *xsltStyleItemApplyImportsPtr; + +/** + * xsltStyleItemApplyTemplates: + * + * + * + * + * + */ +typedef struct _xsltStyleItemApplyTemplates xsltStyleItemApplyTemplates; +typedef xsltStyleItemApplyTemplates *xsltStyleItemApplyTemplatesPtr; + +struct _xsltStyleItemApplyTemplates { + XSLT_ITEM_COMMON_FIELDS + + const xmlChar *mode; /* apply-templates */ + const xmlChar *modeURI; /* apply-templates */ + const xmlChar *select; /* sort, copy-of, value-of, apply-templates */ + xmlXPathCompExprPtr comp; /* a precompiled XPath expression */ + /* TODO: with-params */ +}; + +/** + * xsltStyleItemCallTemplate: + * + * + * + * + * + */ +typedef struct _xsltStyleItemCallTemplate xsltStyleItemCallTemplate; +typedef xsltStyleItemCallTemplate *xsltStyleItemCallTemplatePtr; + +struct _xsltStyleItemCallTemplate { + XSLT_ITEM_COMMON_FIELDS + + xsltTemplatePtr templ; /* call-template */ + const xmlChar *name; /* element, attribute, pi */ + int has_name; /* element, attribute, pi */ + const xmlChar *ns; /* element */ + int has_ns; /* element */ + /* TODO: with-params */ +}; + +/** + * xsltStyleItemCopy: + * + * + * + * + * + */ +typedef struct _xsltStyleItemCopy xsltStyleItemCopy; +typedef xsltStyleItemCopy *xsltStyleItemCopyPtr; + +struct _xsltStyleItemCopy { + XSLT_ITEM_COMMON_FIELDS + const xmlChar *use; /* copy, element */ + int has_use; /* copy, element */ +}; + +/** + * xsltStyleItemIf: + * + * + * + * + * + */ +typedef struct _xsltStyleItemIf xsltStyleItemIf; +typedef xsltStyleItemIf *xsltStyleItemIfPtr; + +struct _xsltStyleItemIf { + XSLT_ITEM_COMMON_FIELDS + + const xmlChar *test; /* if */ + xmlXPathCompExprPtr comp; /* a precompiled XPath expression */ +}; + + +/** + * xsltStyleItemCopyOf: + * + * + * + */ +typedef xsltStyleBasicExpressionItem xsltStyleItemCopyOf; +typedef xsltStyleItemCopyOf *xsltStyleItemCopyOfPtr; + +/** + * xsltStyleItemValueOf: + * + * + * + */ +typedef struct _xsltStyleItemValueOf xsltStyleItemValueOf; +typedef xsltStyleItemValueOf *xsltStyleItemValueOfPtr; + +struct _xsltStyleItemValueOf { + XSLT_ITEM_COMMON_FIELDS + + const xmlChar *select; + xmlXPathCompExprPtr comp; /* a precompiled XPath expression */ + int noescape; +}; + +/** + * xsltStyleItemNumber: + * + * + * + */ +typedef struct _xsltStyleItemNumber xsltStyleItemNumber; +typedef xsltStyleItemNumber *xsltStyleItemNumberPtr; + +struct _xsltStyleItemNumber { + XSLT_ITEM_COMMON_FIELDS + xsltNumberData numdata; /* number */ +}; + +/** + * xsltStyleItemChoose: + * + * + * + * + * + */ +typedef xsltStyleBasicEmptyItem xsltStyleItemChoose; +typedef xsltStyleItemChoose *xsltStyleItemChoosePtr; + +/** + * xsltStyleItemFallback: + * + * + * + * + * + */ +typedef xsltStyleBasicEmptyItem xsltStyleItemFallback; +typedef xsltStyleItemFallback *xsltStyleItemFallbackPtr; + +/** + * xsltStyleItemForEach: + * + * + * + * + * + */ +typedef xsltStyleBasicExpressionItem xsltStyleItemForEach; +typedef xsltStyleItemForEach *xsltStyleItemForEachPtr; + +/** + * xsltStyleItemMessage: + * + * + * + * + * + */ +typedef struct _xsltStyleItemMessage xsltStyleItemMessage; +typedef xsltStyleItemMessage *xsltStyleItemMessagePtr; + +struct _xsltStyleItemMessage { + XSLT_ITEM_COMMON_FIELDS + int terminate; +}; + +/** + * xsltStyleItemDocument: + * + * NOTE: This is not an instruction of XSLT 1.0. + */ +typedef struct _xsltStyleItemDocument xsltStyleItemDocument; +typedef xsltStyleItemDocument *xsltStyleItemDocumentPtr; + +struct _xsltStyleItemDocument { + XSLT_ITEM_COMMON_FIELDS + int ver11; /* assigned: in xsltDocumentComp; + read: nowhere; + TODO: Check if we need. */ + const xmlChar *filename; /* document URL */ + int has_filename; +}; + +/************************************************************************ + * * + * Non-instructions (actually properties of instructions/declarations) * + * * + ************************************************************************/ + +/** + * xsltStyleBasicItemVariable: + * + * Basic struct for xsl:variable, xsl:param and xsl:with-param. + * It's currently important to have equal fields, since + * xsltParseStylesheetCallerParam() is used with xsl:with-param from + * the xslt side and with xsl:param from the exslt side (in + * exsltFuncFunctionFunction()). + * + * FUTURE NOTE: In XSLT 2.0 xsl:param, xsl:variable and xsl:with-param + * have additional different fields. + */ +typedef struct _xsltStyleBasicItemVariable xsltStyleBasicItemVariable; +typedef xsltStyleBasicItemVariable *xsltStyleBasicItemVariablePtr; + +struct _xsltStyleBasicItemVariable { + XSLT_ITEM_COMMON_FIELDS + + const xmlChar *select; + xmlXPathCompExprPtr comp; + + const xmlChar *name; + int has_name; + const xmlChar *ns; + int has_ns; +}; + +/** + * xsltStyleItemVariable: + * + * + * + * + * + */ +typedef xsltStyleBasicItemVariable xsltStyleItemVariable; +typedef xsltStyleItemVariable *xsltStyleItemVariablePtr; + +/** + * xsltStyleItemParam: + * + * + * + * + * + */ +typedef struct _xsltStyleItemParam xsltStyleItemParam; +typedef xsltStyleItemParam *xsltStyleItemParamPtr; + +struct _xsltStyleItemParam { + XSLT_ITEM_COMMON_FIELDS + + const xmlChar *select; + xmlXPathCompExprPtr comp; + + const xmlChar *name; + int has_name; + const xmlChar *ns; + int has_ns; +}; + +/** + * xsltStyleItemWithParam: + * + * + * + * + */ +typedef xsltStyleBasicItemVariable xsltStyleItemWithParam; +typedef xsltStyleItemWithParam *xsltStyleItemWithParamPtr; + +/** + * xsltStyleItemSort: + * + * Reflects the XSLT xsl:sort item. + * Allowed parents: xsl:apply-templates, xsl:for-each + * + */ +typedef struct _xsltStyleItemSort xsltStyleItemSort; +typedef xsltStyleItemSort *xsltStyleItemSortPtr; + +struct _xsltStyleItemSort { + XSLT_ITEM_COMMON_FIELDS + + const xmlChar *stype; /* sort */ + int has_stype; /* sort */ + int number; /* sort */ + const xmlChar *order; /* sort */ + int has_order; /* sort */ + int descending; /* sort */ + const xmlChar *lang; /* sort */ + int has_lang; /* sort */ + const xmlChar *case_order; /* sort */ + int lower_first; /* sort */ + + const xmlChar *use; + int has_use; + + const xmlChar *select; /* sort, copy-of, value-of, apply-templates */ + + xmlXPathCompExprPtr comp; /* a precompiled XPath expression */ +}; + + +/** + * xsltStyleItemWhen: + * + * + * + * + * Allowed parent: xsl:choose + */ +typedef struct _xsltStyleItemWhen xsltStyleItemWhen; +typedef xsltStyleItemWhen *xsltStyleItemWhenPtr; + +struct _xsltStyleItemWhen { + XSLT_ITEM_COMMON_FIELDS + + const xmlChar *test; + xmlXPathCompExprPtr comp; +}; + +/** + * xsltStyleItemOtherwise: + * + * Allowed parent: xsl:choose + * + * + * + */ +typedef struct _xsltStyleItemOtherwise xsltStyleItemOtherwise; +typedef xsltStyleItemOtherwise *xsltStyleItemOtherwisePtr; + +struct _xsltStyleItemOtherwise { + XSLT_ITEM_COMMON_FIELDS +}; + +typedef struct _xsltStyleItemInclude xsltStyleItemInclude; +typedef xsltStyleItemInclude *xsltStyleItemIncludePtr; + +struct _xsltStyleItemInclude { + XSLT_ITEM_COMMON_FIELDS + xsltDocumentPtr include; +}; + +/************************************************************************ + * * + * XSLT elements in forwards-compatible mode * + * * + ************************************************************************/ + +typedef struct _xsltStyleItemUknown xsltStyleItemUknown; +typedef xsltStyleItemUknown *xsltStyleItemUknownPtr; +struct _xsltStyleItemUknown { + XSLT_ITEM_COMMON_FIELDS +}; + + +/************************************************************************ + * * + * Extension elements * + * * + ************************************************************************/ + +/* + * xsltStyleItemExtElement: + * + * Reflects extension elements. + * + * NOTE: Due to the fact that the structure xsltElemPreComp is most + * probably already heavily in use out there by users, so we cannot + * easily change it, we'll create an intermediate structure which will + * hold an xsltElemPreCompPtr. + * BIG NOTE: The only problem I see here is that the user processes the + * content of the stylesheet tree, possibly he'll lookup the node->psvi + * fields in order to find subsequent extension functions. + * In this case, the user's code will break, since the node->psvi + * field will hold now the xsltStyleItemExtElementPtr and not + * the xsltElemPreCompPtr. + * However the place where the structure is anchored in the node-tree, + * namely node->psvi, has beed already once been moved from node->_private + * to node->psvi, so we have a precedent here, which, I think, should allow + * us to change such semantics without headaches. + */ +typedef struct _xsltStyleItemExtElement xsltStyleItemExtElement; +typedef xsltStyleItemExtElement *xsltStyleItemExtElementPtr; +struct _xsltStyleItemExtElement { + XSLT_ITEM_COMMON_FIELDS + xsltElemPreCompPtr item; +}; + +/************************************************************************ + * * + * Literal result elements * + * * + ************************************************************************/ + +typedef struct _xsltEffectiveNs xsltEffectiveNs; +typedef xsltEffectiveNs *xsltEffectiveNsPtr; +struct _xsltEffectiveNs { + xsltEffectiveNsPtr nextInStore; /* storage next */ + xsltEffectiveNsPtr next; /* next item in the list */ + const xmlChar *prefix; + const xmlChar *nsName; + /* + * Indicates if eclared on the literal result element; dunno if really + * needed. + */ + int holdByElem; +}; + +/* + * Info for literal result elements. + * This will be set on the elem->psvi field and will be + * shared by literal result elements, which have the same + * excluded result namespaces; i.e., this *won't* be created uniquely + * for every literal result element. + */ +typedef struct _xsltStyleItemLRElementInfo xsltStyleItemLRElementInfo; +typedef xsltStyleItemLRElementInfo *xsltStyleItemLRElementInfoPtr; +struct _xsltStyleItemLRElementInfo { + XSLT_ITEM_COMMON_FIELDS + /* + * @effectiveNs is the set of effective ns-nodes + * on the literal result element, which will be added to the result + * element if not already existing in the result tree. + * This means that excluded namespaces (via exclude-result-prefixes, + * extension-element-prefixes and the XSLT namespace) not added + * to the set. + * Namespace-aliasing was applied on the @effectiveNs. + */ + xsltEffectiveNsPtr effectiveNs; + +}; + +#ifdef XSLT_REFACTORED + +typedef struct _xsltNsAlias xsltNsAlias; +typedef xsltNsAlias *xsltNsAliasPtr; +struct _xsltNsAlias { + xsltNsAliasPtr next; /* next in the list */ + xmlNsPtr literalNs; + xmlNsPtr targetNs; + xmlDocPtr docOfTargetNs; +}; +#endif + +#ifdef XSLT_REFACTORED_XSLT_NSCOMP + +typedef struct _xsltNsMap xsltNsMap; +typedef xsltNsMap *xsltNsMapPtr; +struct _xsltNsMap { + xsltNsMapPtr next; /* next in the list */ + xmlDocPtr doc; + xmlNodePtr elem; /* the element holding the ns-decl */ + xmlNsPtr ns; /* the xmlNs structure holding the XML namespace name */ + const xmlChar *origNsName; /* the original XML namespace name */ + const xmlChar *newNsName; /* the mapped XML namespace name */ +}; +#endif + +/************************************************************************ + * * + * Compile-time structures for *internal* use only * + * * + ************************************************************************/ + +typedef struct _xsltPrincipalStylesheetData xsltPrincipalStylesheetData; +typedef xsltPrincipalStylesheetData *xsltPrincipalStylesheetDataPtr; + +typedef struct _xsltNsList xsltNsList; +typedef xsltNsList *xsltNsListPtr; +struct _xsltNsList { + xsltNsListPtr next; /* next in the list */ + xmlNsPtr ns; +}; + +/* +* xsltVarInfo: +* +* Used at compilation time for parameters and variables. +*/ +typedef struct _xsltVarInfo xsltVarInfo; +typedef xsltVarInfo *xsltVarInfoPtr; +struct _xsltVarInfo { + xsltVarInfoPtr next; /* next in the list */ + xsltVarInfoPtr prev; + int depth; /* the depth in the tree */ + const xmlChar *name; + const xmlChar *nsName; +}; + +/** + * xsltCompilerNodeInfo: + * + * Per-node information during compile-time. + */ +typedef struct _xsltCompilerNodeInfo xsltCompilerNodeInfo; +typedef xsltCompilerNodeInfo *xsltCompilerNodeInfoPtr; +struct _xsltCompilerNodeInfo { + xsltCompilerNodeInfoPtr next; + xsltCompilerNodeInfoPtr prev; + xmlNodePtr node; + int depth; + xsltTemplatePtr templ; /* The owning template */ + int category; /* XSLT element, LR-element or + extension element */ + xsltStyleType type; + xsltElemPreCompPtr item; /* The compiled information */ + /* The current in-scope namespaces */ + xsltNsListContainerPtr inScopeNs; + /* The current excluded result namespaces */ + xsltPointerListPtr exclResultNs; + /* The current extension instruction namespaces */ + xsltPointerListPtr extElemNs; + + /* The current info for literal result elements. */ + xsltStyleItemLRElementInfoPtr litResElemInfo; + /* + * Set to 1 if in-scope namespaces changed, + * or excluded result namespaces changed, + * or extension element namespaces changed. + * This will trigger creation of new infos + * for literal result elements. + */ + int nsChanged; + int preserveWhitespace; + int stripWhitespace; + int isRoot; /* whether this is the stylesheet's root node */ + int forwardsCompat; /* whether forwards-compatible mode is enabled */ + /* whether the content of an extension element was processed */ + int extContentHandled; + /* the type of the current child */ + xsltStyleType curChildType; +}; + +/** + * XSLT_CCTXT: + * + * get pointer to compiler context + */ +#define XSLT_CCTXT(style) ((xsltCompilerCtxtPtr) style->compCtxt) + +typedef enum { + XSLT_ERROR_SEVERITY_ERROR = 0, + XSLT_ERROR_SEVERITY_WARNING +} xsltErrorSeverityType; + +typedef struct _xsltCompilerCtxt xsltCompilerCtxt; +typedef xsltCompilerCtxt *xsltCompilerCtxtPtr; +struct _xsltCompilerCtxt { + void *errorCtxt; /* user specific error context */ + /* + * used for error/warning reports; e.g. XSLT_ERROR_SEVERITY_WARNING */ + xsltErrorSeverityType errSeverity; + int warnings; /* TODO: number of warnings found at + compilation */ + int errors; /* TODO: number of errors found at + compilation */ + xmlDictPtr dict; + xsltStylesheetPtr style; + int simplified; /* whether this is a simplified stylesheet */ + /* TODO: structured/unstructured error contexts. */ + int depth; /* Current depth of processing */ + + xsltCompilerNodeInfoPtr inode; + xsltCompilerNodeInfoPtr inodeList; + xsltCompilerNodeInfoPtr inodeLast; + xsltPointerListPtr tmpList; /* Used for various purposes */ + /* + * The XSLT version as specified by the stylesheet's root element. + */ + int isInclude; + int hasForwardsCompat; /* whether forwards-compatible mode was used + in a parsing episode */ + int maxNodeInfos; /* TEMP TODO: just for the interest */ + int maxLREs; /* TEMP TODO: just for the interest */ + /* + * In order to keep the old behaviour, applying strict rules of + * the spec can be turned off. This has effect only on special + * mechanisms like whitespace-stripping in the stylesheet. + */ + int strict; + xsltPrincipalStylesheetDataPtr psData; + xsltStyleItemUknownPtr unknownItem; + int hasNsAliases; /* Indicator if there was an xsl:namespace-alias. */ + xsltNsAliasPtr nsAliases; + xsltVarInfoPtr ivars; /* Storage of local in-scope variables/params. */ + xsltVarInfoPtr ivar; /* topmost local variable/param. */ +}; + +#else /* XSLT_REFACTORED */ +/* +* The old structures before refactoring. +*/ + +/** + * _xsltStylePreComp: + * + * The in-memory structure corresponding to XSLT stylesheet constructs + * precomputed data. + */ +struct _xsltStylePreComp { + xsltElemPreCompPtr next; /* chained list */ + xsltStyleType type; /* type of the element */ + xsltTransformFunction func; /* handling function */ + xmlNodePtr inst; /* the instruction */ + + /* + * Pre computed values. + */ + + const xmlChar *stype; /* sort */ + int has_stype; /* sort */ + int number; /* sort */ + const xmlChar *order; /* sort */ + int has_order; /* sort */ + int descending; /* sort */ + const xmlChar *lang; /* sort */ + int has_lang; /* sort */ + const xmlChar *case_order; /* sort */ + int lower_first; /* sort */ + + const xmlChar *use; /* copy, element */ + int has_use; /* copy, element */ + + int noescape; /* text */ + + const xmlChar *name; /* element, attribute, pi */ + int has_name; /* element, attribute, pi */ + const xmlChar *ns; /* element */ + int has_ns; /* element */ + + const xmlChar *mode; /* apply-templates */ + const xmlChar *modeURI; /* apply-templates */ + + const xmlChar *test; /* if */ + + xsltTemplatePtr templ; /* call-template */ + + const xmlChar *select; /* sort, copy-of, value-of, apply-templates */ + + int ver11; /* document */ + const xmlChar *filename; /* document URL */ + int has_filename; /* document */ + + xsltNumberData numdata; /* number */ + + xmlXPathCompExprPtr comp; /* a precompiled XPath expression */ + xmlNsPtr *nsList; /* the namespaces in scope */ + int nsNr; /* the number of namespaces in scope */ +}; + +#endif /* XSLT_REFACTORED */ + + +/* + * The in-memory structure corresponding to an XSLT Variable + * or Param. + */ +typedef struct _xsltStackElem xsltStackElem; +typedef xsltStackElem *xsltStackElemPtr; +struct _xsltStackElem { + struct _xsltStackElem *next;/* chained list */ + xsltStylePreCompPtr comp; /* the compiled form */ + int computed; /* was the evaluation done */ + const xmlChar *name; /* the local part of the name QName */ + const xmlChar *nameURI; /* the URI part of the name QName */ + const xmlChar *select; /* the eval string */ + xmlNodePtr tree; /* the sequence constructor if no eval + string or the location */ + xmlXPathObjectPtr value; /* The value if computed */ + xmlDocPtr fragment; /* The Result Tree Fragments (needed for XSLT 1.0) + which are bound to the variable's lifetime. */ + int level; /* the depth in the tree; + -1 if persistent (e.g. a given xsl:with-param) */ + xsltTransformContextPtr context; /* The transformation context; needed to cache + the variables */ + int flags; +}; + +#ifdef XSLT_REFACTORED + +struct _xsltPrincipalStylesheetData { + /* + * Namespace dictionary for ns-prefixes and ns-names: + * TODO: Shared between stylesheets, and XPath mechanisms. + * Not used yet. + */ + xmlDictPtr namespaceDict; + /* + * Global list of in-scope namespaces. + */ + xsltPointerListPtr inScopeNamespaces; + /* + * Global list of information for [xsl:]excluded-result-prefixes. + */ + xsltPointerListPtr exclResultNamespaces; + /* + * Global list of information for [xsl:]extension-element-prefixes. + */ + xsltPointerListPtr extElemNamespaces; + xsltEffectiveNsPtr effectiveNs; +#ifdef XSLT_REFACTORED_XSLT_NSCOMP + /* + * Namespace name map to get rid of string comparison of namespace names. + */ + xsltNsMapPtr nsMap; +#endif +}; + + +#endif +/* + * Note that we added a @compCtxt field to anchor an stylesheet compilation + * context, since, due to historical reasons, various compile-time function + * take only the stylesheet as argument and not a compilation context. + */ +struct _xsltStylesheet { + /* + * The stylesheet import relation is kept as a tree. + */ + struct _xsltStylesheet *parent; + struct _xsltStylesheet *next; + struct _xsltStylesheet *imports; + + xsltDocumentPtr docList; /* the include document list */ + + /* + * General data on the style sheet document. + */ + xmlDocPtr doc; /* the parsed XML stylesheet */ + xmlHashTablePtr stripSpaces;/* the hash table of the strip-space and + preserve space elements */ + int stripAll; /* strip-space * (1) preserve-space * (-1) */ + xmlHashTablePtr cdataSection;/* the hash table of the cdata-section */ + + /* + * Global variable or parameters. + */ + xsltStackElemPtr variables; /* linked list of param and variables */ + + /* + * Template descriptions. + */ + xsltTemplatePtr templates; /* the ordered list of templates */ + xmlHashTablePtr templatesHash; /* hash table or wherever compiled + templates information is stored */ + struct _xsltCompMatch *rootMatch; /* template based on / */ + struct _xsltCompMatch *keyMatch; /* template based on key() */ + struct _xsltCompMatch *elemMatch; /* template based on * */ + struct _xsltCompMatch *attrMatch; /* template based on @* */ + struct _xsltCompMatch *parentMatch; /* template based on .. */ + struct _xsltCompMatch *textMatch; /* template based on text() */ + struct _xsltCompMatch *piMatch; /* template based on + processing-instruction() */ + struct _xsltCompMatch *commentMatch; /* template based on comment() */ + + /* + * Namespace aliases. + * NOTE: Not used in the refactored code. + */ + xmlHashTablePtr nsAliases; /* the namespace alias hash tables */ + + /* + * Attribute sets. + */ + xmlHashTablePtr attributeSets;/* the attribute sets hash tables */ + + /* + * Namespaces. + * TODO: Eliminate this. + */ + xmlHashTablePtr nsHash; /* the set of namespaces in use: + ATTENTION: This is used for + execution of XPath expressions; unfortunately + it restricts the stylesheet to have distinct + prefixes. + TODO: We need to get rid of this. + */ + void *nsDefs; /* ATTENTION TODO: This is currently used to store + xsltExtDefPtr (in extensions.c) and + *not* xmlNsPtr. + */ + + /* + * Key definitions. + */ + void *keys; /* key definitions */ + + /* + * Output related stuff. + */ + xmlChar *method; /* the output method */ + xmlChar *methodURI; /* associated namespace if any */ + xmlChar *version; /* version string */ + xmlChar *encoding; /* encoding string */ + int omitXmlDeclaration; /* omit-xml-declaration = "yes" | "no" */ + + /* + * Number formatting. + */ + xsltDecimalFormatPtr decimalFormat; + int standalone; /* standalone = "yes" | "no" */ + xmlChar *doctypePublic; /* doctype-public string */ + xmlChar *doctypeSystem; /* doctype-system string */ + int indent; /* should output being indented */ + xmlChar *mediaType; /* media-type string */ + + /* + * Precomputed blocks. + */ + xsltElemPreCompPtr preComps;/* list of precomputed blocks */ + int warnings; /* number of warnings found at compilation */ + int errors; /* number of errors found at compilation */ + + xmlChar *exclPrefix; /* last excluded prefixes */ + xmlChar **exclPrefixTab; /* array of excluded prefixes */ + int exclPrefixNr; /* number of excluded prefixes in scope */ + int exclPrefixMax; /* size of the array */ + + void *_private; /* user defined data */ + + /* + * Extensions. + */ + xmlHashTablePtr extInfos; /* the extension data */ + int extrasNr; /* the number of extras required */ + + /* + * For keeping track of nested includes + */ + xsltDocumentPtr includes; /* points to last nested include */ + + /* + * dictionary: shared between stylesheet, context and documents. + */ + xmlDictPtr dict; + /* + * precompiled attribute value templates. + */ + void *attVTs; + /* + * if namespace-alias has an alias for the default stylesheet prefix + * NOTE: Not used in the refactored code. + */ + const xmlChar *defaultAlias; + /* + * bypass pre-processing (already done) (used in imports) + */ + int nopreproc; + /* + * all document text strings were internalized + */ + int internalized; + /* + * Literal Result Element as Stylesheet c.f. section 2.3 + */ + int literal_result; + /* + * The principal stylesheet + */ + xsltStylesheetPtr principal; +#ifdef XSLT_REFACTORED + /* + * Compilation context used during compile-time. + */ + xsltCompilerCtxtPtr compCtxt; /* TODO: Change this to (void *). */ + + xsltPrincipalStylesheetDataPtr principalData; +#endif + /* + * Forwards-compatible processing + */ + int forwards_compatible; + + xmlHashTablePtr namedTemplates; /* hash table of named templates */ + + xmlXPathContextPtr xpathCtxt; + + unsigned long opLimit; + unsigned long opCount; +}; + +typedef struct _xsltTransformCache xsltTransformCache; +typedef xsltTransformCache *xsltTransformCachePtr; +struct _xsltTransformCache { + xmlDocPtr RVT; + int nbRVT; + xsltStackElemPtr stackItems; + int nbStackItems; +#ifdef XSLT_DEBUG_PROFILE_CACHE + int dbgCachedRVTs; + int dbgReusedRVTs; + int dbgCachedVars; + int dbgReusedVars; +#endif +}; + +/* + * The in-memory structure corresponding to an XSLT Transformation. + */ +typedef enum { + XSLT_OUTPUT_XML = 0, + XSLT_OUTPUT_HTML, + XSLT_OUTPUT_TEXT +} xsltOutputType; + +typedef void * +(*xsltNewLocaleFunc)(const xmlChar *lang, int lowerFirst); +typedef void +(*xsltFreeLocaleFunc)(void *locale); +typedef xmlChar * +(*xsltGenSortKeyFunc)(void *locale, const xmlChar *lang); + +typedef enum { + XSLT_STATE_OK = 0, + XSLT_STATE_ERROR, + XSLT_STATE_STOPPED +} xsltTransformState; + +struct _xsltTransformContext { + xsltStylesheetPtr style; /* the stylesheet used */ + xsltOutputType type; /* the type of output */ + + xsltTemplatePtr templ; /* the current template */ + int templNr; /* Nb of templates in the stack */ + int templMax; /* Size of the templtes stack */ + xsltTemplatePtr *templTab; /* the template stack */ + + xsltStackElemPtr vars; /* the current variable list */ + int varsNr; /* Nb of variable list in the stack */ + int varsMax; /* Size of the variable list stack */ + xsltStackElemPtr *varsTab; /* the variable list stack */ + int varsBase; /* the var base for current templ */ + + /* + * Extensions + */ + xmlHashTablePtr extFunctions; /* the extension functions */ + xmlHashTablePtr extElements; /* the extension elements */ + xmlHashTablePtr extInfos; /* the extension data */ + + const xmlChar *mode; /* the current mode */ + const xmlChar *modeURI; /* the current mode URI */ + + xsltDocumentPtr docList; /* the document list */ + + xsltDocumentPtr document; /* the current source document; can be NULL if an RTF */ + xmlNodePtr node; /* the current node being processed */ + xmlNodeSetPtr nodeList; /* the current node list */ + /* xmlNodePtr current; the node */ + + xmlDocPtr output; /* the resulting document */ + xmlNodePtr insert; /* the insertion node */ + + xmlXPathContextPtr xpathCtxt; /* the XPath context */ + xsltTransformState state; /* the current state */ + + /* + * Global variables + */ + xmlHashTablePtr globalVars; /* the global variables and params */ + + xmlNodePtr inst; /* the instruction in the stylesheet */ + + int xinclude; /* should XInclude be processed */ + + const char * outputFile; /* the output URI if known */ + + int profile; /* is this run profiled */ + long prof; /* the current profiled value */ + int profNr; /* Nb of templates in the stack */ + int profMax; /* Size of the templtaes stack */ + long *profTab; /* the profile template stack */ + + void *_private; /* user defined data */ + + int extrasNr; /* the number of extras used */ + int extrasMax; /* the number of extras allocated */ + xsltRuntimeExtraPtr extras; /* extra per runtime information */ + + xsltDocumentPtr styleList; /* the stylesheet docs list */ + void * sec; /* the security preferences if any */ + + xmlGenericErrorFunc error; /* a specific error handler */ + void * errctx; /* context for the error handler */ + + xsltSortFunc sortfunc; /* a ctxt specific sort routine */ + + /* + * handling of temporary Result Value Tree + * (XSLT 1.0 term: "Result Tree Fragment") + */ + xmlDocPtr tmpRVT; /* list of RVT without persistance */ + xmlDocPtr persistRVT; /* list of persistant RVTs */ + int ctxtflags; /* context processing flags */ + + /* + * Speed optimization when coalescing text nodes + */ + const xmlChar *lasttext; /* last text node content */ + int lasttsize; /* last text node size */ + int lasttuse; /* last text node use */ + /* + * Per Context Debugging + */ + int debugStatus; /* the context level debug status */ + unsigned long* traceCode; /* pointer to the variable holding the mask */ + + int parserOptions; /* parser options xmlParserOption */ + + /* + * dictionary: shared between stylesheet, context and documents. + */ + xmlDictPtr dict; + xmlDocPtr tmpDoc; /* Obsolete; not used in the library. */ + /* + * all document text strings are internalized + */ + int internalized; + int nbKeys; + int hasTemplKeyPatterns; + xsltTemplatePtr currentTemplateRule; /* the Current Template Rule */ + xmlNodePtr initialContextNode; + xmlDocPtr initialContextDoc; + xsltTransformCachePtr cache; + void *contextVariable; /* the current variable item */ + xmlDocPtr localRVT; /* list of local tree fragments; will be freed when + the instruction which created the fragment + exits */ + xmlDocPtr localRVTBase; /* Obsolete */ + int keyInitLevel; /* Needed to catch recursive keys issues */ + int depth; /* Needed to catch recursions */ + int maxTemplateDepth; + int maxTemplateVars; + unsigned long opLimit; + unsigned long opCount; + int sourceDocDirty; + unsigned long currentId; /* For generate-id() */ + + xsltNewLocaleFunc newLocale; + xsltFreeLocaleFunc freeLocale; + xsltGenSortKeyFunc genSortKey; +}; + +/** + * CHECK_STOPPED: + * + * Macro to check if the XSLT processing should be stopped. + * Will return from the function. + */ +#define CHECK_STOPPED if (ctxt->state == XSLT_STATE_STOPPED) return; + +/** + * CHECK_STOPPEDE: + * + * Macro to check if the XSLT processing should be stopped. + * Will goto the error: label. + */ +#define CHECK_STOPPEDE if (ctxt->state == XSLT_STATE_STOPPED) goto error; + +/** + * CHECK_STOPPED0: + * + * Macro to check if the XSLT processing should be stopped. + * Will return from the function with a 0 value. + */ +#define CHECK_STOPPED0 if (ctxt->state == XSLT_STATE_STOPPED) return(0); + +/* + * The macro XML_CAST_FPTR is a hack to avoid a gcc warning about + * possible incompatibilities between function pointers and object + * pointers. It is defined in libxml/hash.h within recent versions + * of libxml2, but is put here for compatibility. + */ +#ifndef XML_CAST_FPTR +/** + * XML_CAST_FPTR: + * @fptr: pointer to a function + * + * Macro to do a casting from an object pointer to a + * function pointer without encountering a warning from + * gcc + * + * #define XML_CAST_FPTR(fptr) (*(void **)(&fptr)) + * This macro violated ISO C aliasing rules (gcc4 on s390 broke) + * so it is disabled now + */ + +#define XML_CAST_FPTR(fptr) fptr +#endif +/* + * Functions associated to the internal types +xsltDecimalFormatPtr xsltDecimalFormatGetByName(xsltStylesheetPtr sheet, + xmlChar *name); + */ +XSLTPUBFUN xsltStylesheetPtr XSLTCALL + xsltNewStylesheet (void); +XSLTPUBFUN xsltStylesheetPtr XSLTCALL + xsltParseStylesheetFile (const xmlChar* filename); +XSLTPUBFUN void XSLTCALL + xsltFreeStylesheet (xsltStylesheetPtr style); +XSLTPUBFUN int XSLTCALL + xsltIsBlank (xmlChar *str); +XSLTPUBFUN void XSLTCALL + xsltFreeStackElemList (xsltStackElemPtr elem); +XSLTPUBFUN xsltDecimalFormatPtr XSLTCALL + xsltDecimalFormatGetByName(xsltStylesheetPtr style, + xmlChar *name); +XSLTPUBFUN xsltDecimalFormatPtr XSLTCALL + xsltDecimalFormatGetByQName(xsltStylesheetPtr style, + const xmlChar *nsUri, + const xmlChar *name); + +XSLTPUBFUN xsltStylesheetPtr XSLTCALL + xsltParseStylesheetProcess(xsltStylesheetPtr ret, + xmlDocPtr doc); +XSLTPUBFUN void XSLTCALL + xsltParseStylesheetOutput(xsltStylesheetPtr style, + xmlNodePtr cur); +XSLTPUBFUN xsltStylesheetPtr XSLTCALL + xsltParseStylesheetDoc (xmlDocPtr doc); +XSLTPUBFUN xsltStylesheetPtr XSLTCALL + xsltParseStylesheetImportedDoc(xmlDocPtr doc, + xsltStylesheetPtr style); +XSLTPUBFUN int XSLTCALL + xsltParseStylesheetUser(xsltStylesheetPtr style, + xmlDocPtr doc); +XSLTPUBFUN xsltStylesheetPtr XSLTCALL + xsltLoadStylesheetPI (xmlDocPtr doc); +XSLTPUBFUN void XSLTCALL + xsltNumberFormat (xsltTransformContextPtr ctxt, + xsltNumberDataPtr data, + xmlNodePtr node); +XSLTPUBFUN xmlXPathError XSLTCALL + xsltFormatNumberConversion(xsltDecimalFormatPtr self, + xmlChar *format, + double number, + xmlChar **result); + +XSLTPUBFUN void XSLTCALL + xsltParseTemplateContent(xsltStylesheetPtr style, + xmlNodePtr templ); +XSLTPUBFUN int XSLTCALL + xsltAllocateExtra (xsltStylesheetPtr style); +XSLTPUBFUN int XSLTCALL + xsltAllocateExtraCtxt (xsltTransformContextPtr ctxt); +/* + * Extra functions for Result Value Trees + */ +XSLTPUBFUN xmlDocPtr XSLTCALL + xsltCreateRVT (xsltTransformContextPtr ctxt); +XSLTPUBFUN int XSLTCALL + xsltRegisterTmpRVT (xsltTransformContextPtr ctxt, + xmlDocPtr RVT); +XSLTPUBFUN int XSLTCALL + xsltRegisterLocalRVT (xsltTransformContextPtr ctxt, + xmlDocPtr RVT); +XSLTPUBFUN int XSLTCALL + xsltRegisterPersistRVT (xsltTransformContextPtr ctxt, + xmlDocPtr RVT); +XSLTPUBFUN int XSLTCALL + xsltExtensionInstructionResultRegister( + xsltTransformContextPtr ctxt, + xmlXPathObjectPtr obj); +XSLTPUBFUN int XSLTCALL + xsltExtensionInstructionResultFinalize( + xsltTransformContextPtr ctxt); +XSLTPUBFUN int XSLTCALL + xsltFlagRVTs( + xsltTransformContextPtr ctxt, + xmlXPathObjectPtr obj, + int val); +XSLTPUBFUN void XSLTCALL + xsltFreeRVTs (xsltTransformContextPtr ctxt); +XSLTPUBFUN void XSLTCALL + xsltReleaseRVT (xsltTransformContextPtr ctxt, + xmlDocPtr RVT); +/* + * Extra functions for Attribute Value Templates + */ +XSLTPUBFUN void XSLTCALL + xsltCompileAttr (xsltStylesheetPtr style, + xmlAttrPtr attr); +XSLTPUBFUN xmlChar * XSLTCALL + xsltEvalAVT (xsltTransformContextPtr ctxt, + void *avt, + xmlNodePtr node); +XSLTPUBFUN void XSLTCALL + xsltFreeAVTList (void *avt); + +/* + * Extra function for successful xsltCleanupGlobals / xsltInit sequence. + */ + +XSLTPUBFUN void XSLTCALL + xsltUninit (void); + +/************************************************************************ + * * + * Compile-time functions for *internal* use only * + * * + ************************************************************************/ + +#ifdef XSLT_REFACTORED +XSLTPUBFUN void XSLTCALL + xsltParseSequenceConstructor( + xsltCompilerCtxtPtr cctxt, + xmlNodePtr start); +XSLTPUBFUN int XSLTCALL + xsltParseAnyXSLTElem (xsltCompilerCtxtPtr cctxt, + xmlNodePtr elem); +#ifdef XSLT_REFACTORED_XSLT_NSCOMP +XSLTPUBFUN int XSLTCALL + xsltRestoreDocumentNamespaces( + xsltNsMapPtr ns, + xmlDocPtr doc); +#endif +#endif /* XSLT_REFACTORED */ + +/************************************************************************ + * * + * Transformation-time functions for *internal* use only * + * * + ************************************************************************/ +XSLTPUBFUN int XSLTCALL + xsltInitCtxtKey (xsltTransformContextPtr ctxt, + xsltDocumentPtr doc, + xsltKeyDefPtr keyd); +XSLTPUBFUN int XSLTCALL + xsltInitAllDocKeys (xsltTransformContextPtr ctxt); +#ifdef __cplusplus +} +#endif + +#endif /* __XML_XSLT_H__ */ + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/xsltconfig.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/xsltconfig.h new file mode 100644 index 00000000..e05f2530 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/xsltconfig.h @@ -0,0 +1,146 @@ +/* + * Summary: compile-time version information for the XSLT engine + * Description: compile-time version information for the XSLT engine + * this module is autogenerated. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XSLTCONFIG_H__ +#define __XML_XSLTCONFIG_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * LIBXSLT_DOTTED_VERSION: + * + * the version string like "1.2.3" + */ +#define LIBXSLT_DOTTED_VERSION "1.1.43" + +/** + * LIBXSLT_VERSION: + * + * the version number: 1.2.3 value is 10203 + */ +#define LIBXSLT_VERSION 10143 + +/** + * LIBXSLT_VERSION_STRING: + * + * the version number string, 1.2.3 value is "10203" + */ +#define LIBXSLT_VERSION_STRING "10143" + +/** + * LIBXSLT_VERSION_EXTRA: + * + * extra version information, used to show a Git commit description + */ +#define LIBXSLT_VERSION_EXTRA "" + +/** + * WITH_XSLT_DEBUG: + * + * Activate the compilation of the debug reporting. Speed penalty + * is insignifiant and being able to run xsltpoc -v is useful. On + * by default unless --without-debug is passed to configure + */ +#if 1 +#define WITH_XSLT_DEBUG +#endif + +/** + * XSLT_NEED_TRIO: + * + * should be activated if the existing libc library lacks some of the + * string formatting function, in that case reuse the Trio ones already + * compiled in the libxml2 library. + */ + +#if 0 +#define XSLT_NEED_TRIO +#endif +#ifdef __VMS +#define HAVE_SYS_STAT_H 1 +#ifndef XSLT_NEED_TRIO +#define XSLT_NEED_TRIO +#endif +#endif + +#ifdef XSLT_NEED_TRIO +#define TRIO_REPLACE_STDIO +#endif + +/** + * WITH_XSLT_DEBUGGER: + * + * Activate the compilation of the debugger support. Speed penalty + * is insignifiant. + * On by default unless --without-debugger is passed to configure + */ +#if 0 +#ifndef WITH_DEBUGGER +#define WITH_DEBUGGER +#endif +#endif + +/** + * WITH_PROFILER: + * + * Activate the compilation of the profiler. Speed penalty + * is insignifiant. + * On by default unless --without-profiler is passed to configure + */ +#if 1 +#ifndef WITH_PROFILER +#define WITH_PROFILER +#endif +#endif + +/** + * WITH_MODULES: + * + * Whether module support is configured into libxslt + * Note: no default module path for win32 platforms + */ +#if 0 +#ifndef WITH_MODULES +#define WITH_MODULES +#endif +#define LIBXSLT_DEFAULT_PLUGINS_PATH() "" +#endif + +/** + * LIBXSLT_ATTR_FORMAT: + * + * This macro is used to indicate to GCC the parameters are printf-like + */ +#ifdef __GNUC__ +#define LIBXSLT_ATTR_FORMAT(fmt,args) __attribute__((__format__(__printf__,fmt,args))) +#else +#define LIBXSLT_ATTR_FORMAT(fmt,args) +#endif + +/** + * LIBXSLT_PUBLIC: + * + * This macro is used to declare PUBLIC variables for Cygwin and for MSC on Windows + */ +#if !defined LIBXSLT_PUBLIC +#if (defined(__CYGWIN__) || defined _MSC_VER) && !defined IN_LIBXSLT && !defined LIBXSLT_STATIC +#define LIBXSLT_PUBLIC __declspec(dllimport) +#else +#define LIBXSLT_PUBLIC +#endif +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_XSLTCONFIG_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/xsltexports.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/xsltexports.h new file mode 100644 index 00000000..95c352fe --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/xsltexports.h @@ -0,0 +1,64 @@ +/* + * Summary: macros for marking symbols as exportable/importable. + * Description: macros for marking symbols as exportable/importable. + * + * Copy: See Copyright for the status of this software. + */ + +#ifndef __XSLT_EXPORTS_H__ +#define __XSLT_EXPORTS_H__ + +#if defined(_WIN32) || defined(__CYGWIN__) +/** DOC_DISABLE */ + +#ifdef LIBXSLT_STATIC + #define XSLTPUBLIC +#elif defined(IN_LIBXSLT) + #define XSLTPUBLIC __declspec(dllexport) +#else + #define XSLTPUBLIC __declspec(dllimport) +#endif + +#define XSLTCALL __cdecl + +/** DOC_ENABLE */ +#else /* not Windows */ + +/** + * XSLTPUBLIC: + * + * Macro which declares a public symbol + */ +#define XSLTPUBLIC + +/** + * XSLTCALL: + * + * Macro which declares the calling convention for exported functions + */ +#define XSLTCALL + +#endif /* platform switch */ + +/* + * XSLTPUBFUN: + * + * Macro which declares an exportable function + */ +#define XSLTPUBFUN XSLTPUBLIC + +/** + * XSLTPUBVAR: + * + * Macro which declares an exportable variable + */ +#define XSLTPUBVAR XSLTPUBLIC extern + +/* Compatibility */ +#if !defined(LIBXSLT_PUBLIC) +#define LIBXSLT_PUBLIC XSLTPUBVAR +#endif + +#endif /* __XSLT_EXPORTS_H__ */ + + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/xsltlocale.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/xsltlocale.h new file mode 100644 index 00000000..c8be58d3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/xsltlocale.h @@ -0,0 +1,44 @@ +/* + * Summary: Locale handling + * Description: Interfaces for locale handling. Needed for language dependent + * sorting. + * + * Copy: See Copyright for the status of this software. + * + * Author: Nick Wellnhofer + */ + +#ifndef __XML_XSLTLOCALE_H__ +#define __XML_XSLTLOCALE_H__ + +#include +#include "xsltexports.h" + +#ifdef __cplusplus +extern "C" { +#endif + +XSLTPUBFUN void * XSLTCALL + xsltNewLocale (const xmlChar *langName, + int lowerFirst); +XSLTPUBFUN void XSLTCALL + xsltFreeLocale (void *locale); +XSLTPUBFUN xmlChar * XSLTCALL + xsltStrxfrm (void *locale, + const xmlChar *string); +XSLTPUBFUN void XSLTCALL + xsltFreeLocales (void); + +/* Backward compatibility */ +typedef void *xsltLocale; +typedef xmlChar xsltLocaleChar; +XSLTPUBFUN int XSLTCALL + xsltLocaleStrcmp (void *locale, + const xmlChar *str1, + const xmlChar *str2); + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_XSLTLOCALE_H__ */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/xsltutils.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/xsltutils.h new file mode 100644 index 00000000..2514774b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/include/libxslt/xsltutils.h @@ -0,0 +1,343 @@ +/* + * Summary: set of utilities for the XSLT engine + * Description: interfaces for the utilities module of the XSLT engine. + * things like message handling, profiling, and other + * generally useful routines. + * + * Copy: See Copyright for the status of this software. + * + * Author: Daniel Veillard + */ + +#ifndef __XML_XSLTUTILS_H__ +#define __XML_XSLTUTILS_H__ + +#include +#include +#include +#include +#include "xsltexports.h" +#include "xsltInternals.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * XSLT_TODO: + * + * Macro to flag unimplemented blocks. + */ +#define XSLT_TODO \ + xsltGenericError(xsltGenericErrorContext, \ + "Unimplemented block at %s:%d\n", \ + __FILE__, __LINE__); + +/** + * XSLT_STRANGE: + * + * Macro to flag that a problem was detected internally. + */ +#define XSLT_STRANGE \ + xsltGenericError(xsltGenericErrorContext, \ + "Internal error at %s:%d\n", \ + __FILE__, __LINE__); + +/** + * IS_XSLT_ELEM: + * + * Checks that the element pertains to XSLT namespace. + */ +#define IS_XSLT_ELEM(n) \ + (((n) != NULL) && ((n)->type == XML_ELEMENT_NODE) && \ + ((n)->ns != NULL) && (xmlStrEqual((n)->ns->href, XSLT_NAMESPACE))) + +/** + * IS_XSLT_NAME: + * + * Checks the value of an element in XSLT namespace. + */ +#define IS_XSLT_NAME(n, val) \ + (xmlStrEqual((n)->name, (const xmlChar *) (val))) + +/** + * IS_XSLT_REAL_NODE: + * + * Check that a node is a 'real' one: document, element, text or attribute. + */ +#define IS_XSLT_REAL_NODE(n) \ + (((n) != NULL) && \ + (((n)->type == XML_ELEMENT_NODE) || \ + ((n)->type == XML_TEXT_NODE) || \ + ((n)->type == XML_CDATA_SECTION_NODE) || \ + ((n)->type == XML_ATTRIBUTE_NODE) || \ + ((n)->type == XML_DOCUMENT_NODE) || \ + ((n)->type == XML_HTML_DOCUMENT_NODE) || \ + ((n)->type == XML_COMMENT_NODE) || \ + ((n)->type == XML_PI_NODE))) + +/* + * Our own version of namespaced attributes lookup. + */ +XSLTPUBFUN xmlChar * XSLTCALL + xsltGetNsProp (xmlNodePtr node, + const xmlChar *name, + const xmlChar *nameSpace); +XSLTPUBFUN const xmlChar * XSLTCALL + xsltGetCNsProp (xsltStylesheetPtr style, + xmlNodePtr node, + const xmlChar *name, + const xmlChar *nameSpace); +XSLTPUBFUN int XSLTCALL + xsltGetUTF8Char (const unsigned char *utf, + int *len); +#ifdef IN_LIBXSLT +/** DOC_DISABLE */ +XSLTPUBFUN int XSLTCALL + xsltGetUTF8CharZ (const unsigned char *utf, + int *len); +/** DOC_ENABLE */ +#endif + +/* + * XSLT Debug Tracing Tracing Types + */ +typedef enum { + XSLT_TRACE_ALL = -1, + XSLT_TRACE_NONE = 0, + XSLT_TRACE_COPY_TEXT = 1<<0, + XSLT_TRACE_PROCESS_NODE = 1<<1, + XSLT_TRACE_APPLY_TEMPLATE = 1<<2, + XSLT_TRACE_COPY = 1<<3, + XSLT_TRACE_COMMENT = 1<<4, + XSLT_TRACE_PI = 1<<5, + XSLT_TRACE_COPY_OF = 1<<6, + XSLT_TRACE_VALUE_OF = 1<<7, + XSLT_TRACE_CALL_TEMPLATE = 1<<8, + XSLT_TRACE_APPLY_TEMPLATES = 1<<9, + XSLT_TRACE_CHOOSE = 1<<10, + XSLT_TRACE_IF = 1<<11, + XSLT_TRACE_FOR_EACH = 1<<12, + XSLT_TRACE_STRIP_SPACES = 1<<13, + XSLT_TRACE_TEMPLATES = 1<<14, + XSLT_TRACE_KEYS = 1<<15, + XSLT_TRACE_VARIABLES = 1<<16 +} xsltDebugTraceCodes; + +/** + * XSLT_TRACE: + * + * Control the type of xsl debugtrace messages emitted. + */ +#define XSLT_TRACE(ctxt,code,call) \ + if (ctxt->traceCode && (*(ctxt->traceCode) & code)) \ + call + +XSLTPUBFUN void XSLTCALL + xsltDebugSetDefaultTrace(xsltDebugTraceCodes val); +XSLTPUBFUN xsltDebugTraceCodes XSLTCALL + xsltDebugGetDefaultTrace(void); + +/* + * XSLT specific error and debug reporting functions. + */ +XSLTPUBVAR xmlGenericErrorFunc xsltGenericError; +XSLTPUBVAR void *xsltGenericErrorContext; +XSLTPUBVAR xmlGenericErrorFunc xsltGenericDebug; +XSLTPUBVAR void *xsltGenericDebugContext; + +XSLTPUBFUN void XSLTCALL + xsltPrintErrorContext (xsltTransformContextPtr ctxt, + xsltStylesheetPtr style, + xmlNodePtr node); +XSLTPUBFUN void XSLTCALL + xsltMessage (xsltTransformContextPtr ctxt, + xmlNodePtr node, + xmlNodePtr inst); +XSLTPUBFUN void XSLTCALL + xsltSetGenericErrorFunc (void *ctx, + xmlGenericErrorFunc handler); +XSLTPUBFUN void XSLTCALL + xsltSetGenericDebugFunc (void *ctx, + xmlGenericErrorFunc handler); +XSLTPUBFUN void XSLTCALL + xsltSetTransformErrorFunc (xsltTransformContextPtr ctxt, + void *ctx, + xmlGenericErrorFunc handler); +XSLTPUBFUN void XSLTCALL + xsltTransformError (xsltTransformContextPtr ctxt, + xsltStylesheetPtr style, + xmlNodePtr node, + const char *msg, + ...) LIBXSLT_ATTR_FORMAT(4,5); + +XSLTPUBFUN int XSLTCALL + xsltSetCtxtParseOptions (xsltTransformContextPtr ctxt, + int options); +/* + * Sorting. + */ + +XSLTPUBFUN void XSLTCALL + xsltDocumentSortFunction (xmlNodeSetPtr list); +XSLTPUBFUN void XSLTCALL + xsltSetSortFunc (xsltSortFunc handler); +XSLTPUBFUN void XSLTCALL + xsltSetCtxtSortFunc (xsltTransformContextPtr ctxt, + xsltSortFunc handler); +XSLTPUBFUN void XSLTCALL + xsltSetCtxtLocaleHandlers (xsltTransformContextPtr ctxt, + xsltNewLocaleFunc newLocale, + xsltFreeLocaleFunc freeLocale, + xsltGenSortKeyFunc genSortKey); +XSLTPUBFUN void XSLTCALL + xsltDefaultSortFunction (xsltTransformContextPtr ctxt, + xmlNodePtr *sorts, + int nbsorts); +XSLTPUBFUN void XSLTCALL + xsltDoSortFunction (xsltTransformContextPtr ctxt, + xmlNodePtr * sorts, + int nbsorts); +XSLTPUBFUN xmlXPathObjectPtr * XSLTCALL + xsltComputeSortResult (xsltTransformContextPtr ctxt, + xmlNodePtr sort); + +/* + * QNames handling. + */ + +XSLTPUBFUN const xmlChar * XSLTCALL + xsltSplitQName (xmlDictPtr dict, + const xmlChar *name, + const xmlChar **prefix); +XSLTPUBFUN const xmlChar * XSLTCALL + xsltGetQNameURI (xmlNodePtr node, + xmlChar **name); + +XSLTPUBFUN const xmlChar * XSLTCALL + xsltGetQNameURI2 (xsltStylesheetPtr style, + xmlNodePtr node, + const xmlChar **name); + +/* + * Output, reuse libxml I/O buffers. + */ +XSLTPUBFUN int XSLTCALL + xsltSaveResultTo (xmlOutputBufferPtr buf, + xmlDocPtr result, + xsltStylesheetPtr style); +XSLTPUBFUN int XSLTCALL + xsltSaveResultToFilename (const char *URI, + xmlDocPtr result, + xsltStylesheetPtr style, + int compression); +XSLTPUBFUN int XSLTCALL + xsltSaveResultToFile (FILE *file, + xmlDocPtr result, + xsltStylesheetPtr style); +XSLTPUBFUN int XSLTCALL + xsltSaveResultToFd (int fd, + xmlDocPtr result, + xsltStylesheetPtr style); +XSLTPUBFUN int XSLTCALL + xsltSaveResultToString (xmlChar **doc_txt_ptr, + int * doc_txt_len, + xmlDocPtr result, + xsltStylesheetPtr style); + +/* + * XPath interface + */ +XSLTPUBFUN xmlXPathCompExprPtr XSLTCALL + xsltXPathCompile (xsltStylesheetPtr style, + const xmlChar *str); +XSLTPUBFUN xmlXPathCompExprPtr XSLTCALL + xsltXPathCompileFlags (xsltStylesheetPtr style, + const xmlChar *str, + int flags); + +#ifdef IN_LIBXSLT +/** DOC_DISABLE */ +#define XSLT_SOURCE_NODE_MASK 15u +#define XSLT_SOURCE_NODE_HAS_KEY 1u +#define XSLT_SOURCE_NODE_HAS_ID 2u +int +xsltGetSourceNodeFlags(xmlNodePtr node); +int +xsltSetSourceNodeFlags(xsltTransformContextPtr ctxt, xmlNodePtr node, + int flags); +int +xsltClearSourceNodeFlags(xmlNodePtr node, int flags); +void ** +xsltGetPSVIPtr(xmlNodePtr cur); +/** DOC_ENABLE */ +#endif + +#ifdef WITH_PROFILER +/* + * Profiling. + */ +XSLTPUBFUN void XSLTCALL + xsltSaveProfiling (xsltTransformContextPtr ctxt, + FILE *output); +XSLTPUBFUN xmlDocPtr XSLTCALL + xsltGetProfileInformation (xsltTransformContextPtr ctxt); + +XSLTPUBFUN long XSLTCALL + xsltTimestamp (void); +XSLTPUBFUN void XSLTCALL + xsltCalibrateAdjust (long delta); +#endif + +/** + * XSLT_TIMESTAMP_TICS_PER_SEC: + * + * Sampling precision for profiling + */ +#define XSLT_TIMESTAMP_TICS_PER_SEC 100000l + +/* + * Hooks for the debugger. + */ + +typedef enum { + XSLT_DEBUG_NONE = 0, /* no debugging allowed */ + XSLT_DEBUG_INIT, + XSLT_DEBUG_STEP, + XSLT_DEBUG_STEPOUT, + XSLT_DEBUG_NEXT, + XSLT_DEBUG_STOP, + XSLT_DEBUG_CONT, + XSLT_DEBUG_RUN, + XSLT_DEBUG_RUN_RESTART, + XSLT_DEBUG_QUIT +} xsltDebugStatusCodes; + +XSLTPUBVAR int xslDebugStatus; + +typedef void (*xsltHandleDebuggerCallback) (xmlNodePtr cur, xmlNodePtr node, + xsltTemplatePtr templ, xsltTransformContextPtr ctxt); +typedef int (*xsltAddCallCallback) (xsltTemplatePtr templ, xmlNodePtr source); +typedef void (*xsltDropCallCallback) (void); + +XSLTPUBFUN int XSLTCALL + xsltGetDebuggerStatus (void); +#ifdef WITH_DEBUGGER +XSLTPUBFUN void XSLTCALL + xsltSetDebuggerStatus (int value); +XSLTPUBFUN int XSLTCALL + xsltSetDebuggerCallbacks (int no, void *block); +XSLTPUBFUN int XSLTCALL + xslAddCall (xsltTemplatePtr templ, + xmlNodePtr source); +XSLTPUBFUN void XSLTCALL + xslDropCall (void); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __XML_XSLTUTILS_H__ */ + + diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/libxml2_polyfill.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/libxml2_polyfill.c new file mode 100644 index 00000000..750b1b52 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/libxml2_polyfill.c @@ -0,0 +1,114 @@ +#include + +#ifndef HAVE_XMLCTXTSETOPTIONS +/* based on libxml2-2.14.0-dev (1d8bd126) parser.c xmlCtxtSetInternalOptions */ +int +xmlCtxtSetOptions(xmlParserCtxtPtr ctxt, int options) +{ + int keepMask = 0; + int allMask; + + if (ctxt == NULL) { + return (-1); + } + + /* + * XInclude options aren't handled by the parser. + * + * XML_PARSE_XINCLUDE + * XML_PARSE_NOXINCNODE + * XML_PARSE_NOBASEFIX + */ + allMask = XML_PARSE_RECOVER | + XML_PARSE_NOENT | + XML_PARSE_DTDLOAD | + XML_PARSE_DTDATTR | + XML_PARSE_DTDVALID | + XML_PARSE_NOERROR | + XML_PARSE_NOWARNING | + XML_PARSE_PEDANTIC | + XML_PARSE_NOBLANKS | +#ifdef LIBXML_SAX1_ENABLED + XML_PARSE_SAX1 | +#endif + XML_PARSE_NONET | + XML_PARSE_NODICT | + XML_PARSE_NSCLEAN | + XML_PARSE_NOCDATA | + XML_PARSE_COMPACT | + XML_PARSE_OLD10 | + XML_PARSE_HUGE | + XML_PARSE_OLDSAX | + XML_PARSE_IGNORE_ENC | + XML_PARSE_BIG_LINES; + + ctxt->options = (ctxt->options & keepMask) | (options & allMask); + + /* + * For some options, struct members are historically the source + * of truth. The values are initalized from global variables and + * old code could also modify them directly. Several older API + * functions that don't take an options argument rely on these + * deprecated mechanisms. + * + * Once public access to struct members and the globals are + * disabled, we can use the options bitmask as source of + * truth, making all these struct members obsolete. + * + * The XML_DETECT_IDS flags is misnamed. It simply enables + * loading of the external subset. + */ + ctxt->recovery = (options & XML_PARSE_RECOVER) ? 1 : 0; + ctxt->replaceEntities = (options & XML_PARSE_NOENT) ? 1 : 0; + ctxt->loadsubset = (options & XML_PARSE_DTDLOAD) ? XML_DETECT_IDS : 0; + ctxt->loadsubset |= (options & XML_PARSE_DTDATTR) ? XML_COMPLETE_ATTRS : 0; + ctxt->validate = (options & XML_PARSE_DTDVALID) ? 1 : 0; + ctxt->pedantic = (options & XML_PARSE_PEDANTIC) ? 1 : 0; + ctxt->keepBlanks = (options & XML_PARSE_NOBLANKS) ? 0 : 1; + ctxt->dictNames = (options & XML_PARSE_NODICT) ? 0 : 1; + + /* + * Changing SAX callbacks is a bad idea. This should be fixed. + */ + if (options & XML_PARSE_NOBLANKS) { + ctxt->sax->ignorableWhitespace = xmlSAX2IgnorableWhitespace; + } + if (options & XML_PARSE_NOCDATA) { + ctxt->sax->cdataBlock = NULL; + } + if (options & XML_PARSE_HUGE) { + if (ctxt->dict != NULL) { + xmlDictSetLimit(ctxt->dict, 0); + } + } + + ctxt->linenumbers = 1; + + return (options & ~allMask); +} +#endif + +#ifndef HAVE_XMLCTXTGETOPTIONS +int +xmlCtxtGetOptions(xmlParserCtxtPtr ctxt) +{ + return (ctxt->options); +} +#endif + +#ifndef HAVE_XMLSWITCHENCODINGNAME +int +xmlSwitchEncodingName(xmlParserCtxtPtr ctxt, const char *encoding) +{ + if (ctxt == NULL) { + return (-1); + } + + xmlCharEncodingHandlerPtr handler = xmlFindCharEncodingHandler(encoding); + if (handler == NULL) { + return (-1); + } + + return (xmlSwitchToEncoding(ctxt, handler)); +} +#endif diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/nokogiri.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/nokogiri.c new file mode 100644 index 00000000..a43813b9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/nokogiri.c @@ -0,0 +1,294 @@ +#include + +VALUE mNokogiri ; +VALUE mNokogiriGumbo ; +VALUE mNokogiriHtml4 ; +VALUE mNokogiriHtml4Sax ; +VALUE mNokogiriHtml5 ; +VALUE mNokogiriXml ; +VALUE mNokogiriXmlSax ; +VALUE mNokogiriXmlXpath ; +VALUE mNokogiriXslt ; + +VALUE cNokogiriSyntaxError; +VALUE cNokogiriXmlCharacterData; +VALUE cNokogiriXmlElement; +VALUE cNokogiriXmlXpathSyntaxError; + +void noko_init_xml_attr(void); +void noko_init_xml_attribute_decl(void); +void noko_init_xml_cdata(void); +void noko_init_xml_comment(void); +void noko_init_xml_document(void); +void noko_init_xml_document_fragment(void); +void noko_init_xml_dtd(void); +void noko_init_xml_element_content(void); +void noko_init_xml_element_decl(void); +void noko_init_xml_encoding_handler(void); +void noko_init_xml_entity_decl(void); +void noko_init_xml_entity_reference(void); +void noko_init_xml_namespace(void); +void noko_init_xml_node(void); +void noko_init_xml_node_set(void); +void noko_init_xml_processing_instruction(void); +void noko_init_xml_reader(void); +void noko_init_xml_relax_ng(void); +void noko_init_xml_sax_parser(void); +void noko_init_xml_sax_parser_context(void); +void noko_init_xml_sax_push_parser(void); +void noko_init_xml_schema(void); +void noko_init_xml_syntax_error(void); +void noko_init_xml_text(void); +void noko_init_xml_xpath_context(void); +void noko_init_xslt_stylesheet(void); +void noko_init_html_document(void); +void noko_init_html_element_description(void); +void noko_init_html_entity_lookup(void); +void noko_init_html_sax_parser_context(void); +void noko_init_html_sax_push_parser(void); +void noko_init_html4_sax_parser(void); +void noko_init_gumbo(void); +void noko_init_test_global_handlers(void); + +static ID id_read, id_write, id_external_encoding; + + +static VALUE +noko_io_read_check(VALUE val) +{ + VALUE *args = (VALUE *)val; + return rb_funcall(args[0], id_read, 1, args[1]); +} + + +static VALUE +noko_io_read_failed(VALUE arg, VALUE exc) +{ + return Qundef; +} + + +int +noko_io_read(void *io, char *c_buffer, int c_buffer_len) +{ + VALUE rb_io = (VALUE)io; + VALUE rb_read_string, rb_args[2]; + size_t n_bytes_read, safe_len; + + rb_args[0] = rb_io; + rb_args[1] = INT2NUM(c_buffer_len); + + rb_read_string = rb_rescue(noko_io_read_check, (VALUE)rb_args, noko_io_read_failed, 0); + + if (NIL_P(rb_read_string)) { return 0; } + if (rb_read_string == Qundef) { return -1; } + if (TYPE(rb_read_string) != T_STRING) { return -1; } + + n_bytes_read = (size_t)RSTRING_LEN(rb_read_string); + safe_len = (n_bytes_read > (size_t)c_buffer_len) ? (size_t)c_buffer_len : n_bytes_read; + memcpy(c_buffer, StringValuePtr(rb_read_string), safe_len); + + return (int)safe_len; +} + + +static VALUE +noko_io_write_check(VALUE rb_args) +{ + VALUE rb_io = ((VALUE *)rb_args)[0]; + VALUE rb_output = ((VALUE *)rb_args)[1]; + return rb_funcall(rb_io, id_write, 1, rb_output); +} + + +static VALUE +noko_io_write_failed(VALUE arg, VALUE exc) +{ + return Qundef; +} + + +int +noko_io_write(void *io, char *c_buffer, int c_buffer_len) +{ + VALUE rb_args[2], rb_n_bytes_written; + VALUE rb_io = (VALUE)io; + VALUE rb_enc = Qnil; + rb_encoding *io_encoding; + + if (rb_respond_to(rb_io, id_external_encoding)) { + rb_enc = rb_funcall(rb_io, id_external_encoding, 0); + } + io_encoding = RB_NIL_P(rb_enc) ? rb_ascii8bit_encoding() : rb_to_encoding(rb_enc); + + rb_args[0] = rb_io; + rb_args[1] = rb_enc_str_new(c_buffer, (long)c_buffer_len, io_encoding); + + rb_n_bytes_written = rb_rescue(noko_io_write_check, (VALUE)rb_args, noko_io_write_failed, 0); + if (rb_n_bytes_written == Qundef) { return -1; } + + return NUM2INT(rb_n_bytes_written); +} + + +int +noko_io_close(void *io) +{ + return 0; +} + + +#if defined(_WIN32) && !defined(NOKOGIRI_PACKAGED_LIBRARIES) +# define NOKOGIRI_WINDOWS_DLLS 1 +#else +# define NOKOGIRI_WINDOWS_DLLS 0 +#endif + +// +// | dlls || true | false | +// | nlmm || | | +// |-----------++---------+---------| +// | NULL || default | ruby | +// | "random" || default | ruby | +// | "ruby" || ruby | ruby | +// | "default" || default | default | +// +// We choose *not* to use Ruby's memory management functions with windows DLLs because of this +// issue: https://github.com/sparklemotion/nokogiri/issues/2241 +// +static void +set_libxml_memory_management(void) +{ + const char *nlmm = getenv("NOKOGIRI_LIBXML_MEMORY_MANAGEMENT"); + if (nlmm) { + if (strcmp(nlmm, "default") == 0) { + goto libxml_uses_default_memory_management; + } else if (strcmp(nlmm, "ruby") == 0) { + goto libxml_uses_ruby_memory_management; + } + } + if (NOKOGIRI_WINDOWS_DLLS) { +libxml_uses_default_memory_management: + rb_const_set(mNokogiri, rb_intern("LIBXML_MEMORY_MANAGEMENT"), NOKOGIRI_STR_NEW2("default")); + return; + } else { +libxml_uses_ruby_memory_management: + rb_const_set(mNokogiri, rb_intern("LIBXML_MEMORY_MANAGEMENT"), NOKOGIRI_STR_NEW2("ruby")); + xmlMemSetup((xmlFreeFunc)ruby_xfree, (xmlMallocFunc)ruby_xmalloc, (xmlReallocFunc)ruby_xrealloc, ruby_strdup); + return; + } +} + + +void +Init_nokogiri(void) +{ + mNokogiri = rb_define_module("Nokogiri"); + mNokogiriGumbo = rb_define_module_under(mNokogiri, "Gumbo"); + mNokogiriHtml4 = rb_define_module_under(mNokogiri, "HTML4"); + mNokogiriHtml4Sax = rb_define_module_under(mNokogiriHtml4, "SAX"); + mNokogiriHtml5 = rb_define_module_under(mNokogiri, "HTML5"); + mNokogiriXml = rb_define_module_under(mNokogiri, "XML"); + mNokogiriXmlSax = rb_define_module_under(mNokogiriXml, "SAX"); + mNokogiriXmlXpath = rb_define_module_under(mNokogiriXml, "XPath"); + mNokogiriXslt = rb_define_module_under(mNokogiri, "XSLT"); + + set_libxml_memory_management(); /* must be before any function calls that might invoke xmlInitParser() */ + xmlInitParser(); + exsltRegisterAll(); + + rb_const_set(mNokogiri, rb_intern("LIBXML_COMPILED_VERSION"), NOKOGIRI_STR_NEW2(LIBXML_DOTTED_VERSION)); + rb_const_set(mNokogiri, rb_intern("LIBXML_LOADED_VERSION"), NOKOGIRI_STR_NEW2(xmlParserVersion)); + + rb_const_set(mNokogiri, rb_intern("LIBXSLT_COMPILED_VERSION"), NOKOGIRI_STR_NEW2(LIBXSLT_DOTTED_VERSION)); + rb_const_set(mNokogiri, rb_intern("LIBXSLT_LOADED_VERSION"), NOKOGIRI_STR_NEW2(xsltEngineVersion)); + + rb_const_set(mNokogiri, rb_intern("LIBXML_ZLIB_ENABLED"), + xmlHasFeature(XML_WITH_ZLIB) == 1 ? Qtrue : Qfalse); + +#ifdef NOKOGIRI_PACKAGED_LIBRARIES + rb_const_set(mNokogiri, rb_intern("PACKAGED_LIBRARIES"), Qtrue); +# ifdef NOKOGIRI_PRECOMPILED_LIBRARIES + rb_const_set(mNokogiri, rb_intern("PRECOMPILED_LIBRARIES"), Qtrue); +# else + rb_const_set(mNokogiri, rb_intern("PRECOMPILED_LIBRARIES"), Qfalse); +# endif + rb_const_set(mNokogiri, rb_intern("LIBXML2_PATCHES"), rb_str_split(NOKOGIRI_STR_NEW2(NOKOGIRI_LIBXML2_PATCHES), " ")); + rb_const_set(mNokogiri, rb_intern("LIBXSLT_PATCHES"), rb_str_split(NOKOGIRI_STR_NEW2(NOKOGIRI_LIBXSLT_PATCHES), " ")); +#else + rb_const_set(mNokogiri, rb_intern("PACKAGED_LIBRARIES"), Qfalse); + rb_const_set(mNokogiri, rb_intern("PRECOMPILED_LIBRARIES"), Qfalse); + rb_const_set(mNokogiri, rb_intern("LIBXML2_PATCHES"), Qnil); + rb_const_set(mNokogiri, rb_intern("LIBXSLT_PATCHES"), Qnil); +#endif + +#ifdef LIBXML_ICONV_ENABLED + rb_const_set(mNokogiri, rb_intern("LIBXML_ICONV_ENABLED"), Qtrue); +#else + rb_const_set(mNokogiri, rb_intern("LIBXML_ICONV_ENABLED"), Qfalse); +#endif + +#ifdef NOKOGIRI_OTHER_LIBRARY_VERSIONS + rb_const_set(mNokogiri, rb_intern("OTHER_LIBRARY_VERSIONS"), NOKOGIRI_STR_NEW2(NOKOGIRI_OTHER_LIBRARY_VERSIONS)); +#endif + + if (xsltExtModuleFunctionLookup((const xmlChar *)"date-time", EXSLT_DATE_NAMESPACE)) { + rb_const_set(mNokogiri, rb_intern("LIBXSLT_DATETIME_ENABLED"), Qtrue); + } else { + rb_const_set(mNokogiri, rb_intern("LIBXSLT_DATETIME_ENABLED"), Qfalse); + } + + cNokogiriSyntaxError = rb_define_class_under(mNokogiri, "SyntaxError", rb_eStandardError); + noko_init_xml_syntax_error(); + assert(cNokogiriXmlSyntaxError); + cNokogiriXmlXpathSyntaxError = rb_define_class_under(mNokogiriXmlXpath, "SyntaxError", cNokogiriXmlSyntaxError); + + noko_init_xml_element_content(); + noko_init_xml_encoding_handler(); + noko_init_xml_namespace(); + noko_init_xml_node_set(); + noko_init_xml_reader(); + + noko_init_xml_sax_parser(); + noko_init_html4_sax_parser(); + + noko_init_xml_xpath_context(); + noko_init_xslt_stylesheet(); + noko_init_html_element_description(); + noko_init_html_entity_lookup(); + + noko_init_xml_schema(); + noko_init_xml_relax_ng(); + + noko_init_xml_sax_parser_context(); + noko_init_html_sax_parser_context(); + + noko_init_xml_sax_push_parser(); + noko_init_html_sax_push_parser(); + + noko_init_xml_node(); + noko_init_xml_attr(); + noko_init_xml_attribute_decl(); + noko_init_xml_dtd(); + noko_init_xml_element_decl(); + noko_init_xml_entity_decl(); + noko_init_xml_entity_reference(); + noko_init_xml_processing_instruction(); + assert(cNokogiriXmlNode); + cNokogiriXmlElement = rb_define_class_under(mNokogiriXml, "Element", cNokogiriXmlNode); + cNokogiriXmlCharacterData = rb_define_class_under(mNokogiriXml, "CharacterData", cNokogiriXmlNode); + noko_init_xml_comment(); + noko_init_xml_text(); + noko_init_xml_cdata(); + + noko_init_xml_document_fragment(); + noko_init_xml_document(); + noko_init_html_document(); + noko_init_gumbo(); + + noko_init_test_global_handlers(); + + id_read = rb_intern("read"); + id_write = rb_intern("write"); + id_external_encoding = rb_intern("external_encoding"); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/nokogiri.h b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/nokogiri.h new file mode 100644 index 00000000..b75ebc47 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/nokogiri.h @@ -0,0 +1,238 @@ +#ifndef NOKOGIRI_NATIVE +#define NOKOGIRI_NATIVE + +#include // https://github.com/sparklemotion/nokogiri/issues/2696 + +#ifdef _MSC_VER +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif /* WIN32_LEAN_AND_MEAN */ + +# ifndef WIN32 +# define WIN32 +# endif /* WIN32 */ + +# include +# include +# include +#endif + +#ifdef _WIN32 +# define NOKOPUBFUN __declspec(dllexport) +# define NOKOPUBVAR __declspec(dllexport) extern +#else +# define NOKOPUBFUN +# define NOKOPUBVAR extern +#endif + +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +/* libxml2_polyfill.c */ +#ifndef HAVE_XMLCTXTSETOPTIONS +int xmlCtxtSetOptions(xmlParserCtxtPtr ctxt, int options); +#endif +#ifndef HAVE_XMLCTXTGETOPTIONS +int xmlCtxtGetOptions(xmlParserCtxtPtr ctxt); +#endif +#ifndef HAVE_XMLSWITCHENCODINGNAME +int xmlSwitchEncodingName(xmlParserCtxtPtr ctxt, const char *encoding); +#endif + +#define XMLNS_PREFIX "xmlns" +#define XMLNS_PREFIX_LEN 6 /* including either colon or \0 */ + +#ifndef xmlErrorConstPtr +# if LIBXML_VERSION >= 21200 +# define xmlErrorConstPtr const xmlError * +# else +# define xmlErrorConstPtr xmlError * +# endif +#endif + +#include +#include +#include +#include +#include + +#define NOKOGIRI_STR_NEW2(str) NOKOGIRI_STR_NEW(str, strlen((const char *)(str))) +#define NOKOGIRI_STR_NEW(str, len) rb_external_str_new_with_enc((const char *)(str), (long)(len), rb_utf8_encoding()) +#define RBSTR_OR_QNIL(_str) (_str ? NOKOGIRI_STR_NEW2(_str) : Qnil) + +#ifndef NORETURN_DECL +# if defined(__GNUC__) +# define NORETURN_DECL __attribute__ ((noreturn)) +# else +# define NORETURN_DECL +# endif +#endif + +#ifndef PRINTFLIKE_DECL +# if defined(__GNUC__) +# define PRINTFLIKE_DECL(stringidx, argidx) __attribute__ ((format(printf,stringidx,argidx))) +# else +# define PRINTFLIKE_DECL(stringidx, argidx) +# endif +#endif + +#if defined(TRUFFLERUBY) && !defined(NOKOGIRI_PACKAGED_LIBRARIES) +# define TRUFFLERUBY_NOKOGIRI_SYSTEM_LIBRARIES +#endif + +NOKOPUBVAR VALUE mNokogiri ; +NOKOPUBVAR VALUE mNokogiriGumbo ; +NOKOPUBVAR VALUE mNokogiriHtml4 ; +NOKOPUBVAR VALUE mNokogiriHtml4Sax ; +NOKOPUBVAR VALUE mNokogiriHtml5 ; +NOKOPUBVAR VALUE mNokogiriXml ; +NOKOPUBVAR VALUE mNokogiriXmlSax ; +NOKOPUBVAR VALUE mNokogiriXmlXpath ; +NOKOPUBVAR VALUE mNokogiriXslt ; + +NOKOPUBVAR VALUE cNokogiriEncodingHandler; +NOKOPUBVAR VALUE cNokogiriSyntaxError; +NOKOPUBVAR VALUE cNokogiriXmlAttr; +NOKOPUBVAR VALUE cNokogiriXmlAttributeDecl; +NOKOPUBVAR VALUE cNokogiriXmlCData; +NOKOPUBVAR VALUE cNokogiriXmlCharacterData; +NOKOPUBVAR VALUE cNokogiriXmlComment; +NOKOPUBVAR VALUE cNokogiriXmlDocument ; +NOKOPUBVAR VALUE cNokogiriXmlDocumentFragment; +NOKOPUBVAR VALUE cNokogiriXmlDtd; +NOKOPUBVAR VALUE cNokogiriXmlElement ; +NOKOPUBVAR VALUE cNokogiriXmlElementContent; +NOKOPUBVAR VALUE cNokogiriXmlElementDecl; +NOKOPUBVAR VALUE cNokogiriXmlEntityDecl; +NOKOPUBVAR VALUE cNokogiriXmlEntityReference; +NOKOPUBVAR VALUE cNokogiriXmlNamespace ; +NOKOPUBVAR VALUE cNokogiriXmlNode ; +NOKOPUBVAR VALUE cNokogiriXmlNodeSet ; +NOKOPUBVAR VALUE cNokogiriXmlProcessingInstruction; +NOKOPUBVAR VALUE cNokogiriXmlReader; +NOKOPUBVAR VALUE cNokogiriXmlRelaxNG; +NOKOPUBVAR VALUE cNokogiriXmlSaxParser ; +NOKOPUBVAR VALUE cNokogiriXmlSaxParserContext; +NOKOPUBVAR VALUE cNokogiriXmlSaxPushParser ; +NOKOPUBVAR VALUE cNokogiriXmlSchema; +NOKOPUBVAR VALUE cNokogiriXmlSyntaxError; +NOKOPUBVAR VALUE cNokogiriXmlText ; +NOKOPUBVAR VALUE cNokogiriXmlXpathContext; +NOKOPUBVAR VALUE cNokogiriXmlXpathSyntaxError; +NOKOPUBVAR VALUE cNokogiriXsltStylesheet ; + +NOKOPUBVAR VALUE cNokogiriHtml4Document ; +NOKOPUBVAR VALUE cNokogiriHtml4SaxPushParser ; +NOKOPUBVAR VALUE cNokogiriHtml4ElementDescription ; +NOKOPUBVAR VALUE cNokogiriHtml4SaxParser; +NOKOPUBVAR VALUE cNokogiriHtml4SaxParserContext; +NOKOPUBVAR VALUE cNokogiriHtml5Document ; + +typedef struct _nokogiriTuple { + VALUE doc; + st_table *unlinkedNodes; + VALUE node_cache; +} nokogiriTuple; +typedef nokogiriTuple *nokogiriTuplePtr; + +typedef struct _libxmlStructuredErrorHandlerState { + void *user_data; + xmlStructuredErrorFunc handler; +} libxmlStructuredErrorHandlerState ; + +typedef struct _nokogiriXsltStylesheetTuple { + xsltStylesheetPtr ss; + VALUE func_instances; +} nokogiriXsltStylesheetTuple; + +void noko_xml_document_pin_node(xmlNodePtr); +void noko_xml_document_pin_namespace(xmlNsPtr, xmlDocPtr); +int noko_xml_document_has_wrapped_blank_nodes_p(xmlDocPtr c_document); + +int noko_io_read(void *ctx, char *buffer, int len); +int noko_io_write(void *ctx, char *buffer, int len); +int noko_io_close(void *ctx); + +#define Noko_Node_Get_Struct(obj,type,sval) ((sval) = (type*)DATA_PTR(obj)) +#define Noko_Namespace_Get_Struct(obj,type,sval) ((sval) = (type*)DATA_PTR(obj)) + +VALUE noko_xml_node_wrap(VALUE klass, xmlNodePtr node) ; +VALUE noko_xml_node_wrap_node_set_result(xmlNodePtr node, VALUE node_set) ; +VALUE noko_xml_node_attrs(xmlNodePtr node) ; + +VALUE noko_xml_namespace_wrap(xmlNsPtr node, xmlDocPtr doc); +VALUE noko_xml_namespace_wrap_xpath_copy(xmlNsPtr node); + +VALUE noko_xml_element_content_wrap(VALUE doc, xmlElementContentPtr element); + +VALUE noko_xml_node_set_wrap(xmlNodeSetPtr node_set, VALUE document) ; +xmlNodeSetPtr noko_xml_node_set_unwrap(VALUE rb_node_set) ; + +VALUE noko_xml_document_wrap_with_init_args(VALUE klass, xmlDocPtr doc, int argc, VALUE *argv); +VALUE noko_xml_document_wrap(VALUE klass, xmlDocPtr doc); +xmlDocPtr noko_xml_document_unwrap(VALUE rb_document); +NOKOPUBFUN VALUE Nokogiri_wrap_xml_document(VALUE klass, + xmlDocPtr doc); /* deprecated. use noko_xml_document_wrap() instead. */ + +xmlSAXHandlerPtr noko_xml_sax_parser_unwrap(VALUE rb_sax_handler); + +xmlParserCtxtPtr noko_xml_sax_push_parser_unwrap(VALUE rb_parser); + +VALUE noko_xml_sax_parser_context_wrap(VALUE klass, xmlParserCtxtPtr c_context); +xmlParserCtxtPtr noko_xml_sax_parser_context_unwrap(VALUE rb_context); +void noko_xml_sax_parser_context_set_encoding(xmlParserCtxtPtr c_context, VALUE rb_encoding); + +#define DOC_RUBY_OBJECT_TEST(x) ((nokogiriTuplePtr)(x->_private)) +#define DOC_RUBY_OBJECT(x) (((nokogiriTuplePtr)(x->_private))->doc) +#define DOC_UNLINKED_NODE_HASH(x) (((nokogiriTuplePtr)(x->_private))->unlinkedNodes) +#define DOC_NODE_CACHE(x) (((nokogiriTuplePtr)(x->_private))->node_cache) +#define NOKOGIRI_NAMESPACE_EH(node) ((node)->type == XML_NAMESPACE_DECL) + +#define DISCARD_CONST_QUAL(t, v) ((t)(uintptr_t)(v)) +#define DISCARD_CONST_QUAL_XMLCHAR(v) DISCARD_CONST_QUAL(xmlChar *, v) + +#if HAVE_RB_CATEGORY_WARNING +# define NOKO_WARN_DEPRECATION(message...) rb_category_warning(RB_WARN_CATEGORY_DEPRECATED, message) +#else +# define NOKO_WARN_DEPRECATION(message...) rb_warning(message) +#endif + +void noko__structured_error_func_save(libxmlStructuredErrorHandlerState *handler_state); +void noko__structured_error_func_save_and_set(libxmlStructuredErrorHandlerState *handler_state, void *user_data, + xmlStructuredErrorFunc handler); +void noko__structured_error_func_restore(libxmlStructuredErrorHandlerState *handler_state); +VALUE noko_xml_syntax_error__wrap(xmlErrorConstPtr error); +void noko__error_array_pusher(void *ctx, xmlErrorConstPtr error); +NORETURN_DECL void noko__error_raise(void *ctx, xmlErrorConstPtr error); +void Nokogiri_marshal_xpath_funcall_and_return_values(xmlXPathParserContextPtr ctx, int nargs, VALUE handler, + const char *function_name) ; + +#endif /* NOKOGIRI_NATIVE */ diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/test_global_handlers.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/test_global_handlers.c new file mode 100644 index 00000000..cec0915f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/test_global_handlers.c @@ -0,0 +1,40 @@ +#include + +static VALUE foreign_error_handler_block = Qnil; + +static void +foreign_error_handler(void *user_data, xmlErrorConstPtr c_error) +{ + rb_funcall(foreign_error_handler_block, rb_intern("call"), 0); +} + +/* + * call-seq: + * __foreign_error_handler { ... } -> nil + * + * Override libxml2's global error handlers to call the block. This method thus has very little + * value except to test that Nokogiri is properly setting error handlers elsewhere in the code. See + * test/helper.rb for how this is being used. + */ +static VALUE +rb_foreign_error_handler(VALUE klass) +{ + rb_need_block(); + foreign_error_handler_block = rb_block_proc(); + xmlSetStructuredErrorFunc(NULL, foreign_error_handler); + return Qnil; +} + +/* + * Document-module: Nokogiri::Test + * + * The Nokogiri::Test module should only be used for testing Nokogiri. + * Do NOT use this outside of the Nokogiri test suite. + */ +void +noko_init_test_global_handlers(void) +{ + VALUE mNokogiriTest = rb_define_module_under(mNokogiri, "Test"); + + rb_define_singleton_method(mNokogiriTest, "__foreign_error_handler", rb_foreign_error_handler, 0); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_attr.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_attr.c new file mode 100644 index 00000000..90eea3c7 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_attr.c @@ -0,0 +1,103 @@ +#include + +VALUE cNokogiriXmlAttr; + +/* + * call-seq: + * value=(content) + * + * Set the value for this Attr to +content+. Use +nil+ to remove the value + * (e.g., a HTML boolean attribute). + */ +static VALUE +set_value(VALUE self, VALUE content) +{ + xmlAttrPtr attr; + xmlChar *value; + xmlNode *cur; + + Noko_Node_Get_Struct(self, xmlAttr, attr); + + if (attr->children) { + xmlFreeNodeList(attr->children); + } + attr->children = attr->last = NULL; + + if (content == Qnil) { + return content; + } + + value = xmlEncodeEntitiesReentrant(attr->doc, (unsigned char *)StringValueCStr(content)); + if (xmlStrlen(value) == 0) { + attr->children = xmlNewDocText(attr->doc, value); + } else { + attr->children = xmlStringGetNodeList(attr->doc, value); + } + xmlFree(value); + + for (cur = attr->children; cur; cur = cur->next) { + cur->parent = (xmlNode *)attr; + cur->doc = attr->doc; + if (cur->next == NULL) { + attr->last = cur; + } + } + + return content; +} + +/* + * call-seq: + * new(document, name) + * + * Create a new Attr element on the +document+ with +name+ + */ +static VALUE +new (int argc, VALUE *argv, VALUE klass) +{ + xmlDocPtr xml_doc; + VALUE document; + VALUE name; + VALUE rest; + xmlAttrPtr node; + VALUE rb_node; + + rb_scan_args(argc, argv, "2*", &document, &name, &rest); + + if (! rb_obj_is_kind_of(document, cNokogiriXmlDocument)) { + rb_raise(rb_eArgError, "parameter must be a Nokogiri::XML::Document"); + } + + xml_doc = noko_xml_document_unwrap(document); + + node = xmlNewDocProp( + xml_doc, + (const xmlChar *)StringValueCStr(name), + NULL + ); + + noko_xml_document_pin_node((xmlNodePtr)node); + + rb_node = noko_xml_node_wrap(klass, (xmlNodePtr)node); + rb_obj_call_init(rb_node, argc, argv); + + if (rb_block_given_p()) { + rb_yield(rb_node); + } + + return rb_node; +} + +void +noko_init_xml_attr(void) +{ + assert(cNokogiriXmlNode); + /* + * Attr represents a Attr node in an xml document. + */ + cNokogiriXmlAttr = rb_define_class_under(mNokogiriXml, "Attr", cNokogiriXmlNode); + + rb_define_singleton_method(cNokogiriXmlAttr, "new", new, -1); + + rb_define_method(cNokogiriXmlAttr, "value=", set_value, 1); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_attribute_decl.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_attribute_decl.c new file mode 100644 index 00000000..3f9bebc8 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_attribute_decl.c @@ -0,0 +1,70 @@ +#include + +VALUE cNokogiriXmlAttributeDecl; + +/* + * call-seq: + * attribute_type + * + * The attribute_type for this AttributeDecl + */ +static VALUE +attribute_type(VALUE self) +{ + xmlAttributePtr node; + Noko_Node_Get_Struct(self, xmlAttribute, node); + return INT2NUM(node->atype); +} + +/* + * call-seq: + * default + * + * The default value + */ +static VALUE +default_value(VALUE self) +{ + xmlAttributePtr node; + Noko_Node_Get_Struct(self, xmlAttribute, node); + + if (node->defaultValue) { return NOKOGIRI_STR_NEW2(node->defaultValue); } + return Qnil; +} + +/* + * call-seq: + * enumeration + * + * An enumeration of possible values + */ +static VALUE +enumeration(VALUE self) +{ + xmlAttributePtr node; + xmlEnumerationPtr enm; + VALUE list; + + Noko_Node_Get_Struct(self, xmlAttribute, node); + + list = rb_ary_new(); + enm = node->tree; + + while (enm) { + rb_ary_push(list, NOKOGIRI_STR_NEW2(enm->name)); + enm = enm->next; + } + + return list; +} + +void +noko_init_xml_attribute_decl(void) +{ + assert(cNokogiriXmlNode); + cNokogiriXmlAttributeDecl = rb_define_class_under(mNokogiriXml, "AttributeDecl", cNokogiriXmlNode); + + rb_define_method(cNokogiriXmlAttributeDecl, "attribute_type", attribute_type, 0); + rb_define_method(cNokogiriXmlAttributeDecl, "default", default_value, 0); + rb_define_method(cNokogiriXmlAttributeDecl, "enumeration", enumeration, 0); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_cdata.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_cdata.c new file mode 100644 index 00000000..4431d200 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_cdata.c @@ -0,0 +1,62 @@ +#include + +VALUE cNokogiriXmlCData; + +/* + * call-seq: + * new(document, content) + * + * Create a new CDATA element on the +document+ with +content+ + * + * If +content+ cannot be implicitly converted to a string, this method will + * raise a TypeError exception. + */ +static VALUE +rb_xml_cdata_s_new(int argc, VALUE *argv, VALUE klass) +{ + xmlDocPtr c_document; + xmlNodePtr c_node; + VALUE rb_document; + VALUE rb_content; + VALUE rb_rest; + VALUE rb_node; + + rb_scan_args(argc, argv, "2*", &rb_document, &rb_content, &rb_rest); + + Check_Type(rb_content, T_STRING); + if (!rb_obj_is_kind_of(rb_document, cNokogiriXmlNode)) { + rb_raise(rb_eTypeError, + "expected first parameter to be a Nokogiri::XML::Document, received %"PRIsVALUE, + rb_obj_class(rb_document)); + } + + if (!rb_obj_is_kind_of(rb_document, cNokogiriXmlDocument)) { + xmlNodePtr deprecated_node_type_arg; + NOKO_WARN_DEPRECATION("Passing a Node as the first parameter to CDATA.new is deprecated. Please pass a Document instead. This will become an error in Nokogiri v1.17.0."); // TODO: deprecated in v1.15.3, remove in v1.17.0 + Noko_Node_Get_Struct(rb_document, xmlNode, deprecated_node_type_arg); + c_document = deprecated_node_type_arg->doc; + } else { + c_document = noko_xml_document_unwrap(rb_document); + } + + c_node = xmlNewCDataBlock(c_document, (xmlChar *)StringValueCStr(rb_content), RSTRING_LENINT(rb_content)); + noko_xml_document_pin_node(c_node); + rb_node = noko_xml_node_wrap(klass, c_node); + rb_obj_call_init(rb_node, argc, argv); + + if (rb_block_given_p()) { rb_yield(rb_node); } + + return rb_node; +} + +void +noko_init_xml_cdata(void) +{ + assert(cNokogiriXmlText); + /* + * CData represents a CData node in an xml document. + */ + cNokogiriXmlCData = rb_define_class_under(mNokogiriXml, "CDATA", cNokogiriXmlText); + + rb_define_singleton_method(cNokogiriXmlCData, "new", rb_xml_cdata_s_new, -1); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_comment.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_comment.c new file mode 100644 index 00000000..211761c9 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_comment.c @@ -0,0 +1,57 @@ +#include + +VALUE cNokogiriXmlComment; + +static ID document_id ; + +/* + * call-seq: + * new(document_or_node, content) + * + * Create a new Comment element on the +document+ with +content+. + * Alternatively, if a +node+ is passed, the +node+'s document is used. + */ +static VALUE +new (int argc, VALUE *argv, VALUE klass) +{ + xmlDocPtr xml_doc; + xmlNodePtr node; + VALUE document; + VALUE content; + VALUE rest; + VALUE rb_node; + + rb_scan_args(argc, argv, "2*", &document, &content, &rest); + + Check_Type(content, T_STRING); + if (rb_obj_is_kind_of(document, cNokogiriXmlNode)) { + document = rb_funcall(document, document_id, 0); + } else if (!rb_obj_is_kind_of(document, cNokogiriXmlDocument) + && !rb_obj_is_kind_of(document, cNokogiriXmlDocumentFragment)) { + rb_raise(rb_eArgError, "first argument must be a XML::Document or XML::Node"); + } + xml_doc = noko_xml_document_unwrap(document); + + node = xmlNewDocComment(xml_doc, (const xmlChar *)StringValueCStr(content)); + noko_xml_document_pin_node(node); + rb_node = noko_xml_node_wrap(klass, node); + rb_obj_call_init(rb_node, argc, argv); + + if (rb_block_given_p()) { rb_yield(rb_node); } + + return rb_node; +} + +void +noko_init_xml_comment(void) +{ + assert(cNokogiriXmlCharacterData); + /* + * Comment represents a comment node in an xml document. + */ + cNokogiriXmlComment = rb_define_class_under(mNokogiriXml, "Comment", cNokogiriXmlCharacterData); + + rb_define_singleton_method(cNokogiriXmlComment, "new", new, -1); + + document_id = rb_intern("document"); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_document.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_document.c new file mode 100644 index 00000000..74081930 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_document.c @@ -0,0 +1,784 @@ +#include + +VALUE cNokogiriXmlDocument ; + +static int +dealloc_node_i2(xmlNodePtr key, xmlNodePtr node, xmlDocPtr doc) +{ + switch (node->type) { + case XML_ATTRIBUTE_NODE: + xmlFreePropList((xmlAttrPtr)node); + break; + case XML_NAMESPACE_DECL: + xmlFreeNs((xmlNsPtr)node); + break; + case XML_DTD_NODE: + xmlFreeDtd((xmlDtdPtr)node); + break; + default: + if (node->parent == NULL) { + node->next = NULL; + node->prev = NULL; + xmlAddChild((xmlNodePtr)doc, node); + } + } + return ST_CONTINUE; +} + +static int +dealloc_node_i(st_data_t key, st_data_t node, st_data_t doc) +{ + return dealloc_node_i2((xmlNodePtr)key, (xmlNodePtr)node, (xmlDocPtr)doc); +} + +static void +remove_private(xmlNodePtr node) +{ + xmlNodePtr child; + + for (child = node->children; child; child = child->next) { + remove_private(child); + } + + if ((node->type == XML_ELEMENT_NODE || + node->type == XML_XINCLUDE_START || + node->type == XML_XINCLUDE_END) && + node->properties) { + for (child = (xmlNodePtr)node->properties; child; child = child->next) { + remove_private(child); + } + } + + node->_private = NULL; +} + +static void +mark(void *data) +{ + xmlDocPtr doc = (xmlDocPtr)data; + nokogiriTuplePtr tuple = (nokogiriTuplePtr)doc->_private; + if (tuple) { + rb_gc_mark(tuple->doc); + rb_gc_mark(tuple->node_cache); + } +} + +static void +dealloc(void *data) +{ + xmlDocPtr doc = (xmlDocPtr)data; + st_table *node_hash; + + node_hash = DOC_UNLINKED_NODE_HASH(doc); + + st_foreach(node_hash, dealloc_node_i, (st_data_t)doc); + st_free_table(node_hash); + + ruby_xfree(doc->_private); + +#if defined(__GNUC__) && __GNUC__ >= 5 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" // xmlDeregisterNodeDefault is deprecated as of libxml2 2.11.0 +#endif + /* + * libxml-ruby < 3.0.0 uses xmlDeregisterNodeDefault. If the user is using one of those older + * versions, the registered callback from libxml-ruby will access the _private pointers set by + * nokogiri, which will result in segfaults. + * + * To avoid this, we need to clear the _private pointers from all nodes in this document tree + * before that callback gets invoked. + * + * libxml-ruby 3.0.0 was released in 2017-02, so at some point we can probably safely remove this + * safeguard (though probably pairing with a runtime check on the libxml-ruby version). + */ + if (xmlDeregisterNodeDefaultValue) { + remove_private((xmlNodePtr)doc); + } +#if defined(__GNUC__) && __GNUC__ >= 5 +#pragma GCC diagnostic pop +#endif + + xmlFreeDoc(doc); +} + +static size_t +memsize_node(const xmlNodePtr node) +{ + /* note we don't count namespace definitions, just going for a good-enough number here */ + xmlNodePtr child; + xmlAttrPtr property; + size_t memsize = 0; + + memsize += (size_t)xmlStrlen(node->name); + + if (node->type == XML_ELEMENT_NODE) { + for (property = node->properties; property; property = property->next) { + memsize += sizeof(xmlAttr) + memsize_node((xmlNodePtr)property); + } + } + if (node->type == XML_TEXT_NODE) { + memsize += (size_t)xmlStrlen(node->content); + } + for (child = node->children; child; child = child->next) { + memsize += sizeof(xmlNode) + memsize_node(child); + } + return memsize; +} + +static size_t +memsize(const void *data) +{ + xmlDocPtr doc = (const xmlDocPtr)data; + size_t memsize = sizeof(xmlDoc); + /* This may not account for all memory use */ + memsize += memsize_node((xmlNodePtr)doc); + return memsize; +} + +static const rb_data_type_t xml_doc_type = { + .wrap_struct_name = "xmlDoc", + .function = { + .dmark = mark, + .dfree = dealloc, + .dsize = memsize, + }, + // .flags = RUBY_TYPED_FREE_IMMEDIATELY, // TODO see https://github.com/sparklemotion/nokogiri/issues/2822 +}; + +static VALUE +_xml_document_alloc(VALUE klass) +{ + return TypedData_Wrap_Struct(klass, &xml_doc_type, NULL); +} + +static void +_xml_document_data_ptr_set(VALUE rb_document, xmlDocPtr c_document) +{ + nokogiriTuplePtr tuple; + + assert(DATA_PTR(rb_document) == NULL); + assert(c_document->_private == NULL); + + DATA_PTR(rb_document) = c_document; + + tuple = (nokogiriTuplePtr)ruby_xmalloc(sizeof(nokogiriTuple)); + tuple->doc = rb_document; + tuple->unlinkedNodes = st_init_numtable_with_size(128); + tuple->node_cache = rb_ary_new(); + + c_document->_private = tuple ; + + rb_iv_set(rb_document, "@node_cache", tuple->node_cache); + + return; +} + +/* :nodoc: */ +static VALUE +rb_xml_document_initialize_copy_with_args(VALUE rb_self, VALUE rb_other, VALUE rb_level) +{ + xmlDocPtr c_other, c_self; + int c_level; + + c_other = noko_xml_document_unwrap(rb_other); + c_level = (int)NUM2INT(rb_level); + + c_self = xmlCopyDoc(c_other, c_level); + if (c_self == NULL) { return Qnil; } + + c_self->type = c_other->type; + _xml_document_data_ptr_set(rb_self, c_self); + + return rb_self ; +} + +static void +recursively_remove_namespaces_from_node(xmlNodePtr node) +{ + xmlNodePtr child ; + xmlAttrPtr property ; + + xmlSetNs(node, NULL); + + for (child = node->children ; child ; child = child->next) { + recursively_remove_namespaces_from_node(child); + } + + if (((node->type == XML_ELEMENT_NODE) || + (node->type == XML_XINCLUDE_START) || + (node->type == XML_XINCLUDE_END)) && + node->nsDef) { + xmlNsPtr curr = node->nsDef; + while (curr) { + noko_xml_document_pin_namespace(curr, node->doc); + curr = curr->next; + } + node->nsDef = NULL; + } + + if (node->type == XML_ELEMENT_NODE && node->properties != NULL) { + property = node->properties ; + while (property != NULL) { + if (property->ns) { property->ns = NULL ; } + property = property->next ; + } + } +} + +/* + * call-seq: + * url + * + * Get the url name for this document. + */ +static VALUE +url(VALUE self) +{ + xmlDocPtr doc = noko_xml_document_unwrap(self); + + if (doc->URL) { return NOKOGIRI_STR_NEW2(doc->URL); } + + return Qnil; +} + +/* + * call-seq: + * root= + * + * Set the root element on this document + */ +static VALUE +rb_xml_document_root_set(VALUE self, VALUE rb_new_root) +{ + xmlDocPtr c_document; + xmlNodePtr c_new_root = NULL, c_current_root; + + c_document = noko_xml_document_unwrap(self); + + c_current_root = xmlDocGetRootElement(c_document); + if (c_current_root) { + xmlUnlinkNode(c_current_root); + noko_xml_document_pin_node(c_current_root); + } + + if (!NIL_P(rb_new_root)) { + if (!rb_obj_is_kind_of(rb_new_root, cNokogiriXmlNode)) { + rb_raise(rb_eArgError, + "expected Nokogiri::XML::Node but received %"PRIsVALUE, + rb_obj_class(rb_new_root)); + } + + Noko_Node_Get_Struct(rb_new_root, xmlNode, c_new_root); + + /* If the new root's document is not the same as the current document, + * then we need to dup the node in to this document. */ + if (c_new_root->doc != c_document) { + c_new_root = xmlDocCopyNode(c_new_root, c_document, 1); + if (!c_new_root) { + rb_raise(rb_eRuntimeError, "Could not reparent node (xmlDocCopyNode)"); + } + } + } + + xmlDocSetRootElement(c_document, c_new_root); + + return rb_new_root; +} + +/* + * call-seq: + * root + * + * Get the root node for this document. + */ +static VALUE +rb_xml_document_root(VALUE self) +{ + xmlDocPtr c_document; + xmlNodePtr c_root; + + c_document = noko_xml_document_unwrap(self); + + c_root = xmlDocGetRootElement(c_document); + if (!c_root) { + return Qnil; + } + + return noko_xml_node_wrap(Qnil, c_root) ; +} + +/* + * call-seq: + * encoding= encoding + * + * Set the encoding string for this Document + */ +static VALUE +set_encoding(VALUE self, VALUE encoding) +{ + xmlDocPtr doc = noko_xml_document_unwrap(self); + + if (doc->encoding) { + xmlFree(DISCARD_CONST_QUAL_XMLCHAR(doc->encoding)); + } + + doc->encoding = xmlStrdup((xmlChar *)StringValueCStr(encoding)); + + return encoding; +} + +/* + * call-seq: + * encoding + * + * Get the encoding for this Document + */ +static VALUE +encoding(VALUE self) +{ + xmlDocPtr doc = noko_xml_document_unwrap(self); + + if (!doc->encoding) { return Qnil; } + return NOKOGIRI_STR_NEW2(doc->encoding); +} + +/* + * call-seq: + * version + * + * Get the XML version for this Document + */ +static VALUE +version(VALUE self) +{ + xmlDocPtr doc = noko_xml_document_unwrap(self); + + if (!doc->version) { return Qnil; } + return NOKOGIRI_STR_NEW2(doc->version); +} + +/* + * call-seq: + * read_io(io, url, encoding, options) + * + * Create a new document from an IO object + */ +static VALUE +noko_xml_document_s_read_io(VALUE rb_class, + VALUE rb_io, + VALUE rb_url, + VALUE rb_encoding, + VALUE rb_options) +{ + /* TODO: deprecate this method, parse should be the preferred entry point. then we can make this + private. */ + libxmlStructuredErrorHandlerState handler_state; + VALUE rb_errors = rb_ary_new(); + + noko__structured_error_func_save_and_set(&handler_state, (void *)rb_errors, noko__error_array_pusher); + + const char *c_url = NIL_P(rb_url) ? NULL : StringValueCStr(rb_url); + const char *c_enc = NIL_P(rb_encoding) ? NULL : StringValueCStr(rb_encoding); + xmlDocPtr c_document = xmlReadIO( + (xmlInputReadCallback)noko_io_read, + (xmlInputCloseCallback)noko_io_close, + (void *)rb_io, + c_url, + c_enc, + (int)NUM2INT(rb_options) + ); + + noko__structured_error_func_restore(&handler_state); + + if (c_document == NULL) { + xmlFreeDoc(c_document); + + VALUE exception = rb_funcall(cNokogiriXmlSyntaxError, rb_intern("aggregate"), 1, rb_errors); + if (RB_TEST(exception)) { + rb_exc_raise(exception); + } else { + rb_raise(rb_eRuntimeError, "Could not parse document"); + } + } + + VALUE rb_document = noko_xml_document_wrap(rb_class, c_document); + rb_iv_set(rb_document, "@errors", rb_errors); + return rb_document; +} + +/* + * call-seq: + * read_memory(string, url, encoding, options) + * + * Create a new document from a String + */ +static VALUE +noko_xml_document_s_read_memory(VALUE rb_class, + VALUE rb_input, + VALUE rb_url, + VALUE rb_encoding, + VALUE rb_options) +{ + /* TODO: deprecate this method, parse should be the preferred entry point. then we can make this + private. */ + VALUE rb_errors = rb_ary_new(); + xmlSetStructuredErrorFunc((void *)rb_errors, noko__error_array_pusher); + + const char *c_buffer = StringValuePtr(rb_input); + const char *c_url = NIL_P(rb_url) ? NULL : StringValueCStr(rb_url); + const char *c_enc = NIL_P(rb_encoding) ? NULL : StringValueCStr(rb_encoding); + int c_buffer_len = (int)RSTRING_LEN(rb_input); + xmlDocPtr c_document = xmlReadMemory(c_buffer, c_buffer_len, c_url, c_enc, (int)NUM2INT(rb_options)); + + xmlSetStructuredErrorFunc(NULL, NULL); + + if (c_document == NULL) { + VALUE exception = rb_funcall(cNokogiriXmlSyntaxError, rb_intern("aggregate"), 1, rb_errors); + if (RB_TEST(exception)) { + rb_exc_raise(exception); + } else { + rb_raise(rb_eRuntimeError, "Could not parse document"); + } + } + + VALUE document = noko_xml_document_wrap(rb_class, c_document); + rb_iv_set(document, "@errors", rb_errors); + return document; +} + +/* + * call-seq: + * new(version = "1.0") + * + * Create a new empty document declaring XML version +version+. + */ +static VALUE +new (int argc, VALUE *argv, VALUE klass) +{ + xmlDocPtr doc; + VALUE version, rest, rb_doc ; + + rb_scan_args(argc, argv, "0*", &rest); + version = rb_ary_entry(rest, (long)0); + if (NIL_P(version)) { version = rb_str_new2("1.0"); } + + doc = xmlNewDoc((xmlChar *)StringValueCStr(version)); + rb_doc = noko_xml_document_wrap_with_init_args(klass, doc, argc, argv); + return rb_doc ; +} + +/* + * call-seq: + * remove_namespaces! + * + * Remove all namespaces from all nodes in the document. + * + * This could be useful for developers who either don't understand namespaces + * or don't care about them. + * + * The following example shows a use case, and you can decide for yourself + * whether this is a good thing or not: + * + * doc = Nokogiri::XML <<-EOXML + * + * + * Michelin Model XGV + * + * + * I'm a bicycle tire! + * + * + * EOXML + * + * doc.xpath("//tire").to_s # => "" + * doc.xpath("//part:tire", "part" => "http://general-motors.com/").to_s # => "Michelin Model XGV" + * doc.xpath("//part:tire", "part" => "http://schwinn.com/").to_s # => "I'm a bicycle tire!" + * + * doc.remove_namespaces! + * + * doc.xpath("//tire").to_s # => "Michelin Model XGVI'm a bicycle tire!" + * doc.xpath("//part:tire", "part" => "http://general-motors.com/").to_s # => "" + * doc.xpath("//part:tire", "part" => "http://schwinn.com/").to_s # => "" + * + * For more information on why this probably is *not* a good thing in general, + * please direct your browser to + * http://tenderlovemaking.com/2009/04/23/namespaces-in-xml.html + */ +static VALUE +remove_namespaces_bang(VALUE self) +{ + xmlDocPtr doc = noko_xml_document_unwrap(self); + + recursively_remove_namespaces_from_node((xmlNodePtr)doc); + return self; +} + +/* call-seq: + * doc.create_entity(name, type, external_id, system_id, content) + * + * Create a new entity named +name+. + * + * +type+ is an integer representing the type of entity to be created, and it defaults to + * +Nokogiri::XML::EntityDecl::INTERNAL_GENERAL+. See the constants on Nokogiri::XML::EntityDecl for + * more information. + * + * +external_id+, +system_id+, and +content+ set the External ID, System ID, + * and content respectively. All of these parameters are optional. + */ +static VALUE +noko_xml_document__create_entity(int argc, VALUE *argv, VALUE rb_document) +{ + VALUE rb_name; + VALUE rb_type; + VALUE rb_ext_id; + VALUE rb_sys_id; + VALUE rb_content; + + rb_scan_args(argc, argv, "14", + &rb_name, + &rb_type, &rb_ext_id, &rb_sys_id, &rb_content); + + xmlDocPtr c_document = noko_xml_document_unwrap(rb_document); + + libxmlStructuredErrorHandlerState handler_state; + VALUE rb_errors = rb_ary_new(); + noko__structured_error_func_save_and_set(&handler_state, (void *)rb_errors, noko__error_array_pusher); + + xmlEntityPtr c_entity = xmlAddDocEntity( + c_document, + (xmlChar *)(NIL_P(rb_name) ? NULL : StringValueCStr(rb_name)), + (int)(NIL_P(rb_type) ? XML_INTERNAL_GENERAL_ENTITY : NUM2INT(rb_type)), + (xmlChar *)(NIL_P(rb_ext_id) ? NULL : StringValueCStr(rb_ext_id)), + (xmlChar *)(NIL_P(rb_sys_id) ? NULL : StringValueCStr(rb_sys_id)), + (xmlChar *)(NIL_P(rb_content) ? NULL : StringValueCStr(rb_content)) + ); + + noko__structured_error_func_restore(&handler_state); + + if (NULL == c_entity) { + VALUE exception = rb_funcall(cNokogiriXmlSyntaxError, rb_intern("aggregate"), 1, rb_errors); + if (RB_TEST(exception)) { + rb_exc_raise(exception); + } else { + rb_raise(rb_eRuntimeError, "Could not create entity"); + } + } + + return noko_xml_node_wrap(cNokogiriXmlEntityDecl, (xmlNodePtr)c_entity); +} + +static int +block_caller(void *ctx, xmlNodePtr c_node, xmlNodePtr c_parent_node) +{ + VALUE block = (VALUE)ctx; + VALUE rb_node; + VALUE rb_parent_node; + VALUE ret; + + if (c_node->type == XML_NAMESPACE_DECL) { + rb_node = noko_xml_namespace_wrap((xmlNsPtr)c_node, c_parent_node->doc); + } else { + rb_node = noko_xml_node_wrap(Qnil, c_node); + } + rb_parent_node = c_parent_node ? noko_xml_node_wrap(Qnil, c_parent_node) : Qnil; + + ret = rb_funcall(block, rb_intern("call"), 2, rb_node, rb_parent_node); + + return (Qfalse == ret || Qnil == ret) ? 0 : 1; +} + +/* call-seq: + * doc.canonicalize(mode=XML_C14N_1_0,inclusive_namespaces=nil,with_comments=false) + * doc.canonicalize { |obj, parent| ... } + * + * Canonicalize a document and return the results. Takes an optional block + * that takes two parameters: the +obj+ and that node's +parent+. + * The +obj+ will be either a Nokogiri::XML::Node, or a Nokogiri::XML::Namespace + * The block must return a non-nil, non-false value if the +obj+ passed in + * should be included in the canonicalized document. + */ +static VALUE +rb_xml_document_canonicalize(int argc, VALUE *argv, VALUE self) +{ + VALUE rb_mode; + VALUE rb_namespaces; + VALUE rb_comments_p; + int c_mode = 0; + xmlChar **c_namespaces; + + xmlDocPtr c_doc; + xmlOutputBufferPtr c_obuf; + xmlC14NIsVisibleCallback c_callback_wrapper = NULL; + void *rb_callback = NULL; + + VALUE rb_cStringIO; + VALUE rb_io; + + rb_scan_args(argc, argv, "03", &rb_mode, &rb_namespaces, &rb_comments_p); + if (!NIL_P(rb_mode)) { + Check_Type(rb_mode, T_FIXNUM); + c_mode = NUM2INT(rb_mode); + } + if (!NIL_P(rb_namespaces)) { + Check_Type(rb_namespaces, T_ARRAY); + if (c_mode == XML_C14N_1_0 || c_mode == XML_C14N_1_1) { + rb_raise(rb_eRuntimeError, "This canonicalizer does not support this operation"); + } + } + + c_doc = noko_xml_document_unwrap(self); + + rb_cStringIO = rb_const_get_at(rb_cObject, rb_intern("StringIO")); + rb_io = rb_class_new_instance(0, 0, rb_cStringIO); + c_obuf = xmlAllocOutputBuffer(NULL); + + c_obuf->writecallback = (xmlOutputWriteCallback)noko_io_write; + c_obuf->closecallback = (xmlOutputCloseCallback)noko_io_close; + c_obuf->context = (void *)rb_io; + + if (rb_block_given_p()) { + c_callback_wrapper = block_caller; + rb_callback = (void *)rb_block_proc(); + } + + if (NIL_P(rb_namespaces)) { + c_namespaces = NULL; + } else { + long ns_len = RARRAY_LEN(rb_namespaces); + c_namespaces = ruby_xcalloc((size_t)ns_len + 1, sizeof(xmlChar *)); + for (int j = 0 ; j < ns_len ; j++) { + VALUE entry = rb_ary_entry(rb_namespaces, j); + c_namespaces[j] = (xmlChar *)StringValueCStr(entry); + } + } + + xmlC14NExecute(c_doc, c_callback_wrapper, rb_callback, + c_mode, + c_namespaces, + (int)RTEST(rb_comments_p), + c_obuf); + + ruby_xfree(c_namespaces); + xmlOutputBufferClose(c_obuf); + + return rb_funcall(rb_io, rb_intern("string"), 0); +} + +VALUE +noko_xml_document_wrap_with_init_args(VALUE klass, xmlDocPtr c_document, int argc, VALUE *argv) +{ + VALUE rb_document; + + if (!klass) { + klass = cNokogiriXmlDocument; + } + + rb_document = _xml_document_alloc(klass); + _xml_document_data_ptr_set(rb_document, c_document); + + rb_iv_set(rb_document, "@decorators", Qnil); + rb_iv_set(rb_document, "@errors", Qnil); + + rb_obj_call_init(rb_document, argc, argv); + + return rb_document ; +} + + +/* deprecated. use noko_xml_document_wrap() instead. */ +VALUE +Nokogiri_wrap_xml_document(VALUE klass, xmlDocPtr doc) +{ + /* TODO: deprecate this method in v2.0 */ + return noko_xml_document_wrap_with_init_args(klass, doc, 0, NULL); +} + +VALUE +noko_xml_document_wrap(VALUE klass, xmlDocPtr doc) +{ + return noko_xml_document_wrap_with_init_args(klass, doc, 0, NULL); +} + +xmlDocPtr +noko_xml_document_unwrap(VALUE rb_document) +{ + xmlDocPtr c_document; + TypedData_Get_Struct(rb_document, xmlDoc, &xml_doc_type, c_document); + return c_document; +} + +/* Schema creation will remove and deallocate "blank" nodes. + * If those blank nodes have been exposed to Ruby, they could get freed + * out from under the VALUE pointer. This function checks to see if any of + * those nodes have been exposed to Ruby, and if so we should raise an exception. + */ +int +noko_xml_document_has_wrapped_blank_nodes_p(xmlDocPtr c_document) +{ + VALUE cache = DOC_NODE_CACHE(c_document); + + if (NIL_P(cache)) { + return 0; + } + + for (long jnode = 0; jnode < RARRAY_LEN(cache); jnode++) { + xmlNodePtr node; + VALUE element = rb_ary_entry(cache, jnode); + + Noko_Node_Get_Struct(element, xmlNode, node); + if (xmlIsBlankNode(node)) { + return 1; + } + } + + return 0; +} + +void +noko_xml_document_pin_node(xmlNodePtr node) +{ + xmlDocPtr doc; + nokogiriTuplePtr tuple; + + doc = node->doc; + tuple = (nokogiriTuplePtr)doc->_private; + st_insert(tuple->unlinkedNodes, (st_data_t)node, (st_data_t)node); +} + + +void +noko_xml_document_pin_namespace(xmlNsPtr ns, xmlDocPtr doc) +{ + nokogiriTuplePtr tuple; + + tuple = (nokogiriTuplePtr)doc->_private; + st_insert(tuple->unlinkedNodes, (st_data_t)ns, (st_data_t)ns); +} + + +void +noko_init_xml_document(void) +{ + assert(cNokogiriXmlNode); + + cNokogiriXmlDocument = rb_define_class_under(mNokogiriXml, "Document", cNokogiriXmlNode); + + rb_define_alloc_func(cNokogiriXmlDocument, _xml_document_alloc); + + rb_define_singleton_method(cNokogiriXmlDocument, "read_memory", noko_xml_document_s_read_memory, 4); + rb_define_singleton_method(cNokogiriXmlDocument, "read_io", noko_xml_document_s_read_io, 4); + rb_define_singleton_method(cNokogiriXmlDocument, "new", new, -1); + + rb_define_method(cNokogiriXmlDocument, "root", rb_xml_document_root, 0); + rb_define_method(cNokogiriXmlDocument, "root=", rb_xml_document_root_set, 1); + rb_define_method(cNokogiriXmlDocument, "encoding", encoding, 0); + rb_define_method(cNokogiriXmlDocument, "encoding=", set_encoding, 1); + rb_define_method(cNokogiriXmlDocument, "version", version, 0); + rb_define_method(cNokogiriXmlDocument, "canonicalize", rb_xml_document_canonicalize, -1); + rb_define_method(cNokogiriXmlDocument, "url", url, 0); + rb_define_method(cNokogiriXmlDocument, "create_entity", noko_xml_document__create_entity, -1); + rb_define_method(cNokogiriXmlDocument, "remove_namespaces!", remove_namespaces_bang, 0); + + rb_define_protected_method(cNokogiriXmlDocument, "initialize_copy_with_args", rb_xml_document_initialize_copy_with_args, + 2); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_document_fragment.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_document_fragment.c new file mode 100644 index 00000000..3f28d28d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_document_fragment.c @@ -0,0 +1,29 @@ +#include + +VALUE cNokogiriXmlDocumentFragment; + +/* :nodoc: */ +static VALUE +noko_xml_document_fragment_s_native_new(VALUE klass, VALUE rb_doc) +{ + xmlDocPtr c_doc; + xmlNodePtr c_node; + VALUE rb_node; + + c_doc = noko_xml_document_unwrap(rb_doc); + c_node = xmlNewDocFragment(c_doc->doc); + noko_xml_document_pin_node(c_node); + rb_node = noko_xml_node_wrap(klass, c_node); + + return rb_node; +} + +void +noko_init_xml_document_fragment(void) +{ + assert(cNokogiriXmlNode); + + cNokogiriXmlDocumentFragment = rb_define_class_under(mNokogiriXml, "DocumentFragment", cNokogiriXmlNode); + + rb_define_singleton_method(cNokogiriXmlDocumentFragment, "native_new", noko_xml_document_fragment_s_native_new, 1); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_dtd.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_dtd.c new file mode 100644 index 00000000..d3610205 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_dtd.c @@ -0,0 +1,208 @@ +#include + +VALUE cNokogiriXmlDtd; + +static void +notation_copier(void *c_notation_ptr, void *rb_hash_ptr, const xmlChar *name) +{ + VALUE rb_hash = (VALUE)rb_hash_ptr; + xmlNotationPtr c_notation = (xmlNotationPtr)c_notation_ptr; + VALUE rb_notation; + VALUE cNokogiriXmlNotation; + VALUE rb_constructor_args[3]; + + rb_constructor_args[0] = (c_notation->name ? NOKOGIRI_STR_NEW2(c_notation->name) : Qnil); + rb_constructor_args[1] = (c_notation->PublicID ? NOKOGIRI_STR_NEW2(c_notation->PublicID) : Qnil); + rb_constructor_args[2] = (c_notation->SystemID ? NOKOGIRI_STR_NEW2(c_notation->SystemID) : Qnil); + + cNokogiriXmlNotation = rb_const_get_at(mNokogiriXml, rb_intern("Notation")); + rb_notation = rb_class_new_instance(3, rb_constructor_args, cNokogiriXmlNotation); + + rb_hash_aset(rb_hash, NOKOGIRI_STR_NEW2(name), rb_notation); +} + +static void +element_copier(void *c_node_ptr, void *rb_hash_ptr, const xmlChar *c_name) +{ + VALUE rb_hash = (VALUE)rb_hash_ptr; + xmlNodePtr c_node = (xmlNodePtr)c_node_ptr; + + VALUE rb_node = noko_xml_node_wrap(Qnil, c_node); + + rb_hash_aset(rb_hash, NOKOGIRI_STR_NEW2(c_name), rb_node); +} + +/* + * call-seq: + * entities + * + * Get a hash of the elements for this DTD. + */ +static VALUE +entities(VALUE self) +{ + xmlDtdPtr dtd; + VALUE hash; + + Noko_Node_Get_Struct(self, xmlDtd, dtd); + + if (!dtd->entities) { return Qnil; } + + hash = rb_hash_new(); + + xmlHashScan((xmlHashTablePtr)dtd->entities, element_copier, (void *)hash); + + return hash; +} + +/* + * call-seq: + * notations() → Hash + * + * [Returns] All the notations for this DTD in a Hash of Notation +name+ to Notation. + */ +static VALUE +notations(VALUE self) +{ + xmlDtdPtr dtd; + VALUE hash; + + Noko_Node_Get_Struct(self, xmlDtd, dtd); + + if (!dtd->notations) { return Qnil; } + + hash = rb_hash_new(); + + xmlHashScan((xmlHashTablePtr)dtd->notations, notation_copier, (void *)hash); + + return hash; +} + +/* + * call-seq: + * attributes + * + * Get a hash of the attributes for this DTD. + */ +static VALUE +attributes(VALUE self) +{ + xmlDtdPtr dtd; + VALUE hash; + + Noko_Node_Get_Struct(self, xmlDtd, dtd); + + hash = rb_hash_new(); + + if (!dtd->attributes) { return hash; } + + xmlHashScan((xmlHashTablePtr)dtd->attributes, element_copier, (void *)hash); + + return hash; +} + +/* + * call-seq: + * elements + * + * Get a hash of the elements for this DTD. + */ +static VALUE +elements(VALUE self) +{ + xmlDtdPtr dtd; + VALUE hash; + + Noko_Node_Get_Struct(self, xmlDtd, dtd); + + if (!dtd->elements) { return Qnil; } + + hash = rb_hash_new(); + + xmlHashScan((xmlHashTablePtr)dtd->elements, element_copier, (void *)hash); + + return hash; +} + +/* + * call-seq: + * validate(document) + * + * Validate +document+ returning a list of errors + */ +static VALUE +validate(VALUE self, VALUE document) +{ + xmlDocPtr doc; + xmlDtdPtr dtd; + xmlValidCtxtPtr ctxt; + VALUE error_list; + + Noko_Node_Get_Struct(self, xmlDtd, dtd); + doc = noko_xml_document_unwrap(document); + error_list = rb_ary_new(); + + ctxt = xmlNewValidCtxt(); + + xmlSetStructuredErrorFunc((void *)error_list, noko__error_array_pusher); + + xmlValidateDtd(ctxt, doc, dtd); + + xmlSetStructuredErrorFunc(NULL, NULL); + + xmlFreeValidCtxt(ctxt); + + return error_list; +} + +/* + * call-seq: + * system_id + * + * Get the System ID for this DTD + */ +static VALUE +system_id(VALUE self) +{ + xmlDtdPtr dtd; + Noko_Node_Get_Struct(self, xmlDtd, dtd); + + if (!dtd->SystemID) { return Qnil; } + + return NOKOGIRI_STR_NEW2(dtd->SystemID); +} + +/* + * call-seq: + * external_id + * + * Get the External ID for this DTD + */ +static VALUE +external_id(VALUE self) +{ + xmlDtdPtr dtd; + Noko_Node_Get_Struct(self, xmlDtd, dtd); + + if (!dtd->ExternalID) { return Qnil; } + + return NOKOGIRI_STR_NEW2(dtd->ExternalID); +} + +void +noko_init_xml_dtd(void) +{ + assert(cNokogiriXmlNode); + /* + * Nokogiri::XML::DTD wraps DTD nodes in an XML document + */ + cNokogiriXmlDtd = rb_define_class_under(mNokogiriXml, "DTD", cNokogiriXmlNode); + + rb_define_method(cNokogiriXmlDtd, "notations", notations, 0); + rb_define_method(cNokogiriXmlDtd, "elements", elements, 0); + rb_define_method(cNokogiriXmlDtd, "entities", entities, 0); + rb_define_method(cNokogiriXmlDtd, "validate", validate, 1); + rb_define_method(cNokogiriXmlDtd, "attributes", attributes, 0); + rb_define_method(cNokogiriXmlDtd, "system_id", system_id, 0); + rb_define_method(cNokogiriXmlDtd, "external_id", external_id, 0); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_element_content.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_element_content.c new file mode 100644 index 00000000..b4fa884a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_element_content.c @@ -0,0 +1,131 @@ +#include + +VALUE cNokogiriXmlElementContent; + +static const rb_data_type_t xml_element_content_type = { + .wrap_struct_name = "xmlElementContent", + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; + +/* + * call-seq: + * name → String + * + * [Returns] The content element's +name+ + */ +static VALUE +get_name(VALUE self) +{ + xmlElementContentPtr elem; + TypedData_Get_Struct(self, xmlElementContent, &xml_element_content_type, elem); + + if (!elem->name) { return Qnil; } + return NOKOGIRI_STR_NEW2(elem->name); +} + +/* + * call-seq: + * type → Integer + * + * [Returns] The content element's +type+. Possible values are +PCDATA+, +ELEMENT+, +SEQ+, or +OR+. + */ +static VALUE +get_type(VALUE self) +{ + xmlElementContentPtr elem; + TypedData_Get_Struct(self, xmlElementContent, &xml_element_content_type, elem); + + return INT2NUM(elem->type); +} + +/* + * Get the first child. + */ +static VALUE +get_c1(VALUE self) +{ + xmlElementContentPtr elem; + TypedData_Get_Struct(self, xmlElementContent, &xml_element_content_type, elem); + + if (!elem->c1) { return Qnil; } + return noko_xml_element_content_wrap(rb_iv_get(self, "@document"), elem->c1); +} + +/* + * Get the second child. + */ +static VALUE +get_c2(VALUE self) +{ + xmlElementContentPtr elem; + TypedData_Get_Struct(self, xmlElementContent, &xml_element_content_type, elem); + + if (!elem->c2) { return Qnil; } + return noko_xml_element_content_wrap(rb_iv_get(self, "@document"), elem->c2); +} + +/* + * call-seq: + * occur → Integer + * + * [Returns] The content element's +occur+ flag. Possible values are +ONCE+, +OPT+, +MULT+ or +PLUS+. + */ +static VALUE +get_occur(VALUE self) +{ + xmlElementContentPtr elem; + TypedData_Get_Struct(self, xmlElementContent, &xml_element_content_type, elem); + + return INT2NUM(elem->ocur); +} + +/* + * call-seq: + * prefix → String + * + * [Returns] The content element's namespace +prefix+. + */ +static VALUE +get_prefix(VALUE self) +{ + xmlElementContentPtr elem; + TypedData_Get_Struct(self, xmlElementContent, &xml_element_content_type, elem); + + if (!elem->prefix) { return Qnil; } + + return NOKOGIRI_STR_NEW2(elem->prefix); +} + +/* + * create a Nokogiri::XML::ElementContent object around an +element+. + */ +VALUE +noko_xml_element_content_wrap(VALUE rb_document, xmlElementContentPtr c_element_content) +{ + VALUE elem = TypedData_Wrap_Struct( + cNokogiriXmlElementContent, + &xml_element_content_type, + c_element_content + ); + + /* keep a handle on the document for GC marking */ + rb_iv_set(elem, "@document", rb_document); + + return elem; +} + +void +noko_init_xml_element_content(void) +{ + cNokogiriXmlElementContent = rb_define_class_under(mNokogiriXml, "ElementContent", rb_cObject); + + rb_undef_alloc_func(cNokogiriXmlElementContent); + + rb_define_method(cNokogiriXmlElementContent, "name", get_name, 0); + rb_define_method(cNokogiriXmlElementContent, "type", get_type, 0); + rb_define_method(cNokogiriXmlElementContent, "occur", get_occur, 0); + rb_define_method(cNokogiriXmlElementContent, "prefix", get_prefix, 0); + + rb_define_private_method(cNokogiriXmlElementContent, "c1", get_c1, 0); + rb_define_private_method(cNokogiriXmlElementContent, "c2", get_c2, 0); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_element_decl.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_element_decl.c new file mode 100644 index 00000000..58981d35 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_element_decl.c @@ -0,0 +1,69 @@ +#include + +VALUE cNokogiriXmlElementDecl; + +static ID id_document; + +/* + * call-seq: + * element_type → Integer + * + * The element_type + */ +static VALUE +element_type(VALUE self) +{ + xmlElementPtr node; + Noko_Node_Get_Struct(self, xmlElement, node); + return INT2NUM(node->etype); +} + +/* + * call-seq: + * content → Nokogiri::XML::ElementContent + * + * [Returns] The root of this element declaration's content tree. + */ +static VALUE +content(VALUE self) +{ + xmlElementPtr node; + Noko_Node_Get_Struct(self, xmlElement, node); + + if (!node->content) { return Qnil; } + + return noko_xml_element_content_wrap( + rb_funcall(self, id_document, 0), + node->content + ); +} + +/* + * call-seq: + * prefix → String + * + * [Returns] The namespace +prefix+ for this element declaration. + */ +static VALUE +prefix(VALUE self) +{ + xmlElementPtr node; + Noko_Node_Get_Struct(self, xmlElement, node); + + if (!node->prefix) { return Qnil; } + + return NOKOGIRI_STR_NEW2(node->prefix); +} + +void +noko_init_xml_element_decl(void) +{ + assert(cNokogiriXmlNode); + cNokogiriXmlElementDecl = rb_define_class_under(mNokogiriXml, "ElementDecl", cNokogiriXmlNode); + + rb_define_method(cNokogiriXmlElementDecl, "element_type", element_type, 0); + rb_define_method(cNokogiriXmlElementDecl, "content", content, 0); + rb_define_method(cNokogiriXmlElementDecl, "prefix", prefix, 0); + + id_document = rb_intern("document"); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_encoding_handler.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_encoding_handler.c new file mode 100644 index 00000000..09927877 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_encoding_handler.c @@ -0,0 +1,112 @@ +#include + +VALUE cNokogiriEncodingHandler; + +static void +xml_encoding_handler_dealloc(void *data) +{ + /* make sure iconv handlers are cleaned up and freed */ + xmlCharEncodingHandlerPtr c_handler = data; + xmlCharEncCloseFunc(c_handler); +} + +static const rb_data_type_t xml_char_encoding_handler_type = { + .wrap_struct_name = "xmlCharEncodingHandler", + .function = { + .dfree = xml_encoding_handler_dealloc, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; + + +/* + * call-seq: Nokogiri::EncodingHandler.[](name) + * + * Get the encoding handler for +name+ + */ +static VALUE +rb_xml_encoding_handler_s_get(VALUE klass, VALUE key) +{ + xmlCharEncodingHandlerPtr handler; + + handler = xmlFindCharEncodingHandler(StringValueCStr(key)); + if (handler) { + return TypedData_Wrap_Struct(klass, &xml_char_encoding_handler_type, handler); + } + + return Qnil; +} + + +/* + * call-seq: Nokogiri::EncodingHandler.delete(name) + * + * Delete the encoding alias named +name+ + */ +static VALUE +rb_xml_encoding_handler_s_delete(VALUE klass, VALUE name) +{ + if (xmlDelEncodingAlias(StringValueCStr(name))) { return Qnil; } + + return Qtrue; +} + + +/* + * call-seq: Nokogiri::EncodingHandler.alias(real_name, alias_name) + * + * Alias encoding handler with name +real_name+ to name +alias_name+ + */ +static VALUE +rb_xml_encoding_handler_s_alias(VALUE klass, VALUE from, VALUE to) +{ + xmlAddEncodingAlias(StringValueCStr(from), StringValueCStr(to)); + + return to; +} + + +/* + * call-seq: Nokogiri::EncodingHandler.clear_aliases! + * + * Remove all encoding aliases. + */ +static VALUE +rb_xml_encoding_handler_s_clear_aliases(VALUE klass) +{ + xmlCleanupEncodingAliases(); + + return klass; +} + + +/* + * call-seq: name + * + * Get the name of this EncodingHandler + */ +static VALUE +rb_xml_encoding_handler_name(VALUE self) +{ + xmlCharEncodingHandlerPtr handler; + + TypedData_Get_Struct(self, xmlCharEncodingHandler, &xml_char_encoding_handler_type, handler); + + return NOKOGIRI_STR_NEW2(handler->name); +} + + +void +noko_init_xml_encoding_handler(void) +{ + cNokogiriEncodingHandler = rb_define_class_under(mNokogiri, "EncodingHandler", rb_cObject); + + rb_undef_alloc_func(cNokogiriEncodingHandler); + + rb_define_singleton_method(cNokogiriEncodingHandler, "[]", rb_xml_encoding_handler_s_get, 1); + rb_define_singleton_method(cNokogiriEncodingHandler, "delete", rb_xml_encoding_handler_s_delete, 1); + rb_define_singleton_method(cNokogiriEncodingHandler, "alias", rb_xml_encoding_handler_s_alias, 2); + rb_define_singleton_method(cNokogiriEncodingHandler, "clear_aliases!", rb_xml_encoding_handler_s_clear_aliases, 0); + + rb_define_method(cNokogiriEncodingHandler, "name", rb_xml_encoding_handler_name, 0); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_entity_decl.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_entity_decl.c new file mode 100644 index 00000000..4b7f4078 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_entity_decl.c @@ -0,0 +1,112 @@ +#include + +VALUE cNokogiriXmlEntityDecl; + +/* + * call-seq: + * original_content + * + * Get the original_content before ref substitution + */ +static VALUE +original_content(VALUE self) +{ + xmlEntityPtr node; + Noko_Node_Get_Struct(self, xmlEntity, node); + + if (!node->orig) { return Qnil; } + + return NOKOGIRI_STR_NEW2(node->orig); +} + +/* + * call-seq: + * content + * + * Get the content + */ +static VALUE +get_content(VALUE self) +{ + xmlEntityPtr node; + Noko_Node_Get_Struct(self, xmlEntity, node); + + if (!node->content) { return Qnil; } + + return NOKOGIRI_STR_NEW(node->content, node->length); +} + +/* + * call-seq: + * entity_type + * + * Get the entity type + */ +static VALUE +entity_type(VALUE self) +{ + xmlEntityPtr node; + Noko_Node_Get_Struct(self, xmlEntity, node); + + return INT2NUM((int)node->etype); +} + +/* + * call-seq: + * external_id + * + * Get the external identifier for PUBLIC + */ +static VALUE +external_id(VALUE self) +{ + xmlEntityPtr node; + Noko_Node_Get_Struct(self, xmlEntity, node); + + if (!node->ExternalID) { return Qnil; } + + return NOKOGIRI_STR_NEW2(node->ExternalID); +} + +/* + * call-seq: + * system_id + * + * Get the URI for a SYSTEM or PUBLIC Entity + */ +static VALUE +system_id(VALUE self) +{ + xmlEntityPtr node; + Noko_Node_Get_Struct(self, xmlEntity, node); + + if (!node->SystemID) { return Qnil; } + + return NOKOGIRI_STR_NEW2(node->SystemID); +} + +void +noko_init_xml_entity_decl(void) +{ + assert(cNokogiriXmlNode); + cNokogiriXmlEntityDecl = rb_define_class_under(mNokogiriXml, "EntityDecl", cNokogiriXmlNode); + + rb_define_method(cNokogiriXmlEntityDecl, "original_content", original_content, 0); + rb_define_method(cNokogiriXmlEntityDecl, "content", get_content, 0); + rb_define_method(cNokogiriXmlEntityDecl, "entity_type", entity_type, 0); + rb_define_method(cNokogiriXmlEntityDecl, "external_id", external_id, 0); + rb_define_method(cNokogiriXmlEntityDecl, "system_id", system_id, 0); + + rb_const_set(cNokogiriXmlEntityDecl, rb_intern("INTERNAL_GENERAL"), + INT2NUM(XML_INTERNAL_GENERAL_ENTITY)); + rb_const_set(cNokogiriXmlEntityDecl, rb_intern("EXTERNAL_GENERAL_PARSED"), + INT2NUM(XML_EXTERNAL_GENERAL_PARSED_ENTITY)); + rb_const_set(cNokogiriXmlEntityDecl, rb_intern("EXTERNAL_GENERAL_UNPARSED"), + INT2NUM(XML_EXTERNAL_GENERAL_UNPARSED_ENTITY)); + rb_const_set(cNokogiriXmlEntityDecl, rb_intern("INTERNAL_PARAMETER"), + INT2NUM(XML_INTERNAL_PARAMETER_ENTITY)); + rb_const_set(cNokogiriXmlEntityDecl, rb_intern("EXTERNAL_PARAMETER"), + INT2NUM(XML_EXTERNAL_PARAMETER_ENTITY)); + rb_const_set(cNokogiriXmlEntityDecl, rb_intern("INTERNAL_PREDEFINED"), + INT2NUM(XML_INTERNAL_PREDEFINED_ENTITY)); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_entity_reference.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_entity_reference.c new file mode 100644 index 00000000..3fcc3e54 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_entity_reference.c @@ -0,0 +1,50 @@ +#include + +VALUE cNokogiriXmlEntityReference; + +/* + * call-seq: + * new(document, content) + * + * Create a new EntityReference element on the +document+ with +name+ + */ +static VALUE +new (int argc, VALUE *argv, VALUE klass) +{ + xmlDocPtr xml_doc; + xmlNodePtr node; + VALUE document; + VALUE name; + VALUE rest; + VALUE rb_node; + + rb_scan_args(argc, argv, "2*", &document, &name, &rest); + + xml_doc = noko_xml_document_unwrap(document); + + node = xmlNewReference( + xml_doc, + (const xmlChar *)StringValueCStr(name) + ); + + noko_xml_document_pin_node(node); + + rb_node = noko_xml_node_wrap(klass, node); + rb_obj_call_init(rb_node, argc, argv); + + if (rb_block_given_p()) { rb_yield(rb_node); } + + return rb_node; +} + +void +noko_init_xml_entity_reference(void) +{ + assert(cNokogiriXmlNode); + /* + * EntityReference represents an EntityReference node in an xml document. + */ + cNokogiriXmlEntityReference = rb_define_class_under(mNokogiriXml, "EntityReference", cNokogiriXmlNode); + + rb_define_singleton_method(cNokogiriXmlEntityReference, "new", new, -1); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_namespace.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_namespace.c new file mode 100644 index 00000000..b16ad455 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_namespace.c @@ -0,0 +1,181 @@ +#include + +/* + * The lifecycle of a Namespace node is more complicated than other Nodes, for two reasons: + * + * 1. the underlying C structure has a different layout than all the other node structs, with the + * `_private` member where we store a pointer to Ruby object data not being in first position. + * 2. xmlNs structures returned in an xmlNodeset from an XPath query are copies of the document's + * namespaces, and so do not share the same memory lifecycle as everything else in a document. + * + * As a result of 1, you may see special handling of XML_NAMESPACE_DECL node types throughout the + * Nokogiri C code, though I intend to wrap up that logic in ruby_object_{get,set} functions + * shortly. + * + * As a result of 2, you will see we have special handling in this file and in xml_node_set.c to + * carefully manage the memory lifecycle of xmlNs structs to match the Ruby object's GC + * lifecycle. In xml_node_set.c we have local versions of xmlXPathNodeSetDel() and + * xmlXPathFreeNodeSet() that avoid freeing xmlNs structs in the node set. In this file, we decide + * whether or not to call dealloc_namespace() depending on whether the xmlNs struct appears to be + * in an xmlNodeSet (and thus the result of an XPath query) or not. + * + * Yes, this is madness. + */ + +VALUE cNokogiriXmlNamespace ; + +static void +_xml_namespace_dealloc(void *ptr) +{ + /* + * this deallocator is only used for namespace nodes that are part of an xpath + * node set. see noko_xml_namespace_wrap(). + */ + xmlNsPtr ns = ptr; + + if (ns->href) { + xmlFree(DISCARD_CONST_QUAL_XMLCHAR(ns->href)); + } + if (ns->prefix) { + xmlFree(DISCARD_CONST_QUAL_XMLCHAR(ns->prefix)); + } + xmlFree(ns); +} + +static void +_xml_namespace_update_references(void *ptr) +{ + xmlNsPtr ns = ptr; + if (ns->_private) { + ns->_private = (void *)rb_gc_location((VALUE)ns->_private); + } +} + +static const rb_data_type_t xml_ns_type_with_free = { + .wrap_struct_name = "xmlNs (with free)", + .function = { + .dfree = _xml_namespace_dealloc, + .dcompact = _xml_namespace_update_references, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; + +static const rb_data_type_t xml_ns_type_without_free = { + .wrap_struct_name = "xmlNs (without free)", + .function = { + .dcompact = _xml_namespace_update_references, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; + +/* + * :call-seq: + * prefix() → String or nil + * + * Return the prefix for this Namespace, or +nil+ if there is no prefix (e.g., default namespace). + * + * *Example* + * + * doc = Nokogiri::XML.parse(<<~XML) + * + * + * + * + * + * XML + * + * doc.root.elements.first.namespace.prefix + * # => nil + * + * doc.root.elements.last.namespace.prefix + * # => "noko" + */ +static VALUE +prefix(VALUE self) +{ + xmlNsPtr ns; + + Noko_Namespace_Get_Struct(self, xmlNs, ns); + if (!ns->prefix) { return Qnil; } + + return NOKOGIRI_STR_NEW2(ns->prefix); +} + +/* + * :call-seq: + * href() → String + * + * Returns the URI reference for this Namespace. + * + * *Example* + * + * doc = Nokogiri::XML.parse(<<~XML) + * + * + * + * + * + * XML + * + * doc.root.elements.first.namespace.href + * # => "http://nokogiri.org/ns/default" + * + * doc.root.elements.last.namespace.href + * # => "http://nokogiri.org/ns/noko" + */ +static VALUE +href(VALUE self) +{ + xmlNsPtr ns; + + Noko_Namespace_Get_Struct(self, xmlNs, ns); + if (!ns->href) { return Qnil; } + + return NOKOGIRI_STR_NEW2(ns->href); +} + +VALUE +noko_xml_namespace_wrap(xmlNsPtr c_namespace, xmlDocPtr c_document) +{ + VALUE rb_namespace; + + if (c_namespace->_private) { + return (VALUE)c_namespace->_private; + } + + if (c_document) { + rb_namespace = TypedData_Wrap_Struct(cNokogiriXmlNamespace, + &xml_ns_type_without_free, + c_namespace); + + if (DOC_RUBY_OBJECT_TEST(c_document)) { + rb_iv_set(rb_namespace, "@document", DOC_RUBY_OBJECT(c_document)); + rb_ary_push(DOC_NODE_CACHE(c_document), rb_namespace); + } + } else { + rb_namespace = TypedData_Wrap_Struct(cNokogiriXmlNamespace, + &xml_ns_type_with_free, + c_namespace); + } + + c_namespace->_private = (void *)rb_namespace; + + return rb_namespace; +} + +VALUE +noko_xml_namespace_wrap_xpath_copy(xmlNsPtr c_namespace) +{ + return noko_xml_namespace_wrap(c_namespace, NULL); +} + +void +noko_init_xml_namespace(void) +{ + cNokogiriXmlNamespace = rb_define_class_under(mNokogiriXml, "Namespace", rb_cObject); + + rb_undef_alloc_func(cNokogiriXmlNamespace); + + rb_define_method(cNokogiriXmlNamespace, "prefix", prefix, 0); + rb_define_method(cNokogiriXmlNamespace, "href", href, 0); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_node.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_node.c new file mode 100644 index 00000000..111e8f7e --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_node.c @@ -0,0 +1,2459 @@ +#include + +#include + +// :stopdoc: + +VALUE cNokogiriXmlNode ; +static ID id_decorate, id_decorate_bang; + +typedef xmlNodePtr(*pivot_reparentee_func)(xmlNodePtr, xmlNodePtr); + +static void +_xml_node_mark(void *ptr) +{ + xmlNodePtr node = ptr; + + if (!DOC_RUBY_OBJECT_TEST(node->doc)) { + return; + } + + xmlDocPtr doc = node->doc; + if (doc->type == XML_DOCUMENT_NODE || doc->type == XML_HTML_DOCUMENT_NODE) { + if (DOC_RUBY_OBJECT_TEST(doc)) { + rb_gc_mark(DOC_RUBY_OBJECT(doc)); + } + } else if (node->doc->_private) { + rb_gc_mark((VALUE)doc->_private); + } +} + +static void +_xml_node_update_references(void *ptr) +{ + xmlNodePtr node = ptr; + + if (node->_private) { + node->_private = (void *)rb_gc_location((VALUE)node->_private); + } +} + +static const rb_data_type_t xml_node_type = { + .wrap_struct_name = "xmlNode", + .function = { + .dmark = _xml_node_mark, + .dcompact = _xml_node_update_references, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY, +}; + +static VALUE +_xml_node_alloc(VALUE klass) +{ + return TypedData_Wrap_Struct(klass, &xml_node_type, NULL); +} + +static void +_xml_node_data_ptr_set(VALUE rb_node, xmlNodePtr c_node) +{ + assert(DATA_PTR(rb_node) == NULL); + assert(c_node->_private == NULL); + + DATA_PTR(rb_node) = c_node; + c_node->_private = (void *)rb_node; + + return; +} + +static void +relink_namespace(xmlNodePtr reparented) +{ + xmlNodePtr child; + xmlAttrPtr attr; + + if (reparented->type != XML_ATTRIBUTE_NODE && + reparented->type != XML_ELEMENT_NODE) { return; } + + if (reparented->ns == NULL || reparented->ns->prefix == NULL) { + xmlNsPtr ns = NULL; + xmlChar *name = NULL, *prefix = NULL; + + name = xmlSplitQName2(reparented->name, &prefix); + + if (reparented->type == XML_ATTRIBUTE_NODE) { + if (prefix == NULL || strcmp((char *)prefix, XMLNS_PREFIX) == 0) { + xmlFree(name); + xmlFree(prefix); + return; + } + } + + ns = xmlSearchNs(reparented->doc, reparented, prefix); + + if (ns != NULL) { + xmlNodeSetName(reparented, name); + xmlSetNs(reparented, ns); + } + + xmlFree(name); + xmlFree(prefix); + } + + /* Avoid segv when relinking against unlinked nodes. */ + if (reparented->type != XML_ELEMENT_NODE || !reparented->parent) { return; } + + /* Make sure that our reparented node has the correct namespaces */ + if (!reparented->ns && + (reparented->doc != (xmlDocPtr)reparented->parent) && + (rb_iv_get(DOC_RUBY_OBJECT(reparented->doc), "@namespace_inheritance") == Qtrue)) { + xmlSetNs(reparented, reparented->parent->ns); + } + + /* Search our parents for an existing definition */ + if (reparented->nsDef) { + xmlNsPtr curr = reparented->nsDef; + xmlNsPtr prev = NULL; + + while (curr) { + xmlNsPtr ns = xmlSearchNsByHref( + reparented->doc, + reparented->parent, + curr->href + ); + /* If we find the namespace is already declared, remove it from this + * definition list. */ + if (ns && ns != curr && xmlStrEqual(ns->prefix, curr->prefix)) { + if (prev) { + prev->next = curr->next; + } else { + reparented->nsDef = curr->next; + } + noko_xml_document_pin_namespace(curr, reparented->doc); + } else { + prev = curr; + } + curr = curr->next; + } + } + + /* + * Search our parents for an existing definition of current namespace, + * because the definition it's pointing to may have just been removed nsDef. + * + * And although that would technically probably be OK, I'd feel better if we + * referred to a namespace that's still present in a node's nsDef somewhere + * in the doc. + */ + if (reparented->ns) { + xmlNsPtr ns = xmlSearchNs(reparented->doc, reparented, reparented->ns->prefix); + if (ns + && ns != reparented->ns + && xmlStrEqual(ns->prefix, reparented->ns->prefix) + && xmlStrEqual(ns->href, reparented->ns->href) + ) { + xmlSetNs(reparented, ns); + } + } + + /* Only walk all children if there actually is a namespace we need to */ + /* reparent. */ + if (NULL == reparented->ns) { return; } + + /* When a node gets reparented, walk its children to make sure that */ + /* their namespaces are reparented as well. */ + child = reparented->children; + while (NULL != child) { + relink_namespace(child); + child = child->next; + } + + if (reparented->type == XML_ELEMENT_NODE) { + attr = reparented->properties; + while (NULL != attr) { + relink_namespace((xmlNodePtr)attr); + attr = attr->next; + } + } +} + + +/* internal function meant to wrap xmlReplaceNode + and fix some issues we have with libxml2 merging nodes */ +static xmlNodePtr +xmlReplaceNodeWrapper(xmlNodePtr pivot, xmlNodePtr new_node) +{ + xmlNodePtr retval ; + + retval = xmlReplaceNode(pivot, new_node) ; + + if (retval == pivot) { + retval = new_node ; /* return semantics for reparent_node_with */ + } + + /* work around libxml2 issue: https://bugzilla.gnome.org/show_bug.cgi?id=615612 */ + if (retval && retval->type == XML_TEXT_NODE) { + if (retval->prev && retval->prev->type == XML_TEXT_NODE) { + retval = xmlTextMerge(retval->prev, retval); + } + if (retval->next && retval->next->type == XML_TEXT_NODE) { + retval = xmlTextMerge(retval, retval->next); + } + } + + return retval ; +} + + +static void +raise_if_ancestor_of_self(xmlNodePtr self) +{ + for (xmlNodePtr ancestor = self->parent ; ancestor ; ancestor = ancestor->parent) { + if (self == ancestor) { + rb_raise(rb_eRuntimeError, "cycle detected: node '%s' is an ancestor of itself", self->name); + } + } +} + + +static VALUE +reparent_node_with(VALUE pivot_obj, VALUE reparentee_obj, pivot_reparentee_func prf) +{ + VALUE reparented_obj ; + xmlNodePtr reparentee, original_reparentee, pivot, reparented, next_text, new_next_text, parent ; + int original_ns_prefix_is_default = 0 ; + + if (!rb_obj_is_kind_of(reparentee_obj, cNokogiriXmlNode)) { + rb_raise(rb_eArgError, "node must be a Nokogiri::XML::Node"); + } + if (rb_obj_is_kind_of(reparentee_obj, cNokogiriXmlDocument)) { + rb_raise(rb_eArgError, "node must be a Nokogiri::XML::Node"); + } + + Noko_Node_Get_Struct(reparentee_obj, xmlNode, reparentee); + Noko_Node_Get_Struct(pivot_obj, xmlNode, pivot); + + /* + * Check if nodes given are appropriate to have a parent-child + * relationship, based on the DOM specification. + * + * cf. http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#ID-1590626202 + */ + if (prf == xmlAddChild) { + parent = pivot; + } else { + parent = pivot->parent; + } + + if (parent) { + switch (parent->type) { + case XML_DOCUMENT_NODE: + case XML_HTML_DOCUMENT_NODE: + switch (reparentee->type) { + case XML_ELEMENT_NODE: + case XML_PI_NODE: + case XML_COMMENT_NODE: + case XML_DOCUMENT_TYPE_NODE: + /* + * The DOM specification says no to adding text-like nodes + * directly to a document, but we allow it for compatibility. + */ + case XML_TEXT_NODE: + case XML_CDATA_SECTION_NODE: + case XML_ENTITY_REF_NODE: + goto ok; + default: + break; + } + break; + case XML_DOCUMENT_FRAG_NODE: + case XML_ENTITY_REF_NODE: + case XML_ELEMENT_NODE: + switch (reparentee->type) { + case XML_ELEMENT_NODE: + case XML_PI_NODE: + case XML_COMMENT_NODE: + case XML_TEXT_NODE: + case XML_CDATA_SECTION_NODE: + case XML_ENTITY_REF_NODE: + goto ok; + default: + break; + } + break; + case XML_ATTRIBUTE_NODE: + switch (reparentee->type) { + case XML_TEXT_NODE: + case XML_ENTITY_REF_NODE: + goto ok; + default: + break; + } + break; + case XML_TEXT_NODE: + /* + * xmlAddChild() breaks the DOM specification in that it allows + * adding a text node to another, in which case text nodes are + * coalesced, but since our JRuby version does not support such + * operation, we should inhibit it. + */ + break; + default: + break; + } + + rb_raise(rb_eArgError, "cannot reparent %s there", rb_obj_classname(reparentee_obj)); + } + +ok: + original_reparentee = reparentee; + + if (reparentee->doc != pivot->doc || reparentee->type == XML_TEXT_NODE) { + /* + * if the reparentee is a text node, there's a very good chance it will be + * merged with an adjacent text node after being reparented, and in that case + * libxml will free the underlying C struct. + * + * since we clearly have a ruby object which references the underlying + * memory, we can't let the C struct get freed. let's pickle the original + * reparentee by rooting it; and then we'll reparent a duplicate of the + * node that we don't care about preserving. + * + * alternatively, if the reparentee is from a different document than the + * pivot node, libxml2 is going to get confused about which document's + * "dictionary" the node's strings belong to (this is an otherwise + * uninteresting libxml2 implementation detail). as a result, we cannot + * reparent the actual reparentee, so we reparent a duplicate. + */ + if (reparentee->type == XML_TEXT_NODE && reparentee->_private) { + /* + * additionally, since we know this C struct isn't going to be related to + * a Ruby object anymore, let's break the relationship on this end as + * well. + * + * this is not absolutely necessary unless libxml-ruby is also in effect, + * in which case its global callback `rxml_node_deregisterNode` will try + * to do things to our data. + * + * for more details on this particular (and particularly nasty) edge + * case, see: + * + * https://github.com/sparklemotion/nokogiri/issues/1426 + */ + reparentee->_private = NULL ; + } + + if (reparentee->ns != NULL && reparentee->ns->prefix == NULL) { + original_ns_prefix_is_default = 1; + } + + noko_xml_document_pin_node(reparentee); + + if (!(reparentee = xmlDocCopyNode(reparentee, pivot->doc, 1))) { + rb_raise(rb_eRuntimeError, "Could not reparent node (xmlDocCopyNode)"); + } + + if (original_ns_prefix_is_default && reparentee->ns != NULL && reparentee->ns->prefix != NULL) { + /* + * issue #391, where new node's prefix may become the string "default" + * see libxml2 tree.c xmlNewReconciliedNs which implements this behavior. + */ + xmlFree(DISCARD_CONST_QUAL_XMLCHAR(reparentee->ns->prefix)); + reparentee->ns->prefix = NULL; + } + } + + xmlUnlinkNode(original_reparentee); + + if (prf != xmlAddPrevSibling && prf != xmlAddNextSibling && prf != xmlAddChild + && reparentee->type == XML_TEXT_NODE && pivot->next && pivot->next->type == XML_TEXT_NODE) { + /* + * libxml merges text nodes in a right-to-left fashion, meaning that if + * there are two text nodes who would be adjacent, the right (or following, + * or next) node will be merged into the left (or preceding, or previous) + * node. + * + * and by "merged" I mean the string contents will be concatenated onto the + * left node's contents, and then the node will be freed. + * + * which means that if we have a ruby object wrapped around the right node, + * its memory would be freed out from under it. + * + * so, we detect this edge case and unlink-and-root the text node before it gets + * merged. then we dup the node and insert that duplicate back into the + * document where the real node was. + * + * yes, this is totally lame. + */ + next_text = pivot->next ; + new_next_text = xmlDocCopyNode(next_text, pivot->doc, 1) ; + + xmlUnlinkNode(next_text); + noko_xml_document_pin_node(next_text); + + xmlAddNextSibling(pivot, new_next_text); + } + + if (!(reparented = (*prf)(pivot, reparentee))) { + rb_raise(rb_eRuntimeError, "Could not reparent node"); + } + + /* + * make sure the ruby object is pointed at the just-reparented node, which + * might be a duplicate (see above) or might be the result of merging + * adjacent text nodes. + */ + DATA_PTR(reparentee_obj) = reparented ; + reparented_obj = noko_xml_node_wrap(Qnil, reparented); + + rb_funcall(reparented_obj, id_decorate_bang, 0); + + /* if we've created a cycle, raise an exception */ + raise_if_ancestor_of_self(reparented); + + relink_namespace(reparented); + + return reparented_obj ; +} + +// :startdoc: + +/* + * :call-seq: + * add_namespace_definition(prefix, href) → Nokogiri::XML::Namespace + * add_namespace(prefix, href) → Nokogiri::XML::Namespace + * + * :category: Manipulating Document Structure + * + * Adds a namespace definition to this node with +prefix+ using +href+ value, as if this node had + * included an attribute "xmlns:prefix=href". + * + * A default namespace definition for this node can be added by passing +nil+ for +prefix+. + * + * [Parameters] + * - +prefix+ (String, +nil+) An {XML Name}[https://www.w3.org/TR/xml-names/#ns-decl] + * - +href+ (String) The {URI reference}[https://www.w3.org/TR/xml-names/#sec-namespaces] + * + * [Returns] The new Nokogiri::XML::Namespace + * + * *Example:* adding a non-default namespace definition + * + * doc = Nokogiri::XML("") + * inventory = doc.at_css("inventory") + * inventory.add_namespace_definition("automobile", "http://alices-autos.com/") + * inventory.add_namespace_definition("bicycle", "http://bobs-bikes.com/") + * inventory.add_child("Michelin model XGV, size 75R") + * doc.to_xml + * # => "\n" + + * # "\n" + + * # " \n" + + * # " Michelin model XGV, size 75R\n" + + * # " \n" + + * # "\n" + * + * *Example:* adding a default namespace definition + * + * doc = Nokogiri::XML("Michelin model XGV, size 75R") + * doc.at_css("tire").add_namespace_definition(nil, "http://bobs-bikes.com/") + * doc.to_xml + * # => "\n" + + * # "\n" + + * # " \n" + + * # " Michelin model XGV, size 75R\n" + + * # " \n" + + * # "\n" + * + */ +static VALUE +rb_xml_node_add_namespace_definition(VALUE rb_node, VALUE rb_prefix, VALUE rb_href) +{ + xmlNodePtr c_node, element; + xmlNsPtr c_namespace; + const xmlChar *c_prefix = (const xmlChar *)(NIL_P(rb_prefix) ? NULL : StringValueCStr(rb_prefix)); + + Noko_Node_Get_Struct(rb_node, xmlNode, c_node); + element = c_node ; + + c_namespace = xmlSearchNs(c_node->doc, c_node, c_prefix); + + if (!c_namespace) { + if (c_node->type != XML_ELEMENT_NODE) { + element = c_node->parent; + } + c_namespace = xmlNewNs(element, (const xmlChar *)StringValueCStr(rb_href), c_prefix); + } + + if (!c_namespace) { + return Qnil ; + } + + if (NIL_P(rb_prefix) || c_node != element) { + xmlSetNs(c_node, c_namespace); + } + + return noko_xml_namespace_wrap(c_namespace, c_node->doc); +} + + +/* + * :call-seq: attribute(name) → Nokogiri::XML::Attr + * + * :category: Working With Node Attributes + * + * [Returns] Attribute (Nokogiri::XML::Attr) belonging to this node with name +name+. + * + * ⚠ Note that attribute namespaces are ignored and only the simple (non-namespace-prefixed) name is + * used to find a matching attribute. In case of a simple name collision, only one of the matching + * attributes will be returned. In this case, you will need to use #attribute_with_ns. + * + * *Example:* + * + * doc = Nokogiri::XML("") + * child = doc.at_css("child") + * child.attribute("size") # => # + * child.attribute("class") # => # + * + * *Example* showing that namespaced attributes will not be returned: + * + * ⚠ Note that only one of the two matching attributes is returned. + * + * doc = Nokogiri::XML(<<~EOF) + * + * + * + * EOF + * doc.at_css("child").attribute("size") + * # => #(Attr:0x550 { + * # name = "size", + * # namespace = #(Namespace:0x564 { + * # prefix = "width", + * # href = "http://example.com/widths" + * # }), + * # value = "broad" + * # }) + */ +static VALUE +rb_xml_node_attribute(VALUE self, VALUE name) +{ + xmlNodePtr node; + xmlAttrPtr prop; + Noko_Node_Get_Struct(self, xmlNode, node); + prop = xmlHasProp(node, (xmlChar *)StringValueCStr(name)); + + if (! prop) { return Qnil; } + return noko_xml_node_wrap(Qnil, (xmlNodePtr)prop); +} + + +/* + * :call-seq: attribute_nodes() → Array + * + * :category: Working With Node Attributes + * + * [Returns] Attributes (an Array of Nokogiri::XML::Attr) belonging to this node. + * + * Note that this is the preferred alternative to #attributes when the simple + * (non-namespace-prefixed) attribute names may collide. + * + * *Example:* + * + * Contrast this with the colliding-name example from #attributes. + * + * doc = Nokogiri::XML(<<~EOF) + * + * + * + * EOF + * doc.at_css("child").attribute_nodes + * # => [#(Attr:0x550 { + * # name = "size", + * # namespace = #(Namespace:0x564 { + * # prefix = "width", + * # href = "http://example.com/widths" + * # }), + * # value = "broad" + * # }), + * # #(Attr:0x578 { + * # name = "size", + * # namespace = #(Namespace:0x58c { + * # prefix = "height", + * # href = "http://example.com/heights" + * # }), + * # value = "tall" + * # })] + */ +static VALUE +rb_xml_node_attribute_nodes(VALUE rb_node) +{ + xmlNodePtr c_node; + + Noko_Node_Get_Struct(rb_node, xmlNode, c_node); + + return noko_xml_node_attrs(c_node); +} + + +/* + * :call-seq: attribute_with_ns(name, namespace) → Nokogiri::XML::Attr + * + * :category: Working With Node Attributes + * + * [Returns] + * Attribute (Nokogiri::XML::Attr) belonging to this node with matching +name+ and +namespace+. + * + * [Parameters] + * - +name+ (String): the simple (non-namespace-prefixed) name of the attribute + * - +namespace+ (String): the URI of the attribute's namespace + * + * See related: #attribute + * + * *Example:* + * + * doc = Nokogiri::XML(<<~EOF) + * + * + * + * EOF + * doc.at_css("child").attribute_with_ns("size", "http://example.com/widths") + * # => #(Attr:0x550 { + * # name = "size", + * # namespace = #(Namespace:0x564 { + * # prefix = "width", + * # href = "http://example.com/widths" + * # }), + * # value = "broad" + * # }) + * doc.at_css("child").attribute_with_ns("size", "http://example.com/heights") + * # => #(Attr:0x578 { + * # name = "size", + * # namespace = #(Namespace:0x58c { + * # prefix = "height", + * # href = "http://example.com/heights" + * # }), + * # value = "tall" + * # }) + */ +static VALUE +rb_xml_node_attribute_with_ns(VALUE self, VALUE name, VALUE namespace) +{ + xmlNodePtr node; + xmlAttrPtr prop; + Noko_Node_Get_Struct(self, xmlNode, node); + prop = xmlHasNsProp(node, (xmlChar *)StringValueCStr(name), + NIL_P(namespace) ? NULL : (xmlChar *)StringValueCStr(namespace)); + + if (! prop) { return Qnil; } + return noko_xml_node_wrap(Qnil, (xmlNodePtr)prop); +} + + + +/* + * call-seq: blank? → Boolean + * + * [Returns] +true+ if the node is an empty or whitespace-only text or cdata node, else +false+. + * + * *Example:* + * + * Nokogiri("").root.child.blank? # => false + * Nokogiri("\t \n").root.child.blank? # => true + * Nokogiri("").root.child.blank? # => true + * Nokogiri("not-blank").root.child + * .tap { |n| n.content = "" }.blank # => true + */ +static VALUE +rb_xml_node_blank_eh(VALUE self) +{ + xmlNodePtr node; + Noko_Node_Get_Struct(self, xmlNode, node); + return (1 == xmlIsBlankNode(node)) ? Qtrue : Qfalse ; +} + + +/* + * :call-seq: child() → Nokogiri::XML::Node + * + * :category: Traversing Document Structure + * + * [Returns] First of this node's children, or +nil+ if there are no children + * + * This is a convenience method and is equivalent to: + * + * node.children.first + * + * See related: #children + */ +static VALUE +rb_xml_node_child(VALUE self) +{ + xmlNodePtr node, child; + Noko_Node_Get_Struct(self, xmlNode, node); + + child = node->children; + if (!child) { return Qnil; } + + return noko_xml_node_wrap(Qnil, child); +} + + +/* + * :call-seq: children() → Nokogiri::XML::NodeSet + * + * :category: Traversing Document Structure + * + * [Returns] Nokogiri::XML::NodeSet containing this node's children. + */ +static VALUE +rb_xml_node_children(VALUE self) +{ + xmlNodePtr node; + xmlNodePtr child; + xmlNodeSetPtr set; + VALUE document; + VALUE node_set; + + Noko_Node_Get_Struct(self, xmlNode, node); + + child = node->children; + set = xmlXPathNodeSetCreate(child); + + document = DOC_RUBY_OBJECT(node->doc); + + if (!child) { return noko_xml_node_set_wrap(set, document); } + + child = child->next; + while (NULL != child) { + xmlXPathNodeSetAddUnique(set, child); + child = child->next; + } + + node_set = noko_xml_node_set_wrap(set, document); + + return node_set; +} + + +/* + * :call-seq: + * content() → String + * inner_text() → String + * text() → String + * to_str() → String + * + * [Returns] + * Contents of all the text nodes in this node's subtree, concatenated together into a single + * String. + * + * ⚠ Note that entities will _always_ be expanded in the returned String. + * + * See related: #inner_html + * + * *Example* of how entities are handled: + * + * Note that < becomes < in the returned String. + * + * doc = Nokogiri::XML.fragment("a < b") + * doc.at_css("child").content + * # => "a < b" + * + * *Example* of how a subtree is handled: + * + * Note that the tags are omitted and only the text node contents are returned, + * concatenated into a single string. + * + * doc = Nokogiri::XML.fragment("first second") + * doc.at_css("child").content + * # => "first second" + */ +static VALUE +rb_xml_node_content(VALUE self) +{ + xmlNodePtr node; + xmlChar *content; + + Noko_Node_Get_Struct(self, xmlNode, node); + + content = xmlNodeGetContent(node); + if (content) { + VALUE rval = NOKOGIRI_STR_NEW2(content); + xmlFree(content); + return rval; + } + return Qnil; +} + + +/* + * :call-seq: document() → Nokogiri::XML::Document + * + * :category: Traversing Document Structure + * + * [Returns] Parent Nokogiri::XML::Document for this node + */ +static VALUE +rb_xml_node_document(VALUE self) +{ + xmlNodePtr node; + Noko_Node_Get_Struct(self, xmlNode, node); + return DOC_RUBY_OBJECT(node->doc); +} + +/* + * :call-seq: pointer_id() → Integer + * + * [Returns] + * A unique id for this node based on the internal memory structures. This method is used by #== + * to determine node identity. + */ +static VALUE +rb_xml_node_pointer_id(VALUE self) +{ + xmlNodePtr node; + Noko_Node_Get_Struct(self, xmlNode, node); + + return rb_uint2inum((uintptr_t)(node)); +} + +/* + * :call-seq: encode_special_chars(string) → String + * + * Encode any special characters in +string+ + */ +static VALUE +encode_special_chars(VALUE self, VALUE string) +{ + xmlNodePtr node; + xmlChar *encoded; + VALUE encoded_str; + + Noko_Node_Get_Struct(self, xmlNode, node); + encoded = xmlEncodeSpecialChars( + node->doc, + (const xmlChar *)StringValueCStr(string) + ); + + encoded_str = NOKOGIRI_STR_NEW2(encoded); + xmlFree(encoded); + + return encoded_str; +} + +/* + * :call-seq: + * create_internal_subset(name, external_id, system_id) + * + * Create the internal subset of a document. + * + * doc.create_internal_subset("chapter", "-//OASIS//DTD DocBook XML//EN", "chapter.dtd") + * # => + * + * doc.create_internal_subset("chapter", nil, "chapter.dtd") + * # => + */ +static VALUE +create_internal_subset(VALUE self, VALUE name, VALUE external_id, VALUE system_id) +{ + xmlNodePtr node; + xmlDocPtr doc; + xmlDtdPtr dtd; + + Noko_Node_Get_Struct(self, xmlNode, node); + + doc = node->doc; + + if (xmlGetIntSubset(doc)) { + rb_raise(rb_eRuntimeError, "Document already has an internal subset"); + } + + dtd = xmlCreateIntSubset( + doc, + NIL_P(name) ? NULL : (const xmlChar *)StringValueCStr(name), + NIL_P(external_id) ? NULL : (const xmlChar *)StringValueCStr(external_id), + NIL_P(system_id) ? NULL : (const xmlChar *)StringValueCStr(system_id) + ); + + if (!dtd) { return Qnil; } + + return noko_xml_node_wrap(Qnil, (xmlNodePtr)dtd); +} + +/* + * :call-seq: + * create_external_subset(name, external_id, system_id) + * + * Create an external subset + */ +static VALUE +create_external_subset(VALUE self, VALUE name, VALUE external_id, VALUE system_id) +{ + xmlNodePtr node; + xmlDocPtr doc; + xmlDtdPtr dtd; + + Noko_Node_Get_Struct(self, xmlNode, node); + + doc = node->doc; + + if (doc->extSubset) { + rb_raise(rb_eRuntimeError, "Document already has an external subset"); + } + + dtd = xmlNewDtd( + doc, + NIL_P(name) ? NULL : (const xmlChar *)StringValueCStr(name), + NIL_P(external_id) ? NULL : (const xmlChar *)StringValueCStr(external_id), + NIL_P(system_id) ? NULL : (const xmlChar *)StringValueCStr(system_id) + ); + + if (!dtd) { return Qnil; } + + return noko_xml_node_wrap(Qnil, (xmlNodePtr)dtd); +} + +/* + * :call-seq: + * external_subset() + * + * Get the external subset + */ +static VALUE +external_subset(VALUE self) +{ + xmlNodePtr node; + xmlDocPtr doc; + xmlDtdPtr dtd; + + Noko_Node_Get_Struct(self, xmlNode, node); + + if (!node->doc) { return Qnil; } + + doc = node->doc; + dtd = doc->extSubset; + + if (!dtd) { return Qnil; } + + return noko_xml_node_wrap(Qnil, (xmlNodePtr)dtd); +} + +/* + * :call-seq: + * internal_subset() + * + * Get the internal subset + */ +static VALUE +internal_subset(VALUE self) +{ + xmlNodePtr node; + xmlDocPtr doc; + xmlDtdPtr dtd; + + Noko_Node_Get_Struct(self, xmlNode, node); + + if (!node->doc) { return Qnil; } + + doc = node->doc; + dtd = xmlGetIntSubset(doc); + + if (!dtd) { return Qnil; } + + return noko_xml_node_wrap(Qnil, (xmlNodePtr)dtd); +} + +/* :nodoc: */ +static VALUE +rb_xml_node_initialize_copy_with_args(VALUE rb_self, VALUE rb_other, VALUE rb_level, VALUE rb_new_parent_doc) +{ + xmlNodePtr c_self, c_other; + int c_level; + xmlDocPtr c_new_parent_doc; + VALUE rb_node_cache; + + Noko_Node_Get_Struct(rb_other, xmlNode, c_other); + c_level = (int)NUM2INT(rb_level); + c_new_parent_doc = noko_xml_document_unwrap(rb_new_parent_doc); + + c_self = xmlDocCopyNode(c_other, c_new_parent_doc, c_level); + if (c_self == NULL) { return Qnil; } + + _xml_node_data_ptr_set(rb_self, c_self); + noko_xml_document_pin_node(c_self); + + rb_node_cache = DOC_NODE_CACHE(c_new_parent_doc); + rb_ary_push(rb_node_cache, rb_self); + rb_funcall(rb_new_parent_doc, id_decorate, 1, rb_self); + + return rb_self; +} + +/* + * :call-seq: + * unlink() → self + * + * Unlink this node from its current context. + */ +static VALUE +unlink_node(VALUE self) +{ + xmlNodePtr node; + Noko_Node_Get_Struct(self, xmlNode, node); + xmlUnlinkNode(node); + noko_xml_document_pin_node(node); + return self; +} + + +/* + * call-seq: + * next_sibling + * + * Returns the next sibling node + */ +static VALUE +next_sibling(VALUE self) +{ + xmlNodePtr node, sibling; + Noko_Node_Get_Struct(self, xmlNode, node); + + sibling = node->next; + if (!sibling) { return Qnil; } + + return noko_xml_node_wrap(Qnil, sibling) ; +} + +/* + * call-seq: + * previous_sibling + * + * Returns the previous sibling node + */ +static VALUE +previous_sibling(VALUE self) +{ + xmlNodePtr node, sibling; + Noko_Node_Get_Struct(self, xmlNode, node); + + sibling = node->prev; + if (!sibling) { return Qnil; } + + return noko_xml_node_wrap(Qnil, sibling); +} + +/* + * call-seq: + * next_element + * + * Returns the next Nokogiri::XML::Element type sibling node. + */ +static VALUE +next_element(VALUE self) +{ + xmlNodePtr node, sibling; + Noko_Node_Get_Struct(self, xmlNode, node); + + sibling = xmlNextElementSibling(node); + if (!sibling) { return Qnil; } + + return noko_xml_node_wrap(Qnil, sibling); +} + +/* + * call-seq: + * previous_element + * + * Returns the previous Nokogiri::XML::Element type sibling node. + */ +static VALUE +previous_element(VALUE self) +{ + xmlNodePtr node, sibling; + Noko_Node_Get_Struct(self, xmlNode, node); + + sibling = xmlPreviousElementSibling(node); + if (!sibling) { return Qnil; } + + return noko_xml_node_wrap(Qnil, sibling); +} + +/* :nodoc: */ +static VALUE +replace(VALUE self, VALUE new_node) +{ + VALUE reparent = reparent_node_with(self, new_node, xmlReplaceNodeWrapper); + + xmlNodePtr pivot; + Noko_Node_Get_Struct(self, xmlNode, pivot); + noko_xml_document_pin_node(pivot); + + return reparent; +} + +/* + * :call-seq: + * element_children() → NodeSet + * elements() → NodeSet + * + * [Returns] + * The node's child elements as a NodeSet. Only children that are elements will be returned, which + * notably excludes Text nodes. + * + * *Example:* + * + * Note that #children returns the Text node "hello" while #element_children does not. + * + * div = Nokogiri::HTML5("
    helloworld").at_css("div") + * div.element_children + * # => [#]>] + * div.children + * # => [#, + * # #]>] + */ +static VALUE +rb_xml_node_element_children(VALUE self) +{ + xmlNodePtr node; + xmlNodePtr child; + xmlNodeSetPtr set; + VALUE document; + VALUE node_set; + + Noko_Node_Get_Struct(self, xmlNode, node); + + child = xmlFirstElementChild(node); + set = xmlXPathNodeSetCreate(child); + + document = DOC_RUBY_OBJECT(node->doc); + + if (!child) { return noko_xml_node_set_wrap(set, document); } + + child = xmlNextElementSibling(child); + while (NULL != child) { + xmlXPathNodeSetAddUnique(set, child); + child = xmlNextElementSibling(child); + } + + node_set = noko_xml_node_set_wrap(set, document); + + return node_set; +} + +/* + * :call-seq: + * first_element_child() → Node + * + * [Returns] The first child Node that is an element. + * + * *Example:* + * + * Note that the "hello" child, which is a Text node, is skipped and the element is + * returned. + * + * div = Nokogiri::HTML5("
    helloworld").at_css("div") + * div.first_element_child + * # => #(Element:0x3c { name = "span", children = [ #(Text "world")] }) + */ +static VALUE +rb_xml_node_first_element_child(VALUE self) +{ + xmlNodePtr node, child; + Noko_Node_Get_Struct(self, xmlNode, node); + + child = xmlFirstElementChild(node); + if (!child) { return Qnil; } + + return noko_xml_node_wrap(Qnil, child); +} + +/* + * :call-seq: + * last_element_child() → Node + * + * [Returns] The last child Node that is an element. + * + * *Example:* + * + * Note that the "hello" child, which is a Text node, is skipped and the yes + * element is returned. + * + * div = Nokogiri::HTML5("
    noyesskip
    ").at_css("div") + * div.last_element_child + * # => #(Element:0x3c { name = "span", children = [ #(Text "yes")] }) + */ +static VALUE +rb_xml_node_last_element_child(VALUE self) +{ + xmlNodePtr node, child; + Noko_Node_Get_Struct(self, xmlNode, node); + + child = xmlLastElementChild(node); + if (!child) { return Qnil; } + + return noko_xml_node_wrap(Qnil, child); +} + +/* + * call-seq: + * key?(attribute) + * + * Returns true if +attribute+ is set + */ +static VALUE +key_eh(VALUE self, VALUE attribute) +{ + xmlNodePtr node; + Noko_Node_Get_Struct(self, xmlNode, node); + if (xmlHasProp(node, (xmlChar *)StringValueCStr(attribute))) { + return Qtrue; + } + return Qfalse; +} + +/* + * call-seq: + * namespaced_key?(attribute, namespace) + * + * Returns true if +attribute+ is set with +namespace+ + */ +static VALUE +namespaced_key_eh(VALUE self, VALUE attribute, VALUE namespace) +{ + xmlNodePtr node; + Noko_Node_Get_Struct(self, xmlNode, node); + if (xmlHasNsProp(node, (xmlChar *)StringValueCStr(attribute), + NIL_P(namespace) ? NULL : (xmlChar *)StringValueCStr(namespace))) { + return Qtrue; + } + return Qfalse; +} + +/* + * call-seq: + * []=(property, value) + * + * Set the +property+ to +value+ + */ +static VALUE +set(VALUE self, VALUE property, VALUE value) +{ + xmlNodePtr node, cur; + xmlAttrPtr prop; + Noko_Node_Get_Struct(self, xmlNode, node); + + /* If a matching attribute node already exists, then xmlSetProp will destroy + * the existing node's children. However, if Nokogiri has a node object + * pointing to one of those children, we are left with a broken reference. + * + * We can avoid this by unlinking these nodes first. + */ + if (node->type != XML_ELEMENT_NODE) { + return (Qnil); + } + prop = xmlHasProp(node, (xmlChar *)StringValueCStr(property)); + if (prop && prop->children) { + for (cur = prop->children; cur; cur = cur->next) { + if (cur->_private) { + noko_xml_document_pin_node(cur); + xmlUnlinkNode(cur); + } + } + } + + xmlSetProp(node, (xmlChar *)StringValueCStr(property), + (xmlChar *)StringValueCStr(value)); + + return value; +} + +/* + * call-seq: + * get(attribute) + * + * Get the value for +attribute+ + */ +static VALUE +get(VALUE self, VALUE rattribute) +{ + xmlNodePtr node; + xmlChar *value = 0; + VALUE rvalue; + xmlChar *colon; + xmlChar *attribute, *attr_name, *prefix; + xmlNsPtr ns; + + if (NIL_P(rattribute)) { return Qnil; } + + Noko_Node_Get_Struct(self, xmlNode, node); + attribute = xmlCharStrdup(StringValueCStr(rattribute)); + + colon = DISCARD_CONST_QUAL_XMLCHAR(xmlStrchr(attribute, (const xmlChar)':')); + if (colon) { + /* split the attribute string into separate prefix and name by + * null-terminating the prefix at the colon */ + prefix = attribute; + attr_name = colon + 1; + (*colon) = 0; + + ns = xmlSearchNs(node->doc, node, prefix); + if (ns) { + value = xmlGetNsProp(node, attr_name, ns->href); + } else { + value = xmlGetProp(node, (xmlChar *)StringValueCStr(rattribute)); + } + } else { + value = xmlGetNoNsProp(node, attribute); + } + + xmlFree((void *)attribute); + if (!value) { return Qnil; } + + rvalue = NOKOGIRI_STR_NEW2(value); + xmlFree((void *)value); + + return rvalue ; +} + +/* + * call-seq: + * set_namespace(namespace) + * + * Set the namespace to +namespace+ + */ +static VALUE +set_namespace(VALUE self, VALUE namespace) +{ + xmlNodePtr node; + xmlNsPtr ns = NULL; + + Noko_Node_Get_Struct(self, xmlNode, node); + + if (!NIL_P(namespace)) { + Noko_Namespace_Get_Struct(namespace, xmlNs, ns); + } + + xmlSetNs(node, ns); + + return self; +} + +/* + * :call-seq: + * namespace() → Namespace + * + * [Returns] The Namespace of the element or attribute node, or +nil+ if there is no namespace. + * + * *Example:* + * + * doc = Nokogiri::XML(<<~EOF) + * + * + * + * + * + * EOF + * doc.at_xpath("//first").namespace + * # => nil + * doc.at_xpath("//xmlns:second", "xmlns" => "http://example.com/child").namespace + * # => #(Namespace:0x3c { href = "http://example.com/child" }) + * doc.at_xpath("//foo:third", "foo" => "http://example.com/foo").namespace + * # => #(Namespace:0x50 { prefix = "foo", href = "http://example.com/foo" }) + */ +static VALUE +rb_xml_node_namespace(VALUE rb_node) +{ + xmlNodePtr c_node ; + Noko_Node_Get_Struct(rb_node, xmlNode, c_node); + + if (c_node->ns) { + return noko_xml_namespace_wrap(c_node->ns, c_node->doc); + } + + return Qnil ; +} + +/* + * :call-seq: + * namespace_definitions() → Array + * + * [Returns] + * Namespaces that are defined directly on this node, as an Array of Namespace objects. The array + * will be empty if no namespaces are defined on this node. + * + * *Example:* + * + * doc = Nokogiri::XML(<<~EOF) + * + * + * + * + * + * EOF + * doc.at_xpath("//root:first", "root" => "http://example.com/root").namespace_definitions + * # => [] + * doc.at_xpath("//xmlns:second", "xmlns" => "http://example.com/child").namespace_definitions + * # => [#(Namespace:0x3c { href = "http://example.com/child" }), + * # #(Namespace:0x50 { + * # prefix = "unused", + * # href = "http://example.com/unused" + * # })] + * doc.at_xpath("//foo:third", "foo" => "http://example.com/foo").namespace_definitions + * # => [#(Namespace:0x64 { prefix = "foo", href = "http://example.com/foo" })] + */ +static VALUE +namespace_definitions(VALUE rb_node) +{ + /* this code in the mode of xmlHasProp() */ + xmlNodePtr c_node ; + xmlNsPtr c_namespace; + VALUE definitions = rb_ary_new(); + + Noko_Node_Get_Struct(rb_node, xmlNode, c_node); + + c_namespace = c_node->nsDef; + if (!c_namespace) { + return definitions; + } + + while (c_namespace != NULL) { + rb_ary_push(definitions, noko_xml_namespace_wrap(c_namespace, c_node->doc)); + c_namespace = c_namespace->next; + } + + return definitions; +} + +/* + * :call-seq: + * namespace_scopes() → Array + * + * [Returns] Array of all the Namespaces on this node and its ancestors. + * + * See also #namespaces + * + * *Example:* + * + * doc = Nokogiri::XML(<<~EOF) + * + * + * + * + * + * EOF + * doc.at_xpath("//root:first", "root" => "http://example.com/root").namespace_scopes + * # => [#(Namespace:0x3c { href = "http://example.com/root" }), + * # #(Namespace:0x50 { prefix = "bar", href = "http://example.com/bar" })] + * doc.at_xpath("//child:second", "child" => "http://example.com/child").namespace_scopes + * # => [#(Namespace:0x64 { href = "http://example.com/child" }), + * # #(Namespace:0x50 { prefix = "bar", href = "http://example.com/bar" })] + * doc.at_xpath("//root:third", "root" => "http://example.com/root").namespace_scopes + * # => [#(Namespace:0x78 { prefix = "foo", href = "http://example.com/foo" }), + * # #(Namespace:0x3c { href = "http://example.com/root" }), + * # #(Namespace:0x50 { prefix = "bar", href = "http://example.com/bar" })] + */ +static VALUE +rb_xml_node_namespace_scopes(VALUE rb_node) +{ + xmlNodePtr c_node ; + xmlNsPtr *namespaces; + VALUE scopes = rb_ary_new(); + int j; + + Noko_Node_Get_Struct(rb_node, xmlNode, c_node); + + namespaces = xmlGetNsList(c_node->doc, c_node); + if (!namespaces) { + return scopes; + } + + for (j = 0 ; namespaces[j] != NULL ; ++j) { + rb_ary_push(scopes, noko_xml_namespace_wrap(namespaces[j], c_node->doc)); + } + + xmlFree(namespaces); + return scopes; +} + +/* + * call-seq: + * node_type + * + * Get the type for this Node + */ +static VALUE +node_type(VALUE self) +{ + xmlNodePtr node; + Noko_Node_Get_Struct(self, xmlNode, node); + return INT2NUM(node->type); +} + +/* + * call-seq: + * native_content=(input) + * + * Set the content of this node to +input+. + * + * [Parameters] + * - +input+ (String) The new content for this node. + * + * ⚠ This method behaves differently depending on the node type. For Text, CDATA, Comment, and + * ProcessingInstruction nodes, it treats the input as raw content, which means that the final DOM + * will contain the entity-escaped version of the input (see example below). For Element and Attr + * nodes, it treats the input as parsed content and expects it to be valid markup that is already + * entity-escaped. + * + * 💡 Use Node#content= for a more consistent API across node types. + * + * [Example] + * Note the behavior differences of this method between Text and Element nodes: + * + * doc = Nokogiri::HTML::Document.parse(<<~HTML) + * + * + *
    asdf
    + *
    asdf
    + * HTML + * + * text_node = doc.at_css("div#first").children.first + * div_node = doc.at_css("div#second") + * + * value = "You & Me" + * + * text_node.native_content = value + * div_node.native_content = value + * + * doc.css("div").to_html + * # => "
    You &amp; Me
    + * #
    You & Me
    " + * + * See also: #content= + */ +static VALUE +set_native_content(VALUE self, VALUE content) +{ + xmlNodePtr node, child, next ; + Noko_Node_Get_Struct(self, xmlNode, node); + + child = node->children; + while (NULL != child) { + next = child->next ; + xmlUnlinkNode(child) ; + noko_xml_document_pin_node(child); + child = next ; + } + + xmlNodeSetContent(node, (xmlChar *)StringValueCStr(content)); + return content; +} + +/* + * call-seq: + * lang= + * + * Set the language of a node, i.e. the values of the xml:lang attribute. + */ +static VALUE +set_lang(VALUE self_rb, VALUE lang_rb) +{ + xmlNodePtr self ; + xmlChar *lang ; + + Noko_Node_Get_Struct(self_rb, xmlNode, self); + lang = (xmlChar *)StringValueCStr(lang_rb); + + xmlNodeSetLang(self, lang); + + return Qnil ; +} + +/* + * call-seq: + * lang + * + * Searches the language of a node, i.e. the values of the xml:lang attribute or + * the one carried by the nearest ancestor. + */ +static VALUE +get_lang(VALUE self_rb) +{ + xmlNodePtr self ; + xmlChar *lang ; + VALUE lang_rb ; + + Noko_Node_Get_Struct(self_rb, xmlNode, self); + + lang = xmlNodeGetLang(self); + if (lang) { + lang_rb = NOKOGIRI_STR_NEW2(lang); + xmlFree(lang); + return lang_rb ; + } + + return Qnil ; +} + +/* :nodoc: */ +static VALUE +add_child(VALUE self, VALUE new_child) +{ + return reparent_node_with(self, new_child, xmlAddChild); +} + +/* + * call-seq: + * parent + * + * Get the parent Node for this Node + */ +static VALUE +get_parent(VALUE self) +{ + xmlNodePtr node, parent; + Noko_Node_Get_Struct(self, xmlNode, node); + + parent = node->parent; + if (!parent) { return Qnil; } + + return noko_xml_node_wrap(Qnil, parent) ; +} + +/* + * call-seq: + * name=(new_name) + * + * Set the name for this Node + */ +static VALUE +set_name(VALUE self, VALUE new_name) +{ + xmlNodePtr node; + Noko_Node_Get_Struct(self, xmlNode, node); + xmlNodeSetName(node, (xmlChar *)StringValueCStr(new_name)); + return new_name; +} + +/* + * call-seq: + * name + * + * Returns the name for this Node + */ +static VALUE +get_name(VALUE self) +{ + xmlNodePtr node; + Noko_Node_Get_Struct(self, xmlNode, node); + if (node->name) { + return NOKOGIRI_STR_NEW2(node->name); + } + return Qnil; +} + +/* + * call-seq: + * path + * + * Returns the path associated with this Node + */ +static VALUE +rb_xml_node_path(VALUE rb_node) +{ + xmlNodePtr c_node; + xmlChar *c_path ; + VALUE rval; + + Noko_Node_Get_Struct(rb_node, xmlNode, c_node); + + c_path = xmlGetNodePath(c_node); + if (c_path == NULL) { + // see https://github.com/sparklemotion/nokogiri/issues/2250 + // this behavior is clearly undesirable, but is what libxml <= 2.9.10 returned, and so we + // do this for now to preserve the behavior across libxml2 versions. + rval = NOKOGIRI_STR_NEW2("?"); + } else { + rval = NOKOGIRI_STR_NEW2(c_path); + xmlFree(c_path); + } + + return rval ; +} + +/* :nodoc: */ +static VALUE +add_next_sibling(VALUE self, VALUE new_sibling) +{ + return reparent_node_with(self, new_sibling, xmlAddNextSibling) ; +} + +/* :nodoc: */ +static VALUE +add_previous_sibling(VALUE self, VALUE new_sibling) +{ + return reparent_node_with(self, new_sibling, xmlAddPrevSibling) ; +} + +/* + * call-seq: + * native_write_to(io, encoding, options) + * + * Write this Node to +io+ with +encoding+ and +options+ + */ +static VALUE +native_write_to( + VALUE self, + VALUE io, + VALUE encoding, + VALUE indent_string, + VALUE options +) +{ + xmlNodePtr node; + const char *before_indent; + xmlSaveCtxtPtr savectx; + + Noko_Node_Get_Struct(self, xmlNode, node); + + xmlIndentTreeOutput = 1; + + before_indent = xmlTreeIndentString; + + xmlTreeIndentString = StringValueCStr(indent_string); + + savectx = xmlSaveToIO( + (xmlOutputWriteCallback)noko_io_write, + (xmlOutputCloseCallback)noko_io_close, + (void *)io, + RTEST(encoding) ? StringValueCStr(encoding) : NULL, + (int)NUM2INT(options) + ); + + xmlSaveTree(savectx, node); + xmlSaveClose(savectx); + + xmlTreeIndentString = before_indent; + return io; +} + + +static inline void +output_partial_string(VALUE out, char const *str, size_t length) +{ + if (length) { + rb_enc_str_buf_cat(out, str, (long)length, rb_utf8_encoding()); + } +} + +static inline void +output_char(VALUE out, char ch) +{ + output_partial_string(out, &ch, 1); +} + +static inline void +output_string(VALUE out, char const *str) +{ + output_partial_string(out, str, strlen(str)); +} + +static inline void +output_tagname(VALUE out, xmlNodePtr elem) +{ + // Elements in the HTML, MathML, and SVG namespaces do not use a namespace + // prefix in the HTML syntax. + char const *name = (char const *)elem->name; + xmlNsPtr ns = elem->ns; + if (ns && ns->href && ns->prefix + && strcmp((char const *)ns->href, "http://www.w3.org/1999/xhtml") + && strcmp((char const *)ns->href, "http://www.w3.org/1998/Math/MathML") + && strcmp((char const *)ns->href, "http://www.w3.org/2000/svg")) { + output_string(out, (char const *)elem->ns->prefix); + output_char(out, ':'); + char const *colon = strchr(name, ':'); + if (colon) { + name = colon + 1; + } + } + output_string(out, name); +} + +static inline void +output_attr_name(VALUE out, xmlAttrPtr attr) +{ + xmlNsPtr ns = attr->ns; + char const *name = (char const *)attr->name; + if (ns && ns->href) { + char const *uri = (char const *)ns->href; + char const *localname = strchr(name, ':'); + if (localname) { + ++localname; + } else { + localname = name; + } + + if (!strcmp(uri, "http://www.w3.org/XML/1998/namespace")) { + output_string(out, "xml:"); + name = localname; + } else if (!strcmp(uri, "http://www.w3.org/2000/xmlns/")) { + // xmlns:xmlns -> xmlns + // xmlns:foo -> xmlns:foo + if (strcmp(localname, "xmlns")) { + output_string(out, "xmlns:"); + } + name = localname; + } else if (!strcmp(uri, "http://www.w3.org/1999/xlink")) { + output_string(out, "xlink:"); + name = localname; + } else if (ns->prefix) { + output_string(out, (char const *)ns->prefix); + output_char(out, ':'); + name = localname; + } + } + output_string(out, name); +} + +static void +output_escaped_string(VALUE out, xmlChar const *start, bool attr) +{ + xmlChar const *next = start; + int ch; + + while ((ch = *next) != 0) { + char const *replacement = NULL; + size_t replaced_bytes = 1; + if (ch == '&') { + replacement = "&"; + } else if (ch == 0xC2 && next[1] == 0xA0) { + // U+00A0 NO-BREAK SPACE has the UTF-8 encoding C2 A0. + replacement = " "; + replaced_bytes = 2; + } else if (attr && ch == '"') { + replacement = """; + } else if (!attr && ch == '<') { + replacement = "<"; + } else if (!attr && ch == '>') { + replacement = ">"; + } else { + ++next; + continue; + } + output_partial_string(out, (char const *)start, (size_t)(next - start)); + output_string(out, replacement); + next += replaced_bytes; + start = next; + } + output_partial_string(out, (char const *)start, (size_t)(next - start)); +} + +static bool +should_prepend_newline(xmlNodePtr node) +{ + char const *name = (char const *)node->name; + xmlNodePtr child = node->children; + + if (!name || !child || (strcmp(name, "pre") && strcmp(name, "textarea") && strcmp(name, "listing"))) { + return false; + } + + return child->type == XML_TEXT_NODE && child->content && child->content[0] == '\n'; +} + +static VALUE +rb_prepend_newline(VALUE self) +{ + xmlNodePtr node; + Noko_Node_Get_Struct(self, xmlNode, node); + return should_prepend_newline(node) ? Qtrue : Qfalse; +} + +static bool +is_one_of(xmlNodePtr node, char const *const *tagnames, size_t num_tagnames) +{ + char const *name = (char const *)node->name; + if (name == NULL) { // fragments don't have a name + return false; + } + + if (node->ns != NULL) { + // if the node has a namespace, it's in a foreign context and is not one of the HTML tags we're + // matching against. + return false; + } + + for (size_t idx = 0; idx < num_tagnames; ++idx) { + if (!strcmp(name, tagnames[idx])) { + return true; + } + } + return false; +} + +static void +output_node( + VALUE out, + xmlNodePtr node, + bool preserve_newline +) +{ + static char const *const VOID_ELEMENTS[] = { + "area", "base", "basefont", "bgsound", "br", "col", "embed", "frame", "hr", + "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr", + }; + + static char const *const UNESCAPED_TEXT_ELEMENTS[] = { + "style", "script", "xmp", "iframe", "noembed", "noframes", "plaintext", "noscript", + }; + + switch (node->type) { + case XML_ELEMENT_NODE: + // Serialize the start tag. + output_char(out, '<'); + output_tagname(out, node); + + // Add attributes. + for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) { + output_char(out, ' '); + output_node(out, (xmlNodePtr)attr, preserve_newline); + } + output_char(out, '>'); + + // Add children and end tag if element is not void. + if (!is_one_of(node, VOID_ELEMENTS, sizeof VOID_ELEMENTS / sizeof VOID_ELEMENTS[0])) { + if (preserve_newline && should_prepend_newline(node)) { + output_char(out, '\n'); + } + for (xmlNodePtr child = node->children; child; child = child->next) { + output_node(out, child, preserve_newline); + } + output_string(out, "'); + } + break; + + case XML_ATTRIBUTE_NODE: { + xmlAttrPtr attr = (xmlAttrPtr)node; + output_attr_name(out, attr); + if (attr->children) { + output_string(out, "=\""); + xmlChar *value = xmlNodeListGetString(attr->doc, attr->children, 1); + output_escaped_string(out, value, true); + xmlFree(value); + output_char(out, '"'); + } else { + // Output name="" + output_string(out, "=\"\""); + } + } + break; + + case XML_TEXT_NODE: + if (node->parent + && is_one_of(node->parent, UNESCAPED_TEXT_ELEMENTS, + sizeof UNESCAPED_TEXT_ELEMENTS / sizeof UNESCAPED_TEXT_ELEMENTS[0])) { + output_string(out, (char const *)node->content); + } else { + output_escaped_string(out, node->content, false); + } + break; + + case XML_CDATA_SECTION_NODE: + output_string(out, "content); + output_string(out, "]]>"); + break; + + case XML_COMMENT_NODE: + output_string(out, ""); + break; + + case XML_PI_NODE: + output_string(out, "content); + output_char(out, '>'); + break; + + case XML_DOCUMENT_TYPE_NODE: + case XML_DTD_NODE: + output_string(out, "name); + output_string(out, ">"); + break; + + case XML_DOCUMENT_NODE: + case XML_DOCUMENT_FRAG_NODE: + case XML_HTML_DOCUMENT_NODE: + for (xmlNodePtr child = node->children; child; child = child->next) { + output_node(out, child, preserve_newline); + } + break; + + default: + rb_raise(rb_eRuntimeError, "Unsupported document node (%d); this is a bug in Nokogiri", node->type); + break; + } +} + +static VALUE +html_standard_serialize( + VALUE self, + VALUE preserve_newline +) +{ + xmlNodePtr node; + Noko_Node_Get_Struct(self, xmlNode, node); + VALUE output = rb_str_buf_new(4096); + output_node(output, node, RTEST(preserve_newline)); + return output; +} + +/* + * :call-seq: + * line() → Integer + * + * [Returns] The line number of this Node. + * + * --- + * + * ⚠ The CRuby and JRuby implementations differ in important ways! + * + * Semantic differences: + * - The CRuby method reflects the node's line number in the parsed string + * - The JRuby method reflects the node's line number in the final DOM structure after + * corrections have been applied + * + * Performance differences: + * - The CRuby method is {O(1)}[https://en.wikipedia.org/wiki/Time_complexity#Constant_time] + * (constant time) + * - The JRuby method is {O(n)}[https://en.wikipedia.org/wiki/Time_complexity#Linear_time] (linear + * time, where n is the number of nodes before/above the element in the DOM) + * + * If you'd like to help improve the JRuby implementation, please review these issues and reach out + * to the maintainers: + * - https://github.com/sparklemotion/nokogiri/issues/1223 + * - https://github.com/sparklemotion/nokogiri/pull/2177 + * - https://github.com/sparklemotion/nokogiri/issues/2380 + */ +static VALUE +rb_xml_node_line(VALUE rb_node) +{ + xmlNodePtr c_node; + Noko_Node_Get_Struct(rb_node, xmlNode, c_node); + + return LONG2NUM(xmlGetLineNo(c_node)); +} + +/* + * call-seq: + * line=(num) + * + * Sets the line for this Node. num must be less than 65535. + */ +static VALUE +rb_xml_node_line_set(VALUE rb_node, VALUE rb_line_number) +{ + xmlNodePtr c_node; + int line_number = NUM2INT(rb_line_number); + + Noko_Node_Get_Struct(rb_node, xmlNode, c_node); + + // libxml2 optionally uses xmlNode.psvi to store longer line numbers, but only for text nodes. + // search for "psvi" in SAX2.c and tree.c to learn more. + if (line_number < 65535) { + c_node->line = (short unsigned)line_number; + } else { + c_node->line = 65535; + if (c_node->type == XML_TEXT_NODE) { + c_node->psvi = (void *)(ptrdiff_t)line_number; + } + } + + return rb_line_number; +} + +/* :nodoc: documented in lib/nokogiri/xml/node.rb */ +static VALUE +rb_xml_node_new(int argc, VALUE *argv, VALUE klass) +{ + xmlNodePtr c_document_node; + xmlNodePtr c_node; + VALUE rb_name; + VALUE rb_document_node; + VALUE rest; + VALUE rb_node; + + rb_scan_args(argc, argv, "2*", &rb_name, &rb_document_node, &rest); + + if (!rb_obj_is_kind_of(rb_document_node, cNokogiriXmlNode)) { + rb_raise(rb_eArgError, "document must be a Nokogiri::XML::Node"); + } + if (!rb_obj_is_kind_of(rb_document_node, cNokogiriXmlDocument)) { + NOKO_WARN_DEPRECATION("Passing a Node as the second parameter to Node.new is deprecated. Please pass a Document instead, or prefer an alternative constructor like Node#add_child. This will become an error in Nokogiri v1.17.0."); // TODO: deprecated in v1.13.0, remove in v1.17.0 + } + Noko_Node_Get_Struct(rb_document_node, xmlNode, c_document_node); + + c_node = xmlNewNode(NULL, (xmlChar *)StringValueCStr(rb_name)); + c_node->doc = c_document_node->doc; + noko_xml_document_pin_node(c_node); + + rb_node = noko_xml_node_wrap( + klass == cNokogiriXmlNode ? (VALUE)NULL : klass, + c_node + ); + rb_obj_call_init(rb_node, argc, argv); + + if (rb_block_given_p()) { rb_yield(rb_node); } + + return rb_node; +} + +/* + * call-seq: + * dump_html + * + * Returns the Node as html. + */ +static VALUE +dump_html(VALUE self) +{ + xmlBufferPtr buf ; + xmlNodePtr node ; + VALUE html; + + Noko_Node_Get_Struct(self, xmlNode, node); + + buf = xmlBufferCreate() ; + htmlNodeDump(buf, node->doc, node); + html = NOKOGIRI_STR_NEW2(xmlBufferContent(buf)); + xmlBufferFree(buf); + return html ; +} + +/* + * call-seq: + * compare(other) + * + * Compare this Node to +other+ with respect to their Document + */ +static VALUE +compare(VALUE self, VALUE _other) +{ + xmlNodePtr node, other; + Noko_Node_Get_Struct(self, xmlNode, node); + Noko_Node_Get_Struct(_other, xmlNode, other); + + return INT2NUM(xmlXPathCmpNodes(other, node)); +} + + +/* + * call-seq: + * process_xincludes(flags) + * + * Loads and substitutes all xinclude elements below the node. The + * parser context will be initialized with +flags+. + */ +static VALUE +noko_xml_node__process_xincludes(VALUE rb_node, VALUE rb_flags) +{ + int status ; + xmlNodePtr c_node; + VALUE rb_errors = rb_ary_new(); + libxmlStructuredErrorHandlerState handler_state; + + Noko_Node_Get_Struct(rb_node, xmlNode, c_node); + + noko__structured_error_func_save_and_set(&handler_state, (void *)rb_errors, noko__error_array_pusher); + + status = xmlXIncludeProcessTreeFlags(c_node, (int)NUM2INT(rb_flags)); + + noko__structured_error_func_restore(&handler_state); + + if (status < 0) { + VALUE exception = rb_funcall(cNokogiriXmlSyntaxError, rb_intern("aggregate"), 1, rb_errors); + + if (RB_TEST(exception)) { + rb_exc_raise(exception); + } else { + rb_raise(rb_eRuntimeError, "Could not perform xinclude substitution"); + } + } + + return rb_node; +} + + +/* TODO: DOCUMENT ME */ +static VALUE +in_context(VALUE self, VALUE _str, VALUE _options) +{ + xmlNodePtr node, list = 0, tmp, child_iter, node_children, doc_children; + xmlNodeSetPtr set; + xmlParserErrors error; + VALUE doc, err; + int doc_is_empty; + + Noko_Node_Get_Struct(self, xmlNode, node); + + doc = DOC_RUBY_OBJECT(node->doc); + err = rb_iv_get(doc, "@errors"); + doc_is_empty = (node->doc->children == NULL) ? 1 : 0; + node_children = node->children; + doc_children = node->doc->children; + + xmlSetStructuredErrorFunc((void *)err, noko__error_array_pusher); + + /* This function adds a fake node to the child of +node+. If the parser + * does not exit cleanly with XML_ERR_OK, the list is freed. This can + * leave the child pointers in a bad state if they were originally empty. + * + * http://git.gnome.org/browse/libxml2/tree/parser.c#n13177 + * */ + error = xmlParseInNodeContext(node, StringValuePtr(_str), + (int)RSTRING_LEN(_str), + (int)NUM2INT(_options), &list); + + /* xmlParseInNodeContext should not mutate the original document or node, + * so reassigning these pointers should be OK. The reason we're reassigning + * is because if there were errors, it's possible for the child pointers + * to be manipulated. */ + if (error != XML_ERR_OK) { + node->doc->children = doc_children; + node->children = node_children; + } + + /* make sure parent/child pointers are coherent so an unlink will work + * properly (#331) + */ + child_iter = node->doc->children ; + while (child_iter) { + child_iter->parent = (xmlNodePtr)node->doc; + child_iter = child_iter->next; + } + + xmlSetStructuredErrorFunc(NULL, NULL); + + /* + * Workaround for a libxml2 bug where a parsing error may leave a broken + * node reference in node->doc->children. + * + * https://bugzilla.gnome.org/show_bug.cgi?id=668155 + * + * This workaround is limited to when a parse error occurs, the document + * went from having no children to having children, and the context node is + * part of a document fragment. + * + * TODO: This was fixed in libxml 2.8.0 by 71a243d + */ + if (error != XML_ERR_OK && doc_is_empty && node->doc->children != NULL) { + child_iter = node; + while (child_iter->parent) { + child_iter = child_iter->parent; + } + + if (child_iter->type == XML_DOCUMENT_FRAG_NODE) { + node->doc->children = NULL; + } + } + + /* FIXME: This probably needs to handle more constants... */ + switch (error) { + case XML_ERR_INTERNAL_ERROR: + case XML_ERR_NO_MEMORY: + rb_raise(rb_eRuntimeError, "error parsing fragment (%d)", error); + break; + default: + break; + } + + set = xmlXPathNodeSetCreate(NULL); + + while (list) { + tmp = list->next; + list->next = NULL; + xmlXPathNodeSetAddUnique(set, list); + noko_xml_document_pin_node(list); + list = tmp; + } + + return noko_xml_node_set_wrap(set, doc); +} + +/* :nodoc: */ +VALUE +rb_xml_node_data_ptr_eh(VALUE self) +{ + xmlNodePtr c_node; + Noko_Node_Get_Struct(self, xmlNode, c_node); + return c_node ? Qtrue : Qfalse; +} + +VALUE +noko_xml_node_wrap(VALUE rb_class, xmlNodePtr c_node) +{ + VALUE rb_document, rb_node_cache, rb_node; + nokogiriTuplePtr node_has_a_document; + xmlDocPtr c_doc; + + assert(c_node); + + if (c_node->type == XML_DOCUMENT_NODE || c_node->type == XML_HTML_DOCUMENT_NODE) { + return DOC_RUBY_OBJECT(c_node->doc); + } + + c_doc = c_node->doc; + + // Nodes yielded from XML::Reader don't have a fully-realized Document + node_has_a_document = DOC_RUBY_OBJECT_TEST(c_doc); + + if (c_node->_private && node_has_a_document) { + return (VALUE)c_node->_private; + } + + if (!RTEST(rb_class)) { + switch (c_node->type) { + case XML_ELEMENT_NODE: + rb_class = cNokogiriXmlElement; + break; + case XML_TEXT_NODE: + rb_class = cNokogiriXmlText; + break; + case XML_ATTRIBUTE_NODE: + rb_class = cNokogiriXmlAttr; + break; + case XML_ENTITY_REF_NODE: + rb_class = cNokogiriXmlEntityReference; + break; + case XML_COMMENT_NODE: + rb_class = cNokogiriXmlComment; + break; + case XML_DOCUMENT_FRAG_NODE: + rb_class = cNokogiriXmlDocumentFragment; + break; + case XML_PI_NODE: + rb_class = cNokogiriXmlProcessingInstruction; + break; + case XML_ENTITY_DECL: + rb_class = cNokogiriXmlEntityDecl; + break; + case XML_CDATA_SECTION_NODE: + rb_class = cNokogiriXmlCData; + break; + case XML_DTD_NODE: + rb_class = cNokogiriXmlDtd; + break; + case XML_ATTRIBUTE_DECL: + rb_class = cNokogiriXmlAttributeDecl; + break; + case XML_ELEMENT_DECL: + rb_class = cNokogiriXmlElementDecl; + break; + default: + rb_class = cNokogiriXmlNode; + } + } + + rb_node = _xml_node_alloc(rb_class); + _xml_node_data_ptr_set(rb_node, c_node); + + if (node_has_a_document) { + rb_document = DOC_RUBY_OBJECT(c_doc); + rb_node_cache = DOC_NODE_CACHE(c_doc); + rb_ary_push(rb_node_cache, rb_node); + rb_funcall(rb_document, id_decorate, 1, rb_node); + } + + return rb_node ; +} + + +/* + * return Array containing the node's attributes + */ +VALUE +noko_xml_node_attrs(xmlNodePtr c_node) +{ + VALUE rb_properties = rb_ary_new(); + xmlAttrPtr c_property; + + c_property = c_node->properties ; + while (c_property != NULL) { + rb_ary_push(rb_properties, noko_xml_node_wrap(Qnil, (xmlNodePtr)c_property)); + c_property = c_property->next ; + } + + return rb_properties; +} + +void +noko_init_xml_node(void) +{ + cNokogiriXmlNode = rb_define_class_under(mNokogiriXml, "Node", rb_cObject); + + rb_define_alloc_func(cNokogiriXmlNode, _xml_node_alloc); + + rb_define_singleton_method(cNokogiriXmlNode, "new", rb_xml_node_new, -1); + + rb_define_method(cNokogiriXmlNode, "add_namespace_definition", rb_xml_node_add_namespace_definition, 2); + rb_define_method(cNokogiriXmlNode, "attribute", rb_xml_node_attribute, 1); + rb_define_method(cNokogiriXmlNode, "attribute_nodes", rb_xml_node_attribute_nodes, 0); + rb_define_method(cNokogiriXmlNode, "attribute_with_ns", rb_xml_node_attribute_with_ns, 2); + rb_define_method(cNokogiriXmlNode, "blank?", rb_xml_node_blank_eh, 0); + rb_define_method(cNokogiriXmlNode, "child", rb_xml_node_child, 0); + rb_define_method(cNokogiriXmlNode, "children", rb_xml_node_children, 0); + rb_define_method(cNokogiriXmlNode, "content", rb_xml_node_content, 0); + rb_define_method(cNokogiriXmlNode, "create_external_subset", create_external_subset, 3); + rb_define_method(cNokogiriXmlNode, "create_internal_subset", create_internal_subset, 3); + rb_define_method(cNokogiriXmlNode, "data_ptr?", rb_xml_node_data_ptr_eh, 0); + rb_define_method(cNokogiriXmlNode, "document", rb_xml_node_document, 0); + rb_define_method(cNokogiriXmlNode, "element_children", rb_xml_node_element_children, 0); + rb_define_method(cNokogiriXmlNode, "encode_special_chars", encode_special_chars, 1); + rb_define_method(cNokogiriXmlNode, "external_subset", external_subset, 0); + rb_define_method(cNokogiriXmlNode, "first_element_child", rb_xml_node_first_element_child, 0); + rb_define_method(cNokogiriXmlNode, "internal_subset", internal_subset, 0); + rb_define_method(cNokogiriXmlNode, "key?", key_eh, 1); + rb_define_method(cNokogiriXmlNode, "lang", get_lang, 0); + rb_define_method(cNokogiriXmlNode, "lang=", set_lang, 1); + rb_define_method(cNokogiriXmlNode, "last_element_child", rb_xml_node_last_element_child, 0); + rb_define_method(cNokogiriXmlNode, "line", rb_xml_node_line, 0); + rb_define_method(cNokogiriXmlNode, "line=", rb_xml_node_line_set, 1); + rb_define_method(cNokogiriXmlNode, "namespace", rb_xml_node_namespace, 0); + rb_define_method(cNokogiriXmlNode, "namespace_definitions", namespace_definitions, 0); + rb_define_method(cNokogiriXmlNode, "namespace_scopes", rb_xml_node_namespace_scopes, 0); + rb_define_method(cNokogiriXmlNode, "namespaced_key?", namespaced_key_eh, 2); + rb_define_method(cNokogiriXmlNode, "native_content=", set_native_content, 1); + rb_define_method(cNokogiriXmlNode, "next_element", next_element, 0); + rb_define_method(cNokogiriXmlNode, "next_sibling", next_sibling, 0); + rb_define_method(cNokogiriXmlNode, "node_name", get_name, 0); + rb_define_method(cNokogiriXmlNode, "node_name=", set_name, 1); + rb_define_method(cNokogiriXmlNode, "node_type", node_type, 0); + rb_define_method(cNokogiriXmlNode, "parent", get_parent, 0); + rb_define_method(cNokogiriXmlNode, "path", rb_xml_node_path, 0); + rb_define_method(cNokogiriXmlNode, "pointer_id", rb_xml_node_pointer_id, 0); + rb_define_method(cNokogiriXmlNode, "previous_element", previous_element, 0); + rb_define_method(cNokogiriXmlNode, "previous_sibling", previous_sibling, 0); + rb_define_method(cNokogiriXmlNode, "unlink", unlink_node, 0); + + rb_define_protected_method(cNokogiriXmlNode, "initialize_copy_with_args", rb_xml_node_initialize_copy_with_args, 3); + + rb_define_private_method(cNokogiriXmlNode, "add_child_node", add_child, 1); + rb_define_private_method(cNokogiriXmlNode, "add_next_sibling_node", add_next_sibling, 1); + rb_define_private_method(cNokogiriXmlNode, "add_previous_sibling_node", add_previous_sibling, 1); + rb_define_private_method(cNokogiriXmlNode, "compare", compare, 1); + rb_define_private_method(cNokogiriXmlNode, "dump_html", dump_html, 0); + rb_define_private_method(cNokogiriXmlNode, "get", get, 1); + rb_define_private_method(cNokogiriXmlNode, "in_context", in_context, 2); + rb_define_private_method(cNokogiriXmlNode, "native_write_to", native_write_to, 4); + rb_define_private_method(cNokogiriXmlNode, "prepend_newline?", rb_prepend_newline, 0); + rb_define_private_method(cNokogiriXmlNode, "html_standard_serialize", html_standard_serialize, 1); + rb_define_private_method(cNokogiriXmlNode, "process_xincludes", noko_xml_node__process_xincludes, 1); + rb_define_private_method(cNokogiriXmlNode, "replace_node", replace, 1); + rb_define_private_method(cNokogiriXmlNode, "set", set, 2); + rb_define_private_method(cNokogiriXmlNode, "set_namespace", set_namespace, 1); + + id_decorate = rb_intern("decorate"); + id_decorate_bang = rb_intern("decorate!"); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_node_set.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_node_set.c new file mode 100644 index 00000000..de1beeb5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_node_set.c @@ -0,0 +1,518 @@ +#include + +VALUE cNokogiriXmlNodeSet ; + +static ID decorate ; + +static void +Check_Node_Set_Node_Type(VALUE node) +{ + if (!(rb_obj_is_kind_of(node, cNokogiriXmlNode) || + rb_obj_is_kind_of(node, cNokogiriXmlNamespace))) { + rb_raise(rb_eArgError, "node must be a Nokogiri::XML::Node or Nokogiri::XML::Namespace"); + } +} + + +static +VALUE +ruby_object_get(xmlNodePtr c_node) +{ + /* see xmlElementType in libxml2 tree.h */ + switch (c_node->type) { + case XML_NAMESPACE_DECL: + /* _private is later in the namespace struct */ + return (VALUE)(((xmlNsPtr)c_node)->_private); + + case XML_DOCUMENT_NODE: + case XML_HTML_DOCUMENT_NODE: + /* in documents we use _private to store a tuple */ + if (DOC_RUBY_OBJECT_TEST(((xmlDocPtr)c_node))) { + return DOC_RUBY_OBJECT((xmlDocPtr)c_node); + } + return (VALUE)NULL; + + default: + return (VALUE)(c_node->_private); + } +} + + +static void +xml_node_set_mark(void *data) +{ + xmlNodeSetPtr node_set = data; + VALUE rb_node; + int jnode; + + for (jnode = 0; jnode < node_set->nodeNr; jnode++) { + rb_node = ruby_object_get(node_set->nodeTab[jnode]); + if (rb_node) { + rb_gc_mark(rb_node); + } + } +} + +static void +xml_node_set_deallocate(void *data) +{ + xmlNodeSetPtr node_set = data; + /* + * For reasons outlined in xml_namespace.c, here we reproduce xmlXPathFreeNodeSet() except for the + * offending call to xmlXPathNodeSetFreeNs(). + */ + if (node_set->nodeTab != NULL) { + xmlFree(node_set->nodeTab); + } + + xmlFree(node_set); +} + +static const rb_data_type_t xml_node_set_type = { + .wrap_struct_name = "xmlNodeSet", + .function = { + .dmark = xml_node_set_mark, + .dfree = xml_node_set_deallocate, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY, +}; + +static VALUE +xml_node_set_allocate(VALUE klass) +{ + return TypedData_Wrap_Struct(klass, &xml_node_set_type, xmlXPathNodeSetCreate(NULL)); +} + +/* :nodoc: */ +static VALUE +rb_xml_node_set_initialize_copy(VALUE rb_self, VALUE rb_other) +{ + xmlNodeSetPtr c_self, c_other; + VALUE rb_document; + + TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self); + TypedData_Get_Struct(rb_other, xmlNodeSet, &xml_node_set_type, c_other); + + xmlXPathNodeSetMerge(c_self, c_other); + + rb_document = rb_iv_get(rb_other, "@document"); + if (!NIL_P(rb_document)) { + rb_iv_set(rb_self, "@document", rb_document); + rb_funcall(rb_document, decorate, 1, rb_self); + } + + return rb_self; +} + +static void +xpath_node_set_del(xmlNodeSetPtr cur, xmlNodePtr val) +{ + /* + * For reasons outlined in xml_namespace.c, here we reproduce xmlXPathNodeSetDel() except for the + * offending call to xmlXPathNodeSetFreeNs(). + */ + int i; + + if (cur == NULL) { return; } + if (val == NULL) { return; } + + /* + * find node in nodeTab + */ + for (i = 0; i < cur->nodeNr; i++) + if (cur->nodeTab[i] == val) { break; } + + if (i >= cur->nodeNr) { /* not found */ + return; + } + cur->nodeNr--; + for (; i < cur->nodeNr; i++) { + cur->nodeTab[i] = cur->nodeTab[i + 1]; + } + cur->nodeTab[cur->nodeNr] = NULL; +} + +/* + * call-seq: + * length + * + * Get the length of the node set + */ +static VALUE +length(VALUE rb_self) +{ + xmlNodeSetPtr c_self; + + TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self); + + return c_self ? INT2NUM(c_self->nodeNr) : INT2NUM(0); +} + +/* + * call-seq: + * push(node) + * + * Append +node+ to the NodeSet. + */ +static VALUE +push(VALUE rb_self, VALUE rb_node) +{ + xmlNodeSetPtr c_self; + xmlNodePtr node; + + Check_Node_Set_Node_Type(rb_node); + + TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self); + Noko_Node_Get_Struct(rb_node, xmlNode, node); + + xmlXPathNodeSetAdd(c_self, node); + + return rb_self; +} + +/* + * call-seq: + * delete(node) + * + * Delete +node+ from the Nodeset, if it is a member. Returns the deleted node + * if found, otherwise returns nil. + */ +static VALUE +delete (VALUE rb_self, VALUE rb_node) +{ + xmlNodeSetPtr c_self; + xmlNodePtr node; + + Check_Node_Set_Node_Type(rb_node); + + TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self); + Noko_Node_Get_Struct(rb_node, xmlNode, node); + + if (xmlXPathNodeSetContains(c_self, node)) { + xpath_node_set_del(c_self, node); + return rb_node; + } + return Qnil ; +} + + +/* + * call-seq: + * &(node_set) + * + * Set Intersection — Returns a new NodeSet containing nodes common to the two NodeSets. + */ +static VALUE +intersection(VALUE rb_self, VALUE rb_other) +{ + xmlNodeSetPtr c_self, c_other ; + xmlNodeSetPtr intersection; + + if (!rb_obj_is_kind_of(rb_other, cNokogiriXmlNodeSet)) { + rb_raise(rb_eArgError, "node_set must be a Nokogiri::XML::NodeSet"); + } + + TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self); + TypedData_Get_Struct(rb_other, xmlNodeSet, &xml_node_set_type, c_other); + + intersection = xmlXPathIntersection(c_self, c_other); + return noko_xml_node_set_wrap(intersection, rb_iv_get(rb_self, "@document")); +} + + +/* + * call-seq: + * include?(node) + * + * Returns true if any member of node set equals +node+. + */ +static VALUE +include_eh(VALUE rb_self, VALUE rb_node) +{ + xmlNodeSetPtr c_self; + xmlNodePtr node; + + Check_Node_Set_Node_Type(rb_node); + + TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self); + Noko_Node_Get_Struct(rb_node, xmlNode, node); + + return (xmlXPathNodeSetContains(c_self, node) ? Qtrue : Qfalse); +} + + +/* + * call-seq: + * |(node_set) + * + * Returns a new set built by merging the set and the elements of the given + * set. + */ +static VALUE +rb_xml_node_set_union(VALUE rb_self, VALUE rb_other) +{ + xmlNodeSetPtr c_self, c_other; + xmlNodeSetPtr c_new_node_set; + + if (!rb_obj_is_kind_of(rb_other, cNokogiriXmlNodeSet)) { + rb_raise(rb_eArgError, "node_set must be a Nokogiri::XML::NodeSet"); + } + + TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self); + TypedData_Get_Struct(rb_other, xmlNodeSet, &xml_node_set_type, c_other); + + c_new_node_set = xmlXPathNodeSetMerge(NULL, c_self); + c_new_node_set = xmlXPathNodeSetMerge(c_new_node_set, c_other); + + return noko_xml_node_set_wrap(c_new_node_set, rb_iv_get(rb_self, "@document")); +} + +/* + * call-seq: + * -(node_set) + * + * Difference - returns a new NodeSet that is a copy of this NodeSet, removing + * each item that also appears in +node_set+ + */ +static VALUE +minus(VALUE rb_self, VALUE rb_other) +{ + xmlNodeSetPtr c_self, c_other; + xmlNodeSetPtr new; + int j ; + + if (!rb_obj_is_kind_of(rb_other, cNokogiriXmlNodeSet)) { + rb_raise(rb_eArgError, "node_set must be a Nokogiri::XML::NodeSet"); + } + + TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self); + TypedData_Get_Struct(rb_other, xmlNodeSet, &xml_node_set_type, c_other); + + new = xmlXPathNodeSetMerge(NULL, c_self); + for (j = 0 ; j < c_other->nodeNr ; ++j) { + xpath_node_set_del(new, c_other->nodeTab[j]); + } + + return noko_xml_node_set_wrap(new, rb_iv_get(rb_self, "@document")); +} + + +static VALUE +index_at(VALUE rb_self, long offset) +{ + xmlNodeSetPtr c_self; + + TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self); + + if (offset >= c_self->nodeNr || abs((int)offset) > c_self->nodeNr) { + return Qnil; + } + + if (offset < 0) { offset += c_self->nodeNr ; } + + return noko_xml_node_wrap_node_set_result(c_self->nodeTab[offset], rb_self); +} + +static VALUE +subseq(VALUE rb_self, long beg, long len) +{ + long j; + xmlNodeSetPtr c_self; + xmlNodeSetPtr new_set ; + + TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self); + + if (beg > c_self->nodeNr) { return Qnil ; } + if (beg < 0 || len < 0) { return Qnil ; } + + if ((beg + len) > c_self->nodeNr) { + len = c_self->nodeNr - beg ; + } + + new_set = xmlXPathNodeSetCreate(NULL); + for (j = beg ; j < beg + len ; ++j) { + xmlXPathNodeSetAddUnique(new_set, c_self->nodeTab[j]); + } + return noko_xml_node_set_wrap(new_set, rb_iv_get(rb_self, "@document")); +} + +/* + * call-seq: + * [index] -> Node or nil + * [start, length] -> NodeSet or nil + * [range] -> NodeSet or nil + * slice(index) -> Node or nil + * slice(start, length) -> NodeSet or nil + * slice(range) -> NodeSet or nil + * + * Element reference - returns the node at +index+, or returns a NodeSet + * containing nodes starting at +start+ and continuing for +length+ elements, or + * returns a NodeSet containing nodes specified by +range+. Negative +indices+ + * count backward from the end of the +node_set+ (-1 is the last node). Returns + * nil if the +index+ (or +start+) are out of range. + */ +static VALUE +slice(int argc, VALUE *argv, VALUE rb_self) +{ + VALUE arg ; + long beg, len ; + xmlNodeSetPtr c_self; + + TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self); + + if (argc == 2) { + beg = NUM2LONG(argv[0]); + len = NUM2LONG(argv[1]); + if (beg < 0) { + beg += c_self->nodeNr ; + } + return subseq(rb_self, beg, len); + } + + if (argc != 1) { + rb_scan_args(argc, argv, "11", NULL, NULL); + } + arg = argv[0]; + + if (FIXNUM_P(arg)) { + return index_at(rb_self, FIX2LONG(arg)); + } + + /* if arg is Range */ + switch (rb_range_beg_len(arg, &beg, &len, (long)c_self->nodeNr, 0)) { + case Qfalse: + break; + case Qnil: + return Qnil; + default: + return subseq(rb_self, beg, len); + } + + return index_at(rb_self, NUM2LONG(arg)); +} + + +/* + * call-seq: + * to_a + * + * Return this list as an Array + */ +static VALUE +to_array(VALUE rb_self) +{ + xmlNodeSetPtr c_self ; + VALUE list; + int i; + + TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self); + + list = rb_ary_new2(c_self->nodeNr); + for (i = 0; i < c_self->nodeNr; i++) { + VALUE elt = noko_xml_node_wrap_node_set_result(c_self->nodeTab[i], rb_self); + rb_ary_push(list, elt); + } + + return list; +} + +/* + * call-seq: + * unlink + * + * Unlink this NodeSet and all Node objects it contains from their current context. + */ +static VALUE +unlink_nodeset(VALUE rb_self) +{ + xmlNodeSetPtr c_self; + int j, nodeNr ; + + TypedData_Get_Struct(rb_self, xmlNodeSet, &xml_node_set_type, c_self); + + nodeNr = c_self->nodeNr ; + for (j = 0 ; j < nodeNr ; j++) { + if (! NOKOGIRI_NAMESPACE_EH(c_self->nodeTab[j])) { + VALUE node ; + xmlNodePtr node_ptr; + node = noko_xml_node_wrap(Qnil, c_self->nodeTab[j]); + rb_funcall(node, rb_intern("unlink"), 0); /* modifies the C struct out from under the object */ + Noko_Node_Get_Struct(node, xmlNode, node_ptr); + c_self->nodeTab[j] = node_ptr ; + } + } + return rb_self ; +} + + +VALUE +noko_xml_node_set_wrap(xmlNodeSetPtr c_node_set, VALUE document) +{ + int j; + VALUE rb_node_set ; + + if (c_node_set == NULL) { + rb_node_set = xml_node_set_allocate(cNokogiriXmlNodeSet); + } else { + rb_node_set = TypedData_Wrap_Struct(cNokogiriXmlNodeSet, &xml_node_set_type, c_node_set); + } + + if (!NIL_P(document)) { + rb_iv_set(rb_node_set, "@document", document); + rb_funcall(document, decorate, 1, rb_node_set); + } + + if (c_node_set) { + /* create ruby objects for all the results, so they'll be marked during the GC mark phase */ + for (j = 0 ; j < c_node_set->nodeNr ; j++) { + noko_xml_node_wrap_node_set_result(c_node_set->nodeTab[j], rb_node_set); + } + } + + return rb_node_set ; +} + + +VALUE +noko_xml_node_wrap_node_set_result(xmlNodePtr node, VALUE node_set) +{ + if (NOKOGIRI_NAMESPACE_EH(node)) { + return noko_xml_namespace_wrap_xpath_copy((xmlNsPtr)node); + } else { + return noko_xml_node_wrap(Qnil, node); + } +} + + +xmlNodeSetPtr +noko_xml_node_set_unwrap(VALUE rb_node_set) +{ + xmlNodeSetPtr c_node_set; + TypedData_Get_Struct(rb_node_set, xmlNodeSet, &xml_node_set_type, c_node_set); + return c_node_set; +} + + +void +noko_init_xml_node_set(void) +{ + cNokogiriXmlNodeSet = rb_define_class_under(mNokogiriXml, "NodeSet", rb_cObject); + + rb_define_alloc_func(cNokogiriXmlNodeSet, xml_node_set_allocate); + + rb_define_method(cNokogiriXmlNodeSet, "&", intersection, 1); + rb_define_method(cNokogiriXmlNodeSet, "-", minus, 1); + rb_define_method(cNokogiriXmlNodeSet, "[]", slice, -1); + rb_define_method(cNokogiriXmlNodeSet, "delete", delete, 1); + rb_define_method(cNokogiriXmlNodeSet, "include?", include_eh, 1); + rb_define_method(cNokogiriXmlNodeSet, "length", length, 0); + rb_define_method(cNokogiriXmlNodeSet, "push", push, 1); + rb_define_method(cNokogiriXmlNodeSet, "slice", slice, -1); + rb_define_method(cNokogiriXmlNodeSet, "to_a", to_array, 0); + rb_define_method(cNokogiriXmlNodeSet, "unlink", unlink_nodeset, 0); + rb_define_method(cNokogiriXmlNodeSet, "|", rb_xml_node_set_union, 1); + + rb_define_private_method(cNokogiriXmlNodeSet, "initialize_copy", rb_xml_node_set_initialize_copy, 1); + + decorate = rb_intern("decorate"); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_processing_instruction.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_processing_instruction.c new file mode 100644 index 00000000..6bcf15f1 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_processing_instruction.c @@ -0,0 +1,54 @@ +#include + +VALUE cNokogiriXmlProcessingInstruction; + +/* + * call-seq: + * new(document, name, content) + * + * Create a new ProcessingInstruction element on the +document+ with +name+ + * and +content+ + */ +static VALUE +new (int argc, VALUE *argv, VALUE klass) +{ + xmlDocPtr xml_doc; + xmlNodePtr node; + VALUE document; + VALUE name; + VALUE content; + VALUE rest; + VALUE rb_node; + + rb_scan_args(argc, argv, "3*", &document, &name, &content, &rest); + + xml_doc = noko_xml_document_unwrap(document); + + node = xmlNewDocPI( + xml_doc, + (const xmlChar *)StringValueCStr(name), + (const xmlChar *)StringValueCStr(content) + ); + + noko_xml_document_pin_node(node); + + rb_node = noko_xml_node_wrap(klass, node); + rb_obj_call_init(rb_node, argc, argv); + + if (rb_block_given_p()) { rb_yield(rb_node); } + + return rb_node; +} + +void +noko_init_xml_processing_instruction(void) +{ + assert(cNokogiriXmlNode); + /* + * ProcessingInstruction represents a ProcessingInstruction node in an xml + * document. + */ + cNokogiriXmlProcessingInstruction = rb_define_class_under(mNokogiriXml, "ProcessingInstruction", cNokogiriXmlNode); + + rb_define_singleton_method(cNokogiriXmlProcessingInstruction, "new", new, -1); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_reader.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_reader.c new file mode 100644 index 00000000..aa857493 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_reader.c @@ -0,0 +1,777 @@ +#include + +VALUE cNokogiriXmlReader; + +static void +xml_reader_deallocate(void *data) +{ + // free the document separately because we _may_ have triggered preservation by calling + // xmlTextReaderCurrentDoc during a read_more. + xmlTextReaderPtr reader = data; + xmlDocPtr doc = xmlTextReaderCurrentDoc(reader); + xmlFreeTextReader(reader); + if (doc) { + xmlFreeDoc(doc); + } +} + +static const rb_data_type_t xml_text_reader_type = { + .wrap_struct_name = "xmlTextReader", + .function = { + .dfree = xml_reader_deallocate, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; + +static int +has_attributes(xmlTextReaderPtr reader) +{ + /* + * this implementation of xmlTextReaderHasAttributes explicitly includes + * namespaces and properties, because some earlier versions ignore + * namespaces. + */ + xmlNodePtr node ; + node = xmlTextReaderCurrentNode(reader); + if (node == NULL) { + return (0); + } + + if ((node->type == XML_ELEMENT_NODE) && + ((node->properties != NULL) || (node->nsDef != NULL))) { + return (1); + } + return (0); +} + +// TODO: merge this function into the `namespaces` method implementation +static void +Nokogiri_xml_node_namespaces(xmlNodePtr node, VALUE attr_hash) +{ + xmlNsPtr ns; + VALUE key; + + if (node->type != XML_ELEMENT_NODE) { return ; } + + ns = node->nsDef; + while (ns != NULL) { + + key = rb_enc_str_new_cstr(XMLNS_PREFIX, rb_utf8_encoding()); + if (ns->prefix) { + rb_str_cat_cstr(key, ":"); + rb_str_cat_cstr(key, (const char *)ns->prefix); + } + + key = rb_str_conv_enc(key, rb_utf8_encoding(), rb_default_internal_encoding()); + rb_hash_aset(attr_hash, + key, + (ns->href ? NOKOGIRI_STR_NEW2(ns->href) : Qnil) + ); + ns = ns->next ; + } +} + + +/* + * call-seq: + * default? + * + * Was an attribute generated from the default value in the DTD or schema? + */ +static VALUE +default_eh(VALUE self) +{ + xmlTextReaderPtr reader; + int eh; + + TypedData_Get_Struct(self, xmlTextReader, &xml_text_reader_type, reader); + eh = xmlTextReaderIsDefault(reader); + if (eh == 0) { return Qfalse; } + if (eh == 1) { return Qtrue; } + + return Qnil; +} + +/* + * call-seq: + * value? + * + * Does this node have a text value? + */ +static VALUE +value_eh(VALUE self) +{ + xmlTextReaderPtr reader; + int eh; + + TypedData_Get_Struct(self, xmlTextReader, &xml_text_reader_type, reader); + eh = xmlTextReaderHasValue(reader); + if (eh == 0) { return Qfalse; } + if (eh == 1) { return Qtrue; } + + return Qnil; +} + +/* + * call-seq: + * attributes? + * + * Does this node have attributes? + */ +static VALUE +attributes_eh(VALUE self) +{ + xmlTextReaderPtr reader; + int eh; + + TypedData_Get_Struct(self, xmlTextReader, &xml_text_reader_type, reader); + eh = has_attributes(reader); + if (eh == 0) { return Qfalse; } + if (eh == 1) { return Qtrue; } + + return Qnil; +} + +/* + * call-seq: + * namespaces + * + * Get a hash of namespaces for this Node + */ +static VALUE +rb_xml_reader_namespaces(VALUE rb_reader) +{ + VALUE rb_namespaces = rb_hash_new() ; + xmlTextReaderPtr c_reader; + xmlNodePtr c_node; + VALUE rb_errors; + + TypedData_Get_Struct(rb_reader, xmlTextReader, &xml_text_reader_type, c_reader); + + if (! has_attributes(c_reader)) { + return rb_namespaces ; + } + + rb_errors = rb_funcall(rb_reader, rb_intern("errors"), 0); + + xmlSetStructuredErrorFunc((void *)rb_errors, noko__error_array_pusher); + c_node = xmlTextReaderExpand(c_reader); + xmlSetStructuredErrorFunc(NULL, NULL); + + if (c_node == NULL) { + if (RARRAY_LEN(rb_errors) > 0) { + VALUE rb_error = rb_ary_entry(rb_errors, 0); + VALUE exception_message = rb_funcall(rb_error, rb_intern("to_s"), 0); + rb_exc_raise(rb_class_new_instance(1, &exception_message, cNokogiriXmlSyntaxError)); + } + return Qnil; + } + + Nokogiri_xml_node_namespaces(c_node, rb_namespaces); + + return rb_namespaces ; +} + +/* + :call-seq: attribute_hash() → Hash + + Get the attributes of the current node as a Hash of names and values. + + See related: #attributes and #namespaces + */ +static VALUE +rb_xml_reader_attribute_hash(VALUE rb_reader) +{ + VALUE rb_attributes = rb_hash_new(); + xmlTextReaderPtr c_reader; + xmlNodePtr c_node; + xmlAttrPtr c_property; + VALUE rb_errors; + + TypedData_Get_Struct(rb_reader, xmlTextReader, &xml_text_reader_type, c_reader); + + if (!has_attributes(c_reader)) { + return rb_attributes; + } + + rb_errors = rb_funcall(rb_reader, rb_intern("errors"), 0); + + xmlSetStructuredErrorFunc((void *)rb_errors, noko__error_array_pusher); + c_node = xmlTextReaderExpand(c_reader); + xmlSetStructuredErrorFunc(NULL, NULL); + + if (c_node == NULL) { + if (RARRAY_LEN(rb_errors) > 0) { + VALUE rb_error = rb_ary_entry(rb_errors, 0); + VALUE exception_message = rb_funcall(rb_error, rb_intern("to_s"), 0); + rb_exc_raise(rb_class_new_instance(1, &exception_message, cNokogiriXmlSyntaxError)); + } + return Qnil; + } + + c_property = c_node->properties; + while (c_property != NULL) { + VALUE rb_name = NOKOGIRI_STR_NEW2(c_property->name); + VALUE rb_value = Qnil; + xmlChar *c_value = xmlNodeGetContent((xmlNode *)c_property); + + if (c_value) { + rb_value = NOKOGIRI_STR_NEW2(c_value); + xmlFree(c_value); + } + + rb_hash_aset(rb_attributes, rb_name, rb_value); + + c_property = c_property->next; + } + + return rb_attributes; +} + +/* + * call-seq: + * attribute_at(index) + * + * Get the value of attribute at +index+ + */ +static VALUE +attribute_at(VALUE self, VALUE index) +{ + xmlTextReaderPtr reader; + xmlChar *value; + VALUE rb_value; + + TypedData_Get_Struct(self, xmlTextReader, &xml_text_reader_type, reader); + + if (NIL_P(index)) { return Qnil; } + index = rb_Integer(index); + + value = xmlTextReaderGetAttributeNo( + reader, + (int)NUM2INT(index) + ); + if (value == NULL) { return Qnil; } + + rb_value = NOKOGIRI_STR_NEW2(value); + xmlFree(value); + return rb_value; +} + +/* + * call-seq: + * attribute(name) + * + * Get the value of attribute named +name+ + */ +static VALUE +reader_attribute(VALUE self, VALUE name) +{ + xmlTextReaderPtr reader; + xmlChar *value ; + VALUE rb_value; + + TypedData_Get_Struct(self, xmlTextReader, &xml_text_reader_type, reader); + + if (NIL_P(name)) { return Qnil; } + name = StringValue(name) ; + + value = xmlTextReaderGetAttribute(reader, (xmlChar *)StringValueCStr(name)); + if (value == NULL) { return Qnil; } + + rb_value = NOKOGIRI_STR_NEW2(value); + xmlFree(value); + return rb_value; +} + +/* + * call-seq: + * attribute_count + * + * Get the number of attributes for the current node + */ +static VALUE +attribute_count(VALUE self) +{ + xmlTextReaderPtr reader; + int count; + + TypedData_Get_Struct(self, xmlTextReader, &xml_text_reader_type, reader); + count = xmlTextReaderAttributeCount(reader); + if (count == -1) { return Qnil; } + + return INT2NUM(count); +} + +/* + * call-seq: + * depth + * + * Get the depth of the node + */ +static VALUE +depth(VALUE self) +{ + xmlTextReaderPtr reader; + int depth; + + TypedData_Get_Struct(self, xmlTextReader, &xml_text_reader_type, reader); + depth = xmlTextReaderDepth(reader); + if (depth == -1) { return Qnil; } + + return INT2NUM(depth); +} + +/* + * call-seq: + * xml_version + * + * Get the XML version of the document being read + */ +static VALUE +xml_version(VALUE self) +{ + xmlTextReaderPtr reader; + const char *version; + + TypedData_Get_Struct(self, xmlTextReader, &xml_text_reader_type, reader); + version = (const char *)xmlTextReaderConstXmlVersion(reader); + if (version == NULL) { return Qnil; } + + return NOKOGIRI_STR_NEW2(version); +} + +/* + * call-seq: + * lang + * + * Get the xml:lang scope within which the node resides. + */ +static VALUE +lang(VALUE self) +{ + xmlTextReaderPtr reader; + const char *lang; + + TypedData_Get_Struct(self, xmlTextReader, &xml_text_reader_type, reader); + lang = (const char *)xmlTextReaderConstXmlLang(reader); + if (lang == NULL) { return Qnil; } + + return NOKOGIRI_STR_NEW2(lang); +} + +/* + * call-seq: + * value + * + * Get the text value of the node if present. Returns a utf-8 encoded string. + */ +static VALUE +value(VALUE self) +{ + xmlTextReaderPtr reader; + const char *value; + + TypedData_Get_Struct(self, xmlTextReader, &xml_text_reader_type, reader); + value = (const char *)xmlTextReaderConstValue(reader); + if (value == NULL) { return Qnil; } + + return NOKOGIRI_STR_NEW2(value); +} + +/* + * call-seq: + * prefix + * + * Get the shorthand reference to the namespace associated with the node. + */ +static VALUE +prefix(VALUE self) +{ + xmlTextReaderPtr reader; + const char *prefix; + + TypedData_Get_Struct(self, xmlTextReader, &xml_text_reader_type, reader); + prefix = (const char *)xmlTextReaderConstPrefix(reader); + if (prefix == NULL) { return Qnil; } + + return NOKOGIRI_STR_NEW2(prefix); +} + +/* + * call-seq: + * namespace_uri + * + * Get the URI defining the namespace associated with the node + */ +static VALUE +namespace_uri(VALUE self) +{ + xmlTextReaderPtr reader; + const char *uri; + + TypedData_Get_Struct(self, xmlTextReader, &xml_text_reader_type, reader); + uri = (const char *)xmlTextReaderConstNamespaceUri(reader); + if (uri == NULL) { return Qnil; } + + return NOKOGIRI_STR_NEW2(uri); +} + +/* + * call-seq: + * local_name + * + * Get the local name of the node + */ +static VALUE +local_name(VALUE self) +{ + xmlTextReaderPtr reader; + const char *name; + + TypedData_Get_Struct(self, xmlTextReader, &xml_text_reader_type, reader); + name = (const char *)xmlTextReaderConstLocalName(reader); + if (name == NULL) { return Qnil; } + + return NOKOGIRI_STR_NEW2(name); +} + +/* + * call-seq: + * name + * + * Get the name of the node. Returns a utf-8 encoded string. + */ +static VALUE +name(VALUE self) +{ + xmlTextReaderPtr reader; + const char *name; + + TypedData_Get_Struct(self, xmlTextReader, &xml_text_reader_type, reader); + name = (const char *)xmlTextReaderConstName(reader); + if (name == NULL) { return Qnil; } + + return NOKOGIRI_STR_NEW2(name); +} + +/* + * call-seq: + * base_uri + * + * Get the xml:base of the node + */ +static VALUE +rb_xml_reader_base_uri(VALUE rb_reader) +{ + VALUE rb_base_uri; + xmlTextReaderPtr c_reader; + xmlChar *c_base_uri; + + TypedData_Get_Struct(rb_reader, xmlTextReader, &xml_text_reader_type, c_reader); + + c_base_uri = xmlTextReaderBaseUri(c_reader); + if (c_base_uri == NULL) { + return Qnil; + } + + rb_base_uri = NOKOGIRI_STR_NEW2(c_base_uri); + xmlFree(c_base_uri); + + return rb_base_uri; +} + +/* + * call-seq: + * state + * + * Get the state of the reader + */ +static VALUE +state(VALUE self) +{ + xmlTextReaderPtr reader; + TypedData_Get_Struct(self, xmlTextReader, &xml_text_reader_type, reader); + return INT2NUM(xmlTextReaderReadState(reader)); +} + +/* + * call-seq: + * node_type + * + * Get the type of readers current node + */ +static VALUE +node_type(VALUE self) +{ + xmlTextReaderPtr reader; + TypedData_Get_Struct(self, xmlTextReader, &xml_text_reader_type, reader); + return INT2NUM(xmlTextReaderNodeType(reader)); +} + +/* + * call-seq: + * read + * + * Move the Reader forward through the XML document. + */ +static VALUE +read_more(VALUE rb_reader) +{ + xmlTextReaderPtr c_reader; + libxmlStructuredErrorHandlerState handler_state; + + TypedData_Get_Struct(rb_reader, xmlTextReader, &xml_text_reader_type, c_reader); + + VALUE rb_errors = rb_funcall(rb_reader, rb_intern("errors"), 0); + noko__structured_error_func_save_and_set(&handler_state, (void *)rb_errors, noko__error_array_pusher); + + int status = xmlTextReaderRead(c_reader); + + noko__structured_error_func_restore(&handler_state); + + xmlDocPtr c_document = xmlTextReaderCurrentDoc(c_reader); + if (c_document && c_document->encoding == NULL) { + VALUE constructor_encoding = rb_iv_get(rb_reader, "@encoding"); + if (RTEST(constructor_encoding)) { + c_document->encoding = xmlStrdup(BAD_CAST StringValueCStr(constructor_encoding)); + } else { + rb_iv_set(rb_reader, "@encoding", NOKOGIRI_STR_NEW2("UTF-8")); + c_document->encoding = xmlStrdup(BAD_CAST "UTF-8"); + } + } + + if (status == 1) { return rb_reader; } + if (status == 0) { return Qnil; } + + /* if we're here, there was an error */ + VALUE exception = rb_funcall(cNokogiriXmlSyntaxError, rb_intern("aggregate"), 1, rb_errors); + if (RB_TEST(exception)) { + rb_exc_raise(exception); + } else { + rb_raise(rb_eRuntimeError, "Error pulling: %d", status); + } +} + +/* + * call-seq: + * inner_xml + * + * Read the contents of the current node, including child nodes and markup. + * Returns a utf-8 encoded string. + */ +static VALUE +inner_xml(VALUE self) +{ + xmlTextReaderPtr reader; + xmlChar *value; + VALUE str; + + TypedData_Get_Struct(self, xmlTextReader, &xml_text_reader_type, reader); + + value = xmlTextReaderReadInnerXml(reader); + + str = Qnil; + if (value) { + str = NOKOGIRI_STR_NEW2((char *)value); + xmlFree(value); + } + + return str; +} + +/* + * call-seq: + * outer_xml + * + * Read the current node and its contents, including child nodes and markup. + * Returns a utf-8 encoded string. + */ +static VALUE +outer_xml(VALUE self) +{ + xmlTextReaderPtr reader; + xmlChar *value; + VALUE str = Qnil; + + TypedData_Get_Struct(self, xmlTextReader, &xml_text_reader_type, reader); + + value = xmlTextReaderReadOuterXml(reader); + + if (value) { + str = NOKOGIRI_STR_NEW2((char *)value); + xmlFree(value); + } + return str; +} + +/* + * call-seq: + * from_memory(string, url = nil, encoding = nil, options = 0) + * + * Create a new Reader to parse a String. + */ +static VALUE +from_memory(int argc, VALUE *argv, VALUE klass) +{ + /* TODO: deprecate this method, since Reader.new can handle both memory and IO. It can then + * become private. */ + VALUE rb_buffer, rb_url, encoding, rb_options; + xmlTextReaderPtr reader; + const char *c_url = NULL; + const char *c_encoding = NULL; + int c_options = 0; + VALUE rb_reader, args[3]; + + rb_scan_args(argc, argv, "13", &rb_buffer, &rb_url, &encoding, &rb_options); + + if (!RTEST(rb_buffer)) { rb_raise(rb_eArgError, "string cannot be nil"); } + if (RTEST(rb_url)) { c_url = StringValueCStr(rb_url); } + if (RTEST(encoding)) { c_encoding = StringValueCStr(encoding); } + if (RTEST(rb_options)) { c_options = (int)NUM2INT(rb_options); } + + reader = xmlReaderForMemory( + StringValuePtr(rb_buffer), + (int)RSTRING_LEN(rb_buffer), + c_url, + c_encoding, + c_options + ); + + if (reader == NULL) { + xmlFreeTextReader(reader); + rb_raise(rb_eRuntimeError, "couldn't create a parser"); + } + + rb_reader = TypedData_Wrap_Struct(klass, &xml_text_reader_type, reader); + args[0] = rb_buffer; + args[1] = rb_url; + args[2] = encoding; + rb_obj_call_init(rb_reader, 3, args); + + return rb_reader; +} + +/* + * call-seq: + * from_io(io, url = nil, encoding = nil, options = 0) + * + * Create a new Reader to parse an IO stream. + */ +static VALUE +from_io(int argc, VALUE *argv, VALUE klass) +{ + /* TODO: deprecate this method, since Reader.new can handle both memory and IO. It can then + * become private. */ + VALUE rb_io, rb_url, encoding, rb_options; + xmlTextReaderPtr reader; + const char *c_url = NULL; + const char *c_encoding = NULL; + int c_options = 0; + VALUE rb_reader, args[3]; + + rb_scan_args(argc, argv, "13", &rb_io, &rb_url, &encoding, &rb_options); + + if (!RTEST(rb_io)) { rb_raise(rb_eArgError, "io cannot be nil"); } + if (RTEST(rb_url)) { c_url = StringValueCStr(rb_url); } + if (RTEST(encoding)) { c_encoding = StringValueCStr(encoding); } + if (RTEST(rb_options)) { c_options = (int)NUM2INT(rb_options); } + + reader = xmlReaderForIO( + (xmlInputReadCallback)noko_io_read, + (xmlInputCloseCallback)noko_io_close, + (void *)rb_io, + c_url, + c_encoding, + c_options + ); + + if (reader == NULL) { + xmlFreeTextReader(reader); + rb_raise(rb_eRuntimeError, "couldn't create a parser"); + } + + rb_reader = TypedData_Wrap_Struct(klass, &xml_text_reader_type, reader); + args[0] = rb_io; + args[1] = rb_url; + args[2] = encoding; + rb_obj_call_init(rb_reader, 3, args); + + return rb_reader; +} + +/* + * call-seq: + * reader.empty_element? # => true or false + * + * Returns true if the current node is empty, otherwise false. + */ +static VALUE +empty_element_p(VALUE self) +{ + xmlTextReaderPtr reader; + + TypedData_Get_Struct(self, xmlTextReader, &xml_text_reader_type, reader); + + if (xmlTextReaderIsEmptyElement(reader)) { + return Qtrue; + } + + return Qfalse; +} + +static VALUE +rb_xml_reader_encoding(VALUE rb_reader) +{ + xmlTextReaderPtr c_reader; + const char *parser_encoding; + VALUE constructor_encoding; + + TypedData_Get_Struct(rb_reader, xmlTextReader, &xml_text_reader_type, c_reader); + parser_encoding = (const char *)xmlTextReaderConstEncoding(c_reader); + if (parser_encoding) { + return NOKOGIRI_STR_NEW2(parser_encoding); + } + + constructor_encoding = rb_iv_get(rb_reader, "@encoding"); + if (RTEST(constructor_encoding)) { + return constructor_encoding; + } + + return Qnil; +} + +void +noko_init_xml_reader(void) +{ + cNokogiriXmlReader = rb_define_class_under(mNokogiriXml, "Reader", rb_cObject); + + rb_undef_alloc_func(cNokogiriXmlReader); + + rb_define_singleton_method(cNokogiriXmlReader, "from_memory", from_memory, -1); + rb_define_singleton_method(cNokogiriXmlReader, "from_io", from_io, -1); + + rb_define_method(cNokogiriXmlReader, "attribute", reader_attribute, 1); + rb_define_method(cNokogiriXmlReader, "attribute_at", attribute_at, 1); + rb_define_method(cNokogiriXmlReader, "attribute_count", attribute_count, 0); + rb_define_method(cNokogiriXmlReader, "attribute_hash", rb_xml_reader_attribute_hash, 0); + rb_define_method(cNokogiriXmlReader, "attributes?", attributes_eh, 0); + rb_define_method(cNokogiriXmlReader, "base_uri", rb_xml_reader_base_uri, 0); + rb_define_method(cNokogiriXmlReader, "default?", default_eh, 0); + rb_define_method(cNokogiriXmlReader, "depth", depth, 0); + rb_define_method(cNokogiriXmlReader, "empty_element?", empty_element_p, 0); + rb_define_method(cNokogiriXmlReader, "encoding", rb_xml_reader_encoding, 0); + rb_define_method(cNokogiriXmlReader, "inner_xml", inner_xml, 0); + rb_define_method(cNokogiriXmlReader, "lang", lang, 0); + rb_define_method(cNokogiriXmlReader, "local_name", local_name, 0); + rb_define_method(cNokogiriXmlReader, "name", name, 0); + rb_define_method(cNokogiriXmlReader, "namespace_uri", namespace_uri, 0); + rb_define_method(cNokogiriXmlReader, "namespaces", rb_xml_reader_namespaces, 0); + rb_define_method(cNokogiriXmlReader, "node_type", node_type, 0); + rb_define_method(cNokogiriXmlReader, "outer_xml", outer_xml, 0); + rb_define_method(cNokogiriXmlReader, "prefix", prefix, 0); + rb_define_method(cNokogiriXmlReader, "read", read_more, 0); + rb_define_method(cNokogiriXmlReader, "state", state, 0); + rb_define_method(cNokogiriXmlReader, "value", value, 0); + rb_define_method(cNokogiriXmlReader, "value?", value_eh, 0); + rb_define_method(cNokogiriXmlReader, "xml_version", xml_version, 0); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_relax_ng.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_relax_ng.c new file mode 100644 index 00000000..07b0567a --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_relax_ng.c @@ -0,0 +1,149 @@ +#include + +VALUE cNokogiriXmlRelaxNG; + +static void +_noko_xml_relax_ng_deallocate(void *data) +{ + xmlRelaxNGPtr schema = data; + xmlRelaxNGFree(schema); +} + +static const rb_data_type_t xml_relax_ng_type = { + .wrap_struct_name = "xmlRelaxNG", + .function = { + .dfree = _noko_xml_relax_ng_deallocate, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; + +static VALUE +noko_xml_relax_ng__validate_document(VALUE self, VALUE document) +{ + xmlDocPtr doc; + xmlRelaxNGPtr schema; + VALUE errors; + xmlRelaxNGValidCtxtPtr valid_ctxt; + + TypedData_Get_Struct(self, xmlRelaxNG, &xml_relax_ng_type, schema); + doc = noko_xml_document_unwrap(document); + + errors = rb_ary_new(); + + valid_ctxt = xmlRelaxNGNewValidCtxt(schema); + + if (NULL == valid_ctxt) { + /* we have a problem */ + rb_raise(rb_eRuntimeError, "Could not create a validation context"); + } + + xmlRelaxNGSetValidStructuredErrors( + valid_ctxt, + noko__error_array_pusher, + (void *)errors + ); + + xmlRelaxNGValidateDoc(valid_ctxt, doc); + + xmlRelaxNGFreeValidCtxt(valid_ctxt); + + return errors; +} + +static VALUE +_noko_xml_relax_ng_parse_schema( + VALUE rb_class, + xmlRelaxNGParserCtxtPtr c_parser_context, + VALUE rb_parse_options +) +{ + VALUE rb_errors; + VALUE rb_schema; + xmlRelaxNGPtr c_schema; + libxmlStructuredErrorHandlerState handler_state; + + if (NIL_P(rb_parse_options)) { + rb_parse_options = rb_const_get_at( + rb_const_get_at(mNokogiriXml, rb_intern("ParseOptions")), + rb_intern("DEFAULT_SCHEMA") + ); + } + + rb_errors = rb_ary_new(); + + noko__structured_error_func_save_and_set(&handler_state, (void *)rb_errors, noko__error_array_pusher); + xmlRelaxNGSetParserStructuredErrors( + c_parser_context, + noko__error_array_pusher, + (void *)rb_errors + ); + + c_schema = xmlRelaxNGParse(c_parser_context); + + xmlRelaxNGFreeParserCtxt(c_parser_context); + noko__structured_error_func_restore(&handler_state); + + if (NULL == c_schema) { + VALUE exception = rb_funcall(cNokogiriXmlSyntaxError, rb_intern("aggregate"), 1, rb_errors); + + if (RB_TEST(exception)) { + rb_exc_raise(exception); + } else { + rb_raise(rb_eRuntimeError, "Could not parse document"); + } + } + + rb_schema = TypedData_Wrap_Struct(rb_class, &xml_relax_ng_type, c_schema); + rb_iv_set(rb_schema, "@errors", rb_errors); + rb_iv_set(rb_schema, "@parse_options", rb_parse_options); + + return rb_schema; +} + +/* + * :call-seq: + * from_document(document) → Nokogiri::XML::RelaxNG + * from_document(document, parse_options) → Nokogiri::XML::RelaxNG + * + * Parse a RELAX NG schema definition from a Document to create a new Nokogiri::XML::RelaxNG. + * + * [Parameters] + * - +document+ (XML::Document) A document containing the RELAX NG schema definition + * - +parse_options+ (Nokogiri::XML::ParseOptions) + * Defaults to ParseOptions::DEFAULT_SCHEMA ⚠ Unused + * + * [Returns] Nokogiri::XML::RelaxNG + * + * ⚠ +parse_options+ is currently unused by this method and is present only as a placeholder for + * future functionality. + */ +static VALUE +noko_xml_relax_ng_s_from_document(int argc, VALUE *argv, VALUE rb_class) +{ + /* TODO: deprecate this method and put file-or-string logic into .new so that becomes the + * preferred entry point, and this can become a private method */ + VALUE rb_document; + VALUE rb_parse_options; + xmlDocPtr c_document; + xmlRelaxNGParserCtxtPtr c_parser_context; + + rb_scan_args(argc, argv, "11", &rb_document, &rb_parse_options); + + c_document = noko_xml_document_unwrap(rb_document); + c_document = c_document->doc; /* In case someone passes us a node. ugh. */ + + c_parser_context = xmlRelaxNGNewDocParserCtxt(c_document); + + return _noko_xml_relax_ng_parse_schema(rb_class, c_parser_context, rb_parse_options); +} + +void +noko_init_xml_relax_ng(void) +{ + assert(cNokogiriXmlSchema); + cNokogiriXmlRelaxNG = rb_define_class_under(mNokogiriXml, "RelaxNG", cNokogiriXmlSchema); + + rb_define_singleton_method(cNokogiriXmlRelaxNG, "from_document", noko_xml_relax_ng_s_from_document, -1); + + rb_define_private_method(cNokogiriXmlRelaxNG, "validate_document", noko_xml_relax_ng__validate_document, 1); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_sax_parser.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_sax_parser.c new file mode 100644 index 00000000..ec2d2ade --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_sax_parser.c @@ -0,0 +1,403 @@ +#include + +VALUE cNokogiriXmlSaxParser ; + +static ID id_start_document; +static ID id_end_document; +static ID id_start_element; +static ID id_end_element; +static ID id_start_element_namespace; +static ID id_end_element_namespace; +static ID id_comment; +static ID id_characters; +static ID id_xmldecl; +static ID id_error; +static ID id_warning; +static ID id_cdata_block; +static ID id_processing_instruction; +static ID id_reference; + +static size_t +xml_sax_parser_memsize(const void *data) +{ + return sizeof(xmlSAXHandler); +} + +/* Used by Nokogiri::XML::SAX::Parser and Nokogiri::HTML::SAX::Parser */ +static const rb_data_type_t xml_sax_parser_type = { + .wrap_struct_name = "xmlSAXHandler", + .function = { + .dfree = RUBY_TYPED_DEFAULT_FREE, + .dsize = xml_sax_parser_memsize + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED +}; + +static void +noko_xml_sax_parser_start_document_callback(void *ctx) +{ + xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr)ctx; + VALUE self = (VALUE)ctxt->_private; + VALUE doc = rb_iv_get(self, "@document"); + + xmlSAX2StartDocument(ctx); + + if (ctxt->standalone != -1) { /* -1 means there was no declaration */ + VALUE encoding = Qnil ; + VALUE standalone = Qnil; + VALUE version; + + if (ctxt->encoding) { + encoding = NOKOGIRI_STR_NEW2(ctxt->encoding) ; + } else if (ctxt->input && ctxt->input->encoding) { // unnecessary after v2.12.0 / gnome/libxml2@ec7be506 + encoding = NOKOGIRI_STR_NEW2(ctxt->input->encoding) ; + } + + version = ctxt->version ? NOKOGIRI_STR_NEW2(ctxt->version) : Qnil; + + /* TODO try using xmlSAX2IsStandalone */ + switch (ctxt->standalone) { + case 0: + standalone = NOKOGIRI_STR_NEW2("no"); + break; + case 1: + standalone = NOKOGIRI_STR_NEW2("yes"); + break; + } + + rb_funcall(doc, id_xmldecl, 3, version, encoding, standalone); + } + + rb_funcall(doc, id_start_document, 0); +} + +static void +noko_xml_sax_parser_end_document_callback(void *ctx) +{ + xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr)ctx; + VALUE self = (VALUE)ctxt->_private; + VALUE doc = rb_iv_get(self, "@document"); + + rb_funcall(doc, id_end_document, 0); +} + +static void +noko_xml_sax_parser_start_element_callback(void *ctx, const xmlChar *name, const xmlChar **atts) +{ + xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr)ctx; + VALUE self = (VALUE)ctxt->_private; + VALUE doc = rb_iv_get(self, "@document"); + + VALUE attributes = rb_ary_new(); + const xmlChar *attr; + int i = 0; + if (atts) { + while ((attr = atts[i]) != NULL) { + const xmlChar *val = atts[i + 1]; + VALUE value = val != NULL ? NOKOGIRI_STR_NEW2(val) : Qnil; + rb_ary_push(attributes, rb_ary_new3(2, NOKOGIRI_STR_NEW2(attr), value)); + i += 2; + } + } + + rb_funcall(doc, + id_start_element, + 2, + NOKOGIRI_STR_NEW2(name), + attributes + ); +} + +static void +noko_xml_sax_parser_end_element_callback(void *ctx, const xmlChar *name) +{ + xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr)ctx; + VALUE self = (VALUE)ctxt->_private; + VALUE doc = rb_iv_get(self, "@document"); + + rb_funcall(doc, id_end_element, 1, NOKOGIRI_STR_NEW2(name)); +} + +static VALUE +xml_sax_parser_marshal_attributes(int attributes_len, const xmlChar **c_attributes) +{ + VALUE rb_array = rb_ary_new2((long)attributes_len); + VALUE cNokogiriXmlSaxParserAttribute; + + cNokogiriXmlSaxParserAttribute = rb_const_get_at(cNokogiriXmlSaxParser, rb_intern("Attribute")); + if (c_attributes) { + /* Each attribute is an array of [localname, prefix, URI, value, end] */ + int i; + for (i = 0; i < attributes_len * 5; i += 5) { + VALUE rb_constructor_args[4], rb_attribute; + + rb_constructor_args[0] = RBSTR_OR_QNIL(c_attributes[i + 0]); /* localname */ + rb_constructor_args[1] = RBSTR_OR_QNIL(c_attributes[i + 1]); /* prefix */ + rb_constructor_args[2] = RBSTR_OR_QNIL(c_attributes[i + 2]); /* URI */ + + /* value */ + rb_constructor_args[3] = NOKOGIRI_STR_NEW((const char *)c_attributes[i + 3], + (c_attributes[i + 4] - c_attributes[i + 3])); + + rb_attribute = rb_class_new_instance(4, rb_constructor_args, cNokogiriXmlSaxParserAttribute); + rb_ary_push(rb_array, rb_attribute); + } + } + + return rb_array; +} + +static void +noko_xml_sax_parser_start_element_ns_callback( + void *ctx, + const xmlChar *localname, + const xmlChar *prefix, + const xmlChar *uri, + int nb_namespaces, + const xmlChar **namespaces, + int nb_attributes, + int nb_defaulted, + const xmlChar **attributes) +{ + xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr)ctx; + VALUE self = (VALUE)ctxt->_private; + VALUE doc = rb_iv_get(self, "@document"); + + VALUE attribute_ary = xml_sax_parser_marshal_attributes(nb_attributes, attributes); + + VALUE ns_list = rb_ary_new2((long)nb_namespaces); + + if (namespaces) { + int i; + for (i = 0; i < nb_namespaces * 2; i += 2) { + rb_ary_push(ns_list, + rb_ary_new3((long)2, + RBSTR_OR_QNIL(namespaces[i + 0]), + RBSTR_OR_QNIL(namespaces[i + 1]) + ) + ); + } + } + + rb_funcall(doc, + id_start_element_namespace, + 5, + NOKOGIRI_STR_NEW2(localname), + attribute_ary, + RBSTR_OR_QNIL(prefix), + RBSTR_OR_QNIL(uri), + ns_list + ); +} + +/** + * end_element_ns was borrowed heavily from libxml-ruby. + */ +static void +noko_xml_sax_parser_end_element_ns_callback( + void *ctx, + const xmlChar *localname, + const xmlChar *prefix, + const xmlChar *uri) +{ + xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr)ctx; + VALUE self = (VALUE)ctxt->_private; + VALUE doc = rb_iv_get(self, "@document"); + + rb_funcall(doc, id_end_element_namespace, 3, + NOKOGIRI_STR_NEW2(localname), + RBSTR_OR_QNIL(prefix), + RBSTR_OR_QNIL(uri) + ); +} + +static void +noko_xml_sax_parser_characters_callback(void *ctx, const xmlChar *ch, int len) +{ + xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr)ctx; + VALUE self = (VALUE)ctxt->_private; + VALUE doc = rb_iv_get(self, "@document"); + + VALUE str = NOKOGIRI_STR_NEW(ch, len); + rb_funcall(doc, id_characters, 1, str); +} + +static void +noko_xml_sax_parser_comment_callback(void *ctx, const xmlChar *value) +{ + xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr)ctx; + VALUE self = (VALUE)ctxt->_private; + VALUE doc = rb_iv_get(self, "@document"); + + VALUE str = NOKOGIRI_STR_NEW2(value); + rb_funcall(doc, id_comment, 1, str); +} + +PRINTFLIKE_DECL(2, 3) +static void +noko_xml_sax_parser_warning_callback(void *ctx, const char *msg, ...) +{ + xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr)ctx; + VALUE self = (VALUE)ctxt->_private; + VALUE doc = rb_iv_get(self, "@document"); + + VALUE rb_message; + +#ifdef TRUFFLERUBY_NOKOGIRI_SYSTEM_LIBRARIES + /* It is not currently possible to pass var args from native + functions to sulong, so we work around the issue here. */ + rb_message = rb_sprintf("warning_func: %s", msg); +#else + va_list args; + va_start(args, msg); + rb_message = rb_vsprintf(msg, args); + va_end(args); +#endif + + rb_funcall(doc, id_warning, 1, rb_message); +} + +PRINTFLIKE_DECL(2, 3) +static void +noko_xml_sax_parser_error_callback(void *ctx, const char *msg, ...) +{ + xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr)ctx; + VALUE self = (VALUE)ctxt->_private; + VALUE doc = rb_iv_get(self, "@document"); + + VALUE rb_message; + +#ifdef TRUFFLERUBY_NOKOGIRI_SYSTEM_LIBRARIES + /* It is not currently possible to pass var args from native + functions to sulong, so we work around the issue here. */ + rb_message = rb_sprintf("error_func: %s", msg); +#else + va_list args; + va_start(args, msg); + rb_message = rb_vsprintf(msg, args); + va_end(args); +#endif + + rb_funcall(doc, id_error, 1, rb_message); +} + +static void +noko_xml_sax_parser_cdata_block_callback(void *ctx, const xmlChar *value, int len) +{ + xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr)ctx; + VALUE self = (VALUE)ctxt->_private; + VALUE doc = rb_iv_get(self, "@document"); + + VALUE string = NOKOGIRI_STR_NEW(value, len); + rb_funcall(doc, id_cdata_block, 1, string); +} + +static void +noko_xml_sax_parser_processing_instruction_callback(void *ctx, const xmlChar *name, const xmlChar *content) +{ + xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr)ctx; + VALUE self = (VALUE)ctxt->_private; + VALUE doc = rb_iv_get(self, "@document"); + + VALUE rb_content = content ? NOKOGIRI_STR_NEW2(content) : Qnil; + + rb_funcall(doc, + id_processing_instruction, + 2, + NOKOGIRI_STR_NEW2(name), + rb_content + ); +} + +static void +noko_xml_sax_parser_reference_callback(void *ctx, const xmlChar *name) +{ + xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr)ctx; + xmlEntityPtr entity = xmlSAX2GetEntity(ctxt, name); + + VALUE self = (VALUE)ctxt->_private; + VALUE doc = rb_iv_get(self, "@document"); + + if (entity && entity->content) { + rb_funcall(doc, id_reference, 2, NOKOGIRI_STR_NEW2(entity->name), NOKOGIRI_STR_NEW2(entity->content)); + } else { + rb_funcall(doc, id_reference, 2, NOKOGIRI_STR_NEW2(name), Qnil); + } +} + +static VALUE +noko_xml_sax_parser__initialize_native(VALUE self) +{ + xmlSAXHandlerPtr handler = noko_xml_sax_parser_unwrap(self); + + handler->startDocument = noko_xml_sax_parser_start_document_callback; + handler->endDocument = noko_xml_sax_parser_end_document_callback; + handler->startElement = noko_xml_sax_parser_start_element_callback; + handler->endElement = noko_xml_sax_parser_end_element_callback; + handler->startElementNs = noko_xml_sax_parser_start_element_ns_callback; + handler->endElementNs = noko_xml_sax_parser_end_element_ns_callback; + handler->characters = noko_xml_sax_parser_characters_callback; + handler->comment = noko_xml_sax_parser_comment_callback; + handler->warning = noko_xml_sax_parser_warning_callback; + handler->error = noko_xml_sax_parser_error_callback; + handler->cdataBlock = noko_xml_sax_parser_cdata_block_callback; + handler->processingInstruction = noko_xml_sax_parser_processing_instruction_callback; + handler->reference = noko_xml_sax_parser_reference_callback; + + /* use some of libxml2's default callbacks to managed DTDs and entities */ + handler->getEntity = xmlSAX2GetEntity; + handler->internalSubset = xmlSAX2InternalSubset; + handler->externalSubset = xmlSAX2ExternalSubset; + handler->isStandalone = xmlSAX2IsStandalone; + handler->hasInternalSubset = xmlSAX2HasInternalSubset; + handler->hasExternalSubset = xmlSAX2HasExternalSubset; + handler->resolveEntity = xmlSAX2ResolveEntity; + handler->getParameterEntity = xmlSAX2GetParameterEntity; + handler->entityDecl = xmlSAX2EntityDecl; + handler->unparsedEntityDecl = xmlSAX2UnparsedEntityDecl; + + handler->initialized = XML_SAX2_MAGIC; + + return self; +} + +static VALUE +noko_xml_sax_parser_allocate(VALUE klass) +{ + xmlSAXHandlerPtr handler; + return TypedData_Make_Struct(klass, xmlSAXHandler, &xml_sax_parser_type, handler); +} + +xmlSAXHandlerPtr +noko_xml_sax_parser_unwrap(VALUE rb_sax_handler) +{ + xmlSAXHandlerPtr c_sax_handler; + TypedData_Get_Struct(rb_sax_handler, xmlSAXHandler, &xml_sax_parser_type, c_sax_handler); + return c_sax_handler; +} + +void +noko_init_xml_sax_parser(void) +{ + cNokogiriXmlSaxParser = rb_define_class_under(mNokogiriXmlSax, "Parser", rb_cObject); + + rb_define_alloc_func(cNokogiriXmlSaxParser, noko_xml_sax_parser_allocate); + + rb_define_private_method(cNokogiriXmlSaxParser, "initialize_native", + noko_xml_sax_parser__initialize_native, 0); + + id_start_document = rb_intern("start_document"); + id_end_document = rb_intern("end_document"); + id_start_element = rb_intern("start_element"); + id_end_element = rb_intern("end_element"); + id_comment = rb_intern("comment"); + id_characters = rb_intern("characters"); + id_xmldecl = rb_intern("xmldecl"); + id_error = rb_intern("error"); + id_warning = rb_intern("warning"); + id_cdata_block = rb_intern("cdata_block"); + id_start_element_namespace = rb_intern("start_element_namespace"); + id_end_element_namespace = rb_intern("end_element_namespace"); + id_processing_instruction = rb_intern("processing_instruction"); + id_reference = rb_intern("reference"); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_sax_parser_context.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_sax_parser_context.c new file mode 100644 index 00000000..0d2b65b5 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_sax_parser_context.c @@ -0,0 +1,396 @@ +#include + +VALUE cNokogiriXmlSaxParserContext ; + +static ID id_read; + +static void +xml_sax_parser_context_type_free(void *data) +{ + xmlParserCtxtPtr ctxt = data; + ctxt->sax = NULL; + if (ctxt->myDoc) { + xmlFreeDoc(ctxt->myDoc); + } + if (ctxt) { + xmlFreeParserCtxt(ctxt); + } +} + +/* + * note that htmlParserCtxtPtr == xmlParserCtxtPtr and xmlFreeParserCtxt() == htmlFreeParserCtxt() + * so we use this type for both XML::SAX::ParserContext and HTML::SAX::ParserContext + */ +static const rb_data_type_t xml_sax_parser_context_type = { + .wrap_struct_name = "xmlParserCtxt", + .function = { + .dfree = xml_sax_parser_context_type_free, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; + +xmlParserCtxtPtr +noko_xml_sax_parser_context_unwrap(VALUE rb_context) +{ + xmlParserCtxtPtr c_context; + TypedData_Get_Struct(rb_context, xmlParserCtxt, &xml_sax_parser_context_type, c_context); + return c_context; +} + +VALUE +noko_xml_sax_parser_context_wrap(VALUE klass, xmlParserCtxtPtr c_context) +{ + return TypedData_Wrap_Struct(klass, &xml_sax_parser_context_type, c_context); +} + +void +noko_xml_sax_parser_context_set_encoding(xmlParserCtxtPtr c_context, VALUE rb_encoding) +{ + if (!NIL_P(rb_encoding)) { + VALUE rb_encoding_name = rb_funcall(rb_encoding, rb_intern("name"), 0); + + char *encoding_name = StringValueCStr(rb_encoding_name); + if (encoding_name) { + libxmlStructuredErrorHandlerState handler_state; + VALUE rb_errors = rb_ary_new(); + + noko__structured_error_func_save_and_set(&handler_state, (void *)rb_errors, noko__error_array_pusher); + + int result = xmlSwitchEncodingName(c_context, encoding_name); + + noko__structured_error_func_restore(&handler_state); + + if (result != 0) { + xmlFreeParserCtxt(c_context); + + VALUE exception = rb_funcall(cNokogiriXmlSyntaxError, rb_intern("aggregate"), 1, rb_errors); + if (!NIL_P(exception)) { + rb_exc_raise(exception); + } else { + rb_raise(rb_eRuntimeError, "could not set encoding"); + } + } + } + } +} + +/* :nodoc: */ +static VALUE +noko_xml_sax_parser_context_s_native_io(VALUE rb_class, VALUE rb_io, VALUE rb_encoding) +{ + if (!rb_respond_to(rb_io, id_read)) { + rb_raise(rb_eTypeError, "argument expected to respond to :read"); + } + + if (!NIL_P(rb_encoding) && !rb_obj_is_kind_of(rb_encoding, rb_cEncoding)) { + rb_raise(rb_eTypeError, "argument must be an Encoding object"); + } + + xmlParserCtxtPtr c_context = + xmlCreateIOParserCtxt(NULL, NULL, + (xmlInputReadCallback)noko_io_read, + (xmlInputCloseCallback)noko_io_close, + (void *)rb_io, XML_CHAR_ENCODING_NONE); + if (!c_context) { + rb_raise(rb_eRuntimeError, "failed to create xml sax parser context"); + } + + noko_xml_sax_parser_context_set_encoding(c_context, rb_encoding); + + if (c_context->sax) { + xmlFree(c_context->sax); + c_context->sax = NULL; + } + + VALUE rb_context = noko_xml_sax_parser_context_wrap(rb_class, c_context); + rb_iv_set(rb_context, "@input", rb_io); + + return rb_context; +} + +/* :nodoc: */ +static VALUE +noko_xml_sax_parser_context_s_native_file(VALUE rb_class, VALUE rb_path, VALUE rb_encoding) +{ + if (!NIL_P(rb_encoding) && !rb_obj_is_kind_of(rb_encoding, rb_cEncoding)) { + rb_raise(rb_eTypeError, "argument must be an Encoding object"); + } + + xmlParserCtxtPtr c_context = xmlCreateFileParserCtxt(StringValueCStr(rb_path)); + if (!c_context) { + rb_raise(rb_eRuntimeError, "failed to create xml sax parser context"); + } + + noko_xml_sax_parser_context_set_encoding(c_context, rb_encoding); + + if (c_context->sax) { + xmlFree(c_context->sax); + c_context->sax = NULL; + } + + return noko_xml_sax_parser_context_wrap(rb_class, c_context); +} + +/* :nodoc: */ +static VALUE +noko_xml_sax_parser_context_s_native_memory(VALUE rb_class, VALUE rb_input, VALUE rb_encoding) +{ + Check_Type(rb_input, T_STRING); + if (!(int)RSTRING_LEN(rb_input)) { + rb_raise(rb_eRuntimeError, "input string cannot be empty"); + } + + if (!NIL_P(rb_encoding) && !rb_obj_is_kind_of(rb_encoding, rb_cEncoding)) { + rb_raise(rb_eTypeError, "argument must be an Encoding object"); + } + + xmlParserCtxtPtr c_context = + xmlCreateMemoryParserCtxt(StringValuePtr(rb_input), (int)RSTRING_LEN(rb_input)); + if (!c_context) { + rb_raise(rb_eRuntimeError, "failed to create xml sax parser context"); + } + + noko_xml_sax_parser_context_set_encoding(c_context, rb_encoding); + + if (c_context->sax) { + xmlFree(c_context->sax); + c_context->sax = NULL; + } + + VALUE rb_context = noko_xml_sax_parser_context_wrap(rb_class, c_context); + rb_iv_set(rb_context, "@input", rb_input); + + return rb_context; +} + +/* + * call-seq: + * parse_with(sax_handler) + * + * Use +sax_handler+ and parse the current document + * + * 💡 Calling this method directly is discouraged. Use Nokogiri::XML::SAX::Parser methods which are + * more convenient for most use cases. + */ +static VALUE +noko_xml_sax_parser_context__parse_with(VALUE rb_context, VALUE rb_sax_parser) +{ + xmlParserCtxtPtr c_context; + xmlSAXHandlerPtr sax; + + if (!rb_obj_is_kind_of(rb_sax_parser, cNokogiriXmlSaxParser)) { + rb_raise(rb_eArgError, "argument must be a Nokogiri::XML::SAX::Parser"); + } + + c_context = noko_xml_sax_parser_context_unwrap(rb_context); + sax = noko_xml_sax_parser_unwrap(rb_sax_parser); + + c_context->sax = sax; + c_context->userData = c_context; /* so we can use libxml2/SAX2.c handlers if we want to */ + c_context->_private = (void *)rb_sax_parser; + + xmlSetStructuredErrorFunc(NULL, NULL); + + /* although we're calling back into Ruby here, we don't need to worry about exceptions, because we + * don't have any cleanup to do. The only memory we need to free is handled by + * xml_sax_parser_context_type_free */ + xmlParseDocument(c_context); + + return Qnil; +} + +/* + * call-seq: + * replace_entities=(value) + * + * See Document@Entity+Handling for an explanation of the behavior controlled by this flag. + * + * [Parameters] + * - +value+ (Boolean) Whether external parsed entities will be resolved. + * + * ⚠ It is UNSAFE to set this option to +true+ when parsing untrusted documents. The option + * defaults to +false+ for this reason. + * + * This option is perhaps misnamed by the libxml2 author, since it controls resolution and not + * replacement. + * + * [Example] + * Because this class is generally not instantiated directly, you would typically set this option + * via the block argument to Nokogiri::XML::SAX::Parser.parse et al: + * + * parser = Nokogiri::XML::SAX::Parser.new(document_handler) + * parser.parse(xml) do |ctx| + * ctx.replace_entities = true # this is UNSAFE for untrusted documents! + * end + */ +static VALUE +noko_xml_sax_parser_context__replace_entities_set(VALUE rb_context, VALUE rb_value) +{ + int error; + xmlParserCtxtPtr ctxt = noko_xml_sax_parser_context_unwrap(rb_context); + + if (RB_TEST(rb_value)) { + error = xmlCtxtSetOptions(ctxt, xmlCtxtGetOptions(ctxt) | XML_PARSE_NOENT); + } else { + error = xmlCtxtSetOptions(ctxt, xmlCtxtGetOptions(ctxt) & ~XML_PARSE_NOENT); + } + + if (error) { + rb_raise(rb_eRuntimeError, "failed to set parser context options (%x)", error); + } + + return rb_value; +} + +/* + * call-seq: + * replace_entities + * + * See Document@Entity+Handling for an explanation of the behavior controlled by this flag. + * + * [Returns] (Boolean) Value of the parse option. (Default +false+) + * + * This option is perhaps misnamed by the libxml2 author, since it controls resolution and not + * replacement. + */ +static VALUE +noko_xml_sax_parser_context__replace_entities_get(VALUE rb_context) +{ + xmlParserCtxtPtr ctxt = noko_xml_sax_parser_context_unwrap(rb_context); + + if (xmlCtxtGetOptions(ctxt) & XML_PARSE_NOENT) { + return Qtrue; + } else { + return Qfalse; + } +} + +/* + * call-seq: line + * + * [Returns] (Integer) the line number of the line being currently parsed. + */ +static VALUE +noko_xml_sax_parser_context__line(VALUE rb_context) +{ + xmlParserInputPtr io; + xmlParserCtxtPtr ctxt = noko_xml_sax_parser_context_unwrap(rb_context); + + io = ctxt->input; + if (io) { + return INT2NUM(io->line); + } + + return Qnil; +} + +/* + * call-seq: column + * + * [Returns] (Integer) the column number of the column being currently parsed. + */ +static VALUE +noko_xml_sax_parser_context__column(VALUE rb_context) +{ + xmlParserCtxtPtr ctxt = noko_xml_sax_parser_context_unwrap(rb_context); + xmlParserInputPtr io; + + io = ctxt->input; + if (io) { + return INT2NUM(io->col); + } + + return Qnil; +} + +/* + * call-seq: + * recovery=(value) + * + * Controls whether this parser will recover from parsing errors. If set to +true+, the parser will + * invoke the SAX::Document#error callback and continue processing the file. If set to +false+, the + * parser will stop processing the file on the first parsing error. + * + * [Parameters] + * - +value+ (Boolean) Recover from parsing errors. (Default is +false+ for XML and +true+ for HTML.) + * + * [Returns] (Boolean) The passed +value+. + * + * [Example] + * Because this class is generally not instantiated directly, you would typically set this option + * via the block argument to Nokogiri::XML::SAX::Parser.parse et al: + * + * parser = Nokogiri::XML::SAX::Parser.new(document_handler) + * parser.parse(xml) do |ctx| + * ctx.recovery = true + * end + */ +static VALUE +noko_xml_sax_parser_context__recovery_set(VALUE rb_context, VALUE rb_value) +{ + int error; + xmlParserCtxtPtr ctxt = noko_xml_sax_parser_context_unwrap(rb_context); + + if (RB_TEST(rb_value)) { + error = xmlCtxtSetOptions(ctxt, xmlCtxtGetOptions(ctxt) | XML_PARSE_RECOVER); + } else { + error = xmlCtxtSetOptions(ctxt, xmlCtxtGetOptions(ctxt) & ~XML_PARSE_RECOVER); + } + + if (error) { + rb_raise(rb_eRuntimeError, "failed to set parser context options (%x)", error); + } + + return rb_value; +} + +/* + * call-seq: + * recovery + * + * Inspect whether this parser will recover from parsing errors. If set to +true+, the parser will + * invoke the SAX::Document#error callback and continue processing the file. If set to +false+, the + * parser will stop processing the file on the first parsing error. + * + * [Returns] (Boolean) Whether this parser will recover from parsing errors. + * + * Default is +false+ for XML and +true+ for HTML. + */ +static VALUE +noko_xml_sax_parser_context__recovery_get(VALUE rb_context) +{ + xmlParserCtxtPtr ctxt = noko_xml_sax_parser_context_unwrap(rb_context); + + if (xmlCtxtGetOptions(ctxt) & XML_PARSE_RECOVER) { + return Qtrue; + } else { + return Qfalse; + } +} + +void +noko_init_xml_sax_parser_context(void) +{ + cNokogiriXmlSaxParserContext = rb_define_class_under(mNokogiriXmlSax, "ParserContext", rb_cObject); + + rb_undef_alloc_func(cNokogiriXmlSaxParserContext); + + rb_define_singleton_method(cNokogiriXmlSaxParserContext, "native_io", + noko_xml_sax_parser_context_s_native_io, 2); + rb_define_singleton_method(cNokogiriXmlSaxParserContext, "native_memory", + noko_xml_sax_parser_context_s_native_memory, 2); + rb_define_singleton_method(cNokogiriXmlSaxParserContext, "native_file", + noko_xml_sax_parser_context_s_native_file, 2); + + rb_define_method(cNokogiriXmlSaxParserContext, "parse_with", noko_xml_sax_parser_context__parse_with, 1); + rb_define_method(cNokogiriXmlSaxParserContext, "replace_entities=", + noko_xml_sax_parser_context__replace_entities_set, 1); + rb_define_method(cNokogiriXmlSaxParserContext, "replace_entities", + noko_xml_sax_parser_context__replace_entities_get, 0); + rb_define_method(cNokogiriXmlSaxParserContext, "recovery=", noko_xml_sax_parser_context__recovery_set, 1); + rb_define_method(cNokogiriXmlSaxParserContext, "recovery", noko_xml_sax_parser_context__recovery_get, 0); + rb_define_method(cNokogiriXmlSaxParserContext, "line", noko_xml_sax_parser_context__line, 0); + rb_define_method(cNokogiriXmlSaxParserContext, "column", noko_xml_sax_parser_context__column, 0); + + id_read = rb_intern("read"); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_sax_push_parser.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_sax_push_parser.c new file mode 100644 index 00000000..e6bffbfe --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_sax_push_parser.c @@ -0,0 +1,206 @@ +#include + +VALUE cNokogiriXmlSaxPushParser ; + +static void +xml_sax_push_parser_free(void *data) +{ + xmlParserCtxtPtr ctx = data; + if (ctx->myDoc) { + xmlFreeDoc(ctx->myDoc); + } + if (ctx) { + xmlFreeParserCtxt(ctx); + } +} + +static const rb_data_type_t xml_sax_push_parser_type = { + .wrap_struct_name = "xmlParserCtxt", + .function = { + .dfree = xml_sax_push_parser_free, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; + +static VALUE +xml_sax_push_parser_allocate(VALUE klass) +{ + return TypedData_Wrap_Struct(klass, &xml_sax_push_parser_type, NULL); +} + +xmlParserCtxtPtr +noko_xml_sax_push_parser_unwrap(VALUE rb_parser) +{ + xmlParserCtxtPtr c_parser; + TypedData_Get_Struct(rb_parser, xmlParserCtxt, &xml_sax_push_parser_type, c_parser); + return c_parser; +} + +/* + * Write +chunk+ to PushParser. +last_chunk+ triggers the end_document handle + */ +static VALUE +noko_xml_sax_push_parser__native_write(VALUE self, VALUE _chunk, VALUE _last_chunk) +{ + xmlParserCtxtPtr ctx; + const char *chunk = NULL; + int size = 0; + + ctx = noko_xml_sax_push_parser_unwrap(self); + + if (Qnil != _chunk) { + chunk = StringValuePtr(_chunk); + size = (int)RSTRING_LEN(_chunk); + } + + xmlSetStructuredErrorFunc(NULL, NULL); + + if (xmlParseChunk(ctx, chunk, size, Qtrue == _last_chunk ? 1 : 0)) { + if (!(xmlCtxtGetOptions(ctx) & XML_PARSE_RECOVER)) { + xmlErrorConstPtr e = xmlCtxtGetLastError(ctx); + noko__error_raise(NULL, e); + } + } + + return self; +} + +/* + * call-seq: + * initialize_native(xml_sax, filename) + * + * Initialize the push parser with +xml_sax+ using +filename+ + */ +static VALUE +noko_xml_sax_push_parser__initialize_native(VALUE self, VALUE _xml_sax, VALUE _filename) +{ + xmlSAXHandlerPtr sax; + const char *filename = NULL; + xmlParserCtxtPtr ctx; + + sax = noko_xml_sax_parser_unwrap(_xml_sax); + + if (_filename != Qnil) { filename = StringValueCStr(_filename); } + + ctx = xmlCreatePushParserCtxt( + sax, + NULL, + NULL, + 0, + filename + ); + if (ctx == NULL) { + rb_raise(rb_eRuntimeError, "Could not create a parser context"); + } + + ctx->userData = ctx; + ctx->_private = (void *)_xml_sax; + + DATA_PTR(self) = ctx; + return self; +} + +static VALUE +noko_xml_sax_push_parser__options_get(VALUE self) +{ + xmlParserCtxtPtr ctx; + + ctx = noko_xml_sax_push_parser_unwrap(self); + + return INT2NUM(xmlCtxtGetOptions(ctx)); +} + +static VALUE +noko_xml_sax_push_parser__options_set(VALUE self, VALUE options) +{ + int error; + xmlParserCtxtPtr ctx; + + ctx = noko_xml_sax_push_parser_unwrap(self); + + error = xmlCtxtSetOptions(ctx, (int)NUM2INT(options)); + if (error) { + rb_raise(rb_eRuntimeError, "Cannot set XML parser context options (%x)", error); + } + + return Qnil; +} + +/* + * call-seq: + * replace_entities + * + * See Document@Entity+Handling for an explanation of the behavior controlled by this flag. + * + * [Returns] (Boolean) Value of the parse option. (Default +false+) + * + * This option is perhaps misnamed by the libxml2 author, since it controls resolution and not + * replacement. + */ +static VALUE +noko_xml_sax_push_parser__replace_entities_get(VALUE self) +{ + xmlParserCtxtPtr ctxt = noko_xml_sax_push_parser_unwrap(self); + + if (xmlCtxtGetOptions(ctxt) & XML_PARSE_NOENT) { + return Qtrue; + } else { + return Qfalse; + } +} + +/* + * call-seq: + * replace_entities=(value) + * + * See Document@Entity+Handling for an explanation of the behavior controlled by this flag. + * + * [Parameters] + * - +value+ (Boolean) Whether external parsed entities will be resolved. + * + * ⚠ It is UNSAFE to set this option to +true+ when parsing untrusted documents. The option + * defaults to +false+ for this reason. + * + * This option is perhaps misnamed by the libxml2 author, since it controls resolution and not + * replacement. + */ +static VALUE +noko_xml_sax_push_parser__replace_entities_set(VALUE self, VALUE value) +{ + int error; + xmlParserCtxtPtr ctxt = noko_xml_sax_push_parser_unwrap(self); + + if (RB_TEST(value)) { + error = xmlCtxtSetOptions(ctxt, xmlCtxtGetOptions(ctxt) | XML_PARSE_NOENT); + } else { + error = xmlCtxtSetOptions(ctxt, xmlCtxtGetOptions(ctxt) & ~XML_PARSE_NOENT); + } + + if (error) { + rb_raise(rb_eRuntimeError, "failed to set parser context options (%x)", error); + } + + return value; +} + +void +noko_init_xml_sax_push_parser(void) +{ + cNokogiriXmlSaxPushParser = rb_define_class_under(mNokogiriXmlSax, "PushParser", rb_cObject); + + rb_define_alloc_func(cNokogiriXmlSaxPushParser, xml_sax_push_parser_allocate); + + rb_define_method(cNokogiriXmlSaxPushParser, "options", + noko_xml_sax_push_parser__options_get, 0); + rb_define_method(cNokogiriXmlSaxPushParser, "options=", + noko_xml_sax_push_parser__options_set, 1); + rb_define_method(cNokogiriXmlSaxPushParser, "replace_entities", + noko_xml_sax_push_parser__replace_entities_get, 0); + rb_define_method(cNokogiriXmlSaxPushParser, "replace_entities=", + noko_xml_sax_push_parser__replace_entities_set, 1); + + rb_define_private_method(cNokogiriXmlSaxPushParser, "initialize_native", + noko_xml_sax_push_parser__initialize_native, 2); + rb_define_private_method(cNokogiriXmlSaxPushParser, "native_write", + noko_xml_sax_push_parser__native_write, 2); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_schema.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_schema.c new file mode 100644 index 00000000..b2bded98 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_schema.c @@ -0,0 +1,226 @@ +#include + +VALUE cNokogiriXmlSchema; + +static void +xml_schema_deallocate(void *data) +{ + xmlSchemaPtr schema = data; + xmlSchemaFree(schema); +} + +static const rb_data_type_t xml_schema_type = { + .wrap_struct_name = "xmlSchema", + .function = { + .dfree = xml_schema_deallocate, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; + +static VALUE +noko_xml_schema__validate_document(VALUE self, VALUE document) +{ + xmlDocPtr doc; + xmlSchemaPtr schema; + xmlSchemaValidCtxtPtr valid_ctxt; + VALUE errors; + + TypedData_Get_Struct(self, xmlSchema, &xml_schema_type, schema); + doc = noko_xml_document_unwrap(document); + + errors = rb_ary_new(); + + valid_ctxt = xmlSchemaNewValidCtxt(schema); + + if (NULL == valid_ctxt) { + /* we have a problem */ + rb_raise(rb_eRuntimeError, "Could not create a validation context"); + } + + xmlSchemaSetValidStructuredErrors( + valid_ctxt, + noko__error_array_pusher, + (void *)errors + ); + + int status = xmlSchemaValidateDoc(valid_ctxt, doc); + + xmlSchemaFreeValidCtxt(valid_ctxt); + + if (status != 0) { + if (RARRAY_LEN(errors) == 0) { + rb_ary_push(errors, rb_str_new2("Could not validate document")); + } + } + + return errors; +} + +static VALUE +noko_xml_schema__validate_file(VALUE self, VALUE rb_filename) +{ + xmlSchemaPtr schema; + xmlSchemaValidCtxtPtr valid_ctxt; + const char *filename ; + VALUE errors; + + TypedData_Get_Struct(self, xmlSchema, &xml_schema_type, schema); + filename = (const char *)StringValueCStr(rb_filename) ; + + errors = rb_ary_new(); + + valid_ctxt = xmlSchemaNewValidCtxt(schema); + + if (NULL == valid_ctxt) { + /* we have a problem */ + rb_raise(rb_eRuntimeError, "Could not create a validation context"); + } + + xmlSchemaSetValidStructuredErrors( + valid_ctxt, + noko__error_array_pusher, + (void *)errors + ); + + int status = xmlSchemaValidateFile(valid_ctxt, filename, 0); + + xmlSchemaFreeValidCtxt(valid_ctxt); + + if (status != 0) { + if (RARRAY_LEN(errors) == 0) { + rb_ary_push(errors, rb_str_new2("Could not validate file.")); + } + } + + return errors; +} + +static VALUE +xml_schema_parse_schema( + VALUE rb_class, + xmlSchemaParserCtxtPtr c_parser_context, + VALUE rb_parse_options +) +{ + xmlExternalEntityLoader saved_loader = 0; + libxmlStructuredErrorHandlerState handler_state; + + if (NIL_P(rb_parse_options)) { + rb_parse_options = rb_const_get_at( + rb_const_get_at(mNokogiriXml, rb_intern("ParseOptions")), + rb_intern("DEFAULT_SCHEMA") + ); + } + int c_parse_options = (int)NUM2INT(rb_funcall(rb_parse_options, rb_intern("to_i"), 0)); + + VALUE rb_errors = rb_ary_new(); + noko__structured_error_func_save_and_set(&handler_state, (void *)rb_errors, noko__error_array_pusher); + + xmlSchemaSetParserStructuredErrors( + c_parser_context, + noko__error_array_pusher, + (void *)rb_errors + ); + + if (c_parse_options & XML_PARSE_NONET) { + saved_loader = xmlGetExternalEntityLoader(); + xmlSetExternalEntityLoader(xmlNoNetExternalEntityLoader); + } + + xmlSchemaPtr c_schema = xmlSchemaParse(c_parser_context); + + if (saved_loader) { + xmlSetExternalEntityLoader(saved_loader); + } + + xmlSchemaFreeParserCtxt(c_parser_context); + noko__structured_error_func_restore(&handler_state); + + if (NULL == c_schema) { + VALUE exception = rb_funcall(cNokogiriXmlSyntaxError, rb_intern("aggregate"), 1, rb_errors); + if (RB_TEST(exception)) { + rb_exc_raise(exception); + } else { + rb_raise(rb_eRuntimeError, "Could not parse document"); + } + } + + VALUE rb_schema = TypedData_Wrap_Struct(rb_class, &xml_schema_type, c_schema); + rb_iv_set(rb_schema, "@errors", rb_errors); + rb_iv_set(rb_schema, "@parse_options", rb_parse_options); + + return rb_schema; +} + +/* + * :call-seq: + * from_document(input) → Nokogiri::XML::Schema + * from_document(input, parse_options) → Nokogiri::XML::Schema + * + * Parse an \XSD schema definition from a Document to create a new Nokogiri::XML::Schema + * + * [Parameters] + * - +input+ (XML::Document) A document containing the \XSD schema definition + * - +parse_options+ (Nokogiri::XML::ParseOptions) + * Defaults to Nokogiri::XML::ParseOptions::DEFAULT_SCHEMA + * + * [Returns] Nokogiri::XML::Schema + */ +static VALUE +noko_xml_schema_s_from_document(int argc, VALUE *argv, VALUE rb_class) +{ + /* TODO: deprecate this method and put file-or-string logic into .new so that becomes the + * preferred entry point, and this can become a private method */ + VALUE rb_document; + VALUE rb_parse_options; + VALUE rb_schema; + xmlDocPtr c_document; + xmlSchemaParserCtxtPtr c_parser_context; + int defensive_copy_p = 0; + + rb_scan_args(argc, argv, "11", &rb_document, &rb_parse_options); + + if (!rb_obj_is_kind_of(rb_document, cNokogiriXmlNode)) { + rb_raise(rb_eTypeError, + "expected parameter to be a Nokogiri::XML::Document, received %"PRIsVALUE, + rb_obj_class(rb_document)); + } + + if (!rb_obj_is_kind_of(rb_document, cNokogiriXmlDocument)) { + xmlNodePtr deprecated_node_type_arg; + NOKO_WARN_DEPRECATION("Passing a Node as the first parameter to Schema.from_document is deprecated. Please pass a Document instead. This will become an error in Nokogiri v1.17.0."); // TODO: deprecated in v1.15.3, remove in v1.17.0 + Noko_Node_Get_Struct(rb_document, xmlNode, deprecated_node_type_arg); + c_document = deprecated_node_type_arg->doc; + } else { + c_document = noko_xml_document_unwrap(rb_document); + } + + if (noko_xml_document_has_wrapped_blank_nodes_p(c_document)) { + // see https://github.com/sparklemotion/nokogiri/pull/2001 + c_document = xmlCopyDoc(c_document, 1); + defensive_copy_p = 1; + } + + c_parser_context = xmlSchemaNewDocParserCtxt(c_document); + rb_schema = xml_schema_parse_schema(rb_class, c_parser_context, rb_parse_options); + + if (defensive_copy_p) { + xmlFreeDoc(c_document); + c_document = NULL; + } + + return rb_schema; +} + +void +noko_init_xml_schema(void) +{ + cNokogiriXmlSchema = rb_define_class_under(mNokogiriXml, "Schema", rb_cObject); + + rb_undef_alloc_func(cNokogiriXmlSchema); + + rb_define_singleton_method(cNokogiriXmlSchema, "from_document", noko_xml_schema_s_from_document, -1); + + rb_define_private_method(cNokogiriXmlSchema, "validate_document", noko_xml_schema__validate_document, 1); + rb_define_private_method(cNokogiriXmlSchema, "validate_file", noko_xml_schema__validate_file, 1); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_syntax_error.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_syntax_error.c new file mode 100644 index 00000000..d0a3bf8d --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_syntax_error.c @@ -0,0 +1,93 @@ +#include + +VALUE cNokogiriXmlSyntaxError; + +void +noko__structured_error_func_save(libxmlStructuredErrorHandlerState *handler_state) +{ + /* this method is tightly coupled to the implementation of xmlSetStructuredErrorFunc */ + handler_state->user_data = xmlStructuredErrorContext; + handler_state->handler = xmlStructuredError; +} + +void +noko__structured_error_func_save_and_set( + libxmlStructuredErrorHandlerState *handler_state, + void *user_data, + xmlStructuredErrorFunc handler +) +{ + noko__structured_error_func_save(handler_state); + xmlSetStructuredErrorFunc(user_data, handler); +} + +void +noko__structured_error_func_restore(libxmlStructuredErrorHandlerState *handler_state) +{ + xmlSetStructuredErrorFunc(handler_state->user_data, handler_state->handler); +} + +void +noko__error_array_pusher(void *ctx, xmlErrorConstPtr error) +{ + VALUE list = (VALUE)ctx; + Check_Type(list, T_ARRAY); + rb_ary_push(list, noko_xml_syntax_error__wrap(error)); +} + +void +noko__error_raise(void *ctx, xmlErrorConstPtr error) +{ + rb_exc_raise(noko_xml_syntax_error__wrap(error)); +} + +VALUE +noko_xml_syntax_error__wrap(xmlErrorConstPtr error) +{ + xmlChar *c_path ; + VALUE msg, e, klass; + + klass = cNokogiriXmlSyntaxError; + + if (error && error->domain == XML_FROM_XPATH) { + klass = cNokogiriXmlXpathSyntaxError; + } + + msg = (error && error->message) ? NOKOGIRI_STR_NEW2(error->message) : Qnil; + + e = rb_class_new_instance( + 1, + &msg, + klass + ); + + if (error) { + c_path = xmlGetNodePath(error->node); + + rb_iv_set(e, "@domain", INT2NUM(error->domain)); + rb_iv_set(e, "@code", INT2NUM(error->code)); + rb_iv_set(e, "@level", INT2NUM((short)error->level)); + rb_iv_set(e, "@file", RBSTR_OR_QNIL(error->file)); + rb_iv_set(e, "@line", INT2NUM(error->line)); + rb_iv_set(e, "@path", RBSTR_OR_QNIL(c_path)); + rb_iv_set(e, "@str1", RBSTR_OR_QNIL(error->str1)); + rb_iv_set(e, "@str2", RBSTR_OR_QNIL(error->str2)); + rb_iv_set(e, "@str3", RBSTR_OR_QNIL(error->str3)); + rb_iv_set(e, "@int1", INT2NUM(error->int1)); + rb_iv_set(e, "@column", INT2NUM(error->int2)); + + xmlFree(c_path); + } + + return e; +} + +void +noko_init_xml_syntax_error(void) +{ + assert(cNokogiriSyntaxError); + /* + * The XML::SyntaxError is raised on parse errors + */ + cNokogiriXmlSyntaxError = rb_define_class_under(mNokogiriXml, "SyntaxError", cNokogiriSyntaxError); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_text.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_text.c new file mode 100644 index 00000000..e15de27f --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_text.c @@ -0,0 +1,59 @@ +#include + +VALUE cNokogiriXmlText ; + +/* + * call-seq: + * new(content, document) + * + * Create a new Text element on the +document+ with +content+ + */ +static VALUE +rb_xml_text_s_new(int argc, VALUE *argv, VALUE klass) +{ + xmlDocPtr c_document; + xmlNodePtr c_node; + VALUE rb_string; + VALUE rb_document; + VALUE rb_rest; + VALUE rb_node; + + rb_scan_args(argc, argv, "2*", &rb_string, &rb_document, &rb_rest); + + Check_Type(rb_string, T_STRING); + if (!rb_obj_is_kind_of(rb_document, cNokogiriXmlNode)) { + rb_raise(rb_eTypeError, + "expected second parameter to be a Nokogiri::XML::Document, received %"PRIsVALUE, + rb_obj_class(rb_document)); + } + + if (!rb_obj_is_kind_of(rb_document, cNokogiriXmlDocument)) { + xmlNodePtr deprecated_node_type_arg; + NOKO_WARN_DEPRECATION("Passing a Node as the second parameter to Text.new is deprecated. Please pass a Document instead. This will become an error in Nokogiri v1.17.0."); // TODO: deprecated in v1.15.3, remove in v1.17.0 + Noko_Node_Get_Struct(rb_document, xmlNode, deprecated_node_type_arg); + c_document = deprecated_node_type_arg->doc; + } else { + c_document = noko_xml_document_unwrap(rb_document); + } + + c_node = xmlNewDocText(c_document, (xmlChar *)StringValueCStr(rb_string)); + noko_xml_document_pin_node(c_node); + rb_node = noko_xml_node_wrap(klass, c_node) ; + rb_obj_call_init(rb_node, argc, argv); + + if (rb_block_given_p()) { rb_yield(rb_node); } + + return rb_node; +} + +void +noko_init_xml_text(void) +{ + assert(cNokogiriXmlCharacterData); + /* + * Wraps Text nodes. + */ + cNokogiriXmlText = rb_define_class_under(mNokogiriXml, "Text", cNokogiriXmlCharacterData); + + rb_define_singleton_method(cNokogiriXmlText, "new", rb_xml_text_s_new, -1); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_xpath_context.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_xpath_context.c new file mode 100644 index 00000000..8f71c2e3 --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xml_xpath_context.c @@ -0,0 +1,486 @@ +#include + +VALUE cNokogiriXmlXpathContext; + +/* + * these constants have matching declarations in + * ext/java/nokogiri/internals/NokogiriNamespaceContext.java + */ +static const xmlChar *NOKOGIRI_PREFIX = (const xmlChar *)"nokogiri"; +static const xmlChar *NOKOGIRI_URI = (const xmlChar *)"http://www.nokogiri.org/default_ns/ruby/extensions_functions"; +static const xmlChar *NOKOGIRI_BUILTIN_PREFIX = (const xmlChar *)"nokogiri-builtin"; +static const xmlChar *NOKOGIRI_BUILTIN_URI = (const xmlChar *)"https://www.nokogiri.org/default_ns/ruby/builtins"; + +static void +_noko_xml_xpath_context_dfree(void *data) +{ + xmlXPathContextPtr c_context = data; + xmlXPathFreeContext(c_context); +} + +static const rb_data_type_t _noko_xml_xpath_context_type = { + .wrap_struct_name = "xmlXPathContext", + .function = { + .dfree = _noko_xml_xpath_context_dfree, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; + +/* find a CSS class in an HTML element's `class` attribute */ +static const xmlChar * +_noko_xml_xpath_context__css_class(const xmlChar *str, const xmlChar *val) +{ + int val_len; + + if (str == NULL) { return (NULL); } + if (val == NULL) { return (NULL); } + + val_len = xmlStrlen(val); + if (val_len == 0) { return (str); } + + while (*str != 0) { + if ((*str == *val) && !xmlStrncmp(str, val, val_len)) { + const xmlChar *next_byte = str + val_len; + + /* only match if the next byte is whitespace or end of string */ + if ((*next_byte == 0) || (IS_BLANK_CH(*next_byte))) { + return ((const xmlChar *)str); + } + } + + /* advance str to whitespace */ + while ((*str != 0) && !IS_BLANK_CH(*str)) { + str++; + } + + /* advance str to start of next word or end of string */ + while ((*str != 0) && IS_BLANK_CH(*str)) { + str++; + } + } + + return (NULL); +} + +/* xmlXPathFunction to wrap _noko_xml_xpath_context__css_class() */ +static void +noko_xml_xpath_context_xpath_func_css_class(xmlXPathParserContextPtr ctxt, int nargs) +{ + xmlXPathObjectPtr hay, needle; + + CHECK_ARITY(2); + + CAST_TO_STRING; + needle = valuePop(ctxt); + if ((needle == NULL) || (needle->type != XPATH_STRING)) { + xmlXPathFreeObject(needle); + XP_ERROR(XPATH_INVALID_TYPE); + } + + CAST_TO_STRING; + hay = valuePop(ctxt); + if ((hay == NULL) || (hay->type != XPATH_STRING)) { + xmlXPathFreeObject(hay); + xmlXPathFreeObject(needle); + XP_ERROR(XPATH_INVALID_TYPE); + } + + if (_noko_xml_xpath_context__css_class(hay->stringval, needle->stringval)) { + valuePush(ctxt, xmlXPathNewBoolean(1)); + } else { + valuePush(ctxt, xmlXPathNewBoolean(0)); + } + + xmlXPathFreeObject(hay); + xmlXPathFreeObject(needle); +} + + +/* xmlXPathFunction to select nodes whose local name matches, for HTML5 CSS queries that should + * ignore namespaces */ +static void +noko_xml_xpath_context_xpath_func_local_name_is(xmlXPathParserContextPtr ctxt, int nargs) +{ + xmlXPathObjectPtr element_name; + + assert(ctxt->context->node); + + CHECK_ARITY(1); + CAST_TO_STRING; + CHECK_TYPE(XPATH_STRING); + element_name = valuePop(ctxt); + + valuePush( + ctxt, + xmlXPathNewBoolean(xmlStrEqual(ctxt->context->node->name, element_name->stringval)) + ); + + xmlXPathFreeObject(element_name); +} + + +/* + * call-seq: + * register_ns(prefix, uri) → Nokogiri::XML::XPathContext + * + * Register the namespace with +prefix+ and +uri+ for use in future queries. + * Passing a uri of +nil+ will unregister the namespace. + * + * [Returns] +self+ + */ +static VALUE +noko_xml_xpath_context_register_ns(VALUE rb_context, VALUE prefix, VALUE uri) +{ + xmlXPathContextPtr c_context; + const xmlChar *ns_uri; + + TypedData_Get_Struct(rb_context, xmlXPathContext, &_noko_xml_xpath_context_type, c_context); + + if (NIL_P(uri)) { + ns_uri = NULL; + } else { + ns_uri = (const xmlChar *)StringValueCStr(uri); + } + + xmlXPathRegisterNs(c_context, (const xmlChar *)StringValueCStr(prefix), ns_uri); + + return rb_context; +} + +/* + * call-seq: + * register_variable(name, value) → Nokogiri::XML::XPathContext + * + * Register the variable +name+ with +value+ for use in future queries. + * Passing a value of +nil+ will unregister the variable. + * + * [Returns] +self+ + */ +static VALUE +noko_xml_xpath_context_register_variable(VALUE rb_context, VALUE name, VALUE value) +{ + xmlXPathContextPtr c_context; + xmlXPathObjectPtr xmlValue; + + TypedData_Get_Struct(rb_context, xmlXPathContext, &_noko_xml_xpath_context_type, c_context); + + if (NIL_P(value)) { + xmlValue = NULL; + } else { + xmlValue = xmlXPathNewCString(StringValueCStr(value)); + } + + xmlXPathRegisterVariable(c_context, (const xmlChar *)StringValueCStr(name), xmlValue); + + return rb_context; +} + + +/* + * convert an XPath object into a Ruby object of the appropriate type. + * returns Qundef if no conversion was possible. + */ +static VALUE +_noko_xml_xpath_context__xpath2ruby(xmlXPathObjectPtr c_xpath_object, xmlXPathContextPtr c_context) +{ + VALUE rb_retval; + + assert(c_context->doc); + assert(DOC_RUBY_OBJECT_TEST(c_context->doc)); + + switch (c_xpath_object->type) { + case XPATH_STRING: + rb_retval = NOKOGIRI_STR_NEW2(c_xpath_object->stringval); + xmlFree(c_xpath_object->stringval); + return rb_retval; + + case XPATH_NODESET: + return noko_xml_node_set_wrap( + c_xpath_object->nodesetval, + DOC_RUBY_OBJECT(c_context->doc) + ); + + case XPATH_NUMBER: + return rb_float_new(c_xpath_object->floatval); + + case XPATH_BOOLEAN: + return (c_xpath_object->boolval == 1) ? Qtrue : Qfalse; + + default: + return Qundef; + } +} + +void +Nokogiri_marshal_xpath_funcall_and_return_values( + xmlXPathParserContextPtr ctxt, + int argc, + VALUE rb_xpath_handler, + const char *method_name +) +{ + VALUE rb_retval; + VALUE *argv; + VALUE rb_node_set = Qnil; + xmlNodeSetPtr c_node_set = NULL; + xmlXPathObjectPtr c_xpath_object; + + assert(ctxt->context->doc); + assert(DOC_RUBY_OBJECT_TEST(ctxt->context->doc)); + + argv = (VALUE *)ruby_xcalloc((size_t)argc, sizeof(VALUE)); + for (int j = 0 ; j < argc ; ++j) { + rb_gc_register_address(&argv[j]); + } + + for (int j = argc - 1 ; j >= 0 ; --j) { + c_xpath_object = valuePop(ctxt); + argv[j] = _noko_xml_xpath_context__xpath2ruby(c_xpath_object, ctxt->context); + if (argv[j] == Qundef) { + argv[j] = NOKOGIRI_STR_NEW2(xmlXPathCastToString(c_xpath_object)); + } + xmlXPathFreeNodeSetList(c_xpath_object); + } + + rb_retval = rb_funcall2( + rb_xpath_handler, + rb_intern((const char *)method_name), + argc, + argv + ); + + for (int j = 0 ; j < argc ; ++j) { + rb_gc_unregister_address(&argv[j]); + } + ruby_xfree(argv); + + switch (TYPE(rb_retval)) { + case T_FLOAT: + case T_BIGNUM: + case T_FIXNUM: + xmlXPathReturnNumber(ctxt, NUM2DBL(rb_retval)); + break; + case T_STRING: + xmlXPathReturnString(ctxt, xmlCharStrdup(StringValueCStr(rb_retval))); + break; + case T_TRUE: + xmlXPathReturnTrue(ctxt); + break; + case T_FALSE: + xmlXPathReturnFalse(ctxt); + break; + case T_NIL: + break; + case T_ARRAY: { + VALUE construct_args[2] = { DOC_RUBY_OBJECT(ctxt->context->doc), rb_retval }; + rb_node_set = rb_class_new_instance(2, construct_args, cNokogiriXmlNodeSet); + c_node_set = noko_xml_node_set_unwrap(rb_node_set); + xmlXPathReturnNodeSet(ctxt, xmlXPathNodeSetMerge(NULL, c_node_set)); + } + break; + case T_DATA: + if (rb_obj_is_kind_of(rb_retval, cNokogiriXmlNodeSet)) { + c_node_set = noko_xml_node_set_unwrap(rb_retval); + /* Copy the node set, otherwise it will get GC'd. */ + xmlXPathReturnNodeSet(ctxt, xmlXPathNodeSetMerge(NULL, c_node_set)); + break; + } + default: + rb_raise(rb_eRuntimeError, "Invalid return type"); + } +} + +static void +_noko_xml_xpath_context__handler_invoker(xmlXPathParserContextPtr ctxt, int argc) +{ + VALUE rb_xpath_handler = Qnil; + const char *method_name = NULL ; + + assert(ctxt); + assert(ctxt->context); + assert(ctxt->context->userData); + assert(ctxt->context->function); + + rb_xpath_handler = (VALUE)(ctxt->context->userData); + method_name = (const char *)(ctxt->context->function); + + Nokogiri_marshal_xpath_funcall_and_return_values( + ctxt, + argc, + rb_xpath_handler, + method_name + ); +} + +static xmlXPathFunction +_noko_xml_xpath_context_handler_lookup(void *data, const xmlChar *c_name, const xmlChar *c_ns_uri) +{ + VALUE rb_handler = (VALUE)data; + if (rb_respond_to(rb_handler, rb_intern((const char *)c_name))) { + if (c_ns_uri == NULL) { + NOKO_WARN_DEPRECATION("A custom XPath or CSS handler function named '%s' is being invoked without a namespace. Please update your query to reference this function as 'nokogiri:%s'. Invoking custom handler functions without a namespace is deprecated and will become an error in Nokogiri v1.17.0.", + c_name, c_name); // TODO deprecated in v1.15.0, remove in v1.19.0 + } + return _noko_xml_xpath_context__handler_invoker; + } + + return NULL; +} + +PRINTFLIKE_DECL(2, 3) +static void +_noko_xml_xpath_context__generic_exception_pusher(void *data, const char *msg, ...) +{ + VALUE rb_errors = (VALUE)data; + VALUE rb_message; + VALUE rb_exception; + + Check_Type(rb_errors, T_ARRAY); + +#ifdef TRUFFLERUBY_NOKOGIRI_SYSTEM_LIBRARIES + /* It is not currently possible to pass var args from native + functions to sulong, so we work around the issue here. */ + rb_message = rb_sprintf("_noko_xml_xpath_context__generic_exception_pusher: %s", msg); +#else + va_list args; + va_start(args, msg); + rb_message = rb_vsprintf(msg, args); + va_end(args); +#endif + + rb_exception = rb_exc_new_str(cNokogiriXmlXpathSyntaxError, rb_message); + rb_ary_push(rb_errors, rb_exception); +} + +/* + * call-seq: + * evaluate(search_path, handler = nil) → Object + * + * Evaluate the +search_path+ query. + * + * [Returns] an object of the appropriate type for the query, which could be +NodeSet+, a +String+, + * a +Float+, or a boolean. + */ +static VALUE +noko_xml_xpath_context_evaluate(int argc, VALUE *argv, VALUE rb_context) +{ + xmlXPathContextPtr c_context; + VALUE rb_expression = Qnil; + VALUE rb_function_lookup_handler = Qnil; + xmlChar *c_expression_str = NULL; + VALUE rb_errors = rb_ary_new(); + xmlXPathObjectPtr c_xpath_object; + VALUE rb_xpath_object = Qnil; + + TypedData_Get_Struct(rb_context, xmlXPathContext, &_noko_xml_xpath_context_type, c_context); + + rb_scan_args(argc, argv, "11", &rb_expression, &rb_function_lookup_handler); + + c_expression_str = (xmlChar *)StringValueCStr(rb_expression); + + if (Qnil != rb_function_lookup_handler) { + /* FIXME: not sure if this is the correct place to shove private data. */ + c_context->userData = (void *)rb_function_lookup_handler; + xmlXPathRegisterFuncLookup( + c_context, + _noko_xml_xpath_context_handler_lookup, + (void *)rb_function_lookup_handler + ); + } + + /* TODO: use xmlXPathSetErrorHandler (as of 2.13.0) */ + xmlSetStructuredErrorFunc((void *)rb_errors, noko__error_array_pusher); + xmlSetGenericErrorFunc((void *)rb_errors, _noko_xml_xpath_context__generic_exception_pusher); + + c_xpath_object = xmlXPathEvalExpression(c_expression_str, c_context); + + xmlSetStructuredErrorFunc(NULL, NULL); + xmlSetGenericErrorFunc(NULL, NULL); + + xmlXPathRegisterFuncLookup(c_context, NULL, NULL); + + if (c_xpath_object == NULL) { + rb_exc_raise(rb_ary_entry(rb_errors, 0)); + } + + rb_xpath_object = _noko_xml_xpath_context__xpath2ruby(c_xpath_object, c_context); + if (rb_xpath_object == Qundef) { + rb_xpath_object = noko_xml_node_set_wrap(NULL, DOC_RUBY_OBJECT(c_context->doc)); + } + + xmlXPathFreeNodeSetList(c_xpath_object); + + return rb_xpath_object; +} + +/* + * call-seq: + * new(node) + * + * Create a new XPathContext with +node+ as the context node. + */ +static VALUE +noko_xml_xpath_context_new(VALUE klass, VALUE rb_node) +{ + xmlNodePtr c_node; + xmlXPathContextPtr c_context; + VALUE rb_context; + + Noko_Node_Get_Struct(rb_node, xmlNode, c_node); + +#if LIBXML_VERSION < 21000 + xmlXPathInit(); /* deprecated in 40483d0 */ +#endif + + c_context = xmlXPathNewContext(c_node->doc); + c_context->node = c_node; + + xmlXPathRegisterNs(c_context, NOKOGIRI_PREFIX, NOKOGIRI_URI); + xmlXPathRegisterNs(c_context, NOKOGIRI_BUILTIN_PREFIX, NOKOGIRI_BUILTIN_URI); + + xmlXPathRegisterFuncNS(c_context, + (const xmlChar *)"css-class", NOKOGIRI_BUILTIN_URI, + noko_xml_xpath_context_xpath_func_css_class); + xmlXPathRegisterFuncNS(c_context, + (const xmlChar *)"local-name-is", NOKOGIRI_BUILTIN_URI, + noko_xml_xpath_context_xpath_func_local_name_is); + + rb_context = TypedData_Wrap_Struct(klass, &_noko_xml_xpath_context_type, c_context); + + return rb_context; +} + + +/* :nodoc: */ +static VALUE +noko_xml_xpath_context_set_node(VALUE rb_context, VALUE rb_node) +{ + xmlNodePtr c_node; + xmlXPathContextPtr c_context; + + TypedData_Get_Struct(rb_context, xmlXPathContext, &_noko_xml_xpath_context_type, c_context); + Noko_Node_Get_Struct(rb_node, xmlNode, c_node); + + c_context->doc = c_node->doc; + c_context->node = c_node; + + return rb_node; +} + +void +noko_init_xml_xpath_context(void) +{ + /* + * XPathContext is the entry point for searching a +Document+ by using XPath. + */ + cNokogiriXmlXpathContext = rb_define_class_under(mNokogiriXml, "XPathContext", rb_cObject); + + rb_undef_alloc_func(cNokogiriXmlXpathContext); + + rb_define_singleton_method(cNokogiriXmlXpathContext, "new", noko_xml_xpath_context_new, 1); + + rb_define_method(cNokogiriXmlXpathContext, "evaluate", noko_xml_xpath_context_evaluate, -1); + rb_define_method(cNokogiriXmlXpathContext, "register_variable", noko_xml_xpath_context_register_variable, 2); + rb_define_method(cNokogiriXmlXpathContext, "register_ns", noko_xml_xpath_context_register_ns, 2); + rb_define_method(cNokogiriXmlXpathContext, "node=", noko_xml_xpath_context_set_node, 1); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xslt_stylesheet.c b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xslt_stylesheet.c new file mode 100644 index 00000000..903f729c --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/ext/nokogiri/xslt_stylesheet.c @@ -0,0 +1,421 @@ +#include + +VALUE cNokogiriXsltStylesheet; + +static void +mark(void *data) +{ + nokogiriXsltStylesheetTuple *wrapper = (nokogiriXsltStylesheetTuple *)data; + rb_gc_mark(wrapper->func_instances); +} + +static void +dealloc(void *data) +{ + nokogiriXsltStylesheetTuple *wrapper = (nokogiriXsltStylesheetTuple *)data; + xsltStylesheetPtr doc = wrapper->ss; + xsltFreeStylesheet(doc); + ruby_xfree(wrapper); +} + +static const rb_data_type_t nokogiri_xslt_stylesheet_tuple_type = { + .wrap_struct_name = "nokogiriXsltStylesheetTuple", + .function = { + .dmark = mark, + .dfree = dealloc, + }, + .flags = RUBY_TYPED_FREE_IMMEDIATELY +}; + +PRINTFLIKE_DECL(2, 3) +static void +xslt_generic_error_handler(void *ctx, const char *msg, ...) +{ + VALUE message; + +#ifdef TRUFFLERUBY_NOKOGIRI_SYSTEM_LIBRARIES + /* It is not currently possible to pass var args from native + functions to sulong, so we work around the issue here. */ + message = rb_sprintf("xslt_generic_error_handler: %s", msg); +#else + va_list args; + va_start(args, msg); + message = rb_vsprintf(msg, args); + va_end(args); +#endif + + rb_str_concat((VALUE)ctx, message); +} + +VALUE +Nokogiri_wrap_xslt_stylesheet(xsltStylesheetPtr ss) +{ + VALUE self; + nokogiriXsltStylesheetTuple *wrapper; + + self = TypedData_Make_Struct( + cNokogiriXsltStylesheet, + nokogiriXsltStylesheetTuple, + &nokogiri_xslt_stylesheet_tuple_type, + wrapper + ); + + ss->_private = (void *)self; + wrapper->ss = ss; + wrapper->func_instances = rb_ary_new(); + + return self; +} + +/* + * call-seq: + * parse_stylesheet_doc(document) + * + * Parse an XSLT::Stylesheet from +document+. + * + * [Parameters] + * - +document+ (Nokogiri::XML::Document) the document to be parsed. + * + * [Returns] Nokogiri::XSLT::Stylesheet + */ +static VALUE +parse_stylesheet_doc(VALUE klass, VALUE xmldocobj) +{ + xmlDocPtr xml, xml_cpy; + VALUE errstr, exception; + xsltStylesheetPtr ss ; + + xml = noko_xml_document_unwrap(xmldocobj); + + errstr = rb_str_new(0, 0); + xsltSetGenericErrorFunc((void *)errstr, xslt_generic_error_handler); + + xml_cpy = xmlCopyDoc(xml, 1); /* 1 => recursive */ + ss = xsltParseStylesheetDoc(xml_cpy); + + xsltSetGenericErrorFunc(NULL, NULL); + + if (!ss) { + xmlFreeDoc(xml_cpy); + exception = rb_exc_new3(rb_eRuntimeError, errstr); + rb_exc_raise(exception); + } + + return Nokogiri_wrap_xslt_stylesheet(ss); +} + + +/* + * call-seq: + * serialize(document) + * + * Serialize +document+ to an xml string, as specified by the +method+ parameter in the Stylesheet. + */ +static VALUE +rb_xslt_stylesheet_serialize(VALUE self, VALUE xmlobj) +{ + xmlDocPtr xml ; + nokogiriXsltStylesheetTuple *wrapper; + xmlChar *doc_ptr ; + int doc_len ; + VALUE rval ; + + xml = noko_xml_document_unwrap(xmlobj); + TypedData_Get_Struct( + self, + nokogiriXsltStylesheetTuple, + &nokogiri_xslt_stylesheet_tuple_type, + wrapper + ); + xsltSaveResultToString(&doc_ptr, &doc_len, xml, wrapper->ss); + rval = NOKOGIRI_STR_NEW(doc_ptr, doc_len); + xmlFree(doc_ptr); + return rval ; +} + +/* + * call-seq: + * transform(document) + * transform(document, params = {}) + * + * Transform an XML::Document as defined by an XSLT::Stylesheet. + * + * [Parameters] + * - +document+ (Nokogiri::XML::Document) the document to be transformed. + * - +params+ (Hash, Array) strings used as XSLT parameters. + * + * [Returns] Nokogiri::XML::Document + * + * *Example* of basic transformation: + * + * xslt = <<~XSLT + * + * + * + * + * + * + * + *

    + *
      + * + *
    1. + *
      + *
    + * + * + *
    + * XSLT + * + * xml = <<~XML + * + * + * + * EMP0001 + * Accountant + * + * + * EMP0002 + * Developer + * + * + * XML + * + * doc = Nokogiri::XML::Document.parse(xml) + * stylesheet = Nokogiri::XSLT.parse(xslt) + * + * ⚠ Note that the +h1+ element is empty because no param has been provided! + * + * stylesheet.transform(doc).to_xml + * # => "\n" + + * # "

    \n" + + * # "
      \n" + + * # "
    1. EMP0001
    2. \n" + + * # "
    3. EMP0002
    4. \n" + + * # "
    \n" + + * # "\n" + * + * *Example* of using an input parameter hash: + * + * ⚠ The title is populated, but note how we need to quote-escape the value. + * + * stylesheet.transform(doc, { "title" => "'Employee List'" }).to_xml + * # => "\n" + + * # "

    Employee List

    \n" + + * # "
      \n" + + * # "
    1. EMP0001
    2. \n" + + * # "
    3. EMP0002
    4. \n" + + * # "
    \n" + + * # "\n" + * + * *Example* using the XSLT.quote_params helper method to safely quote-escape strings: + * + * stylesheet.transform(doc, Nokogiri::XSLT.quote_params({ "title" => "Aaron's List" })).to_xml + * # => "\n" + + * # "

    Aaron's List

    \n" + + * # "
      \n" + + * # "
    1. EMP0001
    2. \n" + + * # "
    3. EMP0002
    4. \n" + + * # "
    \n" + + * # "\n" + * + * *Example* using an array of XSLT parameters + * + * You can also use an array if you want to. + * + * stylesheet.transform(doc, ["title", "'Employee List'"]).to_xml + * # => "\n" + + * # "

    Employee List

    \n" + + * # "
      \n" + + * # "
    1. EMP0001
    2. \n" + + * # "
    3. EMP0002
    4. \n" + + * # "
    \n" + + * # "\n" + * + * Or pass an array to XSLT.quote_params: + * + * stylesheet.transform(doc, Nokogiri::XSLT.quote_params(["title", "Aaron's List"])).to_xml + * # => "\n" + + * # "

    Aaron's List

    \n" + + * # "
      \n" + + * # "
    1. EMP0001
    2. \n" + + * # "
    3. EMP0002
    4. \n" + + * # "
    \n" + + * # "\n" + * + * See: Nokogiri::XSLT.quote_params + */ +static VALUE +rb_xslt_stylesheet_transform(int argc, VALUE *argv, VALUE self) +{ + VALUE rb_document, rb_param, rb_error_str; + xmlDocPtr c_document ; + xmlDocPtr c_result_document ; + nokogiriXsltStylesheetTuple *wrapper; + const char **params ; + long param_len, j ; + int parse_error_occurred ; + int defensive_copy_p = 0; + + rb_scan_args(argc, argv, "11", &rb_document, &rb_param); + if (NIL_P(rb_param)) { rb_param = rb_ary_new2(0L) ; } + if (!rb_obj_is_kind_of(rb_document, cNokogiriXmlDocument)) { + rb_raise(rb_eArgError, "argument must be a Nokogiri::XML::Document"); + } + + /* handle hashes as arguments. */ + if (T_HASH == TYPE(rb_param)) { + rb_param = rb_funcall(rb_param, rb_intern("to_a"), 0); + rb_param = rb_funcall(rb_param, rb_intern("flatten"), 0); + } + + Check_Type(rb_param, T_ARRAY); + + c_document = noko_xml_document_unwrap(rb_document); + TypedData_Get_Struct(self, nokogiriXsltStylesheetTuple, &nokogiri_xslt_stylesheet_tuple_type, wrapper); + + param_len = RARRAY_LEN(rb_param); + params = ruby_xcalloc((size_t)param_len + 1, sizeof(char *)); + for (j = 0 ; j < param_len ; j++) { + VALUE entry = rb_ary_entry(rb_param, j); + const char *ptr = StringValueCStr(entry); + params[j] = ptr; + } + params[param_len] = 0 ; + + xsltTransformContextPtr c_transform_context = xsltNewTransformContext(wrapper->ss, c_document); + if (xsltNeedElemSpaceHandling(c_transform_context) && + noko_xml_document_has_wrapped_blank_nodes_p(c_document)) { + // see https://github.com/sparklemotion/nokogiri/issues/2800 + c_document = xmlCopyDoc(c_document, 1); + defensive_copy_p = 1; + } + xsltFreeTransformContext(c_transform_context); + + rb_error_str = rb_str_new(0, 0); + xsltSetGenericErrorFunc((void *)rb_error_str, xslt_generic_error_handler); + xmlSetGenericErrorFunc((void *)rb_error_str, xslt_generic_error_handler); + + c_result_document = xsltApplyStylesheet(wrapper->ss, c_document, params); + + ruby_xfree(params); + if (defensive_copy_p) { + xmlFreeDoc(c_document); + c_document = NULL; + } + + xsltSetGenericErrorFunc(NULL, NULL); + xmlSetGenericErrorFunc(NULL, NULL); + + parse_error_occurred = (Qfalse == rb_funcall(rb_error_str, rb_intern("empty?"), 0)); + + if (parse_error_occurred) { + rb_exc_raise(rb_exc_new3(rb_eRuntimeError, rb_error_str)); + } + + return noko_xml_document_wrap((VALUE)0, c_result_document) ; +} + +static void +method_caller(xmlXPathParserContextPtr ctxt, int nargs) +{ + VALUE handler; + const char *function_name; + xsltTransformContextPtr transform; + const xmlChar *functionURI; + + transform = xsltXPathGetTransformContext(ctxt); + functionURI = ctxt->context->functionURI; + handler = (VALUE)xsltGetExtData(transform, functionURI); + function_name = (const char *)(ctxt->context->function); + + Nokogiri_marshal_xpath_funcall_and_return_values( + ctxt, + nargs, + handler, + (const char *)function_name + ); +} + +static void * +initFunc(xsltTransformContextPtr ctxt, const xmlChar *uri) +{ + VALUE modules = rb_iv_get(mNokogiriXslt, "@modules"); + VALUE obj = rb_hash_aref(modules, rb_str_new2((const char *)uri)); + VALUE args = { Qfalse }; + VALUE methods = rb_funcall(obj, rb_intern("instance_methods"), 1, args); + VALUE inst; + nokogiriXsltStylesheetTuple *wrapper; + int i; + + for (i = 0; i < RARRAY_LEN(methods); i++) { + VALUE method_name = rb_obj_as_string(rb_ary_entry(methods, i)); + xsltRegisterExtFunction( + ctxt, + (unsigned char *)StringValueCStr(method_name), + uri, + method_caller + ); + } + + TypedData_Get_Struct( + (VALUE)ctxt->style->_private, + nokogiriXsltStylesheetTuple, + &nokogiri_xslt_stylesheet_tuple_type, + wrapper + ); + inst = rb_class_new_instance(0, NULL, obj); + rb_ary_push(wrapper->func_instances, inst); + + return (void *)inst; +} + +static void +shutdownFunc(xsltTransformContextPtr ctxt, + const xmlChar *uri, void *data) +{ + nokogiriXsltStylesheetTuple *wrapper; + + TypedData_Get_Struct( + (VALUE)ctxt->style->_private, + nokogiriXsltStylesheetTuple, + &nokogiri_xslt_stylesheet_tuple_type, + wrapper + ); + + rb_ary_clear(wrapper->func_instances); +} + +/* docstring is in lib/nokogiri/xslt.rb */ +static VALUE +rb_xslt_s_register(VALUE self, VALUE uri, VALUE obj) +{ + VALUE modules = rb_iv_get(self, "@modules"); + if (NIL_P(modules)) { + rb_raise(rb_eRuntimeError, "internal error: @modules not set"); + } + + rb_hash_aset(modules, uri, obj); + xsltRegisterExtModule( + (unsigned char *)StringValueCStr(uri), + initFunc, + shutdownFunc + ); + return self; +} + +void +noko_init_xslt_stylesheet(void) +{ + rb_define_singleton_method(mNokogiriXslt, "register", rb_xslt_s_register, 2); + rb_iv_set(mNokogiriXslt, "@modules", rb_hash_new()); + + cNokogiriXsltStylesheet = rb_define_class_under(mNokogiriXslt, "Stylesheet", rb_cObject); + + rb_undef_alloc_func(cNokogiriXsltStylesheet); + + rb_define_singleton_method(cNokogiriXsltStylesheet, "parse_stylesheet_doc", parse_stylesheet_doc, 1); + rb_define_method(cNokogiriXsltStylesheet, "serialize", rb_xslt_stylesheet_serialize, 1); + rb_define_method(cNokogiriXsltStylesheet, "transform", rb_xslt_stylesheet_transform, -1); +} diff --git a/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/gumbo-parser/CHANGES.md b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/gumbo-parser/CHANGES.md new file mode 100644 index 00000000..277b3a2b --- /dev/null +++ b/vendor/bundle/ruby/3.2.0/gems/nokogiri-1.18.8-x86_64-linux-gnu/gumbo-parser/CHANGES.md @@ -0,0 +1,63 @@ +## Gumbo 0.10.1 (2015-04-30) + +Same as 0.10.0, but with the version number bumped because the last version-number commit to v0.9.4 makes GitHub think that v0.9.4 is the latest version and so it's not highlighted on the webpage. + +## Gumbo 0.10.0 (2015-04-30) + +* Full support for `